diff --git a/.github/workflows/builds.yml b/.github/workflows/builds.yml index 934eaeccfa4..92da5a94e84 100644 --- a/.github/workflows/builds.yml +++ b/.github/workflows/builds.yml @@ -1,6 +1,6 @@ +name: Build Node Docker Images # This workflow is used to build and push one-off images for specific node types. This is useful # when deploying hotfixes or any time a change is not needed for all node roles. -name: Build Node Docker Images on: workflow_dispatch: @@ -38,9 +38,10 @@ on: type: boolean description: 'Observer' required: false - include_without_netgo: + # GHA allows only up to 10 inputs - regroup two entries in one + include_alternative_builds: type: boolean - description: 'Build `without_netgo` images' + description: 'Build amd64 `without_adx` and `without_netgo_without_adx` images, and arm64 images' required: false jobs: @@ -111,17 +112,22 @@ jobs: run: | gcloud auth configure-docker - - name: Build/Push ${{ matrix.role }} images + - name: Build/Push ${{ matrix.role }} amd64 images with adx (default) env: IMAGE_TAG: ${{ inputs.docker_tag }} - GITHUB_CREDS: "machine github.com login ${{ secrets.REPO_SYNC_USER }} password ${{ secrets.REPO_SYNC }}" + CADENCE_DEPLOY_KEY: ${{ secrets.CADENCE_DEPLOY_KEY }} run: | - make docker-build-${{ matrix.role }} docker-push-${{ matrix.role }} + make docker-build-${{ matrix.role }}-with-adx docker-push-${{ matrix.role }}-with-adx - - name: Build/Push ${{ matrix.role }} without_netgo images - if: ${{ inputs.include_without_netgo }} + - name: Build/Push ${{ matrix.role }} amd64 images without netgo and without adx, arm64 images + if: ${{ inputs.include_alternative_builds }} env: IMAGE_TAG: ${{ inputs.docker_tag }} - GITHUB_CREDS: "machine github.com login ${{ secrets.REPO_SYNC_USER }} password ${{ secrets.REPO_SYNC }}" + CADENCE_DEPLOY_KEY: ${{ secrets.CADENCE_DEPLOY_KEY }} run: | - make docker-build-${{ matrix.role }}-without-netgo docker-push-${{ matrix.role }}-without-netgo + make docker-build-${{ matrix.role }}-without-adx docker-push-${{ matrix.role }}-without-adx \ + docker-build-${{ matrix.role }}-without-netgo-without-adx docker-push-${{ matrix.role }}-without-netgo-without-adx \ + docker-cross-build-${{ matrix.role }}-arm docker-push-${{ matrix.role }}-arm + + + diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 6660b14ac29..3efb672e568 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -13,6 +13,7 @@ jobs: steps: - name: Setup Go uses: actions/setup-go@v4 + timeout-minutes: 10 # fail fast. sometimes this step takes an extremely long time with: go-version: "1.20" - name: Checkout repo @@ -29,10 +30,16 @@ jobs: run: | gcloud auth configure-docker - name: Docker build + env: + CADENCE_DEPLOY_KEY: ${{ secrets.CADENCE_DEPLOY_KEY }} run: | - make docker-build-flow - make docker-build-flow-without-netgo + make docker-build-flow-with-adx + make docker-build-flow-without-adx + make docker-build-flow-without-netgo-without-adx + make docker-cross-build-flow-arm - name: Docker push run: | - make docker-push-flow - make docker-push-flow-without-netgo + make docker-push-flow-with-adx + make docker-push-flow-without-adx + make docker-push-flow-without-netgo-without-adx + make docker-push-flow-arm diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6d6f31edc3e..de4e11e6554 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,6 +38,7 @@ jobs: uses: actions/checkout@v3 - name: Setup Go uses: actions/setup-go@v4 + timeout-minutes: 10 # fail fast. sometimes this step takes an extremely long time with: go-version: ${{ env.GO_VERSION }} cache: true @@ -63,6 +64,7 @@ jobs: uses: actions/checkout@v3 - name: Setup Go uses: actions/setup-go@v4 + timeout-minutes: 10 # fail fast. sometimes this step takes an extremely long time with: go-version: ${{ env.GO_VERSION }} cache: true @@ -81,12 +83,13 @@ jobs: uses: actions/checkout@v3 - name: Setup Go uses: actions/setup-go@v4 + timeout-minutes: 10 # fail fast. sometimes this step takes an extremely long time with: go-version: ${{ env.GO_VERSION }} cache: true - name: Set Test Matrix id: set-test-matrix - run: go run utils/test_matrix/test_matrix.go admin cmd consensus engine/access engine/collection engine/common engine/consensus engine/execution/ingestion:buildjet-8vcpu-ubuntu-2204 engine/execution/computation engine/execution engine/verification engine:buildjet-4vcpu-ubuntu-2204 fvm ledger module/dkg module:buildjet-4vcpu-ubuntu-2204 network/alsp network/test/cohort1:buildjet-16vcpu-ubuntu-2204 network/test/cohort2:buildjet-4vcpu-ubuntu-2204 network/p2p/connection network/p2p/p2pnode:buildjet-4vcpu-ubuntu-2204 network/p2p/scoring network/p2p network state storage utils + run: go run utils/test_matrix/test_matrix.go admin cmd consensus engine/access engine/collection engine/common engine/consensus engine/execution/ingestion:buildjet-8vcpu-ubuntu-2204 engine/execution/computation engine/execution engine/verification engine:buildjet-4vcpu-ubuntu-2204 fvm ledger module/dkg module:buildjet-4vcpu-ubuntu-2204 network/alsp network/test/cohort1:buildjet-16vcpu-ubuntu-2204 network/test/cohort2:buildjet-4vcpu-ubuntu-2204 network/p2p/connection network/p2p/node:buildjet-4vcpu-ubuntu-2204 network/p2p/scoring network/p2p network state storage utils unit-test: name: Unit Tests (${{ matrix.targets.name }}) @@ -102,6 +105,7 @@ jobs: uses: actions/checkout@v3 - name: Setup Go uses: actions/setup-go@v4 + timeout-minutes: 10 # fail fast. sometimes this step takes an extremely long time with: go-version: ${{ env.GO_VERSION }} cache: true @@ -145,6 +149,7 @@ jobs: uses: actions/checkout@v3 - name: Setup Go uses: actions/setup-go@v4 + timeout-minutes: 10 # fail fast. sometimes this step takes an extremely long time with: go-version: ${{ env.GO_VERSION }} cache: true @@ -169,6 +174,8 @@ jobs: docker-build: name: Docker Build runs-on: buildjet-16vcpu-ubuntu-2204 + env: + CADENCE_DEPLOY_KEY: ${{ secrets.CADENCE_DEPLOY_KEY }} steps: - name: Checkout repo uses: actions/checkout@v3 @@ -177,11 +184,14 @@ jobs: fetch-depth: 0 - name: Setup Go uses: actions/setup-go@v4 + timeout-minutes: 10 # fail fast. sometimes this step takes an extremely long time with: go-version: ${{ env.GO_VERSION }} cache: true - name: Docker build - run: make docker-build-flow docker-build-flow-corrupt + env: + CADENCE_DEPLOY_KEY: ${{ secrets.CADENCE_DEPLOY_KEY }} + run: make docker-native-build-flow docker-native-build-flow-corrupt - name: Save Docker images run: | docker save \ @@ -267,6 +277,7 @@ jobs: fetch-depth: 0 - name: Setup Go uses: actions/setup-go@v4 + timeout-minutes: 10 # fail fast. sometimes this step takes an extremely long time with: go-version: ${{ env.GO_VERSION }} cache: true diff --git a/.github/workflows/flaky-test-monitor.yml b/.github/workflows/flaky-test-monitor.yml index 3a47ef7b829..146408c67ee 100644 --- a/.github/workflows/flaky-test-monitor.yml +++ b/.github/workflows/flaky-test-monitor.yml @@ -165,7 +165,7 @@ jobs: go-version: ${{ env.GO_VERSION }} cache: true - name: Docker build - run: make docker-build-flow docker-build-flow-corrupt + run: make docker-native-build-flow docker-native-build-flow-corrupt - name: Run tests run: make -es -C integration ${{ matrix.target }} > test-output timeout-minutes: 100 diff --git a/Makefile b/Makefile index 0ff01f0fcc6..de511e58dd8 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,9 @@ ifeq (${IMAGE_TAG},) IMAGE_TAG := ${SHORT_COMMIT} endif -IMAGE_TAG_NO_NETGO := $(IMAGE_TAG)-without-netgo +IMAGE_TAG_NO_ADX := $(IMAGE_TAG)-without-adx +IMAGE_TAG_NO_NETGO_NO_ADX := $(IMAGE_TAG)-without-netgo-without-adx +IMAGE_TAG_ARM := $(IMAGE_TAG)-arm # Name of the cover profile COVER_PROFILE := coverage.txt @@ -39,20 +41,19 @@ K8S_YAMLS_LOCATION_STAGING=./k8s/staging export CONTAINER_REGISTRY := gcr.io/flow-container-registry export DOCKER_BUILDKIT := 1 +# set `CRYPTO_FLAG` when building natively (not cross-compiling) include crypto_adx_flag.mk -CGO_FLAG := CGO_CFLAGS=$(CRYPTO_FLAG) - # needed for CI .PHONY: noop noop: @echo "This is a no-op target" cmd/collection/collection: - $(CGO_FLAG) go build -o cmd/collection/collection cmd/collection/main.go + CGO_CFLAGS=$(CRYPTO_FLAG) go build -o cmd/collection/collection cmd/collection/main.go cmd/util/util: - $(CGO_FLAG) go build -o cmd/util/util cmd/util/main.go + CGO_CFLAGS=$(CRYPTO_FLAG) go build -o cmd/util/util cmd/util/main.go .PHONY: update-core-contracts-version update-core-contracts-version: @@ -71,7 +72,7 @@ update-cadence-version: .PHONY: unittest-main unittest-main: # test all packages - $(CGO_FLAG) go test $(if $(VERBOSE),-v,) -coverprofile=$(COVER_PROFILE) -covermode=atomic $(if $(RACE_DETECTOR),-race,) $(if $(JSON_OUTPUT),-json,) $(if $(NUM_RUNS),-count $(NUM_RUNS),) $(GO_TEST_PACKAGES) + CGO_CFLAGS=$(CRYPTO_FLAG) go test $(if $(VERBOSE),-v,) -coverprofile=$(COVER_PROFILE) -covermode=atomic $(if $(RACE_DETECTOR),-race,) $(if $(JSON_OUTPUT),-json,) $(if $(NUM_RUNS),-count $(NUM_RUNS),) $(GO_TEST_PACKAGES) .PHONY: install-mock-generators install-mock-generators: @@ -97,7 +98,7 @@ go-math-rand-check: # `exclude` should only specify non production code (test, bench..). # If this check fails, try updating your code by using: # - "crypto/rand" or "flow-go/utils/rand" for non-deterministic randomness - # - "flow-go/crypto/random" for deterministic randomness + # - "onflow/crypto/random" for deterministic randomness grep --include=\*.go \ --exclude=*test* --exclude=*helper* --exclude=*example* --exclude=*fixture* --exclude=*benchmark* --exclude=*profiler* \ --exclude-dir=*test* --exclude-dir=*helper* --exclude-dir=*example* --exclude-dir=*fixture* --exclude-dir=*benchmark* --exclude-dir=*profiler* -rnw '"math/rand"'; \ @@ -111,17 +112,17 @@ code-sanity-check: go-math-rand-check .PHONY: fuzz-fvm fuzz-fvm: # run fuzz tests in the fvm package - cd ./fvm && $(CGO_FLAG) go test -fuzz=Fuzz -run ^$$ + cd ./fvm && CGO_CFLAGS=$(CRYPTO_FLAG) go test -fuzz=Fuzz -run ^$$ .PHONY: test test: verify-mocks unittest-main .PHONY: integration-test -integration-test: docker-build-flow +integration-test: docker-native-build-flow $(MAKE) -C integration integration-test .PHONY: benchmark -benchmark: docker-build-flow +benchmark: docker-native-build-flow $(MAKE) -C integration benchmark .PHONY: coverage @@ -149,14 +150,14 @@ generate-proto: .PHONY: generate-fvm-env-wrappers generate-fvm-env-wrappers: - $(CGO_FLAG) go run ./fvm/environment/generate-wrappers fvm/environment/parse_restricted_checker.go + CGO_CFLAGS=$(CRYPTO_FLAG) go run ./fvm/environment/generate-wrappers fvm/environment/parse_restricted_checker.go .PHONY: generate-mocks generate-mocks: install-mock-generators mockery --name '(Connector|PingInfoProvider)' --dir=network/p2p --case=underscore --output="./network/mocknetwork" --outpkg="mocknetwork" - $(CGO_FLAG) mockgen -destination=storage/mocks/storage.go -package=mocks github.com/onflow/flow-go/storage Blocks,Headers,Payloads,Collections,Commits,Events,ServiceEvents,TransactionResults - $(CGO_FLAG) mockgen -destination=module/mocks/network.go -package=mocks github.com/onflow/flow-go/module Local,Requester - $(CGO_FLAG) mockgen -destination=network/mocknetwork/mock_network.go -package=mocknetwork github.com/onflow/flow-go/network EngineRegistry + CGO_CFLAGS=$(CRYPTO_FLAG) mockgen -destination=storage/mocks/storage.go -package=mocks github.com/onflow/flow-go/storage Blocks,Headers,Payloads,Collections,Commits,Events,ServiceEvents,TransactionResults + CGO_CFLAGS=$(CRYPTO_FLAG) mockgen -destination=module/mocks/network.go -package=mocks github.com/onflow/flow-go/module Local,Requester + CGO_CFLAGS=$(CRYPTO_FLAG) mockgen -destination=network/mocknetwork/mock_network.go -package=mocknetwork github.com/onflow/flow-go/network EngineRegistry mockery --name='.*' --dir=integration/benchmark/mocksiface --case=underscore --output="integration/benchmark/mock" --outpkg="mock" mockery --name=ExecutionDataStore --dir=module/executiondatasync/execution_data --case=underscore --output="./module/executiondatasync/execution_data/mock" --outpkg="mock" mockery --name=Downloader --dir=module/executiondatasync/execution_data --case=underscore --output="./module/executiondatasync/execution_data/mock" --outpkg="mock" @@ -207,11 +208,14 @@ generate-mocks: install-mock-generators mockery --name '.*' --dir=./cmd/util/ledger/reporters --case=underscore --output="./cmd/util/ledger/reporters/mock" --outpkg="mock" mockery --name 'Storage' --dir=module/executiondatasync/tracker --case=underscore --output="module/executiondatasync/tracker/mock" --outpkg="mocktracker" mockery --name 'ScriptExecutor' --dir=module/execution --case=underscore --output="module/execution/mock" --outpkg="mock" + mockery --name 'StorageSnapshot' --dir=fvm/storage/snapshot --case=underscore --output="fvm/storage/snapshot/mock" --outpkg="mock" #temporarily make insecure/ a non-module to allow mockery to create mocks mv insecure/go.mod insecure/go2.mod + if [ -f go.work ]; then mv go.work go2.work; fi mockery --name '.*' --dir=insecure/ --case=underscore --output="./insecure/mock" --outpkg="mockinsecure" mv insecure/go2.mod insecure/go.mod + if [ -f go2.work ]; then mv go2.work go.work; fi # this ensures there is no unused dependency being added by accident .PHONY: tidy @@ -240,10 +244,10 @@ ci: install-tools test # Runs integration tests .PHONY: ci-integration ci-integration: - $(MAKE) -C integration ci-integration-test + $(MAKE) -C integration integration-test # Runs benchmark tests -# NOTE: we do not need `docker-build-flow` as this is run as a separate step +# NOTE: we do not need `docker-native-build-flow` as this is run as a separate step # on Teamcity .PHONY: ci-benchmark ci-benchmark: install-tools @@ -271,162 +275,323 @@ docker-ci-integration: -w "/go/flow" "$(CONTAINER_REGISTRY)/golang-cmake:v0.0.7" \ make ci-integration -.PHONY: docker-build-collection -docker-build-collection: - docker build -f cmd/Dockerfile --build-arg TARGET=./cmd/collection --build-arg COMMIT=$(COMMIT) --build-arg VERSION=$(IMAGE_TAG) --build-arg GOARCH=$(GOARCH) --build-arg CGO_FLAG=$(CRYPTO_FLAG) --target production \ - --secret id=git_creds,env=GITHUB_CREDS --build-arg GOPRIVATE=$(GOPRIVATE) \ +# only works on Debian +.SILENT: install-cross-build-tools +install-cross-build-tools: + if [ "$(UNAME)" = "Debian" ] ; then \ + apt-get update && apt-get -y install apt-utils gcc-aarch64-linux-gnu ; \ + elif [ "$(UNAME)" = "Linux" ] ; then \ + apt-get update && apt-get -y install apt-utils gcc-aarch64-linux-gnu ; \ + else \ + echo "this target only works on Debian or Linux, host runs on" $(UNAME) ; \ + fi + +.PHONY: docker-native-build-collection +docker-native-build-collection: + docker build -f cmd/Dockerfile --build-arg TARGET=./cmd/collection --build-arg COMMIT=$(COMMIT) --build-arg VERSION=$(IMAGE_TAG) --build-arg GOARCH=$(GOARCH) --build-arg CGO_FLAG=$(CRYPTO_FLAG) --target production \ + --secret id=cadence_deploy_key,env=CADENCE_DEPLOY_KEY --build-arg GOPRIVATE=$(GOPRIVATE) \ --label "git_commit=${COMMIT}" --label "git_tag=${IMAGE_TAG}" \ - -t "$(CONTAINER_REGISTRY)/collection:latest" -t "$(CONTAINER_REGISTRY)/collection:$(SHORT_COMMIT)" -t "$(CONTAINER_REGISTRY)/collection:$(IMAGE_TAG)" . - -.PHONY: docker-build-collection-without-netgo -docker-build-collection-without-netgo: - docker build -f cmd/Dockerfile --build-arg TARGET=./cmd/collection --build-arg COMMIT=$(COMMIT) --build-arg VERSION=$(IMAGE_TAG_NO_NETGO) --build-arg GOARCH=$(GOARCH) --build-arg CGO_FLAG=$(CRYPTO_FLAG) --target production \ - --secret id=git_creds,env=GITHUB_CREDS --build-arg GOPRIVATE=$(GOPRIVATE) \ - --label "git_commit=${COMMIT}" --label "git_tag=$(IMAGE_TAG_NO_NETGO)" \ - -t "$(CONTAINER_REGISTRY)/collection:$(IMAGE_TAG_NO_NETGO)" . - -.PHONY: docker-build-collection-debug -docker-build-collection-debug: + -t "$(CONTAINER_REGISTRY)/collection:latest" \ + -t "$(CONTAINER_REGISTRY)/collection:$(IMAGE_TAG)" . + +.PHONY: docker-build-collection-with-adx +docker-build-collection-with-adx: + docker build -f cmd/Dockerfile --build-arg TARGET=./cmd/collection --build-arg COMMIT=$(COMMIT) --build-arg VERSION=$(IMAGE_TAG) --build-arg GOARCH=amd64 --target production \ + --secret id=cadence_deploy_key,env=CADENCE_DEPLOY_KEY --build-arg GOPRIVATE=$(GOPRIVATE) \ + --label "git_commit=${COMMIT}" --label "git_tag=$(IMAGE_TAG)" \ + -t "$(CONTAINER_REGISTRY)/collection:$(IMAGE_TAG)" . + +.PHONY: docker-build-collection-without-adx +docker-build-collection-without-adx: + docker build -f cmd/Dockerfile --build-arg TARGET=./cmd/collection --build-arg COMMIT=$(COMMIT) --build-arg VERSION=$(IMAGE_TAG_NO_ADX) --build-arg GOARCH=amd64 --build-arg CGO_FLAG=$(DISABLE_ADX) --target production \ + --secret id=cadence_deploy_key,env=CADENCE_DEPLOY_KEY --build-arg GOPRIVATE=$(GOPRIVATE) \ + --label "git_commit=${COMMIT}" --label "git_tag=$(IMAGE_TAG_NO_ADX)" \ + -t "$(CONTAINER_REGISTRY)/collection:$(IMAGE_TAG_NO_ADX)" . + +.PHONY: docker-build-collection-without-netgo-without-adx +docker-build-collection-without-netgo-without-adx: + docker build -f cmd/Dockerfile --build-arg TARGET=./cmd/collection --build-arg COMMIT=$(COMMIT) --build-arg VERSION=$(IMAGE_TAG_NO_NETGO_NO_ADX) --build-arg GOARCH=amd64 --build-arg TAGS="" --build-arg CGO_FLAG=$(DISABLE_ADX) --target production \ + --secret id=cadence_deploy_key,env=CADENCE_DEPLOY_KEY --build-arg GOPRIVATE=$(GOPRIVATE) \ + --label "git_commit=${COMMIT}" --label "git_tag=$(IMAGE_TAG_NO_NETGO_NO_ADX)" \ + -t "$(CONTAINER_REGISTRY)/collection:$(IMAGE_TAG_NO_NETGO_NO_ADX)" . + +.PHONY: docker-cross-build-collection-arm +docker-cross-build-collection-arm: + docker build -f cmd/Dockerfile --build-arg TARGET=./cmd/collection --build-arg COMMIT=$(COMMIT) --build-arg VERSION=$(IMAGE_TAG_ARM) --build-arg CC=aarch64-linux-gnu-gcc --build-arg GOARCH=arm64 --target production \ + --secret id=cadence_deploy_key,env=CADENCE_DEPLOY_KEY --build-arg GOPRIVATE=$(GOPRIVATE) \ + --label "git_commit=${COMMIT}" --label "git_tag=$(IMAGE_TAG_ARM)" \ + -t "$(CONTAINER_REGISTRY)/collection:$(IMAGE_TAG_ARM)" . + +.PHONY: docker-native-build-collection-debug +docker-native-build-collection-debug: docker build -f cmd/Dockerfile --build-arg TARGET=./cmd/collection --build-arg COMMIT=$(COMMIT) --build-arg VERSION=$(IMAGE_TAG) --build-arg GOARCH=$(GOARCH) --build-arg CGO_FLAG=$(CRYPTO_FLAG) --target debug \ - -t "$(CONTAINER_REGISTRY)/collection-debug:latest" -t "$(CONTAINER_REGISTRY)/collection-debug:$(SHORT_COMMIT)" -t "$(CONTAINER_REGISTRY)/collection-debug:$(IMAGE_TAG)" . + -t "$(CONTAINER_REGISTRY)/collection-debug:latest" \ + -t "$(CONTAINER_REGISTRY)/collection-debug:$(IMAGE_TAG)" . -.PHONY: docker-build-consensus -docker-build-consensus: +.PHONY: docker-native-build-consensus +docker-native-build-consensus: docker build -f cmd/Dockerfile --build-arg TARGET=./cmd/consensus --build-arg COMMIT=$(COMMIT) --build-arg VERSION=$(IMAGE_TAG) --build-arg GOARCH=$(GOARCH) --build-arg CGO_FLAG=$(CRYPTO_FLAG) --target production \ - --secret id=git_creds,env=GITHUB_CREDS --build-arg GOPRIVATE=$(GOPRIVATE) \ + --secret id=cadence_deploy_key,env=CADENCE_DEPLOY_KEY --build-arg GOPRIVATE=$(GOPRIVATE) \ --label "git_commit=${COMMIT}" --label "git_tag=${IMAGE_TAG}" \ - -t "$(CONTAINER_REGISTRY)/consensus:latest" -t "$(CONTAINER_REGISTRY)/consensus:$(SHORT_COMMIT)" -t "$(CONTAINER_REGISTRY)/consensus:$(IMAGE_TAG)" . - -.PHONY: docker-build-consensus-without-netgo -docker-build-consensus-without-netgo: - docker build -f cmd/Dockerfile --build-arg TARGET=./cmd/consensus --build-arg COMMIT=$(COMMIT) --build-arg VERSION=$(IMAGE_TAG_NO_NETGO) --build-arg GOARCH=$(GOARCH) --build-arg CGO_FLAG=$(CRYPTO_FLAG) --target production \ - --secret id=git_creds,env=GITHUB_CREDS --build-arg GOPRIVATE=$(GOPRIVATE) \ - --label "git_commit=${COMMIT}" --label "git_tag=$(IMAGE_TAG_NO_NETGO)" \ - -t "$(CONTAINER_REGISTRY)/consensus:$(IMAGE_TAG_NO_NETGO)" . - -.PHONY: docker-build-consensus-debug -docker-build-consensus-debug: + -t "$(CONTAINER_REGISTRY)/consensus:latest" \ + -t "$(CONTAINER_REGISTRY)/consensus:$(IMAGE_TAG)" . + +.PHONY: docker-build-consensus-with-adx +docker-build-consensus-with-adx: + docker build -f cmd/Dockerfile --build-arg TARGET=./cmd/consensus --build-arg COMMIT=$(COMMIT) --build-arg VERSION=$(IMAGE_TAG) --build-arg GOARCH=amd64 --target production \ + --secret id=cadence_deploy_key,env=CADENCE_DEPLOY_KEY --build-arg GOPRIVATE=$(GOPRIVATE) \ + --label "git_commit=${COMMIT}" --label "git_tag=$(IMAGE_TAG)" \ + -t "$(CONTAINER_REGISTRY)/consensus:$(IMAGE_TAG)" . + +.PHONY: docker-build-consensus-without-adx +docker-build-consensus-without-adx: + docker build -f cmd/Dockerfile --build-arg TARGET=./cmd/consensus --build-arg COMMIT=$(COMMIT) --build-arg VERSION=$(IMAGE_TAG_NO_ADX) --build-arg GOARCH=amd64 --build-arg CGO_FLAG=$(DISABLE_ADX) --target production \ + --secret id=cadence_deploy_key,env=CADENCE_DEPLOY_KEY --build-arg GOPRIVATE=$(GOPRIVATE) \ + --label "git_commit=${COMMIT}" --label "git_tag=$(IMAGE_TAG_NO_ADX)" \ + -t "$(CONTAINER_REGISTRY)/consensus:$(IMAGE_TAG_NO_ADX)" . + +.PHONY: docker-build-consensus-without-netgo-without-adx +docker-build-consensus-without-netgo-without-adx: + docker build -f cmd/Dockerfile --build-arg TARGET=./cmd/consensus --build-arg COMMIT=$(COMMIT) --build-arg VERSION=$(IMAGE_TAG_NO_NETGO_NO_ADX) --build-arg GOARCH=amd64 --build-arg TAGS="" --build-arg CGO_FLAG=$(DISABLE_ADX) --target production \ + --secret id=cadence_deploy_key,env=CADENCE_DEPLOY_KEY --build-arg GOPRIVATE=$(GOPRIVATE) \ + --label "git_commit=${COMMIT}" --label "git_tag=$(IMAGE_TAG_NO_NETGO_NO_ADX)" \ + -t "$(CONTAINER_REGISTRY)/consensus:$(IMAGE_TAG_NO_NETGO_NO_ADX)" . + +.PHONY: docker-cross-build-consensus-arm +docker-cross-build-consensus-arm: + docker build -f cmd/Dockerfile --build-arg TARGET=./cmd/consensus --build-arg COMMIT=$(COMMIT) --build-arg VERSION=$(IMAGE_TAG_ARM) --build-arg GOARCH=arm64 --build-arg CC=aarch64-linux-gnu-gcc --target production \ + --secret id=cadence_deploy_key,env=CADENCE_DEPLOY_KEY --build-arg GOPRIVATE=$(GOPRIVATE) \ + --label "git_commit=${COMMIT}" --label "git_tag=${IMAGE_TAG_ARM}" \ + -t "$(CONTAINER_REGISTRY)/consensus:$(IMAGE_TAG_ARM)" . + + +.PHONY: docker-native-build-consensus-debug +docker-build-native-consensus-debug: docker build -f cmd/Dockerfile --build-arg TARGET=./cmd/consensus --build-arg COMMIT=$(COMMIT) --build-arg VERSION=$(IMAGE_TAG) --build-arg GOARCH=$(GOARCH) --build-arg CGO_FLAG=$(CRYPTO_FLAG) --target debug \ - -t "$(CONTAINER_REGISTRY)/consensus-debug:latest" -t "$(CONTAINER_REGISTRY)/consensus-debug:$(SHORT_COMMIT)" -t "$(CONTAINER_REGISTRY)/consensus-debug:$(IMAGE_TAG)" . + -t "$(CONTAINER_REGISTRY)/consensus-debug:latest" \ + -t "$(CONTAINER_REGISTRY)/consensus-debug:$(IMAGE_TAG)" . -.PHONY: docker-build-execution -docker-build-execution: +.PHONY: docker-native-build-execution +docker-native-build-execution: docker build -f cmd/Dockerfile --build-arg TARGET=./cmd/execution --build-arg COMMIT=$(COMMIT) --build-arg VERSION=$(IMAGE_TAG) --build-arg GOARCH=$(GOARCH) --build-arg CGO_FLAG=$(CRYPTO_FLAG) --target production \ - --secret id=git_creds,env=GITHUB_CREDS --build-arg GOPRIVATE=$(GOPRIVATE) \ + --secret id=cadence_deploy_key,env=CADENCE_DEPLOY_KEY --build-arg GOPRIVATE=$(GOPRIVATE) \ --label "git_commit=${COMMIT}" --label "git_tag=${IMAGE_TAG}" \ - -t "$(CONTAINER_REGISTRY)/execution:latest" -t "$(CONTAINER_REGISTRY)/execution:$(SHORT_COMMIT)" -t "$(CONTAINER_REGISTRY)/execution:$(IMAGE_TAG)" . - -.PHONY: docker-build-execution-without-netgo -docker-build-execution-without-netgo: - docker build -f cmd/Dockerfile --build-arg TARGET=./cmd/execution --build-arg COMMIT=$(COMMIT) --build-arg VERSION=$(IMAGE_TAG_NO_NETGO) --build-arg GOARCH=$(GOARCH) --build-arg CGO_FLAG=$(CRYPTO_FLAG) --target production \ - --secret id=git_creds,env=GITHUB_CREDS --build-arg GOPRIVATE=$(GOPRIVATE) \ - --label "git_commit=${COMMIT}" --label "git_tag=$(IMAGE_TAG_NO_NETGO)" \ - -t "$(CONTAINER_REGISTRY)/execution:$(IMAGE_TAG_NO_NETGO)" . - -.PHONY: docker-build-execution-debug -docker-build-execution-debug: + -t "$(CONTAINER_REGISTRY)/execution:latest" \ + -t "$(CONTAINER_REGISTRY)/execution:$(IMAGE_TAG)" . + +.PHONY: docker-build-execution-with-adx +docker-build-execution-with-adx: + docker build -f cmd/Dockerfile --build-arg TARGET=./cmd/execution --build-arg COMMIT=$(COMMIT) --build-arg VERSION=$(IMAGE_TAG) --build-arg GOARCH=amd64 --target production \ + --secret id=cadence_deploy_key,env=CADENCE_DEPLOY_KEY --build-arg GOPRIVATE=$(GOPRIVATE) \ + --label "git_commit=${COMMIT}" --label "git_tag=$(IMAGE_TAG)" \ + -t "$(CONTAINER_REGISTRY)/execution:$(IMAGE_TAG)" . + +.PHONY: docker-build-execution-without-adx +docker-build-execution-without-adx: + docker build -f cmd/Dockerfile --build-arg TARGET=./cmd/execution --build-arg COMMIT=$(COMMIT) --build-arg VERSION=$(IMAGE_TAG_NO_ADX) --build-arg GOARCH=amd64 --build-arg CGO_FLAG=$(DISABLE_ADX) --target production \ + --secret id=cadence_deploy_key,env=CADENCE_DEPLOY_KEY --build-arg GOPRIVATE=$(GOPRIVATE) \ + --label "git_commit=${COMMIT}" --label "git_tag=$(IMAGE_TAG_NO_ADX)" \ + -t "$(CONTAINER_REGISTRY)/execution:$(IMAGE_TAG_NO_ADX)" . + +.PHONY: docker-build-execution-without-netgo-without-adx +docker-build-execution-without-netgo-without-adx: + docker build -f cmd/Dockerfile --build-arg TARGET=./cmd/execution --build-arg COMMIT=$(COMMIT) --build-arg VERSION=$(IMAGE_TAG_NO_NETGO_NO_ADX) --build-arg GOARCH=amd64 --build-arg TAGS="" --build-arg CGO_FLAG=$(DISABLE_ADX) --target production \ + --secret id=cadence_deploy_key,env=CADENCE_DEPLOY_KEY --build-arg GOPRIVATE=$(GOPRIVATE) \ + --label "git_commit=${COMMIT}" --label "git_tag=$(IMAGE_TAG_NO_NETGO_NO_ADX)" \ + -t "$(CONTAINER_REGISTRY)/execution:$(IMAGE_TAG_NO_NETGO_NO_ADX)" . + +.PHONY: docker-cross-build-execution-arm +docker-cross-build-execution-arm: + docker build -f cmd/Dockerfile --build-arg TARGET=./cmd/execution --build-arg COMMIT=$(COMMIT) --build-arg VERSION=$(IMAGE_TAG_ARM) --build-arg GOARCH=arm64 --build-arg CC=aarch64-linux-gnu-gcc --target production \ + --secret id=cadence_deploy_key,env=CADENCE_DEPLOY_KEY --build-arg GOPRIVATE=$(GOPRIVATE) \ + --label "git_commit=${COMMIT}" --label "git_tag=${IMAGE_TAG_ARM}" \ + -t "$(CONTAINER_REGISTRY)/execution:$(IMAGE_TAG_ARM)" . + +.PHONY: docker-native-build-execution-debug +docker-native-build-execution-debug: docker build -f cmd/Dockerfile --build-arg TARGET=./cmd/execution --build-arg COMMIT=$(COMMIT) --build-arg VERSION=$(IMAGE_TAG) --build-arg GOARCH=$(GOARCH) --build-arg CGO_FLAG=$(CRYPTO_FLAG) --target debug \ - -t "$(CONTAINER_REGISTRY)/execution-debug:latest" -t "$(CONTAINER_REGISTRY)/execution-debug:$(SHORT_COMMIT)" -t "$(CONTAINER_REGISTRY)/execution-debug:$(IMAGE_TAG)" . + -t "$(CONTAINER_REGISTRY)/execution-debug:latest" \ + -t "$(CONTAINER_REGISTRY)/execution-debug:$(IMAGE_TAG)" . # build corrupt execution node for BFT testing -.PHONY: docker-build-execution-corrupt -docker-build-execution-corrupt: +.PHONY: docker-native-build-execution-corrupt +docker-native-build-execution-corrupt: # temporarily make insecure/ a non-module to allow Docker to use corrupt builders there ./insecure/cmd/mods_override.sh docker build -f cmd/Dockerfile --build-arg TARGET=./insecure/cmd/execution --build-arg COMMIT=$(COMMIT) --build-arg VERSION=$(IMAGE_TAG) --build-arg GOARCH=$(GOARCH) --build-arg CGO_FLAG=$(CRYPTO_FLAG) --target production \ --label "git_commit=${COMMIT}" --label "git_tag=${IMAGE_TAG}" \ - -t "$(CONTAINER_REGISTRY)/execution-corrupted:latest" -t "$(CONTAINER_REGISTRY)/execution-corrupted:$(SHORT_COMMIT)" -t "$(CONTAINER_REGISTRY)/execution-corrupted:$(IMAGE_TAG)" . + --secret id=cadence_deploy_key,env=CADENCE_DEPLOY_KEY --build-arg GOPRIVATE=$(GOPRIVATE) \ + -t "$(CONTAINER_REGISTRY)/execution-corrupted:latest" \ + -t "$(CONTAINER_REGISTRY)/execution-corrupted:$(IMAGE_TAG)" . ./insecure/cmd/mods_restore.sh -.PHONY: docker-build-verification -docker-build-verification: +.PHONY: docker-native-build-verification +docker-native-build-verification: docker build -f cmd/Dockerfile --build-arg TARGET=./cmd/verification --build-arg COMMIT=$(COMMIT) --build-arg VERSION=$(IMAGE_TAG) --build-arg GOARCH=$(GOARCH) --build-arg CGO_FLAG=$(CRYPTO_FLAG) --target production \ - --secret id=git_creds,env=GITHUB_CREDS --build-arg GOPRIVATE=$(GOPRIVATE) \ + --secret id=cadence_deploy_key,env=CADENCE_DEPLOY_KEY --build-arg GOPRIVATE=$(GOPRIVATE) \ --label "git_commit=${COMMIT}" --label "git_tag=${IMAGE_TAG}" \ - -t "$(CONTAINER_REGISTRY)/verification:latest" -t "$(CONTAINER_REGISTRY)/verification:$(SHORT_COMMIT)" -t "$(CONTAINER_REGISTRY)/verification:$(IMAGE_TAG)" . - -.PHONY: docker-build-verification-without-netgo -docker-build-verification-without-netgo: - docker build -f cmd/Dockerfile --build-arg TARGET=./cmd/verification --build-arg COMMIT=$(COMMIT) --build-arg VERSION=$(IMAGE_TAG_NO_NETGO) --build-arg GOARCH=$(GOARCH) --build-arg CGO_FLAG=$(CRYPTO_FLAG) --target production \ - --secret id=git_creds,env=GITHUB_CREDS --build-arg GOPRIVATE=$(GOPRIVATE) \ - --label "git_commit=${COMMIT}" --label "git_tag=$(IMAGE_TAG_NO_NETGO)" \ - -t "$(CONTAINER_REGISTRY)/verification:$(IMAGE_TAG_NO_NETGO)" . - -.PHONY: docker-build-verification-debug -docker-build-verification-debug: + -t "$(CONTAINER_REGISTRY)/verification:latest" \ + -t "$(CONTAINER_REGISTRY)/verification:$(IMAGE_TAG)" . + +.PHONY: docker-build-verification-with-adx +docker-build-verification-with-adx: + docker build -f cmd/Dockerfile --build-arg TARGET=./cmd/verification --build-arg COMMIT=$(COMMIT) --build-arg VERSION=$(IMAGE_TAG) --build-arg GOARCH=amd64 --target production \ + --secret id=cadence_deploy_key,env=CADENCE_DEPLOY_KEY --build-arg GOPRIVATE=$(GOPRIVATE) \ + --label "git_commit=${COMMIT}" --label "git_tag=$(IMAGE_TAG)" \ + -t "$(CONTAINER_REGISTRY)/verification:$(IMAGE_TAG)" . + +.PHONY: docker-build-verification-without-adx +docker-build-verification-without-adx: + docker build -f cmd/Dockerfile --build-arg TARGET=./cmd/verification --build-arg COMMIT=$(COMMIT) --build-arg VERSION=$(IMAGE_TAG_NO_ADX) --build-arg GOARCH=amd64 --build-arg CGO_FLAG=$(DISABLE_ADX) --target production \ + --secret id=cadence_deploy_key,env=CADENCE_DEPLOY_KEY --build-arg GOPRIVATE=$(GOPRIVATE) \ + --label "git_commit=${COMMIT}" --label "git_tag=$(IMAGE_TAG_NO_ADX)" \ + -t "$(CONTAINER_REGISTRY)/verification:$(IMAGE_TAG_NO_ADX)" . + +.PHONY: docker-build-verification-without-netgo-without-adx +docker-build-verification-without-netgo-without-adx: + docker build -f cmd/Dockerfile --build-arg TARGET=./cmd/verification --build-arg COMMIT=$(COMMIT) --build-arg VERSION=$(IMAGE_TAG_NO_NETGO_NO_ADX) --build-arg GOARCH=amd64 --build-arg TAGS="" --build-arg CGO_FLAG=$(DISABLE_ADX) --target production \ + --secret id=cadence_deploy_key,env=CADENCE_DEPLOY_KEY --build-arg GOPRIVATE=$(GOPRIVATE) \ + --label "git_commit=${COMMIT}" --label "git_tag=$(IMAGE_TAG_NO_NETGO_NO_ADX)" \ + -t "$(CONTAINER_REGISTRY)/verification:$(IMAGE_TAG_NO_NETGO_NO_ADX)" . + +.PHONY: docker-cross-build-verification-arm +docker-cross-build-verification-arm: + docker build -f cmd/Dockerfile --build-arg TARGET=./cmd/verification --build-arg COMMIT=$(COMMIT) --build-arg VERSION=$(IMAGE_TAG_ARM) --build-arg GOARCH=arm64 --build-arg CC=aarch64-linux-gnu-gcc --target production \ + --secret id=cadence_deploy_key,env=CADENCE_DEPLOY_KEY --build-arg GOPRIVATE=$(GOPRIVATE) \ + --label "git_commit=${COMMIT}" --label "git_tag=${IMAGE_TAG_ARM}" \ + -t "$(CONTAINER_REGISTRY)/verification:$(IMAGE_TAG_ARM)" . + +.PHONY: docker-native-build-verification-debug +docker-native-build-verification-debug: docker build -f cmd/Dockerfile --build-arg TARGET=./cmd/verification --build-arg COMMIT=$(COMMIT) --build-arg VERSION=$(IMAGE_TAG) --build-arg GOARCH=$(GOARCH) --build-arg CGO_FLAG=$(CRYPTO_FLAG) --target debug \ - -t "$(CONTAINER_REGISTRY)/verification-debug:latest" -t "$(CONTAINER_REGISTRY)/verification-debug:$(SHORT_COMMIT)" -t "$(CONTAINER_REGISTRY)/verification-debug:$(IMAGE_TAG)" . + -t "$(CONTAINER_REGISTRY)/verification-debug:latest" \ + -t "$(CONTAINER_REGISTRY)/verification-debug:$(IMAGE_TAG)" . # build corrupt verification node for BFT testing -.PHONY: docker-build-verification-corrupt -docker-build-verification-corrupt: +.PHONY: docker-native-build-verification-corrupt +docker-native-build-verification-corrupt: # temporarily make insecure/ a non-module to allow Docker to use corrupt builders there ./insecure/cmd/mods_override.sh docker build -f cmd/Dockerfile --build-arg TARGET=./insecure/cmd/verification --build-arg COMMIT=$(COMMIT) --build-arg VERSION=$(IMAGE_TAG) --build-arg GOARCH=$(GOARCH) --build-arg CGO_FLAG=$(CRYPTO_FLAG) --target production \ --label "git_commit=${COMMIT}" --label "git_tag=${IMAGE_TAG}" \ - -t "$(CONTAINER_REGISTRY)/verification-corrupted:latest" -t "$(CONTAINER_REGISTRY)/verification-corrupted:$(SHORT_COMMIT)" -t "$(CONTAINER_REGISTRY)/verification-corrupted:$(IMAGE_TAG)" . + --secret id=cadence_deploy_key,env=CADENCE_DEPLOY_KEY --build-arg GOPRIVATE=$(GOPRIVATE) \ + -t "$(CONTAINER_REGISTRY)/verification-corrupted:latest" \ + -t "$(CONTAINER_REGISTRY)/verification-corrupted:$(IMAGE_TAG)" . ./insecure/cmd/mods_restore.sh -.PHONY: docker-build-access -docker-build-access: +.PHONY: docker-native-build-access +docker-native-build-access: docker build -f cmd/Dockerfile --build-arg TARGET=./cmd/access --build-arg COMMIT=$(COMMIT) --build-arg VERSION=$(IMAGE_TAG) --build-arg GOARCH=$(GOARCH) --build-arg CGO_FLAG=$(CRYPTO_FLAG) --target production \ - --secret id=git_creds,env=GITHUB_CREDS --build-arg GOPRIVATE=$(GOPRIVATE) \ --label "git_commit=${COMMIT}" --label "git_tag=${IMAGE_TAG}" \ - -t "$(CONTAINER_REGISTRY)/access:latest" -t "$(CONTAINER_REGISTRY)/access:$(SHORT_COMMIT)" -t "$(CONTAINER_REGISTRY)/access:$(IMAGE_TAG)" . - -.PHONY: docker-build-access-without-netgo -docker-build-access-without-netgo: - docker build -f cmd/Dockerfile --build-arg TARGET=./cmd/access --build-arg COMMIT=$(COMMIT) --build-arg VERSION=$(IMAGE_TAG_NO_NETGO) --build-arg GOARCH=$(GOARCH) --build-arg CGO_FLAG=$(CRYPTO_FLAG) --target production \ - --secret id=git_creds,env=GITHUB_CREDS --build-arg GOPRIVATE=$(GOPRIVATE) \ - --label "git_commit=${COMMIT}" --label "git_tag=$(IMAGE_TAG_NO_NETGO)" \ - -t "$(CONTAINER_REGISTRY)/access:$(IMAGE_TAG_NO_NETGO)" . - -.PHONY: docker-build-access-debug -docker-build-access-debug: + --secret id=cadence_deploy_key,env=CADENCE_DEPLOY_KEY --build-arg GOPRIVATE=$(GOPRIVATE) \ + -t "$(CONTAINER_REGISTRY)/access:latest" \ + -t "$(CONTAINER_REGISTRY)/access:$(IMAGE_TAG)" . + +.PHONY: docker-build-access-with-adx +docker-build-access-with-adx: + docker build -f cmd/Dockerfile --build-arg TARGET=./cmd/access --build-arg COMMIT=$(COMMIT) --build-arg VERSION=$(IMAGE_TAG) --build-arg GOARCH=amd64 --target production \ + --secret id=cadence_deploy_key,env=CADENCE_DEPLOY_KEY --build-arg GOPRIVATE=$(GOPRIVATE) \ + --label "git_commit=${COMMIT}" --label "git_tag=$(IMAGE_TAG)" \ + -t "$(CONTAINER_REGISTRY)/access:$(IMAGE_TAG)" . + +.PHONY: docker-build-access-without-adx +docker-build-access-without-adx: + docker build -f cmd/Dockerfile --build-arg TARGET=./cmd/access --build-arg COMMIT=$(COMMIT) --build-arg VERSION=$(IMAGE_TAG_NO_ADX) --build-arg GOARCH=amd64 --build-arg CGO_FLAG=$(DISABLE_ADX) --target production \ + --secret id=cadence_deploy_key,env=CADENCE_DEPLOY_KEY --build-arg GOPRIVATE=$(GOPRIVATE) \ + --label "git_commit=${COMMIT}" --label "git_tag=$(IMAGE_TAG_NO_ADX)" \ + -t "$(CONTAINER_REGISTRY)/access:$(IMAGE_TAG_NO_ADX)" . + +.PHONY: docker-build-access-without-netgo-without-adx +docker-build-access-without-netgo-without-adx: + docker build -f cmd/Dockerfile --build-arg TARGET=./cmd/access --build-arg COMMIT=$(COMMIT) --build-arg VERSION=$(IMAGE_TAG_NO_NETGO_NO_ADX) --build-arg GOARCH=amd64 --build-arg TAGS="" --build-arg CGO_FLAG=$(DISABLE_ADX) --target production \ + --secret id=cadence_deploy_key,env=CADENCE_DEPLOY_KEY --build-arg GOPRIVATE=$(GOPRIVATE) \ + --label "git_commit=${COMMIT}" --label "git_tag=$(IMAGE_TAG_NO_NETGO_NO_ADX)" \ + -t "$(CONTAINER_REGISTRY)/access:$(IMAGE_TAG_NO_NETGO_NO_ADX)" . + +.PHONY: docker-cross-build-access-arm +docker-cross-build-access-arm: + docker build -f cmd/Dockerfile --build-arg TARGET=./cmd/access --build-arg COMMIT=$(COMMIT) --build-arg VERSION=$(IMAGE_TAG_ARM) --build-arg GOARCH=arm64 --build-arg CC=aarch64-linux-gnu-gcc --target production \ + --secret id=cadence_deploy_key,env=CADENCE_DEPLOY_KEY --build-arg GOPRIVATE=$(GOPRIVATE) \ + --label "git_commit=${COMMIT}" --label "git_tag=${IMAGE_TAG_ARM}" \ + -t "$(CONTAINER_REGISTRY)/access:$(IMAGE_TAG_ARM)" . + + +.PHONY: docker-native-build-access-debug +docker-native-build-access-debug: docker build -f cmd/Dockerfile --build-arg TARGET=./cmd/access --build-arg COMMIT=$(COMMIT) --build-arg VERSION=$(IMAGE_TAG) --build-arg GOARCH=$(GOARCH) --build-arg CGO_FLAG=$(CRYPTO_FLAG) --target debug \ - -t "$(CONTAINER_REGISTRY)/access-debug:latest" -t "$(CONTAINER_REGISTRY)/access-debug:$(SHORT_COMMIT)" -t "$(CONTAINER_REGISTRY)/access-debug:$(IMAGE_TAG)" . + --secret id=cadence_deploy_key,env=CADENCE_DEPLOY_KEY --build-arg GOPRIVATE=$(GOPRIVATE) \ + -t "$(CONTAINER_REGISTRY)/access-debug:latest" \ + -t "$(CONTAINER_REGISTRY)/access-debug:$(IMAGE_TAG)" . # build corrupt access node for BFT testing -.PHONY: docker-build-access-corrupt -docker-build-access-corrupt: +.PHONY: docker-native-build-access-corrupt +docker-native-build-access-corrupt: #temporarily make insecure/ a non-module to allow Docker to use corrupt builders there ./insecure/cmd/mods_override.sh docker build -f cmd/Dockerfile --build-arg TARGET=./insecure/cmd/access --build-arg COMMIT=$(COMMIT) --build-arg VERSION=$(IMAGE_TAG) --build-arg GOARCH=$(GOARCH) --build-arg CGO_FLAG=$(CRYPTO_FLAG) --target production \ + --secret id=cadence_deploy_key,env=CADENCE_DEPLOY_KEY --build-arg GOPRIVATE=$(GOPRIVATE) \ --label "git_commit=${COMMIT}" --label "git_tag=${IMAGE_TAG}" \ - -t "$(CONTAINER_REGISTRY)/access-corrupted:latest" -t "$(CONTAINER_REGISTRY)/access-corrupted:$(SHORT_COMMIT)" -t "$(CONTAINER_REGISTRY)/access-corrupted:$(IMAGE_TAG)" . + -t "$(CONTAINER_REGISTRY)/access-corrupted:latest" \ + -t "$(CONTAINER_REGISTRY)/access-corrupted:$(IMAGE_TAG)" . ./insecure/cmd/mods_restore.sh -.PHONY: docker-build-observer -docker-build-observer: +.PHONY: docker-native-build-observer +docker-native-build-observer: docker build -f cmd/Dockerfile --build-arg TARGET=./cmd/observer --build-arg COMMIT=$(COMMIT) --build-arg VERSION=$(IMAGE_TAG) --build-arg GOARCH=$(GOARCH) --build-arg CGO_FLAG=$(CRYPTO_FLAG) --target production \ - --secret id=git_creds,env=GITHUB_CREDS --build-arg GOPRIVATE=$(GOPRIVATE) \ + --secret id=cadence_deploy_key,env=CADENCE_DEPLOY_KEY --build-arg GOPRIVATE=$(GOPRIVATE) \ --label "git_commit=${COMMIT}" --label "git_tag=${IMAGE_TAG}" \ - -t "$(CONTAINER_REGISTRY)/observer:latest" -t "$(CONTAINER_REGISTRY)/observer:$(SHORT_COMMIT)" -t "$(CONTAINER_REGISTRY)/observer:$(IMAGE_TAG)" . - -.PHONY: docker-build-observer-without-netgo -docker-build-observer-without-netgo: - docker build -f cmd/Dockerfile --build-arg TARGET=./cmd/observer --build-arg COMMIT=$(COMMIT) --build-arg VERSION=$(IMAGE_TAG_NO_NETGO) --build-arg GOARCH=$(GOARCH) --build-arg CGO_FLAG=$(CRYPTO_FLAG) --target production \ - --secret id=git_creds,env=GITHUB_CREDS --build-arg GOPRIVATE=$(GOPRIVATE) \ - --label "git_commit=${COMMIT}" --label "git_tag=$(IMAGE_TAG_NO_NETGO)" \ - -t "$(CONTAINER_REGISTRY)/observer:$(IMAGE_TAG_NO_NETGO)" . - - -.PHONY: docker-build-ghost -docker-build-ghost: + -t "$(CONTAINER_REGISTRY)/observer:latest" \ + -t "$(CONTAINER_REGISTRY)/observer:$(IMAGE_TAG)" . + +.PHONY: docker-build-observer-with-adx +docker-build-observer-with-adx: + docker build -f cmd/Dockerfile --build-arg TARGET=./cmd/observer --build-arg COMMIT=$(COMMIT) --build-arg VERSION=$(IMAGE_TAG) --build-arg GOARCH=amd64 --target production \ + --secret id=cadence_deploy_key,env=CADENCE_DEPLOY_KEY --build-arg GOPRIVATE=$(GOPRIVATE) \ + --label "git_commit=${COMMIT}" --label "git_tag=$(IMAGE_TAG)" \ + -t "$(CONTAINER_REGISTRY)/observer:$(IMAGE_TAG)" . + +.PHONY: docker-build-observer-without-adx +docker-build-observer-without-adx: + docker build -f cmd/Dockerfile --build-arg TARGET=./cmd/observer --build-arg COMMIT=$(COMMIT) --build-arg VERSION=$(IMAGE_TAG_NO_ADX) --build-arg GOARCH=amd64 --build-arg CGO_FLAG=$(DISABLE_ADX) --target production \ + --secret id=cadence_deploy_key,env=CADENCE_DEPLOY_KEY --build-arg GOPRIVATE=$(GOPRIVATE) \ + --label "git_commit=${COMMIT}" --label "git_tag=$(IMAGE_TAG_NO_ADX)" \ + -t "$(CONTAINER_REGISTRY)/observer:$(IMAGE_TAG_NO_ADX)" . + +.PHONY: docker-build-observer-without-netgo-without-adx +docker-build-observer-without-netgo-without-adx: + docker build -f cmd/Dockerfile --build-arg TARGET=./cmd/observer --build-arg COMMIT=$(COMMIT) --build-arg VERSION=$(IMAGE_TAG_NO_NETGO_NO_ADX) --build-arg GOARCH=amd64 --build-arg TAGS="" --build-arg CGO_FLAG=$(DISABLE_ADX) --target production \ + --secret id=cadence_deploy_key,env=CADENCE_DEPLOY_KEY --build-arg GOPRIVATE=$(GOPRIVATE) \ + --label "git_commit=${COMMIT}" --label "git_tag=$(IMAGE_TAG_NO_NETGO_NO_ADX)" \ + -t "$(CONTAINER_REGISTRY)/observer:$(IMAGE_TAG_NO_NETGO_NO_ADX)" . + +.PHONY: docker-cross-build-observer-arm +docker-cross-build-observer-arm: + docker build -f cmd/Dockerfile --build-arg TARGET=./cmd/observer --build-arg COMMIT=$(COMMIT) --build-arg VERSION=$(IMAGE_TAG_ARM) --build-arg GOARCH=arm64 --build-arg CC=aarch64-linux-gnu-gcc --target production \ + --secret id=cadence_deploy_key,env=CADENCE_DEPLOY_KEY --build-arg GOPRIVATE=$(GOPRIVATE) \ + --label "git_commit=${COMMIT}" --label "git_tag=${IMAGE_TAG_ARM}" \ + -t "$(CONTAINER_REGISTRY)/observer:$(IMAGE_TAG_ARM)" . + + +.PHONY: docker-native-build-ghost +docker-native-build-ghost: docker build -f cmd/Dockerfile --build-arg TARGET=./cmd/ghost --build-arg COMMIT=$(COMMIT) --build-arg VERSION=$(IMAGE_TAG) --build-arg GOARCH=$(GOARCH) --build-arg CGO_FLAG=$(CRYPTO_FLAG) --target production \ --label "git_commit=${COMMIT}" --label "git_tag=${IMAGE_TAG}" \ - -t "$(CONTAINER_REGISTRY)/ghost:latest" -t "$(CONTAINER_REGISTRY)/ghost:$(SHORT_COMMIT)" -t "$(CONTAINER_REGISTRY)/ghost:$(IMAGE_TAG)" . + --secret id=cadence_deploy_key,env=CADENCE_DEPLOY_KEY --build-arg GOPRIVATE=$(GOPRIVATE) \ + -t "$(CONTAINER_REGISTRY)/ghost:latest" \ + -t "$(CONTAINER_REGISTRY)/ghost:$(IMAGE_TAG)" . -.PHONY: docker-build-ghost-debug -docker-build-ghost-debug: +.PHONY: docker-native-build-ghost-debug +docker-native-build-ghost-debug: docker build -f cmd/Dockerfile --build-arg TARGET=./cmd/ghost --build-arg COMMIT=$(COMMIT) --build-arg VERSION=$(IMAGE_TAG) --build-arg GOARCH=$(GOARCH) --build-arg CGO_FLAG=$(CRYPTO_FLAG) --target debug \ - -t "$(CONTAINER_REGISTRY)/ghost-debug:latest" -t "$(CONTAINER_REGISTRY)/ghost-debug:$(SHORT_COMMIT)" -t "$(CONTAINER_REGISTRY)/ghost-debug:$(IMAGE_TAG)" . + -t "$(CONTAINER_REGISTRY)/ghost-debug:latest" \ + -t "$(CONTAINER_REGISTRY)/ghost-debug:$(IMAGE_TAG)" . PHONY: docker-build-bootstrap docker-build-bootstrap: docker build -f cmd/Dockerfile --build-arg TARGET=./cmd/bootstrap --build-arg GOARCH=$(GOARCH) --build-arg VERSION=$(IMAGE_TAG) --build-arg CGO_FLAG=$(CRYPTO_FLAG) --target production \ --label "git_commit=${COMMIT}" --label "git_tag=${IMAGE_TAG}" \ - -t "$(CONTAINER_REGISTRY)/bootstrap:latest" -t "$(CONTAINER_REGISTRY)/bootstrap:$(SHORT_COMMIT)" -t "$(CONTAINER_REGISTRY)/bootstrap:$(IMAGE_TAG)" . + -t "$(CONTAINER_REGISTRY)/bootstrap:latest" \ + -t "$(CONTAINER_REGISTRY)/bootstrap:$(IMAGE_TAG)" . PHONY: tool-bootstrap tool-bootstrap: docker-build-bootstrap @@ -436,120 +601,171 @@ tool-bootstrap: docker-build-bootstrap docker-build-bootstrap-transit: docker build -f cmd/Dockerfile --build-arg TARGET=./cmd/bootstrap/transit --build-arg COMMIT=$(COMMIT) --build-arg VERSION=$(VERSION) --build-arg GOARCH=$(GOARCH) --build-arg CGO_FLAG=$(CRYPTO_FLAG) --no-cache \ --target production \ - -t "$(CONTAINER_REGISTRY)/bootstrap-transit:latest" -t "$(CONTAINER_REGISTRY)/bootstrap-transit:$(SHORT_COMMIT)" -t "$(CONTAINER_REGISTRY)/bootstrap-transit:$(IMAGE_TAG)" . + -t "$(CONTAINER_REGISTRY)/bootstrap-transit:latest" \ + -t "$(CONTAINER_REGISTRY)/bootstrap-transit:$(IMAGE_TAG)" . PHONY: tool-transit tool-transit: docker-build-bootstrap-transit docker container create --name transit $(CONTAINER_REGISTRY)/bootstrap-transit:latest;docker container cp transit:/bin/app ./transit;docker container rm transit -.PHONY: docker-build-loader -docker-build-loader: +.PHONY: docker-native-build-loader +docker-native-build-loader: docker build -f ./integration/benchmark/cmd/manual/Dockerfile --build-arg TARGET=./benchmark/cmd/manual --build-arg CGO_FLAG=$(CRYPTO_FLAG) --target production \ --label "git_commit=${COMMIT}" --label "git_tag=${IMAGE_TAG}" \ - -t "$(CONTAINER_REGISTRY)/loader:latest" -t "$(CONTAINER_REGISTRY)/loader:$(SHORT_COMMIT)" -t "$(CONTAINER_REGISTRY)/loader:$(IMAGE_TAG)" . + -t "$(CONTAINER_REGISTRY)/loader:latest" \ + -t "$(CONTAINER_REGISTRY)/loader:$(IMAGE_TAG)" . + +.PHONY: docker-native-build-flow +docker-native-build-flow: docker-native-build-collection docker-native-build-consensus docker-native-build-execution docker-native-build-verification docker-native-build-access docker-native-build-observer docker-native-build-ghost -.PHONY: docker-build-flow -docker-build-flow: docker-build-collection docker-build-consensus docker-build-execution docker-build-verification docker-build-access docker-build-observer docker-build-ghost +.PHONY: docker-build-flow-with-adx +docker-build-flow-with-adx: docker-build-collection-with-adx docker-build-consensus-with-adx docker-build-execution-with-adx docker-build-verification-with-adx docker-build-access-with-adx docker-build-observer-with-adx -.PHONY: docker-build-flow-without-netgo -docker-build-flow-without-netgo: docker-build-collection-without-netgo docker-build-consensus-without-netgo docker-build-execution-without-netgo docker-build-verification-without-netgo docker-build-access-without-netgo docker-build-observer-without-netgo +.PHONY: docker-build-flow-without-adx +docker-build-flow-without-adx: docker-build-collection-without-adx docker-build-consensus-without-adx docker-build-execution-without-adx docker-build-verification-without-adx docker-build-access-without-adx docker-build-observer-without-adx -.PHONY: docker-build-flow-corrupt -docker-build-flow-corrupt: docker-build-execution-corrupt docker-build-verification-corrupt docker-build-access-corrupt +.PHONY: docker-build-flow-without-netgo-without-adx +docker-build-flow-without-netgo-without-adx: docker-build-collection-without-netgo-without-adx docker-build-consensus-without-netgo-without-adx docker-build-execution-without-netgo-without-adx docker-build-verification-without-netgo-without-adx docker-build-access-without-netgo-without-adx docker-build-observer-without-netgo-without-adx -.PHONY: docker-build-benchnet -docker-build-benchnet: docker-build-flow docker-build-loader +# in this target, images are arm64 (aarch64), are build with `netgo` and with `adx`. +# other arm64 images can be built without `netgo` or without `adx` +.PHONY: docker-cross-build-flow-arm +docker-cross-build-flow-arm: docker-cross-build-collection-arm docker-cross-build-consensus-arm docker-cross-build-execution-arm docker-cross-build-verification-arm docker-cross-build-access-arm docker-cross-build-observer-arm -.PHONY: docker-push-collection -docker-push-collection: - docker push "$(CONTAINER_REGISTRY)/collection:$(SHORT_COMMIT)" +.PHONY: docker-native-build-flow-corrupt +docker-native-build-flow-corrupt: docker-native-build-execution-corrupt docker-native-build-verification-corrupt docker-native-build-access-corrupt + +.PHONY: docker-native-build-benchnet +docker-native-build-benchnet: docker-native-build-flow docker-native-build-loader + +.PHONY: docker-push-collection-with-adx +docker-push-collection-with-adx: docker push "$(CONTAINER_REGISTRY)/collection:$(IMAGE_TAG)" -.PHONY: docker-push-collection-without-netgo -docker-push-collection-without-netgo: - docker push "$(CONTAINER_REGISTRY)/collection:$(IMAGE_TAG_NO_NETGO)" +.PHONY: docker-push-collection-without-adx +docker-push-collection-without-adx: + docker push "$(CONTAINER_REGISTRY)/collection:$(IMAGE_TAG_NO_ADX)" + +.PHONY: docker-push-collection-without-netgo-without-adx +docker-push-collection-without-netgo-without-adx: + docker push "$(CONTAINER_REGISTRY)/collection:$(IMAGE_TAG_NO_NETGO_NO_ADX)" + +.PHONY: docker-push-collection-arm +docker-push-collection-arm: + docker push "$(CONTAINER_REGISTRY)/collection:$(IMAGE_TAG_ARM)" .PHONY: docker-push-collection-latest docker-push-collection-latest: docker-push-collection docker push "$(CONTAINER_REGISTRY)/collection:latest" -.PHONY: docker-push-consensus -docker-push-consensus: - docker push "$(CONTAINER_REGISTRY)/consensus:$(SHORT_COMMIT)" +.PHONY: docker-push-consensus-with-adx +docker-push-consensus-with-adx: docker push "$(CONTAINER_REGISTRY)/consensus:$(IMAGE_TAG)" -.PHONY: docker-push-consensus-without-netgo -docker-push-consensus-without-netgo: - docker push "$(CONTAINER_REGISTRY)/consensus:$(IMAGE_TAG_NO_NETGO)" +.PHONY: docker-push-consensus-without-adx +docker-push-consensus-without-adx: + docker push "$(CONTAINER_REGISTRY)/consensus:$(IMAGE_TAG_NO_ADX)" + +.PHONY: docker-push-consensus-without-netgo-without-adx +docker-push-consensus-without-netgo-without-adx: + docker push "$(CONTAINER_REGISTRY)/consensus:$(IMAGE_TAG_NO_NETGO_NO_ADX)" + +.PHONY: docker-push-consensus-arm +docker-push-consensus-arm: + docker push "$(CONTAINER_REGISTRY)/consensus:$(IMAGE_TAG_ARM)" .PHONY: docker-push-consensus-latest docker-push-consensus-latest: docker-push-consensus docker push "$(CONTAINER_REGISTRY)/consensus:latest" -.PHONY: docker-push-execution -docker-push-execution: - docker push "$(CONTAINER_REGISTRY)/execution:$(SHORT_COMMIT)" +.PHONY: docker-push-execution-with-adx +docker-push-execution-with-adx: docker push "$(CONTAINER_REGISTRY)/execution:$(IMAGE_TAG)" .PHONY: docker-push-execution-corrupt docker-push-execution-corrupt: - docker push "$(CONTAINER_REGISTRY)/execution-corrupted:$(SHORT_COMMIT)" docker push "$(CONTAINER_REGISTRY)/execution-corrupted:$(IMAGE_TAG)" +.PHONY: docker-push-execution-without-adx +docker-push-execution-without-adx: + docker push "$(CONTAINER_REGISTRY)/execution:$(IMAGE_TAG_NO_ADX)" -.PHONY: docker-push-execution-without-netgo -docker-push-execution-without-netgo: - docker push "$(CONTAINER_REGISTRY)/execution:$(IMAGE_TAG_NO_NETGO)" +.PHONY: docker-push-execution-without-netgo-without-adx +docker-push-execution-without-netgo-without-adx: + docker push "$(CONTAINER_REGISTRY)/execution:$(IMAGE_TAG_NO_NETGO_NO_ADX)" + +.PHONY: docker-push-execution-arm +docker-push-execution-arm: + docker push "$(CONTAINER_REGISTRY)/execution:$(IMAGE_TAG_ARM)" .PHONY: docker-push-execution-latest docker-push-execution-latest: docker-push-execution docker push "$(CONTAINER_REGISTRY)/execution:latest" -.PHONY: docker-push-verification -docker-push-verification: - docker push "$(CONTAINER_REGISTRY)/verification:$(SHORT_COMMIT)" +.PHONY: docker-push-verification-with-adx +docker-push-verification-with-adx: docker push "$(CONTAINER_REGISTRY)/verification:$(IMAGE_TAG)" +.PHONY: docker-push-verification-without-adx +docker-push-verification-without-adx: + docker push "$(CONTAINER_REGISTRY)/verification:$(IMAGE_TAG_NO_ADX)" + .PHONY: docker-push-verification-corrupt docker-push-verification-corrupt: - docker push "$(CONTAINER_REGISTRY)/verification-corrupted:$(SHORT_COMMIT)" docker push "$(CONTAINER_REGISTRY)/verification-corrupted:$(IMAGE_TAG)" -.PHONY: docker-push-verification-without-netgo -docker-push-verification-without-netgo: - docker push "$(CONTAINER_REGISTRY)/verification:$(IMAGE_TAG_NO_NETGO)" +.PHONY: docker-push-verification-without-netgo-without-adx +docker-push-verification-without-netgo-without-adx: + docker push "$(CONTAINER_REGISTRY)/verification:$(IMAGE_TAG_NO_NETGO_NO_ADX)" + +.PHONY: docker-push-verification-arm +docker-push-verification-arm: + docker push "$(CONTAINER_REGISTRY)/verification:$(IMAGE_TAG_ARM)" .PHONY: docker-push-verification-latest docker-push-verification-latest: docker-push-verification docker push "$(CONTAINER_REGISTRY)/verification:latest" -.PHONY: docker-push-access -docker-push-access: - docker push "$(CONTAINER_REGISTRY)/access:$(SHORT_COMMIT)" +.PHONY: docker-push-access-with-adx +docker-push-access-with-adx: docker push "$(CONTAINER_REGISTRY)/access:$(IMAGE_TAG)" +.PHONY: docker-push-access-without-adx +docker-push-access-without-adx: + docker push "$(CONTAINER_REGISTRY)/access:$(IMAGE_TAG_NO_ADX)" + .PHONY: docker-push-access-corrupt docker-push-access-corrupt: - docker push "$(CONTAINER_REGISTRY)/access-corrupted:$(SHORT_COMMIT)" docker push "$(CONTAINER_REGISTRY)/access-corrupted:$(IMAGE_TAG)" -.PHONY: docker-push-access-without-netgo -docker-push-access-without-netgo: - docker push "$(CONTAINER_REGISTRY)/access:$(IMAGE_TAG_NO_NETGO)" +.PHONY: docker-push-access-without-netgo-without-adx +docker-push-access-without-netgo-without-adx: + docker push "$(CONTAINER_REGISTRY)/access:$(IMAGE_TAG_NO_NETGO_NO_ADX)" + +.PHONY: docker-push-access-arm +docker-push-access-arm: + docker push "$(CONTAINER_REGISTRY)/access:$(IMAGE_TAG_ARM)" .PHONY: docker-push-access-latest docker-push-access-latest: docker-push-access docker push "$(CONTAINER_REGISTRY)/access:latest" -.PHONY: docker-push-observer -docker-push-observer: - docker push "$(CONTAINER_REGISTRY)/observer:$(SHORT_COMMIT)" +.PHONY: docker-push-observer-with-adx +docker-push-observer-with-adx: docker push "$(CONTAINER_REGISTRY)/observer:$(IMAGE_TAG)" -.PHONY: docker-push-observer-without-netgo -docker-push-observer-without-netgo: - docker push "$(CONTAINER_REGISTRY)/observer:$(IMAGE_TAG_NO_NETGO)" +.PHONY: docker-push-observer-without-adx +docker-push-observer-without-adx: + docker push "$(CONTAINER_REGISTRY)/observer:$(IMAGE_TAG_NO_ADX)" + +.PHONY: docker-push-observer-without-netgo-without-adx +docker-push-observer-without-netgo-without-adx: + docker push "$(CONTAINER_REGISTRY)/observer:$(IMAGE_TAG_NO_NETGO_NO_ADX)" + +.PHONY: docker-push-observer-arm +docker-push-observer-arm: + docker push "$(CONTAINER_REGISTRY)/observer:$(IMAGE_TAG_ARM)" .PHONY: docker-push-observer-latest docker-push-observer-latest: docker-push-observer @@ -557,7 +773,6 @@ docker-push-observer-latest: docker-push-observer .PHONY: docker-push-ghost docker-push-ghost: - docker push "$(CONTAINER_REGISTRY)/ghost:$(SHORT_COMMIT)" docker push "$(CONTAINER_REGISTRY)/ghost:$(IMAGE_TAG)" .PHONY: docker-push-ghost-latest @@ -566,18 +781,23 @@ docker-push-ghost-latest: docker-push-ghost .PHONY: docker-push-loader docker-push-loader: - docker push "$(CONTAINER_REGISTRY)/loader:$(SHORT_COMMIT)" docker push "$(CONTAINER_REGISTRY)/loader:$(IMAGE_TAG)" .PHONY: docker-push-loader-latest docker-push-loader-latest: docker-push-loader docker push "$(CONTAINER_REGISTRY)/loader:latest" -.PHONY: docker-push-flow -docker-push-flow: docker-push-collection docker-push-consensus docker-push-execution docker-push-verification docker-push-access docker-push-observer +.PHONY: docker-push-flow-with-adx +docker-push-flow-with-adx: docker-push-collection-with-adx docker-push-consensus-with-adx docker-push-execution-with-adx docker-push-verification-with-adx docker-push-access-with-adx docker-push-observer-with-adx + +.PHONY: docker-push-flow-without-adx +docker-push-flow-without-adx: docker-push-collection-without-adx docker-push-consensus-without-adx docker-push-execution-without-adx docker-push-verification-without-adx docker-push-access-without-adx docker-push-observer-without-adx + +.PHONY: docker-push-flow-without-netgo-without-adx +docker-push-flow-without-netgo-without-adx: docker-push-collection-without-netgo-without-adx docker-push-consensus-without-netgo-without-adx docker-push-execution-without-netgo-without-adx docker-push-verification-without-netgo-without-adx docker-push-access-without-netgo-without-adx docker-push-observer-without-netgo-without-adx -.PHONY: docker-push-flow-without-netgo -docker-push-flow-without-netgo: docker-push-collection-without-netgo docker-push-consensus-without-netgo docker-push-execution-without-netgo docker-push-verification-without-netgo docker-push-access-without-netgo docker-push-observer-without-netgo +.PHONY: docker-push-flow-arm +docker-push-flow-arm: docker-push-collection-arm docker-push-consensus-arm docker-push-execution-arm docker-push-verification-arm docker-push-access-arm docker-push-observer-arm .PHONY: docker-push-flow-latest docker-push-flow-latest: docker-push-collection-latest docker-push-consensus-latest docker-push-execution-latest docker-push-verification-latest docker-push-access-latest docker-push-observer-latest @@ -625,7 +845,8 @@ docker-all-tools: tool-util tool-remove-execution-fork PHONY: docker-build-util docker-build-util: docker build -f cmd/Dockerfile --build-arg TARGET=./cmd/util --build-arg GOARCH=$(GOARCH) --build-arg VERSION=$(IMAGE_TAG) --build-arg CGO_FLAG=$(CRYPTO_FLAG) --target production \ - -t "$(CONTAINER_REGISTRY)/util:latest" -t "$(CONTAINER_REGISTRY)/util:$(SHORT_COMMIT)" -t "$(CONTAINER_REGISTRY)/util:$(IMAGE_TAG)" . + -t "$(CONTAINER_REGISTRY)/util:latest" \ + -t "$(CONTAINER_REGISTRY)/util:$(IMAGE_TAG)" . PHONY: tool-util tool-util: docker-build-util @@ -634,7 +855,8 @@ tool-util: docker-build-util PHONY: docker-build-remove-execution-fork docker-build-remove-execution-fork: docker build -f cmd/Dockerfile --ssh default --build-arg TARGET=./cmd/util/cmd/remove-execution-fork --build-arg GOARCH=$(GOARCH) --build-arg VERSION=$(IMAGE_TAG) --build-arg CGO_FLAG=$(CRYPTO_FLAG) --target production \ - -t "$(CONTAINER_REGISTRY)/remove-execution-fork:latest" -t "$(CONTAINER_REGISTRY)/remove-execution-fork:$(SHORT_COMMIT)" -t "$(CONTAINER_REGISTRY)/remove-execution-fork:$(IMAGE_TAG)" . + -t "$(CONTAINER_REGISTRY)/remove-execution-fork:latest" \ + -t "$(CONTAINER_REGISTRY)/remove-execution-fork:$(IMAGE_TAG)" . PHONY: tool-remove-execution-fork tool-remove-execution-fork: docker-build-remove-execution-fork diff --git a/README.md b/README.md index 291e45de347..7a30d00ab1c 100644 --- a/README.md +++ b/README.md @@ -100,13 +100,13 @@ The recommended way to build and run Flow for local development is using Docker. Build a Docker image for all nodes: ```bash -make docker-build-flow +make docker-native-build-flow ``` Build a Docker image for a particular node role (replace `$ROLE` with `collection`, `consensus`, etc.): ```bash -make docker-build-$ROLE +make docker-native-build-$ROLE ``` ### Local Network diff --git a/access/legacy/convert/convert.go b/access/legacy/convert/convert.go index ebb52d2fe14..31f78add83a 100644 --- a/access/legacy/convert/convert.go +++ b/access/legacy/convert/convert.go @@ -4,13 +4,13 @@ import ( "errors" "fmt" + "github.com/onflow/crypto" + "github.com/onflow/crypto/hash" accessproto "github.com/onflow/flow/protobuf/go/flow/legacy/access" entitiesproto "github.com/onflow/flow/protobuf/go/flow/legacy/entities" "google.golang.org/protobuf/types/known/timestamppb" "github.com/onflow/flow-go/access" - "github.com/onflow/flow-go/crypto" - "github.com/onflow/flow-go/crypto/hash" "github.com/onflow/flow-go/engine/common/rpc/convert" "github.com/onflow/flow-go/model/flow" ) @@ -69,7 +69,7 @@ func MessageToTransaction(m *entitiesproto.Transaction, chain flow.Chain) (flow. t.SetScript(m.GetScript()) t.SetArguments(m.GetArguments()) t.SetReferenceBlockID(flow.HashToID(m.GetReferenceBlockId())) - t.SetGasLimit(m.GetGasLimit()) + t.SetComputeLimit(m.GetGasLimit()) return *t, nil } diff --git a/access/validator.go b/access/validator.go index 2d87604a27a..b59a1539b95 100644 --- a/access/validator.go +++ b/access/validator.go @@ -4,12 +4,11 @@ import ( "errors" "fmt" - "github.com/onflow/flow-go/crypto" - "github.com/onflow/flow-go/state" - "github.com/onflow/cadence/runtime/parser" + "github.com/onflow/crypto" "github.com/onflow/flow-go/model/flow" + "github.com/onflow/flow-go/state" "github.com/onflow/flow-go/state/protocol" ) diff --git a/admin/admin/admin.pb.go b/admin/admin/admin.pb.go index c886224d85c..97279dc1ac4 100644 --- a/admin/admin/admin.pb.go +++ b/admin/admin/admin.pb.go @@ -7,12 +7,13 @@ package admin import ( + reflect "reflect" + sync "sync" + _ "google.golang.org/genproto/googleapis/api/annotations" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" structpb "google.golang.org/protobuf/types/known/structpb" - reflect "reflect" - sync "sync" ) const ( diff --git a/admin/admin/admin_grpc.pb.go b/admin/admin/admin_grpc.pb.go index 43c25676ec7..4ca02408fe7 100644 --- a/admin/admin/admin_grpc.pb.go +++ b/admin/admin/admin_grpc.pb.go @@ -4,6 +4,7 @@ package admin import ( context "context" + grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" status "google.golang.org/grpc/status" diff --git a/cmd/Dockerfile b/cmd/Dockerfile index 5f72b5c1c48..78b24a4a051 100644 --- a/cmd/Dockerfile +++ b/cmd/Dockerfile @@ -6,7 +6,7 @@ FROM golang:1.20-bullseye AS build-setup RUN apt-get update -RUN apt-get -y install zip +RUN apt-get -y install zip apt-utils gcc-aarch64-linux-gnu ## (2) Setup crypto dependencies FROM build-setup AS build-env @@ -23,9 +23,12 @@ ENV GOPRIVATE= COPY . . -RUN --mount=type=cache,sharing=locked,target=/go/pkg/mod \ - --mount=type=cache,target=/root/.cache/go-build \ - --mount=type=secret,id=git_creds,dst=/root/.netrc +# Update the git config to use SSH rather than HTTPS for clones +RUN git config --global url.git@github.com:.insteadOf https://github.com/ +RUN mkdir ~/.ssh + +# Add GitHub known host to avoid prompts or failures on key check +RUN ssh-keyscan -t rsa github.com >> ~/.ssh/known_hosts #################################### ## (3) Build the production app binary @@ -33,18 +36,22 @@ FROM build-env as build-production WORKDIR /app ARG GOARCH=amd64 - # TAGS can be overriden to modify the go build tags (e.g. build without netgo) ARG TAGS="netgo" -# CGO_FLAG can be overwritten -ARG CGO_FLAG +# CC flag can be overwritten to specify a C compiler +ARG CC="" +# CGO_FLAG uses ADX instructions by default, flag can be overwritten to build without ADX +ARG CGO_FLAG="" # Keep Go's build cache between builds. # https://github.com/golang/go/issues/27719#issuecomment-514747274 RUN --mount=type=cache,sharing=locked,target=/go/pkg/mod \ --mount=type=cache,target=/root/.cache/go-build \ - --mount=type=secret,id=git_creds,dst=/root/.netrc \ - CGO_ENABLED=1 GOOS=linux CGO_FLAGS="${CGO_FLAG}" go build --tags "${TAGS}" -ldflags "-extldflags -static \ + --mount=type=secret,id=cadence_deploy_key \ + # We evaluate the SSH agent to safely pass in a key for cloning dependencies + # We explicitly use ";" rather than && as we want to safely pass if it is unavailable + eval `ssh-agent -s` && printf "%s\n" "$(cat /run/secrets/cadence_deploy_key)" | ssh-add - ; \ + CGO_ENABLED=1 GOOS=linux GOARCH=${GOARCH} CC="${CC}" CGO_FLAGS="${CGO_FLAG}" go build --tags "${TAGS}" -ldflags "-extldflags -static \ -X 'github.com/onflow/flow-go/cmd/build.commit=${COMMIT}' -X 'github.com/onflow/flow-go/cmd/build.semver=${VERSION}'" \ -o ./app ${TARGET} @@ -62,10 +69,16 @@ ENTRYPOINT ["/bin/app"] FROM build-env as build-debug WORKDIR /app ARG GOARCH=amd64 +ARG CC="" +ARG CGO_FLAG="" RUN --mount=type=ssh \ --mount=type=cache,sharing=locked,target=/go/pkg/mod \ --mount=type=cache,target=/root/.cache/go-build \ - CGO_ENABLED=1 GOOS=linux CGO_FLAGS="${CGO_FLAG}" go build --tags "netgo" -ldflags "-extldflags -static \ + --mount=type=secret,id=cadence_deploy_key \ + # We evaluate the SSH agent to safely pass in a key for cloning dependencies + # We explicitly use ";" rather than && as we want to safely pass if it is unavailable + eval `ssh-agent -s` && printf "%s\n" "$(cat /run/secrets/cadence_deploy_key)" | ssh-add - ; \ + CGO_ENABLED=1 GOOS=linux GOARCH=${GOARCH} CC="${CC}" CGO_FLAGS="${CGO_FLAG}" go build --tags "netgo" -ldflags "-extldflags -static \ -X 'github.com/onflow/flow-go/cmd/build.commit=${COMMIT}' -X 'github.com/onflow/flow-go/cmd/build.semver=${VERSION}'" \ -gcflags="all=-N -l" -o ./app ${TARGET} diff --git a/cmd/access/node_builder/access_node_builder.go b/cmd/access/node_builder/access_node_builder.go index 306f9c86a9b..f60931e02c4 100644 --- a/cmd/access/node_builder/access_node_builder.go +++ b/cmd/access/node_builder/access_node_builder.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "math" "os" "path" "path/filepath" @@ -13,6 +14,7 @@ import ( badger "github.com/ipfs/go-ds-badger2" "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/routing" + "github.com/onflow/crypto" "github.com/onflow/flow/protobuf/go/flow/access" "github.com/onflow/go-bitswap" "github.com/rs/zerolog" @@ -34,7 +36,6 @@ import ( hotstuffvalidator "github.com/onflow/flow-go/consensus/hotstuff/validator" "github.com/onflow/flow-go/consensus/hotstuff/verification" recovery "github.com/onflow/flow-go/consensus/recovery/protocol" - "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/engine" "github.com/onflow/flow-go/engine/access/ingestion" pingeng "github.com/onflow/flow-go/engine/access/ping" @@ -77,20 +78,19 @@ import ( cborcodec "github.com/onflow/flow-go/network/codec/cbor" "github.com/onflow/flow-go/network/p2p" "github.com/onflow/flow-go/network/p2p/blob" + p2pbuilder "github.com/onflow/flow-go/network/p2p/builder" + p2pbuilderconfig "github.com/onflow/flow-go/network/p2p/builder/config" "github.com/onflow/flow-go/network/p2p/cache" "github.com/onflow/flow-go/network/p2p/conduit" "github.com/onflow/flow-go/network/p2p/connection" "github.com/onflow/flow-go/network/p2p/dht" - "github.com/onflow/flow-go/network/p2p/p2pbuilder" - p2pconfig "github.com/onflow/flow-go/network/p2p/p2pbuilder/config" - "github.com/onflow/flow-go/network/p2p/p2pnet" "github.com/onflow/flow-go/network/p2p/subscription" - "github.com/onflow/flow-go/network/p2p/tracer" "github.com/onflow/flow-go/network/p2p/translator" "github.com/onflow/flow-go/network/p2p/unicast/protocols" relaynet "github.com/onflow/flow-go/network/relay" "github.com/onflow/flow-go/network/slashing" "github.com/onflow/flow-go/network/topology" + "github.com/onflow/flow-go/network/underlay" "github.com/onflow/flow-go/network/validator" "github.com/onflow/flow-go/state/protocol" badgerState "github.com/onflow/flow-go/state/protocol/badger" @@ -148,6 +148,8 @@ type AccessNodeConfig struct { registersDBPath string checkpointFile string scriptExecutorConfig query.QueryConfig + scriptExecMinBlock uint64 + scriptExecMaxBlock uint64 } type PublicNetworkConfig struct { @@ -183,7 +185,8 @@ func DefaultAccessNodeConfig() *AccessNodeConfig { MaxFailures: 5, MaxRequests: 1, }, - ScriptExecutionMode: backend.ScriptExecutionModeExecutionNodesOnly.String(), // default to ENs only for now + ScriptExecutionMode: backend.IndexQueryModeExecutionNodesOnly.String(), // default to ENs only for now + EventQueryMode: backend.IndexQueryModeExecutionNodesOnly.String(), // default to ENs only for now }, RestConfig: rest.Config{ ListenAddress: "", @@ -237,6 +240,8 @@ func DefaultAccessNodeConfig() *AccessNodeConfig { registersDBPath: filepath.Join(homedir, ".flow", "execution_state"), checkpointFile: cmd.NotSet, scriptExecutorConfig: query.NewDefaultConfig(), + scriptExecMinBlock: 0, + scriptExecMaxBlock: math.MaxUint64, } } @@ -274,6 +279,7 @@ type FlowAccessNodeBuilder struct { ExecutionIndexerCore *indexer.IndexerCore ScriptExecutor *backend.ScriptExecutor RegistersAsyncStore *execution.RegistersAsyncStore + IndexerDependencies *cmd.DependencyList // The sync engine participants provider is the libp2p peer store for the access node // which is not available until after the network has started. @@ -489,8 +495,7 @@ func (builder *FlowAccessNodeBuilder) BuildExecutionSyncComponents() *FlowAccess // setup dependency chain to ensure indexer starts after the requester requesterDependable := module.NewProxiedReadyDoneAware() - indexerDependencies := cmd.NewDependencyList() - indexerDependencies.Add(requesterDependable) + builder.IndexerDependencies.Add(requesterDependable) builder. AdminCommand("read-execution-data", func(config *cmd.NodeConfig) commands.AdminCommand { @@ -670,10 +675,6 @@ func (builder *FlowAccessNodeBuilder) BuildExecutionSyncComponents() *FlowAccess indexedBlockHeight = bstorage.NewConsumerProgress(builder.DB, module.ConsumeProgressExecutionDataIndexerBlockHeight) return nil }). - Module("events storage", func(node *cmd.NodeConfig) error { - builder.Storage.Events = bstorage.NewEvents(node.Metrics.Cache, node.DB) - return nil - }). Module("transaction results storage", func(node *cmd.NodeConfig) error { builder.Storage.LightTransactionResults = bstorage.NewLightTransactionResults(node.Metrics.Cache, node.DB, bstorage.DefaultCacheSize) return nil @@ -717,14 +718,20 @@ func (builder *FlowAccessNodeBuilder) BuildExecutionSyncComponents() *FlowAccess checkpointHeight := builder.SealedRootBlock.Header.Height - buutstrap, err := pStorage.NewRegisterBootstrap(pdb, checkpointFile, checkpointHeight, builder.Logger) + if builder.SealedRootBlock.ID() != builder.RootSeal.BlockID { + return nil, fmt.Errorf("mismatching sealed root block and root seal: %v != %v", + builder.SealedRootBlock.ID(), builder.RootSeal.BlockID) + } + + rootHash := ledger.RootHash(builder.RootSeal.FinalState) + bootstrap, err := pStorage.NewRegisterBootstrap(pdb, checkpointFile, checkpointHeight, rootHash, builder.Logger) if err != nil { - return nil, fmt.Errorf("could not create registers bootstrapper: %w", err) + return nil, fmt.Errorf("could not create registers bootstrap: %w", err) } // TODO: find a way to hook a context up to this to allow a graceful shutdown workerCount := 10 - err = buutstrap.IndexCheckpointFile(context.Background(), workerCount) + err = bootstrap.IndexCheckpointFile(context.Background(), workerCount) if err != nil { return nil, fmt.Errorf("could not load checkpoint file: %w", err) } @@ -745,6 +752,7 @@ func (builder *FlowAccessNodeBuilder) BuildExecutionSyncComponents() *FlowAccess builder.Storage.Headers, builder.Storage.Events, builder.Storage.LightTransactionResults, + builder.IngestEng.OnCollection, ) if err != nil { return nil, err @@ -791,7 +799,7 @@ func (builder *FlowAccessNodeBuilder) BuildExecutionSyncComponents() *FlowAccess builder.ScriptExecutor.InitReporter(builder.ExecutionIndexer, scripts) return builder.ExecutionIndexer, nil - }, indexerDependencies) + }, builder.IndexerDependencies) } if builder.stateStreamConf.ListenAddr != "" { @@ -814,11 +822,22 @@ func (builder *FlowAccessNodeBuilder) BuildExecutionSyncComponents() *FlowAccess } broadcaster := engine.NewBroadcaster() + eventQueryMode, err := backend.ParseIndexQueryMode(builder.rpcConf.BackendConfig.EventQueryMode) + if err != nil { + return nil, fmt.Errorf("could not parse event query mode: %w", err) + } + + // use the events index for events if enabled and the node is configured to use it for + // regular event queries + useIndex := builder.executionDataIndexingEnabled && + eventQueryMode != backend.IndexQueryModeExecutionNodesOnly + builder.stateStreamBackend, err = statestreambackend.New( node.Logger, builder.stateStreamConf, node.State, node.Storage.Headers, + node.Storage.Events, node.Storage.Seals, node.Storage.Results, builder.ExecutionDataStore, @@ -826,7 +845,9 @@ func (builder *FlowAccessNodeBuilder) BuildExecutionSyncComponents() *FlowAccess broadcaster, builder.executionDataConfig.InitialBlockHeight, highestAvailableHeight, - builder.RegistersAsyncStore) + builder.RegistersAsyncStore, + useIndex, + ) if err != nil { return nil, fmt.Errorf("could not create state stream backend: %w", err) } @@ -862,6 +883,7 @@ func FlowAccessNode(nodeBuilder *cmd.FlowNodeBuilder) *FlowAccessNodeBuilder { AccessNodeConfig: DefaultAccessNodeConfig(), FlowNodeBuilder: nodeBuilder, FollowerDistributor: dist, + IndexerDependencies: cmd.NewDependencyList(), } } @@ -880,74 +902,224 @@ func (builder *FlowAccessNodeBuilder) extraFlags() { flags.UintVar(&builder.collectionGRPCPort, "collection-ingress-port", defaultConfig.collectionGRPCPort, "the grpc ingress port for all collection nodes") flags.UintVar(&builder.executionGRPCPort, "execution-ingress-port", defaultConfig.executionGRPCPort, "the grpc ingress port for all execution nodes") - flags.StringVarP(&builder.rpcConf.UnsecureGRPCListenAddr, "rpc-addr", "r", defaultConfig.rpcConf.UnsecureGRPCListenAddr, "the address the unsecured gRPC server listens on") - flags.StringVar(&builder.rpcConf.SecureGRPCListenAddr, "secure-rpc-addr", defaultConfig.rpcConf.SecureGRPCListenAddr, "the address the secure gRPC server listens on") - flags.StringVar(&builder.stateStreamConf.ListenAddr, "state-stream-addr", defaultConfig.stateStreamConf.ListenAddr, "the address the state stream server listens on (if empty the server will not be started)") + flags.StringVarP(&builder.rpcConf.UnsecureGRPCListenAddr, + "rpc-addr", + "r", + defaultConfig.rpcConf.UnsecureGRPCListenAddr, + "the address the unsecured gRPC server listens on") + flags.StringVar(&builder.rpcConf.SecureGRPCListenAddr, + "secure-rpc-addr", + defaultConfig.rpcConf.SecureGRPCListenAddr, + "the address the secure gRPC server listens on") + flags.StringVar(&builder.stateStreamConf.ListenAddr, + "state-stream-addr", + defaultConfig.stateStreamConf.ListenAddr, + "the address the state stream server listens on (if empty the server will not be started)") flags.StringVarP(&builder.rpcConf.HTTPListenAddr, "http-addr", "h", defaultConfig.rpcConf.HTTPListenAddr, "the address the http proxy server listens on") - flags.StringVar(&builder.rpcConf.RestConfig.ListenAddress, "rest-addr", defaultConfig.rpcConf.RestConfig.ListenAddress, "the address the REST server listens on (if empty the REST server will not be started)") - flags.DurationVar(&builder.rpcConf.RestConfig.WriteTimeout, "rest-write-timeout", defaultConfig.rpcConf.RestConfig.WriteTimeout, "timeout to use when writing REST response") - flags.DurationVar(&builder.rpcConf.RestConfig.ReadTimeout, "rest-read-timeout", defaultConfig.rpcConf.RestConfig.ReadTimeout, "timeout to use when reading REST request headers") + flags.StringVar(&builder.rpcConf.RestConfig.ListenAddress, + "rest-addr", + defaultConfig.rpcConf.RestConfig.ListenAddress, + "the address the REST server listens on (if empty the REST server will not be started)") + flags.DurationVar(&builder.rpcConf.RestConfig.WriteTimeout, + "rest-write-timeout", + defaultConfig.rpcConf.RestConfig.WriteTimeout, + "timeout to use when writing REST response") + flags.DurationVar(&builder.rpcConf.RestConfig.ReadTimeout, + "rest-read-timeout", + defaultConfig.rpcConf.RestConfig.ReadTimeout, + "timeout to use when reading REST request headers") flags.DurationVar(&builder.rpcConf.RestConfig.IdleTimeout, "rest-idle-timeout", defaultConfig.rpcConf.RestConfig.IdleTimeout, "idle timeout for REST connections") - flags.StringVarP(&builder.rpcConf.CollectionAddr, "static-collection-ingress-addr", "", defaultConfig.rpcConf.CollectionAddr, "the address (of the collection node) to send transactions to") - flags.StringVarP(&builder.ExecutionNodeAddress, "script-addr", "s", defaultConfig.ExecutionNodeAddress, "the address (of the execution node) forward the script to") - flags.StringVarP(&builder.rpcConf.HistoricalAccessAddrs, "historical-access-addr", "", defaultConfig.rpcConf.HistoricalAccessAddrs, "comma separated rpc addresses for historical access nodes") - flags.DurationVar(&builder.rpcConf.BackendConfig.CollectionClientTimeout, "collection-client-timeout", defaultConfig.rpcConf.BackendConfig.CollectionClientTimeout, "grpc client timeout for a collection node") - flags.DurationVar(&builder.rpcConf.BackendConfig.ExecutionClientTimeout, "execution-client-timeout", defaultConfig.rpcConf.BackendConfig.ExecutionClientTimeout, "grpc client timeout for an execution node") - flags.UintVar(&builder.rpcConf.BackendConfig.ConnectionPoolSize, "connection-pool-size", defaultConfig.rpcConf.BackendConfig.ConnectionPoolSize, "maximum number of connections allowed in the connection pool, size of 0 disables the connection pooling, and anything less than the default size will be overridden to use the default size") - flags.UintVar(&builder.rpcConf.MaxMsgSize, "rpc-max-message-size", grpcutils.DefaultMaxMsgSize, "the maximum message size in bytes for messages sent or received over grpc") - flags.UintVar(&builder.rpcConf.BackendConfig.MaxHeightRange, "rpc-max-height-range", defaultConfig.rpcConf.BackendConfig.MaxHeightRange, "maximum size for height range requests") - flags.StringSliceVar(&builder.rpcConf.BackendConfig.PreferredExecutionNodeIDs, "preferred-execution-node-ids", defaultConfig.rpcConf.BackendConfig.PreferredExecutionNodeIDs, "comma separated list of execution nodes ids to choose from when making an upstream call e.g. b4a4dbdcd443d...,fb386a6a... etc.") - flags.StringSliceVar(&builder.rpcConf.BackendConfig.FixedExecutionNodeIDs, "fixed-execution-node-ids", defaultConfig.rpcConf.BackendConfig.FixedExecutionNodeIDs, "comma separated list of execution nodes ids to choose from when making an upstream call if no matching preferred execution id is found e.g. b4a4dbdcd443d...,fb386a6a... etc.") - flags.StringVar(&builder.rpcConf.CompressorName, "grpc-compressor", defaultConfig.rpcConf.CompressorName, "name of grpc compressor that will be used for requests to other nodes. One of (gzip, snappy, deflate)") + flags.StringVarP(&builder.rpcConf.CollectionAddr, + "static-collection-ingress-addr", + "", + defaultConfig.rpcConf.CollectionAddr, + "the address (of the collection node) to send transactions to") + flags.StringVarP(&builder.ExecutionNodeAddress, + "script-addr", + "s", + defaultConfig.ExecutionNodeAddress, + "the address (of the execution node) forward the script to") + flags.StringVarP(&builder.rpcConf.HistoricalAccessAddrs, + "historical-access-addr", + "", + defaultConfig.rpcConf.HistoricalAccessAddrs, + "comma separated rpc addresses for historical access nodes") + flags.DurationVar(&builder.rpcConf.BackendConfig.CollectionClientTimeout, + "collection-client-timeout", + defaultConfig.rpcConf.BackendConfig.CollectionClientTimeout, + "grpc client timeout for a collection node") + flags.DurationVar(&builder.rpcConf.BackendConfig.ExecutionClientTimeout, + "execution-client-timeout", + defaultConfig.rpcConf.BackendConfig.ExecutionClientTimeout, + "grpc client timeout for an execution node") + flags.UintVar(&builder.rpcConf.BackendConfig.ConnectionPoolSize, + "connection-pool-size", + defaultConfig.rpcConf.BackendConfig.ConnectionPoolSize, + "maximum number of connections allowed in the connection pool, size of 0 disables the connection pooling, and anything less than the default size will be overridden to use the default size") + flags.UintVar(&builder.rpcConf.MaxMsgSize, + "rpc-max-message-size", + grpcutils.DefaultMaxMsgSize, + "the maximum message size in bytes for messages sent or received over grpc") + flags.UintVar(&builder.rpcConf.BackendConfig.MaxHeightRange, + "rpc-max-height-range", + defaultConfig.rpcConf.BackendConfig.MaxHeightRange, + "maximum size for height range requests") + flags.StringSliceVar(&builder.rpcConf.BackendConfig.PreferredExecutionNodeIDs, + "preferred-execution-node-ids", + defaultConfig.rpcConf.BackendConfig.PreferredExecutionNodeIDs, + "comma separated list of execution nodes ids to choose from when making an upstream call e.g. b4a4dbdcd443d...,fb386a6a... etc.") + flags.StringSliceVar(&builder.rpcConf.BackendConfig.FixedExecutionNodeIDs, + "fixed-execution-node-ids", + defaultConfig.rpcConf.BackendConfig.FixedExecutionNodeIDs, + "comma separated list of execution nodes ids to choose from when making an upstream call if no matching preferred execution id is found e.g. b4a4dbdcd443d...,fb386a6a... etc.") + flags.StringVar(&builder.rpcConf.CompressorName, + "grpc-compressor", + defaultConfig.rpcConf.CompressorName, + "name of grpc compressor that will be used for requests to other nodes. One of (gzip, snappy, deflate)") flags.BoolVar(&builder.logTxTimeToFinalized, "log-tx-time-to-finalized", defaultConfig.logTxTimeToFinalized, "log transaction time to finalized") flags.BoolVar(&builder.logTxTimeToExecuted, "log-tx-time-to-executed", defaultConfig.logTxTimeToExecuted, "log transaction time to executed") - flags.BoolVar(&builder.logTxTimeToFinalizedExecuted, "log-tx-time-to-finalized-executed", defaultConfig.logTxTimeToFinalizedExecuted, "log transaction time to finalized and executed") - flags.BoolVar(&builder.pingEnabled, "ping-enabled", defaultConfig.pingEnabled, "whether to enable the ping process that pings all other peers and report the connectivity to metrics") + flags.BoolVar(&builder.logTxTimeToFinalizedExecuted, + "log-tx-time-to-finalized-executed", + defaultConfig.logTxTimeToFinalizedExecuted, + "log transaction time to finalized and executed") + flags.BoolVar(&builder.pingEnabled, + "ping-enabled", + defaultConfig.pingEnabled, + "whether to enable the ping process that pings all other peers and report the connectivity to metrics") flags.BoolVar(&builder.retryEnabled, "retry-enabled", defaultConfig.retryEnabled, "whether to enable the retry mechanism at the access node level") flags.BoolVar(&builder.rpcMetricsEnabled, "rpc-metrics-enabled", defaultConfig.rpcMetricsEnabled, "whether to enable the rpc metrics") flags.UintVar(&builder.TxResultCacheSize, "transaction-result-cache-size", defaultConfig.TxResultCacheSize, "transaction result cache size.(Disabled by default i.e 0)") flags.UintVar(&builder.TxErrorMessagesCacheSize, "transaction-error-messages-cache-size", defaultConfig.TxErrorMessagesCacheSize, "transaction error messages cache size.(By default 1000)") - flags.StringVarP(&builder.nodeInfoFile, "node-info-file", "", defaultConfig.nodeInfoFile, "full path to a json file which provides more details about nodes when reporting its reachability metrics") + flags.StringVarP(&builder.nodeInfoFile, + "node-info-file", + "", + defaultConfig.nodeInfoFile, + "full path to a json file which provides more details about nodes when reporting its reachability metrics") flags.StringToIntVar(&builder.apiRatelimits, "api-rate-limits", defaultConfig.apiRatelimits, "per second rate limits for Access API methods e.g. Ping=300,GetTransaction=500 etc.") flags.StringToIntVar(&builder.apiBurstlimits, "api-burst-limits", defaultConfig.apiBurstlimits, "burst limits for Access API methods e.g. Ping=100,GetTransaction=100 etc.") flags.BoolVar(&builder.supportsObserver, "supports-observer", defaultConfig.supportsObserver, "true if this staked access node supports observer or follower connections") flags.StringVar(&builder.PublicNetworkConfig.BindAddress, "public-network-address", defaultConfig.PublicNetworkConfig.BindAddress, "staked access node's public network bind address") - flags.BoolVar(&builder.rpcConf.BackendConfig.CircuitBreakerConfig.Enabled, "circuit-breaker-enabled", defaultConfig.rpcConf.BackendConfig.CircuitBreakerConfig.Enabled, "specifies whether the circuit breaker is enabled for collection and execution API clients.") - flags.DurationVar(&builder.rpcConf.BackendConfig.CircuitBreakerConfig.RestoreTimeout, "circuit-breaker-restore-timeout", defaultConfig.rpcConf.BackendConfig.CircuitBreakerConfig.RestoreTimeout, "duration after which the circuit breaker will restore the connection to the client after closing it due to failures. Default value is 60s") - flags.Uint32Var(&builder.rpcConf.BackendConfig.CircuitBreakerConfig.MaxFailures, "circuit-breaker-max-failures", defaultConfig.rpcConf.BackendConfig.CircuitBreakerConfig.MaxFailures, "maximum number of failed calls to the client that will cause the circuit breaker to close the connection. Default value is 5") - flags.Uint32Var(&builder.rpcConf.BackendConfig.CircuitBreakerConfig.MaxRequests, "circuit-breaker-max-requests", defaultConfig.rpcConf.BackendConfig.CircuitBreakerConfig.MaxRequests, "maximum number of requests to check if connection restored after timeout. Default value is 1") + flags.BoolVar(&builder.rpcConf.BackendConfig.CircuitBreakerConfig.Enabled, + "circuit-breaker-enabled", + defaultConfig.rpcConf.BackendConfig.CircuitBreakerConfig.Enabled, + "specifies whether the circuit breaker is enabled for collection and execution API clients.") + flags.DurationVar(&builder.rpcConf.BackendConfig.CircuitBreakerConfig.RestoreTimeout, + "circuit-breaker-restore-timeout", + defaultConfig.rpcConf.BackendConfig.CircuitBreakerConfig.RestoreTimeout, + "duration after which the circuit breaker will restore the connection to the client after closing it due to failures. Default value is 60s") + flags.Uint32Var(&builder.rpcConf.BackendConfig.CircuitBreakerConfig.MaxFailures, + "circuit-breaker-max-failures", + defaultConfig.rpcConf.BackendConfig.CircuitBreakerConfig.MaxFailures, + "maximum number of failed calls to the client that will cause the circuit breaker to close the connection. Default value is 5") + flags.Uint32Var(&builder.rpcConf.BackendConfig.CircuitBreakerConfig.MaxRequests, + "circuit-breaker-max-requests", + defaultConfig.rpcConf.BackendConfig.CircuitBreakerConfig.MaxRequests, + "maximum number of requests to check if connection restored after timeout. Default value is 1") // ExecutionDataRequester config - flags.BoolVar(&builder.executionDataSyncEnabled, "execution-data-sync-enabled", defaultConfig.executionDataSyncEnabled, "whether to enable the execution data sync protocol") + flags.BoolVar(&builder.executionDataSyncEnabled, + "execution-data-sync-enabled", + defaultConfig.executionDataSyncEnabled, + "whether to enable the execution data sync protocol") flags.StringVar(&builder.executionDataDir, "execution-data-dir", defaultConfig.executionDataDir, "directory to use for Execution Data database") - flags.Uint64Var(&builder.executionDataStartHeight, "execution-data-start-height", defaultConfig.executionDataStartHeight, "height of first block to sync execution data from when starting with an empty Execution Data database") - flags.Uint64Var(&builder.executionDataConfig.MaxSearchAhead, "execution-data-max-search-ahead", defaultConfig.executionDataConfig.MaxSearchAhead, "max number of heights to search ahead of the lowest outstanding execution data height") - flags.DurationVar(&builder.executionDataConfig.FetchTimeout, "execution-data-fetch-timeout", defaultConfig.executionDataConfig.FetchTimeout, "initial timeout to use when fetching execution data from the network. timeout increases using an incremental backoff until execution-data-max-fetch-timeout. e.g. 30s") - flags.DurationVar(&builder.executionDataConfig.MaxFetchTimeout, "execution-data-max-fetch-timeout", defaultConfig.executionDataConfig.MaxFetchTimeout, "maximum timeout to use when fetching execution data from the network e.g. 300s") - flags.DurationVar(&builder.executionDataConfig.RetryDelay, "execution-data-retry-delay", defaultConfig.executionDataConfig.RetryDelay, "initial delay for exponential backoff when fetching execution data fails e.g. 10s") - flags.DurationVar(&builder.executionDataConfig.MaxRetryDelay, "execution-data-max-retry-delay", defaultConfig.executionDataConfig.MaxRetryDelay, "maximum delay for exponential backoff when fetching execution data fails e.g. 5m") + flags.Uint64Var(&builder.executionDataStartHeight, + "execution-data-start-height", + defaultConfig.executionDataStartHeight, + "height of first block to sync execution data from when starting with an empty Execution Data database") + flags.Uint64Var(&builder.executionDataConfig.MaxSearchAhead, + "execution-data-max-search-ahead", + defaultConfig.executionDataConfig.MaxSearchAhead, + "max number of heights to search ahead of the lowest outstanding execution data height") + flags.DurationVar(&builder.executionDataConfig.FetchTimeout, + "execution-data-fetch-timeout", + defaultConfig.executionDataConfig.FetchTimeout, + "initial timeout to use when fetching execution data from the network. timeout increases using an incremental backoff until execution-data-max-fetch-timeout. e.g. 30s") + flags.DurationVar(&builder.executionDataConfig.MaxFetchTimeout, + "execution-data-max-fetch-timeout", + defaultConfig.executionDataConfig.MaxFetchTimeout, + "maximum timeout to use when fetching execution data from the network e.g. 300s") + flags.DurationVar(&builder.executionDataConfig.RetryDelay, + "execution-data-retry-delay", + defaultConfig.executionDataConfig.RetryDelay, + "initial delay for exponential backoff when fetching execution data fails e.g. 10s") + flags.DurationVar(&builder.executionDataConfig.MaxRetryDelay, + "execution-data-max-retry-delay", + defaultConfig.executionDataConfig.MaxRetryDelay, + "maximum delay for exponential backoff when fetching execution data fails e.g. 5m") // Execution State Streaming API flags.Uint32Var(&builder.stateStreamConf.ExecutionDataCacheSize, "execution-data-cache-size", defaultConfig.stateStreamConf.ExecutionDataCacheSize, "block execution data cache size") flags.Uint32Var(&builder.stateStreamConf.MaxGlobalStreams, "state-stream-global-max-streams", defaultConfig.stateStreamConf.MaxGlobalStreams, "global maximum number of concurrent streams") - flags.UintVar(&builder.stateStreamConf.MaxExecutionDataMsgSize, "state-stream-max-message-size", defaultConfig.stateStreamConf.MaxExecutionDataMsgSize, "maximum size for a gRPC message containing block execution data") - flags.StringToIntVar(&builder.stateStreamFilterConf, "state-stream-event-filter-limits", defaultConfig.stateStreamFilterConf, "event filter limits for ExecutionData SubscribeEvents API e.g. EventTypes=100,Addresses=100,Contracts=100 etc.") - flags.DurationVar(&builder.stateStreamConf.ClientSendTimeout, "state-stream-send-timeout", defaultConfig.stateStreamConf.ClientSendTimeout, "maximum wait before timing out while sending a response to a streaming client e.g. 30s") - flags.UintVar(&builder.stateStreamConf.ClientSendBufferSize, "state-stream-send-buffer-size", defaultConfig.stateStreamConf.ClientSendBufferSize, "maximum number of responses to buffer within a stream") - flags.Float64Var(&builder.stateStreamConf.ResponseLimit, "state-stream-response-limit", defaultConfig.stateStreamConf.ResponseLimit, "max number of responses per second to send over streaming endpoints. this helps manage resources consumed by each client querying data not in the cache e.g. 3 or 0.5. 0 means no limit") - flags.Uint64Var(&builder.stateStreamConf.HeartbeatInterval, "state-stream-heartbeat-interval", defaultConfig.stateStreamConf.HeartbeatInterval, "default interval in blocks at which heartbeat messages should be sent. applied when client did not specify a value.") - flags.Uint32Var(&builder.stateStreamConf.RegisterIDsRequestLimit, "state-stream-max-register-values", defaultConfig.stateStreamConf.RegisterIDsRequestLimit, "maximum number of register ids to include in a single request to the GetRegisters endpoint") + flags.UintVar(&builder.stateStreamConf.MaxExecutionDataMsgSize, + "state-stream-max-message-size", + defaultConfig.stateStreamConf.MaxExecutionDataMsgSize, + "maximum size for a gRPC message containing block execution data") + flags.StringToIntVar(&builder.stateStreamFilterConf, + "state-stream-event-filter-limits", + defaultConfig.stateStreamFilterConf, + "event filter limits for ExecutionData SubscribeEvents API e.g. EventTypes=100,Addresses=100,Contracts=100 etc.") + flags.DurationVar(&builder.stateStreamConf.ClientSendTimeout, + "state-stream-send-timeout", + defaultConfig.stateStreamConf.ClientSendTimeout, + "maximum wait before timing out while sending a response to a streaming client e.g. 30s") + flags.UintVar(&builder.stateStreamConf.ClientSendBufferSize, + "state-stream-send-buffer-size", + defaultConfig.stateStreamConf.ClientSendBufferSize, + "maximum number of responses to buffer within a stream") + flags.Float64Var(&builder.stateStreamConf.ResponseLimit, + "state-stream-response-limit", + defaultConfig.stateStreamConf.ResponseLimit, + "max number of responses per second to send over streaming endpoints. this helps manage resources consumed by each client querying data not in the cache e.g. 3 or 0.5. 0 means no limit") + flags.Uint64Var(&builder.stateStreamConf.HeartbeatInterval, + "state-stream-heartbeat-interval", + defaultConfig.stateStreamConf.HeartbeatInterval, + "default interval in blocks at which heartbeat messages should be sent. applied when client did not specify a value.") + flags.Uint32Var(&builder.stateStreamConf.RegisterIDsRequestLimit, + "state-stream-max-register-values", + defaultConfig.stateStreamConf.RegisterIDsRequestLimit, + "maximum number of register ids to include in a single request to the GetRegisters endpoint") // Execution Data Indexer - flags.BoolVar(&builder.executionDataIndexingEnabled, "execution-data-indexing-enabled", defaultConfig.executionDataIndexingEnabled, "whether to enable the execution data indexing") + flags.BoolVar(&builder.executionDataIndexingEnabled, + "execution-data-indexing-enabled", + defaultConfig.executionDataIndexingEnabled, + "whether to enable the execution data indexing") flags.StringVar(&builder.registersDBPath, "execution-state-dir", defaultConfig.registersDBPath, "directory to use for execution-state database") flags.StringVar(&builder.checkpointFile, "execution-state-checkpoint", defaultConfig.checkpointFile, "execution-state checkpoint file") + flags.StringVar(&builder.rpcConf.BackendConfig.EventQueryMode, + "event-query-mode", + defaultConfig.rpcConf.BackendConfig.EventQueryMode, + "mode to use when querying events. one of [local-only, execution-nodes-only(default), failover]") + // Script Execution - flags.StringVar(&builder.rpcConf.BackendConfig.ScriptExecutionMode, "script-execution-mode", defaultConfig.rpcConf.BackendConfig.ScriptExecutionMode, "mode to use when executing scripts. one of (local-only, execution-nodes-only, failover, compare)") - flags.Uint64Var(&builder.scriptExecutorConfig.ComputationLimit, "script-execution-computation-limit", defaultConfig.scriptExecutorConfig.ComputationLimit, "maximum number of computation units a locally executed script can use. default: 100000") - flags.IntVar(&builder.scriptExecutorConfig.MaxErrorMessageSize, "script-execution-max-error-length", defaultConfig.scriptExecutorConfig.MaxErrorMessageSize, "maximum number characters to include in error message strings. additional characters are truncated. default: 1000") - flags.DurationVar(&builder.scriptExecutorConfig.LogTimeThreshold, "script-execution-log-time-threshold", defaultConfig.scriptExecutorConfig.LogTimeThreshold, "emit a log for any scripts that take over this threshold. default: 1s") - flags.DurationVar(&builder.scriptExecutorConfig.ExecutionTimeLimit, "script-execution-timeout", defaultConfig.scriptExecutorConfig.ExecutionTimeLimit, "timeout value for locally executed scripts. default: 10s") + flags.StringVar(&builder.rpcConf.BackendConfig.ScriptExecutionMode, + "script-execution-mode", + defaultConfig.rpcConf.BackendConfig.ScriptExecutionMode, + "mode to use when executing scripts. one of (local-only, execution-nodes-only, failover, compare)") + flags.Uint64Var(&builder.scriptExecutorConfig.ComputationLimit, + "script-execution-computation-limit", + defaultConfig.scriptExecutorConfig.ComputationLimit, + "maximum number of computation units a locally executed script can use. default: 100000") + flags.IntVar(&builder.scriptExecutorConfig.MaxErrorMessageSize, + "script-execution-max-error-length", + defaultConfig.scriptExecutorConfig.MaxErrorMessageSize, + "maximum number characters to include in error message strings. additional characters are truncated. default: 1000") + flags.DurationVar(&builder.scriptExecutorConfig.LogTimeThreshold, + "script-execution-log-time-threshold", + defaultConfig.scriptExecutorConfig.LogTimeThreshold, + "emit a log for any scripts that take over this threshold. default: 1s") + flags.DurationVar(&builder.scriptExecutorConfig.ExecutionTimeLimit, + "script-execution-timeout", + defaultConfig.scriptExecutorConfig.ExecutionTimeLimit, + "timeout value for locally executed scripts. default: 10s") + flags.Uint64Var(&builder.scriptExecMinBlock, + "script-execution-min-height", + defaultConfig.scriptExecMinBlock, + "lowest block height to allow for script execution. default: no limit") + flags.Uint64Var(&builder.scriptExecMaxBlock, + "script-execution-max-height", + defaultConfig.scriptExecMaxBlock, + "highest block height to allow for script execution. default: no limit") }).ValidateFlags(func() error { if builder.supportsObserver && (builder.PublicNetworkConfig.BindAddress == cmd.NotSet || builder.PublicNetworkConfig.BindAddress == "") { @@ -1061,7 +1233,7 @@ func (builder *FlowAccessNodeBuilder) InitIDProviders() { filter.And( filter.HasRole(flow.RoleConsensus), filter.Not(filter.HasNodeID(node.Me.NodeID())), - p2pnet.NotEjectedFilter, + underlay.NotEjectedFilter, ), builder.IdentityProvider, ) @@ -1123,6 +1295,9 @@ func (builder *FlowAccessNodeBuilder) Build() (cmd.Node, error) { builder.BuildExecutionSyncComponents() } + ingestionDependable := module.NewProxiedReadyDoneAware() + builder.IndexerDependencies.Add(ingestionDependable) + builder. BuildConsensusFollower(). Module("collection node client", func(node *cmd.NodeConfig) error { @@ -1259,13 +1434,17 @@ func (builder *FlowAccessNodeBuilder) Build() (cmd.Node, error) { return nil }). Module("backend script executor", func(node *cmd.NodeConfig) error { - builder.ScriptExecutor = backend.NewScriptExecutor() + builder.ScriptExecutor = backend.NewScriptExecutor(builder.Logger, builder.scriptExecMinBlock, builder.scriptExecMaxBlock) return nil }). Module("async register store", func(node *cmd.NodeConfig) error { builder.RegistersAsyncStore = execution.NewRegistersAsyncStore() return nil }). + Module("events storage", func(node *cmd.NodeConfig) error { + builder.Storage.Events = bstorage.NewEvents(node.Metrics.Cache, node.DB) + return nil + }). Component("RPC engine", func(node *cmd.NodeConfig) (module.ReadyDoneAware, error) { config := builder.rpcConf backendConfig := config.BackendConfig @@ -1298,10 +1477,18 @@ func (builder *FlowAccessNodeBuilder) Build() (cmd.Node, error) { ), } - scriptExecMode, err := backend.ParseScriptExecutionMode(config.BackendConfig.ScriptExecutionMode) + scriptExecMode, err := backend.ParseIndexQueryMode(config.BackendConfig.ScriptExecutionMode) + if err != nil { + return nil, fmt.Errorf("could not parse script execution mode: %w", err) + } + + eventQueryMode, err := backend.ParseIndexQueryMode(config.BackendConfig.EventQueryMode) if err != nil { return nil, fmt.Errorf("could not parse script execution mode: %w", err) } + if eventQueryMode == backend.IndexQueryModeCompare { + return nil, fmt.Errorf("event query mode 'compare' is not supported") + } nodeBackend, err := backend.New(backend.Params{ State: node.State, @@ -1309,6 +1496,7 @@ func (builder *FlowAccessNodeBuilder) Build() (cmd.Node, error) { HistoricalAccessNodes: builder.HistoricalAccessRPCs, Blocks: node.Storage.Blocks, Headers: node.Storage.Headers, + Events: node.Storage.Events, Collections: node.Storage.Collections, Transactions: node.Storage.Transactions, ExecutionReceipts: node.Storage.Receipts, @@ -1327,6 +1515,7 @@ func (builder *FlowAccessNodeBuilder) Build() (cmd.Node, error) { TxErrorMessagesCacheSize: builder.TxErrorMessagesCacheSize, ScriptExecutor: builder.ScriptExecutor, ScriptExecutionMode: scriptExecMode, + EventQueryMode: eventQueryMode, }) if err != nil { return nil, fmt.Errorf("could not initialize backend: %w", err) @@ -1399,6 +1588,7 @@ func (builder *FlowAccessNodeBuilder) Build() (cmd.Node, error) { if err != nil { return nil, err } + ingestionDependable.Init(builder.IngestEng) builder.RequestEng.WithHandle(builder.IngestEng.OnCollection) builder.FollowerDistributor.AddOnBlockFinalizedConsumer(builder.IngestEng.OnFinalizedBlock) @@ -1498,7 +1688,7 @@ func (builder *FlowAccessNodeBuilder) enqueuePublicNetworkInit() { return nil, fmt.Errorf("could not register networking receive cache metric: %w", err) } - net, err := p2pnet.NewNetwork(&p2pnet.NetworkConfig{ + net, err := underlay.NewNetwork(&underlay.NetworkConfig{ Logger: builder.Logger.With().Str("module", "public-network").Logger(), Libp2pNode: publicLibp2pNode, Codec: cborcodec.NewCodec(), @@ -1510,7 +1700,7 @@ func (builder *FlowAccessNodeBuilder) enqueuePublicNetworkInit() { ReceiveCache: receiveCache, ConduitFactory: conduit.NewDefaultConduitFactory(), SporkId: builder.SporkID, - UnicastMessageTimeout: p2pnet.DefaultUnicastTimeout, + UnicastMessageTimeout: underlay.DefaultUnicastTimeout, IdentityTranslator: builder.IDTranslator, AlspCfg: &alspmgr.MisbehaviorReportManagerConfig{ Logger: builder.Logger, @@ -1525,7 +1715,7 @@ func (builder *FlowAccessNodeBuilder) enqueuePublicNetworkInit() { SlashingViolationConsumerFactory: func(adapter network.ConduitAdapter) network.ViolationsConsumer { return slashing.NewSlashingViolationsConsumer(builder.Logger, builder.Metrics.Network, adapter) }, - }, p2pnet.WithMessageValidators(msgValidators...)) + }, underlay.WithMessageValidators(msgValidators...)) if err != nil { return nil, fmt.Errorf("could not initialize network: %w", err) } @@ -1557,39 +1747,24 @@ func (builder *FlowAccessNodeBuilder) enqueuePublicNetworkInit() { // Returns: // - The libp2p node instance for the public network. // - Any error encountered during initialization. Any error should be considered fatal. -func (builder *FlowAccessNodeBuilder) initPublicLibp2pNode(networkKey crypto.PrivateKey, bindAddress string, networkMetrics module.LibP2PMetrics) (p2p.LibP2PNode, error) { - connManager, err := connection.NewConnManager(builder.Logger, networkMetrics, &builder.FlowConfig.NetworkConfig.ConnectionManagerConfig) +func (builder *FlowAccessNodeBuilder) initPublicLibp2pNode(networkKey crypto.PrivateKey, bindAddress string, networkMetrics module.LibP2PMetrics) (p2p.LibP2PNode, + error) { + connManager, err := connection.NewConnManager(builder.Logger, networkMetrics, &builder.FlowConfig.NetworkConfig.ConnectionManager) if err != nil { return nil, fmt.Errorf("could not create connection manager: %w", err) } - meshTracerCfg := &tracer.GossipSubMeshTracerConfig{ - Logger: builder.Logger, - Metrics: networkMetrics, - IDProvider: builder.IdentityProvider, - LoggerInterval: builder.FlowConfig.NetworkConfig.GossipSubConfig.LocalMeshLogInterval, - RpcSentTrackerCacheSize: builder.FlowConfig.NetworkConfig.GossipSubConfig.RPCSentTrackerCacheSize, - RpcSentTrackerWorkerQueueCacheSize: builder.FlowConfig.NetworkConfig.GossipSubConfig.RPCSentTrackerQueueCacheSize, - RpcSentTrackerNumOfWorkers: builder.FlowConfig.NetworkConfig.GossipSubConfig.RpcSentTrackerNumOfWorkers, - HeroCacheMetricsFactory: builder.HeroCacheMetricsFactory(), - NetworkingType: network.PublicNetwork, - } - meshTracer := tracer.NewGossipSubMeshTracer(meshTracerCfg) - - libp2pNode, err := p2pbuilder.NewNodeBuilder(builder.Logger, - &p2pconfig.MetricsConfig{ - HeroCacheFactory: builder.HeroCacheMetricsFactory(), - Metrics: networkMetrics, - }, + libp2pNode, err := p2pbuilder.NewNodeBuilder(builder.Logger, &builder.FlowConfig.NetworkConfig.GossipSub, &p2pbuilderconfig.MetricsConfig{ + HeroCacheFactory: builder.HeroCacheMetricsFactory(), + Metrics: networkMetrics, + }, network.PublicNetwork, bindAddress, networkKey, builder.SporkID, builder.IdentityProvider, - builder.FlowConfig.NetworkConfig.GossipSubConfig.GossipSubScoringRegistryConfig, &builder.FlowConfig.NetworkConfig.ResourceManager, - &builder.FlowConfig.NetworkConfig.GossipSubConfig, - &p2pconfig.PeerManagerConfig{ + &p2pbuilderconfig.PeerManagerConfig{ // TODO: eventually, we need pruning enabled even on public network. However, it needs a modified version of // the peer manager that also operate on the public identities. ConnectionPruning: connection.PruningDisabled, @@ -1600,9 +1775,8 @@ func (builder *FlowAccessNodeBuilder) initPublicLibp2pNode(networkKey crypto.Pri MaxSize: builder.FlowConfig.NetworkConfig.DisallowListNotificationCacheSize, Metrics: metrics.DisallowListCacheMetricsFactory(builder.HeroCacheMetricsFactory(), network.PublicNetwork), }, - meshTracer, - &p2pconfig.UnicastConfig{ - UnicastConfig: builder.FlowConfig.NetworkConfig.UnicastConfig, + &p2pbuilderconfig.UnicastConfig{ + Unicast: builder.FlowConfig.NetworkConfig.Unicast, }). SetBasicResolver(builder.Resolver). SetSubscriptionFilter(subscription.NewRoleBasedFilter(flow.RoleAccess, builder.IdentityProvider)). @@ -1610,9 +1784,6 @@ func (builder *FlowAccessNodeBuilder) initPublicLibp2pNode(networkKey crypto.Pri SetRoutingSystem(func(ctx context.Context, h host.Host) (routing.Routing, error) { return dht.NewDHT(ctx, h, protocols.FlowPublicDHTProtocolID(builder.SporkID), builder.Logger, networkMetrics, dht.AsServer()) }). - // disable connection pruning for the access node which supports the observer - SetGossipSubTracer(meshTracer). - SetGossipSubScoreTracerInterval(builder.FlowConfig.NetworkConfig.GossipSubConfig.ScoreTracerInterval). Build() if err != nil { diff --git a/cmd/bootstrap/cmd/access_keygen.go b/cmd/bootstrap/cmd/access_keygen.go index ecd0c5d3945..4e11ee16afc 100644 --- a/cmd/bootstrap/cmd/access_keygen.go +++ b/cmd/bootstrap/cmd/access_keygen.go @@ -13,9 +13,9 @@ import ( "strings" "time" + "github.com/onflow/crypto" "github.com/spf13/cobra" - "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/model/bootstrap" "github.com/onflow/flow-go/utils/grpcutils" ) diff --git a/cmd/bootstrap/cmd/dkg.go b/cmd/bootstrap/cmd/dkg.go index f87cbde2492..42d5d84d838 100644 --- a/cmd/bootstrap/cmd/dkg.go +++ b/cmd/bootstrap/cmd/dkg.go @@ -3,8 +3,9 @@ package cmd import ( "fmt" + "github.com/onflow/crypto" + bootstrapDKG "github.com/onflow/flow-go/cmd/bootstrap/dkg" - "github.com/onflow/flow-go/crypto" model "github.com/onflow/flow-go/model/bootstrap" "github.com/onflow/flow-go/model/dkg" "github.com/onflow/flow-go/model/encodable" diff --git a/cmd/bootstrap/cmd/finalize.go b/cmd/bootstrap/cmd/finalize.go index 9cfee15c9c2..35ed1e23beb 100644 --- a/cmd/bootstrap/cmd/finalize.go +++ b/cmd/bootstrap/cmd/finalize.go @@ -20,7 +20,6 @@ import ( "github.com/onflow/flow-go/model/dkg" "github.com/onflow/flow-go/model/encodable" "github.com/onflow/flow-go/model/flow" - "github.com/onflow/flow-go/model/flow/order" "github.com/onflow/flow-go/module/epochs" "github.com/onflow/flow-go/state/protocol" "github.com/onflow/flow-go/state/protocol/badger" @@ -156,7 +155,7 @@ func finalize(cmd *cobra.Command, args []string) { log.Info().Msg("") // create flow.IdentityList representation of participant set - participants := model.ToIdentityList(stakingNodes).Sort(order.Canonical) + participants := model.ToIdentityList(stakingNodes).Sort(flow.Canonical) log.Info().Msg("reading root block data") block := readRootBlock() @@ -492,7 +491,7 @@ func mergeNodeInfos(internalNodes, partnerNodes []model.NodeInfo) []model.NodeIn } // sort nodes using the canonical ordering - nodes = model.Sort(nodes, order.Canonical) + nodes = model.Sort(nodes, flow.Canonical) return nodes } diff --git a/cmd/bootstrap/cmd/key.go b/cmd/bootstrap/cmd/key.go index 3790f167d56..d8cdc46afa1 100644 --- a/cmd/bootstrap/cmd/key.go +++ b/cmd/bootstrap/cmd/key.go @@ -5,17 +5,15 @@ import ( "net" "strconv" - "github.com/onflow/flow-go/cmd" - "github.com/onflow/flow-go/cmd/bootstrap/utils" - p2putils "github.com/onflow/flow-go/network/p2p/utils" - "github.com/multiformats/go-multiaddr" + "github.com/onflow/crypto" "github.com/spf13/cobra" - "github.com/onflow/flow-go/crypto" - + "github.com/onflow/flow-go/cmd" + "github.com/onflow/flow-go/cmd/bootstrap/utils" model "github.com/onflow/flow-go/model/bootstrap" "github.com/onflow/flow-go/model/flow" + p2putils "github.com/onflow/flow-go/network/p2p/utils" ) var ( diff --git a/cmd/bootstrap/cmd/keys.go b/cmd/bootstrap/cmd/keys.go index 9624ade3a1a..ae4a98cfda0 100644 --- a/cmd/bootstrap/cmd/keys.go +++ b/cmd/bootstrap/cmd/keys.go @@ -3,12 +3,11 @@ package cmd import ( "fmt" - "github.com/onflow/flow-go/crypto/hash" + "github.com/onflow/crypto/hash" "github.com/onflow/flow-go/cmd/bootstrap/utils" - "github.com/onflow/flow-go/model/flow/order" - "github.com/onflow/flow-go/crypto" + "github.com/onflow/crypto" model "github.com/onflow/flow-go/model/bootstrap" "github.com/onflow/flow-go/model/encodable" @@ -49,7 +48,7 @@ func genNetworkAndStakingKeys() []model.NodeInfo { internalNodes = append(internalNodes, nodeInfo) } - return model.Sort(internalNodes, order.Canonical) + return model.Sort(internalNodes, flow.Canonical) } func assembleNodeInfo(nodeConfig model.NodeConfig, networkKey, stakingKey crypto.PrivateKey) model.NodeInfo { diff --git a/cmd/bootstrap/cmd/machine_account.go b/cmd/bootstrap/cmd/machine_account.go index 898ac7b96d9..a1305ae1035 100644 --- a/cmd/bootstrap/cmd/machine_account.go +++ b/cmd/bootstrap/cmd/machine_account.go @@ -5,10 +5,10 @@ import ( "path/filepath" "strings" + "github.com/onflow/crypto" "github.com/spf13/cobra" "github.com/onflow/flow-go/cmd" - "github.com/onflow/flow-go/crypto" model "github.com/onflow/flow-go/model/bootstrap" "github.com/onflow/flow-go/model/flow" ioutils "github.com/onflow/flow-go/utils/io" diff --git a/cmd/bootstrap/cmd/machine_account_key.go b/cmd/bootstrap/cmd/machine_account_key.go index 09a03f0b193..9ec26c68520 100644 --- a/cmd/bootstrap/cmd/machine_account_key.go +++ b/cmd/bootstrap/cmd/machine_account_key.go @@ -4,12 +4,10 @@ import ( "fmt" "path" - "github.com/onflow/flow-go/crypto" - - "github.com/onflow/flow-go/cmd/bootstrap/utils" - + "github.com/onflow/crypto" "github.com/spf13/cobra" + "github.com/onflow/flow-go/cmd/bootstrap/utils" model "github.com/onflow/flow-go/model/bootstrap" ) diff --git a/cmd/bootstrap/cmd/observer_network_key.go b/cmd/bootstrap/cmd/observer_network_key.go index 2b0fea31e3d..330b2cad47e 100644 --- a/cmd/bootstrap/cmd/observer_network_key.go +++ b/cmd/bootstrap/cmd/observer_network_key.go @@ -5,10 +5,9 @@ import ( "fmt" "os" + "github.com/onflow/crypto" "github.com/spf13/cobra" - "github.com/onflow/flow-go/crypto" - "github.com/onflow/flow-go/cmd" "github.com/onflow/flow-go/cmd/bootstrap/utils" ) diff --git a/cmd/bootstrap/cmd/observer_network_key_test.go b/cmd/bootstrap/cmd/observer_network_key_test.go index 255979edba9..b0dd6c30a07 100644 --- a/cmd/bootstrap/cmd/observer_network_key_test.go +++ b/cmd/bootstrap/cmd/observer_network_key_test.go @@ -8,10 +8,10 @@ import ( "strings" "testing" + "github.com/onflow/crypto" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/utils/io" "github.com/onflow/flow-go/utils/unittest" ) diff --git a/cmd/bootstrap/cmd/partner_infos.go b/cmd/bootstrap/cmd/partner_infos.go index 4c9ded401c8..05db3192609 100644 --- a/cmd/bootstrap/cmd/partner_infos.go +++ b/cmd/bootstrap/cmd/partner_infos.go @@ -7,12 +7,12 @@ import ( "strings" "github.com/onflow/cadence" + "github.com/onflow/crypto" "github.com/spf13/cobra" client "github.com/onflow/flow-go-sdk/access/grpc" "github.com/onflow/flow-go/cmd" "github.com/onflow/flow-go/cmd/util/cmd/common" - "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/model/bootstrap" "github.com/onflow/flow-go/model/encodable" "github.com/onflow/flow-go/model/flow" diff --git a/cmd/bootstrap/cmd/seal.go b/cmd/bootstrap/cmd/seal.go index 1a34c394e13..05f1ab293b3 100644 --- a/cmd/bootstrap/cmd/seal.go +++ b/cmd/bootstrap/cmd/seal.go @@ -6,7 +6,6 @@ import ( "github.com/onflow/flow-go/cmd/bootstrap/run" "github.com/onflow/flow-go/model/dkg" "github.com/onflow/flow-go/model/flow" - "github.com/onflow/flow-go/model/flow/order" "github.com/onflow/flow-go/module/signature" ) @@ -39,7 +38,7 @@ func constructRootResultAndSeal( DKGPhase1FinalView: firstView + flagNumViewsInStakingAuction + flagNumViewsInDKGPhase - 1, DKGPhase2FinalView: firstView + flagNumViewsInStakingAuction + flagNumViewsInDKGPhase*2 - 1, DKGPhase3FinalView: firstView + flagNumViewsInStakingAuction + flagNumViewsInDKGPhase*3 - 1, - Participants: participants.Sort(order.Canonical), + Participants: participants.Sort(flow.Canonical), Assignments: assignments, RandomSource: GenerateRandomSeed(flow.EpochSetupRandomSourceLength), } diff --git a/cmd/bootstrap/cmd/util.go b/cmd/bootstrap/cmd/util.go index 0a8cba39e54..38bdc481c8a 100644 --- a/cmd/bootstrap/cmd/util.go +++ b/cmd/bootstrap/cmd/util.go @@ -7,7 +7,8 @@ import ( "os" "path/filepath" - "github.com/onflow/flow-go/crypto" + "github.com/onflow/crypto" + model "github.com/onflow/flow-go/model/bootstrap" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/utils/io" diff --git a/cmd/bootstrap/dkg/dkg.go b/cmd/bootstrap/dkg/dkg.go index 3b65f44964a..8b740d85434 100644 --- a/cmd/bootstrap/dkg/dkg.go +++ b/cmd/bootstrap/dkg/dkg.go @@ -3,7 +3,8 @@ package dkg import ( "fmt" - "github.com/onflow/flow-go/crypto" + "github.com/onflow/crypto" + model "github.com/onflow/flow-go/model/dkg" "github.com/onflow/flow-go/module/signature" ) diff --git a/cmd/bootstrap/dkg/dkg_test.go b/cmd/bootstrap/dkg/dkg_test.go index fb92aad0ee0..326632dae80 100644 --- a/cmd/bootstrap/dkg/dkg_test.go +++ b/cmd/bootstrap/dkg/dkg_test.go @@ -3,9 +3,9 @@ package dkg import ( "testing" + "github.com/onflow/crypto" "github.com/stretchr/testify/require" - "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/utils/unittest" ) diff --git a/cmd/bootstrap/run/cluster_qc.go b/cmd/bootstrap/run/cluster_qc.go index fa91e5cc4f8..7d3e41ed8c8 100644 --- a/cmd/bootstrap/run/cluster_qc.go +++ b/cmd/bootstrap/run/cluster_qc.go @@ -14,7 +14,6 @@ import ( "github.com/onflow/flow-go/model/bootstrap" "github.com/onflow/flow-go/model/cluster" "github.com/onflow/flow-go/model/flow" - "github.com/onflow/flow-go/model/flow/order" "github.com/onflow/flow-go/module/local" ) @@ -29,7 +28,7 @@ func GenerateClusterRootQC(signers []bootstrap.NodeInfo, allCommitteeMembers flo } // STEP 2: create VoteProcessor - ordered := allCommitteeMembers.Sort(order.Canonical) + ordered := allCommitteeMembers.Sort(flow.Canonical) committee, err := committees.NewStaticCommittee(ordered, flow.Identifier{}, nil, nil) if err != nil { return nil, err diff --git a/cmd/bootstrap/run/execution_state_test.go b/cmd/bootstrap/run/execution_state_test.go index 2b6b3b0c237..569fcd8f695 100644 --- a/cmd/bootstrap/run/execution_state_test.go +++ b/cmd/bootstrap/run/execution_state_test.go @@ -5,10 +5,10 @@ import ( "path/filepath" "testing" + "github.com/onflow/crypto" + "github.com/onflow/crypto/hash" "github.com/stretchr/testify/require" - "github.com/onflow/flow-go/crypto" - "github.com/onflow/flow-go/crypto/hash" "github.com/onflow/flow-go/fvm" "github.com/onflow/flow-go/model/bootstrap" "github.com/onflow/flow-go/model/flow" diff --git a/cmd/bootstrap/run/qc.go b/cmd/bootstrap/run/qc.go index c07879eb446..57d1f17aa7a 100644 --- a/cmd/bootstrap/run/qc.go +++ b/cmd/bootstrap/run/qc.go @@ -3,6 +3,7 @@ package run import ( "fmt" + "github.com/onflow/crypto" "github.com/rs/zerolog" "github.com/onflow/flow-go/consensus/hotstuff" @@ -12,7 +13,6 @@ import ( "github.com/onflow/flow-go/consensus/hotstuff/validator" "github.com/onflow/flow-go/consensus/hotstuff/verification" "github.com/onflow/flow-go/consensus/hotstuff/votecollector" - "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/model/bootstrap" "github.com/onflow/flow-go/model/dkg" "github.com/onflow/flow-go/model/flow" diff --git a/cmd/bootstrap/run/qc_test.go b/cmd/bootstrap/run/qc_test.go index 4f925a5e793..cf5777dcf33 100644 --- a/cmd/bootstrap/run/qc_test.go +++ b/cmd/bootstrap/run/qc_test.go @@ -4,12 +4,11 @@ import ( "crypto/rand" "testing" + "github.com/onflow/crypto" "github.com/stretchr/testify/require" - "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/model/bootstrap" "github.com/onflow/flow-go/model/flow" - "github.com/onflow/flow-go/model/flow/order" "github.com/onflow/flow-go/module/signature" "github.com/onflow/flow-go/utils/unittest" ) @@ -45,7 +44,7 @@ func TestGenerateRootQCWithSomeInvalidVotes(t *testing.T) { } func createSignerData(t *testing.T, n int) *ParticipantData { - identities := unittest.IdentityListFixture(n).Sort(order.Canonical) + identities := unittest.IdentityListFixture(n).Sort(flow.Canonical) networkingKeys := unittest.NetworkingKeys(n) stakingKeys := unittest.StakingKeys(n) diff --git a/cmd/bootstrap/utils/key_generation.go b/cmd/bootstrap/utils/key_generation.go index 8a8164ef862..1857bf34eee 100644 --- a/cmd/bootstrap/utils/key_generation.go +++ b/cmd/bootstrap/utils/key_generation.go @@ -7,16 +7,14 @@ import ( gohash "hash" "io" - sdk "github.com/onflow/flow-go-sdk" - - "github.com/onflow/flow-go/model/encodable" - + "github.com/onflow/crypto" "golang.org/x/crypto/hkdf" + sdk "github.com/onflow/flow-go-sdk" sdkcrypto "github.com/onflow/flow-go-sdk/crypto" - "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/model/bootstrap" + "github.com/onflow/flow-go/model/encodable" "github.com/onflow/flow-go/model/flow" ) diff --git a/cmd/bootstrap/utils/key_generation_test.go b/cmd/bootstrap/utils/key_generation_test.go index 299e3c919f6..d4ea3e1e59f 100644 --- a/cmd/bootstrap/utils/key_generation_test.go +++ b/cmd/bootstrap/utils/key_generation_test.go @@ -4,11 +4,11 @@ import ( "fmt" "testing" + "github.com/onflow/crypto" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" sdkcrypto "github.com/onflow/flow-go-sdk/crypto" - "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/model/bootstrap" "github.com/onflow/flow-go/model/flow" diff --git a/cmd/consensus/main.go b/cmd/consensus/main.go index d54e460f4d1..401272ec338 100644 --- a/cmd/consensus/main.go +++ b/cmd/consensus/main.go @@ -89,7 +89,6 @@ func main() { requiredApprovalsForSealVerification uint requiredApprovalsForSealConstruction uint emergencySealing bool - dkgControllerConfig dkgmodule.ControllerConfig dkgMessagingEngineConfig = dkgeng.DefaultMessagingEngineConfig() cruiseCtlConfig = cruisectl.DefaultConfig() cruiseCtlTargetTransitionTimeFlag = cruiseCtlConfig.TargetTransition.String() @@ -161,9 +160,6 @@ func main() { flags.BoolVar(&emergencySealing, "emergency-sealing-active", flow.DefaultEmergencySealingActive, "(de)activation of emergency sealing") flags.BoolVar(&insecureAccessAPI, "insecure-access-api", false, "required if insecure GRPC connection should be used") flags.StringSliceVar(&accessNodeIDS, "access-node-ids", []string{}, fmt.Sprintf("array of access node IDs sorted in priority order where the first ID in this array will get the first connection attempt and each subsequent ID after serves as a fallback. Minimum length %d. Use '*' for all IDs in protocol state.", common.DefaultAccessNodeIDSMinimum)) - flags.DurationVar(&dkgControllerConfig.BaseStartDelay, "dkg-controller-base-start-delay", dkgmodule.DefaultBaseStartDelay, "used to define the range for jitter prior to DKG start (eg. 500µs) - the base value is scaled quadratically with the # of DKG participants") - flags.DurationVar(&dkgControllerConfig.BaseHandleFirstBroadcastDelay, "dkg-controller-base-handle-first-broadcast-delay", dkgmodule.DefaultBaseHandleFirstBroadcastDelay, "used to define the range for jitter prior to DKG handling the first broadcast messages (eg. 50ms) - the base value is scaled quadratically with the # of DKG participants") - flags.DurationVar(&dkgControllerConfig.HandleSubsequentBroadcastDelay, "dkg-controller-handle-subsequent-broadcast-delay", dkgmodule.DefaultHandleSubsequentBroadcastDelay, "used to define the constant delay introduced prior to DKG handling subsequent broadcast messages (eg. 2s)") flags.DurationVar(&dkgMessagingEngineConfig.RetryBaseWait, "dkg-messaging-engine-retry-base-wait", dkgMessagingEngineConfig.RetryBaseWait, "the inter-attempt wait time for the first attempt (base of exponential retry)") flags.Uint64Var(&dkgMessagingEngineConfig.RetryMax, "dkg-messaging-engine-retry-max", dkgMessagingEngineConfig.RetryMax, "the maximum number of retry attempts for an outbound DKG message") flags.Uint64Var(&dkgMessagingEngineConfig.RetryJitterPercent, "dkg-messaging-engine-retry-jitter-percent", dkgMessagingEngineConfig.RetryJitterPercent, "the percentage of jitter to apply to each inter-attempt wait time") @@ -915,7 +911,6 @@ func main() { node.Me, dkgContractClients, dkgBrokerTunnel, - dkgControllerConfig, ), viewsObserver, ) diff --git a/cmd/dynamic_startup.go b/cmd/dynamic_startup.go index a2c38f5bcc5..49ccd3dcb7a 100644 --- a/cmd/dynamic_startup.go +++ b/cmd/dynamic_startup.go @@ -10,12 +10,12 @@ import ( "strings" "time" + "github.com/onflow/crypto" "github.com/rs/zerolog" "github.com/sethvargo/go-retry" client "github.com/onflow/flow-go-sdk/access/grpc" "github.com/onflow/flow-go/cmd/util/cmd/common" - "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/model/bootstrap" "github.com/onflow/flow-go/state/protocol" badgerstate "github.com/onflow/flow-go/state/protocol/badger" diff --git a/cmd/execution_builder.go b/cmd/execution_builder.go index d487455ed82..847a38d45e1 100644 --- a/cmd/execution_builder.go +++ b/cmd/execution_builder.go @@ -801,14 +801,24 @@ func (exeNode *ExecutionNode) LoadRegisterStore( if !bootstrapped { checkpointFile := path.Join(exeNode.exeConf.triedir, modelbootstrap.FilenameWALRootCheckpoint) - root, err := exeNode.builder.RootSnapshot.Head() + sealedRoot, err := node.State.Params().SealedRoot() if err != nil { - return fmt.Errorf("could not get root snapshot head: %w", err) + return fmt.Errorf("could not get sealed root: %w", err) } - checkpointHeight := root.Height + rootSeal, err := node.State.Params().Seal() + if err != nil { + return fmt.Errorf("could not get root seal: %w", err) + } + + if sealedRoot.ID() != rootSeal.BlockID { + return fmt.Errorf("mismatching root seal and sealed root: %v != %v", sealedRoot.ID(), rootSeal.BlockID) + } - err = bootstrap.ImportRegistersFromCheckpoint(node.Logger, checkpointFile, checkpointHeight, pebbledb, exeNode.exeConf.importCheckpointWorkerCount) + checkpointHeight := sealedRoot.Height + rootHash := ledgerpkg.RootHash(rootSeal.FinalState) + + err = bootstrap.ImportRegistersFromCheckpoint(node.Logger, checkpointFile, checkpointHeight, rootHash, pebbledb, exeNode.exeConf.importCheckpointWorkerCount) if err != nil { return fmt.Errorf("could not import registers from checkpoint: %w", err) } @@ -820,12 +830,17 @@ func (exeNode *ExecutionNode) LoadRegisterStore( reader := finalizedreader.NewFinalizedReader(node.Storage.Headers, node.LastFinalizedHeader.Height) node.ProtocolEvents.AddConsumer(reader) + notifier := storehouse.NewRegisterStoreMetrics(exeNode.collector) + + // report latest finalized and executed height as metrics + notifier.OnFinalizedAndExecutedHeightUpdated(diskStore.LatestHeight()) registerStore, err := storehouse.NewRegisterStore( diskStore, nil, // TODO: replace with real WAL reader, node.Logger, + notifier, ) if err != nil { return err @@ -926,12 +941,19 @@ func (exeNode *ExecutionNode) LoadCheckerEngine( module.ReadyDoneAware, error, ) { - exeNode.checkerEng = checker.New( + if !exeNode.exeConf.enableChecker { + node.Logger.Warn().Msgf("checker engine is disabled") + return &module.NoopReadyDoneAware{}, nil + } + + node.Logger.Info().Msgf("checker engine is enabled") + + core := checker.NewCore( node.Logger, node.State, exeNode.executionState, - node.Storage.Seals, ) + exeNode.checkerEng = checker.NewEngine(core) return exeNode.checkerEng, nil } @@ -958,13 +980,17 @@ func (exeNode *ExecutionNode) LoadIngestionEngine( } fetcher := fetcher.NewCollectionFetcher(node.Logger, exeNode.collectionRequester, node.State, exeNode.exeConf.onflowOnlyLNs) - loader := loader.NewUnexecutedLoader(node.Logger, node.State, node.Storage.Headers, exeNode.executionState) + var blockLoader ingestion.BlockLoader + if exeNode.exeConf.enableStorehouse { + blockLoader = loader.NewUnfinalizedLoader(node.Logger, node.State, node.Storage.Headers, exeNode.executionState) + } else { + blockLoader = loader.NewUnexecutedLoader(node.Logger, node.State, node.Storage.Headers, exeNode.executionState) + } exeNode.ingestionEng, err = ingestion.New( exeNode.ingestionUnit, node.Logger, node.EngineRegistry, - node.Me, fetcher, node.Storage.Headers, node.Storage.Blocks, @@ -978,7 +1004,7 @@ func (exeNode *ExecutionNode) LoadIngestionEngine( exeNode.executionDataPruner, exeNode.blockDataUploader, exeNode.stopControl, - loader, + blockLoader, ) // TODO: we should solve these mutual dependencies better @@ -1036,8 +1062,6 @@ func (exeNode *ExecutionNode) LoadFollowerCore( return nil, fmt.Errorf("could not find latest finalized block and pending blocks to recover consensus follower: %w", err) } - exeNode.followerDistributor.AddFinalizationConsumer(exeNode.checkerEng) - // creates a consensus follower with ingestEngine as the notifier // so that it gets notified upon each new finalized block exeNode.followerCore, err = consensus.NewFollower( diff --git a/cmd/execution_config.go b/cmd/execution_config.go index 033bf2c4d87..3a16e5e6a3a 100644 --- a/cmd/execution_config.go +++ b/cmd/execution_config.go @@ -65,6 +65,7 @@ type ExecutionConfig struct { // file descriptors causing connection failures. onflowOnlyLNs bool enableStorehouse bool + enableChecker bool } func (exeConf *ExecutionConfig) SetupFlags(flags *pflag.FlagSet) { @@ -118,6 +119,7 @@ func (exeConf *ExecutionConfig) SetupFlags(flags *pflag.FlagSet) { flags.BoolVar(&exeConf.onflowOnlyLNs, "temp-onflow-only-lns", false, "do not use unless required. forces node to only request collections from onflow collection nodes") flags.BoolVar(&exeConf.enableStorehouse, "enable-storehouse", false, "enable storehouse to store registers on disk, default is false") + flags.BoolVar(&exeConf.enableChecker, "enable-checker", true, "enable checker to check the correctness of the execution result, default is true") } diff --git a/cmd/node_builder.go b/cmd/node_builder.go index 5b11df22a23..267ea791b99 100644 --- a/cmd/node_builder.go +++ b/cmd/node_builder.go @@ -6,13 +6,13 @@ import ( "github.com/dgraph-io/badger/v2" madns "github.com/multiformats/go-multiaddr-dns" + "github.com/onflow/crypto" "github.com/prometheus/client_golang/prometheus" "github.com/rs/zerolog" "github.com/spf13/pflag" "github.com/onflow/flow-go/admin/commands" "github.com/onflow/flow-go/config" - "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/fvm" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/module" diff --git a/cmd/observer/node_builder/observer_builder.go b/cmd/observer/node_builder/observer_builder.go index c54fc3cd88e..63ccb62ecc9 100644 --- a/cmd/observer/node_builder/observer_builder.go +++ b/cmd/observer/node_builder/observer_builder.go @@ -13,9 +13,9 @@ import ( "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/peer" "github.com/libp2p/go-libp2p/core/routing" + "github.com/onflow/crypto" "github.com/rs/zerolog" "github.com/spf13/pflag" - "google.golang.org/grpc/credentials" "github.com/onflow/flow-go/cmd" @@ -28,7 +28,6 @@ import ( hotstuffvalidator "github.com/onflow/flow-go/consensus/hotstuff/validator" "github.com/onflow/flow-go/consensus/hotstuff/verification" recovery "github.com/onflow/flow-go/consensus/recovery/protocol" - "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/engine/access/apiproxy" "github.com/onflow/flow-go/engine/access/rest" restapiproxy "github.com/onflow/flow-go/engine/access/rest/apiproxy" @@ -57,20 +56,19 @@ import ( "github.com/onflow/flow-go/network/channels" "github.com/onflow/flow-go/network/converter" "github.com/onflow/flow-go/network/p2p" + p2pbuilder "github.com/onflow/flow-go/network/p2p/builder" + p2pbuilderconfig "github.com/onflow/flow-go/network/p2p/builder/config" "github.com/onflow/flow-go/network/p2p/cache" "github.com/onflow/flow-go/network/p2p/conduit" p2pdht "github.com/onflow/flow-go/network/p2p/dht" "github.com/onflow/flow-go/network/p2p/keyutils" - "github.com/onflow/flow-go/network/p2p/p2pbuilder" - p2pconfig "github.com/onflow/flow-go/network/p2p/p2pbuilder/config" - "github.com/onflow/flow-go/network/p2p/p2plogging" - "github.com/onflow/flow-go/network/p2p/p2pnet" + p2plogging "github.com/onflow/flow-go/network/p2p/logging" "github.com/onflow/flow-go/network/p2p/subscription" - "github.com/onflow/flow-go/network/p2p/tracer" "github.com/onflow/flow-go/network/p2p/translator" "github.com/onflow/flow-go/network/p2p/unicast/protocols" "github.com/onflow/flow-go/network/p2p/utils" "github.com/onflow/flow-go/network/slashing" + "github.com/onflow/flow-go/network/underlay" "github.com/onflow/flow-go/network/validator" stateprotocol "github.com/onflow/flow-go/state/protocol" badgerState "github.com/onflow/flow-go/state/protocol/badger" @@ -470,24 +468,70 @@ func (builder *ObserverServiceBuilder) extraFlags() { builder.ExtraFlags(func(flags *pflag.FlagSet) { defaultConfig := DefaultObserverServiceConfig() - flags.StringVarP(&builder.rpcConf.UnsecureGRPCListenAddr, "rpc-addr", "r", defaultConfig.rpcConf.UnsecureGRPCListenAddr, "the address the unsecured gRPC server listens on") - flags.StringVar(&builder.rpcConf.SecureGRPCListenAddr, "secure-rpc-addr", defaultConfig.rpcConf.SecureGRPCListenAddr, "the address the secure gRPC server listens on") + flags.StringVarP(&builder.rpcConf.UnsecureGRPCListenAddr, + "rpc-addr", + "r", + defaultConfig.rpcConf.UnsecureGRPCListenAddr, + "the address the unsecured gRPC server listens on") + flags.StringVar(&builder.rpcConf.SecureGRPCListenAddr, + "secure-rpc-addr", + defaultConfig.rpcConf.SecureGRPCListenAddr, + "the address the secure gRPC server listens on") flags.StringVarP(&builder.rpcConf.HTTPListenAddr, "http-addr", "h", defaultConfig.rpcConf.HTTPListenAddr, "the address the http proxy server listens on") - flags.StringVar(&builder.rpcConf.RestConfig.ListenAddress, "rest-addr", defaultConfig.rpcConf.RestConfig.ListenAddress, "the address the REST server listens on (if empty the REST server will not be started)") - flags.DurationVar(&builder.rpcConf.RestConfig.WriteTimeout, "rest-write-timeout", defaultConfig.rpcConf.RestConfig.WriteTimeout, "timeout to use when writing REST response") - flags.DurationVar(&builder.rpcConf.RestConfig.ReadTimeout, "rest-read-timeout", defaultConfig.rpcConf.RestConfig.ReadTimeout, "timeout to use when reading REST request headers") + flags.StringVar(&builder.rpcConf.RestConfig.ListenAddress, + "rest-addr", + defaultConfig.rpcConf.RestConfig.ListenAddress, + "the address the REST server listens on (if empty the REST server will not be started)") + flags.DurationVar(&builder.rpcConf.RestConfig.WriteTimeout, + "rest-write-timeout", + defaultConfig.rpcConf.RestConfig.WriteTimeout, + "timeout to use when writing REST response") + flags.DurationVar(&builder.rpcConf.RestConfig.ReadTimeout, + "rest-read-timeout", + defaultConfig.rpcConf.RestConfig.ReadTimeout, + "timeout to use when reading REST request headers") flags.DurationVar(&builder.rpcConf.RestConfig.IdleTimeout, "rest-idle-timeout", defaultConfig.rpcConf.RestConfig.IdleTimeout, "idle timeout for REST connections") - flags.UintVar(&builder.rpcConf.MaxMsgSize, "rpc-max-message-size", defaultConfig.rpcConf.MaxMsgSize, "the maximum message size in bytes for messages sent or received over grpc") - flags.UintVar(&builder.rpcConf.BackendConfig.ConnectionPoolSize, "connection-pool-size", defaultConfig.rpcConf.BackendConfig.ConnectionPoolSize, "maximum number of connections allowed in the connection pool, size of 0 disables the connection pooling, and anything less than the default size will be overridden to use the default size") - flags.UintVar(&builder.rpcConf.BackendConfig.MaxHeightRange, "rpc-max-height-range", defaultConfig.rpcConf.BackendConfig.MaxHeightRange, "maximum size for height range requests") - flags.StringToIntVar(&builder.apiRatelimits, "api-rate-limits", defaultConfig.apiRatelimits, "per second rate limits for Access API methods e.g. Ping=300,GetTransaction=500 etc.") - flags.StringToIntVar(&builder.apiBurstlimits, "api-burst-limits", defaultConfig.apiBurstlimits, "burst limits for Access API methods e.g. Ping=100,GetTransaction=100 etc.") - flags.StringVar(&builder.observerNetworkingKeyPath, "observer-networking-key-path", defaultConfig.observerNetworkingKeyPath, "path to the networking key for observer") - flags.StringSliceVar(&builder.bootstrapNodeAddresses, "bootstrap-node-addresses", defaultConfig.bootstrapNodeAddresses, "the network addresses of the bootstrap access node if this is an observer e.g. access-001.mainnet.flow.org:9653,access-002.mainnet.flow.org:9653") - flags.StringSliceVar(&builder.bootstrapNodePublicKeys, "bootstrap-node-public-keys", defaultConfig.bootstrapNodePublicKeys, "the networking public key of the bootstrap access node if this is an observer (in the same order as the bootstrap node addresses) e.g. \"d57a5e9c5.....\",\"44ded42d....\"") + flags.UintVar(&builder.rpcConf.MaxMsgSize, + "rpc-max-message-size", + defaultConfig.rpcConf.MaxMsgSize, + "the maximum message size in bytes for messages sent or received over grpc") + flags.UintVar(&builder.rpcConf.BackendConfig.ConnectionPoolSize, + "connection-pool-size", + defaultConfig.rpcConf.BackendConfig.ConnectionPoolSize, + "maximum number of connections allowed in the connection pool, size of 0 disables the connection pooling, and anything less than the default size will be overridden to use the default size") + flags.UintVar(&builder.rpcConf.BackendConfig.MaxHeightRange, + "rpc-max-height-range", + defaultConfig.rpcConf.BackendConfig.MaxHeightRange, + "maximum size for height range requests") + flags.StringToIntVar(&builder.apiRatelimits, + "api-rate-limits", + defaultConfig.apiRatelimits, + "per second rate limits for Access API methods e.g. Ping=300,GetTransaction=500 etc.") + flags.StringToIntVar(&builder.apiBurstlimits, + "api-burst-limits", + defaultConfig.apiBurstlimits, + "burst limits for Access API methods e.g. Ping=100,GetTransaction=100 etc.") + flags.StringVar(&builder.observerNetworkingKeyPath, + "observer-networking-key-path", + defaultConfig.observerNetworkingKeyPath, + "path to the networking key for observer") + flags.StringSliceVar(&builder.bootstrapNodeAddresses, + "bootstrap-node-addresses", + defaultConfig.bootstrapNodeAddresses, + "the network addresses of the bootstrap access node if this is an observer e.g. access-001.mainnet.flow.org:9653,access-002.mainnet.flow.org:9653") + flags.StringSliceVar(&builder.bootstrapNodePublicKeys, + "bootstrap-node-public-keys", + defaultConfig.bootstrapNodePublicKeys, + "the networking public key of the bootstrap access node if this is an observer (in the same order as the bootstrap node addresses) e.g. \"d57a5e9c5.....\",\"44ded42d....\"") flags.DurationVar(&builder.apiTimeout, "upstream-api-timeout", defaultConfig.apiTimeout, "tcp timeout for Flow API gRPC sockets to upstrem nodes") - flags.StringSliceVar(&builder.upstreamNodeAddresses, "upstream-node-addresses", defaultConfig.upstreamNodeAddresses, "the gRPC network addresses of the upstream access node. e.g. access-001.mainnet.flow.org:9000,access-002.mainnet.flow.org:9000") - flags.StringSliceVar(&builder.upstreamNodePublicKeys, "upstream-node-public-keys", defaultConfig.upstreamNodePublicKeys, "the networking public key of the upstream access node (in the same order as the upstream node addresses) e.g. \"d57a5e9c5.....\",\"44ded42d....\"") + flags.StringSliceVar(&builder.upstreamNodeAddresses, + "upstream-node-addresses", + defaultConfig.upstreamNodeAddresses, + "the gRPC network addresses of the upstream access node. e.g. access-001.mainnet.flow.org:9000,access-002.mainnet.flow.org:9000") + flags.StringSliceVar(&builder.upstreamNodePublicKeys, + "upstream-node-public-keys", + defaultConfig.upstreamNodePublicKeys, + "the networking public key of the upstream access node (in the same order as the upstream node addresses) e.g. \"d57a5e9c5.....\",\"44ded42d....\"") flags.BoolVar(&builder.rpcMetricsEnabled, "rpc-metrics-enabled", defaultConfig.rpcMetricsEnabled, "whether to enable the rpc metrics") }) } @@ -697,21 +741,10 @@ func (builder *ObserverServiceBuilder) initPublicLibp2pNode(networkKey crypto.Pr pis = append(pis, pi) } - meshTracerCfg := &tracer.GossipSubMeshTracerConfig{ - Logger: builder.Logger, - Metrics: builder.Metrics.Network, - IDProvider: builder.IdentityProvider, - LoggerInterval: builder.FlowConfig.NetworkConfig.GossipSubConfig.LocalMeshLogInterval, - RpcSentTrackerCacheSize: builder.FlowConfig.NetworkConfig.GossipSubConfig.RPCSentTrackerCacheSize, - RpcSentTrackerWorkerQueueCacheSize: builder.FlowConfig.NetworkConfig.GossipSubConfig.RPCSentTrackerQueueCacheSize, - RpcSentTrackerNumOfWorkers: builder.FlowConfig.NetworkConfig.GossipSubConfig.RpcSentTrackerNumOfWorkers, - HeroCacheMetricsFactory: builder.HeroCacheMetricsFactory(), - NetworkingType: network.PublicNetwork, - } - meshTracer := tracer.NewGossipSubMeshTracer(meshTracerCfg) - - node, err := p2pbuilder.NewNodeBuilder(builder.Logger, - &p2pconfig.MetricsConfig{ + node, err := p2pbuilder.NewNodeBuilder( + builder.Logger, + &builder.FlowConfig.NetworkConfig.GossipSub, + &p2pbuilderconfig.MetricsConfig{ HeroCacheFactory: builder.HeroCacheMetricsFactory(), Metrics: builder.Metrics.Network, }, @@ -720,17 +753,14 @@ func (builder *ObserverServiceBuilder) initPublicLibp2pNode(networkKey crypto.Pr networkKey, builder.SporkID, builder.IdentityProvider, - builder.FlowConfig.NetworkConfig.GossipSubConfig.GossipSubScoringRegistryConfig, &builder.FlowConfig.NetworkConfig.ResourceManager, - &builder.FlowConfig.NetworkConfig.GossipSubConfig, - p2pconfig.PeerManagerDisableConfig(), // disable peer manager for observer node. + p2pbuilderconfig.PeerManagerDisableConfig(), // disable peer manager for observer node. &p2p.DisallowListCacheConfig{ MaxSize: builder.FlowConfig.NetworkConfig.DisallowListNotificationCacheSize, Metrics: metrics.DisallowListCacheMetricsFactory(builder.HeroCacheMetricsFactory(), network.PublicNetwork), }, - meshTracer, - &p2pconfig.UnicastConfig{ - UnicastConfig: builder.FlowConfig.NetworkConfig.UnicastConfig, + &p2pbuilderconfig.UnicastConfig{ + Unicast: builder.FlowConfig.NetworkConfig.Unicast, }). SetSubscriptionFilter( subscription.NewRoleBasedFilter( @@ -745,8 +775,6 @@ func (builder *ObserverServiceBuilder) initPublicLibp2pNode(networkKey crypto.Pr dht.BootstrapPeers(pis...), ) }). - SetGossipSubTracer(meshTracer). - SetGossipSubScoreTracerInterval(builder.FlowConfig.NetworkConfig.GossipSubConfig.ScoreTracerInterval). Build() if err != nil { @@ -811,7 +839,7 @@ func (builder *ObserverServiceBuilder) enqueuePublicNetworkInit() { return nil, fmt.Errorf("could not register networking receive cache metric: %w", err) } - net, err := p2pnet.NewNetwork(&p2pnet.NetworkConfig{ + net, err := underlay.NewNetwork(&underlay.NetworkConfig{ Logger: builder.Logger.With().Str("component", "public-network").Logger(), Codec: builder.CodecFactory(), Me: builder.Me, @@ -823,7 +851,7 @@ func (builder *ObserverServiceBuilder) enqueuePublicNetworkInit() { ReceiveCache: receiveCache, ConduitFactory: conduit.NewDefaultConduitFactory(), SporkId: builder.SporkID, - UnicastMessageTimeout: p2pnet.DefaultUnicastTimeout, + UnicastMessageTimeout: underlay.DefaultUnicastTimeout, IdentityTranslator: builder.IDTranslator, AlspCfg: &alspmgr.MisbehaviorReportManagerConfig{ Logger: builder.Logger, @@ -838,7 +866,7 @@ func (builder *ObserverServiceBuilder) enqueuePublicNetworkInit() { SlashingViolationConsumerFactory: func(adapter network.ConduitAdapter) network.ViolationsConsumer { return slashing.NewSlashingViolationsConsumer(builder.Logger, builder.Metrics.Network, adapter) }, - }, p2pnet.WithMessageValidators(publicNetworkMsgValidators(node.Logger, node.IdentityProvider, node.NodeID)...)) + }, underlay.WithMessageValidators(publicNetworkMsgValidators(node.Logger, node.IdentityProvider, node.NodeID)...)) if err != nil { return nil, fmt.Errorf("could not initialize network: %w", err) } diff --git a/cmd/scaffold.go b/cmd/scaffold.go index d517a8bfcf8..64ffcf20c94 100644 --- a/cmd/scaffold.go +++ b/cmd/scaffold.go @@ -47,19 +47,19 @@ import ( alspmgr "github.com/onflow/flow-go/network/alsp/manager" netcache "github.com/onflow/flow-go/network/cache" "github.com/onflow/flow-go/network/p2p" + p2pbuilder "github.com/onflow/flow-go/network/p2p/builder" + p2pbuilderconfig "github.com/onflow/flow-go/network/p2p/builder/config" "github.com/onflow/flow-go/network/p2p/cache" "github.com/onflow/flow-go/network/p2p/conduit" "github.com/onflow/flow-go/network/p2p/connection" "github.com/onflow/flow-go/network/p2p/dns" - "github.com/onflow/flow-go/network/p2p/p2pbuilder" - p2pconfig "github.com/onflow/flow-go/network/p2p/p2pbuilder/config" - "github.com/onflow/flow-go/network/p2p/p2pnet" "github.com/onflow/flow-go/network/p2p/ping" "github.com/onflow/flow-go/network/p2p/unicast/protocols" "github.com/onflow/flow-go/network/p2p/unicast/ratelimit" "github.com/onflow/flow-go/network/p2p/utils/ratelimiter" "github.com/onflow/flow-go/network/slashing" "github.com/onflow/flow-go/network/topology" + "github.com/onflow/flow-go/network/underlay" "github.com/onflow/flow-go/state/protocol" badgerState "github.com/onflow/flow-go/state/protocol/badger" "github.com/onflow/flow-go/state/protocol/events" @@ -309,21 +309,21 @@ func (fnb *FlowNodeBuilder) EnqueueNetworkInit() { // setup default rate limiter options unicastRateLimiterOpts := []ratelimit.RateLimitersOption{ - ratelimit.WithDisabledRateLimiting(fnb.BaseConfig.FlowConfig.NetworkConfig.UnicastConfig.UnicastRateLimitersConfig.DryRun), + ratelimit.WithDisabledRateLimiting(fnb.BaseConfig.FlowConfig.NetworkConfig.Unicast.RateLimiter.DryRun), ratelimit.WithNotifier(fnb.UnicastRateLimiterDistributor), } // override noop unicast message rate limiter - if fnb.BaseConfig.FlowConfig.NetworkConfig.UnicastConfig.UnicastRateLimitersConfig.MessageRateLimit > 0 { + if fnb.BaseConfig.FlowConfig.NetworkConfig.Unicast.RateLimiter.MessageRateLimit > 0 { unicastMessageRateLimiter := ratelimiter.NewRateLimiter( - rate.Limit(fnb.BaseConfig.FlowConfig.NetworkConfig.UnicastConfig.UnicastRateLimitersConfig.MessageRateLimit), - fnb.BaseConfig.FlowConfig.NetworkConfig.UnicastConfig.UnicastRateLimitersConfig.MessageRateLimit, - fnb.BaseConfig.FlowConfig.NetworkConfig.UnicastConfig.UnicastRateLimitersConfig.LockoutDuration, + rate.Limit(fnb.BaseConfig.FlowConfig.NetworkConfig.Unicast.RateLimiter.MessageRateLimit), + fnb.BaseConfig.FlowConfig.NetworkConfig.Unicast.RateLimiter.MessageRateLimit, + fnb.BaseConfig.FlowConfig.NetworkConfig.Unicast.RateLimiter.LockoutDuration, ) unicastRateLimiterOpts = append(unicastRateLimiterOpts, ratelimit.WithMessageRateLimiter(unicastMessageRateLimiter)) // avoid connection gating and pruning during dry run - if !fnb.BaseConfig.FlowConfig.NetworkConfig.UnicastConfig.UnicastRateLimitersConfig.DryRun { + if !fnb.BaseConfig.FlowConfig.NetworkConfig.Unicast.RateLimiter.DryRun { f := rateLimiterPeerFilter(unicastMessageRateLimiter) // add IsRateLimited peerFilters to conn gater intercept secure peer and peer manager filters list // don't allow rate limited peers to establishing incoming connections @@ -334,16 +334,16 @@ func (fnb *FlowNodeBuilder) EnqueueNetworkInit() { } // override noop unicast bandwidth rate limiter - if fnb.BaseConfig.FlowConfig.NetworkConfig.UnicastConfig.UnicastRateLimitersConfig.BandwidthRateLimit > 0 && fnb.BaseConfig.FlowConfig.NetworkConfig.UnicastConfig.UnicastRateLimitersConfig.BandwidthBurstLimit > 0 { + if fnb.BaseConfig.FlowConfig.NetworkConfig.Unicast.RateLimiter.BandwidthRateLimit > 0 && fnb.BaseConfig.FlowConfig.NetworkConfig.Unicast.RateLimiter.BandwidthBurstLimit > 0 { unicastBandwidthRateLimiter := ratelimit.NewBandWidthRateLimiter( - rate.Limit(fnb.BaseConfig.FlowConfig.NetworkConfig.UnicastConfig.UnicastRateLimitersConfig.BandwidthRateLimit), - fnb.BaseConfig.FlowConfig.NetworkConfig.UnicastConfig.UnicastRateLimitersConfig.BandwidthBurstLimit, - fnb.BaseConfig.FlowConfig.NetworkConfig.UnicastConfig.UnicastRateLimitersConfig.LockoutDuration, + rate.Limit(fnb.BaseConfig.FlowConfig.NetworkConfig.Unicast.RateLimiter.BandwidthRateLimit), + fnb.BaseConfig.FlowConfig.NetworkConfig.Unicast.RateLimiter.BandwidthBurstLimit, + fnb.BaseConfig.FlowConfig.NetworkConfig.Unicast.RateLimiter.LockoutDuration, ) unicastRateLimiterOpts = append(unicastRateLimiterOpts, ratelimit.WithBandwidthRateLimiter(unicastBandwidthRateLimiter)) // avoid connection gating and pruning during dry run - if !fnb.BaseConfig.FlowConfig.NetworkConfig.UnicastConfig.UnicastRateLimitersConfig.DryRun { + if !fnb.BaseConfig.FlowConfig.NetworkConfig.Unicast.RateLimiter.DryRun { f := rateLimiterPeerFilter(unicastBandwidthRateLimiter) // add IsRateLimited peerFilters to conn gater intercept secure peer and peer manager filters list connGaterInterceptSecureFilters = append(connGaterInterceptSecureFilters, f) @@ -354,17 +354,17 @@ func (fnb *FlowNodeBuilder) EnqueueNetworkInit() { // setup unicast rate limiters unicastRateLimiters := ratelimit.NewRateLimiters(unicastRateLimiterOpts...) - uniCfg := &p2pconfig.UnicastConfig{ - UnicastConfig: fnb.BaseConfig.FlowConfig.NetworkConfig.UnicastConfig, + uniCfg := &p2pbuilderconfig.UnicastConfig{ + Unicast: fnb.BaseConfig.FlowConfig.NetworkConfig.Unicast, RateLimiterDistributor: fnb.UnicastRateLimiterDistributor, } - connGaterCfg := &p2pconfig.ConnectionGaterConfig{ + connGaterCfg := &p2pbuilderconfig.ConnectionGaterConfig{ InterceptPeerDialFilters: connGaterPeerDialFilters, InterceptSecuredFilters: connGaterInterceptSecureFilters, } - peerManagerCfg := &p2pconfig.PeerManagerConfig{ + peerManagerCfg := &p2pbuilderconfig.PeerManagerConfig{ ConnectionPruning: fnb.FlowConfig.NetworkConfig.NetworkConnectionPruning, UpdateInterval: fnb.FlowConfig.NetworkConfig.PeerUpdateInterval, ConnectorFactory: connection.DefaultLibp2pBackoffConnectorFactory(), @@ -386,7 +386,7 @@ func (fnb *FlowNodeBuilder) EnqueueNetworkInit() { fnb.NetworkKey, fnb.SporkID, fnb.IdentityProvider, - &p2pconfig.MetricsConfig{ + &p2pbuilderconfig.MetricsConfig{ Metrics: fnb.Metrics.Network, HeroCacheFactory: fnb.HeroCacheMetricsFactory(), }, @@ -394,10 +394,10 @@ func (fnb *FlowNodeBuilder) EnqueueNetworkInit() { fnb.BaseConfig.NodeRole, connGaterCfg, peerManagerCfg, - &fnb.FlowConfig.NetworkConfig.GossipSubConfig, + &fnb.FlowConfig.NetworkConfig.GossipSub, &fnb.FlowConfig.NetworkConfig.ResourceManager, uniCfg, - &fnb.FlowConfig.NetworkConfig.ConnectionManagerConfig, + &fnb.FlowConfig.NetworkConfig.ConnectionManager, &p2p.DisallowListCacheConfig{ MaxSize: fnb.FlowConfig.NetworkConfig.DisallowListNotificationCacheSize, Metrics: metrics.DisallowListCacheMetricsFactory(fnb.HeroCacheMetricsFactory(), network.PrivateNetwork), @@ -452,22 +452,22 @@ func (fnb *FlowNodeBuilder) InitFlowNetworkWithConduitFactory( unicastRateLimiters *ratelimit.RateLimiters, peerManagerFilters []p2p.PeerFilter) (network.EngineRegistry, error) { - var networkOptions []p2pnet.NetworkOption + var networkOptions []underlay.NetworkOption if len(fnb.MsgValidators) > 0 { - networkOptions = append(networkOptions, p2pnet.WithMessageValidators(fnb.MsgValidators...)) + networkOptions = append(networkOptions, underlay.WithMessageValidators(fnb.MsgValidators...)) } // by default if no rate limiter configuration was provided in the CLI args the default // noop rate limiter will be used. - networkOptions = append(networkOptions, p2pnet.WithUnicastRateLimiters(unicastRateLimiters)) + networkOptions = append(networkOptions, underlay.WithUnicastRateLimiters(unicastRateLimiters)) networkOptions = append(networkOptions, - p2pnet.WithPreferredUnicastProtocols(protocols.ToProtocolNames(fnb.FlowConfig.NetworkConfig.PreferredUnicastProtocols)...), + underlay.WithPreferredUnicastProtocols(protocols.ToProtocolNames(fnb.FlowConfig.NetworkConfig.PreferredUnicastProtocols)...), ) // peerManagerFilters are used by the peerManager via the network to filter peers from the topology. if len(peerManagerFilters) > 0 { - networkOptions = append(networkOptions, p2pnet.WithPeerManagerFilters(peerManagerFilters...)) + networkOptions = append(networkOptions, underlay.WithPeerManagerFilters(peerManagerFilters...)) } receiveCache := netcache.NewHeroReceiveCache(fnb.FlowConfig.NetworkConfig.NetworkReceivedMessageCacheSize, @@ -480,7 +480,7 @@ func (fnb *FlowNodeBuilder) InitFlowNetworkWithConduitFactory( } // creates network instance - net, err := p2pnet.NewNetwork(&p2pnet.NetworkConfig{ + net, err := underlay.NewNetwork(&underlay.NetworkConfig{ Logger: fnb.Logger, Libp2pNode: fnb.LibP2PNode, Codec: fnb.CodecFactory(), @@ -492,7 +492,7 @@ func (fnb *FlowNodeBuilder) InitFlowNetworkWithConduitFactory( IdentityProvider: fnb.IdentityProvider, ReceiveCache: receiveCache, ConduitFactory: cf, - UnicastMessageTimeout: fnb.FlowConfig.NetworkConfig.UnicastMessageTimeout, + UnicastMessageTimeout: fnb.FlowConfig.NetworkConfig.Unicast.MessageTimeout, IdentityTranslator: fnb.IDTranslator, AlspCfg: &alspmgr.MisbehaviorReportManagerConfig{ Logger: fnb.Logger, @@ -1061,7 +1061,7 @@ func (fnb *FlowNodeBuilder) InitIDProviders() { filter.And( filter.HasRole(flow.RoleConsensus), filter.Not(filter.HasNodeID(node.Me.NodeID())), - p2pnet.NotEjectedFilter, + underlay.NotEjectedFilter, ), node.IdentityProvider, ) diff --git a/cmd/scaffold_test.go b/cmd/scaffold_test.go index 9941dc35f22..d7aee36c0c0 100644 --- a/cmd/scaffold_test.go +++ b/cmd/scaffold_test.go @@ -28,7 +28,7 @@ import ( "github.com/onflow/flow-go/module/component" "github.com/onflow/flow-go/module/irrecoverable" "github.com/onflow/flow-go/module/profiler" - "github.com/onflow/flow-go/network/p2p/p2pbuilder" + p2pbuilder "github.com/onflow/flow-go/network/p2p/builder" "github.com/onflow/flow-go/utils/unittest" ) diff --git a/cmd/testclient/main.go b/cmd/testclient/main.go index ce42309c78e..280bc73f261 100644 --- a/cmd/testclient/main.go +++ b/cmd/testclient/main.go @@ -80,7 +80,7 @@ func main() { prepare(signer: AuthAccount) { log(signer.address) } } `)). - SetGasLimit(100). + SetComputeLimit(100). SetProposalKey(addr, accountKey.Index, nonce). SetReferenceBlockID(latest.ID). SetPayer(addr). diff --git a/cmd/util/cmd/common/flow_client.go b/cmd/util/cmd/common/flow_client.go index e16438da9f6..4f7fe6a704e 100644 --- a/cmd/util/cmd/common/flow_client.go +++ b/cmd/util/cmd/common/flow_client.go @@ -11,7 +11,6 @@ import ( "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/model/flow/filter" - "github.com/onflow/flow-go/model/flow/order" "github.com/onflow/flow-go/state/protocol" "github.com/onflow/flow-go/utils/grpcutils" ) @@ -97,7 +96,7 @@ func FlowClientConfigs(accessNodeIDS []flow.Identifier, insecureAccessAPI bool, if err != nil { return nil, fmt.Errorf("failed get identities access node identities (ids=%v) from snapshot: %w", accessNodeIDS, err) } - identities = identities.Sort(order.ByReferenceOrder(accessNodeIDS)) + identities = identities.Sort(flow.ByReferenceOrder(accessNodeIDS)) // make sure we have identities for all the access node IDs provided if len(identities) != len(accessNodeIDS) { diff --git a/cmd/util/cmd/execution-state-extract/cmd.go b/cmd/util/cmd/execution-state-extract/cmd.go index 7c10e8dbdcb..55728b428a8 100644 --- a/cmd/util/cmd/execution-state-extract/cmd.go +++ b/cmd/util/cmd/execution-state-extract/cmd.go @@ -15,15 +15,17 @@ import ( ) var ( - flagExecutionStateDir string - flagOutputDir string - flagBlockHash string - flagStateCommitment string - flagDatadir string - flagChain string - flagNoMigration bool - flagNoReport bool - flagNWorker int + flagExecutionStateDir string + flagOutputDir string + flagBlockHash string + flagStateCommitment string + flagDatadir string + flagChain string + flagNWorker int + flagNoMigration bool + flagNoReport bool + flagValidateMigration bool + flagLogVerboseValidationError bool ) var Cmd = &cobra.Command{ @@ -59,6 +61,13 @@ func init() { "don't report the state") Cmd.Flags().IntVar(&flagNWorker, "n-migrate-worker", 10, "number of workers to migrate payload concurrently") + + Cmd.Flags().BoolVar(&flagValidateMigration, "validate", false, + "validate migrated Cadence values (atree migration)") + + Cmd.Flags().BoolVar(&flagLogVerboseValidationError, "log-verbose-validation-error", false, + "log entire Cadence values on validation error (atree migration)") + } func run(*cobra.Command, []string) { @@ -131,16 +140,21 @@ func run(*cobra.Command, []string) { log.Warn().Msgf("--no-report flag is deprecated") } - if flagNoMigration { - log.Warn().Msgf("--no-migration flag is deprecated") + if flagValidateMigration { + log.Warn().Msgf("atree migration validation flag is enabled and will increase duration of migration") + } + + if flagLogVerboseValidationError { + log.Warn().Msgf("atree migration has verbose validation error logging enabled which may increase size of log") } err := extractExecutionState( + log.Logger, flagExecutionStateDir, stateCommitment, flagOutputDir, - log.Logger, flagNWorker, + !flagNoMigration, ) if err != nil { diff --git a/cmd/util/cmd/execution-state-extract/execution_state_extract.go b/cmd/util/cmd/execution-state-extract/execution_state_extract.go index 1436f0ccc94..90bcd70533d 100644 --- a/cmd/util/cmd/execution-state-extract/execution_state_extract.go +++ b/cmd/util/cmd/execution-state-extract/execution_state_extract.go @@ -9,6 +9,7 @@ import ( "github.com/rs/zerolog" "go.uber.org/atomic" + migrators "github.com/onflow/flow-go/cmd/util/ledger/migrations" "github.com/onflow/flow-go/cmd/util/ledger/reporters" "github.com/onflow/flow-go/ledger" "github.com/onflow/flow-go/ledger/common/hash" @@ -27,11 +28,12 @@ func getStateCommitment(commits storage.Commits, blockHash flow.Identifier) (flo } func extractExecutionState( + log zerolog.Logger, dir string, targetHash flow.StateCommitment, outputDir string, - log zerolog.Logger, nWorker int, // number of concurrent worker to migation payloads + runMigrations bool, ) error { log.Info().Msg("init WAL") @@ -83,6 +85,30 @@ func extractExecutionState( }() var migrations []ledger.Migration + + if runMigrations { + rwf := reporters.NewReportFileWriterFactory(dir, log) + + migrations = []ledger.Migration{ + migrators.CreateAccountBasedMigration( + log, + nWorker, + []migrators.AccountBasedMigration{ + migrators.NewAtreeRegisterMigrator( + rwf, + flagValidateMigration, + flagLogVerboseValidationError, + ), + + &migrators.DeduplicateContractNamesMigration{}, + + // This will fix storage used discrepancies caused by the + // DeduplicateContractNamesMigration. + &migrators.AccountUsageMigrator{}, + }), + } + } + newState := ledger.State(targetHash) // migrate the trie if there are migrations diff --git a/cmd/util/cmd/execution-state-extract/execution_state_extract_test.go b/cmd/util/cmd/execution-state-extract/execution_state_extract_test.go index 018c5474c66..2f91ea7d603 100644 --- a/cmd/util/cmd/execution-state-extract/execution_state_extract_test.go +++ b/cmd/util/cmd/execution-state-extract/execution_state_extract_test.go @@ -60,11 +60,12 @@ func TestExtractExecutionState(t *testing.T) { t.Run("empty WAL doesn't find anything", func(t *testing.T) { withDirs(t, func(datadir, execdir, outdir string) { err := extractExecutionState( + zerolog.Nop(), execdir, unittest.StateCommitmentFixture(), outdir, - zerolog.Nop(), 10, + false, ) require.Error(t, err) }) diff --git a/cmd/util/cmd/read-badger/cmd/commits.go b/cmd/util/cmd/read-badger/cmd/commits.go index c16572045da..873bf4746a5 100644 --- a/cmd/util/cmd/read-badger/cmd/commits.go +++ b/cmd/util/cmd/read-badger/cmd/commits.go @@ -4,6 +4,7 @@ import ( "github.com/rs/zerolog/log" "github.com/spf13/cobra" + findBlockByCommits "github.com/onflow/flow-go/cmd/util/cmd/read-badger/cmd/find-block-by-commits" "github.com/onflow/flow-go/model/flow" ) @@ -12,6 +13,8 @@ func init() { commitsCmd.Flags().StringVarP(&flagBlockID, "block-id", "b", "", "the block id of which to query the state commitment") _ = commitsCmd.MarkFlagRequired("block-id") + + rootCmd.AddCommand(findBlockByCommits.Init(InitStorages)) } var commitsCmd = &cobra.Command{ diff --git a/cmd/util/cmd/read-badger/cmd/find-block-by-commits/main.go b/cmd/util/cmd/read-badger/cmd/find-block-by-commits/main.go new file mode 100644 index 00000000000..8eec5ddee0b --- /dev/null +++ b/cmd/util/cmd/read-badger/cmd/find-block-by-commits/main.go @@ -0,0 +1,138 @@ +package find + +import ( + "encoding/hex" + "fmt" + "strings" + + "github.com/dgraph-io/badger/v2" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" + "github.com/spf13/cobra" + + "github.com/onflow/flow-go/model/flow" + "github.com/onflow/flow-go/storage" +) + +var cmd = &cobra.Command{ + Use: "find-block-id-commit", + Short: "find block ID by commit", + Run: run, +} + +var flagStartHeight uint64 +var flagEndHeight uint64 +var flagStateCommitments string + +var loader func() (*storage.All, *badger.DB) = nil + +func Init(f func() (*storage.All, *badger.DB)) *cobra.Command { + loader = f + + cmd.Flags().Uint64Var(&flagStartHeight, "start-height", 0, "start height to block for commit") + _ = cmd.MarkFlagRequired("start-height") + + cmd.Flags().Uint64Var(&flagEndHeight, "end-height", 0, "end height to block for commit") + _ = cmd.MarkFlagRequired("end-height") + + cmd.Flags().StringVar(&flagStateCommitments, "state-commitments", "", + "Comma separated list of state commitments (each must be 64 chars, hex-encoded)") + _ = cmd.MarkFlagRequired("state-commitments") + + return cmd +} + +func FindBlockIDByCommits( + log zerolog.Logger, + headers storage.Headers, + commits storage.Commits, + stateCommitments []flow.StateCommitment, + startHeight uint64, + endHeight uint64, +) (flow.Identifier, error) { + commitMap := make(map[flow.StateCommitment]struct{}, len(stateCommitments)) + for _, commit := range stateCommitments { + commitMap[commit] = struct{}{} + } + + for height := startHeight; height <= endHeight; height++ { + log.Info().Msgf("finding for height %v for height range: [%v, %v]", height, startHeight, endHeight) + blockID, err := headers.BlockIDByHeight(height) + if err != nil { + return flow.ZeroID, fmt.Errorf("could not find block by height %v: %w", height, err) + } + + commit, err := commits.ByBlockID(blockID) + if err != nil { + return flow.ZeroID, fmt.Errorf("could not find commitment at height %v: %w", height, err) + } + + _, ok := commitMap[commit] + if ok { + log.Info().Msgf("successfully found block %v at height %v for commit %v", + blockID, height, commit) + return blockID, nil + } + } + + return flow.ZeroID, fmt.Errorf("could not find commit within height range [%v,%v]", startHeight, endHeight) +} + +func toStateCommitments(commitsStr string) ([]flow.StateCommitment, error) { + commitSlice := strings.Split(commitsStr, ",") + commits := make([]flow.StateCommitment, len(commitSlice)) + for _, c := range commitSlice { + commit, err := toStateCommitment(c) + if err != nil { + return nil, err + } + + commits = append(commits, commit) + } + return commits, nil + +} + +func toStateCommitment(commit string) (flow.StateCommitment, error) { + stateCommitmentBytes, err := hex.DecodeString(commit) + if err != nil { + return flow.DummyStateCommitment, fmt.Errorf("invalid commit string %v, cannot decode", commit) + } + + stateCommitment, err := flow.ToStateCommitment(stateCommitmentBytes) + if err != nil { + return flow.DummyStateCommitment, fmt.Errorf("invalid number of bytes, got %d expected %d, %v", len(stateCommitmentBytes), len(stateCommitment), commit) + } + return stateCommitment, nil +} + +func run(*cobra.Command, []string) { + log.Info().Msgf("looking up block in height range [%v, %v] for commits %v", + flagStartHeight, flagEndHeight, flagStateCommitments) + + stateCommitments, err := toStateCommitments(flagStateCommitments) + if err != nil { + log.Fatal().Err(err).Msgf("fail to convert commitment") + } + + storage, db := loader() + defer func() { + err := db.Close() + if err != nil { + log.Warn().Err(err).Msg("error closing db") + } + }() + + _, err = FindBlockIDByCommits( + log.Logger, + storage.Headers, + storage.Commits, + stateCommitments, + flagStartHeight, + flagEndHeight, + ) + + if err != nil { + log.Fatal().Err(err).Msgf("fail to find block id by commit") + } +} diff --git a/cmd/util/cmd/root.go b/cmd/util/cmd/root.go index d645a5f04fd..d23695404f5 100644 --- a/cmd/util/cmd/root.go +++ b/cmd/util/cmd/root.go @@ -11,6 +11,7 @@ import ( checkpoint_collect_stats "github.com/onflow/flow-go/cmd/util/cmd/checkpoint-collect-stats" checkpoint_list_tries "github.com/onflow/flow-go/cmd/util/cmd/checkpoint-list-tries" + checkpoint_trie_stats "github.com/onflow/flow-go/cmd/util/cmd/checkpoint-trie-stats" epochs "github.com/onflow/flow-go/cmd/util/cmd/epochs/cmd" export "github.com/onflow/flow-go/cmd/util/cmd/exec-data-json-export" edbs "github.com/onflow/flow-go/cmd/util/cmd/execution-data-blobstore/cmd" @@ -65,6 +66,7 @@ func addCommands() { rootCmd.AddCommand(extract.Cmd) rootCmd.AddCommand(export.Cmd) rootCmd.AddCommand(checkpoint_list_tries.Cmd) + rootCmd.AddCommand(checkpoint_trie_stats.Cmd) rootCmd.AddCommand(checkpoint_collect_stats.Cmd) rootCmd.AddCommand(truncate_database.Cmd) rootCmd.AddCommand(read_badger.RootCmd) diff --git a/cmd/util/ledger/migrations/account_based_migration.go b/cmd/util/ledger/migrations/account_based_migration.go index b7ca65a662c..8a78728b7b6 100644 --- a/cmd/util/ledger/migrations/account_based_migration.go +++ b/cmd/util/ledger/migrations/account_based_migration.go @@ -1,190 +1,384 @@ package migrations import ( + "container/heap" + "context" "fmt" + "io" + "sync" + "time" - "github.com/rs/zerolog/log" + "github.com/rs/zerolog" + "github.com/onflow/cadence/runtime/common" + + "github.com/onflow/flow-go/cmd/util/ledger/util" "github.com/onflow/flow-go/ledger" - "github.com/onflow/flow-go/ledger/common/convert" - "github.com/onflow/flow-go/model/flow" - "github.com/onflow/flow-go/module/util" + moduleUtil "github.com/onflow/flow-go/module/util" ) -// PayloadToAccount takes a payload and return: -// - (address, true, nil) if the payload is for an account, the account address is returned -// - ("", false, nil) if the payload is not for an account -// - ("", false, err) if running into any exception -func PayloadToAccount(p ledger.Payload) (string, bool, error) { - k, err := p.Key() - if err != nil { - return "", false, fmt.Errorf("could not find key for payload: %w", err) - } - id, err := convert.LedgerKeyToRegisterID(k) - if err != nil { - return "", false, fmt.Errorf("error converting key to register ID") - } - if len([]byte(id.Owner)) != flow.AddressLength { - return "", false, nil - } - return id.Owner, true, nil +// logTopNDurations is the number of longest migrations to log at the end of the migration +const logTopNDurations = 20 + +// AccountBasedMigration is an interface for migrations that migrate account by account +// concurrently getting all the payloads for each account at a time. +type AccountBasedMigration interface { + InitMigration( + log zerolog.Logger, + allPayloads []*ledger.Payload, + nWorkers int, + ) error + MigrateAccount( + ctx context.Context, + address common.Address, + payloads []*ledger.Payload, + ) ([]*ledger.Payload, error) + io.Closer } -// PayloadGroup groups payloads by account. -// For global payloads, it's stored under NonAccountPayloads field -type PayloadGroup struct { - NonAccountPayloads []ledger.Payload - Accounts map[string][]ledger.Payload +// CreateAccountBasedMigration creates a migration function that migrates the payloads +// account by account using the given migrations +// accounts are processed concurrently using the given number of workers +// but each account is processed sequentially by the given migrations in order. +// The migrations InitMigration function is called once before the migration starts +// And the Close function is called once after the migration finishes if the migration +// is a finisher. +func CreateAccountBasedMigration( + log zerolog.Logger, + nWorker int, + migrations []AccountBasedMigration, +) func(payloads []*ledger.Payload) ([]*ledger.Payload, error) { + return func(payloads []*ledger.Payload) ([]*ledger.Payload, error) { + return MigrateByAccount( + log, + nWorker, + payloads, + migrations, + ) + } } -// PayloadGrouping is a reducer function that adds the given payload to the corresponding -// group under its account -func PayloadGrouping(groups *PayloadGroup, payload ledger.Payload) (*PayloadGroup, error) { - address, isAccount, err := PayloadToAccount(payload) - if err != nil { - return nil, err +// MigrateByAccount takes migrations and all the Payloads, +// and returns the migrated Payloads. +func MigrateByAccount( + log zerolog.Logger, + nWorker int, + allPayloads []*ledger.Payload, + migrations []AccountBasedMigration, +) ( + []*ledger.Payload, + error, +) { + if len(allPayloads) == 0 { + return allPayloads, nil } - if isAccount { - groups.Accounts[address] = append(groups.Accounts[address], payload) - } else { - groups.NonAccountPayloads = append(groups.NonAccountPayloads, payload) + for i, migrator := range migrations { + if err := migrator.InitMigration( + log.With(). + Int("migration_index", i). + Logger(), + allPayloads, + nWorker, + ); err != nil { + return nil, fmt.Errorf("could not init migration: %w", err) + } } - return groups, nil -} + log.Info(). + Int("inner_migrations", len(migrations)). + Int("nWorker", nWorker). + Msgf("created account migrations") + + defer func() { + for i, migrator := range migrations { + log.Info(). + Int("migration_index", i). + Type("migration", migrator). + Msg("closing migration") + if err := migrator.Close(); err != nil { + log.Error().Err(err).Msg("error closing migration") + } + } + }() -// AccountMigrator takes all the payloads that belong to the given account -// and return the migrated payloads -type AccountMigrator interface { - MigratePayloads(account string, payloads []ledger.Payload) ([]ledger.Payload, error) -} + // group the Payloads by account + accountGroups := util.GroupPayloadsByAccount(log, allPayloads, nWorker) + + // migrate the Payloads under accounts + migrated, err := MigrateGroupConcurrently(log, migrations, accountGroups, nWorker) -// MigrateByAccount teaks a migrator function and all the payloads, and return the migrated payloads -func MigrateByAccount(migrator AccountMigrator, allPayloads []ledger.Payload, nWorker int) ( - []ledger.Payload, error) { - groups := &PayloadGroup{ - NonAccountPayloads: make([]ledger.Payload, 0), - Accounts: make(map[string][]ledger.Payload), + if err != nil { + return nil, fmt.Errorf("could not migrate accounts: %w", err) } - log.Info().Msgf("start grouping for a total of %v payloads", len(allPayloads)) + log.Info(). + Int("account_count", accountGroups.Len()). + Int("payload_count", len(allPayloads)). + Msgf("finished migrating Payloads") - var err error - logGrouping := util.LogProgress("grouping payload", len(allPayloads), log.Logger) - for i, payload := range allPayloads { - groups, err = PayloadGrouping(groups, payload) - if err != nil { - return nil, err - } - logGrouping(i) - } + return migrated, nil +} + +// MigrateGroupConcurrently migrate the Payloads in the given account groups. +// It uses nWorker to process the Payloads concurrently. The Payloads in each account +// are processed sequentially by the given migrations in order. +func MigrateGroupConcurrently( + log zerolog.Logger, + migrations []AccountBasedMigration, + accountGroups *util.PayloadAccountGrouping, + nWorker int, +) ([]*ledger.Payload, error) { - log.Info().Msgf("finish grouping for payloads by account: %v groups in total, %v NonAccountPayloads", - len(groups.Accounts), len(groups.NonAccountPayloads)) + ctx := context.Background() + ctx, cancel := context.WithCancelCause(ctx) + defer cancel(nil) - // migrate the payloads under accounts - migrated, err := MigrateGroupConcurrently(migrator, groups.Accounts, nWorker) + jobs := make(chan jobMigrateAccountGroup, accountGroups.Len()) - if err != nil { - return nil, fmt.Errorf("could not migrate group: %w", err) + wg := sync.WaitGroup{} + wg.Add(nWorker) + resultCh := make(chan *migrationResult, accountGroups.Len()) + for i := 0; i < nWorker; i++ { + go func() { + defer wg.Done() + + for { + select { + case <-ctx.Done(): + return + case job, ok := <-jobs: + if !ok { + return + } + start := time.Now() + + // This is not an account, but service level keys. + if util.IsServiceLevelAddress(job.Address) { + resultCh <- &migrationResult{ + migrationDuration: migrationDuration{ + Address: job.Address, + Duration: time.Since(start), + PayloadCount: len(job.Payloads), + }, + Migrated: job.Payloads, + } + continue + } + + if _, ok := knownProblematicAccounts[job.Address]; ok { + log.Info(). + Hex("address", job.Address[:]). + Int("payload_count", len(job.Payloads)). + Msg("skipping problematic account") + resultCh <- &migrationResult{ + migrationDuration: migrationDuration{ + Address: job.Address, + Duration: time.Since(start), + PayloadCount: len(job.Payloads), + }, + Migrated: job.Payloads, + } + continue + } + + var err error + accountMigrated := job.Payloads + for m, migrator := range migrations { + + select { + case <-ctx.Done(): + return + default: + } + + accountMigrated, err = migrator.MigrateAccount(ctx, job.Address, accountMigrated) + if err != nil { + log.Error(). + Err(err). + Int("migration_index", m). + Type("migration", migrator). + Hex("address", job.Address[:]). + Msg("could not migrate account") + cancel(fmt.Errorf("could not migrate account: %w", err)) + return + } + } + + resultCh <- &migrationResult{ + migrationDuration: migrationDuration{ + Address: job.Address, + Duration: time.Since(start), + PayloadCount: len(job.Payloads), + }, + Migrated: accountMigrated, + } + } + } + }() } - log.Info().Msgf("finished migrating payloads for %v account", len(groups.Accounts)) + go func() { + defer close(jobs) + for { + g, err := accountGroups.Next() + if err != nil { + cancel(fmt.Errorf("could not get next account group: %w", err)) + return + } - // add the non accounts which don't need to be migrated - migrated = append(migrated, groups.NonAccountPayloads...) + if g == nil { + break + } - log.Info().Msgf("finished migrating all account based payloads, total migrated payloads: %v", len(migrated)) + job := jobMigrateAccountGroup{ + Address: g.Address, + Payloads: g.Payloads, + } - return migrated, nil -} + select { + case <-ctx.Done(): + return + case jobs <- job: + } + } + }() -// MigrateGroupSequentially migrate the payloads in the given payloadsByAccount map which -// using the migrator -func MigrateGroupSequentially( - migrator AccountMigrator, - payloadsByAccount map[string][]ledger.Payload, -) ( - []ledger.Payload, error) { + // read job results + logAccount := moduleUtil.LogProgress( + log, + moduleUtil.DefaultLogProgressConfig( + "processing account group", + accountGroups.Len(), + ), + ) + + migrated := make([]*ledger.Payload, 0, accountGroups.AllPayloadsCount()) + durations := newMigrationDurations(logTopNDurations) + contextDone := false + for i := 0; i < accountGroups.Len(); i++ { + select { + case <-ctx.Done(): + contextDone = true + break + case result := <-resultCh: + durations.Add(result) + + accountMigrated := result.Migrated + migrated = append(migrated, accountMigrated...) + logAccount(1) + } + if contextDone { + break + } + } - logAccount := util.LogProgress("processing account group", len(payloadsByAccount), log.Logger) + // make sure to exit all workers before returning from this function + // so that the migrator can be closed properly + log.Info().Msg("waiting for migration workers to finish") + wg.Wait() - i := 0 - migrated := make([]ledger.Payload, 0) - for address, payloads := range payloadsByAccount { - accountMigrated, err := migrator.MigratePayloads(address, payloads) - if err != nil { - return nil, fmt.Errorf("could not migrate for account address %v: %w", address, err) - } + log.Info(). + Array("top_longest_migrations", durations.Array()). + Msgf("Top longest migrations") - migrated = append(migrated, accountMigrated...) - logAccount(i) - i++ + if ctx.Err() != nil { + return nil, fmt.Errorf("fail to migrate payload: %w", ctx.Err()) } return migrated, nil } +var knownProblematicAccounts = map[common.Address]string{ + // Testnet accounts with broken contracts + mustHexToAddress("434a1f199a7ae3ba"): "Broken contract FanTopPermission", + mustHexToAddress("454c9991c2b8d947"): "Broken contract Test", + mustHexToAddress("48602d8056ff9d93"): "Broken contract FanTopPermission", + mustHexToAddress("5d63c34d7f05e5a4"): "Broken contract FanTopPermission", + mustHexToAddress("5e3448b3cffb97f2"): "Broken contract FanTopPermission", + mustHexToAddress("7d8c7e050c694eaa"): "Broken contract Test", + mustHexToAddress("ba53f16ede01972d"): "Broken contract FanTopPermission", + mustHexToAddress("c843c1f5a4805c3a"): "Broken contract FanTopPermission", + mustHexToAddress("48d3be92e6e4a973"): "Broken contract FanTopPermission", + // Mainnet account +} + +func mustHexToAddress(hex string) common.Address { + address, err := common.HexToAddress(hex) + if err != nil { + panic(err) + } + return address +} + type jobMigrateAccountGroup struct { - Account string - Payloads []ledger.Payload + Address common.Address + Payloads []*ledger.Payload } type migrationResult struct { - Migrated []ledger.Payload - Err error + migrationDuration + + Migrated []*ledger.Payload } -// MigrateGroupConcurrently migrate the payloads in the given payloadsByAccount map which -// using the migrator -// It's similar to MigrateGroupSequentially, except it will migrate different groups concurrently -func MigrateGroupConcurrently( - migrator AccountMigrator, - payloadsByAccount map[string][]ledger.Payload, - nWorker int, -) ( - []ledger.Payload, error) { +type migrationDuration struct { + Address common.Address + Duration time.Duration + PayloadCount int +} - jobs := make(chan jobMigrateAccountGroup, len(payloadsByAccount)) - go func() { - for account, payloads := range payloadsByAccount { - jobs <- jobMigrateAccountGroup{ - Account: account, - Payloads: payloads, - } - } - close(jobs) - }() +// migrationDurations implements heap methods for the timer results +type migrationDurations struct { + v []migrationDuration - resultCh := make(chan *migrationResult) - for i := 0; i < int(nWorker); i++ { - go func() { - for job := range jobs { - accountMigrated, err := migrator.MigratePayloads(job.Account, job.Payloads) - resultCh <- &migrationResult{ - Migrated: accountMigrated, - Err: err, - } - } - }() + KeepTopN int +} + +// newMigrationDurations creates a new migrationDurations which are used to track the +// accounts that took the longest time to migrate. +func newMigrationDurations(keepTopN int) *migrationDurations { + return &migrationDurations{ + v: make([]migrationDuration, 0, keepTopN), + KeepTopN: keepTopN, } +} - // read job results - logAccount := util.LogProgress("processing account group", len(payloadsByAccount), log.Logger) +func (h *migrationDurations) Len() int { return len(h.v) } +func (h *migrationDurations) Less(i, j int) bool { + return h.v[i].Duration < h.v[j].Duration +} +func (h *migrationDurations) Swap(i, j int) { + h.v[i], h.v[j] = h.v[j], h.v[i] +} +func (h *migrationDurations) Push(x interface{}) { + h.v = append(h.v, x.(migrationDuration)) +} +func (h *migrationDurations) Pop() interface{} { + old := h.v + n := len(old) + x := old[n-1] + h.v = old[0 : n-1] + return x +} - migrated := make([]ledger.Payload, 0) +func (h *migrationDurations) Array() zerolog.LogArrayMarshaler { + array := zerolog.Arr() + for _, result := range h.v { + array = array.Str(fmt.Sprintf("%s [payloads: %d]: %s", + result.Address.Hex(), + result.PayloadCount, + result.Duration.String(), + )) + } + return array +} - for i := 0; i < len(payloadsByAccount); i++ { - result := <-resultCh - if result.Err != nil { - return nil, fmt.Errorf("fail to migrate payload: %w", result.Err) +func (h *migrationDurations) Add(result *migrationResult) { + if h.Len() < h.KeepTopN || result.Duration > h.v[0].Duration { + if h.Len() == h.KeepTopN { + heap.Pop(h) // remove the element with the smallest duration } - - accountMigrated := result.Migrated - migrated = append(migrated, accountMigrated...) - logAccount(i) + heap.Push(h, result.migrationDuration) } - - return migrated, nil } diff --git a/cmd/util/ledger/migrations/account_migration.go b/cmd/util/ledger/migrations/account_migration.go deleted file mode 100644 index e0bd7c029dd..00000000000 --- a/cmd/util/ledger/migrations/account_migration.go +++ /dev/null @@ -1,100 +0,0 @@ -package migrations - -import ( - "fmt" - - "github.com/rs/zerolog/log" - - "github.com/onflow/flow-go/fvm/environment" - "github.com/onflow/flow-go/ledger" - "github.com/onflow/flow-go/ledger/common/convert" - "github.com/onflow/flow-go/model/flow" -) - -func MigrateAccountUsage(payloads []ledger.Payload, nWorker int) ([]ledger.Payload, error) { - return MigrateByAccount(AccountUsageMigrator{}, payloads, nWorker) -} - -func payloadSize(key ledger.Key, payload ledger.Payload) (uint64, error) { - id, err := convert.LedgerKeyToRegisterID(key) - if err != nil { - return 0, err - } - - return uint64(registerSize(id, payload)), nil -} - -func isAccountKey(key ledger.Key) bool { - return string(key.KeyParts[1].Value) == flow.AccountStatusKey -} - -type AccountUsageMigrator struct{} - -// AccountUsageMigrator iterate through each payload, and calculate the storage usage -// and update the accoutns status with the updated storage usage -func (m AccountUsageMigrator) MigratePayloads(account string, payloads []ledger.Payload) ([]ledger.Payload, error) { - var status *environment.AccountStatus - var statusIndex int - totalSize := uint64(0) - for i, payload := range payloads { - key, err := payload.Key() - if err != nil { - return nil, err - } - if isAccountKey(key) { - statusIndex = i - status, err = environment.AccountStatusFromBytes(payload.Value()) - if err != nil { - return nil, fmt.Errorf("could not parse account status: %w", err) - } - - } - - size, err := payloadSize(key, payload) - if err != nil { - return nil, err - } - totalSize += size - } - - err := compareUsage(status, totalSize) - if err != nil { - log.Error().Msgf("%v", err) - } - - if status == nil { - return nil, fmt.Errorf("could not find account status for account %v", account) - } - - // update storage used - status.SetStorageUsed(totalSize) - - newValue := status.ToBytes() - newPayload, err := newPayloadWithValue(payloads[statusIndex], newValue) - if err != nil { - return nil, fmt.Errorf("cannot create new payload with value: %w", err) - } - - payloads[statusIndex] = newPayload - - return payloads, nil -} - -func compareUsage(status *environment.AccountStatus, totalSize uint64) error { - oldSize := status.StorageUsed() - if oldSize != totalSize { - return fmt.Errorf("old size: %v, new size: %v", oldSize, totalSize) - } - return nil -} - -// newPayloadWithValue returns a new payload with the key from the given payload, and -// the value from the argument -func newPayloadWithValue(payload ledger.Payload, value ledger.Value) (ledger.Payload, error) { - key, err := payload.Key() - if err != nil { - return ledger.Payload{}, err - } - newPayload := ledger.NewPayload(key, payload.Value()) - return *newPayload, nil -} diff --git a/cmd/util/ledger/migrations/atree_register_migration.go b/cmd/util/ledger/migrations/atree_register_migration.go new file mode 100644 index 00000000000..1b0873e7a2b --- /dev/null +++ b/cmd/util/ledger/migrations/atree_register_migration.go @@ -0,0 +1,445 @@ +package migrations + +import ( + "context" + "errors" + "fmt" + "io" + runtime2 "runtime" + "time" + + "github.com/rs/zerolog" + + "github.com/onflow/atree" + "github.com/onflow/cadence/runtime" + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/interpreter" + "github.com/onflow/cadence/runtime/stdlib" + + "github.com/onflow/flow-go/cmd/util/ledger/reporters" + "github.com/onflow/flow-go/cmd/util/ledger/util" + "github.com/onflow/flow-go/fvm/environment" + "github.com/onflow/flow-go/ledger" + "github.com/onflow/flow-go/ledger/common/convert" + "github.com/onflow/flow-go/model/flow" + util2 "github.com/onflow/flow-go/module/util" +) + +// AtreeRegisterMigrator is a migrator that converts the storage of an account from the +// old atree format to the new atree format. +// Account "storage used" should be correctly updated after the migration. +type AtreeRegisterMigrator struct { + log zerolog.Logger + + sampler zerolog.Sampler + rw reporters.ReportWriter + rwf reporters.ReportWriterFactory + + nWorkers int + + validateMigratedValues bool + logVerboseValidationError bool +} + +var _ AccountBasedMigration = (*AtreeRegisterMigrator)(nil) +var _ io.Closer = (*AtreeRegisterMigrator)(nil) + +func NewAtreeRegisterMigrator( + rwf reporters.ReportWriterFactory, + validateMigratedValues bool, + logVerboseValidationError bool, +) *AtreeRegisterMigrator { + + sampler := util2.NewTimedSampler(30 * time.Second) + + migrator := &AtreeRegisterMigrator{ + sampler: sampler, + rwf: rwf, + rw: rwf.ReportWriter("atree-register-migrator"), + validateMigratedValues: validateMigratedValues, + logVerboseValidationError: logVerboseValidationError, + } + + return migrator +} + +func (m *AtreeRegisterMigrator) Close() error { + // close the report writer so it flushes to file + m.rw.Close() + + return nil +} + +func (m *AtreeRegisterMigrator) InitMigration( + log zerolog.Logger, + _ []*ledger.Payload, + nWorkers int, +) error { + m.log = log.With().Str("migration", "atree-register-migration").Logger() + m.nWorkers = nWorkers + + return nil +} + +func (m *AtreeRegisterMigrator) MigrateAccount( + _ context.Context, + address common.Address, + oldPayloads []*ledger.Payload, +) ([]*ledger.Payload, error) { + // create all the runtime components we need for the migration + mr, err := newMigratorRuntime(address, oldPayloads) + if err != nil { + return nil, fmt.Errorf("failed to create migrator runtime: %w", err) + } + + // keep track of all storage maps that were accessed + // if they are empty they won't be changed, but we still need to copy them over + storageMapIds := make(map[string]struct{}) + + // Do the storage conversion + changes, err := m.migrateAccountStorage(mr, storageMapIds) + if err != nil { + if errors.Is(err, skippableAccountError) { + return oldPayloads, nil + } + return nil, fmt.Errorf("failed to convert storage for address %s: %w", address.Hex(), err) + } + + originalLen := len(oldPayloads) + + newPayloads, err := m.validateChangesAndCreateNewRegisters(mr, changes, storageMapIds) + if err != nil { + if errors.Is(err, skippableAccountError) { + return oldPayloads, nil + } + return nil, err + } + + if m.validateMigratedValues { + err = validateCadenceValues(address, oldPayloads, newPayloads, m.log, m.logVerboseValidationError) + if err != nil { + return nil, err + } + } + + newLen := len(newPayloads) + + if newLen > originalLen { + // this is possible, its not something to be worried about. + m.rw.Write(migrationProblem{ + Address: address.Hex(), + Key: "", + Size: len(mr.Snapshot.Payloads), + Kind: "more_registers_after_migration", + Msg: fmt.Sprintf("original: %d, new: %d", originalLen, newLen), + }) + } + + return newPayloads, nil +} + +func (m *AtreeRegisterMigrator) migrateAccountStorage( + mr *migratorRuntime, + storageMapIds map[string]struct{}, +) (map[flow.RegisterID]flow.RegisterValue, error) { + + // iterate through all domains and migrate them + for _, domain := range domains { + err := m.convertStorageDomain(mr, storageMapIds, domain) + if err != nil { + return nil, fmt.Errorf("failed to convert storage domain %s : %w", domain, err) + } + } + + err := mr.Storage.Commit(mr.Interpreter, false) + if err != nil { + return nil, fmt.Errorf("failed to commit storage: %w", err) + } + + // finalize the transaction + result, err := mr.TransactionState.FinalizeMainTransaction() + if err != nil { + return nil, fmt.Errorf("failed to finalize main transaction: %w", err) + } + + return result.WriteSet, nil +} + +func (m *AtreeRegisterMigrator) convertStorageDomain( + mr *migratorRuntime, + storageMapIds map[string]struct{}, + domain string, +) error { + + storageMap := mr.Storage.GetStorageMap(mr.Address, domain, false) + if storageMap == nil { + // no storage for this domain + return nil + } + storageMapIds[string(atree.SlabIndexToLedgerKey(storageMap.StorageID().Index))] = struct{}{} + + iterator := storageMap.Iterator(util.NopMemoryGauge{}) + keys := make([]interpreter.StringStorageMapKey, 0, storageMap.Count()) + // to be safe avoid modifying the map while iterating + for { + key := iterator.NextKey() + if key == nil { + break + } + + stringKey, ok := key.(interpreter.StringAtreeValue) + if !ok { + return fmt.Errorf("invalid key type %T, expected interpreter.StringAtreeValue", key) + } + + keys = append(keys, interpreter.StringStorageMapKey(stringKey)) + } + + for _, key := range keys { + err := func() error { + var value interpreter.Value + + err := capturePanic(func() { + value = storageMap.ReadValue(util.NopMemoryGauge{}, key) + }) + if err != nil { + return fmt.Errorf("failed to read value for key %s: %w", key, err) + } + + value, err = m.cloneValue(mr, value) + + if err != nil { + return fmt.Errorf("failed to clone value for key %s: %w", key, err) + } + + err = capturePanic(func() { + // set value will first purge the old value + storageMap.SetValue(mr.Interpreter, key, value) + }) + + if err != nil { + return fmt.Errorf("failed to set value for key %s: %w", key, err) + } + + return nil + }() + if err != nil { + + m.rw.Write(migrationProblem{ + Address: mr.Address.Hex(), + Size: len(mr.Snapshot.Payloads), + Key: string(key), + Kind: "migration_failure", + Msg: err.Error(), + }) + return skippableAccountError + } + } + + return nil +} + +func (m *AtreeRegisterMigrator) validateChangesAndCreateNewRegisters( + mr *migratorRuntime, + changes map[flow.RegisterID]flow.RegisterValue, + storageMapIds map[string]struct{}, +) ([]*ledger.Payload, error) { + originalPayloadsSnapshot := mr.Snapshot + originalPayloads := originalPayloadsSnapshot.Payloads + newPayloads := make([]*ledger.Payload, 0, len(originalPayloads)) + + // store state payload so that it can be updated + var statePayload *ledger.Payload + progressLog := func(int) {} + + for id, value := range changes { + progressLog(1) + // delete all values that were changed from the original payloads so that we can + // check what remains + delete(originalPayloads, id) + + if len(value) == 0 { + // value was deleted + continue + } + + ownerAddress, err := common.BytesToAddress([]byte(id.Owner)) + if err != nil { + return nil, fmt.Errorf("failed to convert owner address: %w", err) + } + + if ownerAddress != mr.Address { + // something was changed that does not belong to this account. Log it. + m.log.Error(). + Str("key", id.String()). + Str("owner_address", ownerAddress.Hex()). + Str("account", mr.Address.Hex()). + Msg("key is part of the change set, but is for a different account") + } + + key := convert.RegisterIDToLedgerKey(id) + + if statePayload == nil && isAccountKey(key) { + statePayload = ledger.NewPayload(key, value) + // we will append this later + continue + } + + newPayloads = append(newPayloads, ledger.NewPayload(key, value)) + } + + removedSize := uint64(0) + + // add all values that were not changed + if len(originalPayloads) > 0 { + + for id, value := range originalPayloads { + progressLog(1) + + if len(value.Value()) == 0 { + // this is strange, but we don't want to add empty values. Log it. + m.log.Warn().Msgf("empty value for key %s", id) + continue + } + + key := convert.RegisterIDToLedgerKey(id) + if statePayload == nil && isAccountKey(key) { + statePayload = value + // we will append this later + continue + } + + if id.IsInternalState() { + // this is expected. Move it to the new payloads + newPayloads = append(newPayloads, value) + continue + } + + if _, isADomainKey := domainsLookupMap[id.Key]; isADomainKey { + // this is expected. Move it to the new payloads + newPayloads = append(newPayloads, value) + continue + } + + if _, ok := storageMapIds[id.Key]; ok { + // This is needed because storage map can be empty. + // Empty storage map only exists in old payloads because there isn't any element to migrate. + newPayloads = append(newPayloads, value) + continue + } + + m.rw.Write(migrationProblem{ + Address: mr.Address.Hex(), + Key: id.String(), + Size: len(mr.Snapshot.Payloads), + Kind: "not_migrated", + Msg: fmt.Sprintf("%x", value.Value()), + }) + + size, err := payloadSize(key, value) + if err != nil { + return nil, fmt.Errorf("failed to get payload size: %w", err) + } + + removedSize += size + + // this is ok + // return nil, skippableAccountError + } + } + + if statePayload == nil { + return nil, fmt.Errorf("state payload was not found") + } + + // since some registers were removed, we need to update the storage used + if removedSize > 0 { + status, err := environment.AccountStatusFromBytes(statePayload.Value()) + if err != nil { + return nil, fmt.Errorf("could not parse account status: %w", err) + } + + status.SetStorageUsed(status.StorageUsed() - removedSize) + + newPayload, err := newPayloadWithValue(statePayload, status.ToBytes()) + if err != nil { + return nil, fmt.Errorf("cannot create new payload with value: %w", err) + } + + statePayload = newPayload + } + + newPayloads = append(newPayloads, statePayload) + + return newPayloads, nil +} + +func (m *AtreeRegisterMigrator) cloneValue( + mr *migratorRuntime, + value interpreter.Value, +) (interpreter.Value, error) { + + err := capturePanic(func() { + // force the value to be read entirely + value = value.Clone(mr.Interpreter) + }) + if err != nil { + return nil, err + } + return value, nil +} + +// capturePanic captures panics and converts them to errors +// this is needed for some cadence functions that panic on error +func capturePanic(f func()) (err error) { + defer func() { + if r := recover(); r != nil { + var stack [100000]byte + n := runtime2.Stack(stack[:], false) + fmt.Printf("%s", stack[:n]) + + switch x := r.(type) { + case runtime.Error: + err = fmt.Errorf("runtime error @%s: %w", x.Location, x) + case error: + err = x + default: + err = fmt.Errorf("panic: %v", r) + } + } + }() + f() + + return +} + +// convert all domains +var domains = []string{ + common.PathDomainStorage.Identifier(), + common.PathDomainPrivate.Identifier(), + common.PathDomainPublic.Identifier(), + runtime.StorageDomainContract, + stdlib.InboxStorageDomain, + stdlib.CapabilityControllerStorageDomain, +} + +var domainsLookupMap = map[string]struct{}{ + common.PathDomainStorage.Identifier(): {}, + common.PathDomainPrivate.Identifier(): {}, + common.PathDomainPublic.Identifier(): {}, + runtime.StorageDomainContract: {}, + stdlib.InboxStorageDomain: {}, + stdlib.CapabilityControllerStorageDomain: {}, +} + +// migrationProblem is a struct for reporting errors +type migrationProblem struct { + Address string + // Size is the account size in register count + Size int + Key string + Kind string + Msg string +} + +var skippableAccountError = errors.New("account can be skipped") diff --git a/cmd/util/ledger/migrations/atree_register_migration_test.go b/cmd/util/ledger/migrations/atree_register_migration_test.go new file mode 100644 index 00000000000..d4acf230d55 --- /dev/null +++ b/cmd/util/ledger/migrations/atree_register_migration_test.go @@ -0,0 +1,150 @@ +package migrations_test + +import ( + "testing" + + "github.com/rs/zerolog" + "github.com/stretchr/testify/require" + + "github.com/onflow/flow-go/cmd/util/ledger/migrations" + "github.com/onflow/flow-go/cmd/util/ledger/reporters" + "github.com/onflow/flow-go/ledger" + "github.com/onflow/flow-go/ledger/common/convert" + "github.com/onflow/flow-go/ledger/common/pathfinder" + "github.com/onflow/flow-go/ledger/complete" + "github.com/onflow/flow-go/ledger/complete/wal" + "github.com/onflow/flow-go/model/flow" + "github.com/onflow/flow-go/module/metrics" +) + +func TestAtreeRegisterMigration(t *testing.T) { + log := zerolog.New(zerolog.NewTestWriter(t)) + dir := t.TempDir() + + // Localnet v0.31 was used to produce an execution state that can be used for the tests. + t.Run( + "test v0.31 state", + testWithExistingState( + + log, + "test-data/bootstrapped_v0.31", + migrations.CreateAccountBasedMigration(log, 2, + []migrations.AccountBasedMigration{ + migrations.NewAtreeRegisterMigrator(reporters.NewReportFileWriterFactory(dir, log), true, false), + }, + ), + func(t *testing.T, oldPayloads []*ledger.Payload, newPayloads []*ledger.Payload) { + + oldPayloadsMap := make(map[flow.RegisterID]*ledger.Payload, len(oldPayloads)) + + for _, payload := range oldPayloads { + key, err := payload.Key() + require.NoError(t, err) + id, err := convert.LedgerKeyToRegisterID(key) + require.NoError(t, err) + oldPayloadsMap[id] = payload + } + + newPayloadsMap := make(map[flow.RegisterID]*ledger.Payload, len(newPayloads)) + + for _, payload := range newPayloads { + key, err := payload.Key() + require.NoError(t, err) + id, err := convert.LedgerKeyToRegisterID(key) + require.NoError(t, err) + newPayloadsMap[id] = payload + } + + for key, payload := range newPayloadsMap { + value := newPayloadsMap[key].Value() + + // TODO: currently the migration does not change the payload values because + // the atree version is not changed. This should be changed in the future. + require.Equal(t, payload.Value(), value) + } + + // commented out helper code to dump the payloads to csv files for manual inspection + //dumpPayloads := func(n string, payloads []ledger.Payload) { + // f, err := os.Create(n) + // require.NoError(t, err) + // + // defer f.Close() + // + // for _, payload := range payloads { + // key, err := payload.Key() + // require.NoError(t, err) + // _, err = f.WriteString(fmt.Sprintf("%x,%s\n", key.String(), payload.Value())) + // require.NoError(t, err) + // } + //} + //dumpPayloads("old.csv", oldPayloads) + //dumpPayloads("new.csv", newPayloads) + + }, + ), + ) + +} + +func testWithExistingState( + log zerolog.Logger, + inputDir string, + migration ledger.Migration, + f func( + t *testing.T, + oldPayloads []*ledger.Payload, + newPayloads []*ledger.Payload, + ), +) func(t *testing.T) { + return func(t *testing.T) { + diskWal, err := wal.NewDiskWAL( + log, + nil, + metrics.NewNoopCollector(), + inputDir, + complete.DefaultCacheSize, + pathfinder.PathByteSize, + wal.SegmentSize, + ) + require.NoError(t, err) + + led, err := complete.NewLedger( + diskWal, + complete.DefaultCacheSize, + &metrics.NoopCollector{}, + log, + complete.DefaultPathFinderVersion) + require.NoError(t, err) + + var oldPayloads []*ledger.Payload + + // we sandwitch the migration between two identity migrations + // so we can capture the Payloads before and after the migration + var mig = []ledger.Migration{ + func(payloads []*ledger.Payload) ([]*ledger.Payload, error) { + oldPayloads = make([]*ledger.Payload, len(payloads)) + copy(oldPayloads, payloads) + return payloads, nil + }, + + migration, + + func(newPayloads []*ledger.Payload) ([]*ledger.Payload, error) { + + f(t, oldPayloads, newPayloads) + + return newPayloads, nil + }, + } + + newState, err := led.MostRecentTouchedState() + require.NoError(t, err) + + _, err = led.MigrateAt( + newState, + mig, + complete.DefaultPathFinderVersion, + ) + require.NoError(t, err) + } +} diff --git a/cmd/util/ledger/migrations/cadence_value_validation.go b/cmd/util/ledger/migrations/cadence_value_validation.go new file mode 100644 index 00000000000..ff45b2e2c97 --- /dev/null +++ b/cmd/util/ledger/migrations/cadence_value_validation.go @@ -0,0 +1,599 @@ +package migrations + +import ( + "fmt" + "strings" + "time" + + "github.com/onflow/atree" + "github.com/onflow/cadence" + "github.com/onflow/cadence/runtime" + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/interpreter" + "github.com/rs/zerolog" + "go.opentelemetry.io/otel/attribute" + + "github.com/onflow/flow-go/cmd/util/ledger/util" + "github.com/onflow/flow-go/ledger" +) + +var nopMemoryGauge = util.NopMemoryGauge{} + +// TODO: optimize memory by reusing payloads snapshot created for migration +func validateCadenceValues( + address common.Address, + oldPayloads []*ledger.Payload, + newPayloads []*ledger.Payload, + log zerolog.Logger, + verboseLogging bool, +) error { + // Create all the runtime components we need for comparing Cadence values. + oldRuntime, err := newReadonlyStorageRuntime(oldPayloads) + if err != nil { + return fmt.Errorf("failed to create validator runtime with old payloads: %w", err) + } + + newRuntime, err := newReadonlyStorageRuntime(newPayloads) + if err != nil { + return fmt.Errorf("failed to create validator runtime with new payloads: %w", err) + } + + // Iterate through all domains and compare cadence values. + for _, domain := range domains { + err := validateStorageDomain(address, oldRuntime, newRuntime, domain, log, verboseLogging) + if err != nil { + return err + } + } + + return nil +} + +func validateStorageDomain( + address common.Address, + oldRuntime *readonlyStorageRuntime, + newRuntime *readonlyStorageRuntime, + domain string, + log zerolog.Logger, + verboseLogging bool, +) error { + + oldStorageMap := oldRuntime.Storage.GetStorageMap(address, domain, false) + + newStorageMap := newRuntime.Storage.GetStorageMap(address, domain, false) + + if oldStorageMap == nil && newStorageMap == nil { + // No storage for this domain. + return nil + } + + if oldStorageMap == nil && newStorageMap != nil { + return fmt.Errorf("old storage map is nil, new storage map isn't nil") + } + + if oldStorageMap != nil && newStorageMap == nil { + return fmt.Errorf("old storage map isn't nil, new storage map is nil") + } + + if oldStorageMap.Count() != newStorageMap.Count() { + return fmt.Errorf("old storage map count %d, new storage map count %d", oldStorageMap.Count(), newStorageMap.Count()) + } + + oldIterator := oldStorageMap.Iterator(nopMemoryGauge) + for { + key, oldValue := oldIterator.Next() + if key == nil { + break + } + + stringKey, ok := key.(interpreter.StringAtreeValue) + if !ok { + return fmt.Errorf("invalid key type %T, expected interpreter.StringAtreeValue", key) + } + + newValue := newStorageMap.ReadValue(nopMemoryGauge, interpreter.StringStorageMapKey(stringKey)) + + err := cadenceValueEqual(oldRuntime.Interpreter, oldValue, newRuntime.Interpreter, newValue) + if err != nil { + if verboseLogging { + log.Info(). + Str("address", address.Hex()). + Str("domain", domain). + Str("key", string(stringKey)). + Str("trace", err.Error()). + Str("old value", oldValue.String()). + Str("new value", newValue.String()). + Msgf("failed to validate value") + } + + return fmt.Errorf("failed to validate value for address %s, domain %s, key %s: %s", address.Hex(), domain, key, err.Error()) + } + } + + return nil +} + +type validationError struct { + trace []string + errorMsg string + traceReversed bool +} + +func newValidationErrorf(format string, a ...any) *validationError { + return &validationError{ + errorMsg: fmt.Sprintf(format, a...), + } +} + +func (e *validationError) addTrace(trace string) { + e.trace = append(e.trace, trace) +} + +func (e *validationError) Error() string { + if len(e.trace) == 0 { + return fmt.Sprintf("failed to validate: %s", e.errorMsg) + } + // Reverse trace + if !e.traceReversed { + for i, j := 0, len(e.trace)-1; i < j; i, j = i+1, j-1 { + e.trace[i], e.trace[j] = e.trace[j], e.trace[i] + } + e.traceReversed = true + } + trace := strings.Join(e.trace, ".") + return fmt.Sprintf("failed to validate %s: %s", trace, e.errorMsg) +} + +func cadenceValueEqual( + vInterpreter *interpreter.Interpreter, + v interpreter.Value, + otherInterpreter *interpreter.Interpreter, + other interpreter.Value, +) *validationError { + switch v := v.(type) { + case *interpreter.ArrayValue: + return cadenceArrayValueEqual(vInterpreter, v, otherInterpreter, other) + + case *interpreter.CompositeValue: + return cadenceCompositeValueEqual(vInterpreter, v, otherInterpreter, other) + + case *interpreter.DictionaryValue: + return cadenceDictionaryValueEqual(vInterpreter, v, otherInterpreter, other) + + case *interpreter.SomeValue: + return cadenceSomeValueEqual(vInterpreter, v, otherInterpreter, other) + + default: + oldValue, ok := v.(interpreter.EquatableValue) + if !ok { + return newValidationErrorf( + "value doesn't implement interpreter.EquatableValue: %T", + oldValue, + ) + } + if !oldValue.Equal(nil, interpreter.EmptyLocationRange, other) { + return newValidationErrorf( + "values differ: %v (%T) != %v (%T)", + oldValue, + oldValue, + other, + other, + ) + } + } + + return nil +} + +func cadenceSomeValueEqual( + vInterpreter *interpreter.Interpreter, + v *interpreter.SomeValue, + otherInterpreter *interpreter.Interpreter, + other interpreter.Value, +) *validationError { + otherSome, ok := other.(*interpreter.SomeValue) + if !ok { + return newValidationErrorf("types differ: %T != %T", v, other) + } + + innerValue := v.InnerValue(vInterpreter, interpreter.EmptyLocationRange) + + otherInnerValue := otherSome.InnerValue(otherInterpreter, interpreter.EmptyLocationRange) + + return cadenceValueEqual(vInterpreter, innerValue, otherInterpreter, otherInnerValue) +} + +func cadenceArrayValueEqual( + vInterpreter *interpreter.Interpreter, + v *interpreter.ArrayValue, + otherInterpreter *interpreter.Interpreter, + other interpreter.Value, +) *validationError { + otherArray, ok := other.(*interpreter.ArrayValue) + if !ok { + return newValidationErrorf("types differ: %T != %T", v, other) + } + + count := v.Count() + if count != otherArray.Count() { + return newValidationErrorf("array counts differ: %d != %d", count, otherArray.Count()) + } + + if v.Type == nil { + if otherArray.Type != nil { + return newValidationErrorf("array types differ: nil != %s", otherArray.Type) + } + } else { // v.Type != nil + if otherArray.Type == nil { + return newValidationErrorf("array types differ: %s != nil", v.Type) + } else if !v.Type.Equal(otherArray.Type) { + return newValidationErrorf("array types differ: %s != %s", v.Type, otherArray.Type) + } + } + + for i := 0; i < count; i++ { + element := v.Get(vInterpreter, interpreter.EmptyLocationRange, i) + otherElement := otherArray.Get(otherInterpreter, interpreter.EmptyLocationRange, i) + + err := cadenceValueEqual(vInterpreter, element, otherInterpreter, otherElement) + if err != nil { + err.addTrace(fmt.Sprintf("(%s[%d])", v.Type, i)) + return err + } + } + + return nil +} + +func cadenceCompositeValueEqual( + vInterpreter *interpreter.Interpreter, + v *interpreter.CompositeValue, + otherInterpreter *interpreter.Interpreter, + other interpreter.Value, +) *validationError { + otherComposite, ok := other.(*interpreter.CompositeValue) + if !ok { + return newValidationErrorf("types differ: %T != %T", v, other) + } + + if !v.StaticType(vInterpreter).Equal(otherComposite.StaticType(otherInterpreter)) { + return newValidationErrorf( + "composite types differ: %s != %s", + v.StaticType(vInterpreter), + otherComposite.StaticType(otherInterpreter), + ) + } + + if v.Kind != otherComposite.Kind { + return newValidationErrorf( + "composite kinds differ: %d != %d", + v.Kind, + otherComposite.Kind, + ) + } + + var err *validationError + vFieldNames := make([]string, 0, 10) // v's field names + v.ForEachField(nopMemoryGauge, func(fieldName string, fieldValue interpreter.Value) bool { + otherFieldValue := otherComposite.GetField(otherInterpreter, interpreter.EmptyLocationRange, fieldName) + + err = cadenceValueEqual(vInterpreter, fieldValue, otherInterpreter, otherFieldValue) + if err != nil { + err.addTrace(fmt.Sprintf("(%s.%s)", v.TypeID(), fieldName)) + return false + } + + vFieldNames = append(vFieldNames, fieldName) + return true + }) + + // TODO: Use CompositeValue.FieldCount() from Cadence after it is merged and available. + otherFieldNames := make([]string, 0, len(vFieldNames)) // otherComposite's field names + otherComposite.ForEachField(nopMemoryGauge, func(fieldName string, _ interpreter.Value) bool { + otherFieldNames = append(otherFieldNames, fieldName) + return true + }) + + if len(vFieldNames) != len(otherFieldNames) { + return newValidationErrorf( + "composite %s fields differ: %v != %v", + v.TypeID(), + vFieldNames, + otherFieldNames, + ) + } + + return err +} + +func cadenceDictionaryValueEqual( + vInterpreter *interpreter.Interpreter, + v *interpreter.DictionaryValue, + otherInterpreter *interpreter.Interpreter, + other interpreter.Value, +) *validationError { + otherDictionary, ok := other.(*interpreter.DictionaryValue) + if !ok { + return newValidationErrorf("types differ: %T != %T", v, other) + } + + if v.Count() != otherDictionary.Count() { + return newValidationErrorf("dict counts differ: %d != %d", v.Count(), otherDictionary.Count()) + } + + if !v.Type.Equal(otherDictionary.Type) { + return newValidationErrorf("dict types differ: %s != %s", v.Type, otherDictionary.Type) + } + + oldIterator := v.Iterator() + for { + key := oldIterator.NextKey(nopMemoryGauge) + if key == nil { + break + } + + oldValue, oldValueExist := v.Get(vInterpreter, interpreter.EmptyLocationRange, key) + if !oldValueExist { + err := newValidationErrorf("old value doesn't exist with key %v (%T)", key, key) + err.addTrace(fmt.Sprintf("(%s[%s])", v.Type, key)) + return err + } + newValue, newValueExist := otherDictionary.Get(otherInterpreter, interpreter.EmptyLocationRange, key) + if !newValueExist { + err := newValidationErrorf("new value doesn't exist with key %v (%T)", key, key) + err.addTrace(fmt.Sprintf("(%s[%s])", otherDictionary.Type, key)) + return err + } + err := cadenceValueEqual(vInterpreter, oldValue, otherInterpreter, newValue) + if err != nil { + err.addTrace(fmt.Sprintf("(%s[%s])", otherDictionary.Type, key)) + return err + } + } + + return nil +} + +type readonlyStorageRuntime struct { + Interpreter *interpreter.Interpreter + Storage *runtime.Storage +} + +func newReadonlyStorageRuntime(payloads []*ledger.Payload) ( + *readonlyStorageRuntime, + error, +) { + snapshot, err := util.NewPayloadSnapshot(payloads) + if err != nil { + return nil, fmt.Errorf("failed to create payload snapshot: %w", err) + } + + readonlyLedger := util.NewPayloadsReadonlyLedger(snapshot) + + storage := runtime.NewStorage(readonlyLedger, nopMemoryGauge) + + env := runtime.NewBaseInterpreterEnvironment(runtime.Config{ + AccountLinkingEnabled: true, + // Attachments are enabled everywhere except for Mainnet + AttachmentsEnabled: true, + // Capability Controllers are enabled everywhere except for Mainnet + CapabilityControllersEnabled: true, + }) + + env.Configure( + &NoopRuntimeInterface{}, + runtime.NewCodesAndPrograms(), + storage, + nil, + ) + + inter, err := interpreter.NewInterpreter(nil, nil, env.InterpreterConfig) + if err != nil { + return nil, err + } + + return &readonlyStorageRuntime{ + Interpreter: inter, + Storage: storage, + }, nil +} + +// NoopRuntimeInterface is a runtime interface that can be used in migrations. +type NoopRuntimeInterface struct { +} + +func (NoopRuntimeInterface) ResolveLocation(_ []runtime.Identifier, _ runtime.Location) ([]runtime.ResolvedLocation, error) { + panic("unexpected ResolveLocation call") +} + +func (NoopRuntimeInterface) GetCode(_ runtime.Location) ([]byte, error) { + panic("unexpected GetCode call") +} + +func (NoopRuntimeInterface) GetAccountContractCode(_ common.AddressLocation) ([]byte, error) { + panic("unexpected GetAccountContractCode call") +} + +func (NoopRuntimeInterface) GetOrLoadProgram(_ runtime.Location, _ func() (*interpreter.Program, error)) (*interpreter.Program, error) { + panic("unexpected GetOrLoadProgram call") +} + +func (NoopRuntimeInterface) MeterMemory(_ common.MemoryUsage) error { + return nil +} + +func (NoopRuntimeInterface) MeterComputation(_ common.ComputationKind, _ uint) error { + return nil +} + +func (NoopRuntimeInterface) GetValue(_, _ []byte) (value []byte, err error) { + panic("unexpected GetValue call") +} + +func (NoopRuntimeInterface) SetValue(_, _, _ []byte) (err error) { + panic("unexpected SetValue call") +} + +func (NoopRuntimeInterface) CreateAccount(_ runtime.Address) (address runtime.Address, err error) { + panic("unexpected CreateAccount call") +} + +func (NoopRuntimeInterface) AddEncodedAccountKey(_ runtime.Address, _ []byte) error { + panic("unexpected AddEncodedAccountKey call") +} + +func (NoopRuntimeInterface) RevokeEncodedAccountKey(_ runtime.Address, _ int) (publicKey []byte, err error) { + panic("unexpected RevokeEncodedAccountKey call") +} + +func (NoopRuntimeInterface) AddAccountKey(_ runtime.Address, _ *runtime.PublicKey, _ runtime.HashAlgorithm, _ int) (*runtime.AccountKey, error) { + panic("unexpected AddAccountKey call") +} + +func (NoopRuntimeInterface) GetAccountKey(_ runtime.Address, _ int) (*runtime.AccountKey, error) { + panic("unexpected GetAccountKey call") +} + +func (NoopRuntimeInterface) RevokeAccountKey(_ runtime.Address, _ int) (*runtime.AccountKey, error) { + panic("unexpected RevokeAccountKey call") +} + +func (NoopRuntimeInterface) UpdateAccountContractCode(_ common.AddressLocation, _ []byte) (err error) { + panic("unexpected UpdateAccountContractCode call") +} + +func (NoopRuntimeInterface) RemoveAccountContractCode(common.AddressLocation) (err error) { + panic("unexpected RemoveAccountContractCode call") +} + +func (NoopRuntimeInterface) GetSigningAccounts() ([]runtime.Address, error) { + panic("unexpected GetSigningAccounts call") +} + +func (NoopRuntimeInterface) ProgramLog(_ string) error { + panic("unexpected ProgramLog call") +} + +func (NoopRuntimeInterface) EmitEvent(_ cadence.Event) error { + panic("unexpected EmitEvent call") +} + +func (NoopRuntimeInterface) ValueExists(_, _ []byte) (exists bool, err error) { + panic("unexpected ValueExists call") +} + +func (NoopRuntimeInterface) GenerateUUID() (uint64, error) { + panic("unexpected GenerateUUID call") +} + +func (NoopRuntimeInterface) GetComputationLimit() uint64 { + panic("unexpected GetComputationLimit call") +} + +func (NoopRuntimeInterface) SetComputationUsed(_ uint64) error { + panic("unexpected SetComputationUsed call") +} + +func (NoopRuntimeInterface) DecodeArgument(_ []byte, _ cadence.Type) (cadence.Value, error) { + panic("unexpected DecodeArgument call") +} + +func (NoopRuntimeInterface) GetCurrentBlockHeight() (uint64, error) { + panic("unexpected GetCurrentBlockHeight call") +} + +func (NoopRuntimeInterface) GetBlockAtHeight(_ uint64) (block runtime.Block, exists bool, err error) { + panic("unexpected GetBlockAtHeight call") +} + +func (NoopRuntimeInterface) ReadRandom([]byte) error { + panic("unexpected ReadRandom call") +} + +func (NoopRuntimeInterface) VerifySignature(_ []byte, _ string, _ []byte, _ []byte, _ runtime.SignatureAlgorithm, _ runtime.HashAlgorithm) (bool, error) { + panic("unexpected VerifySignature call") +} + +func (NoopRuntimeInterface) Hash(_ []byte, _ string, _ runtime.HashAlgorithm) ([]byte, error) { + panic("unexpected Hash call") +} + +func (NoopRuntimeInterface) GetAccountBalance(_ common.Address) (value uint64, err error) { + panic("unexpected GetAccountBalance call") +} + +func (NoopRuntimeInterface) GetAccountAvailableBalance(_ common.Address) (value uint64, err error) { + panic("unexpected GetAccountAvailableBalance call") +} + +func (NoopRuntimeInterface) GetStorageUsed(_ runtime.Address) (value uint64, err error) { + panic("unexpected GetStorageUsed call") +} + +func (NoopRuntimeInterface) GetStorageCapacity(_ runtime.Address) (value uint64, err error) { + panic("unexpected GetStorageCapacity call") +} + +func (NoopRuntimeInterface) ImplementationDebugLog(_ string) error { + panic("unexpected ImplementationDebugLog call") +} + +func (NoopRuntimeInterface) ValidatePublicKey(_ *runtime.PublicKey) error { + panic("unexpected ValidatePublicKey call") +} + +func (NoopRuntimeInterface) GetAccountContractNames(_ runtime.Address) ([]string, error) { + panic("unexpected GetAccountContractNames call") +} + +func (NoopRuntimeInterface) AllocateStorageIndex(_ []byte) (atree.StorageIndex, error) { + panic("unexpected AllocateStorageIndex call") +} + +func (NoopRuntimeInterface) ComputationUsed() (uint64, error) { + panic("unexpected ComputationUsed call") +} + +func (NoopRuntimeInterface) MemoryUsed() (uint64, error) { + panic("unexpected MemoryUsed call") +} + +func (NoopRuntimeInterface) InteractionUsed() (uint64, error) { + panic("unexpected InteractionUsed call") +} + +func (NoopRuntimeInterface) SetInterpreterSharedState(_ *interpreter.SharedState) { + panic("unexpected SetInterpreterSharedState call") +} + +func (NoopRuntimeInterface) GetInterpreterSharedState() *interpreter.SharedState { + panic("unexpected GetInterpreterSharedState call") +} + +func (NoopRuntimeInterface) AccountKeysCount(_ runtime.Address) (uint64, error) { + panic("unexpected AccountKeysCount call") +} + +func (NoopRuntimeInterface) BLSVerifyPOP(_ *runtime.PublicKey, _ []byte) (bool, error) { + panic("unexpected BLSVerifyPOP call") +} + +func (NoopRuntimeInterface) BLSAggregateSignatures(_ [][]byte) ([]byte, error) { + panic("unexpected BLSAggregateSignatures call") +} + +func (NoopRuntimeInterface) BLSAggregatePublicKeys(_ []*runtime.PublicKey) (*runtime.PublicKey, error) { + panic("unexpected BLSAggregatePublicKeys call") +} + +func (NoopRuntimeInterface) ResourceOwnerChanged(_ *interpreter.Interpreter, _ *interpreter.CompositeValue, _ common.Address, _ common.Address) { + panic("unexpected ResourceOwnerChanged call") +} + +func (NoopRuntimeInterface) GenerateAccountID(_ common.Address) (uint64, error) { + panic("unexpected GenerateAccountID call") +} + +func (NoopRuntimeInterface) RecordTrace(_ string, _ runtime.Location, _ time.Duration, _ []attribute.KeyValue) { + panic("unexpected RecordTrace call") +} diff --git a/cmd/util/ledger/migrations/cadence_value_validation_test.go b/cmd/util/ledger/migrations/cadence_value_validation_test.go new file mode 100644 index 00000000000..ab52742a5fd --- /dev/null +++ b/cmd/util/ledger/migrations/cadence_value_validation_test.go @@ -0,0 +1,268 @@ +package migrations + +import ( + "bytes" + "fmt" + "strconv" + "testing" + + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/interpreter" + + "github.com/onflow/flow-go/fvm/environment" + "github.com/onflow/flow-go/ledger" + "github.com/onflow/flow-go/ledger/common/convert" + "github.com/onflow/flow-go/model/flow" + + "github.com/rs/zerolog" + "github.com/stretchr/testify/require" +) + +func TestValidateCadenceValues(t *testing.T) { + address, err := common.HexToAddress("0x1") + require.NoError(t, err) + + domain := common.PathDomainStorage.Identifier() + + t.Run("no mismatch", func(t *testing.T) { + log := zerolog.New(zerolog.NewTestWriter(t)) + + err := validateCadenceValues( + address, + createTestPayloads(t, address, domain), + createTestPayloads(t, address, domain), + log, + false, + ) + require.NoError(t, err) + }) + + t.Run("has mismatch", func(t *testing.T) { + var w bytes.Buffer + log := zerolog.New(&w) + + createPayloads := func(nestedArrayValue interpreter.UInt64Value) []*ledger.Payload { + + // Create account status payload + accountStatus := environment.NewAccountStatus() + accountStatusPayload := ledger.NewPayload( + convert.RegisterIDToLedgerKey( + flow.AccountStatusRegisterID(flow.ConvertAddress(address)), + ), + accountStatus.ToBytes(), + ) + + mr, err := newMigratorRuntime(address, []*ledger.Payload{accountStatusPayload}) + require.NoError(t, err) + + // Create new storage map + storageMap := mr.Storage.GetStorageMap(mr.Address, domain, true) + + // Add Cadence ArrayValue with nested CadenceArray + nestedArray := interpreter.NewArrayValue( + mr.Interpreter, + interpreter.EmptyLocationRange, + interpreter.VariableSizedStaticType{ + Type: interpreter.PrimitiveStaticTypeUInt64, + }, + address, + interpreter.NewUnmeteredUInt64Value(0), + nestedArrayValue, + ) + + storageMap.WriteValue( + mr.Interpreter, + interpreter.StringStorageMapKey(strconv.FormatUint(storageMap.Count(), 10)), + interpreter.NewArrayValue( + mr.Interpreter, + interpreter.EmptyLocationRange, + interpreter.VariableSizedStaticType{ + Type: interpreter.PrimitiveStaticTypeAnyStruct, + }, + address, + nestedArray, + ), + ) + + err = mr.Storage.Commit(mr.Interpreter, false) + require.NoError(t, err) + + // finalize the transaction + result, err := mr.TransactionState.FinalizeMainTransaction() + require.NoError(t, err) + + payloads := make([]*ledger.Payload, 0, len(result.WriteSet)) + for id, value := range result.WriteSet { + key := convert.RegisterIDToLedgerKey(id) + payloads = append(payloads, ledger.NewPayload(key, value)) + } + + return payloads + } + + oldPayloads := createPayloads(interpreter.NewUnmeteredUInt64Value(1)) + newPayloads := createPayloads(interpreter.NewUnmeteredUInt64Value(2)) + wantErrorMsg := "failed to validate value for address 0000000000000001, domain storage, key 0: failed to validate ([AnyStruct][0]).([UInt64][1]): values differ: 1 (interpreter.UInt64Value) != 2 (interpreter.UInt64Value)" + wantVerboseMsg := "{\"level\":\"info\",\"address\":\"0000000000000001\",\"domain\":\"storage\",\"key\":\"0\",\"trace\":\"failed to validate ([AnyStruct][0]).([UInt64][1]): values differ: 1 (interpreter.UInt64Value) != 2 (interpreter.UInt64Value)\",\"old value\":\"[[0, 1]]\",\"new value\":\"[[0, 2]]\",\"message\":\"failed to validate value\"}\n" + + // Disable verbose logging + err := validateCadenceValues( + address, + oldPayloads, + newPayloads, + log, + false, + ) + require.ErrorContains(t, err, wantErrorMsg) + require.Equal(t, 0, w.Len()) + + // Enable verbose logging + err = validateCadenceValues( + address, + oldPayloads, + newPayloads, + log, + true, + ) + require.ErrorContains(t, err, wantErrorMsg) + require.Equal(t, wantVerboseMsg, w.String()) + }) +} + +func createTestPayloads(t *testing.T, address common.Address, domain string) []*ledger.Payload { + + // Create account status payload + accountStatus := environment.NewAccountStatus() + accountStatusPayload := ledger.NewPayload( + convert.RegisterIDToLedgerKey( + flow.AccountStatusRegisterID(flow.ConvertAddress(address)), + ), + accountStatus.ToBytes(), + ) + + mr, err := newMigratorRuntime(address, []*ledger.Payload{accountStatusPayload}) + require.NoError(t, err) + + // Create new storage map + storageMap := mr.Storage.GetStorageMap(mr.Address, domain, true) + + // Add Cadence UInt64Value + storageMap.WriteValue( + mr.Interpreter, + interpreter.StringStorageMapKey(strconv.FormatUint(storageMap.Count(), 10)), + interpreter.NewUnmeteredUInt64Value(1), + ) + + // Add Cadence SomeValue + storageMap.WriteValue( + mr.Interpreter, + interpreter.StringStorageMapKey(strconv.FormatUint(storageMap.Count(), 10)), + interpreter.NewUnmeteredSomeValueNonCopying(interpreter.NewUnmeteredStringValue("InnerValueString")), + ) + + // Add Cadence ArrayValue + const arrayCount = 10 + i := uint64(0) + storageMap.WriteValue( + mr.Interpreter, + interpreter.StringStorageMapKey(strconv.FormatUint(storageMap.Count(), 10)), + interpreter.NewArrayValueWithIterator( + mr.Interpreter, + interpreter.VariableSizedStaticType{ + Type: interpreter.PrimitiveStaticTypeAnyStruct, + }, + address, + 0, + func() interpreter.Value { + if i == arrayCount { + return nil + } + v := interpreter.NewUnmeteredUInt64Value(i) + i++ + return v + }, + ), + ) + + // Add Cadence DictionaryValue + const dictCount = 10 + dictValues := make([]interpreter.Value, 0, dictCount*2) + for i := 0; i < dictCount; i++ { + k := interpreter.NewUnmeteredUInt64Value(uint64(i)) + v := interpreter.NewUnmeteredStringValue(fmt.Sprintf("value %d", i)) + dictValues = append(dictValues, k, v) + } + + storageMap.WriteValue( + mr.Interpreter, + interpreter.StringStorageMapKey(strconv.FormatUint(storageMap.Count(), 10)), + interpreter.NewDictionaryValueWithAddress( + mr.Interpreter, + interpreter.EmptyLocationRange, + interpreter.DictionaryStaticType{ + KeyType: interpreter.PrimitiveStaticTypeUInt64, + ValueType: interpreter.PrimitiveStaticTypeString, + }, + address, + dictValues..., + ), + ) + + // Add Cadence CompositeValue + storageMap.WriteValue( + mr.Interpreter, + interpreter.StringStorageMapKey(strconv.FormatUint(storageMap.Count(), 10)), + interpreter.NewCompositeValue( + mr.Interpreter, + interpreter.EmptyLocationRange, + common.StringLocation("test"), + "Test", + common.CompositeKindStructure, + []interpreter.CompositeField{ + {Name: "field1", Value: interpreter.NewUnmeteredStringValue("value1")}, + {Name: "field2", Value: interpreter.NewUnmeteredStringValue("value2")}, + }, + address, + ), + ) + + // Add Cadence DictionaryValue with nested CadenceArray + nestedArrayValue := interpreter.NewArrayValue( + mr.Interpreter, + interpreter.EmptyLocationRange, + interpreter.VariableSizedStaticType{ + Type: interpreter.PrimitiveStaticTypeUInt64, + }, + address, + interpreter.NewUnmeteredUInt64Value(0), + ) + + storageMap.WriteValue( + mr.Interpreter, + interpreter.StringStorageMapKey(strconv.FormatUint(storageMap.Count(), 10)), + interpreter.NewArrayValue( + mr.Interpreter, + interpreter.EmptyLocationRange, + interpreter.VariableSizedStaticType{ + Type: interpreter.PrimitiveStaticTypeAnyStruct, + }, + address, + nestedArrayValue, + ), + ) + + err = mr.Storage.Commit(mr.Interpreter, false) + require.NoError(t, err) + + // finalize the transaction + result, err := mr.TransactionState.FinalizeMainTransaction() + require.NoError(t, err) + + payloads := make([]*ledger.Payload, 0, len(result.WriteSet)) + for id, value := range result.WriteSet { + key := convert.RegisterIDToLedgerKey(id) + payloads = append(payloads, ledger.NewPayload(key, value)) + } + + return payloads +} diff --git a/cmd/util/ledger/migrations/change_contract_code_migration.go b/cmd/util/ledger/migrations/change_contract_code_migration.go new file mode 100644 index 00000000000..9c335fb9573 --- /dev/null +++ b/cmd/util/ledger/migrations/change_contract_code_migration.go @@ -0,0 +1,343 @@ +package migrations + +import ( + "context" + "fmt" + "sync" + + coreContracts "github.com/onflow/flow-core-contracts/lib/go/contracts" + ftContracts "github.com/onflow/flow-ft/lib/go/contracts" + nftContracts "github.com/onflow/flow-nft/lib/go/contracts" + "github.com/rs/zerolog" + + sdk "github.com/onflow/flow-go-sdk" + + "github.com/onflow/cadence/runtime/common" + + evm "github.com/onflow/flow-go/fvm/evm/stdlib" + "github.com/onflow/flow-go/fvm/systemcontracts" + "github.com/onflow/flow-go/ledger" + "github.com/onflow/flow-go/ledger/common/convert" + "github.com/onflow/flow-go/model/flow" +) + +type ChangeContractCodeMigration struct { + log zerolog.Logger + mutex sync.RWMutex + contracts map[common.Address]map[flow.RegisterID]string +} + +var _ AccountBasedMigration = (*ChangeContractCodeMigration)(nil) + +func (d *ChangeContractCodeMigration) Close() error { + d.mutex.RLock() + defer d.mutex.RUnlock() + + if len(d.contracts) > 0 { + return fmt.Errorf("failed to find all contract registers that need to be changed") + } + + return nil +} + +func (d *ChangeContractCodeMigration) InitMigration( + log zerolog.Logger, + _ []*ledger.Payload, + _ int, +) error { + d.log = log. + With(). + Str("migration", "ChangeContractCodeMigration"). + Logger() + + return nil +} + +func (d *ChangeContractCodeMigration) MigrateAccount( + _ context.Context, + address common.Address, + payloads []*ledger.Payload, +) ([]*ledger.Payload, error) { + + contracts, ok := (func() (map[flow.RegisterID]string, bool) { + d.mutex.Lock() + defer d.mutex.Unlock() + + contracts, ok := d.contracts[address] + + // remove address from set of addresses + // to keep track of which addresses are left to change + delete(d.contracts, address) + + return contracts, ok + })() + + if !ok { + // no contracts to change on this address + return payloads, nil + } + + for payloadIndex, payload := range payloads { + key, err := payload.Key() + if err != nil { + return nil, err + } + + registerID, err := convert.LedgerKeyToRegisterID(key) + if err != nil { + return nil, err + } + + newContract, ok := contracts[registerID] + if !ok { + // not a contract register, or + // not interested in this contract + continue + } + + // change contract code + payloads[payloadIndex] = ledger.NewPayload( + key, + []byte(newContract), + ) + + // TODO: maybe log diff between old and new + + // remove contract from list of contracts to change + // to keep track of which contracts are left to change + delete(contracts, registerID) + } + + if len(contracts) > 0 { + return nil, fmt.Errorf("failed to find all contract registers that need to be changed") + } + + return payloads, nil +} + +func (d *ChangeContractCodeMigration) RegisterContractChange( + address common.Address, + contractName string, + newContractCode string, +) ( + previousNewContractCode string, +) { + d.mutex.Lock() + defer d.mutex.Unlock() + + if d.contracts == nil { + d.contracts = map[common.Address]map[flow.RegisterID]string{} + } + + if _, ok := d.contracts[address]; !ok { + d.contracts[address] = map[flow.RegisterID]string{} + } + + registerID := flow.ContractRegisterID(flow.ConvertAddress(address), contractName) + + previousNewContractCode = d.contracts[address][registerID] + + d.contracts[address][registerID] = newContractCode + + return +} + +type SystemContractChange struct { + Address common.Address + ContractName string + NewContractCode string +} + +func NewSystemContractChange( + systemContract systemcontracts.SystemContract, + newContractCode []byte, +) SystemContractChange { + return SystemContractChange{ + Address: common.Address(systemContract.Address), + ContractName: systemContract.Name, + NewContractCode: string(newContractCode), + } +} + +func SystemContractChanges(chainID flow.ChainID) []SystemContractChange { + systemContracts := systemcontracts.SystemContractsForChain(chainID) + + var stakingCollectionAddress, stakingProxyAddress common.Address + + switch chainID { + case flow.Mainnet: + stakingCollectionAddress = mustHexAddress("0x8d0e87b65159ae63") + stakingProxyAddress = mustHexAddress("0x62430cf28c26d095") + + case flow.Testnet: + stakingCollectionAddress = mustHexAddress("0x95e019a17d0e23d7") + stakingProxyAddress = mustHexAddress("0x7aad92e5a0715d21") + + default: + panic(fmt.Errorf("unsupported chain ID: %s", chainID)) + } + + lockedTokensAddress := stakingCollectionAddress + fungibleTokenMetadataViewsAddress := common.Address(systemContracts.FungibleToken.Address) + fungibleTokenSwitchboardAddress := common.Address(systemContracts.FungibleToken.Address) + + return []SystemContractChange{ + // epoch related contracts + NewSystemContractChange( + systemContracts.Epoch, + coreContracts.FlowEpoch( + systemContracts.FungibleToken.Address.HexWithPrefix(), + systemContracts.FlowToken.Address.HexWithPrefix(), + systemContracts.IDTableStaking.Address.HexWithPrefix(), + systemContracts.ClusterQC.Address.HexWithPrefix(), + systemContracts.DKG.Address.HexWithPrefix(), + systemContracts.FlowFees.Address.HexWithPrefix(), + ), + ), + NewSystemContractChange( + systemContracts.IDTableStaking, + coreContracts.FlowIDTableStaking( + systemContracts.FungibleToken.Address.HexWithPrefix(), + systemContracts.FlowToken.Address.HexWithPrefix(), + systemContracts.FlowFees.Address.HexWithPrefix(), + true, + ), + ), + NewSystemContractChange( + systemContracts.ClusterQC, + coreContracts.FlowQC(), + ), + NewSystemContractChange( + systemContracts.DKG, + coreContracts.FlowDKG(), + ), + + // service account related contracts + NewSystemContractChange( + systemContracts.FlowServiceAccount, + coreContracts.FlowServiceAccount( + systemContracts.FungibleToken.Address.HexWithPrefix(), + systemContracts.FlowToken.Address.HexWithPrefix(), + systemContracts.FlowFees.Address.HexWithPrefix(), + systemContracts.FlowStorageFees.Address.HexWithPrefix(), + ), + ), + NewSystemContractChange( + systemContracts.NodeVersionBeacon, + coreContracts.NodeVersionBeacon(), + ), + NewSystemContractChange( + systemContracts.RandomBeaconHistory, + coreContracts.RandomBeaconHistory(), + ), + NewSystemContractChange( + systemContracts.FlowStorageFees, + coreContracts.FlowStorageFees( + systemContracts.FungibleToken.Address.HexWithPrefix(), + systemContracts.FlowToken.Address.HexWithPrefix(), + ), + ), + { + Address: stakingCollectionAddress, + ContractName: "FlowStakingCollection", + NewContractCode: string(coreContracts.FlowStakingCollection( + systemContracts.FungibleToken.Address.HexWithPrefix(), + systemContracts.FlowToken.Address.HexWithPrefix(), + systemContracts.IDTableStaking.Address.HexWithPrefix(), + stakingProxyAddress.HexWithPrefix(), + lockedTokensAddress.HexWithPrefix(), + systemContracts.FlowStorageFees.Address.HexWithPrefix(), + systemContracts.ClusterQC.Address.HexWithPrefix(), + systemContracts.DKG.Address.HexWithPrefix(), + systemContracts.Epoch.Address.HexWithPrefix(), + )), + }, + { + Address: stakingProxyAddress, + ContractName: "StakingProxy", + NewContractCode: string(coreContracts.FlowStakingProxy()), + }, + { + Address: lockedTokensAddress, + ContractName: "LockedTokens", + NewContractCode: string(coreContracts.FlowLockedTokens( + systemContracts.FungibleToken.Address.HexWithPrefix(), + systemContracts.FlowToken.Address.HexWithPrefix(), + systemContracts.IDTableStaking.Address.HexWithPrefix(), + stakingProxyAddress.HexWithPrefix(), + systemContracts.FlowStorageFees.Address.HexWithPrefix(), + )), + }, + + // token related contracts + NewSystemContractChange( + systemContracts.FlowFees, + coreContracts.FlowFees( + systemContracts.FungibleToken.Address.HexWithPrefix(), + systemContracts.FlowToken.Address.HexWithPrefix(), + systemContracts.FlowStorageFees.Address.HexWithPrefix(), + ), + ), + NewSystemContractChange( + systemContracts.FlowToken, + coreContracts.FlowToken( + systemContracts.FungibleToken.Address.HexWithPrefix(), + systemContracts.MetadataViews.Address.HexWithPrefix(), + systemContracts.ViewResolver.Address.HexWithPrefix(), + ), + ), + NewSystemContractChange( + systemContracts.FungibleToken, + ftContracts.FungibleToken(), + ), + { + Address: fungibleTokenMetadataViewsAddress, + ContractName: "FungibleTokenMetadataViews", + NewContractCode: string(ftContracts.FungibleTokenMetadataViews( + systemContracts.FungibleToken.Address.HexWithPrefix(), + systemContracts.MetadataViews.Address.HexWithPrefix(), + )), + }, + { + Address: fungibleTokenSwitchboardAddress, + ContractName: "FungibleTokenSwitchboard", + NewContractCode: string(ftContracts.FungibleTokenSwitchboard( + systemContracts.FungibleToken.Address.HexWithPrefix(), + )), + }, + + // NFT related contracts + NewSystemContractChange( + systemContracts.NonFungibleToken, + nftContracts.NonFungibleToken(), + ), + NewSystemContractChange( + systemContracts.MetadataViews, + nftContracts.MetadataViews( + sdk.Address(systemContracts.FungibleToken.Address), + sdk.Address(systemContracts.NonFungibleToken.Address), + ), + ), + NewSystemContractChange( + systemContracts.ViewResolver, + nftContracts.Resolver(), + ), + + // EVM related contracts + NewSystemContractChange( + systemContracts.EVMContract, + evm.ContractCode( + systemContracts.FlowToken.Address, + true, + ), + ), + } +} + +func mustHexAddress(hexAddress string) common.Address { + address, err := common.HexToAddress(hexAddress) + if err != nil { + panic(err) + } + return address +} diff --git a/cmd/util/ledger/migrations/change_contract_code_migration_test.go b/cmd/util/ledger/migrations/change_contract_code_migration_test.go new file mode 100644 index 00000000000..1f499115dda --- /dev/null +++ b/cmd/util/ledger/migrations/change_contract_code_migration_test.go @@ -0,0 +1,221 @@ +package migrations_test + +import ( + "context" + "testing" + + "github.com/onflow/cadence/runtime/common" + "github.com/rs/zerolog" + "github.com/stretchr/testify/require" + + "github.com/onflow/flow-go/cmd/util/ledger/migrations" + "github.com/onflow/flow-go/ledger" + "github.com/onflow/flow-go/ledger/common/convert" + "github.com/onflow/flow-go/model/flow" +) + +func newContractPayload(address common.Address, contractName string, contract []byte) *ledger.Payload { + return ledger.NewPayload( + convert.RegisterIDToLedgerKey( + flow.ContractRegisterID(flow.ConvertAddress(address), contractName), + ), + contract, + ) +} + +func TestChangeContractCodeMigration(t *testing.T) { + t.Parallel() + + address1, err := common.HexToAddress("0x1") + require.NoError(t, err) + + address2, err := common.HexToAddress("0x2") + require.NoError(t, err) + + ctx := context.Background() + + t.Run("no contracts", func(t *testing.T) { + t.Parallel() + + migration := migrations.ChangeContractCodeMigration{} + log := zerolog.New(zerolog.NewTestWriter(t)) + err := migration.InitMigration(log, nil, 0) + require.NoError(t, err) + + _, err = migration.MigrateAccount(ctx, address1, + []*ledger.Payload{}, + ) + + require.NoError(t, err) + + err = migration.Close() + require.NoError(t, err) + }) + + t.Run("1 contract - dont migrate", func(t *testing.T) { + t.Parallel() + + migration := migrations.ChangeContractCodeMigration{} + log := zerolog.New(zerolog.NewTestWriter(t)) + err := migration.InitMigration(log, nil, 0) + require.NoError(t, err) + + payloads, err := migration.MigrateAccount(ctx, address1, + []*ledger.Payload{ + newContractPayload(address1, "A", []byte("A")), + }, + ) + + require.NoError(t, err) + require.Len(t, payloads, 1) + require.Equal(t, []byte("A"), []byte(payloads[0].Value())) + + err = migration.Close() + require.NoError(t, err) + }) + + t.Run("1 contract - migrate", func(t *testing.T) { + t.Parallel() + + migration := migrations.ChangeContractCodeMigration{} + log := zerolog.New(zerolog.NewTestWriter(t)) + err := migration.InitMigration(log, nil, 0) + require.NoError(t, err) + + migration.RegisterContractChange(address1, "A", "B") + + payloads, err := migration.MigrateAccount(ctx, address1, + []*ledger.Payload{ + newContractPayload(address1, "A", []byte("A")), + }, + ) + + require.NoError(t, err) + require.Len(t, payloads, 1) + require.Equal(t, []byte("B"), []byte(payloads[0].Value())) + + err = migration.Close() + require.NoError(t, err) + }) + + t.Run("2 contracts - migrate 1", func(t *testing.T) { + t.Parallel() + + migration := migrations.ChangeContractCodeMigration{} + log := zerolog.New(zerolog.NewTestWriter(t)) + err := migration.InitMigration(log, nil, 0) + require.NoError(t, err) + + migration.RegisterContractChange(address1, "A", "B") + + payloads, err := migration.MigrateAccount(ctx, address1, + []*ledger.Payload{ + newContractPayload(address1, "A", []byte("A")), + newContractPayload(address1, "B", []byte("A")), + }, + ) + + require.NoError(t, err) + require.Len(t, payloads, 2) + require.Equal(t, []byte("B"), []byte(payloads[0].Value())) + require.Equal(t, []byte("A"), []byte(payloads[1].Value())) + + err = migration.Close() + require.NoError(t, err) + }) + + t.Run("2 contracts - migrate 2", func(t *testing.T) { + t.Parallel() + + migration := migrations.ChangeContractCodeMigration{} + log := zerolog.New(zerolog.NewTestWriter(t)) + err := migration.InitMigration(log, nil, 0) + require.NoError(t, err) + + migration.RegisterContractChange(address1, "A", "B") + migration.RegisterContractChange(address1, "B", "B") + + payloads, err := migration.MigrateAccount(ctx, address1, + []*ledger.Payload{ + newContractPayload(address1, "A", []byte("A")), + newContractPayload(address1, "B", []byte("A")), + }, + ) + + require.NoError(t, err) + require.Len(t, payloads, 2) + require.Equal(t, []byte("B"), []byte(payloads[0].Value())) + require.Equal(t, []byte("B"), []byte(payloads[1].Value())) + + err = migration.Close() + require.NoError(t, err) + }) + + t.Run("2 contracts on different accounts - migrate 1", func(t *testing.T) { + t.Parallel() + + migration := migrations.ChangeContractCodeMigration{} + log := zerolog.New(zerolog.NewTestWriter(t)) + err := migration.InitMigration(log, nil, 0) + require.NoError(t, err) + + migration.RegisterContractChange(address1, "A", "B") + + payloads, err := migration.MigrateAccount(ctx, address1, + []*ledger.Payload{ + newContractPayload(address1, "A", []byte("A")), + newContractPayload(address2, "A", []byte("A")), + }, + ) + + require.NoError(t, err) + require.Len(t, payloads, 2) + require.Equal(t, []byte("B"), []byte(payloads[0].Value())) + require.Equal(t, []byte("A"), []byte(payloads[1].Value())) + + err = migration.Close() + require.NoError(t, err) + }) + + t.Run("not all contracts on one account migrated", func(t *testing.T) { + t.Parallel() + + migration := migrations.ChangeContractCodeMigration{} + log := zerolog.New(zerolog.NewTestWriter(t)) + err := migration.InitMigration(log, nil, 0) + require.NoError(t, err) + + migration.RegisterContractChange(address1, "A", "B") + migration.RegisterContractChange(address1, "B", "B") + + _, err = migration.MigrateAccount(ctx, address1, + []*ledger.Payload{ + newContractPayload(address1, "A", []byte("A")), + }, + ) + + require.Error(t, err) + }) + + t.Run("not all accounts migrated", func(t *testing.T) { + t.Parallel() + + migration := migrations.ChangeContractCodeMigration{} + log := zerolog.New(zerolog.NewTestWriter(t)) + err := migration.InitMigration(log, nil, 0) + require.NoError(t, err) + + migration.RegisterContractChange(address2, "A", "B") + + _, err = migration.MigrateAccount(ctx, address1, + []*ledger.Payload{ + newContractPayload(address1, "A", []byte("A")), + }, + ) + + require.NoError(t, err) + + err = migration.Close() + require.Error(t, err) + }) +} diff --git a/cmd/util/ledger/migrations/deduplicate_contract_names_migration.go b/cmd/util/ledger/migrations/deduplicate_contract_names_migration.go new file mode 100644 index 00000000000..ab35e04d8a3 --- /dev/null +++ b/cmd/util/ledger/migrations/deduplicate_contract_names_migration.go @@ -0,0 +1,133 @@ +package migrations + +import ( + "context" + "fmt" + + "github.com/fxamacker/cbor/v2" + "github.com/rs/zerolog" + + "github.com/onflow/cadence/runtime/common" + + "github.com/onflow/flow-go/ledger" + "github.com/onflow/flow-go/ledger/common/convert" + "github.com/onflow/flow-go/model/flow" +) + +// DeduplicateContractNamesMigration checks if the contract names have been duplicated and +// removes the duplicate ones. +// +// This migration de-syncs storage used, so it should be run before the StorageUsedMigration. +type DeduplicateContractNamesMigration struct { + log zerolog.Logger +} + +func (d *DeduplicateContractNamesMigration) Close() error { + return nil +} + +func (d *DeduplicateContractNamesMigration) InitMigration( + log zerolog.Logger, + _ []*ledger.Payload, + _ int, +) error { + d.log = log. + With(). + Str("migration", "DeduplicateContractNamesMigration"). + Logger() + + return nil +} + +func (d *DeduplicateContractNamesMigration) MigrateAccount( + ctx context.Context, + address common.Address, + payloads []*ledger.Payload, +) ([]*ledger.Payload, error) { + flowAddress := flow.ConvertAddress(address) + contractNamesID := flow.ContractNamesRegisterID(flowAddress) + + var contractNamesPayload *ledger.Payload + contractNamesPayloadIndex := 0 + for i, payload := range payloads { + key, err := payload.Key() + if err != nil { + return nil, err + } + id, err := convert.LedgerKeyToRegisterID(key) + if err != nil { + return nil, err + } + if id == contractNamesID { + contractNamesPayload = payload + contractNamesPayloadIndex = i + break + } + } + if contractNamesPayload == nil { + return payloads, nil + } + + value := contractNamesPayload.Value() + if len(value) == 0 { + // Remove the empty payload + copy(payloads[contractNamesPayloadIndex:], payloads[contractNamesPayloadIndex+1:]) + payloads = payloads[:len(payloads)-1] + + return payloads, nil + } + + var contractNames []string + err := cbor.Unmarshal(value, &contractNames) + if err != nil { + return nil, fmt.Errorf("failed to get contract names: %w", err) + } + + var foundDuplicate bool + i := 1 + for i < len(contractNames) { + if contractNames[i-1] != contractNames[i] { + + if contractNames[i-1] > contractNames[i] { + // this is not a valid state and we should fail. + // Contract names must be sorted by definition. + return nil, fmt.Errorf( + "contract names for account %s are not sorted: %s", + address.Hex(), + contractNames, + ) + } + + i++ + continue + } + // Found duplicate (contactNames[i-1] == contactNames[i]) + // Remove contractNames[i] + copy(contractNames[i:], contractNames[i+1:]) + contractNames = contractNames[:len(contractNames)-1] + foundDuplicate = true + } + + if !foundDuplicate { + return payloads, nil + } + + d.log.Info(). + Str("address", address.Hex()). + Strs("contract_names", contractNames). + Msg("removing duplicate contract names") + + newContractNames, err := cbor.Marshal(contractNames) + if err != nil { + return nil, fmt.Errorf( + "cannot encode contract names: %s", + contractNames, + ) + } + + payloads[contractNamesPayloadIndex] = ledger.NewPayload(convert.RegisterIDToLedgerKey(contractNamesID), newContractNames) + return payloads, nil + +} + +var _ AccountBasedMigration = &DeduplicateContractNamesMigration{} diff --git a/cmd/util/ledger/migrations/deduplicate_contract_names_migration_test.go b/cmd/util/ledger/migrations/deduplicate_contract_names_migration_test.go new file mode 100644 index 00000000000..ba81bc826cd --- /dev/null +++ b/cmd/util/ledger/migrations/deduplicate_contract_names_migration_test.go @@ -0,0 +1,221 @@ +package migrations_test + +import ( + "context" + "fmt" + "math/rand" + "sort" + "testing" + + "github.com/fxamacker/cbor/v2" + "github.com/rs/zerolog" + "github.com/stretchr/testify/require" + + "github.com/onflow/cadence/runtime/common" + + "github.com/onflow/flow-go/cmd/util/ledger/migrations" + "github.com/onflow/flow-go/fvm/environment" + "github.com/onflow/flow-go/ledger" + "github.com/onflow/flow-go/ledger/common/convert" + "github.com/onflow/flow-go/model/flow" +) + +func TestDeduplicateContractNamesMigration(t *testing.T) { + migration := migrations.DeduplicateContractNamesMigration{} + log := zerolog.New(zerolog.NewTestWriter(t)) + err := migration.InitMigration(log, nil, 0) + require.NoError(t, err) + + address, err := common.HexToAddress("0x1") + require.NoError(t, err) + + ctx := context.Background() + + accountStatus := environment.NewAccountStatus() + accountStatus.SetStorageUsed(1000) + accountStatusPayload := ledger.NewPayload( + convert.RegisterIDToLedgerKey( + flow.AccountStatusRegisterID(flow.ConvertAddress(address)), + ), + accountStatus.ToBytes(), + ) + + contractNamesPayload := func(contractNames []byte) *ledger.Payload { + return ledger.NewPayload( + convert.RegisterIDToLedgerKey( + flow.RegisterID{ + Owner: string(address.Bytes()), + Key: flow.ContractNamesKey, + }, + ), + contractNames, + ) + } + + requireContractNames := func(payloads []*ledger.Payload, f func([]string)) { + for _, payload := range payloads { + key, err := payload.Key() + require.NoError(t, err) + id, err := convert.LedgerKeyToRegisterID(key) + require.NoError(t, err) + + if id.Key != flow.ContractNamesKey { + continue + } + + contracts := make([]string, 0) + err = cbor.Unmarshal(payload.Value(), &contracts) + require.NoError(t, err) + + f(contracts) + + } + } + + t.Run("no contract names", func(t *testing.T) { + payloads, err := migration.MigrateAccount(ctx, address, + []*ledger.Payload{ + accountStatusPayload, + }, + ) + + require.NoError(t, err) + require.Equal(t, 1, len(payloads)) + }) + + t.Run("one contract", func(t *testing.T) { + contractNames := []string{"test"} + newContractNames, err := cbor.Marshal(contractNames) + require.NoError(t, err) + + payloads, err := migration.MigrateAccount(ctx, address, + []*ledger.Payload{ + accountStatusPayload, + contractNamesPayload(newContractNames), + }, + ) + + require.NoError(t, err) + require.Equal(t, 2, len(payloads)) + + requireContractNames(payloads, func(contracts []string) { + require.Equal(t, 1, len(contracts)) + require.Equal(t, "test", contracts[0]) + }) + }) + + t.Run("two unique contracts", func(t *testing.T) { + contractNames := []string{"test", "test2"} + newContractNames, err := cbor.Marshal(contractNames) + require.NoError(t, err) + + payloads, err := migration.MigrateAccount(ctx, address, + []*ledger.Payload{ + accountStatusPayload, + contractNamesPayload(newContractNames), + }, + ) + + require.NoError(t, err) + require.Equal(t, 2, len(payloads)) + + requireContractNames(payloads, func(contracts []string) { + require.Equal(t, 2, len(contracts)) + require.Equal(t, "test", contracts[0]) + require.Equal(t, "test2", contracts[1]) + }) + }) + + t.Run("two contracts", func(t *testing.T) { + contractNames := []string{"test", "test"} + newContractNames, err := cbor.Marshal(contractNames) + require.NoError(t, err) + + payloads, err := migration.MigrateAccount(ctx, address, + []*ledger.Payload{ + accountStatusPayload, + contractNamesPayload(newContractNames), + }, + ) + + require.NoError(t, err) + require.Equal(t, 2, len(payloads)) + + requireContractNames(payloads, func(contracts []string) { + require.Equal(t, 1, len(contracts)) + require.Equal(t, "test", contracts[0]) + }) + }) + + t.Run("not sorted contracts", func(t *testing.T) { + contractNames := []string{"test2", "test"} + newContractNames, err := cbor.Marshal(contractNames) + require.NoError(t, err) + + _, err = migration.MigrateAccount(ctx, address, + []*ledger.Payload{ + accountStatusPayload, + contractNamesPayload(newContractNames), + }, + ) + + require.Error(t, err) + }) + + t.Run("duplicate contracts", func(t *testing.T) { + contractNames := []string{"test", "test", "test2", "test3", "test3"} + newContractNames, err := cbor.Marshal(contractNames) + require.NoError(t, err) + + payloads, err := migration.MigrateAccount(ctx, address, + []*ledger.Payload{ + accountStatusPayload, + contractNamesPayload(newContractNames), + }, + ) + + require.NoError(t, err) + require.Equal(t, 2, len(payloads)) + + requireContractNames(payloads, func(contracts []string) { + require.Equal(t, 3, len(contracts)) + require.Equal(t, "test", contracts[0]) + require.Equal(t, "test2", contracts[1]) + require.Equal(t, "test3", contracts[2]) + }) + }) + + t.Run("random contracts", func(t *testing.T) { + contractNames := make([]string, 1000) + uniqueContracts := 1 + for i := 0; i < 1000; i++ { + // i > 0 so it's easier to know how many unique contracts there are + if i > 0 && rand.Float32() < 0.5 { + uniqueContracts++ + } + contractNames[i] = fmt.Sprintf("test%d", uniqueContracts) + } + + // sort contractNames alphabetically, because they are not sorted + sort.Slice(contractNames, func(i, j int) bool { + return contractNames[i] < contractNames[j] + }) + + newContractNames, err := cbor.Marshal(contractNames) + require.NoError(t, err) + + payloads, err := migration.MigrateAccount(ctx, address, + []*ledger.Payload{ + accountStatusPayload, + contractNamesPayload(newContractNames), + }, + ) + + require.NoError(t, err) + require.Equal(t, 2, len(payloads)) + + requireContractNames(payloads, func(contracts []string) { + require.Equal(t, uniqueContracts, len(contracts)) + }) + }) +} diff --git a/cmd/util/ledger/migrations/migrator_runtime.go b/cmd/util/ledger/migrations/migrator_runtime.go new file mode 100644 index 00000000000..7157e705e7a --- /dev/null +++ b/cmd/util/ledger/migrations/migrator_runtime.go @@ -0,0 +1,84 @@ +package migrations + +import ( + "fmt" + + "github.com/onflow/cadence/runtime" + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/interpreter" + + "github.com/onflow/flow-go/cmd/util/ledger/util" + "github.com/onflow/flow-go/fvm/environment" + "github.com/onflow/flow-go/fvm/storage/state" + "github.com/onflow/flow-go/ledger" +) + +// migratorRuntime is a runtime that can be used to run a migration on a single account +func newMigratorRuntime( + address common.Address, + payloads []*ledger.Payload, +) ( + *migratorRuntime, + error, +) { + snapshot, err := util.NewPayloadSnapshot(payloads) + if err != nil { + return nil, fmt.Errorf("failed to create payload snapshot: %w", err) + } + transactionState := state.NewTransactionState(snapshot, state.DefaultParameters()) + accounts := environment.NewAccounts(transactionState) + + accountsAtreeLedger := util.NewAccountsAtreeLedger(accounts) + storage := runtime.NewStorage(accountsAtreeLedger, util.NopMemoryGauge{}) + + ri := &util.MigrationRuntimeInterface{ + Accounts: accounts, + } + + env := runtime.NewBaseInterpreterEnvironment(runtime.Config{ + AccountLinkingEnabled: true, + // Attachments are enabled everywhere except for Mainnet + AttachmentsEnabled: true, + // Capability Controllers are enabled everywhere except for Mainnet + CapabilityControllersEnabled: true, + }) + + env.Configure( + ri, + runtime.NewCodesAndPrograms(), + storage, + runtime.NewCoverageReport(), + ) + + inter, err := interpreter.NewInterpreter( + nil, + nil, + env.InterpreterConfig) + if err != nil { + return nil, err + } + + return &migratorRuntime{ + Address: address, + Payloads: payloads, + Snapshot: snapshot, + TransactionState: transactionState, + Interpreter: inter, + Storage: storage, + Accounts: accountsAtreeLedger, + }, nil +} + +type migratorRuntime struct { + Snapshot *util.PayloadSnapshot + TransactionState state.NestedTransactionPreparer + Interpreter *interpreter.Interpreter + Storage *runtime.Storage + Payloads []*ledger.Payload + Address common.Address + Accounts *util.AccountsAtreeLedger +} + +func (mr *migratorRuntime) GetReadOnlyStorage() *runtime.Storage { + return runtime.NewStorage(util.NewPayloadsReadonlyLedger(mr.Snapshot), util.NopMemoryGauge{}) +} diff --git a/cmd/util/ledger/migrations/storage_fees_migration.go b/cmd/util/ledger/migrations/storage_fees_migration.go deleted file mode 100644 index 6babd9ed279..00000000000 --- a/cmd/util/ledger/migrations/storage_fees_migration.go +++ /dev/null @@ -1,63 +0,0 @@ -package migrations - -import ( - fvm "github.com/onflow/flow-go/fvm/environment" - "github.com/onflow/flow-go/ledger" - "github.com/onflow/flow-go/ledger/common/convert" - "github.com/onflow/flow-go/ledger/common/utils" - "github.com/onflow/flow-go/model/flow" -) - -// iterates through registers keeping a map of register sizes -// after it has reached the end it add storage used and storage capacity for each address -func StorageFeesMigration(payload []ledger.Payload) ([]ledger.Payload, error) { - storageUsed := make(map[string]uint64) - newPayload := make([]ledger.Payload, len(payload)) - - for i, p := range payload { - err := incrementStorageUsed(p, storageUsed) - if err != nil { - return nil, err - } - newPayload[i] = p - } - - for s, u := range storageUsed { - // this is the storage used by the storage_used register we are about to add - id := flow.NewRegisterID( - string(flow.BytesToAddress([]byte(s)).Bytes()), - "storage_used") - storageUsedByStorageUsed := fvm.RegisterSize(id, make([]byte, 8)) - u = u + uint64(storageUsedByStorageUsed) - - newPayload = append(newPayload, *ledger.NewPayload( - convert.RegisterIDToLedgerKey(id), - utils.Uint64ToBinary(u), - )) - } - return newPayload, nil -} - -func incrementStorageUsed(p ledger.Payload, used map[string]uint64) error { - k, err := p.Key() - if err != nil { - return err - } - id, err := convert.LedgerKeyToRegisterID(k) - if err != nil { - return err - } - if len([]byte(id.Owner)) != flow.AddressLength { - // not an address - return nil - } - if _, ok := used[id.Owner]; !ok { - used[id.Owner] = 0 - } - used[id.Owner] = used[id.Owner] + uint64(registerSize(id, p)) - return nil -} - -func registerSize(id flow.RegisterID, p ledger.Payload) int { - return fvm.RegisterSize(id, p.Value()) -} diff --git a/cmd/util/ledger/migrations/storage_used_migration.go b/cmd/util/ledger/migrations/storage_used_migration.go new file mode 100644 index 00000000000..3e79d67fb22 --- /dev/null +++ b/cmd/util/ledger/migrations/storage_used_migration.go @@ -0,0 +1,151 @@ +package migrations + +import ( + "context" + "fmt" + + "github.com/rs/zerolog" + + "github.com/onflow/cadence/runtime/common" + + "github.com/onflow/flow-go/fvm/environment" + fvm "github.com/onflow/flow-go/fvm/environment" + "github.com/onflow/flow-go/ledger" + "github.com/onflow/flow-go/ledger/common/convert" + "github.com/onflow/flow-go/model/flow" +) + +// AccountUsageMigrator iterates through each payload, and calculate the storage usage +// and update the accounts status with the updated storage usage +type AccountUsageMigrator struct { + log zerolog.Logger +} + +var _ AccountBasedMigration = &AccountUsageMigrator{} + +func (m *AccountUsageMigrator) InitMigration( + log zerolog.Logger, + _ []*ledger.Payload, + _ int, +) error { + m.log = log.With().Str("component", "AccountUsageMigrator").Logger() + return nil +} + +const oldAccountStatusSize = 25 + +func (m *AccountUsageMigrator) Close() error { + return nil +} + +func (m *AccountUsageMigrator) MigrateAccount( + _ context.Context, + address common.Address, + payloads []*ledger.Payload, +) ([]*ledger.Payload, error) { + + var status *environment.AccountStatus + var statusIndex int + actualSize := uint64(0) + for i, payload := range payloads { + key, err := payload.Key() + if err != nil { + return nil, err + } + if isAccountKey(key) { + statusIndex = i + status, err = environment.AccountStatusFromBytes(payload.Value()) + if err != nil { + return nil, fmt.Errorf("could not parse account status: %w", err) + } + + } + + size, err := payloadSize(key, payload) + if err != nil { + return nil, err + } + actualSize += size + } + + if status == nil { + return nil, fmt.Errorf("could not find account status for account %v", address.Hex()) + } + + isOldVersionOfStatusRegister := len(payloads[statusIndex].Value()) == oldAccountStatusSize + + same := m.compareUsage(isOldVersionOfStatusRegister, status, actualSize) + if same { + // there is no problem with the usage, return + return payloads, nil + } + + if isOldVersionOfStatusRegister { + // size will grow by 8 bytes because of the on-the-fly + // migration of account status in AccountStatusFromBytes + actualSize += 8 + } + + // update storage used + status.SetStorageUsed(actualSize) + + newValue := status.ToBytes() + newPayload, err := newPayloadWithValue(payloads[statusIndex], newValue) + if err != nil { + return nil, fmt.Errorf("cannot create new payload with value: %w", err) + } + + payloads[statusIndex] = newPayload + + return payloads, nil +} + +func (m *AccountUsageMigrator) compareUsage( + isOldVersionOfStatusRegister bool, + status *environment.AccountStatus, + actualSize uint64, +) bool { + oldSize := status.StorageUsed() + if isOldVersionOfStatusRegister { + // size will be reported as 8 bytes larger than the actual size due to on-the-fly + // migration of account status in AccountStatusFromBytes + oldSize -= 8 + } + + if oldSize != actualSize { + m.log.Warn(). + Uint64("old_size", oldSize). + Uint64("new_size", actualSize). + Msg("account storage used usage mismatch") + return false + } + return true +} + +// newPayloadWithValue returns a new payload with the key from the given payload, and +// the value from the argument +func newPayloadWithValue(payload *ledger.Payload, value ledger.Value) (*ledger.Payload, error) { + key, err := payload.Key() + if err != nil { + return nil, err + } + newPayload := ledger.NewPayload(key, value) + return newPayload, nil +} + +func registerSize(id flow.RegisterID, p *ledger.Payload) int { + return fvm.RegisterSize(id, p.Value()) +} + +func payloadSize(key ledger.Key, payload *ledger.Payload) (uint64, error) { + id, err := convert.LedgerKeyToRegisterID(key) + if err != nil { + return 0, err + } + + return uint64(registerSize(id, payload)), nil +} + +func isAccountKey(key ledger.Key) bool { + return string(key.KeyParts[1].Value) == flow.AccountStatusKey +} diff --git a/cmd/util/ledger/migrations/test-data/bootstrapped_v0.31/.gitignore b/cmd/util/ledger/migrations/test-data/bootstrapped_v0.31/.gitignore new file mode 100644 index 00000000000..0342671c3bc --- /dev/null +++ b/cmd/util/ledger/migrations/test-data/bootstrapped_v0.31/.gitignore @@ -0,0 +1,4 @@ +* + +!.gitignore +!00000000 diff --git a/cmd/util/ledger/migrations/test-data/bootstrapped_v0.31/00000000 b/cmd/util/ledger/migrations/test-data/bootstrapped_v0.31/00000000 new file mode 100644 index 00000000000..48a6440b89f Binary files /dev/null and b/cmd/util/ledger/migrations/test-data/bootstrapped_v0.31/00000000 differ diff --git a/cmd/util/ledger/migrations/utils.go b/cmd/util/ledger/migrations/utils.go index 11510008f74..e747b3dc508 100644 --- a/cmd/util/ledger/migrations/utils.go +++ b/cmd/util/ledger/migrations/utils.go @@ -22,7 +22,7 @@ var _ atree.Ledger = &AccountsAtreeLedger{} func (a *AccountsAtreeLedger) GetValue(owner, key []byte) ([]byte, error) { v, err := a.Accounts.GetValue( flow.NewRegisterID( - string(flow.BytesToAddress(owner).Bytes()), + flow.BytesToAddress(owner), string(key))) if err != nil { return nil, fmt.Errorf("getting value failed: %w", err) @@ -33,7 +33,7 @@ func (a *AccountsAtreeLedger) GetValue(owner, key []byte) ([]byte, error) { func (a *AccountsAtreeLedger) SetValue(owner, key, value []byte) error { err := a.Accounts.SetValue( flow.NewRegisterID( - string(flow.BytesToAddress(owner).Bytes()), + flow.BytesToAddress(owner), string(key)), value) if err != nil { diff --git a/cmd/util/ledger/reporters/atree_reporter.go b/cmd/util/ledger/reporters/atree_reporter.go index 6d1be625125..39c005dbc55 100644 --- a/cmd/util/ledger/reporters/atree_reporter.go +++ b/cmd/util/ledger/reporters/atree_reporter.go @@ -117,7 +117,7 @@ func getPayloadType(p *ledger.Payload) (payloadType, error) { } id := flow.NewRegisterID( - string(k.KeyParts[0].Value), + flow.BytesToAddress(k.KeyParts[0].Value), string(k.KeyParts[1].Value)) if id.IsInternalState() { return fvmPayloadType, nil diff --git a/cmd/util/ledger/reporters/fungible_token_tracker.go b/cmd/util/ledger/reporters/fungible_token_tracker.go index 24a2c09ac56..a84d8282b7a 100644 --- a/cmd/util/ledger/reporters/fungible_token_tracker.go +++ b/cmd/util/ledger/reporters/fungible_token_tracker.go @@ -13,7 +13,7 @@ import ( "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/interpreter" - "github.com/onflow/flow-go/cmd/util/ledger/migrations" + "github.com/onflow/flow-go/cmd/util/ledger/util" "github.com/onflow/flow-go/fvm/environment" "github.com/onflow/flow-go/fvm/storage/state" "github.com/onflow/flow-go/fvm/systemcontracts" @@ -147,7 +147,7 @@ func (r *FungibleTokenTracker) worker( state.DefaultParameters()) accounts := environment.NewAccounts(txnState) storage := cadenceRuntime.NewStorage( - &migrations.AccountsAtreeLedger{Accounts: accounts}, + &util.AccountsAtreeLedger{Accounts: accounts}, nil, ) diff --git a/cmd/util/ledger/reporters/storage_snapshot.go b/cmd/util/ledger/reporters/storage_snapshot.go index b9ca42c1fe5..bf4698e0559 100644 --- a/cmd/util/ledger/reporters/storage_snapshot.go +++ b/cmd/util/ledger/reporters/storage_snapshot.go @@ -19,7 +19,7 @@ func NewStorageSnapshotFromPayload( } id := flow.NewRegisterID( - string(key.KeyParts[0].Value), + flow.BytesToAddress(key.KeyParts[0].Value), string(key.KeyParts[1].Value)) snapshot[id] = entry.Value() diff --git a/cmd/util/ledger/util/migration_runtime_interface.go b/cmd/util/ledger/util/migration_runtime_interface.go new file mode 100644 index 00000000000..c72d8493095 --- /dev/null +++ b/cmd/util/ledger/util/migration_runtime_interface.go @@ -0,0 +1,295 @@ +package util + +import ( + "fmt" + "time" + + "go.opentelemetry.io/otel/attribute" + + "github.com/onflow/atree" + "github.com/onflow/cadence" + "github.com/onflow/cadence/runtime" + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/interpreter" + + "github.com/onflow/flow-go/fvm/environment" + "github.com/onflow/flow-go/model/flow" +) + +// MigrationRuntimeInterface is a runtime interface that can be used in migrations. +type MigrationRuntimeInterface struct { + Accounts environment.Accounts + Programs *environment.Programs + + // GetOrLoadProgramFunc allows for injecting extra logic + GetOrLoadProgramFunc func(location runtime.Location, load func() (*interpreter.Program, error)) (*interpreter.Program, error) +} + +func (m MigrationRuntimeInterface) ResolveLocation( + identifiers []runtime.Identifier, + location runtime.Location, +) ([]runtime.ResolvedLocation, error) { + + addressLocation, isAddress := location.(common.AddressLocation) + + // if the location is not an address location, e.g. an identifier location (`import Crypto`), + // then return a single resolved location which declares all identifiers. + if !isAddress { + return []runtime.ResolvedLocation{ + { + Location: location, + Identifiers: identifiers, + }, + }, nil + } + + // if the location is an address, + // and no specific identifiers where requested in the import statement, + // then fetch all identifiers at this address + if len(identifiers) == 0 { + address := flow.Address(addressLocation.Address) + + contractNames, err := m.Accounts.GetContractNames(address) + if err != nil { + return nil, fmt.Errorf("ResolveLocation failed: %w", err) + } + + // if there are no contractNames deployed, + // then return no resolved locations + if len(contractNames) == 0 { + return nil, nil + } + + identifiers = make([]runtime.Identifier, len(contractNames)) + + for i := range identifiers { + identifiers[i] = runtime.Identifier{ + Identifier: contractNames[i], + } + } + } + + // return one resolved location per identifier. + // each resolved location is an address contract location + resolvedLocations := make([]runtime.ResolvedLocation, len(identifiers)) + for i := range resolvedLocations { + identifier := identifiers[i] + resolvedLocations[i] = runtime.ResolvedLocation{ + Location: common.AddressLocation{ + Address: addressLocation.Address, + Name: identifier.Identifier, + }, + Identifiers: []runtime.Identifier{identifier}, + } + } + + return resolvedLocations, nil +} + +func (m MigrationRuntimeInterface) GetCode(location runtime.Location) ([]byte, error) { + contractLocation, ok := location.(common.AddressLocation) + if !ok { + return nil, fmt.Errorf("GetCode failed: expected AddressLocation") + } + + add, err := m.Accounts.GetContract(contractLocation.Name, flow.Address(contractLocation.Address)) + if err != nil { + return nil, fmt.Errorf("GetCode failed: %w", err) + } + + return add, nil +} + +func (m MigrationRuntimeInterface) GetAccountContractCode( + l common.AddressLocation, +) (code []byte, err error) { + return m.Accounts.GetContract(l.Name, flow.Address(l.Address)) +} + +func (m MigrationRuntimeInterface) GetOrLoadProgram(location runtime.Location, load func() (*interpreter.Program, error)) (*interpreter.Program, error) { + if m.GetOrLoadProgramFunc != nil { + return m.GetOrLoadProgramFunc(location, load) + } + + return m.Programs.GetOrLoadProgram(location, load) +} + +func (m MigrationRuntimeInterface) MeterMemory(_ common.MemoryUsage) error { + return nil +} + +func (m MigrationRuntimeInterface) MeterComputation(_ common.ComputationKind, _ uint) error { + return nil +} + +func (m MigrationRuntimeInterface) GetValue(_, _ []byte) (value []byte, err error) { + panic("unexpected GetValue call") +} + +func (m MigrationRuntimeInterface) SetValue(_, _, _ []byte) (err error) { + panic("unexpected SetValue call") +} + +func (m MigrationRuntimeInterface) CreateAccount(_ runtime.Address) (address runtime.Address, err error) { + panic("unexpected CreateAccount call") +} + +func (m MigrationRuntimeInterface) AddEncodedAccountKey(_ runtime.Address, _ []byte) error { + panic("unexpected AddEncodedAccountKey call") +} + +func (m MigrationRuntimeInterface) RevokeEncodedAccountKey(_ runtime.Address, _ int) (publicKey []byte, err error) { + panic("unexpected RevokeEncodedAccountKey call") +} + +func (m MigrationRuntimeInterface) AddAccountKey(_ runtime.Address, _ *runtime.PublicKey, _ runtime.HashAlgorithm, _ int) (*runtime.AccountKey, error) { + panic("unexpected AddAccountKey call") +} + +func (m MigrationRuntimeInterface) GetAccountKey(_ runtime.Address, _ int) (*runtime.AccountKey, error) { + panic("unexpected GetAccountKey call") +} + +func (m MigrationRuntimeInterface) RevokeAccountKey(_ runtime.Address, _ int) (*runtime.AccountKey, error) { + panic("unexpected RevokeAccountKey call") +} + +func (m MigrationRuntimeInterface) UpdateAccountContractCode(_ common.AddressLocation, _ []byte) (err error) { + panic("unexpected UpdateAccountContractCode call") +} + +func (m MigrationRuntimeInterface) RemoveAccountContractCode(common.AddressLocation) (err error) { + panic("unexpected RemoveAccountContractCode call") +} + +func (m MigrationRuntimeInterface) GetSigningAccounts() ([]runtime.Address, error) { + panic("unexpected GetSigningAccounts call") +} + +func (m MigrationRuntimeInterface) ProgramLog(_ string) error { + panic("unexpected ProgramLog call") +} + +func (m MigrationRuntimeInterface) EmitEvent(_ cadence.Event) error { + panic("unexpected EmitEvent call") +} + +func (m MigrationRuntimeInterface) ValueExists(_, _ []byte) (exists bool, err error) { + panic("unexpected ValueExists call") +} + +func (m MigrationRuntimeInterface) GenerateUUID() (uint64, error) { + panic("unexpected GenerateUUID call") +} + +func (m MigrationRuntimeInterface) GetComputationLimit() uint64 { + panic("unexpected GetComputationLimit call") +} + +func (m MigrationRuntimeInterface) SetComputationUsed(_ uint64) error { + panic("unexpected SetComputationUsed call") +} + +func (m MigrationRuntimeInterface) DecodeArgument(_ []byte, _ cadence.Type) (cadence.Value, error) { + panic("unexpected DecodeArgument call") +} + +func (m MigrationRuntimeInterface) GetCurrentBlockHeight() (uint64, error) { + panic("unexpected GetCurrentBlockHeight call") +} + +func (m MigrationRuntimeInterface) GetBlockAtHeight(_ uint64) (block runtime.Block, exists bool, err error) { + panic("unexpected GetBlockAtHeight call") +} + +func (m MigrationRuntimeInterface) ReadRandom([]byte) error { + panic("unexpected ReadRandom call") +} + +func (m MigrationRuntimeInterface) VerifySignature(_ []byte, _ string, _ []byte, _ []byte, _ runtime.SignatureAlgorithm, _ runtime.HashAlgorithm) (bool, error) { + panic("unexpected VerifySignature call") +} + +func (m MigrationRuntimeInterface) Hash(_ []byte, _ string, _ runtime.HashAlgorithm) ([]byte, error) { + panic("unexpected Hash call") +} + +func (m MigrationRuntimeInterface) GetAccountBalance(_ common.Address) (value uint64, err error) { + panic("unexpected GetAccountBalance call") +} + +func (m MigrationRuntimeInterface) GetAccountAvailableBalance(_ common.Address) (value uint64, err error) { + panic("unexpected GetAccountAvailableBalance call") +} + +func (m MigrationRuntimeInterface) GetStorageUsed(_ runtime.Address) (value uint64, err error) { + panic("unexpected GetStorageUsed call") +} + +func (m MigrationRuntimeInterface) GetStorageCapacity(_ runtime.Address) (value uint64, err error) { + panic("unexpected GetStorageCapacity call") +} + +func (m MigrationRuntimeInterface) ImplementationDebugLog(_ string) error { + panic("unexpected ImplementationDebugLog call") +} + +func (m MigrationRuntimeInterface) ValidatePublicKey(_ *runtime.PublicKey) error { + panic("unexpected ValidatePublicKey call") +} + +func (m MigrationRuntimeInterface) GetAccountContractNames(_ runtime.Address) ([]string, error) { + panic("unexpected GetAccountContractNames call") +} + +func (m MigrationRuntimeInterface) AllocateStorageIndex(_ []byte) (atree.StorageIndex, error) { + panic("unexpected AllocateStorageIndex call") +} + +func (m MigrationRuntimeInterface) ComputationUsed() (uint64, error) { + panic("unexpected ComputationUsed call") +} + +func (m MigrationRuntimeInterface) MemoryUsed() (uint64, error) { + panic("unexpected MemoryUsed call") +} + +func (m MigrationRuntimeInterface) InteractionUsed() (uint64, error) { + panic("unexpected InteractionUsed call") +} + +func (m MigrationRuntimeInterface) SetInterpreterSharedState(_ *interpreter.SharedState) { + panic("unexpected SetInterpreterSharedState call") +} + +func (m MigrationRuntimeInterface) GetInterpreterSharedState() *interpreter.SharedState { + panic("unexpected GetInterpreterSharedState call") +} + +func (m MigrationRuntimeInterface) AccountKeysCount(_ runtime.Address) (uint64, error) { + panic("unexpected AccountKeysCount call") +} + +func (m MigrationRuntimeInterface) BLSVerifyPOP(_ *runtime.PublicKey, _ []byte) (bool, error) { + panic("unexpected BLSVerifyPOP call") +} + +func (m MigrationRuntimeInterface) BLSAggregateSignatures(_ [][]byte) ([]byte, error) { + panic("unexpected BLSAggregateSignatures call") +} + +func (m MigrationRuntimeInterface) BLSAggregatePublicKeys(_ []*runtime.PublicKey) (*runtime.PublicKey, error) { + panic("unexpected BLSAggregatePublicKeys call") +} + +func (m MigrationRuntimeInterface) ResourceOwnerChanged(_ *interpreter.Interpreter, _ *interpreter.CompositeValue, _ common.Address, _ common.Address) { + panic("unexpected ResourceOwnerChanged call") +} + +func (m MigrationRuntimeInterface) GenerateAccountID(_ common.Address) (uint64, error) { + panic("unexpected GenerateAccountID call") +} + +func (m MigrationRuntimeInterface) RecordTrace(_ string, _ runtime.Location, _ time.Duration, _ []attribute.KeyValue) { + panic("unexpected RecordTrace call") +} diff --git a/cmd/util/ledger/util/nop_meter.go b/cmd/util/ledger/util/nop_meter.go new file mode 100644 index 00000000000..dc8a9c2ac6b --- /dev/null +++ b/cmd/util/ledger/util/nop_meter.go @@ -0,0 +1,49 @@ +package util + +import ( + "github.com/onflow/cadence/runtime/common" + + "github.com/onflow/flow-go/fvm/environment" + "github.com/onflow/flow-go/fvm/meter" +) + +// NopMeter is a meter that does nothing. It can be used in migrations. +type NopMeter struct{} + +func (n NopMeter) ComputationAvailable(_ common.ComputationKind, _ uint) bool { + return false +} + +func (n NopMeter) MeterComputation(_ common.ComputationKind, _ uint) error { + return nil +} + +func (n NopMeter) ComputationUsed() (uint64, error) { + return 0, nil +} + +func (n NopMeter) ComputationIntensities() meter.MeteredComputationIntensities { + return meter.MeteredComputationIntensities{} +} + +func (n NopMeter) MeterMemory(_ common.MemoryUsage) error { + return nil +} + +func (n NopMeter) MemoryUsed() (uint64, error) { + return 0, nil +} + +func (n NopMeter) MeterEmittedEvent(_ uint64) error { + return nil +} + +func (n NopMeter) TotalEmittedEventBytes() uint64 { + return 0 +} + +func (n NopMeter) InteractionUsed() (uint64, error) { + return 0, nil +} + +var _ environment.Meter = NopMeter{} diff --git a/cmd/util/ledger/util/payload_grouping.go b/cmd/util/ledger/util/payload_grouping.go new file mode 100644 index 00000000000..9aec5d76efa --- /dev/null +++ b/cmd/util/ledger/util/payload_grouping.go @@ -0,0 +1,275 @@ +package util + +import ( + "bytes" + "fmt" + "sort" + "sync" + "time" + + "github.com/rs/zerolog" + + "github.com/onflow/cadence/runtime/common" + + "github.com/onflow/flow-go/ledger" + "github.com/onflow/flow-go/ledger/common/convert" + "github.com/onflow/flow-go/model/flow" +) + +// encodedKeyAddressPrefixLength is the length of the address prefix in the encoded key +// 2 for uint16 of number of key parts +// 4 for uint32 of the length of the first key part +// 2 for uint16 of the key part type +// 8 for the address which is the actual length of the first key part +const encodedKeyAddressPrefixLength = 2 + 4 + 2 + flow.AddressLength + +// minSizeForSplitSortingIntoGoroutines below this size, no need to split +// the sorting into goroutines +const minSizeForSplitSortingIntoGoroutines = 100_000 + +const estimatedNumOfAccount = 30_000_000 + +// PayloadAccountGroup is a grouping of payloads by account +type PayloadAccountGroup struct { + Address common.Address + Payloads []*ledger.Payload +} + +// PayloadAccountGrouping is a grouping of payloads by account. +type PayloadAccountGrouping struct { + payloads sortablePayloads + indexes []int + + current int +} + +// Next returns the next account group. If there is no more account group, it returns nil. +// The zero address is used for global Payloads and is not an actual account. +func (g *PayloadAccountGrouping) Next() (*PayloadAccountGroup, error) { + if g.current == len(g.indexes) { + // reached the end + return nil, nil + } + + accountStartIndex := g.indexes[g.current] + accountEndIndex := len(g.payloads) + if g.current != len(g.indexes)-1 { + accountEndIndex = g.indexes[g.current+1] + } + g.current++ + + address, err := payloadToAddress(g.payloads[accountStartIndex]) + if err != nil { + return nil, fmt.Errorf("failed to get address from payload: %w", err) + } + + return &PayloadAccountGroup{ + Address: address, + Payloads: g.payloads[accountStartIndex:accountEndIndex], + }, nil +} + +// Len returns the number of accounts +func (g *PayloadAccountGrouping) Len() int { + return len(g.indexes) +} + +// AllPayloadsCount the number of payloads +func (g *PayloadAccountGrouping) AllPayloadsCount() int { + return len(g.payloads) +} + +// GroupPayloadsByAccount takes a list of payloads and groups them by account. +// it uses nWorkers to sort the payloads by address and find the start and end indexes of +// each account. +func GroupPayloadsByAccount( + log zerolog.Logger, + payloads []*ledger.Payload, + nWorkers int, +) *PayloadAccountGrouping { + if len(payloads) == 0 { + return &PayloadAccountGrouping{} + } + p := sortablePayloads(payloads) + + start := time.Now() + log.Info(). + Int("payloads", len(payloads)). + Int("workers", nWorkers). + Msg("Sorting payloads by address") + + // sort the payloads by address + sortPayloads(0, len(p), p, make(sortablePayloads, len(p)), nWorkers) + end := time.Now() + + log.Info(). + Int("payloads", len(payloads)). + Str("duration", end.Sub(start).Round(1*time.Second).String()). + Msg("Sorted. Finding account boundaries in sorted payloads") + + start = time.Now() + // find the indexes of the payloads that start a new account + indexes := make([]int, 0, estimatedNumOfAccount) + for i := 0; i < len(p); { + indexes = append(indexes, i) + i = p.FindNextKeyIndex(i) + } + end = time.Now() + + log.Info(). + Int("accounts", len(indexes)). + Str("duration", end.Sub(start).Round(1*time.Second).String()). + Msg("Done grouping payloads by account") + + return &PayloadAccountGrouping{ + payloads: p, + indexes: indexes, + } +} + +// payloadToAddress takes a payload and return: +// - (address, nil) if the payload is for an account, the account address is returned +// - (common.ZeroAddress, nil) if the payload is not for an account +// - (common.ZeroAddress, err) if running into any exception +// The zero address is used for global Payloads and is not an actual account +func payloadToAddress(p *ledger.Payload) (common.Address, error) { + k, err := p.Key() + if err != nil { + return common.ZeroAddress, fmt.Errorf("could not find key for payload: %w", err) + } + + id, err := convert.LedgerKeyToRegisterID(k) + if err != nil { + return common.ZeroAddress, fmt.Errorf("error converting key to register ID") + } + + if len([]byte(id.Owner)) != flow.AddressLength { + return common.ZeroAddress, nil + } + + address, err := common.BytesToAddress([]byte(id.Owner)) + if err != nil { + return common.ZeroAddress, fmt.Errorf("invalid account address: %w", err) + } + + return address, nil +} + +type sortablePayloads []*ledger.Payload + +func (s sortablePayloads) Len() int { + return len(s) +} + +func (s sortablePayloads) Less(i, j int) bool { + return s.Compare(i, j) < 0 +} + +func (s sortablePayloads) Compare(i, j int) int { + // sort descending to force one of the big accounts to be more at the beginning + return bytes.Compare( + s[j].EncodedKey()[:encodedKeyAddressPrefixLength], + s[i].EncodedKey()[:encodedKeyAddressPrefixLength], + ) +} + +func (s sortablePayloads) Swap(i, j int) { + s[i], s[j] = s[j], s[i] +} + +func (s sortablePayloads) FindNextKeyIndex(i int) int { + low := i + step := 1 + for low+step < len(s) && s.Compare(low+step, i) == 0 { + low += step + step *= 2 + } + + high := low + step + if high > len(s) { + high = len(s) + } + + for low < high { + mid := (low + high) / 2 + if s.Compare(mid, i) == 0 { + low = mid + 1 + } else { + high = mid + } + } + + return low +} + +// sortPayloads sorts the payloads in the range [i, j) using goroutines and merges +// the results using the intermediate buffer. The goroutine allowance is the number +// of goroutines that can be used for sorting. If the allowance is less than 2, +// the payloads are sorted using the built-in sort. +// The buffer must be of the same length as the source and can be disposed after. +func sortPayloads(i, j int, source, buffer sortablePayloads, goroutineAllowance int) { + // if the length is less than 2, no need to sort + if j-i <= 1 { + return + } + + // if we are out of goroutine allowance, sort with built-in sort + // if the length is less than minSizeForSplit, sort with built-in sort + if goroutineAllowance < 2 || j-i < minSizeForSplitSortingIntoGoroutines { + sort.Sort(source[i:j]) + return + } + + goroutineAllowance -= 2 + allowance1 := goroutineAllowance / 2 + allowance2 := goroutineAllowance - allowance1 + mid := (i + j) / 2 + + wg := sync.WaitGroup{} + wg.Add(2) + go func() { + sortPayloads(i, mid, source, buffer, allowance1) + wg.Done() + }() + go func() { + sortPayloads(mid, j, source, buffer, allowance2) + wg.Done() + }() + wg.Wait() + + mergeInto(source, buffer, i, mid, j) +} + +func mergeInto(source, buffer sortablePayloads, i int, mid int, j int) { + left := i + right := mid + k := i + for left < mid && right < j { + // More elements in the both partitions to process. + if source.Compare(left, right) <= 0 { + // Move left partition elements with the same address to buffer. + nextLeft := source.FindNextKeyIndex(left) + n := copy(buffer[k:], source[left:nextLeft]) + left = nextLeft + k += n + } else { + // Move right partition elements with the same address to buffer. + nextRight := source.FindNextKeyIndex(right) + n := copy(buffer[k:], source[right:nextRight]) + right = nextRight + k += n + } + } + // At this point: + // - one partition is exhausted. + // - remaining elements in the other partition (already sorted) can be copied over. + if left < mid { + // Copy remaining elements in the left partition. + copy(buffer[k:], source[left:mid]) + } else { + // Copy remaining elements in the right partition. + copy(buffer[k:], source[right:j]) + } + // Copy merged buffer back to source. + copy(source[i:j], buffer[i:j]) +} diff --git a/cmd/util/ledger/util/payload_grouping_test.go b/cmd/util/ledger/util/payload_grouping_test.go new file mode 100644 index 00000000000..96b50bd4e5b --- /dev/null +++ b/cmd/util/ledger/util/payload_grouping_test.go @@ -0,0 +1,155 @@ +package util_test + +import ( + "crypto/rand" + "encoding/hex" + rand2 "math/rand" + "runtime" + "testing" + + "github.com/rs/zerolog" + "github.com/stretchr/testify/require" + + "github.com/onflow/cadence/runtime/common" + + "github.com/onflow/flow-go/cmd/util/ledger/util" + "github.com/onflow/flow-go/ledger" + "github.com/onflow/flow-go/ledger/common/convert" + "github.com/onflow/flow-go/model/flow" +) + +func TestGroupPayloadsByAccount(t *testing.T) { + log := zerolog.New(zerolog.NewTestWriter(t)) + payloads := generateRandomPayloads(1000000) + tmp := make([]*ledger.Payload, len(payloads)) + copy(tmp, payloads) + + groups := util.GroupPayloadsByAccount(log, payloads, 0) + + require.Greater(t, groups.Len(), 1) +} + +func TestGroupPayloadsByAccountCompareResults(t *testing.T) { + log := zerolog.Nop() + payloads := generateRandomPayloads(1000000) + tmp1 := make([]*ledger.Payload, len(payloads)) + tmp2 := make([]*ledger.Payload, len(payloads)) + copy(tmp1, payloads) + copy(tmp2, payloads) + + groups1 := util.GroupPayloadsByAccount(log, tmp1, 0) + groups2 := util.GroupPayloadsByAccount(log, tmp2, runtime.NumCPU()) + + groups3 := map[common.Address][]*ledger.Payload{} + for _, payload := range payloads { + key, err := payload.Key() + require.NoError(t, err) + registerID, err := convert.LedgerKeyToRegisterID(key) + require.NoError(t, err) + address, err := common.BytesToAddress([]byte(registerID.Owner)) + require.NoError(t, err) + if _, ok := groups3[address]; !ok { + groups3[address] = []*ledger.Payload{} + } + groups3[address] = append(groups3[address], payload) + } + + require.Equal(t, groups1.Len(), groups2.Len()) + require.Equal(t, groups1.Len(), len(groups3)) + for { + group1, err1 := groups1.Next() + group2, err2 := groups2.Next() + + require.NoError(t, err1) + require.NoError(t, err2) + + if group1 == nil { + require.Nil(t, group2) + break + } + + require.Equal(t, group1.Address, group2.Address) + require.Equal(t, len(group1.Payloads), len(group2.Payloads)) + require.ElementsMatch(t, group1.Payloads, group2.Payloads) + require.Equal(t, len(group1.Payloads), len(groups3[group1.Address])) + require.ElementsMatch(t, group1.Payloads, groups3[group1.Address]) + } +} + +func BenchmarkGroupPayloadsByAccount(b *testing.B) { + log := zerolog.Nop() + payloads := generateRandomPayloads(10000000) + tmp := make([]*ledger.Payload, len(payloads)) + + bench := func(b *testing.B, nWorker int) func(b *testing.B) { + return func(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + b.StopTimer() + copy(tmp, payloads) + b.StartTimer() + util.GroupPayloadsByAccount(log, tmp, nWorker) + } + } + } + + b.Run("1 worker", bench(b, 1)) + b.Run("2 worker", bench(b, 2)) + b.Run("4 worker", bench(b, 4)) + b.Run("8 worker", bench(b, 8)) + b.Run("max worker", bench(b, runtime.NumCPU())) +} + +// GeneratePayloads generates n random payloads +// with a random number of payloads per account (exponentially distributed) +func generateRandomPayloads(n int) []*ledger.Payload { + const meanPayloadsPerAccount = 100 + const minPayloadsPerAccount = 1 + + payloads := make([]*ledger.Payload, 0, n) + + for i := 0; i < n; { + + registersForAccount := minPayloadsPerAccount + int(rand2.ExpFloat64()*(meanPayloadsPerAccount-minPayloadsPerAccount)) + if registersForAccount > n-i { + registersForAccount = n - i + } + i += registersForAccount + + accountKey := generateRandomAccountKey() + for j := 0; j < registersForAccount; j++ { + payloads = append(payloads, + ledger.NewPayload( + accountKey, + []byte(generateRandomString(10)), + )) + } + } + + return payloads +} + +func generateRandomAccountKey() ledger.Key { + return convert.RegisterIDToLedgerKey(flow.RegisterID{ + Owner: generateRandomAddress(), + Key: generateRandomString(10), + }) +} + +func generateRandomString(i int) string { + buf := make([]byte, i) + _, err := rand.Read(buf) + if err != nil { + panic(err) + } + return hex.EncodeToString(buf) +} + +func generateRandomAddress() string { + buf := make([]byte, flow.AddressLength) + _, err := rand.Read(buf) + if err != nil { + panic(err) + } + return string(buf) +} diff --git a/cmd/util/ledger/util/util.go b/cmd/util/ledger/util/util.go new file mode 100644 index 00000000000..46cc54e6850 --- /dev/null +++ b/cmd/util/ledger/util/util.go @@ -0,0 +1,154 @@ +package util + +import ( + "fmt" + + "github.com/onflow/atree" + "github.com/onflow/cadence/runtime/common" + + "github.com/onflow/flow-go/fvm/environment" + "github.com/onflow/flow-go/fvm/storage/snapshot" + "github.com/onflow/flow-go/ledger" + "github.com/onflow/flow-go/ledger/common/convert" + "github.com/onflow/flow-go/model/flow" +) + +type AccountsAtreeLedger struct { + Accounts environment.Accounts +} + +func NewAccountsAtreeLedger(accounts environment.Accounts) *AccountsAtreeLedger { + return &AccountsAtreeLedger{Accounts: accounts} +} + +var _ atree.Ledger = &AccountsAtreeLedger{} + +func (a *AccountsAtreeLedger) GetValue(owner, key []byte) ([]byte, error) { + v, err := a.Accounts.GetValue( + flow.NewRegisterID( + flow.BytesToAddress(owner), + string(key))) + if err != nil { + return nil, fmt.Errorf("getting value failed: %w", err) + } + return v, nil +} + +func (a *AccountsAtreeLedger) SetValue(owner, key, value []byte) error { + err := a.Accounts.SetValue( + flow.NewRegisterID( + flow.BytesToAddress(owner), + string(key)), + value) + if err != nil { + return fmt.Errorf("setting value failed: %w", err) + } + return nil +} + +func (a *AccountsAtreeLedger) ValueExists(owner, key []byte) (exists bool, err error) { + v, err := a.GetValue(owner, key) + if err != nil { + return false, fmt.Errorf("checking value existence failed: %w", err) + } + + return len(v) > 0, nil +} + +// AllocateStorageIndex allocates new storage index under the owner accounts to store a new register +func (a *AccountsAtreeLedger) AllocateStorageIndex(owner []byte) (atree.StorageIndex, error) { + v, err := a.Accounts.AllocateStorageIndex(flow.BytesToAddress(owner)) + if err != nil { + return atree.StorageIndex{}, fmt.Errorf("storage index allocation failed: %w", err) + } + return v, nil +} + +type PayloadSnapshot struct { + Payloads map[flow.RegisterID]*ledger.Payload +} + +var _ snapshot.StorageSnapshot = (*PayloadSnapshot)(nil) + +func NewPayloadSnapshot(payloads []*ledger.Payload) (*PayloadSnapshot, error) { + l := &PayloadSnapshot{ + Payloads: make(map[flow.RegisterID]*ledger.Payload, len(payloads)), + } + for _, payload := range payloads { + key, err := payload.Key() + if err != nil { + return nil, err + } + id, err := convert.LedgerKeyToRegisterID(key) + if err != nil { + return nil, err + } + l.Payloads[id] = payload + } + return l, nil +} + +func (p PayloadSnapshot) Get(id flow.RegisterID) (flow.RegisterValue, error) { + value, exists := p.Payloads[id] + if !exists { + return nil, nil + } + return value.Value(), nil +} + +// NopMemoryGauge is a no-op implementation of the MemoryGauge interface +type NopMemoryGauge struct{} + +func (n NopMemoryGauge) MeterMemory(common.MemoryUsage) error { + return nil +} + +var _ common.MemoryGauge = (*NopMemoryGauge)(nil) + +type PayloadsReadonlyLedger struct { + Snapshot *PayloadSnapshot + + AllocateStorageIndexFunc func(owner []byte) (atree.StorageIndex, error) + SetValueFunc func(owner, key, value []byte) (err error) +} + +func (p *PayloadsReadonlyLedger) GetValue(owner, key []byte) (value []byte, err error) { + v, err := p.Snapshot.Get(flow.NewRegisterID(flow.BytesToAddress(owner), string(key))) + if err != nil { + return nil, fmt.Errorf("getting value failed: %w", err) + } + return v, nil +} + +func (p *PayloadsReadonlyLedger) SetValue(owner, key, value []byte) (err error) { + if p.SetValueFunc != nil { + return p.SetValueFunc(owner, key, value) + } + + panic("SetValue not expected to be called") +} + +func (p *PayloadsReadonlyLedger) ValueExists(owner, key []byte) (exists bool, err error) { + _, ok := p.Snapshot.Payloads[flow.NewRegisterID(flow.BytesToAddress(owner), string(key))] + return ok, nil +} + +func (p *PayloadsReadonlyLedger) AllocateStorageIndex(owner []byte) (atree.StorageIndex, error) { + if p.AllocateStorageIndexFunc != nil { + return p.AllocateStorageIndexFunc(owner) + } + + panic("AllocateStorageIndex not expected to be called") +} + +func NewPayloadsReadonlyLedger(snapshot *PayloadSnapshot) *PayloadsReadonlyLedger { + return &PayloadsReadonlyLedger{Snapshot: snapshot} +} + +// IsServiceLevelAddress returns true if the given address is the service level address. +// Which means it's not an actual account but instead holds service lever registers. +func IsServiceLevelAddress(address common.Address) bool { + return address == common.ZeroAddress +} + +var _ atree.Ledger = &PayloadsReadonlyLedger{} diff --git a/config/config_test.go b/config/config_test.go index 57546348274..fe0f076bd03 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -55,8 +55,8 @@ func TestDefaultConfig(t *testing.T) { func TestFlowConfig_Validate(t *testing.T) { c := defaultConfig(t) // set invalid config values - c.NetworkConfig.UnicastConfig.UnicastRateLimitersConfig.MessageRateLimit = -100 - c.NetworkConfig.UnicastConfig.UnicastRateLimitersConfig.BandwidthRateLimit = -100 + c.NetworkConfig.Unicast.RateLimiter.MessageRateLimit = -100 + c.NetworkConfig.Unicast.RateLimiter.BandwidthRateLimit = -100 err := c.Validate() require.Error(t, err) errs, ok := errors.Unwrap(err).(validator.ValidationErrors) diff --git a/config/default-config.yml b/config/default-config.yml index 07b0d6b408b..5b1879600c6 100644 --- a/config/default-config.yml +++ b/config/default-config.yml @@ -1,4 +1,8 @@ config-file: "./default-config.yml" +# WARNING: Only modify the network configurations below if you fully understand their implications. +# Incorrect settings may lead to system instability, security vulnerabilities, or degraded performance. +# Make changes with caution and refer to the documentation for guidance. +# Network configuration. network-config: # Network Configuration # Connection pruning determines whether connections to nodes @@ -8,34 +12,43 @@ network-config: preferred-unicast-protocols: [ ] received-message-cache-size: 10_000 peerupdate-interval: 10m - unicast-message-timeout: 5s - # Unicast create stream retry delay is initial delay used in the exponential backoff for create stream retries - unicast-create-stream-retry-delay: 1s + dns-cache-ttl: 5m # The size of the queue for notifications about new peers in the disallow list. disallow-list-notification-cache-size: 100 - # unicast rate limiters config - # Setting this to true will disable connection disconnects and gating when unicast rate limiters are configured - unicast-dry-run: true - # The number of seconds a peer will be forced to wait before being allowed to successfully reconnect to the node after being rate limited - unicast-lockout-duration: 10s - # Amount of unicast messages that can be sent by a peer per second - unicast-message-rate-limit: 0 - # Bandwidth size in bytes a peer is allowed to send via unicast streams per second - unicast-bandwidth-rate-limit: 0 - # Bandwidth size in bytes a peer is allowed to send via unicast streams at once - unicast-bandwidth-burst-limit: 1e9 - # The minimum number of consecutive successful streams to reset the unicast stream creation retry budget from zero to the maximum default. If it is set to 100 for example, it - # means that if a peer has 100 consecutive successful streams to the remote peer, and the remote peer has a zero stream creation budget, - # the unicast stream creation retry budget for that remote peer will be reset to the maximum default. - unicast-stream-zero-retry-reset-threshold: 100 - # The maximum number of retry attempts for creating a unicast stream to a remote peer before giving up. If it is set to 3 for example, it means that if a peer fails to create - # retry a unicast stream to a remote peer 3 times, the peer will give up and will not retry creating a unicast stream to that remote peer. - # When it is set to zero it means that the peer will not retry creating a unicast stream to a remote peer if it fails. - unicast-max-stream-creation-retry-attempt-times: 3 - # The size of the dial config cache used to keep track of the dial config for each remote peer. The dial config is used to keep track of the dial retry budget for each remote peer. - # Recommended to set it to the maximum number of remote peers in the network. - unicast-dial-config-cache-size: 10_000 + unicast: + rate-limiter: + # Setting this to true will disable connection disconnects and gating when unicast rate limiters are configured + dry-run: true + # The number of seconds a peer will be forced to wait before being allowed to successfully reconnect to the node after being rate limited + lockout-duration: 10s + # Amount of unicast messages that can be sent by a peer per second + message-rate-limit: 0 + # Bandwidth size in bytes a peer is allowed to send via unicast streams per second + bandwidth-rate-limit: 0 + # Bandwidth size in bytes a peer is allowed to send via unicast streams at once + bandwidth-burst-limit: 1e9 + manager: + # The minimum number of consecutive successful streams to reset the unicast stream creation retry budget from zero to the maximum default. If it is set to 100 for example, it + # means that if a peer has 100 consecutive successful streams to the remote peer, and the remote peer has a zero stream creation budget, + # the unicast stream creation retry budget for that remote peer will be reset to the maximum default. + stream-zero-retry-reset-threshold: 100 + # The maximum number of retry attempts for creating a unicast stream to a remote peer before giving up. If it is set to 3 for example, it means that if a peer fails to create + # retry a unicast stream to a remote peer 3 times, the peer will give up and will not retry creating a unicast stream to that remote peer. + # When it is set to zero it means that the peer will not retry creating a unicast stream to a remote peer if it fails. + max-stream-creation-retry-attempt-times: 3 + # The size of the dial config cache used to keep track of the dial config for each remote peer. The dial config is used to keep track of the dial retry budget for each remote peer. + # Recommended to set it to the maximum number of remote peers in the network. + dial-config-cache-size: 10_000 + # Unicast create stream retry delay is initial delay used in the exponential backoff for create stream retries + create-stream-retry-delay: 1s + message-timeout: 5s + # Enable stream protection for unicast streams; when enabled, all connections that are being established or + # have been already established for unicast streams are protected, meaning that they won't be closed by the connection manager. + # This is useful for preventing the connection manager from closing unicast streams that are being used by the application layer. + # However, it may interfere with the resource manager of libp2p, i.e., the connection manager may not be able to close connections + # that are not being used by the application layer while at the same time the node is running out of resources for new connections. + enable-stream-protection: true # Resource manager config libp2p-resource-manager: # Maximum allowed fraction of file descriptors to be allocated by the libp2p resources in [0,1] @@ -97,112 +110,507 @@ network-config: connections-outbound: 0 # no-override, use default fd: 0 # no-override, use default memory-bytes: 0 # no-override, use default - # Connection manager config - # HighWatermark and LowWatermark govern the number of connections are maintained by the ConnManager. - # When the peer count exceeds the HighWatermark, as many peers will be pruned (and - # their connections terminated) until LowWatermark peers remain. In other words, whenever the - # peer count is x > HighWatermark, the ConnManager will prune x - LowWatermark peers. - # The pruning algorithm is as follows: - # 1. The ConnManager will not prune any peers that have been connected for less than GracePeriod. - # 2. The ConnManager will not prune any peers that are protected. - # 3. The ConnManager will sort the peers based on their number of streams and direction of connections, and - # prunes the peers with the least number of streams. If there are ties, the peer with the incoming connection - # will be pruned. If both peers have incoming connections, and there are still ties, one of the peers will be - # pruned at random. - # Algorithm implementation is in https://github.com/libp2p/go-libp2p/blob/master/p2p/net/connmgr/connmgr.go#L262-L318 - libp2p-high-watermark: 500 - libp2p-low-watermark: 450 - # The time to wait before pruning a new connection - libp2p-silence-period: 10s - # The time to wait before start pruning connections - libp2p-grace-period: 1m + connection-manager: + # HighWatermark and LowWatermark govern the number of connections are maintained by the ConnManager. + # When the peer count exceeds the HighWatermark, as many peers will be pruned (and + # their connections terminated) until LowWatermark peers remain. In other words, whenever the + # peer count is x > HighWatermark, the ConnManager will prune x - LowWatermark peers. + # The pruning algorithm is as follows: + # 1. The ConnManager will not prune any peers that have been connected for less than GracePeriod. + # 2. The ConnManager will not prune any peers that are protected. + # 3. The ConnManager will sort the peers based on their number of streams and direction of connections, and + # prunes the peers with the least number of streams. If there are ties, the peer with the incoming connection + # will be pruned. If both peers have incoming connections, and there are still ties, one of the peers will be + # pruned at random. + # Algorithm implementation is in https://github.com/libp2p/go-libp2p/blob/master/p2p/net/connmgr/connmgr.go#L262-L318 + # We assume number of nodes around 500, and each node is allowed to make at most 8 connections to each certain remote node, + # we hence set the high-watermark to 500 * 8 = 4000, and the low-watermark to 500 * (0.5 * 4) = 1000, this means that when the + # number of peers exceeds 4000, the connection manager will prune the peers with the least number of streams until the number of + # peers is reduced to 1000 assuming an average of 2 connections per peer. + high-watermark: 4000 + low-watermark: 1000 + # The silence period is a regular interval that the connection manager will check for pruning peers if the number of peers exceeds the high-watermark. + # it is a regular interval and 10s is the default libp2p value. + silence-period: 10s + # The time to wait before a new connection is considered for pruning. + grace-period: 1m # Gossipsub config - # The default interval at which the mesh tracer logs the mesh topology. This is used for debugging and forensics purposes. - # Note that we purposefully choose this logging interval high enough to avoid spamming the logs. Moreover, the - # mesh updates will be logged individually and separately. The logging interval is only used to log the mesh - # topology as a whole specially when there are no updates to the mesh topology for a long time. - gossipsub-local-mesh-logging-interval: 1m - # The default interval at which the gossipsub score tracer logs the peer scores. This is used for debugging and forensics purposes. - # Note that we purposefully choose this logging interval high enough to avoid spamming the logs. - gossipsub-score-tracer-interval: 1m - # The default RPC sent tracker cache size. The RPC sent tracker is used to track RPC control messages sent from the local node. - # Note: this cache size must be large enough to keep a history of sent messages in a reasonable time window of past history. - gossipsub-rpc-sent-tracker-cache-size: 1_000_000 - # Cache size of the rpc sent tracker queue used for async tracking. - gossipsub-rpc-sent-tracker-queue-cache-size: 100_000 - # Number of workers for rpc sent tracker worker pool. - gossipsub-rpc-sent-tracker-workers: 5 - # Peer scoring is the default value for enabling peer scoring - gossipsub-peer-scoring-enabled: true - # The interval for updating the list of subscribed peers to all topics in gossipsub. This is used to keep track of subscriptions - # violations and penalize peers accordingly. Recommended value is in the order of a few minutes to avoid contentions; as the operation - # reads all topics and all peers subscribed to each topic. - gossipsub-subscription-provider-update-interval: 10m - # The size of cache for keeping the list of all peers subscribed to each topic (same as the local node). This cache is the local node's - # view of the network and is used to detect subscription violations and penalize peers accordingly. Recommended to be big enough to - # keep the entire network's size. Otherwise, the local node's view of the network will be incomplete due to cache eviction. - # Recommended size is 10x the number of peers in the network. - gossipsub-subscription-provider-cache-size: 10000 - - # Gossipsub rpc inspectors configs - # The size of the queue for notifications about invalid RPC messages - gossipsub-rpc-inspector-notification-cache-size: 10_000 - # RPC control message validation inspector configs - # Rpc validation inspector number of pool workers - gossipsub-rpc-validation-inspector-workers: 5 - # Max number of ihave messages in a sample to be inspected. If the number of ihave messages exceeds this configured value - # the control message ihaves will be truncated to the max sample size. This sample is randomly selected. - gossipsub-rpc-ihave-max-sample-size: 1000 - # Max number of ihave message ids in a sample to be inspected per ihave. Each ihave message includes a list of message ids - # each. If the size of the message ids list for a single ihave message exceeds the configured max message id sample size the list of message ids will be truncated. - gossipsub-rpc-ihave-max-message-id-sample-size: 1000 - # Max number of control messages in a sample to be inspected when inspecting GRAFT and PRUNE message types. If the total number of control messages (GRAFT or PRUNE) - # exceeds this max sample size then the respective message will be truncated before being processed. - gossipsub-rpc-graft-and-prune-message-max-sample-size: 1000 - # Max number of iwant messages in a sample to be inspected. If the total number of iWant control messages - # exceeds this max sample size then the respective message will be truncated before being processed. - gossipsub-rpc-iwant-max-sample-size: 1000 - # Max number of iwant message ids in a sample to be inspected per iwant. Each iwant message includes a list of message ids - # each, if the size of this list exceeds the configured max message id sample size the list of message ids will be truncated. - gossipsub-rpc-iwant-max-message-id-sample-size: 1000 - # The allowed threshold of iWant messages received without a corresponding tracked iHave message that was sent. If the cache miss threshold is exceeded an - # invalid control message notification is disseminated and the sender will be penalized. - gossipsub-rpc-iwant-cache-miss-threshold: .5 - # The iWants size at which message id cache misses will be checked. - gossipsub-rpc-iwant-cache-miss-check-size: 1000 - # The max allowed duplicate message IDs in a single iWant control message. If the duplicate message threshold is exceeded an invalid control message - # notification is disseminated and the sender will be penalized. - gossipsub-rpc-iwant-duplicate-message-id-threshold: .15 - # The size of the queue used by worker pool for the control message validation inspector - gossipsub-rpc-validation-inspector-queue-cache-size: 100 - # Cluster prefixed control message validation configs - # The size of the cache used to track the amount of cluster prefixed topics received by peers - gossipsub-cluster-prefix-tracker-cache-size: 100 - # The decay val used for the geometric decay of cache counters used to keep track of cluster prefixed topics received by peers - gossipsub-cluster-prefix-tracker-cache-decay: 0.99 - # The upper bound on the amount of cluster prefixed control messages that will be processed - gossipsub-rpc-cluster-prefixed-hard-threshold: 100 - # The max sample size used for RPC message validation. If the total number of RPC messages exceeds this value a sample will be taken but messages will not be truncated - gossipsub-rpc-message-max-sample-size: 1000 - # The threshold at which an error will be returned if the number of invalid RPC messages exceeds this value - gossipsub-rpc-message-error-threshold: 500 - # RPC metrics observer inspector configs - # The number of metrics inspector pool workers - gossipsub-rpc-metrics-inspector-workers: 1 - # The size of the queue used by worker pool for the control message metrics inspector - gossipsub-rpc-metrics-inspector-cache-size: 100 - # Threshold level for penalty. At each evaluation period, when a node's penalty is below this value, the decay rate slows down, ensuring longer decay periods for malicious nodes and quicker decay for honest ones. - gossipsub-app-specific-penalty-decay-slowdown-threshold: -99 - # This setting adjusts the decay rate when a node's penalty falls below the threshold. The decay rate, ranging between 0 and 1, dictates how quickly penalties decrease: a higher rate results in slower decay. The decay calculation is multiplicative (newPenalty = decayRate * oldPenalty). The reduction factor increases the decay rate, thus decelerating the penalty reduction. For instance, with a 0.01 reduction factor, the decay rate increases by 0.01 at each evaluation interval when the penalty is below the threshold. Consequently, a decay rate of `x` diminishes the penalty to zero more rapidly than a rate of `x+0.01`. - gossipsub-app-specific-penalty-decay-rate-reduction-factor: .01 - # Defines the frequency for evaluating and potentially adjusting the decay process of a spam record. At each interval, the system assesses the current penalty of a node. If this penalty is below the defined threshold, the decay rate is modified according to the reduction factor, slowing down the penalty reduction process. This reassessment at regular intervals ensures that the decay rate is dynamically adjusted to reflect the node's ongoing behavior, maintaining a balance between penalizing malicious activity and allowing recovery for honest nodes. - gossipsub-app-specific-penalty-decay-evaluation-period: 10m + gossipsub: + rpc-inspector: + # The size of the queue for notifications about invalid RPC messages + notification-cache-size: 10_000 + validation: # RPC control message validation inspector configs + inspection-queue: + # Rpc validation inspector number of pool workers + workers: 5 + # The size of the queue used by worker pool for the control message validation inspector + queue-size: 100 + publish-messages: + # The maximum number of messages in a single RPC message that are randomly sampled for async inspection. + # When the size of a single RPC message exceeds this threshold, a random sample is taken for inspection, but the RPC message is not truncated. + max-sample-size: 1000 + # The threshold at which an error will be returned if the number of invalid RPC messages exceeds this value + error-threshold: 500 + graft-and-prune: + # The maximum number of GRAFT or PRUNE messages in a single RPC message. + # When the total number of GRAFT or PRUNE messages in a single RPC message exceeds this threshold, + # a random sample of GRAFT or PRUNE messages will be taken and the RPC message will be truncated to this sample size. + message-count-threshold: 1000 + # Maximum number of total duplicate topic ids in a single GRAFT or PRUNE message, ideally this should be 0 but we allow for some tolerance + # to avoid penalizing peers that are not malicious but are misbehaving due to bugs or other issues. + # A topic id is considered duplicate if it appears more than once in a single GRAFT or PRUNE message. + duplicate-topic-id-threshold: 50 + ihave: + # The maximum allowed number of iHave messages in a single RPC message. + # Each iHave message represents the list of message ids. When the total number of iHave messages + # in a single RPC message exceeds this threshold, a random sample of iHave messages will be taken and the RPC message will be truncated to this sample size. + # The sample size is equal to the configured message-count-threshold. + message-count-threshold: 1000 + # The maximum allowed number of message ids in a single iHave message. + # Each iHave message represents the list of message ids for a specific topic, and this parameter controls the maximum number of message ids + # that can be included in a single iHave message. When the total number of message ids in a single iHave message exceeds this threshold, + # a random sample of message ids will be taken and the iHave message will be truncated to this sample size. + # The sample size is equal to the configured message-id-count-threshold. + message-id-count-threshold: 1000 + # The tolerance threshold for having duplicate topics in an iHave message under inspection. + # When the total number of duplicate topic ids in a single iHave message exceeds this threshold, the inspection of message will fail. + # Note that a topic ID is counted as a duplicate only if it is repeated more than once. + duplicate-topic-id-threshold: 50 + # Threshold of tolerance for having duplicate message IDs in a single iHave message under inspection. + # When the total number of duplicate message ids in a single iHave message exceeds this threshold, the inspection of message will fail. + # Ideally, an iHave message should not have any duplicate message IDs, hence a message id is considered duplicate when it is repeated more than once + # within the same iHave message. When the total number of duplicate message ids in a single iHave message exceeds this threshold, the inspection of message will fail. + duplicate-message-id-threshold: 100 + iwant: + # The maximum allowed number of iWant messages in a single RPC message. + # Each iWant message represents the list of message ids. When the total number of iWant messages + # in a single RPC message exceeds this threshold, a random sample of iWant messages will be taken and the RPC message will be truncated to this sample size. + # The sample size is equal to the configured message-count-threshold. + message-count-threshold: 1000 + # The maximum allowed number of message ids in a single iWant message. + # Each iWant message represents the list of message ids for a specific topic, and this parameter controls the maximum number of message ids + # that can be included in a single iWant message. When the total number of message ids in a single iWant message exceeds this threshold, + # a random sample of message ids will be taken and the iWant message will be truncated to this sample size. + # The sample size is equal to the configured message-id-count-threshold. + message-id-count-threshold: 1000 + # The allowed threshold of iWant messages received without a corresponding tracked iHave message that was sent. + # If the cache miss threshold is exceeded an invalid control message notification is disseminated and the sender will be penalized. + cache-miss-threshold: 500 + # The max allowed number of duplicate message ids in a single iwant message. + # Note that ideally there should be no duplicate message ids in a single iwant message but + # we allow for some tolerance to avoid penalizing peers that are not malicious + duplicate-message-id-threshold: 100 + cluster-prefixed-messages: + # Cluster prefixed control message validation configs + # The size of the cache used to track the amount of cluster prefixed topics received by peers + tracker-cache-size: 100 + # The decay val used for the geometric decay of cache counters used to keep track of cluster prefixed topics received by peers + tracker-cache-decay: 0.99 + # The upper bound on the amount of cluster prefixed control messages that will be processed + hard-threshold: 100 + process: + inspection: + # Serves as a fail-safe mechanism to globally deactivate inspection logic. When this fail-safe is activated it disables all + # aspects of the inspection logic, irrespective of individual configurations like inspection.enable-graft, inspection.enable-prune, etc. + # Consequently, all metrics collection and logging related to the rpc and inspection will also be disabled. + # It is important to note that activating this fail-safe results in a comprehensive deactivation inspection features. + # Please use this setting judiciously, considering its broad impact on the behavior of control message handling. + disabled: false + # Enables graft control message inspection. + enable-graft: true + # Enables prune control message inspection. + enable-prune: true + # Enables ihave control message inspection. + enable-ihave: true + # Enables iwant control message inspection. + enable-iwant: true + # Enables publish message inspection. + enable-publish: true + truncation: + # Serves as a fail-safe mechanism to globally deactivate truncation logic. When this fail-safe is activated it disables all + # aspects of the truncation logic, irrespective of individual configurations like truncation.enable-graft, truncation.enable-prune, etc. + # Consequently, all metrics collection and logging related to the rpc and inspection will also be disabled. + # It is important to note that activating this fail-safe results in a comprehensive deactivation truncation features. + # Please use this setting judiciously, considering its broad impact on the behavior of control message handling. + disabled: false + # Enables graft control message truncation. + enable-graft: true + # Enables prune control message truncation. + enable-prune: true + # Enables ihave control message truncation. + enable-ihave: true + # Enables ihave message id truncation. + enable-ihave-message-id: true + # Enables iwant control message truncation. + enable-iwant: true + # Enables iwant message id truncation. + enable-iwant-message-id: true + rpc-tracer: + # The default interval at which the mesh tracer logs the mesh topology. This is used for debugging and forensics purposes. + # Note that we purposefully choose this logging interval high enough to avoid spamming the logs. Moreover, the + # mesh updates will be logged individually and separately. The logging interval is only used to log the mesh + # topology as a whole specially when there are no updates to the mesh topology for a long time. + local-mesh-logging-interval: 1m + # The default interval at which the gossipsub score tracer logs the peer scores. This is used for debugging and forensics purposes. + # Note that we purposefully choose this logging interval high enough to avoid spamming the logs. + score-tracer-interval: 1m + # The default RPC sent tracker cache size. The RPC sent tracker is used to track RPC control messages sent from the local node. + # Note: this cache size must be large enough to keep a history of sent messages in a reasonable time window of past history. + rpc-sent-tracker-cache-size: 1_000_000 + # Cache size of the rpc sent tracker queue used for async tracking. + rpc-sent-tracker-queue-cache-size: 100_000 + # Number of workers for rpc sent tracker worker pool. + rpc-sent-tracker-workers: 5 + # Peer scoring is the default value for enabling peer scoring + peer-scoring-enabled: true + scoring-parameters: + peer-scoring: + internal: + # The weight for app-specific scores. + # It is used to scale the app-specific scores to the same range as the other scores. + # At the current version, we don't distinguish between the app-specific scores + # and the other scores, so we set it to 1. + app-specific-score-weight: 1 + # The default decay interval for the overall score of a peer at the GossipSub scoring + # system. We set it to 1 minute so that it is not too short so that a malicious node can recover from a penalty + # and is not too long so that a well-behaved node can't recover from a penalty. + decay-interval: 1m + # The default decay to zero for the overall score of a peer at the GossipSub scoring system. + # It defines the maximum value below which a peer scoring counter is reset to zero. + # This is to prevent the counter from decaying to a very small value. + # The default value is 0.01, which means that a counter will be reset to zero if it decays to 0.01. + # When a counter hits the DecayToZero threshold, it means that the peer did not exhibit the behavior + # for a long time, and we can reset the counter. + decay-to-zero: 0.01 + topic: + # This is the default value for the skip atomic validation flag for topics. + # We set it to true, which means gossipsub parameter validation will not fail if we leave some of the + # topic parameters at their default values, i.e., zero. This is because we are not setting all + # topic parameters at the current implementation. + skip-atomic-validation: true + # This value is applied to the square of the number of invalid message deliveries on a topic. + # It is used to penalize peers that send invalid messages. By an invalid message, we mean a message that is not signed by the + # publisher, or a message that is not signed by the peer that sent it. + # An invalid message also can be a self-origin message, i.e., the peer sees its own message bounced back to it. + # GossipSub has an edge-case that a peer may inadvertently request a self-origin message from a peer that it is connected to, through iHave-iWant messages, which is a + # false-positive edge-case. + # We set it to -10e-4, which means that with around 1414 invalid + # message deliveries within a gossipsub heartbeat interval, the peer will be disconnected. + # Note that we intentionally set this threshold high to avoid false-positively penalizing nodes due to self-origin message requests by iHave-iWants (a known issue in gossipsub). + # The supporting math is as follows: + # - each staked (i.e., authorized) peer is rewarded by the fixed reward of 100 (i.e., DefaultStakedIdentityReward). + # - x invalid message deliveries will result in a penalty of x^2 * DefaultTopicInvalidMessageDeliveriesWeight, i.e., -x^2 * 10-e4. + # - the peer will be disconnected when its penalty reaches -100 (i.e., MaxAppSpecificPenalty). + # - so, the maximum number of invalid message deliveries that a peer can have before being disconnected is sqrt(200/10-e4) ~ 1414. + invalid-message-deliveries-weight: -10e-4 + # The decay factor used to decay the number of invalid message deliveries. + # The total number of invalid message deliveries is multiplied by this factor at each heartbeat interval to + # decay the number of invalid message deliveries, and prevent the peer from being disconnected if it stops + # sending invalid messages. We set it to 0.5, which means that the number of invalid message deliveries will + # decay by 50% at each heartbeat interval. + # The decay heartbeats are defined by the heartbeat interval of the gossipsub scoring system, which is 1 Minute (defaultDecayInterval). + # Note that we set the decay factor low so that the invalid message deliveries will be decayed fast enough to prevent the peer from being disconnected on mediocre loads. + # This is to address the false-positive disconnections that we observed in the network due to the self-origin message requests by iHave-iWants (a known issue in gossipsub). + invalid-message-deliveries-decay: 0.5 + # The default time in mesh quantum for the GossipSub scoring system. It is used to gauge + # a discrete time interval for the time in mesh counter. We set it to 1 hour, which means that every one complete hour a peer is + # in a topic mesh, the time in mesh counter will be incremented by 1 and is counted towards the availability score of the peer in that topic mesh. + # The reason for setting it to 1 hour is that we want to reward peers that are in a topic mesh for a long time, and we want to avoid rewarding peers that + # are churners, i.e., peers that join and leave a topic mesh frequently. + time-in-mesh-quantum: 1h + # The default weight of a topic in the GossipSub scoring system. + # The overall score of a peer in a topic mesh is multiplied by the weight of the topic when calculating the overall score of the peer. + # We set it to 1.0, which means that the overall score of a peer in a topic mesh is not affected by the weight of the topic. + topic-weight: 1.0 + # This is applied to the number of actual message deliveries in a topic mesh + # at each decay interval (i.e., defaultDecayInterval). + # It is used to decay the number of actual message deliveries, and prevents past message + # deliveries from affecting the current score of the peer. + # As the decay interval is 1 minute, we set it to 0.5, which means that the number of actual message + # deliveries will decay by 50% at each decay interval. + mesh-message-deliveries-decay: 0.5 + # The maximum number of actual message deliveries in a topic + # mesh that is used to calculate the score of a peer in that topic mesh. + # We set it to 1000, which means that the maximum number of actual message deliveries in a + # topic mesh that is used to calculate the score of a peer in that topic mesh is 1000. + # This is to prevent the score of a peer in a topic mesh from being affected by a large number of actual + # message deliveries and also affect the score of the peer in other topic meshes. + # When the total delivered messages in a topic mesh exceeds this value, the score of the peer in that topic + # mesh will not be affected by the actual message deliveries in that topic mesh. + # Moreover, this does not allow the peer to accumulate a large number of actual message deliveries in a topic mesh + # and then start under-performing in that topic mesh without being penalized. + mesh-message-deliveries-cap: 1000 + # The threshold for the number of actual message deliveries in a + # topic mesh that is used to calculate the score of a peer in that topic mesh. + # If the number of actual message deliveries in a topic mesh is less than this value, + # the peer will be penalized by square of the difference between the actual message deliveries and the threshold, + # i.e., -w * (actual - threshold)^2 where `actual` and `threshold` are the actual message deliveries and the + # threshold, respectively, and `w` is the weight (i.e., defaultTopicMeshMessageDeliveriesWeight). + # We set it to 0.1 * defaultTopicMeshMessageDeliveriesCap, which means that if a peer delivers less tha 10% of the + # maximum number of actual message deliveries in a topic mesh, it will be considered as an under-performing peer + # in that topic mesh. + mesh-message-deliveries-threshold: 100 + # The weight for applying penalty when a peer is under-performing in a topic mesh. + # Upon every decay interval, if the number of actual message deliveries is less than the topic mesh message deliveries threshold + # (i.e., defaultTopicMeshMessageDeliveriesThreshold), the peer will be penalized by square of the difference between the actual + # message deliveries and the threshold, multiplied by this weight, i.e., -w * (actual - threshold)^2 where w is the weight, and + # `actual` and `threshold` are the actual message deliveries and the threshold, respectively. + # We set this value to be - 0.05 MaxAppSpecificReward / (defaultTopicMeshMessageDeliveriesThreshold^2). This guarantees that even if a peer + # is not delivering any message in a topic mesh, it will not be disconnected. + # Rather, looses part of the MaxAppSpecificReward that is awarded by our app-specific scoring function to all staked + # nodes by default will be withdrawn, and the peer will be slightly penalized. In other words, under-performing in a topic mesh + # will drop the overall score of a peer by 5% of the MaxAppSpecificReward that is awarded by our app-specific scoring function. + # It means that under-performing in a topic mesh will not cause a peer to be disconnected, but it will cause the peer to lose + # its MaxAppSpecificReward that is awarded by our app-specific scoring function. + # At this point, we do not want to disconnect a peer only because it is under-performing in a topic mesh as it might be + # causing a false positive network partition. + mesh-deliveries-weight: -0.0005 + # The window size is time interval that we count a delivery of an already + # seen message towards the score of a peer in a topic mesh. The delivery is counted + # by GossipSub only if the previous sender of the message is different from the current sender. + # We set it to the decay interval of the GossipSub scoring system, which is 1 minute. + # It means that if a peer delivers a message that it has already seen less than one minute ago, + # the delivery will be counted towards the score of the peer in a topic mesh only if the previous sender of the message. + # This also prevents replay attacks of messages that are older than one minute. As replayed messages will not + # be counted towards the actual message deliveries of a peer in a topic mesh. + mesh-message-deliveries-window: 1m + # The time interval that we wait for a new peer that joins a topic mesh + # till start counting the number of actual message deliveries of that peer in that topic mesh. + # We set it to 2 * defaultDecayInterval, which means that we wait for 2 decay intervals before start counting + # the number of actual message deliveries of a peer in a topic mesh. + # With a default decay interval of 1 minute, it means that we wait for 2 minutes before start counting the + # number of actual message deliveries of a peer in a topic mesh. This is to account for + # the time that it takes for a peer to start up and receive messages from other peers in the topic mesh. + mesh-message-delivery-activation: 2m + thresholds: + # This is the threshold when a peer's penalty drops below this threshold, no gossip + # is emitted towards that peer and gossip from that peer is ignored. + # Validation Constraint: GossipThreshold >= PublishThreshold && GossipThreshold < 0 + # How we use it: As the current max penalty is -100, we set the threshold to -99 + # so that all gossips to and from peers with penalty -100 are ignored. + gossip: -99 + # This is the threshold when a peer's penalty drops below this threshold, + # self-published messages are not propagated towards this peer. + # Validation Constraint: + # PublishThreshold >= GraylistThreshold && PublishThreshold <= GossipThreshold && PublishThreshold < 0. + # How we use it: As the current max penalty is -100, we set the threshold to -99 + # so that all penalized peers are deprived of receiving any published messages. + publish: -99 + # This is the threshold when a peer's penalty drops below this threshold, + # the peer is graylisted, i.e., incoming RPCs from the peer are ignored. + # Validation Constraint: + # GraylistThreshold =< PublishThreshold && GraylistThreshold =< GossipThreshold && GraylistThreshold < 0 + # How we use it: As the current max penalty is -100, we set the threshold to -99 + # so that all penalized peers are graylisted. + graylist: -99 + # This is the threshold when a peer sends us PX information with a prune, + # we only accept it and connect to the supplied peers if the originating peer's + # penalty exceeds this threshold. + # Validation Constraint: must be non-negative. + # How we use it: As the current max reward is 100, we set the threshold to 99 + # so that we only receive supplied peers from well-behaved peers. + accept-px: 99 + # This is the threshold when the median peer penalty in the mesh drops + # below this value, the peer may select more peers with penalty above the median + # to opportunistically graft on the mesh. + # Validation Constraint: must be non-negative. + # How we use it: We set it to the -100 + 1 so that we only + # opportunistically graft peers that are not access nodes (i.e., with -1), + # or penalized peers (i.e., with -100). + opportunistic-graft: 101 + behaviour: + # The threshold when the behavior of a peer is considered as bad by GossipSub. + # Currently, the misbehavior is defined as advertising an iHave without responding to the iWants (iHave broken promises), as well as attempting + # on GRAFT when the peer is considered for a PRUNE backoff, i.e., the local peer does not allow the peer to join the local topic mesh + # for a while, and the remote peer keep attempting on GRAFT (aka GRAFT flood). + # When the misbehavior counter of a peer goes beyond this threshold, the peer is penalized by defaultBehaviorPenaltyWeight (see below) for the excess misbehavior. + # + # An iHave broken promise means that a peer advertises an iHave for a message, but does not respond to the iWant requests for that message. + # For iHave broken promises, the gossipsub scoring works as follows: + # It samples ONLY A SINGLE iHave out of the entire RPC. + # If that iHave is not followed by an actual message within the next 3 seconds, the peer misbehavior counter is incremented by 1. + # + # We set it to 10, meaning that we at most tolerate 10 of such RPCs containing iHave broken promises. After that, the peer is penalized for every + # excess RPC containing iHave broken promises. + # The counter is also decayed by (0.99) every decay interval (defaultDecayInterval) i.e., every minute. + # Note that misbehaviors are counted by GossipSub across all topics (and is different from the Application Layer Misbehaviors that we count through + # the ALSP system). + penalty-threshold: 1000 + # The weight for applying penalty when a peer misbehavior goes beyond the threshold. + # Misbehavior of a peer at gossipsub layer is defined as advertising an iHave without responding to the iWants (broken promises), as well as attempting + # on GRAFT when the peer is considered for a PRUNE backoff, i.e., the local peer does not allow the peer to join the local topic mesh + # This is detected by the GossipSub scoring system, and the peer is penalized by defaultBehaviorPenaltyWeight. + # + # An iHave broken promise means that a peer advertises an iHave for a message, but does not respond to the iWant requests for that message. + # For iHave broken promises, the gossipsub scoring works as follows: + # It samples ONLY A SINGLE iHave out of the entire RPC. + # If that iHave is not followed by an actual message within the next 3 seconds, the peer misbehavior counter is incremented by 1. + # + # The penalty is applied to the square of the difference between the misbehavior counter and the threshold, i.e., -|w| * (misbehavior counter - threshold)^2. + # We set it to 0.01 * MaxAppSpecificPenalty, which means that misbehaving 10 times more than the threshold (i.e., 10 + 10) will cause the peer to lose + # its entire AppSpecificReward that is awarded by our app-specific scoring function to all staked (i.e., authorized) nodes by default. + # Moreover, as the MaxAppSpecificPenalty is -MaxAppSpecificReward, misbehaving sqrt(2) * 10 times more than the threshold will cause the peer score + # to be dropped below the MaxAppSpecificPenalty, which is also below the GraylistThreshold, and the peer will be graylisted (i.e., disconnected). + # + # The math is as follows: -|w| * (misbehavior - threshold)^2 = 0.01 * MaxAppSpecificPenalty * (misbehavior - threshold)^2 < 2 * MaxAppSpecificPenalty + # if misbehavior > threshold + sqrt(2) * 10. + # As shown above, with this choice of defaultBehaviorPenaltyWeight, misbehaving sqrt(2) * 10 times more than the threshold will cause the peer score + # to be dropped below the MaxAppSpecificPenalty, which is also below the GraylistThreshold, and the peer will be graylisted (i.e., disconnected). This weight + # is chosen in a way that with almost a few misbehaviors more than the threshold, the peer will be graylisted. The rationale relies on the fact that + # the misbehavior counter is incremented by 1 for each RPC containing one or more broken promises. Hence, it is per RPC, and not per broken promise. + # Having sqrt(2) * 10 broken promises RPC is a blatant misbehavior, and the peer should be graylisted. With decay interval of 1 minute, and decay value of + # 0.99 we expect a graylisted node due to borken promises to get back in about 527 minutes, i.e., (0.99)^x * (sqrt(2) * 10)^2 * MaxAppSpecificPenalty > GraylistThreshold + # where x is the number of decay intervals that the peer is graylisted. As MaxAppSpecificPenalty and GraylistThresholds are close, we can simplify the inequality + # to (0.99)^x * (sqrt(2) * 10)^2 > 1 --> (0.99)^x * 200 > 1 --> (0.99)^x > 1/200 --> x > log(1/200) / log(0.99) --> x > 527.17 decay intervals, i.e., 527 minutes. + # Note that misbehaviors are counted by GossipSub across all topics (and is different from the Application Layer Misbehaviors that we count through + # the ALSP system that are reported by the engines). + penalty-weight: -0.01 + # The decay interval for the misbehavior counter of a peer. The misbehavior counter is + # incremented by GossipSub for iHave broken promises or the GRAFT flooding attacks (i.e., each GRAFT received from a remote peer while that peer is on a PRUNE backoff). + # + # An iHave broken promise means that a peer advertises an iHave for a message, but does not respond to the iWant requests for that message. + # For iHave broken promises, the gossipsub scoring works as follows: + # It samples ONLY A SINGLE iHave out of the entire RPC. + # If that iHave is not followed by an actual message within the next 3 seconds, the peer misbehavior counter is incremented by 1. + # This means that regardless of how many iHave broken promises an RPC contains, the misbehavior counter is incremented by 1. + # That is why we decay the misbehavior counter very slow, as this counter indicates a severe misbehavior. + # + # The misbehavior counter is decayed per decay interval (i.e., defaultDecayInterval = 1 minute) by GossipSub. + # We set it to 0.99, which means that the misbehavior counter is decayed by 1% per decay interval. + # With the generous threshold that we set (i.e., defaultBehaviourPenaltyThreshold = 10), we take the peers going beyond the threshold as persistent misbehaviors, + # We expect honest peers never to go beyond the threshold, and if they do, we expect them to go back below the threshold quickly. + # + # Note that misbehaviors are counted by GossipSub across all topics (and is different from the Application Layer Misbehaviors that we count through + # the ALSP system that is based on the engines report). + penalty-decay: 0.5 + protocol: + # The max number of debug/trace log events per second. + # Logs emitted above this threshold are dropped. + max-debug-logs: 50 + application-specific: + # This is the maximum penalty for severe offenses that we apply + # to a remote node score. The score mechanism of GossipSub in Flow is designed + # in a way that all other infractions are penalized with a fraction of this value. + # We have also set the other parameters such as GraylistThreshold, + # GossipThreshold, and PublishThreshold to be a bit higher than this, + # i.e., -100 + 1. This ensures that a node with a score of + # -100 will be graylisted (i.e., all incoming and outgoing RPCs + # are rejected) and will not be able to publish or gossip any messages. + max-app-specific-penalty: -100 + min-app-specific-penalty: -1 + # This is the penalty for unknown identity. It is + # applied to the peer's score when the peer is not in the identity list. + unknown-identity-penalty: -100 + # This is the penalty for invalid subscription. + # It is applied to the peer's score when the peer subscribes to a topic that it is + # not authorized to subscribe to. + invalid-subscription-penalty: -100 + # This is the reward for well-behaving staked peers. + # If a peer does not have any misbehavior record, e.g., invalid subscription, + # invalid message, etc., it will be rewarded with this score. + max-app-specific-reward: 100 + # This is the reward for staking peers. It is applied + # to the peer's score when the peer does not have any misbehavior record, e.g., + # invalid subscription, invalid message, etc. The purpose is to reward the staking + # peers for their contribution to the network and prioritize them in neighbor selection. + staked-identity-reward: 100 + scoring-registry: + # Defines the duration of time, after the node startup, + # during which the scoring registry remains inactive before penalizing nodes. + # Throughout this startup silence period, the application-specific penalty + # returned for all nodes will be 0, and any invalid control message notifications + # will be ignored. This configuration allows nodes to stabilize and initialize before + # applying penalties or processing invalid control message notifications. + startup-silence-duration: 1h + app-specific-score: + # number of workers that asynchronously update the app specific score requests when they are expired. + score-update-worker-num: 5 + # size of the queue used by the worker pool for the app specific score update requests. The queue is used to buffer the app specific score update requests + # before they are processed by the worker pool. The queue size must be larger than total number of peers in the network. + # The queue is deduplicated based on the peer ids ensuring that there is only one app specific score update request per peer in the queue. + score-update-request-queue-size: 10_000 + # score ttl is the time to live for the app specific score. Once the score is expired; a new request will be sent to the app specific score provider to update the score. + # until the score is updated, the previous score will be used. + score-ttl: 1m + spam-record-cache: + # size of cache used to track spam records at gossipsub. Each peer id is mapped to a spam record that keeps track of the spam score for that peer. + # cache should be big enough to keep track of the entire network's size. Otherwise, the local node's view of the network will be incomplete due to cache eviction. + cache-size: 10_000 + decay: + # Threshold level for spam record penalty. + # At each evaluation period, when a node's penalty is below this value, the decay rate slows down, ensuring longer decay periods for malicious nodes and quicker decay for honest ones. + penalty-decay-slowdown-threshold: -99 + # This setting adjusts the decay rate when a node's penalty falls below the threshold. + # The decay rate, ranging between 0 and 1, dictates how quickly penalties decrease: a higher rate results in slower decay. + # The decay calculation is multiplicative (newPenalty = decayRate * oldPenalty). + # The reduction factor increases the decay rate, thus decelerating the penalty reduction. For instance, with a 0.01 reduction factor, + # the decay rate increases by 0.01 at each evaluation interval when the penalty is below the threshold. + # Consequently, a decay rate of `x` diminishes the penalty to zero more rapidly than a rate of `x+0.01`. + penalty-decay-rate-reduction-factor: 0.01 + # Defines the frequency for evaluating and potentially adjusting the decay process of a spam record. + # At each interval, the system assesses the current penalty of a node. + # If this penalty is below the defined threshold, the decay rate is modified according to the reduction factor, slowing down the penalty reduction process. + # This reassessment at regular intervals ensures that the decay rate is dynamically adjusted to reflect the node's ongoing behavior, + # maintaining a balance between penalizing malicious activity and allowing recovery for honest nodes. + penalty-decay-evaluation-period: 10m + # The minimum speed at which the spam penalty value of a peer is decayed. + # Spam record will be initialized with a decay value between .5 , .7 and this value will then be decayed up to .99 on consecutive misbehavior's, + # The maximum decay value decays the penalty by 1% every second. The decay is applied geometrically, i.e., `newPenalty = oldPenalty * decay`, hence, the higher decay value + # indicates a lower decay speed, i.e., it takes more heartbeat intervals to decay a penalty back to zero when the decay value is high. + # assume: + # penalty = -100 (the maximum application specific penalty is -100) + # skipDecayThreshold = -0.1 + # it takes around 459 seconds for the penalty to decay to reach greater than -0.1 and turn into 0. + # x * 0.99 ^ n > -0.1 (assuming negative x). + # 0.99 ^ n > -0.1 / x + # Now we can take the logarithm of both sides (with any base, but let's use base 10 for simplicity). + # log( 0.99 ^ n ) < log( 0.1 / x ) + # Using the properties of logarithms, we can bring down the exponent: + # n * log( 0.99 ) < log( -0.1 / x ) + # And finally, we can solve for n: + # n > log( -0.1 / x ) / log( 0.99 ) + # We can plug in x = -100: + # n > log( -0.1 / -100 ) / log( 0.99 ) + # n > log( 0.001 ) / log( 0.99 ) + # n > -3 / log( 0.99 ) + # n > 458.22 + minimum-spam-penalty-decay-factor: 0.99 + # The maximum rate at which the spam penalty value of a peer decays. Decay speeds increase + # during sustained malicious activity, leading to a slower recovery of the app-specific score for the penalized node. Conversely, + # decay speeds decrease, allowing faster recoveries, when nodes exhibit fleeting misbehavior. + maximum-spam-penalty-decay-factor: 0.8 + # The threshold for which when the negative penalty is above this value, the decay function will not be called. + # instead, the penalty will be set to 0. This is to prevent the penalty from keeping a small negative value for a long time. + skip-decay-threshold: -0.1 + misbehaviour-penalties: + # The penalty applied to the application specific penalty when a peer conducts a graft misbehaviour. + graft: -10 + # The penalty applied to the application specific penalty when a peer conducts a prune misbehaviour. + prune: -10 + # The penalty applied to the application specific penalty when a peer conducts a iHave misbehaviour. + ihave: -10 + # The penalty applied to the application specific penalty when a peer conducts a iWant misbehaviour. + iwant: -10 + # The penalty applied to the application specific penalty when a peer conducts a rpc publish message misbehaviour. + publish: -10 + # The factor used to reduce the penalty for control message misbehaviours on cluster prefixed topics. This allows a more lenient punishment for nodes + # that fall behind and may need to request old data. + cluster-prefixed-reduction-factor: 0.2 + subscription-provider: + # The interval for updating the list of subscribed peers to all topics in gossipsub. This is used to keep track of subscriptions + # violations and penalize peers accordingly. Recommended value is in the order of a few minutes to avoid contentions; as the operation + # reads all topics and all peers subscribed to each topic. + update-interval: 10m + # The size of cache for keeping the list of all peers subscribed to each topic (same as the local node). This cache is the local node's + # view of the network and is used to detect subscription violations and penalize peers accordingly. Recommended to be big enough to + # keep the entire network's size. Otherwise, the local node's view of the network will be incomplete due to cache eviction. + # Recommended size is 10x the number of peers in the network. + cache-size: 10000 # Application layer spam prevention alsp-spam-record-cache-size: 1000 alsp-spam-report-queue-size: 10_000 alsp-disable-penalty: false alsp-heart-beat-interval: 1s - # Base probability in [0,1] that's used in creating the final probability of creating a # misbehavior report for a BatchRequest message. This is why the word "base" is used in the name of this field, # since it's not the final probability and there are other factors that determine the final probability. @@ -219,7 +627,6 @@ network-config: # batchRequestBaseProb * (1000+1) / synccore.DefaultConfig().MaxSize # = 0.01 * 1001 / 64 = 0.15640625 = 15.640625% alsp-sync-engine-batch-request-base-prob: 0.01 - # Base probability in [0,1] that's used in creating the final probability of creating a # misbehavior report for a RangeRequest message. This is why the word "base" is used in the name of this field, # since it's not the final probability and there are other factors that determine the final probability. @@ -237,7 +644,6 @@ network-config: # rangeRequestBaseProb * (1000+1) / synccore.DefaultConfig().MaxSize # = 0.01 * 1001 / 64 = 0.15640625 = 15.640625% alsp-sync-engine-range-request-base-prob: 0.01 - # Probability in [0,1] of creating a misbehavior report for a SyncRequest message. # create misbehavior report for 1% of SyncRequest messages alsp-sync-engine-sync-request-prob: 0.01 diff --git a/consensus/hotstuff/committees/leader/leader_selection.go b/consensus/hotstuff/committees/leader/leader_selection.go index bc1936cc197..891fc4d7b43 100644 --- a/consensus/hotstuff/committees/leader/leader_selection.go +++ b/consensus/hotstuff/committees/leader/leader_selection.go @@ -5,7 +5,8 @@ import ( "fmt" "math" - "github.com/onflow/flow-go/crypto/random" + "github.com/onflow/crypto/random" + "github.com/onflow/flow-go/model/flow" ) diff --git a/consensus/hotstuff/committees/leader/leader_selection_test.go b/consensus/hotstuff/committees/leader/leader_selection_test.go index ecf13e4aa83..55310de05a2 100644 --- a/consensus/hotstuff/committees/leader/leader_selection_test.go +++ b/consensus/hotstuff/committees/leader/leader_selection_test.go @@ -6,10 +6,10 @@ import ( "sort" "testing" + "github.com/onflow/crypto/random" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/onflow/flow-go/crypto/random" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/model/flow/filter" "github.com/onflow/flow-go/utils/unittest" diff --git a/consensus/hotstuff/committees/static.go b/consensus/hotstuff/committees/static.go index b95c6448dff..40ef00f5ca6 100644 --- a/consensus/hotstuff/committees/static.go +++ b/consensus/hotstuff/committees/static.go @@ -3,11 +3,11 @@ package committees import ( "fmt" + "github.com/onflow/crypto" + "github.com/onflow/flow-go/consensus/hotstuff" "github.com/onflow/flow-go/consensus/hotstuff/model" - "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/model/flow" - "github.com/onflow/flow-go/model/flow/order" "github.com/onflow/flow-go/state/protocol" ) @@ -22,7 +22,7 @@ func NewStaticCommittee(participants flow.IdentityList, myID flow.Identifier, dk // NewStaticCommitteeWithDKG returns a new committee with a static participant set. func NewStaticCommitteeWithDKG(participants flow.IdentityList, myID flow.Identifier, dkg protocol.DKG) (*Static, error) { - valid := order.IdentityListCanonical(participants) + valid := flow.IsIdentityListCanonical(participants) if !valid { return nil, fmt.Errorf("participants %v is not in Canonical order", participants) } diff --git a/consensus/hotstuff/helper/bls_key.go b/consensus/hotstuff/helper/bls_key.go index e455be5b296..215a5c87e28 100644 --- a/consensus/hotstuff/helper/bls_key.go +++ b/consensus/hotstuff/helper/bls_key.go @@ -4,9 +4,8 @@ import ( "crypto/rand" "testing" + "github.com/onflow/crypto" "github.com/stretchr/testify/require" - - "github.com/onflow/flow-go/crypto" ) func MakeBLSKey(t *testing.T) crypto.PrivateKey { diff --git a/consensus/hotstuff/helper/signature.go b/consensus/hotstuff/helper/signature.go index 01fef1a77e4..9c3e4382e79 100644 --- a/consensus/hotstuff/helper/signature.go +++ b/consensus/hotstuff/helper/signature.go @@ -4,8 +4,9 @@ import ( "github.com/stretchr/testify/mock" "go.uber.org/atomic" + "github.com/onflow/crypto" + "github.com/onflow/flow-go/consensus/hotstuff/mocks" - "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/utils/unittest" ) diff --git a/consensus/hotstuff/integration/instance_test.go b/consensus/hotstuff/integration/instance_test.go index bf12244c099..6a8d352d14b 100644 --- a/consensus/hotstuff/integration/instance_test.go +++ b/consensus/hotstuff/integration/instance_test.go @@ -8,6 +8,7 @@ import ( "time" "github.com/gammazero/workerpool" + "github.com/onflow/crypto" "github.com/rs/zerolog" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" @@ -31,7 +32,6 @@ import ( "github.com/onflow/flow-go/consensus/hotstuff/validator" "github.com/onflow/flow-go/consensus/hotstuff/voteaggregator" "github.com/onflow/flow-go/consensus/hotstuff/votecollector" - "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/module/counters" "github.com/onflow/flow-go/module/irrecoverable" diff --git a/consensus/hotstuff/mocks/dkg.go b/consensus/hotstuff/mocks/dkg.go index 77ec3602d69..462f963d7a9 100644 --- a/consensus/hotstuff/mocks/dkg.go +++ b/consensus/hotstuff/mocks/dkg.go @@ -3,7 +3,7 @@ package mocks import ( - crypto "github.com/onflow/flow-go/crypto" + crypto "github.com/onflow/crypto" flow "github.com/onflow/flow-go/model/flow" mock "github.com/stretchr/testify/mock" diff --git a/consensus/hotstuff/mocks/random_beacon_inspector.go b/consensus/hotstuff/mocks/random_beacon_inspector.go index ef53e9cebd4..547cbf69639 100644 --- a/consensus/hotstuff/mocks/random_beacon_inspector.go +++ b/consensus/hotstuff/mocks/random_beacon_inspector.go @@ -3,7 +3,7 @@ package mocks import ( - crypto "github.com/onflow/flow-go/crypto" + crypto "github.com/onflow/crypto" mock "github.com/stretchr/testify/mock" ) diff --git a/consensus/hotstuff/mocks/random_beacon_reconstructor.go b/consensus/hotstuff/mocks/random_beacon_reconstructor.go index 7cb4fe52c75..120a28b393d 100644 --- a/consensus/hotstuff/mocks/random_beacon_reconstructor.go +++ b/consensus/hotstuff/mocks/random_beacon_reconstructor.go @@ -3,7 +3,7 @@ package mocks import ( - crypto "github.com/onflow/flow-go/crypto" + crypto "github.com/onflow/crypto" flow "github.com/onflow/flow-go/model/flow" mock "github.com/stretchr/testify/mock" diff --git a/consensus/hotstuff/mocks/timeout_signature_aggregator.go b/consensus/hotstuff/mocks/timeout_signature_aggregator.go index 2ae0840efce..640cac2d343 100644 --- a/consensus/hotstuff/mocks/timeout_signature_aggregator.go +++ b/consensus/hotstuff/mocks/timeout_signature_aggregator.go @@ -3,7 +3,7 @@ package mocks import ( - crypto "github.com/onflow/flow-go/crypto" + crypto "github.com/onflow/crypto" flow "github.com/onflow/flow-go/model/flow" hotstuff "github.com/onflow/flow-go/consensus/hotstuff" diff --git a/consensus/hotstuff/mocks/weighted_signature_aggregator.go b/consensus/hotstuff/mocks/weighted_signature_aggregator.go index 185d680e244..7b3bac6a023 100644 --- a/consensus/hotstuff/mocks/weighted_signature_aggregator.go +++ b/consensus/hotstuff/mocks/weighted_signature_aggregator.go @@ -3,7 +3,7 @@ package mocks import ( - crypto "github.com/onflow/flow-go/crypto" + crypto "github.com/onflow/crypto" flow "github.com/onflow/flow-go/model/flow" mock "github.com/stretchr/testify/mock" diff --git a/consensus/hotstuff/model/signature_data.go b/consensus/hotstuff/model/signature_data.go index cb6cb5217b3..63f8003f6ad 100644 --- a/consensus/hotstuff/model/signature_data.go +++ b/consensus/hotstuff/model/signature_data.go @@ -4,7 +4,8 @@ import ( "bytes" "fmt" - "github.com/onflow/flow-go/crypto" + "github.com/onflow/crypto" + "github.com/onflow/flow-go/model/encoding/rlp" "github.com/onflow/flow-go/model/flow" ) diff --git a/consensus/hotstuff/model/timeout.go b/consensus/hotstuff/model/timeout.go index 51347e4b41f..a40f3cca680 100644 --- a/consensus/hotstuff/model/timeout.go +++ b/consensus/hotstuff/model/timeout.go @@ -4,9 +4,9 @@ import ( "fmt" "time" + "github.com/onflow/crypto" "github.com/rs/zerolog" - "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/model/flow" ) diff --git a/consensus/hotstuff/model/vote.go b/consensus/hotstuff/model/vote.go index eedf3c975ff..0f50e96bdc8 100644 --- a/consensus/hotstuff/model/vote.go +++ b/consensus/hotstuff/model/vote.go @@ -1,7 +1,8 @@ package model import ( - "github.com/onflow/flow-go/crypto" + "github.com/onflow/crypto" + "github.com/onflow/flow-go/model/flow" ) diff --git a/consensus/hotstuff/randombeacon_inspector.go b/consensus/hotstuff/randombeacon_inspector.go index ceca7c7ab82..85df7777b8d 100644 --- a/consensus/hotstuff/randombeacon_inspector.go +++ b/consensus/hotstuff/randombeacon_inspector.go @@ -1,7 +1,7 @@ package hotstuff import ( - "github.com/onflow/flow-go/crypto" + "github.com/onflow/crypto" ) // RandomBeaconInspector encapsulates all methods needed by a Hotstuff leader to validate the diff --git a/consensus/hotstuff/signature.go b/consensus/hotstuff/signature.go index 0fc56748ab2..672d75feb5c 100644 --- a/consensus/hotstuff/signature.go +++ b/consensus/hotstuff/signature.go @@ -1,7 +1,8 @@ package hotstuff import ( - "github.com/onflow/flow-go/crypto" + "github.com/onflow/crypto" + "github.com/onflow/flow-go/model/flow" ) diff --git a/consensus/hotstuff/signature/block_signer_decoder_test.go b/consensus/hotstuff/signature/block_signer_decoder_test.go index c065e315add..76d13461ccc 100644 --- a/consensus/hotstuff/signature/block_signer_decoder_test.go +++ b/consensus/hotstuff/signature/block_signer_decoder_test.go @@ -11,7 +11,6 @@ import ( hotstuff "github.com/onflow/flow-go/consensus/hotstuff/mocks" "github.com/onflow/flow-go/consensus/hotstuff/model" "github.com/onflow/flow-go/model/flow" - "github.com/onflow/flow-go/model/flow/order" "github.com/onflow/flow-go/module/signature" "github.com/onflow/flow-go/state" "github.com/onflow/flow-go/utils/unittest" @@ -32,7 +31,7 @@ type blockSignerDecoderSuite struct { func (s *blockSignerDecoderSuite) SetupTest() { // the default header fixture creates signerIDs for a committee of 10 nodes, so we prepare a committee same as that - s.allConsensus = unittest.IdentityListFixture(40, unittest.WithRole(flow.RoleConsensus)).Sort(order.Canonical) + s.allConsensus = unittest.IdentityListFixture(40, unittest.WithRole(flow.RoleConsensus)).Sort(flow.Canonical) // mock consensus committee s.committee = hotstuff.NewDynamicCommittee(s.T()) diff --git a/consensus/hotstuff/signature/randombeacon_inspector.go b/consensus/hotstuff/signature/randombeacon_inspector.go index 49d2b1ab50a..e6fa3a1bf0e 100644 --- a/consensus/hotstuff/signature/randombeacon_inspector.go +++ b/consensus/hotstuff/signature/randombeacon_inspector.go @@ -3,8 +3,9 @@ package signature import ( "fmt" + "github.com/onflow/crypto" + "github.com/onflow/flow-go/consensus/hotstuff/model" - "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/module/signature" ) diff --git a/consensus/hotstuff/signature/randombeacon_inspector_test.go b/consensus/hotstuff/signature/randombeacon_inspector_test.go index 3aead48f822..c375b747b87 100644 --- a/consensus/hotstuff/signature/randombeacon_inspector_test.go +++ b/consensus/hotstuff/signature/randombeacon_inspector_test.go @@ -6,13 +6,13 @@ import ( "sync" "testing" + "github.com/onflow/crypto" + "github.com/onflow/crypto/hash" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" "github.com/onflow/flow-go/consensus/hotstuff/model" - "github.com/onflow/flow-go/crypto" - "github.com/onflow/flow-go/crypto/hash" "github.com/onflow/flow-go/module/signature" "github.com/onflow/flow-go/utils/unittest" ) diff --git a/consensus/hotstuff/signature/randombeacon_reconstructor.go b/consensus/hotstuff/signature/randombeacon_reconstructor.go index df18db1acf0..205657bb80e 100644 --- a/consensus/hotstuff/signature/randombeacon_reconstructor.go +++ b/consensus/hotstuff/signature/randombeacon_reconstructor.go @@ -3,9 +3,10 @@ package signature import ( "fmt" + "github.com/onflow/crypto" + "github.com/onflow/flow-go/consensus/hotstuff" "github.com/onflow/flow-go/consensus/hotstuff/model" - "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/state/protocol" ) diff --git a/consensus/hotstuff/signature/randombeacon_signer_store.go b/consensus/hotstuff/signature/randombeacon_signer_store.go index c0092ea289e..c59c5139024 100644 --- a/consensus/hotstuff/signature/randombeacon_signer_store.go +++ b/consensus/hotstuff/signature/randombeacon_signer_store.go @@ -4,7 +4,8 @@ import ( "errors" "fmt" - "github.com/onflow/flow-go/crypto" + "github.com/onflow/crypto" + "github.com/onflow/flow-go/module" "github.com/onflow/flow-go/storage" ) diff --git a/consensus/hotstuff/signature/randombeacon_signer_store_test.go b/consensus/hotstuff/signature/randombeacon_signer_store_test.go index c578e1b2e97..31ed860add4 100644 --- a/consensus/hotstuff/signature/randombeacon_signer_store_test.go +++ b/consensus/hotstuff/signature/randombeacon_signer_store_test.go @@ -5,12 +5,12 @@ import ( "math/rand" "testing" + "github.com/onflow/crypto" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" "github.com/onflow/flow-go/consensus/hotstuff/model" - "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/module" mockmodule "github.com/onflow/flow-go/module/mock" "github.com/onflow/flow-go/storage" diff --git a/consensus/hotstuff/signature/static_randombeacon_signer_store.go b/consensus/hotstuff/signature/static_randombeacon_signer_store.go index 44e2f0d5724..fc211dda390 100644 --- a/consensus/hotstuff/signature/static_randombeacon_signer_store.go +++ b/consensus/hotstuff/signature/static_randombeacon_signer_store.go @@ -1,7 +1,8 @@ package signature import ( - "github.com/onflow/flow-go/crypto" + "github.com/onflow/crypto" + "github.com/onflow/flow-go/module" ) diff --git a/consensus/hotstuff/signature/weighted_signature_aggregator.go b/consensus/hotstuff/signature/weighted_signature_aggregator.go index 6730e30f6f9..3dda42b043b 100644 --- a/consensus/hotstuff/signature/weighted_signature_aggregator.go +++ b/consensus/hotstuff/signature/weighted_signature_aggregator.go @@ -5,9 +5,10 @@ import ( "fmt" "sync" + "github.com/onflow/crypto" + "github.com/onflow/flow-go/consensus/hotstuff" "github.com/onflow/flow-go/consensus/hotstuff/model" - "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/module/signature" ) diff --git a/consensus/hotstuff/signature/weighted_signature_aggregator_test.go b/consensus/hotstuff/signature/weighted_signature_aggregator_test.go index d4b2c28b728..68256071d7c 100644 --- a/consensus/hotstuff/signature/weighted_signature_aggregator_test.go +++ b/consensus/hotstuff/signature/weighted_signature_aggregator_test.go @@ -5,12 +5,11 @@ import ( "sync" "testing" + "github.com/onflow/crypto" + "github.com/onflow/crypto/hash" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/onflow/flow-go/crypto" - "github.com/onflow/flow-go/crypto/hash" - "github.com/onflow/flow-go/consensus/hotstuff/model" "github.com/onflow/flow-go/model/flow" msig "github.com/onflow/flow-go/module/signature" diff --git a/consensus/hotstuff/timeoutcollector/aggregation.go b/consensus/hotstuff/timeoutcollector/aggregation.go index 4a2c3ce5b2b..7e14680c3a6 100644 --- a/consensus/hotstuff/timeoutcollector/aggregation.go +++ b/consensus/hotstuff/timeoutcollector/aggregation.go @@ -4,11 +4,12 @@ import ( "fmt" "sync" + "github.com/onflow/crypto" + "github.com/onflow/crypto/hash" + "github.com/onflow/flow-go/consensus/hotstuff" "github.com/onflow/flow-go/consensus/hotstuff/model" "github.com/onflow/flow-go/consensus/hotstuff/verification" - "github.com/onflow/flow-go/crypto" - "github.com/onflow/flow-go/crypto/hash" "github.com/onflow/flow-go/model/flow" msig "github.com/onflow/flow-go/module/signature" ) diff --git a/consensus/hotstuff/timeoutcollector/aggregation_test.go b/consensus/hotstuff/timeoutcollector/aggregation_test.go index 8adc1cacccc..b449a4f3cc1 100644 --- a/consensus/hotstuff/timeoutcollector/aggregation_test.go +++ b/consensus/hotstuff/timeoutcollector/aggregation_test.go @@ -5,11 +5,10 @@ import ( "sync" "testing" + "github.com/onflow/crypto" + "github.com/onflow/crypto/hash" "github.com/stretchr/testify/require" - "github.com/onflow/flow-go/crypto" - "github.com/onflow/flow-go/crypto/hash" - "github.com/onflow/flow-go/consensus/hotstuff" "github.com/onflow/flow-go/consensus/hotstuff/model" "github.com/onflow/flow-go/consensus/hotstuff/verification" diff --git a/consensus/hotstuff/timeoutcollector/timeout_processor.go b/consensus/hotstuff/timeoutcollector/timeout_processor.go index 60f0e785359..5c959ea8b8f 100644 --- a/consensus/hotstuff/timeoutcollector/timeout_processor.go +++ b/consensus/hotstuff/timeoutcollector/timeout_processor.go @@ -12,7 +12,6 @@ import ( "github.com/onflow/flow-go/consensus/hotstuff/model" "github.com/onflow/flow-go/consensus/hotstuff/tracker" "github.com/onflow/flow-go/model/flow" - "github.com/onflow/flow-go/model/flow/order" "github.com/onflow/flow-go/module/signature" ) @@ -266,8 +265,8 @@ func (p *TimeoutProcessor) buildTC() (*flow.TimeoutCertificate, error) { // we need to canonically order the respective `newestQCView`, so we can properly map signer to `newestQCView` after decoding. // sort data in canonical order - slices.SortFunc(signersData, func(lhs, rhs hotstuff.TimeoutSignerInfo) bool { - return order.IdentifierCanonical(lhs.Signer, rhs.Signer) + slices.SortFunc(signersData, func(lhs, rhs hotstuff.TimeoutSignerInfo) int { + return flow.IdentifierCanonical(lhs.Signer, rhs.Signer) }) // extract signers and data separately diff --git a/consensus/hotstuff/timeoutcollector/timeout_processor_test.go b/consensus/hotstuff/timeoutcollector/timeout_processor_test.go index b37188c5857..c9fe81651f9 100644 --- a/consensus/hotstuff/timeoutcollector/timeout_processor_test.go +++ b/consensus/hotstuff/timeoutcollector/timeout_processor_test.go @@ -7,6 +7,7 @@ import ( "sync" "testing" + "github.com/onflow/crypto" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" @@ -20,9 +21,7 @@ import ( hotstuffvalidator "github.com/onflow/flow-go/consensus/hotstuff/validator" "github.com/onflow/flow-go/consensus/hotstuff/verification" "github.com/onflow/flow-go/consensus/hotstuff/votecollector" - "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/model/flow" - "github.com/onflow/flow-go/model/flow/order" "github.com/onflow/flow-go/module/local" msig "github.com/onflow/flow-go/module/signature" "github.com/onflow/flow-go/utils/unittest" @@ -55,7 +54,7 @@ func (s *TimeoutProcessorTestSuite) SetupTest() { s.validator = mocks.NewValidator(s.T()) s.sigAggregator = mocks.NewTimeoutSignatureAggregator(s.T()) s.notifier = mocks.NewTimeoutCollectorConsumer(s.T()) - s.participants = unittest.IdentityListFixture(11, unittest.WithWeight(s.sigWeight)).Sort(order.Canonical) + s.participants = unittest.IdentityListFixture(11, unittest.WithWeight(s.sigWeight)).Sort(flow.Canonical) s.signer = s.participants[0] s.view = (uint64)(rand.Uint32() + 100) s.totalWeight = *atomic.NewUint64(0) @@ -472,7 +471,7 @@ func TestTimeoutProcessor_BuildVerifyTC(t *testing.T) { signers[identity.NodeID] = verification.NewStakingSigner(me) }) // identities must be in canonical order - stakingSigners = stakingSigners.Sort(order.Canonical) + stakingSigners = stakingSigners.Sort(flow.Canonical) // utility function which generates a valid timeout for every signer createTimeouts := func(participants flow.IdentityList, view uint64, newestQC *flow.QuorumCertificate, lastViewTC *flow.TimeoutCertificate) []*model.TimeoutObject { diff --git a/consensus/hotstuff/verification/combined_signer_v2.go b/consensus/hotstuff/verification/combined_signer_v2.go index a7aa3fd6b5a..49691eaaf79 100644 --- a/consensus/hotstuff/verification/combined_signer_v2.go +++ b/consensus/hotstuff/verification/combined_signer_v2.go @@ -4,9 +4,10 @@ import ( "errors" "fmt" + "github.com/onflow/crypto/hash" + "github.com/onflow/flow-go/consensus/hotstuff" "github.com/onflow/flow-go/consensus/hotstuff/model" - "github.com/onflow/flow-go/crypto/hash" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/module" msig "github.com/onflow/flow-go/module/signature" diff --git a/consensus/hotstuff/verification/combined_signer_v2_test.go b/consensus/hotstuff/verification/combined_signer_v2_test.go index 6947a12acf1..776008c01c8 100644 --- a/consensus/hotstuff/verification/combined_signer_v2_test.go +++ b/consensus/hotstuff/verification/combined_signer_v2_test.go @@ -3,6 +3,7 @@ package verification import ( "testing" + "github.com/onflow/crypto" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" @@ -10,7 +11,6 @@ import ( "github.com/onflow/flow-go/consensus/hotstuff/mocks" "github.com/onflow/flow-go/consensus/hotstuff/model" "github.com/onflow/flow-go/consensus/hotstuff/signature" - "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/module" "github.com/onflow/flow-go/module/local" diff --git a/consensus/hotstuff/verification/combined_signer_v3.go b/consensus/hotstuff/verification/combined_signer_v3.go index 6ab6de760d5..09651fc4925 100644 --- a/consensus/hotstuff/verification/combined_signer_v3.go +++ b/consensus/hotstuff/verification/combined_signer_v3.go @@ -4,9 +4,10 @@ import ( "errors" "fmt" + "github.com/onflow/crypto/hash" + "github.com/onflow/flow-go/consensus/hotstuff" "github.com/onflow/flow-go/consensus/hotstuff/model" - "github.com/onflow/flow-go/crypto/hash" "github.com/onflow/flow-go/model/encoding" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/module" diff --git a/consensus/hotstuff/verification/combined_signer_v3_test.go b/consensus/hotstuff/verification/combined_signer_v3_test.go index 1a59d6d047a..2e533e4f92a 100644 --- a/consensus/hotstuff/verification/combined_signer_v3_test.go +++ b/consensus/hotstuff/verification/combined_signer_v3_test.go @@ -7,7 +7,7 @@ import ( "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - "github.com/onflow/flow-go/crypto" + "github.com/onflow/crypto" "github.com/onflow/flow-go/consensus/hotstuff" "github.com/onflow/flow-go/consensus/hotstuff/mocks" diff --git a/consensus/hotstuff/verification/combined_verifier_v2.go b/consensus/hotstuff/verification/combined_verifier_v2.go index 560cb1f8ece..c66e8c35c4f 100644 --- a/consensus/hotstuff/verification/combined_verifier_v2.go +++ b/consensus/hotstuff/verification/combined_verifier_v2.go @@ -4,9 +4,10 @@ import ( "errors" "fmt" + "github.com/onflow/crypto/hash" + "github.com/onflow/flow-go/consensus/hotstuff" "github.com/onflow/flow-go/consensus/hotstuff/model" - "github.com/onflow/flow-go/crypto/hash" "github.com/onflow/flow-go/model/flow" msig "github.com/onflow/flow-go/module/signature" "github.com/onflow/flow-go/state/protocol" diff --git a/consensus/hotstuff/verification/combined_verifier_v3.go b/consensus/hotstuff/verification/combined_verifier_v3.go index 39af088ae0d..0154f364424 100644 --- a/consensus/hotstuff/verification/combined_verifier_v3.go +++ b/consensus/hotstuff/verification/combined_verifier_v3.go @@ -4,10 +4,11 @@ import ( "errors" "fmt" + "github.com/onflow/crypto" + "github.com/onflow/crypto/hash" + "github.com/onflow/flow-go/consensus/hotstuff" "github.com/onflow/flow-go/consensus/hotstuff/model" - "github.com/onflow/flow-go/crypto" - "github.com/onflow/flow-go/crypto/hash" "github.com/onflow/flow-go/model/encoding" "github.com/onflow/flow-go/model/flow" msig "github.com/onflow/flow-go/module/signature" diff --git a/consensus/hotstuff/verification/common.go b/consensus/hotstuff/verification/common.go index 00d73a0caee..4febede7412 100644 --- a/consensus/hotstuff/verification/common.go +++ b/consensus/hotstuff/verification/common.go @@ -3,9 +3,10 @@ package verification import ( "fmt" + "github.com/onflow/crypto" + "github.com/onflow/crypto/hash" + "github.com/onflow/flow-go/consensus/hotstuff/model" - "github.com/onflow/flow-go/crypto" - "github.com/onflow/flow-go/crypto/hash" "github.com/onflow/flow-go/model/flow" "encoding/binary" diff --git a/consensus/hotstuff/verification/staking_signer.go b/consensus/hotstuff/verification/staking_signer.go index 91c35e1cddd..bbc590d2e07 100644 --- a/consensus/hotstuff/verification/staking_signer.go +++ b/consensus/hotstuff/verification/staking_signer.go @@ -3,9 +3,10 @@ package verification import ( "fmt" + "github.com/onflow/crypto/hash" + "github.com/onflow/flow-go/consensus/hotstuff" "github.com/onflow/flow-go/consensus/hotstuff/model" - "github.com/onflow/flow-go/crypto/hash" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/module" msig "github.com/onflow/flow-go/module/signature" diff --git a/consensus/hotstuff/verification/staking_verifier.go b/consensus/hotstuff/verification/staking_verifier.go index ecd5013f171..94e6918aca4 100644 --- a/consensus/hotstuff/verification/staking_verifier.go +++ b/consensus/hotstuff/verification/staking_verifier.go @@ -3,9 +3,10 @@ package verification import ( "fmt" + "github.com/onflow/crypto/hash" + "github.com/onflow/flow-go/consensus/hotstuff" "github.com/onflow/flow-go/consensus/hotstuff/model" - "github.com/onflow/flow-go/crypto/hash" "github.com/onflow/flow-go/model/flow" msig "github.com/onflow/flow-go/module/signature" ) diff --git a/consensus/hotstuff/votecollector/combined_vote_processor_v2.go b/consensus/hotstuff/votecollector/combined_vote_processor_v2.go index 69d6fb350af..fc7c8686c8e 100644 --- a/consensus/hotstuff/votecollector/combined_vote_processor_v2.go +++ b/consensus/hotstuff/votecollector/combined_vote_processor_v2.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" + "github.com/onflow/crypto" "github.com/rs/zerolog" "go.uber.org/atomic" @@ -11,7 +12,6 @@ import ( "github.com/onflow/flow-go/consensus/hotstuff/model" "github.com/onflow/flow-go/consensus/hotstuff/signature" "github.com/onflow/flow-go/consensus/hotstuff/verification" - "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/model/flow" msig "github.com/onflow/flow-go/module/signature" ) diff --git a/consensus/hotstuff/votecollector/combined_vote_processor_v2_test.go b/consensus/hotstuff/votecollector/combined_vote_processor_v2_test.go index 1c005388d40..15f88a1d7fb 100644 --- a/consensus/hotstuff/votecollector/combined_vote_processor_v2_test.go +++ b/consensus/hotstuff/votecollector/combined_vote_processor_v2_test.go @@ -6,6 +6,7 @@ import ( "sync" "testing" + "github.com/onflow/crypto" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" @@ -22,7 +23,6 @@ import ( hsig "github.com/onflow/flow-go/consensus/hotstuff/signature" hotstuffvalidator "github.com/onflow/flow-go/consensus/hotstuff/validator" "github.com/onflow/flow-go/consensus/hotstuff/verification" - "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/model/encodable" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/module/local" diff --git a/consensus/hotstuff/votecollector/combined_vote_processor_v3.go b/consensus/hotstuff/votecollector/combined_vote_processor_v3.go index 1a2bdf72fee..3a1955c0870 100644 --- a/consensus/hotstuff/votecollector/combined_vote_processor_v3.go +++ b/consensus/hotstuff/votecollector/combined_vote_processor_v3.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" + "github.com/onflow/crypto" "github.com/rs/zerolog" "go.uber.org/atomic" @@ -11,7 +12,6 @@ import ( "github.com/onflow/flow-go/consensus/hotstuff/model" "github.com/onflow/flow-go/consensus/hotstuff/signature" "github.com/onflow/flow-go/consensus/hotstuff/verification" - "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/model/encoding" "github.com/onflow/flow-go/model/flow" msig "github.com/onflow/flow-go/module/signature" diff --git a/consensus/hotstuff/votecollector/combined_vote_processor_v3_test.go b/consensus/hotstuff/votecollector/combined_vote_processor_v3_test.go index 831a68e1650..bede2e54942 100644 --- a/consensus/hotstuff/votecollector/combined_vote_processor_v3_test.go +++ b/consensus/hotstuff/votecollector/combined_vote_processor_v3_test.go @@ -6,6 +6,7 @@ import ( "sync" "testing" + "github.com/onflow/crypto" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" @@ -21,7 +22,6 @@ import ( hsig "github.com/onflow/flow-go/consensus/hotstuff/signature" hotstuffvalidator "github.com/onflow/flow-go/consensus/hotstuff/validator" "github.com/onflow/flow-go/consensus/hotstuff/verification" - "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/model/encodable" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/module/local" diff --git a/consensus/hotstuff/votecollector/staking_vote_processor.go b/consensus/hotstuff/votecollector/staking_vote_processor.go index a470d97bc67..72e4f21e6e6 100644 --- a/consensus/hotstuff/votecollector/staking_vote_processor.go +++ b/consensus/hotstuff/votecollector/staking_vote_processor.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" + "github.com/onflow/crypto" "github.com/rs/zerolog" "go.uber.org/atomic" @@ -11,7 +12,6 @@ import ( "github.com/onflow/flow-go/consensus/hotstuff/model" "github.com/onflow/flow-go/consensus/hotstuff/signature" "github.com/onflow/flow-go/consensus/hotstuff/verification" - "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/model/flow" msig "github.com/onflow/flow-go/module/signature" ) diff --git a/consensus/hotstuff/votecollector/staking_vote_processor_test.go b/consensus/hotstuff/votecollector/staking_vote_processor_test.go index b6efe8f93c4..0f1422b56fd 100644 --- a/consensus/hotstuff/votecollector/staking_vote_processor_test.go +++ b/consensus/hotstuff/votecollector/staking_vote_processor_test.go @@ -5,6 +5,7 @@ import ( "sync" "testing" + "github.com/onflow/crypto" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" @@ -17,7 +18,6 @@ import ( "github.com/onflow/flow-go/consensus/hotstuff/model" hotstuffvalidator "github.com/onflow/flow-go/consensus/hotstuff/validator" "github.com/onflow/flow-go/consensus/hotstuff/verification" - "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/module/local" modulemock "github.com/onflow/flow-go/module/mock" diff --git a/consensus/hotstuff/votecollector/testutil.go b/consensus/hotstuff/votecollector/testutil.go index 26ea9b69547..e36aca23170 100644 --- a/consensus/hotstuff/votecollector/testutil.go +++ b/consensus/hotstuff/votecollector/testutil.go @@ -1,13 +1,13 @@ package votecollector import ( + "github.com/onflow/crypto" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/suite" "github.com/onflow/flow-go/consensus/hotstuff/helper" mockhotstuff "github.com/onflow/flow-go/consensus/hotstuff/mocks" "github.com/onflow/flow-go/consensus/hotstuff/model" - "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/model/flow" ) diff --git a/consensus/integration/epoch_test.go b/consensus/integration/epoch_test.go index aa41de368fe..2a218fbd2b8 100644 --- a/consensus/integration/epoch_test.go +++ b/consensus/integration/epoch_test.go @@ -12,7 +12,6 @@ import ( "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/model/flow/filter" "github.com/onflow/flow-go/model/flow/mapfunc" - "github.com/onflow/flow-go/model/flow/order" "github.com/onflow/flow-go/state/protocol/inmem" "github.com/onflow/flow-go/utils/unittest" ) @@ -218,7 +217,7 @@ func withNextEpoch( // convert to encodable representation for simple modification encodableSnapshot := snapshot.Encodable() - nextEpochIdentities = nextEpochIdentities.Sort(order.Canonical) + nextEpochIdentities = nextEpochIdentities.Sort(flow.Canonical) currEpoch := &encodableSnapshot.Epochs.Current // take pointer so assignments apply currEpoch.FinalView = currEpoch.FirstView + curEpochViews - 1 // first epoch lasts curEpochViews @@ -255,7 +254,7 @@ func withNextEpoch( nextEpochIdentities. Filter(filter.Not(filter.In(encodableSnapshot.Identities))). Map(mapfunc.WithWeight(0))..., - ).Sort(order.Canonical) + ).Sort(flow.Canonical) return inmem.SnapshotFromEncodable(encodableSnapshot) } diff --git a/consensus/integration/nodes_test.go b/consensus/integration/nodes_test.go index 8b8963cdc32..105cea370c6 100644 --- a/consensus/integration/nodes_test.go +++ b/consensus/integration/nodes_test.go @@ -10,6 +10,7 @@ import ( "github.com/dgraph-io/badger/v2" "github.com/gammazero/workerpool" + "github.com/onflow/crypto" "github.com/rs/zerolog" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" @@ -28,14 +29,12 @@ import ( "github.com/onflow/flow-go/consensus/hotstuff/verification" "github.com/onflow/flow-go/consensus/hotstuff/voteaggregator" "github.com/onflow/flow-go/consensus/hotstuff/votecollector" - "github.com/onflow/flow-go/crypto" synceng "github.com/onflow/flow-go/engine/common/synchronization" "github.com/onflow/flow-go/engine/consensus/compliance" "github.com/onflow/flow-go/engine/consensus/message_hub" "github.com/onflow/flow-go/model/bootstrap" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/model/flow/filter" - "github.com/onflow/flow-go/model/flow/order" "github.com/onflow/flow-go/module" "github.com/onflow/flow-go/module/buffer" builder "github.com/onflow/flow-go/module/builder/consensus" @@ -257,7 +256,7 @@ func createRootBlockData(participantData *run.ParticipantData) (*flow.Block, *fl // add other roles to create a complete identity list participants := unittest.CompleteIdentitySet(consensusParticipants...) - participants.Sort(order.Canonical) + participants.Sort(flow.Canonical) dkgParticipantsKeys := make([]crypto.PublicKey, 0, len(consensusParticipants)) for _, participant := range participants.Filter(filter.HasRole(flow.RoleConsensus)) { @@ -289,7 +288,7 @@ func createRootBlockData(participantData *run.ParticipantData) (*flow.Block, *fl } func createPrivateNodeIdentities(n int) []bootstrap.NodeInfo { - consensus := unittest.IdentityListFixture(n, unittest.WithRole(flow.RoleConsensus)).Sort(order.Canonical) + consensus := unittest.IdentityListFixture(n, unittest.WithRole(flow.RoleConsensus)).Sort(flow.Canonical) infos := make([]bootstrap.NodeInfo, 0, n) for _, node := range consensus { networkPrivKey := unittest.NetworkingPrivKeyFixture() diff --git a/crypto/hash/empty.go b/crypto/hash/empty.go new file mode 100644 index 00000000000..7adc22fef06 --- /dev/null +++ b/crypto/hash/empty.go @@ -0,0 +1 @@ +package hash diff --git a/crypto/random/empty.go b/crypto/random/empty.go new file mode 100644 index 00000000000..eeab28a038e --- /dev/null +++ b/crypto/random/empty.go @@ -0,0 +1 @@ +package random diff --git a/crypto_adx_flag.mk b/crypto_adx_flag.mk index 277a4c3fbb4..0d0d5ac7467 100644 --- a/crypto_adx_flag.mk +++ b/crypto_adx_flag.mk @@ -1,5 +1,7 @@ -# This script can be imported by Makefiles in order to set the `CRYPTO_FLAG` automatically. -# The `CRYPTO_FLAG` is a Go command flag that should be used when the machine's CPU executing +# This script can be imported by Makefiles in order to set the `CRYPTO_FLAG` automatically for +# a native build (build and run on the same machine NOT for cross-compilation). +# +# The `CRYPTO_FLAG` is a Go command flag that should be used when the target machine's CPU executing # the command may not support ADX instructions. # For new machines that support ADX instructions, the `CRYPTO_FLAG` flag is not needed (or set # to an empty string). @@ -14,6 +16,8 @@ else ADX_SUPPORT := 1 endif +DISABLE_ADX := "-O2 -D__BLST_PORTABLE__" + # Then, set `CRYPTO_FLAG` # the crypto package uses BLST source files underneath which may use ADX instructions. ifeq ($(ADX_SUPPORT), 1) @@ -21,5 +25,5 @@ ifeq ($(ADX_SUPPORT), 1) CRYPTO_FLAG := "" else # if ADX instructions aren't supported, this CGO flags uses a slower non-ADX implementation - CRYPTO_FLAG := "-O -D__BLST_PORTABLE__" + CRYPTO_FLAG := $(DISABLE_ADX) endif \ No newline at end of file diff --git a/engine/access/access_test.go b/engine/access/access_test.go index 39dbc155634..a84ef6fac56 100644 --- a/engine/access/access_test.go +++ b/engine/access/access_test.go @@ -18,11 +18,12 @@ import ( "github.com/stretchr/testify/suite" "google.golang.org/protobuf/testing/protocmp" + "github.com/onflow/crypto" + "github.com/onflow/flow-go/access" "github.com/onflow/flow-go/cmd/build" hsmock "github.com/onflow/flow-go/consensus/hotstuff/mocks" "github.com/onflow/flow-go/consensus/hotstuff/model" - "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/engine/access/ingestion" accessmock "github.com/onflow/flow-go/engine/access/mock" "github.com/onflow/flow-go/engine/access/rpc/backend" @@ -984,7 +985,7 @@ func (suite *Suite) TestExecuteScript() { Log: suite.log, SnapshotHistoryLimit: backend.DefaultSnapshotHistoryLimit, Communicator: backend.NewNodeCommunicator(false), - ScriptExecutionMode: backend.ScriptExecutionModeExecutionNodesOnly, + ScriptExecutionMode: backend.IndexQueryModeExecutionNodesOnly, TxErrorMessagesCacheSize: 1000, }) require.NoError(suite.T(), err) diff --git a/engine/access/handle_irrecoverable_state_test.go b/engine/access/handle_irrecoverable_state_test.go index d57750dc86a..9848fed0424 100644 --- a/engine/access/handle_irrecoverable_state_test.go +++ b/engine/access/handle_irrecoverable_state_test.go @@ -17,9 +17,9 @@ import ( "google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials/insecure" + "github.com/onflow/crypto" restclient "github.com/onflow/flow/openapi/go-client-generated" - "github.com/onflow/flow-go/crypto" accessmock "github.com/onflow/flow-go/engine/access/mock" "github.com/onflow/flow-go/engine/access/rest" "github.com/onflow/flow-go/engine/access/rest/routes" diff --git a/engine/access/ingestion/engine.go b/engine/access/ingestion/engine.go index 54369ccebdb..d2349487eec 100644 --- a/engine/access/ingestion/engine.go +++ b/engine/access/ingestion/engine.go @@ -26,31 +26,39 @@ import ( "github.com/onflow/flow-go/utils/logging" ) -// time to wait for the all the missing collections to be received at node startup -const collectionCatchupTimeout = 30 * time.Second +const ( + // time to wait for the all the missing collections to be received at node startup + collectionCatchupTimeout = 30 * time.Second -// time to poll the storage to check if missing collections have been received -const collectionCatchupDBPollInterval = 10 * time.Millisecond + // time to poll the storage to check if missing collections have been received + collectionCatchupDBPollInterval = 10 * time.Millisecond -// time to update the FullBlockHeight index -const fullBlockUpdateInterval = 1 * time.Minute + // time to update the FullBlockHeight index + fullBlockRefreshInterval = 1 * time.Second -// a threshold of number of blocks with missing collections beyond which collections should be re-requested -// this is to prevent spamming the collection nodes with request -const missingCollsForBlkThreshold = 100 + // time to request missing collections from the network + missingCollsRequestInterval = 1 * time.Minute -// a threshold of block height beyond which collections should be re-requested (regardless of the number of blocks for which collection are missing) -// this is to ensure that if a collection is missing for a long time (in terms of block height) it is eventually re-requested -const missingCollsForAgeThreshold = 100 + // a threshold of number of blocks with missing collections beyond which collections should be re-requested + // this is to prevent spamming the collection nodes with request + missingCollsForBlkThreshold = 100 -// default queue capacity -const defaultQueueCapacity = 10_000 + // a threshold of block height beyond which collections should be re-requested (regardless of the number of blocks for which collection are missing) + // this is to ensure that if a collection is missing for a long time (in terms of block height) it is eventually re-requested + missingCollsForAgeThreshold = 100 -var defaultCollectionCatchupTimeout = collectionCatchupTimeout -var defaultCollectionCatchupDBPollInterval = collectionCatchupDBPollInterval -var defaultFullBlockUpdateInterval = fullBlockUpdateInterval -var defaultMissingCollsForBlkThreshold = missingCollsForBlkThreshold -var defaultMissingCollsForAgeThreshold = missingCollsForAgeThreshold + // default queue capacity + defaultQueueCapacity = 10_000 +) + +var ( + defaultCollectionCatchupTimeout = collectionCatchupTimeout + defaultCollectionCatchupDBPollInterval = collectionCatchupDBPollInterval + defaultFullBlockRefreshInterval = fullBlockRefreshInterval + defaultMissingCollsRequestInterval = missingCollsRequestInterval + defaultMissingCollsForBlkThreshold = missingCollsForBlkThreshold + defaultMissingCollsForAgeThreshold uint64 = missingCollsForAgeThreshold +) // Engine represents the ingestion engine, used to funnel data from other nodes // to a centralized location that can be queried by a user @@ -200,6 +208,8 @@ func (e *Engine) initLastFullBlockHeightIndex() error { if err != nil { return fmt.Errorf("failed to get root block: %w", err) } + + // insert is a noop if the index has already been initialized and no error is returned err = e.blocks.InsertLastFullBlockHeightIfNotExists(rootBlock.Height) if err != nil { return fmt.Errorf("failed to update last full block height during ingestion engine startup: %w", err) @@ -227,13 +237,30 @@ func (e *Engine) processBackground(ctx irrecoverable.SignalerContext, ready comp } ready() - ticker := time.NewTicker(defaultFullBlockUpdateInterval) + updateTicker := time.NewTicker(defaultFullBlockRefreshInterval) + defer updateTicker.Stop() + + requestTicker := time.NewTicker(defaultMissingCollsRequestInterval) + defer requestTicker.Stop() + for { select { case <-ctx.Done(): return - case <-ticker.C: - e.updateLastFullBlockReceivedIndex() + + // refresh the LastFullBlockReceived index + case <-updateTicker.C: + err := e.updateLastFullBlockReceivedIndex() + if err != nil { + ctx.Throw(err) + } + + // request missing collections from the network + case <-requestTicker.C: + err := e.checkMissingCollections() + if err != nil { + ctx.Throw(err) + } } } } @@ -312,8 +339,6 @@ func (e *Engine) processAvailableFinalizedBlocks(ctx context.Context) error { e.log.Error().Err(err).Hex("block_id", blockID[:]).Msg("failed to process block") continue } - - e.trackFinalizedMetricForBlock(hb) } } @@ -352,7 +377,7 @@ func (e *Engine) SubmitLocal(event interface{}) { // Submit submits the given event from the node with the given origin ID // for processing in a non-blocking manner. It returns instantly and logs // a potential processing error internally when done. -func (e *Engine) Submit(channel channels.Channel, originID flow.Identifier, event interface{}) { +func (e *Engine) Submit(_ channels.Channel, originID flow.Identifier, event interface{}) { err := e.process(originID, event) if err != nil { engine.LogError(e.log, err) @@ -366,7 +391,7 @@ func (e *Engine) ProcessLocal(event interface{}) error { // Process processes the given event from the node with the given origin ID in // a blocking manner. It returns the potential processing error when done. -func (e *Engine) Process(channel channels.Channel, originID flow.Identifier, event interface{}) error { +func (e *Engine) Process(_ channels.Channel, originID flow.Identifier, event interface{}) error { return e.process(originID, event) } @@ -419,20 +444,15 @@ func (e *Engine) processFinalizedBlock(blockID flow.Identifier) error { // queue requesting each of the collections from the collection node e.requestCollectionsInFinalizedBlock(block.Payload.Guarantees) + e.trackFinalizedMetricForBlock(block) + return nil } -func (e *Engine) trackFinalizedMetricForBlock(hb *model.Block) { - // TODO: consider using storage.Index.ByBlockID, the index contains collection id and seals ID - // retrieve the block - block, err := e.blocks.ByID(hb.BlockID) - if err != nil { - e.log.Warn().Err(err).Msg("could not track tx finalized metric: finalized block not found locally") - return - } - - // TODO lookup actual finalization time by looking at the block finalizing `b` +func (e *Engine) trackFinalizedMetricForBlock(block *flow.Block) { + // TODO: lookup actual finalization time by looking at the block finalizing `b` now := time.Now().UTC() + blockID := block.ID() // mark all transactions as finalized // TODO: sample to reduce performance overhead @@ -452,14 +472,14 @@ func (e *Engine) trackFinalizedMetricForBlock(hb *model.Block) { } } - if ti, found := e.blocksToMarkExecuted.ByID(hb.BlockID); found { + if ti, found := e.blocksToMarkExecuted.ByID(blockID); found { e.trackExecutedMetricForBlock(block, ti) e.metrics.UpdateExecutionReceiptMaxHeight(block.Header.Height) - e.blocksToMarkExecuted.Remove(hb.BlockID) + e.blocksToMarkExecuted.Remove(blockID) } } -func (e *Engine) handleExecutionReceipt(originID flow.Identifier, r *flow.ExecutionReceipt) error { +func (e *Engine) handleExecutionReceipt(_ flow.Identifier, r *flow.ExecutionReceipt) error { // persist the execution receipt locally, storing will also index the receipt err := e.executionReceipts.Store(r) if err != nil { @@ -513,17 +533,7 @@ func (e *Engine) trackExecutedMetricForBlock(block *flow.Block, ti time.Time) { } } -// handleCollection handles the response of the a collection request made earlier when a block was received -func (e *Engine) handleCollection(originID flow.Identifier, entity flow.Entity) error { - - // convert the entity to a strictly typed collection - collection, ok := entity.(*flow.Collection) - if !ok { - return fmt.Errorf("invalid entity type (%T)", entity) - } - - light := collection.Light() - +func (e *Engine) trackExecutedMetricForCollection(light *flow.LightCollection) { if ti, found := e.collectionsToMarkFinalized.ByID(light.ID()); found { for _, t := range light.Transactions { e.metrics.TransactionFinalized(t, ti) @@ -537,6 +547,20 @@ func (e *Engine) handleCollection(originID flow.Identifier, entity flow.Entity) } e.collectionsToMarkExecuted.Remove(light.ID()) } +} + +// handleCollection handles the response of the a collection request made earlier when a block was received +func (e *Engine) handleCollection(_ flow.Identifier, entity flow.Entity) error { + + // convert the entity to a strictly typed collection + collection, ok := entity.(*flow.Collection) + if !ok { + return fmt.Errorf("invalid entity type (%T)", entity) + } + + light := collection.Light() + + e.trackExecutedMetricForCollection(&light) // FIX: we can't index guarantees here, as we might have more than one block // with the same collection as long as it is not finalized @@ -613,7 +637,7 @@ func (e *Engine) requestMissingCollections(ctx context.Context) error { missingColls, err := e.missingCollectionsAtHeight(i) if err != nil { - return fmt.Errorf("failed to retreive missing collections by height %d during collection catchup: %w", i, err) + return fmt.Errorf("failed to retrieve missing collections by height %d during collection catchup: %w", i, err) } // request the missing collections @@ -654,14 +678,14 @@ func (e *Engine) requestMissingCollections(ctx context.Context) error { var foundColls []flow.Identifier // query db to find if collections are still missing - for collId := range missingCollMap { - found, err := e.lookupCollection(collId) + for collID := range missingCollMap { + found, err := e.haveCollection(collID) if err != nil { return err } // if collection found in local db, remove it from missingColls later if found { - foundColls = append(foundColls, collId) + foundColls = append(foundColls, collID) } } @@ -676,113 +700,128 @@ func (e *Engine) requestMissingCollections(ctx context.Context) error { return nil } -// updateLastFullBlockReceivedIndex keeps the FullBlockHeight index up to date and requests missing collections if -// the number of blocks missing collection have reached the defaultMissingCollsForBlkThreshold value. -// (The FullBlockHeight index indicates that block for which all collections have been received) -func (e *Engine) updateLastFullBlockReceivedIndex() { +// updateLastFullBlockReceivedIndex finds the next highest height where all previous collections +// have been indexed, and updates the LastFullBlockReceived index to that height +func (e *Engine) updateLastFullBlockReceivedIndex() error { + lastFullHeight, err := e.blocks.GetLastFullBlockHeight() + if err != nil { + return fmt.Errorf("failed to get last full block height: %w", err) + } - logError := func(err error) { - e.log.Error().Err(err).Msg("failed to update the last full block height") + finalBlk, err := e.state.Final().Head() + if err != nil { + return fmt.Errorf("failed to get finalized block: %w", err) } + finalizedHeight := finalBlk.Height - lastFullHeight, err := e.blocks.GetLastFullBlockHeight() + // track the latest contiguous full height + newLastFullHeight, err := e.lowestHeightWithMissingCollection(lastFullHeight, finalizedHeight) if err != nil { - if !errors.Is(err, storage.ErrNotFound) { - logError(err) - return + return fmt.Errorf("failed to find last full block received height: %w", err) + } + + // if more contiguous blocks are now complete, update db + if newLastFullHeight > lastFullHeight { + err = e.blocks.UpdateLastFullBlockHeight(newLastFullHeight) + if err != nil { + return fmt.Errorf("failed to update last full block height") } - // use the root height as the last full height - header, err := e.state.Params().FinalizedRoot() + + e.metrics.UpdateLastFullBlockHeight(newLastFullHeight) + + e.log.Debug(). + Uint64("last_full_block_height", newLastFullHeight). + Msg("updated LastFullBlockReceived index") + } + + return nil +} + +// lowestHeightWithMissingCollection returns the lowest height that is missing collections +func (e *Engine) lowestHeightWithMissingCollection(lastFullHeight, finalizedHeight uint64) (uint64, error) { + newLastFullHeight := lastFullHeight + + for i := lastFullHeight + 1; i <= finalizedHeight; i++ { + missingColls, err := e.missingCollectionsAtHeight(i) if err != nil { - logError(err) - return + return 0, err + } + + // return when we find the first block with missing collections + if len(missingColls) > 0 { + return newLastFullHeight, nil } - lastFullHeight = header.Height + + newLastFullHeight = i } - e.log.Debug().Uint64("last_full_block_height", lastFullHeight).Msg("updating LastFullBlockReceived index...") + return newLastFullHeight, nil +} + +// checkMissingCollections requests missing collections if the number of blocks missing collections +// have reached the defaultMissingCollsForBlkThreshold value. +func (e *Engine) checkMissingCollections() error { + lastFullHeight, err := e.blocks.GetLastFullBlockHeight() + if err != nil { + return err + } finalBlk, err := e.state.Final().Head() if err != nil { - logError(err) - return + return fmt.Errorf("failed to get finalized block: %w", err) } finalizedHeight := finalBlk.Height - // track number of incomplete blocks + // number of blocks with missing collections incompleteBlksCnt := 0 - // track the latest contiguous full height - latestFullHeight := lastFullHeight - // collect all missing collections var allMissingColls []*flow.CollectionGuarantee // start from the next block till we either hit the finalized block or cross the max collection missing threshold for i := lastFullHeight + 1; i <= finalizedHeight && incompleteBlksCnt < defaultMissingCollsForBlkThreshold; i++ { - - // find missing collections for block at height i missingColls, err := e.missingCollectionsAtHeight(i) if err != nil { - logError(err) - return + return fmt.Errorf("failed to find missing collections at height %d: %w", i, err) } - // if there are missing collections - if len(missingColls) > 0 { - - // increment number of incomplete blocks - incompleteBlksCnt++ - - // collect the missing collections for requesting later - allMissingColls = append(allMissingColls, missingColls...) - + if len(missingColls) == 0 { continue } - // if there are no missing collections so far, advance the latestFullHeight pointer - if incompleteBlksCnt == 0 { - latestFullHeight = i - } - } - - // if more contiguous blocks are now complete, update db - if latestFullHeight > lastFullHeight { - err = e.blocks.UpdateLastFullBlockHeight(latestFullHeight) - if err != nil { - logError(err) - return - } + incompleteBlksCnt++ - e.metrics.UpdateLastFullBlockHeight(lastFullHeight) + allMissingColls = append(allMissingColls, missingColls...) } - // additionally, if more than threshold blocks have missing collection OR collections are missing since defaultMissingCollsForAgeThreshold, re-request those collections - if incompleteBlksCnt >= defaultMissingCollsForBlkThreshold || (finalizedHeight-lastFullHeight) > uint64(defaultMissingCollsForAgeThreshold) { + // additionally, if more than threshold blocks have missing collections OR collections are + // missing since defaultMissingCollsForAgeThreshold, re-request those collections + if incompleteBlksCnt >= defaultMissingCollsForBlkThreshold || + (finalizedHeight-lastFullHeight) > defaultMissingCollsForAgeThreshold { // warn log since this should generally not happen e.log.Warn(). + Uint64("finalized_height", finalizedHeight). + Uint64("last_full_blk_height", lastFullHeight). Int("missing_collection_blk_count", incompleteBlksCnt). - Int("threshold", defaultMissingCollsForBlkThreshold). - Uint64("last_full_blk_height", latestFullHeight). + Int("missing_collection_count", len(allMissingColls)). Msg("re-requesting missing collections") e.requestCollectionsInFinalizedBlock(allMissingColls) } - e.log.Debug().Uint64("last_full_blk_height", latestFullHeight).Msg("updated LastFullBlockReceived index") + return nil } // missingCollectionsAtHeight returns all missing collection guarantees at a given height func (e *Engine) missingCollectionsAtHeight(h uint64) ([]*flow.CollectionGuarantee, error) { - blk, err := e.blocks.ByHeight(h) + block, err := e.blocks.ByHeight(h) if err != nil { - return nil, fmt.Errorf("failed to retreive block by height %d: %w", h, err) + return nil, fmt.Errorf("failed to retrieve block by height %d: %w", h, err) } var missingColls []*flow.CollectionGuarantee - for _, guarantee := range blk.Payload.Guarantees { - + for _, guarantee := range block.Payload.Guarantees { collID := guarantee.CollectionID - found, err := e.lookupCollection(collID) + found, err := e.haveCollection(collID) if err != nil { return nil, err } @@ -793,22 +832,21 @@ func (e *Engine) missingCollectionsAtHeight(h uint64) ([]*flow.CollectionGuarant return missingColls, nil } -// lookupCollection looks up the collection from the collection db with collID -func (e *Engine) lookupCollection(collId flow.Identifier) (bool, error) { - _, err := e.collections.LightByID(collId) +// haveCollection looks up the collection from the collection db with collID +func (e *Engine) haveCollection(collID flow.Identifier) (bool, error) { + _, err := e.collections.LightByID(collID) if err == nil { return true, nil } if errors.Is(err, storage.ErrNotFound) { return false, nil } - return false, fmt.Errorf("failed to retreive collection %s: %w", collId.String(), err) + return false, fmt.Errorf("failed to retrieve collection %s: %w", collID.String(), err) } // requestCollectionsInFinalizedBlock registers collection requests with the requester engine func (e *Engine) requestCollectionsInFinalizedBlock(missingColls []*flow.CollectionGuarantee) { for _, cg := range missingColls { - // TODO: move this query out of for loop? guarantors, err := protocol.FindGuarantors(e.state, cg) if err != nil { // failed to find guarantors for guarantees contained in a finalized block is fatal error diff --git a/engine/access/ingestion/engine_test.go b/engine/access/ingestion/engine_test.go index c4d0fb72141..b6753ecb144 100644 --- a/engine/access/ingestion/engine_test.go +++ b/engine/access/ingestion/engine_test.go @@ -64,73 +64,73 @@ func TestIngestEngine(t *testing.T) { suite.Run(t, new(Suite)) } -func (suite *Suite) TearDownTest() { - suite.cancel() +func (s *Suite) TearDownTest() { + s.cancel() } -func (suite *Suite) SetupTest() { +func (s *Suite) SetupTest() { log := zerolog.New(os.Stderr) + ctx, cancel := context.WithCancel(context.Background()) + s.cancel = cancel obsIdentity := unittest.IdentityFixture(unittest.WithRole(flow.RoleAccess)) // mock out protocol state - suite.proto.state = new(protocol.FollowerState) - suite.proto.snapshot = new(protocol.Snapshot) - suite.proto.params = new(protocol.Params) - suite.finalizedBlock = unittest.BlockHeaderFixture(unittest.WithHeaderHeight(0)) - suite.proto.state.On("Identity").Return(obsIdentity, nil) - suite.proto.state.On("Final").Return(suite.proto.snapshot, nil) - suite.proto.state.On("Params").Return(suite.proto.params) - suite.proto.snapshot.On("Head").Return( + s.proto.state = new(protocol.FollowerState) + s.proto.snapshot = new(protocol.Snapshot) + s.proto.params = new(protocol.Params) + s.finalizedBlock = unittest.BlockHeaderFixture(unittest.WithHeaderHeight(0)) + s.proto.state.On("Identity").Return(obsIdentity, nil) + s.proto.state.On("Final").Return(s.proto.snapshot, nil) + s.proto.state.On("Params").Return(s.proto.params) + s.proto.snapshot.On("Head").Return( func() *flow.Header { - return suite.finalizedBlock + return s.finalizedBlock }, nil, ).Maybe() - suite.me = new(module.Local) - suite.me.On("NodeID").Return(obsIdentity.NodeID) + s.me = new(module.Local) + s.me.On("NodeID").Return(obsIdentity.NodeID) net := new(mocknetwork.Network) conduit := new(mocknetwork.Conduit) net.On("Register", channels.ReceiveReceipts, mock.Anything). Return(conduit, nil). Once() - suite.request = new(module.Requester) - - suite.provider = new(mocknetwork.Engine) - suite.blocks = new(storage.Blocks) - suite.headers = new(storage.Headers) - suite.collections = new(storage.Collections) - suite.transactions = new(storage.Transactions) - suite.receipts = new(storage.ExecutionReceipts) - suite.results = new(storage.ExecutionResults) + s.request = new(module.Requester) + + s.provider = new(mocknetwork.Engine) + s.blocks = new(storage.Blocks) + s.headers = new(storage.Headers) + s.collections = new(storage.Collections) + s.transactions = new(storage.Transactions) + s.receipts = new(storage.ExecutionReceipts) + s.results = new(storage.ExecutionResults) collectionsToMarkFinalized, err := stdmap.NewTimes(100) - require.NoError(suite.T(), err) + require.NoError(s.T(), err) collectionsToMarkExecuted, err := stdmap.NewTimes(100) - require.NoError(suite.T(), err) + require.NoError(s.T(), err) blocksToMarkExecuted, err := stdmap.NewTimes(100) - require.NoError(suite.T(), err) + require.NoError(s.T(), err) - eng, err := New(log, net, suite.proto.state, suite.me, suite.request, suite.blocks, suite.headers, suite.collections, - suite.transactions, suite.results, suite.receipts, metrics.NewNoopCollector(), collectionsToMarkFinalized, collectionsToMarkExecuted, + eng, err := New(log, net, s.proto.state, s.me, s.request, s.blocks, s.headers, s.collections, + s.transactions, s.results, s.receipts, metrics.NewNoopCollector(), collectionsToMarkFinalized, collectionsToMarkExecuted, blocksToMarkExecuted) - require.NoError(suite.T(), err) + require.NoError(s.T(), err) - suite.blocks.On("GetLastFullBlockHeight").Once().Return(uint64(0), errors.New("do nothing")) + s.blocks.On("GetLastFullBlockHeight").Once().Return(uint64(0), errors.New("do nothing")) - ctx, cancel := context.WithCancel(context.Background()) irrecoverableCtx, _ := irrecoverable.WithSignaler(ctx) eng.ComponentManager.Start(irrecoverableCtx) <-eng.Ready() - suite.eng = eng - suite.cancel = cancel + s.eng = eng } // TestOnFinalizedBlock checks that when a block is received, a request for each individual collection is made -func (suite *Suite) TestOnFinalizedBlock() { - suite.blocks.On("GetLastFullBlockHeight").Return(uint64(0), nil).Once() +func (s *Suite) TestOnFinalizedBlock() { + s.blocks.On("GetLastFullBlockHeight").Return(uint64(0), nil).Once() block := unittest.BlockFixture() block.SetPayload(unittest.PayloadFixture( @@ -146,7 +146,7 @@ func (suite *Suite) TestOnFinalizedBlock() { // guarantee signers must be cluster committee members, so that access will fetch collection from // the signers that are specified by guarantee.SignerIndices indices, err := signature.EncodeSignersToIndices(clusterCommittee.NodeIDs(), clusterCommittee.NodeIDs()) - require.NoError(suite.T(), err) + require.NoError(s.T(), err) guarantee.SignerIndices = indices } @@ -155,15 +155,15 @@ func (suite *Suite) TestOnFinalizedBlock() { } // we should query the block once and index the guarantee payload once - suite.blocks.On("ByID", block.ID()).Return(&block, nil).Twice() + s.blocks.On("ByID", block.ID()).Return(&block, nil).Twice() for _, g := range block.Payload.Guarantees { collection := unittest.CollectionFixture(1) light := collection.Light() - suite.collections.On("LightByID", g.CollectionID).Return(&light, nil).Twice() + s.collections.On("LightByID", g.CollectionID).Return(&light, nil).Twice() } // expect that the block storage is indexed with each of the collection guarantee - suite.blocks.On("IndexBlockForCollections", block.ID(), []flow.Identifier(flow.GetIDs(block.Payload.Guarantees))).Return(nil).Once() + s.blocks.On("IndexBlockForCollections", block.ID(), []flow.Identifier(flow.GetIDs(block.Payload.Guarantees))).Return(nil).Once() cluster := new(protocol.Cluster) cluster.On("Members").Return(clusterCommittee, nil) @@ -173,8 +173,8 @@ func (suite *Suite) TestOnFinalizedBlock() { epochs.On("Current").Return(epoch) snap := new(protocol.Snapshot) snap.On("Epochs").Return(epochs) - suite.proto.state.On("AtBlockID", refBlockID).Return(snap) - suite.results.On("Index", mock.Anything, mock.Anything).Return(nil) + s.proto.state.On("AtBlockID", refBlockID).Return(snap) + s.results.On("Index", mock.Anything, mock.Anything).Return(nil) // for each of the guarantees, we should request the corresponding collection once needed := make(map[flow.Identifier]struct{}) @@ -185,140 +185,140 @@ func (suite *Suite) TestOnFinalizedBlock() { wg := sync.WaitGroup{} wg.Add(4) - suite.request.On("EntityByID", mock.Anything, mock.Anything).Run( + s.request.On("EntityByID", mock.Anything, mock.Anything).Run( func(args mock.Arguments) { collID := args.Get(0).(flow.Identifier) _, pending := needed[collID] - suite.Assert().True(pending, "collection should be pending (%x)", collID) + s.Assert().True(pending, "collection should be pending (%x)", collID) delete(needed, collID) wg.Done() }, ) // process the block through the finalized callback - suite.eng.OnFinalizedBlock(&hotstuffBlock) - suite.Assertions.Eventually(func() bool { + s.eng.OnFinalizedBlock(&hotstuffBlock) + s.Assertions.Eventually(func() bool { wg.Wait() return true }, time.Millisecond*20, time.Millisecond) // assert that the block was retrieved and all collections were requested - suite.headers.AssertExpectations(suite.T()) - suite.request.AssertNumberOfCalls(suite.T(), "EntityByID", len(block.Payload.Guarantees)) - suite.request.AssertNumberOfCalls(suite.T(), "Index", len(block.Payload.Seals)) + s.headers.AssertExpectations(s.T()) + s.request.AssertNumberOfCalls(s.T(), "EntityByID", len(block.Payload.Guarantees)) + s.request.AssertNumberOfCalls(s.T(), "Index", len(block.Payload.Seals)) } // TestOnCollection checks that when a Collection is received, it is persisted -func (suite *Suite) TestOnCollection() { +func (s *Suite) TestOnCollection() { originID := unittest.IdentifierFixture() collection := unittest.CollectionFixture(5) light := collection.Light() // we should store the light collection and index its transactions - suite.collections.On("StoreLightAndIndexByTransaction", &light).Return(nil).Once() + s.collections.On("StoreLightAndIndexByTransaction", &light).Return(nil).Once() // for each transaction in the collection, we should store it needed := make(map[flow.Identifier]struct{}) for _, txID := range light.Transactions { needed[txID] = struct{}{} } - suite.transactions.On("Store", mock.Anything).Return(nil).Run( + s.transactions.On("Store", mock.Anything).Return(nil).Run( func(args mock.Arguments) { tx := args.Get(0).(*flow.TransactionBody) _, pending := needed[tx.ID()] - suite.Assert().True(pending, "tx not pending (%x)", tx.ID()) + s.Assert().True(pending, "tx not pending (%x)", tx.ID()) }, ) // process the block through the collection callback - suite.eng.OnCollection(originID, &collection) + s.eng.OnCollection(originID, &collection) // check that the collection was stored and indexed, and we stored all transactions - suite.collections.AssertExpectations(suite.T()) - suite.transactions.AssertNumberOfCalls(suite.T(), "Store", len(collection.Transactions)) + s.collections.AssertExpectations(s.T()) + s.transactions.AssertNumberOfCalls(s.T(), "Store", len(collection.Transactions)) } // TestExecutionReceiptsAreIndexed checks that execution receipts are properly indexed -func (suite *Suite) TestExecutionReceiptsAreIndexed() { +func (s *Suite) TestExecutionReceiptsAreIndexed() { originID := unittest.IdentifierFixture() collection := unittest.CollectionFixture(5) light := collection.Light() // we should store the light collection and index its transactions - suite.collections.On("StoreLightAndIndexByTransaction", &light).Return(nil).Once() + s.collections.On("StoreLightAndIndexByTransaction", &light).Return(nil).Once() block := &flow.Block{ Header: &flow.Header{Height: 0}, Payload: &flow.Payload{Guarantees: []*flow.CollectionGuarantee{}}, } - suite.blocks.On("ByID", mock.Anything).Return(block, nil) + s.blocks.On("ByID", mock.Anything).Return(block, nil) // for each transaction in the collection, we should store it needed := make(map[flow.Identifier]struct{}) for _, txID := range light.Transactions { needed[txID] = struct{}{} } - suite.transactions.On("Store", mock.Anything).Return(nil).Run( + s.transactions.On("Store", mock.Anything).Return(nil).Run( func(args mock.Arguments) { tx := args.Get(0).(*flow.TransactionBody) _, pending := needed[tx.ID()] - suite.Assert().True(pending, "tx not pending (%x)", tx.ID()) + s.Assert().True(pending, "tx not pending (%x)", tx.ID()) }, ) er1 := unittest.ExecutionReceiptFixture() er2 := unittest.ExecutionReceiptFixture() - suite.receipts.On("Store", mock.Anything).Return(nil) - suite.blocks.On("ByID", er1.ExecutionResult.BlockID).Return(nil, storerr.ErrNotFound) + s.receipts.On("Store", mock.Anything).Return(nil) + s.blocks.On("ByID", er1.ExecutionResult.BlockID).Return(nil, storerr.ErrNotFound) - suite.receipts.On("Store", mock.Anything).Return(nil) - suite.blocks.On("ByID", er2.ExecutionResult.BlockID).Return(nil, storerr.ErrNotFound) + s.receipts.On("Store", mock.Anything).Return(nil) + s.blocks.On("ByID", er2.ExecutionResult.BlockID).Return(nil, storerr.ErrNotFound) - err := suite.eng.handleExecutionReceipt(originID, er1) - require.NoError(suite.T(), err) + err := s.eng.handleExecutionReceipt(originID, er1) + require.NoError(s.T(), err) - err = suite.eng.handleExecutionReceipt(originID, er2) - require.NoError(suite.T(), err) + err = s.eng.handleExecutionReceipt(originID, er2) + require.NoError(s.T(), err) - suite.receipts.AssertExpectations(suite.T()) - suite.results.AssertExpectations(suite.T()) - suite.receipts.AssertExpectations(suite.T()) + s.receipts.AssertExpectations(s.T()) + s.results.AssertExpectations(s.T()) + s.receipts.AssertExpectations(s.T()) } // TestOnCollection checks that when a duplicate collection is received, the node doesn't // crash but just ignores its transactions. -func (suite *Suite) TestOnCollectionDuplicate() { +func (s *Suite) TestOnCollectionDuplicate() { originID := unittest.IdentifierFixture() collection := unittest.CollectionFixture(5) light := collection.Light() // we should store the light collection and index its transactions - suite.collections.On("StoreLightAndIndexByTransaction", &light).Return(storerr.ErrAlreadyExists).Once() + s.collections.On("StoreLightAndIndexByTransaction", &light).Return(storerr.ErrAlreadyExists).Once() // for each transaction in the collection, we should store it needed := make(map[flow.Identifier]struct{}) for _, txID := range light.Transactions { needed[txID] = struct{}{} } - suite.transactions.On("Store", mock.Anything).Return(nil).Run( + s.transactions.On("Store", mock.Anything).Return(nil).Run( func(args mock.Arguments) { tx := args.Get(0).(*flow.TransactionBody) _, pending := needed[tx.ID()] - suite.Assert().True(pending, "tx not pending (%x)", tx.ID()) + s.Assert().True(pending, "tx not pending (%x)", tx.ID()) }, ) // process the block through the collection callback - suite.eng.OnCollection(originID, &collection) + s.eng.OnCollection(originID, &collection) // check that the collection was stored and indexed, and we stored all transactions - suite.collections.AssertExpectations(suite.T()) - suite.transactions.AssertNotCalled(suite.T(), "Store", "should not store any transactions") + s.collections.AssertExpectations(s.T()) + s.transactions.AssertNotCalled(s.T(), "Store", "should not store any transactions") } // TestRequestMissingCollections tests that the all missing collections are requested on the call to requestMissingCollections -func (suite *Suite) TestRequestMissingCollections() { +func (s *Suite) TestRequestMissingCollections() { blkCnt := 3 startHeight := uint64(1000) @@ -349,14 +349,14 @@ func (suite *Suite) TestRequestMissingCollections() { // guarantee signers must be cluster committee members, so that access will fetch collection from // the signers that are specified by guarantee.SignerIndices indices, err := signature.EncodeSignersToIndices(clusterCommittee.NodeIDs(), clusterCommittee.NodeIDs()) - require.NoError(suite.T(), err) + require.NoError(s.T(), err) c.SignerIndices = indices } } // setup the block storage mock // each block should be queried by height - suite.blocks.On("ByHeight", mock.IsType(uint64(0))).Return( + s.blocks.On("ByHeight", mock.IsType(uint64(0))).Return( func(h uint64) *flow.Block { // simulate a db lookup return heightMap[h] @@ -368,9 +368,9 @@ func (suite *Suite) TestRequestMissingCollections() { return storerr.ErrNotFound }) // consider collections are missing for all blocks - suite.blocks.On("GetLastFullBlockHeight").Return(startHeight-1, nil) + s.blocks.On("GetLastFullBlockHeight").Return(startHeight-1, nil) // consider the last test block as the head - suite.finalizedBlock = blocks[blkCnt-1].Header + s.finalizedBlock = blocks[blkCnt-1].Header // p is the probability of not receiving the collection before the next poll and it // helps simulate the slow trickle of the requested collections being received @@ -381,7 +381,7 @@ func (suite *Suite) TestRequestMissingCollections() { // for the first lookup call for each collection, it will be reported as missing from db // for the subsequent calls, it will be reported as present with the probability p - suite.collections.On("LightByID", mock.Anything).Return( + s.collections.On("LightByID", mock.Anything).Return( func(cID flow.Identifier) *flow.LightCollection { return nil // the actual collection object return is never really read }, @@ -400,10 +400,10 @@ func (suite *Suite) TestRequestMissingCollections() { // setup the requester engine mock // entityByID should be called once per collection for _, c := range collIDs { - suite.request.On("EntityByID", c, mock.Anything).Return() + s.request.On("EntityByID", c, mock.Anything).Return() } // force should be called once - suite.request.On("Force").Return() + s.request.On("Force").Return() cluster := new(protocol.Cluster) cluster.On("Members").Return(clusterCommittee, nil) @@ -413,17 +413,17 @@ func (suite *Suite) TestRequestMissingCollections() { epochs.On("Current").Return(epoch) snap := new(protocol.Snapshot) snap.On("Epochs").Return(epochs) - suite.proto.state.On("AtBlockID", refBlockID).Return(snap) + s.proto.state.On("AtBlockID", refBlockID).Return(snap) assertExpectations := func() { - suite.request.AssertExpectations(suite.T()) - suite.collections.AssertExpectations(suite.T()) - suite.proto.snapshot.AssertExpectations(suite.T()) - suite.blocks.AssertExpectations(suite.T()) + s.request.AssertExpectations(s.T()) + s.collections.AssertExpectations(s.T()) + s.proto.snapshot.AssertExpectations(s.T()) + s.blocks.AssertExpectations(s.T()) } // test 1 - collections are not received before timeout - suite.Run("timeout before all missing collections are received", func() { + s.Run("timeout before all missing collections are received", func() { // simulate that collection are never received p = 1 @@ -432,15 +432,15 @@ func (suite *Suite) TestRequestMissingCollections() { ctx, cancel := context.WithTimeout(context.Background(), 100*defaultCollectionCatchupDBPollInterval) defer cancel() - err := suite.eng.requestMissingCollections(ctx) + err := s.eng.requestMissingCollections(ctx) - require.Error(suite.T(), err) - require.Contains(suite.T(), err.Error(), "context deadline exceeded") + require.Error(s.T(), err) + require.Contains(s.T(), err.Error(), "context deadline exceeded") assertExpectations() }) // test 2 - all collections are eventually received before the deadline - suite.Run("all missing collections are received", func() { + s.Run("all missing collections are received", func() { // 90% of the time, collections are reported as not received when the collection storage is queried p = 0.9 @@ -448,18 +448,19 @@ func (suite *Suite) TestRequestMissingCollections() { ctx, cancel := context.WithTimeout(context.Background(), defaultCollectionCatchupTimeout) defer cancel() - err := suite.eng.requestMissingCollections(ctx) + err := s.eng.requestMissingCollections(ctx) - require.NoError(suite.T(), err) - require.Len(suite.T(), rcvdColl, len(collIDs)) + require.NoError(s.T(), err) + require.Len(s.T(), rcvdColl, len(collIDs)) assertExpectations() }) } -// TestUpdateLastFullBlockReceivedIndex tests that UpdateLastFullBlockReceivedIndex function keeps the FullBlockIndex -// upto date and request collections if blocks with missing collections exceed the threshold. -func (suite *Suite) TestUpdateLastFullBlockReceivedIndex() { +// TestProcessBackgroundCalls tests that updateLastFullBlockReceivedIndex and checkMissingCollections +// function calls keep the FullBlockIndex up-to-date and request collections if blocks with missing +// collections exceed the threshold. +func (s *Suite) TestProcessBackgroundCalls() { blkCnt := 3 collPerBlk := 10 startHeight := uint64(1000) @@ -485,7 +486,7 @@ func (suite *Suite) TestUpdateLastFullBlockReceivedIndex() { // guarantee signers must be cluster committee members, so that access will fetch collection from // the signers that are specified by guarantee.SignerIndices indices, err := signature.EncodeSignersToIndices(clusterCommittee.NodeIDs(), clusterCommittee.NodeIDs()) - require.NoError(suite.T(), err) + require.NoError(s.T(), err) cg.SignerIndices = indices guarantees[j] = cg } @@ -505,7 +506,7 @@ func (suite *Suite) TestUpdateLastFullBlockReceivedIndex() { // setup the block storage mock // each block should be queried by height - suite.blocks.On("ByHeight", mock.IsType(uint64(0))).Return( + s.blocks.On("ByHeight", mock.IsType(uint64(0))).Return( func(h uint64) *flow.Block { // simulate a db lookup return heightMap[h] @@ -525,7 +526,7 @@ func (suite *Suite) TestUpdateLastFullBlockReceivedIndex() { epochs.On("Current").Return(epoch) snap := new(protocol.Snapshot) snap.On("Epochs").Return(epochs) - suite.proto.state.On("AtBlockID", refBlockID).Return(snap) + s.proto.state.On("AtBlockID", refBlockID).Return(snap) // blkMissingColl controls which collections are reported as missing by the collections storage mock blkMissingColl := make([]bool, blkCnt) @@ -533,7 +534,7 @@ func (suite *Suite) TestUpdateLastFullBlockReceivedIndex() { blkMissingColl[i] = false for _, cg := range blocks[i].Payload.Guarantees { j := i - suite.collections.On("LightByID", cg.CollectionID).Return( + s.collections.On("LightByID", cg.CollectionID).Return( func(cID flow.Identifier) *flow.LightCollection { return collMap[cID] }, @@ -546,54 +547,38 @@ func (suite *Suite) TestUpdateLastFullBlockReceivedIndex() { } } - var lastFullBlockHeight uint64 - var rtnErr error - suite.blocks.On("GetLastFullBlockHeight").Return( - func() uint64 { - return lastFullBlockHeight - }, - func() error { - return rtnErr - }) - // consider the last test block as the head - suite.finalizedBlock = finalizedBlk.Header + s.finalizedBlock = finalizedBlk.Header - suite.Run("full block height index is created and advanced if not present", func() { - // simulate the absence of the full block height index - lastFullBlockHeight = 0 - rtnErr = storerr.ErrNotFound - suite.proto.params.On("FinalizedRoot").Return(rootBlk.Header, nil) - suite.blocks.On("UpdateLastFullBlockHeight", finalizedHeight).Return(nil).Once() - - suite.eng.updateLastFullBlockReceivedIndex() - - suite.blocks.AssertExpectations(suite.T()) - }) - - suite.Run("full block height index is advanced if newer full blocks are discovered", func() { - rtnErr = nil + s.Run("full block height index is advanced if newer full blocks are discovered", func() { block := blocks[1] - lastFullBlockHeight = block.Header.Height - suite.blocks.On("UpdateLastFullBlockHeight", finalizedHeight).Return(nil).Once() + s.blocks.On("UpdateLastFullBlockHeight", finalizedHeight).Return(nil).Once() + s.blocks.On("GetLastFullBlockHeight").Return(func() (uint64, error) { + return block.Header.Height, nil + }).Once() - suite.eng.updateLastFullBlockReceivedIndex() + err := s.eng.updateLastFullBlockReceivedIndex() + s.Require().NoError(err) - suite.blocks.AssertExpectations(suite.T()) + s.blocks.AssertExpectations(s.T()) }) - suite.Run("full block height index is not advanced beyond finalized blocks", func() { - rtnErr = nil - lastFullBlockHeight = finalizedHeight + s.Run("full block height index is not advanced beyond finalized blocks", func() { + s.blocks.On("GetLastFullBlockHeight").Return(func() (uint64, error) { + return finalizedHeight, nil + }).Once() + + err := s.eng.updateLastFullBlockReceivedIndex() + s.Require().NoError(err) - suite.eng.updateLastFullBlockReceivedIndex() - suite.blocks.AssertExpectations(suite.T()) // not new call to UpdateLastFullBlockHeight should be made + s.blocks.AssertExpectations(s.T()) // not new call to UpdateLastFullBlockHeight should be made }) - suite.Run("missing collections are requested when count exceeds defaultMissingCollsForBlkThreshold", func() { + s.Run("missing collections are requested when count exceeds defaultMissingCollsForBlkThreshold", func() { // root block is the last complete block - rtnErr = nil - lastFullBlockHeight = rootBlkHeight + s.blocks.On("GetLastFullBlockHeight").Return(func() (uint64, error) { + return rootBlkHeight, nil + }).Once() // lower the block threshold to request missing collections defaultMissingCollsForBlkThreshold = 2 @@ -603,23 +588,25 @@ func (suite *Suite) TestUpdateLastFullBlockReceivedIndex() { blkMissingColl[i] = true // setup receive engine expectations for _, cg := range blocks[i].Payload.Guarantees { - suite.request.On("EntityByID", cg.CollectionID, mock.Anything).Return().Once() + s.request.On("EntityByID", cg.CollectionID, mock.Anything).Return().Once() } } - suite.eng.updateLastFullBlockReceivedIndex() + err := s.eng.checkMissingCollections() + s.Require().NoError(err) // assert that missing collections are requested - suite.request.AssertExpectations(suite.T()) + s.request.AssertExpectations(s.T()) // last full blk index is not advanced - suite.blocks.AssertExpectations(suite.T()) // no new call to UpdateLastFullBlockHeight should be made + s.blocks.AssertExpectations(s.T()) // no new call to UpdateLastFullBlockHeight should be made }) - suite.Run("missing collections are requested when count exceeds defaultMissingCollsForAgeThreshold", func() { + s.Run("missing collections are requested when count exceeds defaultMissingCollsForAgeThreshold", func() { // root block is the last complete block - rtnErr = nil - lastFullBlockHeight = rootBlkHeight + s.blocks.On("GetLastFullBlockHeight").Return(func() (uint64, error) { + return rootBlkHeight, nil + }).Once() // lower the height threshold to request missing collections defaultMissingCollsForAgeThreshold = 1 @@ -632,23 +619,25 @@ func (suite *Suite) TestUpdateLastFullBlockReceivedIndex() { blkMissingColl[i] = true // setup receive engine expectations for _, cg := range blocks[i].Payload.Guarantees { - suite.request.On("EntityByID", cg.CollectionID, mock.Anything).Return().Once() + s.request.On("EntityByID", cg.CollectionID, mock.Anything).Return().Once() } } - suite.eng.updateLastFullBlockReceivedIndex() + err := s.eng.checkMissingCollections() + s.Require().NoError(err) // assert that missing collections are requested - suite.request.AssertExpectations(suite.T()) + s.request.AssertExpectations(s.T()) // last full blk index is not advanced - suite.blocks.AssertExpectations(suite.T()) // not new call to UpdateLastFullBlockHeight should be made + s.blocks.AssertExpectations(s.T()) // not new call to UpdateLastFullBlockHeight should be made }) - suite.Run("missing collections are not requested if defaultMissingCollsForBlkThreshold not reached", func() { + s.Run("missing collections are not requested if defaultMissingCollsForBlkThreshold not reached", func() { // root block is the last complete block - rtnErr = nil - lastFullBlockHeight = rootBlkHeight + s.blocks.On("GetLastFullBlockHeight").Return(func() (uint64, error) { + return rootBlkHeight, nil + }).Once() // raise the thresholds to avoid requesting missing collections defaultMissingCollsForAgeThreshold = 3 @@ -659,22 +648,23 @@ func (suite *Suite) TestUpdateLastFullBlockReceivedIndex() { blkMissingColl[i] = true } - suite.eng.updateLastFullBlockReceivedIndex() + err := s.eng.checkMissingCollections() + s.Require().NoError(err) // assert that missing collections are not requested even though there are collections missing - suite.request.AssertExpectations(suite.T()) + s.request.AssertExpectations(s.T()) // last full blk index is not advanced - suite.blocks.AssertExpectations(suite.T()) // not new call to UpdateLastFullBlockHeight should be made + s.blocks.AssertExpectations(s.T()) // not new call to UpdateLastFullBlockHeight should be made }) } -func (suite *Suite) TestComponentShutdown() { +func (s *Suite) TestComponentShutdown() { // start then shut down the engine - unittest.AssertClosesBefore(suite.T(), suite.eng.Ready(), 10*time.Millisecond) - suite.cancel() - unittest.AssertClosesBefore(suite.T(), suite.eng.Done(), 10*time.Millisecond) + unittest.AssertClosesBefore(s.T(), s.eng.Ready(), 10*time.Millisecond) + s.cancel() + unittest.AssertClosesBefore(s.T(), s.eng.Done(), 10*time.Millisecond) - err := suite.eng.ProcessLocal(&flow.ExecutionReceipt{}) - suite.Assert().ErrorIs(err, component.ErrComponentShutdown) + err := s.eng.ProcessLocal(&flow.ExecutionReceipt{}) + s.Assert().ErrorIs(err, component.ErrComponentShutdown) } diff --git a/engine/access/integration_unsecure_grpc_server_test.go b/engine/access/integration_unsecure_grpc_server_test.go index 810868e36fe..674a9ad4d1e 100644 --- a/engine/access/integration_unsecure_grpc_server_test.go +++ b/engine/access/integration_unsecure_grpc_server_test.go @@ -63,6 +63,7 @@ type SameGRPCPortTestSuite struct { // storage blocks *storagemock.Blocks headers *storagemock.Headers + events *storagemock.Events collections *storagemock.Collections transactions *storagemock.Transactions receipts *storagemock.ExecutionReceipts @@ -101,6 +102,7 @@ func (suite *SameGRPCPortTestSuite) SetupTest() { suite.snapshot.On("Epochs").Return(suite.epochQuery).Maybe() suite.blocks = new(storagemock.Blocks) suite.headers = new(storagemock.Headers) + suite.events = new(storagemock.Events) suite.transactions = new(storagemock.Transactions) suite.collections = new(storagemock.Collections) suite.receipts = new(storagemock.ExecutionReceipts) @@ -241,6 +243,7 @@ func (suite *SameGRPCPortTestSuite) SetupTest() { conf, suite.state, suite.headers, + suite.events, suite.seals, suite.results, nil, @@ -249,6 +252,7 @@ func (suite *SameGRPCPortTestSuite) SetupTest() { rootBlock.Header.Height, rootBlock.Header.Height, suite.registers, + false, ) assert.NoError(suite.T(), err) diff --git a/engine/access/rest/routes/subscribe_events_test.go b/engine/access/rest/routes/subscribe_events_test.go index 0b5626c64b2..6d5b731d6b7 100644 --- a/engine/access/rest/routes/subscribe_events_test.go +++ b/engine/access/rest/routes/subscribe_events_test.go @@ -43,10 +43,11 @@ type testType struct { headers http.Header } +var chainID = flow.Testnet var testEventTypes = []flow.EventType{ - "A.0123456789abcdef.flow.event", - "B.0123456789abcdef.flow.event", - "C.0123456789abcdef.flow.event", + unittest.EventTypeFixture(chainID), + unittest.EventTypeFixture(chainID), + unittest.EventTypeFixture(chainID), } type SubscribeEventsSuite struct { @@ -83,6 +84,8 @@ func (s *SubscribeEventsSuite) SetupTest() { // update payloads with valid CCF encoded data for i := range blockEvents.Events { blockEvents.Events[i].Payload = eventsGenerator.New().Payload + + s.T().Logf("block events %d %v => %v", block.Header.Height, block.ID(), blockEvents.Events[i].Type) } s.blocks = append(s.blocks, block) @@ -143,7 +146,6 @@ func (s *SubscribeEventsSuite) TestSubscribeEvents() { }, }, } - chain := flow.MonotonicEmulator.Chain() // create variations for each of the base test tests := make([]testType, 0, len(testVectors)*2) @@ -159,7 +161,7 @@ func (s *SubscribeEventsSuite) TestSubscribeEvents() { t3 := test t3.name = fmt.Sprintf("%s - non existing events", test.name) - t3.eventTypes = []string{"A.0123456789abcdff.flow.event"} + t3.eventTypes = []string{fmt.Sprintf("%s_new", testEventTypes[0])} tests = append(tests, t3) } @@ -170,7 +172,7 @@ func (s *SubscribeEventsSuite) TestSubscribeEvents() { filter, err := state_stream.NewEventFilter( state_stream.DefaultEventFilterConfig, - chain, + chainID.Chain(), test.eventTypes, test.addresses, test.contracts) @@ -245,9 +247,9 @@ func (s *SubscribeEventsSuite) TestSubscribeEvents() { // closing the connection after 1 second go func() { time.Sleep(1 * time.Second) - close(respRecorder.closed) + respRecorder.Close() }() - executeWsRequest(req, stateStreamBackend, respRecorder) + executeWsRequest(req, stateStreamBackend, respRecorder, chainID.Chain()) requireResponse(s.T(), respRecorder, expectedEventsResponses) }) } @@ -259,7 +261,7 @@ func (s *SubscribeEventsSuite) TestSubscribeEventsHandlesErrors() { req, err := getSubscribeEventsRequest(s.T(), s.blocks[0].ID(), s.blocks[0].Header.Height, nil, nil, nil, 1, nil) require.NoError(s.T(), err) respRecorder := newTestHijackResponseRecorder() - executeWsRequest(req, stateStreamBackend, respRecorder) + executeWsRequest(req, stateStreamBackend, respRecorder, chainID.Chain()) requireError(s.T(), respRecorder, "can only provide either block ID or start height") }) @@ -284,7 +286,7 @@ func (s *SubscribeEventsSuite) TestSubscribeEventsHandlesErrors() { req, err := getSubscribeEventsRequest(s.T(), invalidBlock.ID(), request.EmptyHeight, nil, nil, nil, 1, nil) require.NoError(s.T(), err) respRecorder := newTestHijackResponseRecorder() - executeWsRequest(req, stateStreamBackend, respRecorder) + executeWsRequest(req, stateStreamBackend, respRecorder, chainID.Chain()) requireError(s.T(), respRecorder, "stream encountered an error: subscription error") }) @@ -293,7 +295,7 @@ func (s *SubscribeEventsSuite) TestSubscribeEventsHandlesErrors() { req, err := getSubscribeEventsRequest(s.T(), s.blocks[0].ID(), request.EmptyHeight, []string{"foo"}, nil, nil, 1, nil) require.NoError(s.T(), err) respRecorder := newTestHijackResponseRecorder() - executeWsRequest(req, stateStreamBackend, respRecorder) + executeWsRequest(req, stateStreamBackend, respRecorder, chainID.Chain()) requireError(s.T(), respRecorder, "invalid event type format") }) @@ -318,7 +320,7 @@ func (s *SubscribeEventsSuite) TestSubscribeEventsHandlesErrors() { req, err := getSubscribeEventsRequest(s.T(), s.blocks[0].ID(), request.EmptyHeight, nil, nil, nil, 1, nil) require.NoError(s.T(), err) respRecorder := newTestHijackResponseRecorder() - executeWsRequest(req, stateStreamBackend, respRecorder) + executeWsRequest(req, stateStreamBackend, respRecorder, chainID.Chain()) requireError(s.T(), respRecorder, "subscription channel closed") }) } diff --git a/engine/access/rest/routes/test_helpers.go b/engine/access/rest/routes/test_helpers.go index 1bedb07f383..ebe40fa48df 100644 --- a/engine/access/rest/routes/test_helpers.go +++ b/engine/access/rest/routes/test_helpers.go @@ -55,6 +55,7 @@ func (c fakeNetConn) Close() error { } return nil } + func (c fakeNetConn) LocalAddr() net.Addr { return localAddr } func (c fakeNetConn) RemoteAddr() net.Addr { return remoteAddr } func (c fakeNetConn) SetDeadline(t time.Time) error { return nil } @@ -101,6 +102,15 @@ func (w *testHijackResponseRecorder) Hijack() (net.Conn, *bufio.ReadWriter, erro return fakeNetConn{w.responseBuff, w.closed}, bufio.NewReadWriter(br, bw), nil } +func (w *testHijackResponseRecorder) Close() error { + select { + case <-w.closed: + default: + close(w.closed) + } + return nil +} + // newTestHijackResponseRecorder creates a new instance of testHijackResponseRecorder. func newTestHijackResponseRecorder() *testHijackResponseRecorder { return &testHijackResponseRecorder{ @@ -122,7 +132,7 @@ func executeRequest(req *http.Request, backend access.API) *httptest.ResponseRec return rr } -func executeWsRequest(req *http.Request, stateStreamApi state_stream.API, responseRecorder *testHijackResponseRecorder) { +func executeWsRequest(req *http.Request, stateStreamApi state_stream.API, responseRecorder *testHijackResponseRecorder, chain flow.Chain) { restCollector := metrics.NewNoopCollector() config := backend.Config{ @@ -133,7 +143,7 @@ func executeWsRequest(req *http.Request, stateStreamApi state_stream.API, respon router := NewRouterBuilder(unittest.Logger(), restCollector).AddWsRoutes( stateStreamApi, - flow.Testnet.Chain(), config).Build() + chain, config).Build() router.ServeHTTP(responseRecorder, req) } diff --git a/engine/access/rpc/backend/backend.go b/engine/access/rpc/backend/backend.go index 4e5838d234d..d082277cd8d 100644 --- a/engine/access/rpc/backend/backend.go +++ b/engine/access/rpc/backend/backend.go @@ -83,6 +83,7 @@ type Params struct { HistoricalAccessNodes []accessproto.AccessAPIClient Blocks storage.Blocks Headers storage.Headers + Events storage.Events Collections storage.Collections Transactions storage.Transactions ExecutionReceipts storage.ExecutionReceipts @@ -101,7 +102,8 @@ type Params struct { TxResultCacheSize uint TxErrorMessagesCacheSize uint ScriptExecutor execution.ScriptExecutor - ScriptExecutionMode ScriptExecutionMode + ScriptExecutionMode IndexQueryMode + EventQueryMode IndexQueryMode } // New creates backend instance @@ -146,11 +148,11 @@ func New(params Params) (*Backend, error) { state: params.State, // create the sub-backends backendScripts: backendScripts{ + log: params.Log, headers: params.Headers, executionReceipts: params.ExecutionReceipts, connFactory: params.ConnFactory, state: params.State, - log: params.Log, metrics: params.AccessMetrics, loggedScripts: loggedScripts, nodeCommunicator: params.Communicator, @@ -158,6 +160,7 @@ func New(params Params) (*Backend, error) { scriptExecMode: params.ScriptExecutionMode, }, backendTransactions: backendTransactions{ + log: params.Log, staticCollectionRPC: params.CollectionRPC, state: params.State, chainID: params.ChainID, @@ -171,19 +174,21 @@ func New(params Params) (*Backend, error) { retry: retry, connFactory: params.ConnFactory, previousAccessNodes: params.HistoricalAccessNodes, - log: params.Log, nodeCommunicator: params.Communicator, txResultCache: txResCache, txErrorMessagesCache: txErrorMessagesCache, }, backendEvents: backendEvents{ + log: params.Log, + chain: params.ChainID.Chain(), state: params.State, headers: params.Headers, + events: params.Events, executionReceipts: params.ExecutionReceipts, connFactory: params.ConnFactory, - log: params.Log, maxHeightRange: params.MaxHeightRange, nodeCommunicator: params.Communicator, + queryMode: params.EventQueryMode, }, backendBlockHeaders: backendBlockHeaders{ headers: params.Headers, @@ -194,11 +199,11 @@ func New(params Params) (*Backend, error) { state: params.State, }, backendAccounts: backendAccounts{ + log: params.Log, state: params.State, headers: params.Headers, executionReceipts: params.ExecutionReceipts, connFactory: params.ConnFactory, - log: params.Log, nodeCommunicator: params.Communicator, scriptExecutor: params.ScriptExecutor, scriptExecMode: params.ScriptExecutionMode, diff --git a/engine/access/rpc/backend/backend_accounts.go b/engine/access/rpc/backend/backend_accounts.go index dbb2c03ab64..2e58b904a48 100644 --- a/engine/access/rpc/backend/backend_accounts.go +++ b/engine/access/rpc/backend/backend_accounts.go @@ -31,7 +31,7 @@ type backendAccounts struct { connFactory connection.ConnectionFactory nodeCommunicator Communicator scriptExecutor execution.ScriptExecutor - scriptExecMode ScriptExecutionMode + scriptExecMode IndexQueryMode } // GetAccount returns the account details at the latest sealed block. @@ -66,14 +66,12 @@ func (b *backendAccounts) GetAccountAtBlockHeight( address flow.Address, height uint64, ) (*flow.Account, error) { - header, err := b.headers.ByHeight(height) + blockID, err := b.headers.BlockIDByHeight(height) if err != nil { return nil, rpc.ConvertStorageError(err) } - blockID := header.ID() - - account, err := b.getAccountAtBlock(ctx, address, blockID, header.Height) + account, err := b.getAccountAtBlock(ctx, address, blockID, height) if err != nil { b.log.Debug().Err(err).Msgf("failed to get account at height: %d", height) return nil, err @@ -93,13 +91,13 @@ func (b *backendAccounts) getAccountAtBlock( height uint64, ) (*flow.Account, error) { switch b.scriptExecMode { - case ScriptExecutionModeExecutionNodesOnly: + case IndexQueryModeExecutionNodesOnly: return b.getAccountFromAnyExeNode(ctx, address, blockID) - case ScriptExecutionModeLocalOnly: + case IndexQueryModeLocalOnly: return b.getAccountFromLocalStorage(ctx, address, height) - case ScriptExecutionModeFailover: + case IndexQueryModeFailover: localResult, localErr := b.getAccountFromLocalStorage(ctx, address, height) if localErr == nil { return localResult, nil @@ -110,7 +108,7 @@ func (b *backendAccounts) getAccountAtBlock( return execResult, execErr - case ScriptExecutionModeCompare: + case IndexQueryModeCompare: execResult, execErr := b.getAccountFromAnyExeNode(ctx, address, blockID) // Only compare actual get account errors from the EN, not system errors if execErr != nil && !isInvalidArgumentError(execErr) { diff --git a/engine/access/rpc/backend/backend_accounts_test.go b/engine/access/rpc/backend/backend_accounts_test.go index 614f91bfb49..d8428022228 100644 --- a/engine/access/rpc/backend/backend_accounts_test.go +++ b/engine/access/rpc/backend/backend_accounts_test.go @@ -139,7 +139,7 @@ func (s *BackendAccountsSuite) TestGetAccountFromExecutionNode_HappyPath() { s.setupENSuccessResponse(s.block.ID()) backend := s.defaultBackend() - backend.scriptExecMode = ScriptExecutionModeExecutionNodesOnly + backend.scriptExecMode = IndexQueryModeExecutionNodesOnly s.Run("GetAccount - happy path", func() { s.testGetAccount(ctx, backend, codes.OK) @@ -166,7 +166,7 @@ func (s *BackendAccountsSuite) TestGetAccountFromExecutionNode_Fails() { s.setupENFailingResponse(s.block.ID(), errToReturn) backend := s.defaultBackend() - backend.scriptExecMode = ScriptExecutionModeExecutionNodesOnly + backend.scriptExecMode = IndexQueryModeExecutionNodesOnly s.Run("GetAccount - fails with backend err", func() { s.testGetAccount(ctx, backend, statusCode) @@ -190,7 +190,7 @@ func (s *BackendAccountsSuite) TestGetAccountFromStorage_HappyPath() { Return(s.account, nil) backend := s.defaultBackend() - backend.scriptExecMode = ScriptExecutionModeLocalOnly + backend.scriptExecMode = IndexQueryModeLocalOnly backend.scriptExecutor = scriptExecutor s.Run("GetAccount - happy path", func() { @@ -214,7 +214,7 @@ func (s *BackendAccountsSuite) TestGetAccountFromStorage_Fails() { scriptExecutor := execmock.NewScriptExecutor(s.T()) backend := s.defaultBackend() - backend.scriptExecMode = ScriptExecutionModeLocalOnly + backend.scriptExecMode = IndexQueryModeLocalOnly backend.scriptExecutor = scriptExecutor testCases := []struct { @@ -264,7 +264,7 @@ func (s *BackendAccountsSuite) TestGetAccountFromFailover_HappyPath() { scriptExecutor := execmock.NewScriptExecutor(s.T()) backend := s.defaultBackend() - backend.scriptExecMode = ScriptExecutionModeFailover + backend.scriptExecMode = IndexQueryModeFailover backend.scriptExecutor = scriptExecutor for _, errToReturn := range []error{execution.ErrDataNotAvailable, storage.ErrNotFound} { @@ -302,7 +302,7 @@ func (s *BackendAccountsSuite) TestGetAccountFromFailover_ReturnsENErrors() { Return(nil, execution.ErrDataNotAvailable) backend := s.defaultBackend() - backend.scriptExecMode = ScriptExecutionModeFailover + backend.scriptExecMode = IndexQueryModeFailover backend.scriptExecutor = scriptExecutor s.Run("GetAccount - fails with backend err", func() { @@ -324,7 +324,7 @@ func (s *BackendAccountsSuite) TestGetAccountAtLatestBlockFromStorage_Inconsiste scriptExecutor := execmock.NewScriptExecutor(s.T()) backend := s.defaultBackend() - backend.scriptExecMode = ScriptExecutionModeLocalOnly + backend.scriptExecMode = IndexQueryModeLocalOnly backend.scriptExecutor = scriptExecutor s.Run(fmt.Sprintf("GetAccountAtLatestBlock - fails with %v", "inconsistent node's state"), func() { @@ -376,7 +376,7 @@ func (s *BackendAccountsSuite) testGetAccountAtLatestBlock(ctx context.Context, func (s *BackendAccountsSuite) testGetAccountAtBlockHeight(ctx context.Context, backend *backendAccounts, statusCode codes.Code) { height := s.block.Header.Height - s.headers.On("ByHeight", height).Return(s.block.Header, nil).Once() + s.headers.On("BlockIDByHeight", height).Return(s.block.Header.ID(), nil).Once() if statusCode == codes.OK { actual, err := backend.GetAccountAtBlockHeight(ctx, s.account.Address, height) diff --git a/engine/access/rpc/backend/backend_events.go b/engine/access/rpc/backend/backend_events.go index a5bebada2e7..e35442ca966 100644 --- a/engine/access/rpc/backend/backend_events.go +++ b/engine/access/rpc/backend/backend_events.go @@ -5,6 +5,7 @@ import ( "encoding/hex" "errors" "fmt" + "sort" "time" "github.com/onflow/flow/protobuf/go/flow/entities" @@ -17,6 +18,7 @@ import ( "github.com/onflow/flow-go/engine/access/rpc/connection" "github.com/onflow/flow-go/engine/common/rpc" "github.com/onflow/flow-go/engine/common/rpc/convert" + "github.com/onflow/flow-go/model/events" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/module/irrecoverable" "github.com/onflow/flow-go/state/protocol" @@ -25,12 +27,23 @@ import ( type backendEvents struct { headers storage.Headers + events storage.Events executionReceipts storage.ExecutionReceipts state protocol.State + chain flow.Chain connFactory connection.ConnectionFactory log zerolog.Logger maxHeightRange uint nodeCommunicator Communicator + queryMode IndexQueryMode +} + +// blockMetadata is used to capture information about requested blocks to avoid repeated blockID +// calculations and passing around full block headers. +type blockMetadata struct { + ID flow.Identifier + Height uint64 + Timestamp time.Time } // GetEventsForHeightRange retrieves events for all sealed blocks between the start block height and @@ -43,47 +56,67 @@ func (b *backendEvents) GetEventsForHeightRange( ) ([]flow.BlockEvents, error) { if endHeight < startHeight { - return nil, status.Error(codes.InvalidArgument, "invalid start or end height") + return nil, status.Error(codes.InvalidArgument, "start height must not be larger than end height") } rangeSize := endHeight - startHeight + 1 // range is inclusive on both ends if rangeSize > uint64(b.maxHeightRange) { - return nil, status.Errorf(codes.InvalidArgument, "requested block range (%d) exceeded maximum (%d)", rangeSize, b.maxHeightRange) + return nil, status.Errorf(codes.InvalidArgument, + "requested block range (%d) exceeded maximum (%d)", rangeSize, b.maxHeightRange) } // get the latest sealed block header - head, err := b.state.Sealed().Head() + sealed, err := b.state.Sealed().Head() if err != nil { - // sealed block must be in the store, so return an Internal code even if we got NotFound + // sealed block must be in the store, so throw an exception for any error err := irrecoverable.NewExceptionf("failed to lookup sealed header: %w", err) irrecoverable.Throw(ctx, err) return nil, err } // start height should not be beyond the last sealed height - if head.Height < startHeight { + if startHeight > sealed.Height { return nil, status.Errorf(codes.OutOfRange, - "start height %d is greater than the last sealed block height %d", startHeight, head.Height) + "start height %d is greater than the last sealed block height %d", startHeight, sealed.Height) } // limit max height to last sealed block in the chain - if head.Height < endHeight { - endHeight = head.Height + // + // Note: this causes unintuitive behavior for clients making requests through a proxy that + // fronts multiple nodes. With that setup, clients may receive responses for a smaller range + // than requested because the node serving the request has a slightly delayed view of the chain. + // + // An alternative option is to return an error here, but that's likely to cause more pain for + // these clients since the requests would intermittently fail. it's recommended instead to + // check the block height of the last message in the response. this will be the last block + // height searched, and can be used to determine the start height for the next range. + if endHeight > sealed.Height { + endHeight = sealed.Height } // find the block headers for all the blocks between min and max height (inclusive) - blockHeaders := make([]*flow.Header, 0) + blockHeaders := make([]blockMetadata, 0, endHeight-startHeight+1) for i := startHeight; i <= endHeight; i++ { - header, err := b.headers.ByHeight(i) + // this looks inefficient, but is actually what's done under the covers by `headers.ByHeight` + // and avoids calculating header.ID() for each block. + blockID, err := b.headers.BlockIDByHeight(i) if err != nil { - return nil, rpc.ConvertStorageError(fmt.Errorf("failed to get events: %w", err)) + return nil, rpc.ConvertStorageError(fmt.Errorf("failed to get blockID for %d: %w", i, err)) + } + header, err := b.headers.ByBlockID(blockID) + if err != nil { + return nil, rpc.ConvertStorageError(fmt.Errorf("failed to get block header for %d: %w", i, err)) } - blockHeaders = append(blockHeaders, header) + blockHeaders = append(blockHeaders, blockMetadata{ + ID: blockID, + Height: header.Height, + Timestamp: header.Timestamp, + }) } - return b.getBlockEventsFromExecutionNode(ctx, blockHeaders, eventType, requiredEventEncodingVersion) + return b.getBlockEvents(ctx, blockHeaders, eventType, requiredEventEncodingVersion) } // GetEventsForBlockIDs retrieves events for all the specified block IDs that have the given type @@ -99,31 +132,159 @@ func (b *backendEvents) GetEventsForBlockIDs( } // find the block headers for all the block IDs - blockHeaders := make([]*flow.Header, 0) + blockHeaders := make([]blockMetadata, 0, len(blockIDs)) for _, blockID := range blockIDs { header, err := b.headers.ByBlockID(blockID) if err != nil { - return nil, rpc.ConvertStorageError(fmt.Errorf("failed to get events: %w", err)) + return nil, rpc.ConvertStorageError(fmt.Errorf("failed to get block header for %s: %w", blockID, err)) + } + + blockHeaders = append(blockHeaders, blockMetadata{ + ID: blockID, + Height: header.Height, + Timestamp: header.Timestamp, + }) + } + + return b.getBlockEvents(ctx, blockHeaders, eventType, requiredEventEncodingVersion) +} + +// getBlockEvents retrieves events for all the specified blocks that have the given type +// It gets all events available in storage, and requests the rest from an execution node. +func (b *backendEvents) getBlockEvents( + ctx context.Context, + blockInfos []blockMetadata, + eventType string, + requiredEventEncodingVersion entities.EventEncodingVersion, +) ([]flow.BlockEvents, error) { + target := flow.EventType(eventType) + + if _, err := events.ValidateEvent(target, b.chain); err != nil { + return nil, status.Errorf(codes.InvalidArgument, "invalid event type: %v", err) + } + + switch b.queryMode { + case IndexQueryModeExecutionNodesOnly: + return b.getBlockEventsFromExecutionNode(ctx, blockInfos, eventType, requiredEventEncodingVersion) + + case IndexQueryModeLocalOnly: + localResponse, missingBlocks, err := b.getBlockEventsFromStorage(ctx, blockInfos, target, requiredEventEncodingVersion) + if err != nil { + return nil, err + } + // all blocks should be available. + if len(missingBlocks) > 0 { + return nil, status.Errorf(codes.NotFound, "events not found in local storage for %d blocks", len(missingBlocks)) + } + return localResponse, nil + + case IndexQueryModeFailover: + localResponse, missingBlocks, err := b.getBlockEventsFromStorage(ctx, blockInfos, target, requiredEventEncodingVersion) + if err != nil { + // if there was an error, request all blocks from execution nodes + missingBlocks = blockInfos + b.log.Debug().Err(err).Msg("failed to get events from local storage") + } + + if len(missingBlocks) == 0 { + return localResponse, nil + } + + b.log.Debug(). + Int("missing_blocks", len(missingBlocks)). + Msg("querying execution nodes for events from missing blocks") + + enResponse, err := b.getBlockEventsFromExecutionNode(ctx, missingBlocks, eventType, requiredEventEncodingVersion) + if err != nil { + return nil, err } - blockHeaders = append(blockHeaders, header) + // sort ascending by block height + // this is needed because some blocks may be retrieved from storage and others from execution nodes. + // most likely, the earlier blocks will all be found in local storage, but that's not guaranteed, + // especially for nodes started after a spork, or once pruning is enabled. + // Note: this may not match the order of the original request for clients using GetEventsForBlockIDs + // that provide out of order block IDs + response := append(localResponse, enResponse...) + sort.Slice(response, func(i, j int) bool { + return response[i].BlockHeight < response[j].BlockHeight + }) + return response, nil + + default: + return nil, status.Errorf(codes.Internal, "unknown event query mode: %v", b.queryMode) } +} - // forward the request to the execution node - return b.getBlockEventsFromExecutionNode(ctx, blockHeaders, eventType, requiredEventEncodingVersion) +// getBlockEventsFromStorage retrieves events for all the specified blocks that have the given type +// from the local storage +func (b *backendEvents) getBlockEventsFromStorage( + ctx context.Context, + blockInfos []blockMetadata, + eventType flow.EventType, + requiredEventEncodingVersion entities.EventEncodingVersion, +) ([]flow.BlockEvents, []blockMetadata, error) { + missing := make([]blockMetadata, 0) + resp := make([]flow.BlockEvents, 0) + for _, blockInfo := range blockInfos { + if ctx.Err() != nil { + return nil, nil, rpc.ConvertError(ctx.Err(), "failed to get events from storage", codes.Canceled) + } + + events, err := b.events.ByBlockID(blockInfo.ID) + if err != nil { + // Note: if there are no events for a block, an empty slice is returned + if errors.Is(err, storage.ErrNotFound) { + missing = append(missing, blockInfo) + continue + } + err = fmt.Errorf("failed to get events for block %s: %w", blockInfo.ID, err) + return nil, nil, rpc.ConvertError(err, "failed to get events from storage", codes.Internal) + } + + filteredEvents := make([]flow.Event, 0) + for _, e := range events { + if e.Type != eventType { + continue + } + + // events are encoded in CCF format in storage. convert to JSON-CDC if requested + if requiredEventEncodingVersion == entities.EventEncodingVersion_JSON_CDC_V0 { + payload, err := convert.CcfPayloadToJsonPayload(e.Payload) + if err != nil { + err = fmt.Errorf("failed to convert event payload for block %s: %w", blockInfo.ID, err) + return nil, nil, rpc.ConvertError(err, "failed to convert event payload", codes.Internal) + } + e.Payload = payload + } + + filteredEvents = append(filteredEvents, e) + } + + resp = append(resp, flow.BlockEvents{ + BlockID: blockInfo.ID, + BlockHeight: blockInfo.Height, + BlockTimestamp: blockInfo.Timestamp, + Events: filteredEvents, + }) + } + + return resp, missing, nil } +// getBlockEventsFromExecutionNode retrieves events for all the specified blocks that have the given type +// from an execution node func (b *backendEvents) getBlockEventsFromExecutionNode( ctx context.Context, - blockHeaders []*flow.Header, + blockInfos []blockMetadata, eventType string, requiredEventEncodingVersion entities.EventEncodingVersion, ) ([]flow.BlockEvents, error) { // create an execution API request for events at block ID - blockIDs := make([]flow.Identifier, len(blockHeaders)) - for i := range blockIDs { - blockIDs[i] = blockHeaders[i].ID() + blockIDs := make([]flow.Identifier, len(blockInfos)) + for i := range blockInfos { + blockIDs[i] = blockInfos[i].ID } if len(blockIDs) == 0 { @@ -140,7 +301,6 @@ func (b *backendEvents) getBlockEventsFromExecutionNode( execNodes, err := executionNodesForBlockID(ctx, lastBlockID, b.executionReceipts, b.state, b.log) if err != nil { - b.log.Error().Err(err).Msg("failed to retrieve events from execution node") return nil, rpc.ConvertError(err, "failed to retrieve events from execution node", codes.Internal) } @@ -148,7 +308,6 @@ func (b *backendEvents) getBlockEventsFromExecutionNode( var successfulNode *flow.Identity resp, successfulNode, err = b.getEventsFromAnyExeNode(ctx, execNodes, req) if err != nil { - b.log.Error().Err(err).Msg("failed to retrieve events from execution nodes") return nil, rpc.ConvertError(err, "failed to retrieve events from execution nodes", codes.Internal) } b.log.Trace(). @@ -159,7 +318,7 @@ func (b *backendEvents) getBlockEventsFromExecutionNode( // convert execution node api result to access node api result results, err := verifyAndConvertToAccessEvents( resp.GetResults(), - blockHeaders, + blockInfos, resp.GetEventEncodingVersion(), requiredEventEncodingVersion, ) @@ -170,31 +329,31 @@ func (b *backendEvents) getBlockEventsFromExecutionNode( return results, nil } -// verifyAndConvertToAccessEvents converts execution node api result to access node api result, and verifies that the results contains -// results from each block that was requested +// verifyAndConvertToAccessEvents converts execution node api result to access node api result, +// and verifies that the results contains results from each block that was requested func verifyAndConvertToAccessEvents( execEvents []*execproto.GetEventsForBlockIDsResponse_Result, - requestedBlockHeaders []*flow.Header, + requestedBlockInfos []blockMetadata, from entities.EventEncodingVersion, to entities.EventEncodingVersion, ) ([]flow.BlockEvents, error) { - if len(execEvents) != len(requestedBlockHeaders) { + if len(execEvents) != len(requestedBlockInfos) { return nil, errors.New("number of results does not match number of blocks requested") } - requestedBlockHeaderSet := map[string]*flow.Header{} - for _, header := range requestedBlockHeaders { - requestedBlockHeaderSet[header.ID().String()] = header + requestedBlockInfoSet := map[string]blockMetadata{} + for _, header := range requestedBlockInfos { + requestedBlockInfoSet[header.ID.String()] = header } results := make([]flow.BlockEvents, len(execEvents)) for i, result := range execEvents { - header, expected := requestedBlockHeaderSet[hex.EncodeToString(result.GetBlockId())] + blockInfo, expected := requestedBlockInfoSet[hex.EncodeToString(result.GetBlockId())] if !expected { return nil, fmt.Errorf("unexpected blockID from exe node %x", result.GetBlockId()) } - if result.GetBlockHeight() != header.Height { + if result.GetBlockHeight() != blockInfo.Height { return nil, fmt.Errorf("unexpected block height %d for block %x from exe node", result.GetBlockHeight(), result.GetBlockId()) @@ -207,9 +366,9 @@ func verifyAndConvertToAccessEvents( } results[i] = flow.BlockEvents{ - BlockID: header.ID(), - BlockHeight: header.Height, - BlockTimestamp: header.Timestamp, + BlockID: blockInfo.ID, + BlockHeight: blockInfo.Height, + BlockTimestamp: blockInfo.Timestamp, Events: events, } } @@ -223,7 +382,8 @@ func verifyAndConvertToAccessEvents( // error aggregating all failures is returned. func (b *backendEvents) getEventsFromAnyExeNode(ctx context.Context, execNodes flow.IdentityList, - req *execproto.GetEventsForBlockIDsRequest) (*execproto.GetEventsForBlockIDsResponse, *flow.Identity, error) { + req *execproto.GetEventsForBlockIDsRequest, +) (*execproto.GetEventsForBlockIDsResponse, *flow.Identity, error) { var resp *execproto.GetEventsForBlockIDsResponse var execNode *flow.Identity errToReturn := b.nodeCommunicator.CallAvailableNode( @@ -259,16 +419,13 @@ func (b *backendEvents) getEventsFromAnyExeNode(ctx context.Context, func (b *backendEvents) tryGetEvents(ctx context.Context, execNode *flow.Identity, - req *execproto.GetEventsForBlockIDsRequest) (*execproto.GetEventsForBlockIDsResponse, error) { + req *execproto.GetEventsForBlockIDsRequest, +) (*execproto.GetEventsForBlockIDsResponse, error) { execRPCClient, closer, err := b.connFactory.GetExecutionAPIClient(execNode.Address) if err != nil { return nil, err } defer closer.Close() - resp, err := execRPCClient.GetEventsForBlockIDs(ctx, req) - if err != nil { - return nil, err - } - return resp, nil + return execRPCClient.GetEventsForBlockIDs(ctx, req) } diff --git a/engine/access/rpc/backend/backend_events_test.go b/engine/access/rpc/backend/backend_events_test.go new file mode 100644 index 00000000000..10306a304aa --- /dev/null +++ b/engine/access/rpc/backend/backend_events_test.go @@ -0,0 +1,465 @@ +package backend + +import ( + "context" + "fmt" + "testing" + + "github.com/rs/zerolog" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/suite" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + "github.com/onflow/cadence/encoding/ccf" + jsoncdc "github.com/onflow/cadence/encoding/json" + "github.com/onflow/flow/protobuf/go/flow/entities" + execproto "github.com/onflow/flow/protobuf/go/flow/execution" + + access "github.com/onflow/flow-go/engine/access/mock" + connectionmock "github.com/onflow/flow-go/engine/access/rpc/connection/mock" + "github.com/onflow/flow-go/engine/common/rpc/convert" + "github.com/onflow/flow-go/model/flow" + "github.com/onflow/flow-go/module/irrecoverable" + protocol "github.com/onflow/flow-go/state/protocol/mock" + "github.com/onflow/flow-go/storage" + storagemock "github.com/onflow/flow-go/storage/mock" + "github.com/onflow/flow-go/utils/unittest" + "github.com/onflow/flow-go/utils/unittest/generator" +) + +var targetEvent string + +type testCase struct { + encoding entities.EventEncodingVersion + queryMode IndexQueryMode +} + +type BackendEventsSuite struct { + suite.Suite + + log zerolog.Logger + state *protocol.State + snapshot *protocol.Snapshot + params *protocol.Params + rootHeader *flow.Header + + events *storagemock.Events + headers *storagemock.Headers + receipts *storagemock.ExecutionReceipts + connectionFactory *connectionmock.ConnectionFactory + chainID flow.ChainID + + executionNodes flow.IdentityList + execClient *access.ExecutionAPIClient + + sealedHead *flow.Header + blocks []*flow.Block + blockIDs []flow.Identifier + blockEvents []flow.Event + + testCases []testCase +} + +func TestBackendEventsSuite(t *testing.T) { + suite.Run(t, new(BackendEventsSuite)) +} + +func (s *BackendEventsSuite) SetupTest() { + s.log = unittest.Logger() + s.state = protocol.NewState(s.T()) + s.snapshot = protocol.NewSnapshot(s.T()) + s.rootHeader = unittest.BlockHeaderFixture() + s.params = protocol.NewParams(s.T()) + s.events = storagemock.NewEvents(s.T()) + s.headers = storagemock.NewHeaders(s.T()) + s.receipts = storagemock.NewExecutionReceipts(s.T()) + s.connectionFactory = connectionmock.NewConnectionFactory(s.T()) + s.chainID = flow.Testnet + + s.execClient = access.NewExecutionAPIClient(s.T()) + s.executionNodes = unittest.IdentityListFixture(2, unittest.WithRole(flow.RoleExecution)) + + blockCount := 5 + s.blocks = make([]*flow.Block, blockCount) + s.blockIDs = make([]flow.Identifier, blockCount) + + for i := 0; i < blockCount; i++ { + var header *flow.Header + if i == 0 { + header = unittest.BlockHeaderFixture() + } else { + header = unittest.BlockHeaderWithParentFixture(s.blocks[i-1].Header) + } + + payload := unittest.PayloadFixture() + header.PayloadHash = payload.Hash() + block := &flow.Block{ + Header: header, + Payload: &payload, + } + // the last block is sealed + if i == blockCount-1 { + s.sealedHead = header + } + + s.blocks[i] = block + s.blockIDs[i] = block.ID() + + s.T().Logf("block %d: %s", header.Height, block.ID()) + } + + s.blockEvents = generator.GetEventsWithEncoding(10, entities.EventEncodingVersion_CCF_V0) + targetEvent = string(s.blockEvents[0].Type) + + s.events.On("ByBlockID", mock.Anything).Return(func(blockID flow.Identifier) ([]flow.Event, error) { + for _, headerID := range s.blockIDs { + if blockID == headerID { + return s.blockEvents, nil + } + } + return nil, storage.ErrNotFound + }).Maybe() + + s.headers.On("BlockIDByHeight", mock.Anything).Return(func(height uint64) (flow.Identifier, error) { + for _, block := range s.blocks { + if height == block.Header.Height { + return block.ID(), nil + } + } + return flow.ZeroID, storage.ErrNotFound + }).Maybe() + + s.headers.On("ByBlockID", mock.Anything).Return(func(blockID flow.Identifier) (*flow.Header, error) { + for _, block := range s.blocks { + if blockID == block.ID() { + return block.Header, nil + } + } + return nil, storage.ErrNotFound + }).Maybe() + + s.testCases = make([]testCase, 0) + + for _, encoding := range []entities.EventEncodingVersion{ + entities.EventEncodingVersion_CCF_V0, + entities.EventEncodingVersion_JSON_CDC_V0, + } { + for _, queryMode := range []IndexQueryMode{ + IndexQueryModeExecutionNodesOnly, + IndexQueryModeLocalOnly, + IndexQueryModeFailover, + } { + s.testCases = append(s.testCases, testCase{ + encoding: encoding, + queryMode: queryMode, + }) + } + } +} + +func (s *BackendEventsSuite) defaultBackend() *backendEvents { + return &backendEvents{ + log: s.log, + chain: s.chainID.Chain(), + state: s.state, + events: s.events, + headers: s.headers, + executionReceipts: s.receipts, + connFactory: s.connectionFactory, + nodeCommunicator: NewNodeCommunicator(false), + maxHeightRange: DefaultMaxHeightRange, + queryMode: IndexQueryModeExecutionNodesOnly, + } +} + +// setupExecutionNodes sets up the mocks required to test against an EN backend +func (s *BackendEventsSuite) setupExecutionNodes(block *flow.Block) { + s.params.On("FinalizedRoot").Return(s.rootHeader, nil) + s.state.On("Params").Return(s.params) + s.state.On("Final").Return(s.snapshot) + s.snapshot.On("Identities", mock.Anything).Return(s.executionNodes, nil) + + // this line causes a S1021 lint error because receipts is explicitly declared. this is required + // to ensure the mock library handles the response type correctly + var receipts flow.ExecutionReceiptList //nolint:gosimple + receipts = unittest.ReceiptsForBlockFixture(block, s.executionNodes.NodeIDs()) + s.receipts.On("ByBlockID", block.ID()).Return(receipts, nil) + + s.connectionFactory.On("GetExecutionAPIClient", mock.Anything). + Return(s.execClient, &mockCloser{}, nil) +} + +// setupENSuccessResponse configures the execution node client to return a successful response +func (s *BackendEventsSuite) setupENSuccessResponse(eventType string, blocks []*flow.Block) { + s.setupExecutionNodes(blocks[len(blocks)-1]) + + ids := make([][]byte, len(blocks)) + results := make([]*execproto.GetEventsForBlockIDsResponse_Result, len(blocks)) + + events := make([]*entities.Event, 0) + for _, event := range s.blockEvents { + if string(event.Type) == eventType { + events = append(events, convert.EventToMessage(event)) + } + } + + for i, block := range blocks { + id := block.ID() + ids[i] = id[:] + results[i] = &execproto.GetEventsForBlockIDsResponse_Result{ + BlockId: id[:], + BlockHeight: block.Header.Height, + Events: events, + } + } + expectedExecRequest := &execproto.GetEventsForBlockIDsRequest{ + Type: eventType, + BlockIds: ids, + } + expectedResponse := &execproto.GetEventsForBlockIDsResponse{ + Results: results, + EventEncodingVersion: entities.EventEncodingVersion_CCF_V0, + } + + s.execClient.On("GetEventsForBlockIDs", mock.Anything, expectedExecRequest). + Return(expectedResponse, nil) +} + +// setupENFailingResponse configures the execution node client to return an error +func (s *BackendEventsSuite) setupENFailingResponse(eventType string, headers []*flow.Header, err error) { + ids := make([][]byte, len(headers)) + for i, header := range headers { + id := header.ID() + ids[i] = id[:] + } + failingRequest := &execproto.GetEventsForBlockIDsRequest{ + Type: eventType, + BlockIds: ids, + } + + s.execClient.On("GetEventsForBlockIDs", mock.Anything, failingRequest). + Return(nil, err) +} + +// TestGetEvents_HappyPaths tests the happy paths for GetEventsForBlockIDs and GetEventsForHeightRange +// across all queryModes and encodings +func (s *BackendEventsSuite) TestGetEvents_HappyPaths() { + ctx := context.Background() + + startHeight := s.blocks[0].Header.Height + endHeight := s.sealedHead.Height + + s.state.On("Sealed").Return(s.snapshot) + s.snapshot.On("Head").Return(s.sealedHead, nil) + + s.Run("GetEventsForHeightRange - end height updated", func() { + backend := s.defaultBackend() + backend.queryMode = IndexQueryModeFailover + endHeight := startHeight + 20 // should still return 5 responses + encoding := entities.EventEncodingVersion_CCF_V0 + + response, err := backend.GetEventsForHeightRange(ctx, targetEvent, startHeight, endHeight, encoding) + s.Require().NoError(err) + + s.assertResponse(response, encoding) + }) + + for _, tt := range s.testCases { + s.Run(fmt.Sprintf("all from storage - %s - %s", tt.encoding.String(), tt.queryMode), func() { + switch tt.queryMode { + case IndexQueryModeExecutionNodesOnly: + // not applicable + return + case IndexQueryModeLocalOnly, IndexQueryModeFailover: + // only calls to local storage + } + + backend := s.defaultBackend() + backend.queryMode = tt.queryMode + + response, err := backend.GetEventsForBlockIDs(ctx, targetEvent, s.blockIDs, tt.encoding) + s.Require().NoError(err) + s.assertResponse(response, tt.encoding) + + response, err = backend.GetEventsForHeightRange(ctx, targetEvent, startHeight, endHeight, tt.encoding) + s.Require().NoError(err) + s.assertResponse(response, tt.encoding) + }) + + s.Run(fmt.Sprintf("all from en - %s - %s", tt.encoding.String(), tt.queryMode), func() { + events := storagemock.NewEvents(s.T()) + + switch tt.queryMode { + case IndexQueryModeLocalOnly: + // not applicable + return + case IndexQueryModeExecutionNodesOnly: + // only calls to EN, no calls to storage + case IndexQueryModeFailover: + // all calls to storage fail + events.On("ByBlockID", mock.Anything).Return(nil, storage.ErrNotFound) + } + + backend := s.defaultBackend() + backend.queryMode = tt.queryMode + backend.events = events + + s.setupENSuccessResponse(targetEvent, s.blocks) + + response, err := backend.GetEventsForBlockIDs(ctx, targetEvent, s.blockIDs, tt.encoding) + s.Require().NoError(err) + s.assertResponse(response, tt.encoding) + + response, err = backend.GetEventsForHeightRange(ctx, targetEvent, startHeight, endHeight, tt.encoding) + s.Require().NoError(err) + s.assertResponse(response, tt.encoding) + }) + + s.Run(fmt.Sprintf("mixed storage & en - %s - %s", tt.encoding.String(), tt.queryMode), func() { + events := storagemock.NewEvents(s.T()) + + switch tt.queryMode { + case IndexQueryModeLocalOnly, IndexQueryModeExecutionNodesOnly: + // not applicable + return + case IndexQueryModeFailover: + // only failing blocks queried from EN + s.setupENSuccessResponse(targetEvent, s.blocks[0:2]) + } + + // the first 2 blocks are not available from storage, and should be fetched from the EN + events.On("ByBlockID", s.blockIDs[0]).Return(nil, storage.ErrNotFound) + events.On("ByBlockID", s.blockIDs[1]).Return(nil, storage.ErrNotFound) + events.On("ByBlockID", s.blockIDs[2]).Return(s.blockEvents, nil) + events.On("ByBlockID", s.blockIDs[3]).Return(s.blockEvents, nil) + events.On("ByBlockID", s.blockIDs[4]).Return(s.blockEvents, nil) + + backend := s.defaultBackend() + backend.queryMode = tt.queryMode + backend.events = events + + response, err := backend.GetEventsForBlockIDs(ctx, targetEvent, s.blockIDs, tt.encoding) + s.Require().NoError(err) + s.assertResponse(response, tt.encoding) + + response, err = backend.GetEventsForHeightRange(ctx, targetEvent, startHeight, endHeight, tt.encoding) + s.Require().NoError(err) + s.assertResponse(response, tt.encoding) + }) + } +} + +func (s *BackendEventsSuite) TestGetEventsForHeightRange_HandlesErrors() { + ctx := context.Background() + + startHeight := s.blocks[0].Header.Height + endHeight := s.sealedHead.Height + encoding := entities.EventEncodingVersion_CCF_V0 + + s.Run("returns error for endHeight < startHeight", func() { + backend := s.defaultBackend() + endHeight := startHeight - 1 + + response, err := backend.GetEventsForHeightRange(ctx, targetEvent, startHeight, endHeight, encoding) + s.Assert().Equal(codes.InvalidArgument, status.Code(err)) + s.Assert().Nil(response) + }) + + s.Run("returns error for range larger than max", func() { + backend := s.defaultBackend() + endHeight := startHeight + DefaultMaxHeightRange + + response, err := backend.GetEventsForHeightRange(ctx, targetEvent, startHeight, endHeight, encoding) + s.Assert().Equal(codes.InvalidArgument, status.Code(err)) + s.Assert().Nil(response) + }) + + s.Run("throws irrecoverable if sealed header not available", func() { + s.state.On("Sealed").Return(s.snapshot) + s.snapshot.On("Head").Return(nil, storage.ErrNotFound).Once() + + signCtxErr := irrecoverable.NewExceptionf("failed to lookup sealed header: %w", storage.ErrNotFound) + signalerCtx := irrecoverable.WithSignalerContext(context.Background(), + irrecoverable.NewMockSignalerContextExpectError(s.T(), ctx, signCtxErr)) + + backend := s.defaultBackend() + + response, err := backend.GetEventsForHeightRange(signalerCtx, targetEvent, startHeight, endHeight, encoding) + // these will never be returned in production + s.Assert().Equal(codes.Unknown, status.Code(err)) + s.Assert().Nil(response) + }) + + s.state.On("Sealed").Return(s.snapshot) + s.snapshot.On("Head").Return(s.sealedHead, nil) + + s.Run("returns error for startHeight > sealed height", func() { + backend := s.defaultBackend() + startHeight := s.sealedHead.Height + 1 + endHeight := startHeight + 1 + + response, err := backend.GetEventsForHeightRange(ctx, targetEvent, startHeight, endHeight, encoding) + s.Assert().Equal(codes.OutOfRange, status.Code(err)) + s.Assert().Nil(response) + }) +} + +func (s *BackendEventsSuite) TestGetEventsForBlockIDs_HandlesErrors() { + ctx := context.Background() + + encoding := entities.EventEncodingVersion_CCF_V0 + + s.Run("returns error when too many blockIDs requested", func() { + backend := s.defaultBackend() + backend.maxHeightRange = 3 + + response, err := backend.GetEventsForBlockIDs(ctx, targetEvent, s.blockIDs, encoding) + s.Assert().Equal(codes.InvalidArgument, status.Code(err)) + s.Assert().Nil(response) + }) + + s.Run("returns error for missing header", func() { + headers := storagemock.NewHeaders(s.T()) + backend := s.defaultBackend() + backend.headers = headers + + for i, blockID := range s.blockIDs { + // return error on the last header + if i == len(s.blocks)-1 { + headers.On("ByBlockID", blockID).Return(nil, storage.ErrNotFound) + continue + } + + headers.On("ByBlockID", blockID).Return(s.blocks[i].Header, nil) + } + + response, err := backend.GetEventsForBlockIDs(ctx, targetEvent, s.blockIDs, encoding) + s.Assert().Equal(codes.NotFound, status.Code(err)) + s.Assert().Nil(response) + }) +} + +func (s *BackendEventsSuite) assertResponse(response []flow.BlockEvents, encoding entities.EventEncodingVersion) { + s.Assert().Len(response, len(s.blocks)) + for i, block := range s.blocks { + s.Assert().Equal(block.Header.Height, response[i].BlockHeight) + s.Assert().Equal(block.Header.ID(), response[i].BlockID) + s.Assert().Len(response[i].Events, 1) + + s.assertEncoding(&response[i].Events[0], encoding) + } +} + +func (s *BackendEventsSuite) assertEncoding(event *flow.Event, encoding entities.EventEncodingVersion) { + var err error + switch encoding { + case entities.EventEncodingVersion_CCF_V0: + _, err = ccf.Decode(nil, event.Payload) + case entities.EventEncodingVersion_JSON_CDC_V0: + _, err = jsoncdc.Decode(nil, event.Payload) + default: + s.T().Errorf("unknown encoding: %s", encoding.String()) + } + s.Require().NoError(err) +} diff --git a/engine/access/rpc/backend/backend_scripts.go b/engine/access/rpc/backend/backend_scripts.go index 54e97817d89..e3e0d8db3db 100644 --- a/engine/access/rpc/backend/backend_scripts.go +++ b/engine/access/rpc/backend/backend_scripts.go @@ -37,7 +37,7 @@ type backendScripts struct { loggedScripts *lru.Cache[[md5.Size]byte, time.Time] nodeCommunicator Communicator scriptExecutor execution.ScriptExecutor - scriptExecMode ScriptExecutionMode + scriptExecMode IndexQueryMode } // scriptExecutionRequest encapsulates the data needed to execute a script to make it easier @@ -118,15 +118,15 @@ func (b *backendScripts) executeScript( scriptRequest *scriptExecutionRequest, ) ([]byte, error) { switch b.scriptExecMode { - case ScriptExecutionModeExecutionNodesOnly: + case IndexQueryModeExecutionNodesOnly: result, _, err := b.executeScriptOnAvailableExecutionNodes(ctx, scriptRequest) return result, err - case ScriptExecutionModeLocalOnly: + case IndexQueryModeLocalOnly: result, _, err := b.executeScriptLocally(ctx, scriptRequest) return result, err - case ScriptExecutionModeFailover: + case IndexQueryModeFailover: localResult, localDuration, localErr := b.executeScriptLocally(ctx, scriptRequest) if localErr == nil || isInvalidArgumentError(localErr) || status.Code(localErr) == codes.Canceled { return localResult, localErr @@ -143,7 +143,7 @@ func (b *backendScripts) executeScript( return execResult, execErr - case ScriptExecutionModeCompare: + case IndexQueryModeCompare: execResult, execDuration, execErr := b.executeScriptOnAvailableExecutionNodes(ctx, scriptRequest) // we can only compare the results if there were either no errors or a cadence error // since we cannot distinguish the EN error as caused by the block being pruned or some other reason, @@ -332,13 +332,14 @@ func convertScriptExecutionError(err error, height uint64) error { return nil } + var failure fvmerrors.CodedFailure + if fvmerrors.As(err, &failure) { + return rpc.ConvertError(err, "failed to execute script", codes.Internal) + } + + // general FVM/ledger errors var coded fvmerrors.CodedError if fvmerrors.As(err, &coded) { - // general FVM/ledger errors - if coded.Code().IsFailure() { - return rpc.ConvertError(err, "failed to execute script", codes.Internal) - } - switch coded.Code() { case fvmerrors.ErrCodeScriptExecutionCancelledError: return status.Errorf(codes.Canceled, "script execution canceled: %v", err) diff --git a/engine/access/rpc/backend/backend_scripts_test.go b/engine/access/rpc/backend/backend_scripts_test.go index fbcc4105f9c..43531e3c8e2 100644 --- a/engine/access/rpc/backend/backend_scripts_test.go +++ b/engine/access/rpc/backend/backend_scripts_test.go @@ -34,7 +34,7 @@ var ( expectedResponse = []byte("response_data") cadenceErr = fvmerrors.NewCodedError(fvmerrors.ErrCodeCadenceRunTimeError, "cadence error") - fvmFailureErr = fvmerrors.NewCodedError(fvmerrors.FailureCodeBlockFinderFailure, "fvm error") + fvmFailureErr = fvmerrors.NewCodedFailure(fvmerrors.FailureCodeBlockFinderFailure, "fvm error") ctxCancelErr = fvmerrors.NewCodedError(fvmerrors.ErrCodeScriptExecutionCancelledError, "context canceled error") timeoutErr = fvmerrors.NewCodedError(fvmerrors.ErrCodeScriptExecutionTimedOutError, "timeout error") ) @@ -158,7 +158,7 @@ func (s *BackendScriptsSuite) TestExecuteScriptOnExecutionNode_HappyPath() { s.setupENSuccessResponse(s.block.ID()) backend := s.defaultBackend() - backend.scriptExecMode = ScriptExecutionModeExecutionNodesOnly + backend.scriptExecMode = IndexQueryModeExecutionNodesOnly s.Run("GetAccount", func() { s.testExecuteScriptAtLatestBlock(ctx, backend, codes.OK) @@ -186,7 +186,7 @@ func (s *BackendScriptsSuite) TestExecuteScriptOnExecutionNode_Fails() { s.setupENFailingResponse(s.block.ID(), errToReturn) backend := s.defaultBackend() - backend.scriptExecMode = ScriptExecutionModeExecutionNodesOnly + backend.scriptExecMode = IndexQueryModeExecutionNodesOnly s.Run("GetAccount", func() { s.testExecuteScriptAtLatestBlock(ctx, backend, statusCode) @@ -211,7 +211,7 @@ func (s *BackendScriptsSuite) TestExecuteScriptFromStorage_HappyPath() { Return(expectedResponse, nil) backend := s.defaultBackend() - backend.scriptExecMode = ScriptExecutionModeLocalOnly + backend.scriptExecMode = IndexQueryModeLocalOnly backend.scriptExecutor = scriptExecutor s.Run("GetAccount - happy path", func() { @@ -235,7 +235,7 @@ func (s *BackendScriptsSuite) TestExecuteScriptFromStorage_Fails() { scriptExecutor := execmock.NewScriptExecutor(s.T()) backend := s.defaultBackend() - backend.scriptExecMode = ScriptExecutionModeLocalOnly + backend.scriptExecMode = IndexQueryModeLocalOnly backend.scriptExecutor = scriptExecutor testCases := []struct { @@ -300,7 +300,7 @@ func (s *BackendScriptsSuite) TestExecuteScriptWithFailover_HappyPath() { scriptExecutor := execmock.NewScriptExecutor(s.T()) backend := s.defaultBackend() - backend.scriptExecMode = ScriptExecutionModeFailover + backend.scriptExecMode = IndexQueryModeFailover backend.scriptExecutor = scriptExecutor for _, errToReturn := range errors { @@ -331,7 +331,7 @@ func (s *BackendScriptsSuite) TestExecuteScriptWithFailover_SkippedForCorrectCod scriptExecutor := execmock.NewScriptExecutor(s.T()) backend := s.defaultBackend() - backend.scriptExecMode = ScriptExecutionModeFailover + backend.scriptExecMode = IndexQueryModeFailover backend.scriptExecutor = scriptExecutor testCases := []struct { @@ -386,7 +386,7 @@ func (s *BackendScriptsSuite) TestExecuteScriptWithFailover_ReturnsENErrors() { Return(nil, execution.ErrDataNotAvailable) backend := s.defaultBackend() - backend.scriptExecMode = ScriptExecutionModeFailover + backend.scriptExecMode = IndexQueryModeFailover backend.scriptExecutor = scriptExecutor s.Run("ExecuteScriptAtLatestBlock", func() { @@ -408,7 +408,7 @@ func (s *BackendScriptsSuite) TestExecuteScriptAtLatestBlockFromStorage_Inconsis scriptExecutor := execmock.NewScriptExecutor(s.T()) backend := s.defaultBackend() - backend.scriptExecMode = ScriptExecutionModeLocalOnly + backend.scriptExecMode = IndexQueryModeLocalOnly backend.scriptExecutor = scriptExecutor s.Run(fmt.Sprintf("ExecuteScriptAtLatestBlock - fails with %v", "inconsistent node's state"), func() { @@ -438,7 +438,7 @@ func (s *BackendScriptsSuite) testExecuteScriptAtLatestBlock(ctx context.Context } else { actual, err := backend.ExecuteScriptAtLatestBlock(ctx, s.failingScript, s.arguments) s.Require().Error(err) - s.Require().Equal(statusCode, status.Code(err)) + s.Require().Equal(statusCode, status.Code(err), "error code mismatch: expected %d, got %d: %s", statusCode, status.Code(err), err) s.Require().Nil(actual) } } @@ -454,7 +454,7 @@ func (s *BackendScriptsSuite) testExecuteScriptAtBlockID(ctx context.Context, ba } else { actual, err := backend.ExecuteScriptAtBlockID(ctx, blockID, s.failingScript, s.arguments) s.Require().Error(err) - s.Require().Equal(statusCode, status.Code(err)) + s.Require().Equal(statusCode, status.Code(err), "error code mismatch: expected %d, got %d: %s", statusCode, status.Code(err), err) s.Require().Nil(actual) } } @@ -470,7 +470,7 @@ func (s *BackendScriptsSuite) testExecuteScriptAtBlockHeight(ctx context.Context } else { actual, err := backend.ExecuteScriptAtBlockHeight(ctx, height, s.failingScript, s.arguments) s.Require().Error(err) - s.Require().Equal(statusCode, status.Code(err)) + s.Require().Equalf(statusCode, status.Code(err), "error code mismatch: expected %d, got %d: %s", statusCode, status.Code(err), err) s.Require().Nil(actual) } } diff --git a/engine/access/rpc/backend/backend_test.go b/engine/access/rpc/backend/backend_test.go index fb434fb62b2..0647e3e9119 100644 --- a/engine/access/rpc/backend/backend_test.go +++ b/engine/access/rpc/backend/backend_test.go @@ -8,6 +8,7 @@ import ( "github.com/dgraph-io/badger/v2" accessproto "github.com/onflow/flow/protobuf/go/flow/access" + "github.com/onflow/flow/protobuf/go/flow/entities" entitiesproto "github.com/onflow/flow/protobuf/go/flow/entities" execproto "github.com/onflow/flow/protobuf/go/flow/execution" "github.com/rs/zerolog" @@ -65,7 +66,6 @@ type Suite struct { colClient *access.AccessAPIClient execClient *access.ExecutionAPIClient historicalAccessClient *access.AccessAPIClient - archiveClient *access.AccessAPIClient connectionFactory *connectionmock.ConnectionFactory communicator *backendmock.Communicator @@ -97,7 +97,6 @@ func (suite *Suite) SetupTest() { suite.receipts = new(storagemock.ExecutionReceipts) suite.results = new(storagemock.ExecutionResults) suite.colClient = new(access.AccessAPIClient) - suite.archiveClient = new(access.AccessAPIClient) suite.execClient = new(access.ExecutionAPIClient) suite.transactionResults = storagemock.NewLightTransactionResults(suite.T()) suite.chainID = flow.Testnet @@ -1473,159 +1472,6 @@ type mockCloser struct{} func (mc *mockCloser) Close() error { return nil } -func (suite *Suite) TestGetEventsForBlockIDs() { - suite.state.On("Sealed").Return(suite.snapshot, nil).Maybe() - suite.state.On("Final").Return(suite.snapshot, nil).Maybe() - - exeNodeEventEncodingVersion := entitiesproto.EventEncodingVersion_CCF_V0 - events := generator.GetEventsWithEncoding(10, exeNodeEventEncodingVersion) - validExecutorIdentities := flow.IdentityList{} - - setupStorage := func(n int) []*flow.Header { - headers := make([]*flow.Header, n) - ids := unittest.IdentityListFixture(2, unittest.WithRole(flow.RoleExecution)) - - for i := 0; i < n; i++ { - b := unittest.BlockFixture() - suite.headers. - On("ByBlockID", b.ID()). - Return(b.Header, nil).Once() - - headers[i] = b.Header - - receipt1 := unittest.ReceiptForBlockFixture(&b) - receipt1.ExecutorID = ids[0].NodeID - receipt2 := unittest.ReceiptForBlockFixture(&b) - receipt2.ExecutorID = ids[1].NodeID - receipt1.ExecutionResult = receipt2.ExecutionResult - suite.receipts. - On("ByBlockID", b.ID()). - Return(flow.ExecutionReceiptList{receipt1, receipt2}, nil).Once() - validExecutorIdentities = append(validExecutorIdentities, ids...) - } - - return headers - } - blockHeaders := setupStorage(5) - - suite.snapshot.On("Identities", mock.Anything).Return(validExecutorIdentities, nil) - validENIDs := flow.IdentifierList(validExecutorIdentities.NodeIDs()) - - // create a mock connection factory - connFactory := connectionmock.NewConnectionFactory(suite.T()) - connFactory.On("GetExecutionAPIClient", mock.Anything).Return(suite.execClient, &mockCloser{}, nil) - - // create the expected results from execution node and access node - exeResults := make([]*execproto.GetEventsForBlockIDsResponse_Result, len(blockHeaders)) - - for i := 0; i < len(blockHeaders); i++ { - exeResults[i] = &execproto.GetEventsForBlockIDsResponse_Result{ - BlockId: convert.IdentifierToMessage(blockHeaders[i].ID()), - BlockHeight: blockHeaders[i].Height, - Events: convert.EventsToMessages(events), - } - } - - expected := make([]flow.BlockEvents, len(blockHeaders)) - expectedEvents := generator.GetEventsWithEncoding(10, entitiesproto.EventEncodingVersion_JSON_CDC_V0) - for i := 0; i < len(blockHeaders); i++ { - expected[i] = flow.BlockEvents{ - BlockID: blockHeaders[i].ID(), - BlockHeight: blockHeaders[i].Height, - BlockTimestamp: blockHeaders[i].Timestamp, - Events: expectedEvents, - } - } - - // create the execution node response - exeResp := &execproto.GetEventsForBlockIDsResponse{ - Results: exeResults, - EventEncodingVersion: exeNodeEventEncodingVersion, - } - - ctx := context.Background() - - blockIDs := make([]flow.Identifier, len(blockHeaders)) - for i, header := range blockHeaders { - blockIDs[i] = header.ID() - } - exeReq := &execproto.GetEventsForBlockIDsRequest{ - BlockIds: convert.IdentifiersToMessages(blockIDs), - Type: string(flow.EventAccountCreated), - } - - // create receipt mocks that always returns empty - receipts := new(storagemock.ExecutionReceipts) - receipts. - On("ByBlockID", mock.Anything). - Return(flow.ExecutionReceiptList{}, nil) - - // expect two calls to the executor api client (one for each of the following 2 test cases) - suite.execClient. - On("GetEventsForBlockIDs", ctx, exeReq). - Return(exeResp, nil). - Once() - - suite.Run("with an execution node chosen using block ID form the list of Fixed ENs", func() { - - params := suite.defaultBackendParams() - params.ConnFactory = connFactory - // set the fixed EN Identifiers to the generated execution IDs - params.FixedExecutionNodeIDs = validENIDs.Strings() - - // create the handler - backend, err := New(params) - suite.Require().NoError(err) - - // execute request - actual, err := backend.GetEventsForBlockIDs(ctx, string(flow.EventAccountCreated), blockIDs, entitiesproto.EventEncodingVersion_JSON_CDC_V0) - suite.checkResponse(actual, err) - - suite.Require().Equal(expected, actual) - }) - - suite.Run("with an empty block ID list", func() { - - params := suite.defaultBackendParams() - params.ExecutionReceipts = receipts - params.ConnFactory = connFactory - params.FixedExecutionNodeIDs = validENIDs.Strings() - - // create the handler - backend, err := New(params) - suite.Require().NoError(err) - - // execute request with an empty block id list and expect an empty list of events and no error - resp, err := backend.GetEventsForBlockIDs(ctx, string(flow.EventAccountCreated), []flow.Identifier{}, entitiesproto.EventEncodingVersion_JSON_CDC_V0) - require.NoError(suite.T(), err) - require.Empty(suite.T(), resp) - }) - - for _, version := range eventEncodingVersions { - suite.Run(fmt.Sprintf("test %s event encoding version for GetEventsForBlockIDs", version.String()), func() { - params := suite.defaultBackendParams() - params.ExecutionReceipts = receipts - params.ConnFactory = connFactory - params.FixedExecutionNodeIDs = validENIDs.Strings() - - // create the handler - backend, err := New(params) - suite.Require().NoError(err) - - // execute request with an empty block id list and expect an empty list of events and no error - result, err := backend.GetEventsForBlockIDs(ctx, string(flow.EventAccountCreated), []flow.Identifier{}, version) - expectedResult := generator.GetEventsWithEncoding(1, version) - suite.checkResponse(result, err) - - for _, blockEvent := range result { - suite.Assert().Equal(blockEvent.Events, expectedResult) - } - }) - } - - suite.assertAllExpectations() -} - func (suite *Suite) TestGetExecutionResultByID() { suite.state.On("Sealed").Return(suite.snapshot, nil).Maybe() @@ -1748,300 +1594,6 @@ func (suite *Suite) TestGetExecutionResultByBlockID() { suite.assertAllExpectations() } -func (suite *Suite) TestGetEventsForHeightRange() { - ctx := context.Background() - const minHeight uint64 = 5 - const maxHeight uint64 = 10 - var headHeight uint64 - var blockHeaders []*flow.Header - var nodeIdentities flow.IdentityList - - headersDB := make(map[uint64]*flow.Header) // backend for storage.Headers - var head *flow.Header // backend for Snapshot.Head - - state := new(protocol.State) - snapshot := new(protocol.Snapshot) - state.On("Final").Return(snapshot, nil) - state.On("Sealed").Return(snapshot, nil) - - rootHeader := unittest.BlockHeaderFixture() - stateParams := new(protocol.Params) - stateParams.On("FinalizedRoot").Return(rootHeader, nil) - state.On("Params").Return(stateParams).Maybe() - - snapshot.On("Identities", mock.Anything).Return( - func(_ flow.IdentityFilter) flow.IdentityList { - return nodeIdentities - }, - func(flow.IdentityFilter) error { return nil }, - ) - - // mock Headers to pull from Headers backend - suite.headers.On("ByHeight", mock.Anything).Return( - func(height uint64) *flow.Header { - return headersDB[height] - }, - func(height uint64) error { - _, ok := headersDB[height] - if !ok { - return storage.ErrNotFound - } - return nil - }).Maybe() - - setupHeadHeight := func(height uint64) { - header := unittest.BlockHeaderFixture() // create a mock header - header.Height = height // set the header height - head = header - } - - setupStorage := func(min uint64, max uint64) ([]*flow.Header, []*flow.ExecutionReceipt, flow.IdentityList) { - headersDB = make(map[uint64]*flow.Header) // reset backend - - var headers []*flow.Header - var ers []*flow.ExecutionReceipt - var enIDs flow.IdentityList - for i := min; i <= max; i++ { - block := unittest.BlockFixture() - header := block.Header - headersDB[i] = header - headers = append(headers, header) - newErs, ids := suite.setupReceipts(&block) - ers = append(ers, newErs...) - enIDs = append(enIDs, ids...) - } - return headers, ers, enIDs - } - - setupExecClient := func() []flow.BlockEvents { - blockIDs := make([]flow.Identifier, len(blockHeaders)) - for i, header := range blockHeaders { - blockIDs[i] = header.ID() - } - execReq := &execproto.GetEventsForBlockIDsRequest{ - BlockIds: convert.IdentifiersToMessages(blockIDs), - Type: string(flow.EventAccountCreated), - } - - results := make([]flow.BlockEvents, len(blockHeaders)) - exeResults := make([]*execproto.GetEventsForBlockIDsResponse_Result, len(blockHeaders)) - - exeNodeEventEncodingVersion := entitiesproto.EventEncodingVersion_CCF_V0 - - for i, header := range blockHeaders { - events := generator.GetEventsWithEncoding(1, exeNodeEventEncodingVersion) - height := header.Height - - results[i] = flow.BlockEvents{ - BlockID: header.ID(), - BlockHeight: height, - BlockTimestamp: header.Timestamp, - Events: generator.GetEventsWithEncoding(1, entitiesproto.EventEncodingVersion_JSON_CDC_V0), - } - - exeResults[i] = &execproto.GetEventsForBlockIDsResponse_Result{ - BlockId: convert.IdentifierToMessage(header.ID()), - BlockHeight: header.Height, - Events: convert.EventsToMessages(events), - } - } - - exeResp := &execproto.GetEventsForBlockIDsResponse{ - Results: exeResults, - EventEncodingVersion: exeNodeEventEncodingVersion, - } - - suite.execClient. - On("GetEventsForBlockIDs", ctx, execReq). - Return(exeResp, nil). - Once() - - return results - } - - // tests that signaler context received error when node state is inconsistent - suite.Run("inconsistent node's state", func() { - headHeight = maxHeight - 1 - setupHeadHeight(headHeight) - - // setup mocks - stateParams.On("SporkID").Return(unittest.IdentifierFixture(), nil) - stateParams.On("ProtocolVersion").Return(uint(unittest.Uint64InRange(10, 30)), nil) - stateParams.On("SporkRootBlockHeight").Return(headHeight, nil) - stateParams.On("SealedRoot").Return(head, nil) - - params := suite.defaultBackendParams() - params.State = state - - backend, err := New(params) - suite.Require().NoError(err) - - err = fmt.Errorf("inconsistent node's state") - snapshot.On("Head").Return(nil, err).Once() - - signCtxErr := irrecoverable.NewExceptionf("failed to lookup sealed header: %w", err) - signalerCtx := irrecoverable.WithSignalerContext(context.Background(), - irrecoverable.NewMockSignalerContextExpectError(suite.T(), context.Background(), signCtxErr)) - - actual, err := backend.GetEventsForHeightRange(signalerCtx, string(flow.EventAccountCreated), minHeight, maxHeight, - entitiesproto.EventEncodingVersion_JSON_CDC_V0) - suite.Require().Error(err) - suite.Require().Nil(actual) - }) - - connFactory := suite.setupConnectionFactory() - // mock snapshot to return head backend - snapshot.On("Head").Return( - func() *flow.Header { return head }, - func() error { return nil }, - ) - - //suite.state = state - suite.Run("invalid request max height < min height", func() { - params := suite.defaultBackendParams() - params.ConnFactory = connFactory - - backend, err := New(params) - suite.Require().NoError(err) - - _, err = backend.GetEventsForHeightRange(ctx, string(flow.EventAccountCreated), maxHeight, minHeight, entitiesproto.EventEncodingVersion_JSON_CDC_V0) - suite.Require().Error(err) - - suite.assertAllExpectations() // assert that request was not sent to execution node - }) - - suite.Run("valid request with min_height < max_height < last_sealed_block_height", func() { - - headHeight = maxHeight + 1 - - // setup mocks - setupHeadHeight(headHeight) - blockHeaders, _, nodeIdentities = setupStorage(minHeight, maxHeight) - expectedResp := setupExecClient() - fixedENIdentifiersStr := flow.IdentifierList(nodeIdentities.NodeIDs()).Strings() - - stateParams.On("SporkID").Return(unittest.IdentifierFixture(), nil) - stateParams.On("ProtocolVersion").Return(uint(unittest.Uint64InRange(10, 30)), nil) - stateParams.On("SporkRootBlockHeight").Return(headHeight, nil) - stateParams.On("SealedRoot").Return(head, nil) - - params := suite.defaultBackendParams() - params.State = state - params.ConnFactory = connFactory - params.FixedExecutionNodeIDs = fixedENIdentifiersStr - - backend, err := New(params) - suite.Require().NoError(err) - - // execute request - actualResp, err := backend.GetEventsForHeightRange(ctx, string(flow.EventAccountCreated), minHeight, maxHeight, entitiesproto.EventEncodingVersion_JSON_CDC_V0) - - // check response - suite.checkResponse(actualResp, err) - suite.assertAllExpectations() - suite.Require().Equal(expectedResp, actualResp) - }) - - suite.Run("valid request with max_height > last_sealed_block_height", func() { - headHeight = maxHeight - 1 - setupHeadHeight(headHeight) - blockHeaders, _, nodeIdentities = setupStorage(minHeight, headHeight) - expectedResp := setupExecClient() - fixedENIdentifiersStr := flow.IdentifierList(nodeIdentities.NodeIDs()).Strings() - - stateParams.On("SporkID").Return(unittest.IdentifierFixture(), nil) - stateParams.On("ProtocolVersion").Return(uint(unittest.Uint64InRange(10, 30)), nil) - stateParams.On("SporkRootBlockHeight").Return(headHeight, nil) - stateParams.On("SealedRoot").Return(head, nil) - - params := suite.defaultBackendParams() - params.State = state - params.ConnFactory = connFactory - params.FixedExecutionNodeIDs = fixedENIdentifiersStr - - backend, err := New(params) - suite.Require().NoError(err) - - actualResp, err := backend.GetEventsForHeightRange(ctx, string(flow.EventAccountCreated), minHeight, maxHeight, entitiesproto.EventEncodingVersion_JSON_CDC_V0) - suite.checkResponse(actualResp, err) - - suite.assertAllExpectations() - suite.Require().Equal(expectedResp, actualResp) - }) - - // set max height range to 1 and request range of 2 - suite.Run("invalid request exceeding max height range", func() { - headHeight = maxHeight - 1 - setupHeadHeight(headHeight) - blockHeaders, _, nodeIdentities = setupStorage(minHeight, headHeight) - fixedENIdentifiersStr := flow.IdentifierList(nodeIdentities.NodeIDs()).Strings() - - params := suite.defaultBackendParams() - params.ConnFactory = connFactory - params.MaxHeightRange = 1 - params.FixedExecutionNodeIDs = fixedENIdentifiersStr - - backend, err := New(params) - suite.Require().NoError(err) - - _, err = backend.GetEventsForHeightRange(ctx, string(flow.EventAccountCreated), minHeight, minHeight+1, entitiesproto.EventEncodingVersion_JSON_CDC_V0) - suite.Require().Error(err) - }) - - suite.Run("invalid request last_sealed_block_height < min height", func() { - - // set sealed height to one less than the request start height - headHeight = minHeight - 1 - - // setup mocks - setupHeadHeight(headHeight) - blockHeaders, _, nodeIdentities = setupStorage(minHeight, maxHeight) - fixedENIdentifiersStr := flow.IdentifierList(nodeIdentities.NodeIDs()).Strings() - - params := suite.defaultBackendParams() - params.State = state - params.ConnFactory = connFactory - params.FixedExecutionNodeIDs = fixedENIdentifiersStr - - backend, err := New(params) - suite.Require().NoError(err) - - _, err = backend.GetEventsForHeightRange(ctx, string(flow.EventAccountCreated), minHeight, maxHeight, entitiesproto.EventEncodingVersion_JSON_CDC_V0) - suite.Require().Error(err) - }) - - for _, version := range eventEncodingVersions { - suite.Run(fmt.Sprintf("test %s event encoding version for GetEventsForHeightRange", version.String()), func() { - headHeight = maxHeight - 1 - setupHeadHeight(headHeight) - blockHeaders, _, nodeIdentities = setupStorage(minHeight, headHeight) - _ = setupExecClient() - fixedENIdentifiersStr := nodeIdentities.NodeIDs().Strings() - - stateParams.On("SporkID").Return(unittest.IdentifierFixture(), nil) - stateParams.On("ProtocolVersion").Return(uint(unittest.Uint64InRange(10, 30)), nil) - stateParams.On("SporkRootBlockHeight").Return(headHeight, nil) - stateParams.On("SealedRoot").Return(head, nil) - - params := suite.defaultBackendParams() - params.State = state - params.ConnFactory = connFactory - params.FixedExecutionNodeIDs = fixedENIdentifiersStr - - backend, err := New(params) - suite.Require().NoError(err) - - result, err := backend.GetEventsForHeightRange(ctx, string(flow.EventAccountCreated), minHeight, maxHeight, version) - expectedResult := generator.GetEventsWithEncoding(1, version) - suite.checkResponse(result, err) - - for _, blockEvent := range result { - suite.Assert().Equal(blockEvent.Events, expectedResult) - } - }) - } -} - func (suite *Suite) TestGetNodeVersionInfo() { sporkRootBlock := unittest.BlockHeaderFixture() nodeRootBlock := unittest.BlockHeaderFixture(unittest.WithHeaderHeight(sporkRootBlock.Height + 100)) @@ -2382,15 +1934,14 @@ func (suite *Suite) TestGetTransactionResultEventEncodingVersion() { backend, err := New(params) suite.Require().NoError(err) - exeNodeEventEncodingVersion := entitiesproto.EventEncodingVersion_CCF_V0 - events := generator.GetEventsWithEncoding(1, exeNodeEventEncodingVersion) - eventMessages := convert.EventsToMessages(events) + ccfEvents, jsoncdcEvents := generateEncodedEvents(suite.T(), 1) + eventMessages := convert.EventsToMessages(ccfEvents) for _, version := range eventEncodingVersions { suite.Run(fmt.Sprintf("test %s event encoding version for GetTransactionResult", version.String()), func() { exeEventResp := &execproto.GetTransactionResultResponse{ Events: eventMessages, - EventEncodingVersion: exeNodeEventEncodingVersion, + EventEncodingVersion: entitiesproto.EventEncodingVersion_CCF_V0, } suite.execClient. @@ -2402,9 +1953,16 @@ func (suite *Suite) TestGetTransactionResultEventEncodingVersion() { Once() result, err := backend.GetTransactionResult(ctx, txId, blockId, flow.ZeroID, version) - expectedResult := generator.GetEventsWithEncoding(1, version) suite.checkResponse(result, err) + var expectedResult []flow.Event + switch version { + case entitiesproto.EventEncodingVersion_CCF_V0: + expectedResult = append(expectedResult, ccfEvents...) + case entitiesproto.EventEncodingVersion_JSON_CDC_V0: + expectedResult = append(expectedResult, jsoncdcEvents...) + } + suite.Assert().Equal(result.Events, expectedResult) }) } @@ -2443,8 +2001,8 @@ func (suite *Suite) TestGetTransactionResultByIndexAndBlockIdEventEncodingVersio suite.Require().NoError(err) exeNodeEventEncodingVersion := entitiesproto.EventEncodingVersion_CCF_V0 - events := generator.GetEventsWithEncoding(1, exeNodeEventEncodingVersion) - eventMessages := convert.EventsToMessages(events) + ccfEvents, jsoncdcEvents := generateEncodedEvents(suite.T(), 1) + eventMessages := convert.EventsToMessages(ccfEvents) for _, version := range eventEncodingVersions { suite.Run(fmt.Sprintf("test %s event encoding version for GetTransactionResultByIndex", version.String()), func() { @@ -2464,8 +2022,15 @@ func (suite *Suite) TestGetTransactionResultByIndexAndBlockIdEventEncodingVersio result, err := backend.GetTransactionResultByIndex(ctx, blockId, index, version) suite.checkResponse(result, err) - expectedResult := generator.GetEventsWithEncoding(1, version) - suite.Assert().Equal(result.Events, expectedResult) + var expectedResult []flow.Event + switch version { + case entitiesproto.EventEncodingVersion_CCF_V0: + expectedResult = append(expectedResult, ccfEvents...) + case entitiesproto.EventEncodingVersion_JSON_CDC_V0: + expectedResult = append(expectedResult, jsoncdcEvents...) + } + + suite.Assert().Equal(expectedResult, result.Events) }) suite.Run(fmt.Sprintf("test %s event encoding version for GetTransactionResultsByBlockID", version.String()), func() { @@ -2488,7 +2053,14 @@ func (suite *Suite) TestGetTransactionResultByIndexAndBlockIdEventEncodingVersio results, err := backend.GetTransactionResultsByBlockID(ctx, blockId, version) suite.checkResponse(results, err) - expectedResult := generator.GetEventsWithEncoding(1, version) + var expectedResult []flow.Event + switch version { + case entitiesproto.EventEncodingVersion_CCF_V0: + expectedResult = append(expectedResult, ccfEvents...) + case entitiesproto.EventEncodingVersion_JSON_CDC_V0: + expectedResult = append(expectedResult, jsoncdcEvents...) + } + for _, result := range results { suite.Assert().Equal(result.Events, expectedResult) } @@ -2592,6 +2164,17 @@ func getEvents(n int) []flow.Event { return events } +func generateEncodedEvents(t *testing.T, n int) ([]flow.Event, []flow.Event) { + ccfEvents := generator.GetEventsWithEncoding(n, entities.EventEncodingVersion_CCF_V0) + jsonEvents := make([]flow.Event, n) + for i, e := range ccfEvents { + jsonEvent, err := convert.CcfEventToJsonEvent(e) + require.NoError(t, err) + jsonEvents[i] = *jsonEvent + } + return ccfEvents, jsonEvents +} + func (suite *Suite) defaultBackendParams() Params { return Params{ State: suite.state, diff --git a/engine/access/rpc/backend/backend_transactions.go b/engine/access/rpc/backend/backend_transactions.go index bd20d419df9..6d0303d5ed8 100644 --- a/engine/access/rpc/backend/backend_transactions.go +++ b/engine/access/rpc/backend/backend_transactions.go @@ -91,7 +91,7 @@ func (b *backendTransactions) trySendTransaction(ctx context.Context, tx *flow.T } // otherwise choose all collection nodes to try - collNodes, err := b.chooseCollectionNodes(tx) + collNodes, err := b.chooseCollectionNodes(tx.ID()) if err != nil { return fmt.Errorf("failed to determine collection node for tx %x: %w", tx, err) } @@ -122,7 +122,7 @@ func (b *backendTransactions) trySendTransaction(ctx context.Context, tx *flow.T // chooseCollectionNodes finds a random subset of size sampleSize of collection node addresses from the // collection node cluster responsible for the given tx -func (b *backendTransactions) chooseCollectionNodes(tx *flow.TransactionBody) (flow.IdentityList, error) { +func (b *backendTransactions) chooseCollectionNodes(txID flow.Identifier) (flow.IdentityList, error) { // retrieve the set of collector clusters clusters, err := b.state.Final().Epochs().Current().Clustering() @@ -131,18 +131,20 @@ func (b *backendTransactions) chooseCollectionNodes(tx *flow.TransactionBody) (f } // get the cluster responsible for the transaction - targetNodes, ok := clusters.ByTxID(tx.ID()) + targetNodes, ok := clusters.ByTxID(txID) if !ok { - return nil, fmt.Errorf("could not get local cluster by txID: %x", tx.ID()) + return nil, fmt.Errorf("could not get local cluster by txID: %x", txID) } return targetNodes, nil } // sendTransactionToCollection sends the transaction to the given collection node via grpc -func (b *backendTransactions) sendTransactionToCollector(ctx context.Context, +func (b *backendTransactions) sendTransactionToCollector( + ctx context.Context, tx *flow.TransactionBody, - collectionNodeAddr string) error { + collectionNodeAddr string, +) error { collectionRPC, closer, err := b.connFactory.GetAccessAPIClient(collectionNodeAddr, nil) if err != nil { diff --git a/engine/access/rpc/backend/config.go b/engine/access/rpc/backend/config.go index fd4d54bcab9..2cdbe3bb8cc 100644 --- a/engine/access/rpc/backend/config.go +++ b/engine/access/rpc/backend/config.go @@ -17,52 +17,53 @@ type Config struct { FixedExecutionNodeIDs []string // fixed list of execution node IDs to choose from if no node ID can be chosen from the PreferredExecutionNodeIDs CircuitBreakerConfig connection.CircuitBreakerConfig // the configuration for circuit breaker ScriptExecutionMode string // the mode in which scripts are executed + EventQueryMode string // the mode in which events are queried } -type ScriptExecutionMode int +type IndexQueryMode int const ( - // ScriptExecutionModeLocalOnly executes scripts and gets accounts using only local storage - ScriptExecutionModeLocalOnly ScriptExecutionMode = iota + 1 + // IndexQueryModeLocalOnly executes scripts and gets accounts using only local storage + IndexQueryModeLocalOnly IndexQueryMode = iota + 1 - // ScriptExecutionModeExecutionNodesOnly executes scripts and gets accounts using only + // IndexQueryModeExecutionNodesOnly executes scripts and gets accounts using only // execution nodes - ScriptExecutionModeExecutionNodesOnly + IndexQueryModeExecutionNodesOnly - // ScriptExecutionModeFailover executes scripts and gets accounts using local storage first, + // IndexQueryModeFailover executes scripts and gets accounts using local storage first, // then falls back to execution nodes if data is not available for the height or if request // failed due to a non-user error. - ScriptExecutionModeFailover + IndexQueryModeFailover - // ScriptExecutionModeCompare executes scripts and gets accounts using both local storage and + // IndexQueryModeCompare executes scripts and gets accounts using both local storage and // execution nodes and compares the results. The execution node result is always returned. - ScriptExecutionModeCompare + IndexQueryModeCompare ) -func ParseScriptExecutionMode(s string) (ScriptExecutionMode, error) { +func ParseIndexQueryMode(s string) (IndexQueryMode, error) { switch s { - case ScriptExecutionModeLocalOnly.String(): - return ScriptExecutionModeLocalOnly, nil - case ScriptExecutionModeExecutionNodesOnly.String(): - return ScriptExecutionModeExecutionNodesOnly, nil - case ScriptExecutionModeFailover.String(): - return ScriptExecutionModeFailover, nil - case ScriptExecutionModeCompare.String(): - return ScriptExecutionModeCompare, nil + case IndexQueryModeLocalOnly.String(): + return IndexQueryModeLocalOnly, nil + case IndexQueryModeExecutionNodesOnly.String(): + return IndexQueryModeExecutionNodesOnly, nil + case IndexQueryModeFailover.String(): + return IndexQueryModeFailover, nil + case IndexQueryModeCompare.String(): + return IndexQueryModeCompare, nil default: return 0, errors.New("invalid script execution mode") } } -func (m ScriptExecutionMode) String() string { +func (m IndexQueryMode) String() string { switch m { - case ScriptExecutionModeLocalOnly: + case IndexQueryModeLocalOnly: return "local-only" - case ScriptExecutionModeExecutionNodesOnly: + case IndexQueryModeExecutionNodesOnly: return "execution-nodes-only" - case ScriptExecutionModeFailover: + case IndexQueryModeFailover: return "failover" - case ScriptExecutionModeCompare: + case IndexQueryModeCompare: return "compare" default: return "" diff --git a/engine/access/rpc/backend/script_comparer.go b/engine/access/rpc/backend/script_comparer.go index 4bd219c4c96..e7ec9f3c489 100644 --- a/engine/access/rpc/backend/script_comparer.go +++ b/engine/access/rpc/backend/script_comparer.go @@ -53,7 +53,7 @@ func (c *scriptResultComparison) compare(execResult, localResult *scriptResult) if isOutOfRangeError(localResult.err) { c.metrics.ScriptExecutionNotIndexed() c.logComparison(execResult, localResult, - "script execution results do not match EN because data is not indexed yet") + "script execution results do not match EN because data is not indexed yet", false) return false } @@ -66,7 +66,7 @@ func (c *scriptResultComparison) compare(execResult, localResult *scriptResult) c.metrics.ScriptExecutionErrorMismatch() c.logComparison(execResult, localResult, - "cadence errors from local execution do not match and EN") + "cadence errors from local execution do not match EN", true) return false } @@ -77,12 +77,12 @@ func (c *scriptResultComparison) compare(execResult, localResult *scriptResult) c.metrics.ScriptExecutionResultMismatch() c.logComparison(execResult, localResult, - "script execution results from local execution do not match EN") + "script execution results from local execution do not match EN", true) return false } // logScriptExecutionComparison logs the script execution comparison between local execution and execution node -func (c *scriptResultComparison) logComparison(execResult, localResult *scriptResult, msg string) { +func (c *scriptResultComparison) logComparison(execResult, localResult *scriptResult, msg string, useError bool) { args := make([]string, len(c.request.arguments)) for i, arg := range c.request.arguments { args[i] = string(arg) @@ -109,7 +109,11 @@ func (c *scriptResultComparison) logComparison(execResult, localResult *scriptRe lgCtx = lgCtx.Dur("local_duration_ms", localResult.duration) lg := lgCtx.Logger() - lg.Debug().Msg(msg) + if useError { + lg.Error().Msg(msg) + } else { + lg.Debug().Msg(msg) + } } func isOutOfRangeError(err error) bool { diff --git a/engine/access/rpc/backend/script_executor.go b/engine/access/rpc/backend/script_executor.go index 12d64a0daa9..f0ec0a85c27 100644 --- a/engine/access/rpc/backend/script_executor.go +++ b/engine/access/rpc/backend/script_executor.go @@ -2,8 +2,9 @@ package backend import ( "context" - "sync" + "fmt" + "github.com/rs/zerolog" "go.uber.org/atomic" "github.com/onflow/flow-go/model/flow" @@ -12,6 +13,8 @@ import ( ) type ScriptExecutor struct { + log zerolog.Logger + // scriptExecutor is used to interact with execution state scriptExecutor *execution.Scripts @@ -21,25 +24,51 @@ type ScriptExecutor struct { // initialized is used to signal that the index and executor are ready initialized *atomic.Bool - // init is used to ensure that the object is initialized only once - init sync.Once + // minCompatibleHeight and maxCompatibleHeight are used to limit the block range that can be queried using local execution + // to ensure only blocks that are compatible with the node's current software version are allowed. + // Note: this is a temporary solution for cadence/fvm upgrades while version beacon support is added + minCompatibleHeight *atomic.Uint64 + maxCompatibleHeight *atomic.Uint64 } -func NewScriptExecutor() *ScriptExecutor { +func NewScriptExecutor(log zerolog.Logger, minHeight, maxHeight uint64) *ScriptExecutor { + logger := log.With().Str("component", "script-executor").Logger() + logger.Info(). + Uint64("min_height", minHeight). + Uint64("max_height", maxHeight). + Msg("script executor created") + return &ScriptExecutor{ - initialized: atomic.NewBool(false), + log: logger, + initialized: atomic.NewBool(false), + minCompatibleHeight: atomic.NewUint64(minHeight), + maxCompatibleHeight: atomic.NewUint64(maxHeight), } } +// SetMinCompatibleHeight sets the lowest block height (inclusive) that can be queried using local execution +// Use this to limit the executable block range supported by the node's current software version. +func (s *ScriptExecutor) SetMinCompatibleHeight(height uint64) { + s.minCompatibleHeight.Store(height) + s.log.Info().Uint64("height", height).Msg("minimum compatible height set") +} + +// SetMaxCompatibleHeight sets the highest block height (inclusive) that can be queried using local execution +// Use this to limit the executable block range supported by the node's current software version. +func (s *ScriptExecutor) SetMaxCompatibleHeight(height uint64) { + s.maxCompatibleHeight.Store(height) + s.log.Info().Uint64("height", height).Msg("maximum compatible height set") +} + // InitReporter initializes the indexReporter and script executor // This method can be called at any time after the ScriptExecutor object is created. Any requests // made to the other methods will return execution.ErrDataNotAvailable until this method is called. func (s *ScriptExecutor) InitReporter(indexReporter state_synchronization.IndexReporter, scriptExecutor *execution.Scripts) { - s.init.Do(func() { - defer s.initialized.Store(true) + if s.initialized.CompareAndSwap(false, true) { + s.log.Info().Msg("script executor initialized") s.indexReporter = indexReporter s.scriptExecutor = scriptExecutor - }) + } } // ExecuteAtBlockHeight executes provided script at the provided block height against a local execution state. @@ -49,8 +78,8 @@ func (s *ScriptExecutor) InitReporter(indexReporter state_synchronization.IndexR // - execution.ErrDataNotAvailable if the data for the block height is not available. this could be because // the height is not within the index block range, or the index is not ready. func (s *ScriptExecutor) ExecuteAtBlockHeight(ctx context.Context, script []byte, arguments [][]byte, height uint64) ([]byte, error) { - if !s.isDataAvailable(height) { - return nil, execution.ErrDataNotAvailable + if err := s.checkDataAvailable(height); err != nil { + return nil, err } return s.scriptExecutor.ExecuteAtBlockHeight(ctx, script, arguments, height) @@ -63,13 +92,29 @@ func (s *ScriptExecutor) ExecuteAtBlockHeight(ctx context.Context, script []byte // - execution.ErrDataNotAvailable if the data for the block height is not available. this could be because // the height is not within the index block range, or the index is not ready. func (s *ScriptExecutor) GetAccountAtBlockHeight(ctx context.Context, address flow.Address, height uint64) (*flow.Account, error) { - if !s.isDataAvailable(height) { - return nil, execution.ErrDataNotAvailable + if err := s.checkDataAvailable(height); err != nil { + return nil, err } return s.scriptExecutor.GetAccountAtBlockHeight(ctx, address, height) } -func (s *ScriptExecutor) isDataAvailable(height uint64) bool { - return s.initialized.Load() && height <= s.indexReporter.HighestIndexedHeight() && height >= s.indexReporter.LowestIndexedHeight() +func (s *ScriptExecutor) checkDataAvailable(height uint64) error { + if !s.initialized.Load() { + return fmt.Errorf("%w: script executor not initialized", execution.ErrDataNotAvailable) + } + + if height > s.indexReporter.HighestIndexedHeight() { + return fmt.Errorf("%w: block not indexed yet", execution.ErrDataNotAvailable) + } + + if height < s.indexReporter.LowestIndexedHeight() { + return fmt.Errorf("%w: block is before lowest indexed height", execution.ErrDataNotAvailable) + } + + if height > s.maxCompatibleHeight.Load() || height < s.minCompatibleHeight.Load() { + return fmt.Errorf("%w: node software is not compatible with version required to executed block", execution.ErrDataNotAvailable) + } + + return nil } diff --git a/engine/access/rpc/connection/connection.go b/engine/access/rpc/connection/connection.go index aeea158e292..161aa2949d2 100644 --- a/engine/access/rpc/connection/connection.go +++ b/engine/access/rpc/connection/connection.go @@ -6,13 +6,12 @@ import ( "net" "time" + "github.com/onflow/crypto" + "github.com/onflow/flow/protobuf/go/flow/access" + "github.com/onflow/flow/protobuf/go/flow/execution" "github.com/rs/zerolog" - "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/module" - - "github.com/onflow/flow/protobuf/go/flow/access" - "github.com/onflow/flow/protobuf/go/flow/execution" ) // ConnectionFactory is an interface for creating access and execution API clients. diff --git a/engine/access/rpc/connection/manager.go b/engine/access/rpc/connection/manager.go index 3bb8ffc7286..add02afb4ca 100644 --- a/engine/access/rpc/connection/manager.go +++ b/engine/access/rpc/connection/manager.go @@ -6,6 +6,7 @@ import ( "io" "time" + "github.com/onflow/crypto" "github.com/rs/zerolog" "github.com/sony/gobreaker" "google.golang.org/grpc" @@ -17,7 +18,6 @@ import ( "google.golang.org/grpc/keepalive" "google.golang.org/grpc/status" - "github.com/onflow/flow-go/crypto" _ "github.com/onflow/flow-go/engine/common/grpc/compressor/deflate" //required for gRPC compression _ "github.com/onflow/flow-go/engine/common/grpc/compressor/snappy" //required for gRPC compression "github.com/onflow/flow-go/module" diff --git a/engine/access/rpc/connection/mock/connection_factory.go b/engine/access/rpc/connection/mock/connection_factory.go index 32da8057be2..6d127598024 100644 --- a/engine/access/rpc/connection/mock/connection_factory.go +++ b/engine/access/rpc/connection/mock/connection_factory.go @@ -5,7 +5,7 @@ package mock import ( access "github.com/onflow/flow/protobuf/go/flow/access" - crypto "github.com/onflow/flow-go/crypto" + crypto "github.com/onflow/crypto" execution "github.com/onflow/flow/protobuf/go/flow/execution" diff --git a/engine/access/secure_grpcr_test.go b/engine/access/secure_grpcr_test.go index 55d0dbb441c..3900a151ebd 100644 --- a/engine/access/secure_grpcr_test.go +++ b/engine/access/secure_grpcr_test.go @@ -16,7 +16,8 @@ import ( "google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials/insecure" - "github.com/onflow/flow-go/crypto" + "github.com/onflow/crypto" + accessmock "github.com/onflow/flow-go/engine/access/mock" "github.com/onflow/flow-go/engine/access/rpc" "github.com/onflow/flow-go/engine/access/rpc/backend" diff --git a/engine/access/state_stream/backend/backend.go b/engine/access/state_stream/backend/backend.go index 185afc32666..f2a35ebe97d 100644 --- a/engine/access/state_stream/backend/backend.go +++ b/engine/access/state_stream/backend/backend.go @@ -90,6 +90,7 @@ func New( config Config, state protocol.State, headers storage.Headers, + events storage.Events, seals storage.Seals, results storage.ExecutionResults, execDataStore execution_data.ExecutionDataStore, @@ -98,6 +99,7 @@ func New( rootHeight uint64, highestAvailableHeight uint64, registers *execution.RegistersAsyncStore, + useEventsIndex bool, ) (*StateStreamBackend, error) { logger := log.With().Str("module", "state_stream_api").Logger() @@ -136,12 +138,15 @@ func New( b.EventsBackend = EventsBackend{ log: logger, + events: events, + headers: headers, broadcaster: broadcaster, sendTimeout: config.ClientSendTimeout, responseLimit: config.ResponseLimit, sendBufferSize: int(config.ClientSendBufferSize), getExecutionData: b.getExecutionData, getStartHeight: b.getStartHeight, + useIndex: useEventsIndex, } return b, nil @@ -228,12 +233,17 @@ func (b *StateStreamBackend) GetRegisterValues(ids flow.RegisterIDs, height uint if len(ids) > b.registerRequestLimit { return nil, status.Errorf(codes.InvalidArgument, "number of register IDs exceeds limit of %d", b.registerRequestLimit) } + values, err := b.registers.RegisterValues(ids, height) - if errors.Is(err, storage.ErrHeightNotIndexed) { - return nil, status.Errorf(codes.OutOfRange, "register values for block %d is not available", height) - } - if errors.Is(err, storage.ErrNotFound) { - return nil, status.Errorf(codes.NotFound, "register values for block %d is not available", height) + if err != nil { + if errors.Is(err, storage.ErrHeightNotIndexed) { + return nil, status.Errorf(codes.OutOfRange, "register values for block %d is not available", height) + } + if errors.Is(err, storage.ErrNotFound) { + return nil, status.Errorf(codes.NotFound, "register values for block %d not found", height) + } + return nil, err } - return values, err + + return values, nil } diff --git a/engine/access/state_stream/backend/backend_events.go b/engine/access/state_stream/backend/backend_events.go index ac1297f0e0f..303f8e09e32 100644 --- a/engine/access/state_stream/backend/backend_events.go +++ b/engine/access/state_stream/backend/backend_events.go @@ -10,6 +10,7 @@ import ( "github.com/onflow/flow-go/engine" "github.com/onflow/flow-go/engine/access/state_stream" "github.com/onflow/flow-go/model/flow" + "github.com/onflow/flow-go/storage" "github.com/onflow/flow-go/utils/logging" ) @@ -21,6 +22,8 @@ type EventsResponse struct { type EventsBackend struct { log zerolog.Logger + events storage.Events + headers storage.Headers broadcaster *engine.Broadcaster sendTimeout time.Duration responseLimit float64 @@ -28,6 +31,8 @@ type EventsBackend struct { getExecutionData GetExecutionDataFunc getStartHeight GetStartHeightFunc + + useIndex bool } func (b EventsBackend) SubscribeEvents(ctx context.Context, startBlockID flow.Identifier, startHeight uint64, filter state_stream.EventFilter) state_stream.Subscription { @@ -43,27 +48,61 @@ func (b EventsBackend) SubscribeEvents(ctx context.Context, startBlockID flow.Id return sub } +// getResponseFactory returns a function function that returns the event response for a given height. func (b EventsBackend) getResponseFactory(filter state_stream.EventFilter) GetDataByHeightFunc { - return func(ctx context.Context, height uint64) (interface{}, error) { - executionData, err := b.getExecutionData(ctx, height) - if err != nil { - return nil, fmt.Errorf("could not get execution data for block %d: %w", height, err) + return func(ctx context.Context, height uint64) (response interface{}, err error) { + if b.useIndex { + response, err = b.getEventsFromStorage(height, filter) + } else { + response, err = b.getEventsFromExecutionData(ctx, height, filter) } - events := []flow.Event{} - for _, chunkExecutionData := range executionData.ChunkExecutionDatas { - events = append(events, filter.Filter(chunkExecutionData.Events)...) + if err == nil && b.log.GetLevel() == zerolog.TraceLevel { + eventsResponse := response.(*EventsResponse) + b.log.Trace(). + Hex("block_id", logging.ID(eventsResponse.BlockID)). + Uint64("height", height). + Int("events", len(eventsResponse.Events)). + Msg("sending events") } + return + } +} + +// getEventsFromExecutionData returns the events for a given height extractd from the execution data. +func (b EventsBackend) getEventsFromExecutionData(ctx context.Context, height uint64, filter state_stream.EventFilter) (*EventsResponse, error) { + executionData, err := b.getExecutionData(ctx, height) + if err != nil { + return nil, fmt.Errorf("could not get execution data for block %d: %w", height, err) + } + + var events flow.EventsList + for _, chunkExecutionData := range executionData.ChunkExecutionDatas { + events = append(events, filter.Filter(chunkExecutionData.Events)...) + } - b.log.Trace(). - Hex("block_id", logging.ID(executionData.BlockID)). - Uint64("height", height). - Msgf("sending %d events", len(events)) + return &EventsResponse{ + BlockID: executionData.BlockID, + Height: height, + Events: events, + }, nil +} - return &EventsResponse{ - BlockID: executionData.BlockID, - Height: height, - Events: events, - }, nil +// getEventsFromStorage returns the events for a given height from the index storage. +func (b EventsBackend) getEventsFromStorage(height uint64, filter state_stream.EventFilter) (*EventsResponse, error) { + blockID, err := b.headers.BlockIDByHeight(height) + if err != nil { + return nil, fmt.Errorf("could not get header for height %d: %w", height, err) } + + events, err := b.events.ByBlockID(blockID) + if err != nil { + return nil, fmt.Errorf("could not get events for block %d: %w", height, err) + } + + return &EventsResponse{ + BlockID: blockID, + Height: height, + Events: filter.Filter(events), + }, nil } diff --git a/engine/access/state_stream/backend/backend_events_test.go b/engine/access/state_stream/backend/backend_events_test.go index c9a093ebe82..d01fbbad781 100644 --- a/engine/access/state_stream/backend/backend_events_test.go +++ b/engine/access/state_stream/backend/backend_events_test.go @@ -7,6 +7,7 @@ import ( "time" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" "google.golang.org/grpc/codes" @@ -15,6 +16,7 @@ import ( "github.com/onflow/flow-go/engine/access/state_stream" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/utils/unittest" + "github.com/onflow/flow-go/utils/unittest/mocks" ) type BackendEventsSuite struct { @@ -29,8 +31,24 @@ func (s *BackendEventsSuite) SetupTest() { s.BackendExecutionDataSuite.SetupTest() } -// TestSubscribeEvents tests the SubscribeEvents method happy path -func (s *BackendEventsSuite) TestSubscribeEvents() { +// TestSubscribeEventsFromExecutionData tests the SubscribeEvents method happy path for events +// extracted from ExecutionData +func (s *BackendEventsSuite) TestSubscribeEventsFromExecutionData() { + s.runTestSubscribeEvents() +} + +// TestSubscribeEventsFromLocalStorage tests the SubscribeEvents method happy path for events +// extracted from local storage +func (s *BackendEventsSuite) TestSubscribeEventsFromLocalStorage() { + s.backend.useIndex = true + s.events.On("ByBlockID", mock.AnythingOfType("flow.Identifier")).Return( + mocks.StorageMapGetter(s.blockEvents), + ) + + s.runTestSubscribeEvents() +} + +func (s *BackendEventsSuite) runTestSubscribeEvents() { ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -77,9 +95,6 @@ func (s *BackendEventsSuite) TestSubscribeEvents() { }, } - // supports simple address comparisions for testing - chain := flow.MonotonicEmulator.Chain() - // create variations for each of the base test tests := make([]testType, 0, len(baseTests)*3) for _, test := range baseTests { @@ -90,13 +105,13 @@ func (s *BackendEventsSuite) TestSubscribeEvents() { t2 := test t2.name = fmt.Sprintf("%s - some events", test.name) - t2.filters, err = state_stream.NewEventFilter(state_stream.DefaultEventFilterConfig, chain, []string{string(testEventTypes[0])}, nil, nil) + t2.filters, err = state_stream.NewEventFilter(state_stream.DefaultEventFilterConfig, chainID.Chain(), []string{string(testEventTypes[0])}, nil, nil) require.NoError(s.T(), err) tests = append(tests, t2) t3 := test t3.name = fmt.Sprintf("%s - no events", test.name) - t3.filters, err = state_stream.NewEventFilter(state_stream.DefaultEventFilterConfig, chain, []string{"A.0x1.NonExistent.Event"}, nil, nil) + t3.filters, err = state_stream.NewEventFilter(state_stream.DefaultEventFilterConfig, chainID.Chain(), []string{"A.0x1.NonExistent.Event"}, nil, nil) require.NoError(s.T(), err) tests = append(tests, t3) } @@ -126,7 +141,7 @@ func (s *BackendEventsSuite) TestSubscribeEvents() { s.broadcaster.Publish() } - expectedEvents := flow.EventsList{} + var expectedEvents flow.EventsList for _, event := range s.blockEvents[b.ID()] { if test.filters.Match(event) { expectedEvents = append(expectedEvents, event) diff --git a/engine/access/state_stream/backend/backend_executiondata_test.go b/engine/access/state_stream/backend/backend_executiondata_test.go index 51a9da291f3..5b3ba2d5a4b 100644 --- a/engine/access/state_stream/backend/backend_executiondata_test.go +++ b/engine/access/state_stream/backend/backend_executiondata_test.go @@ -28,12 +28,14 @@ import ( "github.com/onflow/flow-go/storage" storagemock "github.com/onflow/flow-go/storage/mock" "github.com/onflow/flow-go/utils/unittest" + "github.com/onflow/flow-go/utils/unittest/mocks" ) +var chainID = flow.MonotonicEmulator var testEventTypes = []flow.EventType{ - "A.0x1.Foo.Bar", - "A.0x2.Zoo.Moo", - "A.0x3.Goo.Hoo", + unittest.EventTypeFixture(chainID), + unittest.EventTypeFixture(chainID), + unittest.EventTypeFixture(chainID), } type BackendExecutionDataSuite struct { @@ -43,6 +45,7 @@ type BackendExecutionDataSuite struct { params *protocolmock.Params snapshot *protocolmock.Snapshot headers *storagemock.Headers + events *storagemock.Events seals *storagemock.Seals results *storagemock.ExecutionResults registers *storagemock.RegisterIndex @@ -56,11 +59,12 @@ type BackendExecutionDataSuite struct { backend *StateStreamBackend blocks []*flow.Block - blockEvents map[flow.Identifier]flow.EventsList + blockEvents map[flow.Identifier][]flow.Event execDataMap map[flow.Identifier]*execution_data.BlockExecutionDataEntity blockMap map[uint64]*flow.Block sealMap map[flow.Identifier]*flow.Seal resultMap map[flow.Identifier]*flow.ExecutionResult + registerID flow.RegisterID } func TestBackendExecutionDataSuite(t *testing.T) { @@ -74,6 +78,7 @@ func (s *BackendExecutionDataSuite) SetupTest() { s.snapshot = protocolmock.NewSnapshot(s.T()) s.params = protocolmock.NewParams(s.T()) s.headers = storagemock.NewHeaders(s.T()) + s.events = storagemock.NewEvents(s.T()) s.seals = storagemock.NewSeals(s.T()) s.results = storagemock.NewExecutionResults(s.T()) @@ -95,7 +100,7 @@ func (s *BackendExecutionDataSuite) SetupTest() { blockCount := 5 s.execDataMap = make(map[flow.Identifier]*execution_data.BlockExecutionDataEntity, blockCount) - s.blockEvents = make(map[flow.Identifier]flow.EventsList, blockCount) + s.blockEvents = make(map[flow.Identifier][]flow.Event, blockCount) s.blockMap = make(map[uint64]*flow.Block, blockCount) s.sealMap = make(map[flow.Identifier]*flow.Seal, blockCount) s.resultMap = make(map[flow.Identifier]*flow.ExecutionResult, blockCount) @@ -149,6 +154,8 @@ func (s *BackendExecutionDataSuite) SetupTest() { s.T().Logf("adding exec data for block %d %d %v => %v", i, block.Header.Height, block.ID(), result.ExecutionDataID) } + s.registerID = unittest.RegisterIDFixture() + s.registersAsync = execution.NewRegistersAsyncStore() s.registers = storagemock.NewRegisterIndex(s.T()) err = s.registersAsync.InitDataAvailable(s.registers) @@ -157,7 +164,7 @@ func (s *BackendExecutionDataSuite) SetupTest() { s.registers.On("FirstHeight").Return(rootBlock.Header.Height).Maybe() s.registers.On("Get", mock.AnythingOfType("RegisterID"), mock.AnythingOfType("uint64")).Return( func(id flow.RegisterID, height uint64) (flow.RegisterValue, error) { - if id == unittest.RegisterIDFixture() { + if id == s.registerID { return flow.RegisterValue{}, nil } return nil, storage.ErrNotFound @@ -167,82 +174,36 @@ func (s *BackendExecutionDataSuite) SetupTest() { s.snapshot.On("Head").Return(s.blocks[0].Header, nil).Maybe() s.seals.On("FinalizedSealForBlock", mock.AnythingOfType("flow.Identifier")).Return( - func(blockID flow.Identifier) *flow.Seal { - if seal, ok := s.sealMap[blockID]; ok { - return seal - } - return nil - }, - func(blockID flow.Identifier) error { - if _, ok := s.sealMap[blockID]; ok { - return nil - } - return storage.ErrNotFound - }, + mocks.StorageMapGetter(s.sealMap), ).Maybe() s.results.On("ByID", mock.AnythingOfType("flow.Identifier")).Return( - func(resultID flow.Identifier) *flow.ExecutionResult { - if result, ok := s.resultMap[resultID]; ok { - return result - } - return nil - }, - func(resultID flow.Identifier) error { - if _, ok := s.resultMap[resultID]; ok { - return nil - } - return storage.ErrNotFound - }, + mocks.StorageMapGetter(s.resultMap), ).Maybe() s.headers.On("ByBlockID", mock.AnythingOfType("flow.Identifier")).Return( - func(blockID flow.Identifier) *flow.Header { + func(blockID flow.Identifier) (*flow.Header, error) { for _, block := range s.blockMap { if block.ID() == blockID { - return block.Header + return block.Header, nil } } - return nil - }, - func(blockID flow.Identifier) error { - for _, block := range s.blockMap { - if block.ID() == blockID { - return nil - } - } - return storage.ErrNotFound + return nil, storage.ErrNotFound }, ).Maybe() s.headers.On("ByHeight", mock.AnythingOfType("uint64")).Return( - func(height uint64) *flow.Header { - if block, ok := s.blockMap[height]; ok { - return block.Header - } - return nil - }, - func(height uint64) error { - if _, ok := s.blockMap[height]; ok { - return nil - } - return storage.ErrNotFound - }, + mocks.ConvertStorageOutput( + mocks.StorageMapGetter(s.blockMap), + func(block *flow.Block) *flow.Header { return block.Header }, + ), ).Maybe() s.headers.On("BlockIDByHeight", mock.AnythingOfType("uint64")).Return( - func(height uint64) flow.Identifier { - if block, ok := s.blockMap[height]; ok { - return block.Header.ID() - } - return flow.ZeroID - }, - func(height uint64) error { - if _, ok := s.blockMap[height]; ok { - return nil - } - return storage.ErrNotFound - }, + mocks.ConvertStorageOutput( + mocks.StorageMapGetter(s.blockMap), + func(block *flow.Block) flow.Identifier { return block.ID() }, + ), ).Maybe() s.backend, err = New( @@ -250,6 +211,7 @@ func (s *BackendExecutionDataSuite) SetupTest() { conf, s.state, s.headers, + s.events, s.seals, s.results, s.eds, @@ -258,6 +220,7 @@ func (s *BackendExecutionDataSuite) SetupTest() { rootBlock.Header.Height, rootBlock.Header.Height, // initialize with no downloaded data s.registersAsync, + false, ) require.NoError(s.T(), err) } @@ -438,15 +401,15 @@ func (s *BackendExecutionDataSuite) TestSubscribeExecutionDataHandlesErrors() { }) } -func (s *BackendExecutionDataSuite) TestGetRegisterValuesErrors() { +func (s *BackendExecutionDataSuite) TestGetRegisterValues() { s.Run("normal case", func() { - res, err := s.backend.GetRegisterValues(flow.RegisterIDs{unittest.RegisterIDFixture()}, s.backend.rootBlockHeight) + res, err := s.backend.GetRegisterValues(flow.RegisterIDs{s.registerID}, s.backend.rootBlockHeight) require.NoError(s.T(), err) require.NotEmpty(s.T(), res) }) s.Run("returns error if block height is out of range", func() { - _, err := s.backend.GetRegisterValues(flow.RegisterIDs{unittest.RegisterIDFixture()}, s.backend.rootBlockHeight+1) + _, err := s.backend.GetRegisterValues(flow.RegisterIDs{s.registerID}, s.backend.rootBlockHeight+1) require.Equal(s.T(), codes.OutOfRange, status.Code(err)) }) diff --git a/engine/access/state_stream/backend/handler.go b/engine/access/state_stream/backend/handler.go index 9537ce94d4c..6ed22589562 100644 --- a/engine/access/state_stream/backend/handler.go +++ b/engine/access/state_stream/backend/handler.go @@ -202,14 +202,16 @@ func (h *Handler) SubscribeEvents(request *executiondata.SubscribeEventsRequest, func (h *Handler) GetRegisterValues(_ context.Context, request *executiondata.GetRegisterValuesRequest) (*executiondata.GetRegisterValuesResponse, error) { // Convert data - registerIDs, err := convert.MessagesToRegisterIDs(request.GetRegisterIds()) + registerIDs, err := convert.MessagesToRegisterIDs(request.GetRegisterIds(), h.chain) if err != nil { return nil, status.Errorf(codes.InvalidArgument, "could not convert register IDs: %v", err) } + // get payload from store values, err := h.api.GetRegisterValues(registerIDs, request.GetBlockHeight()) if err != nil { return nil, rpc.ConvertError(err, "could not get register values", codes.Internal) } + return &executiondata.GetRegisterValuesResponse{Values: values}, nil } diff --git a/engine/access/state_stream/backend/handler_test.go b/engine/access/state_stream/backend/handler_test.go index a28f58d147d..3cf9d656f8a 100644 --- a/engine/access/state_stream/backend/handler_test.go +++ b/engine/access/state_stream/backend/handler_test.go @@ -198,8 +198,7 @@ func TestGetExecutionDataByBlockID(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - ccfEvents := generator.GetEventsWithEncoding(3, entities.EventEncodingVersion_CCF_V0) - jsonEvents := generator.GetEventsWithEncoding(3, entities.EventEncodingVersion_JSON_CDC_V0) + ccfEvents, jsonEvents := generateEvents(t, 3) tests := []struct { eventVersion entities.EventEncodingVersion @@ -345,8 +344,7 @@ func TestExecutionDataStream(t *testing.T) { } } - ccfEvents := generator.GetEventsWithEncoding(3, entities.EventEncodingVersion_CCF_V0) - jsonEvents := generator.GetEventsWithEncoding(3, entities.EventEncodingVersion_JSON_CDC_V0) + ccfEvents, jsonEvents := generateEvents(t, 3) tests := []struct { eventVersion entities.EventEncodingVersion @@ -472,9 +470,7 @@ func TestEventStream(t *testing.T) { } // generate events with a payload to include - // generators will produce identical event payloads (before encoding) - ccfEvents := generator.GetEventsWithEncoding(3, entities.EventEncodingVersion_CCF_V0) - jsonEvents := generator.GetEventsWithEncoding(3, entities.EventEncodingVersion_JSON_CDC_V0) + ccfEvents, jsonEvents := generateEvents(t, 3) tests := []struct { eventVersion entities.EventEncodingVersion @@ -514,6 +510,10 @@ func TestEventStream(t *testing.T) { // TestGetRegisterValues tests the register values. func TestGetRegisterValues(t *testing.T) { t.Parallel() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + testHeight := uint64(1) // test register IDs + values @@ -531,76 +531,87 @@ func TestGetRegisterValues(t *testing.T) { t.Run("invalid message", func(t *testing.T) { api := ssmock.NewAPI(t) - h := NewHandler(api, flow.Localnet.Chain(), makeConfig(1)) - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() + h := NewHandler(api, flow.Testnet.Chain(), makeConfig(1)) + invalidMessage := &executiondata.GetRegisterValuesRequest{ RegisterIds: nil, } _, err := h.GetRegisterValues(ctx, invalidMessage) - require.Equal(t, status.Code(err), codes.InvalidArgument) + require.Equal(t, codes.InvalidArgument, status.Code(err)) }) t.Run("valid registers", func(t *testing.T) { api := ssmock.NewAPI(t) api.On("GetRegisterValues", testIds, testHeight).Return(testValues, nil) - h := NewHandler(api, flow.Localnet.Chain(), makeConfig(1)) + h := NewHandler(api, flow.Testnet.Chain(), makeConfig(1)) + validRegisters := make([]*entities.RegisterID, len(testIds)) for i, id := range testIds { validRegisters[i] = convert.RegisterIDToMessage(id) } + req := &executiondata.GetRegisterValuesRequest{ RegisterIds: validRegisters, BlockHeight: testHeight, } - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() + resp, err := h.GetRegisterValues(ctx, req) require.NoError(t, err) - require.Equal(t, resp.GetValues(), testValues) - + require.Equal(t, testValues, resp.GetValues()) }) t.Run("unavailable registers", func(t *testing.T) { api := ssmock.NewAPI(t) expectedErr := status.Errorf(codes.NotFound, "could not get register values: %v", storage.ErrNotFound) api.On("GetRegisterValues", invalidIDs, testHeight).Return(nil, expectedErr) - h := NewHandler(api, flow.Localnet.Chain(), makeConfig(1)) + h := NewHandler(api, flow.Testnet.Chain(), makeConfig(1)) + unavailableRegisters := make([]*entities.RegisterID, len(invalidIDs)) for i, id := range invalidIDs { unavailableRegisters[i] = convert.RegisterIDToMessage(id) } + req := &executiondata.GetRegisterValuesRequest{ RegisterIds: unavailableRegisters, BlockHeight: testHeight, } - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - _, err := h.GetRegisterValues(ctx, req) - require.Equal(t, status.Code(err), codes.NotFound) + _, err := h.GetRegisterValues(ctx, req) + require.Equal(t, codes.NotFound, status.Code(err)) }) t.Run("wrong height", func(t *testing.T) { api := ssmock.NewAPI(t) expectedErr := status.Errorf(codes.OutOfRange, "could not get register values: %v", storage.ErrHeightNotIndexed) api.On("GetRegisterValues", testIds, testHeight+1).Return(nil, expectedErr) - h := NewHandler(api, flow.Localnet.Chain(), makeConfig(1)) + h := NewHandler(api, flow.Testnet.Chain(), makeConfig(1)) + validRegisters := make([]*entities.RegisterID, len(testIds)) for i, id := range testIds { validRegisters[i] = convert.RegisterIDToMessage(id) } + req := &executiondata.GetRegisterValuesRequest{ RegisterIds: validRegisters, BlockHeight: testHeight + 1, } - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() + _, err := h.GetRegisterValues(ctx, req) - require.Equal(t, status.Code(err), codes.OutOfRange) + require.Equal(t, codes.OutOfRange, status.Code(err)) }) } +func generateEvents(t *testing.T, n int) ([]flow.Event, []flow.Event) { + ccfEvents := generator.GetEventsWithEncoding(n, entities.EventEncodingVersion_CCF_V0) + jsonEvents := make([]flow.Event, len(ccfEvents)) + for i, e := range ccfEvents { + jsonEvent, err := convert.CcfEventToJsonEvent(e) + require.NoError(t, err) + jsonEvents[i] = *jsonEvent + } + return ccfEvents, jsonEvents +} + func makeConfig(maxGlobalStreams uint32) Config { return Config{ EventFilterConfig: state_stream.DefaultEventFilterConfig, diff --git a/engine/access/state_stream/event_test.go b/engine/access/state_stream/event_test.go deleted file mode 100644 index 3dbccd34406..00000000000 --- a/engine/access/state_stream/event_test.go +++ /dev/null @@ -1,79 +0,0 @@ -package state_stream_test - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/onflow/flow-go/engine/access/state_stream" - "github.com/onflow/flow-go/model/flow" -) - -func TestParseEvent(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - eventType flow.EventType - expected state_stream.ParsedEvent - }{ - { - name: "flow event", - eventType: "flow.AccountCreated", - expected: state_stream.ParsedEvent{ - Type: state_stream.ProtocolEventType, - EventType: "flow.AccountCreated", - Contract: "flow", - ContractName: "flow", - Name: "AccountCreated", - }, - }, - { - name: "account event", - eventType: "A.0000000000000001.Contract1.EventA", - expected: state_stream.ParsedEvent{ - Type: state_stream.AccountEventType, - EventType: "A.0000000000000001.Contract1.EventA", - Address: "0000000000000001", - Contract: "A.0000000000000001.Contract1", - ContractName: "Contract1", - Name: "EventA", - }, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - event, err := state_stream.ParseEvent(test.eventType) - require.NoError(t, err) - - assert.Equal(t, test.expected.Type, event.Type) - assert.Equal(t, test.expected.EventType, event.EventType) - assert.Equal(t, test.expected.Address, event.Address) - assert.Equal(t, test.expected.Contract, event.Contract) - assert.Equal(t, test.expected.Name, event.Name) - }) - } -} - -func TestParseEvent_Invalid(t *testing.T) { - t.Parallel() - - eventTypes := []flow.EventType{ - "", // not enough parts - "invalid", // not enough parts - "invalid.event", // invalid first part - "B.0000000000000001.invalid.event", // invalid first part - "flow", // incorrect number of parts for protocol event - "flow.invalid.event", // incorrect number of parts for protocol event - "A.0000000000000001.invalid", // incorrect number of parts for account event - "A.0000000000000001.invalid.a.b", // incorrect number of parts for account event - - } - - for _, eventType := range eventTypes { - _, err := state_stream.ParseEvent(eventType) - assert.Error(t, err, "expected error for event type: %s", eventType) - } -} diff --git a/engine/access/state_stream/filter.go b/engine/access/state_stream/filter.go index ab90b98240c..8936ba49e0a 100644 --- a/engine/access/state_stream/filter.go +++ b/engine/access/state_stream/filter.go @@ -4,6 +4,7 @@ import ( "fmt" "strings" + "github.com/onflow/flow-go/model/events" "github.com/onflow/flow-go/model/flow" ) @@ -71,7 +72,7 @@ func NewEventFilter( // with criteria that will never match. for _, event := range eventTypes { eventType := flow.EventType(event) - if err := validateEventType(eventType); err != nil { + if err := validateEventType(eventType, chain); err != nil { return EventFilter{}, err } f.EventTypes[eventType] = struct{}{} @@ -120,7 +121,7 @@ func (f *EventFilter) Match(event flow.Event) bool { return true } - parsed, err := ParseEvent(event.Type) + parsed, err := events.ParseEvent(event.Type) if err != nil { // TODO: log this error return false @@ -130,7 +131,7 @@ func (f *EventFilter) Match(event flow.Event) bool { return true } - if parsed.Type == AccountEventType { + if parsed.Type == events.AccountEventType { _, ok := f.Addresses[parsed.Address] return ok } @@ -139,8 +140,8 @@ func (f *EventFilter) Match(event flow.Event) bool { } // validateEventType ensures that the event type matches the expected format -func validateEventType(eventType flow.EventType) error { - _, err := ParseEvent(flow.EventType(eventType)) +func validateEventType(eventType flow.EventType, chain flow.Chain) error { + _, err := events.ValidateEvent(flow.EventType(eventType), chain) if err != nil { return fmt.Errorf("invalid event type %s: %w", eventType, err) } diff --git a/engine/common/follower/cache/cache_test.go b/engine/common/follower/cache/cache_test.go index d5c42f5ea80..b94a7cc31ce 100644 --- a/engine/common/follower/cache/cache_test.go +++ b/engine/common/follower/cache/cache_test.go @@ -239,8 +239,8 @@ func (s *CacheSuite) TestConcurrentAdd() { unittest.RequireReturnsBefore(s.T(), wg.Wait, time.Millisecond*500, "should submit blocks before timeout") require.Len(s.T(), allCertifiedBlocks, len(blocks)-1) - slices.SortFunc(allCertifiedBlocks, func(lhs *flow.Block, rhs *flow.Block) bool { - return lhs.Header.Height < rhs.Header.Height + slices.SortFunc(allCertifiedBlocks, func(lhs *flow.Block, rhs *flow.Block) int { + return int(lhs.Header.Height) - int(rhs.Header.Height) }) require.Equal(s.T(), blocks[:len(blocks)-1], allCertifiedBlocks) } diff --git a/engine/common/follower/pending_tree/pending_tree_test.go b/engine/common/follower/pending_tree/pending_tree_test.go index ac482871aa4..66b53058b23 100644 --- a/engine/common/follower/pending_tree/pending_tree_test.go +++ b/engine/common/follower/pending_tree/pending_tree_test.go @@ -154,8 +154,8 @@ func (s *PendingTreeSuite) TestBatchWithSkipsAndInRandomOrder() { require.NoError(s.T(), err) // restore view based order since that's what we will get from PendingTree - slices.SortFunc(blocks, func(lhs flow.CertifiedBlock, rhs flow.CertifiedBlock) bool { - return lhs.View() < rhs.View() + slices.SortFunc(blocks, func(lhs flow.CertifiedBlock, rhs flow.CertifiedBlock) int { + return int(lhs.View()) - int(rhs.View()) }) assert.Equal(s.T(), blocks, connectedBlocks) diff --git a/engine/common/rpc/convert/accounts.go b/engine/common/rpc/convert/accounts.go index 0440d3c0685..9396dc6ff49 100644 --- a/engine/common/rpc/convert/accounts.go +++ b/engine/common/rpc/convert/accounts.go @@ -1,10 +1,10 @@ package convert import ( + "github.com/onflow/crypto" + "github.com/onflow/crypto/hash" "github.com/onflow/flow/protobuf/go/flow/entities" - "github.com/onflow/flow-go/crypto" - "github.com/onflow/flow-go/crypto/hash" "github.com/onflow/flow-go/model/flow" ) diff --git a/engine/common/rpc/convert/accounts_test.go b/engine/common/rpc/convert/accounts_test.go index a1b3d80e5c7..59dd7926288 100644 --- a/engine/common/rpc/convert/accounts_test.go +++ b/engine/common/rpc/convert/accounts_test.go @@ -6,8 +6,9 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/onflow/flow-go/crypto" - "github.com/onflow/flow-go/crypto/hash" + "github.com/onflow/crypto" + "github.com/onflow/crypto/hash" + "github.com/onflow/flow-go/engine/common/rpc/convert" "github.com/onflow/flow-go/fvm" "github.com/onflow/flow-go/utils/unittest" diff --git a/engine/common/rpc/convert/convert.go b/engine/common/rpc/convert/convert.go index 3419e997def..75d451932de 100644 --- a/engine/common/rpc/convert/convert.go +++ b/engine/common/rpc/convert/convert.go @@ -4,9 +4,9 @@ import ( "errors" "fmt" + "github.com/onflow/crypto" "github.com/onflow/flow/protobuf/go/flow/entities" - "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/model/flow" ) diff --git a/engine/common/rpc/convert/execution_data.go b/engine/common/rpc/convert/execution_data.go index 0c1699c0d90..664728d5655 100644 --- a/engine/common/rpc/convert/execution_data.go +++ b/engine/common/rpc/convert/execution_data.go @@ -268,7 +268,7 @@ func messageToTrustedTransaction( proposalKey := m.GetProposalKey() if proposalKey != nil { - proposalAddress, err := insecureAddress(proposalKey.GetAddress(), chain) + proposalAddress, err := insecureAddress(proposalKey.GetAddress()) if err != nil { return *t, fmt.Errorf("could not convert proposer address: %w", err) } @@ -277,7 +277,7 @@ func messageToTrustedTransaction( payer := m.GetPayer() if payer != nil { - payerAddress, err := insecureAddress(payer, chain) + payerAddress, err := insecureAddress(payer) if err != nil { return *t, fmt.Errorf("could not convert payer address: %w", err) } @@ -311,48 +311,57 @@ func messageToTrustedTransaction( t.SetScript(m.GetScript()) t.SetArguments(m.GetArguments()) t.SetReferenceBlockID(flow.HashToID(m.GetReferenceBlockId())) - t.SetGasLimit(m.GetGasLimit()) + t.SetComputeLimit(m.GetGasLimit()) return *t, nil } -func MessageToRegisterID(m *entities.RegisterID) (flow.RegisterID, error) { +func MessageToRegisterID(m *entities.RegisterID, chain flow.Chain) (flow.RegisterID, error) { if m == nil { return flow.RegisterID{}, ErrEmptyMessage } - return flow.RegisterID{ - Owner: m.GetOwner(), - Key: m.GetKey(), - }, nil + + owner := flow.EmptyAddress + if len(m.GetOwner()) > 0 { + var err error + owner, err = Address(m.GetOwner(), chain) + if err != nil { + return flow.RegisterID{}, fmt.Errorf("could not convert owner address: %w", err) + } + } + + key := string(m.GetKey()) + + return flow.NewRegisterID(owner, key), nil } // MessagesToRegisterIDs converts a protobuf message to RegisterIDs -func MessagesToRegisterIDs(m []*entities.RegisterID) (flow.RegisterIDs, error) { +func MessagesToRegisterIDs(m []*entities.RegisterID, chain flow.Chain) (flow.RegisterIDs, error) { if m == nil { return nil, ErrEmptyMessage } result := make(flow.RegisterIDs, len(m)) for i, entry := range m { - regId, err := MessageToRegisterID(entry) + regID, err := MessageToRegisterID(entry, chain) if err != nil { return nil, fmt.Errorf("failed to convert register id %d: %w", i, err) } - result[i] = regId + result[i] = regID } return result, nil } func RegisterIDToMessage(id flow.RegisterID) *entities.RegisterID { return &entities.RegisterID{ - Owner: id.Owner, - Key: id.Key, + Owner: []byte(id.Owner), + Key: []byte(id.Key), } } // insecureAddress converts a raw address to a flow.Address, skipping validation // This is useful when converting transactions from trusted state like BlockExecutionData. // This should only be used for trusted inputs -func insecureAddress(rawAddress []byte, chain flow.Chain) (flow.Address, error) { +func insecureAddress(rawAddress []byte) (flow.Address, error) { if len(rawAddress) == 0 { return flow.EmptyAddress, status.Error(codes.InvalidArgument, "address cannot be empty") } diff --git a/engine/common/rpc/convert/execution_data_test.go b/engine/common/rpc/convert/execution_data_test.go index 4ad06b820d5..d32ae94d008 100644 --- a/engine/common/rpc/convert/execution_data_test.go +++ b/engine/common/rpc/convert/execution_data_test.go @@ -21,7 +21,12 @@ import ( func TestConvertBlockExecutionDataEventPayloads(t *testing.T) { // generators will produce identical event payloads (before encoding) ccfEvents := generator.GetEventsWithEncoding(3, entities.EventEncodingVersion_CCF_V0) - jsonEvents := generator.GetEventsWithEncoding(3, entities.EventEncodingVersion_JSON_CDC_V0) + jsonEvents := make([]flow.Event, len(ccfEvents)) + for i, e := range ccfEvents { + jsonEvent, err := convert.CcfEventToJsonEvent(e) + require.NoError(t, err) + jsonEvents[i] = *jsonEvent + } // generate BlockExecutionData with CCF encoded events executionData := unittest.BlockExecutionDataFixture( @@ -172,7 +177,8 @@ func TestConvertChunkExecutionData(t *testing.T) { } } -func TestMessageToRegisterIDs(t *testing.T) { +func TestMessageToRegisterID(t *testing.T) { + chain := flow.Testnet.Chain() tests := []struct { name string regID flow.RegisterID @@ -193,12 +199,58 @@ func TestMessageToRegisterIDs(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - registerIDMessage := convert.RegisterIDToMessage(test.regID) - reConvertedRegisterID, err := convert.MessageToRegisterID(registerIDMessage) + msg := convert.RegisterIDToMessage(test.regID) + converted, err := convert.MessageToRegisterID(msg, chain) require.NoError(t, err) - assert.Equal(t, test.regID, reConvertedRegisterID) + assert.Equal(t, test.regID, converted) }) } - _, err := convert.MessageToRegisterID(nil) - require.ErrorIs(t, err, convert.ErrEmptyMessage) + + t.Run("nil owner converts to empty string", func(t *testing.T) { + msg := &entities.RegisterID{ + Owner: nil, + Key: []byte("key"), + } + converted, err := convert.MessageToRegisterID(msg, chain) + require.NoError(t, err) + assert.Equal(t, "", converted.Owner) + assert.Equal(t, "key", converted.Key) + }) + + t.Run("nil message returns error", func(t *testing.T) { + _, err := convert.MessageToRegisterID(nil, chain) + require.ErrorIs(t, err, convert.ErrEmptyMessage) + }) + + t.Run("invalid address returns error", func(t *testing.T) { + // addresses for other chains are invalid + registerID := flow.NewRegisterID( + unittest.RandomAddressFixtureForChain(flow.Mainnet), + "key", + ) + + msg := convert.RegisterIDToMessage(registerID) + _, err := convert.MessageToRegisterID(msg, chain) + require.Error(t, err) + }) + + t.Run("multiple registerIDs", func(t *testing.T) { + expected := flow.RegisterIDs{ + flow.UUIDRegisterID(0), + flow.AccountStatusRegisterID(unittest.AddressFixture()), + unittest.RegisterIDFixture(), + } + + messages := make([]*entities.RegisterID, len(expected)) + for i, regID := range expected { + regID := regID + messages[i] = convert.RegisterIDToMessage(regID) + require.Equal(t, regID.Owner, string(messages[i].Owner)) + require.Equal(t, regID.Key, string(messages[i].Key)) + } + + actual, err := convert.MessagesToRegisterIDs(messages, chain) + require.NoError(t, err) + assert.Equal(t, expected, actual) + }) } diff --git a/engine/common/rpc/convert/transactions.go b/engine/common/rpc/convert/transactions.go index ce94b5bae1c..221f41b0936 100644 --- a/engine/common/rpc/convert/transactions.go +++ b/engine/common/rpc/convert/transactions.go @@ -108,7 +108,7 @@ func MessageToTransaction( t.SetScript(m.GetScript()) t.SetArguments(m.GetArguments()) t.SetReferenceBlockID(flow.HashToID(m.GetReferenceBlockId())) - t.SetGasLimit(m.GetGasLimit()) + t.SetComputeLimit(m.GetGasLimit()) return *t, nil } diff --git a/engine/consensus/approvals/assignment_collector_base.go b/engine/consensus/approvals/assignment_collector_base.go index 58307b6c9f0..55839b5aafc 100644 --- a/engine/consensus/approvals/assignment_collector_base.go +++ b/engine/consensus/approvals/assignment_collector_base.go @@ -4,7 +4,8 @@ import ( "github.com/gammazero/workerpool" "github.com/rs/zerolog" - "github.com/onflow/flow-go/crypto/hash" + "github.com/onflow/crypto/hash" + "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/module" "github.com/onflow/flow-go/module/mempool" diff --git a/engine/consensus/approvals/assignment_collector_tree.go b/engine/consensus/approvals/assignment_collector_tree.go index e161a75faa4..c31282b3e8a 100644 --- a/engine/consensus/approvals/assignment_collector_tree.go +++ b/engine/consensus/approvals/assignment_collector_tree.go @@ -160,11 +160,10 @@ func (t *AssignmentCollectorTree) selectCollectorsForFinalizedFork(startHeight, var fork []*assignmentCollectorVertex for height := startHeight; height <= finalizedHeight; height++ { iter := t.forest.GetVerticesAtLevel(height) - finalizedBlock, err := t.headers.ByHeight(height) + finalizedBlockID, err := t.headers.BlockIDByHeight(height) if err != nil { return nil, fmt.Errorf("could not retrieve finalized block at height %d: %w", height, err) } - finalizedBlockID := finalizedBlock.ID() for iter.HasNext() { vertex := iter.NextVertex().(*assignmentCollectorVertex) if finalizedBlockID == vertex.collector.BlockID() { diff --git a/engine/consensus/approvals/signature_collector.go b/engine/consensus/approvals/signature_collector.go index 6af55f0e475..1b29ef47e52 100644 --- a/engine/consensus/approvals/signature_collector.go +++ b/engine/consensus/approvals/signature_collector.go @@ -1,7 +1,8 @@ package approvals import ( - "github.com/onflow/flow-go/crypto" + "github.com/onflow/crypto" + "github.com/onflow/flow-go/model/flow" ) diff --git a/engine/consensus/approvals/testutil.go b/engine/consensus/approvals/testutil.go index df5e98fa36b..f112761072d 100644 --- a/engine/consensus/approvals/testutil.go +++ b/engine/consensus/approvals/testutil.go @@ -2,10 +2,10 @@ package approvals import ( "github.com/gammazero/workerpool" + "github.com/onflow/crypto/hash" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/suite" - "github.com/onflow/flow-go/crypto/hash" "github.com/onflow/flow-go/model/chunks" "github.com/onflow/flow-go/model/flow" mempool "github.com/onflow/flow-go/module/mempool/mock" @@ -134,20 +134,22 @@ func (s *BaseAssignmentCollectorTestSuite) SetupTest() { return realstorage.ErrNotFound } }) - s.Headers.On("ByHeight", mock.Anything).Return( - func(height uint64) *flow.Header { + s.Headers.On("BlockIDByHeight", mock.Anything).Return( + func(height uint64) (flow.Identifier, error) { if block, found := s.FinalizedAtHeight[height]; found { - return block + return block.ID(), nil } else { - return nil + return flow.ZeroID, realstorage.ErrNotFound } }, - func(height uint64) error { - _, found := s.FinalizedAtHeight[height] - if !found { - return realstorage.ErrNotFound + ) + s.Headers.On("ByHeight", mock.Anything).Return( + func(height uint64) (*flow.Header, error) { + if block, found := s.FinalizedAtHeight[height]; found { + return block, nil + } else { + return nil, realstorage.ErrNotFound } - return nil }, ) diff --git a/engine/consensus/approvals/verifying_assignment_collector_test.go b/engine/consensus/approvals/verifying_assignment_collector_test.go index ee101e03d45..7784c1381f1 100644 --- a/engine/consensus/approvals/verifying_assignment_collector_test.go +++ b/engine/consensus/approvals/verifying_assignment_collector_test.go @@ -12,7 +12,8 @@ import ( "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" - "github.com/onflow/flow-go/crypto/hash" + "github.com/onflow/crypto/hash" + "github.com/onflow/flow-go/engine" "github.com/onflow/flow-go/engine/consensus/approvals/tracker" "github.com/onflow/flow-go/model/chunks" diff --git a/engine/consensus/dkg/reactor_engine.go b/engine/consensus/dkg/reactor_engine.go index 1d23344e4c6..23cbc45dd74 100644 --- a/engine/consensus/dkg/reactor_engine.go +++ b/engine/consensus/dkg/reactor_engine.go @@ -5,9 +5,9 @@ import ( "errors" "fmt" + "github.com/onflow/crypto" "github.com/rs/zerolog" - "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/engine" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/model/flow/filter" diff --git a/engine/consensus/dkg/reactor_engine_test.go b/engine/consensus/dkg/reactor_engine_test.go index 48e2707188d..a0f67f57f88 100644 --- a/engine/consensus/dkg/reactor_engine_test.go +++ b/engine/consensus/dkg/reactor_engine_test.go @@ -12,7 +12,8 @@ import ( "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" - "github.com/onflow/flow-go/crypto" + "github.com/onflow/crypto" + "github.com/onflow/flow-go/engine/consensus/dkg" "github.com/onflow/flow-go/model/flow" dkgmodule "github.com/onflow/flow-go/module/dkg" diff --git a/engine/consensus/mock/proposal_provider.go b/engine/consensus/mock/proposal_provider.go index b53cef236e1..ad0b3d5923b 100644 --- a/engine/consensus/mock/proposal_provider.go +++ b/engine/consensus/mock/proposal_provider.go @@ -3,8 +3,9 @@ package mock import ( - messages "github.com/onflow/flow-go/model/messages" mock "github.com/stretchr/testify/mock" + + messages "github.com/onflow/flow-go/model/messages" ) // ProposalProvider is an autogenerated mock type for the ProposalProvider type diff --git a/engine/consensus/sealing/core.go b/engine/consensus/sealing/core.go index 1bf9350e09f..fd6b9a04c5a 100644 --- a/engine/consensus/sealing/core.go +++ b/engine/consensus/sealing/core.go @@ -9,11 +9,11 @@ import ( "time" "github.com/gammazero/workerpool" + "github.com/onflow/crypto/hash" "github.com/rs/zerolog" "go.opentelemetry.io/otel/attribute" otelTrace "go.opentelemetry.io/otel/trace" - "github.com/onflow/flow-go/crypto/hash" "github.com/onflow/flow-go/engine" "github.com/onflow/flow-go/engine/consensus" "github.com/onflow/flow-go/engine/consensus/approvals" @@ -257,11 +257,11 @@ func (c *Core) processIncorporatedResult(incRes *flow.IncorporatedResult) error // For incorporating blocks at heights that are already finalized, we check that the incorporating block // is on the finalized fork. Otherwise, the incorporating block is orphaned, and we can drop the result. if incorporatedAtHeight <= c.counterLastFinalizedHeight.Value() { - finalized, err := c.headers.ByHeight(incorporatedAtHeight) + finalizedID, err := c.headers.BlockIDByHeight(incorporatedAtHeight) if err != nil { return fmt.Errorf("could not retrieve finalized block at height %d: %w", incorporatedAtHeight, err) } - if finalized.ID() != incRes.IncorporatedBlockID { + if finalizedID != incRes.IncorporatedBlockID { // it means that we got incorporated incRes for a block which doesn't extend our chain // and should be discarded from future processing return engine.NewOutdatedInputErrorf("won't process incorporated incRes from orphan block %s", incRes.IncorporatedBlockID) diff --git a/engine/execution/checker/core.go b/engine/execution/checker/core.go new file mode 100644 index 00000000000..78ca7475dd9 --- /dev/null +++ b/engine/execution/checker/core.go @@ -0,0 +1,150 @@ +package checker + +import ( + "context" + "errors" + "fmt" + + "github.com/rs/zerolog" + + "github.com/onflow/flow-go/engine/execution/state" + "github.com/onflow/flow-go/model/flow" + "github.com/onflow/flow-go/state/protocol" + "github.com/onflow/flow-go/storage" +) + +// Core is the core logic of the checker engine that checks if the execution result matches the sealed result. +type Core struct { + log zerolog.Logger + state protocol.State + execState state.ExecutionState +} + +func NewCore( + logger zerolog.Logger, + state protocol.State, + execState state.ExecutionState, +) *Core { + e := &Core{ + log: logger.With().Str("engine", "checker").Logger(), + state: state, + execState: execState, + } + + return e +} + +// checkMyCommitWithSealedCommit is the main check of the checker engine +func checkMyCommitWithSealedCommit( + executedBlock *flow.Header, + myCommit flow.StateCommitment, + sealedCommit flow.StateCommitment, +) error { + if myCommit != sealedCommit { + // mismatch + return fmt.Errorf("execution result is different from the sealed result, height: %v, block_id: %v, sealed_commit: %v, my_commit: %v", + executedBlock.Height, + executedBlock.ID(), + sealedCommit, + myCommit, + ) + } + + // match + return nil +} + +// RunCheck skips when the last sealed has not been executed, and last executed has not been finalized. +func (c *Core) RunCheck() error { + // find last sealed block + lastSealedBlock, lastFinal, seal, err := c.findLastSealedBlock() + if err != nil { + return err + } + + mycommitAtLastSealed, err := c.execState.StateCommitmentByBlockID(lastSealedBlock.ID()) + if err == nil { + // if last sealed block has been executed, then check if they match + return checkMyCommitWithSealedCommit(lastSealedBlock, mycommitAtLastSealed, seal.FinalState) + } + + // if last sealed block has not been executed, then check if recent executed block has + // been sealed already, if yes, check if they match. + lastExecutedHeight, err := c.findLastExecutedBlockHeight() + if err != nil { + return err + } + + if lastExecutedHeight > lastFinal.Height { + // last executed block has not been finalized yet, + // can't check since unfinalized block is also unsealed, skip + return nil + } + + // TODO: better to query seals from protocol state, + // switch to state.Final().LastSealed() when available + sealedExecuted, seal, err := c.findLatestSealedAtHeight(lastExecutedHeight) + if err != nil { + return fmt.Errorf("could not get the last sealed block at height: %v, err: %w", lastExecutedHeight, err) + } + + sealedCommit := seal.FinalState + + mycommit, err := c.execState.StateCommitmentByBlockID(seal.BlockID) + if errors.Is(err, storage.ErrNotFound) { + // have not executed the sealed block yet + // in other words, this can't detect execution fork, if the execution is behind + // the sealing + return nil + } + + if err != nil { + return fmt.Errorf("could not get my state commitment OnFinalizedBlock, blockID: %v", seal.BlockID) + } + + return checkMyCommitWithSealedCommit(sealedExecuted, mycommit, sealedCommit) +} + +// findLastSealedBlock finds the last sealed block +func (c *Core) findLastSealedBlock() (*flow.Header, *flow.Header, *flow.Seal, error) { + finalized := c.state.Final() + lastFinal, err := finalized.Head() + if err != nil { + return nil, nil, nil, err + } + + _, lastSeal, err := finalized.SealedResult() + if err != nil { + return nil, nil, nil, fmt.Errorf("could not get the last sealed for the finalized block: %w", err) + } + + lastSealed, err := c.state.AtBlockID(lastSeal.BlockID).Head() + if err != nil { + return nil, nil, nil, fmt.Errorf("could not get the last sealed block: %w", err) + } + + return lastSealed, lastFinal, lastSeal, nil +} + +// findLastExecutedBlockHeight finds the last executed block height +func (c *Core) findLastExecutedBlockHeight() (uint64, error) { + height, _, err := c.execState.GetHighestExecutedBlockID(context.Background()) + if err != nil { + return 0, fmt.Errorf("could not get the last executed block: %w", err) + } + return height, nil +} + +// findLatestSealedAtHeight finds the latest sealed block at the given height +func (c *Core) findLatestSealedAtHeight(finalizedHeight uint64) (*flow.Header, *flow.Seal, error) { + _, seal, err := c.state.AtHeight(finalizedHeight).SealedResult() + if err != nil { + return nil, nil, fmt.Errorf("could not get the last sealed for the finalized block: %w", err) + } + + sealed, err := c.state.AtBlockID(seal.BlockID).Head() + if err != nil { + return nil, nil, fmt.Errorf("could not get the last sealed block: %w", err) + } + return sealed, seal, nil +} diff --git a/engine/execution/checker/core_test.go b/engine/execution/checker/core_test.go new file mode 100644 index 00000000000..cd27c5cabdc --- /dev/null +++ b/engine/execution/checker/core_test.go @@ -0,0 +1,156 @@ +package checker_test + +import ( + "testing" + + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + + "github.com/onflow/flow-go/engine/execution/checker" + stateMock "github.com/onflow/flow-go/engine/execution/state/mock" + "github.com/onflow/flow-go/model/flow" + protocol "github.com/onflow/flow-go/state/protocol/mock" + "github.com/onflow/flow-go/storage" + "github.com/onflow/flow-go/utils/unittest" +) + +func makeCore(t *testing.T) (*checker.Core, *protocol.State, *stateMock.ExecutionState) { + logger := unittest.Logger() + state := protocol.NewState(t) + execState := stateMock.NewExecutionState(t) + core := checker.NewCore(logger, state, execState) + return core, state, execState +} + +func mockFinalizedBlock(t *testing.T, state *protocol.State, finalized *flow.Header) *protocol.Snapshot { + finalizedSnapshot := protocol.NewSnapshot(t) + finalizedSnapshot.On("Head").Return(finalized, nil) + state.On("Final").Return(finalizedSnapshot) + return finalizedSnapshot +} + +func mockAtBlockID(t *testing.T, state *protocol.State, header *flow.Header) *protocol.Snapshot { + snapshot := protocol.NewSnapshot(t) + snapshot.On("Head").Return(header, nil) + state.On("AtBlockID", header.ID()).Return(snapshot) + return snapshot +} + +func mockSealedBlock(t *testing.T, state *protocol.State, finalized *protocol.Snapshot, sealed *flow.Header) (*flow.ExecutionResult, *flow.Seal) { + lastSealResult := unittest.ExecutionResultFixture(func(r *flow.ExecutionResult) { + r.BlockID = sealed.ID() + }) + lastSeal := unittest.Seal.Fixture(unittest.Seal.WithResult(lastSealResult)) + finalized.On("SealedResult").Return(lastSealResult, lastSeal, nil) + return lastSealResult, lastSeal +} + +func mockFinalizedSealedBlock(t *testing.T, state *protocol.State, finalized *flow.Header, sealed *flow.Header) (*flow.ExecutionResult, *flow.Seal) { + finalizedSnapshot := mockFinalizedBlock(t, state, finalized) + return mockSealedBlock(t, state, finalizedSnapshot, sealed) +} + +func mockSealedBlockAtHeight(t *testing.T, state *protocol.State, height uint64, lastSealed *flow.Header) (*flow.ExecutionResult, *flow.Seal) { + snapshotAtHeight := protocol.NewSnapshot(t) + lastSealedResultAtHeight := unittest.ExecutionResultFixture(func(r *flow.ExecutionResult) { + r.BlockID = lastSealed.ID() + }) + lastSealAtHeight := unittest.Seal.Fixture(unittest.Seal.WithResult(lastSealedResultAtHeight)) + snapshotAtHeight.On("SealedResult").Return(lastSealedResultAtHeight, lastSealAtHeight, nil) + state.On("AtHeight", height).Return(snapshotAtHeight, nil) + return lastSealedResultAtHeight, lastSealAtHeight +} + +func mockExecutedBlock(t *testing.T, es *stateMock.ExecutionState, executed *flow.Header, result *flow.ExecutionResult) { + commit, err := result.FinalStateCommitment() + require.NoError(t, err) + es.On("StateCommitmentByBlockID", executed.ID()).Return(commit, nil) +} + +func mockUnexecutedBlock(t *testing.T, es *stateMock.ExecutionState, unexecuted *flow.Header) { + es.On("StateCommitmentByBlockID", unexecuted.ID()).Return(nil, storage.ErrNotFound) +} + +func TestCheckPassIfLastSealedIsExecutedAndMatch(t *testing.T) { + // ..<- LastSealed(executed) <- .. <- LastFinalized <- .. <- LastExecuted <- ... + chain, _, _ := unittest.ChainFixture(10) + lastFinal := chain[7].Header + lastSealed := chain[5].Header + + core, state, es := makeCore(t) + lastSealedResult, _ := mockFinalizedSealedBlock(t, state, lastFinal, lastSealed) + mockAtBlockID(t, state, lastSealed) + mockExecutedBlock(t, es, lastSealed, lastSealedResult) + + require.NoError(t, core.RunCheck()) +} + +func TestCheckFailIfLastSealedIsExecutedButMismatch(t *testing.T) { + // ..<- LastSealed(executed) <- .. <- LastFinalized <- .. <- LastExecuted <- ... + chain, _, _ := unittest.ChainFixture(10) + lastFinal := chain[7].Header + lastSealed := chain[5].Header + + core, state, es := makeCore(t) + _, _ = mockFinalizedSealedBlock(t, state, lastFinal, lastSealed) + mockAtBlockID(t, state, lastSealed) + + mismatchingResult := unittest.ExecutionResultFixture() + + mockExecutedBlock(t, es, lastSealed, mismatchingResult) + + require.Error(t, core.RunCheck()) + require.Contains(t, core.RunCheck().Error(), "execution result is different from the sealed result") +} + +func TestCheckPassIfLastSealedIsNotExecutedAndLastExecutedMatch(t *testing.T) { + // LastSealedExecuted (sealed) <..<- LastExecuted(finalized) <..<- LastSealed(not executed) <..<- LastFinalized + chain, _, _ := unittest.ChainFixture(10) + lastFinal := chain[7].Header + lastSealed := chain[5].Header + lastExecuted := chain[3].Header + lastSealedExecuted := chain[1].Header + + core, state, es := makeCore(t) + // mock that last sealed is not executed + mockFinalizedSealedBlock(t, state, lastFinal, lastSealed) + mockAtBlockID(t, state, lastSealed) + mockUnexecutedBlock(t, es, lastSealed) + + // mock the last sealed and is also executed + es.On("GetHighestExecutedBlockID", mock.Anything).Return(lastExecuted.Height, lastExecuted.ID(), nil) + lastSealedResultAtExecutedHeight, _ := mockSealedBlockAtHeight(t, state, lastExecuted.Height, lastSealedExecuted) + mockAtBlockID(t, state, lastSealedExecuted) + + // mock with matching result + mockExecutedBlock(t, es, lastSealedExecuted, lastSealedResultAtExecutedHeight) + + require.NoError(t, core.RunCheck()) +} + +func TestCheckFailIfLastSealedIsNotExecutedAndLastExecutedMismatch(t *testing.T) { + // LastSealedExecuted (sealed) <..<- LastExecuted(finalized) <..<- LastSealed(not executed) <..<- LastFinalized + chain, _, _ := unittest.ChainFixture(10) + lastFinal := chain[7].Header + lastSealed := chain[5].Header + lastExecuted := chain[3].Header + lastSealedExecuted := chain[1].Header + + core, state, es := makeCore(t) + // mock that last sealed is not executed + mockFinalizedSealedBlock(t, state, lastFinal, lastSealed) + mockAtBlockID(t, state, lastSealed) + mockUnexecutedBlock(t, es, lastSealed) + + // mock the last sealed and is also executed + es.On("GetHighestExecutedBlockID", mock.Anything).Return(lastExecuted.Height, lastExecuted.ID(), nil) + mockSealedBlockAtHeight(t, state, lastExecuted.Height, lastSealedExecuted) + mockAtBlockID(t, state, lastSealedExecuted) + + // mock with mismatching result + mismatchingResult := unittest.ExecutionResultFixture() + mockExecutedBlock(t, es, lastSealedExecuted, mismatchingResult) + + require.Error(t, core.RunCheck()) + require.Contains(t, core.RunCheck().Error(), "execution result is different from the sealed result") +} diff --git a/engine/execution/checker/engine.go b/engine/execution/checker/engine.go index a1a96184105..5c9a5bc1404 100644 --- a/engine/execution/checker/engine.go +++ b/engine/execution/checker/engine.go @@ -1,108 +1,57 @@ package checker import ( - "errors" - "fmt" + "context" + "time" - "github.com/rs/zerolog" - - "github.com/onflow/flow-go/consensus/hotstuff/model" - "github.com/onflow/flow-go/consensus/hotstuff/notifications" - "github.com/onflow/flow-go/engine" - "github.com/onflow/flow-go/engine/execution/state" - "github.com/onflow/flow-go/model/flow" - "github.com/onflow/flow-go/state/protocol" - "github.com/onflow/flow-go/storage" + "github.com/onflow/flow-go/module/component" + "github.com/onflow/flow-go/module/irrecoverable" ) type Engine struct { - notifications.NoopConsumer // satisfy the FinalizationConsumer interface - - unit *engine.Unit - log zerolog.Logger - state protocol.State - execState state.ExecutionState - sealsDB storage.Seals -} - -func New( - logger zerolog.Logger, - state protocol.State, - execState state.ExecutionState, - sealsDB storage.Seals, -) *Engine { - return &Engine{ - unit: engine.NewUnit(), - log: logger.With().Str("engine", "checker").Logger(), - state: state, - execState: execState, - sealsDB: sealsDB, - } + *component.ComponentManager + core *Core } -func (e *Engine) Ready() <-chan struct{} { - // make sure we will run into a crashloop if result gets inconsistent - // with sealed result. +// DefaultTimeInterval triggers the check once every minute, +const DefaultTimeInterval = time.Minute * 1 - finalized, err := e.state.Final().Head() - - if err != nil { - e.log.Fatal().Err(err).Msg("could not get finalized block on startup") +func NewEngine(core *Core) *Engine { + e := &Engine{ + core: core, } - err = e.checkLastSealed(finalized.ID()) - if err != nil { - e.log.Fatal().Err(err).Msg("execution consistency check failed on startup") - } - return e.unit.Ready() -} - -func (e *Engine) Done() <-chan struct{} { - return e.unit.Done() -} + e.ComponentManager = component.NewComponentManagerBuilder(). + AddWorker(func(ctx irrecoverable.SignalerContext, ready component.ReadyFunc) { + ready() + err := e.runLoop(ctx, DefaultTimeInterval) + if err != nil { + ctx.Throw(err) + } + }). + Build() -// when a block is finalized check if the last sealed has been executed, -// if it has been executed, check whether if the sealed result is consistent -// with the executed result -func (e *Engine) OnFinalizedBlock(block *model.Block) { - err := e.checkLastSealed(block.BlockID) - if err != nil { - e.log.Fatal().Err(err).Msg("execution consistency check failed") - } + return e } -func (e *Engine) checkLastSealed(finalizedID flow.Identifier) error { - // TODO: better to query seals from protocol state, - // switch to state.Final().LastSealed() when available - seal, err := e.sealsDB.HighestInFork(finalizedID) - if err != nil { - return fmt.Errorf("could not get the last sealed for the finalized block: %w", err) - } - - blockID := seal.BlockID - sealedCommit := seal.FinalState - - mycommit, err := e.execState.StateCommitmentByBlockID(blockID) - if errors.Is(err, storage.ErrNotFound) { - // have not executed the sealed block yet - // in other words, this can't detect execution fork, if the execution is behind - // the sealing - return nil - } - - if err != nil { - return fmt.Errorf("could not get my state commitment OnFinalizedBlock, blockID: %v", blockID) - } - - if mycommit != sealedCommit { - sealed, err := e.state.AtBlockID(blockID).Head() - if err != nil { - return fmt.Errorf("could not get sealed block when checkLastSealed: %v, err: %w", blockID, err) +// runLoop runs the check every minute. +// Why using a timer instead of listening to finalized and executed events? +// because it's simpler as it doesn't need to subscribe to those events. +// It also runs less checks, note: the checker doesn't need to find the +// first mismatched block, as long as it can find a mismatch, it's good enough. +// A timer could reduce the number of checks, as it only checks once every minute. +func (e *Engine) runLoop(ctx context.Context, tickInterval time.Duration) error { + ticker := time.NewTicker(tickInterval) + defer ticker.Stop() // critical for ticker to be garbage collected + for { + select { + case <-ticker.C: + err := e.core.RunCheck() + if err != nil { + return err + } + case <-ctx.Done(): + return nil } - - return fmt.Errorf("execution result is different from the sealed result, height: %v, block_id: %v, sealed_commit: %x, my_commit: %x", - sealed.Height, blockID, sealedCommit, mycommit) } - - return nil } diff --git a/engine/execution/computation/computer/computer.go b/engine/execution/computation/computer/computer.go index 25345aba997..2500353fd35 100644 --- a/engine/execution/computation/computer/computer.go +++ b/engine/execution/computation/computer/computer.go @@ -5,11 +5,11 @@ import ( "fmt" "sync" + "github.com/onflow/crypto/hash" "github.com/rs/zerolog" "go.opentelemetry.io/otel/attribute" otelTrace "go.opentelemetry.io/otel/trace" - "github.com/onflow/flow-go/crypto/hash" "github.com/onflow/flow-go/engine/execution" "github.com/onflow/flow-go/engine/execution/computation/result" "github.com/onflow/flow-go/engine/execution/utils" diff --git a/engine/execution/computation/computer/computer_test.go b/engine/execution/computation/computer/computer_test.go index 1f7e5429761..ce74b69347c 100644 --- a/engine/execution/computation/computer/computer_test.go +++ b/engine/execution/computation/computer/computer_test.go @@ -601,7 +601,8 @@ func TestBlockExecutor_ExecuteBlock(t *testing.T) { serviceEvents := systemcontracts.ServiceEventsForChain(execCtx.Chain.ChainID()) - payload, err := ccf.Decode(nil, unittest.EpochSetupFixtureCCF) + randomSource := unittest.EpochSetupRandomSourceFixture() + payload, err := ccf.Decode(nil, unittest.EpochSetupFixtureCCF(randomSource)) require.NoError(t, err) serviceEventA, ok := payload.(cadence.Event) @@ -1255,7 +1256,7 @@ func Test_ExecutingSystemCollection(t *testing.T) { noopCollector := metrics.NewNoopCollector() expectedNumberOfEvents := 3 - expectedEventSize := 1434 + expectedEventSize := 1493 // bootstrapping does not cache programs expectedCachedPrograms := 0 diff --git a/engine/execution/computation/computer/result_collector.go b/engine/execution/computation/computer/result_collector.go index 703fae44488..636aeb9a4e1 100644 --- a/engine/execution/computation/computer/result_collector.go +++ b/engine/execution/computation/computer/result_collector.go @@ -6,10 +6,10 @@ import ( "sync" "time" + "github.com/onflow/crypto" + "github.com/onflow/crypto/hash" otelTrace "go.opentelemetry.io/otel/trace" - "github.com/onflow/flow-go/crypto" - "github.com/onflow/flow-go/crypto/hash" "github.com/onflow/flow-go/engine/execution" "github.com/onflow/flow-go/engine/execution/computation/result" "github.com/onflow/flow-go/engine/execution/storehouse" diff --git a/engine/execution/computation/execution_verification_test.go b/engine/execution/computation/execution_verification_test.go index 3583c9e8796..44411bb4f8a 100644 --- a/engine/execution/computation/execution_verification_test.go +++ b/engine/execution/computation/execution_verification_test.go @@ -15,7 +15,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/onflow/flow-go/crypto" + "github.com/onflow/crypto" "github.com/onflow/flow-go/engine/execution" "github.com/onflow/flow-go/engine/execution/computation/committer" @@ -263,7 +263,7 @@ func Test_ExecutionMatchesVerification(t *testing.T) { }`), } - spamTx.SetGasLimit(800000) + spamTx.SetComputeLimit(800000) err = testutil.SignTransaction(spamTx, accountAddress, accountPrivKey, 0) require.NoError(t, err) diff --git a/engine/execution/computation/manager.go b/engine/execution/computation/manager.go index 946f2457a00..f4d0d2cc748 100644 --- a/engine/execution/computation/manager.go +++ b/engine/execution/computation/manager.go @@ -242,6 +242,7 @@ func DefaultFVMOptions(chainID flow.ChainID, cadenceTracing bool, extensiveTraci CapabilityControllersEnabled: chainID != flow.Mainnet, }, )), + fvm.WithEVMEnabled(true), } if extensiveTracing { diff --git a/engine/execution/computation/manager_benchmark_test.go b/engine/execution/computation/manager_benchmark_test.go index d5d55a50691..665d96b85f9 100644 --- a/engine/execution/computation/manager_benchmark_test.go +++ b/engine/execution/computation/manager_benchmark_test.go @@ -307,7 +307,7 @@ func createTokenTransferTransaction(b *testing.B, accs *testAccounts) *flow.Tran tx := testutil.CreateTokenTransferTransaction(chain, 1, dst.address, src.address) tx.SetProposalKey(chain.ServiceAddress(), 0, accs.seq). - SetGasLimit(1000). + SetComputeLimit(1000). SetPayer(chain.ServiceAddress()) accs.seq++ diff --git a/engine/execution/computation/manager_test.go b/engine/execution/computation/manager_test.go index b5b3452a769..30969282e8b 100644 --- a/engine/execution/computation/manager_test.go +++ b/engine/execution/computation/manager_test.go @@ -68,7 +68,7 @@ func TestComputeBlockWithStorage(t *testing.T) { tx1 := testutil.DeployCounterContractTransaction(accounts[0], chain) tx1.SetProposalKey(chain.ServiceAddress(), 0, 0). - SetGasLimit(1000). + SetComputeLimit(1000). SetPayer(chain.ServiceAddress()) err = testutil.SignPayload(tx1, accounts[0], privateKeys[0]) @@ -79,7 +79,7 @@ func TestComputeBlockWithStorage(t *testing.T) { tx2 := testutil.CreateCounterTransaction(accounts[0], accounts[1]) tx2.SetProposalKey(chain.ServiceAddress(), 0, 0). - SetGasLimit(1000). + SetComputeLimit(1000). SetPayer(chain.ServiceAddress()) err = testutil.SignPayload(tx2, accounts[1], privateKeys[1]) diff --git a/engine/execution/execution_test.go b/engine/execution/execution_test.go index 064948b7711..12a79e90706 100644 --- a/engine/execution/execution_test.go +++ b/engine/execution/execution_test.go @@ -17,7 +17,6 @@ import ( "github.com/onflow/flow-go/engine/testutil" testmock "github.com/onflow/flow-go/engine/testutil/mock" "github.com/onflow/flow-go/model/flow" - "github.com/onflow/flow-go/model/flow/order" "github.com/onflow/flow-go/model/messages" "github.com/onflow/flow-go/module/signature" "github.com/onflow/flow-go/network/channels" @@ -60,7 +59,7 @@ func TestExecutionFlow(t *testing.T) { unittest.WithKeys, ) - identities := unittest.CompleteIdentitySet(colID, conID, exeID, verID).Sort(order.Canonical) + identities := unittest.CompleteIdentitySet(colID, conID, exeID, verID).Sort(flow.Canonical) // create execution node exeNode := testutil.ExecutionNode(t, hub, exeID, identities, 21, chainID) diff --git a/engine/execution/ingestion/engine.go b/engine/execution/ingestion/engine.go index 6f3fe614f89..eb73f9ef04f 100644 --- a/engine/execution/ingestion/engine.go +++ b/engine/execution/ingestion/engine.go @@ -25,7 +25,6 @@ import ( "github.com/onflow/flow-go/module/mempool/stdmap" "github.com/onflow/flow-go/module/trace" "github.com/onflow/flow-go/network" - "github.com/onflow/flow-go/network/channels" psEvents "github.com/onflow/flow-go/state/protocol/events" "github.com/onflow/flow-go/storage" "github.com/onflow/flow-go/utils/logging" @@ -37,7 +36,6 @@ type Engine struct { unit *engine.Unit log zerolog.Logger - me module.Local collectionFetcher CollectionFetcher headers storage.Headers // see comments on getHeaderByHeight for why we need it blocks storage.Blocks @@ -47,7 +45,6 @@ type Engine struct { mempool *Mempool execState state.ExecutionState metrics module.ExecutionMetrics - maxCollectionHeight uint64 tracer module.Tracer extensiveLogging bool executionDataPruner *pruner.Pruner @@ -60,7 +57,6 @@ func New( unit *engine.Unit, logger zerolog.Logger, net network.EngineRegistry, - me module.Local, collectionFetcher CollectionFetcher, headers storage.Headers, blocks storage.Blocks, @@ -83,7 +79,6 @@ func New( eng := Engine{ unit: unit, log: log, - me: me, collectionFetcher: collectionFetcher, headers: headers, blocks: blocks, @@ -93,7 +88,6 @@ func New( mempool: mempool, execState: execState, metrics: metrics, - maxCollectionHeight: 0, tracer: tracer, extensiveLogging: extLog, executionDataPruner: pruner, @@ -130,51 +124,11 @@ func (e *Engine) Done() <-chan struct{} { return e.unit.Done() } -// SubmitLocal submits an event originating on the local node. -func (e *Engine) SubmitLocal(event interface{}) { - e.unit.Launch(func() { - err := e.process(e.me.NodeID(), event) - if err != nil { - engine.LogError(e.log, err) - } - }) -} - -// Submit submits the given event from the node with the given origin ID -// for processing in a non-blocking manner. It returns instantly and logs -// a potential processing error internally when done. -func (e *Engine) Submit( - channel channels.Channel, - originID flow.Identifier, - event interface{}, -) { - e.unit.Launch(func() { - err := e.process(originID, event) - if err != nil { - engine.LogError(e.log, err) - } - }) -} - // ProcessLocal processes an event originating on the local node. func (e *Engine) ProcessLocal(event interface{}) error { return fmt.Errorf("ingestion error does not process local events") } -func (e *Engine) Process( - channel channels.Channel, - originID flow.Identifier, - event interface{}, -) error { - return e.unit.Do(func() error { - return e.process(originID, event) - }) -} - -func (e *Engine) process(originID flow.Identifier, event interface{}) error { - return nil -} - // on nodes startup, we need to load all the unexecuted blocks to the execution queues. // blocks have to be loaded in the way that the parent has been loaded before loading its children func (e *Engine) reloadUnexecutedBlocks() error { @@ -202,7 +156,7 @@ func (e *Engine) reloadUnexecutedBlocks() error { e.log.Debug().Hex("block_id", blockID[:]).Msg("reloaded block") } - log.Info().Msg("all unexecuted have been successfully reloaded") + e.log.Info().Int("count", len(unexecuted)).Msg("all unexecuted have been successfully reloaded") return nil }) @@ -396,6 +350,8 @@ func (e *Engine) enqueueBlockAndCheckExecutable( Uint64("first_unexecuted_in_queue", firstUnexecutedHeight). Bool("complete", complete). Bool("head_of_queue", head). + Int("cols", len(executableBlock.Block.Payload.Guarantees)). + Int("missing_cols", len(missingCollections)). Msg("block is enqueued") return missingCollections, nil @@ -724,12 +680,7 @@ func (e *Engine) addCollectionToMempool( blockID) } - // record collection max height metrics - blockHeight := executableBlock.Block.Header.Height - if blockHeight > e.maxCollectionHeight { - e.metrics.UpdateCollectionMaxHeight(blockHeight) - e.maxCollectionHeight = blockHeight - } + e.metrics.UpdateCollectionMaxHeight(executableBlock.Block.Header.Height) if completeCollection.IsCompleted() { // already received transactions for this collection diff --git a/engine/execution/ingestion/engine_test.go b/engine/execution/ingestion/engine_test.go index 64d66aeec1f..de3e88fec69 100644 --- a/engine/execution/ingestion/engine_test.go +++ b/engine/execution/ingestion/engine_test.go @@ -8,13 +8,11 @@ import ( "testing" "time" + "github.com/onflow/crypto" "github.com/rs/zerolog" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - "github.com/onflow/flow-go/crypto" - "github.com/onflow/flow-go/fvm/storage/snapshot" - enginePkg "github.com/onflow/flow-go/engine" "github.com/onflow/flow-go/engine/execution" computation "github.com/onflow/flow-go/engine/execution/computation/mock" @@ -25,7 +23,7 @@ import ( uploadermock "github.com/onflow/flow-go/engine/execution/ingestion/uploader/mock" provider "github.com/onflow/flow-go/engine/execution/provider/mock" stateMock "github.com/onflow/flow-go/engine/execution/state/mock" - "github.com/onflow/flow-go/engine/testutil/mocklocal" + "github.com/onflow/flow-go/fvm/storage/snapshot" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/module/mempool/entity" "github.com/onflow/flow-go/module/metrics" @@ -69,8 +67,6 @@ func runWithEngine(t *testing.T, f func(testingContext)) { myIdentity.Role = flow.RoleExecution myIdentity.StakingPubKey = sk.PublicKey() - me := mocklocal.NewMockLocal(sk, myIdentity.ID(), t) - headers := storage.NewHeaders(t) blocks := storage.NewBlocks(t) collections := mocks.NewMockCollectionStore() @@ -119,7 +115,6 @@ func runWithEngine(t *testing.T, f func(testingContext)) { unit, log, net, - me, fetcher, headers, blocks, diff --git a/engine/execution/ingestion/loader/unexecuted_loader.go b/engine/execution/ingestion/loader/unexecuted_loader.go index a9eba76115f..7d32ba11ec4 100644 --- a/engine/execution/ingestion/loader/unexecuted_loader.go +++ b/engine/execution/ingestion/loader/unexecuted_loader.go @@ -17,7 +17,7 @@ import ( type UnexecutedLoader struct { log zerolog.Logger state protocol.State - headers storage.Headers // see comments on getHeaderByHeight for why we need it + headers storage.Headers execState state.ExecutionState } @@ -157,12 +157,12 @@ func (e *UnexecutedLoader) finalizedUnexecutedBlocks(ctx context.Context, finali } for ; lastExecuted > rootBlock.Height; lastExecuted-- { - header, err := e.getHeaderByHeight(lastExecuted) + finalizedID, err := e.headers.BlockIDByHeight(lastExecuted) if err != nil { return nil, fmt.Errorf("could not get header at height: %v, %w", lastExecuted, err) } - executed, err := e.execState.IsBlockExecuted(header.Height, header.ID()) + executed, err := e.execState.IsBlockExecuted(lastExecuted, finalizedID) if err != nil { return nil, fmt.Errorf("could not check whether block is executed: %w", err) } @@ -179,12 +179,12 @@ func (e *UnexecutedLoader) finalizedUnexecutedBlocks(ctx context.Context, finali // starting from the first unexecuted block, go through each unexecuted and finalized block // reload its block to execution queues for height := firstUnexecuted; height <= final.Height; height++ { - header, err := e.getHeaderByHeight(height) + finalizedID, err := e.headers.BlockIDByHeight(height) if err != nil { return nil, fmt.Errorf("could not get header at height: %v, %w", height, err) } - unexecuted = append(unexecuted, header.ID()) + unexecuted = append(unexecuted, finalizedID) } e.log.Info(). @@ -227,12 +227,3 @@ func (e *UnexecutedLoader) pendingUnexecutedBlocks(ctx context.Context, finalize return unexecuted, nil } - -// if the EN is dynamically bootstrapped, the finalized blocks at height range: -// [ sealedRoot.Height, finalizedRoot.Height - 1] can not be retrieved from -// protocol state, but only from headers -func (e *UnexecutedLoader) getHeaderByHeight(height uint64) (*flow.Header, error) { - // we don't use protocol state because for dynamic boostrapped execution node - // the last executed and sealed block is below the finalized root block - return e.headers.ByHeight(height) -} diff --git a/engine/execution/ingestion/loader/unexecuted_loader_test.go b/engine/execution/ingestion/loader/unexecuted_loader_test.go index 23779394c5b..df5ef452606 100644 --- a/engine/execution/ingestion/loader/unexecuted_loader_test.go +++ b/engine/execution/ingestion/loader/unexecuted_loader_test.go @@ -200,7 +200,7 @@ func TestLoadingUnexecutedBlocks(t *testing.T) { loader := loader.NewUnexecutedLoader(log, ps, headers, es) // block C is the only finalized block, index its header by its height - headers.EXPECT().ByHeight(blockC.Header.Height).Return(blockC.Header, nil) + headers.EXPECT().BlockIDByHeight(blockC.Header.Height).Return(blockC.Header.ID(), nil) es.ExecuteBlock(t, blockA) es.ExecuteBlock(t, blockB) @@ -238,7 +238,7 @@ func TestLoadingUnexecutedBlocks(t *testing.T) { loader := loader.NewUnexecutedLoader(log, ps, headers, es) // block C is finalized, index its header by its height - headers.EXPECT().ByHeight(blockC.Header.Height).Return(blockC.Header, nil) + headers.EXPECT().BlockIDByHeight(blockC.Header.Height).Return(blockC.Header.ID(), nil) es.ExecuteBlock(t, blockA) es.ExecuteBlock(t, blockB) @@ -278,7 +278,7 @@ func TestLoadingUnexecutedBlocks(t *testing.T) { loader := loader.NewUnexecutedLoader(log, ps, headers, es) // block A is finalized, index its header by its height - headers.EXPECT().ByHeight(blockA.Header.Height).Return(blockA.Header, nil) + headers.EXPECT().BlockIDByHeight(blockA.Header.Height).Return(blockA.Header.ID(), nil) es.ExecuteBlock(t, blockA) es.ExecuteBlock(t, blockB) @@ -346,7 +346,7 @@ func TestLoadingUnexecutedBlocks(t *testing.T) { loader := loader.NewUnexecutedLoader(log, ps, headers, es) // block C is finalized, index its header by its height - headers.EXPECT().ByHeight(blockC.Header.Height).Return(blockC.Header, nil) + headers.EXPECT().BlockIDByHeight(blockC.Header.Height).Return(blockC.Header.ID(), nil) es.ExecuteBlock(t, blockA) es.ExecuteBlock(t, blockB) diff --git a/engine/execution/ingestion/loader/unfinalized_loader.go b/engine/execution/ingestion/loader/unfinalized_loader.go index bcfc699074a..d71c737cbc3 100644 --- a/engine/execution/ingestion/loader/unfinalized_loader.go +++ b/engine/execution/ingestion/loader/unfinalized_loader.go @@ -15,7 +15,7 @@ import ( type UnfinalizedLoader struct { log zerolog.Logger state protocol.State - headers storage.Headers // see comments on getHeaderByHeight for why we need it + headers storage.Headers execState state.FinalizedExecutionState } @@ -46,19 +46,28 @@ func (e *UnfinalizedLoader) LoadUnexecuted(ctx context.Context) ([]flow.Identifi return nil, fmt.Errorf("could not get finalized block: %w", err) } - // TODO: dynamically bootstrapped execution node will reload blocks from + lg := e.log.With(). + Uint64("last_finalized", final.Height). + Uint64("last_finalized_executed", lastExecuted). + Logger() + + lg.Info().Msgf("start loading unfinalized blocks") + + // dynamically bootstrapped execution node will have highest finalized executed as sealed root, + // which is lower than finalized root. so we will reload blocks from + // [sealedRoot.Height + 1, finalizedRoot.Height] and execute them on startup. unexecutedFinalized := make([]flow.Identifier, 0) // starting from the first unexecuted block, go through each unexecuted and finalized block // reload its block to execution queues // loading finalized blocks for height := lastExecuted + 1; height <= final.Height; height++ { - header, err := e.getHeaderByHeight(height) + finalizedID, err := e.headers.BlockIDByHeight(height) if err != nil { return nil, fmt.Errorf("could not get header at height: %v, %w", height, err) } - unexecutedFinalized = append(unexecutedFinalized, header.ID()) + unexecutedFinalized = append(unexecutedFinalized, finalizedID) } // loaded all pending blocks @@ -69,9 +78,7 @@ func (e *UnfinalizedLoader) LoadUnexecuted(ctx context.Context) ([]flow.Identifi unexecuted := append(unexecutedFinalized, pendings...) - e.log.Info(). - Uint64("last_finalized", final.Height). - Uint64("last_finalized_executed", lastExecuted). + lg.Info(). // Uint64("sealed_root_height", rootBlock.Height). // Hex("sealed_root_id", logging.Entity(rootBlock)). Int("total_finalized_unexecuted", len(unexecutedFinalized)). @@ -80,12 +87,3 @@ func (e *UnfinalizedLoader) LoadUnexecuted(ctx context.Context) ([]flow.Identifi return unexecuted, nil } - -// if the EN is dynamically bootstrapped, the finalized blocks at height range: -// [ sealedRoot.Height, finalizedRoot.Height - 1] can not be retrieved from -// protocol state, but only from headers -func (e *UnfinalizedLoader) getHeaderByHeight(height uint64) (*flow.Header, error) { - // we don't use protocol state because for dynamic boostrapped execution node - // the last executed and sealed block is below the finalized root block - return e.headers.ByHeight(height) -} diff --git a/engine/execution/ingestion/loader/unfinalized_loader_test.go b/engine/execution/ingestion/loader/unfinalized_loader_test.go index 3c8b84aed40..c5ded941006 100644 --- a/engine/execution/ingestion/loader/unfinalized_loader_test.go +++ b/engine/execution/ingestion/loader/unfinalized_loader_test.go @@ -37,9 +37,9 @@ func TestLoadingUnfinalizedBlocks(t *testing.T) { es := new(stateMock.FinalizedExecutionState) es.On("GetHighestFinalizedExecuted").Return(genesis.Header.Height) headers := new(storage.Headers) - headers.On("ByHeight", blockA.Header.Height).Return(blockA.Header, nil) - headers.On("ByHeight", blockB.Header.Height).Return(blockB.Header, nil) - headers.On("ByHeight", blockC.Header.Height).Return(blockC.Header, nil) + headers.On("BlockIDByHeight", blockA.Header.Height).Return(blockA.Header.ID(), nil) + headers.On("BlockIDByHeight", blockB.Header.Height).Return(blockB.Header.ID(), nil) + headers.On("BlockIDByHeight", blockC.Header.Height).Return(blockC.Header.ID(), nil) loader := loader.NewUnfinalizedLoader(unittest.Logger(), ps, headers, es) diff --git a/engine/execution/ingestion/stop/stop_control.go b/engine/execution/ingestion/stop/stop_control.go index bb14e8905d5..b91738e5d31 100644 --- a/engine/execution/ingestion/stop/stop_control.go +++ b/engine/execution/ingestion/stop/stop_control.go @@ -148,7 +148,7 @@ func (s stopBoundary) String() string { // StopControlHeaders is an interface for fetching headers // Its jut a small subset of storage.Headers for comments see storage.Headers type StopControlHeaders interface { - ByHeight(height uint64) (*flow.Header, error) + BlockIDByHeight(height uint64) (flow.Identifier, error) } // NewStopControl creates new StopControl. @@ -476,12 +476,12 @@ func (s *StopControl) blockFinalized( // Let's find the ID of the block that should be executed last // which is the parent of the block at the stopHeight - header, err := s.headers.ByHeight(s.stopBoundary.StopBeforeHeight - 1) + finalizedID, err := s.headers.BlockIDByHeight(s.stopBoundary.StopBeforeHeight - 1) if err != nil { handleErr(fmt.Errorf("failed to get header by height: %w", err)) return } - parentID = header.ID() + parentID = finalizedID } s.stopBoundary.stopAfterExecuting = parentID diff --git a/engine/execution/ingestion/stop/stop_control_test.go b/engine/execution/ingestion/stop/stop_control_test.go index 6698c3cc7b8..759c3de8e9a 100644 --- a/engine/execution/ingestion/stop/stop_control_test.go +++ b/engine/execution/ingestion/stop/stop_control_test.go @@ -162,12 +162,12 @@ type stopControlMockHeaders struct { headers map[uint64]*flow.Header } -func (m *stopControlMockHeaders) ByHeight(height uint64) (*flow.Header, error) { +func (m *stopControlMockHeaders) BlockIDByHeight(height uint64) (flow.Identifier, error) { h, ok := m.headers[height] if !ok { - return nil, fmt.Errorf("header not found") + return flow.ZeroID, fmt.Errorf("header not found") } - return h, nil + return h.ID(), nil } func TestAddStopForPastBlocks(t *testing.T) { @@ -865,4 +865,11 @@ func Test_StopControlWorkers(t *testing.T) { func TestPatchedVersion(t *testing.T) { require.True(t, semver.New("0.31.20").LessThan(*semver.New("0.31.21"))) require.True(t, semver.New("0.31.20-patch.1").LessThan(*semver.New("0.31.20"))) // be careful with this one + require.True(t, semver.New("0.31.20-without-adx").LessThan(*semver.New("0.31.20"))) + + // a special build created with "+" would not change the version priority for standard and pre-release versions + require.True(t, semver.New("0.31.20+without-adx").Equal(*semver.New("0.31.20"))) + require.True(t, semver.New("0.31.20-patch.1+without-adx").Equal(*semver.New("0.31.20-patch.1"))) + require.True(t, semver.New("0.31.20+without-netgo-without-adx").Equal(*semver.New("0.31.20"))) + require.True(t, semver.New("0.31.20+arm").Equal(*semver.New("0.31.20"))) } diff --git a/engine/execution/ingestion/uploader/mock/uploader.go b/engine/execution/ingestion/uploader/mock/uploader.go index 32aea526dd8..dcae1ebd24f 100644 --- a/engine/execution/ingestion/uploader/mock/uploader.go +++ b/engine/execution/ingestion/uploader/mock/uploader.go @@ -3,8 +3,9 @@ package mock import ( - execution "github.com/onflow/flow-go/engine/execution" mock "github.com/stretchr/testify/mock" + + execution "github.com/onflow/flow-go/engine/execution" ) // Uploader is an autogenerated mock type for the Uploader type diff --git a/engine/execution/mock/register_store_notifier.go b/engine/execution/mock/register_store_notifier.go new file mode 100644 index 00000000000..7959e9e766b --- /dev/null +++ b/engine/execution/mock/register_store_notifier.go @@ -0,0 +1,30 @@ +// Code generated by mockery v2.21.4. DO NOT EDIT. + +package mock + +import mock "github.com/stretchr/testify/mock" + +// RegisterStoreNotifier is an autogenerated mock type for the RegisterStoreNotifier type +type RegisterStoreNotifier struct { + mock.Mock +} + +// OnFinalizedAndExecutedHeightUpdated provides a mock function with given fields: height +func (_m *RegisterStoreNotifier) OnFinalizedAndExecutedHeightUpdated(height uint64) { + _m.Called(height) +} + +type mockConstructorTestingTNewRegisterStoreNotifier interface { + mock.TestingT + Cleanup(func()) +} + +// NewRegisterStoreNotifier creates a new instance of RegisterStoreNotifier. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewRegisterStoreNotifier(t mockConstructorTestingTNewRegisterStoreNotifier) *RegisterStoreNotifier { + mock := &RegisterStoreNotifier{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/engine/execution/scripts/engine.go b/engine/execution/scripts/engine.go index 689d7858223..ea46f273d73 100644 --- a/engine/execution/scripts/engine.go +++ b/engine/execution/scripts/engine.go @@ -74,7 +74,7 @@ func (e *Engine) GetRegisterAtBlockID( return nil, fmt.Errorf("failed to create storage snapshot: %w", err) } - id := flow.NewRegisterID(string(owner), string(key)) + id := flow.NewRegisterID(flow.BytesToAddress(owner), string(key)) data, err := blockSnapshot.Get(id) if err != nil { return nil, fmt.Errorf("failed to get the register (%s): %w", id, err) diff --git a/engine/execution/state/bootstrap/bootstrap.go b/engine/execution/state/bootstrap/bootstrap.go index 7bc96dddb6c..97656092d09 100644 --- a/engine/execution/state/bootstrap/bootstrap.go +++ b/engine/execution/state/bootstrap/bootstrap.go @@ -144,10 +144,10 @@ func (b *Bootstrapper) BootstrapExecutionDatabase( return nil } -func ImportRegistersFromCheckpoint(logger zerolog.Logger, checkpointFile string, checkpointHeight uint64, pdb *pebble.DB, workerCount int) error { - logger.Info().Msgf("importing registers from checkpoint file %s at height %d", checkpointFile, checkpointHeight) +func ImportRegistersFromCheckpoint(logger zerolog.Logger, checkpointFile string, checkpointHeight uint64, checkpointRootHash ledger.RootHash, pdb *pebble.DB, workerCount int) error { + logger.Info().Msgf("importing registers from checkpoint file %s at height %d with root hash: %v", checkpointFile, checkpointHeight, checkpointRootHash) - bootstrap, err := pStorage.NewRegisterBootstrap(pdb, checkpointFile, checkpointHeight, logger) + bootstrap, err := pStorage.NewRegisterBootstrap(pdb, checkpointFile, checkpointHeight, checkpointRootHash, logger) if err != nil { return fmt.Errorf("could not create registers bootstrapper: %w", err) } diff --git a/engine/execution/state/bootstrap/bootstrap_test.go b/engine/execution/state/bootstrap/bootstrap_test.go index 1231434b3af..7ef8bc35d84 100644 --- a/engine/execution/state/bootstrap/bootstrap_test.go +++ b/engine/execution/state/bootstrap/bootstrap_test.go @@ -53,7 +53,7 @@ func TestBootstrapLedger(t *testing.T) { } func TestBootstrapLedger_ZeroTokenSupply(t *testing.T) { - expectedStateCommitmentBytes, _ := hex.DecodeString("33ff069b59f8eab1e58a15875bf859ebfc2068432c5cd7680ca084dbfc6c60ed") + expectedStateCommitmentBytes, _ := hex.DecodeString("8d9d52a66a832898f6f2416b703759b7ecd1eb390db6d5e727c2daeec001ffc6") expectedStateCommitment, err := flow.ToStateCommitment(expectedStateCommitmentBytes) require.NoError(t, err) diff --git a/engine/execution/state/mock/execution_state.go b/engine/execution/state/mock/execution_state.go index 88080cf2f50..a3d5903b210 100644 --- a/engine/execution/state/mock/execution_state.go +++ b/engine/execution/state/mock/execution_state.go @@ -138,6 +138,20 @@ func (_m *ExecutionState) GetHighestExecutedBlockID(_a0 context.Context) (uint64 return r0, r1, r2 } +// GetHighestFinalizedExecuted provides a mock function with given fields: +func (_m *ExecutionState) GetHighestFinalizedExecuted() uint64 { + ret := _m.Called() + + var r0 uint64 + if rf, ok := ret.Get(0).(func() uint64); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(uint64) + } + + return r0 +} + // IsBlockExecuted provides a mock function with given fields: height, blockID func (_m *ExecutionState) IsBlockExecuted(height uint64, blockID flow.Identifier) (bool, error) { ret := _m.Called(height, blockID) diff --git a/engine/execution/state/state.go b/engine/execution/state/state.go index 17e00df0834..9f2de2807ef 100644 --- a/engine/execution/state/state.go +++ b/engine/execution/state/state.go @@ -84,6 +84,10 @@ type ExecutionState interface { ctx context.Context, result *execution.ComputationResult, ) error + + // only available with storehouse enabled + // panic when called with storehouse disabled (which should be a bug) + GetHighestFinalizedExecuted() uint64 } type state struct { @@ -483,11 +487,11 @@ func (s *state) GetHighestExecutedBlockID(ctx context.Context) (uint64, flow.Ide // when storehouse is enabled, the highest executed block is consisted as // the highest finalized and executed block height := s.GetHighestFinalizedExecuted() - header, err := s.headers.ByHeight(height) + finalizedID, err := s.headers.BlockIDByHeight(height) if err != nil { return 0, flow.ZeroID, fmt.Errorf("could not get header by height %v: %w", height, err) } - return height, header.ID(), nil + return height, finalizedID, nil } var blockID flow.Identifier diff --git a/engine/execution/state/state_storehouse_test.go b/engine/execution/state/state_storehouse_test.go index 60cd4851f5c..70f30563525 100644 --- a/engine/execution/state/state_storehouse_test.go +++ b/engine/execution/state/state_storehouse_test.go @@ -10,7 +10,7 @@ import ( "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - "github.com/onflow/flow-go/crypto" + "github.com/onflow/crypto" "github.com/onflow/flow-go/engine/execution" "github.com/onflow/flow-go/engine/execution/state" @@ -114,7 +114,7 @@ func withRegisterStore(t *testing.T, fn func( log := unittest.Logger() var wal execution.ExecutedFinalizedWAL finalized, headerByHeight, highest := testutil.NewMockFinalizedReader(10, 100) - rs, err := storehouse.NewRegisterStore(diskStore, wal, finalized, log) + rs, err := storehouse.NewRegisterStore(diskStore, wal, finalized, log, storehouse.NewNoopNotifier()) require.NoError(t, err) fn(t, rs, diskStore, finalized, 10, highest, headerByHeight) }) diff --git a/engine/execution/state/unittest/fixtures.go b/engine/execution/state/unittest/fixtures.go index 117f9e7ed19..26bc0584fcb 100644 --- a/engine/execution/state/unittest/fixtures.go +++ b/engine/execution/state/unittest/fixtures.go @@ -4,9 +4,9 @@ import ( "context" "testing" + "github.com/onflow/crypto" "github.com/stretchr/testify/require" - "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/engine/execution" "github.com/onflow/flow-go/fvm/meter" "github.com/onflow/flow-go/fvm/storage/snapshot" diff --git a/engine/execution/storehouse.go b/engine/execution/storehouse.go index d6682ea5e43..47864217c36 100644 --- a/engine/execution/storehouse.go +++ b/engine/execution/storehouse.go @@ -57,6 +57,11 @@ type RegisterStore interface { IsBlockExecuted(height uint64, blockID flow.Identifier) (bool, error) } +// RegisterStoreNotifier is the interface for register store to notify when a block is finalized and executed +type RegisterStoreNotifier interface { + OnFinalizedAndExecutedHeightUpdated(height uint64) +} + type FinalizedReader interface { // FinalizedBlockIDAtHeight returns the block ID of the finalized block at the given height. // It return storage.NotFound if the given height has not been finalized yet diff --git a/engine/execution/storehouse/register_store.go b/engine/execution/storehouse/register_store.go index fafe0903b74..e52e4d1fb5f 100644 --- a/engine/execution/storehouse/register_store.go +++ b/engine/execution/storehouse/register_store.go @@ -20,16 +20,30 @@ type RegisterStore struct { finalized execution.FinalizedReader log zerolog.Logger finalizing *atomic.Bool // making sure only one goroutine is finalizing at a time + notifier execution.RegisterStoreNotifier } var _ execution.RegisterStore = (*RegisterStore)(nil) +type NoopNotifier struct{} + +func NewNoopNotifier() *NoopNotifier { return &NoopNotifier{} } + +func (n *NoopNotifier) OnFinalizedAndExecutedHeightUpdated(height uint64) {} + +var _ execution.RegisterStoreNotifier = (*NoopNotifier)(nil) + func NewRegisterStore( diskStore execution.OnDiskRegisterStore, wal execution.ExecutedFinalizedWAL, finalized execution.FinalizedReader, log zerolog.Logger, + notifier execution.RegisterStoreNotifier, ) (*RegisterStore, error) { + if notifier == nil { + return nil, fmt.Errorf("notifier is empty, use NoopNotifier if you don't need it") + } + // replay the executed and finalized blocks from the write ahead logs // to the OnDiskRegisterStore height, err := syncDiskStore(wal, diskStore, log) @@ -55,6 +69,7 @@ func NewRegisterStore( finalized: finalized, finalizing: atomic.NewBool(false), log: log.With().Str("module", "register-store").Logger(), + notifier: notifier, }, nil } @@ -195,6 +210,8 @@ func (r *RegisterStore) onBlockFinalized() error { return fmt.Errorf("cannot save %v registers to disk store for height %v: %w", len(regs), next, err) } + r.notifier.OnFinalizedAndExecutedHeightUpdated(next) + err = r.memStore.Prune(next, blockID) if err != nil { return fmt.Errorf("cannot prune memStore for height %v: %w", next, err) diff --git a/engine/execution/storehouse/register_store_metrics.go b/engine/execution/storehouse/register_store_metrics.go new file mode 100644 index 00000000000..1542b29d384 --- /dev/null +++ b/engine/execution/storehouse/register_store_metrics.go @@ -0,0 +1,22 @@ +package storehouse + +import ( + "github.com/onflow/flow-go/engine/execution" + "github.com/onflow/flow-go/module" +) + +type RegisterStoreMetrics struct { + collector module.ExecutionMetrics +} + +var _ execution.RegisterStoreNotifier = (*RegisterStoreMetrics)(nil) + +func NewRegisterStoreMetrics(collector module.ExecutionMetrics) *RegisterStoreMetrics { + return &RegisterStoreMetrics{ + collector: collector, + } +} + +func (m *RegisterStoreMetrics) OnFinalizedAndExecutedHeightUpdated(height uint64) { + m.collector.ExecutionLastFinalizedExecutedBlockHeight(height) +} diff --git a/engine/execution/storehouse/register_store_test.go b/engine/execution/storehouse/register_store_test.go index 5b2f9594c08..7435842311e 100644 --- a/engine/execution/storehouse/register_store_test.go +++ b/engine/execution/storehouse/register_store_test.go @@ -15,6 +15,14 @@ import ( "github.com/onflow/flow-go/utils/unittest" ) +type notifier struct { + height uint64 +} + +func (n *notifier) OnFinalizedAndExecutedHeightUpdated(height uint64) { + n.height = height +} + func withRegisterStore(t *testing.T, fn func( t *testing.T, rs *storehouse.RegisterStore, @@ -23,14 +31,16 @@ func withRegisterStore(t *testing.T, fn func( rootHeight uint64, endHeight uint64, headers map[uint64]*flow.Header, + n *notifier, )) { pebble.RunWithRegistersStorageAtInitialHeights(t, 10, 10, func(diskStore *pebble.Registers) { log := unittest.Logger() var wal execution.ExecutedFinalizedWAL finalized, headerByHeight, highest := testutil.NewMockFinalizedReader(10, 100) - rs, err := storehouse.NewRegisterStore(diskStore, wal, finalized, log) + n := ¬ifier{height: 10} + rs, err := storehouse.NewRegisterStore(diskStore, wal, finalized, log, n) require.NoError(t, err) - fn(t, rs, diskStore, finalized, 10, highest, headerByHeight) + fn(t, rs, diskStore, finalized, 10, highest, headerByHeight, n) }) } @@ -49,6 +59,7 @@ func TestRegisterStoreGetRegisterFail(t *testing.T) { rootHeight uint64, endHeight uint64, headerByHeight map[uint64]*flow.Header, + n *notifier, ) { // unknown block _, err := rs.GetRegister(rootHeight+1, unknownBlock, unknownReg.Key) @@ -88,6 +99,7 @@ func TestRegisterStoreSaveRegistersShouldFail(t *testing.T) { rootHeight uint64, endHeight uint64, headerByHeight map[uint64]*flow.Header, + n *notifier, ) { wrongParent := unittest.BlockHeaderFixture(unittest.WithHeaderHeight(rootHeight + 1)) err := rs.SaveRegisters(wrongParent, flow.RegisterEntries{}) @@ -117,6 +129,7 @@ func TestRegisterStoreSaveRegistersShouldOK(t *testing.T) { rootHeight uint64, endHeight uint64, headerByHeight map[uint64]*flow.Header, + n *notifier, ) { // not executed executed, err := rs.IsBlockExecuted(rootHeight+1, headerByHeight[rootHeight+1].ID()) @@ -169,6 +182,7 @@ func TestRegisterStoreIsBlockExecuted(t *testing.T) { rootHeight uint64, endHeight uint64, headerByHeight map[uint64]*flow.Header, + n *notifier, ) { // save block 11 reg := makeReg("X", "1") @@ -214,6 +228,7 @@ func TestRegisterStoreReadingFromDisk(t *testing.T) { rootHeight uint64, endHeight uint64, headerByHeight map[uint64]*flow.Header, + n *notifier, ) { // R <- 11 (X: 1, Y: 2) <- 12 (Y: 3) <- 13 (X: 4) @@ -229,9 +244,13 @@ func TestRegisterStoreReadingFromDisk(t *testing.T) { err = rs.SaveRegisters(headerByHeight[rootHeight+3], flow.RegisterEntries{makeReg("X", "4")}) require.NoError(t, err) + require.Equal(t, rootHeight, n.height) + require.NoError(t, finalized.MockFinal(rootHeight+2)) require.NoError(t, rs.OnBlockFinalized()) // notify 12 is finalized + require.Equal(t, rootHeight+2, n.height) + val, err := rs.GetRegister(rootHeight+1, headerByHeight[rootHeight+1].ID(), makeReg("Y", "2").Key) require.NoError(t, err) // value at block 11 is now stored in OnDiskRegisterStore, which is 2 @@ -262,6 +281,7 @@ func TestRegisterStoreReadingFromInMemStore(t *testing.T) { rootHeight uint64, endHeight uint64, headerByHeight map[uint64]*flow.Header, + n *notifier, ) { // R <- 11 (X: 1, Y: 2) <- 12 (Y: 3) @@ -321,6 +341,7 @@ func TestRegisterStoreReadRegisterAtPrunedHeight(t *testing.T) { rootHeight uint64, endHeight uint64, headerByHeight map[uint64]*flow.Header, + n *notifier, ) { // R <- 11 (X: 1) @@ -371,6 +392,7 @@ func TestRegisterStoreExecuteFinalizedBlockOrFinalizeExecutedBlockShouldNotCallF rootHeight uint64, endHeight uint64, headerByHeight map[uint64]*flow.Header, + n *notifier, ) { require.Equal(t, 1, finalized.FinalizedCalled()) // called by NewRegisterStore @@ -428,6 +450,7 @@ func TestRegisterStoreExecuteFirstFinalizeLater(t *testing.T) { rootHeight uint64, endHeight uint64, headerByHeight map[uint64]*flow.Header, + n *notifier, ) { // save block 11 err := rs.SaveRegisters(headerByHeight[rootHeight+1], flow.RegisterEntries{makeReg("X", "1")}) @@ -444,17 +467,22 @@ func TestRegisterStoreExecuteFirstFinalizeLater(t *testing.T) { require.NoError(t, err) require.Equal(t, rootHeight, rs.LastFinalizedAndExecutedHeight()) + require.Equal(t, rootHeight, n.height) + require.NoError(t, finalized.MockFinal(rootHeight+1)) require.NoError(t, rs.OnBlockFinalized()) // notify 11 is finalized require.Equal(t, rootHeight+1, rs.LastFinalizedAndExecutedHeight()) + require.Equal(t, rootHeight+1, n.height) require.NoError(t, finalized.MockFinal(rootHeight+2)) require.NoError(t, rs.OnBlockFinalized()) // notify 12 is finalized require.Equal(t, rootHeight+2, rs.LastFinalizedAndExecutedHeight()) + require.Equal(t, rootHeight+2, n.height) require.NoError(t, finalized.MockFinal(rootHeight+3)) require.NoError(t, rs.OnBlockFinalized()) // notify 13 is finalized require.Equal(t, rootHeight+3, rs.LastFinalizedAndExecutedHeight()) + require.Equal(t, rootHeight+3, n.height) }) } @@ -474,6 +502,7 @@ func TestRegisterStoreFinalizeFirstExecuteLater(t *testing.T) { rootHeight uint64, endHeight uint64, headerByHeight map[uint64]*flow.Header, + n *notifier, ) { require.NoError(t, finalized.MockFinal(rootHeight+1)) require.NoError(t, rs.OnBlockFinalized()) // notify 11 is finalized @@ -487,20 +516,25 @@ func TestRegisterStoreFinalizeFirstExecuteLater(t *testing.T) { require.NoError(t, rs.OnBlockFinalized()) // notify 13 is finalized require.Equal(t, rootHeight, rs.LastFinalizedAndExecutedHeight()) + require.Equal(t, rootHeight, n.height) + // save block 11 err := rs.SaveRegisters(headerByHeight[rootHeight+1], flow.RegisterEntries{makeReg("X", "1")}) require.NoError(t, err) require.Equal(t, rootHeight+1, rs.LastFinalizedAndExecutedHeight()) + require.Equal(t, rootHeight+1, n.height) // save block 12 err = rs.SaveRegisters(headerByHeight[rootHeight+2], flow.RegisterEntries{makeReg("X", "2")}) require.NoError(t, err) require.Equal(t, rootHeight+2, rs.LastFinalizedAndExecutedHeight()) + require.Equal(t, rootHeight+2, n.height) // save block 13 err = rs.SaveRegisters(headerByHeight[rootHeight+3], flow.RegisterEntries{makeReg("X", "3")}) require.NoError(t, err) require.Equal(t, rootHeight+3, rs.LastFinalizedAndExecutedHeight()) + require.Equal(t, rootHeight+3, n.height) }) } @@ -517,6 +551,7 @@ func TestRegisterStoreConcurrentFinalizeAndExecute(t *testing.T) { rootHeight uint64, endHeight uint64, headerByHeight map[uint64]*flow.Header, + n *notifier, ) { var wg sync.WaitGroup diff --git a/engine/execution/testutil/fixtures.go b/engine/execution/testutil/fixtures.go index 3113f2df9af..8529d3d2d8a 100644 --- a/engine/execution/testutil/fixtures.go +++ b/engine/execution/testutil/fixtures.go @@ -13,8 +13,9 @@ import ( "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - "github.com/onflow/flow-go/crypto" - "github.com/onflow/flow-go/crypto/hash" + "github.com/onflow/crypto" + "github.com/onflow/crypto/hash" + "github.com/onflow/flow-go/engine/execution" "github.com/onflow/flow-go/engine/execution/utils" "github.com/onflow/flow-go/fvm" diff --git a/engine/execution/utils/hasher.go b/engine/execution/utils/hasher.go index 81cf55ddba1..1ef8b87e036 100644 --- a/engine/execution/utils/hasher.go +++ b/engine/execution/utils/hasher.go @@ -3,7 +3,8 @@ package utils import ( "fmt" - "github.com/onflow/flow-go/crypto/hash" + "github.com/onflow/crypto/hash" + "github.com/onflow/flow-go/module/signature" ) diff --git a/engine/testutil/mocklocal/local.go b/engine/testutil/mocklocal/local.go index a4a819797b5..6e8a0385792 100644 --- a/engine/testutil/mocklocal/local.go +++ b/engine/testutil/mocklocal/local.go @@ -1,11 +1,11 @@ package mocklocal import ( + "github.com/onflow/crypto" + "github.com/onflow/crypto/hash" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - "github.com/onflow/flow-go/crypto" - "github.com/onflow/flow-go/crypto/hash" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/model/flow/filter" ) diff --git a/engine/testutil/nodes.go b/engine/testutil/nodes.go index c6175d3bb01..cdbd6526181 100644 --- a/engine/testutil/nodes.go +++ b/engine/testutil/nodes.go @@ -13,6 +13,7 @@ import ( "github.com/ipfs/go-datastore" dssync "github.com/ipfs/go-datastore/sync" blockstore "github.com/ipfs/go-ipfs-blockstore" + "github.com/onflow/crypto" "github.com/rs/zerolog" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" @@ -26,7 +27,6 @@ import ( "github.com/onflow/flow-go/consensus/hotstuff/model" "github.com/onflow/flow-go/consensus/hotstuff/notifications" "github.com/onflow/flow-go/consensus/hotstuff/notifications/pubsub" - "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/engine" "github.com/onflow/flow-go/engine/collection/epochmgr" "github.com/onflow/flow-go/engine/collection/epochmgr/factories" @@ -462,7 +462,13 @@ func ConsensusNode(t *testing.T, hub *stub.Hub, identity *flow.Identity, identit assigner, err := chunks.NewChunkAssigner(flow.DefaultChunkAssignmentAlpha, node.State) require.Nil(t, err) - receiptValidator := validation.NewReceiptValidator(node.State, node.Headers, node.Index, resultsDB, node.Seals) + receiptValidator := validation.NewReceiptValidator( + node.State, + node.Headers, + node.Index, + resultsDB, + node.Seals, + ) sealingEngine, err := sealing.NewEngine( node.Log, @@ -637,7 +643,7 @@ func ExecutionNode(t *testing.T, hub *stub.Hub, identity *flow.Identity, identit require.NoError(t, err) checkpointHeight := uint64(0) - require.NoError(t, esbootstrap.ImportRegistersFromCheckpoint(node.Log, checkpointFile, checkpointHeight, pebbledb, 2)) + require.NoError(t, esbootstrap.ImportRegistersFromCheckpoint(node.Log, checkpointFile, checkpointHeight, matchTrie.RootHash(), pebbledb, 2)) diskStore, err := storagepebble.NewRegisters(pebbledb) require.NoError(t, err) @@ -647,7 +653,9 @@ func ExecutionNode(t *testing.T, hub *stub.Hub, identity *flow.Identity, identit diskStore, nil, // TOOD(leo): replace with real WAL reader, - node.Log) + node.Log, + storehouse.NewNoopNotifier(), + ) require.NoError(t, err) storehouseEnabled := true @@ -755,7 +763,6 @@ func ExecutionNode(t *testing.T, hub *stub.Hub, identity *flow.Identity, identit unit, node.Log, node.Net, - node.Me, fetcher, node.Headers, node.Blocks, diff --git a/engine/verification/utils/hasher.go b/engine/verification/utils/hasher.go index 56ab130aaf4..b2fef99c640 100644 --- a/engine/verification/utils/hasher.go +++ b/engine/verification/utils/hasher.go @@ -1,7 +1,8 @@ package utils import ( - "github.com/onflow/flow-go/crypto/hash" + "github.com/onflow/crypto/hash" + "github.com/onflow/flow-go/module/signature" ) diff --git a/engine/verification/utils/unittest/helper.go b/engine/verification/utils/unittest/helper.go index 62f26cd7f70..ff837456624 100644 --- a/engine/verification/utils/unittest/helper.go +++ b/engine/verification/utils/unittest/helper.go @@ -7,6 +7,7 @@ import ( "testing" "time" + "github.com/onflow/crypto" "github.com/rs/zerolog" "github.com/rs/zerolog/log" "github.com/stretchr/testify/assert" @@ -14,7 +15,6 @@ import ( "github.com/stretchr/testify/require" "github.com/onflow/flow-go/consensus/hotstuff/model" - "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/engine/testutil" enginemock "github.com/onflow/flow-go/engine/testutil/mock" "github.com/onflow/flow-go/engine/verification/assigner/blockconsumer" diff --git a/engine/verification/verifier/engine.go b/engine/verification/verifier/engine.go index d63975dbaff..fd7f6571f6e 100644 --- a/engine/verification/verifier/engine.go +++ b/engine/verification/verifier/engine.go @@ -4,11 +4,11 @@ import ( "context" "fmt" + "github.com/onflow/crypto" + "github.com/onflow/crypto/hash" "github.com/rs/zerolog" "go.opentelemetry.io/otel/attribute" - "github.com/onflow/flow-go/crypto" - "github.com/onflow/flow-go/crypto/hash" "github.com/onflow/flow-go/engine" "github.com/onflow/flow-go/engine/verification/utils" chmodels "github.com/onflow/flow-go/model/chunks" diff --git a/engine/verification/verifier/engine_test.go b/engine/verification/verifier/engine_test.go index 2bcb963d516..ee24d16eba1 100644 --- a/engine/verification/verifier/engine_test.go +++ b/engine/verification/verifier/engine_test.go @@ -10,8 +10,8 @@ import ( "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" - "github.com/onflow/flow-go/crypto" - "github.com/onflow/flow-go/crypto/hash" + "github.com/onflow/crypto" + "github.com/onflow/crypto/hash" "github.com/onflow/flow-go/engine/testutil/mocklocal" "github.com/onflow/flow-go/engine/verification/utils" diff --git a/follower/consensus_follower.go b/follower/consensus_follower.go index 56863bcf530..7eecf02300f 100644 --- a/follower/consensus_follower.go +++ b/follower/consensus_follower.go @@ -6,12 +6,12 @@ import ( "sync" "github.com/dgraph-io/badger/v2" + "github.com/onflow/crypto" "github.com/rs/zerolog" "github.com/onflow/flow-go/cmd" "github.com/onflow/flow-go/consensus/hotstuff/model" "github.com/onflow/flow-go/consensus/hotstuff/notifications/pubsub" - "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/module/chainsync" "github.com/onflow/flow-go/module/compliance" diff --git a/follower/follower_builder.go b/follower/follower_builder.go index ad57676420b..5e2edd31eb0 100644 --- a/follower/follower_builder.go +++ b/follower/follower_builder.go @@ -11,6 +11,7 @@ import ( "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/peer" "github.com/libp2p/go-libp2p/core/routing" + "github.com/onflow/crypto" "github.com/rs/zerolog" "github.com/onflow/flow-go/cmd" @@ -24,7 +25,6 @@ import ( hotstuffvalidator "github.com/onflow/flow-go/consensus/hotstuff/validator" "github.com/onflow/flow-go/consensus/hotstuff/verification" recovery "github.com/onflow/flow-go/consensus/recovery/protocol" - "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/engine/common/follower" synceng "github.com/onflow/flow-go/engine/common/synchronization" "github.com/onflow/flow-go/model/encodable" @@ -44,20 +44,19 @@ import ( cborcodec "github.com/onflow/flow-go/network/codec/cbor" "github.com/onflow/flow-go/network/converter" "github.com/onflow/flow-go/network/p2p" + p2pbuilder "github.com/onflow/flow-go/network/p2p/builder" + p2pbuilderconfig "github.com/onflow/flow-go/network/p2p/builder/config" "github.com/onflow/flow-go/network/p2p/cache" "github.com/onflow/flow-go/network/p2p/conduit" p2pdht "github.com/onflow/flow-go/network/p2p/dht" "github.com/onflow/flow-go/network/p2p/keyutils" - "github.com/onflow/flow-go/network/p2p/p2pbuilder" - p2pconfig "github.com/onflow/flow-go/network/p2p/p2pbuilder/config" - "github.com/onflow/flow-go/network/p2p/p2plogging" - "github.com/onflow/flow-go/network/p2p/p2pnet" + p2plogging "github.com/onflow/flow-go/network/p2p/logging" "github.com/onflow/flow-go/network/p2p/subscription" - "github.com/onflow/flow-go/network/p2p/tracer" "github.com/onflow/flow-go/network/p2p/translator" "github.com/onflow/flow-go/network/p2p/unicast/protocols" "github.com/onflow/flow-go/network/p2p/utils" "github.com/onflow/flow-go/network/slashing" + "github.com/onflow/flow-go/network/underlay" "github.com/onflow/flow-go/network/validator" "github.com/onflow/flow-go/state/protocol" badgerState "github.com/onflow/flow-go/state/protocol/badger" @@ -573,21 +572,10 @@ func (builder *FollowerServiceBuilder) initPublicLibp2pNode(networkKey crypto.Pr pis = append(pis, pi) } - meshTracerCfg := &tracer.GossipSubMeshTracerConfig{ - Logger: builder.Logger, - Metrics: builder.Metrics.Network, - IDProvider: builder.IdentityProvider, - LoggerInterval: builder.FlowConfig.NetworkConfig.GossipSubConfig.LocalMeshLogInterval, - RpcSentTrackerCacheSize: builder.FlowConfig.NetworkConfig.GossipSubConfig.RPCSentTrackerCacheSize, - RpcSentTrackerWorkerQueueCacheSize: builder.FlowConfig.NetworkConfig.GossipSubConfig.RPCSentTrackerQueueCacheSize, - RpcSentTrackerNumOfWorkers: builder.FlowConfig.NetworkConfig.GossipSubConfig.RpcSentTrackerNumOfWorkers, - HeroCacheMetricsFactory: builder.HeroCacheMetricsFactory(), - NetworkingType: network.PublicNetwork, - } - meshTracer := tracer.NewGossipSubMeshTracer(meshTracerCfg) - - node, err := p2pbuilder.NewNodeBuilder(builder.Logger, - &p2pconfig.MetricsConfig{ + node, err := p2pbuilder.NewNodeBuilder( + builder.Logger, + &builder.FlowConfig.NetworkConfig.GossipSub, + &p2pbuilderconfig.MetricsConfig{ HeroCacheFactory: builder.HeroCacheMetricsFactory(), Metrics: builder.Metrics.Network, }, @@ -596,17 +584,14 @@ func (builder *FollowerServiceBuilder) initPublicLibp2pNode(networkKey crypto.Pr networkKey, builder.SporkID, builder.IdentityProvider, - builder.FlowConfig.NetworkConfig.GossipSubConfig.GossipSubScoringRegistryConfig, &builder.FlowConfig.NetworkConfig.ResourceManager, - &builder.FlowConfig.NetworkConfig.GossipSubConfig, - p2pconfig.PeerManagerDisableConfig(), // disable peer manager for follower + p2pbuilderconfig.PeerManagerDisableConfig(), // disable peer manager for follower &p2p.DisallowListCacheConfig{ MaxSize: builder.FlowConfig.NetworkConfig.DisallowListNotificationCacheSize, Metrics: metrics.DisallowListCacheMetricsFactory(builder.HeroCacheMetricsFactory(), network.PublicNetwork), }, - meshTracer, - &p2pconfig.UnicastConfig{ - UnicastConfig: builder.FlowConfig.NetworkConfig.UnicastConfig, + &p2pbuilderconfig.UnicastConfig{ + Unicast: builder.FlowConfig.NetworkConfig.Unicast, }). SetSubscriptionFilter( subscription.NewRoleBasedFilter( @@ -620,10 +605,7 @@ func (builder *FollowerServiceBuilder) initPublicLibp2pNode(networkKey crypto.Pr p2pdht.AsClient(), dht.BootstrapPeers(pis...), ) - }). - SetGossipSubTracer(meshTracer). - SetGossipSubScoreTracerInterval(builder.FlowConfig.NetworkConfig.GossipSubConfig.ScoreTracerInterval). - Build() + }).Build() if err != nil { return nil, fmt.Errorf("could not build public libp2p node: %w", err) } @@ -686,7 +668,7 @@ func (builder *FollowerServiceBuilder) enqueuePublicNetworkInit() { return nil, fmt.Errorf("could not register networking receive cache metric: %w", err) } - net, err := p2pnet.NewNetwork(&p2pnet.NetworkConfig{ + net, err := underlay.NewNetwork(&underlay.NetworkConfig{ Logger: builder.Logger.With().Str("component", "public-network").Logger(), Codec: cborcodec.NewCodec(), Me: builder.Me, @@ -698,7 +680,7 @@ func (builder *FollowerServiceBuilder) enqueuePublicNetworkInit() { ReceiveCache: receiveCache, ConduitFactory: conduit.NewDefaultConduitFactory(), SporkId: builder.SporkID, - UnicastMessageTimeout: p2pnet.DefaultUnicastTimeout, + UnicastMessageTimeout: underlay.DefaultUnicastTimeout, IdentityTranslator: builder.IDTranslator, AlspCfg: &alspmgr.MisbehaviorReportManagerConfig{ Logger: builder.Logger, @@ -713,7 +695,7 @@ func (builder *FollowerServiceBuilder) enqueuePublicNetworkInit() { SlashingViolationConsumerFactory: func(adapter network.ConduitAdapter) network.ViolationsConsumer { return slashing.NewSlashingViolationsConsumer(builder.Logger, builder.Metrics.Network, adapter) }, - }, p2pnet.WithMessageValidators(publicNetworkMsgValidators(node.Logger, node.IdentityProvider, node.NodeID)...)) + }, underlay.WithMessageValidators(publicNetworkMsgValidators(node.Logger, node.IdentityProvider, node.NodeID)...)) if err != nil { return nil, fmt.Errorf("could not initialize network: %w", err) } diff --git a/fvm/blueprints/fees.go b/fvm/blueprints/fees.go index 486dda41886..197ceb883b4 100644 --- a/fvm/blueprints/fees.go +++ b/fvm/blueprints/fees.go @@ -36,6 +36,9 @@ var setupParametersTransactionTemplate string //go:embed scripts/setupStorageForServiceAccountsTemplate.cdc var setupStorageForServiceAccountsTemplate string +//go:embed scripts/setupStorageForAccount.cdc +var setupStorageForAccountTemplate string + //go:embed scripts/setupFeesTransactionTemplate.cdc var setupFeesTransactionTemplate string @@ -116,6 +119,22 @@ func SetupStorageForServiceAccountsTransaction( AddAuthorizer(feeContract) } +func SetupStorageForAccountTransaction( + account, service, fungibleToken, flowToken flow.Address, +) *flow.TransactionBody { + return flow.NewTransactionBody(). + SetScript([]byte(templates.ReplaceAddresses(setupStorageForAccountTemplate, + templates.Environment{ + ServiceAccountAddress: service.Hex(), + StorageFeesAddress: service.Hex(), + FungibleTokenAddress: fungibleToken.Hex(), + FlowTokenAddress: flowToken.Hex(), + })), + ). + AddAuthorizer(account). + AddAuthorizer(service) +} + func SetupFeesTransaction( service flow.Address, flowFees flow.Address, diff --git a/fvm/blueprints/scripts/setupStorageForAccount.cdc b/fvm/blueprints/scripts/setupStorageForAccount.cdc new file mode 100644 index 00000000000..016172bc7a7 --- /dev/null +++ b/fvm/blueprints/scripts/setupStorageForAccount.cdc @@ -0,0 +1,25 @@ +import FlowServiceAccount from 0xFLOWSERVICEADDRESS +import FlowStorageFees from 0xFLOWSTORAGEFEESADDRESS +import FungibleToken from 0xFUNGIBLETOKENADDRESS +import FlowToken from 0xFLOWTOKENADDRESS + +// This transaction sets up storage on a auth account. +// This is used during bootstrapping a local environment +transaction() { + prepare(account: AuthAccount, service: AuthAccount) { + + // take all the funds from the service account + let tokenVault = service.borrow<&FlowToken.Vault>(from: /storage/flowTokenVault) + ?? panic("Unable to borrow reference to the default token vault") + + let storageReservation <- tokenVault.withdraw(amount: FlowStorageFees.minimumStorageReservation) as! @FlowToken.Vault + let hasReceiver = account.getCapability(/public/flowTokenReceiver)!.check<&{FungibleToken.Receiver}>() + if !hasReceiver { + FlowServiceAccount.initDefaultToken(account) + } + let receiver = account.getCapability(/public/flowTokenReceiver)!.borrow<&{FungibleToken.Receiver}>() + ?? panic("Could not borrow receiver reference to the recipient's Vault") + + receiver.deposit(from: <-storageReservation) + } +} diff --git a/fvm/blueprints/system.go b/fvm/blueprints/system.go index 3683883c1e1..1dff14a0824 100644 --- a/fvm/blueprints/system.go +++ b/fvm/blueprints/system.go @@ -32,7 +32,7 @@ func SystemChunkTransaction(chain flow.Chain) (*flow.TransactionBody, error) { // The heartbeat resources needed by the system tx have are on the service account, // therefore, the service account is the only authorizer needed. AddAuthorizer(chain.ServiceAddress()). - SetGasLimit(SystemChunkTransactionGasLimit) + SetComputeLimit(SystemChunkTransactionGasLimit) return tx, nil } diff --git a/fvm/bootstrap.go b/fvm/bootstrap.go index c00ee80d289..ea1610a2304 100644 --- a/fvm/bootstrap.go +++ b/fvm/bootstrap.go @@ -76,7 +76,13 @@ type BootstrapParams struct { minimumStorageReservation cadence.UFix64 storagePerFlow cadence.UFix64 restrictedAccountCreationEnabled cadence.Bool - setupEVMEnabled cadence.Bool + + // `setupEVMEnabled` == true && `evmAbiOnly` == true will enable the ABI-only EVM + // `setupEVMEnabled` == true && `evmAbiOnly` == false will enable the full EVM functionality + // `setupEVMEnabled` == false will disable EVM + // This will allow to quickly disable the ABI-only EVM, in case there's a bug or something. + setupEVMEnabled cadence.Bool + evmAbiOnly cadence.Bool // versionFreezePeriod is the number of blocks in the future where the version // changes are frozen. The Node version beacon manages the freeze period, @@ -219,6 +225,13 @@ func WithSetupEVMEnabled(enabled cadence.Bool) BootstrapProcedureOption { } } +func WithEVMABIOnly(evmAbiOnly cadence.Bool) BootstrapProcedureOption { + return func(bp *BootstrapProcedure) *BootstrapProcedure { + bp.evmAbiOnly = evmAbiOnly + return bp + } +} + func WithRestrictedContractDeployment(restricted *bool) BootstrapProcedureOption { return func(bp *BootstrapProcedure) *BootstrapProcedure { bp.restrictedContractDeployment = restricted @@ -390,7 +403,7 @@ func (b *bootstrapExecutor) Execute() error { b.setStakingAllowlist(service, b.identities.NodeIDs()) // sets up the EVM environment - b.setupEVM(service, flowToken) + b.setupEVM(service, fungibleToken, flowToken) return nil } @@ -771,6 +784,22 @@ func (b *bootstrapExecutor) setupStorageForServiceAccounts( panicOnMetaInvokeErrf("failed to setup storage for service accounts: %s", txError, err) } +func (b *bootstrapExecutor) setupStorageForAccount( + account, service, fungibleToken, flowToken flow.Address, +) { + txError, err := b.invokeMetaTransaction( + b.ctx, + Transaction( + blueprints.SetupStorageForAccountTransaction( + account, + service, + fungibleToken, + flowToken), + 0), + ) + panicOnMetaInvokeErrf("failed to setup storage for service accounts: %s", txError, err) +} + func (b *bootstrapExecutor) setStakingAllowlist( service flow.Address, allowedIDs []flow.Identifier, @@ -788,12 +817,18 @@ func (b *bootstrapExecutor) setStakingAllowlist( panicOnMetaInvokeErrf("failed to set staking allow-list: %s", txError, err) } -func (b *bootstrapExecutor) setupEVM(serviceAddress, flowTokenAddress flow.Address) { +func (b *bootstrapExecutor) setupEVM(serviceAddress, fungibleTokenAddress, flowTokenAddress flow.Address) { if b.setupEVMEnabled { - b.createAccount(nil) // account for storage + // account for storage + // we dont need to deploy anything to this account, but it needs to exist + // so that we can store the EVM state on it + evmAcc := b.createAccount(nil) + b.setupStorageForAccount(evmAcc, serviceAddress, fungibleTokenAddress, flowTokenAddress) + + // deploy the EVM contract to the service account tx := blueprints.DeployContractTransaction( serviceAddress, - stdlib.ContractCode(flowTokenAddress), + stdlib.ContractCode(flowTokenAddress, bool(b.evmAbiOnly)), stdlib.ContractName, ) // WithEVMEnabled should only be used after we create an account for storage diff --git a/fvm/crypto/crypto.go b/fvm/crypto/crypto.go index 28d781f2801..bb802441435 100644 --- a/fvm/crypto/crypto.go +++ b/fvm/crypto/crypto.go @@ -5,9 +5,9 @@ import ( "fmt" "github.com/onflow/cadence/runtime" + "github.com/onflow/crypto" + "github.com/onflow/crypto/hash" - "github.com/onflow/flow-go/crypto" - "github.com/onflow/flow-go/crypto/hash" "github.com/onflow/flow-go/fvm/errors" "github.com/onflow/flow-go/model/flow" msig "github.com/onflow/flow-go/module/signature" diff --git a/fvm/crypto/crypto_test.go b/fvm/crypto/crypto_test.go index ffbdec3a730..ce46bc36edb 100644 --- a/fvm/crypto/crypto_test.go +++ b/fvm/crypto/crypto_test.go @@ -8,11 +8,11 @@ import ( "github.com/fxamacker/cbor/v2" "github.com/onflow/cadence/runtime" + onflowCrypto "github.com/onflow/crypto" + "github.com/onflow/crypto/hash" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - gocrypto "github.com/onflow/flow-go/crypto" - "github.com/onflow/flow-go/crypto/hash" "github.com/onflow/flow-go/fvm/crypto" "github.com/onflow/flow-go/fvm/errors" "github.com/onflow/flow-go/model/flow" @@ -90,7 +90,7 @@ func TestVerifySignatureFromRuntime(t *testing.T) { seed := make([]byte, seedLength) _, err := rand.Read(seed) require.NoError(t, err) - pk, err := gocrypto.GeneratePrivateKey(crypto.RuntimeToCryptoSigningAlgorithm(s), seed) + pk, err := onflowCrypto.GeneratePrivateKey(crypto.RuntimeToCryptoSigningAlgorithm(s), seed) require.NoError(t, err) tag := "random_tag" @@ -182,7 +182,7 @@ func TestVerifySignatureFromRuntime(t *testing.T) { seed := make([]byte, seedLength) _, err := rand.Read(seed) require.NoError(t, err) - pk, err := gocrypto.GeneratePrivateKey(gocrypto.BLSBLS12381, seed) + pk, err := onflowCrypto.GeneratePrivateKey(onflowCrypto.BLSBLS12381, seed) require.NoError(t, err) hasher := msig.NewBLSHasher(string(c.signTag)) @@ -265,7 +265,7 @@ func TestVerifySignatureFromRuntime(t *testing.T) { seed := make([]byte, seedLength) _, err := rand.Read(seed) require.NoError(t, err) - pk, err := gocrypto.GeneratePrivateKey(crypto.RuntimeToCryptoSigningAlgorithm(s), seed) + pk, err := onflowCrypto.GeneratePrivateKey(crypto.RuntimeToCryptoSigningAlgorithm(s), seed) require.NoError(t, err) hasher, err := crypto.NewPrefixedHashing(crypto.RuntimeToCryptoHashingAlgorithm(h), c.signTag) @@ -298,12 +298,12 @@ func TestVerifySignatureFromTransaction(t *testing.T) { // make sure the seed length is larger than miniumum seed lengths of all signature schemes seedLength := 64 - correctCombinations := map[gocrypto.SigningAlgorithm]map[hash.HashingAlgorithm]struct{}{ - gocrypto.ECDSAP256: { + correctCombinations := map[onflowCrypto.SigningAlgorithm]map[hash.HashingAlgorithm]struct{}{ + onflowCrypto.ECDSAP256: { hash.SHA2_256: {}, hash.SHA3_256: {}, }, - gocrypto.ECDSASecp256k1: { + onflowCrypto.ECDSASecp256k1: { hash.SHA2_256: {}, hash.SHA3_256: {}, }, @@ -311,10 +311,10 @@ func TestVerifySignatureFromTransaction(t *testing.T) { t.Run("verify should fail on incorrect combinations", func(t *testing.T) { - signatureAlgos := []gocrypto.SigningAlgorithm{ - gocrypto.ECDSAP256, - gocrypto.ECDSASecp256k1, - gocrypto.BLSBLS12381, + signatureAlgos := []onflowCrypto.SigningAlgorithm{ + onflowCrypto.ECDSAP256, + onflowCrypto.ECDSASecp256k1, + onflowCrypto.BLSBLS12381, } hashAlgos := []hash.HashingAlgorithm{ hash.SHA2_256, @@ -331,7 +331,7 @@ func TestVerifySignatureFromTransaction(t *testing.T) { seed := make([]byte, seedLength) _, err := rand.Read(seed) require.NoError(t, err) - sk, err := gocrypto.GeneratePrivateKey(s, seed) + sk, err := onflowCrypto.GeneratePrivateKey(s, seed) require.NoError(t, err) tag := string(flow.TransactionDomainTag[:]) @@ -403,7 +403,7 @@ func TestVerifySignatureFromTransaction(t *testing.T) { seed := make([]byte, seedLength) _, err := rand.Read(seed) require.NoError(t, err) - sk, err := gocrypto.GeneratePrivateKey(s, seed) + sk, err := onflowCrypto.GeneratePrivateKey(s, seed) require.NoError(t, err) hasher, err := crypto.NewPrefixedHashing(h, c.signTag) @@ -426,10 +426,10 @@ func TestVerifySignatureFromTransaction(t *testing.T) { func TestValidatePublicKey(t *testing.T) { validPublicKey := func(t *testing.T, s runtime.SignatureAlgorithm) []byte { - seed := make([]byte, gocrypto.KeyGenSeedMinLen) + seed := make([]byte, onflowCrypto.KeyGenSeedMinLen) _, err := rand.Read(seed) require.NoError(t, err) - sk, err := gocrypto.GeneratePrivateKey(crypto.RuntimeToCryptoSigningAlgorithm(s), seed) + sk, err := onflowCrypto.GeneratePrivateKey(crypto.RuntimeToCryptoSigningAlgorithm(s), seed) require.NoError(t, err) return sk.PublicKey().Encode() } @@ -490,10 +490,10 @@ func TestHashingAlgorithmConversion(t *testing.T) { } func TestSigningAlgorithmConversion(t *testing.T) { - signingAlgoMapping := map[runtime.SignatureAlgorithm]gocrypto.SigningAlgorithm{ - runtime.SignatureAlgorithmECDSA_P256: gocrypto.ECDSAP256, - runtime.SignatureAlgorithmECDSA_secp256k1: gocrypto.ECDSASecp256k1, - runtime.SignatureAlgorithmBLS_BLS12_381: gocrypto.BLSBLS12381, + signingAlgoMapping := map[runtime.SignatureAlgorithm]onflowCrypto.SigningAlgorithm{ + runtime.SignatureAlgorithmECDSA_P256: onflowCrypto.ECDSAP256, + runtime.SignatureAlgorithmECDSA_secp256k1: onflowCrypto.ECDSASecp256k1, + runtime.SignatureAlgorithmBLS_BLS12_381: onflowCrypto.BLSBLS12381, } for runtimeAlgo, cryptoAlgo := range signingAlgoMapping { diff --git a/fvm/crypto/hash.go b/fvm/crypto/hash.go index d1e16c5e2fa..e2e3dde516b 100644 --- a/fvm/crypto/hash.go +++ b/fvm/crypto/hash.go @@ -4,7 +4,8 @@ import ( "errors" "fmt" - "github.com/onflow/flow-go/crypto/hash" + "github.com/onflow/crypto/hash" + "github.com/onflow/flow-go/model/flow" ) diff --git a/fvm/crypto/hash_test.go b/fvm/crypto/hash_test.go index 58d15d19b17..29eeb7ec09c 100644 --- a/fvm/crypto/hash_test.go +++ b/fvm/crypto/hash_test.go @@ -7,11 +7,11 @@ import ( "crypto/sha256" "crypto/sha512" + "github.com/onflow/crypto/hash" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "golang.org/x/crypto/sha3" - "github.com/onflow/flow-go/crypto/hash" "github.com/onflow/flow-go/fvm/crypto" "github.com/onflow/flow-go/model/flow" ) diff --git a/fvm/environment/account_key_reader_test.go b/fvm/environment/account_key_reader_test.go index 8f91f7c1ec1..0a2ef025cae 100644 --- a/fvm/environment/account_key_reader_test.go +++ b/fvm/environment/account_key_reader_test.go @@ -8,8 +8,9 @@ import ( testMock "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - "github.com/onflow/flow-go/crypto" - "github.com/onflow/flow-go/crypto/hash" + "github.com/onflow/crypto" + "github.com/onflow/crypto/hash" + "github.com/onflow/flow-go/fvm/environment" "github.com/onflow/flow-go/fvm/environment/mock" "github.com/onflow/flow-go/fvm/tracing" diff --git a/fvm/environment/account_key_updater.go b/fvm/environment/account_key_updater.go index 96c601cb1aa..916479dfec0 100644 --- a/fvm/environment/account_key_updater.go +++ b/fvm/environment/account_key_updater.go @@ -8,8 +8,9 @@ import ( "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/sema" - fgcrypto "github.com/onflow/flow-go/crypto" - fghash "github.com/onflow/flow-go/crypto/hash" + fgcrypto "github.com/onflow/crypto" + fghash "github.com/onflow/crypto/hash" + "github.com/onflow/flow-go/fvm/crypto" "github.com/onflow/flow-go/fvm/errors" "github.com/onflow/flow-go/fvm/storage/state" diff --git a/fvm/environment/account_key_updater_test.go b/fvm/environment/account_key_updater_test.go index 61bb4c00d7d..09cb1ba9491 100644 --- a/fvm/environment/account_key_updater_test.go +++ b/fvm/environment/account_key_updater_test.go @@ -9,11 +9,11 @@ import ( "github.com/onflow/atree" "github.com/onflow/cadence/runtime" "github.com/onflow/cadence/runtime/sema" + "github.com/onflow/crypto" + "github.com/onflow/crypto/hash" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/onflow/flow-go/crypto" - "github.com/onflow/flow-go/crypto/hash" "github.com/onflow/flow-go/fvm/environment" "github.com/onflow/flow-go/fvm/errors" "github.com/onflow/flow-go/fvm/tracing" diff --git a/fvm/environment/accounts.go b/fvm/environment/accounts.go index 6af7c24aed5..01041f19a3f 100644 --- a/fvm/environment/accounts.go +++ b/fvm/environment/accounts.go @@ -8,9 +8,9 @@ import ( "github.com/fxamacker/cbor/v2" "github.com/onflow/atree" + "github.com/onflow/crypto" + "github.com/onflow/crypto/hash" - "github.com/onflow/flow-go/crypto" - "github.com/onflow/flow-go/crypto/hash" "github.com/onflow/flow-go/fvm/errors" "github.com/onflow/flow-go/fvm/storage/state" "github.com/onflow/flow-go/model/flow" @@ -75,7 +75,7 @@ func (a *StatefulAccounts) AllocateStorageIndex( key := atree.SlabIndexToLedgerKey(index) a.txnState.RunWithAllLimitsDisabled(func() { err = a.txnState.Set( - flow.NewRegisterID(string(address.Bytes()), string(key)), + flow.NewRegisterID(address, string(key)), []byte{}) }) if err != nil { diff --git a/fvm/environment/accounts_test.go b/fvm/environment/accounts_test.go index 7c4302278f2..c6ef3cce467 100644 --- a/fvm/environment/accounts_test.go +++ b/fvm/environment/accounts_test.go @@ -63,7 +63,7 @@ func TestAccounts_GetPublicKey(t *testing.T) { address := flow.HexToAddress("01") registerId := flow.NewRegisterID( - string(address.Bytes()), + address, "public_key_0") for _, value := range [][]byte{{}, nil} { @@ -88,7 +88,7 @@ func TestAccounts_GetPublicKeyCount(t *testing.T) { address := flow.HexToAddress("01") registerId := flow.NewRegisterID( - string(address.Bytes()), + address, "public_key_count") for _, value := range [][]byte{{}, nil} { @@ -114,7 +114,7 @@ func TestAccounts_GetPublicKeys(t *testing.T) { address := flow.HexToAddress("01") registerId := flow.NewRegisterID( - string(address.Bytes()), + address, "public_key_count") for _, value := range [][]byte{{}, nil} { @@ -243,7 +243,7 @@ func TestAccount_StorageUsed(t *testing.T) { txnState := testutils.NewSimpleTransaction(nil) accounts := environment.NewAccounts(txnState) address := flow.HexToAddress("01") - key := flow.NewRegisterID(string(address.Bytes()), "some_key") + key := flow.NewRegisterID(address, "some_key") err := accounts.Create(nil, address) require.NoError(t, err) @@ -260,7 +260,7 @@ func TestAccount_StorageUsed(t *testing.T) { txnState := testutils.NewSimpleTransaction(nil) accounts := environment.NewAccounts(txnState) address := flow.HexToAddress("01") - key := flow.NewRegisterID(string(address.Bytes()), "some_key") + key := flow.NewRegisterID(address, "some_key") err := accounts.Create(nil, address) require.NoError(t, err) @@ -279,7 +279,7 @@ func TestAccount_StorageUsed(t *testing.T) { txnState := testutils.NewSimpleTransaction(nil) accounts := environment.NewAccounts(txnState) address := flow.HexToAddress("01") - key := flow.NewRegisterID(string(address.Bytes()), "some_key") + key := flow.NewRegisterID(address, "some_key") err := accounts.Create(nil, address) require.NoError(t, err) @@ -298,7 +298,7 @@ func TestAccount_StorageUsed(t *testing.T) { txnState := testutils.NewSimpleTransaction(nil) accounts := environment.NewAccounts(txnState) address := flow.HexToAddress("01") - key := flow.NewRegisterID(string(address.Bytes()), "some_key") + key := flow.NewRegisterID(address, "some_key") err := accounts.Create(nil, address) require.NoError(t, err) @@ -317,7 +317,7 @@ func TestAccount_StorageUsed(t *testing.T) { txnState := testutils.NewSimpleTransaction(nil) accounts := environment.NewAccounts(txnState) address := flow.HexToAddress("01") - key := flow.NewRegisterID(string(address.Bytes()), "some_key") + key := flow.NewRegisterID(address, "some_key") err := accounts.Create(nil, address) require.NoError(t, err) @@ -340,19 +340,19 @@ func TestAccount_StorageUsed(t *testing.T) { err := accounts.Create(nil, address) require.NoError(t, err) - key1 := flow.NewRegisterID(string(address.Bytes()), "some_key") + key1 := flow.NewRegisterID(address, "some_key") err = accounts.SetValue(key1, createByteArray(12)) require.NoError(t, err) err = accounts.SetValue(key1, createByteArray(11)) require.NoError(t, err) - key2 := flow.NewRegisterID(string(address.Bytes()), "some_key2") + key2 := flow.NewRegisterID(address, "some_key2") err = accounts.SetValue(key2, createByteArray(22)) require.NoError(t, err) err = accounts.SetValue(key2, createByteArray(23)) require.NoError(t, err) - key3 := flow.NewRegisterID(string(address.Bytes()), "some_key3") + key3 := flow.NewRegisterID(address, "some_key3") err = accounts.SetValue(key3, createByteArray(22)) require.NoError(t, err) err = accounts.SetValue(key3, createByteArray(0)) diff --git a/fvm/environment/derived_data_invalidator_test.go b/fvm/environment/derived_data_invalidator_test.go index aa86aaeb258..026f0dd119a 100644 --- a/fvm/environment/derived_data_invalidator_test.go +++ b/fvm/environment/derived_data_invalidator_test.go @@ -300,19 +300,21 @@ func TestMeterParamOverridesUpdated(t *testing.T) { executionSnapshot, err = txnState.FinalizeMainTransaction() require.NoError(t, err) + owner := ctx.Chain.ServiceAddress() + otherOwner := unittest.RandomAddressFixtureForChain(ctx.Chain.ChainID()) + for _, registerId := range executionSnapshot.AllRegisterIDs() { checkForUpdates(registerId, true) checkForUpdates( - flow.NewRegisterID("other owner", registerId.Key), + flow.NewRegisterID(otherOwner, registerId.Key), false) } - owner := string(ctx.Chain.ServiceAddress().Bytes()) stabIndexKey := flow.NewRegisterID(owner, "$12345678") require.True(t, stabIndexKey.IsSlabIndex()) checkForUpdates(stabIndexKey, true) checkForUpdates(flow.NewRegisterID(owner, "other keys"), false) - checkForUpdates(flow.NewRegisterID("other owner", stabIndexKey.Key), false) - checkForUpdates(flow.NewRegisterID("other owner", "other key"), false) + checkForUpdates(flow.NewRegisterID(otherOwner, stabIndexKey.Key), false) + checkForUpdates(flow.NewRegisterID(otherOwner, "other key"), false) } diff --git a/fvm/environment/meter.go b/fvm/environment/meter.go index 75250d1c4c7..1541ef69740 100644 --- a/fvm/environment/meter.go +++ b/fvm/environment/meter.go @@ -12,46 +12,49 @@ import ( const ( // [2_000, 3_000) reserved for the FVM - ComputationKindHash = 2001 - ComputationKindVerifySignature = 2002 - ComputationKindAddAccountKey = 2003 - ComputationKindAddEncodedAccountKey = 2004 - ComputationKindAllocateStorageIndex = 2005 - ComputationKindCreateAccount = 2006 - ComputationKindEmitEvent = 2007 - ComputationKindGenerateUUID = 2008 - ComputationKindGetAccountAvailableBalance = 2009 - ComputationKindGetAccountBalance = 2010 - ComputationKindGetAccountContractCode = 2011 - ComputationKindGetAccountContractNames = 2012 - ComputationKindGetAccountKey = 2013 - ComputationKindGetBlockAtHeight = 2014 - ComputationKindGetCode = 2015 - ComputationKindGetCurrentBlockHeight = 2016 - _ = 2017 - ComputationKindGetStorageCapacity = 2018 - ComputationKindGetStorageUsed = 2019 - ComputationKindGetValue = 2020 - ComputationKindRemoveAccountContractCode = 2021 - ComputationKindResolveLocation = 2022 - ComputationKindRevokeAccountKey = 2023 - ComputationKindRevokeEncodedAccountKey = 2024 - _ = 2025 - ComputationKindSetValue = 2026 - ComputationKindUpdateAccountContractCode = 2027 - ComputationKindValidatePublicKey = 2028 - ComputationKindValueExists = 2029 - ComputationKindAccountKeysCount = 2030 - ComputationKindBLSVerifyPOP = 2031 - ComputationKindBLSAggregateSignatures = 2032 - ComputationKindBLSAggregatePublicKeys = 2033 - ComputationKindGetOrLoadProgram = 2034 - ComputationKindGenerateAccountLocalID = 2035 - ComputationKindGetRandomSourceHistory = 2036 - ComputationKindEVMGasUsage = 2037 - ComputationKindRLPEncoding = 2038 - ComputationKindRLPDecoding = 2039 - ComputationKindEncodeEvent = 2040 + ComputationKindHash = 2001 + iota + ComputationKindVerifySignature + ComputationKindAddAccountKey + ComputationKindAddEncodedAccountKey + ComputationKindAllocateStorageIndex + ComputationKindCreateAccount + ComputationKindEmitEvent + ComputationKindGenerateUUID + ComputationKindGetAccountAvailableBalance + ComputationKindGetAccountBalance + ComputationKindGetAccountContractCode + ComputationKindGetAccountContractNames + ComputationKindGetAccountKey + ComputationKindGetBlockAtHeight + ComputationKindGetCode + ComputationKindGetCurrentBlockHeight + _ + ComputationKindGetStorageCapacity + ComputationKindGetStorageUsed + ComputationKindGetValue + ComputationKindRemoveAccountContractCode + ComputationKindResolveLocation + ComputationKindRevokeAccountKey + ComputationKindRevokeEncodedAccountKey + _ + ComputationKindSetValue + ComputationKindUpdateAccountContractCode + ComputationKindValidatePublicKey + ComputationKindValueExists + ComputationKindAccountKeysCount + ComputationKindBLSVerifyPOP + ComputationKindBLSAggregateSignatures + ComputationKindBLSAggregatePublicKeys + ComputationKindGetOrLoadProgram + ComputationKindGenerateAccountLocalID + ComputationKindGetRandomSourceHistory + ComputationKindEVMGasUsage + ComputationKindRLPEncoding + ComputationKindRLPDecoding + ComputationKindEncodeEvent + _ + ComputationKindEVMEncodeABI + ComputationKindEVMDecodeABI ) type Meter interface { diff --git a/fvm/environment/random_generator.go b/fvm/environment/random_generator.go index 83b3aa2638f..fde2c1a10f1 100644 --- a/fvm/environment/random_generator.go +++ b/fvm/environment/random_generator.go @@ -3,7 +3,8 @@ package environment import ( "fmt" - "github.com/onflow/flow-go/crypto/random" + "github.com/onflow/crypto/random" + "github.com/onflow/flow-go/fvm/storage/state" "github.com/onflow/flow-go/fvm/tracing" "github.com/onflow/flow-go/module/trace" diff --git a/fvm/environment/random_generator_test.go b/fvm/environment/random_generator_test.go index fbca9dc03d8..e1c94bc561e 100644 --- a/fvm/environment/random_generator_test.go +++ b/fvm/environment/random_generator_test.go @@ -6,9 +6,9 @@ import ( mrand "math/rand" "testing" + "github.com/onflow/crypto/random" "github.com/stretchr/testify/require" - "github.com/onflow/flow-go/crypto/random" "github.com/onflow/flow-go/fvm/environment" "github.com/onflow/flow-go/fvm/environment/mock" "github.com/onflow/flow-go/fvm/tracing" diff --git a/fvm/environment/uuids.go b/fvm/environment/uuids.go index 2612ac6d1f8..6775d067ac9 100644 --- a/fvm/environment/uuids.go +++ b/fvm/environment/uuids.go @@ -14,9 +14,20 @@ import ( "github.com/onflow/flow-go/utils/slices" ) +// uuid is partitioned with 3rd byte for compatibility reasons. +// (database types and Javascript safe integer limits) +// +// counter(C) is 7 bytes, paritition(P) is 1 byte +// uuid is assembled by first reading the counter from the register value of the partitioned register, +// and then left shifting the 6th and 7th byte, and placing the partition byte at 6th byte: +// C7 C6 P C5 C4 C3 C2 C1 +// +// Until resource ids start filling the bits above the 48th one, dapps will have enough time +// to switch to a larger data type. + const ( - // The max value for any is uuid partition is MaxUint56, since the top - // 8 bits in the uuid are used for partitioning. + // The max value for any is uuid partition is MaxUint56, since one byte + // in the uuid is used for partitioning. MaxUint56 = (uint64(1) << 56) - 1 // Start warning when there's only a single high bit left. This should give @@ -108,8 +119,8 @@ func NewUUIDGenerator( } } -// getUint64 reads the uint64 value from the partitioned uuid register. -func (generator *uUIDGenerator) getUint64() (uint64, error) { +// getCounter reads the uint64 value from the partitioned uuid register. +func (generator *uUIDGenerator) getCounter() (uint64, error) { stateBytes, err := generator.txnState.Get(generator.registerId) if err != nil { return 0, fmt.Errorf( @@ -122,8 +133,8 @@ func (generator *uUIDGenerator) getUint64() (uint64, error) { return binary.BigEndian.Uint64(bytes), nil } -// setUint56 sets a new uint56 value into the partitioned uuid register. -func (generator *uUIDGenerator) setUint56( +// setCounter sets a new uint56 value into the partitioned uuid register. +func (generator *uUIDGenerator) setCounter( value uint64, ) error { if value > Uint56OverflowWarningThreshold { @@ -184,17 +195,20 @@ func (generator *uUIDGenerator) GenerateUUID() (uint64, error) { generator.maybeInitializePartition() - value, err := generator.getUint64() + counter, err := generator.getCounter() if err != nil { return 0, fmt.Errorf("cannot generate UUID: %w", err) } - err = generator.setUint56(value + 1) + err = generator.setCounter(counter + 1) if err != nil { return 0, fmt.Errorf("cannot generate UUID: %w", err) } // Since the partition counter only goes up to MaxUint56, we can use the - // upper 8 bits to represent which partition was used. - return (uint64(generator.partition) << 56) | value, nil + // assemble a UUID value with the partition (P) and the counter (C). + // Note: partition (P) is represented by the 6th byte + // (C7 C6) | P | (C5 C4 C3 C2 C1) + return ((counter & 0xFF_FF00_0000_0000) << 8) | (uint64(generator.partition) << 40) | (counter & 0xFF_FFFF_FFFF), nil + } diff --git a/fvm/environment/uuids_test.go b/fvm/environment/uuids_test.go index b83bd5b1821..a9852bff6a9 100644 --- a/fvm/environment/uuids_test.go +++ b/fvm/environment/uuids_test.go @@ -115,8 +115,9 @@ func testUUIDGenerator(t *testing.T, blockHeader *flow.Header, txnIndex uint32) generator.maybeInitializePartition() partition := generator.partition - partitionMinValue := uint64(partition) << 56 - maxUint56 := uint64(72057594037927935) // (1 << 56) - 1 + partitionMinValue := uint64(partition) << 40 + maxUint56 := uint64(0xFFFFFFFFFFFFFF) + maxUint56Split := uint64(0xFFFF00FFFFFFFFFF) t.Run( fmt.Sprintf("basic get and set uint (partition: %d)", partition), @@ -131,11 +132,11 @@ func testUUIDGenerator(t *testing.T, blockHeader *flow.Header, txnIndex uint32) txnIndex) uuidsA.maybeInitializePartition() - uuid, err := uuidsA.getUint64() // start from zero + uuid, err := uuidsA.getCounter() // start from zero require.NoError(t, err) require.Equal(t, uint64(0), uuid) - err = uuidsA.setUint56(5) + err = uuidsA.setCounter(5) require.NoError(t, err) // create new UUIDs instance @@ -148,7 +149,7 @@ func testUUIDGenerator(t *testing.T, blockHeader *flow.Header, txnIndex uint32) txnIndex) uuidsB.maybeInitializePartition() - uuid, err = uuidsB.getUint64() // should read saved value + uuid, err = uuidsB.getCounter() // should read saved value require.NoError(t, err) require.Equal(t, uint64(5), uuid) @@ -204,7 +205,7 @@ func testUUIDGenerator(t *testing.T, blockHeader *flow.Header, txnIndex uint32) }) t.Run( - fmt.Sprintf("setUint56 overflows (partition: %d)", partition), + fmt.Sprintf("setCounter overflows (partition: %d)", partition), func(t *testing.T) { txnState := state.NewTransactionState(nil, state.DefaultParameters()) uuids := NewUUIDGenerator( @@ -216,17 +217,17 @@ func testUUIDGenerator(t *testing.T, blockHeader *flow.Header, txnIndex uint32) txnIndex) uuids.maybeInitializePartition() - err := uuids.setUint56(maxUint56) + err := uuids.setCounter(maxUint56) require.NoError(t, err) - value, err := uuids.getUint64() + value, err := uuids.getCounter() require.NoError(t, err) require.Equal(t, value, maxUint56) - err = uuids.setUint56(maxUint56 + 1) + err = uuids.setCounter(maxUint56 + 1) require.ErrorContains(t, err, "overflowed") - value, err = uuids.getUint64() + value, err = uuids.getCounter() require.NoError(t, err) require.Equal(t, value, maxUint56) }) @@ -244,22 +245,22 @@ func testUUIDGenerator(t *testing.T, blockHeader *flow.Header, txnIndex uint32) txnIndex) uuids.maybeInitializePartition() - err := uuids.setUint56(maxUint56 - 1) + err := uuids.setCounter(maxUint56 - 1) require.NoError(t, err) value, err := uuids.GenerateUUID() require.NoError(t, err) - require.Equal(t, value, partitionMinValue+maxUint56-1) - require.Equal(t, value, partitionMinValue|(maxUint56-1)) + require.Equal(t, value, partitionMinValue+maxUint56Split-1) + require.Equal(t, value, partitionMinValue|(maxUint56Split-1)) - value, err = uuids.getUint64() + value, err = uuids.getCounter() require.NoError(t, err) require.Equal(t, value, maxUint56) _, err = uuids.GenerateUUID() require.ErrorContains(t, err, "overflowed") - value, err = uuids.getUint64() + value, err = uuids.getCounter() require.NoError(t, err) require.Equal(t, value, maxUint56) }) @@ -282,33 +283,33 @@ func TestUUIDGeneratorHardcodedPartitionIdGeneration(t *testing.T) { value, err := uuids.GenerateUUID() require.NoError(t, err) - require.Equal(t, value, uint64(0xde00000000000000)) + require.Equal(t, value, uint64(0x0000de0000000000)) - value, err = uuids.getUint64() + value, err = uuids.getCounter() require.NoError(t, err) require.Equal(t, value, uint64(1)) value, err = uuids.GenerateUUID() require.NoError(t, err) - require.Equal(t, value, uint64(0xde00000000000001)) + require.Equal(t, value, uint64(0x0000de0000000001)) - value, err = uuids.getUint64() + value, err = uuids.getCounter() require.NoError(t, err) require.Equal(t, value, uint64(2)) value, err = uuids.GenerateUUID() require.NoError(t, err) - require.Equal(t, value, uint64(0xde00000000000002)) + require.Equal(t, value, uint64(0x0000de0000000002)) - value, err = uuids.getUint64() + value, err = uuids.getCounter() require.NoError(t, err) require.Equal(t, value, uint64(3)) // pretend we increamented the counter up to cafBad cafBad := uint64(0x1c2a3f4b5a6d70) - decafBad := uint64(0xde1c2a3f4b5a6d70) + decafBad := uint64(0x1c2ade3f4b5a6d70) - err = uuids.setUint56(cafBad) + err = uuids.setCounter(cafBad) require.NoError(t, err) for i := 0; i < 5; i++ { @@ -317,35 +318,71 @@ func TestUUIDGeneratorHardcodedPartitionIdGeneration(t *testing.T) { require.Equal(t, value, decafBad+uint64(i)) } - value, err = uuids.getUint64() + value, err = uuids.getCounter() require.NoError(t, err) require.Equal(t, value, cafBad+uint64(5)) // pretend we increamented the counter up to overflow - 2 maxUint56Minus2 := uint64(0xfffffffffffffd) - err = uuids.setUint56(maxUint56Minus2) + err = uuids.setCounter(maxUint56Minus2) require.NoError(t, err) value, err = uuids.GenerateUUID() require.NoError(t, err) - require.Equal(t, value, uint64(0xdefffffffffffffd)) + require.Equal(t, value, uint64(0xffffdefffffffffd)) - value, err = uuids.getUint64() + value, err = uuids.getCounter() require.NoError(t, err) require.Equal(t, value, maxUint56Minus2+1) value, err = uuids.GenerateUUID() require.NoError(t, err) - require.Equal(t, value, uint64(0xdefffffffffffffe)) + require.Equal(t, value, uint64(0xffffdefffffffffe)) - value, err = uuids.getUint64() + value, err = uuids.getCounter() require.NoError(t, err) require.Equal(t, value, maxUint56Minus2+2) _, err = uuids.GenerateUUID() require.ErrorContains(t, err, "overflowed") - value, err = uuids.getUint64() + value, err = uuids.getCounter() require.NoError(t, err) require.Equal(t, value, maxUint56Minus2+2) } + +func TestContinuati(t *testing.T) { + txnState := state.NewTransactionState(nil, state.DefaultParameters()) + uuids := NewUUIDGenerator( + tracing.NewTracerSpan(), + zerolog.Nop(), + NewMeter(txnState), + txnState, + nil, + 0) + + // Hardcoded the partition to check for exact bytes + uuids.initialized = true + uuids.partition = 0x01 + uuids.registerId = flow.UUIDRegisterID(0x01) + + value, err := uuids.GenerateUUID() + require.NoError(t, err) + require.Equal(t, value, uint64(0x0000010000000000)) + + err = uuids.setCounter(0xFFFFFFFFFF) + require.NoError(t, err) + + value, err = uuids.GenerateUUID() + require.NoError(t, err) + require.Equal(t, value, uint64(0x000001FFFFFFFFFF)) + + value, err = uuids.GenerateUUID() + require.NoError(t, err) + require.Equal(t, value, uint64(0x0001010000000000)) + + value, err = uuids.GenerateUUID() + require.NoError(t, err) + require.Equal(t, value, uint64(0x0001010000000001)) + +} diff --git a/fvm/errors/codes.go b/fvm/errors/codes.go index cdbc734bd3d..9e8069e3a10 100644 --- a/fvm/errors/codes.go +++ b/fvm/errors/codes.go @@ -4,31 +4,30 @@ import "fmt" type ErrorCode uint16 -func (ec ErrorCode) IsFailure() bool { - return ec >= FailureCodeUnknownFailure -} - func (ec ErrorCode) String() string { - if ec.IsFailure() { - return fmt.Sprintf("[Failure Code: %d]", ec) - } return fmt.Sprintf("[Error Code: %d]", ec) } +type FailureCode uint16 + +func (fc FailureCode) String() string { + return fmt.Sprintf("[Failure Code: %d]", fc) +} + const ( - FailureCodeUnknownFailure ErrorCode = 2000 - FailureCodeEncodingFailure ErrorCode = 2001 - FailureCodeLedgerFailure ErrorCode = 2002 - FailureCodeStateMergeFailure ErrorCode = 2003 - FailureCodeBlockFinderFailure ErrorCode = 2004 - // Deprecated: No longer used. - FailureCodeHasherFailure ErrorCode = 2005 - FailureCodeParseRestrictedModeInvalidAccessFailure ErrorCode = 2006 - FailureCodePayerBalanceCheckFailure ErrorCode = 2007 - FailureCodeDerivedDataCacheImplementationFailure ErrorCode = 2008 - FailureCodeRandomSourceFailure ErrorCode = 2009 - // Deprecated: No longer used. - FailureCodeMetaTransactionFailure ErrorCode = 2100 + FailureCodeUnknownFailure FailureCode = 2000 + FailureCodeEncodingFailure FailureCode = 2001 + FailureCodeLedgerFailure FailureCode = 2002 + FailureCodeStateMergeFailure FailureCode = 2003 + FailureCodeBlockFinderFailure FailureCode = 2004 + // Deprecated: No longer used. + FailureCodeHasherFailure FailureCode = 2005 + FailureCodeParseRestrictedModeInvalidAccessFailure FailureCode = 2006 + FailureCodePayerBalanceCheckFailure FailureCode = 2007 + FailureCodeDerivedDataCacheImplementationFailure FailureCode = 2008 + FailureCodeRandomSourceFailure FailureCode = 2009 + // Deprecated: No longer used. + FailureCodeMetaTransactionFailure FailureCode = 2100 ) const ( diff --git a/fvm/errors/errors.go b/fvm/errors/errors.go index 0ff5ee1b37a..a78907c65d7 100644 --- a/fvm/errors/errors.go +++ b/fvm/errors/errors.go @@ -6,12 +6,19 @@ import ( "github.com/hashicorp/go-multierror" "github.com/onflow/cadence/runtime" + "github.com/onflow/cadence/runtime/errors" ) type Unwrappable interface { + error Unwrap() error } +type UnwrappableErrors interface { + error + Unwrap() []error +} + type CodedError interface { Code() ErrorCode @@ -19,6 +26,13 @@ type CodedError interface { error } +type CodedFailure interface { + Code() FailureCode + + Unwrappable + error +} + // Is is a utility function to call std error lib `Is` function for instance equality checks. func Is(err error, target error) bool { return stdErrors.Is(err, target) @@ -32,15 +46,14 @@ func As(err error, target interface{}) bool { return stdErrors.As(err, target) } -// findImportantCodedError recursively unwraps the error to search for important -// coded error: +// findRootCodedError recursively unwraps the error to search for the root (deepest) coded error: // 1. If err is nil, this returns (nil, false), // 2. If err has no error code, this returns (nil, true), -// 3. If err has a failure error code, this returns -// (, false), -// 4. If err has a non-failure error code, this returns -// (, false) -func findImportantCodedError(err error) (CodedError, bool) { +// 3. If err has an error code, this returns +// (, false) +// +// Note: This assumes the caller has already checked if the error contains a CodedFailure. +func findRootCodedError(err error) (CodedError, bool) { if err == nil { return nil, false } @@ -51,10 +64,6 @@ func findImportantCodedError(err error) (CodedError, bool) { } for { - if coded.Code().IsFailure() { - return coded, false - } - var nextCoded CodedError if !As(coded.Unwrap(), &nextCoded) { return coded, false @@ -67,32 +76,45 @@ func findImportantCodedError(err error) (CodedError, bool) { // IsFailure returns true if the error is un-coded, or if the error contains // a failure code. func IsFailure(err error) bool { + return AsFailure(err) != nil +} + +func AsFailure(err error) CodedFailure { if err == nil { - return false + return nil } - coded, isUnknown := findImportantCodedError(err) - return isUnknown || coded.Code().IsFailure() + var failure CodedFailure + if As(err, &failure) { + return failure + } + + var coded CodedError + if !As(err, &coded) { + return NewUnknownFailure(err) + } + + return nil } // SplitErrorTypes splits the error into fatal (failures) and non-fatal errors -func SplitErrorTypes(inp error) (err CodedError, failure CodedError) { +func SplitErrorTypes(inp error) (err CodedError, failure CodedFailure) { if inp == nil { return nil, nil } - coded, isUnknown := findImportantCodedError(inp) - if isUnknown { - return nil, NewUnknownFailure(inp) - } - - if coded.Code().IsFailure() { - return nil, WrapCodedError( - coded.Code(), + if failure = AsFailure(inp); failure != nil { + return nil, WrapCodedFailure( + failure.Code(), inp, "failure caused by") } + coded, isUnknown := findRootCodedError(inp) + if isUnknown { + return nil, NewUnknownFailure(inp) + } + return WrapCodedError( coded.Code(), inp, @@ -117,38 +139,86 @@ func HandleRuntimeError(err error) error { return NewCadenceRuntimeError(runErr) } -// This returns true if the error or one of its nested errors matches the +// HasErrorCode returns true if the error or one of its nested errors matches the // specified error code. func HasErrorCode(err error, code ErrorCode) bool { return Find(err, code) != nil } -// This recursively unwraps the error and returns first CodedError that matches +// HasFailureCode returns true if the error or one of its nested errors matches the +// specified failure code. +func HasFailureCode(err error, code FailureCode) bool { + return FindFailure(err, code) != nil +} + +// Find recursively unwraps the error and returns the first CodedError that matches // the specified error code. func Find(originalErr error, code ErrorCode) CodedError { if originalErr == nil { return nil } - var unwrappable Unwrappable - if !As(originalErr, &unwrappable) { + // Handle non-chained errors + var unwrappedErrs []error + switch err := originalErr.(type) { + case *multierror.Error: + unwrappedErrs = err.WrappedErrors() + case UnwrappableErrors: + unwrappedErrs = err.Unwrap() + + // IMPORTANT: this check needs to run after *multierror.Error because multierror does implement + // the Unwrappable interface, however its implementation only visits the base errors in the list, + // and ignores their descendants. + case Unwrappable: + coded, ok := err.(CodedError) + if ok && coded.Code() == code { + return coded + } + return Find(err.Unwrap(), code) + default: return nil } - coded, ok := unwrappable.(CodedError) - if ok && coded.Code() == code { - return coded + for _, innerErr := range unwrappedErrs { + coded := Find(innerErr, code) + if coded != nil { + return coded + } } - // NOTE: we need to special case multierror.Error since As() will only - // inspect the first error within multierror.Error. - errors, ok := unwrappable.(*multierror.Error) - if !ok { - return Find(unwrappable.Unwrap(), code) + return nil +} + +// FindFailure recursively unwraps the error and returns the first CodedFailure that matches +// the specified error code. +func FindFailure(originalErr error, code FailureCode) CodedFailure { + if originalErr == nil { + return nil } - for _, innerErr := range errors.Errors { - coded = Find(innerErr, code) + // Handle non-chained errors + var unwrappedErrs []error + switch err := originalErr.(type) { + case *multierror.Error: + unwrappedErrs = err.WrappedErrors() + case UnwrappableErrors: + unwrappedErrs = err.Unwrap() + + // IMPORTANT: this check needs to run after *multierror.Error because multierror does implement + // the Unwrappable interface, however its implementation only visits the base errors in the list, + // and ignores their descendants. + case Unwrappable: + coded, ok := err.(CodedFailure) + if ok && coded.Code() == code { + return coded + } + return FindFailure(err.Unwrap(), code) + default: + return nil + } + + for _, innerErr := range unwrappedErrs { + coded := FindFailure(innerErr, code) if coded != nil { return coded } @@ -157,6 +227,8 @@ func Find(originalErr error, code ErrorCode) CodedError { return nil } +var _ CodedError = (*codedError)(nil) + type codedError struct { code ErrorCode @@ -206,6 +278,56 @@ func (err codedError) Code() ErrorCode { return err.code } +var _ CodedFailure = (*codedFailure)(nil) + +type codedFailure struct { + code FailureCode + err error +} + +func newFailure( + code FailureCode, + rootCause error, +) codedFailure { + return codedFailure{ + code: code, + err: rootCause, + } +} + +func WrapCodedFailure( + code FailureCode, + err error, + prefixMsgFormat string, + formatArguments ...interface{}, +) codedFailure { + if prefixMsgFormat != "" { + msg := fmt.Sprintf(prefixMsgFormat, formatArguments...) + err = fmt.Errorf("%s: %w", msg, err) + } + return newFailure(code, err) +} + +func NewCodedFailure( + code FailureCode, + format string, + formatArguments ...interface{}, +) codedFailure { + return newFailure(code, fmt.Errorf(format, formatArguments...)) +} + +func (err codedFailure) Unwrap() error { + return err.err +} + +func (err codedFailure) Error() string { + return fmt.Sprintf("%v %v", err.code, err.err) +} + +func (err codedFailure) Code() FailureCode { + return err.code +} + // NewEventEncodingError construct a new CodedError which indicates // that encoding event has failed func NewEventEncodingError(err error) CodedError { @@ -213,3 +335,29 @@ func NewEventEncodingError(err error) CodedError { ErrCodeEventEncodingError, "error while encoding emitted event: %w ", err) } + +// EVMError needs to satisfy the user error interface +// in order for Cadence to correctly handle the error +var _ errors.UserError = &(EVMError{}) + +type EVMError struct { + CodedError +} + +func (e EVMError) IsUserError() {} + +// NewEVMError constructs a new CodedError which captures a +// collection of errors provided by (non-fatal) evm runtime. +func NewEVMError(err error) EVMError { + return EVMError{ + WrapCodedError( + ErrEVMExecutionError, + err, + "evm runtime error"), + } +} + +// IsEVMError returns true if error is an EVM error +func IsEVMError(err error) bool { + return HasErrorCode(err, ErrEVMExecutionError) +} diff --git a/fvm/errors/errors_test.go b/fvm/errors/errors_test.go index d0a262e0147..b634995b617 100644 --- a/fvm/errors/errors_test.go +++ b/fvm/errors/errors_test.go @@ -4,6 +4,11 @@ import ( "fmt" "testing" + "github.com/hashicorp/go-multierror" + "github.com/onflow/cadence/runtime" + cadenceErr "github.com/onflow/cadence/runtime/errors" + "github.com/onflow/cadence/runtime/sema" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/onflow/flow-go/model/flow" @@ -39,7 +44,7 @@ func TestErrorHandling(t *testing.T) { e5 := NewInvalidProposalSignatureError(flow.ProposalKey{}, e4) e6 := fmt.Errorf("wrapped: %w", e5) - expectedErr := WrapCodedError( + expectedErr := WrapCodedFailure( e3.Code(), // The shallowest failure's error code e6, // All the error message detail. "failure caused by") @@ -61,3 +66,334 @@ func TestErrorHandling(t *testing.T) { require.True(t, IsFailure(e1)) }) } + +func TestHandleRuntimeError(t *testing.T) { + baseErr := fmt.Errorf("base error") + tests := []struct { + name string + err error + errorCode ErrorCode + failureCode FailureCode + }{ + { + name: "nil error", + err: nil, + }, + { + name: "unknown error", + err: baseErr, + failureCode: FailureCodeUnknownFailure, + }, + { + name: "runtime error", + err: runtime.Error{Err: baseErr}, + errorCode: ErrCodeCadenceRunTimeError, + }, + { + name: "coded error in Unwrappable error", + err: runtime.Error{ + Err: cadenceErr.ExternalError{ + Recovered: NewScriptExecutionCancelledError(baseErr), + }, + }, + errorCode: ErrCodeScriptExecutionCancelledError, + }, + { + name: "coded error in ParentError error", + err: createCheckerErr([]error{ + fmt.Errorf("first error"), + NewScriptExecutionTimedOutError(), + }), + errorCode: ErrCodeScriptExecutionTimedOutError, + }, + { + name: "first coded error returned", + err: createCheckerErr([]error{ + fmt.Errorf("first error"), + NewScriptExecutionTimedOutError(), + NewScriptExecutionCancelledError(baseErr), + }), + errorCode: ErrCodeScriptExecutionTimedOutError, + }, + { + name: "failure returned", + err: createCheckerErr([]error{ + fmt.Errorf("first error"), + NewLedgerFailure(baseErr), + }), + failureCode: FailureCodeLedgerFailure, + }, + { + name: "error before failure returns failure", + err: createCheckerErr([]error{ + fmt.Errorf("first error"), + NewScriptExecutionTimedOutError(), + NewLedgerFailure(baseErr), + }), + failureCode: FailureCodeLedgerFailure, + }, + { + name: "embedded coded errors return deepest error", + err: createCheckerErr([]error{ + fmt.Errorf("first error"), + NewScriptExecutionCancelledError( + NewScriptExecutionTimedOutError(), + ), + }), + errorCode: ErrCodeScriptExecutionTimedOutError, + }, + { + name: "failure with embedded error returns failure", + err: createCheckerErr([]error{ + fmt.Errorf("first error"), + NewLedgerFailure( + NewScriptExecutionTimedOutError(), + ), + }), + failureCode: FailureCodeLedgerFailure, + }, + { + name: "coded error with embedded failure returns failure", + err: createCheckerErr([]error{ + fmt.Errorf("first error"), + NewScriptExecutionCancelledError( + NewLedgerFailure(baseErr), + ), + }), + failureCode: FailureCodeLedgerFailure, + }, + { + name: "error tree with failure returns failure", + err: createCheckerErr([]error{ + fmt.Errorf("first error"), + NewScriptExecutionCancelledError(baseErr), + createCheckerErr([]error{ + fmt.Errorf("first error"), + NewScriptExecutionCancelledError( + NewLedgerFailure(baseErr), + ), + }), + }), + failureCode: FailureCodeLedgerFailure, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + actual := HandleRuntimeError(tc.err) + if tc.err == nil { + assert.NoError(t, actual) + return + } + + actualCoded, failureCoded := SplitErrorTypes(actual) + + if tc.failureCode != 0 { + assert.NoError(t, actualCoded) + assert.Equalf(t, tc.failureCode, failureCoded.Code(), "error code mismatch: expected %d, got %d", tc.failureCode, failureCoded.Code()) + } else { + assert.NoError(t, failureCoded) + assert.Equalf(t, tc.errorCode, actualCoded.Code(), "error code mismatch: expected %d, got %d", tc.errorCode, actualCoded.Code()) + } + }) + } +} + +func TestFind(t *testing.T) { + targetCode := ErrCodeScriptExecutionCancelledError + baseErr := fmt.Errorf("base error") + + tests := []struct { + name string + err error + found bool + }{ + { + name: "nil error", + err: nil, + found: false, + }, + { + name: "plain error", + err: baseErr, + found: false, + }, + { + name: "wrapped plain error", + err: fmt.Errorf("wrapped: %w", baseErr), + found: false, + }, + { + name: "coded failure", + err: NewLedgerFailure(baseErr), + found: false, + }, + { + name: "incorrect coded error", + err: NewScriptExecutionTimedOutError(), + found: false, + }, + { + name: "found", + err: NewScriptExecutionCancelledError(baseErr), + found: true, + }, + { + name: "found with embedded errors", + err: NewScriptExecutionCancelledError(NewLedgerFailure(NewScriptExecutionTimedOutError())), + found: true, + }, + { + name: "found embedded in error", + err: NewDerivedDataCacheImplementationFailure(NewScriptExecutionCancelledError(baseErr)), + found: true, + }, + { + name: "found embedded in failure", + err: NewLedgerFailure(NewScriptExecutionCancelledError(baseErr)), + found: true, + }, + { + name: "found embedded with multierror", + err: &multierror.Error{ + Errors: []error{ + baseErr, + NewScriptExecutionTimedOutError(), + NewLedgerFailure(NewScriptExecutionCancelledError(baseErr)), + }, + }, + found: true, + }, + { + name: "found within embedded error tree", + err: createCheckerErr([]error{ + fmt.Errorf("first error"), + NewLedgerFailure(baseErr), + createCheckerErr([]error{ + fmt.Errorf("first error"), + NewLedgerFailure( + NewScriptExecutionCancelledError(baseErr), + ), + }), + }), + found: true, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + actual := Find(tc.err, targetCode) + if !tc.found { + assert.NoError(t, actual) + return + } + + require.Error(t, actual, "expected error but none found") + assert.Equalf(t, targetCode, actual.Code(), "error code mismatch: expected %d, got %d", targetCode, actual.Code()) + }) + } +} + +func TestFindFailure(t *testing.T) { + targetCode := FailureCodeLedgerFailure + baseErr := fmt.Errorf("base error") + tests := []struct { + name string + err error + found bool + }{ + { + name: "nil error", + err: nil, + found: false, + }, + { + name: "plain error", + err: baseErr, + found: false, + }, + { + name: "wrapped plain error", + err: fmt.Errorf("wrapped: %w", baseErr), + found: false, + }, + { + name: "coded error", + err: NewScriptExecutionTimedOutError(), + found: false, + }, + { + name: "incorrect coded failure", + err: NewStateMergeFailure(baseErr), + found: false, + }, + { + name: "found", + err: NewLedgerFailure(baseErr), + found: true, + }, + { + name: "found with embedded errors", + err: NewLedgerFailure(NewScriptExecutionCancelledError(NewScriptExecutionTimedOutError())), + found: true, + }, + { + name: "found embedded in error", + err: NewDerivedDataCacheImplementationFailure(NewLedgerFailure(baseErr)), + found: true, + }, + { + name: "found embedded in failure", + err: NewStateMergeFailure(NewLedgerFailure(baseErr)), + found: true, + }, + { + name: "found embedded with multierror", + err: &multierror.Error{ + Errors: []error{ + baseErr, + NewScriptExecutionTimedOutError(), + NewScriptExecutionCancelledError(NewLedgerFailure(baseErr)), + }, + }, + found: true, + }, + { + name: "found within embedded error tree", + err: createCheckerErr([]error{ + fmt.Errorf("first error"), + NewScriptExecutionCancelledError(baseErr), + createCheckerErr([]error{ + fmt.Errorf("first error"), + NewScriptExecutionCancelledError( + NewLedgerFailure(baseErr), + ), + }), + }), + found: true, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + actual := FindFailure(tc.err, targetCode) + if !tc.found { + assert.NoError(t, actual) + return + } + + require.Error(t, actual, "expected error but none found") + assert.Equalf(t, targetCode, actual.Code(), "error code mismatch: expected %d, got %d", targetCode, actual.Code()) + }) + } +} + +func createCheckerErr(errs []error) error { + return runtime.Error{ + Err: cadenceErr.ExternalError{ + Recovered: sema.CheckerError{ + Errors: errs, + }, + }, + } +} diff --git a/fvm/errors/execution.go b/fvm/errors/execution.go index e741345c67c..38a78bc8d21 100644 --- a/fvm/errors/execution.go +++ b/fvm/errors/execution.go @@ -76,8 +76,8 @@ func IsInsufficientPayerBalanceError(err error) bool { func NewPayerBalanceCheckFailure( payer flow.Address, err error, -) CodedError { - return WrapCodedError( +) CodedFailure { + return WrapCodedFailure( FailureCodePayerBalanceCheckFailure, err, "failed to check if the payer %s has sufficient balance", @@ -88,8 +88,8 @@ func NewPayerBalanceCheckFailure( // the derived data cache. func NewDerivedDataCacheImplementationFailure( err error, -) CodedError { - return WrapCodedError( +) CodedFailure { + return WrapCodedFailure( FailureCodeDerivedDataCacheImplementationFailure, err, "implementation error in derived data cache") @@ -99,8 +99,8 @@ func NewDerivedDataCacheImplementationFailure( // the random source provider. func NewRandomSourceFailure( err error, -) CodedError { - return WrapCodedError( +) CodedFailure { + return WrapCodedFailure( FailureCodeRandomSourceFailure, err, "implementation error in random source provider") @@ -289,17 +289,3 @@ func NewInvalidInternalStateAccessError( opType, id) } - -// NewEVMError constructs a new CodedError which captures a -// collection of errors provided by (non-fatal) evm runtime. -func NewEVMError(err error) CodedError { - return WrapCodedError( - ErrEVMExecutionError, - err, - "evm runtime error") -} - -// IsEVMError returns true if error is an EVM error -func IsEVMError(err error) bool { - return HasErrorCode(err, ErrEVMExecutionError) -} diff --git a/fvm/errors/failures.go b/fvm/errors/failures.go index 322fd0ac117..6ed5dd56225 100644 --- a/fvm/errors/failures.go +++ b/fvm/errors/failures.go @@ -4,8 +4,8 @@ import ( "github.com/onflow/flow-go/module/trace" ) -func NewUnknownFailure(err error) CodedError { - return WrapCodedError( +func NewUnknownFailure(err error) CodedFailure { + return WrapCodedFailure( FailureCodeUnknownFailure, err, "unknown failure") @@ -16,8 +16,8 @@ func NewEncodingFailuref( err error, msg string, args ...interface{}, -) CodedError { - return WrapCodedError( +) CodedFailure { + return WrapCodedFailure( FailureCodeEncodingFailure, err, "encoding failed: "+msg, @@ -26,8 +26,8 @@ func NewEncodingFailuref( // NewLedgerFailure constructs a new CodedError which captures a fatal error // cause by ledger failures. -func NewLedgerFailure(err error) CodedError { - return WrapCodedError( +func NewLedgerFailure(err error) CodedFailure { + return WrapCodedFailure( FailureCodeLedgerFailure, err, "ledger returns unsuccessful") @@ -36,13 +36,13 @@ func NewLedgerFailure(err error) CodedError { // IsLedgerFailure returns true if the error or any of the wrapped errors is // a ledger failure func IsLedgerFailure(err error) bool { - return HasErrorCode(err, FailureCodeLedgerFailure) + return HasFailureCode(err, FailureCodeLedgerFailure) } // NewStateMergeFailure constructs a new CodedError which captures a fatal // caused by state merge. -func NewStateMergeFailure(err error) CodedError { - return WrapCodedError( +func NewStateMergeFailure(err error) CodedFailure { + return WrapCodedFailure( FailureCodeStateMergeFailure, err, "can not merge the state") @@ -50,8 +50,8 @@ func NewStateMergeFailure(err error) CodedError { // NewBlockFinderFailure constructs a new CodedError which captures a fatal // caused by block finder. -func NewBlockFinderFailure(err error) CodedError { - return WrapCodedError( +func NewBlockFinderFailure(err error) CodedFailure { + return WrapCodedFailure( FailureCodeBlockFinderFailure, err, "can not retrieve the block") @@ -62,8 +62,8 @@ func NewBlockFinderFailure(err error) CodedError { // operation while it is parsing programs. func NewParseRestrictedModeInvalidAccessFailure( spanName trace.SpanName, -) CodedError { - return NewCodedError( +) CodedFailure { + return NewCodedFailure( FailureCodeParseRestrictedModeInvalidAccessFailure, "cannot access %s while cadence is in parse restricted mode", spanName) diff --git a/fvm/evm/emulator/config.go b/fvm/evm/emulator/config.go index 74f012839a9..394c88bc842 100644 --- a/fvm/evm/emulator/config.go +++ b/fvm/evm/emulator/config.go @@ -4,11 +4,13 @@ import ( "math" "math/big" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/vm" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/params" + gethCommon "github.com/ethereum/go-ethereum/common" + gethCore "github.com/ethereum/go-ethereum/core" + gethVM "github.com/ethereum/go-ethereum/core/vm" + gethCrypto "github.com/ethereum/go-ethereum/crypto" + gethParams "github.com/ethereum/go-ethereum/params" + + "github.com/onflow/flow-go/fvm/evm/types" ) var ( @@ -21,15 +23,24 @@ var ( // Config sets the required parameters type Config struct { // Chain Config - ChainConfig *params.ChainConfig + ChainConfig *gethParams.ChainConfig // EVM config - EVMConfig vm.Config + EVMConfig gethVM.Config // block context - BlockContext *vm.BlockContext + BlockContext *gethVM.BlockContext // transaction context - TxContext *vm.TxContext + TxContext *gethVM.TxContext // base unit of gas for direct calls DirectCallBaseGasUsage uint64 + // a set of extra precompiles to be injected + ExtraPrecompiles map[gethCommon.Address]gethVM.PrecompiledContract +} + +func (c *Config) ChainRules() gethParams.Rules { + return c.ChainConfig.Rules( + c.BlockContext.BlockNumber, + c.BlockContext.Random != nil, + c.BlockContext.Time) } // DefaultChainConfig is the default chain config which @@ -39,7 +50,7 @@ type Config struct { // For the future changes of EVM, we need to update the EVM go mod version // and set a proper height for the specific release based on the Flow EVM heights // so it could gets activated at a desired time. -var DefaultChainConfig = ¶ms.ChainConfig{ +var DefaultChainConfig = &gethParams.ChainConfig{ ChainID: FlowEVMTestnetChainID, // default is testnet // Fork scheduling based on block heights @@ -66,17 +77,20 @@ var DefaultChainConfig = ¶ms.ChainConfig{ func defaultConfig() *Config { return &Config{ ChainConfig: DefaultChainConfig, - EVMConfig: vm.Config{ + EVMConfig: gethVM.Config{ NoBaseFee: true, }, - TxContext: &vm.TxContext{}, - BlockContext: &vm.BlockContext{ - CanTransfer: core.CanTransfer, - Transfer: core.Transfer, + TxContext: &gethVM.TxContext{ + GasPrice: new(big.Int), + BlobFeeCap: new(big.Int), + }, + BlockContext: &gethVM.BlockContext{ + CanTransfer: gethCore.CanTransfer, + Transfer: gethCore.Transfer, GasLimit: BlockLevelGasLimit, // block gas limit BaseFee: big.NewInt(0), - GetHash: func(n uint64) common.Hash { // default returns some random hash values - return common.BytesToHash(crypto.Keccak256([]byte(new(big.Int).SetUint64(n).String()))) + GetHash: func(n uint64) gethCommon.Hash { // default returns some random hash values + return gethCommon.BytesToHash(gethCrypto.Keccak256([]byte(new(big.Int).SetUint64(n).String()))) }, }, } @@ -111,7 +125,7 @@ func WithMainnetChainID() Option { } // WithOrigin sets the origin of the transaction (signer) -func WithOrigin(origin common.Address) Option { +func WithOrigin(origin gethCommon.Address) Option { return func(c *Config) *Config { c.TxContext.Origin = origin return c @@ -135,7 +149,7 @@ func WithGasLimit(gasLimit uint64) Option { } // WithCoinbase sets the coinbase of the block where the fees are collected in -func WithCoinbase(coinbase common.Address) Option { +func WithCoinbase(coinbase gethCommon.Address) Option { return func(c *Config) *Config { c.BlockContext.Coinbase = coinbase return c @@ -159,7 +173,7 @@ func WithBlockTime(time uint64) Option { } // WithGetBlockHashFunction sets the functionality to look up block hash by height -func WithGetBlockHashFunction(getHash vm.GetHashFunc) Option { +func WithGetBlockHashFunction(getHash gethVM.GetHashFunc) Option { return func(c *Config) *Config { c.BlockContext.GetHash = getHash return c @@ -173,3 +187,24 @@ func WithDirectCallBaseGasUsage(gas uint64) Option { return c } } + +// WithExtraPrecompiles appends precompile list with extra precompiles +func WithExtraPrecompiles(precompiles []types.Precompile) Option { + return func(c *Config) *Config { + for _, pc := range precompiles { + if c.ExtraPrecompiles == nil { + c.ExtraPrecompiles = make(map[gethCommon.Address]gethVM.PrecompiledContract) + } + c.ExtraPrecompiles[pc.Address().ToCommon()] = pc + } + return c + } +} + +// WithRandom sets the block context random field +func WithRandom(rand *gethCommon.Hash) Option { + return func(c *Config) *Config { + c.BlockContext.Random = rand + return c + } +} diff --git a/fvm/evm/emulator/database/database.go b/fvm/evm/emulator/database/database.go deleted file mode 100644 index 8b18e56e7bc..00000000000 --- a/fvm/evm/emulator/database/database.go +++ /dev/null @@ -1,424 +0,0 @@ -package database - -import ( - stdErrors "errors" - "runtime" - "sync" - - gethCommon "github.com/ethereum/go-ethereum/common" - gethTypes "github.com/ethereum/go-ethereum/core/types" - gethDB "github.com/ethereum/go-ethereum/ethdb" - "github.com/onflow/atree" - - "github.com/onflow/flow-go/fvm/errors" - "github.com/onflow/flow-go/fvm/evm/types" - "github.com/onflow/flow-go/model/flow" -) - -const ( - FlowEVMRootSlabKey = "RootSlabKey" - FlowEVMRootHashKey = "RootHash" - StorageIDSize = 16 -) - -// Database is where EVM data is stored. -// -// under the hood, databases uses an Atree map -// stored under account `flowEVMRootAddress` -// each key value pairs inserted into this map is -// of type of ByteStringValue; we use this type instead -// of atree array, given the EVM environment is not smart enough -// to interact with a portion of the value and would load everything under a key -// before opearting on it. This means it could lead to having large slabs for a single value. -type Database struct { - flowEVMRootAddress flow.Address - led atree.Ledger - storage *atree.PersistentSlabStorage - baseStorage *atree.LedgerBaseStorage - atreemap *atree.OrderedMap - rootIDBytesToBeStored []byte // if is empty means we don't need to store anything - // Ramtin: other database implementations for EVM uses a lock - // to protect the storage against concurrent operations - // though one might do more research to see if we need - // these type of locking if the underlying structure (atree) - // has such protections or if EVM really needs it - lock sync.RWMutex -} - -var _ types.Database = &Database{} - -// NewDatabase returns a wrapped map that implements all the required database interface methods. -func NewDatabase(led atree.Ledger, flowEVMRootAddress flow.Address) (*Database, error) { - baseStorage := atree.NewLedgerBaseStorage(led) - - storage, err := NewPersistentSlabStorage(baseStorage) - if err != nil { - return nil, handleError(err) - } - - db := &Database{ - led: led, - baseStorage: baseStorage, - flowEVMRootAddress: flowEVMRootAddress, - storage: storage, - } - - err = db.retrieveOrCreateMapRoot() - if err != nil { - return nil, handleError(err) - } - return db, nil -} - -func (db *Database) retrieveOrCreateMapRoot() error { - rootIDBytes, err := db.led.GetValue(db.flowEVMRootAddress.Bytes(), []byte(FlowEVMRootSlabKey)) - if err != nil { - return err - } - - var m *atree.OrderedMap - if len(rootIDBytes) == 0 { - m, err = atree.NewMap(db.storage, atree.Address(db.flowEVMRootAddress), atree.NewDefaultDigesterBuilder(), emptyTypeInfo{}) - if err != nil { - return err - } - rootIDBytes := make([]byte, StorageIDSize) - _, err := m.StorageID().ToRawBytes(rootIDBytes) - if err != nil { - return err - } - db.rootIDBytesToBeStored = rootIDBytes - } else { - storageID, err := atree.NewStorageIDFromRawBytes(rootIDBytes) - if err != nil { - return err - } - m, err = atree.NewMapWithRootID(db.storage, storageID, atree.NewDefaultDigesterBuilder()) - if err != nil { - return err - } - } - db.atreemap = m - return nil -} - -// Get retrieves the given key if it's present in the key-value store. -func (db *Database) Get(key []byte) ([]byte, error) { - db.lock.RLock() - defer db.lock.RUnlock() - - value, err := db.get(key) - return value, handleError(err) -} - -func (db *Database) get(key []byte) ([]byte, error) { - data, err := db.atreemap.Get(compare, hashInputProvider, NewByteStringValue(key)) - if err != nil { - return nil, err - } - - v, err := data.StoredValue(db.atreemap.Storage) - if err != nil { - return nil, err - } - - return v.(ByteStringValue).Bytes(), nil -} - -// Put inserts the given value into the key-value store. -func (db *Database) Put(key []byte, value []byte) error { - db.lock.Lock() - defer db.lock.Unlock() - - err := db.put(key, value) - return handleError(err) -} - -func (db *Database) put(key []byte, value []byte) error { - existingValueStorable, err := db.atreemap.Set(compare, hashInputProvider, NewByteStringValue(key), NewByteStringValue(value)) - if err != nil { - return err - } - - if id, ok := existingValueStorable.(atree.StorageIDStorable); ok { - // NOTE: deep remove isn't necessary because value is ByteStringValue (not container) - err := db.storage.Remove(atree.StorageID(id)) - if err != nil { - return err - } - } - - return nil -} - -// Has checks if a key is present in the key-value store. -func (db *Database) Has(key []byte) (bool, error) { - db.lock.RLock() - defer db.lock.RUnlock() - has, err := db.has(key) - return has, handleError(err) -} - -func (db *Database) has(key []byte) (bool, error) { - has, err := db.atreemap.Has(compare, hashInputProvider, NewByteStringValue(key)) - if err != nil { - return false, err - } - return has, nil -} - -// Delete removes the key from the key-value store. -func (db *Database) Delete(key []byte) error { - db.lock.Lock() - defer db.lock.Unlock() - err := db.delete(key) - return handleError(err) -} - -func (db *Database) delete(key []byte) error { - removedMapKeyStorable, removedMapValueStorable, err := db.atreemap.Remove(compare, hashInputProvider, NewByteStringValue(key)) - if err != nil { - return err - } - - if id, ok := removedMapKeyStorable.(atree.StorageIDStorable); ok { - // NOTE: deep remove isn't necessary because key is ByteStringValue (not container) - err := db.storage.Remove(atree.StorageID(id)) - if err != nil { - return err - } - } - - if id, ok := removedMapValueStorable.(atree.StorageIDStorable); ok { - // NOTE: deep remove isn't necessary because value is ByteStringValue (not container) - err := db.storage.Remove(atree.StorageID(id)) - if err != nil { - return err - } - } - return nil -} - -// ApplyBatch applys changes from a batch into the database -func (db *Database) ApplyBatch(b *batch) error { - db.lock.Lock() - defer db.lock.Unlock() - err := db.applyBatch(b) - return err -} - -func (db *Database) applyBatch(b *batch) error { - var err error - for _, keyvalue := range b.writes { - if err != nil { - return err - } - if keyvalue.delete { - err = db.delete(keyvalue.key) - continue - } - err = db.put(keyvalue.key, keyvalue.value) - } - return err -} - -// GetRootHash returns the active root hash -func (db *Database) GetRootHash() (gethCommon.Hash, error) { - db.lock.Lock() - defer db.lock.Unlock() - - hash, err := db.getRootHash() - return hash, handleError(err) -} - -func (db *Database) getRootHash() (gethCommon.Hash, error) { - data, err := db.led.GetValue(db.flowEVMRootAddress[:], []byte(FlowEVMRootHashKey)) - if err != nil { - return gethCommon.Hash{}, handleError(err) - } - if len(data) == 0 { - return gethTypes.EmptyRootHash, nil - } - return gethCommon.BytesToHash(data), nil -} - -// Commits the changes from atree into the underlying storage -// -// this method can be merged as part of batcher -func (db *Database) Commit(root gethCommon.Hash) error { - db.lock.Lock() - defer db.lock.Unlock() - - err := db.commit(root) - return handleError(err) -} - -func (db *Database) commit(root gethCommon.Hash) error { - err := db.storage.FastCommit(runtime.NumCPU()) - if err != nil { - return err - } - - // check if we have to store the rootID - if len(db.rootIDBytesToBeStored) > 0 { - err = db.led.SetValue(db.flowEVMRootAddress.Bytes(), []byte(FlowEVMRootSlabKey), db.rootIDBytesToBeStored[:]) - if err != nil { - return err - } - } - - err = db.led.SetValue(db.flowEVMRootAddress[:], []byte(FlowEVMRootHashKey), root[:]) - if err != nil { - return err - } - return nil -} - -// Close is a no-op -func (db *Database) Close() error { - return nil -} - -// NewBatch creates a write-only key-value store that buffers changes to its host -// database until a final write is called. -func (db *Database) NewBatch() gethDB.Batch { - return &batch{ - db: db, - } -} - -// NewBatchWithSize creates a write-only database batch with pre-allocated buffer. -func (db *Database) NewBatchWithSize(size int) gethDB.Batch { - return &batch{ - db: db, - writes: make([]keyvalue, 0, size), - } -} - -// NewIterator is not supported in this database -// if needed in the future we could implement it using atree iterators -func (db *Database) NewIterator(prefix []byte, start []byte) gethDB.Iterator { - panic(types.ErrNotImplemented) -} - -// NewSnapshot is not supported -func (db *Database) NewSnapshot() (gethDB.Snapshot, error) { - return nil, types.ErrNotImplemented -} - -// Stat method is not supported -func (db *Database) Stat(property string) (string, error) { - return "", types.ErrNotImplemented -} - -func (db *Database) BytesRetrieved() int { - return db.baseStorage.BytesRetrieved() -} - -func (db *Database) BytesStored() int { - return db.baseStorage.BytesStored() -} -func (db *Database) ResetReporter() { - db.baseStorage.ResetReporter() -} - -// Compact is not supported on a memory database, but there's no need either as -// a memory database doesn't waste space anyway. -// Compact is a no op -func (db *Database) Compact(start []byte, limit []byte) error { - return nil -} - -// Len returns the number of entries currently present in the memory database. -// -// Note, this method is only used for testing (i.e. not public in general) and -// does not have explicit checks for closed-ness to allow simpler testing code. -func (db *Database) Len() int { - db.lock.RLock() - defer db.lock.RUnlock() - - return int(db.atreemap.Count()) -} - -// keyvalue is a key-value tuple tagged with a deletion field to allow creating -// memory-database write batches. -type keyvalue struct { - key []byte - value []byte - delete bool -} - -// batch is a write-only memory batch that commits changes to its host -// database when Write is called. A batch cannot be used concurrently. -type batch struct { - db *Database - writes []keyvalue - size int -} - -// Put inserts the given value into the batch for later committing. -func (b *batch) Put(key, value []byte) error { - return b.set(key, value, false) -} - -// Delete inserts the a key removal into the batch for later committing. -func (b *batch) Delete(key []byte) error { - return b.set(key, nil, true) -} - -func (b *batch) set(key []byte, value []byte, delete bool) error { - b.writes = append(b.writes, keyvalue{gethCommon.CopyBytes(key), gethCommon.CopyBytes(value), delete}) - b.size += len(key) + len(value) - return nil -} - -// DropCache drops the database read cache -func (db *Database) DropCache() { - db.storage.DropCache() -} - -// ValueSize retrieves the amount of data queued up for writing. -func (b *batch) ValueSize() int { - return b.size -} - -// Write flushes any accumulated data to the memory database. -func (b *batch) Write() error { - return b.db.ApplyBatch(b) -} - -// Reset resets the batch for reuse. -func (b *batch) Reset() { - // TODO: reset writes elements to release memory if value is large. - b.writes = b.writes[:0] - b.size = 0 -} - -// Replay replays the batch contents. -func (b *batch) Replay(w gethDB.KeyValueWriter) error { - for _, keyvalue := range b.writes { - if keyvalue.delete { - if err := w.Delete(keyvalue.key); err != nil { - return err - } - continue - } - if err := w.Put(keyvalue.key, keyvalue.value); err != nil { - return err - } - } - return nil -} - -func handleError(err error) error { - if err == nil { - return nil - } - var atreeFatalError *atree.FatalError - // if is a atree fatal error or fvm fatal error (the second one captures external errors) - if stdErrors.As(err, &atreeFatalError) || errors.IsFailure(err) { - return types.NewFatalError(err) - } - // wrap the non-fatal error with DB error - return types.NewDatabaseError(err) -} diff --git a/fvm/evm/emulator/database/database_test.go b/fvm/evm/emulator/database/database_test.go deleted file mode 100644 index a23e38d4295..00000000000 --- a/fvm/evm/emulator/database/database_test.go +++ /dev/null @@ -1,177 +0,0 @@ -package database_test - -import ( - "fmt" - "testing" - - gethCommon "github.com/ethereum/go-ethereum/common" - gethTypes "github.com/ethereum/go-ethereum/core/types" - "github.com/onflow/atree" - "github.com/stretchr/testify/require" - - "github.com/onflow/flow-go/fvm/errors" - "github.com/onflow/flow-go/fvm/evm/emulator/database" - "github.com/onflow/flow-go/fvm/evm/testutils" - "github.com/onflow/flow-go/fvm/evm/types" - "github.com/onflow/flow-go/model/flow" -) - -func TestDatabase(t *testing.T) { - - key1 := []byte("ABC") - key2 := []byte("DEF") - value1 := []byte{1, 2, 3, 4, 5, 6, 7, 8} - value2 := []byte{9, 10, 11} - - t.Run("test basic database functionality", func(t *testing.T) { - testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { - testutils.RunWithTestFlowEVMRootAddress(t, backend, func(flowEVMRoot flow.Address) { - db, err := database.NewDatabase(backend, flowEVMRoot) - require.NoError(t, err) - - err = db.Put(key1, value1) - require.NoError(t, err) - - err = db.Commit(gethTypes.EmptyRootHash) - require.NoError(t, err) - - newdb, err := database.NewDatabase(backend, flowEVMRoot) - require.NoError(t, err) - - has, err := newdb.Has(key1) - require.NoError(t, err) - require.True(t, has) - - retValue, err := newdb.Get(key1) - require.NoError(t, err) - - require.Equal(t, value1, retValue) - - err = newdb.Delete(key1) - require.NoError(t, err) - - has, err = newdb.Has(key1) - require.NoError(t, err) - require.False(t, has) - - h, err := newdb.GetRootHash() - require.NoError(t, err) - require.Equal(t, gethTypes.EmptyRootHash, h) - - newRoot := gethCommon.Hash{1, 2, 3} - err = newdb.Commit(newRoot) - require.NoError(t, err) - - h, err = newdb.GetRootHash() - require.NoError(t, err) - require.Equal(t, newRoot, h) - }) - }) - }) - - t.Run("test batch functionality", func(t *testing.T) { - testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { - testutils.RunWithTestFlowEVMRootAddress(t, backend, func(flowEVMRoot flow.Address) { - db, err := database.NewDatabase(backend, flowEVMRoot) - require.NoError(t, err) - - err = db.Put(key1, value1) - require.NoError(t, err) - - has, err := db.Has(key1) - require.NoError(t, err) - require.True(t, has) - - batch := db.NewBatch() - err = batch.Delete(key1) - require.NoError(t, err) - - err = batch.Put(key2, value2) - require.NoError(t, err) - - has, err = db.Has(key2) - require.NoError(t, err) - require.False(t, has) - - err = batch.Write() - require.NoError(t, err) - - retVal, err := db.Get(key2) - require.NoError(t, err) - require.Equal(t, value2, retVal) - - has, err = db.Has(key1) - require.NoError(t, err) - require.False(t, has) - }) - }) - }) - - t.Run("test non fatal error", func(t *testing.T) { - ledger := &testutils.TestValueStore{ - GetValueFunc: func(_, _ []byte) ([]byte, error) { - return nil, errors.NewLedgerInteractionLimitExceededError(0, 0) - }, - SetValueFunc: func(owner, key, value []byte) error { - return nil - }, - } - testutils.RunWithTestFlowEVMRootAddress(t, ledger, func(flowEVMRoot flow.Address) { - _, err := database.NewDatabase(ledger, flowEVMRoot) - require.Error(t, err) - require.True(t, types.IsADatabaseError(err)) - }) - }) - - t.Run("test fatal error (get value)", func(t *testing.T) { - ledger := &testutils.TestValueStore{ - GetValueFunc: func(_, _ []byte) ([]byte, error) { - return nil, fmt.Errorf("a fatal error") - }, - SetValueFunc: func(owner, key, value []byte) error { - return nil - }, - } - testutils.RunWithTestFlowEVMRootAddress(t, ledger, func(flowEVMRoot flow.Address) { - _, err := database.NewDatabase(ledger, flowEVMRoot) - require.Error(t, err) - require.True(t, types.IsAFatalError(err)) - }) - }) - - t.Run("test fatal error (storage id allocation)", func(t *testing.T) { - ledger := &testutils.TestValueStore{ - AllocateStorageIndexFunc: func(_ []byte) (atree.StorageIndex, error) { - return atree.StorageIndex{}, fmt.Errorf("a fatal error") - }, - GetValueFunc: func(_, _ []byte) ([]byte, error) { - return nil, nil - }, - SetValueFunc: func(owner, key, value []byte) error { - return nil - }, - } - testutils.RunWithTestFlowEVMRootAddress(t, ledger, func(flowEVMRoot flow.Address) { - _, err := database.NewDatabase(ledger, flowEVMRoot) - require.Error(t, err) - require.True(t, types.IsAFatalError(err)) - }) - }) - - t.Run("test fatal error (not implemented methods)", func(t *testing.T) { - testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { - testutils.RunWithTestFlowEVMRootAddress(t, backend, func(flowEVMRoot flow.Address) { - db, err := database.NewDatabase(backend, flowEVMRoot) - require.NoError(t, err) - - _, err = db.Stat("") - require.Error(t, err) - require.True(t, types.IsAFatalError(err)) - - _, err = db.NewSnapshot() - require.Error(t, err) - require.True(t, types.IsAFatalError(err)) - }) - }) - }) -} diff --git a/fvm/evm/emulator/database/metered_database.go b/fvm/evm/emulator/database/metered_database.go deleted file mode 100644 index ab8853c5ae7..00000000000 --- a/fvm/evm/emulator/database/metered_database.go +++ /dev/null @@ -1,42 +0,0 @@ -package database - -import ( - "github.com/onflow/atree" - - "github.com/onflow/flow-go/model/flow" -) - -// MeteredDatabase wrapper around the database purposely built for testing and benchmarking. -type MeteredDatabase struct { - *Database -} - -// NewMeteredDatabase create a database wrapper purposely built for testing and benchmarking. -func NewMeteredDatabase(led atree.Ledger, flowEVMRootAddress flow.Address) (*MeteredDatabase, error) { - database, err := NewDatabase(led, flowEVMRootAddress) - if err != nil { - return nil, err - } - - return &MeteredDatabase{ - Database: database, - }, nil -} - -func (m *MeteredDatabase) DropCache() { - m.storage.DropCache() -} - -func (m *MeteredDatabase) BytesRead() int { - return m.baseStorage.BytesRetrieved() -} - -func (m *MeteredDatabase) BytesWritten() int { - return m.baseStorage.BytesStored() -} - -func (m *MeteredDatabase) ResetReporter() { - m.baseStorage.ResetReporter() - m.baseStorage.Size() - m.storage.Count() -} diff --git a/fvm/evm/emulator/database/storable.go b/fvm/evm/emulator/database/storable.go deleted file mode 100644 index 520c47573f1..00000000000 --- a/fvm/evm/emulator/database/storable.go +++ /dev/null @@ -1,242 +0,0 @@ -package database - -import ( - "bytes" - "encoding/binary" - "fmt" - "math" - - "github.com/fxamacker/cbor/v2" - - "github.com/onflow/atree" -) - -type ByteStringValue struct { - data []byte - size uint32 -} - -var _ atree.Value = &ByteStringValue{} -var _ atree.Storable = &ByteStringValue{} - -func NewByteStringValue(data []byte) ByteStringValue { - size := atree.GetUintCBORSize(uint64(len(data))) + uint32(len(data)) - return ByteStringValue{data: data, size: size} -} - -func (v ByteStringValue) ChildStorables() []atree.Storable { - return nil -} - -func (v ByteStringValue) StoredValue(_ atree.SlabStorage) (atree.Value, error) { - return v, nil -} - -func (v ByteStringValue) Storable(storage atree.SlabStorage, address atree.Address, maxInlineSize uint64) (atree.Storable, error) { - if uint64(v.ByteSize()) > maxInlineSize { - - // Create StorableSlab - id, err := storage.GenerateStorageID(address) - if err != nil { - return nil, err - } - - slab := &atree.StorableSlab{ - StorageID: id, - Storable: v, - } - - // Store StorableSlab in storage - err = storage.Store(id, slab) - if err != nil { - return nil, err - } - - // Return storage id as storable - return atree.StorageIDStorable(id), nil - } - - return v, nil -} - -func (v ByteStringValue) Encode(enc *atree.Encoder) error { - return enc.CBOR.EncodeBytes(v.data) -} - -func (v ByteStringValue) getHashInput(scratch []byte) ([]byte, error) { - - const cborTypeByteString = 0x40 - - buf := scratch - if uint32(len(buf)) < v.size { - buf = make([]byte, v.size) - } else { - buf = buf[:v.size] - } - - slen := len(v.data) - - if slen <= 23 { - buf[0] = cborTypeByteString | byte(slen) - copy(buf[1:], v.data) - return buf, nil - } - - if slen <= math.MaxUint8 { - buf[0] = cborTypeByteString | byte(24) - buf[1] = byte(slen) - copy(buf[2:], v.data) - return buf, nil - } - - if slen <= math.MaxUint16 { - buf[0] = cborTypeByteString | byte(25) - binary.BigEndian.PutUint16(buf[1:], uint16(slen)) - copy(buf[3:], v.data) - return buf, nil - } - - if slen <= math.MaxUint32 { - buf[0] = cborTypeByteString | byte(26) - binary.BigEndian.PutUint32(buf[1:], uint32(slen)) - copy(buf[5:], v.data) - return buf, nil - } - - buf[0] = cborTypeByteString | byte(27) - binary.BigEndian.PutUint64(buf[1:], uint64(slen)) - copy(buf[9:], v.data) - return buf, nil -} - -func (v ByteStringValue) ByteSize() uint32 { - return v.size -} - -func (v ByteStringValue) String() string { - return string(v.data) -} - -func (v ByteStringValue) Bytes() []byte { - return v.data -} - -func decodeStorable(dec *cbor.StreamDecoder, _ atree.StorageID) (atree.Storable, error) { - t, err := dec.NextType() - if err != nil { - return nil, err - } - - switch t { - case cbor.ByteStringType: - s, err := dec.DecodeBytes() - if err != nil { - return nil, err - } - return NewByteStringValue(s), nil - - case cbor.TagType: - tagNumber, err := dec.DecodeTagNumber() - if err != nil { - return nil, err - } - - switch tagNumber { - - case atree.CBORTagStorageID: - return atree.DecodeStorageIDStorable(dec) - - default: - return nil, fmt.Errorf("invalid tag number %d", tagNumber) - } - - default: - return nil, fmt.Errorf("invalid cbor type %s for storable", t) - } -} - -func compare(storage atree.SlabStorage, value atree.Value, storable atree.Storable) (bool, error) { - switch v := value.(type) { - - case ByteStringValue: - other, ok := storable.(ByteStringValue) - if ok { - return bytes.Equal(other.data, v.data), nil - } - - // Retrieve value from storage - otherValue, err := storable.StoredValue(storage) - if err != nil { - return false, err - } - other, ok = otherValue.(ByteStringValue) - if ok { - return bytes.Equal(other.data, v.data), nil - } - - return false, nil - } - - return false, fmt.Errorf("value %T not supported for comparison", value) -} - -func hashInputProvider(value atree.Value, buffer []byte) ([]byte, error) { - switch v := value.(type) { - case ByteStringValue: - return v.getHashInput(buffer) - } - - return nil, fmt.Errorf("value %T not supported for hash input", value) -} - -func NewPersistentSlabStorage(baseStorage atree.BaseStorage) (*atree.PersistentSlabStorage, error) { - - encMode, err := cbor.EncOptions{}.EncMode() - if err != nil { - return nil, err - } - - decMode, err := cbor.DecOptions{}.DecMode() - if err != nil { - return nil, err - } - - return atree.NewPersistentSlabStorage( - baseStorage, - encMode, - decMode, - decodeStorable, - decodeTypeInfo, - ), nil - -} - -type emptyTypeInfo struct{} - -var _ atree.TypeInfo = emptyTypeInfo{} - -func (emptyTypeInfo) Encode(e *cbor.StreamEncoder) error { - return e.EncodeNil() -} - -func (i emptyTypeInfo) Equal(other atree.TypeInfo) bool { - _, ok := other.(emptyTypeInfo) - return ok -} - -func decodeTypeInfo(dec *cbor.StreamDecoder) (atree.TypeInfo, error) { - ty, err := dec.NextType() - if err != nil { - return nil, err - } - switch ty { - case cbor.NilType: - err := dec.DecodeNil() - if err != nil { - return nil, err - } - return emptyTypeInfo{}, nil - } - - return nil, fmt.Errorf("not supported type info") -} diff --git a/fvm/evm/emulator/emulator.go b/fvm/evm/emulator/emulator.go index eef720912f7..bddc4e61e73 100644 --- a/fvm/evm/emulator/emulator.go +++ b/fvm/evm/emulator/emulator.go @@ -5,28 +5,32 @@ import ( gethCommon "github.com/ethereum/go-ethereum/common" gethCore "github.com/ethereum/go-ethereum/core" - gethRawDB "github.com/ethereum/go-ethereum/core/rawdb" - gethState "github.com/ethereum/go-ethereum/core/state" gethTypes "github.com/ethereum/go-ethereum/core/types" gethVM "github.com/ethereum/go-ethereum/core/vm" gethCrypto "github.com/ethereum/go-ethereum/crypto" + "github.com/onflow/atree" + "github.com/onflow/flow-go/fvm/evm/emulator/state" "github.com/onflow/flow-go/fvm/evm/types" + "github.com/onflow/flow-go/model/flow" ) // Emulator handles operations against evm runtime type Emulator struct { - Database types.Database + rootAddr flow.Address + ledger atree.Ledger } var _ types.Emulator = &Emulator{} // NewEmulator constructs a new EVM Emulator func NewEmulator( - db types.Database, + ledger atree.Ledger, + rootAddr flow.Address, ) *Emulator { return &Emulator{ - Database: db, + rootAddr: rootAddr, + ledger: ledger, } } @@ -35,12 +39,14 @@ func newConfig(ctx types.BlockContext) *Config { WithBlockNumber(new(big.Int).SetUint64(ctx.BlockNumber)), WithCoinbase(ctx.GasFeeCollector.ToCommon()), WithDirectCallBaseGasUsage(ctx.DirectCallBaseGasUsage), + WithExtraPrecompiles(ctx.ExtraPrecompiles), + WithRandom(&ctx.Random), ) } // NewReadOnlyBlockView constructs a new readonly block view func (em *Emulator) NewReadOnlyBlockView(ctx types.BlockContext) (types.ReadOnlyBlockView, error) { - execState, err := newState(em.Database) + execState, err := state.NewStateDB(em.ledger, em.rootAddr) return &ReadOnlyBlockView{ state: execState, }, err @@ -49,16 +55,18 @@ func (em *Emulator) NewReadOnlyBlockView(ctx types.BlockContext) (types.ReadOnly // NewBlockView constructs a new block view (mutable) func (em *Emulator) NewBlockView(ctx types.BlockContext) (types.BlockView, error) { cfg := newConfig(ctx) + SetupPrecompile(cfg) return &BlockView{ config: cfg, - database: em.Database, + rootAddr: em.rootAddr, + ledger: em.ledger, }, nil } // ReadOnlyBlockView provides a read only view of a block // could be used multiple times for queries type ReadOnlyBlockView struct { - state *gethState.StateDB + state types.StateDB } // BalanceOf returns the balance of the given address @@ -82,7 +90,8 @@ func (bv *ReadOnlyBlockView) NonceOf(address types.Address) (uint64, error) { // TODO: add block level commit (separation of trie commit to storage) type BlockView struct { config *Config - database types.Database + rootAddr flow.Address + ledger atree.Ledger } // DirectCall executes a direct call @@ -100,10 +109,7 @@ func (bl *BlockView) DirectCall(call *types.DirectCall) (*types.Result, error) { default: res, err = proc.run(call.Message(), types.DirectCallTxType) } - if err != nil { - return res, err - } - return res, bl.commit(res.StateRootHash) + return res, err } // RunTransaction runs an evm transaction @@ -126,15 +132,11 @@ func (bl *BlockView) RunTransaction( // update tx context origin proc.evm.TxContext.Origin = msg.From res, err := proc.run(msg, tx.Type()) - if err != nil { - return res, err - } - - return res, bl.commit(res.StateRootHash) + return res, err } func (bl *BlockView) newProcedure() (*procedure, error) { - execState, err := newState(bl.database) + execState, err := state.NewStateDB(bl.ledger, bl.rootAddr) if err != nil { return nil, err } @@ -148,59 +150,27 @@ func (bl *BlockView) newProcedure() (*procedure, error) { cfg.ChainConfig, cfg.EVMConfig, ), - state: execState, - database: bl.database, + state: execState, }, nil } -func (bl *BlockView) commit(rootHash gethCommon.Hash) error { - // commit atree changes back to the backend - err := bl.database.Commit(rootHash) - return handleCommitError(err) -} - type procedure struct { - config *Config - evm *gethVM.EVM - state *gethState.StateDB - database types.Database + config *Config + evm *gethVM.EVM + state types.StateDB } // commit commits the changes to the state. -func (proc *procedure) commit() (gethCommon.Hash, error) { - // commits the changes from the journal into the in memory trie. - // in the future if we want to move this to the block level we could use finalize - // to get the root hash - newRoot, err := proc.state.Commit(true) - if err != nil { - return gethTypes.EmptyRootHash, handleCommitError(err) - } - - // flush the trie to the lower level db - // the reason we have to do this, is the original database - // is designed to keep changes in memory until the state.Commit - // is called then the changes moves into the trie, but the trie - // would stay in memory for faster transaction execution. you - // have to explicitly ask the trie to commit to the underlying storage - err = proc.state.Database().TrieDB().Commit(newRoot, false) - if err != nil { - return gethTypes.EmptyRootHash, handleCommitError(err) - } - - // // remove the read registers (no history tracking) - // err = proc.database.DeleteAndCleanReadKey() - // if err != nil { - // return gethTypes.EmptyRootHash, types.NewFatalError(err) - // } - return newRoot, nil +func (proc *procedure) commit() error { + return handleCommitError(proc.state.Commit()) } func handleCommitError(err error) error { if err == nil { return nil } - // if known types (database errors) don't do anything and return - if types.IsAFatalError(err) || types.IsADatabaseError(err) { + // if known types (state errors) don't do anything and return + if types.IsAFatalError(err) || types.IsAStateError(err) { return err } @@ -209,7 +179,6 @@ func handleCommitError(err error) error { } func (proc *procedure) mintTo(address types.Address, amount *big.Int) (*types.Result, error) { - var err error addr := address.ToCommon() res := &types.Result{ GasConsumed: proc.config.DirectCallBaseGasUsage, @@ -225,13 +194,10 @@ func (proc *procedure) mintTo(address types.Address, amount *big.Int) (*types.Re proc.state.AddBalance(addr, amount) // we don't need to increment any nonce, given the origin doesn't exist - res.StateRootHash, err = proc.commit() - - return res, err + return res, proc.commit() } func (proc *procedure) withdrawFrom(address types.Address, amount *big.Int) (*types.Result, error) { - var err error addr := address.ToCommon() res := &types.Result{ @@ -264,8 +230,7 @@ func (proc *procedure) withdrawFrom(address types.Address, amount *big.Int) (*ty nonce := proc.state.GetNonce(addr) proc.state.SetNonce(addr, nonce+1) - res.StateRootHash, err = proc.commit() - return res, err + return res, proc.commit() } func (proc *procedure) run(msg *gethCore.Message, txType uint8) (*types.Result, error) { @@ -281,8 +246,8 @@ func (proc *procedure) run(msg *gethCore.Message, txType uint8) (*types.Result, ).TransitionDb() if err != nil { res.Failed = true - // if the error is a fatal error or a non-fatal database error return it - if types.IsAFatalError(err) || types.IsADatabaseError(err) { + // if the error is a fatal error or a non-fatal state error return it + if types.IsAFatalError(err) || types.IsAStateError(err) { return &res, err } // otherwise is a validation error (pre-check failure) @@ -299,31 +264,43 @@ func (proc *procedure) run(msg *gethCore.Message, txType uint8) (*types.Result, if msg.To == nil { res.DeployedContractAddress = types.NewAddress(gethCrypto.CreateAddress(msg.From, msg.Nonce)) } - res.Logs = proc.state.Logs() + res.Logs = proc.state.Logs( + // TODO pass proper hash values + gethCommon.Hash{}, + proc.config.BlockContext.BlockNumber.Uint64(), + gethCommon.Hash{}, + 0, + ) } else { res.Failed = true err = types.NewEVMExecutionError(execResult.Err) } } - var commitErr error - res.StateRootHash, commitErr = proc.commit() + commitErr := proc.commit() if commitErr != nil { return &res, commitErr } return &res, err } -// Ramtin: this is the part of the code that we have to update if we hit performance problems -// the NewDatabase from the RawDB might have to change. -func newState(database types.Database) (*gethState.StateDB, error) { - root, err := database.GetRootHash() - if err != nil { - return nil, err +func SetupPrecompile(cfg *Config) { + rules := cfg.ChainRules() + // captures the pointer to the map that has to be augmented + var precompiles map[gethCommon.Address]gethVM.PrecompiledContract + switch { + case rules.IsCancun: + precompiles = gethVM.PrecompiledContractsCancun + case rules.IsBerlin: + precompiles = gethVM.PrecompiledContractsBerlin + case rules.IsIstanbul: + precompiles = gethVM.PrecompiledContractsIstanbul + case rules.IsByzantium: + precompiles = gethVM.PrecompiledContractsByzantium + default: + precompiles = gethVM.PrecompiledContractsHomestead + } + for addr, contract := range cfg.ExtraPrecompiles { + // we override if exist since we call this method on every block + precompiles[addr] = contract } - - return gethState.New(root, - gethState.NewDatabase( - gethRawDB.NewDatabase(database), - ), - nil) } diff --git a/fvm/evm/emulator/emulator_test.go b/fvm/evm/emulator/emulator_test.go index caaf5853cac..074912f0bde 100644 --- a/fvm/evm/emulator/emulator_test.go +++ b/fvm/evm/emulator/emulator_test.go @@ -1,7 +1,6 @@ package emulator_test import ( - "fmt" "math" "math/big" "testing" @@ -12,7 +11,6 @@ import ( "github.com/stretchr/testify/require" "github.com/onflow/flow-go/fvm/evm/emulator" - "github.com/onflow/flow-go/fvm/evm/emulator/database" "github.com/onflow/flow-go/fvm/evm/testutils" "github.com/onflow/flow-go/fvm/evm/types" "github.com/onflow/flow-go/model/flow" @@ -21,18 +19,8 @@ import ( var blockNumber = big.NewInt(10) var defaultCtx = types.NewDefaultBlockContext(blockNumber.Uint64()) -func RunWithTestDB(t testing.TB, f func(types.Database)) { - testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { - testutils.RunWithTestFlowEVMRootAddress(t, backend, func(flowEVMRoot flow.Address) { - db, err := database.NewDatabase(backend, flowEVMRoot) - require.NoError(t, err) - f(db) - }) - }) -} - -func RunWithNewEmulator(t testing.TB, db types.Database, f func(*emulator.Emulator)) { - env := emulator.NewEmulator(db) +func RunWithNewEmulator(t testing.TB, backend *testutils.TestBackend, rootAddr flow.Address, f func(*emulator.Emulator)) { + env := emulator.NewEmulator(backend, rootAddr) f(env) } @@ -49,350 +37,391 @@ func RunWithNewReadOnlyBlockView(t testing.TB, em *emulator.Emulator, f func(blk } func TestNativeTokenBridging(t *testing.T) { - RunWithTestDB(t, func(db types.Database) { - originalBalance := big.NewInt(10000) - testAccount := types.NewAddressFromString("test") + testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { + testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { + originalBalance := big.NewInt(10000) + testAccount := types.NewAddressFromString("test") - t.Run("mint tokens to the first account", func(t *testing.T) { - RunWithNewEmulator(t, db, func(env *emulator.Emulator) { - RunWithNewBlockView(t, env, func(blk types.BlockView) { - res, err := blk.DirectCall(types.NewDepositCall(testAccount, originalBalance)) - require.NoError(t, err) - require.Equal(t, defaultCtx.DirectCallBaseGasUsage, res.GasConsumed) + t.Run("mint tokens to the first account", func(t *testing.T) { + RunWithNewEmulator(t, backend, rootAddr, func(env *emulator.Emulator) { + RunWithNewBlockView(t, env, func(blk types.BlockView) { + res, err := blk.DirectCall(types.NewDepositCall(testAccount, originalBalance)) + require.NoError(t, err) + require.Equal(t, defaultCtx.DirectCallBaseGasUsage, res.GasConsumed) + }) }) }) - }) - t.Run("tokens withdraw", func(t *testing.T) { - amount := big.NewInt(1000) - RunWithNewEmulator(t, db, func(env *emulator.Emulator) { - RunWithNewReadOnlyBlockView(t, env, func(blk types.ReadOnlyBlockView) { - retBalance, err := blk.BalanceOf(testAccount) - require.NoError(t, err) - require.Equal(t, originalBalance, retBalance) + t.Run("tokens withdraw", func(t *testing.T) { + amount := big.NewInt(1000) + RunWithNewEmulator(t, backend, rootAddr, func(env *emulator.Emulator) { + RunWithNewReadOnlyBlockView(t, env, func(blk types.ReadOnlyBlockView) { + retBalance, err := blk.BalanceOf(testAccount) + require.NoError(t, err) + require.Equal(t, originalBalance, retBalance) + }) }) - }) - RunWithNewEmulator(t, db, func(env *emulator.Emulator) { - RunWithNewBlockView(t, env, func(blk types.BlockView) { - res, err := blk.DirectCall(types.NewWithdrawCall(testAccount, amount)) - require.NoError(t, err) - require.Equal(t, defaultCtx.DirectCallBaseGasUsage, res.GasConsumed) + RunWithNewEmulator(t, backend, rootAddr, func(env *emulator.Emulator) { + RunWithNewBlockView(t, env, func(blk types.BlockView) { + res, err := blk.DirectCall(types.NewWithdrawCall(testAccount, amount)) + require.NoError(t, err) + require.Equal(t, defaultCtx.DirectCallBaseGasUsage, res.GasConsumed) + }) }) - }) - RunWithNewEmulator(t, db, func(env *emulator.Emulator) { - RunWithNewReadOnlyBlockView(t, env, func(blk types.ReadOnlyBlockView) { - retBalance, err := blk.BalanceOf(testAccount) - require.NoError(t, err) - require.Equal(t, amount.Sub(originalBalance, amount), retBalance) + RunWithNewEmulator(t, backend, rootAddr, func(env *emulator.Emulator) { + RunWithNewReadOnlyBlockView(t, env, func(blk types.ReadOnlyBlockView) { + retBalance, err := blk.BalanceOf(testAccount) + require.NoError(t, err) + require.Equal(t, amount.Sub(originalBalance, amount), retBalance) + }) }) }) }) }) - } func TestContractInteraction(t *testing.T) { - RunWithTestDB(t, func(db types.Database) { + testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { + testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { - testContract := testutils.GetStorageTestContract(t) + testContract := testutils.GetStorageTestContract(t) - testAccount := types.NewAddressFromString("test") - amount := big.NewInt(0).Mul(big.NewInt(1337), big.NewInt(gethParams.Ether)) - amountToBeTransfered := big.NewInt(0).Mul(big.NewInt(100), big.NewInt(gethParams.Ether)) + testAccount := types.NewAddressFromString("test") + amount := big.NewInt(0).Mul(big.NewInt(1337), big.NewInt(gethParams.Ether)) + amountToBeTransfered := big.NewInt(0).Mul(big.NewInt(100), big.NewInt(gethParams.Ether)) - // fund test account - RunWithNewEmulator(t, db, func(env *emulator.Emulator) { - RunWithNewBlockView(t, env, func(blk types.BlockView) { - _, err := blk.DirectCall(types.NewDepositCall(testAccount, amount)) - require.NoError(t, err) + // fund test account + RunWithNewEmulator(t, backend, rootAddr, func(env *emulator.Emulator) { + RunWithNewBlockView(t, env, func(blk types.BlockView) { + _, err := blk.DirectCall(types.NewDepositCall(testAccount, amount)) + require.NoError(t, err) + }) }) - }) - var contractAddr types.Address + var contractAddr types.Address + + t.Run("deploy contract", func(t *testing.T) { + RunWithNewEmulator(t, backend, rootAddr, func(env *emulator.Emulator) { + RunWithNewBlockView(t, env, func(blk types.BlockView) { + res, err := blk.DirectCall( + types.NewDeployCall( + testAccount, + testContract.ByteCode, + math.MaxUint64, + amountToBeTransfered), + ) + require.NoError(t, err) + contractAddr = res.DeployedContractAddress + }) + RunWithNewReadOnlyBlockView(t, env, func(blk types.ReadOnlyBlockView) { + require.NotNil(t, contractAddr) + retCode, err := blk.CodeOf(contractAddr) + require.NoError(t, err) + require.NotEmpty(t, retCode) + + retBalance, err := blk.BalanceOf(contractAddr) + require.NoError(t, err) + require.Equal(t, amountToBeTransfered, retBalance) + + retBalance, err = blk.BalanceOf(testAccount) + require.NoError(t, err) + require.Equal(t, amount.Sub(amount, amountToBeTransfered), retBalance) + }) + }) + }) - t.Run("deploy contract", func(t *testing.T) { - RunWithNewEmulator(t, db, func(env *emulator.Emulator) { - RunWithNewBlockView(t, env, func(blk types.BlockView) { - res, err := blk.DirectCall( - types.NewDeployCall( - testAccount, - testContract.ByteCode, - math.MaxUint64, - amountToBeTransfered), - ) - require.NoError(t, err) - contractAddr = res.DeployedContractAddress + t.Run("call contract", func(t *testing.T) { + num := big.NewInt(10) + RunWithNewEmulator(t, backend, rootAddr, func(env *emulator.Emulator) { + RunWithNewBlockView(t, env, func(blk types.BlockView) { + res, err := blk.DirectCall( + types.NewContractCall( + testAccount, + contractAddr, + testContract.MakeCallData(t, "store", num), + 1_000_000, + big.NewInt(0), // this should be zero because the contract doesn't have receiver + ), + ) + require.NoError(t, err) + require.GreaterOrEqual(t, res.GasConsumed, uint64(40_000)) + }) }) - RunWithNewReadOnlyBlockView(t, env, func(blk types.ReadOnlyBlockView) { - require.NotNil(t, contractAddr) - retCode, err := blk.CodeOf(contractAddr) - require.NoError(t, err) - require.NotEmpty(t, retCode) - retBalance, err := blk.BalanceOf(contractAddr) - require.NoError(t, err) - require.Equal(t, amountToBeTransfered, retBalance) + RunWithNewEmulator(t, backend, rootAddr, func(env *emulator.Emulator) { + RunWithNewBlockView(t, env, func(blk types.BlockView) { + res, err := blk.DirectCall( + types.NewContractCall( + testAccount, + contractAddr, + testContract.MakeCallData(t, "retrieve"), + 1_000_000, + big.NewInt(0), // this should be zero because the contract doesn't have receiver + ), + ) + require.NoError(t, err) + + ret := new(big.Int).SetBytes(res.ReturnedValue) + require.Equal(t, num, ret) + require.GreaterOrEqual(t, res.GasConsumed, uint64(23_000)) + }) + }) - retBalance, err = blk.BalanceOf(testAccount) - require.NoError(t, err) - require.Equal(t, amount.Sub(amount, amountToBeTransfered), retBalance) + RunWithNewEmulator(t, backend, rootAddr, func(env *emulator.Emulator) { + RunWithNewBlockView(t, env, func(blk types.BlockView) { + res, err := blk.DirectCall( + types.NewContractCall( + testAccount, + contractAddr, + testContract.MakeCallData(t, "blockNumber"), + 1_000_000, + big.NewInt(0), // this should be zero because the contract doesn't have receiver + ), + ) + require.NoError(t, err) + + ret := new(big.Int).SetBytes(res.ReturnedValue) + require.Equal(t, blockNumber, ret) + }) }) }) - }) - t.Run("call contract", func(t *testing.T) { - num := big.NewInt(10) + t.Run("test sending transactions (happy case)", func(t *testing.T) { + account := testutils.GetTestEOAAccount(t, testutils.EOATestAccount1KeyHex) + fAddr := account.Address() + RunWithNewEmulator(t, backend, rootAddr, func(env *emulator.Emulator) { + RunWithNewBlockView(t, env, func(blk types.BlockView) { + _, err := blk.DirectCall(types.NewDepositCall(fAddr, amount)) + require.NoError(t, err) + }) + }) - RunWithNewEmulator(t, db, func(env *emulator.Emulator) { - RunWithNewBlockView(t, env, func(blk types.BlockView) { - res, err := blk.DirectCall( - types.NewContractCall( - testAccount, - contractAddr, - testContract.MakeCallData(t, "store", num), - 1_000_000, - big.NewInt(0), // this should be zero because the contract doesn't have receiver - ), - ) + RunWithNewEmulator(t, backend, rootAddr, func(env *emulator.Emulator) { + ctx := types.NewDefaultBlockContext(blockNumber.Uint64()) + ctx.GasFeeCollector = types.NewAddressFromString("coinbase") + coinbaseOrgBalance := gethCommon.Big1 + // small amount of money to create account + RunWithNewBlockView(t, env, func(blk types.BlockView) { + _, err := blk.DirectCall(types.NewDepositCall(ctx.GasFeeCollector, coinbaseOrgBalance)) + require.NoError(t, err) + }) + + blk, err := env.NewBlockView(ctx) require.NoError(t, err) - require.GreaterOrEqual(t, res.GasConsumed, uint64(40_000)) - }) - }) + tx := account.PrepareAndSignTx( + t, + testAccount.ToCommon(), // to + nil, // data + big.NewInt(1000), // amount + gethParams.TxGas, // gas limit + gethCommon.Big1, // gas fee - RunWithNewEmulator(t, db, func(env *emulator.Emulator) { - RunWithNewBlockView(t, env, func(blk types.BlockView) { - res, err := blk.DirectCall( - types.NewContractCall( - testAccount, - contractAddr, - testContract.MakeCallData(t, "retrieve"), - 1_000_000, - big.NewInt(0), // this should be zero because the contract doesn't have receiver - ), ) + _, err = blk.RunTransaction(tx) require.NoError(t, err) - ret := new(big.Int).SetBytes(res.ReturnedValue) - require.Equal(t, num, ret) - require.GreaterOrEqual(t, res.GasConsumed, uint64(23_000)) + // check the balance of coinbase + RunWithNewReadOnlyBlockView(t, env, func(blk2 types.ReadOnlyBlockView) { + bal, err := blk2.BalanceOf(ctx.GasFeeCollector) + require.NoError(t, err) + expected := gethParams.TxGas*gethCommon.Big1.Uint64() + gethCommon.Big1.Uint64() + require.Equal(t, expected, bal.Uint64()) + + nonce, err := blk2.NonceOf(fAddr) + require.NoError(t, err) + require.Equal(t, 1, int(nonce)) + }) }) }) + t.Run("test sending transactions (invalid nonce)", func(t *testing.T) { + account := testutils.GetTestEOAAccount(t, testutils.EOATestAccount1KeyHex) + fAddr := account.Address() + RunWithNewEmulator(t, backend, rootAddr, func(env *emulator.Emulator) { + RunWithNewBlockView(t, env, func(blk types.BlockView) { + _, err := blk.DirectCall(types.NewDepositCall(fAddr, amount)) + require.NoError(t, err) + }) + }) - RunWithNewEmulator(t, db, func(env *emulator.Emulator) { - RunWithNewBlockView(t, env, func(blk types.BlockView) { - res, err := blk.DirectCall( - types.NewContractCall( - testAccount, - contractAddr, - testContract.MakeCallData(t, "blockNumber"), - 1_000_000, - big.NewInt(0), // this should be zero because the contract doesn't have receiver + RunWithNewEmulator(t, backend, rootAddr, func(env *emulator.Emulator) { + ctx := types.NewDefaultBlockContext(blockNumber.Uint64()) + blk, err := env.NewBlockView(ctx) + require.NoError(t, err) + tx := account.SignTx(t, + gethTypes.NewTransaction( + 100, // nonce + testAccount.ToCommon(), // to + big.NewInt(1000), // amount + gethParams.TxGas, // gas limit + gethCommon.Big1, // gas fee + nil, // data ), ) - require.NoError(t, err) - - ret := new(big.Int).SetBytes(res.ReturnedValue) - require.Equal(t, blockNumber, ret) + _, err = blk.RunTransaction(tx) + require.Error(t, err) + require.True(t, types.IsEVMValidationError(err)) }) }) - }) - - t.Run("test sending transactions (happy case)", func(t *testing.T) { - account := testutils.GetTestEOAAccount(t, testutils.EOATestAccount1KeyHex) - fAddr := account.Address() - RunWithNewEmulator(t, db, func(env *emulator.Emulator) { - RunWithNewBlockView(t, env, func(blk types.BlockView) { - _, err := blk.DirectCall(types.NewDepositCall(fAddr, amount)) + t.Run("test sending transactions (bad signature)", func(t *testing.T) { + RunWithNewEmulator(t, backend, rootAddr, func(env *emulator.Emulator) { + ctx := types.NewDefaultBlockContext(blockNumber.Uint64()) + blk, err := env.NewBlockView(ctx) require.NoError(t, err) + tx := gethTypes.NewTx(&gethTypes.LegacyTx{ + Nonce: 0, + GasPrice: gethCommon.Big1, + Gas: gethParams.TxGas, // gas limit + To: nil, // to + Value: big.NewInt(1000), // amount + Data: nil, // data + V: big.NewInt(1), + R: big.NewInt(2), + S: big.NewInt(3), + }) + _, err = blk.RunTransaction(tx) + require.Error(t, err) + require.True(t, types.IsEVMValidationError(err)) }) }) - RunWithNewEmulator(t, db, func(env *emulator.Emulator) { - ctx := types.NewDefaultBlockContext(blockNumber.Uint64()) - ctx.GasFeeCollector = types.NewAddressFromString("coinbase") - coinbaseOrgBalance := gethCommon.Big1 - // small amount of money to create account - RunWithNewBlockView(t, env, func(blk types.BlockView) { - _, err := blk.DirectCall(types.NewDepositCall(ctx.GasFeeCollector, coinbaseOrgBalance)) - require.NoError(t, err) - }) + }) + }) +} - blk, err := env.NewBlockView(ctx) - require.NoError(t, err) - tx := account.PrepareAndSignTx( - t, - testAccount.ToCommon(), // to - nil, // data - big.NewInt(1000), // amount - gethParams.TxGas, // gas limit - gethCommon.Big1, // gas fee +func TestTransfers(t *testing.T) { + testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { + testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { - ) - _, err = blk.RunTransaction(tx) - require.NoError(t, err) + testAccount1 := types.NewAddressFromString("test1") + testAccount2 := types.NewAddressFromString("test2") - // check the balance of coinbase - RunWithNewReadOnlyBlockView(t, env, func(blk2 types.ReadOnlyBlockView) { - bal, err := blk2.BalanceOf(ctx.GasFeeCollector) - require.NoError(t, err) - expected := gethParams.TxGas*gethCommon.Big1.Uint64() + gethCommon.Big1.Uint64() - require.Equal(t, expected, bal.Uint64()) + amount := big.NewInt(0).Mul(big.NewInt(1337), big.NewInt(gethParams.Ether)) + amountToBeTransfered := big.NewInt(0).Mul(big.NewInt(100), big.NewInt(gethParams.Ether)) - nonce, err := blk2.NonceOf(fAddr) + RunWithNewEmulator(t, backend, rootAddr, func(em *emulator.Emulator) { + RunWithNewBlockView(t, em, func(blk types.BlockView) { + _, err := blk.DirectCall(types.NewDepositCall(testAccount1, amount)) require.NoError(t, err) - require.Equal(t, 1, int(nonce)) }) }) - }) - t.Run("test sending transactions (invalid nonce)", func(t *testing.T) { - account := testutils.GetTestEOAAccount(t, testutils.EOATestAccount1KeyHex) - fAddr := account.Address() - RunWithNewEmulator(t, db, func(env *emulator.Emulator) { - RunWithNewBlockView(t, env, func(blk types.BlockView) { - _, err := blk.DirectCall(types.NewDepositCall(fAddr, amount)) + + RunWithNewEmulator(t, backend, rootAddr, func(em *emulator.Emulator) { + RunWithNewBlockView(t, em, func(blk types.BlockView) { + _, err := blk.DirectCall(types.NewTransferCall(testAccount1, testAccount2, amountToBeTransfered)) require.NoError(t, err) }) }) - RunWithNewEmulator(t, db, func(env *emulator.Emulator) { - ctx := types.NewDefaultBlockContext(blockNumber.Uint64()) - blk, err := env.NewBlockView(ctx) - require.NoError(t, err) - tx := account.SignTx(t, - gethTypes.NewTransaction( - 100, // nonce - testAccount.ToCommon(), // to - big.NewInt(1000), // amount - gethParams.TxGas, // gas limit - gethCommon.Big1, // gas fee - nil, // data - ), - ) - _, err = blk.RunTransaction(tx) - require.Error(t, err) - require.True(t, types.IsEVMValidationError(err)) - }) - }) + RunWithNewEmulator(t, backend, rootAddr, func(em *emulator.Emulator) { + RunWithNewReadOnlyBlockView(t, em, func(blk types.ReadOnlyBlockView) { + bal, err := blk.BalanceOf(testAccount2) + require.NoError(t, err) + require.Equal(t, amountToBeTransfered.Uint64(), bal.Uint64()) - t.Run("test sending transactions (bad signature)", func(t *testing.T) { - RunWithNewEmulator(t, db, func(env *emulator.Emulator) { - ctx := types.NewDefaultBlockContext(blockNumber.Uint64()) - blk, err := env.NewBlockView(ctx) - require.NoError(t, err) - tx := gethTypes.NewTx(&gethTypes.LegacyTx{ - Nonce: 0, - GasPrice: gethCommon.Big1, - Gas: gethParams.TxGas, // gas limit - To: nil, // to - Value: big.NewInt(1000), // amount - Data: nil, // data - V: big.NewInt(1), - R: big.NewInt(2), - S: big.NewInt(3), + bal, err = blk.BalanceOf(testAccount1) + require.NoError(t, err) + require.Equal(t, new(big.Int).Sub(amount, amountToBeTransfered).Uint64(), bal.Uint64()) }) - _, err = blk.RunTransaction(tx) - require.Error(t, err) - require.True(t, types.IsEVMValidationError(err)) }) }) - }) } -func TestTransfers(t *testing.T) { - RunWithTestDB(t, func(db types.Database) { - testAccount1 := types.NewAddressFromString("test1") - testAccount2 := types.NewAddressFromString("test2") - - amount := big.NewInt(0).Mul(big.NewInt(1337), big.NewInt(gethParams.Ether)) - amountToBeTransfered := big.NewInt(0).Mul(big.NewInt(100), big.NewInt(gethParams.Ether)) - - RunWithNewEmulator(t, db, func(em *emulator.Emulator) { +func TestStorageNoSideEffect(t *testing.T) { + testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { + testutils.RunWithTestFlowEVMRootAddress(t, backend, func(flowEVMRoot flow.Address) { + var err error + em := emulator.NewEmulator(backend, flowEVMRoot) + testAccount := types.NewAddressFromString("test") + amount := big.NewInt(10) RunWithNewBlockView(t, em, func(blk types.BlockView) { - _, err := blk.DirectCall(types.NewDepositCall(testAccount1, amount)) + _, err = blk.DirectCall(types.NewDepositCall(testAccount, amount)) require.NoError(t, err) }) - }) - RunWithNewEmulator(t, db, func(em *emulator.Emulator) { + orgSize := backend.TotalStorageSize() RunWithNewBlockView(t, em, func(blk types.BlockView) { - _, err := blk.DirectCall(types.NewTransferCall(testAccount1, testAccount2, amountToBeTransfered)) - require.NoError(t, err) - }) - }) - - RunWithNewEmulator(t, db, func(em *emulator.Emulator) { - RunWithNewReadOnlyBlockView(t, em, func(blk types.ReadOnlyBlockView) { - bal, err := blk.BalanceOf(testAccount2) - require.NoError(t, err) - require.Equal(t, amountToBeTransfered.Uint64(), bal.Uint64()) - - bal, err = blk.BalanceOf(testAccount1) + _, err = blk.DirectCall(types.NewDepositCall(testAccount, amount)) require.NoError(t, err) - require.Equal(t, new(big.Int).Sub(amount, amountToBeTransfered).Uint64(), bal.Uint64()) }) + require.Equal(t, orgSize, backend.TotalStorageSize()) }) }) } -func TestDatabaseErrorHandling(t *testing.T) { +func TestCallingExtraPrecompiles(t *testing.T) { + testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { + testutils.RunWithTestFlowEVMRootAddress(t, backend, func(flowEVMRoot flow.Address) { + RunWithNewEmulator(t, backend, flowEVMRoot, func(em *emulator.Emulator) { - t.Run("test non-fatal db error handling", func(t *testing.T) { - db := &testutils.TestDatabase{ - GetRootHashFunc: func() (gethCommon.Hash, error) { - return gethTypes.EmptyRootHash, types.NewDatabaseError(fmt.Errorf("some non-fatal error")) - }, - } + testAccount := types.NewAddressFromString("test") + amount := big.NewInt(10_000_000) + RunWithNewBlockView(t, em, func(blk types.BlockView) { + _, err := blk.DirectCall(types.NewDepositCall(testAccount, amount)) + require.NoError(t, err) + }) - RunWithNewEmulator(t, db, func(em *emulator.Emulator) { - RunWithNewBlockView(t, em, func(blk types.BlockView) { - _, err := blk.DirectCall(types.NewDepositCall(types.EmptyAddress, big.NewInt(1))) - require.Error(t, err) - require.True(t, types.IsADatabaseError(err)) - }) - }) - }) + input := []byte{1, 2} + output := []byte{3, 4} + addr := testutils.RandomAddress(t) + pc := &MockedPrecompile{ + AddressFunc: func() types.Address { + return addr + }, + RequiredGasFunc: func(input []byte) uint64 { + return uint64(10) + }, + RunFunc: func(inp []byte) ([]byte, error) { + require.Equal(t, input, inp) + return output, nil + }, + } + + ctx := types.NewDefaultBlockContext(blockNumber.Uint64()) + ctx.ExtraPrecompiles = []types.Precompile{pc} - t.Run("test fatal db error handling", func(t *testing.T) { - db := &testutils.TestDatabase{ - GetRootHashFunc: func() (gethCommon.Hash, error) { - return gethTypes.EmptyRootHash, types.NewFatalError(fmt.Errorf("some non-fatal error")) - }, - } + blk, err := em.NewBlockView(ctx) + require.NoError(t, err) - RunWithNewEmulator(t, db, func(em *emulator.Emulator) { - RunWithNewBlockView(t, em, func(blk types.BlockView) { - _, err := blk.DirectCall(types.NewDepositCall(types.EmptyAddress, big.NewInt(1))) - require.Error(t, err) - require.True(t, types.IsAFatalError(err)) + res, err := blk.DirectCall( + types.NewContractCall( + testAccount, + types.NewAddress(addr.ToCommon()), + input, + 1_000_000, + big.NewInt(0), // this should be zero because the contract doesn't have receiver + ), + ) + require.NoError(t, err) + require.Equal(t, output, res.ReturnedValue) }) }) }) } -func TestStorageNoSideEffect(t *testing.T) { - t.Skip("we need to fix this issue ") - - testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { - testutils.RunWithTestFlowEVMRootAddress(t, backend, func(flowEVMRoot flow.Address) { - db, err := database.NewDatabase(backend, flowEVMRoot) - require.NoError(t, err) +type MockedPrecompile struct { + AddressFunc func() types.Address + RequiredGasFunc func(input []byte) uint64 + RunFunc func(input []byte) ([]byte, error) +} - em := emulator.NewEmulator(db) - testAccount := types.NewAddressFromString("test") +func (mp *MockedPrecompile) Address() types.Address { + if mp.AddressFunc == nil { + panic("Address not set for the mocked precompile") + } + return mp.AddressFunc() +} - amount := big.NewInt(100) - RunWithNewBlockView(t, em, func(blk types.BlockView) { - _, err = blk.DirectCall(types.NewDepositCall(testAccount, amount)) - require.NoError(t, err) - }) +func (mp *MockedPrecompile) RequiredGas(input []byte) uint64 { + if mp.RequiredGasFunc == nil { + panic("RequiredGas not set for the mocked precompile") + } + return mp.RequiredGasFunc(input) +} - orgSize := backend.TotalStorageSize() - RunWithNewBlockView(t, em, func(blk types.BlockView) { - _, err = blk.DirectCall(types.NewDepositCall(testAccount, amount)) - require.NoError(t, err) - }) - require.Equal(t, orgSize, backend.TotalStorageSize()) - }) - }) +func (mp *MockedPrecompile) Run(input []byte) ([]byte, error) { + if mp.RunFunc == nil { + panic("Run not set for the mocked precompile") + } + return mp.RunFunc(input) } diff --git a/fvm/evm/emulator/state/account.go b/fvm/evm/emulator/state/account.go new file mode 100644 index 00000000000..489d944b798 --- /dev/null +++ b/fvm/evm/emulator/state/account.go @@ -0,0 +1,62 @@ +package state + +import ( + "math/big" + + gethCommon "github.com/ethereum/go-ethereum/common" + gethTypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/rlp" +) + +// Account holds the metadata of an address and provides (de)serialization functionality +// +// Note that code and storage slots of an address is not part of this data structure +type Account struct { + // address + Address gethCommon.Address + // balance of the address + Balance *big.Int + // nonce of the address + Nonce uint64 + // hash of the code + // if no code the gethTypes.EmptyCodeHash is stored + CodeHash gethCommon.Hash + // the id of the collection holds storage slots for this account + // this value is nil for EOA accounts + CollectionID []byte +} + +// NewAccount constructs a new account +func NewAccount( + address gethCommon.Address, + balance *big.Int, + nonce uint64, + codeHash gethCommon.Hash, + collectionID []byte, +) *Account { + return &Account{ + Address: address, + Balance: balance, + Nonce: nonce, + CodeHash: codeHash, + CollectionID: collectionID, + } +} + +func (a *Account) HasCode() bool { + return a.CodeHash != gethTypes.EmptyCodeHash +} + +// Encode encodes the account +func (a *Account) Encode() ([]byte, error) { + return rlp.EncodeToBytes(a) +} + +// DecodeAccount constructs a new account from the encoded data +func DecodeAccount(inp []byte) (*Account, error) { + if len(inp) == 0 { + return nil, nil + } + a := &Account{} + return a, rlp.DecodeBytes(inp, a) +} diff --git a/fvm/evm/emulator/state/account_test.go b/fvm/evm/emulator/state/account_test.go new file mode 100644 index 00000000000..c3e2fca047e --- /dev/null +++ b/fvm/evm/emulator/state/account_test.go @@ -0,0 +1,28 @@ +package state_test + +import ( + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" + + "github.com/onflow/flow-go/fvm/evm/emulator/state" + "github.com/onflow/flow-go/fvm/evm/testutils" +) + +func TestAccountEncoding(t *testing.T) { + acc := state.NewAccount( + testutils.RandomCommonAddress(t), + testutils.RandomBigInt(1000), + uint64(2), + common.BytesToHash([]byte{1}), + []byte{2}, + ) + + encoded, err := acc.Encode() + require.NoError(t, err) + + ret, err := state.DecodeAccount(encoded) + require.NoError(t, err) + require.Equal(t, acc, ret) +} diff --git a/fvm/evm/emulator/state/base.go b/fvm/evm/emulator/state/base.go new file mode 100644 index 00000000000..c2b83ce9fa3 --- /dev/null +++ b/fvm/evm/emulator/state/base.go @@ -0,0 +1,593 @@ +package state + +import ( + "fmt" + "math/big" + + gethCommon "github.com/ethereum/go-ethereum/common" + gethTypes "github.com/ethereum/go-ethereum/core/types" + "github.com/onflow/atree" + + "github.com/onflow/flow-go/fvm/evm/types" + "github.com/onflow/flow-go/model/flow" +) + +const ( + // AccountsStorageIDKey is the path where we store the collection ID for accounts + AccountsStorageIDKey = "AccountsStorageIDKey" + // CodesStorageIDKey is the path where we store the collection ID for codes + CodesStorageIDKey = "CodesStorageIDKey" +) + +// BaseView implements a types.BaseView +// it acts as the base layer of state queries for the stateDB +// it stores accounts, codes and storage slots. +// +// under the hood it uses a set of collections, +// one for account's meta data, one for codes +// and one for each of account storage space. +type BaseView struct { + rootAddress flow.Address + ledger atree.Ledger + collectionProvider *CollectionProvider + + // collections + accounts *Collection + codes *Collection + slots map[gethCommon.Address]*Collection + + // cached values + cachedAccounts map[gethCommon.Address]*Account + cachedCodes map[gethCommon.Address][]byte + cachedSlots map[types.SlotAddress]gethCommon.Hash + + // flags + accountSetupOnCommit bool + codeSetupOnCommit bool +} + +var _ types.BaseView = &BaseView{} + +// NewBaseView constructs a new base view +func NewBaseView(ledger atree.Ledger, rootAddress flow.Address) (*BaseView, error) { + cp, err := NewCollectionProvider(atree.Address(rootAddress), ledger) + if err != nil { + return nil, err + } + + view := &BaseView{ + ledger: ledger, + rootAddress: rootAddress, + collectionProvider: cp, + + slots: make(map[gethCommon.Address]*Collection), + + cachedAccounts: make(map[gethCommon.Address]*Account), + cachedCodes: make(map[gethCommon.Address][]byte), + cachedSlots: make(map[types.SlotAddress]gethCommon.Hash), + } + + // fetch the account collection, if not exist, create one + view.accounts, view.accountSetupOnCommit, err = view.fetchOrCreateCollection(AccountsStorageIDKey) + if err != nil { + return nil, err + } + + // fetch the code collection, if not exist, create one + view.codes, view.codeSetupOnCommit, err = view.fetchOrCreateCollection(CodesStorageIDKey) + if err != nil { + return nil, err + } + + return view, nil +} + +// Exist returns true if the address exist in the state +func (v *BaseView) Exist(addr gethCommon.Address) (bool, error) { + acc, err := v.getAccount(addr) + return acc != nil, err +} + +// IsCreated returns true if the address has been created in the context of this transaction +func (v *BaseView) IsCreated(gethCommon.Address) bool { + return false +} + +// HasSelfDestructed returns true if an address is flagged for destruction at the end of transaction +func (v *BaseView) HasSelfDestructed(gethCommon.Address) (bool, *big.Int) { + return false, new(big.Int) +} + +// GetBalance returns the balance of an address +// +// for non-existent accounts it returns a balance of zero +func (v *BaseView) GetBalance(addr gethCommon.Address) (*big.Int, error) { + acc, err := v.getAccount(addr) + bal := big.NewInt(0) + if acc != nil { + bal = acc.Balance + } + return bal, err +} + +// GetNonce returns the nonce of an address +// +// for non-existent accounts it returns zero +func (v *BaseView) GetNonce(addr gethCommon.Address) (uint64, error) { + acc, err := v.getAccount(addr) + nonce := uint64(0) + if acc != nil { + nonce = acc.Nonce + } + return nonce, err +} + +// GetCode returns the code of an address +// +// for non-existent accounts or accounts without a code (e.g. EOAs) it returns nil +func (v *BaseView) GetCode(addr gethCommon.Address) ([]byte, error) { + return v.getCode(addr) +} + +// GetCodeHash returns the code hash of an address +// +// for non-existent accounts it returns gethCommon.Hash{} +// and for accounts without a code (e.g. EOAs) it returns default empty +// hash value (gethTypes.EmptyCodeHash) +func (v *BaseView) GetCodeHash(addr gethCommon.Address) (gethCommon.Hash, error) { + acc, err := v.getAccount(addr) + codeHash := gethCommon.Hash{} + if acc != nil { + codeHash = acc.CodeHash + } + return codeHash, err +} + +// GetCodeSize returns the code size of an address +// +// for non-existent accounts or accounts without a code (e.g. EOAs) it returns zero +func (v *BaseView) GetCodeSize(addr gethCommon.Address) (int, error) { + code, err := v.GetCode(addr) + return len(code), err +} + +// GetState returns values for a slot in the main storage +// +// for non-existent slots it returns the default empty hash value (gethTypes.EmptyCodeHash) +func (v *BaseView) GetState(sk types.SlotAddress) (gethCommon.Hash, error) { + return v.getSlot(sk) +} + +// UpdateSlot updates the value for a slot +func (v *BaseView) UpdateSlot(sk types.SlotAddress, value gethCommon.Hash) error { + return v.storeSlot(sk, value) +} + +// GetRefund returns the total amount of (gas) refund +// +// this method returns the value of zero +func (v *BaseView) GetRefund() uint64 { + return 0 +} + +// GetTransientState returns values for an slot transient storage +// +// transient storage is not a functionality for the base view so it always +// returns the default value for non-existent slots +func (v *BaseView) GetTransientState(types.SlotAddress) gethCommon.Hash { + return gethCommon.Hash{} +} + +// AddressInAccessList checks if an address is in the access list +// +// access list control is not a functionality of the base view +// it always returns false +func (v *BaseView) AddressInAccessList(gethCommon.Address) bool { + return false +} + +// SlotInAccessList checks if a slot is in the access list +// +// access list control is not a functionality of the base view +// it always returns false +func (v *BaseView) SlotInAccessList(types.SlotAddress) (addressOk bool, slotOk bool) { + return false, false +} + +// CreateAccount creates a new account +func (v *BaseView) CreateAccount( + addr gethCommon.Address, + balance *big.Int, + nonce uint64, + code []byte, + codeHash gethCommon.Hash, +) error { + var colID []byte + // if is an smart contract account + if len(code) > 0 { + err := v.updateAccountCode(addr, code, codeHash) + if err != nil { + return err + } + } + + // create a new account and store it + acc := NewAccount(addr, balance, nonce, codeHash, colID) + + // no need to update the cache , storeAccount would update the cache + return v.storeAccount(acc) +} + +// UpdateAccount updates an account's meta data +func (v *BaseView) UpdateAccount( + addr gethCommon.Address, + balance *big.Int, + nonce uint64, + code []byte, + codeHash gethCommon.Hash, +) error { + acc, err := v.getAccount(addr) + if err != nil { + return err + } + // if update is called on a non existing account + // we gracefully call the create account + // TODO: but we might need to revisit this action in the future + if acc == nil { + return v.CreateAccount(addr, balance, nonce, code, codeHash) + } + + // update account code + err = v.updateAccountCode(addr, code, codeHash) + if err != nil { + return err + } + // TODO: maybe purge the state in the future as well + // currently the behaviour of stateDB doesn't purge the data + // We don't need to check if the code is empty and we purge the state + // this is not possible right now. + + newAcc := NewAccount(addr, balance, nonce, codeHash, acc.CollectionID) + // no need to update the cache , storeAccount would update the cache + return v.storeAccount(newAcc) +} + +// DeleteAccount deletes an account's meta data, code, and +// storage slots associated with that address +func (v *BaseView) DeleteAccount(addr gethCommon.Address) error { + // 1. check account exists + acc, err := v.getAccount(addr) + if err != nil { + return err + } + if acc == nil { + return fmt.Errorf("account doesn't exist to be deleted") + } + + // 2. remove the code + if acc.HasCode() { + err = v.updateAccountCode(addr, nil, gethTypes.EmptyCodeHash) + if err != nil { + return err + } + } + + // 3. update the cache + delete(v.cachedAccounts, addr) + + // 4. collections + err = v.accounts.Remove(addr.Bytes()) + if err != nil { + return err + } + + // 5. remove storage slots + if len(acc.CollectionID) > 0 { + col, found := v.slots[addr] + if !found { + col, err = v.collectionProvider.CollectionByID(acc.CollectionID) + if err != nil { + return err + } + } + // delete all slots related to this account (eip-6780) + keys, err := col.Destroy() + if err != nil { + return err + } + + delete(v.slots, addr) + + for _, key := range keys { + delete(v.cachedSlots, types.SlotAddress{ + Address: addr, + Key: gethCommon.BytesToHash(key), + }) + } + } + return nil +} + +// Commit commits the changes to the underlying storage layers +func (v *BaseView) Commit() error { + // commit collection changes + err := v.collectionProvider.Commit() + if err != nil { + return err + } + + // if this is the first time we are setting up an + // account collection, store its collection id. + if v.accountSetupOnCommit { + err = v.ledger.SetValue(v.rootAddress[:], []byte(AccountsStorageIDKey), v.accounts.CollectionID()) + if err != nil { + return err + } + v.accountSetupOnCommit = false + + } + + // if this is the first time we are setting up an + // code collection, store its collection id. + if v.codeSetupOnCommit { + err = v.ledger.SetValue(v.rootAddress[:], []byte(CodesStorageIDKey), v.codes.CollectionID()) + if err != nil { + return err + } + v.codeSetupOnCommit = false + } + return nil +} + +// NumberOfContracts returns the number of unique contracts +func (v *BaseView) NumberOfContracts() uint64 { + return v.codes.Size() +} + +// NumberOfContracts returns the number of accounts +func (v *BaseView) NumberOfAccounts() uint64 { + return v.accounts.Size() +} + +func (v *BaseView) fetchOrCreateCollection(path string) (collection *Collection, created bool, error error) { + collectionID, err := v.ledger.GetValue(v.rootAddress[:], []byte(path)) + if err != nil { + return nil, false, err + } + if len(collectionID) == 0 { + collection, err = v.collectionProvider.NewCollection() + return collection, true, err + } + collection, err = v.collectionProvider.CollectionByID(collectionID) + return collection, false, err +} + +func (v *BaseView) getAccount(addr gethCommon.Address) (*Account, error) { + // check cached accounts first + acc, found := v.cachedAccounts[addr] + if found { + return acc, nil + } + + // then collect it from the account collection + data, err := v.accounts.Get(addr.Bytes()) + if err != nil { + return nil, err + } + // decode it + acc, err = DecodeAccount(data) + if err != nil { + return nil, err + } + // cache it + if acc != nil { + v.cachedAccounts[addr] = acc + } + return acc, nil +} + +func (v *BaseView) storeAccount(acc *Account) error { + data, err := acc.Encode() + if err != nil { + return err + } + // update the cache + v.cachedAccounts[acc.Address] = acc + return v.accounts.Set(acc.Address.Bytes(), data) +} + +func (v *BaseView) getCode(addr gethCommon.Address) ([]byte, error) { + // check the cache first + code, found := v.cachedCodes[addr] + if found { + return code, nil + } + + // get account + acc, err := v.getAccount(addr) + if err != nil { + return nil, err + } + + if acc == nil || !acc.HasCode() { + return nil, nil + } + + // collect the container from the code collection by codeHash + encoded, err := v.codes.Get(acc.CodeHash.Bytes()) + if err != nil { + return nil, err + } + if len(encoded) == 0 { + return nil, nil + } + + codeCont, err := CodeContainerFromEncoded(encoded) + if err != nil { + return nil, err + } + code = codeCont.Code() + if len(code) > 0 { + v.cachedCodes[addr] = code + } + return code, nil +} + +func (v *BaseView) updateAccountCode(addr gethCommon.Address, code []byte, codeHash gethCommon.Hash) error { + // get account + acc, err := v.getAccount(addr) + if err != nil { + return err + } + // if is a new account + if acc == nil { + if len(code) == 0 { + return nil + } + v.cachedCodes[addr] = code + return v.addCode(code, codeHash) + } + + // skip if is the same code + if acc.CodeHash == codeHash { + return nil + } + + // clean old code first if exist + if acc.HasCode() { + delete(v.cachedCodes, addr) + err = v.removeCode(acc.CodeHash) + if err != nil { + return err + } + } + + // add new code + if len(code) == 0 { + return nil + } + v.cachedCodes[addr] = code + return v.addCode(code, codeHash) +} + +func (v *BaseView) removeCode(codeHash gethCommon.Hash) error { + encoded, err := v.codes.Get(codeHash.Bytes()) + if err != nil { + return err + } + if len(encoded) == 0 { + return nil + } + + cc, err := CodeContainerFromEncoded(encoded) + if err != nil { + return err + } + if cc.DecRefCount() { + return v.codes.Remove(codeHash.Bytes()) + } + return v.codes.Set(codeHash.Bytes(), cc.Encode()) +} + +func (v *BaseView) addCode(code []byte, codeHash gethCommon.Hash) error { + encoded, err := v.codes.Get(codeHash.Bytes()) + if err != nil { + return err + } + // if is the first time the code is getting deployed + if len(encoded) == 0 { + return v.codes.Set(codeHash.Bytes(), NewCodeContainer(code).Encode()) + } + + // otherwise update the cc + cc, err := CodeContainerFromEncoded(encoded) + if err != nil { + return err + } + cc.IncRefCount() + return v.codes.Set(codeHash.Bytes(), cc.Encode()) +} + +func (v *BaseView) getSlot(sk types.SlotAddress) (gethCommon.Hash, error) { + value, found := v.cachedSlots[sk] + if found { + return value, nil + } + + acc, err := v.getAccount(sk.Address) + if err != nil { + return gethCommon.Hash{}, err + } + if acc == nil || len(acc.CollectionID) == 0 { + return gethCommon.Hash{}, nil + } + + col, err := v.getSlotCollection(acc) + if err != nil { + return gethCommon.Hash{}, err + } + + val, err := col.Get(sk.Key.Bytes()) + if err != nil { + return gethCommon.Hash{}, err + } + value = gethCommon.BytesToHash(val) + v.cachedSlots[sk] = value + return value, nil +} + +func (v *BaseView) storeSlot(sk types.SlotAddress, data gethCommon.Hash) error { + acc, err := v.getAccount(sk.Address) + if err != nil { + return err + } + if acc == nil { + return fmt.Errorf("slot belongs to a non-existing account") + } + if !acc.HasCode() { + return fmt.Errorf("slot belongs to a non-smart contract account") + } + col, err := v.getSlotCollection(acc) + if err != nil { + return err + } + + emptyValue := gethCommon.Hash{} + if data == emptyValue { + delete(v.cachedSlots, sk) + return col.Remove(sk.Key.Bytes()) + } + v.cachedSlots[sk] = data + return col.Set(sk.Key.Bytes(), data.Bytes()) +} + +func (v *BaseView) getSlotCollection(acc *Account) (*Collection, error) { + var err error + + if len(acc.CollectionID) == 0 { + // create a new collection for slots + col, err := v.collectionProvider.NewCollection() + if err != nil { + return nil, err + } + // cache collection + v.slots[acc.Address] = col + // update account's collection ID + acc.CollectionID = col.CollectionID() + err = v.storeAccount(acc) + if err != nil { + return nil, err + } + return col, nil + } + + col, found := v.slots[acc.Address] + if !found { + col, err = v.collectionProvider.CollectionByID(acc.CollectionID) + if err != nil { + return nil, err + } + v.slots[acc.Address] = col + } + return col, nil +} diff --git a/fvm/evm/emulator/state/base_test.go b/fvm/evm/emulator/state/base_test.go new file mode 100644 index 00000000000..af4696abdfe --- /dev/null +++ b/fvm/evm/emulator/state/base_test.go @@ -0,0 +1,339 @@ +package state_test + +import ( + "math/big" + "testing" + + gethCommon "github.com/ethereum/go-ethereum/common" + gethTypes "github.com/ethereum/go-ethereum/core/types" + gethCrypto "github.com/ethereum/go-ethereum/crypto" + "github.com/stretchr/testify/require" + + "github.com/onflow/flow-go/fvm/evm/emulator/state" + "github.com/onflow/flow-go/fvm/evm/testutils" + "github.com/onflow/flow-go/fvm/evm/types" + "github.com/onflow/flow-go/model/flow" +) + +func TestBaseView(t *testing.T) { + t.Parallel() + + t.Run("test account functionalities", func(t *testing.T) { + ledger := testutils.GetSimpleValueStore() + rootAddr := flow.Address{1, 2, 3, 4, 5, 6, 7, 8} + view, err := state.NewBaseView(ledger, rootAddr) + require.NoError(t, err) + + addr1 := testutils.RandomCommonAddress(t) + + // data calls for a non-existent account + checkAccount(t, + view, + addr1, + false, + big.NewInt(0), + uint64(0), + nil, + gethCommon.Hash{}, + ) + + // create an account with code + newBal := big.NewInt(10) + newNonce := uint64(5) + newCode := []byte("some code") + newCodeHash := gethCommon.Hash{1, 2} + + err = view.CreateAccount(addr1, newBal, newNonce, newCode, newCodeHash) + require.NoError(t, err) + + // check data from cache + checkAccount(t, + view, + addr1, + true, + newBal, + newNonce, + newCode, + newCodeHash, + ) + + // commit the changes and create a new baseview + err = view.Commit() + require.NoError(t, err) + + view, err = state.NewBaseView(ledger, rootAddr) + require.NoError(t, err) + + checkAccount(t, + view, + addr1, + true, + newBal, + newNonce, + newCode, + newCodeHash, + ) + + // test update account + + newBal = big.NewInt(12) + newNonce = uint64(6) + newCode = []byte("some new code") + newCodeHash = gethCommon.Hash{2, 3} + err = view.UpdateAccount(addr1, newBal, newNonce, newCode, newCodeHash) + require.NoError(t, err) + + // check data from cache + checkAccount(t, + view, + addr1, + true, + newBal, + newNonce, + newCode, + newCodeHash, + ) + + // commit the changes and create a new baseview + err = view.Commit() + require.NoError(t, err) + + view, err = state.NewBaseView(ledger, rootAddr) + require.NoError(t, err) + + checkAccount(t, + view, + addr1, + true, + newBal, + newNonce, + newCode, + newCodeHash, + ) + + // test delete account + + err = view.DeleteAccount(addr1) + require.NoError(t, err) + + // check from cache + checkAccount(t, + view, + addr1, + false, + big.NewInt(0), + uint64(0), + nil, + gethCommon.Hash{}, + ) + + // commit the changes and create a new baseview + err = view.Commit() + require.NoError(t, err) + + view, err = state.NewBaseView(ledger, rootAddr) + require.NoError(t, err) + + checkAccount(t, + view, + addr1, + false, + big.NewInt(0), + uint64(0), + nil, + gethCommon.Hash{}, + ) + }) + + t.Run("test slot storage", func(t *testing.T) { + ledger := testutils.GetSimpleValueStore() + rootAddr := flow.Address{1, 2, 3, 4, 5, 6, 7, 8} + view, err := state.NewBaseView(ledger, rootAddr) + require.NoError(t, err) + + addr1 := testutils.RandomCommonAddress(t) + key1 := testutils.RandomCommonHash(t) + slot1 := types.SlotAddress{ + Address: addr1, + Key: key1, + } + + // non-existent account + value, err := view.GetState(slot1) + require.NoError(t, err) + require.Equal(t, value, gethCommon.Hash{}) + + // store a new value + newValue := testutils.RandomCommonHash(t) + + // updating slot for non-existent account should fail + err = view.UpdateSlot(slot1, newValue) + require.Error(t, err) + + // account should have code to have slots + err = view.CreateAccount(addr1, big.NewInt(10), 0, []byte("ABC"), gethCommon.Hash{1, 2, 3}) + require.NoError(t, err) + + err = view.UpdateSlot(slot1, newValue) + require.NoError(t, err) + + // return result from the cache + value, err = view.GetState(slot1) + require.NoError(t, err) + require.Equal(t, newValue, value) + + // commit changes + err = view.Commit() + require.NoError(t, err) + + view2, err := state.NewBaseView(ledger, rootAddr) + require.NoError(t, err) + + // return state from ledger + value, err = view2.GetState(slot1) + require.NoError(t, err) + require.Equal(t, newValue, value) + }) + + t.Run("default values method calls", func(t *testing.T) { + // calls to these method that has always same value + view, err := state.NewBaseView(testutils.GetSimpleValueStore(), flow.Address{1, 2, 3, 4}) + require.NoError(t, err) + + dest, bal := view.HasSelfDestructed(gethCommon.Address{}) + require.Equal(t, false, dest) + require.Equal(t, new(big.Int), bal) + require.Equal(t, false, view.IsCreated(gethCommon.Address{})) + require.Equal(t, uint64(0), view.GetRefund()) + require.Equal(t, gethCommon.Hash{}, view.GetTransientState(types.SlotAddress{})) + require.Equal(t, false, view.AddressInAccessList(gethCommon.Address{})) + addrFound, slotFound := view.SlotInAccessList(types.SlotAddress{}) + require.Equal(t, false, addrFound) + require.Equal(t, false, slotFound) + }) + + t.Run("test code storage", func(t *testing.T) { + ledger := testutils.GetSimpleValueStore() + rootAddr := flow.Address{1, 2, 3, 4, 5, 6, 7, 8} + view, err := state.NewBaseView(ledger, rootAddr) + require.NoError(t, err) + + bal := new(big.Int) + nonce := uint64(0) + + addr1 := testutils.RandomCommonAddress(t) + var code1 []byte + codeHash1 := gethTypes.EmptyCodeHash + err = view.CreateAccount(addr1, bal, nonce, code1, codeHash1) + require.NoError(t, err) + + ret, err := view.GetCode(addr1) + require.NoError(t, err) + require.Equal(t, code1, ret) + + addr2 := testutils.RandomCommonAddress(t) + code2 := []byte("code2") + codeHash2 := gethCrypto.Keccak256Hash(code2) + err = view.CreateAccount(addr2, bal, nonce, code2, codeHash2) + require.NoError(t, err) + + ret, err = view.GetCode(addr2) + require.NoError(t, err) + require.Equal(t, code2, ret) + + err = view.Commit() + require.NoError(t, err) + orgSize := ledger.TotalStorageSize() + require.Equal(t, uint64(1), view.NumberOfContracts()) + + err = view.UpdateAccount(addr1, bal, nonce, code2, codeHash2) + require.NoError(t, err) + + err = view.Commit() + require.NoError(t, err) + require.Equal(t, orgSize, ledger.TotalStorageSize()) + require.Equal(t, uint64(1), view.NumberOfContracts()) + + ret, err = view.GetCode(addr1) + require.NoError(t, err) + require.Equal(t, code2, ret) + + // now remove the code from account 1 + err = view.UpdateAccount(addr1, bal, nonce, code1, codeHash1) + require.NoError(t, err) + + // there should not be any side effect on the code return for account 2 + // and no impact on storage size + ret, err = view.GetCode(addr2) + require.NoError(t, err) + require.Equal(t, code2, ret) + + ret, err = view.GetCode(addr1) + require.NoError(t, err) + require.Equal(t, code1, ret) + + err = view.Commit() + require.NoError(t, err) + require.Equal(t, orgSize, ledger.TotalStorageSize()) + require.Equal(t, uint64(1), view.NumberOfContracts()) + + // now update account 2 and there should a reduction in storage + err = view.UpdateAccount(addr2, bal, nonce, code1, codeHash1) + require.NoError(t, err) + + ret, err = view.GetCode(addr2) + require.NoError(t, err) + require.Equal(t, code1, ret) + + err = view.Commit() + require.NoError(t, err) + require.Greater(t, orgSize, ledger.TotalStorageSize()) + require.Equal(t, uint64(0), view.NumberOfContracts()) + + // delete account 2 + err = view.DeleteAccount(addr2) + require.NoError(t, err) + + ret, err = view.GetCode(addr2) + require.NoError(t, err) + require.Len(t, ret, 0) + + require.Greater(t, orgSize, ledger.TotalStorageSize()) + require.Equal(t, uint64(1), view.NumberOfAccounts()) + }) + +} + +func checkAccount(t *testing.T, + view *state.BaseView, + addr gethCommon.Address, + exists bool, + balance *big.Int, + nonce uint64, + code []byte, + codeHash gethCommon.Hash, +) { + ex, err := view.Exist(addr) + require.NoError(t, err) + require.Equal(t, exists, ex) + + bal, err := view.GetBalance(addr) + require.NoError(t, err) + require.Equal(t, balance, bal) + + no, err := view.GetNonce(addr) + require.NoError(t, err) + require.Equal(t, nonce, no) + + cd, err := view.GetCode(addr) + require.NoError(t, err) + require.Equal(t, code, cd) + + cs, err := view.GetCodeSize(addr) + require.NoError(t, err) + require.Equal(t, len(code), cs) + + ch, err := view.GetCodeHash(addr) + require.NoError(t, err) + require.Equal(t, codeHash, ch) +} diff --git a/fvm/evm/emulator/state/code.go b/fvm/evm/emulator/state/code.go new file mode 100644 index 00000000000..bb893a30d39 --- /dev/null +++ b/fvm/evm/emulator/state/code.go @@ -0,0 +1,79 @@ +package state + +import ( + "encoding/binary" + "fmt" +) + +// CodeContainer contains codes and keeps +// track of reference counts +type CodeContainer struct { + code []byte + // keeping encoded so we can reuse it later + buffer []byte + refCount uint64 +} + +// NewCodeContainer constructs a new code container +func NewCodeContainer(code []byte) *CodeContainer { + return &CodeContainer{ + code: code, + refCount: 1, + } +} + +// CodeContainerFromEncoded constructs a code container from the encoded data +func CodeContainerFromEncoded(encoded []byte) (*CodeContainer, error) { + if len(encoded) < 8 { + return nil, fmt.Errorf("invalid length for the encoded code container") + } + return &CodeContainer{ + refCount: binary.BigEndian.Uint64(encoded[:8]), + buffer: encoded, // keep encoded as buffer for future use + code: encoded[8:], + }, nil +} + +// Code returns the code part of the code container +func (cc *CodeContainer) Code() []byte { + return cc.code +} + +// RefCount returns the ref count +func (cc *CodeContainer) RefCount() uint64 { + return cc.refCount +} + +// IncRefCount increment the ref count +func (cc *CodeContainer) IncRefCount() { + cc.refCount++ +} + +// DecRefCount decrement the ref count and +// returns true if the ref has reached to zero +func (cc *CodeContainer) DecRefCount() bool { + // check if ref is already zero + // this condition should never happen + // but better to be here to prevent underflow + if cc.refCount == 0 { + return true + } + cc.refCount-- + return cc.refCount == 0 +} + +// Encoded returns the encoded content of the code container +func (cc *CodeContainer) Encode() []byte { + // try using the buffer if possible to avoid + // extra allocations + encodedLen := 8 + len(cc.code) + var encoded []byte + if len(cc.buffer) < encodedLen { + encoded = make([]byte, encodedLen) + } else { + encoded = cc.buffer[:encodedLen] + } + binary.BigEndian.PutUint64(encoded[:8], cc.refCount) + copy(encoded[8:], cc.code) + return encoded +} diff --git a/fvm/evm/emulator/state/code_test.go b/fvm/evm/emulator/state/code_test.go new file mode 100644 index 00000000000..2a351e08073 --- /dev/null +++ b/fvm/evm/emulator/state/code_test.go @@ -0,0 +1,35 @@ +package state_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/onflow/flow-go/fvm/evm/emulator/state" +) + +func TestCodeContainer(t *testing.T) { + code := []byte("some code") + + // test construction + cc := state.NewCodeContainer(code) + require.Equal(t, uint64(1), cc.RefCount()) + require.Equal(t, code, cc.Code()) + + // test increment + cc.IncRefCount() + require.Equal(t, uint64(2), cc.RefCount()) + + // test encoding + encoded := cc.Encode() + cc, err := state.CodeContainerFromEncoded(encoded) + require.NoError(t, err) + require.Equal(t, uint64(2), cc.RefCount()) + require.Equal(t, code, cc.Code()) + + // test decrement + require.Equal(t, false, cc.DecRefCount()) + require.Equal(t, uint64(1), cc.RefCount()) + require.Equal(t, true, cc.DecRefCount()) + require.Equal(t, uint64(0), cc.RefCount()) +} diff --git a/fvm/evm/emulator/state/collection.go b/fvm/evm/emulator/state/collection.go new file mode 100644 index 00000000000..780d81652df --- /dev/null +++ b/fvm/evm/emulator/state/collection.go @@ -0,0 +1,431 @@ +package state + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "math" + "runtime" + + "github.com/fxamacker/cbor/v2" + "github.com/onflow/atree" +) + +const ( + storageIDSize = 16 +) + +// CollectionProvider provides access to collections +type CollectionProvider struct { + rootAddr atree.Address + storage *atree.PersistentSlabStorage +} + +// NewCollectionProvider constructs a new CollectionProvider +func NewCollectionProvider( + rootAddr atree.Address, + ledger atree.Ledger, +) (*CollectionProvider, error) { + // empty address is not allowed (causes issues with atree) + if rootAddr == atree.AddressUndefined { + return nil, fmt.Errorf("empty address as root is not allowed") + } + baseStorage := atree.NewLedgerBaseStorage(ledger) + storage, err := NewPersistentSlabStorage(baseStorage) + return &CollectionProvider{ + rootAddr: rootAddr, + storage: storage, + }, err +} + +// CollectionByID returns the collection by collection ID +// +// if no collection is found with that collection id, it return error +// Warning: this method should only used only once for each collection and +// the returned pointer should be kept for the future. +// calling twice for the same collection might result in odd-behaviours +// currently collection provider doesn't do any internal caching to protect aginast these cases +func (cp *CollectionProvider) CollectionByID(collectionID []byte) (*Collection, error) { + storageID, err := atree.NewStorageIDFromRawBytes(collectionID) + if err != nil { + return nil, err + } + // sanity check the storage ID address + if storageID.Address != cp.rootAddr { + return nil, fmt.Errorf("root address mismatch %x != %x", storageID.Address, cp.rootAddr) + } + + omap, err := atree.NewMapWithRootID(cp.storage, storageID, atree.NewDefaultDigesterBuilder()) + if err != nil { + return nil, err + } + return &Collection{ + omap: omap, + storage: cp.storage, + collectionID: collectionID, + }, nil +} + +// NewCollection constructs a new collection +func (cp *CollectionProvider) NewCollection() (*Collection, error) { + omap, err := atree.NewMap(cp.storage, cp.rootAddr, atree.NewDefaultDigesterBuilder(), emptyTypeInfo{}) + if err != nil { + return nil, err + } + storageIDBytes := make([]byte, storageIDSize) + _, err = omap.StorageID().ToRawBytes(storageIDBytes) + if err != nil { + return nil, err + } + return &Collection{ + storage: cp.storage, + omap: omap, + collectionID: storageIDBytes, // we reuse the storageID bytes as collectionID + }, nil +} + +// Commit commits all changes to the collections with changes +func (cp *CollectionProvider) Commit() error { + return cp.storage.FastCommit(runtime.NumCPU()) +} + +// Collection provides a persistent and compact way of storing key/value pairs +// each collection has a unique collectionID that can be used to fetch the collection +// +// TODO(ramtin): we might not need any extra hashing on the atree side +// and optimize this to just use the key given the keys are hashed ? +type Collection struct { + omap *atree.OrderedMap + storage *atree.PersistentSlabStorage + collectionID []byte +} + +// CollectionID returns the unique id for the collection +func (c *Collection) CollectionID() []byte { + return c.collectionID +} + +// Get gets the value for the given key +// +// if key doesn't exist it returns nil (no error) +func (c *Collection) Get(key []byte) ([]byte, error) { + data, err := c.omap.Get(compare, hashInputProvider, NewByteStringValue(key)) + if err != nil { + var keyNotFoundError *atree.KeyNotFoundError + if errors.As(err, &keyNotFoundError) { + return nil, nil + } + return nil, err + } + + value, err := data.StoredValue(c.omap.Storage) + if err != nil { + return nil, err + } + + return value.(ByteStringValue).Bytes(), nil +} + +// Set sets the value for the given key +// +// if a value already stored at the given key it replaces the value +func (c *Collection) Set(key, value []byte) error { + existingValueStorable, err := c.omap.Set(compare, hashInputProvider, NewByteStringValue(key), NewByteStringValue(value)) + if err != nil { + return err + } + + if id, ok := existingValueStorable.(atree.StorageIDStorable); ok { + // NOTE: deep remove isn't necessary because value is ByteStringValue (not container) + err := c.storage.Remove(atree.StorageID(id)) + if err != nil { + return err + } + } + return nil +} + +// Remove removes a key from the collection +// +// if the key doesn't exist it return no error +func (c *Collection) Remove(key []byte) error { + _, existingValueStorable, err := c.omap.Remove(compare, hashInputProvider, NewByteStringValue(key)) + if err != nil { + var keyNotFoundError *atree.KeyNotFoundError + if errors.As(err, &keyNotFoundError) { + return nil + } + return err + } + + if id, ok := existingValueStorable.(atree.StorageIDStorable); ok { + // NOTE: deep remove isn't necessary because value is ByteStringValue (not container) + err := c.storage.Remove(atree.StorageID(id)) + if err != nil { + return err + } + } + return nil +} + +// Destroy destroys the whole collection +func (c *Collection) Destroy() ([][]byte, error) { + var cachedErr error + keys := make([][]byte, c.omap.Count()) + i := 0 + err := c.omap.PopIterate(func(keyStorable atree.Storable, valueStorable atree.Storable) { + if id, ok := valueStorable.(atree.StorageIDStorable); ok { + err := c.storage.Remove(atree.StorageID(id)) + if err != nil && cachedErr == nil { + cachedErr = err + } + } + key, err := keyStorable.StoredValue(c.omap.Storage) + if err != nil && cachedErr == nil { + cachedErr = err + } + keys[i] = key.(ByteStringValue).Bytes() + i++ + }) + if cachedErr != nil { + return keys, cachedErr + } + if err != nil { + return keys, err + } + return keys, c.storage.Remove(c.omap.StorageID()) +} + +// Size returns the number of items in the collection +func (c *Collection) Size() uint64 { + return c.omap.Count() +} + +type ByteStringValue struct { + data []byte + size uint32 +} + +var _ atree.Value = &ByteStringValue{} +var _ atree.Storable = &ByteStringValue{} + +func NewByteStringValue(data []byte) ByteStringValue { + size := atree.GetUintCBORSize(uint64(len(data))) + uint32(len(data)) + return ByteStringValue{data: data, size: size} +} + +func (v ByteStringValue) ChildStorables() []atree.Storable { + return nil +} + +func (v ByteStringValue) StoredValue(_ atree.SlabStorage) (atree.Value, error) { + return v, nil +} + +func (v ByteStringValue) Storable(storage atree.SlabStorage, address atree.Address, maxInlineSize uint64) (atree.Storable, error) { + if uint64(v.ByteSize()) <= maxInlineSize { + return v, nil + } + + // Create StorableSlab + id, err := storage.GenerateStorageID(address) + if err != nil { + return nil, err + } + + slab := &atree.StorableSlab{ + StorageID: id, + Storable: v, + } + + // Store StorableSlab in storage + err = storage.Store(id, slab) + if err != nil { + return nil, err + } + + // Return storage id as storable + return atree.StorageIDStorable(id), nil +} + +func (v ByteStringValue) Encode(enc *atree.Encoder) error { + return enc.CBOR.EncodeBytes(v.data) +} + +func (v ByteStringValue) getHashInput(scratch []byte) ([]byte, error) { + + const cborTypeByteString = 0x40 + + buf := scratch + if uint32(len(buf)) < v.size { + buf = make([]byte, v.size) + } else { + buf = buf[:v.size] + } + + slen := len(v.data) + + if slen <= 23 { + buf[0] = cborTypeByteString | byte(slen) + copy(buf[1:], v.data) + return buf, nil + } + + if slen <= math.MaxUint8 { + buf[0] = cborTypeByteString | byte(24) + buf[1] = byte(slen) + copy(buf[2:], v.data) + return buf, nil + } + + if slen <= math.MaxUint16 { + buf[0] = cborTypeByteString | byte(25) + binary.BigEndian.PutUint16(buf[1:], uint16(slen)) + copy(buf[3:], v.data) + return buf, nil + } + + if slen <= math.MaxUint32 { + buf[0] = cborTypeByteString | byte(26) + binary.BigEndian.PutUint32(buf[1:], uint32(slen)) + copy(buf[5:], v.data) + return buf, nil + } + + buf[0] = cborTypeByteString | byte(27) + binary.BigEndian.PutUint64(buf[1:], uint64(slen)) + copy(buf[9:], v.data) + return buf, nil +} + +func (v ByteStringValue) ByteSize() uint32 { + return v.size +} + +func (v ByteStringValue) String() string { + return string(v.data) +} + +func (v ByteStringValue) Bytes() []byte { + return v.data +} + +func decodeStorable(dec *cbor.StreamDecoder, _ atree.StorageID) (atree.Storable, error) { + t, err := dec.NextType() + if err != nil { + return nil, err + } + + switch t { + case cbor.ByteStringType: + s, err := dec.DecodeBytes() + if err != nil { + return nil, err + } + return NewByteStringValue(s), nil + + case cbor.TagType: + tagNumber, err := dec.DecodeTagNumber() + if err != nil { + return nil, err + } + + switch tagNumber { + + case atree.CBORTagStorageID: + return atree.DecodeStorageIDStorable(dec) + + default: + return nil, fmt.Errorf("invalid tag number %d", tagNumber) + } + + default: + return nil, fmt.Errorf("invalid cbor type %s for storable", t) + } +} + +func compare(storage atree.SlabStorage, value atree.Value, storable atree.Storable) (bool, error) { + switch v := value.(type) { + + case ByteStringValue: + other, ok := storable.(ByteStringValue) + if ok { + return bytes.Equal(other.data, v.data), nil + } + + // Retrieve value from storage + otherValue, err := storable.StoredValue(storage) + if err != nil { + return false, err + } + other, ok = otherValue.(ByteStringValue) + if ok { + return bytes.Equal(other.data, v.data), nil + } + + return false, nil + } + + return false, fmt.Errorf("value %T not supported for comparison", value) +} + +func hashInputProvider(value atree.Value, buffer []byte) ([]byte, error) { + switch v := value.(type) { + case ByteStringValue: + return v.getHashInput(buffer) + } + + return nil, fmt.Errorf("value %T not supported for hash input", value) +} + +func NewPersistentSlabStorage(baseStorage atree.BaseStorage) (*atree.PersistentSlabStorage, error) { + encMode, err := cbor.EncOptions{}.EncMode() + if err != nil { + return nil, err + } + + decMode, err := cbor.DecOptions{}.DecMode() + if err != nil { + return nil, err + } + + return atree.NewPersistentSlabStorage( + baseStorage, + encMode, + decMode, + decodeStorable, + decodeTypeInfo, + ), nil +} + +type emptyTypeInfo struct{} + +var _ atree.TypeInfo = emptyTypeInfo{} + +func (emptyTypeInfo) Encode(e *cbor.StreamEncoder) error { + return e.EncodeNil() +} + +func (i emptyTypeInfo) Equal(other atree.TypeInfo) bool { + _, ok := other.(emptyTypeInfo) + return ok +} + +func decodeTypeInfo(dec *cbor.StreamDecoder) (atree.TypeInfo, error) { + ty, err := dec.NextType() + if err != nil { + return nil, err + } + switch ty { + case cbor.NilType: + err := dec.DecodeNil() + if err != nil { + return nil, err + } + return emptyTypeInfo{}, nil + default: + } + + return nil, fmt.Errorf("not supported type info") +} diff --git a/fvm/evm/emulator/state/collection_test.go b/fvm/evm/emulator/state/collection_test.go new file mode 100644 index 00000000000..526d9e94a3e --- /dev/null +++ b/fvm/evm/emulator/state/collection_test.go @@ -0,0 +1,69 @@ +package state_test + +import ( + "testing" + + "github.com/onflow/atree" + "github.com/stretchr/testify/require" + + "github.com/onflow/flow-go/fvm/evm/emulator/state" + "github.com/onflow/flow-go/fvm/evm/testutils" +) + +func TestCollection(t *testing.T) { + + cp := setupTestCollection(t) + c1, err := cp.NewCollection() + require.NoError(t, err) + + key1 := []byte("A") + key2 := []byte("B") + value1 := []byte{1} + value2 := []byte{2} + + // get value for A + ret, err := c1.Get(key1) + require.NoError(t, err) + require.Empty(t, ret) + + // set value1 for A + err = c1.Set(key1, value1) + require.NoError(t, err) + + ret, err = c1.Get(key1) + require.NoError(t, err) + require.Equal(t, ret, value1) + + err = c1.Remove(key1) + require.NoError(t, err) + + ret, err = c1.Get(key1) + require.NoError(t, err) + require.Empty(t, ret) + + err = c1.Set(key2, value2) + require.NoError(t, err) + + c2, err := cp.CollectionByID(c1.CollectionID()) + require.NoError(t, err) + + ret, err = c2.Get(key2) + require.NoError(t, err) + require.Equal(t, value2, ret) + + // destroy + keys, err := c1.Destroy() + require.NoError(t, err) + require.Len(t, keys, 1) + require.Equal(t, key2, keys[0]) + + _, err = cp.CollectionByID(c1.CollectionID()) + require.Error(t, err) +} + +func setupTestCollection(t *testing.T) *state.CollectionProvider { + ledger := testutils.GetSimpleValueStore() + cp, err := state.NewCollectionProvider(atree.Address{1, 2, 3, 4, 5, 6, 7, 8}, ledger) + require.NoError(t, err) + return cp +} diff --git a/fvm/evm/emulator/state/delta.go b/fvm/evm/emulator/state/delta.go new file mode 100644 index 00000000000..50575789d50 --- /dev/null +++ b/fvm/evm/emulator/state/delta.go @@ -0,0 +1,502 @@ +package state + +import ( + "fmt" + "math/big" + + gethCommon "github.com/ethereum/go-ethereum/common" + gethTypes "github.com/ethereum/go-ethereum/core/types" + gethCrypto "github.com/ethereum/go-ethereum/crypto" + + "github.com/onflow/flow-go/fvm/evm/types" +) + +// DeltaView captures the changes to the state during the execution +// +// for most of the read calls it checks its change logs and if no record is +// found it would redirect the call to the parent view. +type DeltaView struct { + parent types.ReadOnlyView + + // dirtyAddresses keeps a set of addresses with changes + dirtyAddresses map[gethCommon.Address]struct{} + // created keeps a set of recently created addresses + created map[gethCommon.Address]struct{} + // toBeDestructed keeps a set of addresses flagged to be destructed at the + // end of transaction, it also keeps the balance of the addresses before destruction + toBeDestructed map[gethCommon.Address]*big.Int + // is a flag used to track accounts that has been flagged for + // destruction but recreated later + recreated map[gethCommon.Address]struct{} + // balances keeps the changes to the account balances + balances map[gethCommon.Address]*big.Int + // nonces keeps the changes to the account nonces + nonces map[gethCommon.Address]uint64 + // codes keeps the changes to the account codes + codes map[gethCommon.Address][]byte + // codeHashes keeps the changes to account code hashes + codeHashes map[gethCommon.Address]gethCommon.Hash + + // slots keeps a set of slots that has been changed in this view + slots map[types.SlotAddress]gethCommon.Hash + + // transient storage + transient map[types.SlotAddress]gethCommon.Hash + + // access lists + accessListAddresses map[gethCommon.Address]struct{} + accessListSlots map[types.SlotAddress]struct{} + + // logs + logs []*gethTypes.Log + + // preimages + preimages map[gethCommon.Hash][]byte + + // refund + refund uint64 +} + +var _ types.HotView = &DeltaView{} + +// NewDeltaView constructs a new delta view +func NewDeltaView(parent types.ReadOnlyView) *DeltaView { + return &DeltaView{ + parent: parent, + + dirtyAddresses: make(map[gethCommon.Address]struct{}), + created: make(map[gethCommon.Address]struct{}), + toBeDestructed: make(map[gethCommon.Address]*big.Int), + recreated: make(map[gethCommon.Address]struct{}), + balances: make(map[gethCommon.Address]*big.Int), + nonces: make(map[gethCommon.Address]uint64), + codes: make(map[gethCommon.Address][]byte), + codeHashes: make(map[gethCommon.Address]gethCommon.Hash), + + slots: make(map[types.SlotAddress]gethCommon.Hash), + + // for refund we just copy the data + refund: parent.GetRefund(), + } +} + +// NewChildView constructs a new delta view having the current view as parent +func (d *DeltaView) NewChildView() *DeltaView { + return NewDeltaView(d) +} + +// Exist returns true if address exists +// +// it also returns true for both newly created accounts or accounts that has been flagged for deletion +func (d *DeltaView) Exist(addr gethCommon.Address) (bool, error) { + _, found := d.created[addr] + if found { + return true, nil + } + _, found = d.toBeDestructed[addr] + if found { + return true, nil + } + return d.parent.Exist(addr) +} + +// CreateAccount creates a new account for the given address +// +// if address already extists (even if destructed), carry over the balance +// and reset the data from the orginal account. +func (d *DeltaView) CreateAccount(addr gethCommon.Address) error { + // if is already created return + if d.IsCreated(addr) { + return nil + } + exist, err := d.Exist(addr) + if err != nil { + return err + } + if exist { + // check if already destructed + destructed, balance := d.HasSelfDestructed(addr) + if !destructed { + balance, err = d.GetBalance(addr) + if err != nil { + return err + } + err = d.SelfDestruct(addr) + if err != nil { + return err + } + } + + d.nonces[addr] = 0 + d.codes[addr] = nil + d.codeHashes[addr] = gethTypes.EmptyCodeHash + // carrying over the balance. (legacy behaviour of the Geth stateDB) + d.balances[addr] = balance + + // flag addr as recreated, this flag helps with postponing deletion of slabs + // otherwise we have to iterate over all slabs of this account and set the to nil + d.recreated[addr] = struct{}{} + + // remove slabs from cache related to this account + for k := range d.slots { + if k.Address == addr { + delete(d.slots, k) + } + } + } + d.dirtyAddresses[addr] = struct{}{} + d.created[addr] = struct{}{} + return nil +} + +// IsCreated returns true if address has been created in this tx +func (d *DeltaView) IsCreated(addr gethCommon.Address) bool { + _, found := d.created[addr] + if found { + return true + } + return d.parent.IsCreated(addr) +} + +// HasSelfDestructed returns true if address has been flagged for destruction +// it also returns the balance of the address before the destruction call +func (d *DeltaView) HasSelfDestructed(addr gethCommon.Address) (bool, *big.Int) { + bal, found := d.toBeDestructed[addr] + if found { + return true, bal + } + return d.parent.HasSelfDestructed(addr) +} + +// SelfDestruct sets a flag to destruct the account at the end of transaction +// +// if an account has been created in this transaction, it would return an error +func (d *DeltaView) SelfDestruct(addr gethCommon.Address) error { + // if it has been recently created, calling self destruct is not a valid operation + if d.IsCreated(addr) { + return fmt.Errorf("invalid operation, can't selfdestruct an account that is just created") + } + + // if it doesn't exist, return false + exists, err := d.Exist(addr) + if err != nil { + return err + } + if !exists { + return nil + } + + // flag the account for destruction and capture the balance + // before destruction + d.toBeDestructed[addr], err = d.GetBalance(addr) + if err != nil { + return err + } + // flag the address as dirty + d.dirtyAddresses[addr] = struct{}{} + + // set balance to zero + d.balances[addr] = new(big.Int) + return nil +} + +// GetBalance returns the balance of the given address +func (d *DeltaView) GetBalance(addr gethCommon.Address) (*big.Int, error) { + val, found := d.balances[addr] + if found { + return val, nil + } + // if newly created and no balance is set yet + _, newlyCreated := d.created[addr] + if newlyCreated { + return big.NewInt(0), nil + } + return d.parent.GetBalance(addr) +} + +// AddBalance adds the amount to the current balance of the given address +func (d *DeltaView) AddBalance(addr gethCommon.Address, amount *big.Int) error { + // if amount is 0 skip + if amount.Sign() == 0 { + return nil + } + // get the latest balance + orgBalance, err := d.GetBalance(addr) + if err != nil { + return err + } + // update the balance + newBalance := new(big.Int).Add(orgBalance, amount) + d.balances[addr] = newBalance + + // flag the address as dirty + d.dirtyAddresses[addr] = struct{}{} + return nil +} + +// SubBalance subtracts the amount from the current balance of the given address +func (d *DeltaView) SubBalance(addr gethCommon.Address, amount *big.Int) error { + // if amount is 0 skip + if amount.Sign() == 0 { + return nil + } + + // get the latest balance + orgBalance, err := d.GetBalance(addr) + if err != nil { + return err + } + + // update the new balance + newBalance := new(big.Int).Sub(orgBalance, amount) + + // if new balance is negative error + if newBalance.Sign() < 0 { + return fmt.Errorf("account balance is negative %d", newBalance) + } + + // update the balance + d.balances[addr] = newBalance + + // flag the address as dirty + d.dirtyAddresses[addr] = struct{}{} + return nil +} + +// GetNonce returns the nonce of the given address +func (d *DeltaView) GetNonce(addr gethCommon.Address) (uint64, error) { + val, found := d.nonces[addr] + if found { + return val, nil + } + // if newly created + _, newlyCreated := d.created[addr] + if newlyCreated { + return 0, nil + } + return d.parent.GetNonce(addr) +} + +// SetNonce sets the nonce for the given address +func (d *DeltaView) SetNonce(addr gethCommon.Address, nonce uint64) error { + // update the nonce + d.nonces[addr] = nonce + + // flag the address as dirty + d.dirtyAddresses[addr] = struct{}{} + return nil +} + +// GetCode returns the code of the given address +func (d *DeltaView) GetCode(addr gethCommon.Address) ([]byte, error) { + code, found := d.codes[addr] + if found { + return code, nil + } + // if newly created + _, newlyCreated := d.created[addr] + if newlyCreated { + return nil, nil + } + return d.parent.GetCode(addr) +} + +// GetCodeSize returns the code size of the given address +func (d *DeltaView) GetCodeSize(addr gethCommon.Address) (int, error) { + code, err := d.GetCode(addr) + return len(code), err +} + +// GetCodeHash returns the code hash of the given address +func (d *DeltaView) GetCodeHash(addr gethCommon.Address) (gethCommon.Hash, error) { + codeHash, found := d.codeHashes[addr] + if found { + return codeHash, nil + } + // if newly created + _, newlyCreated := d.created[addr] + if newlyCreated { + return gethTypes.EmptyCodeHash, nil + } + return d.parent.GetCodeHash(addr) +} + +// SetCode sets the code for the given address +func (d *DeltaView) SetCode(addr gethCommon.Address, code []byte) error { + // update code + d.codes[addr] = code + + // update code hash + codeHash := gethTypes.EmptyCodeHash + if len(code) > 0 { + codeHash = gethCrypto.Keccak256Hash(code) + } + d.codeHashes[addr] = codeHash + + // flag the address as dirty + d.dirtyAddresses[addr] = struct{}{} + return nil +} + +// GetState returns the value of the slot of the main state +func (d *DeltaView) GetState(sk types.SlotAddress) (gethCommon.Hash, error) { + val, found := d.slots[sk] + if found { + return val, nil + } + // if address is deleted in the scope of this delta view, + // don't go backward. this has been done to skip the step to iterate + // over all the state slabs and delete them. + _, recreated := d.recreated[sk.Address] + if recreated { + return gethCommon.Hash{}, nil + } + return d.parent.GetState(sk) +} + +// SetState adds sets a value for the given slot of the main storage +func (d *DeltaView) SetState(sk types.SlotAddress, value gethCommon.Hash) error { + lastValue, err := d.GetState(sk) + if err != nil { + return err + } + // if the value hasn't changed, skip + if value == lastValue { + return nil + } + d.slots[sk] = value + return nil +} + +// GetTransientState returns the value of the slot of the transient state +func (d *DeltaView) GetTransientState(sk types.SlotAddress) gethCommon.Hash { + if d.transient != nil { + val, found := d.transient[sk] + if found { + return val + } + } + return d.parent.GetTransientState(sk) +} + +// SetTransientState adds sets a value for the given slot of the transient storage +func (d *DeltaView) SetTransientState(sk types.SlotAddress, value gethCommon.Hash) { + if d.transient == nil { + d.transient = make(map[types.SlotAddress]gethCommon.Hash) + } + d.transient[sk] = value +} + +// GetRefund returns the total (gas) refund +func (d *DeltaView) GetRefund() uint64 { + return d.refund +} + +// AddRefund adds the amount to the total (gas) refund +func (d *DeltaView) AddRefund(amount uint64) error { + d.refund += amount + return nil +} + +// SubRefund subtracts the amount from the total (gas) refund +func (d *DeltaView) SubRefund(amount uint64) error { + if amount > d.refund { + return fmt.Errorf("refund counter below zero (gas: %d > refund: %d)", amount, d.refund) + } + d.refund -= amount + return nil +} + +// AddressInAccessList checks if the address is in the access list +func (d *DeltaView) AddressInAccessList(addr gethCommon.Address) bool { + if d.accessListAddresses != nil { + _, addressFound := d.accessListAddresses[addr] + if addressFound { + return true + } + } + return d.parent.AddressInAccessList(addr) +} + +// AddAddressToAccessList adds an address to the access list +func (d *DeltaView) AddAddressToAccessList(addr gethCommon.Address) bool { + if d.accessListAddresses == nil { + d.accessListAddresses = make(map[gethCommon.Address]struct{}) + } + + addrPresent := d.AddressInAccessList(addr) + d.accessListAddresses[addr] = struct{}{} + return !addrPresent +} + +// SlotInAccessList checks if the slot is in the access list +func (d *DeltaView) SlotInAccessList(sk types.SlotAddress) (addressOk bool, slotOk bool) { + addressFound := d.AddressInAccessList(sk.Address) + if d.accessListSlots != nil { + _, slotFound := d.accessListSlots[sk] + if slotFound { + return addressFound, true + } + } + _, slotFound := d.parent.SlotInAccessList(sk) + return addressFound, slotFound +} + +// AddSlotToAccessList adds a slot to the access list +// it also adds the address to the address list +func (d *DeltaView) AddSlotToAccessList(sk types.SlotAddress) (addrAdded bool, slotAdded bool) { + addrPresent, slotPresent := d.SlotInAccessList(sk) + if d.accessListAddresses == nil { + d.accessListAddresses = make(map[gethCommon.Address]struct{}) + } + d.accessListAddresses[sk.Address] = struct{}{} + if d.accessListSlots == nil { + d.accessListSlots = make(map[types.SlotAddress]struct{}) + } + d.accessListSlots[sk] = struct{}{} + return !addrPresent, !slotPresent +} + +// AddLog appends a log to the log collection +func (d *DeltaView) AddLog(log *gethTypes.Log) { + if d.logs == nil { + d.logs = make([]*gethTypes.Log, 0) + } + d.logs = append(d.logs, log) +} + +// Logs returns the logs that has been captured in this view +func (d *DeltaView) Logs() []*gethTypes.Log { + return d.logs +} + +// AddPreimage adds a preimage +func (d *DeltaView) AddPreimage(hash gethCommon.Hash, preimage []byte) { + if d.preimages == nil { + d.preimages = make(map[gethCommon.Hash][]byte) + } + + // make a copy (legacy behaviour) + pi := make([]byte, len(preimage)) + copy(pi, preimage) + d.preimages[hash] = pi +} + +// Preimages returns a map of preimages +func (d *DeltaView) Preimages() map[gethCommon.Hash][]byte { + return d.preimages +} + +// DirtyAddresses returns a set of addresses that has been updated in this view +func (d *DeltaView) DirtyAddresses() map[gethCommon.Address]struct{} { + return d.dirtyAddresses +} + +// DirtySlots returns a set of slots that has been updated in this view +func (d *DeltaView) DirtySlots() map[types.SlotAddress]struct{} { + dirtySlots := make(map[types.SlotAddress]struct{}) + for sk := range d.slots { + dirtySlots[sk] = struct{}{} + } + return dirtySlots +} diff --git a/fvm/evm/emulator/state/delta_test.go b/fvm/evm/emulator/state/delta_test.go new file mode 100644 index 00000000000..820ba2c2ce1 --- /dev/null +++ b/fvm/evm/emulator/state/delta_test.go @@ -0,0 +1,849 @@ +package state_test + +import ( + "fmt" + "math/big" + "testing" + + gethCommon "github.com/ethereum/go-ethereum/common" + gethTypes "github.com/ethereum/go-ethereum/core/types" + gethCrypto "github.com/ethereum/go-ethereum/crypto" + "github.com/stretchr/testify/require" + + "github.com/onflow/flow-go/fvm/evm/emulator/state" + "github.com/onflow/flow-go/fvm/evm/testutils" + "github.com/onflow/flow-go/fvm/evm/types" +) + +var emptyRefund = func() uint64 { + return 0 +} + +func TestDeltaView(t *testing.T) { + t.Parallel() + + t.Run("test account exist/creation/self-destruct functionality", func(t *testing.T) { + addr1 := testutils.RandomCommonAddress(t) + addr2 := testutils.RandomCommonAddress(t) + addr3 := testutils.RandomCommonAddress(t) + + view := state.NewDeltaView( + &MockedReadOnlyView{ + // we need get refund for parent + GetRefundFunc: emptyRefund, + ExistFunc: func(addr gethCommon.Address) (bool, error) { + switch addr { + case addr1: + return true, nil + case addr2: + return false, nil + default: + return false, fmt.Errorf("some error") + } + }, + IsCreatedFunc: func(a gethCommon.Address) bool { + return false + }, + GetBalanceFunc: func(gethCommon.Address) (*big.Int, error) { + return new(big.Int), nil + }, + HasSelfDestructedFunc: func(gethCommon.Address) (bool, *big.Int) { + return false, new(big.Int) + }, + }) + + // check existing account on the parent + found, err := view.Exist(addr1) + require.NoError(t, err) + require.True(t, found) + + // account doesn't exist on parent + found, err = view.Exist(addr2) + require.NoError(t, err) + require.False(t, found) + + // handling error on the parent + _, err = view.Exist(addr3) + require.Error(t, err) + + // create a account at address 2 + err = view.CreateAccount(addr2) + require.NoError(t, err) + require.True(t, view.IsCreated(addr2)) + + // now it should be found + found, err = view.Exist(addr2) + require.NoError(t, err) + require.True(t, found) + + // test HasSelfDestructed first + success, _ := view.HasSelfDestructed(addr1) + require.False(t, success) + + // set addr1 for deletion + err = view.SelfDestruct(addr1) + require.NoError(t, err) + + // check HasSelfDestructed now + success, _ = view.HasSelfDestructed(addr1) + require.True(t, success) + + // addr1 should still exist after self destruct call + found, err = view.Exist(addr1) + require.NoError(t, err) + require.True(t, found) + }) + + t.Run("test account balance functionality", func(t *testing.T) { + addr1 := testutils.RandomCommonAddress(t) + addr1InitBal := big.NewInt(10) + addr2 := testutils.RandomCommonAddress(t) + addr2InitBal := big.NewInt(5) + addr3 := testutils.RandomCommonAddress(t) + + view := state.NewDeltaView( + &MockedReadOnlyView{ + // we need get refund for parent + GetRefundFunc: emptyRefund, + ExistFunc: func(addr gethCommon.Address) (bool, error) { + switch addr { + case addr1, addr2: + return true, nil + default: + return false, nil + } + }, + HasSelfDestructedFunc: func(a gethCommon.Address) (bool, *big.Int) { + return false, new(big.Int) + }, + IsCreatedFunc: func(a gethCommon.Address) bool { + return false + }, + GetBalanceFunc: func(addr gethCommon.Address) (*big.Int, error) { + switch addr { + case addr1: + return addr1InitBal, nil + case addr2: + return addr2InitBal, nil + default: + return nil, fmt.Errorf("some error") + } + }, + }) + + // get balance through parent + bal, err := view.GetBalance(addr1) + require.NoError(t, err) + require.Equal(t, addr1InitBal, bal) + + // call self destruct on addr + err = view.SelfDestruct(addr1) + require.NoError(t, err) + + // now it should return balance of zero + bal, err = view.GetBalance(addr1) + require.NoError(t, err) + require.Equal(t, big.NewInt(0), bal) + + // add balance to addr2 + amount := big.NewInt(7) + expected := new(big.Int).Add(addr2InitBal, amount) + err = view.AddBalance(addr2, amount) + require.NoError(t, err) + newBal, err := view.GetBalance(addr2) + require.NoError(t, err) + require.Equal(t, expected, newBal) + + // sub balance from addr2 + amount = big.NewInt(9) + expected = new(big.Int).Sub(newBal, amount) + err = view.SubBalance(addr2, amount) + require.NoError(t, err) + bal, err = view.GetBalance(addr2) + require.NoError(t, err) + require.Equal(t, expected, bal) + + // negative balance error + err = view.SubBalance(addr2, big.NewInt(100)) + require.Error(t, err) + + // handling error on the parent + _, err = view.GetBalance(addr3) + require.Error(t, err) + }) + + t.Run("test nonce functionality", func(t *testing.T) { + addr1 := testutils.RandomCommonAddress(t) + addr1InitNonce := uint64(1) + addr2 := testutils.RandomCommonAddress(t) + + view := state.NewDeltaView( + &MockedReadOnlyView{ + // we need get refund for parent + GetRefundFunc: emptyRefund, + ExistFunc: func(addr gethCommon.Address) (bool, error) { + switch addr { + case addr1: + return true, nil + default: + return false, nil + } + }, + HasSelfDestructedFunc: func(a gethCommon.Address) (bool, *big.Int) { + return false, new(big.Int) + }, + IsCreatedFunc: func(a gethCommon.Address) bool { + return false + }, + GetBalanceFunc: func(a gethCommon.Address) (*big.Int, error) { + return new(big.Int), nil + }, + GetNonceFunc: func(addr gethCommon.Address) (uint64, error) { + switch addr { + case addr1: + return addr1InitNonce, nil + default: + return 0, fmt.Errorf("some error") + } + }, + }) + + // get nonce through parent + nonce, err := view.GetNonce(addr1) + require.NoError(t, err) + require.Equal(t, addr1InitNonce, nonce) + + // set nonce + new := uint64(100) + err = view.SetNonce(addr1, new) + require.NoError(t, err) + nonce, err = view.GetNonce(addr1) + require.NoError(t, err) + require.Equal(t, new, nonce) + + // handling error on the parent + _, err = view.GetNonce(addr2) + require.Error(t, err) + + // create a new account at addr2 + err = view.CreateAccount(addr2) + require.NoError(t, err) + + // now the nonce should return 0 + nonce, err = view.GetNonce(addr2) + require.NoError(t, err) + require.Equal(t, uint64(0), nonce) + }) + + t.Run("test code functionality", func(t *testing.T) { + addr1 := testutils.RandomCommonAddress(t) + addr1InitCode := []byte("code1") + addr1IntiCodeHash := gethCommon.BytesToHash([]byte{1, 2}) + addr2 := testutils.RandomCommonAddress(t) + + view := state.NewDeltaView( + &MockedReadOnlyView{ + // we need get refund for parent + GetRefundFunc: emptyRefund, + ExistFunc: func(addr gethCommon.Address) (bool, error) { + switch addr { + case addr1: + return true, nil + default: + return false, nil + } + }, + HasSelfDestructedFunc: func(a gethCommon.Address) (bool, *big.Int) { + return false, new(big.Int) + }, + IsCreatedFunc: func(a gethCommon.Address) bool { + return false + }, + GetBalanceFunc: func(a gethCommon.Address) (*big.Int, error) { + return new(big.Int), nil + }, + GetCodeFunc: func(addr gethCommon.Address) ([]byte, error) { + switch addr { + case addr1: + return addr1InitCode, nil + default: + return nil, fmt.Errorf("some error") + } + }, + GetCodeSizeFunc: func(addr gethCommon.Address) (int, error) { + switch addr { + case addr1: + return len(addr1InitCode), nil + default: + return 0, fmt.Errorf("some error") + } + }, + GetCodeHashFunc: func(addr gethCommon.Address) (gethCommon.Hash, error) { + switch addr { + case addr1: + return addr1IntiCodeHash, nil + default: + return gethCommon.Hash{}, fmt.Errorf("some error") + } + }, + }) + + // get code through parent + code, err := view.GetCode(addr1) + require.NoError(t, err) + require.Equal(t, addr1InitCode, code) + + // get code size through parent + codeSize, err := view.GetCodeSize(addr1) + require.NoError(t, err) + require.Equal(t, len(addr1InitCode), codeSize) + + // get code hash through parent + codeHash, err := view.GetCodeHash(addr1) + require.NoError(t, err) + require.Equal(t, addr1IntiCodeHash, codeHash) + + // set code for addr1 + newCode := []byte("new code") + err = view.SetCode(addr1, newCode) + require.NoError(t, err) + + code, err = view.GetCode(addr1) + require.NoError(t, err) + require.Equal(t, newCode, code) + + codeSize, err = view.GetCodeSize(addr1) + require.NoError(t, err) + require.Equal(t, len(newCode), codeSize) + + codeHash, err = view.GetCodeHash(addr1) + require.NoError(t, err) + require.Equal(t, gethCrypto.Keccak256Hash(code), codeHash) + + // handling error on the parent + _, err = view.GetCode(addr2) + require.Error(t, err) + + // create a new account at addr2 + err = view.CreateAccount(addr2) + require.NoError(t, err) + + // now the code should return empty code + code, err = view.GetCode(addr2) + require.NoError(t, err) + require.Len(t, code, 0) + + codeHash, err = view.GetCodeHash(addr2) + require.NoError(t, err) + require.Equal(t, gethTypes.EmptyCodeHash, codeHash) + }) + + t.Run("test state access functionality", func(t *testing.T) { + slot1 := types.SlotAddress{ + Address: testutils.RandomCommonAddress(t), + Key: gethCommon.BytesToHash([]byte{1, 2}), + } + + slot1InitValue := gethCommon.BytesToHash([]byte{3, 4}) + + slot2 := types.SlotAddress{ + Address: testutils.RandomCommonAddress(t), + Key: gethCommon.BytesToHash([]byte{5, 6}), + } + + view := state.NewDeltaView( + &MockedReadOnlyView{ + // we need get refund for parent + GetRefundFunc: emptyRefund, + + GetStateFunc: func(slot types.SlotAddress) (gethCommon.Hash, error) { + switch slot { + case slot1: + return slot1InitValue, nil + default: + return gethCommon.Hash{}, fmt.Errorf("some error") + } + }, + }) + + // get state through parent + value, err := view.GetState(slot1) + require.NoError(t, err) + require.Equal(t, slot1InitValue, value) + + // handle error from parent + _, err = view.GetState(slot2) + require.Error(t, err) + + // check dirty slots + dirtySlots := view.DirtySlots() + require.Empty(t, dirtySlots) + + // set slot1 with some new value + newValue := gethCommon.BytesToHash([]byte{9, 8}) + err = view.SetState(slot1, newValue) + require.NoError(t, err) + + value, err = view.GetState(slot1) + require.NoError(t, err) + require.Equal(t, newValue, value) + + // check dirty slots + dirtySlots = view.DirtySlots() + require.Len(t, dirtySlots, 1) + + _, found := dirtySlots[slot1] + require.True(t, found) + }) + + t.Run("test transient state access functionality", func(t *testing.T) { + slot1 := types.SlotAddress{ + Address: testutils.RandomCommonAddress(t), + Key: gethCommon.BytesToHash([]byte{1, 2}), + } + + slot1InitValue := gethCommon.BytesToHash([]byte{3, 4}) + + view := state.NewDeltaView( + &MockedReadOnlyView{ + // we need get refund for parent + GetRefundFunc: emptyRefund, + GetTransientStateFunc: func(slot types.SlotAddress) gethCommon.Hash { + switch slot { + case slot1: + return slot1InitValue + default: + return gethCommon.Hash{} + } + }, + }) + + // get state through parent + value := view.GetTransientState(slot1) + require.Equal(t, slot1InitValue, value) + + // set slot1 with some new value + newValue := gethCommon.BytesToHash([]byte{9, 8}) + view.SetTransientState(slot1, newValue) + + value = view.GetTransientState(slot1) + require.Equal(t, newValue, value) + }) + + t.Run("test refund functionality", func(t *testing.T) { + initRefund := uint64(10) + view := state.NewDeltaView( + &MockedReadOnlyView{ + GetRefundFunc: func() uint64 { + return initRefund + }, + }) + + // get refund through parent + value := view.GetRefund() + require.Equal(t, initRefund, value) + + // add refund + addition := uint64(7) + err := view.AddRefund(addition) + require.NoError(t, err) + require.Equal(t, initRefund+addition, view.GetRefund()) + + // sub refund + subtract := uint64(2) + err = view.SubRefund(subtract) + require.NoError(t, err) + require.Equal(t, initRefund+addition-subtract, view.GetRefund()) + + // refund goes negative + err = view.SubRefund(1000) + require.Error(t, err) + }) + + t.Run("test access list functionality", func(t *testing.T) { + addr1 := testutils.RandomCommonAddress(t) + addr2 := testutils.RandomCommonAddress(t) + slot1 := types.SlotAddress{ + Address: testutils.RandomCommonAddress(t), + Key: gethCommon.BytesToHash([]byte{1, 2}), + } + + slot2 := types.SlotAddress{ + Address: testutils.RandomCommonAddress(t), + Key: gethCommon.BytesToHash([]byte{3, 4}), + } + + view := state.NewDeltaView( + &MockedReadOnlyView{ + GetRefundFunc: emptyRefund, + AddressInAccessListFunc: func(addr gethCommon.Address) bool { + switch addr { + case addr1: + return true + default: + return false + } + }, + SlotInAccessListFunc: func(slot types.SlotAddress) (addressOk bool, slotOk bool) { + switch slot { + case slot1: + return false, true + default: + return false, false + } + }, + }) + + // check address through parent + require.True(t, view.AddressInAccessList(addr1)) + + // add addr 2 to the list + require.False(t, view.AddressInAccessList(addr2)) + added := view.AddAddressToAccessList(addr2) + require.True(t, added) + require.True(t, view.AddressInAccessList(addr2)) + + // adding again + added = view.AddAddressToAccessList(addr2) + require.False(t, added) + + // check slot through parent + addrFound, slotFound := view.SlotInAccessList(slot1) + require.False(t, addrFound) + require.True(t, slotFound) + + // add slot 2 to the list + addrFound, slotFound = view.SlotInAccessList(slot2) + require.False(t, addrFound) + require.False(t, slotFound) + + addressAdded, slotAdded := view.AddSlotToAccessList(slot2) + require.True(t, addressAdded) + require.True(t, slotAdded) + + addrFound, slotFound = view.SlotInAccessList(slot2) + require.True(t, addrFound) + require.True(t, slotFound) + + // adding again + addressAdded, slotAdded = view.AddSlotToAccessList(slot2) + require.False(t, addressAdded) + require.False(t, slotAdded) + }) + + t.Run("test log functionality", func(t *testing.T) { + view := state.NewDeltaView( + &MockedReadOnlyView{ + GetRefundFunc: emptyRefund, + }) + + logs := view.Logs() + require.Empty(t, logs) + + log1 := &gethTypes.Log{ + Address: testutils.RandomCommonAddress(t), + } + view.AddLog(log1) + + log2 := &gethTypes.Log{ + Address: testutils.RandomCommonAddress(t), + } + view.AddLog(log2) + + logs = view.Logs() + require.Equal(t, []*gethTypes.Log{log1, log2}, logs) + }) + + t.Run("test preimage functionality", func(t *testing.T) { + view := state.NewDeltaView( + &MockedReadOnlyView{ + GetRefundFunc: emptyRefund, + }) + + preimages := view.Preimages() + require.Empty(t, preimages) + + preimage1 := []byte{1, 2} + hash1 := gethCommon.BytesToHash([]byte{2, 3}) + view.AddPreimage(hash1, preimage1) + + preimage2 := []byte{4, 5} + hash2 := gethCommon.BytesToHash([]byte{6, 7}) + view.AddPreimage(hash2, preimage2) + + expected := make(map[gethCommon.Hash][]byte) + expected[hash1] = preimage1 + expected[hash2] = preimage2 + + preimages = view.Preimages() + require.Equal(t, expected, preimages) + }) + + t.Run("test dirty addresses functionality", func(t *testing.T) { + addrCount := 6 + addresses := make([]gethCommon.Address, addrCount) + for i := 0; i < addrCount; i++ { + addresses[i] = testutils.RandomCommonAddress(t) + } + + view := state.NewDeltaView( + &MockedReadOnlyView{ + // we need get refund for parent + GetRefundFunc: emptyRefund, + ExistFunc: func(addr gethCommon.Address) (bool, error) { + return true, nil + }, + GetBalanceFunc: func(addr gethCommon.Address) (*big.Int, error) { + return big.NewInt(10), nil + }, + GetNonceFunc: func(addr gethCommon.Address) (uint64, error) { + return 0, nil + }, + IsCreatedFunc: func(a gethCommon.Address) bool { + return false + }, + HasSelfDestructedFunc: func(gethCommon.Address) (bool, *big.Int) { + return false, new(big.Int) + }, + }) + + // check dirty addresses + dirtyAddresses := view.DirtyAddresses() + require.Empty(t, dirtyAddresses) + + // create a account at address 1 + err := view.CreateAccount(addresses[0]) + require.NoError(t, err) + + // self destruct address 2 + err = view.SelfDestruct(addresses[1]) + require.NoError(t, err) + + // add balance for address 3 + err = view.AddBalance(addresses[2], big.NewInt(5)) + require.NoError(t, err) + + // sub balance for address 4 + err = view.AddBalance(addresses[3], big.NewInt(5)) + require.NoError(t, err) + + // set nonce for address 5 + err = view.SetNonce(addresses[4], 5) + require.NoError(t, err) + + // set code for address 6 + err = view.SetCode(addresses[5], []byte{1, 2}) + require.NoError(t, err) + + // now check dirty addresses + dirtyAddresses = view.DirtyAddresses() + require.Len(t, dirtyAddresses, addrCount) + for _, addr := range addresses { + _, found := dirtyAddresses[addr] + require.True(t, found) + } + }) + + t.Run("test account creation after selfdestruct call", func(t *testing.T) { + addr1 := testutils.RandomCommonAddress(t) + + view := state.NewDeltaView( + &MockedReadOnlyView{ + // we need get refund for parent + GetRefundFunc: emptyRefund, + ExistFunc: func(addr gethCommon.Address) (bool, error) { + return true, nil + }, + HasSelfDestructedFunc: func(gethCommon.Address) (bool, *big.Int) { + return true, big.NewInt(2) + }, + IsCreatedFunc: func(a gethCommon.Address) bool { + return false + }, + GetBalanceFunc: func(addr gethCommon.Address) (*big.Int, error) { + return new(big.Int), nil + }, + GetStateFunc: func(sa types.SlotAddress) (gethCommon.Hash, error) { + return gethCommon.Hash{}, nil + }, + }) + + found, err := view.Exist(addr1) + require.NoError(t, err) + require.True(t, found) + + // set balance + initBalance := big.NewInt(10) + err = view.AddBalance(addr1, initBalance) + require.NoError(t, err) + + bal, err := view.GetBalance(addr1) + require.NoError(t, err) + require.Equal(t, initBalance, bal) + + // set code + code := []byte{1, 2, 3} + err = view.SetCode(addr1, code) + require.NoError(t, err) + + ret, err := view.GetCode(addr1) + require.NoError(t, err) + require.Equal(t, code, ret) + + // set key values + key := testutils.RandomCommonHash(t) + value := testutils.RandomCommonHash(t) + sk := types.SlotAddress{Address: addr1, Key: key} + err = view.SetState(sk, value) + require.NoError(t, err) + + vret, err := view.GetState(sk) + require.NoError(t, err) + require.Equal(t, value, vret) + + err = view.SelfDestruct(addr1) + require.NoError(t, err) + + // balance should be returned zero + bal, err = view.GetBalance(addr1) + require.NoError(t, err) + require.Equal(t, new(big.Int), bal) + + // get code should still work + ret, err = view.GetCode(addr1) + require.NoError(t, err) + require.Equal(t, code, ret) + + // get state should also still work + vret, err = view.GetState(sk) + require.NoError(t, err) + require.Equal(t, value, vret) + + // now re-create account + err = view.CreateAccount(addr1) + require.NoError(t, err) + + // it should carry over the balance + bal, err = view.GetBalance(addr1) + require.NoError(t, err) + require.Equal(t, initBalance, bal) + + ret, err = view.GetCode(addr1) + require.NoError(t, err) + require.Len(t, ret, 0) + + vret, err = view.GetState(sk) + require.NoError(t, err) + emptyValue := gethCommon.Hash{} + require.Equal(t, emptyValue, vret) + }) +} + +type MockedReadOnlyView struct { + ExistFunc func(gethCommon.Address) (bool, error) + HasSelfDestructedFunc func(gethCommon.Address) (bool, *big.Int) + IsCreatedFunc func(gethCommon.Address) bool + GetBalanceFunc func(gethCommon.Address) (*big.Int, error) + GetNonceFunc func(gethCommon.Address) (uint64, error) + GetCodeFunc func(gethCommon.Address) ([]byte, error) + GetCodeHashFunc func(gethCommon.Address) (gethCommon.Hash, error) + GetCodeSizeFunc func(gethCommon.Address) (int, error) + GetStateFunc func(types.SlotAddress) (gethCommon.Hash, error) + GetTransientStateFunc func(types.SlotAddress) gethCommon.Hash + GetRefundFunc func() uint64 + AddressInAccessListFunc func(gethCommon.Address) bool + SlotInAccessListFunc func(types.SlotAddress) (addressOk bool, slotOk bool) +} + +var _ types.ReadOnlyView = &MockedReadOnlyView{} + +func (v *MockedReadOnlyView) Exist(addr gethCommon.Address) (bool, error) { + if v.ExistFunc == nil { + panic("Exist is not set in this mocked view") + } + return v.ExistFunc(addr) +} + +func (v *MockedReadOnlyView) IsCreated(addr gethCommon.Address) bool { + if v.IsCreatedFunc == nil { + panic("IsCreated is not set in this mocked view") + } + return v.IsCreatedFunc(addr) +} + +func (v *MockedReadOnlyView) HasSelfDestructed(addr gethCommon.Address) (bool, *big.Int) { + if v.HasSelfDestructedFunc == nil { + panic("HasSelfDestructed is not set in this mocked view") + } + return v.HasSelfDestructedFunc(addr) +} + +func (v *MockedReadOnlyView) GetBalance(addr gethCommon.Address) (*big.Int, error) { + if v.GetBalanceFunc == nil { + panic("GetBalance is not set in this mocked view") + } + return v.GetBalanceFunc(addr) +} + +func (v *MockedReadOnlyView) GetNonce(addr gethCommon.Address) (uint64, error) { + if v.GetNonceFunc == nil { + panic("GetNonce is not set in this mocked view") + } + return v.GetNonceFunc(addr) +} + +func (v *MockedReadOnlyView) GetCode(addr gethCommon.Address) ([]byte, error) { + if v.GetCodeFunc == nil { + panic("GetCode is not set in this mocked view") + } + return v.GetCodeFunc(addr) +} + +func (v *MockedReadOnlyView) GetCodeHash(addr gethCommon.Address) (gethCommon.Hash, error) { + if v.GetCodeHashFunc == nil { + panic("GetCodeHash is not set in this mocked view") + } + return v.GetCodeHashFunc(addr) +} + +func (v *MockedReadOnlyView) GetCodeSize(addr gethCommon.Address) (int, error) { + if v.GetCodeSizeFunc == nil { + panic("GetCodeSize is not set in this mocked view") + } + return v.GetCodeSizeFunc(addr) +} + +func (v *MockedReadOnlyView) GetState(slot types.SlotAddress) (gethCommon.Hash, error) { + if v.GetStateFunc == nil { + panic("GetState is not set in this mocked view") + } + return v.GetStateFunc(slot) +} + +func (v *MockedReadOnlyView) GetTransientState(slot types.SlotAddress) gethCommon.Hash { + if v.GetTransientStateFunc == nil { + panic("GetTransientState is not set in this mocked view") + } + return v.GetTransientStateFunc(slot) +} + +func (v *MockedReadOnlyView) GetRefund() uint64 { + if v.GetRefundFunc == nil { + panic("GetRefund is not set in this mocked view") + } + return v.GetRefundFunc() +} + +func (v *MockedReadOnlyView) AddressInAccessList(addr gethCommon.Address) bool { + if v.AddressInAccessListFunc == nil { + panic("AddressInAccessList is not set in this mocked view") + } + return v.AddressInAccessListFunc(addr) +} + +func (v *MockedReadOnlyView) SlotInAccessList(slot types.SlotAddress) (addressOk bool, slotOk bool) { + if v.SlotInAccessListFunc == nil { + panic("SlotInAccessList is not set in this mocked view") + } + return v.SlotInAccessListFunc(slot) +} diff --git a/fvm/evm/emulator/state/stateDB.go b/fvm/evm/emulator/state/stateDB.go new file mode 100644 index 00000000000..3488f26cda0 --- /dev/null +++ b/fvm/evm/emulator/state/stateDB.go @@ -0,0 +1,469 @@ +package state + +import ( + "bytes" + stdErrors "errors" + "fmt" + "math/big" + "sort" + + gethCommon "github.com/ethereum/go-ethereum/common" + gethTypes "github.com/ethereum/go-ethereum/core/types" + gethParams "github.com/ethereum/go-ethereum/params" + "github.com/onflow/atree" + + "github.com/onflow/flow-go/fvm/errors" + "github.com/onflow/flow-go/fvm/evm/types" + "github.com/onflow/flow-go/model/flow" +) + +// StateDB implements a types.StateDB interface +// +// stateDB interface defined by the Geth doesn't support returning errors +// when state calls are happening, and requires stateDB to cache the error +// and return it at a later time (when commit is called). Only the first error +// is expected to be returned. +// Warning: current implementation of the StateDB is considered +// to be used for a single EVM transaction execution and is not +// thread safe. yet the current design supports addition of concurrency in the +// future if needed +type StateDB struct { + ledger atree.Ledger + root flow.Address + baseView types.BaseView + views []*DeltaView + cachedError error +} + +var _ types.StateDB = &StateDB{} + +// NewStateDB constructs a new StateDB +func NewStateDB(ledger atree.Ledger, root flow.Address) (*StateDB, error) { + bv, err := NewBaseView(ledger, root) + if err != nil { + return nil, err + } + return &StateDB{ + ledger: ledger, + root: root, + baseView: bv, + views: []*DeltaView{NewDeltaView(bv)}, + cachedError: nil, + }, nil +} + +// Exist returns true if the given address exists in state. +// +// this should also return true for self destructed accounts during the transaction execution. +func (db *StateDB) Exist(addr gethCommon.Address) bool { + exist, err := db.lastestView().Exist(addr) + db.handleError(err) + return exist +} + +// Empty returns whether the given account is empty. +// +// Empty is defined according to EIP161 (balance = nonce = code = 0). +func (db *StateDB) Empty(addr gethCommon.Address) bool { + if !db.Exist(addr) { + return true + } + return db.GetNonce(addr) == 0 && + db.GetBalance(addr).Sign() == 0 && + bytes.Equal(db.GetCodeHash(addr).Bytes(), gethTypes.EmptyCodeHash.Bytes()) +} + +// CreateAccount creates a new account for the given address +// it sets the nonce to zero +func (db *StateDB) CreateAccount(addr gethCommon.Address) { + err := db.lastestView().CreateAccount(addr) + db.handleError(err) +} + +// IsCreated returns true if address is recently created (context of a transaction) +func (db *StateDB) IsCreated(addr gethCommon.Address) bool { + return db.lastestView().IsCreated(addr) +} + +// SelfDestruct flags the address for deletion. +// +// while this address exists for the rest of transaction, +// the balance of this account is return zero after the SelfDestruct call. +func (db *StateDB) SelfDestruct(addr gethCommon.Address) { + err := db.lastestView().SelfDestruct(addr) + db.handleError(err) +} + +// Selfdestruct6780 would only follow the self destruct steps if account is created +func (db *StateDB) Selfdestruct6780(addr gethCommon.Address) { + if db.IsCreated(addr) { + db.SelfDestruct(addr) + } +} + +// HasSelfDestructed returns true if address is flaged with self destruct. +func (db *StateDB) HasSelfDestructed(addr gethCommon.Address) bool { + destructed, _ := db.lastestView().HasSelfDestructed(addr) + return destructed +} + +// SubBalance substitutes the amount from the balance of the given address +func (db *StateDB) SubBalance(addr gethCommon.Address, amount *big.Int) { + err := db.lastestView().SubBalance(addr, amount) + db.handleError(err) +} + +// AddBalance adds the amount from the balance of the given address +func (db *StateDB) AddBalance(addr gethCommon.Address, amount *big.Int) { + err := db.lastestView().AddBalance(addr, amount) + db.handleError(err) +} + +// GetBalance returns the balance of the given address +func (db *StateDB) GetBalance(addr gethCommon.Address) *big.Int { + bal, err := db.lastestView().GetBalance(addr) + db.handleError(err) + return bal +} + +// GetNonce returns the nonce of the given address +func (db *StateDB) GetNonce(addr gethCommon.Address) uint64 { + nonce, err := db.lastestView().GetNonce(addr) + db.handleError(err) + return nonce +} + +// SetNonce sets the nonce value for the given address +func (db *StateDB) SetNonce(addr gethCommon.Address, nonce uint64) { + err := db.lastestView().SetNonce(addr, nonce) + db.handleError(err) +} + +// GetCodeHash returns the code hash of the given address +func (db *StateDB) GetCodeHash(addr gethCommon.Address) gethCommon.Hash { + hash, err := db.lastestView().GetCodeHash(addr) + db.handleError(err) + return hash +} + +// GetCode returns the code for the given address +func (db *StateDB) GetCode(addr gethCommon.Address) []byte { + code, err := db.lastestView().GetCode(addr) + db.handleError(err) + return code +} + +// GetCodeSize returns the size of the code for the given address +func (db *StateDB) GetCodeSize(addr gethCommon.Address) int { + codeSize, err := db.lastestView().GetCodeSize(addr) + db.handleError(err) + return codeSize +} + +// SetCode sets the code for the given address +func (db *StateDB) SetCode(addr gethCommon.Address, code []byte) { + err := db.lastestView().SetCode(addr, code) + db.handleError(err) +} + +// AddRefund adds the amount to the total (gas) refund +func (db *StateDB) AddRefund(amount uint64) { + err := db.lastestView().AddRefund(amount) + db.handleError(err) +} + +// SubRefund subtracts the amount from the total (gas) refund +func (db *StateDB) SubRefund(amount uint64) { + err := db.lastestView().SubRefund(amount) + db.handleError(err) +} + +// GetRefund returns the total (gas) refund +func (db *StateDB) GetRefund() uint64 { + return db.lastestView().GetRefund() +} + +// GetCommittedState returns the value for the given storage slot considering only the commited state and not +// changes in the scope of current transaction. +func (db *StateDB) GetCommittedState(addr gethCommon.Address, key gethCommon.Hash) gethCommon.Hash { + value, err := db.baseView.GetState(types.SlotAddress{Address: addr, Key: key}) + db.handleError(err) + return value +} + +// GetState returns the value for the given storage slot +func (db *StateDB) GetState(addr gethCommon.Address, key gethCommon.Hash) gethCommon.Hash { + state, err := db.lastestView().GetState(types.SlotAddress{Address: addr, Key: key}) + db.handleError(err) + return state +} + +// SetState sets a value for the given storage slot +func (db *StateDB) SetState(addr gethCommon.Address, key gethCommon.Hash, value gethCommon.Hash) { + err := db.lastestView().SetState(types.SlotAddress{Address: addr, Key: key}, value) + db.handleError(err) +} + +// GetTransientState returns the value for the given key of the transient storage +func (db *StateDB) GetTransientState(addr gethCommon.Address, key gethCommon.Hash) gethCommon.Hash { + return db.lastestView().GetTransientState(types.SlotAddress{Address: addr, Key: key}) +} + +// SetTransientState sets a value for the given key of the transient storage +func (db *StateDB) SetTransientState(addr gethCommon.Address, key, value gethCommon.Hash) { + db.lastestView().SetTransientState(types.SlotAddress{Address: addr, Key: key}, value) +} + +// AddressInAccessList checks if an address is in the access list +func (db *StateDB) AddressInAccessList(addr gethCommon.Address) bool { + return db.lastestView().AddressInAccessList(addr) +} + +// SlotInAccessList checks if the given (address,slot) is in the access list +func (db *StateDB) SlotInAccessList(addr gethCommon.Address, key gethCommon.Hash) (addressOk bool, slotOk bool) { + return db.lastestView().SlotInAccessList(types.SlotAddress{Address: addr, Key: key}) +} + +// AddAddressToAccessList adds the given address to the access list. +func (db *StateDB) AddAddressToAccessList(addr gethCommon.Address) { + db.lastestView().AddAddressToAccessList(addr) +} + +// AddSlotToAccessList adds the given (address,slot) to the access list. +func (db *StateDB) AddSlotToAccessList(addr gethCommon.Address, key gethCommon.Hash) { + db.lastestView().AddSlotToAccessList(types.SlotAddress{Address: addr, Key: key}) +} + +// AddLog appends a lot to the collection of logs +func (db *StateDB) AddLog(log *gethTypes.Log) { + db.lastestView().AddLog(log) +} + +// AddPreimage adds a preimage to the collection of preimages +func (db *StateDB) AddPreimage(hash gethCommon.Hash, data []byte) { + db.lastestView().AddPreimage(hash, data) +} + +// RevertToSnapshot reverts the changes until we reach the given snaptshot +func (db *StateDB) RevertToSnapshot(index int) { + if index > len(db.views) { + db.cachedError = fmt.Errorf("invalid revert") + return + } + db.views = db.views[:index] +} + +// Snapshot takes an snapshot of the state and returns an int +// that can be used later for revert calls. +func (db *StateDB) Snapshot() int { + newView := db.lastestView().NewChildView() + db.views = append(db.views, newView) + return len(db.views) - 1 +} + +// Logs returns the list of logs +// it also update each log with the block and tx info +func (db *StateDB) Logs( + blockHash gethCommon.Hash, + blockNumber uint64, + txHash gethCommon.Hash, + txIndex uint, +) []*gethTypes.Log { + allLogs := make([]*gethTypes.Log, 0) + for _, view := range db.views { + for _, log := range view.Logs() { + log.BlockNumber = blockNumber + log.BlockHash = blockHash + log.TxHash = txHash + log.TxIndex = txIndex + allLogs = append(allLogs, log) + } + } + return allLogs +} + +// Preimages returns a set of preimages +func (db *StateDB) Preimages() map[gethCommon.Hash][]byte { + preImages := make(map[gethCommon.Hash][]byte, 0) + for _, view := range db.views { + for k, v := range view.Preimages() { + preImages[k] = v + } + } + return preImages +} + +// Commit commits state changes back to the underlying +func (db *StateDB) Commit() error { + // return error if any has been acumulated + if db.cachedError != nil { + return wrapError(db.cachedError) + } + + var err error + + // iterate views and collect dirty addresses and slots + addresses := make(map[gethCommon.Address]struct{}) + slots := make(map[types.SlotAddress]struct{}) + for _, view := range db.views { + for key := range view.DirtyAddresses() { + addresses[key] = struct{}{} + } + for key := range view.DirtySlots() { + slots[key] = struct{}{} + } + } + + // sort addresses + sortedAddresses := make([]gethCommon.Address, 0, len(addresses)) + for addr := range addresses { + sortedAddresses = append(sortedAddresses, addr) + } + + sort.Slice(sortedAddresses, + func(i, j int) bool { + return bytes.Compare(sortedAddresses[i][:], sortedAddresses[j][:]) < 0 + }) + + // update accounts + for _, addr := range sortedAddresses { + deleted := false + // first we need to delete accounts + if db.HasSelfDestructed(addr) { + err = db.baseView.DeleteAccount(addr) + if err != nil { + return wrapError(err) + } + deleted = true + } + // then create new ones + // an account might be in a single transaction be deleted and recreated + if db.IsCreated(addr) { + err = db.baseView.CreateAccount( + addr, + db.GetBalance(addr), + db.GetNonce(addr), + db.GetCode(addr), + db.GetCodeHash(addr), + ) + if err != nil { + return wrapError(err) + } + continue + } + if deleted { + continue + } + err = db.baseView.UpdateAccount( + addr, + db.GetBalance(addr), + db.GetNonce(addr), + db.GetCode(addr), + db.GetCodeHash(addr), + ) + if err != nil { + return wrapError(err) + } + } + + // sort slots + sortedSlots := make([]types.SlotAddress, 0, len(slots)) + for slot := range slots { + sortedSlots = append(sortedSlots, slot) + } + sort.Slice(sortedSlots, func(i, j int) bool { + comp := bytes.Compare(sortedSlots[i].Address[:], sortedSlots[j].Address[:]) + if comp == 0 { + return bytes.Compare(sortedSlots[i].Key[:], sortedSlots[j].Key[:]) < 0 + } + return comp < 0 + }) + + // update slots + for _, sk := range sortedSlots { + err = db.baseView.UpdateSlot( + sk, + db.GetState(sk.Address, sk.Key), + ) + if err != nil { + return wrapError(err) + } + } + + // don't purge views yet, people might call the logs etc + err = db.baseView.Commit() + if err != nil { + return wrapError(err) + } + return nil +} + +// Prepare is a highlevel logic that sadly is considered to be part of the +// stateDB interface and not on the layers above. +// based on parameters that are passed it updates accesslists +func (db *StateDB) Prepare(rules gethParams.Rules, sender, coinbase gethCommon.Address, dest *gethCommon.Address, precompiles []gethCommon.Address, txAccesses gethTypes.AccessList) { + if rules.IsBerlin { + db.AddAddressToAccessList(sender) + + if dest != nil { + db.AddAddressToAccessList(*dest) + // If it's a create-tx, the destination will be added inside egethVM.create + } + for _, addr := range precompiles { + db.AddAddressToAccessList(addr) + } + for _, el := range txAccesses { + db.AddAddressToAccessList(el.Address) + for _, key := range el.StorageKeys { + db.AddSlotToAccessList(el.Address, key) + } + } + if rules.IsShanghai { // EIP-3651: warm coinbase + db.AddAddressToAccessList(coinbase) + } + } +} + +// Error returns the memorized database failure occurred earlier. +func (s *StateDB) Error() error { + return wrapError(s.cachedError) +} + +func (db *StateDB) lastestView() *DeltaView { + return db.views[len(db.views)-1] +} + +// set error captures the first non-nil error it is called with. +func (db *StateDB) handleError(err error) { + if err == nil { + return + } + if db.cachedError == nil { + db.cachedError = err + } +} + +func wrapError(err error) error { + if err == nil { + return nil + } + + var atreeUserError *atree.UserError + // if is an atree user error + if stdErrors.As(err, &atreeUserError) { + return types.NewStateError(err) + } + + var atreeFatalError *atree.FatalError + // if is a atree fatal error or + if stdErrors.As(err, &atreeFatalError) { + return types.NewFatalError(err) + } + + // if is fvm fatal error + if errors.IsFailure(err) { + return types.NewFatalError(err) + } + + return types.NewStateError(err) +} diff --git a/fvm/evm/emulator/state/stateDB_test.go b/fvm/evm/emulator/state/stateDB_test.go new file mode 100644 index 00000000000..2d45395a72e --- /dev/null +++ b/fvm/evm/emulator/state/stateDB_test.go @@ -0,0 +1,290 @@ +package state_test + +import ( + "fmt" + "math/big" + "testing" + + gethCommon "github.com/ethereum/go-ethereum/common" + gethTypes "github.com/ethereum/go-ethereum/core/types" + gethParams "github.com/ethereum/go-ethereum/params" + "github.com/onflow/atree" + "github.com/stretchr/testify/require" + + "github.com/onflow/flow-go/fvm/evm/emulator/state" + "github.com/onflow/flow-go/fvm/evm/testutils" + "github.com/onflow/flow-go/fvm/evm/types" + "github.com/onflow/flow-go/model/flow" +) + +var rootAddr = flow.Address{1, 2, 3, 4, 5, 6, 7, 8} + +func TestStateDB(t *testing.T) { + t.Parallel() + + t.Run("test Empty method", func(t *testing.T) { + ledger := testutils.GetSimpleValueStore() + db, err := state.NewStateDB(ledger, rootAddr) + require.NoError(t, err) + + addr1 := testutils.RandomCommonAddress(t) + // non-existent account + require.True(t, db.Empty(addr1)) + require.NoError(t, db.Error()) + + db.CreateAccount(addr1) + require.NoError(t, db.Error()) + + require.True(t, db.Empty(addr1)) + require.NoError(t, db.Error()) + + db.AddBalance(addr1, big.NewInt(10)) + require.NoError(t, db.Error()) + + require.False(t, db.Empty(addr1)) + }) + + t.Run("test commit functionality", func(t *testing.T) { + ledger := testutils.GetSimpleValueStore() + db, err := state.NewStateDB(ledger, rootAddr) + require.NoError(t, err) + + addr1 := testutils.RandomCommonAddress(t) + key1 := testutils.RandomCommonHash(t) + value1 := testutils.RandomCommonHash(t) + + db.CreateAccount(addr1) + require.NoError(t, db.Error()) + + db.AddBalance(addr1, big.NewInt(5)) + require.NoError(t, db.Error()) + + // should have code to be able to set state + db.SetCode(addr1, []byte{1, 2, 3}) + require.NoError(t, db.Error()) + + db.SetState(addr1, key1, value1) + + ret := db.GetState(addr1, key1) + require.Equal(t, value1, ret) + + ret = db.GetCommittedState(addr1, key1) + require.Equal(t, gethCommon.Hash{}, ret) + + err = db.Commit() + require.NoError(t, err) + + ret = db.GetCommittedState(addr1, key1) + require.Equal(t, value1, ret) + + // create a new db + db, err = state.NewStateDB(ledger, rootAddr) + require.NoError(t, err) + + bal := db.GetBalance(addr1) + require.NoError(t, db.Error()) + require.Equal(t, big.NewInt(5), bal) + + val := db.GetState(addr1, key1) + require.NoError(t, db.Error()) + require.Equal(t, value1, val) + }) + + t.Run("test snapshot and revert functionality", func(t *testing.T) { + ledger := testutils.GetSimpleValueStore() + db, err := state.NewStateDB(ledger, rootAddr) + require.NoError(t, err) + + addr1 := testutils.RandomCommonAddress(t) + require.False(t, db.Exist(addr1)) + require.NoError(t, db.Error()) + + snapshot1 := db.Snapshot() + require.Equal(t, 1, snapshot1) + + db.CreateAccount(addr1) + require.NoError(t, db.Error()) + + require.True(t, db.Exist(addr1)) + require.NoError(t, db.Error()) + + db.AddBalance(addr1, big.NewInt(5)) + require.NoError(t, db.Error()) + + bal := db.GetBalance(addr1) + require.NoError(t, db.Error()) + require.Equal(t, big.NewInt(5), bal) + + snapshot2 := db.Snapshot() + require.Equal(t, 2, snapshot2) + + db.AddBalance(addr1, big.NewInt(5)) + require.NoError(t, db.Error()) + + bal = db.GetBalance(addr1) + require.NoError(t, db.Error()) + require.Equal(t, big.NewInt(10), bal) + + // revert to snapshot 2 + db.RevertToSnapshot(snapshot2) + require.NoError(t, db.Error()) + + bal = db.GetBalance(addr1) + require.NoError(t, db.Error()) + require.Equal(t, big.NewInt(5), bal) + + // revert to snapshot 1 + db.RevertToSnapshot(snapshot1) + require.NoError(t, db.Error()) + + bal = db.GetBalance(addr1) + require.NoError(t, db.Error()) + require.Equal(t, big.NewInt(0), bal) + + // revert to an invalid snapshot + db.RevertToSnapshot(10) + require.Error(t, db.Error()) + }) + + t.Run("test log functionality", func(t *testing.T) { + ledger := testutils.GetSimpleValueStore() + db, err := state.NewStateDB(ledger, rootAddr) + require.NoError(t, err) + + logs := []*gethTypes.Log{ + testutils.GetRandomLogFixture(t), + testutils.GetRandomLogFixture(t), + testutils.GetRandomLogFixture(t), + testutils.GetRandomLogFixture(t), + } + + db.AddLog(logs[0]) + db.AddLog(logs[1]) + + _ = db.Snapshot() + + db.AddLog(logs[2]) + db.AddLog(logs[3]) + + snapshot := db.Snapshot() + db.AddLog(testutils.GetRandomLogFixture(t)) + db.RevertToSnapshot(snapshot) + + ret := db.Logs(gethCommon.Hash{}, 1, gethCommon.Hash{}, 1) + require.Equal(t, ret, logs) + }) + + t.Run("test refund functionality", func(t *testing.T) { + ledger := testutils.GetSimpleValueStore() + db, err := state.NewStateDB(ledger, rootAddr) + require.NoError(t, err) + + require.Equal(t, uint64(0), db.GetRefund()) + db.AddRefund(10) + require.Equal(t, uint64(10), db.GetRefund()) + db.SubRefund(3) + require.Equal(t, uint64(7), db.GetRefund()) + + snap1 := db.Snapshot() + db.AddRefund(10) + require.Equal(t, uint64(17), db.GetRefund()) + + db.RevertToSnapshot(snap1) + require.Equal(t, uint64(7), db.GetRefund()) + }) + + t.Run("test Prepare functionality", func(t *testing.T) { + ledger := testutils.GetSimpleValueStore() + db, err := state.NewStateDB(ledger, rootAddr) + + sender := testutils.RandomCommonAddress(t) + coinbase := testutils.RandomCommonAddress(t) + dest := testutils.RandomCommonAddress(t) + precompiles := []gethCommon.Address{ + testutils.RandomCommonAddress(t), + testutils.RandomCommonAddress(t), + } + + txAccesses := gethTypes.AccessList([]gethTypes.AccessTuple{ + {Address: testutils.RandomCommonAddress(t), + StorageKeys: []gethCommon.Hash{ + testutils.RandomCommonHash(t), + testutils.RandomCommonHash(t), + }, + }, + }) + + rules := gethParams.Rules{ + IsBerlin: true, + IsShanghai: true, + } + + require.NoError(t, err) + db.Prepare(rules, sender, coinbase, &dest, precompiles, txAccesses) + + require.True(t, db.AddressInAccessList(sender)) + require.True(t, db.AddressInAccessList(coinbase)) + require.True(t, db.AddressInAccessList(dest)) + + for _, add := range precompiles { + require.True(t, db.AddressInAccessList(add)) + } + + for _, el := range txAccesses { + for _, key := range el.StorageKeys { + addrFound, slotFound := db.SlotInAccessList(el.Address, key) + require.True(t, addrFound) + require.True(t, slotFound) + } + } + }) + + t.Run("test non-fatal error handling", func(t *testing.T) { + ledger := &testutils.TestValueStore{ + GetValueFunc: func(owner, key []byte) ([]byte, error) { + return nil, nil + }, + SetValueFunc: func(owner, key, value []byte) error { + return atree.NewUserError(fmt.Errorf("key not found")) + }, + AllocateStorageIndexFunc: func(owner []byte) (atree.StorageIndex, error) { + return atree.StorageIndex{}, nil + }, + } + db, err := state.NewStateDB(ledger, rootAddr) + require.NoError(t, err) + + db.CreateAccount(testutils.RandomCommonAddress(t)) + + err = db.Commit() + // ret := db.Error() + require.Error(t, err) + // check wrapping + require.True(t, types.IsAStateError(err)) + }) + + t.Run("test fatal error handling", func(t *testing.T) { + ledger := &testutils.TestValueStore{ + GetValueFunc: func(owner, key []byte) ([]byte, error) { + return nil, nil + }, + SetValueFunc: func(owner, key, value []byte) error { + return atree.NewFatalError(fmt.Errorf("key not found")) + }, + AllocateStorageIndexFunc: func(owner []byte) (atree.StorageIndex, error) { + return atree.StorageIndex{}, nil + }, + } + db, err := state.NewStateDB(ledger, rootAddr) + require.NoError(t, err) + + db.CreateAccount(testutils.RandomCommonAddress(t)) + + err = db.Commit() + // ret := db.Error() + require.Error(t, err) + // check wrapping + require.True(t, types.IsAFatalError(err)) + }) + +} diff --git a/fvm/evm/emulator/state_test.go b/fvm/evm/emulator/state/state_growth_test.go similarity index 77% rename from fvm/evm/emulator/state_test.go rename to fvm/evm/emulator/state/state_growth_test.go index a7341ba6c97..b7502728d8b 100644 --- a/fvm/evm/emulator/state_test.go +++ b/fvm/evm/emulator/state/state_growth_test.go @@ -1,4 +1,4 @@ -package emulator_test +package state_test import ( "encoding/binary" @@ -10,15 +10,12 @@ import ( "github.com/onflow/flow-go/utils/io" - "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/common" - gethRawDB "github.com/ethereum/go-ethereum/core/rawdb" - gethState "github.com/ethereum/go-ethereum/core/state" "github.com/stretchr/testify/require" - "github.com/onflow/flow-go/fvm/evm/emulator/database" + "github.com/onflow/flow-go/fvm/evm/emulator/state" "github.com/onflow/flow-go/fvm/evm/testutils" + "github.com/onflow/flow-go/fvm/evm/types" "github.com/onflow/flow-go/model/flow" ) @@ -36,37 +33,16 @@ const ( type storageTest struct { store *testutils.TestValueStore - db *database.MeteredDatabase - ethDB ethdb.Database - stateDB gethState.Database addressIndex uint64 - hash common.Hash metrics *metrics } func newStorageTest() (*storageTest, error) { simpleStore := testutils.GetSimpleValueStore() - db, err := database.NewMeteredDatabase(simpleStore, flow.Address{0x01}) - if err != nil { - return nil, err - } - - hash, err := db.GetRootHash() - if err != nil { - return nil, err - } - - rawDB := gethRawDB.NewDatabase(db) - stateDB := gethState.NewDatabase(rawDB) - return &storageTest{ store: simpleStore, - db: db, - ethDB: rawDB, - stateDB: stateDB, addressIndex: 100, - hash: hash, metrics: newMetrics(), }, nil } @@ -80,33 +56,21 @@ func (s *storageTest) newAddress() common.Address { // run the provided runner with a newly created state which gets comitted after the runner // is finished. Storage metrics are being recorded with each run. -func (s *storageTest) run(runner func(state *gethState.StateDB)) error { - state, err := gethState.New(s.hash, s.stateDB, nil) +func (s *storageTest) run(runner func(state types.StateDB)) error { + state, err := state.NewStateDB(s.store, flow.Address{0x01}) if err != nil { return err } runner(state) - s.hash, err = state.Commit(true) - if err != nil { - return err - } - - err = state.Database().TrieDB().Commit(s.hash, true) + err = state.Commit() if err != nil { return err } - err = s.db.Commit(s.hash) - if err != nil { - return err - } - - s.db.DropCache() - - s.metrics.add(bytesWrittenMetric, s.db.BytesStored()) - s.metrics.add(bytesReadMetric, s.db.BytesRetrieved()) + s.metrics.add(bytesWrittenMetric, s.store.TotalBytesWritten()) + s.metrics.add(bytesReadMetric, s.store.TotalBytesRead()) s.metrics.add(storageItemsMetric, s.store.TotalStorageItems()) s.metrics.add(storageBytesMetric, s.store.TotalStorageSize()) @@ -168,7 +132,7 @@ func Test_AccountCreations(t *testing.T) { accountChart := "accounts,storage_size" maxAccounts := 50_000 for i := 0; i < maxAccounts; i++ { - err = tester.run(func(state *gethState.StateDB) { + err = tester.run(func(state types.StateDB) { state.AddBalance(tester.newAddress(), big.NewInt(100)) }) require.NoError(t, err) @@ -205,16 +169,19 @@ func Test_AccountContractInteraction(t *testing.T) { interactions := 50000 for i := 0; i < interactions; i++ { - err = tester.run(func(state *gethState.StateDB) { + err = tester.run(func(state types.StateDB) { // create a new account accAddr := tester.newAddress() state.AddBalance(accAddr, big.NewInt(100)) // create a contract contractAddr := tester.newAddress() - state.SetBalance(contractAddr, big.NewInt(int64(i))) + state.AddBalance(contractAddr, big.NewInt(int64(i))) state.SetCode(contractAddr, code) - state.SetStorage(contractAddr, contractState) + + for k, v := range contractState { + state.SetState(contractAddr, k, v) + } // simulate interaction with contract state and account balance for fees state.SetState(contractAddr, common.HexToHash("0x03"), common.HexToHash("0x40")) diff --git a/fvm/evm/evm.go b/fvm/evm/evm.go index a44b8be4552..37a9cb07862 100644 --- a/fvm/evm/evm.go +++ b/fvm/evm/evm.go @@ -5,7 +5,6 @@ import ( "github.com/onflow/cadence/runtime/common" evm "github.com/onflow/flow-go/fvm/evm/emulator" - "github.com/onflow/flow-go/fvm/evm/emulator/database" "github.com/onflow/flow-go/fvm/evm/handler" "github.com/onflow/flow-go/fvm/evm/stdlib" "github.com/onflow/flow-go/fvm/evm/types" @@ -13,9 +12,14 @@ import ( "github.com/onflow/flow-go/model/flow" ) -func RootAccountAddress(chainID flow.ChainID) (flow.Address, error) { +func ContractAccountAddress(chainID flow.ChainID) (flow.Address, error) { sc := systemcontracts.SystemContractsForChain(chainID) - return sc.EVM.Address, nil + return sc.EVMContract.Address, nil +} + +func StorageAccountAddress(chainID flow.ChainID) (flow.Address, error) { + sc := systemcontracts.SystemContractsForChain(chainID) + return sc.EVMStorage.Address, nil } func SetupEnvironment( @@ -25,32 +29,31 @@ func SetupEnvironment( service flow.Address, flowToken flow.Address, ) error { - // TODO: setup proper root address based on chainID - evmRootAddress, err := RootAccountAddress(chainID) + evmStorageAccountAddress, err := StorageAccountAddress(chainID) if err != nil { return err } - db, err := database.NewDatabase(backend, evmRootAddress) + evmContractAccountAddress, err := ContractAccountAddress(chainID) if err != nil { return err } - em := evm.NewEmulator(db) + em := evm.NewEmulator(backend, evmStorageAccountAddress) - bs, err := handler.NewBlockStore(backend, evmRootAddress) + bs, err := handler.NewBlockStore(backend, evmStorageAccountAddress) if err != nil { return err } - aa, err := handler.NewAddressAllocator(backend, evmRootAddress) + aa, err := handler.NewAddressAllocator(backend, evmStorageAccountAddress) if err != nil { return err } contractHandler := handler.NewContractHandler(common.Address(flowToken), bs, aa, backend, em) - stdlib.SetupEnvironment(env, contractHandler, service) + stdlib.SetupEnvironment(env, contractHandler, evmContractAccountAddress) return nil } diff --git a/fvm/evm/evm_test.go b/fvm/evm/evm_test.go index 6108b57aaaf..32f4823df20 100644 --- a/fvm/evm/evm_test.go +++ b/fvm/evm/evm_test.go @@ -9,6 +9,7 @@ import ( "github.com/onflow/cadence/encoding/json" "github.com/stretchr/testify/require" + "github.com/onflow/flow-go/engine/execution/testutil" "github.com/onflow/flow-go/fvm" "github.com/onflow/flow-go/fvm/evm/stdlib" "github.com/onflow/flow-go/fvm/evm/testutils" @@ -33,6 +34,7 @@ func TestEVMRun(t *testing.T) { chain := flow.Emulator.Chain() RunWithNewTestVM(t, chain, func(ctx fvm.Context, vm fvm.VM, snapshot snapshot.SnapshotTree) { + sc := systemcontracts.SystemContractsForChain(chain.ChainID()) code := []byte(fmt.Sprintf( ` import EVM from %s @@ -43,7 +45,7 @@ func TestEVMRun(t *testing.T) { EVM.run(tx: tx, coinbase: coinbase) } `, - chain.ServiceAddress().HexWithPrefix(), + sc.EVMContract.Address.HexWithPrefix(), )) gasLimit := uint64(100_000) @@ -84,10 +86,12 @@ func TestEVMRun(t *testing.T) { } func RunWithNewTestVM(t *testing.T, chain flow.Chain, f func(fvm.Context, fvm.VM, snapshot.SnapshotTree)) { + opts := []fvm.Option{ fvm.WithChain(chain), fvm.WithAuthorizationChecksEnabled(false), fvm.WithSequenceNumberCheckAndIncrementEnabled(false), + fvm.WithEntropyProvider(testutil.EntropyProviderFixture(nil)), } ctx := fvm.NewContext(opts...) @@ -126,25 +130,25 @@ func TestEVMAddressDeposit(t *testing.T) { code := []byte(fmt.Sprintf( ` - import EVM from %[1]s - import FlowToken from %[2]s + import EVM from %s + import FlowToken from %s access(all) fun main() { - let admin = getAuthAccount(%[1]s) + let admin = getAuthAccount(%s) .borrow<&FlowToken.Administrator>(from: /storage/flowTokenAdmin)! let minter <- admin.createNewMinter(allowedAmount: 1.23) let vault <- minter.mintTokens(amount: 1.23) destroy minter - let address = EVM.EVMAddress( - bytes: [2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - ) - address.deposit(from: <-vault) + let bridgedAccount <- EVM.createBridgedAccount() + bridgedAccount.deposit(from: <-vault) + destroy bridgedAccount } `, - sc.FlowServiceAccount.Address.HexWithPrefix(), + sc.EVMContract.Address.HexWithPrefix(), sc.FlowToken.Address.HexWithPrefix(), + sc.FlowServiceAccount.Address.HexWithPrefix(), )) script := fvm.Script(code) @@ -181,19 +185,19 @@ func TestBridgedAccountWithdraw(t *testing.T) { code := []byte(fmt.Sprintf( ` - import EVM from %[1]s - import FlowToken from %[2]s + import EVM from %s + import FlowToken from %s access(all) fun main(): UFix64 { - let admin = getAuthAccount(%[1]s) + let admin = getAuthAccount(%s) .borrow<&FlowToken.Administrator>(from: /storage/flowTokenAdmin)! let minter <- admin.createNewMinter(allowedAmount: 2.34) let vault <- minter.mintTokens(amount: 2.34) destroy minter let bridgedAccount <- EVM.createBridgedAccount() - bridgedAccount.address().deposit(from: <-vault) + bridgedAccount.deposit(from: <-vault) let vault2 <- bridgedAccount.withdraw(balance: EVM.Balance(flow: 1.23)) let balance = vault2.balance @@ -203,8 +207,9 @@ func TestBridgedAccountWithdraw(t *testing.T) { return balance } `, - sc.FlowServiceAccount.Address.HexWithPrefix(), + sc.EVMContract.Address.HexWithPrefix(), sc.FlowToken.Address.HexWithPrefix(), + sc.FlowServiceAccount.Address.HexWithPrefix(), )) script := fvm.Script(code) @@ -225,11 +230,8 @@ func TestBridgedAccountWithdraw(t *testing.T) { }) } -// TODO: provide proper contract code func TestBridgedAccountDeploy(t *testing.T) { - t.Parallel() - RunWithTestBackend(t, func(backend *testutils.TestBackend) { RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { tc := GetStorageTestContract(t) @@ -242,19 +244,19 @@ func TestBridgedAccountDeploy(t *testing.T) { code := []byte(fmt.Sprintf( ` - import EVM from %[1]s - import FlowToken from %[2]s + import EVM from %s + import FlowToken from %s access(all) fun main(): [UInt8; 20] { - let admin = getAuthAccount(%[1]s) + let admin = getAuthAccount(%s) .borrow<&FlowToken.Administrator>(from: /storage/flowTokenAdmin)! let minter <- admin.createNewMinter(allowedAmount: 2.34) let vault <- minter.mintTokens(amount: 2.34) destroy minter let bridgedAccount <- EVM.createBridgedAccount() - bridgedAccount.address().deposit(from: <-vault) + bridgedAccount.deposit(from: <-vault) let address = bridgedAccount.deploy( code: [], @@ -265,8 +267,9 @@ func TestBridgedAccountDeploy(t *testing.T) { return address.bytes } `, - sc.FlowServiceAccount.Address.HexWithPrefix(), + sc.EVMContract.Address.HexWithPrefix(), sc.FlowToken.Address.HexWithPrefix(), + sc.FlowServiceAccount.Address.HexWithPrefix(), )) script := fvm.Script(code) diff --git a/fvm/evm/handler/addressAllocator.go b/fvm/evm/handler/addressAllocator.go index 28e476d9427..d1dc8299130 100644 --- a/fvm/evm/handler/addressAllocator.go +++ b/fvm/evm/handler/addressAllocator.go @@ -9,7 +9,19 @@ import ( "github.com/onflow/flow-go/model/flow" ) -const ledgerAddressAllocatorKey = "AddressAllocator" +const ( + ledgerAddressAllocatorKey = "AddressAllocator" + uint64ByteSize = 8 + addressPrefixLen = 12 +) + +var ( + // prefixes: + // the first 12 bytes of addresses allocation + // leading zeros helps with storage and all zero is reserved for the EVM precompiles + FlowEVMPrecompileAddressPrefix = [addressPrefixLen]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1} + FlowEVMCOAAddressPrefix = [addressPrefixLen]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2} +) type AddressAllocator struct { led atree.Ledger @@ -26,8 +38,8 @@ func NewAddressAllocator(led atree.Ledger, flexAddress flow.Address) (*AddressAl }, nil } -// AllocateAddress allocates an address -func (aa *AddressAllocator) AllocateAddress() (types.Address, error) { +// AllocateCOAAddress allocates an address for COA +func (aa *AddressAllocator) AllocateCOAAddress() (types.Address, error) { data, err := aa.led.GetValue(aa.flexAddress[:], []byte(ledgerAddressAllocatorKey)) if err != nil { return types.Address{}, err @@ -38,10 +50,7 @@ func (aa *AddressAllocator) AllocateAddress() (types.Address, error) { uuid = binary.BigEndian.Uint64(data) } - target := types.Address{} - // first 12 bytes would be zero - // the next 8 bytes would be an increment of the UUID index - binary.BigEndian.PutUint64(target[12:], uuid) + target := MakeCOAAddress(uuid) // store new uuid newData := make([]byte, 8) @@ -53,3 +62,24 @@ func (aa *AddressAllocator) AllocateAddress() (types.Address, error) { return target, nil } + +func MakeCOAAddress(index uint64) types.Address { + return makePrefixedAddress(index, FlowEVMCOAAddressPrefix) +} + +func (aa *AddressAllocator) AllocatePrecompileAddress(index uint64) types.Address { + target := MakePrecompileAddress(index) + return target +} + +func MakePrecompileAddress(index uint64) types.Address { + return makePrefixedAddress(index, FlowEVMPrecompileAddressPrefix) +} + +func makePrefixedAddress(index uint64, prefix [addressPrefixLen]byte) types.Address { + var addr types.Address + prefixIndex := types.AddressLength - uint64ByteSize + copy(addr[:prefixIndex], prefix[:]) + binary.BigEndian.PutUint64(addr[prefixIndex:], index) + return addr +} diff --git a/fvm/evm/handler/addressAllocator_test.go b/fvm/evm/handler/addressAllocator_test.go index ab8eb0de2b4..03794baea9a 100644 --- a/fvm/evm/handler/addressAllocator_test.go +++ b/fvm/evm/handler/addressAllocator_test.go @@ -19,16 +19,20 @@ func TestAddressAllocator(t *testing.T) { aa, err := handler.NewAddressAllocator(backend, root) require.NoError(t, err) + adr := aa.AllocatePrecompileAddress(3) + expectedAddress := types.NewAddress(gethCommon.HexToAddress("0x0000000000000000000000010000000000000003")) + require.Equal(t, expectedAddress, adr) + // test default value fall back - adr, err := aa.AllocateAddress() + adr, err = aa.AllocateCOAAddress() require.NoError(t, err) - expectedAddress := types.NewAddress(gethCommon.HexToAddress("0x00000000000000000001")) + expectedAddress = types.NewAddress(gethCommon.HexToAddress("0x0000000000000000000000020000000000000001")) require.Equal(t, expectedAddress, adr) // continous allocation logic - adr, err = aa.AllocateAddress() + adr, err = aa.AllocateCOAAddress() require.NoError(t, err) - expectedAddress = types.NewAddress(gethCommon.HexToAddress("0x00000000000000000002")) + expectedAddress = types.NewAddress(gethCommon.HexToAddress("0x0000000000000000000000020000000000000002")) require.Equal(t, expectedAddress, adr) }) diff --git a/fvm/evm/handler/blockstore_test.go b/fvm/evm/handler/blockstore_test.go index 77720b143a2..b198f04c053 100644 --- a/fvm/evm/handler/blockstore_test.go +++ b/fvm/evm/handler/blockstore_test.go @@ -1,6 +1,7 @@ package handler_test import ( + "math/big" "testing" "github.com/stretchr/testify/require" @@ -32,7 +33,7 @@ func TestBlockStore(t *testing.T) { require.Equal(t, expectedParentHash, bp.ParentBlockHash) // commit block proposal - supply := uint64(100) + supply := big.NewInt(100) bp.TotalSupply = supply err = bs.CommitBlockProposal() require.NoError(t, err) diff --git a/fvm/evm/handler/handler.go b/fvm/evm/handler/handler.go index 4ed802e08a8..7c9987f0d10 100644 --- a/fvm/evm/handler/handler.go +++ b/fvm/evm/handler/handler.go @@ -2,32 +2,28 @@ package handler import ( "bytes" + "math/big" + gethCommon "github.com/ethereum/go-ethereum/common" gethTypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/rlp" "github.com/onflow/cadence/runtime/common" "github.com/onflow/flow-go/fvm/environment" "github.com/onflow/flow-go/fvm/errors" + "github.com/onflow/flow-go/fvm/evm/precompiles" "github.com/onflow/flow-go/fvm/evm/types" ) // ContractHandler is responsible for triggering calls to emulator, metering, // event emission and updating the block -// -// TODO and Warning: currently database keeps a copy of roothash, and if after -// commiting the changes by the evm we want to revert in this code we need to reset that -// or we should always do all the checks and return before calling the emulator, -// after that should be only event emissions and computation usage updates. -// thats another reason we first check the computation limit before using. -// in the future we might benefit from a view style of access to db passed as -// a param to the emulator. type ContractHandler struct { flowTokenAddress common.Address blockstore types.BlockStore addressAllocator types.AddressAllocator backend types.Backend emulator types.Emulator + precompiles []types.Precompile } func (h *ContractHandler) FlowTokenAddress() common.Address { @@ -49,12 +45,25 @@ func NewContractHandler( addressAllocator: addressAllocator, backend: backend, emulator: emulator, + precompiles: getPrecompiles(addressAllocator, backend), } } +func getPrecompiles( + addressAllocator types.AddressAllocator, + backend types.Backend, +) []types.Precompile { + archAddress := addressAllocator.AllocatePrecompileAddress(1) + archContract := precompiles.ArchContract( + archAddress, + backend.GetCurrentBlockHeight, + ) + return []types.Precompile{archContract} +} + // AllocateAddress allocates an address to be used by the bridged accounts func (h *ContractHandler) AllocateAddress() types.Address { - target, err := h.addressAllocator.AllocateAddress() + target, err := h.addressAllocator.AllocateCOAAddress() handleError(err) return target } @@ -103,7 +112,6 @@ func (h *ContractHandler) Run(rlpEncodedTx []byte, coinbase types.Address) { bp, err := h.blockstore.BlockProposal() handleError(err) - bp.StateRoot = res.StateRootHash txHash := tx.Hash() bp.AppendTxHash(txHash) @@ -146,9 +154,14 @@ func (h *ContractHandler) emitEvent(event *types.Event) { func (h *ContractHandler) getBlockContext() types.BlockContext { bp, err := h.blockstore.BlockProposal() handleError(err) + rand := gethCommon.Hash{} + err = h.backend.ReadRandom(rand[:]) + handleError(err) return types.BlockContext{ BlockNumber: bp.Height, DirectCallBaseGasUsage: types.DefaultDirectCallBaseGasUsage, + ExtraPrecompiles: h.precompiles, + Random: rand, } } @@ -185,9 +198,7 @@ func (a *Account) Balance() types.Balance { bl, err := blk.BalanceOf(a.address) handleError(err) - balance, err := types.NewBalanceFromAttoFlow(bl) - handleError(err) - return balance + return types.NewBalance(bl) } // Deposit deposits the token from the given vault into the flow evm main vault @@ -198,9 +209,9 @@ func (a *Account) Deposit(v *types.FLOWTokenVault) { call := types.NewDepositCall( a.address, - v.Balance().ToAttoFlow(), + v.Balance(), ) - a.executeAndHandleCall(a.fch.getBlockContext(), call, v.Balance().ToAttoFlow().Uint64(), false) + a.executeAndHandleCall(a.fch.getBlockContext(), call, v.Balance(), false) } // Withdraw deducts the balance from the account and @@ -214,15 +225,19 @@ func (a *Account) Withdraw(b types.Balance) *types.FLOWTokenVault { // check balance of flex vault bp, err := a.fch.blockstore.BlockProposal() handleError(err) - if b.ToAttoFlow().Uint64() > bp.TotalSupply { + // b > total supply + if types.BalanceToBigInt(b).Cmp(bp.TotalSupply) == 1 { handleError(types.ErrInsufficientTotalSupply) } - + // Don't allow withdraw for balances that has rounding error + if types.BalanceConvertionToUFix64ProneToRoundingError(b) { + handleError(types.ErrWithdrawBalanceRounding) + } call := types.NewWithdrawCall( a.address, - b.ToAttoFlow(), + b, ) - a.executeAndHandleCall(a.fch.getBlockContext(), call, b.ToAttoFlow().Uint64(), true) + a.executeAndHandleCall(a.fch.getBlockContext(), call, b, true) return types.NewFlowTokenVault(b) } @@ -237,9 +252,9 @@ func (a *Account) Transfer(to types.Address, balance types.Balance) { call := types.NewTransferCall( a.address, to, - balance.ToAttoFlow(), + balance, ) - a.executeAndHandleCall(ctx, call, 0, false) + a.executeAndHandleCall(ctx, call, nil, false) } // Deploy deploys a contract to the EVM environment @@ -253,9 +268,9 @@ func (a *Account) Deploy(code types.Code, gaslimit types.GasLimit, balance types a.address, code, uint64(gaslimit), - balance.ToAttoFlow(), + balance, ) - res := a.executeAndHandleCall(a.fch.getBlockContext(), call, 0, false) + res := a.executeAndHandleCall(a.fch.getBlockContext(), call, nil, false) return types.Address(res.DeployedContractAddress) } @@ -271,16 +286,16 @@ func (a *Account) Call(to types.Address, data types.Data, gaslimit types.GasLimi to, data, uint64(gaslimit), - balance.ToAttoFlow(), + balance, ) - res := a.executeAndHandleCall(a.fch.getBlockContext(), call, 0, false) + res := a.executeAndHandleCall(a.fch.getBlockContext(), call, nil, false) return res.ReturnedValue } func (a *Account) executeAndHandleCall( ctx types.BlockContext, call *types.DirectCall, - totalSupplyDiff uint64, + totalSupplyDiff *big.Int, deductSupplyDiff bool, ) *types.Result { // execute the call @@ -301,12 +316,13 @@ func (a *Account) executeAndHandleCall( bp, err := a.fch.blockstore.BlockProposal() handleError(err) bp.AppendTxHash(callHash) - bp.StateRoot = res.StateRootHash - if deductSupplyDiff { - bp.TotalSupply -= totalSupplyDiff - } else { - // TODO: add overflow errors (even though we might never get there) - bp.TotalSupply += totalSupplyDiff + if totalSupplyDiff != nil { + if deductSupplyDiff { + bp.TotalSupply = new(big.Int).Sub(bp.TotalSupply, totalSupplyDiff) + } else { + bp.TotalSupply = new(big.Int).Add(bp.TotalSupply, totalSupplyDiff) + } + } // emit events diff --git a/fvm/evm/handler/handler_benchmark_test.go b/fvm/evm/handler/handler_benchmark_test.go index 73f0f0ed59d..0aa1b7c4de6 100644 --- a/fvm/evm/handler/handler_benchmark_test.go +++ b/fvm/evm/handler/handler_benchmark_test.go @@ -21,14 +21,13 @@ func benchmarkStorageGrowth(b *testing.B, accountCount, setupKittyCount int) { backend, rootAddr, func(tc *testutils.TestContract) { - db, handler := SetupHandler(b, backend, rootAddr) - numOfAccounts := 100000 - accounts := make([]types.Account, numOfAccounts) + handler := SetupHandler(b, backend, rootAddr) + accounts := make([]types.Account, accountCount) // setup several of accounts // note that trie growth is the function of number of accounts - for i := 0; i < numOfAccounts; i++ { + for i := 0; i < accountCount; i++ { account := handler.AccountByAddress(handler.AllocateAddress(), true) - account.Deposit(types.NewFlowTokenVault(types.Balance(100))) + account.Deposit(types.NewFlowTokenVault(types.NewBalanceFromUFix64(100))) accounts[i] = account } backend.DropEvents() @@ -50,16 +49,13 @@ func benchmarkStorageGrowth(b *testing.B, accountCount, setupKittyCount int) { genes, ), 300_000_000, - types.Balance(0), + types.NewBalanceFromUFix64(0), ) require.Equal(b, 2, len(backend.Events())) backend.DropEvents() // this would make things lighter + backend.ResetStats() // reset stats } - // measure the impact of mint after the setup phase - db.ResetReporter() - db.DropCache() - accounts[0].Call( tc.DeployedAt, tc.MakeCallData(b, @@ -70,11 +66,11 @@ func benchmarkStorageGrowth(b *testing.B, accountCount, setupKittyCount int) { testutils.RandomBigInt(1000), ), 300_000_000, - types.Balance(0), + types.NewBalanceFromUFix64(0), ) - b.ReportMetric(float64(db.BytesRetrieved()), "bytes_read") - b.ReportMetric(float64(db.BytesStored()), "bytes_written") + b.ReportMetric(float64(backend.TotalBytesRead()), "bytes_read") + b.ReportMetric(float64(backend.TotalBytesWritten()), "bytes_written") b.ReportMetric(float64(backend.TotalStorageSize()), "total_storage_size") }) }) diff --git a/fvm/evm/handler/handler_test.go b/fvm/evm/handler/handler_test.go index db78cf2d827..1ba0a6b33c4 100644 --- a/fvm/evm/handler/handler_test.go +++ b/fvm/evm/handler/handler_test.go @@ -23,8 +23,8 @@ import ( "github.com/onflow/flow-go/fvm/errors" "github.com/onflow/flow-go/fvm/evm/emulator" - "github.com/onflow/flow-go/fvm/evm/emulator/database" "github.com/onflow/flow-go/fvm/evm/handler" + "github.com/onflow/flow-go/fvm/evm/precompiles" "github.com/onflow/flow-go/fvm/evm/testutils" "github.com/onflow/flow-go/fvm/evm/types" "github.com/onflow/flow-go/fvm/systemcontracts" @@ -52,7 +52,6 @@ func TestHandler_TransactionRun(t *testing.T) { require.NoError(t, err) result := &types.Result{ - StateRootHash: testutils.RandomCommonHash(t), DeployedContractAddress: types.Address(testutils.RandomAddress(t)), ReturnedValue: testutils.RandomData(t), GasConsumed: testutils.RandomGas(1000), @@ -194,33 +193,32 @@ func TestHandler_TransactionRun(t *testing.T) { testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { - _, handler := SetupHandler(t, backend, rootAddr) + handler := SetupHandler(t, backend, rootAddr) eoa := testutils.GetTestEOAAccount(t, testutils.EOATestAccount1KeyHex) // deposit 1 Flow to the foa account addr := handler.AllocateAddress() - orgBalance, err := types.NewBalanceFromAttoFlow(types.OneFlowInAttoFlow) - require.NoError(t, err) + orgBalance := types.NewBalanceFromUFix64(types.OneFlowInUFix64) vault := types.NewFlowTokenVault(orgBalance) foa := handler.AccountByAddress(addr, true) foa.Deposit(vault) // transfer 0.1 flow to the non-foa address - deduction, err := types.NewBalanceFromAttoFlow(big.NewInt(1e17)) - require.NoError(t, err) + deduction := types.NewBalance(big.NewInt(1e17)) foa.Call(eoa.Address(), nil, 400000, deduction) - require.Equal(t, orgBalance.Sub(deduction), foa.Balance()) + expected, err := types.SubBalance(orgBalance, deduction) + require.NoError(t, err) + require.Equal(t, expected, foa.Balance()) // transfer 0.01 flow back to the foa through - addition, err := types.NewBalanceFromAttoFlow(big.NewInt(1e16)) - require.NoError(t, err) + addition := types.NewBalance(big.NewInt(1e16)) tx := eoa.PrepareSignAndEncodeTx( t, foa.Address().ToCommon(), nil, - addition.ToAttoFlow(), + addition, gethParams.TxGas*10, big.NewInt(1e8), // high gas fee to test coinbase collection, ) @@ -228,15 +226,18 @@ func TestHandler_TransactionRun(t *testing.T) { // setup coinbase foa2 := handler.AllocateAddress() account2 := handler.AccountByAddress(foa2, true) - require.Equal(t, types.Balance(0), account2.Balance()) + require.Equal(t, types.NewBalanceFromUFix64(0), account2.Balance()) // no panic means success here handler.Run(tx, account2.Address()) - require.Equal(t, orgBalance.Sub(deduction).Add(addition), foa.Balance()) + expected, err = types.SubBalance(orgBalance, deduction) + require.NoError(t, err) + expected, err = types.AddBalance(expected, addition) + require.NoError(t, err) + require.Equal(t, expected, foa.Balance()) // fees has been collected to the coinbase - require.NotEqual(t, types.Balance(0), account2.Balance()) - + require.NotEqual(t, types.NewBalanceFromUFix64(0), account2.Balance()) }) }) }) @@ -250,7 +251,7 @@ func TestHandler_OpsWithoutEmulator(t *testing.T) { testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { - _, handler := SetupHandler(t, backend, rootAddr) + handler := SetupHandler(t, backend, rootAddr) // test call last executed block without initialization b := handler.LastExecutedBlock() @@ -259,8 +260,7 @@ func TestHandler_OpsWithoutEmulator(t *testing.T) { // do some changes address := testutils.RandomAddress(t) account := handler.AccountByAddress(address, true) - bal, err := types.NewBalanceFromAttoFlow(types.OneFlowInAttoFlow) - require.NoError(t, err) + bal := types.OneFlowBalance account.Deposit(types.NewFlowTokenVault(bal)) // check if block height has been incremented @@ -281,12 +281,12 @@ func TestHandler_OpsWithoutEmulator(t *testing.T) { aa, err := handler.NewAddressAllocator(backend, rootAddr) require.NoError(t, err) - handler := handler.NewContractHandler(flowTokenAddress, blockchain, aa, backend, nil) + h := handler.NewContractHandler(flowTokenAddress, blockchain, aa, backend, nil) - foa := handler.AllocateAddress() + foa := h.AllocateAddress() require.NotNil(t, foa) - expectedAddress := types.NewAddress(gethCommon.HexToAddress("0x00000000000000000001")) + expectedAddress := handler.MakeCOAAddress(1) require.Equal(t, expectedAddress, foa) }) }) @@ -300,28 +300,23 @@ func TestHandler_BridgedAccount(t *testing.T) { testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { - _, handler := SetupHandler(t, backend, rootAddr) + handler := SetupHandler(t, backend, rootAddr) foa := handler.AccountByAddress(handler.AllocateAddress(), true) require.NotNil(t, foa) - zeroBalance, err := types.NewBalanceFromAttoFlow(big.NewInt(0)) - require.NoError(t, err) + zeroBalance := types.NewBalance(big.NewInt(0)) require.Equal(t, zeroBalance, foa.Balance()) - balance, err := types.NewBalanceFromAttoFlow(types.OneFlowInAttoFlow) - require.NoError(t, err) + balance := types.OneFlowBalance vault := types.NewFlowTokenVault(balance) foa.Deposit(vault) - require.NoError(t, err) require.Equal(t, balance, foa.Balance()) v := foa.Withdraw(balance) - require.NoError(t, err) require.Equal(t, balance, v.Balance()) - require.NoError(t, err) require.Equal(t, zeroBalance, foa.Balance()) events := backend.Events() @@ -338,7 +333,7 @@ func TestHandler_BridgedAccount(t *testing.T) { // transaction event event = events[2] assert.Equal(t, event.Type, types.EventTypeTransactionExecuted) - _, err = jsoncdc.Decode(nil, event.Payload) + _, err := jsoncdc.Decode(nil, event.Payload) require.NoError(t, err) // TODO: decode encoded tx and check for the amount and value // assert.Equal(t, foa.Address(), ret.Address) @@ -352,13 +347,20 @@ func TestHandler_BridgedAccount(t *testing.T) { computationUsed, err := backend.ComputationUsed() require.NoError(t, err) require.Equal(t, types.DefaultDirectCallBaseGasUsage*2, computationUsed) + + // Withdraw with invalid balance + assertPanic(t, types.IsWithdrawBalanceRoundingError, func() { + // deposit some money + foa.Deposit(vault) + // then withdraw invalid balance + foa.Withdraw(types.NewBalance(big.NewInt(1))) + }) + }) }) }) t.Run("test withdraw (unhappy case)", func(t *testing.T) { - t.Parallel() - testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { testutils.RunWithEOATestAccount(t, backend, rootAddr, func(eoa *testutils.EOATestAccount) { @@ -375,7 +377,7 @@ func TestHandler_BridgedAccount(t *testing.T) { handler := handler.NewContractHandler(flowTokenAddress, bs, aa, backend, em) account := handler.AccountByAddress(testutils.RandomAddress(t), false) - account.Withdraw(types.Balance(1)) + account.Withdraw(types.NewBalanceFromUFix64(1)) }) // test insufficient total supply @@ -389,7 +391,7 @@ func TestHandler_BridgedAccount(t *testing.T) { handler := handler.NewContractHandler(flowTokenAddress, bs, aa, backend, em) account := handler.AccountByAddress(testutils.RandomAddress(t), true) - account.Withdraw(types.Balance(1)) + account.Withdraw(types.NewBalanceFromUFix64(1)) }) // test non fatal error of emulator @@ -403,7 +405,7 @@ func TestHandler_BridgedAccount(t *testing.T) { handler := handler.NewContractHandler(flowTokenAddress, bs, aa, backend, em) account := handler.AccountByAddress(testutils.RandomAddress(t), true) - account.Withdraw(types.Balance(0)) + account.Withdraw(types.NewBalanceFromUFix64(0)) }) // test fatal error of emulator @@ -417,7 +419,7 @@ func TestHandler_BridgedAccount(t *testing.T) { handler := handler.NewContractHandler(flowTokenAddress, bs, aa, backend, em) account := handler.AccountByAddress(testutils.RandomAddress(t), true) - account.Withdraw(types.Balance(0)) + account.Withdraw(types.NewBalanceFromUFix64(0)) }) }) }) @@ -447,7 +449,7 @@ func TestHandler_BridgedAccount(t *testing.T) { handler := handler.NewContractHandler(flowTokenAddress, bs, aa, backend, em) account := handler.AccountByAddress(testutils.RandomAddress(t), true) - account.Deposit(types.NewFlowTokenVault(1)) + account.Deposit(types.NewFlowTokenVault(types.NewBalanceFromUFix64(1))) }) // test fatal error of emulator @@ -461,7 +463,7 @@ func TestHandler_BridgedAccount(t *testing.T) { handler := handler.NewContractHandler(flowTokenAddress, bs, aa, backend, em) account := handler.AccountByAddress(testutils.RandomAddress(t), true) - account.Deposit(types.NewFlowTokenVault(1)) + account.Deposit(types.NewFlowTokenVault(types.NewBalanceFromUFix64(1))) }) }) }) @@ -474,19 +476,17 @@ func TestHandler_BridgedAccount(t *testing.T) { // TODO update this test with events, gas metering, etc testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { - _, handler := SetupHandler(t, backend, rootAddr) + handler := SetupHandler(t, backend, rootAddr) foa := handler.AccountByAddress(handler.AllocateAddress(), true) require.NotNil(t, foa) // deposit 10000 flow - orgBalance, err := types.NewBalanceFromAttoFlow(new(big.Int).Mul(big.NewInt(1e18), big.NewInt(10000))) - require.NoError(t, err) - vault := types.NewFlowTokenVault(orgBalance) + vault := types.NewFlowTokenVault(types.MakeABalanceInFlow(10000)) foa.Deposit(vault) testContract := testutils.GetStorageTestContract(t) - addr := foa.Deploy(testContract.ByteCode, math.MaxUint64, types.Balance(0)) + addr := foa.Deploy(testContract.ByteCode, math.MaxUint64, types.NewBalanceFromUFix64(0)) require.NotNil(t, addr) num := big.NewInt(22) @@ -495,19 +495,77 @@ func TestHandler_BridgedAccount(t *testing.T) { addr, testContract.MakeCallData(t, "store", num), math.MaxUint64, - types.Balance(0)) + types.NewBalanceFromUFix64(0)) ret := foa.Call( addr, testContract.MakeCallData(t, "retrieve"), math.MaxUint64, - types.Balance(0)) + types.NewBalanceFromUFix64(0)) require.Equal(t, num, new(big.Int).SetBytes(ret)) }) }) }) + t.Run("test call to cadence arch", func(t *testing.T) { + t.Parallel() + + testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { + blockHeight := uint64(123) + backend.GetCurrentBlockHeightFunc = func() (uint64, error) { + return blockHeight, nil + } + testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { + h := SetupHandler(t, backend, rootAddr) + + foa := h.AccountByAddress(h.AllocateAddress(), true) + require.NotNil(t, foa) + + vault := types.NewFlowTokenVault(types.MakeABalanceInFlow(10000)) + foa.Deposit(vault) + + arch := handler.MakePrecompileAddress(1) + + ret := foa.Call(arch, precompiles.FlowBlockHeightFuncSig[:], math.MaxUint64, types.NewBalanceFromUFix64(0)) + require.Equal(t, big.NewInt(int64(blockHeight)), new(big.Int).SetBytes(ret)) + }) + }) + }) + + t.Run("test block.random call (with integrated emulator)", func(t *testing.T) { + t.Parallel() + + testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { + random := testutils.RandomCommonHash(t) + backend.ReadRandomFunc = func(buffer []byte) error { + copy(buffer, random.Bytes()) + return nil + } + testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { + handler := SetupHandler(t, backend, rootAddr) + + foa := handler.AccountByAddress(handler.AllocateAddress(), true) + require.NotNil(t, foa) + + vault := types.NewFlowTokenVault(types.MakeABalanceInFlow(100)) + foa.Deposit(vault) + + testContract := testutils.GetStorageTestContract(t) + addr := foa.Deploy(testContract.ByteCode, math.MaxUint64, types.EmptyBalance) + require.NotNil(t, addr) + + ret := foa.Call( + addr, + testContract.MakeCallData(t, "random"), + math.MaxUint64, + types.EmptyBalance) + + require.Equal(t, random.Bytes(), []byte(ret)) + }) + }) + }) + // TODO add test with test emulator for unhappy cases (emulator) } @@ -533,18 +591,15 @@ func assertPanic(t *testing.T, check checkError, f func()) { f() } -func SetupHandler(t testing.TB, backend types.Backend, rootAddr flow.Address) (*database.Database, *handler.ContractHandler) { +func SetupHandler(t testing.TB, backend types.Backend, rootAddr flow.Address) *handler.ContractHandler { bs, err := handler.NewBlockStore(backend, rootAddr) require.NoError(t, err) aa, err := handler.NewAddressAllocator(backend, rootAddr) require.NoError(t, err) - db, err := database.NewDatabase(backend, rootAddr) - require.NoError(t, err) - - emulator := emulator.NewEmulator(db) + emulator := emulator.NewEmulator(backend, rootAddr) handler := handler.NewContractHandler(flowTokenAddress, bs, aa, backend, emulator) - return db, handler + return handler } diff --git a/fvm/evm/precompiles/arch.go b/fvm/evm/precompiles/arch.go new file mode 100644 index 00000000000..ca6477e311a --- /dev/null +++ b/fvm/evm/precompiles/arch.go @@ -0,0 +1,56 @@ +package precompiles + +import ( + "encoding/binary" + "fmt" + + gethCommon "github.com/ethereum/go-ethereum/common" + + "github.com/onflow/flow-go/fvm/evm/types" +) + +var ( + FlowBlockHeightFuncSig = ComputeFunctionSelector("flowBlockHeight", nil) + // TODO update me with a higher value if needed + FlowBlockHeightFixedGas = uint64(1) +) + +// ArchContract return a procompile for the Cadence Arch contract +// which facilitates access of Flow EVM environment into the Cadence environment. +// for more details see this Flip 223. +func ArchContract( + address types.Address, + heightProvider func() (uint64, error), +) types.Precompile { + return MultiFunctionPrecompileContract( + address, + []Function{&flowBlockHeightFunction{heightProvider}}, + ) +} + +type flowBlockHeightFunction struct { + flowBlockHeightLookUp func() (uint64, error) +} + +func (c *flowBlockHeightFunction) FunctionSelector() FunctionSelector { + return FlowBlockHeightFuncSig +} + +func (c *flowBlockHeightFunction) ComputeGas(input []byte) uint64 { + return FlowBlockHeightFixedGas +} + +func (c *flowBlockHeightFunction) Run(input []byte) ([]byte, error) { + if len(input) > 0 { + return nil, fmt.Errorf("unexpected input is provided") + } + bh, err := c.flowBlockHeightLookUp() + if err != nil { + return nil, err + } + encoded := make([]byte, 8) + binary.BigEndian.PutUint64(encoded, bh) + // the EVM works natively in 256-bit words, + // we left pad to that size to prevent extra gas consumtion for masking. + return gethCommon.LeftPadBytes(encoded, 32), nil +} diff --git a/fvm/evm/precompiles/arch_test.go b/fvm/evm/precompiles/arch_test.go new file mode 100644 index 00000000000..9f0cf186da7 --- /dev/null +++ b/fvm/evm/precompiles/arch_test.go @@ -0,0 +1,35 @@ +package precompiles_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/onflow/flow-go/fvm/evm/precompiles" + "github.com/onflow/flow-go/fvm/evm/testutils" +) + +func TestArchContract(t *testing.T) { + address := testutils.RandomAddress(t) + + height := uint64(12) + pc := precompiles.ArchContract( + address, + func() (uint64, error) { + return height, nil + }, + ) + + input := precompiles.FlowBlockHeightFuncSig.Bytes() + require.Equal(t, address, pc.Address()) + require.Equal(t, precompiles.FlowBlockHeightFixedGas, pc.RequiredGas(input)) + ret, err := pc.Run(input) + require.NoError(t, err) + + expected := make([]byte, 32) + expected[31] = 12 + require.Equal(t, expected, ret) + + _, err = pc.Run([]byte{1, 2, 3}) + require.Error(t, err) +} diff --git a/fvm/evm/precompiles/precompile.go b/fvm/evm/precompiles/precompile.go new file mode 100644 index 00000000000..85f96cd59c1 --- /dev/null +++ b/fvm/evm/precompiles/precompile.go @@ -0,0 +1,75 @@ +package precompiles + +import ( + "errors" + + "github.com/onflow/flow-go/fvm/evm/types" +) + +// InvalidMethodCallGasUsage captures how much gas we charge for invalid method call +const InvalidMethodCallGasUsage = uint64(1) + +// ErrInvalidMethodCall is returned when the method is not available on the contract +var ErrInvalidMethodCall = errors.New("invalid method call") + +// Function is an interface for a function in a multi-function precompile contract +type Function interface { + // FunctionSelector returns the function selector bytes for this function + FunctionSelector() FunctionSelector + + // ComputeGas computes the gas needed for the given input + ComputeGas(input []byte) uint64 + + // Run runs the function on the given data + Run(input []byte) ([]byte, error) +} + +// MultiFunctionPrecompileContract constructs a multi-function precompile smart contract +func MultiFunctionPrecompileContract( + address types.Address, + functions []Function, +) types.Precompile { + pc := &precompile{ + functions: make(map[FunctionSelector]Function), + address: address, + } + for _, f := range functions { + pc.functions[f.FunctionSelector()] = f + } + return pc +} + +type precompile struct { + address types.Address + functions map[FunctionSelector]Function +} + +func (p *precompile) Address() types.Address { + return p.address +} + +// RequiredGas calculates the contract gas use +func (p *precompile) RequiredGas(input []byte) uint64 { + if len(input) < FunctionSelectorLength { + return InvalidMethodCallGasUsage + } + sig, data := SplitFunctionSelector(input) + callable, found := p.functions[sig] + if !found { + return InvalidMethodCallGasUsage + } + return callable.ComputeGas(data) +} + +// Run runs the precompiled contract +func (p *precompile) Run(input []byte) ([]byte, error) { + if len(input) < FunctionSelectorLength { + return nil, ErrInvalidMethodCall + } + sig, data := SplitFunctionSelector(input) + callable, found := p.functions[sig] + if !found { + return nil, ErrInvalidMethodCall + } + return callable.Run(data) +} diff --git a/fvm/evm/precompiles/precompile_test.go b/fvm/evm/precompiles/precompile_test.go new file mode 100644 index 00000000000..654cc71c14f --- /dev/null +++ b/fvm/evm/precompiles/precompile_test.go @@ -0,0 +1,73 @@ +package precompiles_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/onflow/flow-go/fvm/evm/precompiles" + "github.com/onflow/flow-go/fvm/evm/testutils" +) + +func TestMutiFunctionContract(t *testing.T) { + t.Parallel() + + address := testutils.RandomAddress(t) + sig := precompiles.FunctionSelector{1, 2, 3, 4} + data := "data" + input := append(sig[:], data...) + gas := uint64(20) + output := []byte("output") + + pc := precompiles.MultiFunctionPrecompileContract(address, []precompiles.Function{ + &mockedFunction{ + FunctionSelectorFunc: func() precompiles.FunctionSelector { + return sig + }, + ComputeGasFunc: func(inp []byte) uint64 { + require.Equal(t, []byte(data), inp) + return gas + }, + RunFunc: func(inp []byte) ([]byte, error) { + require.Equal(t, []byte(data), inp) + return output, nil + }, + }}) + + require.Equal(t, address, pc.Address()) + require.Equal(t, gas, pc.RequiredGas(input)) + ret, err := pc.Run(input) + require.NoError(t, err) + require.Equal(t, output, ret) + + input2 := []byte("non existing signature and data") + _, err = pc.Run(input2) + require.Equal(t, precompiles.ErrInvalidMethodCall, err) +} + +type mockedFunction struct { + FunctionSelectorFunc func() precompiles.FunctionSelector + ComputeGasFunc func(input []byte) uint64 + RunFunc func(input []byte) ([]byte, error) +} + +func (mf *mockedFunction) FunctionSelector() precompiles.FunctionSelector { + if mf.FunctionSelectorFunc == nil { + panic("method not set for mocked function") + } + return mf.FunctionSelectorFunc() +} + +func (mf *mockedFunction) ComputeGas(input []byte) uint64 { + if mf.ComputeGasFunc == nil { + panic("method not set for mocked function") + } + return mf.ComputeGasFunc(input) +} + +func (mf *mockedFunction) Run(input []byte) ([]byte, error) { + if mf.RunFunc == nil { + panic("method not set for mocked function") + } + return mf.RunFunc(input) +} diff --git a/fvm/evm/precompiles/signature.go b/fvm/evm/precompiles/signature.go new file mode 100644 index 00000000000..a62c8f5b9ac --- /dev/null +++ b/fvm/evm/precompiles/signature.go @@ -0,0 +1,35 @@ +package precompiles + +import ( + "fmt" + "strings" + + gethCrypto "github.com/ethereum/go-ethereum/crypto" +) + +const FunctionSelectorLength = 4 + +// This is derived as the first 4 bytes of the Keccak hash of the ASCII form of the signature of the method +type FunctionSelector [FunctionSelectorLength]byte + +func (fs FunctionSelector) Bytes() []byte { + return fs[:] +} + +// ComputeFunctionSelector computes the function selector +// given the canonical name of function and args. +// for example the canonical format for int is int256 +func ComputeFunctionSelector(name string, args []string) FunctionSelector { + var sig FunctionSelector + input := fmt.Sprintf("%v(%v)", name, strings.Join(args, ",")) + copy(sig[0:FunctionSelectorLength], gethCrypto.Keccak256([]byte(input))[:FunctionSelectorLength]) + return sig +} + +// SplitFunctionSelector splits the function signature from input data and +// returns the rest of the data +func SplitFunctionSelector(input []byte) (FunctionSelector, []byte) { + var funcSig FunctionSelector + copy(funcSig[:], input[0:FunctionSelectorLength]) + return funcSig, input[FunctionSelectorLength:] +} diff --git a/fvm/evm/precompiles/signature_test.go b/fvm/evm/precompiles/signature_test.go new file mode 100644 index 00000000000..d6f36b9fffe --- /dev/null +++ b/fvm/evm/precompiles/signature_test.go @@ -0,0 +1,27 @@ +package precompiles_test + +import ( + "testing" + + gethCrypto "github.com/ethereum/go-ethereum/crypto" + "github.com/stretchr/testify/require" + + "github.com/onflow/flow-go/fvm/evm/precompiles" +) + +func TestFunctionSelector(t *testing.T) { + t.Parallel() + + expected := gethCrypto.Keccak256([]byte("test()"))[:4] + require.Equal(t, expected, precompiles.ComputeFunctionSelector("test", nil).Bytes()) + + expected = gethCrypto.Keccak256([]byte("test(uint32,uint16)"))[:precompiles.FunctionSelectorLength] + require.Equal(t, expected, + precompiles.ComputeFunctionSelector("test", []string{"uint32", "uint16"}).Bytes()) + + selector := []byte{1, 2, 3, 4} + data := []byte{5, 6, 7, 8} + retSelector, retData := precompiles.SplitFunctionSelector(append(selector, data...)) + require.Equal(t, selector, retSelector[:]) + require.Equal(t, data, retData) +} diff --git a/fvm/evm/stdlib/abiOnlyContract.cdc b/fvm/evm/stdlib/abiOnlyContract.cdc new file mode 100644 index 00000000000..45378726215 --- /dev/null +++ b/fvm/evm/stdlib/abiOnlyContract.cdc @@ -0,0 +1,60 @@ +access(all) +contract EVM { + + /// EVMAddress is an EVM-compatible address + access(all) + struct EVMAddress { + + /// Bytes of the address + access(all) + let bytes: [UInt8; 20] + + /// Constructs a new EVM address from the given byte representation + init(bytes: [UInt8; 20]) { + self.bytes = bytes + } + + } + + access(all) + fun encodeABI(_ values: [AnyStruct]): [UInt8] { + return InternalEVM.encodeABI(values) + } + + access(all) + fun decodeABI(types: [Type], data: [UInt8]): [AnyStruct] { + return InternalEVM.decodeABI(types: types, data: data) + } + + access(all) + fun encodeABIWithSignature( + _ signature: String, + _ values: [AnyStruct] + ): [UInt8] { + let methodID = HashAlgorithm.KECCAK_256.hash( + signature.utf8 + ).slice(from: 0, upTo: 4) + let arguments = InternalEVM.encodeABI(values) + + return methodID.concat(arguments) + } + + access(all) + fun decodeABIWithSignature( + _ signature: String, + types: [Type], + data: [UInt8] + ): [AnyStruct] { + let methodID = HashAlgorithm.KECCAK_256.hash( + signature.utf8 + ).slice(from: 0, upTo: 4) + + for byte in methodID { + if byte != data.removeFirst() { + panic("signature mismatch") + } + } + + return InternalEVM.decodeABI(types: types, data: data) + } +} diff --git a/fvm/evm/stdlib/contract.cdc b/fvm/evm/stdlib/contract.cdc index ab3cf4c06c6..3151542131b 100644 --- a/fvm/evm/stdlib/contract.cdc +++ b/fvm/evm/stdlib/contract.cdc @@ -16,15 +16,6 @@ contract EVM { self.bytes = bytes } - /// Deposits the given vault into the EVM account with the given address - access(all) - fun deposit(from: @FlowToken.Vault) { - InternalEVM.deposit( - from: <-from, - to: self.bytes - ) - } - /// Balance of the address access(all) fun balance(): Balance { @@ -81,7 +72,10 @@ contract EVM { /// Deposits the given vault into the bridged account's balance access(all) fun deposit(from: @FlowToken.Vault) { - self.address().deposit(from: <-from) + InternalEVM.deposit( + from: <-from, + to: self.addressBytes + ) } /// Withdraws the balance from the bridged account's balance @@ -147,4 +141,46 @@ contract EVM { fun run(tx: [UInt8], coinbase: EVMAddress) { InternalEVM.run(tx: tx, coinbase: coinbase.bytes) } + + access(all) + fun encodeABI(_ values: [AnyStruct]): [UInt8] { + return InternalEVM.encodeABI(values) + } + + access(all) + fun decodeABI(types: [Type], data: [UInt8]): [AnyStruct] { + return InternalEVM.decodeABI(types: types, data: data) + } + + access(all) + fun encodeABIWithSignature( + _ signature: String, + _ values: [AnyStruct] + ): [UInt8] { + let methodID = HashAlgorithm.KECCAK_256.hash( + signature.utf8 + ).slice(from: 0, upTo: 4) + let arguments = InternalEVM.encodeABI(values) + + return methodID.concat(arguments) + } + + access(all) + fun decodeABIWithSignature( + _ signature: String, + types: [Type], + data: [UInt8] + ): [AnyStruct] { + let methodID = HashAlgorithm.KECCAK_256.hash( + signature.utf8 + ).slice(from: 0, upTo: 4) + + for byte in methodID { + if byte != data.removeFirst() { + panic("signature mismatch") + } + } + + return InternalEVM.decodeABI(types: types, data: data) + } } diff --git a/fvm/evm/stdlib/contract.go b/fvm/evm/stdlib/contract.go index 22f18fab434..bd19dab6a80 100644 --- a/fvm/evm/stdlib/contract.go +++ b/fvm/evm/stdlib/contract.go @@ -3,8 +3,14 @@ package stdlib import ( _ "embed" "fmt" + "math" + "math/big" + "reflect" "regexp" + "strings" + gethABI "github.com/ethereum/go-ethereum/accounts/abi" + gethCommon "github.com/ethereum/go-ethereum/common" "github.com/onflow/cadence" "github.com/onflow/cadence/runtime" "github.com/onflow/cadence/runtime/common" @@ -13,6 +19,7 @@ import ( "github.com/onflow/cadence/runtime/sema" "github.com/onflow/cadence/runtime/stdlib" + "github.com/onflow/flow-go/fvm/environment" "github.com/onflow/flow-go/fvm/evm/types" "github.com/onflow/flow-go/model/flow" ) @@ -20,9 +27,16 @@ import ( //go:embed contract.cdc var contractCode string +//go:embed abiOnlyContract.cdc +var abiOnlyContractCode string + var flowTokenImportPattern = regexp.MustCompile(`^import "FlowToken"\n`) -func ContractCode(flowTokenAddress flow.Address) []byte { +func ContractCode(flowTokenAddress flow.Address, evmAbiOnly bool) []byte { + if evmAbiOnly { + return []byte(abiOnlyContractCode) + } + return []byte(flowTokenImportPattern.ReplaceAllString( contractCode, fmt.Sprintf("import FlowToken from %s", flowTokenAddress.HexWithPrefix()), @@ -30,6 +44,10 @@ func ContractCode(flowTokenAddress flow.Address) []byte { } const ContractName = "EVM" +const evmAddressTypeBytesFieldName = "bytes" +const evmAddressTypeQualifiedIdentifier = "EVM.EVMAddress" + +const abiEncodingByteSize = 32 var EVMTransactionBytesCadenceType = cadence.NewVariableSizedArrayType(cadence.TheUInt8Type) var evmTransactionBytesType = sema.NewVariableSizedType(nil, sema.UInt8Type) @@ -38,6 +56,863 @@ var evmAddressBytesType = sema.NewConstantSizedType(nil, sema.UInt8Type, types.A var evmAddressBytesStaticType = interpreter.ConvertSemaArrayTypeToStaticArrayType(nil, evmAddressBytesType) var EVMAddressBytesCadenceType = cadence.NewConstantSizedArrayType(types.AddressLength, cadence.TheUInt8Type) +// abiEncodingError +type abiEncodingError struct { + Type interpreter.StaticType +} + +var _ errors.UserError = abiEncodingError{} + +func (abiEncodingError) IsUserError() {} + +func (e abiEncodingError) Error() string { + var b strings.Builder + b.WriteString("failed to ABI encode value") + + ty := e.Type + if ty != nil { + b.WriteString(" of type ") + b.WriteString(ty.String()) + } + + return b.String() +} + +// abiDecodingError +type abiDecodingError struct { + Type interpreter.StaticType + Message string +} + +var _ errors.UserError = abiDecodingError{} + +func (abiDecodingError) IsUserError() {} + +func (e abiDecodingError) Error() string { + var b strings.Builder + b.WriteString("failed to ABI decode data") + + ty := e.Type + if ty != nil { + b.WriteString(" with type ") + b.WriteString(ty.String()) + } + + message := e.Message + if message != "" { + b.WriteString(": ") + b.WriteString(message) + } + + return b.String() +} + +func reportABIEncodingComputation( + inter *interpreter.Interpreter, + values *interpreter.ArrayValue, + evmAddressTypeID common.TypeID, + reportComputation func(intensity uint), +) { + values.Iterate(inter, func(element interpreter.Value) (resume bool) { + switch value := element.(type) { + case *interpreter.StringValue: + // Dynamic variables, such as strings, are encoded + // in 2+ chunks of 32 bytes. The first chunk contains + // the index where information for the string begin, + // the second chunk contains the number of bytes the + // string occupies, and the third chunk contains the + // value of the string itself. + computation := uint(2 * abiEncodingByteSize) + stringLength := len(value.Str) + chunks := math.Ceil(float64(stringLength) / float64(abiEncodingByteSize)) + computation += uint(chunks * abiEncodingByteSize) + reportComputation(computation) + + case interpreter.BoolValue, + interpreter.UInt8Value, + interpreter.UInt16Value, + interpreter.UInt32Value, + interpreter.UInt64Value, + interpreter.UInt128Value, + interpreter.UInt256Value, + interpreter.Int8Value, + interpreter.Int16Value, + interpreter.Int32Value, + interpreter.Int64Value, + interpreter.Int128Value, + interpreter.Int256Value: + + // Numeric and bool variables are also static variables + // with a fixed size of 32 bytes. + reportComputation(abiEncodingByteSize) + + case *interpreter.CompositeValue: + if value.TypeID() == evmAddressTypeID { + // EVM addresses are static variables with a fixed + // size of 32 bytes. + reportComputation(abiEncodingByteSize) + } else { + panic(abiEncodingError{ + Type: value.StaticType(inter), + }) + } + case *interpreter.ArrayValue: + // Dynamic variables, such as arrays & slices, are encoded + // in 2+ chunks of 32 bytes. The first chunk contains + // the index where information for the array begin, + // the second chunk contains the number of bytes the + // array occupies, and the third chunk contains the + // values of the array itself. + computation := uint(2 * abiEncodingByteSize) + reportComputation(computation) + reportABIEncodingComputation(inter, value, evmAddressTypeID, reportComputation) + + default: + panic(abiEncodingError{ + Type: element.StaticType(inter), + }) + } + + // continue iteration + return true + }) +} + +// EVM.encodeABI + +const internalEVMTypeEncodeABIFunctionName = "encodeABI" + +var internalEVMTypeEncodeABIFunctionType = &sema.FunctionType{ + Parameters: []sema.Parameter{ + { + Label: sema.ArgumentLabelNotRequired, + Identifier: "values", + TypeAnnotation: sema.NewTypeAnnotation( + sema.NewVariableSizedType(nil, sema.AnyStructType), + ), + }, + }, + ReturnTypeAnnotation: sema.NewTypeAnnotation(sema.ByteArrayType), +} + +func newInternalEVMTypeEncodeABIFunction( + gauge common.MemoryGauge, + location common.AddressLocation, +) *interpreter.HostFunctionValue { + + evmAddressTypeID := location.TypeID(gauge, evmAddressTypeQualifiedIdentifier) + + return interpreter.NewHostFunctionValue( + gauge, + internalEVMTypeEncodeABIFunctionType, + func(invocation interpreter.Invocation) interpreter.Value { + inter := invocation.Interpreter + locationRange := invocation.LocationRange + + // Get `values` argument + + valuesArray, ok := invocation.Arguments[0].(*interpreter.ArrayValue) + if !ok { + panic(errors.NewUnreachableError()) + } + + reportABIEncodingComputation( + inter, + valuesArray, + evmAddressTypeID, + func(intensity uint) { + inter.ReportComputation(environment.ComputationKindEVMEncodeABI, intensity) + }, + ) + + size := valuesArray.Count() + + values := make([]any, 0, size) + arguments := make(gethABI.Arguments, 0, size) + + valuesArray.Iterate(inter, func(element interpreter.Value) (resume bool) { + value, ty, err := encodeABI( + inter, + locationRange, + element, + element.StaticType(inter), + evmAddressTypeID, + ) + if err != nil { + panic(err) + } + + values = append(values, value) + arguments = append(arguments, gethABI.Argument{Type: ty}) + + // continue iteration + return true + }) + + encodedValues, err := arguments.Pack(values...) + if err != nil { + panic(abiEncodingError{}) + } + + return interpreter.ByteSliceToByteArrayValue(inter, encodedValues) + }, + ) +} + +var gethTypeString = gethABI.Type{T: gethABI.StringTy} +var gethTypeBool = gethABI.Type{T: gethABI.BoolTy} +var gethTypeUint8 = gethABI.Type{T: gethABI.UintTy, Size: 8} +var gethTypeUint16 = gethABI.Type{T: gethABI.UintTy, Size: 16} +var gethTypeUint32 = gethABI.Type{T: gethABI.UintTy, Size: 32} +var gethTypeUint64 = gethABI.Type{T: gethABI.UintTy, Size: 64} +var gethTypeUint128 = gethABI.Type{T: gethABI.UintTy, Size: 128} +var gethTypeUint256 = gethABI.Type{T: gethABI.UintTy, Size: 256} +var gethTypeInt8 = gethABI.Type{T: gethABI.IntTy, Size: 8} +var gethTypeInt16 = gethABI.Type{T: gethABI.IntTy, Size: 16} +var gethTypeInt32 = gethABI.Type{T: gethABI.IntTy, Size: 32} +var gethTypeInt64 = gethABI.Type{T: gethABI.IntTy, Size: 64} +var gethTypeInt128 = gethABI.Type{T: gethABI.IntTy, Size: 128} +var gethTypeInt256 = gethABI.Type{T: gethABI.IntTy, Size: 256} +var gethTypeAddress = gethABI.Type{Size: 20, T: gethABI.AddressTy} + +func gethABIType(staticType interpreter.StaticType, evmAddressTypeID common.TypeID) (gethABI.Type, bool) { + switch staticType { + case interpreter.PrimitiveStaticTypeString: + return gethTypeString, true + case interpreter.PrimitiveStaticTypeBool: + return gethTypeBool, true + case interpreter.PrimitiveStaticTypeUInt8: + return gethTypeUint8, true + case interpreter.PrimitiveStaticTypeUInt16: + return gethTypeUint16, true + case interpreter.PrimitiveStaticTypeUInt32: + return gethTypeUint32, true + case interpreter.PrimitiveStaticTypeUInt64: + return gethTypeUint64, true + case interpreter.PrimitiveStaticTypeUInt128: + return gethTypeUint128, true + case interpreter.PrimitiveStaticTypeUInt256: + return gethTypeUint256, true + case interpreter.PrimitiveStaticTypeInt8: + return gethTypeInt8, true + case interpreter.PrimitiveStaticTypeInt16: + return gethTypeInt16, true + case interpreter.PrimitiveStaticTypeInt32: + return gethTypeInt32, true + case interpreter.PrimitiveStaticTypeInt64: + return gethTypeInt64, true + case interpreter.PrimitiveStaticTypeInt128: + return gethTypeInt128, true + case interpreter.PrimitiveStaticTypeInt256: + return gethTypeInt256, true + case interpreter.PrimitiveStaticTypeAddress: + return gethTypeAddress, true + } + + switch staticType := staticType.(type) { + case interpreter.CompositeStaticType: + if staticType.TypeID != evmAddressTypeID { + break + } + + return gethTypeAddress, true + + case interpreter.ConstantSizedStaticType: + elementGethABIType, ok := gethABIType( + staticType.ElementType(), + evmAddressTypeID, + ) + if !ok { + break + } + + return gethABI.Type{ + T: gethABI.ArrayTy, + Elem: &elementGethABIType, + Size: int(staticType.Size), + }, true + + case interpreter.VariableSizedStaticType: + elementGethABIType, ok := gethABIType( + staticType.ElementType(), + evmAddressTypeID, + ) + if !ok { + break + } + + return gethABI.Type{ + T: gethABI.SliceTy, + Elem: &elementGethABIType, + }, true + + } + + return gethABI.Type{}, false +} + +func goType( + staticType interpreter.StaticType, + evmAddressTypeID common.TypeID, +) (reflect.Type, bool) { + switch staticType { + case interpreter.PrimitiveStaticTypeString: + return reflect.TypeOf(""), true + case interpreter.PrimitiveStaticTypeBool: + return reflect.TypeOf(true), true + case interpreter.PrimitiveStaticTypeUInt8: + return reflect.TypeOf(uint8(0)), true + case interpreter.PrimitiveStaticTypeUInt16: + return reflect.TypeOf(uint16(0)), true + case interpreter.PrimitiveStaticTypeUInt32: + return reflect.TypeOf(uint32(0)), true + case interpreter.PrimitiveStaticTypeUInt64: + return reflect.TypeOf(uint64(0)), true + case interpreter.PrimitiveStaticTypeUInt128: + return reflect.TypeOf((*big.Int)(nil)), true + case interpreter.PrimitiveStaticTypeUInt256: + return reflect.TypeOf((*big.Int)(nil)), true + case interpreter.PrimitiveStaticTypeInt8: + return reflect.TypeOf(int8(0)), true + case interpreter.PrimitiveStaticTypeInt16: + return reflect.TypeOf(int16(0)), true + case interpreter.PrimitiveStaticTypeInt32: + return reflect.TypeOf(int32(0)), true + case interpreter.PrimitiveStaticTypeInt64: + return reflect.TypeOf(int64(0)), true + case interpreter.PrimitiveStaticTypeInt128: + return reflect.TypeOf((*big.Int)(nil)), true + case interpreter.PrimitiveStaticTypeInt256: + return reflect.TypeOf((*big.Int)(nil)), true + case interpreter.PrimitiveStaticTypeAddress: + return reflect.TypeOf((*big.Int)(nil)), true + } + + switch staticType := staticType.(type) { + case interpreter.ConstantSizedStaticType: + elementType, ok := goType(staticType.ElementType(), evmAddressTypeID) + if !ok { + break + } + + return reflect.ArrayOf(int(staticType.Size), elementType), true + + case interpreter.VariableSizedStaticType: + elementType, ok := goType(staticType.ElementType(), evmAddressTypeID) + if !ok { + break + } + + return reflect.SliceOf(elementType), true + } + + if staticType.ID() == evmAddressTypeID { + return reflect.TypeOf(gethCommon.Address{}), true + } + + return nil, false +} + +func encodeABI( + inter *interpreter.Interpreter, + locationRange interpreter.LocationRange, + value interpreter.Value, + staticType interpreter.StaticType, + evmAddressTypeID common.TypeID, +) ( + any, + gethABI.Type, + error, +) { + + switch value := value.(type) { + case *interpreter.StringValue: + if staticType == interpreter.PrimitiveStaticTypeString { + return value.Str, gethTypeString, nil + } + + case interpreter.BoolValue: + if staticType == interpreter.PrimitiveStaticTypeBool { + return bool(value), gethTypeBool, nil + } + + case interpreter.UInt8Value: + if staticType == interpreter.PrimitiveStaticTypeUInt8 { + return uint8(value), gethTypeUint8, nil + } + + case interpreter.UInt16Value: + if staticType == interpreter.PrimitiveStaticTypeUInt16 { + return uint16(value), gethTypeUint16, nil + } + + case interpreter.UInt32Value: + if staticType == interpreter.PrimitiveStaticTypeUInt32 { + return uint32(value), gethTypeUint32, nil + } + + case interpreter.UInt64Value: + if staticType == interpreter.PrimitiveStaticTypeUInt64 { + return uint64(value), gethTypeUint64, nil + } + + case interpreter.UInt128Value: + if staticType == interpreter.PrimitiveStaticTypeUInt128 { + return value.BigInt, gethTypeUint128, nil + } + + case interpreter.UInt256Value: + if staticType == interpreter.PrimitiveStaticTypeUInt256 { + return value.BigInt, gethTypeUint256, nil + } + + case interpreter.Int8Value: + if staticType == interpreter.PrimitiveStaticTypeInt8 { + return int8(value), gethTypeInt8, nil + } + + case interpreter.Int16Value: + if staticType == interpreter.PrimitiveStaticTypeInt16 { + return int16(value), gethTypeInt16, nil + } + + case interpreter.Int32Value: + if staticType == interpreter.PrimitiveStaticTypeInt32 { + return int32(value), gethTypeInt32, nil + } + + case interpreter.Int64Value: + if staticType == interpreter.PrimitiveStaticTypeInt64 { + return int64(value), gethTypeInt64, nil + } + + case interpreter.Int128Value: + if staticType == interpreter.PrimitiveStaticTypeInt128 { + return value.BigInt, gethTypeInt128, nil + } + + case interpreter.Int256Value: + if staticType == interpreter.PrimitiveStaticTypeInt256 { + return value.BigInt, gethTypeInt256, nil + } + + case *interpreter.CompositeValue: + if value.TypeID() == evmAddressTypeID { + addressBytesArrayValue := value.GetMember(inter, locationRange, evmAddressTypeBytesFieldName) + bytes, err := interpreter.ByteArrayValueToByteSlice( + inter, + addressBytesArrayValue, + locationRange, + ) + if err != nil { + panic(err) + } + + return gethCommon.Address(bytes), gethTypeAddress, nil + } + + case *interpreter.ArrayValue: + arrayStaticType := value.Type + + arrayGethABIType, ok := gethABIType(arrayStaticType, evmAddressTypeID) + if !ok { + break + } + + elementStaticType := arrayStaticType.ElementType() + + elementGoType, ok := goType(elementStaticType, evmAddressTypeID) + if !ok { + break + } + + var result reflect.Value + + switch arrayStaticType := arrayStaticType.(type) { + case interpreter.ConstantSizedStaticType: + size := int(arrayStaticType.Size) + result = reflect.Indirect(reflect.New(reflect.ArrayOf(size, elementGoType))) + + case interpreter.VariableSizedStaticType: + size := value.Count() + result = reflect.MakeSlice(reflect.SliceOf(elementGoType), size, size) + } + + var index int + value.Iterate(inter, func(element interpreter.Value) (resume bool) { + + arrayElement, _, err := encodeABI( + inter, + locationRange, + element, + element.StaticType(inter), + evmAddressTypeID, + ) + if err != nil { + panic(err) + } + + result.Index(index).Set(reflect.ValueOf(arrayElement)) + + index++ + + // continue iteration + return true + }) + + return result.Interface(), arrayGethABIType, nil + } + + return nil, gethABI.Type{}, abiEncodingError{ + Type: value.StaticType(inter), + } +} + +// EVM.decodeABI + +const internalEVMTypeDecodeABIFunctionName = "decodeABI" + +var internalEVMTypeDecodeABIFunctionType = &sema.FunctionType{ + Parameters: []sema.Parameter{ + { + Identifier: "types", + TypeAnnotation: sema.NewTypeAnnotation( + sema.NewVariableSizedType(nil, sema.MetaType), + ), + }, + { + Label: "data", + TypeAnnotation: sema.NewTypeAnnotation(sema.ByteArrayType), + }, + }, + ReturnTypeAnnotation: sema.NewTypeAnnotation( + sema.NewVariableSizedType(nil, sema.AnyStructType), + ), +} + +func newInternalEVMTypeDecodeABIFunction( + gauge common.MemoryGauge, + location common.AddressLocation, +) *interpreter.HostFunctionValue { + evmAddressTypeID := location.TypeID(gauge, evmAddressTypeQualifiedIdentifier) + + return interpreter.NewHostFunctionValue( + gauge, + internalEVMTypeDecodeABIFunctionType, + func(invocation interpreter.Invocation) interpreter.Value { + inter := invocation.Interpreter + locationRange := invocation.LocationRange + + // Get `types` argument + + typesArray, ok := invocation.Arguments[0].(*interpreter.ArrayValue) + if !ok { + panic(errors.NewUnreachableError()) + } + + // Get `data` argument + + dataValue, ok := invocation.Arguments[1].(*interpreter.ArrayValue) + if !ok { + panic(errors.NewUnreachableError()) + } + + invocation.Interpreter.ReportComputation( + environment.ComputationKindEVMDecodeABI, + uint(dataValue.Count()), + ) + + data, err := interpreter.ByteArrayValueToByteSlice(inter, dataValue, locationRange) + if err != nil { + panic(err) + } + + var arguments gethABI.Arguments + typesArray.Iterate(inter, func(element interpreter.Value) (resume bool) { + typeValue, ok := element.(interpreter.TypeValue) + if !ok { + panic(errors.NewUnreachableError()) + } + + staticType := typeValue.Type + + gethABITy, ok := gethABIType(staticType, evmAddressTypeID) + if !ok { + panic(abiDecodingError{ + Type: staticType, + }) + } + + arguments = append( + arguments, + gethABI.Argument{ + Type: gethABITy, + }, + ) + + // continue iteration + return true + }) + + decodedValues, err := arguments.Unpack(data) + if err != nil { + panic(abiDecodingError{}) + } + + var index int + values := make([]interpreter.Value, 0, len(decodedValues)) + + typesArray.Iterate(inter, func(element interpreter.Value) (resume bool) { + typeValue, ok := element.(interpreter.TypeValue) + if !ok { + panic(errors.NewUnreachableError()) + } + + staticType := typeValue.Type + + value, err := decodeABI( + inter, + locationRange, + decodedValues[index], + staticType, + location, + evmAddressTypeID, + ) + if err != nil { + panic(err) + } + + index++ + + values = append(values, value) + + // continue iteration + return true + }) + + arrayType := interpreter.NewVariableSizedStaticType( + inter, + interpreter.NewPrimitiveStaticType( + inter, + interpreter.PrimitiveStaticTypeAnyStruct, + ), + ) + + return interpreter.NewArrayValue( + inter, + locationRange, + arrayType, + common.ZeroAddress, + values..., + ) + }, + ) +} + +func decodeABI( + inter *interpreter.Interpreter, + locationRange interpreter.LocationRange, + value any, + staticType interpreter.StaticType, + location common.AddressLocation, + evmAddressTypeID common.TypeID, +) ( + interpreter.Value, + error, +) { + + switch staticType { + case interpreter.PrimitiveStaticTypeString: + value, ok := value.(string) + if !ok { + break + } + return interpreter.NewStringValue( + inter, + common.NewStringMemoryUsage(len(value)), + func() string { + return value + }, + ), nil + + case interpreter.PrimitiveStaticTypeBool: + value, ok := value.(bool) + if !ok { + break + } + return interpreter.BoolValue(value), nil + + case interpreter.PrimitiveStaticTypeUInt8: + value, ok := value.(uint8) + if !ok { + break + } + return interpreter.NewUInt8Value(inter, func() uint8 { return value }), nil + + case interpreter.PrimitiveStaticTypeUInt16: + value, ok := value.(uint16) + if !ok { + break + } + return interpreter.NewUInt16Value(inter, func() uint16 { return value }), nil + + case interpreter.PrimitiveStaticTypeUInt32: + value, ok := value.(uint32) + if !ok { + break + } + return interpreter.NewUInt32Value(inter, func() uint32 { return value }), nil + + case interpreter.PrimitiveStaticTypeUInt64: + value, ok := value.(uint64) + if !ok { + break + } + return interpreter.NewUInt64Value(inter, func() uint64 { return value }), nil + + case interpreter.PrimitiveStaticTypeUInt128: + value, ok := value.(*big.Int) + if !ok { + break + } + return interpreter.NewUInt128ValueFromBigInt(inter, func() *big.Int { return value }), nil + + case interpreter.PrimitiveStaticTypeUInt256: + value, ok := value.(*big.Int) + if !ok { + break + } + return interpreter.NewUInt256ValueFromBigInt(inter, func() *big.Int { return value }), nil + + case interpreter.PrimitiveStaticTypeInt8: + value, ok := value.(int8) + if !ok { + break + } + return interpreter.NewInt8Value(inter, func() int8 { return value }), nil + + case interpreter.PrimitiveStaticTypeInt16: + value, ok := value.(int16) + if !ok { + break + } + return interpreter.NewInt16Value(inter, func() int16 { return value }), nil + + case interpreter.PrimitiveStaticTypeInt32: + value, ok := value.(int32) + if !ok { + break + } + return interpreter.NewInt32Value(inter, func() int32 { return value }), nil + + case interpreter.PrimitiveStaticTypeInt64: + value, ok := value.(int64) + if !ok { + break + } + return interpreter.NewInt64Value(inter, func() int64 { return value }), nil + + case interpreter.PrimitiveStaticTypeInt128: + value, ok := value.(*big.Int) + if !ok { + break + } + return interpreter.NewInt128ValueFromBigInt(inter, func() *big.Int { return value }), nil + + case interpreter.PrimitiveStaticTypeInt256: + value, ok := value.(*big.Int) + if !ok { + break + } + return interpreter.NewInt256ValueFromBigInt(inter, func() *big.Int { return value }), nil + } + + switch staticType := staticType.(type) { + case interpreter.ArrayStaticType: + array := reflect.ValueOf(value) + + elementStaticType := staticType.ElementType() + + size := array.Len() + + var index int + return interpreter.NewArrayValueWithIterator( + inter, + staticType, + common.ZeroAddress, + uint64(size), + func() interpreter.Value { + if index >= size { + return nil + } + + element := array.Index(index).Interface() + + result, err := decodeABI( + inter, + locationRange, + element, + elementStaticType, + location, + evmAddressTypeID, + ) + if err != nil { + panic(err) + } + + index++ + + return result + }, + ), nil + + case interpreter.CompositeStaticType: + if staticType.TypeID != evmAddressTypeID { + break + } + + addr, ok := value.(gethCommon.Address) + if !ok { + break + } + + var address types.Address + copy(address[:], addr.Bytes()) + return NewEVMAddress( + inter, + locationRange, + location, + address, + ), nil + } + + return nil, abiDecodingError{ + Type: staticType, + } +} + +func NewEVMAddress( + inter *interpreter.Interpreter, + locationRange interpreter.LocationRange, + location common.AddressLocation, + address types.Address, +) *interpreter.CompositeValue { + return interpreter.NewCompositeValue( + inter, + locationRange, + location, + evmAddressTypeQualifiedIdentifier, + common.CompositeKindStructure, + []interpreter.CompositeField{ + { + Name: evmAddressTypeBytesFieldName, + Value: EVMAddressToAddressBytesArrayValue(inter, address), + }, + }, + common.ZeroAddress, + ) +} + const internalEVMTypeRunFunctionName = "run" var internalEVMTypeRunFunctionType = &sema.FunctionType{ @@ -250,8 +1125,7 @@ func newInternalEVMTypeCallFunction( panic(errors.NewUnreachableError()) } - balance := types.Balance(balanceValue) - + balance := types.NewBalanceFromUFix64(cadence.UFix64(balanceValue)) // Call const isAuthorized = true @@ -329,7 +1203,7 @@ func newInternalEVMTypeDepositFunction( panic(errors.NewUnreachableError()) } - amount := types.Balance(amountValue) + amount := types.NewBalanceFromUFix64(cadence.UFix64(amountValue)) // Get to address @@ -396,7 +1270,12 @@ func newInternalEVMTypeBalanceFunction( const isAuthorized = false account := handler.AccountByAddress(address, isAuthorized) - return interpreter.UFix64Value(account.Balance()) + // TODO: return roundoff flag or handle it + ufix, _, err := types.ConvertBalanceToUFix64(account.Balance()) + if err != nil { + panic(err) + } + return interpreter.UFix64Value(ufix) }, ) } @@ -447,7 +1326,7 @@ func newInternalEVMTypeWithdrawFunction( panic(errors.NewUnreachableError()) } - amount := types.Balance(amountValue) + amount := types.NewBalanceFromUFix64(cadence.UFix64(amountValue)) // Withdraw @@ -455,6 +1334,12 @@ func newInternalEVMTypeWithdrawFunction( account := handler.AccountByAddress(fromAddress, isAuthorized) vault := account.Withdraw(amount) + // TODO: return rounded off flag or handle it ? + ufix, _, err := types.ConvertBalanceToUFix64(vault.Balance()) + if err != nil { + panic(err) + } + // TODO: improve: maybe call actual constructor return interpreter.NewCompositeValue( inter, @@ -466,7 +1351,7 @@ func newInternalEVMTypeWithdrawFunction( { Name: "balance", Value: interpreter.NewUFix64Value(gauge, func() uint64 { - return uint64(vault.Balance()) + return uint64(ufix) }), }, }, @@ -551,7 +1436,7 @@ func newInternalEVMTypeDeployFunction( panic(errors.NewUnreachableError()) } - amount := types.Balance(amountValue) + amount := types.NewBalanceFromUFix64(cadence.UFix64(amountValue)) // Deploy @@ -567,6 +1452,7 @@ func newInternalEVMTypeDeployFunction( func NewInternalEVMContractValue( gauge common.MemoryGauge, handler types.ContractHandler, + location common.AddressLocation, ) *interpreter.SimpleCompositeValue { return interpreter.NewSimpleCompositeValue( gauge, @@ -581,6 +1467,8 @@ func NewInternalEVMContractValue( internalEVMTypeWithdrawFunctionName: newInternalEVMTypeWithdrawFunction(gauge, handler), internalEVMTypeDeployFunctionName: newInternalEVMTypeDeployFunction(gauge, handler), internalEVMTypeBalanceFunctionName: newInternalEVMTypeBalanceFunction(gauge, handler), + internalEVMTypeEncodeABIFunctionName: newInternalEVMTypeEncodeABIFunction(gauge, location), + internalEVMTypeDecodeABIFunctionName: newInternalEVMTypeDecodeABIFunction(gauge, location), }, nil, nil, @@ -639,6 +1527,18 @@ var InternalEVMContractType = func() *sema.CompositeType { internalEVMTypeBalanceFunctionType, "", ), + sema.NewUnmeteredPublicFunctionMember( + ty, + internalEVMTypeEncodeABIFunctionName, + internalEVMTypeEncodeABIFunctionType, + "", + ), + sema.NewUnmeteredPublicFunctionMember( + ty, + internalEVMTypeDecodeABIFunctionName, + internalEVMTypeDecodeABIFunctionType, + "", + ), }) return ty }() @@ -651,11 +1551,12 @@ var internalEVMContractStaticType = interpreter.ConvertSemaCompositeTypeToStatic func newInternalEVMStandardLibraryValue( gauge common.MemoryGauge, handler types.ContractHandler, + location common.AddressLocation, ) stdlib.StandardLibraryValue { return stdlib.StandardLibraryValue{ Name: InternalEVMContractName, Type: InternalEVMContractType, - Value: NewInternalEVMContractValue(gauge, handler), + Value: NewInternalEVMContractValue(gauge, handler, location), Kind: common.DeclarationKindContract, } } @@ -668,12 +1569,13 @@ var internalEVMStandardLibraryType = stdlib.StandardLibraryType{ func SetupEnvironment(env runtime.Environment, handler types.ContractHandler, service flow.Address) { location := common.NewAddressLocation(nil, common.Address(service), ContractName) + env.DeclareType( internalEVMStandardLibraryType, location, ) env.DeclareValue( - newInternalEVMStandardLibraryValue(nil, handler), + newInternalEVMStandardLibraryValue(nil, handler, location), location, ) } @@ -681,7 +1583,7 @@ func SetupEnvironment(env runtime.Environment, handler types.ContractHandler, se func NewEVMAddressCadenceType(address common.Address) *cadence.StructType { return cadence.NewStructType( common.NewAddressLocation(nil, address, ContractName), - "EVM.EVMAddress", + evmAddressTypeQualifiedIdentifier, []cadence.Field{ { Identifier: "bytes", diff --git a/fvm/evm/stdlib/contract_test.go b/fvm/evm/stdlib/contract_test.go index ec4b8249cbb..7ad8cc64adb 100644 --- a/fvm/evm/stdlib/contract_test.go +++ b/fvm/evm/stdlib/contract_test.go @@ -4,15 +4,18 @@ import ( "encoding/binary" "testing" + "github.com/ethereum/go-ethereum/crypto" "github.com/onflow/cadence" "github.com/onflow/cadence/encoding/json" "github.com/onflow/cadence/runtime" "github.com/onflow/cadence/runtime/common" - contracts2 "github.com/onflow/flow-core-contracts/lib/go/contracts" + "github.com/onflow/cadence/runtime/tests/utils" + coreContracts "github.com/onflow/flow-core-contracts/lib/go/contracts" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/onflow/flow-go/fvm/blueprints" + "github.com/onflow/flow-go/fvm/environment" "github.com/onflow/flow-go/fvm/evm/stdlib" . "github.com/onflow/flow-go/fvm/evm/testutils" "github.com/onflow/flow-go/fvm/evm/types" @@ -83,7 +86,7 @@ func (t *testFlowAccount) Address() types.Address { func (t *testFlowAccount) Balance() types.Balance { if t.balance == nil { - return types.Balance(0) + return types.NewBalanceFromUFix64(0) } return t.balance() } @@ -130,6 +133,7 @@ func deployContracts( runtimeInterface *TestRuntimeInterface, transactionEnvironment runtime.Environment, nextTransactionLocation func() common.TransactionLocation, + evmAbiOnly bool, ) { contractsAddressHex := contractsAddress.Hex() @@ -141,33 +145,33 @@ func deployContracts( }{ { name: "FungibleToken", - code: contracts2.FungibleToken(), + code: coreContracts.FungibleToken(), }, { name: "NonFungibleToken", - code: contracts2.NonFungibleToken(), + code: coreContracts.NonFungibleToken(), }, { name: "MetadataViews", - code: contracts2.MetadataViews( + code: coreContracts.MetadataViews( contractsAddressHex, contractsAddressHex, ), }, { name: "FungibleTokenMetadataViews", - code: contracts2.FungibleTokenMetadataViews( + code: coreContracts.FungibleTokenMetadataViews( contractsAddressHex, contractsAddressHex, ), }, { name: "ViewResolver", - code: contracts2.ViewResolver(), + code: coreContracts.ViewResolver(), }, { name: "FlowToken", - code: contracts2.FlowToken( + code: coreContracts.FlowToken( contractsAddressHex, contractsAddressHex, contractsAddressHex, @@ -182,7 +186,7 @@ func deployContracts( }, { name: stdlib.ContractName, - code: stdlib.ContractCode(contractsAddress), + code: stdlib.ContractCode(contractsAddress, evmAbiOnly), }, } @@ -236,6 +240,2287 @@ func newEVMScriptEnvironment(handler types.ContractHandler, service flow.Address return scriptEnvironment } +func TestEVMEncodeABI(t *testing.T) { + + t.Parallel() + + handler := &testContractHandler{} + + contractsAddress := flow.BytesToAddress([]byte{0x1}) + + transactionEnvironment := newEVMTransactionEnvironment(handler, contractsAddress) + scriptEnvironment := newEVMScriptEnvironment(handler, contractsAddress) + + rt := runtime.NewInterpreterRuntime(runtime.Config{}) + + script := []byte(` + import EVM from 0x1 + + access(all) + fun main(): [UInt8] { + return EVM.encodeABI(["John Doe", UInt64(33), false]) + } + `) + + accountCodes := map[common.Location][]byte{} + var events []cadence.Event + + computation := uint(0) + runtimeInterface := &TestRuntimeInterface{ + Storage: NewTestLedger(nil, nil), + OnGetSigningAccounts: func() ([]runtime.Address, error) { + return []runtime.Address{runtime.Address(contractsAddress)}, nil + }, + OnResolveLocation: SingleIdentifierLocationResolver(t), + OnUpdateAccountContractCode: func(location common.AddressLocation, code []byte) error { + accountCodes[location] = code + return nil + }, + OnGetAccountContractCode: func(location common.AddressLocation) (code []byte, err error) { + code = accountCodes[location] + return code, nil + }, + OnEmitEvent: func(event cadence.Event) error { + events = append(events, event) + return nil + }, + OnDecodeArgument: func(b []byte, t cadence.Type) (cadence.Value, error) { + return json.Decode(nil, b) + }, + OnMeterComputation: func(compKind common.ComputationKind, intensity uint) error { + if compKind == environment.ComputationKindEVMEncodeABI { + computation += intensity + } + return nil + }, + } + + nextTransactionLocation := NewTransactionLocationGenerator() + nextScriptLocation := NewScriptLocationGenerator() + + // Deploy contracts + + deployContracts( + t, + rt, + contractsAddress, + runtimeInterface, + transactionEnvironment, + nextTransactionLocation, + true, + ) + + // Run script + + result, err := rt.ExecuteScript( + runtime.Script{ + Source: script, + Arguments: [][]byte{}, + }, + runtime.Context{ + Interface: runtimeInterface, + Environment: scriptEnvironment, + Location: nextScriptLocation(), + }, + ) + require.NoError(t, err) + + abiBytes := []byte{ + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x60, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x21, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x8, 0x4a, 0x6f, 0x68, 0x6e, 0x20, 0x44, 0x6f, 0x65, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + } + cdcBytes := make([]cadence.Value, 0) + for _, bt := range abiBytes { + cdcBytes = append(cdcBytes, cadence.UInt8(bt)) + } + encodedABI := cadence.NewArray( + cdcBytes, + ).WithType(cadence.NewVariableSizedArrayType(cadence.TheUInt8Type)) + + assert.Equal(t, + encodedABI, + result, + ) + assert.Equal(t, computation, uint(len(cdcBytes))) +} + +func TestEVMEncodeABIComputation(t *testing.T) { + + t.Parallel() + + handler := &testContractHandler{} + + contractsAddress := flow.BytesToAddress([]byte{0x1}) + + transactionEnvironment := newEVMTransactionEnvironment(handler, contractsAddress) + scriptEnvironment := newEVMScriptEnvironment(handler, contractsAddress) + + rt := runtime.NewInterpreterRuntime(runtime.Config{}) + + script := []byte(` + import EVM from 0x1 + + access(all) + fun main(): [UInt8] { + let address = EVM.EVMAddress( + bytes: [ + 122, 88, 192, 190, 114, 190, 33, 139, 65, 198, + 8, 183, 254, 124, 91, 182, 48, 115, 108, 113 + ] + ) + let arr: [UInt8] = [1, 2, 3, 4, 5] + + return EVM.encodeABI([ + "John Doe", + UInt64(33), + false, + address, + [arr], + ["one", "two", "three"] + ]) + } + `) + + accountCodes := map[common.Location][]byte{} + var events []cadence.Event + + computation := uint(0) + runtimeInterface := &TestRuntimeInterface{ + Storage: NewTestLedger(nil, nil), + OnGetSigningAccounts: func() ([]runtime.Address, error) { + return []runtime.Address{runtime.Address(contractsAddress)}, nil + }, + OnResolveLocation: SingleIdentifierLocationResolver(t), + OnUpdateAccountContractCode: func(location common.AddressLocation, code []byte) error { + accountCodes[location] = code + return nil + }, + OnGetAccountContractCode: func(location common.AddressLocation) (code []byte, err error) { + code = accountCodes[location] + return code, nil + }, + OnEmitEvent: func(event cadence.Event) error { + events = append(events, event) + return nil + }, + OnDecodeArgument: func(b []byte, t cadence.Type) (cadence.Value, error) { + return json.Decode(nil, b) + }, + OnMeterComputation: func(compKind common.ComputationKind, intensity uint) error { + if compKind == environment.ComputationKindEVMEncodeABI { + computation += intensity + } + return nil + }, + } + + nextTransactionLocation := NewTransactionLocationGenerator() + nextScriptLocation := NewScriptLocationGenerator() + + // Deploy contracts + + deployContracts( + t, + rt, + contractsAddress, + runtimeInterface, + transactionEnvironment, + nextTransactionLocation, + true, + ) + + // Run script + + result, err := rt.ExecuteScript( + runtime.Script{ + Source: script, + Arguments: [][]byte{}, + }, + runtime.Context{ + Interface: runtimeInterface, + Environment: scriptEnvironment, + Location: nextScriptLocation(), + }, + ) + require.NoError(t, err) + + cdcBytes, ok := result.(cadence.Array) + require.True(t, ok) + // computation & len(cdcBytes.Values) is equal to 832 + assert.Equal(t, computation, uint(len(cdcBytes.Values))) +} + +func TestEVMEncodeABIComputationEmptyDynamicVariables(t *testing.T) { + + t.Parallel() + + handler := &testContractHandler{} + + contractsAddress := flow.BytesToAddress([]byte{0x1}) + + transactionEnvironment := newEVMTransactionEnvironment(handler, contractsAddress) + scriptEnvironment := newEVMScriptEnvironment(handler, contractsAddress) + + rt := runtime.NewInterpreterRuntime(runtime.Config{}) + + script := []byte(` + import EVM from 0x1 + + access(all) + fun main(): [UInt8] { + return EVM.encodeABI([ + "", + [[""], [] as [String]], + [] as [UInt8], + ["", "", ""] + ]) + } + `) + + accountCodes := map[common.Location][]byte{} + var events []cadence.Event + + computation := uint(0) + runtimeInterface := &TestRuntimeInterface{ + Storage: NewTestLedger(nil, nil), + OnGetSigningAccounts: func() ([]runtime.Address, error) { + return []runtime.Address{runtime.Address(contractsAddress)}, nil + }, + OnResolveLocation: SingleIdentifierLocationResolver(t), + OnUpdateAccountContractCode: func(location common.AddressLocation, code []byte) error { + accountCodes[location] = code + return nil + }, + OnGetAccountContractCode: func(location common.AddressLocation) (code []byte, err error) { + code = accountCodes[location] + return code, nil + }, + OnEmitEvent: func(event cadence.Event) error { + events = append(events, event) + return nil + }, + OnDecodeArgument: func(b []byte, t cadence.Type) (cadence.Value, error) { + return json.Decode(nil, b) + }, + OnMeterComputation: func(compKind common.ComputationKind, intensity uint) error { + if compKind == environment.ComputationKindEVMEncodeABI { + computation += intensity + } + return nil + }, + } + + nextTransactionLocation := NewTransactionLocationGenerator() + nextScriptLocation := NewScriptLocationGenerator() + + // Deploy contracts + + deployContracts( + t, + rt, + contractsAddress, + runtimeInterface, + transactionEnvironment, + nextTransactionLocation, + true, + ) + + // Run script + + result, err := rt.ExecuteScript( + runtime.Script{ + Source: script, + Arguments: [][]byte{}, + }, + runtime.Context{ + Interface: runtimeInterface, + Environment: scriptEnvironment, + Location: nextScriptLocation(), + }, + ) + require.NoError(t, err) + + cdcBytes, ok := result.(cadence.Array) + require.True(t, ok) + // computation & len(cdcBytes.Values) is equal to 832 + assert.Equal(t, computation, uint(len(cdcBytes.Values))) +} + +func TestEVMEncodeABIComputationDynamicVariablesAboveChunkSize(t *testing.T) { + + t.Parallel() + + handler := &testContractHandler{} + + contractsAddress := flow.BytesToAddress([]byte{0x1}) + + transactionEnvironment := newEVMTransactionEnvironment(handler, contractsAddress) + scriptEnvironment := newEVMScriptEnvironment(handler, contractsAddress) + + rt := runtime.NewInterpreterRuntime(runtime.Config{}) + + script := []byte(` + import EVM from 0x1 + + access(all) + fun main(): [UInt8] { + let str = "abcdefghijklmnopqrstuvwxyz" + let arr: [UInt64] = [ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, + 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53 + ] + + return EVM.encodeABI([ + str, + str.concat(str).concat(str), + [[str]], + arr, + [arr], + arr.concat(arr).concat(arr) + ]) + } + `) + + accountCodes := map[common.Location][]byte{} + var events []cadence.Event + + computation := uint(0) + runtimeInterface := &TestRuntimeInterface{ + Storage: NewTestLedger(nil, nil), + OnGetSigningAccounts: func() ([]runtime.Address, error) { + return []runtime.Address{runtime.Address(contractsAddress)}, nil + }, + OnResolveLocation: SingleIdentifierLocationResolver(t), + OnUpdateAccountContractCode: func(location common.AddressLocation, code []byte) error { + accountCodes[location] = code + return nil + }, + OnGetAccountContractCode: func(location common.AddressLocation) (code []byte, err error) { + code = accountCodes[location] + return code, nil + }, + OnEmitEvent: func(event cadence.Event) error { + events = append(events, event) + return nil + }, + OnDecodeArgument: func(b []byte, t cadence.Type) (cadence.Value, error) { + return json.Decode(nil, b) + }, + OnMeterComputation: func(compKind common.ComputationKind, intensity uint) error { + if compKind == environment.ComputationKindEVMEncodeABI { + computation += intensity + } + return nil + }, + } + + nextTransactionLocation := NewTransactionLocationGenerator() + nextScriptLocation := NewScriptLocationGenerator() + + // Deploy contracts + + deployContracts( + t, + rt, + contractsAddress, + runtimeInterface, + transactionEnvironment, + nextTransactionLocation, + true, + ) + + // Run script + + result, err := rt.ExecuteScript( + runtime.Script{ + Source: script, + Arguments: [][]byte{}, + }, + runtime.Context{ + Interface: runtimeInterface, + Environment: scriptEnvironment, + Location: nextScriptLocation(), + }, + ) + require.NoError(t, err) + + cdcBytes, ok := result.(cadence.Array) + require.True(t, ok) + // computation & len(cdcBytes.Values) is equal to 832 + assert.Equal(t, computation, uint(len(cdcBytes.Values))) +} + +func TestEVMDecodeABI(t *testing.T) { + + t.Parallel() + + handler := &testContractHandler{} + + contractsAddress := flow.BytesToAddress([]byte{0x1}) + + transactionEnvironment := newEVMTransactionEnvironment(handler, contractsAddress) + scriptEnvironment := newEVMScriptEnvironment(handler, contractsAddress) + + rt := runtime.NewInterpreterRuntime(runtime.Config{}) + + script := []byte(` + import EVM from 0x1 + + access(all) + fun main(data: [UInt8]): Bool { + let types = [Type(), Type(), Type()] + let values = EVM.decodeABI(types: types, data: data) + + assert(values.length == 3) + assert((values[0] as! String) == "John Doe") + assert((values[1] as! UInt64) == UInt64(33)) + assert((values[2] as! Bool) == false) + + return true + } + `) + + accountCodes := map[common.Location][]byte{} + var events []cadence.Event + + computation := uint(0) + runtimeInterface := &TestRuntimeInterface{ + Storage: NewTestLedger(nil, nil), + OnGetSigningAccounts: func() ([]runtime.Address, error) { + return []runtime.Address{runtime.Address(contractsAddress)}, nil + }, + OnResolveLocation: SingleIdentifierLocationResolver(t), + OnUpdateAccountContractCode: func(location common.AddressLocation, code []byte) error { + accountCodes[location] = code + return nil + }, + OnGetAccountContractCode: func(location common.AddressLocation) (code []byte, err error) { + code = accountCodes[location] + return code, nil + }, + OnEmitEvent: func(event cadence.Event) error { + events = append(events, event) + return nil + }, + OnDecodeArgument: func(b []byte, t cadence.Type) (cadence.Value, error) { + return json.Decode(nil, b) + }, + OnMeterComputation: func(compKind common.ComputationKind, intensity uint) error { + if compKind == environment.ComputationKindEVMDecodeABI { + computation += intensity + } + return nil + }, + } + + nextTransactionLocation := NewTransactionLocationGenerator() + nextScriptLocation := NewScriptLocationGenerator() + + // Deploy contracts + + deployContracts( + t, + rt, + contractsAddress, + runtimeInterface, + transactionEnvironment, + nextTransactionLocation, + true, + ) + + // Run script + abiBytes := []byte{ + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x60, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x21, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x8, 0x4a, 0x6f, 0x68, 0x6e, 0x20, 0x44, 0x6f, 0x65, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + } + cdcBytes := make([]cadence.Value, 0) + for _, bt := range abiBytes { + cdcBytes = append(cdcBytes, cadence.UInt8(bt)) + } + encodedABI := cadence.NewArray( + cdcBytes, + ).WithType(cadence.NewVariableSizedArrayType(cadence.TheUInt8Type)) + + result, err := rt.ExecuteScript( + runtime.Script{ + Source: script, + Arguments: EncodeArgs([]cadence.Value{ + encodedABI, + }), + }, + runtime.Context{ + Interface: runtimeInterface, + Environment: scriptEnvironment, + Location: nextScriptLocation(), + }, + ) + require.NoError(t, err) + + assert.Equal(t, cadence.NewBool(true), result) + assert.Equal(t, computation, uint(len(cdcBytes))) +} + +func TestEVMDecodeABIComputation(t *testing.T) { + + t.Parallel() + + handler := &testContractHandler{} + + contractsAddress := flow.BytesToAddress([]byte{0x1}) + + transactionEnvironment := newEVMTransactionEnvironment(handler, contractsAddress) + scriptEnvironment := newEVMScriptEnvironment(handler, contractsAddress) + + rt := runtime.NewInterpreterRuntime(runtime.Config{}) + + script := []byte(` + import EVM from 0x1 + + access(all) + fun main(): [UInt8] { + let address = EVM.EVMAddress( + bytes: [ + 122, 88, 192, 190, 114, 190, 33, 139, 65, 198, + 8, 183, 254, 124, 91, 182, 48, 115, 108, 113 + ] + ) + let arr: [UInt8] = [1, 2, 3, 4, 5] + + let data = EVM.encodeABI([ + "John Doe", + UInt64(33), + true, + address, + [arr], + ["one", "two", "three"] + ]) + + let types = [ + Type(), Type(), Type(), Type(), + Type<[[UInt8]]>(), Type<[String]>() + ] + let values = EVM.decodeABI(types: types, data: data) + + return data + } + `) + + accountCodes := map[common.Location][]byte{} + var events []cadence.Event + + computation := uint(0) + runtimeInterface := &TestRuntimeInterface{ + Storage: NewTestLedger(nil, nil), + OnGetSigningAccounts: func() ([]runtime.Address, error) { + return []runtime.Address{runtime.Address(contractsAddress)}, nil + }, + OnResolveLocation: SingleIdentifierLocationResolver(t), + OnUpdateAccountContractCode: func(location common.AddressLocation, code []byte) error { + accountCodes[location] = code + return nil + }, + OnGetAccountContractCode: func(location common.AddressLocation) (code []byte, err error) { + code = accountCodes[location] + return code, nil + }, + OnEmitEvent: func(event cadence.Event) error { + events = append(events, event) + return nil + }, + OnDecodeArgument: func(b []byte, t cadence.Type) (cadence.Value, error) { + return json.Decode(nil, b) + }, + OnMeterComputation: func(compKind common.ComputationKind, intensity uint) error { + if compKind == environment.ComputationKindEVMDecodeABI { + computation += intensity + } + return nil + }, + } + + nextTransactionLocation := NewTransactionLocationGenerator() + nextScriptLocation := NewScriptLocationGenerator() + + // Deploy contracts + + deployContracts( + t, + rt, + contractsAddress, + runtimeInterface, + transactionEnvironment, + nextTransactionLocation, + true, + ) + + // Run script + + result, err := rt.ExecuteScript( + runtime.Script{ + Source: script, + Arguments: [][]byte{}, + }, + runtime.Context{ + Interface: runtimeInterface, + Environment: scriptEnvironment, + Location: nextScriptLocation(), + }, + ) + require.NoError(t, err) + + cdcBytes, ok := result.(cadence.Array) + require.True(t, ok) + // computation & len(cdcBytes.Values) is equal to 832 + assert.Equal(t, computation, uint(len(cdcBytes.Values))) +} + +func TestEVMEncodeDecodeABIRoundtrip(t *testing.T) { + + t.Parallel() + + handler := &testContractHandler{} + + contractsAddress := flow.BytesToAddress([]byte{0x1}) + + transactionEnvironment := newEVMTransactionEnvironment(handler, contractsAddress) + scriptEnvironment := newEVMScriptEnvironment(handler, contractsAddress) + + rt := runtime.NewInterpreterRuntime(runtime.Config{}) + + script := []byte(` + import EVM from 0x1 + + access(all) + fun main(): Bool { + // Check EVM.EVMAddress encode/decode + // bytes for address 0x7A58c0Be72BE218B41C608b7Fe7C5bB630736C71 + let address = EVM.EVMAddress( + bytes: [ + 122, 88, 192, 190, 114, 190, 33, 139, 65, 198, + 8, 183, 254, 124, 91, 182, 48, 115, 108, 113 + ] + ) + var data = EVM.encodeABI([address]) + var values = EVM.decodeABI(types: [Type()], data: data) + assert(values.length == 1) + assert((values[0] as! EVM.EVMAddress).bytes == address.bytes) + + // Check String encode/decode + data = EVM.encodeABI(["John Doe", ""]) + values = EVM.decodeABI(types: [Type(), Type()], data: data) + assert((values[0] as! String) == "John Doe") + assert((values[1] as! String) == "") + + // Check Bool encode/decode + data = EVM.encodeABI([true, false]) + values = EVM.decodeABI(types: [Type(), Type()], data: data) + assert((values[0] as! Bool) == true) + assert((values[1] as! Bool) == false) + + // Check UInt*/Int* encode/decode + data = EVM.encodeABI([ + UInt8(33), + UInt16(33), + UInt32(33), + UInt64(33), + UInt128(33), + UInt256(33), + Int8(-33), + Int16(-33), + Int32(-33), + Int64(-33), + Int128(-33), + Int256(-33) + ]) + values = EVM.decodeABI( + types: [ + Type(), + Type(), + Type(), + Type(), + Type(), + Type(), + Type(), + Type(), + Type(), + Type(), + Type(), + Type() + ], + data: data + ) + assert((values[0] as! UInt8) == 33) + assert((values[1] as! UInt16) == 33) + assert((values[2] as! UInt32) == 33) + assert((values[3] as! UInt64) == 33) + assert((values[4] as! UInt128) == 33) + assert((values[5] as! UInt256) == 33) + assert((values[6] as! Int8) == -33) + assert((values[7] as! Int16) == -33) + assert((values[8] as! Int32) == -33) + assert((values[9] as! Int64) == -33) + assert((values[10] as! Int128) == -33) + assert((values[11] as! Int256) == -33) + + // Check variable-size array of leaf types encode/decode + data = EVM.encodeABI([ + ["one", "two"], + [true, false], + [5, 10] as [UInt8], + [5, 10] as [UInt16], + [5, 10] as [UInt32], + [5, 10] as [UInt64], + [5, 10] as [UInt128], + [5, 10] as [UInt256], + [-5, -10] as [Int8], + [-5, -10] as [Int16], + [-5, -10] as [Int32], + [-5, -10] as [Int64], + [-5, -10] as [Int128], + [-5, -10] as [Int256], + [address] as [EVM.EVMAddress] + ]) + values = EVM.decodeABI( + types: [ + Type<[String]>(), + Type<[Bool]>(), + Type<[UInt8]>(), + Type<[UInt16]>(), + Type<[UInt32]>(), + Type<[UInt64]>(), + Type<[UInt128]>(), + Type<[UInt256]>(), + Type<[Int8]>(), + Type<[Int16]>(), + Type<[Int32]>(), + Type<[Int64]>(), + Type<[Int128]>(), + Type<[Int256]>(), + Type<[EVM.EVMAddress]>() + ], + data: data + ) + assert((values[0] as! [String]) == ["one", "two"]) + assert((values[1] as! [Bool]) == [true, false]) + assert((values[2] as! [UInt8]) == [5, 10]) + assert((values[3] as! [UInt16]) == [5, 10]) + assert((values[4] as! [UInt32]) == [5, 10]) + assert((values[5] as! [UInt64]) == [5, 10]) + assert((values[6] as! [UInt128]) == [5, 10]) + assert((values[7] as! [UInt256]) == [5, 10]) + assert((values[8] as! [Int8]) == [-5, -10]) + assert((values[9] as! [Int16]) == [-5, -10]) + assert((values[10] as! [Int32]) == [-5, -10]) + assert((values[11] as! [Int64]) == [-5, -10]) + assert((values[12] as! [Int128]) == [-5, -10]) + assert((values[13] as! [Int256]) == [-5, -10]) + assert((values[14] as! [EVM.EVMAddress])[0].bytes == [address][0].bytes) + + // Check constant-size array of leaf types encode/decode + data = EVM.encodeABI([ + ["one", "two"] as [String; 2], + [true, false] as [Bool; 2], + [5, 10] as [UInt8; 2], + [5, 10] as [UInt16; 2], + [5, 10] as [UInt32; 2], + [5, 10] as [UInt64; 2], + [5, 10] as [UInt128; 2], + [5, 10] as [UInt256; 2], + [-5, -10] as [Int8; 2], + [-5, -10] as [Int16; 2], + [-5, -10] as [Int32; 2], + [-5, -10] as [Int64; 2], + [-5, -10] as [Int128; 2], + [-5, -10] as [Int256; 2], + [address] as [EVM.EVMAddress; 1] + ]) + values = EVM.decodeABI( + types: [ + Type<[String; 2]>(), + Type<[Bool; 2]>(), + Type<[UInt8; 2]>(), + Type<[UInt16; 2]>(), + Type<[UInt32; 2]>(), + Type<[UInt64; 2]>(), + Type<[UInt128; 2]>(), + Type<[UInt256; 2]>(), + Type<[Int8; 2]>(), + Type<[Int16; 2]>(), + Type<[Int32; 2]>(), + Type<[Int64; 2]>(), + Type<[Int128; 2]>(), + Type<[Int256; 2]>(), + Type<[EVM.EVMAddress; 1]>() + ], + data: data + ) + assert((values[0] as! [String; 2]) == ["one", "two"]) + assert((values[1] as! [Bool; 2]) == [true, false]) + assert((values[2] as! [UInt8; 2]) == [5, 10]) + assert((values[3] as! [UInt16; 2]) == [5, 10]) + assert((values[4] as! [UInt32; 2]) == [5, 10]) + assert((values[5] as! [UInt64; 2]) == [5, 10]) + assert((values[6] as! [UInt128; 2]) == [5, 10]) + assert((values[7] as! [UInt256; 2]) == [5, 10]) + assert((values[8] as! [Int8; 2]) == [-5, -10]) + assert((values[9] as! [Int16; 2]) == [-5, -10]) + assert((values[10] as! [Int32; 2]) == [-5, -10]) + assert((values[11] as! [Int64; 2]) == [-5, -10]) + assert((values[12] as! [Int128; 2]) == [-5, -10]) + assert((values[13] as! [Int256; 2]) == [-5, -10]) + assert((values[14] as! [EVM.EVMAddress; 1])[0].bytes == [address][0].bytes) + + // Check partial decoding of encoded data + data = EVM.encodeABI(["Peter", UInt64(9999)]) + values = EVM.decodeABI(types: [Type()], data: data) + assert(values.length == 1) + assert((values[0] as! String) == "Peter") + + // Check nested arrays of leaf values + data = EVM.encodeABI([[["Foo", "Bar"], ["Baz", "Qux"]]]) + values = EVM.decodeABI(types: [Type<[[String]]>()], data: data) + assert(values.length == 1) + assert((values[0] as! [[String]]) == [["Foo", "Bar"], ["Baz", "Qux"]]) + + return true + } + `) + + accountCodes := map[common.Location][]byte{} + var events []cadence.Event + + runtimeInterface := &TestRuntimeInterface{ + Storage: NewTestLedger(nil, nil), + OnGetSigningAccounts: func() ([]runtime.Address, error) { + return []runtime.Address{runtime.Address(contractsAddress)}, nil + }, + OnResolveLocation: SingleIdentifierLocationResolver(t), + OnUpdateAccountContractCode: func(location common.AddressLocation, code []byte) error { + accountCodes[location] = code + return nil + }, + OnGetAccountContractCode: func(location common.AddressLocation) (code []byte, err error) { + code = accountCodes[location] + return code, nil + }, + OnEmitEvent: func(event cadence.Event) error { + events = append(events, event) + return nil + }, + OnDecodeArgument: func(b []byte, t cadence.Type) (cadence.Value, error) { + return json.Decode(nil, b) + }, + } + + nextTransactionLocation := NewTransactionLocationGenerator() + nextScriptLocation := NewScriptLocationGenerator() + + // Deploy contracts + + deployContracts( + t, + rt, + contractsAddress, + runtimeInterface, + transactionEnvironment, + nextTransactionLocation, + true, + ) + + // Run script + + result, err := rt.ExecuteScript( + runtime.Script{ + Source: script, + Arguments: [][]byte{}, + }, + runtime.Context{ + Interface: runtimeInterface, + Environment: scriptEnvironment, + Location: nextScriptLocation(), + }, + ) + require.NoError(t, err) + + assert.Equal(t, + cadence.Bool(true), + result, + ) +} + +func TestEVMEncodeDecodeABIErrors(t *testing.T) { + + t.Parallel() + + t.Run("encodeABI with unsupported Address type", func(t *testing.T) { + + t.Parallel() + + handler := &testContractHandler{} + + contractsAddress := flow.BytesToAddress([]byte{0x1}) + + transactionEnvironment := newEVMTransactionEnvironment(handler, contractsAddress) + scriptEnvironment := newEVMScriptEnvironment(handler, contractsAddress) + + rt := runtime.NewInterpreterRuntime(runtime.Config{}) + + accountCodes := map[common.Location][]byte{} + var events []cadence.Event + + runtimeInterface := &TestRuntimeInterface{ + Storage: NewTestLedger(nil, nil), + OnGetSigningAccounts: func() ([]runtime.Address, error) { + return []runtime.Address{runtime.Address(contractsAddress)}, nil + }, + OnResolveLocation: SingleIdentifierLocationResolver(t), + OnUpdateAccountContractCode: func(location common.AddressLocation, code []byte) error { + accountCodes[location] = code + return nil + }, + OnGetAccountContractCode: func(location common.AddressLocation) (code []byte, err error) { + code = accountCodes[location] + return code, nil + }, + OnEmitEvent: func(event cadence.Event) error { + events = append(events, event) + return nil + }, + OnDecodeArgument: func(b []byte, t cadence.Type) (cadence.Value, error) { + return json.Decode(nil, b) + }, + } + + nextTransactionLocation := NewTransactionLocationGenerator() + nextScriptLocation := NewScriptLocationGenerator() + + // Deploy contracts + + deployContracts( + t, + rt, + contractsAddress, + runtimeInterface, + transactionEnvironment, + nextTransactionLocation, + true, + ) + + // Run script + + script := []byte(` + import EVM from 0x1 + + access(all) + fun main(): Bool { + let address: Address = 0x045a1763c93006ca + let data = EVM.encodeABI([address]) + + return true + } + `) + + _, err := rt.ExecuteScript( + runtime.Script{ + Source: script, + Arguments: [][]byte{}, + }, + runtime.Context{ + Interface: runtimeInterface, + Environment: scriptEnvironment, + Location: nextScriptLocation(), + }, + ) + utils.RequireError(t, err) + assert.ErrorContains( + t, + err, + "failed to ABI encode value of type Address", + ) + }) + + t.Run("encodeABI with unsupported fixed-point number type", func(t *testing.T) { + + t.Parallel() + + handler := &testContractHandler{} + + contractsAddress := flow.BytesToAddress([]byte{0x1}) + + transactionEnvironment := newEVMTransactionEnvironment(handler, contractsAddress) + scriptEnvironment := newEVMScriptEnvironment(handler, contractsAddress) + + rt := runtime.NewInterpreterRuntime(runtime.Config{}) + + accountCodes := map[common.Location][]byte{} + var events []cadence.Event + + runtimeInterface := &TestRuntimeInterface{ + Storage: NewTestLedger(nil, nil), + OnGetSigningAccounts: func() ([]runtime.Address, error) { + return []runtime.Address{runtime.Address(contractsAddress)}, nil + }, + OnResolveLocation: SingleIdentifierLocationResolver(t), + OnUpdateAccountContractCode: func(location common.AddressLocation, code []byte) error { + accountCodes[location] = code + return nil + }, + OnGetAccountContractCode: func(location common.AddressLocation) (code []byte, err error) { + code = accountCodes[location] + return code, nil + }, + OnEmitEvent: func(event cadence.Event) error { + events = append(events, event) + return nil + }, + OnDecodeArgument: func(b []byte, t cadence.Type) (cadence.Value, error) { + return json.Decode(nil, b) + }, + } + + nextTransactionLocation := NewTransactionLocationGenerator() + nextScriptLocation := NewScriptLocationGenerator() + + // Deploy contracts + + deployContracts( + t, + rt, + contractsAddress, + runtimeInterface, + transactionEnvironment, + nextTransactionLocation, + true, + ) + + // Run script + + script := []byte(` + import EVM from 0x1 + + access(all) + fun main(): Bool { + let data = EVM.encodeABI([0.2]) + + return true + } + `) + + _, err := rt.ExecuteScript( + runtime.Script{ + Source: script, + Arguments: [][]byte{}, + }, + runtime.Context{ + Interface: runtimeInterface, + Environment: scriptEnvironment, + Location: nextScriptLocation(), + }, + ) + utils.RequireError(t, err) + assert.ErrorContains( + t, + err, + "failed to ABI encode value of type UFix64", + ) + }) + + t.Run("encodeABI with unsupported dictionary type", func(t *testing.T) { + + t.Parallel() + + handler := &testContractHandler{} + + contractsAddress := flow.BytesToAddress([]byte{0x1}) + + transactionEnvironment := newEVMTransactionEnvironment(handler, contractsAddress) + scriptEnvironment := newEVMScriptEnvironment(handler, contractsAddress) + + rt := runtime.NewInterpreterRuntime(runtime.Config{}) + + accountCodes := map[common.Location][]byte{} + var events []cadence.Event + + runtimeInterface := &TestRuntimeInterface{ + Storage: NewTestLedger(nil, nil), + OnGetSigningAccounts: func() ([]runtime.Address, error) { + return []runtime.Address{runtime.Address(contractsAddress)}, nil + }, + OnResolveLocation: SingleIdentifierLocationResolver(t), + OnUpdateAccountContractCode: func(location common.AddressLocation, code []byte) error { + accountCodes[location] = code + return nil + }, + OnGetAccountContractCode: func(location common.AddressLocation) (code []byte, err error) { + code = accountCodes[location] + return code, nil + }, + OnEmitEvent: func(event cadence.Event) error { + events = append(events, event) + return nil + }, + OnDecodeArgument: func(b []byte, t cadence.Type) (cadence.Value, error) { + return json.Decode(nil, b) + }, + } + + nextTransactionLocation := NewTransactionLocationGenerator() + nextScriptLocation := NewScriptLocationGenerator() + + // Deploy contracts + + deployContracts( + t, + rt, + contractsAddress, + runtimeInterface, + transactionEnvironment, + nextTransactionLocation, + true, + ) + + // Run script + + script := []byte(` + import EVM from 0x1 + + access(all) + fun main(): Bool { + let dict: {Int: Bool} = {0: false, 1: true} + let data = EVM.encodeABI([dict]) + + return true + } + `) + + _, err := rt.ExecuteScript( + runtime.Script{ + Source: script, + Arguments: [][]byte{}, + }, + runtime.Context{ + Interface: runtimeInterface, + Environment: scriptEnvironment, + Location: nextScriptLocation(), + }, + ) + utils.RequireError(t, err) + assert.ErrorContains( + t, + err, + "failed to ABI encode value of type {Int: Bool}", + ) + }) + + t.Run("encodeABI with unsupported array element type", func(t *testing.T) { + + t.Parallel() + + handler := &testContractHandler{} + + contractsAddress := flow.BytesToAddress([]byte{0x1}) + + transactionEnvironment := newEVMTransactionEnvironment(handler, contractsAddress) + scriptEnvironment := newEVMScriptEnvironment(handler, contractsAddress) + + rt := runtime.NewInterpreterRuntime(runtime.Config{}) + + accountCodes := map[common.Location][]byte{} + var events []cadence.Event + + runtimeInterface := &TestRuntimeInterface{ + Storage: NewTestLedger(nil, nil), + OnGetSigningAccounts: func() ([]runtime.Address, error) { + return []runtime.Address{runtime.Address(contractsAddress)}, nil + }, + OnResolveLocation: SingleIdentifierLocationResolver(t), + OnUpdateAccountContractCode: func(location common.AddressLocation, code []byte) error { + accountCodes[location] = code + return nil + }, + OnGetAccountContractCode: func(location common.AddressLocation) (code []byte, err error) { + code = accountCodes[location] + return code, nil + }, + OnEmitEvent: func(event cadence.Event) error { + events = append(events, event) + return nil + }, + OnDecodeArgument: func(b []byte, t cadence.Type) (cadence.Value, error) { + return json.Decode(nil, b) + }, + } + + nextTransactionLocation := NewTransactionLocationGenerator() + nextScriptLocation := NewScriptLocationGenerator() + + // Deploy contracts + + deployContracts( + t, + rt, + contractsAddress, + runtimeInterface, + transactionEnvironment, + nextTransactionLocation, + true, + ) + + // Run script + + script := []byte(` + import EVM from 0x1 + + access(all) + fun main(): Bool { + let chars: [Character] = ["a", "b", "c"] + let data = EVM.encodeABI([chars]) + + return true + } + `) + + _, err := rt.ExecuteScript( + runtime.Script{ + Source: script, + Arguments: [][]byte{}, + }, + runtime.Context{ + Interface: runtimeInterface, + Environment: scriptEnvironment, + Location: nextScriptLocation(), + }, + ) + utils.RequireError(t, err) + assert.ErrorContains( + t, + err, + "failed to ABI encode value of type Character", + ) + }) + + t.Run("encodeABI with unsupported custom composite type", func(t *testing.T) { + + t.Parallel() + + handler := &testContractHandler{} + + contractsAddress := flow.BytesToAddress([]byte{0x1}) + + transactionEnvironment := newEVMTransactionEnvironment(handler, contractsAddress) + scriptEnvironment := newEVMScriptEnvironment(handler, contractsAddress) + + rt := runtime.NewInterpreterRuntime(runtime.Config{}) + + accountCodes := map[common.Location][]byte{} + var events []cadence.Event + + runtimeInterface := &TestRuntimeInterface{ + Storage: NewTestLedger(nil, nil), + OnGetSigningAccounts: func() ([]runtime.Address, error) { + return []runtime.Address{runtime.Address(contractsAddress)}, nil + }, + OnResolveLocation: SingleIdentifierLocationResolver(t), + OnUpdateAccountContractCode: func(location common.AddressLocation, code []byte) error { + accountCodes[location] = code + return nil + }, + OnGetAccountContractCode: func(location common.AddressLocation) (code []byte, err error) { + code = accountCodes[location] + return code, nil + }, + OnEmitEvent: func(event cadence.Event) error { + events = append(events, event) + return nil + }, + OnDecodeArgument: func(b []byte, t cadence.Type) (cadence.Value, error) { + return json.Decode(nil, b) + }, + } + + nextTransactionLocation := NewTransactionLocationGenerator() + nextScriptLocation := NewScriptLocationGenerator() + + // Deploy contracts + + deployContracts( + t, + rt, + contractsAddress, + runtimeInterface, + transactionEnvironment, + nextTransactionLocation, + true, + ) + + // Run script + + script := []byte(` + import EVM from 0x1 + + access(all) struct Token { + access(all) let id: Int + access(all) var balance: Int + + init(id: Int, balance: Int) { + self.id = id + self.balance = balance + } + } + + access(all) + fun main(): Bool { + let token = Token(id: 9, balance: 150) + let data = EVM.encodeABI([token]) + + return true + } + `) + + _, err := rt.ExecuteScript( + runtime.Script{ + Source: script, + Arguments: [][]byte{}, + }, + runtime.Context{ + Interface: runtimeInterface, + Environment: scriptEnvironment, + Location: nextScriptLocation(), + }, + ) + utils.RequireError(t, err) + assert.ErrorContains( + t, + err, + "failed to ABI encode value of type s.0100000000000000000000000000000000000000000000000000000000000000.Token", + ) + }) + + t.Run("decodeABI with mismatched type", func(t *testing.T) { + + t.Parallel() + + handler := &testContractHandler{} + + contractsAddress := flow.BytesToAddress([]byte{0x1}) + + transactionEnvironment := newEVMTransactionEnvironment(handler, contractsAddress) + scriptEnvironment := newEVMScriptEnvironment(handler, contractsAddress) + + rt := runtime.NewInterpreterRuntime(runtime.Config{}) + + accountCodes := map[common.Location][]byte{} + var events []cadence.Event + + runtimeInterface := &TestRuntimeInterface{ + Storage: NewTestLedger(nil, nil), + OnGetSigningAccounts: func() ([]runtime.Address, error) { + return []runtime.Address{runtime.Address(contractsAddress)}, nil + }, + OnResolveLocation: SingleIdentifierLocationResolver(t), + OnUpdateAccountContractCode: func(location common.AddressLocation, code []byte) error { + accountCodes[location] = code + return nil + }, + OnGetAccountContractCode: func(location common.AddressLocation) (code []byte, err error) { + code = accountCodes[location] + return code, nil + }, + OnEmitEvent: func(event cadence.Event) error { + events = append(events, event) + return nil + }, + OnDecodeArgument: func(b []byte, t cadence.Type) (cadence.Value, error) { + return json.Decode(nil, b) + }, + } + + nextTransactionLocation := NewTransactionLocationGenerator() + nextScriptLocation := NewScriptLocationGenerator() + + // Deploy contracts + + deployContracts( + t, + rt, + contractsAddress, + runtimeInterface, + transactionEnvironment, + nextTransactionLocation, + true, + ) + + // Run script + + script := []byte(` + import EVM from 0x1 + + access(all) + fun main(): Bool { + let data = EVM.encodeABI(["Peter"]) + let values = EVM.decodeABI(types: [Type()], data: data) + + return true + } + `) + + _, err := rt.ExecuteScript( + runtime.Script{ + Source: script, + Arguments: [][]byte{}, + }, + runtime.Context{ + Interface: runtimeInterface, + Environment: scriptEnvironment, + Location: nextScriptLocation(), + }, + ) + utils.RequireError(t, err) + assert.ErrorContains( + t, + err, + "failed to ABI decode data", + ) + }) + + t.Run("decodeABI with surplus of types", func(t *testing.T) { + + t.Parallel() + + handler := &testContractHandler{} + + contractsAddress := flow.BytesToAddress([]byte{0x1}) + + transactionEnvironment := newEVMTransactionEnvironment(handler, contractsAddress) + scriptEnvironment := newEVMScriptEnvironment(handler, contractsAddress) + + rt := runtime.NewInterpreterRuntime(runtime.Config{}) + + accountCodes := map[common.Location][]byte{} + var events []cadence.Event + + runtimeInterface := &TestRuntimeInterface{ + Storage: NewTestLedger(nil, nil), + OnGetSigningAccounts: func() ([]runtime.Address, error) { + return []runtime.Address{runtime.Address(contractsAddress)}, nil + }, + OnResolveLocation: SingleIdentifierLocationResolver(t), + OnUpdateAccountContractCode: func(location common.AddressLocation, code []byte) error { + accountCodes[location] = code + return nil + }, + OnGetAccountContractCode: func(location common.AddressLocation) (code []byte, err error) { + code = accountCodes[location] + return code, nil + }, + OnEmitEvent: func(event cadence.Event) error { + events = append(events, event) + return nil + }, + OnDecodeArgument: func(b []byte, t cadence.Type) (cadence.Value, error) { + return json.Decode(nil, b) + }, + } + + nextTransactionLocation := NewTransactionLocationGenerator() + nextScriptLocation := NewScriptLocationGenerator() + + // Deploy contracts + + deployContracts( + t, + rt, + contractsAddress, + runtimeInterface, + transactionEnvironment, + nextTransactionLocation, + true, + ) + + // Run script + + script := []byte(` + import EVM from 0x1 + + access(all) + fun main(): Bool { + let data = EVM.encodeABI(["Peter"]) + let values = EVM.decodeABI(types: [Type(), Type()], data: data) + + return true + } + `) + + _, err := rt.ExecuteScript( + runtime.Script{ + Source: script, + Arguments: [][]byte{}, + }, + runtime.Context{ + Interface: runtimeInterface, + Environment: scriptEnvironment, + Location: nextScriptLocation(), + }, + ) + utils.RequireError(t, err) + assert.ErrorContains( + t, + err, + "failed to ABI decode data", + ) + }) + + t.Run("decodeABI with unsupported fixed-point number type", func(t *testing.T) { + + t.Parallel() + + handler := &testContractHandler{} + + contractsAddress := flow.BytesToAddress([]byte{0x1}) + + transactionEnvironment := newEVMTransactionEnvironment(handler, contractsAddress) + scriptEnvironment := newEVMScriptEnvironment(handler, contractsAddress) + + rt := runtime.NewInterpreterRuntime(runtime.Config{}) + + accountCodes := map[common.Location][]byte{} + var events []cadence.Event + + runtimeInterface := &TestRuntimeInterface{ + Storage: NewTestLedger(nil, nil), + OnGetSigningAccounts: func() ([]runtime.Address, error) { + return []runtime.Address{runtime.Address(contractsAddress)}, nil + }, + OnResolveLocation: SingleIdentifierLocationResolver(t), + OnUpdateAccountContractCode: func(location common.AddressLocation, code []byte) error { + accountCodes[location] = code + return nil + }, + OnGetAccountContractCode: func(location common.AddressLocation) (code []byte, err error) { + code = accountCodes[location] + return code, nil + }, + OnEmitEvent: func(event cadence.Event) error { + events = append(events, event) + return nil + }, + OnDecodeArgument: func(b []byte, t cadence.Type) (cadence.Value, error) { + return json.Decode(nil, b) + }, + } + + nextTransactionLocation := NewTransactionLocationGenerator() + nextScriptLocation := NewScriptLocationGenerator() + + // Deploy contracts + + deployContracts( + t, + rt, + contractsAddress, + runtimeInterface, + transactionEnvironment, + nextTransactionLocation, + true, + ) + + // Run script + + script := []byte(` + import EVM from 0x1 + + access(all) + fun main(): Bool { + let data = EVM.encodeABI(["Peter"]) + let values = EVM.decodeABI(types: [Type()], data: data) + + return true + } + `) + + _, err := rt.ExecuteScript( + runtime.Script{ + Source: script, + Arguments: [][]byte{}, + }, + runtime.Context{ + Interface: runtimeInterface, + Environment: scriptEnvironment, + Location: nextScriptLocation(), + }, + ) + utils.RequireError(t, err) + assert.ErrorContains( + t, + err, + "failed to ABI decode data with type UFix64", + ) + }) + + t.Run("decodeABI with unsupported dictionary type", func(t *testing.T) { + + t.Parallel() + + handler := &testContractHandler{} + + contractsAddress := flow.BytesToAddress([]byte{0x1}) + + transactionEnvironment := newEVMTransactionEnvironment(handler, contractsAddress) + scriptEnvironment := newEVMScriptEnvironment(handler, contractsAddress) + + rt := runtime.NewInterpreterRuntime(runtime.Config{}) + + accountCodes := map[common.Location][]byte{} + var events []cadence.Event + + runtimeInterface := &TestRuntimeInterface{ + Storage: NewTestLedger(nil, nil), + OnGetSigningAccounts: func() ([]runtime.Address, error) { + return []runtime.Address{runtime.Address(contractsAddress)}, nil + }, + OnResolveLocation: SingleIdentifierLocationResolver(t), + OnUpdateAccountContractCode: func(location common.AddressLocation, code []byte) error { + accountCodes[location] = code + return nil + }, + OnGetAccountContractCode: func(location common.AddressLocation) (code []byte, err error) { + code = accountCodes[location] + return code, nil + }, + OnEmitEvent: func(event cadence.Event) error { + events = append(events, event) + return nil + }, + OnDecodeArgument: func(b []byte, t cadence.Type) (cadence.Value, error) { + return json.Decode(nil, b) + }, + } + + nextTransactionLocation := NewTransactionLocationGenerator() + nextScriptLocation := NewScriptLocationGenerator() + + // Deploy contracts + + deployContracts( + t, + rt, + contractsAddress, + runtimeInterface, + transactionEnvironment, + nextTransactionLocation, + true, + ) + + // Run script + + script := []byte(` + import EVM from 0x1 + + access(all) + fun main(): Bool { + let data = EVM.encodeABI(["Peter"]) + let values = EVM.decodeABI(types: [Type<{Int: Bool}>()], data: data) + + return true + } + `) + + _, err := rt.ExecuteScript( + runtime.Script{ + Source: script, + Arguments: [][]byte{}, + }, + runtime.Context{ + Interface: runtimeInterface, + Environment: scriptEnvironment, + Location: nextScriptLocation(), + }, + ) + utils.RequireError(t, err) + assert.ErrorContains( + t, + err, + "failed to ABI decode data with type {Int: Bool}", + ) + }) + + t.Run("decodeABI with unsupported array element type", func(t *testing.T) { + + t.Parallel() + + handler := &testContractHandler{} + + contractsAddress := flow.BytesToAddress([]byte{0x1}) + + transactionEnvironment := newEVMTransactionEnvironment(handler, contractsAddress) + scriptEnvironment := newEVMScriptEnvironment(handler, contractsAddress) + + rt := runtime.NewInterpreterRuntime(runtime.Config{}) + + accountCodes := map[common.Location][]byte{} + var events []cadence.Event + + runtimeInterface := &TestRuntimeInterface{ + Storage: NewTestLedger(nil, nil), + OnGetSigningAccounts: func() ([]runtime.Address, error) { + return []runtime.Address{runtime.Address(contractsAddress)}, nil + }, + OnResolveLocation: SingleIdentifierLocationResolver(t), + OnUpdateAccountContractCode: func(location common.AddressLocation, code []byte) error { + accountCodes[location] = code + return nil + }, + OnGetAccountContractCode: func(location common.AddressLocation) (code []byte, err error) { + code = accountCodes[location] + return code, nil + }, + OnEmitEvent: func(event cadence.Event) error { + events = append(events, event) + return nil + }, + OnDecodeArgument: func(b []byte, t cadence.Type) (cadence.Value, error) { + return json.Decode(nil, b) + }, + } + + nextTransactionLocation := NewTransactionLocationGenerator() + nextScriptLocation := NewScriptLocationGenerator() + + // Deploy contracts + + deployContracts( + t, + rt, + contractsAddress, + runtimeInterface, + transactionEnvironment, + nextTransactionLocation, + true, + ) + + // Run script + + script := []byte(` + import EVM from 0x1 + + access(all) + fun main(): Bool { + let data = EVM.encodeABI(["Peter"]) + let values = EVM.decodeABI(types: [Type<[Character]>()], data: data) + + return true + } + `) + + _, err := rt.ExecuteScript( + runtime.Script{ + Source: script, + Arguments: [][]byte{}, + }, + runtime.Context{ + Interface: runtimeInterface, + Environment: scriptEnvironment, + Location: nextScriptLocation(), + }, + ) + utils.RequireError(t, err) + assert.ErrorContains( + t, + err, + "failed to ABI decode data with type [Character]", + ) + }) + + t.Run("decodeABI with unsupported custom composite type", func(t *testing.T) { + + t.Parallel() + + handler := &testContractHandler{} + + contractsAddress := flow.BytesToAddress([]byte{0x1}) + + transactionEnvironment := newEVMTransactionEnvironment(handler, contractsAddress) + scriptEnvironment := newEVMScriptEnvironment(handler, contractsAddress) + + rt := runtime.NewInterpreterRuntime(runtime.Config{}) + + accountCodes := map[common.Location][]byte{} + var events []cadence.Event + + runtimeInterface := &TestRuntimeInterface{ + Storage: NewTestLedger(nil, nil), + OnGetSigningAccounts: func() ([]runtime.Address, error) { + return []runtime.Address{runtime.Address(contractsAddress)}, nil + }, + OnResolveLocation: SingleIdentifierLocationResolver(t), + OnUpdateAccountContractCode: func(location common.AddressLocation, code []byte) error { + accountCodes[location] = code + return nil + }, + OnGetAccountContractCode: func(location common.AddressLocation) (code []byte, err error) { + code = accountCodes[location] + return code, nil + }, + OnEmitEvent: func(event cadence.Event) error { + events = append(events, event) + return nil + }, + OnDecodeArgument: func(b []byte, t cadence.Type) (cadence.Value, error) { + return json.Decode(nil, b) + }, + } + + nextTransactionLocation := NewTransactionLocationGenerator() + nextScriptLocation := NewScriptLocationGenerator() + + // Deploy contracts + + deployContracts( + t, + rt, + contractsAddress, + runtimeInterface, + transactionEnvironment, + nextTransactionLocation, + true, + ) + + // Run script + + script := []byte(` + import EVM from 0x1 + + access(all) struct Token { + access(all) let id: Int + access(all) var balance: Int + + init(id: Int, balance: Int) { + self.id = id + self.balance = balance + } + } + + access(all) + fun main(): Bool { + let data = EVM.encodeABI(["Peter"]) + let values = EVM.decodeABI(types: [Type()], data: data) + + return true + } + `) + + _, err := rt.ExecuteScript( + runtime.Script{ + Source: script, + Arguments: [][]byte{}, + }, + runtime.Context{ + Interface: runtimeInterface, + Environment: scriptEnvironment, + Location: nextScriptLocation(), + }, + ) + utils.RequireError(t, err) + assert.ErrorContains( + t, + err, + "failed to ABI decode data with type s.0100000000000000000000000000000000000000000000000000000000000000.Token", + ) + }) +} + +func TestEVMEncodeABIWithSignature(t *testing.T) { + + t.Parallel() + + handler := &testContractHandler{} + + contractsAddress := flow.BytesToAddress([]byte{0x1}) + + transactionEnvironment := newEVMTransactionEnvironment(handler, contractsAddress) + scriptEnvironment := newEVMScriptEnvironment(handler, contractsAddress) + + rt := runtime.NewInterpreterRuntime(runtime.Config{}) + + script := []byte(` + import EVM from 0x1 + + access(all) + fun main(): [UInt8] { + // bytes for address 0x7A58c0Be72BE218B41C608b7Fe7C5bB630736C71 + let address = EVM.EVMAddress( + bytes: [ + 122, 88, 192, 190, 114, 190, 33, 139, 65, 198, + 8, 183, 254, 124, 91, 182, 48, 115, 108, 113 + ] + ) + + return EVM.encodeABIWithSignature( + "withdraw(address,uint256)", + [address, UInt256(250)] + ) + } + `) + + accountCodes := map[common.Location][]byte{} + var events []cadence.Event + + computation := uint(0) + runtimeInterface := &TestRuntimeInterface{ + Storage: NewTestLedger(nil, nil), + OnGetSigningAccounts: func() ([]runtime.Address, error) { + return []runtime.Address{runtime.Address(contractsAddress)}, nil + }, + OnResolveLocation: SingleIdentifierLocationResolver(t), + OnUpdateAccountContractCode: func(location common.AddressLocation, code []byte) error { + accountCodes[location] = code + return nil + }, + OnGetAccountContractCode: func(location common.AddressLocation) (code []byte, err error) { + code = accountCodes[location] + return code, nil + }, + OnEmitEvent: func(event cadence.Event) error { + events = append(events, event) + return nil + }, + OnDecodeArgument: func(b []byte, t cadence.Type) (cadence.Value, error) { + return json.Decode(nil, b) + }, + OnMeterComputation: func(compKind common.ComputationKind, intensity uint) error { + if compKind == environment.ComputationKindEVMEncodeABI { + computation += intensity + } + return nil + }, + OnHash: func( + data []byte, + tag string, + hashAlgorithm runtime.HashAlgorithm, + ) ([]byte, error) { + return crypto.Keccak256(data), nil + }, + } + + nextTransactionLocation := NewTransactionLocationGenerator() + nextScriptLocation := NewScriptLocationGenerator() + + // Deploy contracts + + deployContracts( + t, + rt, + contractsAddress, + runtimeInterface, + transactionEnvironment, + nextTransactionLocation, + true, + ) + + // Run script + + result, err := rt.ExecuteScript( + runtime.Script{ + Source: script, + Arguments: [][]byte{}, + }, + runtime.Context{ + Interface: runtimeInterface, + Environment: scriptEnvironment, + Location: nextScriptLocation(), + }, + ) + require.NoError(t, err) + + abiBytes := []byte{ + 0xf3, 0xfe, 0xf3, 0xa3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x7a, 0x58, 0xc0, 0xbe, 0x72, 0xbe, 0x21, 0x8b, 0x41, + 0xc6, 0x8, 0xb7, 0xfe, 0x7c, 0x5b, 0xb6, 0x30, 0x73, 0x6c, 0x71, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xfa, + } + cdcBytes := make([]cadence.Value, 0) + for _, bt := range abiBytes { + cdcBytes = append(cdcBytes, cadence.UInt8(bt)) + } + encodedABI := cadence.NewArray( + cdcBytes, + ).WithType(cadence.NewVariableSizedArrayType(cadence.TheUInt8Type)) + + assert.Equal(t, + encodedABI, + result, + ) + // The method ID is a byte array of length 4 + assert.Equal(t, computation+4, uint(len(cdcBytes))) +} + +func TestEVMDecodeABIWithSignature(t *testing.T) { + + t.Parallel() + + handler := &testContractHandler{} + + contractsAddress := flow.BytesToAddress([]byte{0x1}) + + transactionEnvironment := newEVMTransactionEnvironment(handler, contractsAddress) + scriptEnvironment := newEVMScriptEnvironment(handler, contractsAddress) + + rt := runtime.NewInterpreterRuntime(runtime.Config{}) + + script := []byte(` + import EVM from 0x1 + + access(all) + fun main(data: [UInt8]): Bool { + let values = EVM.decodeABIWithSignature( + "withdraw(address,uint256)", + types: [Type(), Type()], + data: data + ) + + // bytes for address 0x7A58c0Be72BE218B41C608b7Fe7C5bB630736C71 + let address = EVM.EVMAddress( + bytes: [ + 122, 88, 192, 190, 114, 190, 33, 139, 65, 198, + 8, 183, 254, 124, 91, 182, 48, 115, 108, 113 + ] + ) + + assert(values.length == 2) + assert((values[0] as! EVM.EVMAddress).bytes == address.bytes) + assert((values[1] as! UInt256) == UInt256(250)) + + return true + } + `) + + accountCodes := map[common.Location][]byte{} + var events []cadence.Event + + computation := uint(0) + runtimeInterface := &TestRuntimeInterface{ + Storage: NewTestLedger(nil, nil), + OnGetSigningAccounts: func() ([]runtime.Address, error) { + return []runtime.Address{runtime.Address(contractsAddress)}, nil + }, + OnResolveLocation: SingleIdentifierLocationResolver(t), + OnUpdateAccountContractCode: func(location common.AddressLocation, code []byte) error { + accountCodes[location] = code + return nil + }, + OnGetAccountContractCode: func(location common.AddressLocation) (code []byte, err error) { + code = accountCodes[location] + return code, nil + }, + OnEmitEvent: func(event cadence.Event) error { + events = append(events, event) + return nil + }, + OnDecodeArgument: func(b []byte, t cadence.Type) (cadence.Value, error) { + return json.Decode(nil, b) + }, + OnMeterComputation: func(compKind common.ComputationKind, intensity uint) error { + if compKind == environment.ComputationKindEVMDecodeABI { + computation += intensity + } + return nil + }, + OnHash: func( + data []byte, + tag string, + hashAlgorithm runtime.HashAlgorithm, + ) ([]byte, error) { + return crypto.Keccak256(data), nil + }, + } + + nextTransactionLocation := NewTransactionLocationGenerator() + nextScriptLocation := NewScriptLocationGenerator() + + // Deploy contracts + + deployContracts( + t, + rt, + contractsAddress, + runtimeInterface, + transactionEnvironment, + nextTransactionLocation, + true, + ) + + // Run script + abiBytes := []byte{ + 0xf3, 0xfe, 0xf3, 0xa3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x7a, 0x58, 0xc0, 0xbe, 0x72, 0xbe, 0x21, 0x8b, 0x41, + 0xc6, 0x8, 0xb7, 0xfe, 0x7c, 0x5b, 0xb6, 0x30, 0x73, 0x6c, 0x71, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xfa, + } + cdcBytes := make([]cadence.Value, 0) + for _, bt := range abiBytes { + cdcBytes = append(cdcBytes, cadence.UInt8(bt)) + } + encodedABI := cadence.NewArray( + cdcBytes, + ).WithType(cadence.NewVariableSizedArrayType(cadence.TheUInt8Type)) + + result, err := rt.ExecuteScript( + runtime.Script{ + Source: script, + Arguments: EncodeArgs([]cadence.Value{ + encodedABI, + }), + }, + runtime.Context{ + Interface: runtimeInterface, + Environment: scriptEnvironment, + Location: nextScriptLocation(), + }, + ) + require.NoError(t, err) + + assert.Equal(t, cadence.NewBool(true), result) + // The method ID is a byte array of length 4 + assert.Equal(t, computation+4, uint(len(cdcBytes))) +} + +func TestEVMDecodeABIWithSignatureMismatch(t *testing.T) { + + t.Parallel() + + handler := &testContractHandler{} + + contractsAddress := flow.BytesToAddress([]byte{0x1}) + + transactionEnvironment := newEVMTransactionEnvironment(handler, contractsAddress) + scriptEnvironment := newEVMScriptEnvironment(handler, contractsAddress) + + rt := runtime.NewInterpreterRuntime(runtime.Config{}) + + script := []byte(` + import EVM from 0x1 + + access(all) + fun main(data: [UInt8]): Bool { + // The data was encoded for the function "withdraw(address,uint256)", + // but we pass a different function signature + let values = EVM.decodeABIWithSignature( + "deposit(uint256, address)", + types: [Type(), Type()], + data: data + ) + + return true + } + `) + + accountCodes := map[common.Location][]byte{} + var events []cadence.Event + + runtimeInterface := &TestRuntimeInterface{ + Storage: NewTestLedger(nil, nil), + OnGetSigningAccounts: func() ([]runtime.Address, error) { + return []runtime.Address{runtime.Address(contractsAddress)}, nil + }, + OnResolveLocation: SingleIdentifierLocationResolver(t), + OnUpdateAccountContractCode: func(location common.AddressLocation, code []byte) error { + accountCodes[location] = code + return nil + }, + OnGetAccountContractCode: func(location common.AddressLocation) (code []byte, err error) { + code = accountCodes[location] + return code, nil + }, + OnEmitEvent: func(event cadence.Event) error { + events = append(events, event) + return nil + }, + OnDecodeArgument: func(b []byte, t cadence.Type) (cadence.Value, error) { + return json.Decode(nil, b) + }, + OnHash: func( + data []byte, + tag string, + hashAlgorithm runtime.HashAlgorithm, + ) ([]byte, error) { + return crypto.Keccak256(data), nil + }, + } + + nextTransactionLocation := NewTransactionLocationGenerator() + nextScriptLocation := NewScriptLocationGenerator() + + // Deploy contracts + + deployContracts( + t, + rt, + contractsAddress, + runtimeInterface, + transactionEnvironment, + nextTransactionLocation, + true, + ) + + // Run script + abiBytes := []byte{ + 0xf3, 0xfe, 0xf3, 0xa3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x7a, 0x58, 0xc0, 0xbe, 0x72, 0xbe, 0x21, 0x8b, 0x41, + 0xc6, 0x8, 0xb7, 0xfe, 0x7c, 0x5b, 0xb6, 0x30, 0x73, 0x6c, 0x71, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xfa, + } + cdcBytes := make([]cadence.Value, 0) + for _, bt := range abiBytes { + cdcBytes = append(cdcBytes, cadence.UInt8(bt)) + } + encodedABI := cadence.NewArray( + cdcBytes, + ).WithType(cadence.NewVariableSizedArrayType(cadence.TheUInt8Type)) + + _, err := rt.ExecuteScript( + runtime.Script{ + Source: script, + Arguments: EncodeArgs([]cadence.Value{ + encodedABI, + }), + }, + runtime.Context{ + Interface: runtimeInterface, + Environment: scriptEnvironment, + Location: nextScriptLocation(), + }, + ) + require.Error(t, err) + assert.ErrorContains(t, err, "panic: signature mismatch") +} + func TestEVMAddressConstructionAndReturn(t *testing.T) { t.Parallel() @@ -309,6 +2594,7 @@ func TestEVMAddressConstructionAndReturn(t *testing.T) { runtimeInterface, transactionEnvironment, nextTransactionLocation, + true, ) // Run script @@ -401,6 +2687,7 @@ func TestBalanceConstructionAndReturn(t *testing.T) { runtimeInterface, transactionEnvironment, nextTransactionLocation, + false, ) // Run script @@ -531,6 +2818,7 @@ func TestEVMRun(t *testing.T) { runtimeInterface, transactionEnvironment, nextTransactionLocation, + false, ) // Run script @@ -618,6 +2906,7 @@ func TestEVMCreateBridgedAccount(t *testing.T) { runtimeInterface, transactionEnvironment, nextTransactionLocation, + false, ) // Run script @@ -676,7 +2965,7 @@ func TestBridgedAccountCall(t *testing.T) { assert.Equal(t, types.Address{2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, toAddress) assert.Equal(t, types.Data{4, 5, 6}, data) assert.Equal(t, types.GasLimit(9999), limit) - assert.Equal(t, types.Balance(expectedBalance), balance) + assert.Equal(t, types.NewBalanceFromUFix64(expectedBalance), balance) return types.Data{3, 1, 4} }, @@ -748,6 +3037,7 @@ func TestBridgedAccountCall(t *testing.T) { runtimeInterface, transactionEnvironment, nextTransactionLocation, + false, ) // Run script @@ -785,7 +3075,7 @@ func TestEVMAddressDeposit(t *testing.T) { handler := &testContractHandler{ accountByAddress: func(fromAddress types.Address, isAuthorized bool) types.Account { - assert.Equal(t, types.Address{2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, fromAddress) + assert.Equal(t, types.Address{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, fromAddress) assert.False(t, isAuthorized) return &testFlowAccount{ @@ -794,7 +3084,7 @@ func TestEVMAddressDeposit(t *testing.T) { deposited = true assert.Equal( t, - types.Balance(expectedBalance), + types.NewBalanceFromUFix64(expectedBalance), vault.Balance(), ) }, @@ -821,10 +3111,9 @@ func TestEVMAddressDeposit(t *testing.T) { let vault <- minter.mintTokens(amount: 1.23) destroy minter - let address = EVM.EVMAddress( - bytes: [2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - ) - address.deposit(from: <-vault) + let bridgedAccount <- EVM.createBridgedAccount() + bridgedAccount.deposit(from: <-vault) + destroy bridgedAccount } `) @@ -866,6 +3155,7 @@ func TestEVMAddressDeposit(t *testing.T) { runtimeInterface, transactionEnvironment, nextTransactionLocation, + false, ) // Run script @@ -911,13 +3201,13 @@ func TestBridgedAccountWithdraw(t *testing.T) { deposit: func(vault *types.FLOWTokenVault) { deposited = true assert.Equal(t, - types.Balance(expectedDepositBalance), + types.NewBalanceFromUFix64(expectedDepositBalance), vault.Balance(), ) }, withdraw: func(balance types.Balance) *types.FLOWTokenVault { assert.Equal(t, - types.Balance(expectedWithdrawBalance), + types.NewBalanceFromUFix64(expectedWithdrawBalance), balance, ) withdrew = true @@ -945,7 +3235,7 @@ func TestBridgedAccountWithdraw(t *testing.T) { destroy minter let bridgedAccount <- EVM.createBridgedAccount() - bridgedAccount.address().deposit(from: <-vault) + bridgedAccount.deposit(from: <-vault) let vault2 <- bridgedAccount.withdraw(balance: EVM.Balance(flow: 1.23)) let balance = vault2.balance @@ -994,6 +3284,7 @@ func TestBridgedAccountWithdraw(t *testing.T) { runtimeInterface, transactionEnvironment, nextTransactionLocation, + false, ) // Run script @@ -1039,7 +3330,7 @@ func TestBridgedAccountDeploy(t *testing.T) { deployed = true assert.Equal(t, types.Code{4, 5, 6}, code) assert.Equal(t, types.GasLimit(9999), limit) - assert.Equal(t, types.Balance(expectedBalance), balance) + assert.Equal(t, types.NewBalanceFromUFix64(expectedBalance), balance) return handler.AllocateAddress() }, @@ -1107,6 +3398,7 @@ func TestBridgedAccountDeploy(t *testing.T) { runtimeInterface, transactionEnvironment, nextTransactionLocation, + false, ) // Run script @@ -1166,7 +3458,7 @@ func TestEVMAccountBalance(t *testing.T) { return &testFlowAccount{ address: fromAddress, balance: func() types.Balance { - return types.Balance(expectedBalanceValue) + return types.NewBalanceFromUFix64(expectedBalanceValue) }, } }, @@ -1227,6 +3519,7 @@ func TestEVMAccountBalance(t *testing.T) { runtimeInterface, transactionEnvironment, nextTransactionLocation, + false, ) // Run script @@ -1246,3 +3539,111 @@ func TestEVMAccountBalance(t *testing.T) { require.NoError(t, err) require.Equal(t, expectedBalance, actual) } + +func TestEVMAccountBalanceForABIOnlyContract(t *testing.T) { + + t.Parallel() + + contractsAddress := flow.BytesToAddress([]byte{0x1}) + + expectedBalanceValue, err := cadence.NewUFix64FromParts(1, 1337000) + require.NoError(t, err) + + handler := &testContractHandler{ + flowTokenAddress: common.Address(contractsAddress), + accountByAddress: func(fromAddress types.Address, isAuthorized bool) types.Account { + assert.Equal(t, types.Address{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, fromAddress) + assert.False(t, isAuthorized) + + return &testFlowAccount{ + address: fromAddress, + balance: func() types.Balance { + return types.NewBalanceFromUFix64(expectedBalanceValue) + }, + } + }, + } + + transactionEnvironment := newEVMTransactionEnvironment(handler, contractsAddress) + scriptEnvironment := newEVMScriptEnvironment(handler, contractsAddress) + + rt := runtime.NewInterpreterRuntime(runtime.Config{}) + + script := []byte(` + import EVM from 0x1 + + access(all) + fun main(): EVM.Balance { + let bridgedAccount <- EVM.createBridgedAccount() + let balance = bridgedAccount.balance() + destroy bridgedAccount + return balance + } + `) + + accountCodes := map[common.Location][]byte{} + var events []cadence.Event + + runtimeInterface := &TestRuntimeInterface{ + Storage: NewTestLedger(nil, nil), + OnGetSigningAccounts: func() ([]runtime.Address, error) { + return []runtime.Address{runtime.Address(contractsAddress)}, nil + }, + OnResolveLocation: SingleIdentifierLocationResolver(t), + OnUpdateAccountContractCode: func(location common.AddressLocation, code []byte) error { + accountCodes[location] = code + return nil + }, + OnGetAccountContractCode: func(location common.AddressLocation) (code []byte, err error) { + code = accountCodes[location] + return code, nil + }, + OnEmitEvent: func(event cadence.Event) error { + events = append(events, event) + return nil + }, + OnDecodeArgument: func(b []byte, t cadence.Type) (cadence.Value, error) { + return json.Decode(nil, b) + }, + } + + nextTransactionLocation := NewTransactionLocationGenerator() + nextScriptLocation := NewScriptLocationGenerator() + + // Deploy contracts + + deployContracts( + t, + rt, + contractsAddress, + runtimeInterface, + transactionEnvironment, + nextTransactionLocation, + true, + ) + + // Run script + + _, err = rt.ExecuteScript( + runtime.Script{ + Source: script, + }, + runtime.Context{ + Interface: runtimeInterface, + Environment: scriptEnvironment, + Location: nextScriptLocation(), + }, + ) + require.Error(t, err) + + assert.ErrorContains( + t, + err, + "error: cannot find type in this scope: `EVM.Balance`", + ) + assert.ErrorContains( + t, + err, + "error: value of type `EVM` has no member `createBridgedAccount`", + ) +} diff --git a/fvm/evm/testutils/accounts.go b/fvm/evm/testutils/accounts.go index 34487b8e91f..8df4b712508 100644 --- a/fvm/evm/testutils/accounts.go +++ b/fvm/evm/testutils/accounts.go @@ -16,7 +16,6 @@ import ( "github.com/onflow/atree" "github.com/onflow/flow-go/fvm/evm/emulator" - "github.com/onflow/flow-go/fvm/evm/emulator/database" "github.com/onflow/flow-go/fvm/evm/types" "github.com/onflow/flow-go/model/flow" ) @@ -126,11 +125,7 @@ func FundAndGetEOATestAccount(t testing.TB, led atree.Ledger, flowEVMRootAddress account := GetTestEOAAccount(t, EOATestAccount1KeyHex) // fund account - db, err := database.NewDatabase(led, flowEVMRootAddress) - require.NoError(t, err) - - e := emulator.NewEmulator(db) - require.NoError(t, err) + e := emulator.NewEmulator(led, flowEVMRootAddress) blk, err := e.NewBlockView(types.NewDefaultBlockContext(2)) require.NoError(t, err) diff --git a/fvm/evm/testutils/backend.go b/fvm/evm/testutils/backend.go index 308b46f41e2..c25a3626b0c 100644 --- a/fvm/evm/testutils/backend.go +++ b/fvm/evm/testutils/backend.go @@ -1,6 +1,7 @@ package testutils import ( + "crypto/rand" "encoding/binary" "fmt" "math" @@ -9,11 +10,13 @@ import ( "github.com/onflow/atree" "github.com/onflow/cadence" jsoncdc "github.com/onflow/cadence/encoding/json" + "github.com/onflow/cadence/runtime" "github.com/onflow/cadence/runtime/common" "github.com/stretchr/testify/require" "golang.org/x/exp/maps" "github.com/onflow/flow-go/fvm/environment" + "github.com/onflow/flow-go/fvm/evm/types" "github.com/onflow/flow-go/fvm/meter" "github.com/onflow/flow-go/model/flow" ) @@ -30,9 +33,11 @@ func RunWithTestFlowEVMRootAddress(t testing.TB, backend atree.Ledger, f func(fl func RunWithTestBackend(t testing.TB, f func(*TestBackend)) { tb := &TestBackend{ - TestValueStore: GetSimpleValueStore(), - testEventEmitter: getSimpleEventEmitter(), - testMeter: getSimpleMeter(), + TestValueStore: GetSimpleValueStore(), + testEventEmitter: getSimpleEventEmitter(), + testMeter: getSimpleMeter(), + TestBlockInfo: &TestBlockInfo{}, + TestRandomGenerator: getSimpleRandomGenerator(), } f(tb) } @@ -52,24 +57,34 @@ func fullKey(owner, key []byte) string { func GetSimpleValueStore() *TestValueStore { data := make(map[string][]byte) allocator := make(map[string]uint64) - + bytesRead := 0 + bytesWritten := 0 return &TestValueStore{ GetValueFunc: func(owner, key []byte) ([]byte, error) { - return data[fullKey(owner, key)], nil + fk := fullKey(owner, key) + value := data[fk] + bytesRead += len(fk) + len(value) + return value, nil }, SetValueFunc: func(owner, key, value []byte) error { - data[fullKey(owner, key)] = value + fk := fullKey(owner, key) + data[fk] = value + bytesWritten += len(fk) + len(value) return nil }, ValueExistsFunc: func(owner, key []byte) (bool, error) { - return len(data[fullKey(owner, key)]) > 0, nil - + fk := fullKey(owner, key) + value := data[fk] + bytesRead += len(fk) + len(value) + return len(value) > 0, nil }, AllocateStorageIndexFunc: func(owner []byte) (atree.StorageIndex, error) { index := allocator[string(owner)] var data [8]byte allocator[string(owner)] = index + 1 binary.BigEndian.PutUint64(data[:], index) + bytesRead += len(owner) + 8 + bytesWritten += len(owner) + 8 return atree.StorageIndex(data), nil }, TotalStorageSizeFunc: func() int { @@ -82,9 +97,19 @@ func GetSimpleValueStore() *TestValueStore { } return size }, + TotalBytesReadFunc: func() int { + return bytesRead + }, + TotalBytesWrittenFunc: func() int { + return bytesWritten + }, TotalStorageItemsFunc: func() int { return len(maps.Keys(data)) + len(maps.Keys(allocator)) }, + ResetStatsFunc: func() { + bytesRead = 0 + bytesWritten = 0 + }, } } @@ -133,8 +158,12 @@ type TestBackend struct { *TestValueStore *testMeter *testEventEmitter + *TestBlockInfo + *TestRandomGenerator } +var _ types.Backend = &TestBackend{} + func (tb *TestBackend) TotalStorageSize() int { if tb.TotalStorageSizeFunc == nil { panic("method not set") @@ -155,7 +184,10 @@ type TestValueStore struct { ValueExistsFunc func(owner, key []byte) (bool, error) AllocateStorageIndexFunc func(owner []byte) (atree.StorageIndex, error) TotalStorageSizeFunc func() int + TotalBytesReadFunc func() int + TotalBytesWrittenFunc func() int TotalStorageItemsFunc func() int + ResetStatsFunc func() } var _ environment.ValueStore = &TestValueStore{} @@ -188,6 +220,20 @@ func (vs *TestValueStore) AllocateStorageIndex(owner []byte) (atree.StorageIndex return vs.AllocateStorageIndexFunc(owner) } +func (vs *TestValueStore) TotalBytesRead() int { + if vs.TotalBytesReadFunc == nil { + panic("method not set") + } + return vs.TotalBytesReadFunc() +} + +func (vs *TestValueStore) TotalBytesWritten() int { + if vs.TotalBytesWrittenFunc == nil { + panic("method not set") + } + return vs.TotalBytesWrittenFunc() +} + func (vs *TestValueStore) TotalStorageSize() int { if vs.TotalStorageSizeFunc == nil { panic("method not set") @@ -202,6 +248,13 @@ func (vs *TestValueStore) TotalStorageItems() int { return vs.TotalStorageItemsFunc() } +func (vs *TestValueStore) ResetStats() { + if vs.ResetStatsFunc == nil { + panic("method not set") + } + vs.ResetStatsFunc() +} + type testMeter struct { meterComputation func(common.ComputationKind, uint) error hasComputationCapacity func(common.ComputationKind, uint) bool @@ -332,3 +385,48 @@ func (vs *testEventEmitter) Reset() { } vs.reset() } + +type TestBlockInfo struct { + GetCurrentBlockHeightFunc func() (uint64, error) + GetBlockAtHeightFunc func(height uint64) (runtime.Block, bool, error) +} + +var _ environment.BlockInfo = &TestBlockInfo{} + +// GetCurrentBlockHeight returns the current block height. +func (tb *TestBlockInfo) GetCurrentBlockHeight() (uint64, error) { + if tb.GetCurrentBlockHeightFunc == nil { + panic("GetCurrentBlockHeight method is not set") + } + return tb.GetCurrentBlockHeightFunc() +} + +// GetBlockAtHeight returns the block at the given height. +func (tb *TestBlockInfo) GetBlockAtHeight(height uint64) (runtime.Block, bool, error) { + if tb.GetBlockAtHeightFunc == nil { + panic("GetBlockAtHeight method is not set") + } + return tb.GetBlockAtHeightFunc(height) +} + +type TestRandomGenerator struct { + ReadRandomFunc func([]byte) error +} + +var _ environment.RandomGenerator = &TestRandomGenerator{} + +func (t *TestRandomGenerator) ReadRandom(buffer []byte) error { + if t.ReadRandomFunc == nil { + panic("ReadRandomFunc method is not set") + } + return t.ReadRandomFunc(buffer) +} + +func getSimpleRandomGenerator() *TestRandomGenerator { + return &TestRandomGenerator{ + ReadRandomFunc: func(buffer []byte) error { + _, err := rand.Read(buffer) + return err + }, + } +} diff --git a/fvm/evm/testutils/contract.go b/fvm/evm/testutils/contract.go index 78316f44cd2..5e9f000b61e 100644 --- a/fvm/evm/testutils/contract.go +++ b/fvm/evm/testutils/contract.go @@ -14,7 +14,6 @@ import ( "github.com/onflow/atree" "github.com/onflow/flow-go/fvm/evm/emulator" - "github.com/onflow/flow-go/fvm/evm/emulator/database" "github.com/onflow/flow-go/fvm/evm/types" "github.com/onflow/flow-go/model/flow" ) @@ -39,10 +38,12 @@ func (tc *TestContract) SetDeployedAt(deployedAt types.Address) { } func GetStorageTestContract(tb testing.TB) *TestContract { - byteCodes, err := hex.DecodeString("608060405261022c806100136000396000f3fe608060405234801561001057600080fd5b50600436106100575760003560e01c80632e64cec11461005c57806348b151661461007a57806357e871e7146100985780636057361d146100b657806385df51fd146100d2575b600080fd5b610064610102565b6040516100719190610149565b60405180910390f35b61008261010b565b60405161008f9190610149565b60405180910390f35b6100a0610113565b6040516100ad9190610149565b60405180910390f35b6100d060048036038101906100cb9190610195565b61011b565b005b6100ec60048036038101906100e79190610195565b610125565b6040516100f991906101db565b60405180910390f35b60008054905090565b600042905090565b600043905090565b8060008190555050565b600081409050919050565b6000819050919050565b61014381610130565b82525050565b600060208201905061015e600083018461013a565b92915050565b600080fd5b61017281610130565b811461017d57600080fd5b50565b60008135905061018f81610169565b92915050565b6000602082840312156101ab576101aa610164565b5b60006101b984828501610180565b91505092915050565b6000819050919050565b6101d5816101c2565b82525050565b60006020820190506101f060008301846101cc565b9291505056fea26469706673582212203ee61567a25f0b1848386ae6b8fdbd7733c8a502c83b5ed305b921b7933f4e8164736f6c63430008120033") + byteCodes, err := hex.DecodeString("6080604052610249806100115f395ff3fe608060405234801561000f575f80fd5b5060043610610060575f3560e01c80632e64cec11461006457806348b151661461008257806357e871e7146100a05780635ec01e4d146100be5780636057361d146100dc57806385df51fd146100f8575b5f80fd5b61006c610128565b6040516100799190610170565b60405180910390f35b61008a610130565b6040516100979190610170565b60405180910390f35b6100a8610137565b6040516100b59190610170565b60405180910390f35b6100c661013e565b6040516100d39190610170565b60405180910390f35b6100f660048036038101906100f191906101b7565b610145565b005b610112600480360381019061010d91906101b7565b61014e565b60405161011f91906101fa565b60405180910390f35b5f8054905090565b5f42905090565b5f43905090565b5f44905090565b805f8190555050565b5f81409050919050565b5f819050919050565b61016a81610158565b82525050565b5f6020820190506101835f830184610161565b92915050565b5f80fd5b61019681610158565b81146101a0575f80fd5b50565b5f813590506101b18161018d565b92915050565b5f602082840312156101cc576101cb610189565b5b5f6101d9848285016101a3565b91505092915050565b5f819050919050565b6101f4816101e2565b82525050565b5f60208201905061020d5f8301846101eb565b9291505056fea26469706673582212204e444dbbee71334344ae4d9fe1b45944b0aff9ffd6b8ac8a33cc0f31c6e21d6664736f6c63430008170033") require.NoError(tb, err) return &TestContract{ Code: ` + pragma solidity >=0.7.0 <0.9.0; + contract Storage { uint256 number; constructor() payable { @@ -57,11 +58,15 @@ func GetStorageTestContract(tb testing.TB) *TestContract { return block.number; } function blockTime() public view returns (uint) { - return block.timestamp; + return block.timestamp; } function blockHash(uint num) public view returns (bytes32) { return blockhash(num); } + + function random() public view returns (uint256) { + return block.prevrandao; + } } `, @@ -117,6 +122,19 @@ func GetStorageTestContract(tb testing.TB) *TestContract { "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "random", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "retrieve", @@ -387,10 +405,7 @@ func RunWithDeployedContract(t testing.TB, tc *TestContract, led atree.Ledger, f func DeployContract(t testing.TB, tc *TestContract, led atree.Ledger, flowEVMRootAddress flow.Address) { // deploy contract - db, err := database.NewDatabase(led, flowEVMRootAddress) - require.NoError(t, err) - - e := emulator.NewEmulator(db) + e := emulator.NewEmulator(led, flowEVMRootAddress) blk, err := e.NewBlockView(types.NewDefaultBlockContext(2)) require.NoError(t, err) diff --git a/fvm/evm/testutils/database.go b/fvm/evm/testutils/database.go deleted file mode 100644 index fcc4ef3d117..00000000000 --- a/fvm/evm/testutils/database.go +++ /dev/null @@ -1,117 +0,0 @@ -package testutils - -import ( - gethCommon "github.com/ethereum/go-ethereum/common" - gethDB "github.com/ethereum/go-ethereum/ethdb" - - "github.com/onflow/flow-go/fvm/evm/types" -) - -type TestDatabase struct { - GetFunc func(key []byte) ([]byte, error) - HasFunc func(key []byte) (bool, error) - PutFunc func(key []byte, value []byte) error - DeleteFunc func(key []byte) error - StatFunc func(property string) (string, error) - NewBatchFunc func() gethDB.Batch - NewBatchWithSizeFunc func(size int) gethDB.Batch - NewIteratorFunc func(prefix []byte, start []byte) gethDB.Iterator - CompactFunc func(start []byte, limit []byte) error - NewSnapshotFunc func() (gethDB.Snapshot, error) - CloseFunc func() error - GetRootHashFunc func() (gethCommon.Hash, error) - CommitFunc func(roothash gethCommon.Hash) error -} - -var _ types.Database = &TestDatabase{} - -func (db *TestDatabase) Get(key []byte) ([]byte, error) { - if db.GetFunc == nil { - panic("method not set") - } - return db.GetFunc(key) -} - -func (db *TestDatabase) Has(key []byte) (bool, error) { - if db.HasFunc == nil { - panic("method not set") - } - return db.HasFunc(key) -} - -func (db *TestDatabase) Put(key []byte, value []byte) error { - if db.PutFunc == nil { - panic("method not set") - } - return db.PutFunc(key, value) -} - -func (db *TestDatabase) Delete(key []byte) error { - if db.DeleteFunc == nil { - panic("method not set") - } - return db.DeleteFunc(key) -} - -func (db *TestDatabase) Commit(root gethCommon.Hash) error { - if db.CommitFunc == nil { - panic("method not set") - } - return db.CommitFunc(root) -} - -func (db *TestDatabase) GetRootHash() (gethCommon.Hash, error) { - if db.GetRootHashFunc == nil { - panic("method not set") - } - return db.GetRootHashFunc() -} - -func (db *TestDatabase) Stat(property string) (string, error) { - if db.StatFunc == nil { - panic("method not set") - } - return db.StatFunc(property) -} - -func (db *TestDatabase) NewBatch() gethDB.Batch { - if db.NewBatchFunc == nil { - panic("method not set") - } - return db.NewBatchFunc() -} - -func (db *TestDatabase) NewBatchWithSize(size int) gethDB.Batch { - if db.NewBatchWithSizeFunc == nil { - panic("method not set") - } - return db.NewBatchWithSizeFunc(size) -} - -func (db *TestDatabase) NewIterator(prefix []byte, start []byte) gethDB.Iterator { - if db.NewIteratorFunc == nil { - panic("method not set") - } - return db.NewIteratorFunc(prefix, start) -} - -func (db *TestDatabase) Compact(start []byte, limit []byte) error { - if db.CompactFunc == nil { - panic("method not set") - } - return db.CompactFunc(start, limit) -} - -func (db *TestDatabase) NewSnapshot() (gethDB.Snapshot, error) { - if db.NewSnapshotFunc == nil { - panic("method not set") - } - return db.NewSnapshotFunc() -} - -func (db *TestDatabase) Close() error { - if db.CloseFunc == nil { - panic("method not set") - } - return db.CloseFunc() -} diff --git a/fvm/evm/testutils/emulator.go b/fvm/evm/testutils/emulator.go index 5f7f2ce3068..0cdc0d4d93c 100644 --- a/fvm/evm/testutils/emulator.go +++ b/fvm/evm/testutils/emulator.go @@ -1,14 +1,9 @@ package testutils import ( - cryptoRand "crypto/rand" "math/big" - "math/rand" - "testing" - gethCommon "github.com/ethereum/go-ethereum/common" gethTypes "github.com/ethereum/go-ethereum/core/types" - "github.com/stretchr/testify/require" "github.com/onflow/flow-go/fvm/evm/types" ) @@ -72,49 +67,3 @@ func (em *TestEmulator) RunTransaction(tx *gethTypes.Transaction) (*types.Result } return em.RunTransactionFunc(tx) } - -func RandomCommonHash(t testing.TB) gethCommon.Hash { - ret := gethCommon.Hash{} - _, err := cryptoRand.Read(ret[:gethCommon.HashLength]) - require.NoError(t, err) - return ret -} - -func RandomBigInt(limit int64) *big.Int { - return big.NewInt(rand.Int63n(limit) + 1) -} - -func RandomAddress(t testing.TB) types.Address { - return types.NewAddress(RandomCommonAddress(t)) -} - -func RandomCommonAddress(t testing.TB) gethCommon.Address { - ret := gethCommon.Address{} - _, err := cryptoRand.Read(ret[:gethCommon.AddressLength]) - require.NoError(t, err) - return ret -} - -func RandomGas(limit int64) uint64 { - return uint64(rand.Int63n(limit) + 1) -} - -func RandomData(t testing.TB) []byte { - // byte size [1, 100] - size := rand.Intn(100) + 1 - ret := make([]byte, size) - _, err := cryptoRand.Read(ret[:]) - require.NoError(t, err) - return ret -} - -func GetRandomLogFixture(t testing.TB) *gethTypes.Log { - return &gethTypes.Log{ - Address: RandomCommonAddress(t), - Topics: []gethCommon.Hash{ - RandomCommonHash(t), - RandomCommonHash(t), - }, - Data: RandomData(t), - } -} diff --git a/fvm/evm/testutils/misc.go b/fvm/evm/testutils/misc.go new file mode 100644 index 00000000000..50ecf81646c --- /dev/null +++ b/fvm/evm/testutils/misc.go @@ -0,0 +1,60 @@ +package testutils + +import ( + cryptoRand "crypto/rand" + "math/big" + "math/rand" + "testing" + + gethCommon "github.com/ethereum/go-ethereum/common" + gethTypes "github.com/ethereum/go-ethereum/core/types" + "github.com/stretchr/testify/require" + + "github.com/onflow/flow-go/fvm/evm/types" +) + +func RandomCommonHash(t testing.TB) gethCommon.Hash { + ret := gethCommon.Hash{} + _, err := cryptoRand.Read(ret[:gethCommon.HashLength]) + require.NoError(t, err) + return ret +} + +func RandomBigInt(limit int64) *big.Int { + return big.NewInt(rand.Int63n(limit) + 1) +} + +func RandomAddress(t testing.TB) types.Address { + return types.NewAddress(RandomCommonAddress(t)) +} + +func RandomCommonAddress(t testing.TB) gethCommon.Address { + ret := gethCommon.Address{} + _, err := cryptoRand.Read(ret[:gethCommon.AddressLength]) + require.NoError(t, err) + return ret +} + +func RandomGas(limit int64) uint64 { + return uint64(rand.Int63n(limit) + 1) +} + +func RandomData(t testing.TB) []byte { + // byte size [1, 100] + size := rand.Intn(100) + 1 + ret := make([]byte, size) + _, err := cryptoRand.Read(ret[:]) + require.NoError(t, err) + return ret +} + +func GetRandomLogFixture(t testing.TB) *gethTypes.Log { + return &gethTypes.Log{ + Address: RandomCommonAddress(t), + Topics: []gethCommon.Hash{ + RandomCommonHash(t), + RandomCommonHash(t), + }, + Data: RandomData(t), + } +} diff --git a/fvm/evm/types/balance.go b/fvm/evm/types/balance.go index d4ea1e46faa..1de293ae851 100644 --- a/fvm/evm/types/balance.go +++ b/fvm/evm/types/balance.go @@ -1,67 +1,92 @@ package types import ( - "encoding/binary" + "fmt" + "math" "math/big" "github.com/onflow/cadence" + "github.com/onflow/cadence/fixedpoint" ) var ( - SmallestAcceptableBalanceValueInAttoFlow = new(big.Int).SetInt64(1e10) - OneFlowInAttoFlow = new(big.Int).SetInt64(1e18) + AttoScale = 18 + UFixedScale = fixedpoint.Fix64Scale + UFixedToAttoConversionScale = AttoScale - UFixedScale + UFixToAttoConversionMultiplier = new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(UFixedToAttoConversionScale)), nil) + + OneFlowInUFix64 = cadence.UFix64(uint64(math.Pow(10, float64(UFixedScale)))) + OneFlowBalance = Balance(new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(AttoScale)), nil)) + EmptyBalance = Balance(new(big.Int)) ) // Balance represents the balance of an address -// in the evm environment, balances are kept in attoflow (1e10^-18 flow), +// in the evm environment (Flow EVM), balances are kept in attoflow (1e10^-18 flow); // the smallest denomination of FLOW token (similar to how Wei is used to store Eth) -// But on the FLOW Vaults, we use Cadence.UFix64 to store values in Flow. -// this could result in accidental conversion mistakes, the balance object here would -// do the conversions and does appropriate checks. -// -// For example the smallest unit of Flow token that a FlowVault could store is 1e10^-8, -// so transfering smaller values (or values with smalls fractions) could result in loss in -// conversion. The balance object checks it to prevent invalid balance. -// This means that values smaller than 1e10^-8 flow could not be bridged between FVM and Flow EVM. -type Balance cadence.UFix64 +// But A Cadence FLOW Vault uses a Cadence.UFix64 to store values in Flow, which means +// 1e18^-8 is the smallest value that can be stored on the vault. +// The balance here considers the highest precision (attoflow) but utility +// function has been provided for conversion from/to UFix64 to prevent accidental +// conversion errors and dealing with rounding errors. +type Balance *big.Int -// ToAttoFlow converts the balance into AttoFlow -func (b Balance) ToAttoFlow() *big.Int { - return new(big.Int).Mul(new(big.Int).SetUint64(uint64(b)), SmallestAcceptableBalanceValueInAttoFlow) +// NewBalanceconstructs a new balance from an atto flow value +func NewBalance(inp *big.Int) Balance { + return Balance(inp) } -// Sub subtract the other balance from this balance -func (b Balance) Sub(other Balance) Balance { - // no need to check for underflow, as go does it - return Balance(uint64(b) - uint64(other)) +// NewBalanceFromUFix64 constructs a new balance from flow value (how its stored in Cadence Flow) +func NewBalanceFromUFix64(inp cadence.UFix64) Balance { + return new(big.Int).Mul( + new(big.Int).SetUint64(uint64(inp)), + UFixToAttoConversionMultiplier) } -// Add adds the other balance from this balance -func (b Balance) Add(other Balance) Balance { - // no need to check for overflow, as go does it - return Balance(uint64(b) + uint64(other)) +// CopyBalance creates a copy of the balance +func CopyBalance(inp Balance) Balance { + return Balance(new(big.Int).Set(inp)) } -// Encode encodes the balance into byte slice -func (b Balance) Encode() []byte { - encoded := make([]byte, 8) - binary.BigEndian.PutUint64(encoded, b.ToAttoFlow().Uint64()) - return encoded +// BalanceToBigInt convert balance into big int +func BalanceToBigInt(bal Balance) *big.Int { + return (*big.Int)(bal) +} + +// ConvertBalanceToUFix64 casts the balance into a UFix64, +// +// Warning! The smallest unit of Flow token that a FlowVault (Cadence) could store is 1e10^-8, +// so transfering smaller values (or values with smalls fractions) could result in loss in +// conversion. The rounded flag should be used to prevent loss of assets. +func ConvertBalanceToUFix64(bal Balance) (value cadence.UFix64, roundedOff bool, err error) { + converted := new(big.Int).Div(bal, UFixToAttoConversionMultiplier) + if !converted.IsUint64() { + // this should never happen + err = fmt.Errorf("balance can't be casted to a uint64") + } + return cadence.UFix64(converted.Uint64()), BalanceConvertionToUFix64ProneToRoundingError(bal), err + } -// DecodeBalance decodes a balance from an encoded byte slice -func DecodeBalance(encoded []byte) (Balance, error) { - balance := new(big.Int) - return NewBalanceFromAttoFlow(balance.SetUint64(binary.BigEndian.Uint64(encoded))) +// BalanceConvertionToUFix64ProneToRoundingError returns true +// if casting to UFix64 could result in rounding error +func BalanceConvertionToUFix64ProneToRoundingError(bal Balance) bool { + return new(big.Int).Mod(bal, UFixToAttoConversionMultiplier).BitLen() != 0 } -// NewBalanceFromAttoFlow constructs a new balance from atto flow value -func NewBalanceFromAttoFlow(inp *big.Int) (Balance, error) { - if new(big.Int).Mod(inp, SmallestAcceptableBalanceValueInAttoFlow).Cmp(big.NewInt(0)) != 0 { - return 0, ErrBalanceConversion +// Subtract balance 2 from balance 1 and returns the result as a new balance +func SubBalance(bal1 Balance, bal2 Balance) (Balance, error) { + if (*big.Int)(bal1).Cmp(bal2) == -1 { + return nil, ErrInsufficientBalance } + return new(big.Int).Sub(bal1, bal2), nil +} + +// AddBalance balance 2 to balance 1 and returns the result as a new balance +func AddBalance(bal1 Balance, bal2 Balance) (Balance, error) { + return new(big.Int).Add(bal1, bal2), nil +} - // we only need to divide by 10 given we already have 8 as factor - converted := new(big.Int).Div(inp, SmallestAcceptableBalanceValueInAttoFlow) - return Balance(cadence.UFix64(converted.Uint64())), nil +// MakeABalanceInFlow makes a balance object that has `amount` Flow Token in it +func MakeABalanceInFlow(amount uint64) Balance { + return NewBalance(new(big.Int).Mul(OneFlowBalance, new(big.Int).SetUint64(amount))) } diff --git a/fvm/evm/types/balance_test.go b/fvm/evm/types/balance_test.go index 9b92fc92b46..c13d7d0d870 100644 --- a/fvm/evm/types/balance_test.go +++ b/fvm/evm/types/balance_test.go @@ -13,28 +13,28 @@ import ( func TestBalance(t *testing.T) { // test attoflow to flow - - bal, err := types.NewBalanceFromAttoFlow(types.OneFlowInAttoFlow) - require.NoError(t, err) - - conv := bal.ToAttoFlow() - require.Equal(t, types.OneFlowInAttoFlow, conv) - - // encoding decoding - ret, err := types.DecodeBalance(bal.Encode()) - require.NoError(t, err) - require.Equal(t, bal, ret) + bal := types.OneFlowBalance + require.Equal(t, bal, types.NewBalanceFromUFix64(types.OneFlowInUFix64)) // 100.0002 Flow u, err := cadence.NewUFix64("100.0002") require.NoError(t, err) require.Equal(t, "100.00020000", u.String()) - bb := types.Balance(u).ToAttoFlow() - require.Equal(t, "100000200000000000000", bb.String()) - - // invalid conversion - _, err = types.NewBalanceFromAttoFlow(big.NewInt(1)) - require.Error(t, err) + bb := types.NewBalanceFromUFix64(u) + require.Equal(t, "100000200000000000000", types.BalanceToBigInt(bb).String()) + require.False(t, types.BalanceConvertionToUFix64ProneToRoundingError(bb)) + bret, roundedOff, err := types.ConvertBalanceToUFix64(bb) + require.NoError(t, err) + require.Equal(t, u, bret) + require.False(t, roundedOff) + // rounded off flag + bal = types.NewBalance(big.NewInt(1)) + require.NoError(t, err) + require.True(t, types.BalanceConvertionToUFix64ProneToRoundingError(bal)) + bret, roundedOff, err = types.ConvertBalanceToUFix64(bal) + require.NoError(t, err) + require.Equal(t, cadence.UFix64(0), bret) + require.True(t, roundedOff) } diff --git a/fvm/evm/types/block.go b/fvm/evm/types/block.go index 6c20c6a4d90..9ec08551104 100644 --- a/fvm/evm/types/block.go +++ b/fvm/evm/types/block.go @@ -1,6 +1,8 @@ package types import ( + "math/big" + gethCommon "github.com/ethereum/go-ethereum/common" gethTypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/rlp" @@ -15,11 +17,8 @@ type Block struct { // Height returns the height of this block Height uint64 - // holds the total amount of the native token deposited in the evm side. - TotalSupply uint64 - - // StateRoot returns the EVM root hash of the state after executing this block - StateRoot gethCommon.Hash + // holds the total amount of the native token deposited in the evm side. (in attoflow) + TotalSupply *big.Int // ReceiptRoot returns the root hash of the receipts emitted in this block ReceiptRoot gethCommon.Hash @@ -45,14 +44,13 @@ func (b *Block) AppendTxHash(txHash gethCommon.Hash) { } // NewBlock constructs a new block -func NewBlock(height, uuidIndex, totalSupply uint64, +func NewBlock(height, uuidIndex uint64, totalSupply *big.Int, stateRoot, receiptRoot gethCommon.Hash, txHashes []gethCommon.Hash, ) *Block { return &Block{ Height: height, TotalSupply: totalSupply, - StateRoot: stateRoot, ReceiptRoot: receiptRoot, TransactionHashes: txHashes, } @@ -69,6 +67,6 @@ func NewBlockFromBytes(encoded []byte) (*Block, error) { var GenesisBlock = &Block{ ParentBlockHash: gethCommon.Hash{}, Height: uint64(0), - StateRoot: gethTypes.EmptyRootHash, + TotalSupply: new(big.Int), ReceiptRoot: gethTypes.EmptyRootHash, } diff --git a/fvm/evm/types/emulator.go b/fvm/evm/types/emulator.go index 1b86e06fe7b..1997624569f 100644 --- a/fvm/evm/types/emulator.go +++ b/fvm/evm/types/emulator.go @@ -5,7 +5,7 @@ import ( gethCommon "github.com/ethereum/go-ethereum/common" gethTypes "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/ethdb" + gethVM "github.com/ethereum/go-ethereum/core/vm" ) var ( @@ -16,12 +16,21 @@ var ( BlockNumberForEVMRules = big.NewInt(1) ) +type Precompile interface { + gethVM.PrecompiledContract + Address() Address +} + // BlockContext holds the context needed for the emulator operations type BlockContext struct { BlockNumber uint64 DirectCallBaseGasUsage uint64 DirectCallGasPrice uint64 GasFeeCollector Address + Random gethCommon.Hash + + // a set of extra precompiles to be injected + ExtraPrecompiles []Precompile } // NewDefaultBlockContext returns a new default block context @@ -65,17 +74,3 @@ type Emulator interface { // constructs a new block NewBlockView(ctx BlockContext) (BlockView, error) } - -// Database provides what Emulator needs for storing tries and accounts -// Errors returned by the methods are one of the followings: -// - Fatal error -// - Database error (non-fatal) -type Database interface { - ethdb.KeyValueStore - - // Commit commits the changes - Commit(rootHash gethCommon.Hash) error - - // GetRootHash returns the active root hash - GetRootHash() (gethCommon.Hash, error) -} diff --git a/fvm/evm/types/errors.go b/fvm/evm/types/errors.go index fa008b741a2..75085b6ea39 100644 --- a/fvm/evm/types/errors.go +++ b/fvm/evm/types/errors.go @@ -16,18 +16,18 @@ var ( // left in the context of flow transaction to execute the evm operation. ErrInsufficientComputation = errors.New("insufficient computation") - // unauthorized method call, usually emited when calls are called on EOA accounts + // ErrUnAuthroizedMethodCall method call, usually emited when calls are called on EOA accounts ErrUnAuthroizedMethodCall = errors.New("unauthroized method call") + // ErrInsufficientTotalSupply is returned when flow token // is withdraw request is there but not enough balance is on EVM vault // this should never happen but its a saftey measure to protect Flow against EVM issues. // TODO: we might consider this fatal ErrInsufficientTotalSupply = errors.New("insufficient total supply") - // ErrBalanceConversion is returned conversion of balance has failed, usually - // is returned when the balance presented in attoflow has values that could - // be marginally lost on the conversion. - ErrBalanceConversion = errors.New("balance converion error") + // ErrWithdrawBalanceRounding is returned when withdraw call has a balance that could + // yeild to rounding error, i.e. the balance contains fractions smaller than 10^8 Flow (smallest unit allowed to transfer). + ErrWithdrawBalanceRounding = errors.New("withdraw failed! balance is susceptible to the rounding error") // ErrNotImplemented is a fatal error when something is called that is not implemented ErrNotImplemented = NewFatalError(errors.New("a functionality is called that is not implemented")) @@ -89,35 +89,35 @@ func IsEVMValidationError(err error) bool { return errors.As(err, &EVMValidationError{}) } -// DatabaseError is a non-fatal error, returned when a database operation +// StateError is a non-fatal error, returned when a state operation // has failed (e.g. reaching storage interaction limit) -type DatabaseError struct { +type StateError struct { err error } -// NewDatabaseError returns a new DatabaseError -func NewDatabaseError(rootCause error) DatabaseError { - return DatabaseError{ +// NewStateError returns a new StateError +func NewStateError(rootCause error) StateError { + return StateError{ err: rootCause, } } // Unwrap unwraps the underlying evm error -func (err DatabaseError) Unwrap() error { +func (err StateError) Unwrap() error { return err.err } -func (err DatabaseError) Error() string { - return fmt.Sprintf("database error: %v", err.err) +func (err StateError) Error() string { + return fmt.Sprintf("state error: %v", err.err) } -// IsADatabaseError returns true if the error or any underlying errors +// IsAStateError returns true if the error or any underlying errors // is of the type EVM validation error -func IsADatabaseError(err error) bool { - return errors.As(err, &DatabaseError{}) +func IsAStateError(err error) bool { + return errors.As(err, &StateError{}) } -// FatalError is user for any error that is not user related and something +// FatalError is used for any error that is not user related and something // unusual has happend. Usually we stop the node when this happens // given it might have a non-deterministic root. type FatalError struct { @@ -152,6 +152,12 @@ func IsAInsufficientTotalSupplyError(err error) bool { return errors.Is(err, ErrInsufficientTotalSupply) } +// IsWithdrawBalanceRoundingError returns true if the error type is +// ErrWithdrawBalanceRounding +func IsWithdrawBalanceRoundingError(err error) bool { + return errors.Is(err, ErrWithdrawBalanceRounding) +} + // IsAUnAuthroizedMethodCallError returns true if the error type is // UnAuthroizedMethodCallError func IsAUnAuthroizedMethodCallError(err error) bool { diff --git a/fvm/evm/types/events.go b/fvm/evm/types/events.go index 148c3f59ede..ee19d63c450 100644 --- a/fvm/evm/types/events.go +++ b/fvm/evm/types/events.go @@ -2,18 +2,22 @@ package types import ( "encoding/hex" + "encoding/json" + "fmt" + "strings" gethCommon "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/rlp" "github.com/onflow/cadence" - "github.com/onflow/cadence/runtime/stdlib" + "github.com/onflow/cadence/runtime/common" "github.com/onflow/flow-go/model/flow" ) const ( - EventTypeBlockExecuted flow.EventType = "evm.BlockExecuted" - EventTypeTransactionExecuted flow.EventType = "evm.TransactionExecuted" + EventTypeBlockExecuted flow.EventType = "BlockExecuted" + EventTypeTransactionExecuted flow.EventType = "TransactionExecuted" + locationDivider = "." ) type EventPayload interface { @@ -26,6 +30,72 @@ type Event struct { Payload EventPayload } +var _ common.Location = EVMLocation{} + +type EVMLocation struct{} + +func (l EVMLocation) TypeID(memoryGauge common.MemoryGauge, qualifiedIdentifier string) common.TypeID { + id := fmt.Sprintf("%s%s%s", flow.EVMLocationPrefix, locationDivider, qualifiedIdentifier) + common.UseMemory(memoryGauge, common.NewRawStringMemoryUsage(len(id))) + + return common.TypeID(id) +} + +func (l EVMLocation) QualifiedIdentifier(typeID common.TypeID) string { + pieces := strings.SplitN(string(typeID), locationDivider, 2) + + if len(pieces) < 2 { + return "" + } + + return pieces[1] +} + +func (l EVMLocation) String() string { + return flow.EVMLocationPrefix +} + +func (l EVMLocation) Description() string { + return flow.EVMLocationPrefix +} + +func (l EVMLocation) ID() string { + return flow.EVMLocationPrefix +} + +func (l EVMLocation) MarshalJSON() ([]byte, error) { + return json.Marshal(&struct { + Type string + }{ + Type: "EVMLocation", + }) +} + +func init() { + common.RegisterTypeIDDecoder( + flow.EVMLocationPrefix, + func(_ common.MemoryGauge, typeID string) (common.Location, string, error) { + if typeID == "" { + return nil, "", fmt.Errorf("invalid EVM type location ID: missing type prefix") + } + + parts := strings.SplitN(typeID, ".", 2) + prefix := parts[0] + if prefix != flow.EVMLocationPrefix { + return EVMLocation{}, "", fmt.Errorf("invalid EVM type location ID: invalid prefix") + } + + var qualifiedIdentifier string + pieceCount := len(parts) + if pieceCount > 1 { + qualifiedIdentifier = parts[1] + } + + return EVMLocation{}, qualifiedIdentifier, nil + }, + ) +} + // we might break this event into two (tx included /tx executed) if size becomes an issue type TransactionExecutedPayload struct { BlockHeight uint64 @@ -34,25 +104,6 @@ type TransactionExecutedPayload struct { Result *Result } -var transactionExecutedEventCadenceType = &cadence.EventType{ - Location: stdlib.FlowLocation{}, - QualifiedIdentifier: string(EventTypeTransactionExecuted), - Fields: []cadence.Field{ - cadence.NewField("blockHeight", cadence.UInt64Type{}), - cadence.NewField("transactionHash", cadence.StringType{}), - cadence.NewField("transaction", cadence.StringType{}), - cadence.NewField("failed", cadence.BoolType{}), - cadence.NewField("transactionType", cadence.UInt8Type{}), - cadence.NewField("gasConsumed", cadence.UInt64Type{}), - cadence.NewField("stateRootHash", cadence.StringType{}), - cadence.NewField("deployedContractAddress", cadence.StringType{}), - cadence.NewField("returnedValue", cadence.StringType{}), - cadence.NewField("logs", cadence.StringType{}), - }, -} - -// todo add decoder for events from cadence to evm payload - func (p *TransactionExecutedPayload) CadenceEvent() (cadence.Event, error) { var encodedLogs []byte var err error @@ -63,22 +114,35 @@ func (p *TransactionExecutedPayload) CadenceEvent() (cadence.Event, error) { } } - fields := []cadence.Value{ - cadence.NewUInt64(p.BlockHeight), - cadence.String(p.TxHash.String()), - cadence.String(hex.EncodeToString(p.TxEncoded)), - cadence.NewBool(p.Result.Failed), - cadence.NewUInt8(p.Result.TxType), - cadence.NewUInt64(p.Result.GasConsumed), - cadence.String(p.Result.StateRootHash.String()), - cadence.String(hex.EncodeToString(p.Result.DeployedContractAddress.Bytes())), - cadence.String(hex.EncodeToString(p.Result.ReturnedValue)), - cadence.String(hex.EncodeToString(encodedLogs)), - } - - return cadence. - NewEvent(fields). - WithType(transactionExecutedEventCadenceType), nil + return cadence.Event{ + EventType: cadence.NewEventType( + EVMLocation{}, + string(EventTypeTransactionExecuted), + []cadence.Field{ + cadence.NewField("blockHeight", cadence.UInt64Type{}), + cadence.NewField("transactionHash", cadence.StringType{}), + cadence.NewField("transaction", cadence.StringType{}), + cadence.NewField("failed", cadence.BoolType{}), + cadence.NewField("transactionType", cadence.UInt8Type{}), + cadence.NewField("gasConsumed", cadence.UInt64Type{}), + cadence.NewField("deployedContractAddress", cadence.StringType{}), + cadence.NewField("returnedValue", cadence.StringType{}), + cadence.NewField("logs", cadence.StringType{}), + }, + nil, + ), + Fields: []cadence.Value{ + cadence.NewUInt64(p.BlockHeight), + cadence.String(p.TxHash.String()), + cadence.String(hex.EncodeToString(p.TxEncoded)), + cadence.NewBool(p.Result.Failed), + cadence.NewUInt8(p.Result.TxType), + cadence.NewUInt64(p.Result.GasConsumed), + cadence.String(hex.EncodeToString(p.Result.DeployedContractAddress.Bytes())), + cadence.String(hex.EncodeToString(p.Result.ReturnedValue)), + cadence.String(hex.EncodeToString(encodedLogs)), + }, + }, nil } func NewTransactionExecutedEvent( @@ -99,13 +163,13 @@ func NewTransactionExecutedEvent( } var blockExecutedEventCadenceType = &cadence.EventType{ - Location: stdlib.FlowLocation{}, // todo create evm custom location + Location: EVMLocation{}, QualifiedIdentifier: string(EventTypeBlockExecuted), Fields: []cadence.Field{ cadence.NewField("height", cadence.UInt64Type{}), - cadence.NewField("totalSupply", cadence.UInt64Type{}), + cadence.NewField("hash", cadence.StringType{}), + cadence.NewField("totalSupply", cadence.IntType{}), cadence.NewField("parentHash", cadence.StringType{}), - cadence.NewField("stateRoot", cadence.StringType{}), cadence.NewField("receiptRoot", cadence.StringType{}), cadence.NewField( "transactionHashes", @@ -124,12 +188,17 @@ func (p *BlockExecutedEventPayload) CadenceEvent() (cadence.Event, error) { hashes[i] = cadence.String(hash.String()) } + blockHash, err := p.Block.Hash() + if err != nil { + return cadence.Event{}, err + } + fields := []cadence.Value{ cadence.NewUInt64(p.Block.Height), - cadence.NewUInt64(p.Block.TotalSupply), - cadence.String(p.Block.ReceiptRoot.String()), + cadence.String(blockHash.String()), + cadence.NewIntFromBig(p.Block.TotalSupply), cadence.String(p.Block.ParentBlockHash.String()), - cadence.String(p.Block.StateRoot.String()), + cadence.String(p.Block.ReceiptRoot.String()), cadence.NewArray(hashes).WithType(cadence.NewVariableSizedArrayType(cadence.StringType{})), } diff --git a/fvm/evm/types/handler.go b/fvm/evm/types/handler.go index 3badb5c6175..502e43025fd 100644 --- a/fvm/evm/types/handler.go +++ b/fvm/evm/types/handler.go @@ -47,12 +47,17 @@ type Backend interface { environment.ValueStore environment.Meter environment.EventEmitter + environment.BlockInfo + environment.RandomGenerator } // AddressAllocator allocates addresses, used by the handler type AddressAllocator interface { - // AllocateAddress allocates an address to be used by a bridged account resource - AllocateAddress() (Address, error) + // AllocateAddress allocates an address to be used by a COA resource + AllocateCOAAddress() (Address, error) + + // AllocateAddress allocates an address by index to be used by a precompile contract + AllocatePrecompileAddress(index uint64) Address } // BlockStore stores the chain of blocks diff --git a/fvm/evm/types/result.go b/fvm/evm/types/result.go index 6e4248b2d58..fb7321a5847 100644 --- a/fvm/evm/types/result.go +++ b/fvm/evm/types/result.go @@ -1,7 +1,6 @@ package types import ( - gethCommon "github.com/ethereum/go-ethereum/common" gethTypes "github.com/ethereum/go-ethereum/core/types" ) @@ -20,8 +19,6 @@ type Result struct { TxType uint8 // total gas consumed during an opeartion GasConsumed uint64 - // the root hash of the state after execution - StateRootHash gethCommon.Hash // the address where the contract is deployed (if any) DeployedContractAddress Address // returned value from a function call @@ -35,7 +32,6 @@ type Result struct { func (res *Result) Receipt() *gethTypes.ReceiptForStorage { receipt := &gethTypes.Receipt{ Type: res.TxType, - PostState: res.StateRootHash[:], CumulativeGasUsed: res.GasConsumed, // TODO: update to capture cumulative Logs: res.Logs, ContractAddress: res.DeployedContractAddress.ToCommon(), diff --git a/fvm/evm/types/state.go b/fvm/evm/types/state.go new file mode 100644 index 00000000000..ee31f94fa89 --- /dev/null +++ b/fvm/evm/types/state.go @@ -0,0 +1,140 @@ +package types + +import ( + "math/big" + + gethCommon "github.com/ethereum/go-ethereum/common" + gethTypes "github.com/ethereum/go-ethereum/core/types" + gethVM "github.com/ethereum/go-ethereum/core/vm" +) + +// StateDB acts as the main interface to the EVM runtime +type StateDB interface { + gethVM.StateDB + + // Commit commits the changes + Commit() error + + // Logs collects and prepares logs + Logs( + blockHash gethCommon.Hash, + blockNumber uint64, + txHash gethCommon.Hash, + txIndex uint, + ) []*gethTypes.Log + + // returns a map of preimages + Preimages() map[gethCommon.Hash][]byte +} + +// ReadOnlyView provides a readonly view of the state +type ReadOnlyView interface { + // Exist returns true if the address exist in the state + Exist(gethCommon.Address) (bool, error) + // IsCreated returns true if address has been created in this tx + IsCreated(gethCommon.Address) bool + // HasSelfDestructed returns true if an address has self destructed + // it also returns the balance of address before selfdestruction call + HasSelfDestructed(gethCommon.Address) (bool, *big.Int) + // GetBalance returns the balance of an address + GetBalance(gethCommon.Address) (*big.Int, error) + // GetNonce returns the nonce of an address + GetNonce(gethCommon.Address) (uint64, error) + // GetCode returns the code of an address + GetCode(gethCommon.Address) ([]byte, error) + // GetCodeHash returns the code hash of an address + GetCodeHash(gethCommon.Address) (gethCommon.Hash, error) + // GetCodeSize returns the code size of an address + GetCodeSize(gethCommon.Address) (int, error) + // GetState returns values for an slot in the main storage + GetState(SlotAddress) (gethCommon.Hash, error) + // GetTransientState returns values for an slot transient storage + GetTransientState(SlotAddress) gethCommon.Hash + // GetRefund returns the total amount of (gas) refund + GetRefund() uint64 + // AddressInAccessList checks if an address is in the access list + AddressInAccessList(gethCommon.Address) bool + // SlotInAccessList checks if a slot is in the access list + SlotInAccessList(SlotAddress) (addressOk bool, slotOk bool) +} + +// HotView captures a high-level mutable view of the state +type HotView interface { + ReadOnlyView + + // CreateAccount creates a new account + CreateAccount(gethCommon.Address) error + // SelfDestruct set the flag for destruction of the account after execution + SelfDestruct(gethCommon.Address) error + + // SubBalance subtracts the amount from the balance the given address + SubBalance(gethCommon.Address, *big.Int) error + // AddBalance adds the amount to the balance of the given address + AddBalance(gethCommon.Address, *big.Int) error + // SetNonce sets the nonce for the given address + SetNonce(gethCommon.Address, uint64) error + // SetCode sets the code for the given address + SetCode(gethCommon.Address, []byte) error + + // SetState sets a value for the given slot in the main storage + SetState(SlotAddress, gethCommon.Hash) error + // SetTransientState sets a value for the given slot in the transient storage + SetTransientState(SlotAddress, gethCommon.Hash) + + // AddRefund adds the amount to the total (gas) refund + AddRefund(uint64) error + // SubRefund subtracts the amount from the total (gas) refund + SubRefund(uint64) error + + // AddAddressToAccessList adds an address to the per-transaction access list + AddAddressToAccessList(addr gethCommon.Address) (addressAdded bool) + // AddSlotToAccessList adds a slot to the per-transaction access list + AddSlotToAccessList(SlotAddress) (addressAdded, slotAdded bool) + + // AddLog append a log to the log collection + AddLog(*gethTypes.Log) + // AddPreimage adds a preimage to the list of preimages (input -> hash mapping) + AddPreimage(gethCommon.Hash, []byte) +} + +// BaseView is a low-level mutable view of the state +// baseview is usually updated at the commit calls to the higher level view +type BaseView interface { + ReadOnlyView + + // Creates a new account + CreateAccount( + addr gethCommon.Address, + balance *big.Int, + nonce uint64, + code []byte, + codeHash gethCommon.Hash, + ) error + + // UpdateAccount updates a account + UpdateAccount( + addr gethCommon.Address, + balance *big.Int, + nonce uint64, + code []byte, + codeHash gethCommon.Hash, + ) error + + // DeleteAccount deletes an account + DeleteAccount(addr gethCommon.Address) error + + // UpdateSlot updates the value for the given slot in the main storage + UpdateSlot( + slot SlotAddress, + value gethCommon.Hash, + ) error + + // Commit commits the changes + Commit() error +} + +// SlotAddress captures an address to a storage slot +type SlotAddress struct { + Address gethCommon.Address + Key gethCommon.Hash +} diff --git a/fvm/evm/types/tokenVault.go b/fvm/evm/types/tokenVault.go index 8815382874d..77eb4a67f3e 100644 --- a/fvm/evm/types/tokenVault.go +++ b/fvm/evm/types/tokenVault.go @@ -1,5 +1,7 @@ package types +import "math/big" + // FLOWTokenVault holds a balance of flow token type FLOWTokenVault struct { balance Balance @@ -13,11 +15,16 @@ func (t *FLOWTokenVault) Balance() Balance { return t.balance } -func (t *FLOWTokenVault) Withdraw(b Balance) *FLOWTokenVault { - t.balance = t.balance.Sub(b) - return NewFlowTokenVault(b) +func (t *FLOWTokenVault) Withdraw(b Balance) (*FLOWTokenVault, error) { + var err error + t.balance, err = SubBalance(t.balance, b) + return NewFlowTokenVault(b), err } -func (t *FLOWTokenVault) Deposit(inp *FLOWTokenVault) { - t.balance = t.balance.Add(inp.Balance()) +func (t *FLOWTokenVault) Deposit(inp *FLOWTokenVault) error { + var err error + t.balance, err = AddBalance(t.balance, inp.balance) + // reset balance for the inp incase + inp.balance = new(big.Int) + return err } diff --git a/fvm/evm/types/tokenVault_test.go b/fvm/evm/types/tokenVault_test.go new file mode 100644 index 00000000000..2dfefcf672b --- /dev/null +++ b/fvm/evm/types/tokenVault_test.go @@ -0,0 +1,26 @@ +package types_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/onflow/flow-go/fvm/evm/types" +) + +func TestVault(t *testing.T) { + + vault1 := types.NewFlowTokenVault(types.MakeABalanceInFlow(3)) + + vault2, err := vault1.Withdraw(types.OneFlowBalance) + require.NoError(t, err) + + require.Equal(t, types.MakeABalanceInFlow(2), vault1.Balance()) + require.Equal(t, types.OneFlowBalance, vault2.Balance()) + + toBeDeposited := types.NewFlowTokenVault(types.OneFlowBalance) + err = vault1.Deposit(toBeDeposited) + require.NoError(t, err) + require.Equal(t, types.MakeABalanceInFlow(3), vault1.Balance()) + require.Equal(t, types.EmptyBalance, toBeDeposited.Balance()) +} diff --git a/fvm/fvm_bench_test.go b/fvm/fvm_bench_test.go index 880f86720ee..619c86c19b8 100644 --- a/fvm/fvm_bench_test.go +++ b/fvm/fvm_bench_test.go @@ -154,9 +154,7 @@ func NewBasicBlockExecutor(tb testing.TB, chain flow.Chain, logger zerolog.Logge opts := []fvm.Option{ fvm.WithTransactionFeesEnabled(true), - // TODO (JanezP): enable storage feee once we figure out how storage limits work - // with the EVM account - fvm.WithAccountStorageLimit(false), + fvm.WithAccountStorageLimit(true), fvm.WithChain(chain), fvm.WithLogger(logger), fvm.WithMaxStateInteractionSize(interactionLimit), @@ -425,7 +423,7 @@ func BenchmarkRuntimeTransaction(b *testing.B) { } sc := systemcontracts.SystemContractsForChain(chain.ChainID()) - testContractAddress, err := chain.AddressAtIndex(systemcontracts.EVMAccountIndex + 1) + testContractAddress, err := chain.AddressAtIndex(systemcontracts.EVMStorageAccountIndex + 1) require.NoError(b, err) benchTransaction := func( @@ -450,10 +448,9 @@ func BenchmarkRuntimeTransaction(b *testing.B) { for _, account := range accounts { addrs = append(addrs, account.Address) } - // TODO (JanezP): fix when the evm account has a receiver - //evmAddress, err := chain.AddressAtIndex(environment.EVMAccountIndex) - //require.NoError(b, err) - //addrs = append(addrs, evmAddress) + evmAddress, err := chain.AddressAtIndex(systemcontracts.EVMStorageAccountIndex) + require.NoError(b, err) + addrs = append(addrs, evmAddress) // fund all accounts so not to run into storage problems fundAccounts(b, blockExecutor, cadence.UFix64(1_000_000_000_000), addrs...) @@ -545,7 +542,7 @@ func BenchmarkRuntimeTransaction(b *testing.B) { sc.FungibleToken.Address.Hex(), sc.FlowToken.Address.Hex(), testContractAddress, - sc.FlowServiceAccount.Address.Hex(), + sc.EVMContract.Address.Hex(), rep, prepare, ) @@ -961,7 +958,7 @@ func mintNFTs(b *testing.B, be TestBenchBlockExecutor, batchNFTAccount *TestBenc mintScript := []byte(fmt.Sprintf(mintScriptTemplate, batchNFTAccount.Address.Hex(), size)) txBody := flow.NewTransactionBody(). - SetGasLimit(999999). + SetComputeLimit(999999). SetScript(mintScript). SetProposalKey(serviceAccount.Address, 0, serviceAccount.RetAndIncSeqNumber()). AddAuthorizer(batchNFTAccount.Address). diff --git a/fvm/fvm_blockcontext_test.go b/fvm/fvm_blockcontext_test.go index dcf73b9280b..55203d1e957 100644 --- a/fvm/fvm_blockcontext_test.go +++ b/fvm/fvm_blockcontext_test.go @@ -13,11 +13,11 @@ import ( "github.com/onflow/cadence/encoding/ccf" jsoncdc "github.com/onflow/cadence/encoding/json" "github.com/onflow/cadence/runtime" + "github.com/onflow/crypto" + "github.com/onflow/crypto/hash" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - "github.com/onflow/flow-go/crypto" - "github.com/onflow/flow-go/crypto/hash" "github.com/onflow/flow-go/engine/execution/testutil" "github.com/onflow/flow-go/fvm" "github.com/onflow/flow-go/fvm/blueprints" @@ -909,7 +909,7 @@ func TestBlockContext_ExecuteTransaction_GasLimit(t *testing.T) { t.Run(tt.label, func(t *testing.T) { txBody := flow.NewTransactionBody(). SetScript([]byte(tt.script)). - SetGasLimit(tt.gasLimit) + SetComputeLimit(tt.gasLimit) err := testutil.SignTransactionAsServiceAccount(txBody, 0, chain) require.NoError(t, err) @@ -948,6 +948,9 @@ func TestBlockContext_ExecuteTransaction_StorageLimit(t *testing.T) { fvm.WithAccountCreationFee(fvm.DefaultAccountCreationFee), fvm.WithMinimumStorageReservation(fvm.DefaultMinimumStorageReservation), fvm.WithStorageMBPerFLOW(fvm.DefaultStorageMBPerFLOW), + // The evm account has a storage exception, and if we don't bootstrap with evm, + // the first created account will have that address. + fvm.WithSetupEVMEnabled(true), } t.Run("Storing too much data fails", newVMTest().withBootstrapProcedureOptions(bootstrapOptions...). @@ -1859,6 +1862,9 @@ func TestBlockContext_ExecuteTransaction_FailingTransactions(t *testing.T) { fvm.WithAccountCreationFee(fvm.DefaultAccountCreationFee), fvm.WithStorageMBPerFLOW(fvm.DefaultStorageMBPerFLOW), fvm.WithExecutionMemoryLimit(math.MaxUint64), + // The evm account has a storage exception, and if we don't bootstrap with evm, + // the first created account will have that address. + fvm.WithSetupEVMEnabled(true), ).run( func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, snapshotTree snapshot.SnapshotTree) { ctx.LimitAccountStorage = true // this test requires storage limits to be enforced diff --git a/fvm/fvm_fuzz_test.go b/fvm/fvm_fuzz_test.go index a34e78a95b3..41c1c201d31 100644 --- a/fvm/fvm_fuzz_test.go +++ b/fvm/fvm_fuzz_test.go @@ -37,7 +37,7 @@ func FuzzTransactionComputationLimit(f *testing.F) { // create the transaction txBody := tt.createTxBody(t, tctx) // set the computation limit - txBody.SetGasLimit(computationLimit) + txBody.SetComputeLimit(computationLimit) // sign the transaction err := testutil.SignEnvelope( diff --git a/fvm/fvm_signature_test.go b/fvm/fvm_signature_test.go index 6a4e20ad284..e4ed90c35bb 100644 --- a/fvm/fvm_signature_test.go +++ b/fvm/fvm_signature_test.go @@ -10,8 +10,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/onflow/flow-go/crypto" - "github.com/onflow/flow-go/crypto/hash" + "github.com/onflow/crypto" + "github.com/onflow/crypto/hash" "github.com/onflow/flow-go/engine/execution/testutil" "github.com/onflow/flow-go/fvm" diff --git a/fvm/fvm_test.go b/fvm/fvm_test.go index 4017ec1f638..f466a7fcf43 100644 --- a/fvm/fvm_test.go +++ b/fvm/fvm_test.go @@ -8,27 +8,30 @@ import ( "strings" "testing" - "github.com/onflow/flow-go/fvm/evm/stdlib" - "github.com/onflow/cadence" "github.com/onflow/cadence/encoding/ccf" jsoncdc "github.com/onflow/cadence/encoding/json" "github.com/onflow/cadence/runtime" "github.com/onflow/cadence/runtime/common" + cadenceErrors "github.com/onflow/cadence/runtime/errors" "github.com/onflow/cadence/runtime/tests/utils" + "github.com/onflow/crypto" + "github.com/stretchr/testify/assert" + mockery "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - "github.com/onflow/flow-go/crypto" - "github.com/onflow/flow-go/engine/execution/testutil" exeUtils "github.com/onflow/flow-go/engine/execution/utils" "github.com/onflow/flow-go/fvm" fvmCrypto "github.com/onflow/flow-go/fvm/crypto" "github.com/onflow/flow-go/fvm/environment" - errors "github.com/onflow/flow-go/fvm/errors" + "github.com/onflow/flow-go/fvm/errors" + "github.com/onflow/flow-go/fvm/evm/stdlib" + "github.com/onflow/flow-go/fvm/evm/types" "github.com/onflow/flow-go/fvm/meter" reusableRuntime "github.com/onflow/flow-go/fvm/runtime" "github.com/onflow/flow-go/fvm/storage/snapshot" + "github.com/onflow/flow-go/fvm/storage/snapshot/mock" "github.com/onflow/flow-go/fvm/storage/testutils" "github.com/onflow/flow-go/fvm/systemcontracts" "github.com/onflow/flow-go/model/flow" @@ -75,6 +78,7 @@ func (vmt vmTest) run( baseOpts := []fvm.Option{ // default chain is Testnet fvm.WithChain(flow.Testnet.Chain()), + fvm.WithEntropyProvider(testutil.EntropyProviderFixture(nil)), } opts := append(baseOpts, vmt.contextOptions...) @@ -1007,9 +1011,9 @@ func TestTransactionFeeDeduction(t *testing.T) { txBody.SetPayer(address) if tc.gasLimit == 0 { - txBody.SetGasLimit(fvm.DefaultComputationLimit) + txBody.SetComputeLimit(fvm.DefaultComputationLimit) } else { - txBody.SetGasLimit(tc.gasLimit) + txBody.SetComputeLimit(tc.gasLimit) } err = testutil.SignEnvelope( @@ -1419,7 +1423,7 @@ func TestSettingExecutionWeights(t *testing.T) { SetProposalKey(chain.ServiceAddress(), 0, 0). AddAuthorizer(chain.ServiceAddress()). SetPayer(chain.ServiceAddress()). - SetGasLimit(maxExecutionEffort) + SetComputeLimit(maxExecutionEffort) err := testutil.SignTransactionAsServiceAccount(txBody, 0, chain) require.NoError(t, err) @@ -1445,7 +1449,7 @@ func TestSettingExecutionWeights(t *testing.T) { SetProposalKey(chain.ServiceAddress(), 0, 1). AddAuthorizer(chain.ServiceAddress()). SetPayer(chain.ServiceAddress()). - SetGasLimit(maxExecutionEffort) + SetComputeLimit(maxExecutionEffort) err = testutil.SignTransactionAsServiceAccount(txBody, 1, chain) require.NoError(t, err) @@ -1632,11 +1636,11 @@ func TestEnforcingComputationLimit(t *testing.T) { txBody := flow.NewTransactionBody(). SetScript(script). - SetGasLimit(computationLimit) + SetComputeLimit(computationLimit) if test.payerIsServAcc { txBody.SetPayer(chain.ServiceAddress()). - SetGasLimit(0) + SetComputeLimit(0) } tx := fvm.Transaction(txBody, 0) @@ -2955,12 +2959,10 @@ func TestTransientNetworkCoreContractAddresses(t *testing.T) { } func TestEVM(t *testing.T) { - t.Run("successful transaction", newVMTest(). withBootstrapProcedureOptions(fvm.WithSetupEVMEnabled(true)). - // we keep this dissabled during bootstrap and later overwrite in the test for test transaction withContextOptions( - fvm.WithEVMEnabled(false), + fvm.WithEVMEnabled(true), fvm.WithCadenceLogging(true), ). run(func( @@ -2979,6 +2981,8 @@ func TestEVM(t *testing.T) { encodedArg, err := jsoncdc.Encode(addrBytes) require.NoError(t, err) + sc := systemcontracts.SystemContractsForChain(chain.ChainID()) + txBody := flow.NewTransactionBody(). SetScript([]byte(fmt.Sprintf(` import EVM from %s @@ -2989,7 +2993,7 @@ func TestEVM(t *testing.T) { log(addr) } } - `, chain.ServiceAddress().HexWithPrefix()))). + `, sc.EVMContract.Address.HexWithPrefix()))). SetProposalKey(chain.ServiceAddress(), 0, 0). SetPayer(chain.ServiceAddress()). AddArgument(encodedArg) @@ -2997,7 +3001,6 @@ func TestEVM(t *testing.T) { err = testutil.SignTransactionAsServiceAccount(txBody, 0, chain) require.NoError(t, err) - ctx = fvm.NewContextFromParent(ctx, fvm.WithEVMEnabled(true)) _, output, err := vm.Run( ctx, fvm.Transaction(txBody, 0), @@ -3008,9 +3011,218 @@ func TestEVM(t *testing.T) { require.Len(t, output.Logs, 1) require.Equal(t, output.Logs[0], fmt.Sprintf( "A.%s.EVM.EVMAddress(bytes: %s)", - chain.ServiceAddress(), + sc.EVMContract.Address, addrBytes.String(), )) }), ) + + // this test makes sure that only ABI encoding/decoding functionality is + // available through the EVM contract, when bootstraped with `WithEVMABIOnly` + t.Run("with ABI only EVM", newVMTest(). + withBootstrapProcedureOptions( + fvm.WithSetupEVMEnabled(true), + fvm.WithEVMABIOnly(true), + ). + withContextOptions( + fvm.WithEVMEnabled(true), + ). + run(func( + t *testing.T, + vm fvm.VM, + chain flow.Chain, + ctx fvm.Context, + snapshotTree snapshot.SnapshotTree, + ) { + txBody := flow.NewTransactionBody(). + SetScript([]byte(fmt.Sprintf(` + import EVM from %s + + transaction { + execute { + let data = EVM.encodeABI(["John Doe", UInt64(33), false]) + log(data.length) + assert(data.length == 160) + + let acc <- EVM.createBridgedAccount() + destroy acc + } + } + `, chain.ServiceAddress().HexWithPrefix()))). + SetProposalKey(chain.ServiceAddress(), 0, 0). + SetPayer(chain.ServiceAddress()) + + err := testutil.SignTransactionAsServiceAccount(txBody, 0, chain) + require.NoError(t, err) + + _, output, err := vm.Run( + ctx, + fvm.Transaction(txBody, 0), + snapshotTree) + + require.NoError(t, err) + require.Error(t, output.Err) + assert.ErrorContains( + t, + output.Err, + "value of type `EVM` has no member `createBridgedAccount`", + ) + }), + ) + + // this test makes sure the execution error is correctly handled and returned as a correct type + t.Run("execution reverted", newVMTest(). + withBootstrapProcedureOptions(fvm.WithSetupEVMEnabled(true)). + withContextOptions(fvm.WithEVMEnabled(true)). + run(func( + t *testing.T, + vm fvm.VM, + chain flow.Chain, + ctx fvm.Context, + snapshotTree snapshot.SnapshotTree, + ) { + sc := systemcontracts.SystemContractsForChain(chain.ChainID()) + script := fvm.Script([]byte(fmt.Sprintf(` + import EVM from %s + + pub fun main() { + let bal = EVM.Balance(flow: 1.0); + let acc <- EVM.createBridgedAccount(); + // withdraw insufficient balance + destroy acc.withdraw(balance: bal); + destroy acc; + } + `, sc.EVMContract.Address.HexWithPrefix()))) + + _, output, err := vm.Run(ctx, script, snapshotTree) + + require.NoError(t, err) + require.Error(t, output.Err) + require.True(t, errors.IsEVMError(output.Err)) + + // make sure error is not treated as internal error by Cadence + var internal cadenceErrors.InternalError + require.False(t, errors.As(output.Err, &internal)) + }), + ) + + // this test makes sure the EVM error is correctly returned as an error and has a correct type + // we have implemented a snapshot wrapper to return an error from the EVM + t.Run("internal evm error handling", newVMTest(). + withBootstrapProcedureOptions(fvm.WithSetupEVMEnabled(true)). + withContextOptions(fvm.WithEVMEnabled(true)). + run(func( + t *testing.T, + vm fvm.VM, + chain flow.Chain, + ctx fvm.Context, + snapshotTree snapshot.SnapshotTree, + ) { + sc := systemcontracts.SystemContractsForChain(chain.ChainID()) + + tests := []struct { + err error + errChecker func(error) bool + }{{ + types.ErrNotImplemented, + types.IsAFatalError, + }, { + types.NewStateError(fmt.Errorf("test state error")), + types.IsAStateError, + }} + + for _, e := range tests { + // this mock will return an error we provide with the test once it starts to access address allocator registers + // that is done to make sure the error is coming out of EVM execution + errStorage := &mock.StorageSnapshot{} + errStorage. + On("Get", mockery.AnythingOfType("flow.RegisterID")). + Return(func(id flow.RegisterID) (flow.RegisterValue, error) { + if id.Key == "AddressAllocator" { + return nil, e.err + } + return snapshotTree.Get(id) + }) + + script := fvm.Script([]byte(fmt.Sprintf(` + import EVM from %s + + pub fun main() { + destroy <- EVM.createBridgedAccount(); + } + `, sc.EVMContract.Address.HexWithPrefix()))) + + _, output, err := vm.Run(ctx, script, errStorage) + + require.NoError(t, output.Err) + require.Error(t, err) + // make sure error it's the right type of error + require.True(t, e.errChecker(err), "error is not of the right type") + } + }), + ) + + t.Run("deploy contract code", newVMTest(). + withBootstrapProcedureOptions( + fvm.WithSetupEVMEnabled(true), + ). + withContextOptions( + // default is testnet, but testnet has a special EVM storage contract location + // so we have to use emulator here so that the EVM storage contract is deployed + // to the 5th address + fvm.WithChain(flow.Emulator.Chain()), + ). + run(func( + t *testing.T, + vm fvm.VM, + chain flow.Chain, + ctx fvm.Context, + snapshotTree snapshot.SnapshotTree, + ) { + sc := systemcontracts.SystemContractsForChain(chain.ChainID()) + + txBody := flow.NewTransactionBody(). + SetScript([]byte(fmt.Sprintf(` + import FungibleToken from %s + import FlowToken from %s + import EVM from %s + + transaction() { + prepare(acc: AuthAccount) { + let vaultRef = acc.borrow<&{FungibleToken.Provider}>(from: /storage/flowTokenVault) + ?? panic("Could not borrow reference to the owner's Vault!") + + let acc <- EVM.createBridgedAccount() + let amount <- vaultRef.withdraw(amount: 0.0000001) as! @FlowToken.Vault + acc.deposit(from: <- amount) + destroy acc + } + }`, + sc.FungibleToken.Address.HexWithPrefix(), + sc.FlowToken.Address.HexWithPrefix(), + sc.FlowServiceAccount.Address.HexWithPrefix(), // TODO this should be sc.EVM.Address not found there??? + ))). + SetProposalKey(chain.ServiceAddress(), 0, 0). + AddAuthorizer(chain.ServiceAddress()). + SetPayer(chain.ServiceAddress()) + + err := testutil.SignTransactionAsServiceAccount(txBody, 0, chain) + require.NoError(t, err) + + ctx = fvm.NewContextFromParent(ctx, fvm.WithEVMEnabled(true)) + _, output, err := vm.Run( + ctx, + fvm.Transaction(txBody, 0), + snapshotTree) + + require.NoError(t, err) + require.NoError(t, output.Err) + require.Len(t, output.Events, 3) + + evmLocation := types.EVMLocation{} + txExe, blockExe := output.Events[1], output.Events[2] + assert.Equal(t, evmLocation.TypeID(nil, string(types.EventTypeTransactionExecuted)), common.TypeID(txExe.Type)) + assert.Equal(t, evmLocation.TypeID(nil, string(types.EventTypeBlockExecuted)), common.TypeID(blockExe.Type)) + }), + ) } diff --git a/fvm/meter/meter_test.go b/fvm/meter/meter_test.go index 46d9bbc32a5..4234966c260 100644 --- a/fvm/meter/meter_test.go +++ b/fvm/meter/meter_test.go @@ -408,7 +408,7 @@ func TestStorageLimits(t *testing.T) { meter.DefaultParameters(), ) - key1 := flow.NewRegisterID("", "1") + key1 := flow.NewRegisterID(flow.EmptyAddress, "1") val1 := []byte{0x1, 0x2, 0x3} size1 := meter.GetStorageKeyValueSizeForTesting(key1, val1) @@ -423,7 +423,7 @@ func TestStorageLimits(t *testing.T) { require.Equal(t, meter1.TotalBytesReadFromStorage(), size1) // first read of key2 - key2 := flow.NewRegisterID("", "2") + key2 := flow.NewRegisterID(flow.EmptyAddress, "2") val2 := []byte{0x3, 0x2, 0x1} size2 := meter.GetStorageKeyValueSizeForTesting(key2, val2) @@ -437,7 +437,7 @@ func TestStorageLimits(t *testing.T) { meter.DefaultParameters(), ) - key1 := flow.NewRegisterID("", "1") + key1 := flow.NewRegisterID(flow.EmptyAddress, "1") val1 := []byte{0x1, 0x2, 0x3} val2 := []byte{0x1, 0x2, 0x3, 0x4} @@ -452,7 +452,7 @@ func TestStorageLimits(t *testing.T) { require.Equal(t, meter1.TotalBytesWrittenToStorage(), meter.GetStorageKeyValueSizeForTesting(key1, val2)) // first write of key2 - key2 := flow.NewRegisterID("", "2") + key2 := flow.NewRegisterID(flow.EmptyAddress, "2") err = meter1.MeterStorageWrite(key2, val2, false) require.NoError(t, err) require.Equal(t, meter1.TotalBytesWrittenToStorage(), @@ -464,7 +464,7 @@ func TestStorageLimits(t *testing.T) { meter.DefaultParameters().WithStorageInteractionLimit(1), ) - key1 := flow.NewRegisterID("", "1") + key1 := flow.NewRegisterID(flow.EmptyAddress, "1") val1 := []byte{0x1, 0x2, 0x3} err := meter1.MeterStorageRead(key1, val1, false /* not enforced */) @@ -478,7 +478,7 @@ func TestStorageLimits(t *testing.T) { meter.DefaultParameters().WithStorageInteractionLimit(testLimit), ) - key1 := flow.NewRegisterID("", "1") + key1 := flow.NewRegisterID(flow.EmptyAddress, "1") val1 := []byte{0x1, 0x2, 0x3} err := meter1.MeterStorageRead(key1, val1, true /* enforced */) @@ -496,7 +496,7 @@ func TestStorageLimits(t *testing.T) { meter.DefaultParameters().WithStorageInteractionLimit(testLimit), ) - key1 := flow.NewRegisterID("", "1") + key1 := flow.NewRegisterID(flow.EmptyAddress, "1") val1 := []byte{0x1, 0x2, 0x3} err := meter1.MeterStorageWrite(key1, val1, false /* not enforced */) @@ -509,7 +509,7 @@ func TestStorageLimits(t *testing.T) { meter.DefaultParameters().WithStorageInteractionLimit(testLimit), ) - key1 := flow.NewRegisterID("", "1") + key1 := flow.NewRegisterID(flow.EmptyAddress, "1") val1 := []byte{0x1, 0x2, 0x3} err := meter1.MeterStorageWrite(key1, val1, true /* enforced */) @@ -526,8 +526,8 @@ func TestStorageLimits(t *testing.T) { meter.DefaultParameters(), ) - key1 := flow.NewRegisterID("", "1") - key2 := flow.NewRegisterID("", "2") + key1 := flow.NewRegisterID(flow.EmptyAddress, "1") + key2 := flow.NewRegisterID(flow.EmptyAddress, "2") val1 := []byte{0x1, 0x2, 0x3} val2 := []byte{0x1, 0x2, 0x3, 0x4} size1 := meter.GetStorageKeyValueSizeForTesting(key1, val1) @@ -547,8 +547,8 @@ func TestStorageLimits(t *testing.T) { }) t.Run("metering storage read and written - exceeding limit - not enforced", func(t *testing.T) { - key1 := flow.NewRegisterID("", "1") - key2 := flow.NewRegisterID("", "2") + key1 := flow.NewRegisterID(flow.EmptyAddress, "1") + key2 := flow.NewRegisterID(flow.EmptyAddress, "2") val1 := []byte{0x1, 0x2, 0x3} val2 := []byte{0x1, 0x2, 0x3, 0x4} size1 := meter.GetStorageKeyValueSizeForTesting(key1, val1) @@ -572,8 +572,8 @@ func TestStorageLimits(t *testing.T) { }) t.Run("metering storage read and written - exceeding limit - enforced", func(t *testing.T) { - key1 := flow.NewRegisterID("", "1") - key2 := flow.NewRegisterID("", "2") + key1 := flow.NewRegisterID(flow.EmptyAddress, "1") + key2 := flow.NewRegisterID(flow.EmptyAddress, "2") val1 := []byte{0x1, 0x2, 0x3} val2 := []byte{0x1, 0x2, 0x3, 0x4} size1 := meter.GetStorageKeyValueSizeForTesting(key1, val1) @@ -603,13 +603,13 @@ func TestStorageLimits(t *testing.T) { meter1 := meter.NewMeter( meter.DefaultParameters(), ) - readKey1 := flow.NewRegisterID("", "r1") + readKey1 := flow.NewRegisterID(flow.EmptyAddress, "r1") readVal1 := []byte{0x1, 0x2, 0x3} readSize1 := meter.GetStorageKeyValueSizeForTesting(readKey1, readVal1) err := meter1.MeterStorageRead(readKey1, readVal1, false) require.NoError(t, err) - writeKey1 := flow.NewRegisterID("", "w1") + writeKey1 := flow.NewRegisterID(flow.EmptyAddress, "w1") writeVal1 := []byte{0x1, 0x2, 0x3, 0x4} writeSize1 := meter.GetStorageKeyValueSizeForTesting(writeKey1, writeVal1) err = meter1.MeterStorageWrite(writeKey1, writeVal1, false) @@ -620,7 +620,7 @@ func TestStorageLimits(t *testing.T) { meter.DefaultParameters(), ) - writeKey2 := flow.NewRegisterID("", "w2") + writeKey2 := flow.NewRegisterID(flow.EmptyAddress, "w2") writeVal2 := []byte{0x1, 0x2, 0x3, 0x4, 0x5} writeSize2 := meter.GetStorageKeyValueSizeForTesting(writeKey2, writeVal2) diff --git a/fvm/storage/derived/table_test.go b/fvm/storage/derived/table_test.go index 774e9ba3003..2d9656c47de 100644 --- a/fvm/storage/derived/table_test.go +++ b/fvm/storage/derived/table_test.go @@ -12,6 +12,7 @@ import ( "github.com/onflow/flow-go/fvm/storage/snapshot" "github.com/onflow/flow-go/fvm/storage/state" "github.com/onflow/flow-go/model/flow" + "github.com/onflow/flow-go/utils/unittest" ) func newEmptyTestBlock() *DerivedDataTable[string, *string] { @@ -962,7 +963,7 @@ func (computer *testValueComputer) Compute( func TestDerivedDataTableGetOrCompute(t *testing.T) { blockDerivedData := NewEmptyTable[flow.RegisterID, int](0) - key := flow.NewRegisterID("addr", "key") + key := flow.NewRegisterID(unittest.RandomAddressFixture(), "key") value := 12345 t.Run("compute value", func(t *testing.T) { diff --git a/fvm/storage/snapshot/execution_snapshot.go b/fvm/storage/snapshot/execution_snapshot.go index 420c4ffccb4..9b26e4e07b6 100644 --- a/fvm/storage/snapshot/execution_snapshot.go +++ b/fvm/storage/snapshot/execution_snapshot.go @@ -1,6 +1,8 @@ package snapshot import ( + "strings" + "golang.org/x/exp/slices" "github.com/onflow/flow-go/fvm/meter" @@ -29,9 +31,12 @@ func (snapshot *ExecutionSnapshot) UpdatedRegisters() flow.RegisterEntries { entries = append(entries, flow.RegisterEntry{Key: key, Value: value}) } - slices.SortFunc(entries, func(a, b flow.RegisterEntry) bool { - return (a.Key.Owner < b.Key.Owner) || - (a.Key.Owner == b.Key.Owner && a.Key.Key < b.Key.Key) + slices.SortFunc(entries, func(a, b flow.RegisterEntry) int { + ownerCmp := strings.Compare(a.Key.Owner, b.Key.Owner) + if ownerCmp != 0 { + return ownerCmp + } + return strings.Compare(a.Key.Key, b.Key.Key) }) return entries diff --git a/fvm/storage/snapshot/mock/storage_snapshot.go b/fvm/storage/snapshot/mock/storage_snapshot.go new file mode 100644 index 00000000000..4b9e5c33f26 --- /dev/null +++ b/fvm/storage/snapshot/mock/storage_snapshot.go @@ -0,0 +1,54 @@ +// Code generated by mockery v2.21.4. DO NOT EDIT. + +package mock + +import ( + flow "github.com/onflow/flow-go/model/flow" + mock "github.com/stretchr/testify/mock" +) + +// StorageSnapshot is an autogenerated mock type for the StorageSnapshot type +type StorageSnapshot struct { + mock.Mock +} + +// Get provides a mock function with given fields: id +func (_m *StorageSnapshot) Get(id flow.RegisterID) ([]byte, error) { + ret := _m.Called(id) + + var r0 []byte + var r1 error + if rf, ok := ret.Get(0).(func(flow.RegisterID) ([]byte, error)); ok { + return rf(id) + } + if rf, ok := ret.Get(0).(func(flow.RegisterID) []byte); ok { + r0 = rf(id) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + + if rf, ok := ret.Get(1).(func(flow.RegisterID) error); ok { + r1 = rf(id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +type mockConstructorTestingTNewStorageSnapshot interface { + mock.TestingT + Cleanup(func()) +} + +// NewStorageSnapshot creates a new instance of StorageSnapshot. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewStorageSnapshot(t mockConstructorTestingTNewStorageSnapshot) *StorageSnapshot { + mock := &StorageSnapshot{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/fvm/storage/snapshot/snapshot_tree_test.go b/fvm/storage/snapshot/snapshot_tree_test.go index 5ccf83481e6..0395e861a7f 100644 --- a/fvm/storage/snapshot/snapshot_tree_test.go +++ b/fvm/storage/snapshot/snapshot_tree_test.go @@ -10,10 +10,10 @@ import ( ) func TestSnapshotTree(t *testing.T) { - id1 := flow.NewRegisterID("1", "") - id2 := flow.NewRegisterID("2", "") - id3 := flow.NewRegisterID("3", "") - missingId := flow.NewRegisterID("missing", "") + id1 := flow.NewRegisterID(flow.HexToAddress("0x1"), "") + id2 := flow.NewRegisterID(flow.HexToAddress("0x2"), "") + id3 := flow.NewRegisterID(flow.HexToAddress("0x3"), "") + missingId := flow.NewRegisterID(flow.HexToAddress("0x99"), "") value1v0 := flow.RegisterValue("1v0") diff --git a/fvm/storage/state/execution_state_test.go b/fvm/storage/state/execution_state_test.go index d12a1f34b6c..29961fa7e98 100644 --- a/fvm/storage/state/execution_state_test.go +++ b/fvm/storage/state/execution_state_test.go @@ -8,6 +8,7 @@ import ( "github.com/onflow/flow-go/fvm/meter" "github.com/onflow/flow-go/fvm/storage/state" "github.com/onflow/flow-go/model/flow" + "github.com/onflow/flow-go/utils/unittest" ) func createByteArray(size int) []byte { @@ -23,12 +24,12 @@ func TestExecutionState_Finalize(t *testing.T) { child := parent.NewChild() - readId := flow.NewRegisterID("0", "x") + readId := flow.NewRegisterID(unittest.RandomAddressFixture(), "x") _, err := child.Get(readId) require.NoError(t, err) - writeId := flow.NewRegisterID("1", "y") + writeId := flow.NewRegisterID(unittest.RandomAddressFixture(), "y") writeValue := flow.RegisterValue("a") err = child.Set(writeId, writeValue) @@ -65,8 +66,10 @@ func TestExecutionState_Finalize(t *testing.T) { func TestExecutionState_ChildMergeFunctionality(t *testing.T) { st := state.NewExecutionState(nil, state.DefaultParameters()) + owner := unittest.RandomAddressFixture() + t.Run("test read from parent state (backoff)", func(t *testing.T) { - key := flow.NewRegisterID("address", "key1") + key := flow.NewRegisterID(owner, "key1") value := createByteArray(1) // set key1 on parent err := st.Set(key, value) @@ -80,7 +83,7 @@ func TestExecutionState_ChildMergeFunctionality(t *testing.T) { }) t.Run("test write to child (no merge)", func(t *testing.T) { - key := flow.NewRegisterID("address", "key2") + key := flow.NewRegisterID(owner, "key2") value := createByteArray(2) stChild := st.NewChild() @@ -95,7 +98,7 @@ func TestExecutionState_ChildMergeFunctionality(t *testing.T) { }) t.Run("test write to child and merge", func(t *testing.T) { - key := flow.NewRegisterID("address", "key3") + key := flow.NewRegisterID(owner, "key3") value := createByteArray(3) stChild := st.NewChild() @@ -119,7 +122,7 @@ func TestExecutionState_ChildMergeFunctionality(t *testing.T) { }) t.Run("test write to ledger", func(t *testing.T) { - key := flow.NewRegisterID("address", "key4") + key := flow.NewRegisterID(owner, "key4") value := createByteArray(4) // set key4 on parent err := st.Set(key, value) @@ -138,7 +141,7 @@ func TestExecutionState_MaxValueSize(t *testing.T) { nil, state.DefaultParameters().WithMaxValueSizeAllowed(6)) - key := flow.NewRegisterID("address", "key") + key := flow.NewRegisterID(unittest.RandomAddressFixture(), "key") // update should pass value := createByteArray(5) @@ -157,8 +160,8 @@ func TestExecutionState_MaxKeySize(t *testing.T) { // Note: owners are always 8 bytes state.DefaultParameters().WithMaxKeySizeAllowed(8+2)) - key1 := flow.NewRegisterID("1", "23") - key2 := flow.NewRegisterID("123", "234") + key1 := flow.NewRegisterID(unittest.RandomAddressFixture(), "23") + key2 := flow.NewRegisterID(unittest.RandomAddressFixture(), "234") // read _, err := st.Get(key1) @@ -179,19 +182,19 @@ func TestExecutionState_MaxKeySize(t *testing.T) { } func TestExecutionState_MaxInteraction(t *testing.T) { - key1 := flow.NewRegisterID("1", "2") + key1 := flow.NewRegisterID(unittest.RandomAddressFixture(), "2") key1Size := uint64(8 + 1) value1 := []byte("A") value1Size := uint64(1) - key2 := flow.NewRegisterID("123", "23") + key2 := flow.NewRegisterID(unittest.RandomAddressFixture(), "23") key2Size := uint64(8 + 2) - key3 := flow.NewRegisterID("234", "345") + key3 := flow.NewRegisterID(unittest.RandomAddressFixture(), "345") key3Size := uint64(8 + 3) - key4 := flow.NewRegisterID("3", "4567") + key4 := flow.NewRegisterID(unittest.RandomAddressFixture(), "4567") key4Size := uint64(8 + 4) st := state.NewExecutionState( diff --git a/fvm/storage/state/spock_state.go b/fvm/storage/state/spock_state.go index 9a47ac08710..da042886793 100644 --- a/fvm/storage/state/spock_state.go +++ b/fvm/storage/state/spock_state.go @@ -4,7 +4,8 @@ import ( "encoding/binary" "fmt" - "github.com/onflow/flow-go/crypto/hash" + "github.com/onflow/crypto/hash" + "github.com/onflow/flow-go/fvm/storage/snapshot" "github.com/onflow/flow-go/model/flow" ) diff --git a/fvm/storage/state/spock_state_test.go b/fvm/storage/state/spock_state_test.go index eafd30c1305..c5aed7a9f02 100644 --- a/fvm/storage/state/spock_state_test.go +++ b/fvm/storage/state/spock_state_test.go @@ -9,10 +9,13 @@ import ( "github.com/onflow/flow-go/fvm/storage/snapshot" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/utils/rand" + "github.com/onflow/flow-go/utils/unittest" ) type spockTestOp func(*testing.T, *spockState) +var fooOwner = unittest.RandomAddressFixture() + func chainSpockTestOps(prevOps spockTestOp, op spockTestOp) spockTestOp { return func(t *testing.T, state *spockState) { if prevOps != nil { @@ -50,7 +53,8 @@ func testSpock( } func TestSpockStateGet(t *testing.T) { - registerId := flow.NewRegisterID("foo", "bar") + otherOwner := unittest.RandomAddressFixture() + registerId := flow.NewRegisterID(fooOwner, "bar") states := testSpock( t, @@ -71,11 +75,11 @@ func TestSpockStateGet(t *testing.T) { }, // Reading different register ids will result in different spock func(t *testing.T, state *spockState) { - _, err := state.Get(flow.NewRegisterID("fo0", "bar")) + _, err := state.Get(flow.NewRegisterID(otherOwner, "bar")) require.NoError(t, err) }, func(t *testing.T, state *spockState) { - _, err := state.Get(flow.NewRegisterID("foo", "baR")) + _, err := state.Get(flow.NewRegisterID(fooOwner, "baR")) require.NoError(t, err) }, }) @@ -94,7 +98,7 @@ func TestSpockStateGet(t *testing.T) { } func TestSpockStateGetDifferentUnderlyingStorage(t *testing.T) { - badRegisterId := flow.NewRegisterID("foo", "bad") + badRegisterId := flow.NewRegisterID(fooOwner, "bad") value1 := flow.RegisterValue([]byte("abc")) value2 := flow.RegisterValue([]byte("blah")) @@ -127,7 +131,7 @@ func TestSpockStateGetDifferentUnderlyingStorage(t *testing.T) { } func TestSpockStateGetVsSetNil(t *testing.T) { - registerId := flow.NewRegisterID("foo", "bar") + registerId := flow.NewRegisterID(fooOwner, "bar") _ = testSpock( t, @@ -144,7 +148,8 @@ func TestSpockStateGetVsSetNil(t *testing.T) { } func TestSpockStateSet(t *testing.T) { - registerId := flow.NewRegisterID("foo", "bar") + otherOwner := unittest.RandomAddressFixture() + registerId := flow.NewRegisterID(fooOwner, "bar") value := flow.RegisterValue([]byte("value")) states := testSpock( @@ -166,11 +171,11 @@ func TestSpockStateSet(t *testing.T) { }, // Setting different register id will result in different spock func(t *testing.T, state *spockState) { - err := state.Set(flow.NewRegisterID("foo", "baR"), value) + err := state.Set(flow.NewRegisterID(fooOwner, "baR"), value) require.NoError(t, err) }, func(t *testing.T, state *spockState) { - err := state.Set(flow.NewRegisterID("foO", "bar"), value) + err := state.Set(flow.NewRegisterID(otherOwner, "bar"), value) require.NoError(t, err) }, // Setting different register value will result in different spock @@ -194,8 +199,8 @@ func TestSpockStateSet(t *testing.T) { } func TestSpockStateSetValueInjection(t *testing.T) { - registerId1 := flow.NewRegisterID("foo", "injection") - registerId2 := flow.NewRegisterID("foo", "inject") + registerId1 := flow.NewRegisterID(fooOwner, "injection") + registerId2 := flow.NewRegisterID(fooOwner, "inject") _ = testSpock( t, @@ -213,7 +218,7 @@ func TestSpockStateSetValueInjection(t *testing.T) { func TestSpockStateMerge(t *testing.T) { readSet := map[flow.RegisterID]struct{}{ - flow.NewRegisterID("foo", "bar"): struct{}{}, + flow.NewRegisterID(fooOwner, "bar"): struct{}{}, } states := testSpock( @@ -265,13 +270,13 @@ func TestSpockStateMerge(t *testing.T) { require.ErrorContains(t, err, "cannot Merge on a finalized state") } func TestSpockStateDropChanges(t *testing.T) { - registerId := flow.NewRegisterID("foo", "read") + registerId := flow.NewRegisterID(fooOwner, "read") setup := func(t *testing.T, state *spockState) { _, err := state.Get(registerId) require.NoError(t, err) - err = state.Set(flow.NewRegisterID("foo", "write"), []byte("blah")) + err = state.Set(flow.NewRegisterID(fooOwner, "write"), []byte("blah")) require.NoError(t, err) } @@ -331,7 +336,7 @@ func TestSpockStateRandomOps(t *testing.T) { chain[len(chain)-1], func(t *testing.T, state *spockState) { _, err := state.Get( - flow.NewRegisterID("", fmt.Sprintf("%d", id))) + flow.NewRegisterID(flow.EmptyAddress, fmt.Sprintf("%d", id))) require.NoError(t, err) })) case uint(1): @@ -347,7 +352,7 @@ func TestSpockStateRandomOps(t *testing.T) { chain[len(chain)-1], func(t *testing.T, state *spockState) { err := state.Set( - flow.NewRegisterID("", fmt.Sprintf("%d", id)), + flow.NewRegisterID(flow.EmptyAddress, fmt.Sprintf("%d", id)), []byte(fmt.Sprintf("%d", value))) require.NoError(t, err) })) @@ -383,18 +388,21 @@ func TestSpockStateRandomOps(t *testing.T) { _ = testSpock(t, chain) } func TestSpockStateNewChild(t *testing.T) { - baseRegisterId := flow.NewRegisterID("", "base") + baseRegisterId := flow.NewRegisterID(flow.EmptyAddress, "base") baseValue := flow.RegisterValue([]byte("base")) - parentRegisterId1 := flow.NewRegisterID("parent", "1") + parentOwner := unittest.RandomAddressFixture() + childOwner := unittest.RandomAddressFixture() + + parentRegisterId1 := flow.NewRegisterID(parentOwner, "1") parentValue := flow.RegisterValue([]byte("parent")) - parentRegisterId2 := flow.NewRegisterID("parent", "2") + parentRegisterId2 := flow.NewRegisterID(parentOwner, "2") - childRegisterId1 := flow.NewRegisterID("child", "1") + childRegisterId1 := flow.NewRegisterID(childOwner, "1") childValue := flow.RegisterValue([]byte("child")) - childRegisterId2 := flow.NewRegisterID("child", "2") + childRegisterId2 := flow.NewRegisterID(childOwner, "2") parent := newSpockState( snapshot.MapStorageSnapshot{ diff --git a/fvm/storage/state/storage_state_test.go b/fvm/storage/state/storage_state_test.go index 87ff6a195ac..ab852bd91f5 100644 --- a/fvm/storage/state/storage_state_test.go +++ b/fvm/storage/state/storage_state_test.go @@ -7,13 +7,16 @@ import ( "github.com/onflow/flow-go/fvm/storage/snapshot" "github.com/onflow/flow-go/model/flow" + "github.com/onflow/flow-go/utils/unittest" ) func TestStorageStateSet(t *testing.T) { - registerId1 := flow.NewRegisterID("foo", "1") + fooOwner := unittest.RandomAddressFixture() + + registerId1 := flow.NewRegisterID(fooOwner, "1") value1 := flow.RegisterValue([]byte("value1")) - registerId2 := flow.NewRegisterID("foo", "2") + registerId2 := flow.NewRegisterID(fooOwner, "2") value2 := flow.RegisterValue([]byte("value2")) state := newStorageState(nil) @@ -40,13 +43,13 @@ func TestStorageStateSet(t *testing.T) { func TestStorageStateGetFromNilBase(t *testing.T) { state := newStorageState(nil) - value, err := state.Get(flow.NewRegisterID("foo", "bar")) + value, err := state.Get(flow.NewRegisterID(unittest.RandomAddressFixture(), "bar")) require.NoError(t, err) require.Nil(t, value) } func TestStorageStateGetFromBase(t *testing.T) { - registerId := flow.NewRegisterID("", "base") + registerId := flow.NewRegisterID(flow.EmptyAddress, "base") baseValue := flow.RegisterValue([]byte("base")) state := newStorageState( @@ -89,7 +92,7 @@ func TestStorageStateGetFromBase(t *testing.T) { } func TestStorageStateGetFromWriteSet(t *testing.T) { - registerId := flow.NewRegisterID("", "base") + registerId := flow.NewRegisterID(flow.EmptyAddress, "base") expectedValue := flow.RegisterValue([]byte("base")) state := newStorageState(nil) @@ -112,22 +115,25 @@ func TestStorageStateGetFromWriteSet(t *testing.T) { } func TestStorageStateMerge(t *testing.T) { - baseRegisterId := flow.NewRegisterID("", "base") + parentOwner := unittest.RandomAddressFixture() + childOwner := unittest.RandomAddressFixture() + + baseRegisterId := flow.NewRegisterID(flow.EmptyAddress, "base") baseValue := flow.RegisterValue([]byte("base")) - parentRegisterId1 := flow.NewRegisterID("parent", "1") + parentRegisterId1 := flow.NewRegisterID(parentOwner, "1") parentValue := flow.RegisterValue([]byte("parent")) - parentRegisterId2 := flow.NewRegisterID("parent", "2") + parentRegisterId2 := flow.NewRegisterID(parentOwner, "2") - parentRegisterId3 := flow.NewRegisterID("parent", "3") + parentRegisterId3 := flow.NewRegisterID(parentOwner, "3") originalParentValue3 := flow.RegisterValue([]byte("parent value")) updatedParentValue3 := flow.RegisterValue([]byte("child value")) - childRegisterId1 := flow.NewRegisterID("child", "1") + childRegisterId1 := flow.NewRegisterID(childOwner, "1") childValue1 := flow.RegisterValue([]byte("child")) - childRegisterId2 := flow.NewRegisterID("child", "2") + childRegisterId2 := flow.NewRegisterID(childOwner, "2") parent := newStorageState( snapshot.MapStorageSnapshot{ diff --git a/fvm/storage/state/transaction_state_test.go b/fvm/storage/state/transaction_state_test.go index 5f91fe8b4b5..87144f14b0f 100644 --- a/fvm/storage/state/transaction_state_test.go +++ b/fvm/storage/state/transaction_state_test.go @@ -10,6 +10,7 @@ import ( "github.com/onflow/flow-go/fvm/meter" "github.com/onflow/flow-go/fvm/storage/state" "github.com/onflow/flow-go/model/flow" + "github.com/onflow/flow-go/utils/unittest" ) func newTestTransactionState() state.NestedTransactionPreparer { @@ -50,7 +51,7 @@ func TestUnrestrictedNestedTransactionBasic(t *testing.T) { // Ensure the values are written to the correctly nested state - key := flow.NewRegisterID("address", "key") + key := flow.NewRegisterID(unittest.RandomAddressFixture(), "key") val := createByteArray(2) err = txn.Set(key, val) @@ -173,7 +174,7 @@ func TestParseRestrictedNestedTransactionBasic(t *testing.T) { // Sanity check - key := flow.NewRegisterID("address", "key") + key := flow.NewRegisterID(unittest.RandomAddressFixture(), "key") v, err := restrictedNestedState2.Get(key) require.NoError(t, err) @@ -280,7 +281,7 @@ func TestRestartNestedTransaction(t *testing.T) { id, err := txn.BeginNestedTransaction() require.NoError(t, err) - key := flow.NewRegisterID("address", "key") + key := flow.NewRegisterID(unittest.RandomAddressFixture(), "key") val := createByteArray(2) for i := 0; i < 10; i++ { @@ -332,7 +333,7 @@ func TestRestartNestedTransactionWithInvalidId(t *testing.T) { id, err := txn.BeginNestedTransaction() require.NoError(t, err) - key := flow.NewRegisterID("address", "key") + key := flow.NewRegisterID(unittest.RandomAddressFixture(), "key") val := createByteArray(2) err = txn.Set(key, val) @@ -498,7 +499,7 @@ func TestFinalizeMainTransaction(t *testing.T) { id1, err := txn.BeginNestedTransaction() require.NoError(t, err) - registerId := flow.NewRegisterID("foo", "bar") + registerId := flow.NewRegisterID(unittest.RandomAddressFixture(), "bar") value, err := txn.Get(registerId) require.NoError(t, err) @@ -531,18 +532,21 @@ func TestInterimReadSet(t *testing.T) { // Setup test with a bunch of outstanding nested transaction. - readRegisterId1 := flow.NewRegisterID("read", "1") - readRegisterId2 := flow.NewRegisterID("read", "2") - readRegisterId3 := flow.NewRegisterID("read", "3") - readRegisterId4 := flow.NewRegisterID("read", "4") + readOwner := unittest.RandomAddressFixture() + writeOwner := unittest.RandomAddressFixture() - writeRegisterId1 := flow.NewRegisterID("write", "1") + readRegisterId1 := flow.NewRegisterID(readOwner, "1") + readRegisterId2 := flow.NewRegisterID(readOwner, "2") + readRegisterId3 := flow.NewRegisterID(readOwner, "3") + readRegisterId4 := flow.NewRegisterID(readOwner, "4") + + writeRegisterId1 := flow.NewRegisterID(writeOwner, "1") writeValue1 := flow.RegisterValue([]byte("value1")) - writeRegisterId2 := flow.NewRegisterID("write", "2") + writeRegisterId2 := flow.NewRegisterID(writeOwner, "2") writeValue2 := flow.RegisterValue([]byte("value2")) - writeRegisterId3 := flow.NewRegisterID("write", "3") + writeRegisterId3 := flow.NewRegisterID(writeOwner, "3") writeValue3 := flow.RegisterValue([]byte("value3")) err := txn.Set(writeRegisterId1, writeValue1) diff --git a/fvm/systemcontracts/system_contracts.go b/fvm/systemcontracts/system_contracts.go index 192e5dd67cc..a0c3a39848d 100644 --- a/fvm/systemcontracts/system_contracts.go +++ b/fvm/systemcontracts/system_contracts.go @@ -40,6 +40,9 @@ const ( ContractNameViewResolver = "ViewResolver" ContractNameEVM = "EVM" + // AccountNameEVMStorage is not a contract, but a special account that is used to store EVM state + AccountNameEVMStorage = "EVMStorageAccount" + // Unqualified names of service events (not including address prefix or contract name) EventNameEpochSetup = "EpochSetup" @@ -61,7 +64,7 @@ const ( FungibleTokenAccountIndex = 2 FlowTokenAccountIndex = 3 FlowFeesAccountIndex = 4 - EVMAccountIndex = 5 + EVMStorageAccountIndex = 5 ) // Well-known addresses for system contracts on long-running networks. @@ -79,6 +82,11 @@ var ( nftTokenAddressMainnet = flow.HexToAddress("1d7e57aa55817448") // nftTokenAddressTestnet is the address of the NonFungibleToken contract on Testnet nftTokenAddressTestnet = flow.HexToAddress("631e88ae7f1d7c20") + + // evmStorageAddressTestnet is the address of the EVM state storage contract on Testnet + evmStorageAddressTestnet = flow.HexToAddress("1a54ed2be7552821") + // evmStorageAddressMainnet is the address of the EVM state storage contract on Mainnet + evmStorageAddressMainnet = flow.HexToAddress("d421a63faae318f9") ) // SystemContract represents a system contract on a particular chain. @@ -87,6 +95,12 @@ type SystemContract struct { Name string } +// SystemAccount represents an address used by the system. +type SystemAccount struct { + Address flow.Address + Name string +} + // ServiceEvent represents a service event on a particular chain. type ServiceEvent struct { Address flow.Address @@ -131,7 +145,8 @@ type SystemContracts struct { ViewResolver SystemContract // EVM related contracts - EVM SystemContract + EVMContract SystemContract + EVMStorage SystemAccount } // AsTemplateEnv returns a template environment with all system contracts filled in. @@ -184,7 +199,8 @@ func (c SystemContracts) All() []SystemContract { c.MetadataViews, c.ViewResolver, - c.EVM, + c.EVMContract, + // EVMStorage is not included here, since it is not a contract } } @@ -273,6 +289,17 @@ func init() { } } + evmStorageEVMFunc := func(chain flow.ChainID) flow.Address { + switch chain { + case flow.Mainnet: + return evmStorageAddressMainnet + case flow.Testnet: + return evmStorageAddressTestnet + default: + return nthAddressFunc(EVMStorageAccountIndex)(chain) + } + } + contractAddressFunc = map[string]func(id flow.ChainID) flow.Address{ ContractNameIDTableStaking: epochAddressFunc, ContractNameEpoch: epochAddressFunc, @@ -292,12 +319,13 @@ func init() { ContractNameMetadataViews: nftTokenAddressFunc, ContractNameViewResolver: nftTokenAddressFunc, - ContractNameEVM: nthAddressFunc(EVMAccountIndex), + ContractNameEVM: serviceAddressFunc, + AccountNameEVMStorage: evmStorageEVMFunc, } getSystemContractsForChain := func(chainID flow.ChainID) *SystemContracts { - contract := func(name string) SystemContract { + addressOfContract := func(name string) SystemContract { addressFunc, ok := contractAddressFunc[name] if !ok { // this is a panic, since it can only happen if the code is wrong @@ -310,26 +338,40 @@ func init() { } } - contracts := &SystemContracts{ - Epoch: contract(ContractNameEpoch), - IDTableStaking: contract(ContractNameIDTableStaking), - ClusterQC: contract(ContractNameClusterQC), - DKG: contract(ContractNameDKG), - - FlowServiceAccount: contract(ContractNameServiceAccount), - NodeVersionBeacon: contract(ContractNameNodeVersionBeacon), - RandomBeaconHistory: contract(ContractNameRandomBeaconHistory), - FlowStorageFees: contract(ContractNameStorageFees), - - FlowFees: contract(ContractNameFlowFees), - FlowToken: contract(ContractNameFlowToken), - FungibleToken: contract(ContractNameFungibleToken), + addressOfAccount := func(name string) SystemAccount { + addressFunc, ok := contractAddressFunc[name] + if !ok { + // this is a panic, since it can only happen if the code is wrong + panic(fmt.Sprintf("unknown system account name: %s", name)) + } - NonFungibleToken: contract(ContractNameNonFungibleToken), - MetadataViews: contract(ContractNameMetadataViews), - ViewResolver: contract(ContractNameViewResolver), + return SystemAccount{ + Address: addressFunc(chainID), + Name: name, + } + } - EVM: contract(ContractNameEVM), + contracts := &SystemContracts{ + Epoch: addressOfContract(ContractNameEpoch), + IDTableStaking: addressOfContract(ContractNameIDTableStaking), + ClusterQC: addressOfContract(ContractNameClusterQC), + DKG: addressOfContract(ContractNameDKG), + + FlowServiceAccount: addressOfContract(ContractNameServiceAccount), + NodeVersionBeacon: addressOfContract(ContractNameNodeVersionBeacon), + RandomBeaconHistory: addressOfContract(ContractNameRandomBeaconHistory), + FlowStorageFees: addressOfContract(ContractNameStorageFees), + + FlowFees: addressOfContract(ContractNameFlowFees), + FlowToken: addressOfContract(ContractNameFlowToken), + FungibleToken: addressOfContract(ContractNameFungibleToken), + + NonFungibleToken: addressOfContract(ContractNameNonFungibleToken), + MetadataViews: addressOfContract(ContractNameMetadataViews), + ViewResolver: addressOfContract(ContractNameViewResolver), + + EVMContract: addressOfContract(ContractNameEVM), + EVMStorage: addressOfAccount(AccountNameEVMStorage), } return contracts diff --git a/fvm/transactionInvoker.go b/fvm/transactionInvoker.go index acd4088c4bd..57c0c449cbf 100644 --- a/fvm/transactionInvoker.go +++ b/fvm/transactionInvoker.go @@ -406,6 +406,7 @@ func (executor *transactionExecutor) normalExecution() ( // actual balance, for the purpose of calculating storage capacity, because the payer will have to pay for this tx. executor.txnState.RunWithAllLimitsDisabled(func() { err = executor.CheckStorageLimits( + executor.ctx, executor.env, bodySnapshot, executor.proc.Transaction.Payer, diff --git a/fvm/transactionPayerBalanceChecker_test.go b/fvm/transactionPayerBalanceChecker_test.go index 931f2984bd1..99bf19354a6 100644 --- a/fvm/transactionPayerBalanceChecker_test.go +++ b/fvm/transactionPayerBalanceChecker_test.go @@ -52,7 +52,7 @@ func TestTransactionPayerBalanceChecker(t *testing.T) { d := fvm.TransactionPayerBalanceChecker{} maxFees, err := d.CheckPayerBalanceAndReturnMaxFees(proc, txnState, env) require.Error(t, err) - require.True(t, errors.HasErrorCode(err, errors.FailureCodePayerBalanceCheckFailure)) + require.True(t, errors.HasFailureCode(err, errors.FailureCodePayerBalanceCheckFailure)) require.ErrorIs(t, err, someError) require.Equal(t, uint64(0), maxFees) }) @@ -73,7 +73,7 @@ func TestTransactionPayerBalanceChecker(t *testing.T) { d := fvm.TransactionPayerBalanceChecker{} maxFees, err := d.CheckPayerBalanceAndReturnMaxFees(proc, txnState, env) require.Error(t, err) - require.True(t, errors.HasErrorCode(err, errors.FailureCodePayerBalanceCheckFailure)) + require.True(t, errors.HasFailureCode(err, errors.FailureCodePayerBalanceCheckFailure)) require.Equal(t, uint64(0), maxFees) }) diff --git a/fvm/transactionStorageLimiter.go b/fvm/transactionStorageLimiter.go index 9d504adf7bf..261942bf8b0 100644 --- a/fvm/transactionStorageLimiter.go +++ b/fvm/transactionStorageLimiter.go @@ -11,6 +11,7 @@ import ( "github.com/onflow/flow-go/fvm/environment" "github.com/onflow/flow-go/fvm/errors" "github.com/onflow/flow-go/fvm/storage/snapshot" + "github.com/onflow/flow-go/fvm/systemcontracts" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/module/trace" ) @@ -33,6 +34,7 @@ type TransactionStorageLimiter struct{} // The payers balance is considered to be maxTxFees lower that its actual balance, due to the fact that // the fee deduction step happens after the storage limit check. func (limiter TransactionStorageLimiter) CheckStorageLimits( + ctx Context, env environment.Environment, snapshot *snapshot.ExecutionSnapshot, payer flow.Address, @@ -44,7 +46,7 @@ func (limiter TransactionStorageLimiter) CheckStorageLimits( defer env.StartChildSpan(trace.FVMTransactionStorageUsedCheck).End() - err := limiter.checkStorageLimits(env, snapshot, payer, maxTxFees) + err := limiter.checkStorageLimits(ctx, env, snapshot, payer, maxTxFees) if err != nil { return fmt.Errorf("storage limit check failed: %w", err) } @@ -55,6 +57,7 @@ func (limiter TransactionStorageLimiter) CheckStorageLimits( // storage limit is exceeded. The returned list include addresses of updated // registers (and the payer's address). func (limiter TransactionStorageLimiter) getStorageCheckAddresses( + ctx Context, snapshot *snapshot.ExecutionSnapshot, payer flow.Address, maxTxFees uint64, @@ -71,12 +74,17 @@ func (limiter TransactionStorageLimiter) getStorageCheckAddresses( addresses = append(addresses, payer) } + sc := systemcontracts.SystemContractsForChain(ctx.Chain.ChainID()) for id := range snapshot.WriteSet { address, ok := addressFromRegisterId(id) if !ok { continue } + if limiter.shouldSkipSpecialAddress(ctx, address, sc) { + continue + } + _, ok = dedup[address] if ok { continue @@ -88,10 +96,10 @@ func (limiter TransactionStorageLimiter) getStorageCheckAddresses( slices.SortFunc( addresses, - func(a flow.Address, b flow.Address) bool { + func(a flow.Address, b flow.Address) int { // reverse order to maintain compatibility with previous // implementation. - return bytes.Compare(a[:], b[:]) >= 0 + return bytes.Compare(b[:], a[:]) }) return addresses } @@ -99,12 +107,13 @@ func (limiter TransactionStorageLimiter) getStorageCheckAddresses( // checkStorageLimits checks if the transaction changed the storage of any // address and exceeded the storage limit. func (limiter TransactionStorageLimiter) checkStorageLimits( + ctx Context, env environment.Environment, snapshot *snapshot.ExecutionSnapshot, payer flow.Address, maxTxFees uint64, ) error { - addresses := limiter.getStorageCheckAddresses(snapshot, payer, maxTxFees) + addresses := limiter.getStorageCheckAddresses(ctx, snapshot, payer, maxTxFees) usages := make([]uint64, len(addresses)) @@ -155,3 +164,18 @@ func (limiter TransactionStorageLimiter) checkStorageLimits( return nil } + +// shouldSkipSpecialAddress returns true if the address is a special address where storage +// limits are not enforced. +// This is currently only the EVM storage address. This is a temporary solution. +func (limiter TransactionStorageLimiter) shouldSkipSpecialAddress( + ctx Context, + address flow.Address, + sc *systemcontracts.SystemContracts, +) bool { + if !ctx.EVMEnabled { + return false + } + + return sc.EVMStorage.Address == address +} diff --git a/fvm/transactionStorageLimiter_test.go b/fvm/transactionStorageLimiter_test.go index b9b2a87ec3a..aa97fd2b4cd 100644 --- a/fvm/transactionStorageLimiter_test.go +++ b/fvm/transactionStorageLimiter_test.go @@ -8,9 +8,11 @@ import ( "github.com/stretchr/testify/require" "github.com/onflow/flow-go/fvm" + "github.com/onflow/flow-go/fvm/environment" fvmmock "github.com/onflow/flow-go/fvm/environment/mock" "github.com/onflow/flow-go/fvm/errors" "github.com/onflow/flow-go/fvm/storage/snapshot" + "github.com/onflow/flow-go/fvm/systemcontracts" "github.com/onflow/flow-go/fvm/tracing" "github.com/onflow/flow-go/model/flow" ) @@ -19,15 +21,19 @@ func TestTransactionStorageLimiter(t *testing.T) { owner := flow.HexToAddress("1") executionSnapshot := &snapshot.ExecutionSnapshot{ WriteSet: map[flow.RegisterID]flow.RegisterValue{ - flow.NewRegisterID(string(owner[:]), "a"): flow.RegisterValue("foo"), - flow.NewRegisterID(string(owner[:]), "b"): flow.RegisterValue("bar"), + flow.NewRegisterID(owner, "a"): flow.RegisterValue("foo"), + flow.NewRegisterID(owner, "b"): flow.RegisterValue("bar"), + }, + } + + ctx := fvm.Context{ + EnvironmentParams: environment.EnvironmentParams{ + Chain: flow.Emulator.Chain(), }, } t.Run("capacity > storage -> OK", func(t *testing.T) { - chain := flow.Mainnet.Chain() env := &fvmmock.Environment{} - env.On("Chain").Return(chain) env.On("LimitAccountStorage").Return(true) env.On("StartChildSpan", mock.Anything).Return( tracing.NewMockTracerSpan()) @@ -40,13 +46,11 @@ func TestTransactionStorageLimiter(t *testing.T) { ) d := &fvm.TransactionStorageLimiter{} - err := d.CheckStorageLimits(env, executionSnapshot, flow.EmptyAddress, 0) + err := d.CheckStorageLimits(ctx, env, executionSnapshot, flow.EmptyAddress, 0) require.NoError(t, err, "Transaction with higher capacity than storage used should work") }) t.Run("capacity = storage -> OK", func(t *testing.T) { - chain := flow.Mainnet.Chain() env := &fvmmock.Environment{} - env.On("Chain").Return(chain) env.On("LimitAccountStorage").Return(true) env.On("StartChildSpan", mock.Anything).Return( tracing.NewMockTracerSpan()) @@ -59,13 +63,11 @@ func TestTransactionStorageLimiter(t *testing.T) { ) d := &fvm.TransactionStorageLimiter{} - err := d.CheckStorageLimits(env, executionSnapshot, flow.EmptyAddress, 0) + err := d.CheckStorageLimits(ctx, env, executionSnapshot, flow.EmptyAddress, 0) require.NoError(t, err, "Transaction with equal capacity than storage used should work") }) t.Run("capacity = storage -> OK (dedup payer)", func(t *testing.T) { - chain := flow.Mainnet.Chain() env := &fvmmock.Environment{} - env.On("Chain").Return(chain) env.On("LimitAccountStorage").Return(true) env.On("StartChildSpan", mock.Anything).Return( tracing.NewMockTracerSpan()) @@ -78,13 +80,11 @@ func TestTransactionStorageLimiter(t *testing.T) { ) d := &fvm.TransactionStorageLimiter{} - err := d.CheckStorageLimits(env, executionSnapshot, owner, 0) + err := d.CheckStorageLimits(ctx, env, executionSnapshot, owner, 0) require.NoError(t, err, "Transaction with equal capacity than storage used should work") }) t.Run("capacity < storage -> Not OK", func(t *testing.T) { - chain := flow.Mainnet.Chain() env := &fvmmock.Environment{} - env.On("Chain").Return(chain) env.On("LimitAccountStorage").Return(true) env.On("StartChildSpan", mock.Anything).Return( tracing.NewMockTracerSpan()) @@ -97,13 +97,11 @@ func TestTransactionStorageLimiter(t *testing.T) { ) d := &fvm.TransactionStorageLimiter{} - err := d.CheckStorageLimits(env, executionSnapshot, flow.EmptyAddress, 0) + err := d.CheckStorageLimits(ctx, env, executionSnapshot, flow.EmptyAddress, 0) require.Error(t, err, "Transaction with lower capacity than storage used should fail") }) t.Run("capacity > storage -> OK (payer not updated)", func(t *testing.T) { - chain := flow.Mainnet.Chain() env := &fvmmock.Environment{} - env.On("Chain").Return(chain) env.On("LimitAccountStorage").Return(true) env.On("StartChildSpan", mock.Anything).Return( tracing.NewMockTracerSpan()) @@ -118,13 +116,11 @@ func TestTransactionStorageLimiter(t *testing.T) { executionSnapshot = &snapshot.ExecutionSnapshot{} d := &fvm.TransactionStorageLimiter{} - err := d.CheckStorageLimits(env, executionSnapshot, owner, 1) + err := d.CheckStorageLimits(ctx, env, executionSnapshot, owner, 1) require.NoError(t, err, "Transaction with higher capacity than storage used should work") }) t.Run("capacity < storage -> Not OK (payer not updated)", func(t *testing.T) { - chain := flow.Mainnet.Chain() env := &fvmmock.Environment{} - env.On("Chain").Return(chain) env.On("LimitAccountStorage").Return(true) env.On("StartChildSpan", mock.Anything).Return( tracing.NewMockTracerSpan()) @@ -139,13 +135,11 @@ func TestTransactionStorageLimiter(t *testing.T) { executionSnapshot = &snapshot.ExecutionSnapshot{} d := &fvm.TransactionStorageLimiter{} - err := d.CheckStorageLimits(env, executionSnapshot, owner, 1000) + err := d.CheckStorageLimits(ctx, env, executionSnapshot, owner, 1000) require.Error(t, err, "Transaction with lower capacity than storage used should fail") }) t.Run("if ctx LimitAccountStorage false-> OK", func(t *testing.T) { - chain := flow.Mainnet.Chain() env := &fvmmock.Environment{} - env.On("Chain").Return(chain) env.On("LimitAccountStorage").Return(false) env.On("StartChildSpan", mock.Anything).Return( tracing.NewMockTracerSpan()) @@ -159,27 +153,70 @@ func TestTransactionStorageLimiter(t *testing.T) { ) d := &fvm.TransactionStorageLimiter{} - err := d.CheckStorageLimits(env, executionSnapshot, flow.EmptyAddress, 0) + err := d.CheckStorageLimits(ctx, env, executionSnapshot, flow.EmptyAddress, 0) require.NoError(t, err, "Transaction with higher capacity than storage used should work") }) - t.Run("non existing accounts or any other errors on fetching storage used -> Not OK", func(t *testing.T) { - chain := flow.Mainnet.Chain() + t.Run( + "non existing accounts or any other errors on fetching storage used -> Not OK", + func(t *testing.T) { + env := &fvmmock.Environment{} + env.On("LimitAccountStorage").Return(true) + env.On("StartChildSpan", mock.Anything).Return( + tracing.NewMockTracerSpan()) + env.On("GetStorageUsed", mock.Anything). + Return(uint64(0), errors.NewAccountNotFoundError(owner)) + env.On("AccountsStorageCapacity", mock.Anything, mock.Anything, mock.Anything).Return( + cadence.NewArray([]cadence.Value{ + bytesToUFix64(100), + }), + nil, + ) + + d := &fvm.TransactionStorageLimiter{} + err := d.CheckStorageLimits(ctx, env, executionSnapshot, flow.EmptyAddress, 0) + require.Error( + t, + err, + "check storage used on non existing account (not general registers) should fail", + ) + }, + ) + t.Run("special account is skipped", func(t *testing.T) { + sc := systemcontracts.SystemContractsForChain(ctx.Chain.ChainID()) + evm := sc.EVMStorage.Address + + executionSnapshot := &snapshot.ExecutionSnapshot{ + WriteSet: map[flow.RegisterID]flow.RegisterValue{ + flow.NewRegisterID(evm, "a"): flow.RegisterValue("foo"), + }, + } + env := &fvmmock.Environment{} - env.On("Chain").Return(chain) env.On("LimitAccountStorage").Return(true) env.On("StartChildSpan", mock.Anything).Return( tracing.NewMockTracerSpan()) - env.On("GetStorageUsed", mock.Anything).Return(uint64(0), errors.NewAccountNotFoundError(owner)) - env.On("AccountsStorageCapacity", mock.Anything, mock.Anything, mock.Anything).Return( - cadence.NewArray([]cadence.Value{ - bytesToUFix64(100), - }), - nil, - ) + env.On("GetStorageUsed", mock.Anything). + Return(uint64(0), errors.NewAccountNotFoundError(owner)) + env.On("AccountsStorageCapacity", mock.Anything, mock.Anything, mock.Anything). + Run(func(args mock.Arguments) { + require.Len(t, args.Get(0).([]flow.Address), 0) + }). + Return( + // since the special account is skipped, the resulting array from AccountsStorageCapacity should be empty + cadence.NewArray([]cadence.Value{}), + nil, + ) d := &fvm.TransactionStorageLimiter{} - err := d.CheckStorageLimits(env, executionSnapshot, flow.EmptyAddress, 0) - require.Error(t, err, "check storage used on non existing account (not general registers) should fail") + + // if EVM is disabled don't skip the storage check + err := d.CheckStorageLimits(ctx, env, executionSnapshot, flow.EmptyAddress, 0) + require.Error(t, err) + + // if EVM is enabled skip the storage check + ctx := fvm.NewContextFromParent(ctx, fvm.WithEVMEnabled(true)) + err = d.CheckStorageLimits(ctx, env, executionSnapshot, flow.EmptyAddress, 0) + require.NoError(t, err) }) } diff --git a/go.mod b/go.mod index 3831002f113..599adfc0b90 100644 --- a/go.mod +++ b/go.mod @@ -7,22 +7,22 @@ require ( cloud.google.com/go/profiler v0.3.0 cloud.google.com/go/storage v1.30.1 github.com/antihax/optional v1.0.0 - github.com/aws/aws-sdk-go-v2/config v1.18.19 + github.com/aws/aws-sdk-go-v2/config v1.25.5 github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.5.1 github.com/aws/aws-sdk-go-v2/service/s3 v1.15.0 github.com/btcsuite/btcd/btcec/v2 v2.2.1 github.com/davecgh/go-spew v1.1.1 github.com/dgraph-io/badger/v2 v2.2007.4 github.com/ef-ds/deque v1.0.4 - github.com/ethereum/go-ethereum v1.12.0 + github.com/ethereum/go-ethereum v1.13.5 github.com/fxamacker/cbor/v2 v2.4.1-0.20230228173756-c0c9f774e40c github.com/gammazero/workerpool v1.1.2 github.com/gogo/protobuf v1.3.2 github.com/golang/mock v1.6.0 github.com/golang/protobuf v1.5.3 - github.com/google/go-cmp v0.5.9 + github.com/google/go-cmp v0.6.0 github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 - github.com/google/uuid v1.3.0 + github.com/google/uuid v1.4.0 github.com/gorilla/mux v1.8.0 github.com/grpc-ecosystem/go-grpc-middleware/providers/zerolog/v2 v2.0.0-rc.2 github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.0-20200501113911-9a95f0fdbfea @@ -51,13 +51,13 @@ require ( github.com/multiformats/go-multiaddr-dns v0.3.1 github.com/multiformats/go-multihash v0.2.3 github.com/onflow/atree v0.6.0 - github.com/onflow/cadence v0.42.6 + github.com/onflow/cadence v0.42.8 + github.com/onflow/crypto v0.25.0 github.com/onflow/flow v0.3.4 - github.com/onflow/flow-core-contracts/lib/go/contracts v0.14.0 - github.com/onflow/flow-core-contracts/lib/go/templates v0.14.0 - github.com/onflow/flow-go-sdk v0.41.17 - github.com/onflow/flow-go/crypto v0.25.0 - github.com/onflow/flow/protobuf/go/flow v0.3.2-0.20231124194313-106cc495def6 + github.com/onflow/flow-core-contracts/lib/go/contracts v0.15.1-0.20231219201108-fbdb10b0a2da + github.com/onflow/flow-core-contracts/lib/go/templates v0.15.1-0.20231219201108-fbdb10b0a2da + github.com/onflow/flow-go-sdk v0.44.0 + github.com/onflow/flow/protobuf/go/flow v0.3.2-0.20231213135419-ae911cc351a2 github.com/onflow/go-bitswap v0.0.0-20230703214630-6d3db958c73d github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 github.com/pierrec/lz4 v2.6.1+incompatible @@ -81,16 +81,16 @@ require ( go.opentelemetry.io/otel/trace v1.16.0 go.uber.org/atomic v1.11.0 go.uber.org/multierr v1.11.0 - golang.org/x/crypto v0.12.0 - golang.org/x/exp v0.0.0-20230321023759-10a507213a29 - golang.org/x/sync v0.3.0 - golang.org/x/sys v0.11.0 - golang.org/x/text v0.12.0 - golang.org/x/time v0.1.0 - golang.org/x/tools v0.9.1 - google.golang.org/api v0.126.0 - google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98 - google.golang.org/grpc v1.58.3 + golang.org/x/crypto v0.17.0 + golang.org/x/exp v0.0.0-20231214170342-aacd6d4b4611 + golang.org/x/sync v0.5.0 + golang.org/x/sys v0.15.0 + golang.org/x/text v0.14.0 + golang.org/x/time v0.3.0 + golang.org/x/tools v0.16.0 + google.golang.org/api v0.151.0 + google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b + google.golang.org/grpc v1.59.0 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.2.0 google.golang.org/protobuf v1.31.0 gotest.tools v2.2.0+incompatible @@ -98,41 +98,44 @@ require ( ) require ( - github.com/cockroachdb/pebble v0.0.0-20230906160148-46873a6a7a06 + github.com/cockroachdb/pebble v0.0.0-20230928194634-aa077af62593 github.com/coreos/go-semver v0.3.0 github.com/go-playground/validator/v10 v10.14.1 github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb github.com/gorilla/websocket v1.5.0 github.com/hashicorp/golang-lru/v2 v2.0.2 github.com/mitchellh/mapstructure v1.5.0 + github.com/onflow/flow-ft/lib/go/contracts v0.7.1-0.20230711213910-baad011d2b13 + github.com/onflow/flow-nft/lib/go/contracts v1.1.0 github.com/onflow/wal v0.0.0-20230529184820-bc9f8244608d github.com/slok/go-http-metrics v0.10.0 github.com/sony/gobreaker v0.5.0 - google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 - google.golang.org/genproto/googleapis/bytestream v0.0.0-20230530153820-e85fd2cbaebc + google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b + google.golang.org/genproto/googleapis/bytestream v0.0.0-20231030173426-d783a09b4405 gopkg.in/yaml.v2 v2.4.0 ) require ( - cloud.google.com/go v0.110.4 // indirect - cloud.google.com/go/compute v1.21.0 // indirect - cloud.google.com/go/iam v1.1.1 // indirect + cloud.google.com/go v0.110.8 // indirect + cloud.google.com/go/compute v1.23.1 // indirect + cloud.google.com/go/iam v1.1.3 // indirect github.com/DataDog/zstd v1.5.2 // indirect - github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 // indirect - github.com/VictoriaMetrics/fastcache v1.6.0 // indirect - github.com/aws/aws-sdk-go-v2 v1.17.7 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.13.18 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.1 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.31 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.25 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.3.32 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.3.0 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.25 // indirect + github.com/Microsoft/go-winio v0.6.1 // indirect + github.com/StackExchange/wmi v1.2.1 // indirect + github.com/VictoriaMetrics/fastcache v1.12.1 // indirect + github.com/aws/aws-sdk-go-v2 v1.23.1 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.16.4 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.5 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.4 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.4 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.7.1 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.1 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.4 // indirect github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.7.0 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.12.6 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.6 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.18.7 // indirect - github.com/aws/smithy-go v1.13.5 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.17.3 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.20.1 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.25.4 // indirect + github.com/aws/smithy-go v1.17.0 // indirect github.com/benbjohnson/clock v1.3.5 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bits-and-blooms/bitset v1.7.0 // indirect @@ -144,8 +147,12 @@ require ( github.com/cockroachdb/errors v1.9.1 // indirect github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect github.com/cockroachdb/redact v1.1.3 // indirect + github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect + github.com/consensys/bavard v0.1.13 // indirect + github.com/consensys/gnark-crypto v0.12.1 // indirect github.com/containerd/cgroups v1.1.0 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect + github.com/crate-crypto/go-kzg-4844 v0.7.0 // indirect github.com/cskr/pubsub v1.0.2 // indirect github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect github.com/deckarep/golang-set/v2 v2.1.0 // indirect @@ -156,6 +163,7 @@ require ( github.com/docker/go-units v0.5.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/elastic/gosigar v0.14.2 // indirect + github.com/ethereum/c-kzg-4844 v0.4.0 // indirect github.com/felixge/fgprof v0.9.3 // indirect github.com/flynn/noise v1.0.0 // indirect github.com/francoispqt/gojay v1.2.13 // indirect @@ -177,17 +185,17 @@ require ( github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/gofrs/flock v0.8.1 // indirect - github.com/golang/glog v1.1.0 // indirect + github.com/golang/glog v1.1.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/google/gopacket v1.1.19 // indirect - github.com/google/s2a-go v0.1.4 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect - github.com/googleapis/gax-go/v2 v2.11.0 // indirect + github.com/google/s2a-go v0.1.7 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect + github.com/googleapis/gax-go/v2 v2.12.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/holiman/bloomfilter/v2 v2.0.3 // indirect - github.com/holiman/uint256 v1.2.2-0.20230321075855-87b91420868c // indirect - github.com/huin/goupnp v1.2.0 // indirect + github.com/holiman/uint256 v1.2.3 // indirect + github.com/huin/goupnp v1.3.0 // indirect github.com/inconshreveable/mousetrap v1.0.1 // indirect github.com/ipfs/bbloom v0.0.4 // indirect github.com/ipfs/boxo v0.10.0 // indirect @@ -231,14 +239,15 @@ require ( github.com/magiconair/properties v1.8.7 // indirect github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.19 // indirect - github.com/mattn/go-runewidth v0.0.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/miekg/dns v1.1.54 // indirect github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect github.com/minio/sha256-simd v1.0.1 // indirect github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect + github.com/mmcloughlin/addchain v0.4.0 // indirect github.com/mr-tron/base58 v1.2.0 // indirect github.com/multiformats/go-base32 v0.1.0 // indirect github.com/multiformats/go-base36 v0.2.0 // indirect @@ -248,8 +257,6 @@ require ( github.com/multiformats/go-multistream v0.4.1 // indirect github.com/multiformats/go-varint v0.0.7 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect - github.com/onflow/flow-ft/lib/go/contracts v0.7.1-0.20230711213910-baad011d2b13 // indirect - github.com/onflow/flow-nft/lib/go/contracts v1.1.0 // indirect github.com/onflow/sdks v0.5.0 // indirect github.com/onsi/ginkgo/v2 v2.9.7 // indirect github.com/opencontainers/runtime-spec v1.0.2 // indirect @@ -272,15 +279,16 @@ require ( github.com/rogpeppe/go-internal v1.9.0 // indirect github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect - github.com/spf13/afero v1.9.3 // indirect + github.com/spf13/afero v1.10.0 // indirect github.com/spf13/cast v1.5.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/stretchr/objx v0.5.0 // indirect github.com/subosito/gotenv v1.4.2 // indirect + github.com/supranational/blst v0.3.11 // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect github.com/texttheater/golang-levenshtein/levenshtein v0.0.0-20200805054039-cae8b0eaed6c // indirect - github.com/tklauser/go-sysconf v0.3.9 // indirect - github.com/tklauser/numcpus v0.3.0 // indirect + github.com/tklauser/go-sysconf v0.3.12 // indirect + github.com/tklauser/numcpus v0.6.1 // indirect github.com/turbolent/prettier v0.0.0-20220320183459-661cc755135d // indirect github.com/vmihailenco/tagparser v0.1.1 // indirect github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 // indirect @@ -295,17 +303,17 @@ require ( go.uber.org/dig v1.17.0 // indirect go.uber.org/fx v1.19.2 // indirect go.uber.org/zap v1.24.0 // indirect - golang.org/x/mod v0.10.0 // indirect - golang.org/x/net v0.12.0 // indirect - golang.org/x/oauth2 v0.10.0 // indirect - golang.org/x/term v0.11.0 // indirect + golang.org/x/mod v0.14.0 // indirect + golang.org/x/net v0.19.0 // indirect + golang.org/x/oauth2 v0.13.0 // indirect + golang.org/x/term v0.15.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect gonum.org/v1/gonum v0.13.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405 // indirect gopkg.in/ini.v1 v1.67.0 // indirect - gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect gopkg.in/yaml.v3 v3.0.1 // indirect lukechampine.com/blake3 v1.2.1 // indirect nhooyr.io/websocket v1.8.7 // indirect + rsc.io/tmplfunc v0.0.3 // indirect ) diff --git a/go.sum b/go.sum index 3aad26209c9..639469ea769 100644 --- a/go.sum +++ b/go.sum @@ -31,8 +31,8 @@ cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= -cloud.google.com/go v0.110.4 h1:1JYyxKMN9hd5dR2MYTPWkGUgcoxVVhg0LKNKEo0qvmk= -cloud.google.com/go v0.110.4/go.mod h1:+EYjdK8e5RME/VY/qLCAtuyALQ9q67dvuum8i+H5xsI= +cloud.google.com/go v0.110.8 h1:tyNdfIxjzaWctIiLYOTalaLKZ17SI44SKFW26QbOhME= +cloud.google.com/go v0.110.8/go.mod h1:Iz8AkXJf1qmxC3Oxoep8R1T36w8B92yU29PcBhHO5fk= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= @@ -44,15 +44,15 @@ cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJW cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s= cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= -cloud.google.com/go/compute v1.21.0 h1:JNBsyXVoOoNJtTQcnEY5uYpZIbeCTYIeDe0Xh1bySMk= -cloud.google.com/go/compute v1.21.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= +cloud.google.com/go/compute v1.23.1 h1:V97tBoDaZHb6leicZ1G6DLK2BAaZLJ/7+9BB/En3hR0= +cloud.google.com/go/compute v1.23.1/go.mod h1:CqB3xpmPKKt3OJpW2ndFIXnA9A4xAy/F3Xp1ixncW78= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= -cloud.google.com/go/iam v1.1.1 h1:lW7fzj15aVIXYHREOqjRBV9PsH0Z6u8Y46a1YGvQP4Y= -cloud.google.com/go/iam v1.1.1/go.mod h1:A5avdyVL2tCppe4unb0951eI9jreack+RJ0/d+KUZOU= +cloud.google.com/go/iam v1.1.3 h1:18tKG7DzydKWUnLjonWcJO6wjSCAtzh4GcRKlH/Hrzc= +cloud.google.com/go/iam v1.1.3/go.mod h1:3khUlaBXfPKKe7huYgEpDn6FtgRyMEqbkvBxrQyY5SE= cloud.google.com/go/kms v1.0.0/go.mod h1:nhUehi+w7zht2XrUfvTRNpxrfayBHqP4lu2NSywui/0= cloud.google.com/go/profiler v0.3.0 h1:R6y/xAeifaUXxd2x6w+jIwKxoKl8Cv5HJvcvASTPWJo= cloud.google.com/go/profiler v0.3.0/go.mod h1:9wYk9eY4iZHsev8TQb61kh3wiOiSyz/xOYixWPzweCU= @@ -100,17 +100,20 @@ github.com/DataDog/zstd v1.5.2/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwS github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Kubuxu/go-os-helper v0.0.1/go.mod h1:N8B+I7vPCT80IcP58r50u4+gEEcsZETFUpAzWW2ep1Y= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OneOfOne/xxhash v1.2.5 h1:zl/OfRA6nftbBK9qTohYBJ5xvw6C/oNKizR7cZGl3cI= github.com/OneOfOne/xxhash v1.2.5/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= -github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 h1:fLjPD/aNc3UIOA6tDi6QXUemppXK3P9BI7mr2hd6gx8= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= +github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA= +github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= github.com/VictoriaMetrics/fastcache v1.5.3/go.mod h1:+jv9Ckb+za/P1ZRg/sulP5Ni1v49daAVERr0H3CuscE= -github.com/VictoriaMetrics/fastcache v1.6.0 h1:C/3Oi3EiBCqufydp1neRZkqcwmEiuRT9c3fqvvgKm5o= -github.com/VictoriaMetrics/fastcache v1.6.0/go.mod h1:0qHz5QP0GMX4pfmMA/zt5RgfNuXJrTP0zS7DqpHGGTw= +github.com/VictoriaMetrics/fastcache v1.12.1 h1:i0mICQuojGDL3KblA7wUNlY5lOK6a4bwt3uRKnkZU40= +github.com/VictoriaMetrics/fastcache v1.12.1/go.mod h1:tX04vaqcNoQeGLD+ra5pU5sWkuxnzWhEzLwhP9w653o= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= @@ -138,46 +141,47 @@ github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQ github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= github.com/aws/aws-sdk-go-v2 v1.9.0/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4= -github.com/aws/aws-sdk-go-v2 v1.17.7 h1:CLSjnhJSTSogvqUGhIC6LqFKATMRexcxLZ0i/Nzk9Eg= -github.com/aws/aws-sdk-go-v2 v1.17.7/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw= +github.com/aws/aws-sdk-go-v2 v1.23.1 h1:qXaFsOOMA+HsZtX8WoCa+gJnbyW7qyFFBlPqvTSzbaI= +github.com/aws/aws-sdk-go-v2 v1.23.1/go.mod h1:i1XDttT4rnf6vxc9AuskLc6s7XBee8rlLilKlc03uAA= github.com/aws/aws-sdk-go-v2/config v1.8.0/go.mod h1:w9+nMZ7soXCe5nT46Ri354SNhXDQ6v+V5wqDjnZE+GY= -github.com/aws/aws-sdk-go-v2/config v1.18.19 h1:AqFK6zFNtq4i1EYu+eC7lcKHYnZagMn6SW171la0bGw= -github.com/aws/aws-sdk-go-v2/config v1.18.19/go.mod h1:XvTmGMY8d52ougvakOv1RpiTLPz9dlG/OQHsKU/cMmY= +github.com/aws/aws-sdk-go-v2/config v1.25.5 h1:UGKm9hpQS2hoK8CEJ1BzAW8NbUpvwDJJ4lyqXSzu8bk= +github.com/aws/aws-sdk-go-v2/config v1.25.5/go.mod h1:Bf4gDvy4ZcFIK0rqDu1wp9wrubNba2DojiPB2rt6nvI= github.com/aws/aws-sdk-go-v2/credentials v1.4.0/go.mod h1:dgGR+Qq7Wjcd4AOAW5Rf5Tnv3+x7ed6kETXyS9WCuAY= -github.com/aws/aws-sdk-go-v2/credentials v1.13.18 h1:EQMdtHwz0ILTW1hoP+EwuWhwCG1hD6l3+RWFQABET4c= -github.com/aws/aws-sdk-go-v2/credentials v1.13.18/go.mod h1:vnwlwjIe+3XJPBYKu1et30ZPABG3VaXJYr8ryohpIyM= +github.com/aws/aws-sdk-go-v2/credentials v1.16.4 h1:i7UQYYDSJrtc30RSwJwfBKwLFNnBTiICqAJ0pPdum8E= +github.com/aws/aws-sdk-go-v2/credentials v1.16.4/go.mod h1:Kdh/okh+//vQ/AjEt81CjvkTo64+/zIE4OewP7RpfXk= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.5.0/go.mod h1:CpNzHK9VEFUCknu50kkB8z58AH2B5DvPP7ea1LHve/Y= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.1 h1:gt57MN3liKiyGopcqgNzJb2+d9MJaKT/q1OksHNXVE4= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.1/go.mod h1:lfUx8puBRdM5lVVMQlwt2v+ofiG/X6Ms+dy0UkG/kXw= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.5 h1:KehRNiVzIfAcj6gw98zotVbb/K67taJE0fkfgM6vzqU= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.5/go.mod h1:VhnExhw6uXy9QzetvpXDolo1/hjhx4u9qukBGkuUwjs= github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.5.1 h1:VGkV9KmhGqOQWnHyi4gLG98kE6OecT42fdrCGFWxJsc= github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.5.1/go.mod h1:PLlnMiki//sGnCJiW+aVpvP/C8Kcm8mEj/IVm9+9qk4= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.31 h1:sJLYcS+eZn5EeNINGHSCRAwUJMFVqklwkH36Vbyai7M= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.31/go.mod h1:QT0BqUvX1Bh2ABdTGnjqEjvjzrCfIniM9Sc8zn9Yndo= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.25 h1:1mnRASEKnkqsntcxHaysxwgVoUUp5dkiB+l3llKnqyg= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.25/go.mod h1:zBHOPwhBc3FlQjQJE/D3IfPWiWaQmT06Vq9aNukDo0k= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.4 h1:LAm3Ycm9HJfbSCd5I+wqC2S9Ej7FPrgr5CQoOljJZcE= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.4/go.mod h1:xEhvbJcyUf/31yfGSQBe01fukXwXJ0gxDp7rLfymWE0= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.4 h1:4GV0kKZzUxiWxSVpn/9gwR0g21NF1Jsyduzo9rHgC/Q= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.4/go.mod h1:dYvTNAggxDZy6y1AF7YDwXsPuHFy/VNEpEI/2dWK9IU= github.com/aws/aws-sdk-go-v2/internal/ini v1.2.2/go.mod h1:BQV0agm+JEhqR+2RT5e1XTFIDcAAV0eW6z2trp+iduw= -github.com/aws/aws-sdk-go-v2/internal/ini v1.3.32 h1:p5luUImdIqywn6JpQsW3tq5GNOxKmOnEpybzPx+d1lk= -github.com/aws/aws-sdk-go-v2/internal/ini v1.3.32/go.mod h1:XGhIBZDEgfqmFIugclZ6FU7v75nHhBDtzuB4xB/tEi4= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.3.0 h1:gceOysEWNNwLd6cki65IMBZ4WAM0MwgBQq2n7kejoT8= +github.com/aws/aws-sdk-go-v2/internal/ini v1.7.1 h1:uR9lXYjdPX0xY+NhvaJ4dD8rpSRz5VY81ccIIoNG+lw= +github.com/aws/aws-sdk-go-v2/internal/ini v1.7.1/go.mod h1:6fQQgfuGmw8Al/3M2IgIllycxV7ZW7WCdVSqfBeUiCY= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.3.0/go.mod h1:v8ygadNyATSm6elwJ/4gzJwcFhri9RqS8skgHKiwXPU= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.1 h1:rpkF4n0CyFcrJUG/rNNohoTmhtWlFTRI4BsZOh9PvLs= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.1/go.mod h1:l9ymW25HOqymeU2m1gbUQ3rUIsTwKs8gYHXkqDQUhiI= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.3.0/go.mod h1:R1KK+vY8AfalhG1AOu5e35pOD2SdoPKQCFLTvnxiohk= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.25 h1:5LHn8JQ0qvjD9L9JhMtylnkcw7j05GDZqM9Oin6hpr0= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.25/go.mod h1:/95IA+0lMnzW6XzqYJRpjjsAbKEORVeO0anQqjd2CNU= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.4 h1:rdovz3rEu0vZKbzoMYPTehp0E8veoE9AyfzqCr5Eeao= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.4/go.mod h1:aYCGNjyUCUelhofxlZyj63srdxWUSsBSGg5l6MCuXuE= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.7.0 h1:HWsM0YQWX76V6MOp07YuTYacm8k7h69ObJuw7Nck+og= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.7.0/go.mod h1:LKb3cKNQIMh+itGnEpKGcnL/6OIjPZqrtYah1w5f+3o= github.com/aws/aws-sdk-go-v2/service/s3 v1.15.0 h1:nPLfLPfglacc29Y949sDxpr3X/blaY40s3B85WT2yZU= github.com/aws/aws-sdk-go-v2/service/s3 v1.15.0/go.mod h1:Iv2aJVtVSm/D22rFoX99cLG4q4uB7tppuCsulGe98k4= github.com/aws/aws-sdk-go-v2/service/sso v1.4.0/go.mod h1:+1fpWnL96DL23aXPpMGbsmKe8jLTEfbjuQoA4WS1VaA= -github.com/aws/aws-sdk-go-v2/service/sso v1.12.6 h1:5V7DWLBd7wTELVz5bPpwzYy/sikk0gsgZfj40X+l5OI= -github.com/aws/aws-sdk-go-v2/service/sso v1.12.6/go.mod h1:Y1VOmit/Fn6Tz1uFAeCO6Q7M2fmfXSCLeL5INVYsLuY= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.6 h1:B8cauxOH1W1v7rd8RdI/MWnoR4Ze0wIHWrb90qczxj4= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.6/go.mod h1:Lh/bc9XUf8CfOY6Jp5aIkQtN+j1mc+nExc+KXj9jx2s= +github.com/aws/aws-sdk-go-v2/service/sso v1.17.3 h1:CdsSOGlFF3Pn+koXOIpTtvX7st0IuGsZ8kJqcWMlX54= +github.com/aws/aws-sdk-go-v2/service/sso v1.17.3/go.mod h1:oA6VjNsLll2eVuUoF2D+CMyORgNzPEW/3PyUdq6WQjI= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.20.1 h1:cbRqFTVnJV+KRpwFl76GJdIZJKKCdTPnjUZ7uWh3pIU= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.20.1/go.mod h1:hHL974p5auvXlZPIjJTblXJpbkfK4klBczlsEaMCGVY= github.com/aws/aws-sdk-go-v2/service/sts v1.7.0/go.mod h1:0qcSMCyASQPN2sk/1KQLQ2Fh6yq8wm0HSDAimPhzCoM= -github.com/aws/aws-sdk-go-v2/service/sts v1.18.7 h1:bWNgNdRko2x6gqa0blfATqAZKZokPIeM1vfmQt2pnvM= -github.com/aws/aws-sdk-go-v2/service/sts v1.18.7/go.mod h1:JuTnSoeePXmMVe9G8NcjjwgOKEfZ4cOjMuT2IBT/2eI= +github.com/aws/aws-sdk-go-v2/service/sts v1.25.4 h1:yEvZ4neOQ/KpUqyR+X0ycUTW/kVRNR4nDZ38wStHGAA= +github.com/aws/aws-sdk-go-v2/service/sts v1.25.4/go.mod h1:feTnm2Tk/pJxdX+eooEsxvlvTWBvDm6CasRZ+JOs2IY= github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= -github.com/aws/smithy-go v1.13.5 h1:hgz0X/DX0dGqTYpGALqXJoRKRj5oQ7150i5FdTePzO8= -github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= +github.com/aws/smithy-go v1.17.0 h1:wWJD7LX6PBV6etBUwO0zElG0nWN9rUhp0WdYeHSHAaI= +github.com/aws/smithy-go v1.17.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE= github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= @@ -249,18 +253,24 @@ github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/cockroachdb/datadriven v1.0.2/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= -github.com/cockroachdb/datadriven v1.0.3-0.20230801171734-e384cf455877 h1:1MLK4YpFtIEo3ZtMA5C795Wtv5VuUnrXX7mQG+aHg6o= +github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4= github.com/cockroachdb/errors v1.9.1 h1:yFVvsI0VxmRShfawbt/laCIDy/mtTqqnvoNgiy5bEV8= github.com/cockroachdb/errors v1.9.1/go.mod h1:2sxOtL2WIc096WSZqZ5h8fa17rdDq9HZOZLBCor4mBk= github.com/cockroachdb/logtags v0.0.0-20211118104740-dabe8e521a4f/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE= github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= -github.com/cockroachdb/pebble v0.0.0-20230906160148-46873a6a7a06 h1:T+Np/xtzIjYM/P5NAw0e2Rf1FGvzDau1h54MKvx8G7w= -github.com/cockroachdb/pebble v0.0.0-20230906160148-46873a6a7a06/go.mod h1:bynZ3gvVyhlvjLI7PT6dmZ7g76xzJ7HpxfjgkzCGz6s= +github.com/cockroachdb/pebble v0.0.0-20230928194634-aa077af62593 h1:aPEJyR4rPBvDmeyi+l/FS/VtA00IWvjeFvjen1m1l1A= +github.com/cockroachdb/pebble v0.0.0-20230928194634-aa077af62593/go.mod h1:6hk1eMY/u5t+Cf18q5lFMUA1Rc+Sm5I6Ra1QuPyxXCo= github.com/cockroachdb/redact v1.1.3 h1:AKZds10rFSIj7qADf0g46UixK8NNLwWTNdCIGS5wfSQ= github.com/cockroachdb/redact v1.1.3/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= +github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo= +github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= +github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/YjhQ= +github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI= +github.com/consensys/gnark-crypto v0.12.1 h1:lHH39WuuFgVHONRl3J0LRBtuYdQTumFSDtJF7HpyG8M= +github.com/consensys/gnark-crypto v0.12.1/go.mod h1:v2Gy7L/4ZRosZ7Ivs+9SfUDr0f5UlG+EM5t7MPHiLuY= github.com/containerd/cgroups v0.0.0-20201119153540-4cbc285b3327/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw= @@ -283,6 +293,8 @@ github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwc github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/crate-crypto/go-kzg-4844 v0.7.0 h1:C0vgZRk4q4EZ/JgPfzuSoxdCq3C3mOZMBShovmncxvA= +github.com/crate-crypto/go-kzg-4844 v0.7.0/go.mod h1:1kMhvPgI0Ky3yIa+9lFySEBUBXkYxeOi8ZF1sYioxhc= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cskr/pubsub v1.0.2 h1:vlOzMhl6PFn60gRlTQQsIfVwaPB/B/8MziK8FhEPt/0= @@ -350,9 +362,11 @@ github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go. github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= +github.com/ethereum/c-kzg-4844 v0.4.0 h1:3MS1s4JtA868KpJxroZoepdV0ZKBp3u/O5HcZ7R3nlY= +github.com/ethereum/c-kzg-4844 v0.4.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= github.com/ethereum/go-ethereum v1.9.9/go.mod h1:a9TqabFudpDu1nucId+k9S8R9whYaHnGBLKFouA5EAo= -github.com/ethereum/go-ethereum v1.12.0 h1:bdnhLPtqETd4m3mS8BGMNvBTf36bO5bx/hxE2zljOa0= -github.com/ethereum/go-ethereum v1.12.0/go.mod h1:/oo2X/dZLJjf2mJ6YT9wcWxa4nNJDBKDBU6sFIpx1Gs= +github.com/ethereum/go-ethereum v1.13.5 h1:U6TCRciCqZRe4FPXmy1sMGxTfuk8P7u2UoinF3VbaFk= +github.com/ethereum/go-ethereum v1.13.5/go.mod h1:yMTu38GSuyxaYzQMViqNmQ1s3cE84abZexQmTgenWk0= github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8= github.com/fatih/color v1.3.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= @@ -427,6 +441,7 @@ 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= github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= +github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= @@ -456,7 +471,7 @@ github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8= github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo= github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= -github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= @@ -478,8 +493,8 @@ github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzq github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= -github.com/golang/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE= -github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= +github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo= +github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -543,8 +558,8 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -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-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= 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= @@ -577,16 +592,17 @@ github.com/google/pprof v0.0.0-20220412212628-83db2b799d1f/go.mod h1:Pt31oes+eGI github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 h1:hR7/MlvK23p6+lIw9SN1TigNLn9ZnF3W4SYRKq2gAHs= github.com/google/pprof v0.0.0-20230602150820-91b7bce49751/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/s2a-go v0.1.4 h1:1kZ/sQM3srePvKs3tXAvQzo66XfcReoqFpIpIccE7Oc= -github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= +github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= +github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= +github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9aurXEpJX+U6FLtpYTdC3R06k= -github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= +github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= @@ -596,8 +612,8 @@ github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0 github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM= github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= -github.com/googleapis/gax-go/v2 v2.11.0 h1:9V9PWXEsWnPpQhu/PeQIkS4eGzMlTLGgt80cUUI8Ki4= -github.com/googleapis/gax-go/v2 v2.11.0/go.mod h1:DxmR61SGKkGLa2xigwuZIQpkCI2S5iydzRfb3peWZJI= +github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas= +github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= @@ -666,14 +682,14 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= 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= -github.com/holiman/uint256 v1.2.2-0.20230321075855-87b91420868c/go.mod h1:SC8Ryt4n+UBbPbIBKaG9zbbDlp4jOru9xFZmPzLUTxw= +github.com/holiman/uint256 v1.2.3 h1:K8UWO1HUJpRMXBxbmaY1Y8IAMZC/RsKB+ArEnnK4l5o= +github.com/holiman/uint256 v1.2.3/go.mod h1:SC8Ryt4n+UBbPbIBKaG9zbbDlp4jOru9xFZmPzLUTxw= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/huin/goupnp v0.0.0-20161224104101-679507af18f3/go.mod h1:MZ2ZmwcBpvOoJ22IJsc7va19ZwoheaBk43rKg12SKag= github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc= -github.com/huin/goupnp v1.2.0 h1:uOKW26NG1hsSSbXIZ1IR7XP9Gjd1U8pnLaCMgntmkmY= -github.com/huin/goupnp v1.2.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= +github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= +github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= github.com/hydrogen18/memlistener v0.0.0-20200120041712-dcc25e7acd91/go.mod h1:qEIFzExnS6016fRpRfxrExeVn2gbClQA99gQhnIcdhE= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -1163,16 +1179,17 @@ github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-tty v0.0.3/go.mod h1:ihxohKRERHTVzN+aSVRwACLCeqIoZAWpoICkkvrWyR0= github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= @@ -1216,6 +1233,9 @@ github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:F github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY= +github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU= +github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -1325,27 +1345,27 @@ github.com/onflow/atree v0.1.0-beta1.0.20211027184039-559ee654ece9/go.mod h1:+6x github.com/onflow/atree v0.6.0 h1:j7nQ2r8npznx4NX39zPpBYHmdy45f4xwoi+dm37Jk7c= github.com/onflow/atree v0.6.0/go.mod h1:gBHU0M05qCbv9NN0kijLWMgC47gHVNBIp4KmsVFi0tc= github.com/onflow/cadence v0.20.1/go.mod h1:7mzUvPZUIJztIbr9eTvs+fQjWWHTF8veC+yk4ihcNIA= -github.com/onflow/cadence v0.42.6 h1:VtI0EpKrdbfqITRMsvyZC4dhgcW1x1LNUQuEpdMDzus= -github.com/onflow/cadence v0.42.6/go.mod h1:raU8va8QRyTa/eUbhej4mbyW2ETePfSaywoo36MddgE= +github.com/onflow/cadence v0.42.8 h1:6OWhKMjL2Lql6a3L2xeA44uoOAVBp5+cMNE5BDPvo+E= +github.com/onflow/cadence v0.42.8/go.mod h1:1wFd+LiNiN6qoZXof3MBdpM6d8BsxbVIxOA77LbIYmE= +github.com/onflow/crypto v0.25.0 h1:BeWbLsh3ZD13Ej+Uky6kg1PL1ZIVBDVX+2MVBNwqddg= +github.com/onflow/crypto v0.25.0/go.mod h1:C8FbaX0x8y+FxWjbkHy0Q4EASCDR9bSPWZqlpCLYyVI= github.com/onflow/flow v0.3.4 h1:FXUWVdYB90f/rjNcY0Owo30gL790tiYff9Pb/sycXYE= github.com/onflow/flow v0.3.4/go.mod h1:lzyAYmbu1HfkZ9cfnL5/sjrrsnJiUU8fRL26CqLP7+c= -github.com/onflow/flow-core-contracts/lib/go/contracts v0.14.0 h1:DpkgyNAP3SAe7dMQX/Tb7BWFstodqtREo4hxWFHwdS0= -github.com/onflow/flow-core-contracts/lib/go/contracts v0.14.0/go.mod h1:jM6GMAL+m0hjusUgiYDNrixPQ6b9s8xjoJQoEu5bHQI= -github.com/onflow/flow-core-contracts/lib/go/templates v0.14.0 h1:qZoMYSe7LaO2s6DWyB781FDntyded/gX5Guihldiv54= -github.com/onflow/flow-core-contracts/lib/go/templates v0.14.0/go.mod h1:ZeLxwaBkzuSInESGjL8/IPZWezF+YOYsYbMrZlhN+q4= +github.com/onflow/flow-core-contracts/lib/go/contracts v0.15.1-0.20231219201108-fbdb10b0a2da h1:8CEioYNnP0rwjnRbKDgs8SmiQTsdaroeX4d/Q3pQuh4= +github.com/onflow/flow-core-contracts/lib/go/contracts v0.15.1-0.20231219201108-fbdb10b0a2da/go.mod h1:WHp24VkUQfcfZi0XjI1uRVRt5alM5SHVkwOil1U2Tpc= +github.com/onflow/flow-core-contracts/lib/go/templates v0.15.1-0.20231219201108-fbdb10b0a2da h1:V2zI6AfDtPykMGhgw69ZEGcvyMudRUFOVHYCMN4BbQo= +github.com/onflow/flow-core-contracts/lib/go/templates v0.15.1-0.20231219201108-fbdb10b0a2da/go.mod h1:c09d6sNyF/j5/pAynK7sNPb1XKqJqk1rxZPEqEL+dUo= github.com/onflow/flow-ft/lib/go/contracts v0.7.1-0.20230711213910-baad011d2b13 h1:B4ll7e3j+MqTJv2122Enq3RtDNzmIGRu9xjV7fo7un0= github.com/onflow/flow-ft/lib/go/contracts v0.7.1-0.20230711213910-baad011d2b13/go.mod h1:kTMFIySzEJJeupk+7EmXs0EJ6CBWY/MV9fv9iYQk+RU= github.com/onflow/flow-go-sdk v0.24.0/go.mod h1:IoptMLPyFXWvyd9yYA6/4EmSeeozl6nJoIv4FaEMg74= -github.com/onflow/flow-go-sdk v0.41.17 h1:HpNn3j2fqLGA6H3HGfAuh2A+TsPBv8gWO3kvK9Hvtic= -github.com/onflow/flow-go-sdk v0.41.17/go.mod h1:ZIj2XBI9R0QiKzbI6iPwOeqyIy/M4+atczoMOEWdKYw= +github.com/onflow/flow-go-sdk v0.44.0 h1:gVRLcZ6LUNs/5mzHDx0mp4mEnBAWD62O51P4/nYm4rE= +github.com/onflow/flow-go-sdk v0.44.0/go.mod h1:mm1Fi2hiMrexNMwRzTrAN2zwTvlP8iQ5CF2JSAgJR8U= github.com/onflow/flow-go/crypto v0.21.3/go.mod h1:vI6V4CY3R6c4JKBxdcRiR/AnjBfL8OSD97bJc60cLuQ= -github.com/onflow/flow-go/crypto v0.25.0 h1:6lmoiAQ3APCF+nV7f4f2AXL3PuDKqQiWqRJXmjrMEq4= -github.com/onflow/flow-go/crypto v0.25.0/go.mod h1:OOb2vYcS8AOCajBClhHTJ0NKftFl1RQgTQ0+Vh4nbqk= github.com/onflow/flow-nft/lib/go/contracts v1.1.0 h1:rhUDeD27jhLwOqQKI/23008CYfnqXErrJvc4EFRP2a0= github.com/onflow/flow-nft/lib/go/contracts v1.1.0/go.mod h1:YsvzYng4htDgRB9sa9jxdwoTuuhjK8WYWXTyLkIigZY= github.com/onflow/flow/protobuf/go/flow v0.2.2/go.mod h1:gQxYqCfkI8lpnKsmIjwtN2mV/N2PIwc1I+RUK4HPIc8= -github.com/onflow/flow/protobuf/go/flow v0.3.2-0.20231124194313-106cc495def6 h1:KMN+OEVaw7KAgxL3p8ux7CMuyTvacAlYTbasOqowh4M= -github.com/onflow/flow/protobuf/go/flow v0.3.2-0.20231124194313-106cc495def6/go.mod h1:NA2pX2nw8zuaxfKphhKsk00kWLwfd+tv8mS23YXO4Sk= +github.com/onflow/flow/protobuf/go/flow v0.3.2-0.20231213135419-ae911cc351a2 h1:+rT+UsfTR39JZO8ht2+4fkaWfHw74SCj1fyz1lWuX8A= +github.com/onflow/flow/protobuf/go/flow v0.3.2-0.20231213135419-ae911cc351a2/go.mod h1:NA2pX2nw8zuaxfKphhKsk00kWLwfd+tv8mS23YXO4Sk= github.com/onflow/go-bitswap v0.0.0-20230703214630-6d3db958c73d h1:QcOAeEyF3iAUHv21LQ12sdcsr0yFrJGoGLyCAzYYtvI= github.com/onflow/go-bitswap v0.0.0-20230703214630-6d3db958c73d/go.mod h1:GCPpiyRoHncdqPj++zPr9ZOYBX4hpJ0pYZRYqSE8VKk= github.com/onflow/sdks v0.5.0 h1:2HCRibwqDaQ1c9oUApnkZtEAhWiNY2GTpRD5+ftdkN8= @@ -1565,8 +1585,8 @@ github.com/spaolacci/murmur3 v1.0.1-0.20190317074736-539464a789e9/go.mod h1:JwIa 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.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= -github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= +github.com/spf13/afero v1.10.0 h1:EaGW2JJh15aKOejeuJ+wpFSHnbd7GE6Wvp3TsNhb6LY= +github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= @@ -1612,6 +1632,8 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/supranational/blst v0.3.4/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= +github.com/supranational/blst v0.3.11 h1:LyU6FolezeWAhvQk0k6O/d49jqgO52MSDDfYgbeoEm4= +github.com/supranational/blst v0.3.11/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d/go.mod h1:9OrXJhf154huy1nPWmuSrkgjPUtUNhA+Zmy+6AESzuA= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= @@ -1619,10 +1641,12 @@ github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45 github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= github.com/texttheater/golang-levenshtein/levenshtein v0.0.0-20200805054039-cae8b0eaed6c h1:HelZ2kAFadG0La9d+4htN4HzQ68Bm2iM9qKMSMES6xg= github.com/texttheater/golang-levenshtein/levenshtein v0.0.0-20200805054039-cae8b0eaed6c/go.mod h1:JlzghshsemAMDGZLytTFY8C1JQxQPhnatWqNwUXjggo= -github.com/tklauser/go-sysconf v0.3.9 h1:JeUVdAOWhhxVcU6Eqr/ATFHgXk/mmiItdKeJPev3vTo= github.com/tklauser/go-sysconf v0.3.9/go.mod h1:11DU/5sG7UexIrp/O6g35hrWzu0JxlwQ3LSFUzyeuhs= -github.com/tklauser/numcpus v0.3.0 h1:ILuRUQBtssgnxw0XXIjKUC56fgnOrFoQQ/4+DeU2biQ= +github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= +github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= github.com/tklauser/numcpus v0.3.0/go.mod h1:yFGUr7TUHQRAhyqBcEg0Ge34zDBAsIvJJcyE6boqnA8= +github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/turbolent/prettier v0.0.0-20220320183459-661cc755135d h1:5JInRQbk5UBX8JfUvKh2oYTLMVwj3p6n+wapDDm7hko= @@ -1683,7 +1707,6 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= @@ -1794,10 +1817,9 @@ golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf/go.mod h1:P+XmwS30IXTQdn5 golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 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.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= -golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +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-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1812,8 +1834,8 @@ golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u0 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-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= -golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug= -golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/exp v0.0.0-20231214170342-aacd6d4b4611 h1:qCEDpW1G+vcj3Y7Fy52pEM1AWm3abj8WimGYejI3SC4= +golang.org/x/exp v0.0.0-20231214170342-aacd6d4b4611/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= 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= @@ -1841,9 +1863,8 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 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.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= -golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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= @@ -1911,9 +1932,8 @@ golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= -golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +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-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1935,8 +1955,8 @@ golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8= -golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI= +golang.org/x/oauth2 v0.13.0 h1:jDDenyj+WgFtmV3zYVoi8aE2BwtXFLWOA67ZfNWftiY= +golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0= golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1949,9 +1969,8 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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-20180810173357-98c5dad5d1a0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -2036,7 +2055,6 @@ golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210317225723-c4fcb01b228e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -2068,21 +2086,21 @@ golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -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.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.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-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0= -golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= +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= @@ -2092,16 +2110,15 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 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.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= -golang.org/x/text v0.12.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-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 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= golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.1.0 h1:xYY+Bajn2a7VBmTM5GikTmnK8ZuX8YgnQCqZpbBNtmA= -golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -2173,9 +2190,8 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= -golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= +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= @@ -2231,8 +2247,8 @@ google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs= google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw= -google.golang.org/api v0.126.0 h1:q4GJq+cAdMAC7XP7njvQ4tvohGLiSlytuL4BQxbIZ+o= -google.golang.org/api v0.126.0/go.mod h1:mBwVAtz+87bEN6CbA1GtZPDOqY2R5ONPqJeIlvyo4Aw= +google.golang.org/api v0.151.0 h1:FhfXLO/NFdJIzQtCqjpysWwqKk8AzGWBUhMIx67cVDU= +google.golang.org/api v0.151.0/go.mod h1:ccy+MJ6nrYFgE3WgRx/AMXOxOmU8Q4hSa+jjibzhxcg= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -2331,14 +2347,14 @@ google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98 h1:Z0hjGZePRE0ZBWotvtrwxFNrNE9CUAGtplaDK5NNI/g= -google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98/go.mod h1:S7mY02OqCJTD0E1OiQy1F72PWFB4bZJ87cAtLPYgDR0= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= -google.golang.org/genproto/googleapis/bytestream v0.0.0-20230530153820-e85fd2cbaebc h1:g3hIDl0jRNd9PPTs2uBzYuaD5mQuwOkZY0vSc0LR32o= -google.golang.org/genproto/googleapis/bytestream v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:ylj+BE99M198VPbBh6A8d9n3w8fChvyLK3wwBOjXBFA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 h1:bVf09lpb+OJbByTj913DRJioFFAjf/ZGxEz7MajTp2U= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= +google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b h1:+YaDE2r2OG8t/z5qmsh7Y+XXwCbvadxxZ0YY6mTdrVA= +google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:CgAqfJo+Xmu0GwA0411Ht3OU3OntXwsGmrmjI8ioGXI= +google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b h1:CIC2YMXmIhYw6evmhPxBKJ4fmLbOFtXQN/GV3XOZR8k= +google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:IBQ646DjkDkvUIsVq/cc03FUFQ9wbZu7yE396YcL870= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20231030173426-d783a09b4405 h1:o4S3HvTUEXgRsNSUQsALDVog0O9F/U1JJlHmmUN8Uas= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20231030173426-d783a09b4405/go.mod h1:GRUCuLdzVqZte8+Dl/D4N25yLzcGqqWaYkeVOwulFqw= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405 h1:AB/lmRny7e2pLhFEYIbl5qkDAUt2h0ZRO4wGPhZf+ik= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405/go.mod h1:67X1fPuzjcrkymZzZV1vvkFeTn2Rvc6lYF9MYFGCcwE= google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= @@ -2379,8 +2395,8 @@ google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ5 google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= -google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= +google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.2.0 h1:TLkBREm4nIsEcexnCjgQd5GQWaHcqMzwQV0TX9pq8S0= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.2.0/go.mod h1:DNq5QpG7LJqD2AamLZ7zvKE0DEpVl2BSEVjFycAAjRY= @@ -2418,7 +2434,6 @@ gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= -gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= gopkg.in/olebedev/go-duktape.v3 v3.0.0-20190213234257-ec84240a7772/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= @@ -2466,6 +2481,8 @@ rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8 rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU= +rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck= diff --git a/insecure/cmd/corrupted_builder.go b/insecure/cmd/corrupted_builder.go index 3b0e5987b22..a4b26211c21 100644 --- a/insecure/cmd/corrupted_builder.go +++ b/insecure/cmd/corrupted_builder.go @@ -14,8 +14,8 @@ import ( "github.com/onflow/flow-go/module/metrics" "github.com/onflow/flow-go/network" "github.com/onflow/flow-go/network/p2p" + p2pbuilderconfig "github.com/onflow/flow-go/network/p2p/builder/config" "github.com/onflow/flow-go/network/p2p/connection" - p2pconfig "github.com/onflow/flow-go/network/p2p/p2pbuilder/config" "github.com/onflow/flow-go/network/p2p/unicast/ratelimit" "github.com/onflow/flow-go/utils/logging" ) @@ -73,17 +73,17 @@ func (cnb *CorruptedNodeBuilder) enqueueNetworkingLayer() { myAddr = cnb.FlowNodeBuilder.BaseConfig.BindAddr } - uniCfg := &p2pconfig.UnicastConfig{ + uniCfg := &p2pbuilderconfig.UnicastConfig{ RateLimiterDistributor: cnb.UnicastRateLimiterDistributor, - UnicastConfig: cnb.FlowConfig.NetworkConfig.UnicastConfig, + Unicast: cnb.FlowConfig.NetworkConfig.Unicast, } - connGaterCfg := &p2pconfig.ConnectionGaterConfig{ + connGaterCfg := &p2pbuilderconfig.ConnectionGaterConfig{ InterceptPeerDialFilters: []p2p.PeerFilter{}, // disable connection gater onInterceptPeerDialFilters InterceptSecuredFilters: []p2p.PeerFilter{}, // disable connection gater onInterceptSecuredFilters } - peerManagerCfg := &p2pconfig.PeerManagerConfig{ + peerManagerCfg := &p2pbuilderconfig.PeerManagerConfig{ ConnectionPruning: cnb.FlowConfig.NetworkConfig.NetworkConnectionPruning, UpdateInterval: cnb.FlowConfig.NetworkConfig.PeerUpdateInterval, ConnectorFactory: connection.DefaultLibp2pBackoffConnectorFactory(), diff --git a/insecure/corruptlibp2p/libp2p_node_factory.go b/insecure/corruptlibp2p/libp2p_node_factory.go index b3ad94599ef..5bc70a50f0c 100644 --- a/insecure/corruptlibp2p/libp2p_node_factory.go +++ b/insecure/corruptlibp2p/libp2p_node_factory.go @@ -10,16 +10,19 @@ import ( "github.com/rs/zerolog" corrupt "github.com/yhassanzadeh13/go-libp2p-pubsub" + fcrypto "github.com/onflow/crypto" + "github.com/onflow/flow-go/cmd" - fcrypto "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/module" "github.com/onflow/flow-go/module/metrics" "github.com/onflow/flow-go/network" + "github.com/onflow/flow-go/network/codec/cbor" "github.com/onflow/flow-go/network/netconf" "github.com/onflow/flow-go/network/p2p" - "github.com/onflow/flow-go/network/p2p/p2pbuilder" - p2pconfig "github.com/onflow/flow-go/network/p2p/p2pbuilder/config" + p2pbuilder "github.com/onflow/flow-go/network/p2p/builder" + p2pbuilderconfig "github.com/onflow/flow-go/network/p2p/builder/config" + p2pnode "github.com/onflow/flow-go/network/p2p/node" ) // InitCorruptLibp2pNode initializes and returns a corrupt libp2p node that should only be used for BFT testing in @@ -56,9 +59,9 @@ func InitCorruptLibp2pNode( metricsCfg module.NetworkMetrics, resolver madns.BasicResolver, role string, - connGaterCfg *p2pconfig.ConnectionGaterConfig, - peerManagerCfg *p2pconfig.PeerManagerConfig, - uniCfg *p2pconfig.UnicastConfig, + connGaterCfg *p2pbuilderconfig.ConnectionGaterConfig, + peerManagerCfg *p2pbuilderconfig.PeerManagerConfig, + uniCfg *p2pbuilderconfig.UnicastConfig, netConfig *netconf.Config, disallowListCacheCfg *p2p.DisallowListCacheConfig, topicValidatorDisabled, @@ -69,7 +72,7 @@ func InitCorruptLibp2pNode( panic("illegal chain id for using corrupt libp2p node") } - metCfg := &p2pconfig.MetricsConfig{ + metCfg := &p2pbuilderconfig.MetricsConfig{ HeroCacheFactory: metrics.NewNoopHeroCacheMetricsFactory(), Metrics: metricsCfg, } @@ -90,10 +93,10 @@ func InitCorruptLibp2pNode( role, connGaterCfg, peerManagerCfg, - &netConfig.GossipSubConfig, + &netConfig.GossipSub, &netConfig.ResourceManager, uniCfg, - &netConfig.ConnectionManagerConfig, + &netConfig.ConnectionManager, disallowListCacheCfg, dhtActivationStatus) @@ -101,7 +104,21 @@ func InitCorruptLibp2pNode( return nil, fmt.Errorf("could not create corrupt libp2p node builder: %w", err) } if topicValidatorDisabled { - builder.SetCreateNode(NewCorruptLibP2PNode) + builder.OverrideNodeConstructor(func(config *p2p.NodeConfig) (p2p.LibP2PNode, error) { + node, err := p2pnode.NewNode(&p2p.NodeConfig{ + Logger: config.Logger, + Host: config.Host, + PeerManager: config.PeerManager, + Parameters: config.Parameters, + DisallowListCacheCfg: disallowListCacheCfg, + }) + + if err != nil { + return nil, fmt.Errorf("could not create libp2p node part of the corrupt libp2p: %w", err) + } + + return &CorruptP2PNode{Node: node, logger: config.Logger.With().Str("component", "corrupt_libp2p").Logger(), codec: cbor.NewCodec()}, nil + }) } overrideWithCorruptGossipSub( diff --git a/insecure/corruptlibp2p/p2p_node.go b/insecure/corruptlibp2p/p2p_node.go index b3fbd0cb36d..8d16cfdb69b 100644 --- a/insecure/corruptlibp2p/p2p_node.go +++ b/insecure/corruptlibp2p/p2p_node.go @@ -4,16 +4,14 @@ import ( "context" pubsub "github.com/libp2p/go-libp2p-pubsub" - "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/peer" "github.com/rs/zerolog" "github.com/onflow/flow-go/network" "github.com/onflow/flow-go/network/channels" - "github.com/onflow/flow-go/network/codec/cbor" "github.com/onflow/flow-go/network/message" "github.com/onflow/flow-go/network/p2p" - "github.com/onflow/flow-go/network/p2p/p2pnode" + p2pnode "github.com/onflow/flow-go/network/p2p/node" validator "github.com/onflow/flow-go/network/validator/pubsub" ) @@ -49,14 +47,3 @@ func (n *CorruptP2PNode) Subscribe(topic channels.Topic, _ p2p.TopicValidatorFun topicValidator := AcceptAllTopicValidator() return n.Node.Subscribe(topic, topicValidator) } - -// NewCorruptLibP2PNode returns corrupted libP2PNode that will subscribe to topics using the AcceptAllTopicValidator. -func NewCorruptLibP2PNode( - logger zerolog.Logger, - host host.Host, - pCache p2p.ProtocolPeerCache, - peerManager p2p.PeerManager, - disallowListCacheCfg *p2p.DisallowListCacheConfig) p2p.LibP2PNode { - node := p2pnode.NewNode(logger, host, pCache, peerManager, disallowListCacheCfg) - return &CorruptP2PNode{Node: node, logger: logger, codec: cbor.NewCodec()} -} diff --git a/insecure/corruptlibp2p/pubsub_adapter.go b/insecure/corruptlibp2p/pubsub_adapter.go index 64975a18e3c..80abf1f5454 100644 --- a/insecure/corruptlibp2p/pubsub_adapter.go +++ b/insecure/corruptlibp2p/pubsub_adapter.go @@ -14,8 +14,9 @@ import ( "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/module/component" "github.com/onflow/flow-go/module/irrecoverable" + "github.com/onflow/flow-go/network/channels" "github.com/onflow/flow-go/network/p2p" - "github.com/onflow/flow-go/network/p2p/p2plogging" + p2plogging "github.com/onflow/flow-go/network/p2p/logging" "github.com/onflow/flow-go/utils/logging" ) @@ -108,6 +109,11 @@ func (c *CorruptGossipSubAdapter) ListPeers(topic string) []peer.ID { return c.gossipSub.ListPeers(topic) } +func (c *CorruptGossipSubAdapter) GetLocalMeshPeers(topic channels.Topic) []peer.ID { + // this method is a no-op in the corrupt gossipsub; as the corrupt gossipsub is solely used for testing, it does not come with a mesh tracer. + return []peer.ID{} +} + func (c *CorruptGossipSubAdapter) ActiveClustersChanged(lst flow.ChainIDList) { c.clusterChangeConsumer.ActiveClustersChanged(lst) } @@ -127,7 +133,11 @@ func (c *CorruptGossipSubAdapter) PeerScoreExposer() p2p.PeerScoreExposer { return c.peerScoreExposer } -func NewCorruptGossipSubAdapter(ctx context.Context, logger zerolog.Logger, h host.Host, cfg p2p.PubSubAdapterConfig, clusterChangeConsumer p2p.CollectionClusterChangesConsumer) (p2p.PubSubAdapter, *corrupt.GossipSubRouter, error) { +func NewCorruptGossipSubAdapter(ctx context.Context, + logger zerolog.Logger, + h host.Host, + cfg p2p.PubSubAdapterConfig, + clusterChangeConsumer p2p.CollectionClusterChangesConsumer) (p2p.PubSubAdapter, *corrupt.GossipSubRouter, error) { gossipSubConfig, ok := cfg.(*CorruptPubSubAdapterConfig) if !ok { return nil, nil, fmt.Errorf("invalid gossipsub config type: %T", cfg) diff --git a/insecure/corruptnet/network.go b/insecure/corruptnet/network.go index c2f4c48d5b5..8051bfce1ef 100644 --- a/insecure/corruptnet/network.go +++ b/insecure/corruptnet/network.go @@ -14,8 +14,9 @@ import ( "github.com/rs/zerolog" "google.golang.org/grpc" - "github.com/onflow/flow-go/crypto" - "github.com/onflow/flow-go/crypto/hash" + "github.com/onflow/crypto" + "github.com/onflow/crypto/hash" + "github.com/onflow/flow-go/engine/execution/computation/computer" "github.com/onflow/flow-go/engine/execution/utils" verutils "github.com/onflow/flow-go/engine/verification/utils" diff --git a/insecure/go.mod b/insecure/go.mod index c34f44141b6..79caf6de233 100644 --- a/insecure/go.mod +++ b/insecure/go.mod @@ -10,8 +10,8 @@ require ( github.com/libp2p/go-libp2p v0.28.1 github.com/libp2p/go-libp2p-pubsub v0.9.3 github.com/multiformats/go-multiaddr-dns v0.3.1 + github.com/onflow/crypto v0.25.0 github.com/onflow/flow-go v0.32.4-0.20231130134727-3c01c7f8966c - github.com/onflow/flow-go/crypto v0.25.0 github.com/rs/zerolog v1.29.0 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.8.4 @@ -22,30 +22,31 @@ require ( ) require ( - cloud.google.com/go v0.110.7 // indirect - cloud.google.com/go/compute v1.23.0 // indirect + cloud.google.com/go v0.110.8 // indirect + cloud.google.com/go/compute v1.23.1 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect - cloud.google.com/go/iam v1.1.1 // indirect + cloud.google.com/go/iam v1.1.3 // indirect cloud.google.com/go/storage v1.30.1 // indirect github.com/DataDog/zstd v1.5.2 // indirect - github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 // indirect - github.com/VictoriaMetrics/fastcache v1.6.0 // indirect - github.com/aws/aws-sdk-go-v2 v1.17.7 // indirect - github.com/aws/aws-sdk-go-v2/config v1.18.19 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.13.18 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.1 // indirect + github.com/Microsoft/go-winio v0.6.1 // indirect + github.com/StackExchange/wmi v1.2.1 // indirect + github.com/VictoriaMetrics/fastcache v1.12.1 // indirect + github.com/aws/aws-sdk-go-v2 v1.23.1 // indirect + github.com/aws/aws-sdk-go-v2/config v1.25.5 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.16.4 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.5 // indirect github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.5.1 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.31 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.25 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.3.32 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.3.0 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.25 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.4 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.4 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.7.1 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.1 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.4 // indirect github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.7.0 // indirect github.com/aws/aws-sdk-go-v2/service/s3 v1.15.0 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.12.6 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.6 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.18.7 // indirect - github.com/aws/smithy-go v1.13.5 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.17.3 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.20.1 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.25.4 // indirect + github.com/aws/smithy-go v1.17.0 // indirect github.com/benbjohnson/clock v1.3.5 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bits-and-blooms/bitset v1.7.0 // indirect @@ -56,11 +57,15 @@ require ( github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/cockroachdb/errors v1.9.1 // indirect github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect - github.com/cockroachdb/pebble v0.0.0-20230906160148-46873a6a7a06 // indirect + github.com/cockroachdb/pebble v0.0.0-20230928194634-aa077af62593 // indirect github.com/cockroachdb/redact v1.1.3 // indirect + github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect + github.com/consensys/bavard v0.1.13 // indirect + github.com/consensys/gnark-crypto v0.12.1 // indirect github.com/containerd/cgroups v1.1.0 // indirect github.com/coreos/go-semver v0.3.0 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect + github.com/crate-crypto/go-kzg-4844 v0.7.0 // indirect github.com/cskr/pubsub v1.0.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect @@ -74,7 +79,8 @@ require ( github.com/dustin/go-humanize v1.0.1 // indirect github.com/ef-ds/deque v1.0.4 // indirect github.com/elastic/gosigar v0.14.2 // indirect - github.com/ethereum/go-ethereum v1.12.0 // indirect + github.com/ethereum/c-kzg-4844 v0.4.0 // indirect + github.com/ethereum/go-ethereum v1.13.5 // indirect github.com/flynn/noise v1.0.0 // indirect github.com/francoispqt/gojay v1.2.13 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect @@ -104,10 +110,10 @@ require ( github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect github.com/google/gopacket v1.1.19 // indirect github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 // indirect - github.com/google/s2a-go v0.1.4 // indirect - github.com/google/uuid v1.3.1 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect - github.com/googleapis/gax-go/v2 v2.11.0 // indirect + github.com/google/s2a-go v0.1.7 // indirect + github.com/google/uuid v1.4.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect + github.com/googleapis/gax-go/v2 v2.12.0 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/gorilla/websocket v1.5.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware/providers/zerolog/v2 v2.0.0-rc.2 // indirect @@ -119,8 +125,8 @@ require ( github.com/hashicorp/golang-lru/v2 v2.0.2 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/holiman/bloomfilter/v2 v2.0.3 // indirect - github.com/holiman/uint256 v1.2.2-0.20230321075855-87b91420868c // indirect - github.com/huin/goupnp v1.2.0 // indirect + github.com/holiman/uint256 v1.2.3 // indirect + github.com/huin/goupnp v1.3.0 // indirect github.com/improbable-eng/grpc-web v0.15.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/ipfs/bbloom v0.0.4 // indirect @@ -176,8 +182,8 @@ require ( github.com/magiconair/properties v1.8.7 // indirect github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.19 // indirect - github.com/mattn/go-runewidth v0.0.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/miekg/dns v1.1.54 // indirect github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect @@ -185,6 +191,7 @@ require ( github.com/minio/sha256-simd v1.0.1 // indirect github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/mmcloughlin/addchain v0.4.0 // indirect github.com/mr-tron/base58 v1.2.0 // indirect github.com/multiformats/go-base32 v0.1.0 // indirect github.com/multiformats/go-base36 v0.2.0 // indirect @@ -198,13 +205,13 @@ require ( github.com/nxadm/tail v1.4.8 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/onflow/atree v0.6.0 // indirect - github.com/onflow/cadence v0.42.6 // indirect - github.com/onflow/flow-core-contracts/lib/go/contracts v1.2.4-0.20231016154253-a00dbf7c061f // indirect - github.com/onflow/flow-core-contracts/lib/go/templates v1.2.4-0.20231016154253-a00dbf7c061f // indirect + github.com/onflow/cadence v0.42.8 // indirect + github.com/onflow/flow-core-contracts/lib/go/contracts v0.15.1-0.20231219201108-fbdb10b0a2da // indirect + github.com/onflow/flow-core-contracts/lib/go/templates v0.15.1-0.20231219201108-fbdb10b0a2da // indirect github.com/onflow/flow-ft/lib/go/contracts v0.7.1-0.20230711213910-baad011d2b13 // indirect - github.com/onflow/flow-go-sdk v0.41.17 // indirect + github.com/onflow/flow-go-sdk v0.44.0 // indirect github.com/onflow/flow-nft/lib/go/contracts v1.1.0 // indirect - github.com/onflow/flow/protobuf/go/flow v0.3.2-0.20231124194313-106cc495def6 // indirect + github.com/onflow/flow/protobuf/go/flow v0.3.2-0.20231213135419-ae911cc351a2 // indirect github.com/onflow/go-bitswap v0.0.0-20230703214630-6d3db958c73d // indirect github.com/onflow/sdks v0.5.0 // indirect github.com/onflow/wal v0.0.0-20230529184820-bc9f8244608d // indirect @@ -239,17 +246,18 @@ require ( github.com/slok/go-http-metrics v0.10.0 // indirect github.com/sony/gobreaker v0.5.0 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect - github.com/spf13/afero v1.9.3 // indirect + github.com/spf13/afero v1.10.0 // indirect github.com/spf13/cast v1.5.0 // indirect github.com/spf13/cobra v1.8.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/viper v1.15.0 // indirect github.com/stretchr/objx v0.5.0 // indirect github.com/subosito/gotenv v1.4.2 // indirect + github.com/supranational/blst v0.3.11 // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect github.com/texttheater/golang-levenshtein/levenshtein v0.0.0-20200805054039-cae8b0eaed6c // indirect - github.com/tklauser/go-sysconf v0.3.9 // indirect - github.com/tklauser/numcpus v0.3.0 // indirect + github.com/tklauser/go-sysconf v0.3.12 // indirect + github.com/tklauser/numcpus v0.6.1 // indirect github.com/turbolent/prettier v0.0.0-20220320183459-661cc755135d // indirect github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect github.com/vmihailenco/msgpack/v4 v4.3.11 // indirect @@ -271,30 +279,30 @@ require ( go.uber.org/fx v1.19.2 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.24.0 // indirect - golang.org/x/crypto v0.12.0 // indirect - golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect - golang.org/x/mod v0.10.0 // indirect - golang.org/x/net v0.14.0 // indirect - golang.org/x/oauth2 v0.11.0 // indirect - golang.org/x/sync v0.3.0 // indirect - golang.org/x/sys v0.11.0 // indirect - golang.org/x/term v0.11.0 // indirect - golang.org/x/text v0.12.0 // indirect - golang.org/x/time v0.1.0 // indirect - golang.org/x/tools v0.9.1 // indirect + golang.org/x/crypto v0.17.0 // indirect + golang.org/x/exp v0.0.0-20231214170342-aacd6d4b4611 // indirect + golang.org/x/mod v0.14.0 // indirect + golang.org/x/net v0.19.0 // indirect + golang.org/x/oauth2 v0.13.0 // indirect + golang.org/x/sync v0.5.0 // indirect + golang.org/x/sys v0.15.0 // indirect + golang.org/x/term v0.15.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/time v0.3.0 // indirect + golang.org/x/tools v0.16.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect gonum.org/v1/gonum v0.13.0 // indirect - google.golang.org/api v0.126.0 // indirect + google.golang.org/api v0.151.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect + google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405 // indirect google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.2.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect - gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect gopkg.in/yaml.v3 v3.0.1 // indirect lukechampine.com/blake3 v1.2.1 // indirect nhooyr.io/websocket v1.8.7 // indirect + rsc.io/tmplfunc v0.0.3 // indirect ) replace github.com/onflow/flow-go => ../ diff --git a/insecure/go.sum b/insecure/go.sum index 7aa52f8ac9b..3614f3ee537 100644 --- a/insecure/go.sum +++ b/insecure/go.sum @@ -29,22 +29,22 @@ cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aD cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= -cloud.google.com/go v0.110.7 h1:rJyC7nWRg2jWGZ4wSJ5nY65GTdYJkg0cd/uXb+ACI6o= -cloud.google.com/go v0.110.7/go.mod h1:+EYjdK8e5RME/VY/qLCAtuyALQ9q67dvuum8i+H5xsI= +cloud.google.com/go v0.110.8 h1:tyNdfIxjzaWctIiLYOTalaLKZ17SI44SKFW26QbOhME= +cloud.google.com/go v0.110.8/go.mod h1:Iz8AkXJf1qmxC3Oxoep8R1T36w8B92yU29PcBhHO5fk= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/compute v1.23.0 h1:tP41Zoavr8ptEqaW6j+LQOnyBBhO7OkOMAGrgLopTwY= -cloud.google.com/go/compute v1.23.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= +cloud.google.com/go/compute v1.23.1 h1:V97tBoDaZHb6leicZ1G6DLK2BAaZLJ/7+9BB/En3hR0= +cloud.google.com/go/compute v1.23.1/go.mod h1:CqB3xpmPKKt3OJpW2ndFIXnA9A4xAy/F3Xp1ixncW78= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/iam v1.1.1 h1:lW7fzj15aVIXYHREOqjRBV9PsH0Z6u8Y46a1YGvQP4Y= -cloud.google.com/go/iam v1.1.1/go.mod h1:A5avdyVL2tCppe4unb0951eI9jreack+RJ0/d+KUZOU= +cloud.google.com/go/iam v1.1.3 h1:18tKG7DzydKWUnLjonWcJO6wjSCAtzh4GcRKlH/Hrzc= +cloud.google.com/go/iam v1.1.3/go.mod h1:3khUlaBXfPKKe7huYgEpDn6FtgRyMEqbkvBxrQyY5SE= cloud.google.com/go/kms v1.0.0/go.mod h1:nhUehi+w7zht2XrUfvTRNpxrfayBHqP4lu2NSywui/0= cloud.google.com/go/profiler v0.3.0 h1:R6y/xAeifaUXxd2x6w+jIwKxoKl8Cv5HJvcvASTPWJo= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= @@ -90,17 +90,20 @@ github.com/DataDog/zstd v1.5.2/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwS github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Kubuxu/go-os-helper v0.0.1/go.mod h1:N8B+I7vPCT80IcP58r50u4+gEEcsZETFUpAzWW2ep1Y= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OneOfOne/xxhash v1.2.5 h1:zl/OfRA6nftbBK9qTohYBJ5xvw6C/oNKizR7cZGl3cI= github.com/OneOfOne/xxhash v1.2.5/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= -github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 h1:fLjPD/aNc3UIOA6tDi6QXUemppXK3P9BI7mr2hd6gx8= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= +github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA= +github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= github.com/VictoriaMetrics/fastcache v1.5.3/go.mod h1:+jv9Ckb+za/P1ZRg/sulP5Ni1v49daAVERr0H3CuscE= -github.com/VictoriaMetrics/fastcache v1.6.0 h1:C/3Oi3EiBCqufydp1neRZkqcwmEiuRT9c3fqvvgKm5o= -github.com/VictoriaMetrics/fastcache v1.6.0/go.mod h1:0qHz5QP0GMX4pfmMA/zt5RgfNuXJrTP0zS7DqpHGGTw= +github.com/VictoriaMetrics/fastcache v1.12.1 h1:i0mICQuojGDL3KblA7wUNlY5lOK6a4bwt3uRKnkZU40= +github.com/VictoriaMetrics/fastcache v1.12.1/go.mod h1:tX04vaqcNoQeGLD+ra5pU5sWkuxnzWhEzLwhP9w653o= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= @@ -127,46 +130,47 @@ github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQ github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= github.com/aws/aws-sdk-go-v2 v1.9.0/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4= -github.com/aws/aws-sdk-go-v2 v1.17.7 h1:CLSjnhJSTSogvqUGhIC6LqFKATMRexcxLZ0i/Nzk9Eg= -github.com/aws/aws-sdk-go-v2 v1.17.7/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw= +github.com/aws/aws-sdk-go-v2 v1.23.1 h1:qXaFsOOMA+HsZtX8WoCa+gJnbyW7qyFFBlPqvTSzbaI= +github.com/aws/aws-sdk-go-v2 v1.23.1/go.mod h1:i1XDttT4rnf6vxc9AuskLc6s7XBee8rlLilKlc03uAA= github.com/aws/aws-sdk-go-v2/config v1.8.0/go.mod h1:w9+nMZ7soXCe5nT46Ri354SNhXDQ6v+V5wqDjnZE+GY= -github.com/aws/aws-sdk-go-v2/config v1.18.19 h1:AqFK6zFNtq4i1EYu+eC7lcKHYnZagMn6SW171la0bGw= -github.com/aws/aws-sdk-go-v2/config v1.18.19/go.mod h1:XvTmGMY8d52ougvakOv1RpiTLPz9dlG/OQHsKU/cMmY= +github.com/aws/aws-sdk-go-v2/config v1.25.5 h1:UGKm9hpQS2hoK8CEJ1BzAW8NbUpvwDJJ4lyqXSzu8bk= +github.com/aws/aws-sdk-go-v2/config v1.25.5/go.mod h1:Bf4gDvy4ZcFIK0rqDu1wp9wrubNba2DojiPB2rt6nvI= github.com/aws/aws-sdk-go-v2/credentials v1.4.0/go.mod h1:dgGR+Qq7Wjcd4AOAW5Rf5Tnv3+x7ed6kETXyS9WCuAY= -github.com/aws/aws-sdk-go-v2/credentials v1.13.18 h1:EQMdtHwz0ILTW1hoP+EwuWhwCG1hD6l3+RWFQABET4c= -github.com/aws/aws-sdk-go-v2/credentials v1.13.18/go.mod h1:vnwlwjIe+3XJPBYKu1et30ZPABG3VaXJYr8ryohpIyM= +github.com/aws/aws-sdk-go-v2/credentials v1.16.4 h1:i7UQYYDSJrtc30RSwJwfBKwLFNnBTiICqAJ0pPdum8E= +github.com/aws/aws-sdk-go-v2/credentials v1.16.4/go.mod h1:Kdh/okh+//vQ/AjEt81CjvkTo64+/zIE4OewP7RpfXk= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.5.0/go.mod h1:CpNzHK9VEFUCknu50kkB8z58AH2B5DvPP7ea1LHve/Y= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.1 h1:gt57MN3liKiyGopcqgNzJb2+d9MJaKT/q1OksHNXVE4= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.1/go.mod h1:lfUx8puBRdM5lVVMQlwt2v+ofiG/X6Ms+dy0UkG/kXw= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.5 h1:KehRNiVzIfAcj6gw98zotVbb/K67taJE0fkfgM6vzqU= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.5/go.mod h1:VhnExhw6uXy9QzetvpXDolo1/hjhx4u9qukBGkuUwjs= github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.5.1 h1:VGkV9KmhGqOQWnHyi4gLG98kE6OecT42fdrCGFWxJsc= github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.5.1/go.mod h1:PLlnMiki//sGnCJiW+aVpvP/C8Kcm8mEj/IVm9+9qk4= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.31 h1:sJLYcS+eZn5EeNINGHSCRAwUJMFVqklwkH36Vbyai7M= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.31/go.mod h1:QT0BqUvX1Bh2ABdTGnjqEjvjzrCfIniM9Sc8zn9Yndo= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.25 h1:1mnRASEKnkqsntcxHaysxwgVoUUp5dkiB+l3llKnqyg= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.25/go.mod h1:zBHOPwhBc3FlQjQJE/D3IfPWiWaQmT06Vq9aNukDo0k= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.4 h1:LAm3Ycm9HJfbSCd5I+wqC2S9Ej7FPrgr5CQoOljJZcE= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.4/go.mod h1:xEhvbJcyUf/31yfGSQBe01fukXwXJ0gxDp7rLfymWE0= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.4 h1:4GV0kKZzUxiWxSVpn/9gwR0g21NF1Jsyduzo9rHgC/Q= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.4/go.mod h1:dYvTNAggxDZy6y1AF7YDwXsPuHFy/VNEpEI/2dWK9IU= github.com/aws/aws-sdk-go-v2/internal/ini v1.2.2/go.mod h1:BQV0agm+JEhqR+2RT5e1XTFIDcAAV0eW6z2trp+iduw= -github.com/aws/aws-sdk-go-v2/internal/ini v1.3.32 h1:p5luUImdIqywn6JpQsW3tq5GNOxKmOnEpybzPx+d1lk= -github.com/aws/aws-sdk-go-v2/internal/ini v1.3.32/go.mod h1:XGhIBZDEgfqmFIugclZ6FU7v75nHhBDtzuB4xB/tEi4= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.3.0 h1:gceOysEWNNwLd6cki65IMBZ4WAM0MwgBQq2n7kejoT8= +github.com/aws/aws-sdk-go-v2/internal/ini v1.7.1 h1:uR9lXYjdPX0xY+NhvaJ4dD8rpSRz5VY81ccIIoNG+lw= +github.com/aws/aws-sdk-go-v2/internal/ini v1.7.1/go.mod h1:6fQQgfuGmw8Al/3M2IgIllycxV7ZW7WCdVSqfBeUiCY= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.3.0/go.mod h1:v8ygadNyATSm6elwJ/4gzJwcFhri9RqS8skgHKiwXPU= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.1 h1:rpkF4n0CyFcrJUG/rNNohoTmhtWlFTRI4BsZOh9PvLs= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.1/go.mod h1:l9ymW25HOqymeU2m1gbUQ3rUIsTwKs8gYHXkqDQUhiI= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.3.0/go.mod h1:R1KK+vY8AfalhG1AOu5e35pOD2SdoPKQCFLTvnxiohk= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.25 h1:5LHn8JQ0qvjD9L9JhMtylnkcw7j05GDZqM9Oin6hpr0= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.25/go.mod h1:/95IA+0lMnzW6XzqYJRpjjsAbKEORVeO0anQqjd2CNU= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.4 h1:rdovz3rEu0vZKbzoMYPTehp0E8veoE9AyfzqCr5Eeao= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.4/go.mod h1:aYCGNjyUCUelhofxlZyj63srdxWUSsBSGg5l6MCuXuE= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.7.0 h1:HWsM0YQWX76V6MOp07YuTYacm8k7h69ObJuw7Nck+og= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.7.0/go.mod h1:LKb3cKNQIMh+itGnEpKGcnL/6OIjPZqrtYah1w5f+3o= github.com/aws/aws-sdk-go-v2/service/s3 v1.15.0 h1:nPLfLPfglacc29Y949sDxpr3X/blaY40s3B85WT2yZU= github.com/aws/aws-sdk-go-v2/service/s3 v1.15.0/go.mod h1:Iv2aJVtVSm/D22rFoX99cLG4q4uB7tppuCsulGe98k4= github.com/aws/aws-sdk-go-v2/service/sso v1.4.0/go.mod h1:+1fpWnL96DL23aXPpMGbsmKe8jLTEfbjuQoA4WS1VaA= -github.com/aws/aws-sdk-go-v2/service/sso v1.12.6 h1:5V7DWLBd7wTELVz5bPpwzYy/sikk0gsgZfj40X+l5OI= -github.com/aws/aws-sdk-go-v2/service/sso v1.12.6/go.mod h1:Y1VOmit/Fn6Tz1uFAeCO6Q7M2fmfXSCLeL5INVYsLuY= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.6 h1:B8cauxOH1W1v7rd8RdI/MWnoR4Ze0wIHWrb90qczxj4= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.6/go.mod h1:Lh/bc9XUf8CfOY6Jp5aIkQtN+j1mc+nExc+KXj9jx2s= +github.com/aws/aws-sdk-go-v2/service/sso v1.17.3 h1:CdsSOGlFF3Pn+koXOIpTtvX7st0IuGsZ8kJqcWMlX54= +github.com/aws/aws-sdk-go-v2/service/sso v1.17.3/go.mod h1:oA6VjNsLll2eVuUoF2D+CMyORgNzPEW/3PyUdq6WQjI= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.20.1 h1:cbRqFTVnJV+KRpwFl76GJdIZJKKCdTPnjUZ7uWh3pIU= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.20.1/go.mod h1:hHL974p5auvXlZPIjJTblXJpbkfK4klBczlsEaMCGVY= github.com/aws/aws-sdk-go-v2/service/sts v1.7.0/go.mod h1:0qcSMCyASQPN2sk/1KQLQ2Fh6yq8wm0HSDAimPhzCoM= -github.com/aws/aws-sdk-go-v2/service/sts v1.18.7 h1:bWNgNdRko2x6gqa0blfATqAZKZokPIeM1vfmQt2pnvM= -github.com/aws/aws-sdk-go-v2/service/sts v1.18.7/go.mod h1:JuTnSoeePXmMVe9G8NcjjwgOKEfZ4cOjMuT2IBT/2eI= +github.com/aws/aws-sdk-go-v2/service/sts v1.25.4 h1:yEvZ4neOQ/KpUqyR+X0ycUTW/kVRNR4nDZ38wStHGAA= +github.com/aws/aws-sdk-go-v2/service/sts v1.25.4/go.mod h1:feTnm2Tk/pJxdX+eooEsxvlvTWBvDm6CasRZ+JOs2IY= github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= -github.com/aws/smithy-go v1.13.5 h1:hgz0X/DX0dGqTYpGALqXJoRKRj5oQ7150i5FdTePzO8= -github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= +github.com/aws/smithy-go v1.17.0 h1:wWJD7LX6PBV6etBUwO0zElG0nWN9rUhp0WdYeHSHAaI= +github.com/aws/smithy-go v1.17.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE= github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= @@ -237,18 +241,24 @@ github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/cockroachdb/datadriven v1.0.2/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= -github.com/cockroachdb/datadriven v1.0.3-0.20230801171734-e384cf455877 h1:1MLK4YpFtIEo3ZtMA5C795Wtv5VuUnrXX7mQG+aHg6o= +github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4= github.com/cockroachdb/errors v1.9.1 h1:yFVvsI0VxmRShfawbt/laCIDy/mtTqqnvoNgiy5bEV8= github.com/cockroachdb/errors v1.9.1/go.mod h1:2sxOtL2WIc096WSZqZ5h8fa17rdDq9HZOZLBCor4mBk= github.com/cockroachdb/logtags v0.0.0-20211118104740-dabe8e521a4f/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE= github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= -github.com/cockroachdb/pebble v0.0.0-20230906160148-46873a6a7a06 h1:T+Np/xtzIjYM/P5NAw0e2Rf1FGvzDau1h54MKvx8G7w= -github.com/cockroachdb/pebble v0.0.0-20230906160148-46873a6a7a06/go.mod h1:bynZ3gvVyhlvjLI7PT6dmZ7g76xzJ7HpxfjgkzCGz6s= +github.com/cockroachdb/pebble v0.0.0-20230928194634-aa077af62593 h1:aPEJyR4rPBvDmeyi+l/FS/VtA00IWvjeFvjen1m1l1A= +github.com/cockroachdb/pebble v0.0.0-20230928194634-aa077af62593/go.mod h1:6hk1eMY/u5t+Cf18q5lFMUA1Rc+Sm5I6Ra1QuPyxXCo= github.com/cockroachdb/redact v1.1.3 h1:AKZds10rFSIj7qADf0g46UixK8NNLwWTNdCIGS5wfSQ= github.com/cockroachdb/redact v1.1.3/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= +github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo= +github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= +github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/YjhQ= +github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI= +github.com/consensys/gnark-crypto v0.12.1 h1:lHH39WuuFgVHONRl3J0LRBtuYdQTumFSDtJF7HpyG8M= +github.com/consensys/gnark-crypto v0.12.1/go.mod h1:v2Gy7L/4ZRosZ7Ivs+9SfUDr0f5UlG+EM5t7MPHiLuY= github.com/containerd/cgroups v0.0.0-20201119153540-4cbc285b3327/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw= @@ -271,6 +281,8 @@ github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwc github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/crate-crypto/go-kzg-4844 v0.7.0 h1:C0vgZRk4q4EZ/JgPfzuSoxdCq3C3mOZMBShovmncxvA= +github.com/crate-crypto/go-kzg-4844 v0.7.0/go.mod h1:1kMhvPgI0Ky3yIa+9lFySEBUBXkYxeOi8ZF1sYioxhc= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cskr/pubsub v1.0.2 h1:vlOzMhl6PFn60gRlTQQsIfVwaPB/B/8MziK8FhEPt/0= @@ -338,9 +350,11 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.m github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= +github.com/ethereum/c-kzg-4844 v0.4.0 h1:3MS1s4JtA868KpJxroZoepdV0ZKBp3u/O5HcZ7R3nlY= +github.com/ethereum/c-kzg-4844 v0.4.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= github.com/ethereum/go-ethereum v1.9.9/go.mod h1:a9TqabFudpDu1nucId+k9S8R9whYaHnGBLKFouA5EAo= -github.com/ethereum/go-ethereum v1.12.0 h1:bdnhLPtqETd4m3mS8BGMNvBTf36bO5bx/hxE2zljOa0= -github.com/ethereum/go-ethereum v1.12.0/go.mod h1:/oo2X/dZLJjf2mJ6YT9wcWxa4nNJDBKDBU6sFIpx1Gs= +github.com/ethereum/go-ethereum v1.13.5 h1:U6TCRciCqZRe4FPXmy1sMGxTfuk8P7u2UoinF3VbaFk= +github.com/ethereum/go-ethereum v1.13.5/go.mod h1:yMTu38GSuyxaYzQMViqNmQ1s3cE84abZexQmTgenWk0= github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8= github.com/fatih/color v1.3.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= @@ -412,6 +426,7 @@ 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= github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= +github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= @@ -441,7 +456,7 @@ github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8= github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo= github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= -github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= @@ -527,8 +542,7 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ 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.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= 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= @@ -559,24 +573,25 @@ github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 h1:hR7/MlvK23p6+lIw9SN1TigNLn9ZnF3W4SYRKq2gAHs= github.com/google/pprof v0.0.0-20230602150820-91b7bce49751/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/s2a-go v0.1.4 h1:1kZ/sQM3srePvKs3tXAvQzo66XfcReoqFpIpIccE7Oc= -github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= +github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= +github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= +github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= -github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9aurXEpJX+U6FLtpYTdC3R06k= -github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= +github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= -github.com/googleapis/gax-go/v2 v2.11.0 h1:9V9PWXEsWnPpQhu/PeQIkS4eGzMlTLGgt80cUUI8Ki4= -github.com/googleapis/gax-go/v2 v2.11.0/go.mod h1:DxmR61SGKkGLa2xigwuZIQpkCI2S5iydzRfb3peWZJI= +github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas= +github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c h1:7lF+Vz0LqiRidnzC1Oq86fpX1q/iEv2KJdrCtttYjT4= @@ -646,14 +661,14 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= 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= -github.com/holiman/uint256 v1.2.2-0.20230321075855-87b91420868c/go.mod h1:SC8Ryt4n+UBbPbIBKaG9zbbDlp4jOru9xFZmPzLUTxw= +github.com/holiman/uint256 v1.2.3 h1:K8UWO1HUJpRMXBxbmaY1Y8IAMZC/RsKB+ArEnnK4l5o= +github.com/holiman/uint256 v1.2.3/go.mod h1:SC8Ryt4n+UBbPbIBKaG9zbbDlp4jOru9xFZmPzLUTxw= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/huin/goupnp v0.0.0-20161224104101-679507af18f3/go.mod h1:MZ2ZmwcBpvOoJ22IJsc7va19ZwoheaBk43rKg12SKag= github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc= -github.com/huin/goupnp v1.2.0 h1:uOKW26NG1hsSSbXIZ1IR7XP9Gjd1U8pnLaCMgntmkmY= -github.com/huin/goupnp v1.2.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= +github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= +github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= github.com/hydrogen18/memlistener v0.0.0-20200120041712-dcc25e7acd91/go.mod h1:qEIFzExnS6016fRpRfxrExeVn2gbClQA99gQhnIcdhE= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -1141,16 +1156,17 @@ github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-tty v0.0.3/go.mod h1:ihxohKRERHTVzN+aSVRwACLCeqIoZAWpoICkkvrWyR0= github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= @@ -1194,6 +1210,9 @@ github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:F github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY= +github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU= +github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -1302,25 +1321,25 @@ github.com/onflow/atree v0.1.0-beta1.0.20211027184039-559ee654ece9/go.mod h1:+6x github.com/onflow/atree v0.6.0 h1:j7nQ2r8npznx4NX39zPpBYHmdy45f4xwoi+dm37Jk7c= github.com/onflow/atree v0.6.0/go.mod h1:gBHU0M05qCbv9NN0kijLWMgC47gHVNBIp4KmsVFi0tc= github.com/onflow/cadence v0.20.1/go.mod h1:7mzUvPZUIJztIbr9eTvs+fQjWWHTF8veC+yk4ihcNIA= -github.com/onflow/cadence v0.42.6 h1:VtI0EpKrdbfqITRMsvyZC4dhgcW1x1LNUQuEpdMDzus= -github.com/onflow/cadence v0.42.6/go.mod h1:raU8va8QRyTa/eUbhej4mbyW2ETePfSaywoo36MddgE= -github.com/onflow/flow-core-contracts/lib/go/contracts v1.2.4-0.20231016154253-a00dbf7c061f h1:S8yIZw9LFXfYD1V5H9BiixihHw3GrXVPrmfplSzYaww= -github.com/onflow/flow-core-contracts/lib/go/contracts v1.2.4-0.20231016154253-a00dbf7c061f/go.mod h1:jM6GMAL+m0hjusUgiYDNrixPQ6b9s8xjoJQoEu5bHQI= -github.com/onflow/flow-core-contracts/lib/go/templates v1.2.4-0.20231016154253-a00dbf7c061f h1:Ep+Mpo2miWMe4pjPGIaEvEzshRep30dvNgxqk+//FrQ= -github.com/onflow/flow-core-contracts/lib/go/templates v1.2.4-0.20231016154253-a00dbf7c061f/go.mod h1:ZeLxwaBkzuSInESGjL8/IPZWezF+YOYsYbMrZlhN+q4= +github.com/onflow/cadence v0.42.8 h1:6OWhKMjL2Lql6a3L2xeA44uoOAVBp5+cMNE5BDPvo+E= +github.com/onflow/cadence v0.42.8/go.mod h1:1wFd+LiNiN6qoZXof3MBdpM6d8BsxbVIxOA77LbIYmE= +github.com/onflow/crypto v0.25.0 h1:BeWbLsh3ZD13Ej+Uky6kg1PL1ZIVBDVX+2MVBNwqddg= +github.com/onflow/crypto v0.25.0/go.mod h1:C8FbaX0x8y+FxWjbkHy0Q4EASCDR9bSPWZqlpCLYyVI= +github.com/onflow/flow-core-contracts/lib/go/contracts v0.15.1-0.20231219201108-fbdb10b0a2da h1:8CEioYNnP0rwjnRbKDgs8SmiQTsdaroeX4d/Q3pQuh4= +github.com/onflow/flow-core-contracts/lib/go/contracts v0.15.1-0.20231219201108-fbdb10b0a2da/go.mod h1:WHp24VkUQfcfZi0XjI1uRVRt5alM5SHVkwOil1U2Tpc= +github.com/onflow/flow-core-contracts/lib/go/templates v0.15.1-0.20231219201108-fbdb10b0a2da h1:V2zI6AfDtPykMGhgw69ZEGcvyMudRUFOVHYCMN4BbQo= +github.com/onflow/flow-core-contracts/lib/go/templates v0.15.1-0.20231219201108-fbdb10b0a2da/go.mod h1:c09d6sNyF/j5/pAynK7sNPb1XKqJqk1rxZPEqEL+dUo= github.com/onflow/flow-ft/lib/go/contracts v0.7.1-0.20230711213910-baad011d2b13 h1:B4ll7e3j+MqTJv2122Enq3RtDNzmIGRu9xjV7fo7un0= github.com/onflow/flow-ft/lib/go/contracts v0.7.1-0.20230711213910-baad011d2b13/go.mod h1:kTMFIySzEJJeupk+7EmXs0EJ6CBWY/MV9fv9iYQk+RU= github.com/onflow/flow-go-sdk v0.24.0/go.mod h1:IoptMLPyFXWvyd9yYA6/4EmSeeozl6nJoIv4FaEMg74= -github.com/onflow/flow-go-sdk v0.41.17 h1:HpNn3j2fqLGA6H3HGfAuh2A+TsPBv8gWO3kvK9Hvtic= -github.com/onflow/flow-go-sdk v0.41.17/go.mod h1:ZIj2XBI9R0QiKzbI6iPwOeqyIy/M4+atczoMOEWdKYw= +github.com/onflow/flow-go-sdk v0.44.0 h1:gVRLcZ6LUNs/5mzHDx0mp4mEnBAWD62O51P4/nYm4rE= +github.com/onflow/flow-go-sdk v0.44.0/go.mod h1:mm1Fi2hiMrexNMwRzTrAN2zwTvlP8iQ5CF2JSAgJR8U= github.com/onflow/flow-go/crypto v0.21.3/go.mod h1:vI6V4CY3R6c4JKBxdcRiR/AnjBfL8OSD97bJc60cLuQ= -github.com/onflow/flow-go/crypto v0.25.0 h1:6lmoiAQ3APCF+nV7f4f2AXL3PuDKqQiWqRJXmjrMEq4= -github.com/onflow/flow-go/crypto v0.25.0/go.mod h1:OOb2vYcS8AOCajBClhHTJ0NKftFl1RQgTQ0+Vh4nbqk= github.com/onflow/flow-nft/lib/go/contracts v1.1.0 h1:rhUDeD27jhLwOqQKI/23008CYfnqXErrJvc4EFRP2a0= github.com/onflow/flow-nft/lib/go/contracts v1.1.0/go.mod h1:YsvzYng4htDgRB9sa9jxdwoTuuhjK8WYWXTyLkIigZY= github.com/onflow/flow/protobuf/go/flow v0.2.2/go.mod h1:gQxYqCfkI8lpnKsmIjwtN2mV/N2PIwc1I+RUK4HPIc8= -github.com/onflow/flow/protobuf/go/flow v0.3.2-0.20231124194313-106cc495def6 h1:KMN+OEVaw7KAgxL3p8ux7CMuyTvacAlYTbasOqowh4M= -github.com/onflow/flow/protobuf/go/flow v0.3.2-0.20231124194313-106cc495def6/go.mod h1:NA2pX2nw8zuaxfKphhKsk00kWLwfd+tv8mS23YXO4Sk= +github.com/onflow/flow/protobuf/go/flow v0.3.2-0.20231213135419-ae911cc351a2 h1:+rT+UsfTR39JZO8ht2+4fkaWfHw74SCj1fyz1lWuX8A= +github.com/onflow/flow/protobuf/go/flow v0.3.2-0.20231213135419-ae911cc351a2/go.mod h1:NA2pX2nw8zuaxfKphhKsk00kWLwfd+tv8mS23YXO4Sk= github.com/onflow/go-bitswap v0.0.0-20230703214630-6d3db958c73d h1:QcOAeEyF3iAUHv21LQ12sdcsr0yFrJGoGLyCAzYYtvI= github.com/onflow/go-bitswap v0.0.0-20230703214630-6d3db958c73d/go.mod h1:GCPpiyRoHncdqPj++zPr9ZOYBX4hpJ0pYZRYqSE8VKk= github.com/onflow/sdks v0.5.0 h1:2HCRibwqDaQ1c9oUApnkZtEAhWiNY2GTpRD5+ftdkN8= @@ -1538,8 +1557,8 @@ github.com/spaolacci/murmur3 v1.0.1-0.20190317074736-539464a789e9/go.mod h1:JwIa 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.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= -github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= +github.com/spf13/afero v1.10.0 h1:EaGW2JJh15aKOejeuJ+wpFSHnbd7GE6Wvp3TsNhb6LY= +github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= @@ -1585,6 +1604,8 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/supranational/blst v0.3.4/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= +github.com/supranational/blst v0.3.11 h1:LyU6FolezeWAhvQk0k6O/d49jqgO52MSDDfYgbeoEm4= +github.com/supranational/blst v0.3.11/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d/go.mod h1:9OrXJhf154huy1nPWmuSrkgjPUtUNhA+Zmy+6AESzuA= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= @@ -1592,10 +1613,12 @@ github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45 github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= github.com/texttheater/golang-levenshtein/levenshtein v0.0.0-20200805054039-cae8b0eaed6c h1:HelZ2kAFadG0La9d+4htN4HzQ68Bm2iM9qKMSMES6xg= github.com/texttheater/golang-levenshtein/levenshtein v0.0.0-20200805054039-cae8b0eaed6c/go.mod h1:JlzghshsemAMDGZLytTFY8C1JQxQPhnatWqNwUXjggo= -github.com/tklauser/go-sysconf v0.3.9 h1:JeUVdAOWhhxVcU6Eqr/ATFHgXk/mmiItdKeJPev3vTo= github.com/tklauser/go-sysconf v0.3.9/go.mod h1:11DU/5sG7UexIrp/O6g35hrWzu0JxlwQ3LSFUzyeuhs= -github.com/tklauser/numcpus v0.3.0 h1:ILuRUQBtssgnxw0XXIjKUC56fgnOrFoQQ/4+DeU2biQ= +github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= +github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= github.com/tklauser/numcpus v0.3.0/go.mod h1:yFGUr7TUHQRAhyqBcEg0Ge34zDBAsIvJJcyE6boqnA8= +github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/turbolent/prettier v0.0.0-20220320183459-661cc755135d h1:5JInRQbk5UBX8JfUvKh2oYTLMVwj3p6n+wapDDm7hko= @@ -1658,7 +1681,6 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= @@ -1769,10 +1791,9 @@ golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf/go.mod h1:P+XmwS30IXTQdn5 golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 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.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= -golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +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-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1787,8 +1808,8 @@ golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u0 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-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= -golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug= -golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/exp v0.0.0-20231214170342-aacd6d4b4611 h1:qCEDpW1G+vcj3Y7Fy52pEM1AWm3abj8WimGYejI3SC4= +golang.org/x/exp v0.0.0-20231214170342-aacd6d4b4611/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= 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= @@ -1816,9 +1837,8 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 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.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= -golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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= @@ -1881,9 +1901,8 @@ golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211008194852-3b03d305991f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= -golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= +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-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1902,8 +1921,8 @@ golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.11.0 h1:vPL4xzxBM4niKCW6g9whtaWVXTJf1U5e4aZxxFx/gbU= -golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk= +golang.org/x/oauth2 v0.13.0 h1:jDDenyj+WgFtmV3zYVoi8aE2BwtXFLWOA67ZfNWftiY= +golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0= golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1916,9 +1935,8 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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-20180810173357-98c5dad5d1a0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -2003,7 +2021,6 @@ golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210317225723-c4fcb01b228e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -2028,21 +2045,20 @@ golang.org/x/sys v0.0.0-20211025112917-711f33c9992c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -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.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.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-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0= -golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= +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= @@ -2052,16 +2068,15 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 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.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= -golang.org/x/text v0.12.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-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 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= golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.1.0 h1:xYY+Bajn2a7VBmTM5GikTmnK8ZuX8YgnQCqZpbBNtmA= -golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -2133,9 +2148,8 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= -golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= +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= @@ -2182,8 +2196,8 @@ google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqiv google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= google.golang.org/api v0.58.0/go.mod h1:cAbP2FsxoGVNwtgNAmmn3y5G1TWAiVYRmg4yku3lv+E= -google.golang.org/api v0.126.0 h1:q4GJq+cAdMAC7XP7njvQ4tvohGLiSlytuL4BQxbIZ+o= -google.golang.org/api v0.126.0/go.mod h1:mBwVAtz+87bEN6CbA1GtZPDOqY2R5ONPqJeIlvyo4Aw= +google.golang.org/api v0.151.0 h1:FhfXLO/NFdJIzQtCqjpysWwqKk8AzGWBUhMIx67cVDU= +google.golang.org/api v0.151.0/go.mod h1:ccy+MJ6nrYFgE3WgRx/AMXOxOmU8Q4hSa+jjibzhxcg= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -2264,13 +2278,13 @@ google.golang.org/genproto v0.0.0-20210921142501-181ce0d877f6/go.mod h1:5CzLGKJ6 google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211007155348-82e027067bd4/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d h1:VBu5YqKPv6XiJ199exd8Br+Aetz+o08F+PLMnwJQHAY= -google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d/go.mod h1:yZTlhN0tQnXo3h00fuXNCxJdLdIdnVFVBaRJ5LWBbw4= -google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d h1:DoPTO70H+bcDXcd39vOqb2viZxgqeBeSGtZ55yZU4/Q= -google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk= -google.golang.org/genproto/googleapis/bytestream v0.0.0-20230530153820-e85fd2cbaebc h1:g3hIDl0jRNd9PPTs2uBzYuaD5mQuwOkZY0vSc0LR32o= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d h1:uvYuEyMHKNt+lT4K3bN6fGswmK8qSvcreM3BwjDh+y4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= +google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b h1:+YaDE2r2OG8t/z5qmsh7Y+XXwCbvadxxZ0YY6mTdrVA= +google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:CgAqfJo+Xmu0GwA0411Ht3OU3OntXwsGmrmjI8ioGXI= +google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b h1:CIC2YMXmIhYw6evmhPxBKJ4fmLbOFtXQN/GV3XOZR8k= +google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:IBQ646DjkDkvUIsVq/cc03FUFQ9wbZu7yE396YcL870= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20231030173426-d783a09b4405 h1:o4S3HvTUEXgRsNSUQsALDVog0O9F/U1JJlHmmUN8Uas= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405 h1:AB/lmRny7e2pLhFEYIbl5qkDAUt2h0ZRO4wGPhZf+ik= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405/go.mod h1:67X1fPuzjcrkymZzZV1vvkFeTn2Rvc6lYF9MYFGCcwE= google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= @@ -2307,7 +2321,6 @@ google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnD google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= @@ -2346,7 +2359,6 @@ gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= -gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= gopkg.in/olebedev/go-duktape.v3 v3.0.0-20190213234257-ec84240a7772/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= @@ -2393,6 +2405,8 @@ rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8 rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU= +rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck= diff --git a/insecure/integration/functional/test/gossipsub/rpc_inspector/metrics_inspector_test.go b/insecure/integration/functional/test/gossipsub/rpc_inspector/metrics_inspector_test.go deleted file mode 100644 index baafcc70f87..00000000000 --- a/insecure/integration/functional/test/gossipsub/rpc_inspector/metrics_inspector_test.go +++ /dev/null @@ -1,87 +0,0 @@ -package rpc_inspector - -import ( - "context" - "testing" - "time" - - pubsub "github.com/libp2p/go-libp2p-pubsub" - "github.com/libp2p/go-libp2p/core/peer" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - "go.uber.org/atomic" - - "github.com/onflow/flow-go/insecure/corruptlibp2p" - "github.com/onflow/flow-go/insecure/internal" - "github.com/onflow/flow-go/model/flow" - "github.com/onflow/flow-go/module/irrecoverable" - "github.com/onflow/flow-go/network/channels" - "github.com/onflow/flow-go/network/p2p" - "github.com/onflow/flow-go/network/p2p/inspector" - mockp2p "github.com/onflow/flow-go/network/p2p/mock" - p2ptest "github.com/onflow/flow-go/network/p2p/test" - "github.com/onflow/flow-go/utils/unittest" -) - -// TestMetricsInspector_ObserveRPC ensures that the gossipsub rpc metrics inspector observes metrics for control messages as expected. -func TestMetricsInspector_ObserveRPC(t *testing.T) { - role := flow.RoleConsensus - sporkID := unittest.IdentifierFixture() - idProvider := unittest.NewUpdatableIDProvider(flow.IdentityList{}) - spammer := corruptlibp2p.NewGossipSubRouterSpammer(t, sporkID, role, idProvider) - ctx, cancel := context.WithCancel(context.Background()) - signalerCtx := irrecoverable.NewMockSignalerContext(t, ctx) - - messageCount := 100 - controlMessageCount := 5 - - metricsObservedCount := atomic.NewInt64(0) - mockMetricsObserver := mockp2p.NewGossipSubControlMetricsObserver(t) - mockMetricsObserver.On("ObserveRPC", mock.Anything, mock.Anything). - Run(func(args mock.Arguments) { - peerID, ok := args.Get(0).(peer.ID) - require.True(t, ok) - require.Equal(t, spammer.SpammerNode.ID(), peerID) - rpc, ok := args.Get(1).(*pubsub.RPC) - require.True(t, ok) - // there are some default rpc messages exchanged between the nodes on startup - // we can ignore those rpc messages not configured directly by this test - if len(rpc.GetControl().GetPrune()) != 100 { - return - } - require.True(t, messageCount == len(rpc.GetControl().GetPrune())) - require.True(t, messageCount == len(rpc.GetControl().GetGraft())) - require.True(t, messageCount == len(rpc.GetControl().GetIhave())) - metricsObservedCount.Inc() - }) - metricsInspector := inspector.NewControlMsgMetricsInspector(unittest.Logger(), mockMetricsObserver, 2) - corruptInspectorFunc := corruptlibp2p.CorruptInspectorFunc(metricsInspector) - victimNode, victimIdentity := p2ptest.NodeFixture( - t, - sporkID, - t.Name(), - idProvider, - p2ptest.WithRole(role), - internal.WithCorruptGossipSub(corruptlibp2p.CorruptGossipSubFactory(), - corruptlibp2p.CorruptGossipSubConfigFactoryWithInspector(corruptInspectorFunc)), - ) - idProvider.SetIdentities(flow.IdentityList{&victimIdentity, &spammer.SpammerId}) - metricsInspector.Start(signalerCtx) - nodes := []p2p.LibP2PNode{victimNode, spammer.SpammerNode} - startNodesAndEnsureConnected(t, signalerCtx, nodes, sporkID) - spammer.Start(t) - defer stopComponents(t, cancel, nodes, metricsInspector) - // prepare to spam - generate control messages - ctlMsgs := spammer.GenerateCtlMessages(controlMessageCount, - p2ptest.WithGraft(messageCount, channels.PushBlocks.String()), - p2ptest.WithPrune(messageCount, channels.PushBlocks.String()), - p2ptest.WithIHave(messageCount, 1000, channels.PushBlocks.String())) - - // start spamming the victim peer - spammer.SpamControlMessage(t, victimNode, ctlMsgs) - - // eventually we should process each spammed control message and observe metrics for them - require.Eventually(t, func() bool { - return metricsObservedCount.Load() == int64(controlMessageCount) - }, 5*time.Second, 10*time.Millisecond, "did not observe metrics for all control messages on time") -} diff --git a/insecure/integration/functional/test/gossipsub/rpc_inspector/utils.go b/insecure/integration/functional/test/gossipsub/rpc_inspector/utils.go index 555a06a6bba..fdbba188f45 100644 --- a/insecure/integration/functional/test/gossipsub/rpc_inspector/utils.go +++ b/insecure/integration/functional/test/gossipsub/rpc_inspector/utils.go @@ -65,9 +65,9 @@ func meshTracerFixture(flowConfig *config.FlowConfig, idProvider module.Identity IDProvider: idProvider, LoggerInterval: time.Second, HeroCacheMetricsFactory: metrics.NewNoopHeroCacheMetricsFactory(), - RpcSentTrackerCacheSize: flowConfig.NetworkConfig.GossipSubConfig.RPCSentTrackerCacheSize, - RpcSentTrackerWorkerQueueCacheSize: flowConfig.NetworkConfig.GossipSubConfig.RPCSentTrackerQueueCacheSize, - RpcSentTrackerNumOfWorkers: flowConfig.NetworkConfig.GossipSubConfig.RpcSentTrackerNumOfWorkers, + RpcSentTrackerCacheSize: flowConfig.NetworkConfig.GossipSub.RpcTracer.RPCSentTrackerCacheSize, + RpcSentTrackerWorkerQueueCacheSize: flowConfig.NetworkConfig.GossipSub.RpcTracer.RPCSentTrackerQueueCacheSize, + RpcSentTrackerNumOfWorkers: flowConfig.NetworkConfig.GossipSub.RpcTracer.RpcSentTrackerNumOfWorkers, } return tracer.NewGossipSubMeshTracer(meshTracerCfg) } diff --git a/insecure/integration/functional/test/gossipsub/rpc_inspector/validation_inspector_test.go b/insecure/integration/functional/test/gossipsub/rpc_inspector/validation_inspector_test.go index 3b291f185bc..0f68de8a4d7 100644 --- a/insecure/integration/functional/test/gossipsub/rpc_inspector/validation_inspector_test.go +++ b/insecure/integration/functional/test/gossipsub/rpc_inspector/validation_inspector_test.go @@ -3,6 +3,7 @@ package rpc_inspector import ( "context" "fmt" + "math" "os" "testing" "time" @@ -28,7 +29,6 @@ import ( "github.com/onflow/flow-go/network/p2p/inspector/validation" p2pmsg "github.com/onflow/flow-go/network/p2p/message" mockp2p "github.com/onflow/flow-go/network/p2p/mock" - "github.com/onflow/flow-go/network/p2p/scoring" p2ptest "github.com/onflow/flow-go/network/p2p/test" "github.com/onflow/flow-go/utils/unittest" ) @@ -44,10 +44,10 @@ func TestValidationInspector_InvalidTopicId_Detection(t *testing.T) { sporkID := unittest.IdentifierFixture() flowConfig, err := config.DefaultConfig() require.NoError(t, err) - inspectorConfig := flowConfig.NetworkConfig.GossipSubConfig.GossipSubRPCInspectorsConfig.GossipSubRPCValidationInspectorConfigs + inspectorConfig := flowConfig.NetworkConfig.GossipSub.RpcInspector.Validation messageCount := 100 - inspectorConfig.NumberOfWorkers = 1 + inspectorConfig.InspectionQueue.NumberOfWorkers = 1 controlMessageCount := int64(1) count := atomic.NewUint64(0) @@ -90,6 +90,7 @@ func TestValidationInspector_InvalidTopicId_Detection(t *testing.T) { distributor := mockp2p.NewGossipSubInspectorNotificationDistributor(t) p2ptest.MockInspectorNotificationDistributorReadyDoneAware(distributor) withExpectedNotificationDissemination(expectedNumOfTotalNotif, inspectDisseminatedNotifyFunc)(distributor, spammer) + meshTracer := meshTracerFixture(flowConfig, idProvider) topicProvider := newMockUpdatableTopicProvider() validationInspector, err := validation.NewControlMsgValidationInspector(&validation.InspectorParams{ @@ -113,7 +114,6 @@ func TestValidationInspector_InvalidTopicId_Detection(t *testing.T) { t.Name(), idProvider, p2ptest.WithRole(role), - p2ptest.WithGossipSubTracer(meshTracer), internal.WithCorruptGossipSub(corruptlibp2p.CorruptGossipSubFactory(), corruptlibp2p.CorruptGossipSubConfigFactoryWithInspector(corruptInspectorFunc))) idProvider.On("ByPeerID", victimNode.ID()).Return(&victimIdentity, true).Maybe() idProvider.On("ByPeerID", spammer.SpammerNode.ID()).Return(&spammer.SpammerId, true).Maybe() @@ -178,11 +178,13 @@ func TestValidationInspector_DuplicateTopicId_Detection(t *testing.T) { sporkID := unittest.IdentifierFixture() flowConfig, err := config.DefaultConfig() require.NoError(t, err) - inspectorConfig := flowConfig.NetworkConfig.GossipSubConfig.GossipSubRPCInspectorsConfig.GossipSubRPCValidationInspectorConfigs + inspectorConfig := flowConfig.NetworkConfig.GossipSub.RpcInspector.Validation - inspectorConfig.NumberOfWorkers = 1 + inspectorConfig.InspectionQueue.NumberOfWorkers = 1 - messageCount := 10 + // sets the message count to the max of the duplicate topic id threshold for graft and prune control messages to ensure + // a successful attack + messageCount := int(math.Max(float64(inspectorConfig.IHave.DuplicateTopicIdThreshold), float64(inspectorConfig.GraftPrune.DuplicateTopicIdThreshold))) + 2 controlMessageCount := int64(1) count := atomic.NewInt64(0) @@ -249,7 +251,6 @@ func TestValidationInspector_DuplicateTopicId_Detection(t *testing.T) { t.Name(), idProvider, p2ptest.WithRole(role), - p2ptest.WithGossipSubTracer(meshTracer), internal.WithCorruptGossipSub(corruptlibp2p.CorruptGossipSubFactory(), corruptlibp2p.CorruptGossipSubConfigFactoryWithInspector(corruptInspectorFunc))) idProvider.On("ByPeerID", victimNode.ID()).Return(&victimIdentity, true).Maybe() idProvider.On("ByPeerID", spammer.SpammerNode.ID()).Return(&spammer.SpammerId, true).Maybe() @@ -289,9 +290,9 @@ func TestValidationInspector_IHaveDuplicateMessageId_Detection(t *testing.T) { sporkID := unittest.IdentifierFixture() flowConfig, err := config.DefaultConfig() require.NoError(t, err) - inspectorConfig := flowConfig.NetworkConfig.GossipSubConfig.GossipSubRPCInspectorsConfig.GossipSubRPCValidationInspectorConfigs + inspectorConfig := flowConfig.NetworkConfig.GossipSub.RpcInspector.Validation - inspectorConfig.NumberOfWorkers = 1 + inspectorConfig.InspectionQueue.NumberOfWorkers = 1 count := atomic.NewInt64(0) done := make(chan struct{}) @@ -303,7 +304,7 @@ func TestValidationInspector_IHaveDuplicateMessageId_Detection(t *testing.T) { notification, ok := args[0].(*p2p.InvCtrlMsgNotif) require.True(t, ok) require.Equal(t, notification.TopicType, p2p.CtrlMsgNonClusterTopicType, "IsClusterPrefixed is expected to be false, no RPC with cluster prefixed topic sent in this test") - require.True(t, validation.IsDuplicateTopicErr(notification.Error)) + require.True(t, validation.IsDuplicateMessageIDErr(notification.Error)) require.Equal(t, spammer.SpammerNode.ID(), notification.PeerID) require.True(t, notification.MsgType == p2pmsg.CtrlMsgIHave, @@ -350,7 +351,6 @@ func TestValidationInspector_IHaveDuplicateMessageId_Detection(t *testing.T) { t.Name(), idProvider, p2ptest.WithRole(role), - p2ptest.WithGossipSubTracer(meshTracer), internal.WithCorruptGossipSub(corruptlibp2p.CorruptGossipSubFactory(), corruptlibp2p.CorruptGossipSubConfigFactoryWithInspector(corruptInspectorFunc))) idProvider.On("ByPeerID", victimNode.ID()).Return(&victimIdentity, true).Maybe() idProvider.On("ByPeerID", spammer.SpammerNode.ID()).Return(&spammer.SpammerId, true).Maybe() @@ -364,21 +364,25 @@ func TestValidationInspector_IHaveDuplicateMessageId_Detection(t *testing.T) { validationInspector.Start(signalerCtx) nodes := []p2p.LibP2PNode{victimNode, spammer.SpammerNode} startNodesAndEnsureConnected(t, signalerCtx, nodes, sporkID) + // to suppress peers provider not set + p2ptest.RegisterPeerProviders(t, nodes) spammer.Start(t) defer stopComponents(t, cancel, nodes, validationInspector) // generate 2 control messages with iHaves for different topics - ihaveCtlMsgs1 := spammer.GenerateCtlMessages(1, p2ptest.WithIHave(1, 1, pushBlocks.String())) - ihaveCtlMsgs2 := spammer.GenerateCtlMessages(1, p2ptest.WithIHave(1, 1, reqChunks.String())) - - // duplicate message ids for a single topic is invalid and will cause an error - ihaveCtlMsgs1[0].Ihave[0].MessageIDs = append(ihaveCtlMsgs1[0].Ihave[0].MessageIDs, ihaveCtlMsgs1[0].Ihave[0].MessageIDs[0]) - // duplicate message ids across different topics is valid - ihaveCtlMsgs2[0].Ihave[0].MessageIDs[0] = ihaveCtlMsgs1[0].Ihave[0].MessageIDs[0] + messageIdCount := inspectorConfig.IHave.DuplicateMessageIdThreshold + 2 + messageIds := unittest.IdentifierListFixture(1) + for i := 0; i < messageIdCount; i++ { + messageIds = append(messageIds, messageIds[0]) + } + // prepares 2 control messages with iHave messages for different topics with duplicate message IDs + ihaveCtlMsgs1 := spammer.GenerateCtlMessages( + 1, + p2ptest.WithIHaveMessageIDs(messageIds.Strings(), pushBlocks.String()), + p2ptest.WithIHaveMessageIDs(messageIds.Strings(), reqChunks.String())) // start spamming the victim peer spammer.SpamControlMessage(t, victimNode, ihaveCtlMsgs1) - spammer.SpamControlMessage(t, victimNode, ihaveCtlMsgs2) unittest.RequireCloseBefore(t, done, 5*time.Second, "failed to inspect RPC messages on time") // ensure we receive the expected number of invalid control message notifications @@ -392,11 +396,11 @@ func TestValidationInspector_UnknownClusterId_Detection(t *testing.T) { sporkID := unittest.IdentifierFixture() flowConfig, err := config.DefaultConfig() require.NoError(t, err) - inspectorConfig := flowConfig.NetworkConfig.GossipSubConfig.GossipSubRPCInspectorsConfig.GossipSubRPCValidationInspectorConfigs + inspectorConfig := flowConfig.NetworkConfig.GossipSub.RpcInspector.Validation // set hard threshold to 0 so that in the case of invalid cluster ID // we force the inspector to return an error - inspectorConfig.ClusterPrefixHardThreshold = 0 - inspectorConfig.NumberOfWorkers = 1 + inspectorConfig.ClusterPrefixedMessage.HardThreshold = 0 + inspectorConfig.InspectionQueue.NumberOfWorkers = 1 // SafetyThreshold < messageCount < HardThreshold ensures that the RPC message will be further inspected and topic IDs will be checked // restricting the message count to 1 allows us to only aggregate a single error when the error is logged in the inspector. @@ -463,7 +467,6 @@ func TestValidationInspector_UnknownClusterId_Detection(t *testing.T) { t.Name(), idProvider, p2ptest.WithRole(role), - p2ptest.WithGossipSubTracer(meshTracer), internal.WithCorruptGossipSub(corruptlibp2p.CorruptGossipSubFactory(), corruptlibp2p.CorruptGossipSubConfigFactoryWithInspector(corruptInspectorFunc))) idProvider.On("ByPeerID", victimNode.ID()).Return(&victimIdentity, true).Maybe() idProvider.On("ByPeerID", spammer.SpammerNode.ID()).Return(&spammer.SpammerId, true).Times(4) @@ -504,9 +507,9 @@ func TestValidationInspector_ActiveClusterIdsNotSet_Graft_Detection(t *testing.T sporkID := unittest.IdentifierFixture() flowConfig, err := config.DefaultConfig() require.NoError(t, err) - inspectorConfig := flowConfig.NetworkConfig.GossipSubConfig.GossipSubRPCInspectorsConfig.GossipSubRPCValidationInspectorConfigs - inspectorConfig.ClusterPrefixHardThreshold = 5 - inspectorConfig.NumberOfWorkers = 1 + inspectorConfig := flowConfig.NetworkConfig.GossipSub.RpcInspector.Validation + inspectorConfig.ClusterPrefixedMessage.HardThreshold = 5 + inspectorConfig.InspectionQueue.NumberOfWorkers = 1 controlMessageCount := int64(10) count := atomic.NewInt64(0) @@ -558,7 +561,6 @@ func TestValidationInspector_ActiveClusterIdsNotSet_Graft_Detection(t *testing.T t.Name(), idProvider, p2ptest.WithRole(role), - p2ptest.WithGossipSubTracer(meshTracer), internal.WithCorruptGossipSub(corruptlibp2p.CorruptGossipSubFactory(), corruptlibp2p.CorruptGossipSubConfigFactoryWithInspector(corruptInspectorFunc))) idProvider.On("ByPeerID", victimNode.ID()).Return(&victimIdentity, true).Maybe() idProvider.On("ByPeerID", spammer.SpammerNode.ID()).Return(&spammer.SpammerId, true).Maybe() @@ -593,9 +595,9 @@ func TestValidationInspector_ActiveClusterIdsNotSet_Prune_Detection(t *testing.T sporkID := unittest.IdentifierFixture() flowConfig, err := config.DefaultConfig() require.NoError(t, err) - inspectorConfig := flowConfig.NetworkConfig.GossipSubConfig.GossipSubRPCInspectorsConfig.GossipSubRPCValidationInspectorConfigs - inspectorConfig.ClusterPrefixHardThreshold = 5 - inspectorConfig.NumberOfWorkers = 1 + inspectorConfig := flowConfig.NetworkConfig.GossipSub.RpcInspector.Validation + inspectorConfig.ClusterPrefixedMessage.HardThreshold = 5 + inspectorConfig.InspectionQueue.NumberOfWorkers = 1 controlMessageCount := int64(10) count := atomic.NewInt64(0) @@ -644,7 +646,6 @@ func TestValidationInspector_ActiveClusterIdsNotSet_Prune_Detection(t *testing.T t.Name(), idProvider, p2ptest.WithRole(role), - p2ptest.WithGossipSubTracer(meshTracer), internal.WithCorruptGossipSub(corruptlibp2p.CorruptGossipSubFactory(), corruptlibp2p.CorruptGossipSubConfigFactoryWithInspector(corruptInspectorFunc))) idProvider.On("ByPeerID", victimNode.ID()).Return(&victimIdentity, true).Maybe() idProvider.On("ByPeerID", spammer.SpammerNode.ID()).Return(&spammer.SpammerId, true).Maybe() @@ -671,18 +672,18 @@ func TestValidationInspector_ActiveClusterIdsNotSet_Prune_Detection(t *testing.T unittest.RequireCloseBefore(t, done, 5*time.Second, "failed to inspect RPC messages on time") } -// TestValidationInspector_UnstakedNode_Detection ensures that RPC control message inspector disseminates an invalid control message notification when an unstaked peer +// TestValidationInspector_Unstaked_Node_Detection ensures that RPC control message inspector disseminates an invalid control message notification when an unstaked peer // sends a control message for a cluster prefixed topic. func TestValidationInspector_UnstakedNode_Detection(t *testing.T) { role := flow.RoleConsensus sporkID := unittest.IdentifierFixture() flowConfig, err := config.DefaultConfig() require.NoError(t, err) - inspectorConfig := flowConfig.NetworkConfig.GossipSubConfig.GossipSubRPCInspectorsConfig.GossipSubRPCValidationInspectorConfigs + inspectorConfig := flowConfig.NetworkConfig.GossipSub.RpcInspector.Validation // set hard threshold to 0 so that in the case of invalid cluster ID // we force the inspector to return an error - inspectorConfig.ClusterPrefixHardThreshold = 0 - inspectorConfig.NumberOfWorkers = 1 + inspectorConfig.ClusterPrefixedMessage.HardThreshold = 0 + inspectorConfig.InspectionQueue.NumberOfWorkers = 1 // SafetyThreshold < messageCount < HardThreshold ensures that the RPC message will be further inspected and topic IDs will be checked // restricting the message count to 1 allows us to only aggregate a single error when the error is logged in the inspector. @@ -736,7 +737,6 @@ func TestValidationInspector_UnstakedNode_Detection(t *testing.T) { t.Name(), idProvider, p2ptest.WithRole(role), - p2ptest.WithGossipSubTracer(meshTracer), internal.WithCorruptGossipSub(corruptlibp2p.CorruptGossipSubFactory(), corruptlibp2p.CorruptGossipSubConfigFactoryWithInspector(corruptInspectorFunc))) idProvider.On("ByPeerID", victimNode.ID()).Return(&victimIdentity, true).Maybe() idProvider.On("ByPeerID", spammer.SpammerNode.ID()).Return(&spammer.SpammerId, true).Maybe() @@ -777,12 +777,10 @@ func TestValidationInspector_InspectIWants_CacheMissThreshold(t *testing.T) { // create our RPC validation inspector flowConfig, err := config.DefaultConfig() require.NoError(t, err) - inspectorConfig := flowConfig.NetworkConfig.GossipSubConfig.GossipSubRPCInspectorsConfig.GossipSubRPCValidationInspectorConfigs - // force all cache miss checks - inspectorConfig.IWantRPCInspectionConfig.CacheMissCheckSize = 1 - inspectorConfig.NumberOfWorkers = 1 - inspectorConfig.IWantRPCInspectionConfig.CacheMissThreshold = .5 // set cache miss threshold to 50% - messageCount := 1 + inspectorConfig := flowConfig.NetworkConfig.GossipSub.RpcInspector.Validation + inspectorConfig.InspectionQueue.NumberOfWorkers = 1 + inspectorConfig.IWant.CacheMissThreshold = 10 + messageCount := 10 controlMessageCount := int64(1) cacheMissThresholdNotifCount := atomic.NewUint64(0) done := make(chan struct{}) @@ -838,7 +836,6 @@ func TestValidationInspector_InspectIWants_CacheMissThreshold(t *testing.T) { t.Name(), idProvider, p2ptest.WithRole(role), - p2ptest.WithGossipSubTracer(meshTracer), internal.WithCorruptGossipSub(corruptlibp2p.CorruptGossipSubFactory(), corruptlibp2p.CorruptGossipSubConfigFactoryWithInspector(corruptInspectorFunc))) idProvider.On("ByPeerID", victimNode.ID()).Return(&victimIdentity, true).Maybe() idProvider.On("ByPeerID", spammer.SpammerNode.ID()).Return(&spammer.SpammerId, true).Maybe() @@ -882,8 +879,8 @@ func TestValidationInspector_InspectRpcPublishMessages(t *testing.T) { // create our RPC validation inspector flowConfig, err := config.DefaultConfig() require.NoError(t, err) - inspectorConfig := flowConfig.NetworkConfig.GossipSubConfig.GossipSubRPCInspectorsConfig.GossipSubRPCValidationInspectorConfigs - inspectorConfig.NumberOfWorkers = 1 + inspectorConfig := flowConfig.NetworkConfig.GossipSub.RpcInspector.Validation + inspectorConfig.InspectionQueue.NumberOfWorkers = 1 idProvider := mock.NewIdentityProvider(t) spammer := corruptlibp2p.NewGossipSubRouterSpammer(t, sporkID, role, idProvider) @@ -976,7 +973,7 @@ func TestValidationInspector_InspectRpcPublishMessages(t *testing.T) { topicProvider.UpdateTopics(topics) // after 7 errors encountered disseminate a notification - inspectorConfig.RpcMessageErrorThreshold = 6 + inspectorConfig.PublishMessages.ErrorThreshold = 6 require.NoError(t, err) corruptInspectorFunc := corruptlibp2p.CorruptInspectorFunc(validationInspector) @@ -985,7 +982,6 @@ func TestValidationInspector_InspectRpcPublishMessages(t *testing.T) { t.Name(), idProvider, p2ptest.WithRole(role), - p2ptest.WithGossipSubTracer(meshTracer), internal.WithCorruptGossipSub(corruptlibp2p.CorruptGossipSubFactory(), corruptlibp2p.CorruptGossipSubConfigFactoryWithInspector(corruptInspectorFunc))) idProvider.On("ByPeerID", victimNode.ID()).Return(&victimIdentity, true).Maybe() idProvider.On("ByPeerID", spammer.SpammerNode.ID()).Return(&spammer.SpammerId, true).Maybe() @@ -1028,13 +1024,17 @@ func TestGossipSubSpamMitigationIntegration(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) signalerCtx := irrecoverable.NewMockSignalerContext(t, ctx) + cfg, err := config.DefaultConfig() + require.NoError(t, err) + // set the scoring parameters to be more aggressive to speed up the test + cfg.NetworkConfig.GossipSub.RpcTracer.ScoreTracerInterval = 100 * time.Millisecond + cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.AppSpecificScore.ScoreTTL = 100 * time.Millisecond victimNode, victimId := p2ptest.NodeFixture(t, sporkID, t.Name(), idProvider, - p2ptest.WithPeerScoreTracerInterval(100*time.Millisecond), p2ptest.WithRole(flow.RoleConsensus), - p2ptest.EnablePeerScoringWithOverride(p2p.PeerScoringConfigNoOverride)) + p2ptest.OverrideFlowConfig(cfg)) ids := flow.IdentityList{&victimId, &spammer.SpammerId} idProvider.On("ByPeerID", mockery.Anything).Return(func(peerId peer.ID) *flow.Identity { @@ -1092,12 +1092,14 @@ func TestGossipSubSpamMitigationIntegration(t *testing.T) { graftCtlMsgsWithUnknownTopic := spammer.GenerateCtlMessages(int(spamCtrlMsgCount), p2ptest.WithGraft(spamRpcCount, unknownTopic.String())) graftCtlMsgsWithMalformedTopic := spammer.GenerateCtlMessages(int(spamCtrlMsgCount), p2ptest.WithGraft(spamRpcCount, malformedTopic.String())) graftCtlMsgsInvalidSporkIDTopic := spammer.GenerateCtlMessages(int(spamCtrlMsgCount), p2ptest.WithGraft(spamRpcCount, invalidSporkIDTopic.String())) - graftCtlMsgsDuplicateTopic := spammer.GenerateCtlMessages(int(spamCtrlMsgCount), p2ptest.WithGraft(3, duplicateTopic.String())) + graftCtlMsgsDuplicateTopic := spammer.GenerateCtlMessages(int(spamCtrlMsgCount), // sets duplicate to +2 above the threshold to ensure that the victim node will penalize the spammer node + p2ptest.WithGraft(cfg.NetworkConfig.GossipSub.RpcInspector.Validation.GraftPrune.DuplicateTopicIdThreshold+2, duplicateTopic.String())) pruneCtlMsgsWithUnknownTopic := spammer.GenerateCtlMessages(int(spamCtrlMsgCount), p2ptest.WithPrune(spamRpcCount, unknownTopic.String())) pruneCtlMsgsWithMalformedTopic := spammer.GenerateCtlMessages(int(spamCtrlMsgCount), p2ptest.WithPrune(spamRpcCount, malformedTopic.String())) pruneCtlMsgsInvalidSporkIDTopic := spammer.GenerateCtlMessages(int(spamCtrlMsgCount), p2ptest.WithGraft(spamRpcCount, invalidSporkIDTopic.String())) - pruneCtlMsgsDuplicateTopic := spammer.GenerateCtlMessages(int(spamCtrlMsgCount), p2ptest.WithPrune(3, duplicateTopic.String())) + pruneCtlMsgsDuplicateTopic := spammer.GenerateCtlMessages(int(spamCtrlMsgCount), // sets duplicate to +2 above the threshold to ensure that the victim node will penalize the spammer node + p2ptest.WithPrune(cfg.NetworkConfig.GossipSub.RpcInspector.Validation.GraftPrune.DuplicateTopicIdThreshold+2, duplicateTopic.String())) // start spamming the victim peer spammer.SpamControlMessage(t, victimNode, graftCtlMsgsWithUnknownTopic) @@ -1109,11 +1111,11 @@ func TestGossipSubSpamMitigationIntegration(t *testing.T) { spammer.SpamControlMessage(t, victimNode, pruneCtlMsgsWithMalformedTopic) spammer.SpamControlMessage(t, victimNode, pruneCtlMsgsInvalidSporkIDTopic) spammer.SpamControlMessage(t, victimNode, pruneCtlMsgsDuplicateTopic) - + scoreOptParameters := cfg.NetworkConfig.GossipSub.ScoringParameters.PeerScoring.Internal.Thresholds // wait for three GossipSub heartbeat intervals to ensure that the victim node has penalized the spammer node. require.Eventually(t, func() bool { score, ok := victimNode.PeerScoreExposer().GetScore(spammer.SpammerNode.ID()) - return ok && score < 2*scoring.DefaultGraylistThreshold + return ok && score < 2*scoreOptParameters.Graylist }, 5*time.Second, 100*time.Millisecond, "expected victim node to penalize spammer node") // now we expect the detection and mitigation to kick in and the victim node to disconnect from the spammer node. diff --git a/insecure/integration/functional/test/gossipsub/scoring/ihave_spam_test.go b/insecure/integration/functional/test/gossipsub/scoring/ihave_spam_test.go index 37524922907..c43b7435f55 100644 --- a/insecure/integration/functional/test/gossipsub/scoring/ihave_spam_test.go +++ b/insecure/integration/functional/test/gossipsub/scoring/ihave_spam_test.go @@ -3,11 +3,11 @@ package scoring import ( "context" "fmt" - "sync" "testing" "time" pubsub "github.com/libp2p/go-libp2p-pubsub" + pb "github.com/libp2p/go-libp2p-pubsub/pb" "github.com/libp2p/go-libp2p/core/peer" "github.com/stretchr/testify/require" corrupt "github.com/yhassanzadeh13/go-libp2p-pubsub" @@ -16,10 +16,8 @@ import ( "github.com/onflow/flow-go/insecure/corruptlibp2p" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/module/irrecoverable" - "github.com/onflow/flow-go/module/mock" "github.com/onflow/flow-go/network/channels" "github.com/onflow/flow-go/network/p2p" - "github.com/onflow/flow-go/network/p2p/scoring" p2ptest "github.com/onflow/flow-go/network/p2p/test" "github.com/onflow/flow-go/utils/unittest" ) @@ -27,22 +25,19 @@ import ( // TestGossipSubIHaveBrokenPromises_Below_Threshold tests that as long as the spammer stays below the ihave spam thresholds, it is not caught and // penalized by the victim node. // The thresholds are: -// Maximum messages that include iHave per heartbeat is: 10 (gossipsub parameter). -// Threshold for broken promises of iHave per heartbeat is: 10 (Flow-specific) parameter. It means that GossipSub samples one iHave id out of the -// entire RPC and if that iHave id is not eventually delivered within 3 seconds (gossipsub parameter), then the promise is considered broken. We set +// - Maximum messages that include iHave per heartbeat is: 10 (gossipsub parameter; GossipSubMaxIHaveMessages), after which iHave messages are dropped. +// - Threshold for broken promises of iHave per heartbeat is: 10 (Flow parameter; defaultBehaviourPenaltyThreshold). It means that GossipSub samples one iHave id out of the +// entire RPC and if that iHave id is not eventually delivered within 3 seconds (gossipsub parameter, GossipSubIWantFollowupTime), then the promise is considered broken. We set // this threshold to 10 meaning that the first 10 broken promises are ignored. This is to allow for some network churn. -// Also, per hearbeat (i.e., decay interval), the spammer is allowed to send at most 5000 ihave messages (gossip sub parameter) on aggregate, and +// - Per hearbeat (gossipsub parameter GossipSubHeartbeatInterval, 1 second ), the spammer is allowed to send at most 5000 ihave messages (gossip sub parameter; GossipSubMaxIHaveLength) on aggregate, and // excess messages are dropped (without being counted as broken promises). func TestGossipSubIHaveBrokenPromises_Below_Threshold(t *testing.T) { - - unittest.SkipUnless(t, unittest.TEST_FLAKY, "flaky test") - role := flow.RoleConsensus sporkId := unittest.IdentifierFixture() blockTopic := channels.TopicFromChannel(channels.PushBlocks, sporkId) receivedIWants := unittest.NewProtectedMap[string, struct{}]() - idProvider := mock.NewIdentityProvider(t) + idProvider := unittest.NewUpdatableIDProvider(flow.IdentityList{}) spammer := corruptlibp2p.NewGossipSubRouterSpammerWithRpcInspector(t, sporkId, role, idProvider, func(id peer.ID, rpc *corrupt.RPC) error { // override rpc inspector of the spammer node to keep track of the iwants it has received. if rpc.RPC.Control == nil || rpc.RPC.Control.Iwant == nil { @@ -59,31 +54,49 @@ func TestGossipSubIHaveBrokenPromises_Below_Threshold(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) signalerCtx := irrecoverable.NewMockSignalerContext(t, ctx) // we override some of the default scoring parameters in order to speed up the test in a time-efficient manner. - blockTopicOverrideParams := scoring.DefaultTopicScoreParams() + blockTopicOverrideParams := defaultTopicScoreParams(t) blockTopicOverrideParams.MeshMessageDeliveriesActivation = 1 * time.Second // we start observing the mesh message deliveries after 1 second of the node startup. // we disable invalid message delivery parameters, as the way we implement spammer, when it spams ihave messages, it does not sign them. Hence, without decaying the invalid message deliveries, // the node would be penalized for invalid message delivery way sooner than it can mount an ihave broken-promises spam attack. blockTopicOverrideParams.InvalidMessageDeliveriesWeight = 0.0 blockTopicOverrideParams.InvalidMessageDeliveriesDecay = 0.0 + + conf, err := config.DefaultConfig() + require.NoError(t, err) + // we override the decay interval to 1 second so that the score is updated within 1 second intervals. + conf.NetworkConfig.GossipSub.ScoringParameters.PeerScoring.Internal.DecayInterval = 1 * time.Second + // score tracer interval is set to 500 milliseconds to speed up the test, it should be shorter than the heartbeat interval (1 second) of gossipsub to catch the score updates in time. + conf.NetworkConfig.GossipSub.RpcTracer.ScoreTracerInterval = 500 * time.Millisecond + + // relaxing the scoring parameters to fit the test scenario. + conf.NetworkConfig.GossipSub.ScoringParameters.PeerScoring.Internal.Behaviour.PenaltyDecay = 0.99 + conf.NetworkConfig.GossipSub.ScoringParameters.PeerScoring.Internal.Behaviour.PenaltyThreshold = 10 + conf.NetworkConfig.GossipSub.ScoringParameters.PeerScoring.Internal.Behaviour.PenaltyWeight = -1 + victimNode, victimIdentity := p2ptest.NodeFixture( t, sporkId, t.Name(), idProvider, p2ptest.WithRole(role), - p2ptest.WithPeerScoreTracerInterval(1*time.Second), + p2ptest.OverrideFlowConfig(conf), p2ptest.EnablePeerScoringWithOverride(&p2p.PeerScoringConfigOverride{ TopicScoreParams: map[channels.Topic]*pubsub.TopicScoreParams{ blockTopic: blockTopicOverrideParams, }, - DecayInterval: 1 * time.Second, // we override the decay interval to 1 second so that the score is updated within 1 second intervals. }), ) - idProvider.On("ByPeerID", victimNode.ID()).Return(&victimIdentity, true).Maybe() - idProvider.On("ByPeerID", spammer.SpammerNode.ID()).Return(&spammer.SpammerId, true).Maybe() ids := flow.IdentityList{&spammer.SpammerId, &victimIdentity} + idProvider.SetIdentities(ids) nodes := []p2p.LibP2PNode{spammer.SpammerNode, victimNode} + // to suppress the logs of "peer provider has not set" + victimNode.WithPeersProvider(func() peer.IDSlice { + return peer.IDSlice{spammer.SpammerNode.ID()} + }) + spammer.SpammerNode.WithPeersProvider(func() peer.IDSlice { + return peer.IDSlice{victimNode.ID()} + }) p2ptest.StartNodes(t, signalerCtx, nodes) defer p2ptest.StopNodes(t, nodes, cancel) @@ -106,38 +119,41 @@ func TestGossipSubIHaveBrokenPromises_Below_Threshold(t *testing.T) { if !ok { return false } - // We set 7.5 as the threshold to compensate for the scoring decay in between RPC's being processed by the inspector - // ideally it must be 10 (one per RPC), but we give it a buffer of 1 to account for decays and floating point errors. + // ideally, we should have 10, but we give it a buffer of 2.5 to account for 25% discrepancy due to scoring decays, timer asynchrony, and floating point errors. if behavioralPenalty < 7.5 { + t.Logf("pending on behavioral penalty %f", behavioralPenalty) return false } - + t.Logf("success on behavioral penalty %f", behavioralPenalty) initialBehavioralPenalty = behavioralPenalty return true // Note: we have to wait at least 3 seconds for an iHave to be considered as broken promise (gossipsub parameters), we set it to 10 // seconds to be on the safe side. - }, 10*time.Second, 100*time.Millisecond) + // Also, the internal heartbeat of GossipSub is 1 second, hence, there is no need to have ticks shorter than 500 milliseconds. + }, 10*time.Second, 500*time.Millisecond) + + scoreParams := conf.NetworkConfig.GossipSub.ScoringParameters spammerScore, ok := victimNode.PeerScoreExposer().GetScore(spammer.SpammerNode.ID()) require.True(t, ok, "sanity check failed, we should have a score for the spammer node") // since spammer is not yet considered to be penalized, its score must be greater than the gossipsub health thresholds. require.Greaterf(t, spammerScore, - scoring.DefaultGossipThreshold, + scoreParams.PeerScoring.Internal.Thresholds.Gossip, "sanity check failed, the score of the spammer node must be greater than gossip threshold: %f, actual: %f", - scoring.DefaultGossipThreshold, + scoreParams.PeerScoring.Internal.Thresholds.Gossip, spammerScore) require.Greaterf(t, spammerScore, - scoring.DefaultPublishThreshold, + scoreParams.PeerScoring.Internal.Thresholds.Publish, "sanity check failed, the score of the spammer node must be greater than publish threshold: %f, actual: %f", - scoring.DefaultPublishThreshold, + scoreParams.PeerScoring.Internal.Thresholds.Publish, spammerScore) require.Greaterf(t, spammerScore, - scoring.DefaultGraylistThreshold, + scoreParams.PeerScoring.Internal.Thresholds.Graylist, "sanity check failed, the score of the spammer node must be greater than graylist threshold: %f, actual: %f", - scoring.DefaultGraylistThreshold, + scoreParams.PeerScoring.Internal.Thresholds.Graylist, spammerScore) // eventually, after a heartbeat the spammer behavioral counter must be decayed @@ -151,7 +167,7 @@ func TestGossipSubIHaveBrokenPromises_Below_Threshold(t *testing.T) { } return true - }, 2*time.Second, 100*time.Millisecond, "sanity check failed, the spammer behavioral counter must be decayed after a heartbeat") + }, 2*time.Second, 500*time.Millisecond, "sanity check failed, the spammer behavioral counter must be decayed after a heartbeat") // since spammer stays below the threshold, it should be able to exchange messages with the victim node over pubsub. p2ptest.EnsurePubsubMessageExchange(t, ctx, nodes, blockTopic, 1, func() interface{} { @@ -162,19 +178,18 @@ func TestGossipSubIHaveBrokenPromises_Below_Threshold(t *testing.T) { // TestGossipSubIHaveBrokenPromises_Above_Threshold tests that a continuous stream of spam iHave broken promises will // eventually cause the spammer node to be graylisted (i.e., no incoming RPCs from the spammer node will be accepted, and // no outgoing RPCs to the spammer node will be sent). -// The test performs 3 rounds of attacks: each round with 10 RPCs, each RPC with 10 iHave messages, each iHave message with 50 message ids, hence overall, we have 5000 iHave message ids. -// Note that based on GossipSub parameters 5000 iHave is the most one can send within one decay interval. +// The test performs 3 rounds of attacks: each round with 10 RPCs, each RPC with 1 iHave messages, each iHave message with 500 message ids, hence overall, we have 5000 iHave message ids. +// Note that based on GossipSub parameters 5000 iHave is the most one can send within one heart beat. // First round of attack makes spammers broken promises still below the threshold of 10 RPCs (broken promises are counted per RPC), hence no degradation of the spammers score. // Second round of attack makes spammers broken promises above the threshold of 10 RPCs, hence a degradation of the spammers score. // Third round of attack makes spammers broken promises to around 20 RPCs above the threshold, which causes the graylisting of the spammer node. func TestGossipSubIHaveBrokenPromises_Above_Threshold(t *testing.T) { - unittest.SkipUnless(t, unittest.TEST_FLAKY, "flaky in CI") role := flow.RoleConsensus sporkId := unittest.IdentifierFixture() blockTopic := channels.TopicFromChannel(channels.PushBlocks, sporkId) receivedIWants := unittest.NewProtectedMap[string, struct{}]() - idProvider := mock.NewIdentityProvider(t) + idProvider := unittest.NewUpdatableIDProvider(flow.IdentityList{}) spammer := corruptlibp2p.NewGossipSubRouterSpammerWithRpcInspector(t, sporkId, role, idProvider, func(id peer.ID, rpc *corrupt.RPC) error { // override rpc inspector of the spammer node to keep track of the iwants it has received. if rpc.RPC.Control == nil || rpc.RPC.Control.Iwant == nil { @@ -191,20 +206,35 @@ func TestGossipSubIHaveBrokenPromises_Above_Threshold(t *testing.T) { conf, err := config.DefaultConfig() require.NoError(t, err) // overcompensate for RPC truncation - conf.NetworkConfig.GossipSubRPCInspectorsConfig.IHaveRPCInspectionConfig.MaxSampleSize = 10000 - conf.NetworkConfig.GossipSubRPCInspectorsConfig.IHaveRPCInspectionConfig.MaxMessageIDSampleSize = 10000 - conf.NetworkConfig.GossipSubRPCInspectorsConfig.IWantRPCInspectionConfig.MaxSampleSize = 10000 - conf.NetworkConfig.GossipSubRPCInspectorsConfig.IWantRPCInspectionConfig.MaxMessageIDSampleSize = 10000 + conf.NetworkConfig.GossipSub.RpcInspector.Validation.IHave.MessageCountThreshold = 10000 + conf.NetworkConfig.GossipSub.RpcInspector.Validation.IHave.MessageIdCountThreshold = 10000 + conf.NetworkConfig.GossipSub.RpcInspector.Validation.IHave.MessageCountThreshold = 10000 + conf.NetworkConfig.GossipSub.RpcInspector.Validation.IHave.MessageIdCountThreshold = 10000 + // we override the decay interval to 1 second so that the score is updated within 1 second intervals. + conf.NetworkConfig.GossipSub.ScoringParameters.PeerScoring.Internal.DecayInterval = 1 * time.Second + // score tracer interval is set to 500 milliseconds to speed up the test, it should be shorter than the heartbeat interval (1 second) of gossipsub to catch the score updates in time. + conf.NetworkConfig.GossipSub.RpcTracer.ScoreTracerInterval = 500 * time.Millisecond + + // relaxing the scoring parameters to fit the test scenario. + conf.NetworkConfig.GossipSub.ScoringParameters.PeerScoring.Internal.Behaviour.PenaltyDecay = 0.99 + conf.NetworkConfig.GossipSub.ScoringParameters.PeerScoring.Internal.Behaviour.PenaltyThreshold = 10 + conf.NetworkConfig.GossipSub.ScoringParameters.PeerScoring.Internal.Behaviour.PenaltyWeight = -1 ctx, cancel := context.WithCancel(context.Background()) signalerCtx := irrecoverable.NewMockSignalerContext(t, ctx) // we override some of the default scoring parameters in order to speed up the test in a time-efficient manner. - blockTopicOverrideParams := scoring.DefaultTopicScoreParams() + blockTopicOverrideParams := defaultTopicScoreParams(t) blockTopicOverrideParams.MeshMessageDeliveriesActivation = 1 * time.Second // we start observing the mesh message deliveries after 1 second of the node startup. // we disable invalid message delivery parameters, as the way we implement spammer, when it spams ihave messages, it does not sign them. Hence, without decaying the invalid message deliveries, // the node would be penalized for invalid message delivery way sooner than it can mount an ihave broken-promises spam attack. blockTopicOverrideParams.InvalidMessageDeliveriesWeight = 0.0 blockTopicOverrideParams.InvalidMessageDeliveriesDecay = 0.0 + + // relaxing the scoring parameters to fit the test scenario. + conf.NetworkConfig.GossipSub.ScoringParameters.PeerScoring.Internal.Behaviour.PenaltyDecay = 0.99 + conf.NetworkConfig.GossipSub.ScoringParameters.PeerScoring.Internal.Behaviour.PenaltyThreshold = 10 + conf.NetworkConfig.GossipSub.ScoringParameters.PeerScoring.Internal.Behaviour.PenaltyWeight = -1 + victimNode, victimIdentity := p2ptest.NodeFixture( t, sporkId, @@ -212,19 +242,23 @@ func TestGossipSubIHaveBrokenPromises_Above_Threshold(t *testing.T) { idProvider, p2ptest.OverrideFlowConfig(conf), p2ptest.WithRole(role), - p2ptest.WithPeerScoreTracerInterval(1*time.Second), p2ptest.EnablePeerScoringWithOverride(&p2p.PeerScoringConfigOverride{ TopicScoreParams: map[channels.Topic]*pubsub.TopicScoreParams{ blockTopic: blockTopicOverrideParams, }, - DecayInterval: 1 * time.Second, // we override the decay interval to 1 second so that the score is updated within 1 second intervals. }), ) - idProvider.On("ByPeerID", victimNode.ID()).Return(&victimIdentity, true).Maybe() - idProvider.On("ByPeerID", spammer.SpammerNode.ID()).Return(&spammer.SpammerId, true).Maybe() ids := flow.IdentityList{&spammer.SpammerId, &victimIdentity} + idProvider.SetIdentities(ids) nodes := []p2p.LibP2PNode{spammer.SpammerNode, victimNode} + // to suppress the logs of "peer provider has not set" + victimNode.WithPeersProvider(func() peer.IDSlice { + return peer.IDSlice{spammer.SpammerNode.ID()} + }) + spammer.SpammerNode.WithPeersProvider(func() peer.IDSlice { + return peer.IDSlice{victimNode.ID()} + }) p2ptest.StartNodes(t, signalerCtx, nodes) defer p2ptest.StopNodes(t, nodes, cancel) @@ -242,6 +276,7 @@ func TestGossipSubIHaveBrokenPromises_Above_Threshold(t *testing.T) { // FIRST ROUND OF ATTACK: spammer sends 10 RPCs to the victim node, each containing 500 iHave messages. spamIHaveBrokenPromise(t, spammer, blockTopic.String(), receivedIWants, victimNode) + t.Log("first round of attack finished") // wait till victim counts the spam iHaves as broken promises for the second round of attack (one per RPC for a total of 10). require.Eventually(t, func() bool { @@ -249,17 +284,18 @@ func TestGossipSubIHaveBrokenPromises_Above_Threshold(t *testing.T) { if !ok { return false } - // We set 7.5 as the threshold to compensate for the scoring decay in between RPC's being processed by the inspector - // ideally it must be 10 (one per RPC), but we give it a buffer of 1 to account for decays and floating point errors. - // note that we intentionally override the decay speed to be 60-times faster in this test. + + // ideally, we should have 10, but we give it a buffer of 2.5 to account for 25% discrepancy due to scoring decays, timer asynchrony, and floating point errors. if behavioralPenalty < 7.5 { + t.Logf("[first round] pending on behavioral penalty %f", behavioralPenalty) return false } + t.Logf("[first round] success on behavioral penalty %f", behavioralPenalty) return true - // Note: we have to wait at least 3 seconds for an iHave to be considered as broken promise (gossipsub parameters), we set it to 10 - // seconds to be on the safe side. - }, 10*time.Second, 100*time.Millisecond) + // Note: we have to wait at least 3 seconds for an iHave to be considered as broken promise (gossipsub parameters), we set it to 10 seconds to be on the safe side. + // Also, the internal heartbeat of GossipSub is 1 second, hence, there is no need to have ticks shorter than 500 milliseconds. + }, 10*time.Second, 500*time.Millisecond) scoreAfterFirstRound, ok := victimNode.PeerScoreExposer().GetScore(spammer.SpammerNode.ID()) require.True(t, ok, "score for spammer node must be present") @@ -269,7 +305,7 @@ func TestGossipSubIHaveBrokenPromises_Above_Threshold(t *testing.T) { // SECOND ROUND OF ATTACK: spammer sends 10 RPCs to the victim node, each containing 500 iHave messages. spamIHaveBrokenPromise(t, spammer, blockTopic.String(), receivedIWants, victimNode) - + t.Log("second round of attack finished") // wait till victim counts the spam iHaves as broken promises for the second round of attack (one per RPC for a total of 10). require.Eventually(t, func() bool { behavioralPenalty, ok := victimNode.PeerScoreExposer().GetBehaviourPenalty(spammer.SpammerNode.ID()) @@ -277,44 +313,47 @@ func TestGossipSubIHaveBrokenPromises_Above_Threshold(t *testing.T) { return false } - // ideally we should have 20 (10 from the first round, 10 from the second round), but we give it a buffer of 2 to account for decays and floating point errors. - // note that we intentionally override the decay speed to be 60-times faster in this test. - if behavioralPenalty < 18 { + // ideally, we should have 20 (10 from the first round, 10 from the second round), but we give it a buffer of 5 to account for 25% discrepancy due to scoring decays, timer asynchrony, and floating point errors. + if behavioralPenalty < 15 { + t.Logf("[second round] pending on behavioral penalty %f", behavioralPenalty) return false } + t.Logf("[second round] success on behavioral penalty %f", behavioralPenalty) return true - // Note: we have to wait at least 3 seconds for an iHave to be considered as broken promise (gossipsub parameters), we set it to 10 - // seconds to be on the safe side. - }, 10*time.Second, 100*time.Millisecond) + // Note: we have to wait at least 3 seconds for an iHave to be considered as broken promise (gossipsub parameters), we set it to 10 seconds to be on the safe side. + // Also, the internal heartbeat of GossipSub is 1 second, hence, there is no need to have ticks shorter than 500 milliseconds. + }, 10*time.Second, 500*time.Millisecond) + + scoreParams := conf.NetworkConfig.GossipSub.ScoringParameters spammerScore, ok := victimNode.PeerScoreExposer().GetScore(spammer.SpammerNode.ID()) require.True(t, ok, "sanity check failed, we should have a score for the spammer node") // with the second round of the attack, the spammer is about 10 broken promises above the threshold (total ~20 broken promises, but the first 10 are not counted). - // we expect the score to be dropped to initScore - 10 * 10 * 0.01 * scoring.MaxAppSpecificReward, however, instead of 10, we consider 8 about the threshold, to account for decays. + // we expect the score to be dropped to initScore - 10 * 10 * 0.01 * scoring.MaxAppSpecificReward, however, instead of 10, we consider 5 about the threshold, to account for decays. require.LessOrEqual(t, spammerScore, - initScore-8*8*0.01*scoring.MaxAppSpecificReward, + initScore-5*5*0.01*scoreParams.PeerScoring.Protocol.AppSpecificScore.MaxAppSpecificReward, "sanity check failed, the score of the spammer node must be less than the initial score minus 8 * 8 * 0.01 * scoring.MaxAppSpecificReward: %f, actual: %f", - initScore-10*10*10-2*scoring.MaxAppSpecificReward, + initScore-5*5*0.1*scoreParams.PeerScoring.Protocol.AppSpecificScore.MaxAppSpecificReward, spammerScore) require.Greaterf(t, spammerScore, - scoring.DefaultGossipThreshold, + scoreParams.PeerScoring.Internal.Thresholds.Gossip, "sanity check failed, the score of the spammer node must be greater than gossip threshold: %f, actual: %f", - scoring.DefaultGossipThreshold, + scoreParams.PeerScoring.Internal.Thresholds.Gossip, spammerScore) require.Greaterf(t, spammerScore, - scoring.DefaultPublishThreshold, + scoreParams.PeerScoring.Internal.Thresholds.Publish, "sanity check failed, the score of the spammer node must be greater than publish threshold: %f, actual: %f", - scoring.DefaultPublishThreshold, + scoreParams.PeerScoring.Internal.Thresholds.Publish, spammerScore) require.Greaterf(t, spammerScore, - scoring.DefaultGraylistThreshold, + scoreParams.PeerScoring.Internal.Thresholds.Graylist, "sanity check failed, the score of the spammer node must be greater than graylist threshold: %f, actual: %f", - scoring.DefaultGraylistThreshold, + scoreParams.PeerScoring.Internal.Thresholds.Graylist, spammerScore) // since the spammer score is above the gossip, graylist and publish thresholds, it should be still able to exchange messages with victim. @@ -324,23 +363,24 @@ func TestGossipSubIHaveBrokenPromises_Above_Threshold(t *testing.T) { // THIRD ROUND OF ATTACK: spammer sends 10 RPCs to the victim node, each containing 500 iHave messages, we expect spammer to be graylisted. spamIHaveBrokenPromise(t, spammer, blockTopic.String(), receivedIWants, victimNode) - + t.Log("third round of attack finished") // wait till victim counts the spam iHaves as broken promises for the third round of attack (one per RPC for a total of 10). require.Eventually(t, func() bool { behavioralPenalty, ok := victimNode.PeerScoreExposer().GetBehaviourPenalty(spammer.SpammerNode.ID()) if !ok { return false } - // ideally we should have 30 (10 from the first round, 10 from the second round, 10 from the third round), but we give it a buffer of 3 to account for decays and floating point errors. - // note that we intentionally override the decay speed to be 60-times faster in this test. - if behavioralPenalty < 27 { + // ideally, we should have 30 (10 from the first round, 10 from the second round, 10 from the third round), but we give it a buffer of 7.5 to account for 25% discrepancy due to scoring decays, timer asynchrony, and floating point errors. + if behavioralPenalty < 22.5 { + t.Logf("[third round] pending on behavioral penalty %f", behavioralPenalty) return false } + t.Logf("[third round] success on behavioral penalty %f", behavioralPenalty) return true - // Note: we have to wait at least 3 seconds for an iHave to be considered as broken promise (gossipsub parameters), we set it to 10 - // seconds to be on the safe side. - }, 10*time.Second, 100*time.Millisecond) + // Note: we have to wait at least 3 seconds for an iHave to be considered as broken promise (gossipsub parameters), we set it to 10 seconds to be on the safe side. + // Also, the internal heartbeat of GossipSub is 1 second, hence, there is no need to have ticks shorter than 500 milliseconds. + }, 10*time.Second, 500*time.Millisecond) spammerScore, ok = victimNode.PeerScoreExposer().GetScore(spammer.SpammerNode.ID()) require.True(t, ok, "sanity check failed, we should have a score for the spammer node") @@ -348,21 +388,21 @@ func TestGossipSubIHaveBrokenPromises_Above_Threshold(t *testing.T) { // victim will not exchange messages with it anymore, and also that it will be graylisted meaning all incoming and outgoing RPCs to and from the spammer will be dropped by the victim. require.Lessf(t, spammerScore, - scoring.DefaultGossipThreshold, + scoreParams.PeerScoring.Internal.Thresholds.Gossip, "sanity check failed, the score of the spammer node must be less than gossip threshold: %f, actual: %f", - scoring.DefaultGossipThreshold, + scoreParams.PeerScoring.Internal.Thresholds.Gossip, spammerScore) require.Lessf(t, spammerScore, - scoring.DefaultPublishThreshold, + scoreParams.PeerScoring.Internal.Thresholds.Publish, "sanity check failed, the score of the spammer node must be less than publish threshold: %f, actual: %f", - scoring.DefaultPublishThreshold, + scoreParams.PeerScoring.Internal.Thresholds.Publish, spammerScore) require.Lessf(t, spammerScore, - scoring.DefaultGraylistThreshold, + scoreParams.PeerScoring.Internal.Thresholds.Graylist, "sanity check failed, the score of the spammer node must be less than graylist threshold: %f, actual: %f", - scoring.DefaultGraylistThreshold, + scoreParams.PeerScoring.Internal.Thresholds.Graylist, spammerScore) // since the spammer score is below the gossip, graylist and publish thresholds, it should not be able to exchange messages with victim anymore. @@ -380,9 +420,14 @@ func TestGossipSubIHaveBrokenPromises_Above_Threshold(t *testing.T) { }) } -// spamIHaveBrokenPromises is a test utility function that is exclusive for the TestGossipSubIHaveBrokenPromises tests. -// It creates and sends 10 RPCs each with 10 iHave messages, each iHave message has 50 message ids, hence overall, we have 5000 iHave message ids. -// It then sends those iHave spams to the victim node and waits till the victim node receives them. +// spamIHaveBrokenPromises is a test utility function that is exclusive for the TestGossipSubIHaveBrokenPromises_.* tests. +// It creates and sends 10 RPCs each with 1 iHave message, each iHave message has 500 message ids, hence overall, we have 5000 iHave message ids. +// It then sends those iHave spams to the victim node and waits till the victim node responds with iWants for all the spam iHaves. +// There are some notes to consider: +// - we can't send more than one iHave message per RPC in this test, as each iHave should have a distinct topic, and we only have one subscribed topic in the TestGossipSubIHaveBrokenPromises_.* tests. +// - we can't send more than 10 RPCs containing iHave messages per heartbeat (1 sec). This is a gossipsub parameter (GossipSubMaxIHaveMessages). Hence, we choose 10 RPCs to always stay at the threshold. +// - we can't send more than 5000 iHave messages per heartbeat (1 sec). This is a gossipsub parameter (GossipSubMaxIHaveLength). Hence, we choose 500 message ids per iHave message to always stay at the threshold (10 * 500 = 5000). +// - Note that victim nodes picks one iHave id out of the entire RPC and if that iHave id is not eventually delivered within 3 seconds (gossipsub parameter, GossipSubIWantFollowupTime), then the promise is considered broken. Hence, broken promises are counted per RPC (not per iHave message). // Args: // - t: the test instance. // - spammer: the spammer node. @@ -394,10 +439,25 @@ func spamIHaveBrokenPromise(t *testing.T, topic string, receivedIWants *unittest.ProtectedMap[string, struct{}], victimNode p2p.LibP2PNode) { - spamMsgs := spammer.GenerateCtlMessages(1, p2ptest.WithIHave(1, 500, topic)) + rpcCount := 10 + // we can't send more than one iHave per RPC in this test, as each iHave should have a distinct topic, and we only have one subscribed topic. + // when the node does not have a topic subscription, it will discard the iHave message. + iHavesPerRPC := 1 + // there is a cap on the max iHaves a gossipsub node processes per heartbeat (1 sec), we don't want to exceed that (currently 5000 iHave messages per heartbeat). + messageIdsPerIHave := 500 + spamCtrlMsgs := spammer.GenerateCtlMessages(rpcCount, p2ptest.WithIHave(iHavesPerRPC, messageIdsPerIHave, topic)) + + // sanity check + require.Len(t, spamCtrlMsgs, rpcCount) var sentIHaves []string - for _, msg := range spamMsgs { + + // checks that iHave message ids are not duplicated + for _, msg := range spamCtrlMsgs { + // sanity check + require.Len(t, msg.Ihave, iHavesPerRPC) for _, iHave := range msg.Ihave { + // sanity check + require.Len(t, iHave.MessageIDs, messageIdsPerIHave) for _, msgId := range iHave.MessageIDs { require.NotContains(t, sentIHaves, msgId) sentIHaves = append(sentIHaves, msgId) @@ -407,17 +467,16 @@ func spamIHaveBrokenPromise(t *testing.T, // spams the victim node with spam iHave messages, since iHave messages are for junk message ids, there will be no // reply from spammer to victim over the iWants. Hence, the victim must count this towards 10 broken promises eventually. + // Note that victim nodes picks one iHave id out of the entire RPC and if that iHave id is not eventually delivered within 3 seconds (gossipsub parameter, GossipSubIWantFollowupTime), + // then the promise is considered broken. Hence, broken promises are counted per RPC (not per iHave message). // This sums up to 10 broken promises (1 per RPC). - var wg sync.WaitGroup - for i := 0; i < 50; i++ { - wg.Add(1) - go func() { - defer wg.Done() - spammer.SpamControlMessage(t, victimNode, spamMsgs, p2ptest.PubsubMessageFixture(t, p2ptest.WithTopic(topic))) - }() + for i := 0; i < len(spamCtrlMsgs); i++ { + spammer.SpamControlMessage(t, victimNode, []pb.ControlMessage{spamCtrlMsgs[i]}) + // we wait 50 milliseconds between each RPC to add an artificial delay between RPCs; this is to reduce the chance that all RPCs arrive in the same heartbeat, hence + // victim node dropping some. + time.Sleep(50 * time.Millisecond) } - unittest.AssertReturnsBefore(t, wg.Wait, 3*time.Second, "could not send RPCs on time") // wait till all the spam iHaves are responded with iWants. require.Eventually(t, func() bool { @@ -428,8 +487,7 @@ func spamIHaveBrokenPromise(t *testing.T, } return true - }, - 5*time.Second, + }, 10*time.Second, 100*time.Millisecond, fmt.Sprintf("sanity check failed, we should have received all the iWants for the spam iHaves, expected: %d, actual: %d", len(sentIHaves), receivedIWants.Size())) } diff --git a/insecure/integration/functional/test/gossipsub/scoring/scoring_test.go b/insecure/integration/functional/test/gossipsub/scoring/scoring_test.go index 152636ad9cf..daed9f30953 100644 --- a/insecure/integration/functional/test/gossipsub/scoring/scoring_test.go +++ b/insecure/integration/functional/test/gossipsub/scoring/scoring_test.go @@ -10,6 +10,7 @@ import ( "github.com/libp2p/go-libp2p/core/peer" "github.com/stretchr/testify/require" + "github.com/onflow/flow-go/config" "github.com/onflow/flow-go/insecure/corruptlibp2p" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/model/messages" @@ -17,7 +18,6 @@ import ( "github.com/onflow/flow-go/module/mock" "github.com/onflow/flow-go/network/channels" "github.com/onflow/flow-go/network/p2p" - "github.com/onflow/flow-go/network/p2p/scoring" p2ptest "github.com/onflow/flow-go/network/p2p/test" validator "github.com/onflow/flow-go/network/validator/pubsub" "github.com/onflow/flow-go/utils/unittest" @@ -27,8 +27,6 @@ import ( // a spammer peer, the victim will eventually penalize the spammer and stop receiving messages from them. // Note: the term integration is used here because it requires integrating all components of the libp2p stack. func TestGossipSubInvalidMessageDelivery_Integration(t *testing.T) { - t.Parallel() - tt := []struct { name string spamMsgFactory func(spammerId peer.ID, victimId peer.ID, topic channels.Topic) *pubsub_pb.Message @@ -101,24 +99,25 @@ func testGossipSubInvalidMessageDeliveryScoring(t *testing.T, spamMsgFactory fun sporkId := unittest.IdentifierFixture() blockTopic := channels.TopicFromChannel(channels.PushBlocks, sporkId) - idProvider := mock.NewIdentityProvider(t) + idProvider := unittest.NewUpdatableIDProvider(flow.IdentityList{}) spammer := corruptlibp2p.NewGossipSubRouterSpammer(t, sporkId, role, idProvider) ctx, cancel := context.WithCancel(context.Background()) signalerCtx := irrecoverable.NewMockSignalerContext(t, ctx) + cfg, err := config.DefaultConfig() + require.NoError(t, err) + // we override the decay interval to 1 second so that the score is updated within 1 second intervals. + cfg.NetworkConfig.GossipSub.RpcTracer.ScoreTracerInterval = 1 * time.Second victimNode, victimIdentity := p2ptest.NodeFixture( t, sporkId, t.Name(), idProvider, p2ptest.WithRole(role), - p2ptest.WithPeerScoreTracerInterval(1*time.Second), - p2ptest.EnablePeerScoringWithOverride(p2p.PeerScoringConfigNoOverride), - ) + p2ptest.OverrideFlowConfig(cfg)) - idProvider.On("ByPeerID", victimNode.ID()).Return(&victimIdentity, true).Maybe() - idProvider.On("ByPeerID", spammer.SpammerNode.ID()).Return(&spammer.SpammerId, true).Maybe() ids := flow.IdentityList{&spammer.SpammerId, &victimIdentity} + idProvider.SetIdentities(ids) nodes := []p2p.LibP2PNode{spammer.SpammerNode, victimNode} p2ptest.StartNodes(t, signalerCtx, nodes) @@ -132,13 +131,20 @@ func testGossipSubInvalidMessageDeliveryScoring(t *testing.T, spamMsgFactory fun return unittest.ProposalFixture() }) - totalSpamMessages := 20 + // generates 2000 spam messages to send to the victim node; based on default-config.yaml, ~1400 of these messages are enough to + // penalize the spammer node to disconnect from the victim node. + totalSpamMessages := 2000 + msgs := make([]*pubsub_pb.Message, 0) for i := 0; i <= totalSpamMessages; i++ { - spammer.SpamControlMessage(t, victimNode, - spammer.GenerateCtlMessages(1), - spamMsgFactory(spammer.SpammerNode.ID(), victimNode.ID(), blockTopic)) + msgs = append(msgs, spamMsgFactory(spammer.SpammerNode.ID(), victimNode.ID(), blockTopic)) } + // sends all 2000 spam messages to the victim node over 1 RPC. + spammer.SpamControlMessage(t, victimNode, + spammer.GenerateCtlMessages(1), msgs...) + + scoreParams := cfg.NetworkConfig.GossipSub.ScoringParameters + // wait for at most 3 seconds for the victim node to penalize the spammer node. // Each heartbeat is 1 second, so 3 heartbeats should be enough to penalize the spammer node. // Ideally, we should wait for 1 heartbeat, but the score may not be updated immediately after the heartbeat. @@ -147,15 +153,15 @@ func testGossipSubInvalidMessageDeliveryScoring(t *testing.T, spamMsgFactory fun if !ok { return false } - if spammerScore >= scoring.DefaultGossipThreshold { + if spammerScore >= scoreParams.PeerScoring.Internal.Thresholds.Gossip { // ensure the score is low enough so that no gossip is routed by victim node to spammer node. return false } - if spammerScore >= scoring.DefaultPublishThreshold { + if spammerScore >= scoreParams.PeerScoring.Internal.Thresholds.Publish { // ensure the score is low enough so that non of the published messages of the victim node are routed to the spammer node. return false } - if spammerScore >= scoring.DefaultGraylistThreshold { + if spammerScore >= scoreParams.PeerScoring.Internal.Thresholds.Graylist { // ensure the score is low enough so that the victim node does not accept RPC messages from the spammer node. return false } @@ -170,8 +176,11 @@ func testGossipSubInvalidMessageDeliveryScoring(t *testing.T, spamMsgFactory fun blkTopicSnapshot, ok := topicsSnapshot[blockTopic.String()] require.True(t, ok) - // ensure that the topic snapshot of the spammer contains a record of at least (60%) of the spam messages sent. The 60% is to account for the messages that were delivered before the score was updated, after the spammer is PRUNED, as well as to account for decay. - require.True(t, blkTopicSnapshot.InvalidMessageDeliveries > 0.6*float64(totalSpamMessages), "invalid message deliveries must be greater than %f. invalid message deliveries: %f", 0.9*float64(totalSpamMessages), blkTopicSnapshot.InvalidMessageDeliveries) + // ensure that the topic snapshot of the spammer contains a record of at least (40%) of the spam messages sent. The 40% is to account for the messages that were + // delivered before the score was updated, after the spammer is PRUNED, as well as to account for decay. + require.True(t, blkTopicSnapshot.InvalidMessageDeliveries > 0.4*float64(totalSpamMessages), + "invalid message deliveries must be greater than %f. invalid message deliveries: %f", 0.4*float64(totalSpamMessages), + blkTopicSnapshot.InvalidMessageDeliveries) p2ptest.EnsureNoPubsubExchangeBetweenGroups( t, @@ -201,21 +210,26 @@ func TestGossipSubMeshDeliveryScoring_UnderDelivery_SingleTopic(t *testing.T) { blockTopic := channels.TopicFromChannel(channels.PushBlocks, sporkId) // we override some of the default scoring parameters in order to speed up the test in a time-efficient manner. - blockTopicOverrideParams := scoring.DefaultTopicScoreParams() + blockTopicOverrideParams := defaultTopicScoreParams(t) blockTopicOverrideParams.MeshMessageDeliveriesActivation = 1 * time.Second // we start observing the mesh message deliveries after 1 second of the node startup. - thisNode, thisId := p2ptest.NodeFixture( // this node is the one that will be penalizing the under-performer node. + + conf, err := config.DefaultConfig() + require.NoError(t, err) + // we override the decay interval to 1 second so that the score is updated within 1 second intervals. + conf.NetworkConfig.GossipSub.ScoringParameters.PeerScoring.Internal.DecayInterval = 1 * time.Second + conf.NetworkConfig.GossipSub.RpcTracer.ScoreTracerInterval = 1 * time.Second + thisNode, thisId := p2ptest.NodeFixture( // this node is the one that will be penalizing the under-performer node. t, sporkId, t.Name(), idProvider, p2ptest.WithRole(role), - p2ptest.WithPeerScoreTracerInterval(1*time.Second), + p2ptest.OverrideFlowConfig(conf), p2ptest.EnablePeerScoringWithOverride( &p2p.PeerScoringConfigOverride{ TopicScoreParams: map[channels.Topic]*pubsub.TopicScoreParams{ blockTopic: blockTopicOverrideParams, }, - DecayInterval: 1 * time.Second, // we override the decay interval to 1 second so that the score is updated within 1 second intervals. }), ) @@ -243,6 +257,8 @@ func TestGossipSubMeshDeliveryScoring_UnderDelivery_SingleTopic(t *testing.T) { return unittest.ProposalFixture() }) + scoreParams := conf.NetworkConfig.GossipSub.ScoringParameters.PeerScoring.Protocol + // Also initially the under-performing node should have a score that is at least equal to the MaxAppSpecificReward. // The reason is in our scoring system, we reward the staked nodes by MaxAppSpecificReward, and the under-performing node is considered staked // as it is in the id provider of thisNode. @@ -251,7 +267,7 @@ func TestGossipSubMeshDeliveryScoring_UnderDelivery_SingleTopic(t *testing.T) { if !ok { return false } - if underPerformingNodeScore < scoring.MaxAppSpecificReward { + if underPerformingNodeScore < scoreParams.AppSpecificScore.MaxAppSpecificReward { // ensure the score is high enough so that gossip is routed by victim node to spammer node. return false } @@ -266,17 +282,17 @@ func TestGossipSubMeshDeliveryScoring_UnderDelivery_SingleTopic(t *testing.T) { if !ok { return false } - if underPerformingNodeScore > 0.96*scoring.MaxAppSpecificReward { // score must be penalized by -0.05 * MaxAppSpecificReward. + if underPerformingNodeScore > 0.96*scoreParams.AppSpecificScore.MaxAppSpecificReward { // score must be penalized by -0.05 * MaxAppSpecificReward. // 0.96 is to account for floating point errors. return false } - if underPerformingNodeScore < scoring.DefaultGossipThreshold { // even the node is slightly penalized, it should still be able to gossip with this node. + if underPerformingNodeScore < conf.NetworkConfig.GossipSub.ScoringParameters.PeerScoring.Internal.Thresholds.Gossip { // even the node is slightly penalized, it should still be able to gossip with this node. return false } - if underPerformingNodeScore < scoring.DefaultPublishThreshold { // even the node is slightly penalized, it should still be able to publish to this node. + if underPerformingNodeScore < conf.NetworkConfig.GossipSub.ScoringParameters.PeerScoring.Internal.Thresholds.Publish { // even the node is slightly penalized, it should still be able to publish to this node. return false } - if underPerformingNodeScore < scoring.DefaultGraylistThreshold { // even the node is slightly penalized, it should still be able to establish rpc connection with this node. + if underPerformingNodeScore < conf.NetworkConfig.GossipSub.ScoringParameters.PeerScoring.Internal.Thresholds.Graylist { // even the node is slightly penalized, it should still be able to establish rpc connection with this node. return false } @@ -304,24 +320,29 @@ func TestGossipSubMeshDeliveryScoring_UnderDelivery_TwoTopics(t *testing.T) { dkgTopic := channels.TopicFromChannel(channels.DKGCommittee, sporkId) // we override some of the default scoring parameters in order to speed up the test in a time-efficient manner. - blockTopicOverrideParams := scoring.DefaultTopicScoreParams() + blockTopicOverrideParams := defaultTopicScoreParams(t) blockTopicOverrideParams.MeshMessageDeliveriesActivation = 1 * time.Second // we start observing the mesh message deliveries after 1 second of the node startup. - dkgTopicOverrideParams := scoring.DefaultTopicScoreParams() + dkgTopicOverrideParams := defaultTopicScoreParams(t) dkgTopicOverrideParams.MeshMessageDeliveriesActivation = 1 * time.Second // we start observing the mesh message deliveries after 1 second of the node startup. - thisNode, thisId := p2ptest.NodeFixture( // this node is the one that will be penalizing the under-performer node. + + conf, err := config.DefaultConfig() + require.NoError(t, err) + // we override the decay interval to 1 second so that the score is updated within 1 second intervals. + conf.NetworkConfig.GossipSub.ScoringParameters.PeerScoring.Internal.DecayInterval = 1 * time.Second + conf.NetworkConfig.GossipSub.RpcTracer.ScoreTracerInterval = 1 * time.Second + thisNode, thisId := p2ptest.NodeFixture( // this node is the one that will be penalizing the under-performer node. t, sporkId, t.Name(), idProvider, p2ptest.WithRole(role), - p2ptest.WithPeerScoreTracerInterval(1*time.Second), + p2ptest.OverrideFlowConfig(conf), p2ptest.EnablePeerScoringWithOverride( &p2p.PeerScoringConfigOverride{ TopicScoreParams: map[channels.Topic]*pubsub.TopicScoreParams{ blockTopic: blockTopicOverrideParams, dkgTopic: dkgTopicOverrideParams, }, - DecayInterval: 1 * time.Second, // we override the decay interval to 1 second so that the score is updated within 1 second intervals. }), ) @@ -352,6 +373,8 @@ func TestGossipSubMeshDeliveryScoring_UnderDelivery_TwoTopics(t *testing.T) { } } + scoreParams := conf.NetworkConfig.GossipSub.ScoringParameters.PeerScoring.Protocol.AppSpecificScore + // Initially the under-performing node should have a score that is at least equal to the MaxAppSpecificReward. // The reason is in our scoring system, we reward the staked nodes by MaxAppSpecificReward, and the under-performing node is considered staked // as it is in the id provider of thisNode. @@ -360,7 +383,7 @@ func TestGossipSubMeshDeliveryScoring_UnderDelivery_TwoTopics(t *testing.T) { if !ok { return false } - if underPerformingNodeScore < scoring.MaxAppSpecificReward { + if underPerformingNodeScore < scoreParams.MaxAppSpecificReward { // ensure the score is high enough so that gossip is routed by victim node to spammer node. return false } @@ -376,17 +399,17 @@ func TestGossipSubMeshDeliveryScoring_UnderDelivery_TwoTopics(t *testing.T) { if !ok { return false } - if underPerformingNodeScore > 0.91*scoring.MaxAppSpecificReward { // score must be penalized by ~ 2 * -0.05 * MaxAppSpecificReward. + if underPerformingNodeScore > 0.91*scoreParams.MaxAppSpecificReward { // score must be penalized by ~ 2 * -0.05 * MaxAppSpecificReward. // 0.91 is to account for the floating point errors. return false } - if underPerformingNodeScore < scoring.DefaultGossipThreshold { // even the node is slightly penalized, it should still be able to gossip with this node. + if underPerformingNodeScore < conf.NetworkConfig.GossipSub.ScoringParameters.PeerScoring.Internal.Thresholds.Gossip { // even the node is slightly penalized, it should still be able to gossip with this node. return false } - if underPerformingNodeScore < scoring.DefaultPublishThreshold { // even the node is slightly penalized, it should still be able to publish to this node. + if underPerformingNodeScore < conf.NetworkConfig.GossipSub.ScoringParameters.PeerScoring.Internal.Thresholds.Publish { // even the node is slightly penalized, it should still be able to publish to this node. return false } - if underPerformingNodeScore < scoring.DefaultGraylistThreshold { // even the node is slightly penalized, it should still be able to establish rpc connection with this node. + if underPerformingNodeScore < conf.NetworkConfig.GossipSub.ScoringParameters.PeerScoring.Internal.Thresholds.Graylist { // even the node is slightly penalized, it should still be able to establish rpc connection with this node. return false } @@ -416,7 +439,12 @@ func TestGossipSubMeshDeliveryScoring_Replay_Will_Not_Counted(t *testing.T) { blockTopic := channels.TopicFromChannel(channels.PushBlocks, sporkId) // we override some of the default scoring parameters in order to speed up the test in a time-efficient manner. - blockTopicOverrideParams := scoring.DefaultTopicScoreParams() + conf, err := config.DefaultConfig() + require.NoError(t, err) + // we override the decay interval to 1 second so that the score is updated within 1 second intervals. + conf.NetworkConfig.GossipSub.ScoringParameters.PeerScoring.Internal.DecayInterval = 1 * time.Second + conf.NetworkConfig.GossipSub.RpcTracer.ScoreTracerInterval = 1 * time.Second + blockTopicOverrideParams := defaultTopicScoreParams(t) blockTopicOverrideParams.MeshMessageDeliveriesActivation = 1 * time.Second // we start observing the mesh message deliveries after 1 second of the node startup. thisNode, thisId := p2ptest.NodeFixture( // this node is the one that will be penalizing the under-performer node. t, @@ -424,13 +452,12 @@ func TestGossipSubMeshDeliveryScoring_Replay_Will_Not_Counted(t *testing.T) { t.Name(), idProvider, p2ptest.WithRole(role), - p2ptest.WithPeerScoreTracerInterval(1*time.Second), + p2ptest.OverrideFlowConfig(conf), p2ptest.EnablePeerScoringWithOverride( &p2p.PeerScoringConfigOverride{ TopicScoreParams: map[channels.Topic]*pubsub.TopicScoreParams{ blockTopic: blockTopicOverrideParams, }, - DecayInterval: 1 * time.Second, // we override the decay interval to 1 second so that the score is updated within 1 second intervals. }), ) @@ -458,6 +485,8 @@ func TestGossipSubMeshDeliveryScoring_Replay_Will_Not_Counted(t *testing.T) { return unittest.ProposalFixture() }) + scoreParams := conf.NetworkConfig.GossipSub.ScoringParameters.PeerScoring.Protocol.AppSpecificScore + // Initially the replaying node should have a score that is at least equal to the MaxAppSpecificReward. // The reason is in our scoring system, we reward the staked nodes by MaxAppSpecificReward, and initially every node is considered staked // as it is in the id provider of thisNode. @@ -467,7 +496,7 @@ func TestGossipSubMeshDeliveryScoring_Replay_Will_Not_Counted(t *testing.T) { if !ok { return false } - if replayingNodeScore < scoring.MaxAppSpecificReward { + if replayingNodeScore < scoreParams.MaxAppSpecificReward { // ensure the score is high enough so that gossip is routed by victim node to spammer node. return false } @@ -494,7 +523,7 @@ func TestGossipSubMeshDeliveryScoring_Replay_Will_Not_Counted(t *testing.T) { if !ok { return false } - if replayingNodeScore < scoring.MaxAppSpecificReward { + if replayingNodeScore < scoreParams.MaxAppSpecificReward { // ensure the score is high enough so that gossip is routed by victim node to spammer node. return false } @@ -534,22 +563,22 @@ func TestGossipSubMeshDeliveryScoring_Replay_Will_Not_Counted(t *testing.T) { return false } - if replayingNodeScore >= scoring.MaxAppSpecificReward { + if replayingNodeScore >= scoreParams.MaxAppSpecificReward { // node must be penalized for just replaying the same messages. return false } // following if-statements check that even though the node is penalized, it is not penalized too much, and // can still participate in the network. We don't desire to disallow list a node for just under-performing. - if replayingNodeScore < scoring.DefaultGossipThreshold { + if replayingNodeScore < conf.NetworkConfig.GossipSub.ScoringParameters.PeerScoring.Internal.Thresholds.Gossip { return false } - if replayingNodeScore < scoring.DefaultPublishThreshold { + if replayingNodeScore < conf.NetworkConfig.GossipSub.ScoringParameters.PeerScoring.Internal.Thresholds.Publish { return false } - if replayingNodeScore < scoring.DefaultGraylistThreshold { + if replayingNodeScore < conf.NetworkConfig.GossipSub.ScoringParameters.PeerScoring.Internal.Thresholds.Graylist { return false } @@ -562,3 +591,24 @@ func TestGossipSubMeshDeliveryScoring_Replay_Will_Not_Counted(t *testing.T) { return unittest.ProposalFixture() }) } + +// defaultTopicScoreParams returns the default score params for topics. +func defaultTopicScoreParams(t *testing.T) *pubsub.TopicScoreParams { + defaultConfig, err := config.DefaultConfig() + require.NoError(t, err) + topicScoreParams := defaultConfig.NetworkConfig.GossipSub.ScoringParameters.PeerScoring.Internal.TopicParameters + p := &pubsub.TopicScoreParams{ + TopicWeight: topicScoreParams.TopicWeight, + SkipAtomicValidation: topicScoreParams.SkipAtomicValidation, + InvalidMessageDeliveriesWeight: topicScoreParams.InvalidMessageDeliveriesWeight, + InvalidMessageDeliveriesDecay: topicScoreParams.InvalidMessageDeliveriesDecay, + TimeInMeshQuantum: topicScoreParams.TimeInMeshQuantum, + MeshMessageDeliveriesWeight: topicScoreParams.MeshDeliveriesWeight, + MeshMessageDeliveriesDecay: topicScoreParams.MeshMessageDeliveriesDecay, + MeshMessageDeliveriesCap: topicScoreParams.MeshMessageDeliveriesCap, + MeshMessageDeliveriesThreshold: topicScoreParams.MeshMessageDeliveryThreshold, + MeshMessageDeliveriesWindow: topicScoreParams.MeshMessageDeliveriesWindow, + MeshMessageDeliveriesActivation: topicScoreParams.MeshMessageDeliveryActivation, + } + return p +} diff --git a/insecure/network.pb.go b/insecure/network.pb.go index 8461d3aff54..461c20b6643 100644 --- a/insecure/network.pb.go +++ b/insecure/network.pb.go @@ -6,12 +6,13 @@ package insecure import ( context "context" fmt "fmt" + math "math" + proto "github.com/golang/protobuf/proto" grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" status "google.golang.org/grpc/status" emptypb "google.golang.org/protobuf/types/known/emptypb" - math "math" ) // Reference imports to suppress errors if they are not otherwise used. diff --git a/integration/Makefile b/integration/Makefile index 361013a41d1..2811de6ddb3 100644 --- a/integration/Makefile +++ b/integration/Makefile @@ -14,16 +14,16 @@ CGO_FLAG := CGO_CFLAGS=$(CRYPTO_FLAG) # Run the integration test suite .PHONY: integration-test -integration-test: access-tests ghost-tests mvp-tests execution-tests verification-tests upgrades-tests collection-tests epochs-cohort1-tests epochs-cohort2-tests network-tests consensus-tests - -.PHONY: ci-integration-test -ci-integration-test: access-tests ghost-tests mvp-tests epochs-cohort1-tests epochs-cohort2-tests consensus-tests execution-tests verification-tests upgrades-tests network-tests collection-tests +integration-test: access-tests ghost-tests mvp-tests execution-tests verification-tests upgrades-tests collection-tests epochs-tests network-tests consensus-tests # Run unit tests for test utilities in this module .PHONY: test test: $(CGO_FLAG) go test $(if $(VERBOSE),-v,) -coverprofile=$(COVER_PROFILE) $(RACE_FLAG) $(if $(JSON_OUTPUT),-json,) $(if $(NUM_RUNS),-count $(NUM_RUNS),) `go list ./... | grep -v -e integration/tests` +.PHONY: access-tests +access-tests: access-cohort1-tests access-cohort2-tests access-cohort3-tests + .PHONY: access-cohort1-tests access-cohort1-tests: $(CGO_FLAG) go test $(if $(VERBOSE),-v,) $(RACE_FLAG) $(if $(JSON_OUTPUT),-json,) $(if $(NUM_RUNS),-count $(NUM_RUNS),) ./tests/access/cohort1/... @@ -36,7 +36,6 @@ access-cohort2-tests: access-cohort3-tests: $(CGO_FLAG) go test $(if $(VERBOSE),-v,) $(RACE_FLAG) $(if $(JSON_OUTPUT),-json,) $(if $(NUM_RUNS),-count $(NUM_RUNS),) ./tests/access/cohort3/... - .PHONY: collection-tests collection-tests: $(CGO_FLAG) go test $(if $(VERBOSE),-v,) $(RACE_FLAG) $(if $(JSON_OUTPUT),-json,) $(if $(NUM_RUNS),-count $(NUM_RUNS),) ./tests/collection/... @@ -45,6 +44,9 @@ collection-tests: consensus-tests: $(CGO_FLAG) go test $(if $(VERBOSE),-v,) $(RACE_FLAG) $(if $(JSON_OUTPUT),-json,) $(if $(NUM_RUNS),-count $(NUM_RUNS),) ./tests/consensus/... +.PHONY: epochs-tests +epochs-tests: epochs-cohort1-tests epochs-cohort2-tests + .PHONY: epochs-cohort1-tests epochs-cohort1-tests: # Use a higher timeout of 20m for the suite of tests which span full epochs diff --git a/integration/README.md b/integration/README.md index b6b59f4fa82..8b479f06477 100644 --- a/integration/README.md +++ b/integration/README.md @@ -14,15 +14,15 @@ Since the test cases run docker instances as a network of nodes, we need to ensu To ensure the latest docker images have been built, you can run: ``` -make docker-build-access -make docker-build-collection -make docker-build-consensus -make docker-build-execution -make docker-build-verification -make docker-build-ghost +make docker-native-build-access +make docker-native-build-collection +make docker-native-build-consensus +make docker-native-build-execution +make docker-native-build-verification +make docker-native-build-ghost ``` -Or simply run `make docker-build-flow` +Or simply run `make docker-native-build-flow` After images have been built, we can run the integration tests: ``` @@ -65,11 +65,11 @@ Because launching a full execution node in the consensus integration tests will ### Rebuild image when debugging During test cases debugging, you might want to update some code. However, if you run `make integration-test` after updating the code, the new change will not be included, because the integration tests still use the old code from the docker image, which was built before adding the changes. -So you need to rebuild all the images by running `make docker-build-flow` again before re-running the integration tests. +So you need to rebuild all the images by running `make docker-native-build-flow` again before re-running the integration tests. Rebuilding all images takes quite some time, here is a shortcut: -If consensus's code was changed, then only consensus's image need to be rebuilt, so simply run `make docker-build-consensus` instead of rebuilding all the images. +If consensus's code was changed, then only consensus's image need to be rebuilt, so simply run `make docker-native-build-consensus` instead of rebuilding all the images. ### Organization @@ -81,4 +81,4 @@ in the Makefile. To send random transactions, for example to load test a network, run `cd integration/localnet; make load`. -In order to build a docker container with the benchmarking binary, run `make docker-build-loader` from the root of this repository. +In order to build a docker container with the benchmarking binary, run `make docker-native-build-loader` from the root of this repository. diff --git a/integration/benchmark/contLoadGenerator.go b/integration/benchmark/contLoadGenerator.go index 4d8a93fe3ca..c719699e910 100644 --- a/integration/benchmark/contLoadGenerator.go +++ b/integration/benchmark/contLoadGenerator.go @@ -355,7 +355,7 @@ func (lg *ContLoadGenerator) setupFavContract() error { deploymentTx := flowsdk.NewTransaction(). SetReferenceBlockID(lg.follower.BlockID()). SetScript(deployScript). - SetGasLimit(9999) + SetComputeLimit(9999) lg.log.Trace().Msg("signing transaction") @@ -482,7 +482,7 @@ func (lg *ContLoadGenerator) createAccounts(num int) error { createAccountTx := flowsdk.NewTransaction(). SetScript(CreateAccountsScript(*lg.networkParams.FungibleTokenAddress, *lg.networkParams.FlowTokenAddress)). SetReferenceBlockID(lg.follower.BlockID()). - SetGasLimit(999999) + SetComputeLimit(999999) publicKey := bytesToCadenceArray(accountKey.PublicKey.Encode()) count := cadence.NewInt(num) @@ -601,7 +601,7 @@ func (lg *ContLoadGenerator) createAddKeyTx(accountAddress flowsdk.Address, numb SetScript(addKeysScript). AddAuthorizer(accountAddress). SetReferenceBlockID(lg.follower.BlockID()). - SetGasLimit(9999) + SetComputeLimit(9999) err = addKeysTx.AddArgument(cadenceKeysArray) if err != nil { @@ -715,7 +715,7 @@ func (lg *ContLoadGenerator) sendConstExecCostTx(workerID int) { tx := flowsdk.NewTransaction(). SetReferenceBlockID(lg.follower.BlockID()). SetScript(txScriptNoComment). - SetGasLimit(10). // const-exec tx has empty transaction + SetComputeLimit(10). // const-exec tx has empty transaction SetProposalKey(*proposerKey.Address, proposerKey.Index, proposerKey.SequenceNumber). SetPayer(*proposerKey.Address) @@ -845,7 +845,7 @@ func (lg *ContLoadGenerator) sendTokenTransferTx(workerID int) { log.Trace().Msg("creating token transfer transaction") transferTx = transferTx. SetReferenceBlockID(lg.follower.BlockID()). - SetGasLimit(9999) + SetComputeLimit(9999) log.Trace().Msg("signing transaction") @@ -925,7 +925,7 @@ func (lg *ContLoadGenerator) sendFavContractTx(workerID int) { tx := flowsdk.NewTransaction(). SetReferenceBlockID(lg.follower.BlockID()). SetScript(txScript). - SetGasLimit(9999) + SetComputeLimit(9999) log.Trace().Msg("signing transaction") diff --git a/integration/benchmark/mock/client.go b/integration/benchmark/mock/client.go index c8b6e6797d8..57cf6d439a5 100644 --- a/integration/benchmark/mock/client.go +++ b/integration/benchmark/mock/client.go @@ -473,6 +473,32 @@ func (_m *Client) GetLatestProtocolStateSnapshot(ctx context.Context) ([]byte, e return r0, r1 } +// GetNetworkParameters provides a mock function with given fields: ctx +func (_m *Client) GetNetworkParameters(ctx context.Context) (*flow.NetworkParameters, error) { + ret := _m.Called(ctx) + + var r0 *flow.NetworkParameters + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (*flow.NetworkParameters, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) *flow.NetworkParameters); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*flow.NetworkParameters) + } + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // GetTransaction provides a mock function with given fields: ctx, txID func (_m *Client) GetTransaction(ctx context.Context, txID flow.Identifier) (*flow.Transaction, error) { ret := _m.Called(ctx, txID) diff --git a/integration/benchmark/proto/macro_benchmark.pb.go b/integration/benchmark/proto/macro_benchmark.pb.go index ada83bc8cc4..15fdb7b4cf9 100644 --- a/integration/benchmark/proto/macro_benchmark.pb.go +++ b/integration/benchmark/proto/macro_benchmark.pb.go @@ -7,11 +7,12 @@ package proto import ( + reflect "reflect" + sync "sync" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" emptypb "google.golang.org/protobuf/types/known/emptypb" - reflect "reflect" - sync "sync" ) const ( diff --git a/integration/benchmark/proto/macro_benchmark_grpc.pb.go b/integration/benchmark/proto/macro_benchmark_grpc.pb.go index 3dd37ac58ca..065a26fcb39 100644 --- a/integration/benchmark/proto/macro_benchmark_grpc.pb.go +++ b/integration/benchmark/proto/macro_benchmark_grpc.pb.go @@ -8,6 +8,7 @@ package proto import ( context "context" + grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" status "google.golang.org/grpc/status" diff --git a/integration/benchnet2/Makefile b/integration/benchnet2/Makefile index b7911fdc0f9..53473ffa590 100644 --- a/integration/benchnet2/Makefile +++ b/integration/benchnet2/Makefile @@ -4,8 +4,8 @@ CONFIGURATION_BUCKET := flow-benchnet-automation # default values that callers can override when calling target ACCESS = 1 -COLLECTION = 6 -VALID_COLLECTION := $(shell test $(COLLECTION) -ge 6; echo $$?) +COLLECTION = 2 +VALID_COLLECTION := $(shell test $(COLLECTION) -ge 2; echo $$?) CONSENSUS = 2 VALID_CONSENSUS := $(shell test $(CONSENSUS) -ge 2; echo $$?) EXECUTION = 2 @@ -19,7 +19,7 @@ ifeq ($(strip $(VALID_EXECUTION)), 1) else ifeq ($(strip $(VALID_CONSENSUS)), 1) $(error Number of Consensus nodes should be no less than 2) else ifeq ($(strip $(VALID_COLLECTION)), 1) - $(error Number of Collection nodes should be no less than 6) + $(error Number of Collection nodes should be no less than 2) else ifeq ($(strip $(NETWORK_ID)),) $(error NETWORK_ID cannot be empty) else ifeq ($(strip $(NAMESPACE)),) @@ -64,11 +64,15 @@ clean-gen-helm: rm -f values.yml rm -f template-data.json +download-values-file: + gsutil cp gs://${CONFIGURATION_BUCKET}/${NETWORK_ID}/values.yml . + upload-bootstrap: tar -cvf ${NETWORK_ID}.tar -C ./bootstrap . gsutil cp ${NETWORK_ID}.tar gs://${CONFIGURATION_BUCKET}/${NETWORK_ID}.tar + gsutil cp values.yml gs://${CONFIGURATION_BUCKET}/${NETWORK_ID}/values.yml -helm-deploy: +helm-deploy: download-values-file helm upgrade --install -f ./values.yml ${NETWORK_ID} ./flow --set ingress.enabled=true --set networkId="${NETWORK_ID}" --set owner="${OWNER}" --set configurationBucket="${CONFIGURATION_BUCKET}" --debug --namespace ${NAMESPACE} --wait k8s-delete: @@ -77,6 +81,7 @@ k8s-delete: delete-configuration: gsutil rm gs://${CONFIGURATION_BUCKET}/${NETWORK_ID}.tar + gsutil rm gs://${CONFIGURATION_BUCKET}/${NETWORK_ID}/values.yml k8s-pod-health: validate kubectl get pods --namespace ${NAMESPACE} diff --git a/integration/benchnet2/automate/templates/helm-values-all-nodes.yml b/integration/benchnet2/automate/templates/helm-values-all-nodes.yml index 211ec7ca1fd..141f0edda24 100644 --- a/integration/benchnet2/automate/templates/helm-values-all-nodes.yml +++ b/integration/benchnet2/automate/templates/helm-values-all-nodes.yml @@ -10,7 +10,7 @@ access: limits: cpu: "800m" memory: "10Gi" - storage: 1G + storage: 2G nodes: {{- range $val := .}}{{if eq ($val.role) ("access")}} {{$val.name}}: @@ -38,7 +38,7 @@ collection: limits: cpu: "800m" memory: "10Gi" - storage: 1G + storage: 2G nodes: {{- range $val := .}}{{if eq ($val.role) ("collection")}} {{$val.name}}: @@ -85,7 +85,7 @@ execution: limits: cpu: "800m" memory: "10Gi" - storage: 10G + storage: 50G nodes: {{- range $val := .}}{{if eq ($val.role) ("execution")}} {{$val.name}}: diff --git a/integration/dkg/dkg_client_test.go b/integration/dkg/dkg_client_test.go index 0ce53967015..53643610675 100644 --- a/integration/dkg/dkg_client_test.go +++ b/integration/dkg/dkg_client_test.go @@ -10,9 +10,11 @@ import ( "github.com/stretchr/testify/suite" "github.com/onflow/cadence" + jsoncdc "github.com/onflow/cadence/encoding/json" emulator "github.com/onflow/flow-emulator/emulator" + "github.com/onflow/crypto" "github.com/onflow/flow-core-contracts/lib/go/contracts" "github.com/onflow/flow-core-contracts/lib/go/templates" @@ -21,7 +23,6 @@ import ( sdktemplates "github.com/onflow/flow-go-sdk/templates" "github.com/onflow/flow-go-sdk/test" - "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/integration/utils" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/module/dkg" @@ -230,7 +231,7 @@ func (s *ClientSuite) setUpAdmin() { // set up admin resource setUpAdminTx := sdk.NewTransaction(). SetScript(templates.GeneratePublishDKGParticipantScript(s.env)). - SetGasLimit(9999). + SetComputeLimit(9999). SetProposalKey(s.blockchain.ServiceKey().Address, s.blockchain.ServiceKey().Index, s.blockchain.ServiceKey().SequenceNumber). SetPayer(s.blockchain.ServiceKey().Address). @@ -258,7 +259,7 @@ func (s *ClientSuite) startDKGWithParticipants(nodeIDs []flow.Identifier) { // start DKG using admin resource startDKGTx := sdk.NewTransaction(). SetScript(templates.GenerateStartDKGScript(s.env)). - SetGasLimit(9999). + SetComputeLimit(9999). SetProposalKey(s.blockchain.ServiceKey().Address, s.blockchain.ServiceKey().Index, s.blockchain.ServiceKey().SequenceNumber). SetPayer(s.blockchain.ServiceKey().Address). @@ -289,7 +290,7 @@ func (s *ClientSuite) createParticipant(nodeID flow.Identifier, authoriser sdk.A // create DKG partcipant createParticipantTx := sdk.NewTransaction(). SetScript(templates.GenerateCreateDKGParticipantScript(s.env)). - SetGasLimit(9999). + SetComputeLimit(9999). SetProposalKey(s.blockchain.ServiceKey().Address, s.blockchain.ServiceKey().Index, s.blockchain.ServiceKey().SequenceNumber). SetPayer(s.blockchain.ServiceKey().Address). diff --git a/integration/dkg/dkg_client_wrapper.go b/integration/dkg/dkg_client_wrapper.go index d23ded924d3..9d9b4e5ca5f 100644 --- a/integration/dkg/dkg_client_wrapper.go +++ b/integration/dkg/dkg_client_wrapper.go @@ -5,11 +5,11 @@ import ( "fmt" "time" + "github.com/onflow/crypto" "go.uber.org/atomic" sdk "github.com/onflow/flow-go-sdk" - "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/model/messages" model "github.com/onflow/flow-go/model/messages" diff --git a/integration/dkg/dkg_emulator_suite.go b/integration/dkg/dkg_emulator_suite.go index 87484e4d502..8e301c95078 100644 --- a/integration/dkg/dkg_emulator_suite.go +++ b/integration/dkg/dkg_emulator_suite.go @@ -158,7 +158,7 @@ func (s *EmulatorSuite) deployDKGContract() { func (s *EmulatorSuite) setupDKGAdmin() { setUpAdminTx := sdk.NewTransaction(). SetScript(templates.GeneratePublishDKGParticipantScript(s.env)). - SetGasLimit(9999). + SetComputeLimit(9999). SetProposalKey( s.blockchain.ServiceKey().Address, s.blockchain.ServiceKey().Index, @@ -303,7 +303,7 @@ func (s *EmulatorSuite) startDKGWithParticipants(accounts []*nodeAccount) { // start DKG using admin resource startDKGTx := sdk.NewTransaction(). SetScript(templates.GenerateStartDKGScript(s.env)). - SetGasLimit(9999). + SetComputeLimit(9999). SetProposalKey( s.blockchain.ServiceKey().Address, s.blockchain.ServiceKey().Index, @@ -330,7 +330,7 @@ func (s *EmulatorSuite) startDKGWithParticipants(accounts []*nodeAccount) { func (s *EmulatorSuite) claimDKGParticipant(node *node) { createParticipantTx := sdk.NewTransaction(). SetScript(templates.GenerateCreateDKGParticipantScript(s.env)). - SetGasLimit(9999). + SetComputeLimit(9999). SetProposalKey( s.blockchain.ServiceKey().Address, s.blockchain.ServiceKey().Index, @@ -461,12 +461,6 @@ func (s *EmulatorSuite) initEngines(node *node, ids flow.IdentityList) { controllerFactoryLogger = zerolog.New(os.Stdout).Hook(hook) } - // create a config with no delays for tests - config := dkg.ControllerConfig{ - BaseStartDelay: 0, - BaseHandleFirstBroadcastDelay: 0, - } - // the reactor engine reacts to new views being finalized and drives the // DKG protocol reactorEngine := dkgeng.NewReactorEngine( @@ -479,7 +473,6 @@ func (s *EmulatorSuite) initEngines(node *node, ids flow.IdentityList) { core.Me, []module.DKGContractClient{node.dkgContractClient}, brokerTunnel, - config, ), viewsObserver, ) diff --git a/integration/dkg/dkg_emulator_test.go b/integration/dkg/dkg_emulator_test.go index 8d349bd8899..cb5b4c36fee 100644 --- a/integration/dkg/dkg_emulator_test.go +++ b/integration/dkg/dkg_emulator_test.go @@ -11,7 +11,8 @@ import ( "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" - "github.com/onflow/flow-go/crypto" + "github.com/onflow/crypto" + "github.com/onflow/flow-go/model/flow" msig "github.com/onflow/flow-go/module/signature" "github.com/onflow/flow-go/utils/unittest" @@ -51,7 +52,7 @@ func (s *EmulatorSuite) runTest(goodNodes int, emulatorProblems bool) { DKGPhase3FinalView: 250, FinalView: 300, Participants: s.netIDs, - RandomSource: []byte("random bytes for seed"), + RandomSource: unittest.EpochSetupRandomSourceFixture(), } // create the EpochSetup that will trigger the next DKG run with all the @@ -59,7 +60,7 @@ func (s *EmulatorSuite) runTest(goodNodes int, emulatorProblems bool) { nextEpochSetup := flow.EpochSetup{ Counter: currentCounter + 1, Participants: s.netIDs, - RandomSource: []byte("random bytes for seed"), + RandomSource: unittest.EpochSetupRandomSourceFixture(), FirstView: 301, FinalView: 600, } diff --git a/integration/dkg/dkg_whiteboard_client.go b/integration/dkg/dkg_whiteboard_client.go index 1dc4213029e..9b4593cefae 100644 --- a/integration/dkg/dkg_whiteboard_client.go +++ b/integration/dkg/dkg_whiteboard_client.go @@ -3,7 +3,8 @@ package dkg import ( "sync" - "github.com/onflow/flow-go/crypto" + "github.com/onflow/crypto" + "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/model/messages" ) diff --git a/integration/dkg/dkg_whiteboard_test.go b/integration/dkg/dkg_whiteboard_test.go index a7b00fa1172..64a05c6fbb7 100644 --- a/integration/dkg/dkg_whiteboard_test.go +++ b/integration/dkg/dkg_whiteboard_test.go @@ -10,14 +10,14 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/onflow/flow-go/module" - "github.com/onflow/flow-go/module/metrics" + "github.com/onflow/crypto" - "github.com/onflow/flow-go/crypto" dkgeng "github.com/onflow/flow-go/engine/consensus/dkg" "github.com/onflow/flow-go/engine/testutil" "github.com/onflow/flow-go/model/flow" + "github.com/onflow/flow-go/module" "github.com/onflow/flow-go/module/dkg" + "github.com/onflow/flow-go/module/metrics" msig "github.com/onflow/flow-go/module/signature" "github.com/onflow/flow-go/network/stub" "github.com/onflow/flow-go/state/protocol/events/gadgets" @@ -138,12 +138,6 @@ func createNode( }) controllerFactoryLogger := zerolog.New(os.Stdout).Hook(hook) - // create a config with no delays for tests - config := dkg.ControllerConfig{ - BaseStartDelay: 0, - BaseHandleFirstBroadcastDelay: 0, - } - // the reactor engine reacts to new views being finalized and drives the // DKG protocol reactorEngine := dkgeng.NewReactorEngine( @@ -156,7 +150,6 @@ func createNode( core.Me, []module.DKGContractClient{NewWhiteboardClient(id.NodeID, whiteboard)}, brokerTunnel, - config, ), viewsObserver, ) @@ -228,7 +221,7 @@ func TestWithWhiteboard(t *testing.T) { DKGPhase3FinalView: 250, FinalView: 300, Participants: conIdentities, - RandomSource: []byte("random bytes for seed"), + RandomSource: unittest.EpochSetupRandomSourceFixture(), } // create the EpochSetup that will trigger the next DKG run with all the @@ -236,7 +229,7 @@ func TestWithWhiteboard(t *testing.T) { nextEpochSetup := flow.EpochSetup{ Counter: currentCounter + 1, Participants: conIdentities, - RandomSource: []byte("random bytes for seed"), + RandomSource: unittest.EpochSetupRandomSourceFixture(), } nodes, _ := createNodes( diff --git a/integration/epochs/cluster_epoch_test.go b/integration/epochs/cluster_epoch_test.go index 5ddb2cf1073..565ef9889ee 100644 --- a/integration/epochs/cluster_epoch_test.go +++ b/integration/epochs/cluster_epoch_test.go @@ -3,20 +3,23 @@ package epochs import ( "encoding/hex" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + "github.com/onflow/cadence" jsoncdc "github.com/onflow/cadence/encoding/json" + "github.com/onflow/crypto" "github.com/onflow/flow-core-contracts/lib/go/contracts" "github.com/onflow/flow-core-contracts/lib/go/templates" + emulator "github.com/onflow/flow-emulator/emulator" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/stretchr/testify/suite" sdk "github.com/onflow/flow-go-sdk" sdkcrypto "github.com/onflow/flow-go-sdk/crypto" sdktemplates "github.com/onflow/flow-go-sdk/templates" "github.com/onflow/flow-go-sdk/test" - "github.com/onflow/flow-go/crypto" + "github.com/onflow/flow-go/integration/utils" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/model/flow/factory" @@ -98,7 +101,7 @@ func (s *Suite) PublishVoter() { // sign and publish voter transaction publishVoterTx := sdk.NewTransaction(). SetScript(templates.GeneratePublishVoterScript(s.env)). - SetGasLimit(9999). + SetComputeLimit(9999). SetProposalKey(s.blockchain.ServiceKey().Address, s.blockchain.ServiceKey().Index, s.blockchain.ServiceKey().SequenceNumber). SetPayer(s.blockchain.ServiceKey().Address). @@ -118,7 +121,7 @@ func (s *Suite) StartVoting(clustering flow.ClusterList, clusterCount, nodesPerC // submit admin transaction to start voting startVotingTx := sdk.NewTransaction(). SetScript(templates.GenerateStartVotingScript(s.env)). - SetGasLimit(9999). + SetComputeLimit(9999). SetProposalKey(s.blockchain.ServiceKey().Address, s.blockchain.ServiceKey().Index, s.blockchain.ServiceKey().SequenceNumber). SetPayer(s.blockchain.ServiceKey().Address). @@ -174,7 +177,7 @@ func (s *Suite) CreateVoterResource(address sdk.Address, nodeID flow.Identifier, registerVoterTx := sdk.NewTransaction(). SetScript(templates.GenerateCreateVoterScript(s.env)). - SetGasLimit(9999). + SetComputeLimit(9999). SetProposalKey(s.blockchain.ServiceKey().Address, s.blockchain.ServiceKey().Index, s.blockchain.ServiceKey().SequenceNumber). SetPayer(s.blockchain.ServiceKey().Address). @@ -204,7 +207,7 @@ func (s *Suite) CreateVoterResource(address sdk.Address, nodeID flow.Identifier, func (s *Suite) StopVoting() { tx := sdk.NewTransaction(). SetScript(templates.GenerateStopVotingScript(s.env)). - SetGasLimit(9999). + SetComputeLimit(9999). SetProposalKey(s.blockchain.ServiceKey().Address, s.blockchain.ServiceKey().Index, s.blockchain.ServiceKey().SequenceNumber). SetPayer(s.blockchain.ServiceKey().Address). diff --git a/integration/go.mod b/integration/go.mod index fa2b1117041..4429a899093 100644 --- a/integration/go.mod +++ b/integration/go.mod @@ -3,7 +3,7 @@ module github.com/onflow/flow-go/integration go 1.20 require ( - cloud.google.com/go/bigquery v1.53.0 + cloud.google.com/go/bigquery v1.56.0 github.com/VividCortex/ewma v1.2.0 github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2 github.com/coreos/go-semver v0.3.0 @@ -19,15 +19,15 @@ require ( github.com/ipfs/go-datastore v0.6.0 github.com/ipfs/go-ds-badger2 v0.1.3 github.com/ipfs/go-ipfs-blockstore v1.3.0 - github.com/onflow/cadence v0.42.6 + github.com/onflow/cadence v1.0.0-preview.1.0.20231213191345-0ff20e15e7e1 + github.com/onflow/crypto v0.25.0 github.com/onflow/flow-core-contracts/lib/go/contracts v1.2.4-0.20231016154253-a00dbf7c061f github.com/onflow/flow-core-contracts/lib/go/templates v1.2.4-0.20231016154253-a00dbf7c061f - github.com/onflow/flow-emulator v0.58.1-0.20231130142844-f22e54339f85 + github.com/onflow/flow-emulator v0.58.1-0.20240118140159-d334a0fcd380 github.com/onflow/flow-go v0.32.7 - github.com/onflow/flow-go-sdk v0.41.17 - github.com/onflow/flow-go/crypto v0.25.0 + github.com/onflow/flow-go-sdk v0.44.0 github.com/onflow/flow-go/insecure v0.0.0-00010101000000-000000000000 - github.com/onflow/flow/protobuf/go/flow v0.3.2-0.20231124194313-106cc495def6 + github.com/onflow/flow/protobuf/go/flow v0.3.2-0.20231213135419-ae911cc351a2 github.com/plus3it/gorecurcopy v0.0.1 github.com/prometheus/client_golang v1.16.0 github.com/prometheus/client_model v0.5.0 @@ -36,44 +36,44 @@ require ( github.com/stretchr/testify v1.8.4 go.einride.tech/pid v0.1.0 go.uber.org/atomic v1.11.0 - golang.org/x/exp v0.0.0-20230321023759-10a507213a29 - golang.org/x/sync v0.3.0 + golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc + golang.org/x/sync v0.5.0 google.golang.org/grpc v1.59.0 google.golang.org/protobuf v1.31.0 ) require ( - cloud.google.com/go v0.110.7 // indirect - cloud.google.com/go/compute v1.23.0 // indirect + cloud.google.com/go v0.110.8 // indirect + cloud.google.com/go/compute v1.23.1 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect - cloud.google.com/go/iam v1.1.1 // indirect + cloud.google.com/go/iam v1.1.3 // indirect cloud.google.com/go/storage v1.30.1 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/DataDog/zstd v1.5.2 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4 // indirect - github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 // indirect - github.com/VictoriaMetrics/fastcache v1.6.0 // indirect + github.com/StackExchange/wmi v1.2.1 // indirect + github.com/VictoriaMetrics/fastcache v1.12.1 // indirect github.com/acomagu/bufpipe v1.0.3 // indirect github.com/andybalholm/brotli v1.0.4 // indirect github.com/apache/arrow/go/v12 v12.0.0 // indirect github.com/apache/thrift v0.16.0 // indirect - github.com/aws/aws-sdk-go-v2 v1.17.7 // indirect - github.com/aws/aws-sdk-go-v2/config v1.18.19 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.13.18 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.1 // indirect + github.com/aws/aws-sdk-go-v2 v1.23.1 // indirect + github.com/aws/aws-sdk-go-v2/config v1.25.5 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.16.4 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.5 // indirect github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.5.1 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.31 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.25 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.3.32 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.3.0 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.25 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.4 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.4 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.7.1 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.1 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.4 // indirect github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.7.0 // indirect github.com/aws/aws-sdk-go-v2/service/s3 v1.15.0 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.12.6 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.6 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.18.7 // indirect - github.com/aws/smithy-go v1.13.5 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.17.3 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.20.1 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.25.4 // indirect + github.com/aws/smithy-go v1.17.0 // indirect github.com/benbjohnson/clock v1.3.5 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bits-and-blooms/bitset v1.7.0 // indirect @@ -85,11 +85,15 @@ require ( github.com/cloudflare/circl v1.1.0 // indirect github.com/cockroachdb/errors v1.9.1 // indirect github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect - github.com/cockroachdb/pebble v0.0.0-20230906160148-46873a6a7a06 // indirect + github.com/cockroachdb/pebble v0.0.0-20230928194634-aa077af62593 // indirect github.com/cockroachdb/redact v1.1.3 // indirect + github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect + github.com/consensys/bavard v0.1.13 // indirect + github.com/consensys/gnark-crypto v0.12.1 // indirect github.com/containerd/cgroups v1.1.0 // indirect github.com/containerd/fifo v1.1.0 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect + github.com/crate-crypto/go-kzg-4844 v0.7.0 // indirect github.com/cskr/pubsub v1.0.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect @@ -108,7 +112,8 @@ require ( github.com/ef-ds/deque v1.0.4 // indirect github.com/elastic/gosigar v0.14.2 // indirect github.com/emirpasic/gods v1.18.1 // indirect - github.com/ethereum/go-ethereum v1.12.0 // indirect + github.com/ethereum/c-kzg-4844 v0.4.0 // indirect + github.com/ethereum/go-ethereum v1.13.5 // indirect github.com/flynn/noise v1.0.0 // indirect github.com/francoispqt/gojay v1.2.13 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect @@ -133,7 +138,7 @@ require ( github.com/go-redis/redis/v8 v8.11.5 // indirect github.com/go-stack/stack v1.8.1 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect - github.com/goccy/go-json v0.9.11 // indirect + github.com/goccy/go-json v0.10.2 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/gofrs/flock v0.8.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect @@ -145,10 +150,10 @@ require ( github.com/google/flatbuffers v2.0.8+incompatible // indirect github.com/google/gopacket v1.1.19 // indirect github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 // indirect - github.com/google/s2a-go v0.1.4 // indirect - github.com/google/uuid v1.3.1 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect - github.com/googleapis/gax-go/v2 v2.11.0 // indirect + github.com/google/s2a-go v0.1.7 // indirect + github.com/google/uuid v1.4.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect + github.com/googleapis/gax-go/v2 v2.12.0 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/grpc-ecosystem/go-grpc-middleware/providers/zerolog/v2 v2.0.0-rc.2 // indirect github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.0-rc.2 // indirect @@ -160,8 +165,8 @@ require ( github.com/hashicorp/golang-lru/v2 v2.0.2 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/holiman/bloomfilter/v2 v2.0.3 // indirect - github.com/holiman/uint256 v1.2.2-0.20230321075855-87b91420868c // indirect - github.com/huin/goupnp v1.2.0 // indirect + github.com/holiman/uint256 v1.2.3 // indirect + github.com/huin/goupnp v1.3.0 // indirect github.com/imdario/mergo v0.3.13 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/ipfs/bbloom v0.0.4 // indirect @@ -188,7 +193,7 @@ require ( github.com/jbenet/goprocess v0.1.4 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/k0kubun/pp/v3 v3.2.0 // indirect - github.com/kevinburke/go-bindata v3.23.0+incompatible // indirect + github.com/kevinburke/go-bindata v3.24.0+incompatible // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/klauspost/asmfmt v1.3.2 // indirect github.com/klauspost/compress v1.16.5 // indirect @@ -219,8 +224,8 @@ require ( github.com/magiconair/properties v1.8.7 // indirect github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.19 // indirect - github.com/mattn/go-runewidth v0.0.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/miekg/dns v1.1.54 // indirect github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect @@ -230,6 +235,7 @@ require ( github.com/minio/sha256-simd v1.0.1 // indirect github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/mmcloughlin/addchain v0.4.0 // indirect github.com/moby/term v0.5.0 // indirect github.com/morikuni/aec v1.0.0 // indirect github.com/mr-tron/base58 v1.2.0 // indirect @@ -244,12 +250,13 @@ require ( github.com/multiformats/go-multistream v0.4.1 // indirect github.com/multiformats/go-varint v0.0.7 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect - github.com/onflow/atree v0.6.0 // indirect - github.com/onflow/flow-ft/lib/go/contracts v0.7.1-0.20230711213910-baad011d2b13 // indirect - github.com/onflow/flow-nft/lib/go/contracts v1.1.0 // indirect + github.com/onflow/atree v0.6.1-0.20230711151834-86040b30171f // indirect + github.com/onflow/flow-ft/lib/go/contracts v0.7.1-0.20231212194336-a2802ba36596 // indirect + github.com/onflow/flow-go/crypto v0.25.0 // indirect + github.com/onflow/flow-nft/lib/go/contracts v1.1.1-0.20231213195450-0b951b342b14 // indirect github.com/onflow/go-bitswap v0.0.0-20230703214630-6d3db958c73d // indirect github.com/onflow/nft-storefront/lib/go/contracts v0.0.0-20221222181731-14b90207cead // indirect - github.com/onflow/sdks v0.5.0 // indirect + github.com/onflow/sdks v0.5.1-0.20230912225508-b35402f12bba // indirect github.com/onflow/wal v0.0.0-20230529184820-bc9f8244608d // indirect github.com/onsi/ginkgo/v2 v2.9.7 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect @@ -279,7 +286,7 @@ require ( github.com/rogpeppe/go-internal v1.9.0 // indirect github.com/rootless-containers/rootlesskit v1.1.1 // indirect github.com/schollz/progressbar/v3 v3.13.1 // indirect - github.com/sergi/go-diff v1.1.0 // indirect + github.com/sergi/go-diff v1.2.0 // indirect github.com/sethvargo/go-retry v0.2.3 // indirect github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect github.com/shirou/gopsutil/v3 v3.22.2 // indirect @@ -288,7 +295,7 @@ require ( github.com/slok/go-http-metrics v0.10.0 // indirect github.com/sony/gobreaker v0.5.0 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect - github.com/spf13/afero v1.9.3 // indirect + github.com/spf13/afero v1.10.0 // indirect github.com/spf13/cast v1.5.0 // indirect github.com/spf13/cobra v1.8.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect @@ -296,10 +303,11 @@ require ( github.com/spf13/viper v1.15.0 // indirect github.com/stretchr/objx v0.5.0 // indirect github.com/subosito/gotenv v1.4.2 // indirect + github.com/supranational/blst v0.3.11 // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect github.com/texttheater/golang-levenshtein/levenshtein v0.0.0-20200805054039-cae8b0eaed6c // indirect - github.com/tklauser/go-sysconf v0.3.9 // indirect - github.com/tklauser/numcpus v0.3.0 // indirect + github.com/tklauser/go-sysconf v0.3.12 // indirect + github.com/tklauser/numcpus v0.6.1 // indirect github.com/turbolent/prettier v0.0.0-20220320183459-661cc755135d // indirect github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect github.com/vmihailenco/msgpack/v4 v4.3.12 // indirect @@ -324,25 +332,24 @@ require ( go.uber.org/fx v1.19.2 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.24.0 // indirect - golang.org/x/crypto v0.12.0 // indirect - golang.org/x/mod v0.10.0 // indirect - golang.org/x/net v0.14.0 // indirect - golang.org/x/oauth2 v0.11.0 // indirect - golang.org/x/sys v0.12.0 // indirect - golang.org/x/term v0.11.0 // indirect - golang.org/x/text v0.12.0 // indirect + golang.org/x/crypto v0.17.0 // indirect + golang.org/x/mod v0.14.0 // indirect + golang.org/x/net v0.19.0 // indirect + golang.org/x/oauth2 v0.13.0 // indirect + golang.org/x/sys v0.15.0 // indirect + golang.org/x/term v0.15.0 // indirect + golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.9.1 // indirect + golang.org/x/tools v0.16.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect gonum.org/v1/gonum v0.13.0 // indirect - google.golang.org/api v0.126.0 // indirect + google.golang.org/api v0.151.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect + google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405 // indirect google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.2.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect - gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect lukechampine.com/blake3 v1.2.1 // indirect @@ -350,8 +357,21 @@ require ( modernc.org/mathutil v1.5.0 // indirect modernc.org/memory v1.5.0 // indirect modernc.org/sqlite v1.21.1 // indirect + rsc.io/tmplfunc v0.0.3 // indirect ) replace github.com/onflow/flow-go => ../ replace github.com/onflow/flow-go/insecure => ../insecure + +// the replaces below should not be needed once flow-go and all the repos are updated +// to use Cadence 1.0 +replace github.com/onflow/cadence => github.com/onflow/cadence v0.42.8 + +replace github.com/onflow/flow-ft/lib/go/contracts => github.com/onflow/flow-ft/lib/go/contracts v0.7.1-0.20230711213910-baad011d2b13 + +replace github.com/onflow/flow-nft/lib/go/contracts => github.com/onflow/flow-nft/lib/go/contracts v1.1.0 + +replace github.com/onflow/sdks => github.com/onflow/sdks v0.5.0 + +replace github.com/onflow/flow-core-contracts/lib/go/contracts => github.com/onflow/flow-core-contracts/lib/go/contracts v0.15.1-0.20231219201108-fbdb10b0a2da diff --git a/integration/go.sum b/integration/go.sum index 24d2de73b6b..9367f85a1f8 100644 --- a/integration/go.sum +++ b/integration/go.sum @@ -29,27 +29,27 @@ cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aD cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= -cloud.google.com/go v0.110.7 h1:rJyC7nWRg2jWGZ4wSJ5nY65GTdYJkg0cd/uXb+ACI6o= -cloud.google.com/go v0.110.7/go.mod h1:+EYjdK8e5RME/VY/qLCAtuyALQ9q67dvuum8i+H5xsI= +cloud.google.com/go v0.110.8 h1:tyNdfIxjzaWctIiLYOTalaLKZ17SI44SKFW26QbOhME= +cloud.google.com/go v0.110.8/go.mod h1:Iz8AkXJf1qmxC3Oxoep8R1T36w8B92yU29PcBhHO5fk= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/bigquery v1.53.0 h1:K3wLbjbnSlxhuG5q4pntHv5AEbQM1QqHKGYgwFIqOTg= -cloud.google.com/go/bigquery v1.53.0/go.mod h1:3b/iXjRQGU4nKa87cXeg6/gogLjO8C6PmuM8i5Bi/u4= -cloud.google.com/go/compute v1.23.0 h1:tP41Zoavr8ptEqaW6j+LQOnyBBhO7OkOMAGrgLopTwY= -cloud.google.com/go/compute v1.23.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= +cloud.google.com/go/bigquery v1.56.0 h1:LHIc9E7Kw+ftFpQFKzZYBB88IAFz7qONawXXx0F3QBo= +cloud.google.com/go/bigquery v1.56.0/go.mod h1:KDcsploXTEY7XT3fDQzMUZlpQLHzE4itubHrnmhUrZA= +cloud.google.com/go/compute v1.23.1 h1:V97tBoDaZHb6leicZ1G6DLK2BAaZLJ/7+9BB/En3hR0= +cloud.google.com/go/compute v1.23.1/go.mod h1:CqB3xpmPKKt3OJpW2ndFIXnA9A4xAy/F3Xp1ixncW78= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= -cloud.google.com/go/datacatalog v1.16.0 h1:qVeQcw1Cz93/cGu2E7TYUPh8Lz5dn5Ws2siIuQ17Vng= +cloud.google.com/go/datacatalog v1.18.1 h1:xJp9mZrc2HPaoxIz3sP9pCmf/impifweQ/yGG9VBfio= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/iam v1.1.1 h1:lW7fzj15aVIXYHREOqjRBV9PsH0Z6u8Y46a1YGvQP4Y= -cloud.google.com/go/iam v1.1.1/go.mod h1:A5avdyVL2tCppe4unb0951eI9jreack+RJ0/d+KUZOU= +cloud.google.com/go/iam v1.1.3 h1:18tKG7DzydKWUnLjonWcJO6wjSCAtzh4GcRKlH/Hrzc= +cloud.google.com/go/iam v1.1.3/go.mod h1:3khUlaBXfPKKe7huYgEpDn6FtgRyMEqbkvBxrQyY5SE= cloud.google.com/go/kms v1.0.0/go.mod h1:nhUehi+w7zht2XrUfvTRNpxrfayBHqP4lu2NSywui/0= -cloud.google.com/go/longrunning v0.5.1 h1:Fr7TXftcqTudoyRJa113hyaqlGdiBQkp0Gq7tErFDWI= +cloud.google.com/go/longrunning v0.5.2 h1:u+oFqfEwwU7F9dIELigxbe0XVnBAo9wqMuQLA50CZ5k= cloud.google.com/go/profiler v0.3.0 h1:R6y/xAeifaUXxd2x6w+jIwKxoKl8Cv5HJvcvASTPWJo= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= @@ -109,11 +109,12 @@ github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4/go.mod h1:UBY github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= -github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 h1:fLjPD/aNc3UIOA6tDi6QXUemppXK3P9BI7mr2hd6gx8= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= +github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA= +github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= github.com/VictoriaMetrics/fastcache v1.5.3/go.mod h1:+jv9Ckb+za/P1ZRg/sulP5Ni1v49daAVERr0H3CuscE= -github.com/VictoriaMetrics/fastcache v1.6.0 h1:C/3Oi3EiBCqufydp1neRZkqcwmEiuRT9c3fqvvgKm5o= -github.com/VictoriaMetrics/fastcache v1.6.0/go.mod h1:0qHz5QP0GMX4pfmMA/zt5RgfNuXJrTP0zS7DqpHGGTw= +github.com/VictoriaMetrics/fastcache v1.12.1 h1:i0mICQuojGDL3KblA7wUNlY5lOK6a4bwt3uRKnkZU40= +github.com/VictoriaMetrics/fastcache v1.12.1/go.mod h1:tX04vaqcNoQeGLD+ra5pU5sWkuxnzWhEzLwhP9w653o= github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow= github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= @@ -154,46 +155,47 @@ github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQ github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= github.com/aws/aws-sdk-go-v2 v1.9.0/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4= -github.com/aws/aws-sdk-go-v2 v1.17.7 h1:CLSjnhJSTSogvqUGhIC6LqFKATMRexcxLZ0i/Nzk9Eg= -github.com/aws/aws-sdk-go-v2 v1.17.7/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw= +github.com/aws/aws-sdk-go-v2 v1.23.1 h1:qXaFsOOMA+HsZtX8WoCa+gJnbyW7qyFFBlPqvTSzbaI= +github.com/aws/aws-sdk-go-v2 v1.23.1/go.mod h1:i1XDttT4rnf6vxc9AuskLc6s7XBee8rlLilKlc03uAA= github.com/aws/aws-sdk-go-v2/config v1.8.0/go.mod h1:w9+nMZ7soXCe5nT46Ri354SNhXDQ6v+V5wqDjnZE+GY= -github.com/aws/aws-sdk-go-v2/config v1.18.19 h1:AqFK6zFNtq4i1EYu+eC7lcKHYnZagMn6SW171la0bGw= -github.com/aws/aws-sdk-go-v2/config v1.18.19/go.mod h1:XvTmGMY8d52ougvakOv1RpiTLPz9dlG/OQHsKU/cMmY= +github.com/aws/aws-sdk-go-v2/config v1.25.5 h1:UGKm9hpQS2hoK8CEJ1BzAW8NbUpvwDJJ4lyqXSzu8bk= +github.com/aws/aws-sdk-go-v2/config v1.25.5/go.mod h1:Bf4gDvy4ZcFIK0rqDu1wp9wrubNba2DojiPB2rt6nvI= github.com/aws/aws-sdk-go-v2/credentials v1.4.0/go.mod h1:dgGR+Qq7Wjcd4AOAW5Rf5Tnv3+x7ed6kETXyS9WCuAY= -github.com/aws/aws-sdk-go-v2/credentials v1.13.18 h1:EQMdtHwz0ILTW1hoP+EwuWhwCG1hD6l3+RWFQABET4c= -github.com/aws/aws-sdk-go-v2/credentials v1.13.18/go.mod h1:vnwlwjIe+3XJPBYKu1et30ZPABG3VaXJYr8ryohpIyM= +github.com/aws/aws-sdk-go-v2/credentials v1.16.4 h1:i7UQYYDSJrtc30RSwJwfBKwLFNnBTiICqAJ0pPdum8E= +github.com/aws/aws-sdk-go-v2/credentials v1.16.4/go.mod h1:Kdh/okh+//vQ/AjEt81CjvkTo64+/zIE4OewP7RpfXk= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.5.0/go.mod h1:CpNzHK9VEFUCknu50kkB8z58AH2B5DvPP7ea1LHve/Y= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.1 h1:gt57MN3liKiyGopcqgNzJb2+d9MJaKT/q1OksHNXVE4= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.1/go.mod h1:lfUx8puBRdM5lVVMQlwt2v+ofiG/X6Ms+dy0UkG/kXw= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.5 h1:KehRNiVzIfAcj6gw98zotVbb/K67taJE0fkfgM6vzqU= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.5/go.mod h1:VhnExhw6uXy9QzetvpXDolo1/hjhx4u9qukBGkuUwjs= github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.5.1 h1:VGkV9KmhGqOQWnHyi4gLG98kE6OecT42fdrCGFWxJsc= github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.5.1/go.mod h1:PLlnMiki//sGnCJiW+aVpvP/C8Kcm8mEj/IVm9+9qk4= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.31 h1:sJLYcS+eZn5EeNINGHSCRAwUJMFVqklwkH36Vbyai7M= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.31/go.mod h1:QT0BqUvX1Bh2ABdTGnjqEjvjzrCfIniM9Sc8zn9Yndo= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.25 h1:1mnRASEKnkqsntcxHaysxwgVoUUp5dkiB+l3llKnqyg= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.25/go.mod h1:zBHOPwhBc3FlQjQJE/D3IfPWiWaQmT06Vq9aNukDo0k= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.4 h1:LAm3Ycm9HJfbSCd5I+wqC2S9Ej7FPrgr5CQoOljJZcE= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.4/go.mod h1:xEhvbJcyUf/31yfGSQBe01fukXwXJ0gxDp7rLfymWE0= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.4 h1:4GV0kKZzUxiWxSVpn/9gwR0g21NF1Jsyduzo9rHgC/Q= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.4/go.mod h1:dYvTNAggxDZy6y1AF7YDwXsPuHFy/VNEpEI/2dWK9IU= github.com/aws/aws-sdk-go-v2/internal/ini v1.2.2/go.mod h1:BQV0agm+JEhqR+2RT5e1XTFIDcAAV0eW6z2trp+iduw= -github.com/aws/aws-sdk-go-v2/internal/ini v1.3.32 h1:p5luUImdIqywn6JpQsW3tq5GNOxKmOnEpybzPx+d1lk= -github.com/aws/aws-sdk-go-v2/internal/ini v1.3.32/go.mod h1:XGhIBZDEgfqmFIugclZ6FU7v75nHhBDtzuB4xB/tEi4= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.3.0 h1:gceOysEWNNwLd6cki65IMBZ4WAM0MwgBQq2n7kejoT8= +github.com/aws/aws-sdk-go-v2/internal/ini v1.7.1 h1:uR9lXYjdPX0xY+NhvaJ4dD8rpSRz5VY81ccIIoNG+lw= +github.com/aws/aws-sdk-go-v2/internal/ini v1.7.1/go.mod h1:6fQQgfuGmw8Al/3M2IgIllycxV7ZW7WCdVSqfBeUiCY= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.3.0/go.mod h1:v8ygadNyATSm6elwJ/4gzJwcFhri9RqS8skgHKiwXPU= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.1 h1:rpkF4n0CyFcrJUG/rNNohoTmhtWlFTRI4BsZOh9PvLs= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.1/go.mod h1:l9ymW25HOqymeU2m1gbUQ3rUIsTwKs8gYHXkqDQUhiI= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.3.0/go.mod h1:R1KK+vY8AfalhG1AOu5e35pOD2SdoPKQCFLTvnxiohk= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.25 h1:5LHn8JQ0qvjD9L9JhMtylnkcw7j05GDZqM9Oin6hpr0= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.25/go.mod h1:/95IA+0lMnzW6XzqYJRpjjsAbKEORVeO0anQqjd2CNU= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.4 h1:rdovz3rEu0vZKbzoMYPTehp0E8veoE9AyfzqCr5Eeao= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.4/go.mod h1:aYCGNjyUCUelhofxlZyj63srdxWUSsBSGg5l6MCuXuE= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.7.0 h1:HWsM0YQWX76V6MOp07YuTYacm8k7h69ObJuw7Nck+og= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.7.0/go.mod h1:LKb3cKNQIMh+itGnEpKGcnL/6OIjPZqrtYah1w5f+3o= github.com/aws/aws-sdk-go-v2/service/s3 v1.15.0 h1:nPLfLPfglacc29Y949sDxpr3X/blaY40s3B85WT2yZU= github.com/aws/aws-sdk-go-v2/service/s3 v1.15.0/go.mod h1:Iv2aJVtVSm/D22rFoX99cLG4q4uB7tppuCsulGe98k4= github.com/aws/aws-sdk-go-v2/service/sso v1.4.0/go.mod h1:+1fpWnL96DL23aXPpMGbsmKe8jLTEfbjuQoA4WS1VaA= -github.com/aws/aws-sdk-go-v2/service/sso v1.12.6 h1:5V7DWLBd7wTELVz5bPpwzYy/sikk0gsgZfj40X+l5OI= -github.com/aws/aws-sdk-go-v2/service/sso v1.12.6/go.mod h1:Y1VOmit/Fn6Tz1uFAeCO6Q7M2fmfXSCLeL5INVYsLuY= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.6 h1:B8cauxOH1W1v7rd8RdI/MWnoR4Ze0wIHWrb90qczxj4= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.6/go.mod h1:Lh/bc9XUf8CfOY6Jp5aIkQtN+j1mc+nExc+KXj9jx2s= +github.com/aws/aws-sdk-go-v2/service/sso v1.17.3 h1:CdsSOGlFF3Pn+koXOIpTtvX7st0IuGsZ8kJqcWMlX54= +github.com/aws/aws-sdk-go-v2/service/sso v1.17.3/go.mod h1:oA6VjNsLll2eVuUoF2D+CMyORgNzPEW/3PyUdq6WQjI= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.20.1 h1:cbRqFTVnJV+KRpwFl76GJdIZJKKCdTPnjUZ7uWh3pIU= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.20.1/go.mod h1:hHL974p5auvXlZPIjJTblXJpbkfK4klBczlsEaMCGVY= github.com/aws/aws-sdk-go-v2/service/sts v1.7.0/go.mod h1:0qcSMCyASQPN2sk/1KQLQ2Fh6yq8wm0HSDAimPhzCoM= -github.com/aws/aws-sdk-go-v2/service/sts v1.18.7 h1:bWNgNdRko2x6gqa0blfATqAZKZokPIeM1vfmQt2pnvM= -github.com/aws/aws-sdk-go-v2/service/sts v1.18.7/go.mod h1:JuTnSoeePXmMVe9G8NcjjwgOKEfZ4cOjMuT2IBT/2eI= +github.com/aws/aws-sdk-go-v2/service/sts v1.25.4 h1:yEvZ4neOQ/KpUqyR+X0ycUTW/kVRNR4nDZ38wStHGAA= +github.com/aws/aws-sdk-go-v2/service/sts v1.25.4/go.mod h1:feTnm2Tk/pJxdX+eooEsxvlvTWBvDm6CasRZ+JOs2IY= github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= -github.com/aws/smithy-go v1.13.5 h1:hgz0X/DX0dGqTYpGALqXJoRKRj5oQ7150i5FdTePzO8= -github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= +github.com/aws/smithy-go v1.17.0 h1:wWJD7LX6PBV6etBUwO0zElG0nWN9rUhp0WdYeHSHAaI= +github.com/aws/smithy-go v1.17.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE= github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= @@ -204,6 +206,7 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bits-and-blooms/bitset v1.5.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= github.com/bits-and-blooms/bitset v1.7.0 h1:YjAGVd3XmtK9ktAbX8Zg2g2PwLIMjGREZJHlV4j7NEo= github.com/bits-and-blooms/bitset v1.7.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= @@ -231,8 +234,8 @@ github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtE github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= -github.com/bytecodealliance/wasmtime-go v0.22.0/go.mod h1:q320gUxqyI8yB+ZqRuaJOEnGkAnHh6WtJjMaT2CW4wI= -github.com/c-bata/go-prompt v0.2.5/go.mod h1:vFnjEGDIIA/Lib7giyE4E9c50Lvl8j0S+7FVlAwDAVw= +github.com/bytecodealliance/wasmtime-go/v7 v7.0.0/go.mod h1:bu6fic7trDt20w+LMooX7j3fsOwv4/ln6j8gAdP6vmA= +github.com/c-bata/go-prompt v0.2.6/go.mod h1:/LMAke8wD2FsNu9EXNdHxNLbd9MedkPnCdfpU9wwHfY= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= @@ -266,18 +269,24 @@ github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/cockroachdb/datadriven v1.0.2/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= -github.com/cockroachdb/datadriven v1.0.3-0.20230801171734-e384cf455877 h1:1MLK4YpFtIEo3ZtMA5C795Wtv5VuUnrXX7mQG+aHg6o= +github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4= github.com/cockroachdb/errors v1.9.1 h1:yFVvsI0VxmRShfawbt/laCIDy/mtTqqnvoNgiy5bEV8= github.com/cockroachdb/errors v1.9.1/go.mod h1:2sxOtL2WIc096WSZqZ5h8fa17rdDq9HZOZLBCor4mBk= github.com/cockroachdb/logtags v0.0.0-20211118104740-dabe8e521a4f/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE= github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= -github.com/cockroachdb/pebble v0.0.0-20230906160148-46873a6a7a06 h1:T+Np/xtzIjYM/P5NAw0e2Rf1FGvzDau1h54MKvx8G7w= -github.com/cockroachdb/pebble v0.0.0-20230906160148-46873a6a7a06/go.mod h1:bynZ3gvVyhlvjLI7PT6dmZ7g76xzJ7HpxfjgkzCGz6s= +github.com/cockroachdb/pebble v0.0.0-20230928194634-aa077af62593 h1:aPEJyR4rPBvDmeyi+l/FS/VtA00IWvjeFvjen1m1l1A= +github.com/cockroachdb/pebble v0.0.0-20230928194634-aa077af62593/go.mod h1:6hk1eMY/u5t+Cf18q5lFMUA1Rc+Sm5I6Ra1QuPyxXCo= github.com/cockroachdb/redact v1.1.3 h1:AKZds10rFSIj7qADf0g46UixK8NNLwWTNdCIGS5wfSQ= github.com/cockroachdb/redact v1.1.3/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= +github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo= +github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= +github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/YjhQ= +github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI= +github.com/consensys/gnark-crypto v0.12.1 h1:lHH39WuuFgVHONRl3J0LRBtuYdQTumFSDtJF7HpyG8M= +github.com/consensys/gnark-crypto v0.12.1/go.mod h1:v2Gy7L/4ZRosZ7Ivs+9SfUDr0f5UlG+EM5t7MPHiLuY= github.com/containerd/cgroups v0.0.0-20201119153540-4cbc285b3327/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw= @@ -303,6 +312,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:ma github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/crate-crypto/go-kzg-4844 v0.7.0 h1:C0vgZRk4q4EZ/JgPfzuSoxdCq3C3mOZMBShovmncxvA= +github.com/crate-crypto/go-kzg-4844 v0.7.0/go.mod h1:1kMhvPgI0Ky3yIa+9lFySEBUBXkYxeOi8ZF1sYioxhc= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= @@ -312,6 +323,15 @@ github.com/cskr/pubsub v1.0.2/go.mod h1:/8MzYXk/NJAz782G8RPkFzXTZVu63VotefPnR9TI github.com/danieljoos/wincred v1.2.0/go.mod h1:FzQLLMKBFdvu+osBrnFODiv32YGwCfx0SkRa/eYHgec= github.com/dapperlabs/testingdock v0.4.5-0.20231020233342-a2853fe18724 h1:zOOpPLu5VvH8ixyoDWHnQHWoEHtryT1ne31vwz0G7Fo= github.com/dapperlabs/testingdock v0.4.5-0.20231020233342-a2853fe18724/go.mod h1:U0cEcbf9hAwPSuuoPVqXKhcWV+IU4CStK75cJ52f2/A= +github.com/dave/astrid v0.0.0-20170323122508-8c2895878b14/go.mod h1:Sth2QfxfATb/nW4EsrSi2KyJmbcniZ8TgTaji17D6ms= +github.com/dave/brenda v1.1.0/go.mod h1:4wCUr6gSlu5/1Tk7akE5X7UorwiQ8Rij0SKH3/BGMOM= +github.com/dave/courtney v0.3.0/go.mod h1:BAv3hA06AYfNUjfjQr+5gc6vxeBVOupLqrColj+QSD8= +github.com/dave/dst v0.27.2/go.mod h1:jHh6EOibnHgcUW3WjKHisiooEkYwqpHLBSX1iOBhEyc= +github.com/dave/gopackages v0.0.0-20170318123100-46e7023ec56e/go.mod h1:i00+b/gKdIDIxuLDFob7ustLAVqhsZRk2qVZrArELGQ= +github.com/dave/jennifer v1.5.0/go.mod h1:4MnyiFIlZS3l5tSDn8VnzE6ffAhYBMB2SZntBsZGUok= +github.com/dave/kerr v0.0.0-20170318121727-bc25dd6abe8e/go.mod h1:qZqlPyPvfsDJt+3wHJ1EvSXDuVjFTK0j2p/ca+gtsb8= +github.com/dave/patsy v0.0.0-20210517141501-957256f50cba/go.mod h1:qfR88CgEGLoiqDaE+xxDCi5QA5v4vUoW0UCX2Nd5Tlc= +github.com/dave/rebecca v0.9.1/go.mod h1:N6XYdMD/OKw3lkF3ywh8Z6wPGuwNFDNtWYEMFWEmXBA= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -392,9 +412,11 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.m github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= +github.com/ethereum/c-kzg-4844 v0.4.0 h1:3MS1s4JtA868KpJxroZoepdV0ZKBp3u/O5HcZ7R3nlY= +github.com/ethereum/c-kzg-4844 v0.4.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= github.com/ethereum/go-ethereum v1.9.9/go.mod h1:a9TqabFudpDu1nucId+k9S8R9whYaHnGBLKFouA5EAo= -github.com/ethereum/go-ethereum v1.12.0 h1:bdnhLPtqETd4m3mS8BGMNvBTf36bO5bx/hxE2zljOa0= -github.com/ethereum/go-ethereum v1.12.0/go.mod h1:/oo2X/dZLJjf2mJ6YT9wcWxa4nNJDBKDBU6sFIpx1Gs= +github.com/ethereum/go-ethereum v1.13.5 h1:U6TCRciCqZRe4FPXmy1sMGxTfuk8P7u2UoinF3VbaFk= +github.com/ethereum/go-ethereum v1.13.5/go.mod h1:yMTu38GSuyxaYzQMViqNmQ1s3cE84abZexQmTgenWk0= github.com/fanliao/go-promise v0.0.0-20141029170127-1890db352a72/go.mod h1:PjfxuH4FZdUyfMdtBio2lsRr1AKEaVPwelzuHuh8Lqc= github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8= github.com/fatih/color v1.3.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= @@ -416,10 +438,9 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= -github.com/fxamacker/cbor/v2 v2.2.1-0.20210927235116-3d6d5d1de29b/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= +github.com/fxamacker/cbor/v2 v2.4.1-0.20220515183430-ad2eae63303f/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= github.com/fxamacker/cbor/v2 v2.4.1-0.20230228173756-c0c9f774e40c h1:5tm/Wbs9d9r+qZaUFXk59CWDD0+77PBqDREffYkyi5c= github.com/fxamacker/cbor/v2 v2.4.1-0.20230228173756-c0c9f774e40c/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= -github.com/fxamacker/circlehash v0.1.0/go.mod h1:3aq3OfVvsWtkWMb6A1owjOQFA+TLsD5FgJflnaQwtMM= github.com/fxamacker/circlehash v0.3.0 h1:XKdvTtIJV9t7DDUtsf0RIpC1OcxZtPbmgIH7ekx28WA= github.com/fxamacker/circlehash v0.3.0/go.mod h1:3aq3OfVvsWtkWMb6A1owjOQFA+TLsD5FgJflnaQwtMM= github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= @@ -469,12 +490,14 @@ github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA= github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 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= github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= +github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= @@ -492,14 +515,13 @@ 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-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= -github.com/go-test/deep v1.0.5/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8= github.com/go-yaml/yaml v2.1.0+incompatible h1:RYi2hDdss1u4YE7GwixGzWwVo47T8UQwnTLB6vQiq+o= github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0= 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= -github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk= -github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= @@ -587,9 +609,8 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ 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.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -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-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= 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= @@ -620,25 +641,26 @@ github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 h1:hR7/MlvK23p6+lIw9SN1TigNLn9ZnF3W4SYRKq2gAHs= github.com/google/pprof v0.0.0-20230602150820-91b7bce49751/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/s2a-go v0.1.4 h1:1kZ/sQM3srePvKs3tXAvQzo66XfcReoqFpIpIccE7Oc= -github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= +github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= +github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= +github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= -github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9aurXEpJX+U6FLtpYTdC3R06k= -github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= +github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= -github.com/googleapis/gax-go/v2 v2.11.0 h1:9V9PWXEsWnPpQhu/PeQIkS4eGzMlTLGgt80cUUI8Ki4= -github.com/googleapis/gax-go/v2 v2.11.0/go.mod h1:DxmR61SGKkGLa2xigwuZIQpkCI2S5iydzRfb3peWZJI= +github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas= +github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c h1:7lF+Vz0LqiRidnzC1Oq86fpX1q/iEv2KJdrCtttYjT4= @@ -709,15 +731,15 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= 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= -github.com/holiman/uint256 v1.2.2-0.20230321075855-87b91420868c/go.mod h1:SC8Ryt4n+UBbPbIBKaG9zbbDlp4jOru9xFZmPzLUTxw= +github.com/holiman/uint256 v1.2.3 h1:K8UWO1HUJpRMXBxbmaY1Y8IAMZC/RsKB+ArEnnK4l5o= +github.com/holiman/uint256 v1.2.3/go.mod h1:SC8Ryt4n+UBbPbIBKaG9zbbDlp4jOru9xFZmPzLUTxw= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis= github.com/huin/goupnp v0.0.0-20161224104101-679507af18f3/go.mod h1:MZ2ZmwcBpvOoJ22IJsc7va19ZwoheaBk43rKg12SKag= github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc= -github.com/huin/goupnp v1.2.0 h1:uOKW26NG1hsSSbXIZ1IR7XP9Gjd1U8pnLaCMgntmkmY= -github.com/huin/goupnp v1.2.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= +github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= +github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= github.com/hydrogen18/memlistener v0.0.0-20200120041712-dcc25e7acd91/go.mod h1:qEIFzExnS6016fRpRfxrExeVn2gbClQA99gQhnIcdhE= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -853,6 +875,8 @@ github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/ github.com/iris-contrib/jade v1.1.3/go.mod h1:H/geBymxJhShH5kecoiOCSssPX7QWYH7UaeZTSWddIk= github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0GqwkjqxNd0u65g= github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw= +github.com/itchyny/gojq v0.12.14/go.mod h1:y1G7oO7XkcR1LPZO59KyoCRy08T3j9vDYRV0GgYSS+s= +github.com/itchyny/timefmt-go v0.1.5/go.mod h1:nEP7L+2YmAbT2kZ2HfSs1d8Xtw9LY8D2stDBckWakZ8= github.com/jackpal/gateway v1.0.5/go.mod h1:lTpwd4ACLXmpyiCTRtfiNyVnUmqT9RivzCDQetPfnjA= github.com/jackpal/go-nat-pmp v1.0.1/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= @@ -913,8 +937,8 @@ github.com/kataras/neffos v0.0.14/go.mod h1:8lqADm8PnbeFfL7CLXh1WHw53dG27MC3pgi2 github.com/kataras/pio v0.0.2/go.mod h1:hAoW0t9UmXi4R5Oyq5Z4irTbaTsOemSrDGUtaTl7Dro= github.com/kataras/sitemap v0.0.5/go.mod h1:KY2eugMKiPwsJgx7+U103YZehfvNGOXURubcGyk0Bz8= github.com/kevinburke/go-bindata v3.22.0+incompatible/go.mod h1:/pEEZ72flUW2p0yi30bslSp9YqD9pysLxunQDdb2CPM= -github.com/kevinburke/go-bindata v3.23.0+incompatible h1:rqNOXZlqrYhMVVAsQx8wuc+LaA73YcfbQ407wAykyS8= -github.com/kevinburke/go-bindata v3.23.0+incompatible/go.mod h1:/pEEZ72flUW2p0yi30bslSp9YqD9pysLxunQDdb2CPM= +github.com/kevinburke/go-bindata v3.24.0+incompatible h1:qajFA3D0pH94OTLU4zcCCKCDgR+Zr2cZK/RPJHDdFoY= +github.com/kevinburke/go-bindata v3.24.0+incompatible/go.mod h1:/pEEZ72flUW2p0yi30bslSp9YqD9pysLxunQDdb2CPM= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= @@ -934,6 +958,7 @@ github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa02 github.com/klauspost/cpuid/v2 v2.0.6/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= +github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -952,6 +977,7 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= @@ -960,6 +986,7 @@ github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+ github.com/labstack/echo/v4 v4.5.0/go.mod h1:czIriw4a0C1dFun+ObrXp7ok03xON0N1awStJ6ArI7Y= github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c= +github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= @@ -1173,7 +1200,6 @@ github.com/libp2p/go-yamux/v4 v4.0.0 h1:+Y80dV2Yx/kv7Y7JKu0LECyVdMXm1VUoko+VQ9rB github.com/libp2p/go-yamux/v4 v4.0.0/go.mod h1:NWjl8ZTLOGlozrXSOZ/HlfG++39iKNnM5wwmtQP1YB4= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= -github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/logrusorgru/aurora/v4 v4.0.0 h1:sRjfPpun/63iADiSvGGjgA1cAYegEWMPCJdUpJYn9JA= @@ -1221,17 +1247,19 @@ github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-tty v0.0.3/go.mod h1:ihxohKRERHTVzN+aSVRwACLCeqIoZAWpoICkkvrWyR0= +github.com/mattn/go-tty v0.0.4/go.mod h1:u5GGXBtZU6RQoKV8gY5W6UhMudbR5vXnUe7j3pxse28= github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= @@ -1284,6 +1312,9 @@ github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:F github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY= +github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU= +github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU= github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= @@ -1390,31 +1421,32 @@ github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXW github.com/olekukonko/tablewriter v0.0.2-0.20190409134802-7e037d187b0c/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= -github.com/onflow/atree v0.1.0-beta1.0.20211027184039-559ee654ece9/go.mod h1:+6x071HgCF/0v5hQcaE5qqjc2UqN5gCU8h5Mk6uqpOg= -github.com/onflow/atree v0.6.0 h1:j7nQ2r8npznx4NX39zPpBYHmdy45f4xwoi+dm37Jk7c= github.com/onflow/atree v0.6.0/go.mod h1:gBHU0M05qCbv9NN0kijLWMgC47gHVNBIp4KmsVFi0tc= -github.com/onflow/cadence v0.20.1/go.mod h1:7mzUvPZUIJztIbr9eTvs+fQjWWHTF8veC+yk4ihcNIA= -github.com/onflow/cadence v0.42.6 h1:VtI0EpKrdbfqITRMsvyZC4dhgcW1x1LNUQuEpdMDzus= -github.com/onflow/cadence v0.42.6/go.mod h1:raU8va8QRyTa/eUbhej4mbyW2ETePfSaywoo36MddgE= -github.com/onflow/flow-core-contracts/lib/go/contracts v1.2.4-0.20231016154253-a00dbf7c061f h1:S8yIZw9LFXfYD1V5H9BiixihHw3GrXVPrmfplSzYaww= -github.com/onflow/flow-core-contracts/lib/go/contracts v1.2.4-0.20231016154253-a00dbf7c061f/go.mod h1:jM6GMAL+m0hjusUgiYDNrixPQ6b9s8xjoJQoEu5bHQI= +github.com/onflow/atree v0.6.1-0.20230711151834-86040b30171f h1:Z8/PgTqOgOg02MTRpTBYO2k16FE6z4wEOtaC2WBR9Xo= +github.com/onflow/atree v0.6.1-0.20230711151834-86040b30171f/go.mod h1:xvP61FoOs95K7IYdIYRnNcYQGf4nbF/uuJ0tHf4DRuM= +github.com/onflow/cadence v0.42.8 h1:6OWhKMjL2Lql6a3L2xeA44uoOAVBp5+cMNE5BDPvo+E= +github.com/onflow/cadence v0.42.8/go.mod h1:1wFd+LiNiN6qoZXof3MBdpM6d8BsxbVIxOA77LbIYmE= +github.com/onflow/crypto v0.25.0 h1:BeWbLsh3ZD13Ej+Uky6kg1PL1ZIVBDVX+2MVBNwqddg= +github.com/onflow/crypto v0.25.0/go.mod h1:C8FbaX0x8y+FxWjbkHy0Q4EASCDR9bSPWZqlpCLYyVI= +github.com/onflow/flow-core-contracts/lib/go/contracts v0.15.1-0.20231219201108-fbdb10b0a2da h1:8CEioYNnP0rwjnRbKDgs8SmiQTsdaroeX4d/Q3pQuh4= +github.com/onflow/flow-core-contracts/lib/go/contracts v0.15.1-0.20231219201108-fbdb10b0a2da/go.mod h1:WHp24VkUQfcfZi0XjI1uRVRt5alM5SHVkwOil1U2Tpc= github.com/onflow/flow-core-contracts/lib/go/templates v1.2.4-0.20231016154253-a00dbf7c061f h1:Ep+Mpo2miWMe4pjPGIaEvEzshRep30dvNgxqk+//FrQ= github.com/onflow/flow-core-contracts/lib/go/templates v1.2.4-0.20231016154253-a00dbf7c061f/go.mod h1:ZeLxwaBkzuSInESGjL8/IPZWezF+YOYsYbMrZlhN+q4= -github.com/onflow/flow-emulator v0.58.1-0.20231130142844-f22e54339f85 h1:GWAZqWQmckvmvGtoFxpM1q+LMTNUT3DKxHnl266Ke9A= -github.com/onflow/flow-emulator v0.58.1-0.20231130142844-f22e54339f85/go.mod h1:Iv+lFLKbN4aGZeFOlrF7v7LIjpclfrdyGLsOTNXyLUQ= +github.com/onflow/flow-emulator v0.58.1-0.20240118140159-d334a0fcd380 h1:bne/jKVCNEB9IhAT1QoRHzPkcNA06qN+rnIyYlFc3vk= +github.com/onflow/flow-emulator v0.58.1-0.20240118140159-d334a0fcd380/go.mod h1:YBUnOmciqFV5ADgzY08/YkPyuuIv96hHmSt9tOzm4vg= github.com/onflow/flow-ft/lib/go/contracts v0.7.1-0.20230711213910-baad011d2b13 h1:B4ll7e3j+MqTJv2122Enq3RtDNzmIGRu9xjV7fo7un0= github.com/onflow/flow-ft/lib/go/contracts v0.7.1-0.20230711213910-baad011d2b13/go.mod h1:kTMFIySzEJJeupk+7EmXs0EJ6CBWY/MV9fv9iYQk+RU= github.com/onflow/flow-go-sdk v0.24.0/go.mod h1:IoptMLPyFXWvyd9yYA6/4EmSeeozl6nJoIv4FaEMg74= -github.com/onflow/flow-go-sdk v0.41.17 h1:HpNn3j2fqLGA6H3HGfAuh2A+TsPBv8gWO3kvK9Hvtic= -github.com/onflow/flow-go-sdk v0.41.17/go.mod h1:ZIj2XBI9R0QiKzbI6iPwOeqyIy/M4+atczoMOEWdKYw= +github.com/onflow/flow-go-sdk v0.44.0 h1:gVRLcZ6LUNs/5mzHDx0mp4mEnBAWD62O51P4/nYm4rE= +github.com/onflow/flow-go-sdk v0.44.0/go.mod h1:mm1Fi2hiMrexNMwRzTrAN2zwTvlP8iQ5CF2JSAgJR8U= github.com/onflow/flow-go/crypto v0.21.3/go.mod h1:vI6V4CY3R6c4JKBxdcRiR/AnjBfL8OSD97bJc60cLuQ= github.com/onflow/flow-go/crypto v0.25.0 h1:6lmoiAQ3APCF+nV7f4f2AXL3PuDKqQiWqRJXmjrMEq4= github.com/onflow/flow-go/crypto v0.25.0/go.mod h1:OOb2vYcS8AOCajBClhHTJ0NKftFl1RQgTQ0+Vh4nbqk= github.com/onflow/flow-nft/lib/go/contracts v1.1.0 h1:rhUDeD27jhLwOqQKI/23008CYfnqXErrJvc4EFRP2a0= github.com/onflow/flow-nft/lib/go/contracts v1.1.0/go.mod h1:YsvzYng4htDgRB9sa9jxdwoTuuhjK8WYWXTyLkIigZY= github.com/onflow/flow/protobuf/go/flow v0.2.2/go.mod h1:gQxYqCfkI8lpnKsmIjwtN2mV/N2PIwc1I+RUK4HPIc8= -github.com/onflow/flow/protobuf/go/flow v0.3.2-0.20231124194313-106cc495def6 h1:KMN+OEVaw7KAgxL3p8ux7CMuyTvacAlYTbasOqowh4M= -github.com/onflow/flow/protobuf/go/flow v0.3.2-0.20231124194313-106cc495def6/go.mod h1:NA2pX2nw8zuaxfKphhKsk00kWLwfd+tv8mS23YXO4Sk= +github.com/onflow/flow/protobuf/go/flow v0.3.2-0.20231213135419-ae911cc351a2 h1:+rT+UsfTR39JZO8ht2+4fkaWfHw74SCj1fyz1lWuX8A= +github.com/onflow/flow/protobuf/go/flow v0.3.2-0.20231213135419-ae911cc351a2/go.mod h1:NA2pX2nw8zuaxfKphhKsk00kWLwfd+tv8mS23YXO4Sk= github.com/onflow/go-bitswap v0.0.0-20230703214630-6d3db958c73d h1:QcOAeEyF3iAUHv21LQ12sdcsr0yFrJGoGLyCAzYYtvI= github.com/onflow/go-bitswap v0.0.0-20230703214630-6d3db958c73d/go.mod h1:GCPpiyRoHncdqPj++zPr9ZOYBX4hpJ0pYZRYqSE8VKk= github.com/onflow/nft-storefront/lib/go/contracts v0.0.0-20221222181731-14b90207cead h1:2j1Unqs76Z1b95Gu4C3Y28hzNUHBix7wL490e61SMSw= @@ -1487,7 +1519,7 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= -github.com/pkg/term v1.1.0/go.mod h1:E25nymQcrSllhX42Ok8MRm1+hyBdHY0dCeiKZ9jpNGw= +github.com/pkg/term v1.2.0-beta.2/go.mod h1:E25nymQcrSllhX42Ok8MRm1+hyBdHY0dCeiKZ9jpNGw= github.com/plus3it/gorecurcopy v0.0.1 h1:H7AgvM0N/uIo7o1PQRlewEGQ92BNr7DqbPy5lnR3uJI= github.com/plus3it/gorecurcopy v0.0.1/go.mod h1:NvVTm4RX68A1vQbHmHunDO4OtBLVroT6CrsiqAzNyJA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -1589,13 +1621,13 @@ github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g= -github.com/schollz/progressbar/v3 v3.8.3/go.mod h1:pWnVCjSBZsT2X3nx9HfRdnCDrpbevliMeoEVhStwHko= github.com/schollz/progressbar/v3 v3.13.1 h1:o8rySDYiQ59Mwzy2FELeHY5ZARXZTVJC7iHD6PEFUiE= github.com/schollz/progressbar/v3 v3.13.1/go.mod h1:xvrbki8kfT1fzWzBT/UZd9L6GA+jdL7HAgq2RFnO6fQ= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= +github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sethvargo/go-retry v0.2.3 h1:oYlgvIvsju3jNbottWABtbnoLC+GDtLdBHxKWxQm/iU= github.com/sethvargo/go-retry v0.2.3/go.mod h1:1afjQuvh7s4gflMObvjLPaWgluLLyhA1wmVZ6KLpICw= github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1:Bn1aCHHRnjv4Bl16T8rcaFjYSrGrIZvpiGO6P3Q4GpU= @@ -1657,8 +1689,8 @@ github.com/spaolacci/murmur3 v1.0.1-0.20190317074736-539464a789e9/go.mod h1:JwIa 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.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= -github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= +github.com/spf13/afero v1.10.0 h1:EaGW2JJh15aKOejeuJ+wpFSHnbd7GE6Wvp3TsNhb6LY= +github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= @@ -1704,6 +1736,8 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/supranational/blst v0.3.4/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= +github.com/supranational/blst v0.3.11 h1:LyU6FolezeWAhvQk0k6O/d49jqgO52MSDDfYgbeoEm4= +github.com/supranational/blst v0.3.11/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d/go.mod h1:9OrXJhf154huy1nPWmuSrkgjPUtUNhA+Zmy+6AESzuA= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= @@ -1711,10 +1745,13 @@ github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45 github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= github.com/texttheater/golang-levenshtein/levenshtein v0.0.0-20200805054039-cae8b0eaed6c h1:HelZ2kAFadG0La9d+4htN4HzQ68Bm2iM9qKMSMES6xg= github.com/texttheater/golang-levenshtein/levenshtein v0.0.0-20200805054039-cae8b0eaed6c/go.mod h1:JlzghshsemAMDGZLytTFY8C1JQxQPhnatWqNwUXjggo= -github.com/tklauser/go-sysconf v0.3.9 h1:JeUVdAOWhhxVcU6Eqr/ATFHgXk/mmiItdKeJPev3vTo= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tklauser/go-sysconf v0.3.9/go.mod h1:11DU/5sG7UexIrp/O6g35hrWzu0JxlwQ3LSFUzyeuhs= -github.com/tklauser/numcpus v0.3.0 h1:ILuRUQBtssgnxw0XXIjKUC56fgnOrFoQQ/4+DeU2biQ= +github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= +github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= github.com/tklauser/numcpus v0.3.0/go.mod h1:yFGUr7TUHQRAhyqBcEg0Ge34zDBAsIvJJcyE6boqnA8= +github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/turbolent/prettier v0.0.0-20220320183459-661cc755135d h1:5JInRQbk5UBX8JfUvKh2oYTLMVwj3p6n+wapDDm7hko= @@ -1782,15 +1819,15 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= -github.com/zeebo/blake3 v0.2.0/go.mod h1:G9pM4qQwjRzF1/v7+vabMj/c5mWpGZ2Wzo3Eb4z0pb4= +github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg= github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ= -github.com/zeebo/pcg v1.0.0/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo= github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= @@ -1813,6 +1850,7 @@ go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/otel v1.14.0/go.mod h1:o4buv+dJzx8rohcUeRmWUZhqupFvzWis188WlggnNeU= go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s= go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4= go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.16.0 h1:t4ZwRPU+emrcvM2e9DHd0Fsf0JTPVcbfa/BhTDF03d0= @@ -1825,6 +1863,7 @@ go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26 go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4= go.opentelemetry.io/otel/sdk v1.16.0 h1:Z1Ok1YsijYL0CSJpHt4cS3wDDh7p572grzNrBMiMWgE= go.opentelemetry.io/otel/sdk v1.16.0/go.mod h1:tMsIuKXuuIWPBAOrH+eHtvhTL+SntFtXF9QD68aP6p4= +go.opentelemetry.io/otel/trace v1.14.0/go.mod h1:8avnQLK+CG77yNLUae4ea2JDQ6iT+gozhnZjy/rw9G8= go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs= go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= @@ -1842,9 +1881,9 @@ go.uber.org/dig v1.17.0/go.mod h1:rTxpf7l5I0eBTlE6/9RL+lDybC7WFwY2QH55ZSjy1mU= go.uber.org/fx v1.19.2 h1:SyFgYQFr1Wl0AYstE8vyYIzP4bFz2URrScjwC4cwUvY= go.uber.org/fx v1.19.2/go.mod h1:43G1VcqSzbIv77y00p1DRAsyZS8WdzuYdhZXmEUkMyQ= go.uber.org/goleak v1.0.0/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= -go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= 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/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= @@ -1895,17 +1934,16 @@ 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-20210506145944-38f3c27a63bf/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= -golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 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.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= -golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +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-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1919,8 +1957,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-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug= -golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc h1:ao2WRsKSzW6KuUY9IWPwWahcHCgR0s52IfwutMfEbdM= +golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= 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= @@ -1948,11 +1986,13 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 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.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= -golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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= @@ -2014,6 +2054,7 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211008194852-3b03d305991f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= @@ -2021,9 +2062,10 @@ golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= -golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= +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-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -2042,8 +2084,8 @@ golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.11.0 h1:vPL4xzxBM4niKCW6g9whtaWVXTJf1U5e4aZxxFx/gbU= -golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk= +golang.org/x/oauth2 v0.13.0 h1:jDDenyj+WgFtmV3zYVoi8aE2BwtXFLWOA67ZfNWftiY= +golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0= golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -2058,8 +2100,9 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ 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.1.0/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-20180810173357-98c5dad5d1a0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -2131,7 +2174,6 @@ golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200918174421-af09f7315aff/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201014080544-cc95f250f6bc/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -2147,7 +2189,6 @@ golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210317225723-c4fcb01b228e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -2163,16 +2204,17 @@ golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210910150752-751e447fb3d0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211025112917-711f33c9992c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -2185,11 +2227,12 @@ golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.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-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -2197,8 +2240,8 @@ golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0= -golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= +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= @@ -2208,12 +2251,12 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 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.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= -golang.org/x/text v0.12.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-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 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= @@ -2279,7 +2322,6 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200828161849-5deb26317202/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= @@ -2293,11 +2335,13 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= -golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= +golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= +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= @@ -2344,8 +2388,8 @@ google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqiv google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= google.golang.org/api v0.58.0/go.mod h1:cAbP2FsxoGVNwtgNAmmn3y5G1TWAiVYRmg4yku3lv+E= -google.golang.org/api v0.126.0 h1:q4GJq+cAdMAC7XP7njvQ4tvohGLiSlytuL4BQxbIZ+o= -google.golang.org/api v0.126.0/go.mod h1:mBwVAtz+87bEN6CbA1GtZPDOqY2R5ONPqJeIlvyo4Aw= +google.golang.org/api v0.151.0 h1:FhfXLO/NFdJIzQtCqjpysWwqKk8AzGWBUhMIx67cVDU= +google.golang.org/api v0.151.0/go.mod h1:ccy+MJ6nrYFgE3WgRx/AMXOxOmU8Q4hSa+jjibzhxcg= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -2424,13 +2468,13 @@ google.golang.org/genproto v0.0.0-20210921142501-181ce0d877f6/go.mod h1:5CzLGKJ6 google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211007155348-82e027067bd4/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d h1:VBu5YqKPv6XiJ199exd8Br+Aetz+o08F+PLMnwJQHAY= -google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d/go.mod h1:yZTlhN0tQnXo3h00fuXNCxJdLdIdnVFVBaRJ5LWBbw4= -google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d h1:DoPTO70H+bcDXcd39vOqb2viZxgqeBeSGtZ55yZU4/Q= -google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk= -google.golang.org/genproto/googleapis/bytestream v0.0.0-20230530153820-e85fd2cbaebc h1:g3hIDl0jRNd9PPTs2uBzYuaD5mQuwOkZY0vSc0LR32o= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d h1:uvYuEyMHKNt+lT4K3bN6fGswmK8qSvcreM3BwjDh+y4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= +google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b h1:+YaDE2r2OG8t/z5qmsh7Y+XXwCbvadxxZ0YY6mTdrVA= +google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:CgAqfJo+Xmu0GwA0411Ht3OU3OntXwsGmrmjI8ioGXI= +google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b h1:CIC2YMXmIhYw6evmhPxBKJ4fmLbOFtXQN/GV3XOZR8k= +google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:IBQ646DjkDkvUIsVq/cc03FUFQ9wbZu7yE396YcL870= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20231030173426-d783a09b4405 h1:o4S3HvTUEXgRsNSUQsALDVog0O9F/U1JJlHmmUN8Uas= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405 h1:AB/lmRny7e2pLhFEYIbl5qkDAUt2h0ZRO4wGPhZf+ik= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405/go.mod h1:67X1fPuzjcrkymZzZV1vvkFeTn2Rvc6lYF9MYFGCcwE= google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= @@ -2466,7 +2510,6 @@ google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnD google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= @@ -2492,6 +2535,7 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= @@ -2505,11 +2549,11 @@ gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= -gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= gopkg.in/olebedev/go-duktape.v3 v3.0.0-20190213234257-ec84240a7772/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78= +gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98= gopkg.in/src-d/go-cli.v0 v0.0.0-20181105080154-d492247bbc0d/go.mod h1:z+K8VcOYVYcSwSjGebuDL6176A1XskgbtNl64NSg+n8= gopkg.in/src-d/go-log.v1 v1.0.1/go.mod h1:GN34hKP0g305ysm2/hctJ0Y8nWP3zxXXJ8GFabTyABE= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= @@ -2547,6 +2591,7 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +lukechampine.com/blake3 v1.1.7/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA= lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI= lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k= modernc.org/libc v1.22.3 h1:D/g6O5ftAfavceqlLOFwaZuA5KYafKwmr30A6iSqoyY= @@ -2563,6 +2608,8 @@ rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8 rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU= +rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck= diff --git a/integration/localnet/Makefile b/integration/localnet/Makefile index d889d22e150..34e4a687e77 100644 --- a/integration/localnet/Makefile +++ b/integration/localnet/Makefile @@ -7,9 +7,10 @@ VERIFICATION = 1 ACCESS = 1 OBSERVER = 0 NCLUSTERS=1 -EPOCHLEN=10000 # 0 means use default -STAKINGLEN=2000 # 0 means use default -DKGLEN=2000 # 0 means use default +EPOCHLEN=10000 # 0 means use default +STAKINGLEN=2000 # 0 means use default +DKGLEN=2000 # 0 means use default +EPOCHCOMMITSAFETYTHRESHOLD=1000 # 0 means use default CONSENSUS_DELAY=800ms COLLECTION_DELAY=950ms @@ -62,6 +63,7 @@ else -epoch-length=$(EPOCHLEN) \ -epoch-staking-phase-length=$(STAKINGLEN) \ -epoch-dkg-phase-length=$(DKGLEN) \ + -epoch-commit-safety-threshold=$(EPOCHCOMMITSAFETYTHRESHOLD) \ -profiler=$(PROFILER) \ -profile-uploader=$(PROFILE_UPLOADER) \ -tracing=$(TRACING) \ @@ -85,7 +87,7 @@ bootstrap-ci: # Creates a version of localnet configured with short epochs .PHONY: bootstrap-short-epochs bootstrap-short-epochs: - $(MAKE) -e EPOCHLEN=200 STAKINGLEN=10 DKGLEN=50 bootstrap + $(MAKE) -e EPOCHLEN=200 STAKINGLEN=5 DKGLEN=50 EPOCHCOMMITSAFETYTHRESHOLD=20 bootstrap # Starts the network - must have been bootstrapped first. Builds fresh images. .PHONY: start diff --git a/integration/localnet/builder/bootstrap.go b/integration/localnet/builder/bootstrap.go index 1aeee814b1d..f505ee0a952 100644 --- a/integration/localnet/builder/bootstrap.go +++ b/integration/localnet/builder/bootstrap.go @@ -51,24 +51,25 @@ const ( ) var ( - collectionCount int - consensusCount int - executionCount int - verificationCount int - accessCount int - observerCount int - nClusters uint - numViewsInStakingPhase uint64 - numViewsInDKGPhase uint64 - numViewsEpoch uint64 - profiler bool - profileUploader bool - tracing bool - cadenceTracing bool - extesiveTracing bool - consensusDelay time.Duration - collectionDelay time.Duration - logLevel string + collectionCount int + consensusCount int + executionCount int + verificationCount int + accessCount int + observerCount int + nClusters uint + numViewsInStakingPhase uint64 + numViewsInDKGPhase uint64 + numViewsEpoch uint64 + epochCommitSafetyThreshold uint64 + profiler bool + profileUploader bool + tracing bool + cadenceTracing bool + extesiveTracing bool + consensusDelay time.Duration + collectionDelay time.Duration + logLevel string ports *PortAllocator ) @@ -84,6 +85,7 @@ func init() { flag.Uint64Var(&numViewsEpoch, "epoch-length", 10000, "number of views in epoch") flag.Uint64Var(&numViewsInStakingPhase, "epoch-staking-phase-length", 2000, "number of views in epoch staking phase") flag.Uint64Var(&numViewsInDKGPhase, "epoch-dkg-phase-length", 2000, "number of views in epoch dkg phase") + flag.Uint64Var(&epochCommitSafetyThreshold, "epoch-commit-safety-threshold", 1000, "number of views for safety threshold T (assume: one finalization occurs within T blocks)") flag.BoolVar(&profiler, "profiler", DefaultProfiler, "whether to enable the auto-profiler") flag.BoolVar(&profileUploader, "profile-uploader", DefaultProfileUploader, "whether to upload profiles to the cloud") flag.BoolVar(&tracing, "tracing", DefaultTracing, "whether to enable low-overhead tracing in flow") @@ -129,6 +131,9 @@ func main() { if numViewsInDKGPhase != 0 { flowNetworkOpts = append(flowNetworkOpts, testnet.WithViewsInDKGPhase(numViewsInDKGPhase)) } + if epochCommitSafetyThreshold != 0 { + flowNetworkOpts = append(flowNetworkOpts, testnet.WithEpochCommitSafetyThreshold(epochCommitSafetyThreshold)) + } flowNetworkConf := testnet.NewNetworkConfig("localnet", flowNodes, flowNetworkOpts...) displayFlowNetworkConf(flowNetworkConf) diff --git a/integration/testnet/client.go b/integration/testnet/client.go index 5e760cf1256..ff16277109c 100644 --- a/integration/testnet/client.go +++ b/integration/testnet/client.go @@ -366,7 +366,7 @@ func (c *Client) CreateAccount( if err != nil { return sdk.Address{}, fmt.Errorf("failed cusnctruct create account transaction %w", err) } - tx.SetGasLimit(1000). + tx.SetComputeLimit(1000). SetReferenceBlockID(latestBlockID). SetProposalKey(payer, 0, c.GetSeqNumber()). SetPayer(payer) diff --git a/integration/testnet/container.go b/integration/testnet/container.go index f3612e11996..4fc1f44a7d5 100644 --- a/integration/testnet/container.go +++ b/integration/testnet/container.go @@ -13,6 +13,7 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" "github.com/docker/go-connections/nat" + "github.com/onflow/crypto" "github.com/rs/zerolog" "github.com/stretchr/testify/require" "google.golang.org/grpc" @@ -22,7 +23,6 @@ import ( sdkclient "github.com/onflow/flow-go-sdk/access/grpc" "github.com/onflow/flow-go/cmd/bootstrap/utils" - "github.com/onflow/flow-go/crypto" ghostclient "github.com/onflow/flow-go/engine/ghost/client" "github.com/onflow/flow-go/integration/client" "github.com/onflow/flow-go/model/bootstrap" diff --git a/integration/testnet/network.go b/integration/testnet/network.go index e47e94da3a3..0bd4a215827 100644 --- a/integration/testnet/network.go +++ b/integration/testnet/network.go @@ -42,7 +42,6 @@ import ( "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/model/flow/factory" "github.com/onflow/flow-go/model/flow/filter" - "github.com/onflow/flow-go/model/flow/order" "github.com/onflow/flow-go/module/epochs" "github.com/onflow/flow-go/module/signature" "github.com/onflow/flow-go/network/p2p/keyutils" @@ -110,9 +109,10 @@ const ( // PrimaryAN is the container name for the primary access node to use for API requests PrimaryAN = "access_1" - DefaultViewsInStakingAuction uint64 = 5 - DefaultViewsInDKGPhase uint64 = 50 - DefaultViewsInEpoch uint64 = 180 + DefaultViewsInStakingAuction uint64 = 5 + DefaultViewsInDKGPhase uint64 = 50 + DefaultViewsInEpoch uint64 = 200 + DefaultEpochCommitSafetyThreshold uint64 = 20 // DefaultMinimumNumOfAccessNodeIDS at-least 1 AN ID must be configured for LN & SN DefaultMinimumNumOfAccessNodeIDS = 1 @@ -429,12 +429,13 @@ type NetworkConfigOpt func(*NetworkConfig) func NewNetworkConfig(name string, nodes NodeConfigs, opts ...NetworkConfigOpt) NetworkConfig { c := NetworkConfig{ - Nodes: nodes, - Name: name, - NClusters: 1, // default to 1 cluster - ViewsInStakingAuction: DefaultViewsInStakingAuction, - ViewsInDKGPhase: DefaultViewsInDKGPhase, - ViewsInEpoch: DefaultViewsInEpoch, + Nodes: nodes, + Name: name, + NClusters: 1, // default to 1 cluster + ViewsInStakingAuction: DefaultViewsInStakingAuction, + ViewsInDKGPhase: DefaultViewsInDKGPhase, + ViewsInEpoch: DefaultViewsInEpoch, + EpochCommitSafetyThreshold: DefaultEpochCommitSafetyThreshold, } for _, apply := range opts { @@ -1043,7 +1044,7 @@ func BootstrapNetwork(networkConf NetworkConfig, bootstrapDir string, chainID fl // IMPORTANT: we must use this ordering when writing the DKG keys as // this ordering defines the DKG participant's indices - stakedNodeInfos := bootstrap.Sort(toNodeInfos(stakedConfs), order.Canonical) + stakedNodeInfos := bootstrap.Sort(toNodeInfos(stakedConfs), flow.Canonical) dkg, err := runBeaconKG(stakedConfs) if err != nil { @@ -1310,7 +1311,7 @@ func runBeaconKG(confs []ContainerConfig) (dkgmod.DKGData, error) { func setupClusterGenesisBlockQCs(nClusters uint, epochCounter uint64, confs []ContainerConfig) ([]*cluster.Block, flow.AssignmentList, []*flow.QuorumCertificate, error) { participantsUnsorted := toParticipants(confs) - participants := participantsUnsorted.Sort(order.Canonical) + participants := participantsUnsorted.Sort(flow.Canonical) collectors := participants.Filter(filter.HasRole(flow.RoleCollection)) assignments := unittest.ClusterAssignment(nClusters, collectors) clusters, err := factory.NewClusterList(assignments, collectors) @@ -1343,7 +1344,7 @@ func setupClusterGenesisBlockQCs(nClusters uint, epochCounter uint64, confs []Co } // must order in canonical ordering otherwise decoding signer indices from cluster QC would fail - clusterCommittee := bootstrap.ToIdentityList(clusterNodeInfos).Sort(order.Canonical) + clusterCommittee := bootstrap.ToIdentityList(clusterNodeInfos).Sort(flow.Canonical) qc, err := run.GenerateClusterRootQC(clusterNodeInfos, clusterCommittee, block) if err != nil { return nil, nil, nil, fmt.Errorf("fail to generate cluster root QC with clusterNodeInfos %v, %w", diff --git a/integration/testnet/node_config.go b/integration/testnet/node_config.go index bf1469a0c20..011d280ac2a 100644 --- a/integration/testnet/node_config.go +++ b/integration/testnet/node_config.go @@ -149,3 +149,10 @@ func WithAdditionalFlag(flag string) func(config *NodeConfig) { func WithAdditionalFlagf(format string, a ...any) func(config *NodeConfig) { return WithAdditionalFlag(fmt.Sprintf(format, a...)) } + +// WithMetricsServer exposes the metrics server +func WithMetricsServer() func(config *NodeConfig) { + return func(config *NodeConfig) { + config.EnableMetricsServer = true + } +} diff --git a/integration/testnet/util.go b/integration/testnet/util.go index 52ab6af17a0..181e6dddd4e 100644 --- a/integration/testnet/util.go +++ b/integration/testnet/util.go @@ -11,11 +11,11 @@ import ( "path/filepath" "testing" + "github.com/onflow/crypto" "github.com/stretchr/testify/require" "github.com/onflow/flow-go/cmd/bootstrap/cmd" "github.com/onflow/flow-go/cmd/bootstrap/utils" - "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/model/bootstrap" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/state/protocol/inmem" diff --git a/integration/tests/access/cohort1/access_api_test.go b/integration/tests/access/cohort1/access_api_test.go index 24409f84ad2..d849c8bef90 100644 --- a/integration/tests/access/cohort1/access_api_test.go +++ b/integration/tests/access/cohort1/access_api_test.go @@ -72,7 +72,7 @@ func (s *AccessAPISuite) SetupTest() { flow.RoleAccess, testnet.WithLogLevel(zerolog.FatalLevel), // make sure test continues to test as expected if the default config changes - testnet.WithAdditionalFlagf("--script-execution-mode=%s", backend.ScriptExecutionModeExecutionNodesOnly), + testnet.WithAdditionalFlagf("--script-execution-mode=%s", backend.IndexQueryModeExecutionNodesOnly), ) indexingAccessConfig := testnet.NewNodeConfig( @@ -83,7 +83,7 @@ func (s *AccessAPISuite) SetupTest() { testnet.WithAdditionalFlag("--execution-data-retry-delay=1s"), testnet.WithAdditionalFlag("--execution-data-indexing-enabled=true"), testnet.WithAdditionalFlagf("--execution-state-dir=%s", testnet.DefaultExecutionStateDir), - testnet.WithAdditionalFlagf("--script-execution-mode=%s", backend.ScriptExecutionModeLocalOnly), + testnet.WithAdditionalFlagf("--script-execution-mode=%s", backend.IndexQueryModeLocalOnly), ) consensusConfigs := []func(config *testnet.NodeConfig){ @@ -324,7 +324,7 @@ func (s *AccessAPISuite) deployContract() *sdk.TransactionResult { SetProposalKey(serviceAddress, 0, s.serviceClient.GetSeqNumber()). SetPayer(serviceAddress). AddAuthorizer(serviceAddress). - SetGasLimit(9999) + SetComputeLimit(9999) err = s.serviceClient.SignAndSendTransaction(s.ctx, createCounterTx) s.Require().NoError(err) diff --git a/integration/tests/access/cohort2/observer_test.go b/integration/tests/access/cohort2/observer_test.go index 755ab087c63..c73d3999a95 100644 --- a/integration/tests/access/cohort2/observer_test.go +++ b/integration/tests/access/cohort2/observer_test.go @@ -402,7 +402,7 @@ func (s *ObserverSuite) getRestEndpoints() []RestEndpointTest { block := unittest.BlockFixture() executionResult := unittest.ExecutionResultFixture() collection := unittest.CollectionFixture(2) - eventType := "A.0123456789abcdef.flow.event" + eventType := unittest.EventTypeFixture(flow.Localnet) return []RestEndpointTest{ { diff --git a/integration/tests/access/cohort3/access_circuit_breaker_test.go b/integration/tests/access/cohort3/access_circuit_breaker_test.go index 5a4f0f25715..55537fc0fad 100644 --- a/integration/tests/access/cohort3/access_circuit_breaker_test.go +++ b/integration/tests/access/cohort3/access_circuit_breaker_test.go @@ -150,7 +150,7 @@ func (s *AccessCircuitBreakerSuite) TestCircuitBreaker() { SetReferenceBlockID(sdk.Identifier(latestBlockID)). SetProposalKey(serviceAddress, 0, accessClient.GetSeqNumber()). SetPayer(serviceAddress). - SetGasLimit(9999) + SetComputeLimit(9999) // Sign the transaction signedTx, err := accessClient.SignTransaction(createAccountTx) diff --git a/integration/tests/access/cohort3/collection_indexing_test.go b/integration/tests/access/cohort3/collection_indexing_test.go new file mode 100644 index 00000000000..dcf23e175ab --- /dev/null +++ b/integration/tests/access/cohort3/collection_indexing_test.go @@ -0,0 +1,120 @@ +package cohort3 + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/rs/zerolog" + "github.com/stretchr/testify/suite" + + "github.com/onflow/flow-go/integration/testnet" + "github.com/onflow/flow-go/model/flow" +) + +// This suite tests collection syncing using the ingestion engine and the indexer. + +const lastFullBlockMetric = "access_ingestion_last_full_finalized_block_height" + +func TestCollectionIndexing(t *testing.T) { + suite.Run(t, new(CollectionIndexingSuite)) +} + +type CollectionIndexingSuite struct { + suite.Suite + net *testnet.FlowNetwork + + cancel context.CancelFunc +} + +func (s *CollectionIndexingSuite) SetupTest() { + // access_1 is not running the indexer, so all collections are indexed using the ingestion engine + defaultAccessOpts := []func(config *testnet.NodeConfig){ + testnet.WithLogLevel(zerolog.FatalLevel), + testnet.WithAdditionalFlag("--execution-data-sync-enabled=true"), + testnet.WithAdditionalFlagf("--execution-data-dir=%s", testnet.DefaultExecutionDataServiceDir), + testnet.WithAdditionalFlagf("--execution-state-dir=%s", testnet.DefaultExecutionStateDir), + testnet.WithMetricsServer(), + } + // access_2 is running the indexer, so all collections are indexed using the indexer + testANOpts := append(defaultAccessOpts, + testnet.WithLogLevel(zerolog.DebugLevel), + testnet.WithAdditionalFlag("--execution-data-indexing-enabled=true"), + ) + + nodeConfigs := []testnet.NodeConfig{ + testnet.NewNodeConfig(flow.RoleCollection, testnet.WithLogLevel(zerolog.FatalLevel)), + testnet.NewNodeConfig(flow.RoleCollection, testnet.WithLogLevel(zerolog.FatalLevel)), + testnet.NewNodeConfig(flow.RoleExecution, testnet.WithLogLevel(zerolog.FatalLevel)), + testnet.NewNodeConfig(flow.RoleExecution, testnet.WithLogLevel(zerolog.FatalLevel)), + testnet.NewNodeConfig(flow.RoleConsensus, testnet.WithLogLevel(zerolog.FatalLevel)), + testnet.NewNodeConfig(flow.RoleConsensus, testnet.WithLogLevel(zerolog.FatalLevel)), + testnet.NewNodeConfig(flow.RoleConsensus, testnet.WithLogLevel(zerolog.FatalLevel)), + testnet.NewNodeConfig(flow.RoleVerification, testnet.WithLogLevel(zerolog.FatalLevel)), + testnet.NewNodeConfig(flow.RoleAccess, defaultAccessOpts...), + testnet.NewNodeConfig(flow.RoleAccess, testANOpts...), + } + + // prepare the network + conf := testnet.NewNetworkConfig("access_collection_indexing_test", nodeConfigs) + s.net = testnet.PrepareFlowNetwork(s.T(), conf, flow.Localnet) + + // start the network + ctx, cancel := context.WithCancel(context.Background()) + s.cancel = cancel + + s.net.Start(ctx) +} + +func (s *CollectionIndexingSuite) TearDownTest() { + if s.net != nil { + s.net.Remove() + s.net = nil + } + if s.cancel != nil { + s.cancel() + s.cancel = nil + } +} + +func (s *CollectionIndexingSuite) Test() { + // start the network with access_2 disconnected. + // this simulates it falling behind on syncing collections + access2 := s.net.ContainerByName("access_2") + s.Require().NoError(access2.Disconnect()) + + // wait for access_1 to sync collections + targetBlockCount := uint64(50) + s.Eventually(func() bool { + value, err := s.getLastFullHeight("access_1") + s.T().Logf("access_1 last full height: %d", value) + return err == nil && value > targetBlockCount + }, 60*time.Second, 1*time.Second) + + // stop the collection nodes + // this will prevent access_2 from syncing collections from the network + s.Require().NoError(s.net.ContainerByName("collection_1").Pause()) + s.Require().NoError(s.net.ContainerByName("collection_2").Pause()) + + // now start access_2, and wait for it to catch up with collections + s.Require().NoError(access2.Connect()) + + s.Eventually(func() bool { + value, err := s.getLastFullHeight("access_2") + s.T().Logf("access_2 last full height: %d", value) + return err == nil && value > targetBlockCount + }, 60*time.Second, 1*time.Second) +} + +func (s *CollectionIndexingSuite) getLastFullHeight(containerName string) (uint64, error) { + node := s.net.ContainerByName(containerName) + metricsURL := fmt.Sprintf("http://0.0.0.0:%s/metrics", node.Port(testnet.MetricsPort)) + values := s.net.GetMetricFromContainer(s.T(), containerName, metricsURL, lastFullBlockMetric) + + if len(values) == 0 { + return 0, fmt.Errorf("no values found") + } + + return uint64(values[0].GetGauge().GetValue()), nil +} diff --git a/integration/tests/access/cohort3/consensus_follower_test.go b/integration/tests/access/cohort3/consensus_follower_test.go index d227a09ad2e..26817eeef69 100644 --- a/integration/tests/access/cohort3/consensus_follower_test.go +++ b/integration/tests/access/cohort3/consensus_follower_test.go @@ -10,9 +10,10 @@ import ( "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" + "github.com/onflow/crypto" + "github.com/onflow/flow-go/cmd/bootstrap/utils" "github.com/onflow/flow-go/consensus/hotstuff/model" - "github.com/onflow/flow-go/crypto" consensus_follower "github.com/onflow/flow-go/follower" "github.com/onflow/flow-go/integration/testnet" "github.com/onflow/flow-go/model/flow" @@ -117,7 +118,6 @@ func (s *ConsensusFollowerSuite) TestReceiveBlocks() { func (s *ConsensusFollowerSuite) buildNetworkConfig() { // staked access node - unittest.IdentityFixture() s.stakedID = unittest.IdentifierFixture() stakedConfig := testnet.NewNodeConfig( flow.RoleAccess, diff --git a/integration/tests/access/cohort3/rest_state_stream_test.go b/integration/tests/access/cohort3/rest_state_stream_test.go index ad6eab9d8b5..6e22e7d0390 100644 --- a/integration/tests/access/cohort3/rest_state_stream_test.go +++ b/integration/tests/access/cohort3/rest_state_stream_test.go @@ -60,8 +60,10 @@ func (s *RestStateStreamSuite) SetupTest() { flow.RoleAccess, testnet.WithLogLevel(zerolog.InfoLevel), testnet.WithAdditionalFlag("--execution-data-sync-enabled=true"), - testnet.WithAdditionalFlag(fmt.Sprintf("--execution-data-dir=%s", testnet.DefaultExecutionDataServiceDir)), + testnet.WithAdditionalFlagf("--execution-data-dir=%s", testnet.DefaultExecutionDataServiceDir), testnet.WithAdditionalFlag("--execution-data-retry-delay=1s"), + testnet.WithAdditionalFlag("--execution-data-indexing-enabled=true"), + testnet.WithAdditionalFlagf("--execution-state-dir=%s", testnet.DefaultExecutionStateDir), ) // add the ghost (access) node config @@ -176,15 +178,20 @@ func (s *RestStateStreamSuite) requireEvents(receivedEventsResponse []*backend.E for eventType, receivedEventList := range receivedEventMap { // get events by block id and event type - response, err := MakeApiRequest(grpcClient.GetEventsForBlockIDs, grpcCtx, - &accessproto.GetEventsForBlockIDsRequest{BlockIds: [][]byte{convert.IdentifierToMessage(receivedEventResponse.BlockID)}, - Type: string(eventType)}) + response, err := MakeApiRequest( + grpcClient.GetEventsForBlockIDs, + grpcCtx, + &accessproto.GetEventsForBlockIDsRequest{ + BlockIds: [][]byte{convert.IdentifierToMessage(receivedEventResponse.BlockID)}, + Type: string(eventType), + }, + ) require.NoError(s.T(), err) require.Equal(s.T(), 1, len(response.Results), "expect to get 1 result") expectedEventsResult := response.Results[0] require.Equal(s.T(), expectedEventsResult.BlockHeight, receivedEventResponse.Height, "expect the same block height") - require.Equal(s.T(), len(expectedEventsResult.Events), len(receivedEventList), "expect the same count of events") + require.Equal(s.T(), len(expectedEventsResult.Events), len(receivedEventList), "expect the same count of events: want: %+v, got: %+v", expectedEventsResult.Events, receivedEventList) for i, event := range receivedEventList { require.Equal(s.T(), expectedEventsResult.Events[i].EventIndex, event.EventIndex, "expect the same event index") diff --git a/integration/tests/consensus/sealing_test.go b/integration/tests/consensus/sealing_test.go index 4ef4aa57c88..056671cb112 100644 --- a/integration/tests/consensus/sealing_test.go +++ b/integration/tests/consensus/sealing_test.go @@ -5,11 +5,11 @@ import ( "testing" "time" + "github.com/onflow/crypto" "github.com/rs/zerolog" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" - "github.com/onflow/flow-go/crypto" exeUtils "github.com/onflow/flow-go/engine/execution/utils" "github.com/onflow/flow-go/engine/ghost/client" verUtils "github.com/onflow/flow-go/engine/verification/utils" diff --git a/integration/tests/epochs/suite.go b/integration/tests/epochs/suite.go index 14b6ee1b814..a11b1127958 100644 --- a/integration/tests/epochs/suite.go +++ b/integration/tests/epochs/suite.go @@ -14,26 +14,26 @@ import ( "strings" "time" - "github.com/onflow/cadence" - "github.com/onflow/flow-core-contracts/lib/go/templates" "github.com/rs/zerolog" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" + "github.com/onflow/cadence" + "github.com/onflow/crypto" + "github.com/onflow/flow-core-contracts/lib/go/templates" + sdk "github.com/onflow/flow-go-sdk" sdkcrypto "github.com/onflow/flow-go-sdk/crypto" - "github.com/onflow/flow-go/fvm/blueprints" - "github.com/onflow/flow-go/state/protocol" - - "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/engine/ghost/client" + "github.com/onflow/flow-go/fvm/blueprints" "github.com/onflow/flow-go/integration/testnet" "github.com/onflow/flow-go/integration/tests/lib" "github.com/onflow/flow-go/integration/utils" "github.com/onflow/flow-go/model/bootstrap" "github.com/onflow/flow-go/model/encodable" "github.com/onflow/flow-go/model/flow" + "github.com/onflow/flow-go/state/protocol" "github.com/onflow/flow-go/state/protocol/inmem" "github.com/onflow/flow-go/utils/unittest" ) @@ -342,7 +342,7 @@ func (s *Suite) SubmitSetApprovedListTx(ctx context.Context, env templates.Envir idTableAddress := sdk.HexToAddress(env.IDTableAddress) tx := sdk.NewTransaction(). SetScript(templates.GenerateSetApprovedNodesScript(env)). - SetGasLimit(9999). + SetComputeLimit(9999). SetReferenceBlockID(sdk.Identifier(latestBlockID)). SetProposalKey(s.Client.SDKServiceAddress(), 0, s.Client.Account().Keys[0].SequenceNumber). SetPayer(s.Client.SDKServiceAddress()). diff --git a/integration/tests/mvp/common.go b/integration/tests/mvp/common.go index dc50242f6e5..65107f56e55 100644 --- a/integration/tests/mvp/common.go +++ b/integration/tests/mvp/common.go @@ -55,7 +55,7 @@ func RunMVPTest(t *testing.T, ctx context.Context, net *testnet.FlowNetwork, acc SetReferenceBlockID(sdk.Identifier(latestBlockID)). SetProposalKey(serviceAddress, 0, serviceAccountClient.GetSeqNumber()). SetPayer(serviceAddress). - SetGasLimit(9999) + SetComputeLimit(9999) childCtx, cancel := context.WithTimeout(ctx, defaultTimeout) err = serviceAccountClient.SignAndSendTransaction(childCtx, createAccountTx) @@ -109,7 +109,7 @@ func RunMVPTest(t *testing.T, ctx context.Context, net *testnet.FlowNetwork, acc SetReferenceBlockID(sdk.Identifier(latestBlockID)). SetProposalKey(serviceAddress, 0, serviceAccountClient.GetSeqNumber()). SetPayer(serviceAddress). - SetGasLimit(9999) + SetComputeLimit(9999) err = fundAccountTx.AddArgument(cadence.UFix64(1_0000_0000)) require.NoError(t, err) @@ -150,7 +150,7 @@ func RunMVPTest(t *testing.T, ctx context.Context, net *testnet.FlowNetwork, acc SetProposalKey(newAccountAddress, 0, 0). SetPayer(newAccountAddress). AddAuthorizer(newAccountAddress). - SetGasLimit(9999) + SetComputeLimit(9999) t.Log(">> creating counter...") diff --git a/integration/utils/transactions.go b/integration/utils/transactions.go index b08a0f0c385..27ca59794fb 100644 --- a/integration/utils/transactions.go +++ b/integration/utils/transactions.go @@ -6,12 +6,13 @@ import ( "fmt" "github.com/onflow/cadence" + "github.com/onflow/crypto" "github.com/onflow/flow-core-contracts/lib/go/templates" sdk "github.com/onflow/flow-go-sdk" sdkcrypto "github.com/onflow/flow-go-sdk/crypto" sdktemplates "github.com/onflow/flow-go-sdk/templates" - "github.com/onflow/flow-go/crypto" + "github.com/onflow/flow-go/integration/testnet" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/utils/unittest" @@ -58,7 +59,7 @@ func MakeCreateAndSetupNodeTx( script := []byte(templates.ReplaceAddresses(createAndSetupNodeTxScript, env)) tx := sdk.NewTransaction(). SetScript(script). - SetGasLimit(9999). + SetComputeLimit(9999). SetReferenceBlockID(latestBlockID). SetProposalKey(service.Address, 0, service.Keys[0].SequenceNumber). AddAuthorizer(service.Address). @@ -164,7 +165,7 @@ func MakeAdminRemoveNodeTx( accountKey := adminAccount.Keys[adminAccountKeyID] tx := sdk.NewTransaction(). SetScript([]byte(templates.ReplaceAddresses(removeNodeTxScript, env))). - SetGasLimit(9999). + SetComputeLimit(9999). SetReferenceBlockID(latestBlockID). SetProposalKey(adminAccount.Address, adminAccountKeyID, accountKey.SequenceNumber). SetPayer(adminAccount.Address). diff --git a/ledger/common/convert/convert.go b/ledger/common/convert/convert.go index d1c9d732570..4285cfd7ac4 100644 --- a/ledger/common/convert/convert.go +++ b/ledger/common/convert/convert.go @@ -28,7 +28,7 @@ func LedgerKeyToRegisterID(key ledger.Key) (flow.RegisterID, error) { } return flow.NewRegisterID( - string(key.KeyParts[0].Value), + flow.BytesToAddress(key.KeyParts[0].Value), string(key.KeyParts[1].Value), ), nil } diff --git a/ledger/common/convert/convert_test.go b/ledger/common/convert/convert_test.go index 286f7544d7e..55900aa7bc6 100644 --- a/ledger/common/convert/convert_test.go +++ b/ledger/common/convert/convert_test.go @@ -8,14 +8,17 @@ import ( "github.com/onflow/flow-go/ledger" "github.com/onflow/flow-go/ledger/common/convert" "github.com/onflow/flow-go/model/flow" + "github.com/onflow/flow-go/utils/unittest" ) func TestLedgerKeyToRegisterID(t *testing.T) { + expectedRegisterID := unittest.RegisterIDFixture() + key := ledger.Key{ KeyParts: []ledger.KeyPart{ { Type: convert.KeyPartOwner, - Value: []byte("owner"), + Value: []byte(expectedRegisterID.Owner), }, { Type: convert.KeyPartKey, @@ -24,7 +27,6 @@ func TestLedgerKeyToRegisterID(t *testing.T) { }, } - expectedRegisterID := flow.NewRegisterID("owner", "key") registerID, err := convert.LedgerKeyToRegisterID(key) require.NoError(t, err) require.Equal(t, expectedRegisterID, registerID) @@ -70,14 +72,14 @@ func TestLedgerKeyToRegisterID_Error(t *testing.T) { } func TestRegisterIDToLedgerKey(t *testing.T) { - registerID := flow.NewRegisterID("owner", "key") + registerID := unittest.RegisterIDFixture() expectedKey := ledger.Key{ KeyParts: []ledger.KeyPart{ { Type: convert.KeyPartOwner, // Note: the owner field is extended to address length during NewRegisterID // so we have to do the same here - Value: flow.BytesToAddress([]byte("owner")).Bytes(), + Value: []byte(registerID.Owner), }, { Type: convert.KeyPartKey, @@ -110,20 +112,21 @@ func TestRegisterIDToLedgerKey_Global(t *testing.T) { } func TestPayloadToRegister(t *testing.T) { + expected := unittest.RegisterIDFixture() t.Run("can convert", func(t *testing.T) { value := []byte("value") p := ledger.NewPayload( ledger.NewKey( []ledger.KeyPart{ - ledger.NewKeyPart(convert.KeyPartOwner, []byte("owner")), - ledger.NewKeyPart(convert.KeyPartKey, []byte("key")), + ledger.NewKeyPart(convert.KeyPartOwner, []byte(expected.Owner)), + ledger.NewKeyPart(convert.KeyPartKey, []byte(expected.Key)), }, ), value, ) regID, regValue, err := convert.PayloadToRegister(p) require.NoError(t, err) - require.Equal(t, flow.NewRegisterID("owner", "key"), regID) + require.Equal(t, expected, regID) require.Equal(t, value, regValue) }) @@ -140,7 +143,7 @@ func TestPayloadToRegister(t *testing.T) { ) regID, regValue, err := convert.PayloadToRegister(p) require.NoError(t, err) - require.Equal(t, flow.NewRegisterID("", "uuid"), regID) + require.Equal(t, flow.NewRegisterID(flow.EmptyAddress, "uuid"), regID) require.Equal(t, "", regID.Owner) require.Equal(t, "uuid", regID.Key) require.True(t, regID.IsInternalState()) diff --git a/ledger/common/hash/hash_test.go b/ledger/common/hash/hash_test.go index 69a1102e358..1b49293761c 100644 --- a/ledger/common/hash/hash_test.go +++ b/ledger/common/hash/hash_test.go @@ -4,12 +4,12 @@ import ( "crypto/rand" "testing" - "golang.org/x/crypto/sha3" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "golang.org/x/crypto/sha3" + + cryhash "github.com/onflow/crypto/hash" - cryhash "github.com/onflow/flow-go/crypto/hash" "github.com/onflow/flow-go/ledger" "github.com/onflow/flow-go/ledger/common/hash" ) diff --git a/ledger/common/pathfinder/pathfinder.go b/ledger/common/pathfinder/pathfinder.go index 6e6a0497c68..7849cf28256 100644 --- a/ledger/common/pathfinder/pathfinder.go +++ b/ledger/common/pathfinder/pathfinder.go @@ -5,7 +5,8 @@ import ( "crypto/sha256" "fmt" - "github.com/onflow/flow-go/crypto/hash" + "github.com/onflow/crypto/hash" + "github.com/onflow/flow-go/ledger" ) diff --git a/ledger/common/pathfinder/pathfinder_test.go b/ledger/common/pathfinder/pathfinder_test.go index 44eb963735c..321c03fa64c 100644 --- a/ledger/common/pathfinder/pathfinder_test.go +++ b/ledger/common/pathfinder/pathfinder_test.go @@ -4,9 +4,9 @@ import ( "crypto/sha256" "testing" + "github.com/onflow/crypto/hash" "github.com/stretchr/testify/require" - "github.com/onflow/flow-go/crypto/hash" "github.com/onflow/flow-go/ledger" "github.com/onflow/flow-go/ledger/common/pathfinder" "github.com/onflow/flow-go/ledger/common/testutils" diff --git a/ledger/complete/wal/checkpointer.go b/ledger/complete/wal/checkpointer.go index cad90ec37e8..1c6aaa0aef3 100644 --- a/ledger/complete/wal/checkpointer.go +++ b/ledger/complete/wal/checkpointer.go @@ -517,9 +517,15 @@ func StoreCheckpointV5(dir string, fileName string, logger zerolog.Logger, tries } func logProgress(msg string, estimatedSubtrieNodeCount int, logger zerolog.Logger) func(nodeCounter uint64) { - lg := util.LogProgress(msg, estimatedSubtrieNodeCount, logger) + lg := util.LogProgress( + logger, + util.DefaultLogProgressConfig( + msg, + estimatedSubtrieNodeCount, + ), + ) return func(index uint64) { - lg(int(index)) + lg(1) } } diff --git a/ledger/partial/ledger_test.go b/ledger/partial/ledger_test.go index ac11b6930ba..209bf707ed0 100644 --- a/ledger/partial/ledger_test.go +++ b/ledger/partial/ledger_test.go @@ -127,7 +127,7 @@ func TestProofsForEmptyRegisters(t *testing.T) { require.NoError(t, err) // Read one register during execution. - registerID := flow.NewRegisterID("b", "nk") + registerID := flow.NewRegisterID(unittest.RandomAddressFixture(), "nk") allKeys := []ledger.Key{ convert.RegisterIDToLedgerKey(registerID), } diff --git a/ledger/trie.go b/ledger/trie.go index 17f2ba1a232..1228dd64b1b 100644 --- a/ledger/trie.go +++ b/ledger/trie.go @@ -9,7 +9,8 @@ import ( "github.com/fxamacker/cbor/v2" - cryptoHash "github.com/onflow/flow-go/crypto/hash" + cryptoHash "github.com/onflow/crypto/hash" + "github.com/onflow/flow-go/ledger/common/bitutils" "github.com/onflow/flow-go/ledger/common/hash" ) @@ -319,6 +320,16 @@ func (p *Payload) Key() (Key, error) { return *k, nil } +// EncodedKey returns payload key. +// CAUTION: do not modify returned encoded key +// because it shares underlying data with payload key. +func (p *Payload) EncodedKey() []byte { + if p == nil { + return nil + } + return p.encKey +} + // Value returns payload value. // CAUTION: do not modify returned value because it shares underlying data with payload value. func (p *Payload) Value() Value { diff --git a/model/bootstrap/node_info.go b/model/bootstrap/node_info.go index 62a33f6f442..d47ec559781 100644 --- a/model/bootstrap/node_info.go +++ b/model/bootstrap/node_info.go @@ -4,13 +4,14 @@ package bootstrap import ( "encoding/json" "fmt" - "sort" "strings" + "github.com/onflow/crypto" + "golang.org/x/exp/slices" + sdk "github.com/onflow/flow-go-sdk" sdkcrypto "github.com/onflow/flow-go-sdk/crypto" - "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/model/encodable" "github.com/onflow/flow-go/model/flow" ) @@ -402,12 +403,14 @@ func FilterByRole(nodes []NodeInfo, role flow.Role) []NodeInfo { return filtered } -// Sort sorts the NodeInfo list using the given ordering. +// Sort sorts the NodeInfo list using the given order. +// +// The sorted list is returned and the original list is untouched. func Sort(nodes []NodeInfo, order flow.IdentityOrder) []NodeInfo { dup := make([]NodeInfo, len(nodes)) copy(dup, nodes) - sort.Slice(dup, func(i, j int) bool { - return order(dup[i].Identity(), dup[j].Identity()) + slices.SortFunc(dup, func(i, j NodeInfo) int { + return order(i.Identity(), j.Identity()) }) return dup } diff --git a/model/bootstrap/node_info_test.go b/model/bootstrap/node_info_test.go index 39294de5f69..b00f6cd986a 100644 --- a/model/bootstrap/node_info_test.go +++ b/model/bootstrap/node_info_test.go @@ -9,14 +9,36 @@ import ( "github.com/stretchr/testify/require" "github.com/onflow/flow-go/model/bootstrap" - "github.com/onflow/flow-go/model/flow/order" + "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/utils/unittest" ) -func TestSort(t *testing.T) { +func TestIdentityListCanonical(t *testing.T) { nodes := unittest.NodeInfosFixture(20) - nodes = bootstrap.Sort(nodes, order.Canonical) - require.True(t, bootstrap.ToIdentityList(nodes).Sorted(order.Canonical)) + // make sure the list is not sorted + nodes[0].NodeID[0], nodes[1].NodeID[0] = 2, 1 + require.False(t, flow.IsIdentifierCanonical(nodes[0].NodeID, nodes[1].NodeID)) + ids := bootstrap.ToIdentityList(nodes) + assert.False(t, flow.IsIdentityListCanonical(ids)) + + // make a copy of the original list of nodes + nodesCopy := make([]bootstrap.NodeInfo, len(nodes)) + copy(nodesCopy, nodes) + + sortedNodes := bootstrap.Sort(nodes, flow.Canonical) + sortedIds := bootstrap.ToIdentityList(sortedNodes) + require.True(t, flow.IsIdentityListCanonical(sortedIds)) + // make sure original list didn't change + assert.Equal(t, nodesCopy, nodes) + + // check `IsIdentityListCanonical` detects order equality in a sorted list + nodes[1] = nodes[10] // add a duplication + copy(nodesCopy, nodes) + sortedNodes = bootstrap.Sort(nodes, flow.Canonical) + sortedIds = bootstrap.ToIdentityList(sortedNodes) + assert.False(t, flow.IsIdentityListCanonical(sortedIds)) + // make sure original list didn't change + assert.Equal(t, nodesCopy, nodes) } func TestNodeConfigEncodingJSON(t *testing.T) { diff --git a/model/convert/fixtures_test.go b/model/convert/fixtures_test.go index 7ed60dfee2b..7ebcd21277c 100644 --- a/model/convert/fixtures_test.go +++ b/model/convert/fixtures_test.go @@ -1,7 +1,8 @@ package convert_test import ( - "github.com/onflow/flow-go/crypto" + "github.com/onflow/crypto" + "github.com/onflow/flow-go/fvm/systemcontracts" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/utils/unittest" diff --git a/model/convert/service_event.go b/model/convert/service_event.go index 669daca5a84..a414b2c60b3 100644 --- a/model/convert/service_event.go +++ b/model/convert/service_event.go @@ -5,14 +5,14 @@ import ( "fmt" "github.com/coreos/go-semver/semver" + "github.com/onflow/cadence" "github.com/onflow/cadence/encoding/ccf" + "github.com/onflow/crypto" - "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/fvm/systemcontracts" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/model/flow/assignment" - "github.com/onflow/flow-go/model/flow/order" ) // ServiceEvent converts a service event encoded as the generic flow.Event @@ -199,22 +199,25 @@ func convertServiceEventEpochSetup(event flow.Event) (*flow.ServiceEvent, error) DKGPhase3FinalView: uint64(dkgPhase3FinalView), } - // Cadence's unsafeRandom().toString() produces a string of variable length. - // Here we pad it with enough 0s to meet the required length. - paddedRandomSrcHex := fmt.Sprintf( - "%0*s", - 2*flow.EpochSetupRandomSourceLength, - string(randomSrcHex), - ) - setup.RandomSource, err = hex.DecodeString(paddedRandomSrcHex) + // random source from the event must be a hex string + // containing exactly 128 bits (equivalent to 16 bytes or 32 hex characters) + setup.RandomSource, err = hex.DecodeString(string(randomSrcHex)) if err != nil { return nil, fmt.Errorf( "could not decode random source hex (%v): %w", - paddedRandomSrcHex, + randomSrcHex, err, ) } + if len(setup.RandomSource) != flow.EpochSetupRandomSourceLength { + return nil, fmt.Errorf( + "random source in epoch setup event must be of (%d) bytes, got (%d)", + flow.EpochSetupRandomSourceLength, + len(setup.RandomSource), + ) + } + // parse cluster assignments setup.Assignments, err = convertClusterAssignments(cdcClusters.Values) if err != nil { @@ -631,7 +634,7 @@ func convertParticipants(cdcParticipants []cadence.Value) (flow.IdentityList, er participants = append(participants, identity) } - participants = participants.Sort(order.Canonical) + participants = participants.Sort(flow.Canonical) return participants, nil } diff --git a/model/convert/service_event_test.go b/model/convert/service_event_test.go index 9c50a98d1c3..9afd3a32499 100644 --- a/model/convert/service_event_test.go +++ b/model/convert/service_event_test.go @@ -4,11 +4,10 @@ import ( "fmt" "testing" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/onflow/cadence" "github.com/onflow/cadence/encoding/ccf" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/onflow/flow-go/fvm/systemcontracts" "github.com/onflow/flow-go/model/convert" @@ -39,6 +38,55 @@ func TestEventConversion(t *testing.T) { }, ) + t.Run( + "epoch setup with random source with leading zeroes", func(t *testing.T) { + + fixture, _ := unittest.EpochSetupFixtureByChainID(chainID) + // all zero source to cover all cases of endiannesses + randomSource := make([]byte, flow.EpochSetupRandomSourceLength) + // update the random source in event fixture + fixture.Payload = unittest.EpochSetupFixtureCCF(randomSource) + + // convert Cadence types to Go types + event, err := convert.ServiceEvent(chainID, fixture) + require.NoError(t, err) + require.NotNil(t, event) + + // cast event type to epoch setup + _, ok := event.Event.(*flow.EpochSetup) + require.True(t, ok) + }, + ) + + t.Run( + "epoch setup with short random source", func(t *testing.T) { + + fixture, _ := unittest.EpochSetupFixtureByChainID(chainID) + // update the random source in event fixture + randomSource := unittest.EpochSetupRandomSourceFixture() + fixture.Payload = unittest.EpochSetupFixtureCCF(randomSource[:flow.EpochSetupRandomSourceLength-1]) + + // convert Cadence types to Go types + event, err := convert.ServiceEvent(chainID, fixture) + require.Error(t, err) + require.Nil(t, event) + }, + ) + + t.Run( + "epoch setup with non-hex random source", func(t *testing.T) { + + fixture, _ := unittest.EpochSetupFixtureByChainID(chainID) + // update the random source in event fixture + fixture.Payload = unittest.EpochSetupCCFWithNonHexRandomSource() + + // convert Cadence types to Go types + event, err := convert.ServiceEvent(chainID, fixture) + require.Error(t, err) + require.Nil(t, event) + }, + ) + t.Run( "epoch commit", func(t *testing.T) { diff --git a/model/dkg/dkg.go b/model/dkg/dkg.go index 255815710dd..d1481cc1fd3 100644 --- a/model/dkg/dkg.go +++ b/model/dkg/dkg.go @@ -1,7 +1,7 @@ package dkg import ( - "github.com/onflow/flow-go/crypto" + "github.com/onflow/crypto" ) // DKGData represents all the output data from the DKG process, including private information. diff --git a/model/encodable/keys.go b/model/encodable/keys.go index 0049d4c24eb..513456e26cc 100644 --- a/model/encodable/keys.go +++ b/model/encodable/keys.go @@ -10,7 +10,7 @@ import ( "github.com/fxamacker/cbor/v2" "github.com/vmihailenco/msgpack" - "github.com/onflow/flow-go/crypto" + "github.com/onflow/crypto" ) // ConsensusVoteSigLen is the length of a consensus vote as well as aggregated consensus votes. diff --git a/model/encodable/keys_test.go b/model/encodable/keys_test.go index 338c1708366..f7da893a376 100644 --- a/model/encodable/keys_test.go +++ b/model/encodable/keys_test.go @@ -9,7 +9,7 @@ import ( "github.com/stretchr/testify/require" - "github.com/onflow/flow-go/crypto" + "github.com/onflow/crypto" ) func isHexString(enc []byte) error { diff --git a/engine/access/state_stream/event.go b/model/events/parse.go similarity index 60% rename from engine/access/state_stream/event.go rename to model/events/parse.go index c88c78c9a66..1ddcfda2f30 100644 --- a/engine/access/state_stream/event.go +++ b/model/events/parse.go @@ -1,4 +1,4 @@ -package state_stream +package events import ( "fmt" @@ -23,15 +23,16 @@ type ParsedEvent struct { Name string } -// ParseEvent parses an event type into its parts. There are 2 valid EventType formats: +// ParseEvent parses an event type into its parts. There are 3 valid EventType formats: // - flow.[EventName] +// - evm.[EventName] // - A.[Address].[Contract].[EventName] // Any other format results in an error. func ParseEvent(eventType flow.EventType) (*ParsedEvent, error) { parts := strings.Split(string(eventType), ".") switch parts[0] { - case "flow": + case "flow", flow.EVMLocationPrefix: if len(parts) == 2 { return &ParsedEvent{ Type: ProtocolEventType, @@ -57,3 +58,24 @@ func ParseEvent(eventType flow.EventType) (*ParsedEvent, error) { return nil, fmt.Errorf("invalid event type: %s", eventType) } + +// ValidateEvent validates an event type is properly formed and for the correct network, and returns +// a parsed event. If the event type is invalid, an error is returned. +func ValidateEvent(eventType flow.EventType, chain flow.Chain) (*ParsedEvent, error) { + parsed, err := ParseEvent(eventType) + if err != nil { + return nil, err + } + + // only account type events have an address field + if parsed.Type != AccountEventType { + return parsed, nil + } + + contractAddress := flow.HexToAddress(parsed.Address) + if !chain.IsValid(contractAddress) { + return nil, fmt.Errorf("invalid event contract address") + } + + return parsed, nil +} diff --git a/model/events/parse_test.go b/model/events/parse_test.go new file mode 100644 index 00000000000..fed421c86bc --- /dev/null +++ b/model/events/parse_test.go @@ -0,0 +1,175 @@ +package events_test + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/onflow/flow-go/model/events" + "github.com/onflow/flow-go/model/flow" + "github.com/onflow/flow-go/utils/unittest" +) + +func TestParseEvent(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + eventType flow.EventType + expected events.ParsedEvent + }{ + { + name: "flow event", + eventType: "flow.AccountCreated", + expected: events.ParsedEvent{ + Type: events.ProtocolEventType, + EventType: "flow.AccountCreated", + Contract: "flow", + ContractName: "flow", + Name: "AccountCreated", + }, + }, + { + name: "account event", + eventType: "A.0000000000000001.Contract1.EventA", + expected: events.ParsedEvent{ + Type: events.AccountEventType, + EventType: "A.0000000000000001.Contract1.EventA", + Address: "0000000000000001", + Contract: "A.0000000000000001.Contract1", + ContractName: "Contract1", + Name: "EventA", + }, + }, + { + name: "evm event", + eventType: "evm.BlockExecuted", + expected: events.ParsedEvent{ + Type: events.ProtocolEventType, + EventType: "evm.BlockExecuted", + Contract: "evm", + ContractName: "evm", + Name: "BlockExecuted", + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + event, err := events.ParseEvent(test.eventType) + require.NoError(t, err) + + assert.Equal(t, test.expected.Type, event.Type) + assert.Equal(t, test.expected.EventType, event.EventType) + assert.Equal(t, test.expected.Address, event.Address) + assert.Equal(t, test.expected.Contract, event.Contract) + assert.Equal(t, test.expected.Name, event.Name) + }) + } +} + +func TestParseEvent_Invalid(t *testing.T) { + t.Parallel() + + eventTypes := []flow.EventType{ + "", // not enough parts + "invalid", // not enough parts + "invalid.event", // invalid first part + "B.0000000000000001.invalid.event", // invalid first part + "flow", // incorrect number of parts for protocol event + "flow.invalid.event", // incorrect number of parts for protocol event + "evm", // incorrect number of parts for protocol event + "evm.invalid.event", // incorrect number of parts for protocol event + "A.0000000000000001.invalid", // incorrect number of parts for account event + "A.0000000000000001.invalid.a.b", // incorrect number of parts for account event + + } + + for _, eventType := range eventTypes { + _, err := events.ParseEvent(eventType) + assert.Error(t, err, "expected error for event type: %s", eventType) + } +} + +func TestValidateEvent(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + eventType flow.EventType + expected events.ParsedEvent + }{ + { + name: "flow event", + eventType: "flow.AccountCreated", + expected: events.ParsedEvent{ + Type: events.ProtocolEventType, + EventType: "flow.AccountCreated", + Contract: "flow", + ContractName: "flow", + Name: "AccountCreated", + }, + }, + { + name: "account event", + eventType: "A.0000000000000001.Contract1.EventA", + expected: events.ParsedEvent{ + Type: events.AccountEventType, + EventType: "A.0000000000000001.Contract1.EventA", + Address: "0000000000000001", + Contract: "A.0000000000000001.Contract1", + ContractName: "Contract1", + Name: "EventA", + }, + }, + { + name: "evm event", + eventType: "evm.BlockExecuted", + expected: events.ParsedEvent{ + Type: events.ProtocolEventType, + EventType: "evm.BlockExecuted", + Contract: "evm", + ContractName: "evm", + Name: "BlockExecuted", + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + event, err := events.ValidateEvent(test.eventType, flow.MonotonicEmulator.Chain()) + require.NoError(t, err) + + assert.Equal(t, test.expected.Type, event.Type) + assert.Equal(t, test.expected.EventType, event.EventType) + assert.Equal(t, test.expected.Address, event.Address) + assert.Equal(t, test.expected.Contract, event.Contract) + assert.Equal(t, test.expected.Name, event.Name) + }) + } +} + +func TestValidateEvent_Invalid(t *testing.T) { + t.Parallel() + + eventTypes := []flow.EventType{ + "", // not enough parts + "invalid", // not enough parts + "invalid.event", // invalid first part + "B.0000000000000001.invalid.event", // invalid first part + "flow", // incorrect number of parts for protocol event + "flow.invalid.event", // incorrect number of parts for protocol event + "evm", // incorrect number of parts for protocol event + "evm.invalid.event", // incorrect number of parts for protocol event + "A.0000000000000001.invalid", // incorrect number of parts for account event + "A.0000000000000001.invalid.a.b", // incorrect number of parts for account event + flow.EventType(fmt.Sprintf("A.%s.Contract1.EventA", unittest.RandomAddressFixture())), // address from wrong chain + } + + for _, eventType := range eventTypes { + _, err := events.ValidateEvent(eventType, flow.MonotonicEmulator.Chain()) + assert.Error(t, err, "expected error for event type: %s", eventType) + } +} diff --git a/model/flow/account.go b/model/flow/account.go index 35606897abd..f9747f632da 100644 --- a/model/flow/account.go +++ b/model/flow/account.go @@ -6,8 +6,8 @@ import ( "encoding/json" "fmt" - "github.com/onflow/flow-go/crypto" - "github.com/onflow/flow-go/crypto/hash" + "github.com/onflow/crypto" + "github.com/onflow/crypto/hash" ) // Account represents an account on the Flow network. diff --git a/model/flow/account_encoder.go b/model/flow/account_encoder.go index 9b4bf1ad5ae..1357f515dbc 100644 --- a/model/flow/account_encoder.go +++ b/model/flow/account_encoder.go @@ -7,8 +7,8 @@ import ( "github.com/onflow/cadence" - "github.com/onflow/flow-go/crypto" - "github.com/onflow/flow-go/crypto/hash" + "github.com/onflow/crypto" + "github.com/onflow/crypto/hash" ) // accountPublicKeyWrapper is used for encoding and decoding. diff --git a/model/flow/account_encoder_test.go b/model/flow/account_encoder_test.go index 761511c7c1d..125214ebfdc 100644 --- a/model/flow/account_encoder_test.go +++ b/model/flow/account_encoder_test.go @@ -5,11 +5,11 @@ import ( "testing" "github.com/ethereum/go-ethereum/rlp" + "github.com/onflow/crypto" + "github.com/onflow/crypto/hash" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/onflow/flow-go/crypto" - "github.com/onflow/flow-go/crypto/hash" "github.com/onflow/flow-go/model/flow" ) diff --git a/model/flow/aggregated_signature.go b/model/flow/aggregated_signature.go index a3a5c9076f5..ecc58627eac 100644 --- a/model/flow/aggregated_signature.go +++ b/model/flow/aggregated_signature.go @@ -1,7 +1,7 @@ package flow import ( - "github.com/onflow/flow-go/crypto" + "github.com/onflow/crypto" ) // AggregatedSignature contains a set of of signatures from verifiers attesting diff --git a/model/flow/assignment/sort.go b/model/flow/assignment/sort.go index 3b135d91152..8e590a86089 100644 --- a/model/flow/assignment/sort.go +++ b/model/flow/assignment/sort.go @@ -2,7 +2,6 @@ package assignment import ( "github.com/onflow/flow-go/model/flow" - "github.com/onflow/flow-go/model/flow/order" ) // FromIdentifierLists creates a `flow.AssignmentList` with canonical ordering from @@ -11,7 +10,7 @@ func FromIdentifierLists(identifierLists []flow.IdentifierList) flow.AssignmentL assignments := make(flow.AssignmentList, 0, len(identifierLists)) // in place sort to order the assignment in canonical order for _, identities := range identifierLists { - assignment := flow.IdentifierList(identities).Sort(order.IdentifierCanonical) + assignment := flow.IdentifierList(identities).Sort(flow.IdentifierCanonical) assignments = append(assignments, assignment) } return assignments diff --git a/model/flow/collectionGuarantee.go b/model/flow/collectionGuarantee.go index c05307c11a7..393505a6de2 100644 --- a/model/flow/collectionGuarantee.go +++ b/model/flow/collectionGuarantee.go @@ -3,7 +3,7 @@ package flow import ( - "github.com/onflow/flow-go/crypto" + "github.com/onflow/crypto" ) // CollectionGuarantee is a signed hash for a collection, which is used diff --git a/model/flow/epoch.go b/model/flow/epoch.go index 3f27586f2a2..2e4c16ff14b 100644 --- a/model/flow/epoch.go +++ b/model/flow/epoch.go @@ -8,9 +8,9 @@ import ( "github.com/ethereum/go-ethereum/rlp" "github.com/fxamacker/cbor/v2" + "github.com/onflow/crypto" "github.com/vmihailenco/msgpack/v4" - "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/model/encodable" ) diff --git a/model/flow/epoch_test.go b/model/flow/epoch_test.go index 9c9a542540d..0803e807a0a 100644 --- a/model/flow/epoch_test.go +++ b/model/flow/epoch_test.go @@ -3,11 +3,11 @@ package flow_test import ( "testing" - "github.com/onflow/flow-go/crypto" + "github.com/onflow/crypto" + "github.com/stretchr/testify/require" + "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/utils/unittest" - - "github.com/stretchr/testify/require" ) func TestClusterQCVoteData_Equality(t *testing.T) { diff --git a/model/flow/event.go b/model/flow/event.go index c645bf22603..8c6e16a4170 100644 --- a/model/flow/event.go +++ b/model/flow/event.go @@ -17,6 +17,8 @@ const ( EventAccountUpdated EventType = "flow.AccountUpdated" ) +const EVMLocationPrefix = "evm" + type EventType string type Event struct { diff --git a/model/flow/execution_receipt.go b/model/flow/execution_receipt.go index 7c272df64f2..f77b9e4b98d 100644 --- a/model/flow/execution_receipt.go +++ b/model/flow/execution_receipt.go @@ -3,7 +3,7 @@ package flow import ( "encoding/json" - "github.com/onflow/flow-go/crypto" + "github.com/onflow/crypto" ) type Spock []byte diff --git a/model/flow/factory/cluster_list.go b/model/flow/factory/cluster_list.go index 29bf374ac23..9ff7e0c7464 100644 --- a/model/flow/factory/cluster_list.go +++ b/model/flow/factory/cluster_list.go @@ -4,14 +4,17 @@ import ( "fmt" "github.com/onflow/flow-go/model/flow" - "github.com/onflow/flow-go/model/flow/order" ) // NewClusterList creates a new cluster list based on the given cluster assignment // and the provided list of identities. -// The caller must ensure each assignment contains identities ordered in canonical order, so that -// each cluster in the returned cluster list is ordered in canonical order as well. If not, -// an error will be returned. +// +// The caller must ensure the following prerequisites: +// - each assignment contains identities ordered in canonical order +// - every collector has a unique NodeID, i.e. there are no two elements in `collectors` with the same NodeID +// +// These prerequisites ensures that each cluster in the returned cluster list is ordered in canonical order as well. +// This function checks that the prerequisites are satisfied and errors otherwise. func NewClusterList(assignments flow.AssignmentList, collectors flow.IdentityList) (flow.ClusterList, error) { // build a lookup for all the identities by node identifier @@ -28,7 +31,7 @@ func NewClusterList(assignments flow.AssignmentList, collectors flow.IdentityLis for i, participants := range assignments { cluster := make(flow.IdentityList, 0, len(participants)) if len(participants) == 0 { - return nil, fmt.Errorf("particpants in assignment list is empty, cluster index %v", i) + return nil, fmt.Errorf("participants in assignment list is empty, cluster index %v", i) } // Check assignments is sorted in canonical order @@ -43,8 +46,8 @@ func NewClusterList(assignments flow.AssignmentList, collectors flow.IdentityLis delete(lookup, participantID) if i > 0 { - if !order.IdentifierCanonical(prev, participantID) { - return nil, fmt.Errorf("the assignments is not sorted in canonical order in cluster index %v, prev %v, next %v", + if !flow.IsIdentifierCanonical(prev, participantID) { + return nil, fmt.Errorf("the assignments is not sorted in canonical order or there are duplicates in cluster index %v, prev %v, next %v", i, prev, participantID) } } diff --git a/model/flow/filter/identity.go b/model/flow/filter/identity.go index 2c312c05028..03ee618bc52 100644 --- a/model/flow/filter/identity.go +++ b/model/flow/filter/identity.go @@ -3,7 +3,8 @@ package filter import ( - "github.com/onflow/flow-go/crypto" + "github.com/onflow/crypto" + "github.com/onflow/flow-go/model/flow" ) diff --git a/model/flow/header_test.go b/model/flow/header_test.go index cbc6b2fc272..9f1f1adf78e 100644 --- a/model/flow/header_test.go +++ b/model/flow/header_test.go @@ -6,12 +6,12 @@ import ( "time" "github.com/fxamacker/cbor/v2" + "github.com/onflow/crypto" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/vmihailenco/msgpack/v4" "github.com/onflow/flow-go/consensus/hotstuff/helper" - "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/model/encoding/rlp" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/utils/unittest" diff --git a/model/flow/identifier.go b/model/flow/identifier.go index e205e74a716..1ebc2dc1c77 100644 --- a/model/flow/identifier.go +++ b/model/flow/identifier.go @@ -11,8 +11,9 @@ import ( "github.com/ipfs/go-cid" mh "github.com/multiformats/go-multihash" - "github.com/onflow/flow-go/crypto" - "github.com/onflow/flow-go/crypto/hash" + "github.com/onflow/crypto" + "github.com/onflow/crypto/hash" + "github.com/onflow/flow-go/model/fingerprint" "github.com/onflow/flow-go/storage/merkle" "github.com/onflow/flow-go/utils/rand" @@ -26,8 +27,16 @@ type Identifier [IdentifierLen]byte // IdentifierFilter is a filter on identifiers. type IdentifierFilter func(Identifier) bool -// IdentifierOrder is a sort for identifier -type IdentifierOrder func(Identifier, Identifier) bool +// IdentifierOrder is an order function for identifiers. +// +// It defines a strict weak ordering between identifiers. +// It returns a negative number if the first identifier is "strictly less" than the second, +// a positive number if the second identifier is "strictly less" than the first, +// and zero if the two identifiers are equal. +// +// `IdentifierOrder` can be used to sort identifiers with +// https://pkg.go.dev/golang.org/x/exp/slices#SortFunc. +type IdentifierOrder func(Identifier, Identifier) int var ( // ZeroID is the lowest value in the 32-byte ID space. diff --git a/model/flow/identifierList.go b/model/flow/identifierList.go index 1cf3e0263a8..29eb02de735 100644 --- a/model/flow/identifierList.go +++ b/model/flow/identifierList.go @@ -1,16 +1,13 @@ package flow import ( - "bytes" - "sort" - - "github.com/rs/zerolog/log" + "golang.org/x/exp/slices" ) // IdentifierList defines a sortable list of identifiers type IdentifierList []Identifier -// Len returns length of the IdentiferList in the number of stored identifiers. +// Len returns length of the IdentifierList in the number of stored identifiers. // It satisfies the sort.Interface making the IdentifierList sortable. func (il IdentifierList) Len() int { return len(il) @@ -29,16 +26,7 @@ func (il IdentifierList) Lookup() map[Identifier]struct{} { // Otherwise it returns true. // It satisfies the sort.Interface making the IdentifierList sortable. func (il IdentifierList) Less(i, j int) bool { - // bytes package already implements Comparable for []byte. - switch bytes.Compare(il[i][:], il[j][:]) { - case -1: - return true - case 0, 1: - return false - default: - log.Error().Msg("not fail-able with `bytes.Comparable` bounded [-1, 1].") - return false - } + return IsIdentifierCanonical(il[i], il[j]) } // Swap swaps the element i and j in the IdentifierList. @@ -120,22 +108,9 @@ IDLoop: return dup } +// Sort returns a sorted _copy_ of the IdentifierList, leaving the original invariant. func (il IdentifierList) Sort(less IdentifierOrder) IdentifierList { dup := il.Copy() - sort.Slice(dup, func(i int, j int) bool { - return less(dup[i], dup[j]) - }) + slices.SortFunc(dup, less) return dup } - -// Sorted returns whether the list is sorted by the input ordering. -func (il IdentifierList) Sorted(less IdentifierOrder) bool { - for i := 0; i < len(il)-1; i++ { - a := il[i] - b := il[i+1] - if !less(a, b) { - return false - } - } - return true -} diff --git a/model/flow/identifierList_test.go b/model/flow/identifierList_test.go index 7e18b6ee921..166b6dcbd5a 100644 --- a/model/flow/identifierList_test.go +++ b/model/flow/identifierList_test.go @@ -12,6 +12,14 @@ import ( "github.com/onflow/flow-go/utils/unittest" ) +// Test the canonical ordering of identity and identifier match +func TestCanonicalOrderingMatch(t *testing.T) { + identities := unittest.IdentityListFixture(100) + require.Equal(t, + identities.Sort(flow.Canonical).NodeIDs(), + identities.NodeIDs().Sort(flow.IdentifierCanonical)) +} + // TestIdentifierListSort tests the IdentityList against its implemented sort interface // it generates and sorts a list of ids, and then evaluates sorting in ascending order func TestIdentifierListSort(t *testing.T) { diff --git a/model/flow/identifier_order.go b/model/flow/identifier_order.go new file mode 100644 index 00000000000..af258f531bc --- /dev/null +++ b/model/flow/identifier_order.go @@ -0,0 +1,53 @@ +package flow + +import ( + "bytes" +) + +// IdentifierCanonical is a function that defines a weak strict ordering "<" for identifiers. +// It returns: +// - a strict negative number if id1 < id2 +// - a strict positive number if id2 < id1 +// - zero if id1 and id2 are equal +// +// By definition, two Identifiers (id1, id2) are in canonical order if id1 is lexicographically +// _strictly_ smaller than id2. The strictness is important, meaning that duplicates do not +// satisfy canonical ordering (order is irreflexive). Hence, only a returned strictly negative +// value means the pair is in canonical order. +// Use `IsIdentifierCanonical` for canonical order checks. +// +// The current function is based on the identifiers bytes lexicographic comparison. +// Example: +// +// IdentifierCanonical(Identifier{1}, Identifier{2}) // -1 +// IdentifierCanonical(Identifier{2}, Identifier{1}) // 1 +// IdentifierCanonical(Identifier{1}, Identifier{1}) // 0 +// IdentifierCanonical(Identifier{0, 1}, Identifier{0, 2}) // -1 +func IdentifierCanonical(id1 Identifier, id2 Identifier) int { + return bytes.Compare(id1[:], id2[:]) +} + +// IsCanonical returns true if and only if the given identifiers are in canonical order. +// +// By convention, two identifiers (i1, i2) are in canonical order if i1's bytes +// are lexicographically _strictly_ smaller than i2's bytes. +// +// The strictness is important, meaning that the canonical order +// is irreflexive ((i,i) isn't in canonical order). +func IsIdentifierCanonical(i1, i2 Identifier) bool { + return IdentifierCanonical(i1, i2) < 0 +} + +// IsIdentityListCanonical returns true if and only if the given list is +// _strictly_ sorted with regards to the canonical order. +// +// The strictness is important here, meaning that a list with 2 equal identifiers +// isn't considered well sorted. +func IsIdentifierListCanonical(il IdentifierList) bool { + for i := 0; i < len(il)-1; i++ { + if !IsIdentifierCanonical(il[i], il[i+1]) { + return false + } + } + return true +} diff --git a/model/flow/identity.go b/model/flow/identity.go index 975baa556e9..171abf9ed42 100644 --- a/model/flow/identity.go +++ b/model/flow/identity.go @@ -1,7 +1,6 @@ package flow import ( - "bytes" "encoding/json" "fmt" "io" @@ -13,10 +12,10 @@ import ( "github.com/ethereum/go-ethereum/rlp" "github.com/fxamacker/cbor/v2" + "github.com/onflow/crypto" "github.com/pkg/errors" "github.com/vmihailenco/msgpack" - "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/utils/rand" ) @@ -301,8 +300,16 @@ func (iy *Identity) EqualTo(other *Identity) bool { // IdentityFilter is a filter on identities. type IdentityFilter func(*Identity) bool -// IdentityOrder is a sort for identities. -type IdentityOrder func(*Identity, *Identity) bool +// IdentityOrder is an order function for identities. +// +// It defines a strict weak ordering between identities. +// It returns a negative number if the first identity is "strictly less" than the second, +// a positive number if the second identity is "strictly less" than the first, +// and zero if the two identities are equal. +// +// `IdentityOrder` can be used to sort identities with +// https://pkg.go.dev/golang.org/x/exp/slices#SortFunc. +type IdentityOrder func(*Identity, *Identity) int // IdentityMapFunc is a modifier function for map operations for identities. // Identities are COPIED from the source slice. @@ -388,11 +395,6 @@ func (il IdentityList) Sort(less IdentityOrder) IdentityList { return dup } -// Sorted returns whether the list is sorted by the input ordering. -func (il IdentityList) Sorted(less IdentityOrder) bool { - return slices.IsSortedFunc(il, less) -} - // NodeIDs returns the NodeIDs of the nodes in the list. func (il IdentityList) NodeIDs() IdentifierList { nodeIDs := make([]Identifier, 0, len(il)) @@ -521,7 +523,7 @@ func (il IdentityList) SamplePct(pct float64) (IdentityList, error) { // Union returns a new identity list containing every identity that occurs in // either `il`, or `other`, or both. There are no duplicates in the output, // where duplicates are identities with the same node ID. -// The returned IdentityList is sorted +// The returned IdentityList is sorted canonically. func (il IdentityList) Union(other IdentityList) IdentityList { maxLen := len(il) + len(other) @@ -537,10 +539,7 @@ func (il IdentityList) Union(other IdentityList) IdentityList { } } - slices.SortFunc(union, func(a, b *Identity) bool { - return bytes.Compare(a.NodeID[:], b.NodeID[:]) < 0 - }) - + slices.SortFunc(union, Canonical) return union } @@ -564,9 +563,7 @@ func (il IdentityList) Exists(target *Identity) bool { // target: value to search for // CAUTION: The identity list MUST be sorted prior to calling this method func (il IdentityList) IdentifierExists(target Identifier) bool { - _, ok := slices.BinarySearchFunc(il, &Identity{NodeID: target}, func(a, b *Identity) int { - return bytes.Compare(a.NodeID[:], b.NodeID[:]) - }) + _, ok := slices.BinarySearchFunc(il, &Identity{NodeID: target}, Canonical) return ok } diff --git a/model/flow/identity_order.go b/model/flow/identity_order.go new file mode 100644 index 00000000000..17930d79d82 --- /dev/null +++ b/model/flow/identity_order.go @@ -0,0 +1,62 @@ +// (c) 2019 Dapper Labs - ALL RIGHTS RESERVED + +package flow + +// Canonical is a function that defines a weak strict ordering "<" for identities. +// It returns: +// - a strict negative number if id1 < id2 +// - a strict positive number if id2 < id1 +// - zero if id1 and id2 are equal +// +// By definition, two identities (id1, id2) are in canonical order if id1's NodeID is lexicographically +// _strictly_ smaller than id2's NodeID. The strictness is important, meaning that identities +// with equal NodeIDs do not satisfy canonical ordering (order is irreflexive). +// Hence, only a returned strictly negative value means the pair is in canonical order. +// Use `IsCanonical` for canonical order checks. +// +// The current function is based on the identifiers bytes lexicographic comparison. +func Canonical(identity1 *Identity, identity2 *Identity) int { + return IdentifierCanonical(identity1.NodeID, identity2.NodeID) +} + +// IsCanonical returns true if and only if the given Identities are in canonical order. +// +// By convention, two Identities (i1, i2) are in canonical order if i1's NodeID bytes +// are lexicographically _strictly_ smaller than i2's NodeID bytes. +// +// The strictness is important, meaning that two identities with the same +// NodeID do not satisfy the canonical order. +// This also implies that the canonical order is irreflexive ((i,i) isn't in canonical order). +func IsCanonical(i1, i2 *Identity) bool { + return Canonical(i1, i2) < 0 +} + +// ByReferenceOrder return a function for sorting identities based on the order +// of the given nodeIDs +func ByReferenceOrder(nodeIDs []Identifier) func(*Identity, *Identity) int { + indices := make(map[Identifier]int) + for index, nodeID := range nodeIDs { + _, ok := indices[nodeID] + if ok { + panic("should never order by reference order with duplicate node IDs") + } + indices[nodeID] = index + } + return func(identity1 *Identity, identity2 *Identity) int { + return indices[identity1.NodeID] - indices[identity2.NodeID] + } +} + +// IsIdentityListCanonical returns true if and only if the given IdentityList is +// _strictly_ sorted with regards to the canonical order. +// +// The strictness is important here, meaning that a list with 2 successive entities +// with equal NodeID isn't considered well sorted. +func IsIdentityListCanonical(il IdentityList) bool { + for i := 0; i < len(il)-1; i++ { + if !IsCanonical(il[i], il[i+1]) { + return false + } + } + return true +} diff --git a/model/flow/identity_test.go b/model/flow/identity_test.go index 849db712d7d..c3350ab94e2 100644 --- a/model/flow/identity_test.go +++ b/model/flow/identity_test.go @@ -5,14 +5,13 @@ import ( "strings" "testing" + "github.com/onflow/crypto" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/vmihailenco/msgpack/v4" - "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/model/encodable" "github.com/onflow/flow-go/model/flow" - "github.com/onflow/flow-go/model/flow/order" "github.com/onflow/flow-go/utils/unittest" ) @@ -103,7 +102,7 @@ func TestIdentityList_Exists(t *testing.T) { il2 := unittest.IdentityListFixture(1) // sort the first list - il1 = il1.Sort(order.Canonical) + il1 = il1.Sort(flow.Canonical) for i := 0; i < 10; i++ { assert.True(t, il1.Exists(il1[i])) @@ -118,7 +117,7 @@ func TestIdentityList_IdentifierExists(t *testing.T) { il2 := unittest.IdentityListFixture(1) // sort the first list - il1 = il1.Sort(order.Canonical) + il1 = il1.Sort(flow.Canonical) for i := 0; i < 10; i++ { assert.True(t, il1.IdentifierExists(il1[i].NodeID)) @@ -243,12 +242,18 @@ func TestIdentity_ID(t *testing.T) { func TestIdentity_Sort(t *testing.T) { il := unittest.IdentityListFixture(20) - random, err := il.Shuffle() - require.NoError(t, err) - assert.False(t, random.Sorted(order.Canonical)) - - canonical := il.Sort(order.Canonical) - assert.True(t, canonical.Sorted(order.Canonical)) + // make sure the list is not sorted + il[0].NodeID[0], il[1].NodeID[0] = 2, 1 + require.False(t, flow.IsCanonical(il[0], il[1])) + assert.False(t, flow.IsIdentityListCanonical(il)) + + canonical := il.Sort(flow.Canonical) + assert.True(t, flow.IsIdentityListCanonical(canonical)) + + // check `IsIdentityListCanonical` detects order equality in a sorted list + il[1] = il[10] // add a duplication + canonical = il.Sort(flow.Canonical) + assert.False(t, flow.IsIdentityListCanonical(canonical)) } func TestIdentity_EqualTo(t *testing.T) { diff --git a/model/flow/ledger.go b/model/flow/ledger.go index 96c33849f5f..b6824c8a49e 100644 --- a/model/flow/ledger.go +++ b/model/flow/ledger.go @@ -81,18 +81,18 @@ func ContractRegisterID(address Address, contractName string) RegisterID { func CadenceRegisterID(owner []byte, key []byte) RegisterID { return RegisterID{ - Owner: string(BytesToAddress(owner).Bytes()), + Owner: addressToOwner(BytesToAddress(owner)), Key: string(key), } } -func NewRegisterID(owner, key string) RegisterID { +func NewRegisterID(owner Address, key string) RegisterID { // global registers have an empty owner field ownerString := "" // all other registers have the account's address - if len(owner) > 0 { - ownerString = addressToOwner(BytesToAddress([]byte(owner))) + if owner != EmptyAddress { + ownerString = addressToOwner(owner) } return RegisterID{ diff --git a/model/flow/ledger_test.go b/model/flow/ledger_test.go index 0857a8c373c..b287c3d3bb0 100644 --- a/model/flow/ledger_test.go +++ b/model/flow/ledger_test.go @@ -1,4 +1,4 @@ -package flow +package flow_test import ( "encoding/hex" @@ -10,6 +10,9 @@ import ( "github.com/stretchr/testify/require" "github.com/onflow/atree" + + "github.com/onflow/flow-go/model/flow" + "github.com/onflow/flow-go/utils/unittest" ) // this benchmark can run with this command: @@ -20,7 +23,7 @@ var length int func BenchmarkString(b *testing.B) { - r := NewRegisterID("theowner", "123412341234") + r := flow.NewRegisterID(unittest.RandomAddressFixture(), "123412341234") ownerLen := len(r.Owner) @@ -40,7 +43,7 @@ func BenchmarkString(b *testing.B) { func BenchmarkOriginalString(b *testing.B) { - r := NewRegisterID("theowner", "123412341234") + r := flow.NewRegisterID(unittest.RandomAddressFixture(), "123412341234") ret := fmt.Sprintf("%x/%x", r.Owner, r.Key) @@ -48,37 +51,37 @@ func BenchmarkOriginalString(b *testing.B) { } func TestRegisterID_IsInternalState(t *testing.T) { - requireTrue := func(owner string, key string) { - id := NewRegisterID(owner, key) + requireTrue := func(owner flow.Address, key string) { + id := flow.NewRegisterID(owner, key) require.True(t, id.IsInternalState()) } - requireFalse := func(owner string, key string) { - id := NewRegisterID(owner, key) + requireFalse := func(owner flow.Address, key string) { + id := flow.NewRegisterID(owner, key) require.False(t, id.IsInternalState()) } for i := 0; i < 256; i++ { - uuid := UUIDRegisterID(byte(i)) + uuid := flow.UUIDRegisterID(byte(i)) if i == 0 { - require.Equal(t, uuid.Key, UUIDKeyPrefix) - requireTrue("", UUIDKeyPrefix) + require.Equal(t, uuid.Key, flow.UUIDKeyPrefix) + requireTrue(flow.EmptyAddress, flow.UUIDKeyPrefix) } else { - require.Equal(t, uuid.Key, fmt.Sprintf("%s_%d", UUIDKeyPrefix, i)) - requireTrue("", fmt.Sprintf("%s_%d", UUIDKeyPrefix, i)) + require.Equal(t, uuid.Key, fmt.Sprintf("%s_%d", flow.UUIDKeyPrefix, i)) + requireTrue(flow.EmptyAddress, fmt.Sprintf("%s_%d", flow.UUIDKeyPrefix, i)) } require.True(t, uuid.IsInternalState()) } - require.True(t, AddressStateRegisterID.IsInternalState()) - requireTrue("", AddressStateKey) - requireFalse("", "other") - requireFalse("Address", UUIDKeyPrefix) - requireFalse("Address", AddressStateKey) - requireTrue("Address", "public_key_12") - requireTrue("Address", ContractNamesKey) - requireTrue("Address", "code.MYCODE") - requireTrue("Address", AccountStatusKey) - requireFalse("Address", "anything else") + require.True(t, flow.AddressStateRegisterID.IsInternalState()) + requireTrue(flow.EmptyAddress, flow.AddressStateKey) + requireFalse(flow.EmptyAddress, "other") + requireFalse(unittest.RandomAddressFixture(), flow.UUIDKeyPrefix) + requireFalse(unittest.RandomAddressFixture(), flow.AddressStateKey) + requireTrue(unittest.RandomAddressFixture(), "public_key_12") + requireTrue(unittest.RandomAddressFixture(), flow.ContractNamesKey) + requireTrue(unittest.RandomAddressFixture(), "code.MYCODE") + requireTrue(unittest.RandomAddressFixture(), flow.AccountStatusKey) + requireFalse(unittest.RandomAddressFixture(), "anything else") } func TestRegisterID_String(t *testing.T) { @@ -86,8 +89,8 @@ func TestRegisterID_String(t *testing.T) { // slab with 189 should result in \\xbd slabIndex := atree.StorageIndex([8]byte{0, 0, 0, 0, 0, 0, 0, 189}) - id := NewRegisterID( - string([]byte{1, 2, 3, 10}), + id := flow.NewRegisterID( + flow.BytesToAddress([]byte{1, 2, 3, 10}), string(atree.SlabIndexToLedgerKey(slabIndex))) require.False(t, utf8.ValidString(id.Key)) printable := id.String() @@ -96,7 +99,7 @@ func TestRegisterID_String(t *testing.T) { }) t.Run("non slab invalid utf-8", func(t *testing.T) { - id := NewRegisterID("b\xc5y", "a\xc5z") + id := flow.NewRegisterID(flow.BytesToAddress([]byte("b\xc5y")), "a\xc5z") require.False(t, utf8.ValidString(id.Owner)) require.False(t, utf8.ValidString(id.Key)) printable := id.String() @@ -105,8 +108,8 @@ func TestRegisterID_String(t *testing.T) { }) t.Run("global register", func(t *testing.T) { - uuidRegisterID := UUIDRegisterID(0) - id := NewRegisterID(uuidRegisterID.Owner, uuidRegisterID.Key) + uuidRegisterID := flow.UUIDRegisterID(0) + id := flow.NewRegisterID(flow.EmptyAddress, uuidRegisterID.Key) require.Equal(t, uuidRegisterID.Owner, id.Owner) require.Equal(t, uuidRegisterID.Key, id.Key) printable := id.String() diff --git a/model/flow/order/identifier.go b/model/flow/order/identifier.go deleted file mode 100644 index 0102005b1b8..00000000000 --- a/model/flow/order/identifier.go +++ /dev/null @@ -1,13 +0,0 @@ -package order - -import ( - "bytes" - - "github.com/onflow/flow-go/model/flow" -) - -// IdentifierCanonical is a function for sorting IdentifierList into -// canonical order -func IdentifierCanonical(id1 flow.Identifier, id2 flow.Identifier) bool { - return bytes.Compare(id1[:], id2[:]) < 0 -} diff --git a/model/flow/order/identity.go b/model/flow/order/identity.go deleted file mode 100644 index 5b78c7a3dd4..00000000000 --- a/model/flow/order/identity.go +++ /dev/null @@ -1,47 +0,0 @@ -// (c) 2019 Dapper Labs - ALL RIGHTS RESERVED - -package order - -import ( - "github.com/onflow/flow-go/model/flow" -) - -// Canonical represents the canonical ordering for identity lists. -func Canonical(identity1 *flow.Identity, identity2 *flow.Identity) bool { - return IdentifierCanonical(identity1.NodeID, identity2.NodeID) -} - -// ByReferenceOrder return a function for sorting identities based on the order -// of the given nodeIDs -func ByReferenceOrder(nodeIDs []flow.Identifier) func(*flow.Identity, *flow.Identity) bool { - indices := make(map[flow.Identifier]uint) - for index, nodeID := range nodeIDs { - _, ok := indices[nodeID] - if ok { - panic("should never order by reference order with duplicate node IDs") - } - indices[nodeID] = uint(index) - } - return func(identity1 *flow.Identity, identity2 *flow.Identity) bool { - return indices[identity1.NodeID] < indices[identity2.NodeID] - } -} - -// IdentityListCanonical takes a list of identities and -// check if it's ordered in canonical order. -func IdentityListCanonical(identities flow.IdentityList) bool { - if len(identities) == 0 { - return true - } - - prev := identities[0].ID() - for i := 1; i < len(identities); i++ { - id := identities[i].ID() - if !IdentifierCanonical(prev, id) { - return false - } - prev = id - } - - return true -} diff --git a/model/flow/order/identity_test.go b/model/flow/order/identity_test.go deleted file mode 100644 index 2c79b61ab4a..00000000000 --- a/model/flow/order/identity_test.go +++ /dev/null @@ -1,18 +0,0 @@ -// (c) 2019 Dapper Labs - ALL RIGHTS RESERVED - -package order_test - -import ( - "testing" - - "github.com/stretchr/testify/require" - - "github.com/onflow/flow-go/model/flow/order" - "github.com/onflow/flow-go/utils/unittest" -) - -// Test the canonical ordering of identity and identifier match -func TestCanonicalOrderingMatch(t *testing.T) { - identities := unittest.IdentityListFixture(100) - require.Equal(t, identities.Sort(order.Canonical).NodeIDs(), identities.NodeIDs().Sort(order.IdentifierCanonical)) -} diff --git a/model/flow/resultApproval.go b/model/flow/resultApproval.go index 3f1b3f5701e..5b096a5a936 100644 --- a/model/flow/resultApproval.go +++ b/model/flow/resultApproval.go @@ -1,7 +1,7 @@ package flow import ( - "github.com/onflow/flow-go/crypto" + "github.com/onflow/crypto" ) // Attestation confirms correctness of a chunk of an exec result diff --git a/model/flow/sealing_segment.go b/model/flow/sealing_segment.go index ea96d69fb64..dffd5a9cef5 100644 --- a/model/flow/sealing_segment.go +++ b/model/flow/sealing_segment.go @@ -315,8 +315,8 @@ func (builder *SealingSegmentBuilder) SealingSegment() (*SealingSegment, error) // SealingSegment must store extra blocks in ascending order, builder stores them in descending. // Apply a sort to reverse the slice and use correct ordering. - slices.SortFunc(builder.extraBlocks, func(lhs, rhs *Block) bool { - return lhs.Header.Height < rhs.Header.Height + slices.SortFunc(builder.extraBlocks, func(lhs, rhs *Block) int { + return int(lhs.Header.Height) - int(rhs.Header.Height) }) return &SealingSegment{ diff --git a/model/flow/service_event_test.go b/model/flow/service_event_test.go index 90c571fc4ba..b1a723a2ea8 100644 --- a/model/flow/service_event_test.go +++ b/model/flow/service_event_test.go @@ -6,11 +6,11 @@ import ( "github.com/fxamacker/cbor/v2" "github.com/google/go-cmp/cmp" + "github.com/onflow/crypto" "github.com/stretchr/testify/require" "github.com/vmihailenco/msgpack" "gotest.tools/assert" - "github.com/onflow/flow-go/crypto" cborcodec "github.com/onflow/flow-go/model/encoding/cbor" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/utils/unittest" diff --git a/model/flow/timeout_certificate.go b/model/flow/timeout_certificate.go index 481d86d90b6..e7a4df39058 100644 --- a/model/flow/timeout_certificate.go +++ b/model/flow/timeout_certificate.go @@ -1,6 +1,6 @@ package flow -import "github.com/onflow/flow-go/crypto" +import "github.com/onflow/crypto" // TimeoutCertificate proves that a super-majority of consensus participants want to abandon the specified View. // At its core, a timeout certificate is an aggregation of TimeoutObjects, which individual nodes send to signal diff --git a/model/flow/transaction.go b/model/flow/transaction.go index 95556ff4c05..37525aa0511 100644 --- a/model/flow/transaction.go +++ b/model/flow/transaction.go @@ -2,10 +2,11 @@ package flow import ( "fmt" - "sort" - "github.com/onflow/flow-go/crypto" - "github.com/onflow/flow-go/crypto/hash" + "github.com/onflow/crypto" + "github.com/onflow/crypto/hash" + "golang.org/x/exp/slices" + "github.com/onflow/flow-go/model/fingerprint" ) @@ -124,8 +125,8 @@ func (tb *TransactionBody) SetReferenceBlockID(blockID Identifier) *TransactionB return tb } -// SetGasLimit sets the gas limit for this transaction. -func (tb *TransactionBody) SetGasLimit(limit uint64) *TransactionBody { +// SetComputeLimit sets the gas limit for this transaction. +func (tb *TransactionBody) SetComputeLimit(limit uint64) *TransactionBody { tb.GasLimit = limit return tb } @@ -306,7 +307,7 @@ func (tb *TransactionBody) AddPayloadSignature(address Address, keyID uint64, si s := tb.createSignature(address, keyID, sig) tb.PayloadSignatures = append(tb.PayloadSignatures, s) - sort.Slice(tb.PayloadSignatures, compareSignatures(tb.PayloadSignatures)) + slices.SortFunc(tb.PayloadSignatures, compareSignatures) return tb } @@ -316,7 +317,7 @@ func (tb *TransactionBody) AddEnvelopeSignature(address Address, keyID uint64, s s := tb.createSignature(address, keyID, sig) tb.EnvelopeSignatures = append(tb.EnvelopeSignatures, s) - sort.Slice(tb.EnvelopeSignatures, compareSignatures(tb.EnvelopeSignatures)) + slices.SortFunc(tb.EnvelopeSignatures, compareSignatures) return tb } @@ -488,17 +489,12 @@ func (s TransactionSignature) canonicalForm() interface{} { } } -func compareSignatures(signatures []TransactionSignature) func(i, j int) bool { - return func(i, j int) bool { - sigA := signatures[i] - sigB := signatures[j] - - if sigA.SignerIndex == sigB.SignerIndex { - return sigA.KeyIndex < sigB.KeyIndex - } - - return sigA.SignerIndex < sigB.SignerIndex +func compareSignatures(sigA, sigB TransactionSignature) int { + if sigA.SignerIndex == sigB.SignerIndex { + return int(sigA.KeyIndex) - int(sigB.KeyIndex) } + + return sigA.SignerIndex - sigB.SignerIndex } type signaturesList []TransactionSignature diff --git a/model/hash/hash.go b/model/hash/hash.go index d72c8dcf0a1..ec494525b49 100644 --- a/model/hash/hash.go +++ b/model/hash/hash.go @@ -1,7 +1,7 @@ package hash import ( - "github.com/onflow/flow-go/crypto/hash" + "github.com/onflow/crypto/hash" ) // DefaultComputeHash is the default hasher used by Flow. diff --git a/model/messages/dkg.go b/model/messages/dkg.go index 9555f5cd7ba..63fc8f0820b 100644 --- a/model/messages/dkg.go +++ b/model/messages/dkg.go @@ -1,7 +1,8 @@ package messages import ( - "github.com/onflow/flow-go/crypto" + "github.com/onflow/crypto" + "github.com/onflow/flow-go/model/flow" ) diff --git a/model/verification/chunkDataPackRequest_test.go b/model/verification/chunkDataPackRequest_test.go index cb64b7ec502..6bb6bfe419e 100644 --- a/model/verification/chunkDataPackRequest_test.go +++ b/model/verification/chunkDataPackRequest_test.go @@ -8,7 +8,6 @@ import ( "github.com/stretchr/testify/require" "github.com/onflow/flow-go/model/flow" - "github.com/onflow/flow-go/model/flow/order" "github.com/onflow/flow-go/model/verification" "github.com/onflow/flow-go/utils/unittest" ) @@ -56,7 +55,7 @@ func TestChunkDataPackRequestList_UniqueRequestInfo(t *testing.T) { return bytes.Compare(thisChunkIDReqInfo.Disagrees[p][:], thisChunkIDReqInfo.Disagrees[q][:]) < 0 }) - thisChunkIDReqInfo.Targets = thisChunkIDReqInfo.Targets.Sort(order.Canonical) + thisChunkIDReqInfo.Targets = thisChunkIDReqInfo.Targets.Sort(flow.Canonical) require.Equal(t, thisChunkIDReqInfo.Agrees, thisReq1.Agrees.Union(thisReq2.Agrees)) require.Equal(t, thisChunkIDReqInfo.Disagrees, thisReq1.Disagrees.Union(thisReq2.Disagrees)) diff --git a/module/chunks/chunkVerifier_test.go b/module/chunks/chunkVerifier_test.go index e5bfb6f8e22..4ea50cb3fed 100644 --- a/module/chunks/chunkVerifier_test.go +++ b/module/chunks/chunkVerifier_test.go @@ -50,8 +50,8 @@ var eventsList = flow.EventsList{ const computationUsed = uint64(100) -var id0 = flow.NewRegisterID("00", "") -var id5 = flow.NewRegisterID("05", "") +var id0 = flow.NewRegisterID(unittest.RandomAddressFixture(), "") +var id5 = flow.NewRegisterID(unittest.RandomAddressFixture(), "") // the chain we use for this test suite var testChain = flow.Emulator @@ -403,16 +403,13 @@ func blockFixture(collection *flow.Collection) *flow.Block { } func generateStateUpdates(t *testing.T, f *completeLedger.Ledger) (ledger.State, ledger.Proof, *ledger.Update) { - id1 := flow.NewRegisterID("00", "") - id2 := flow.NewRegisterID("05", "") - entries := flow.RegisterEntries{ { - Key: id1, + Key: id0, Value: []byte{'a'}, }, { - Key: id2, + Key: id5, Value: []byte{'b'}, }, } @@ -432,7 +429,7 @@ func generateStateUpdates(t *testing.T, f *completeLedger.Ledger) (ledger.State, entries = flow.RegisterEntries{ { - Key: id2, + Key: id5, Value: []byte{'B'}, }, } diff --git a/module/chunks/chunk_assigner.go b/module/chunks/chunk_assigner.go index dc34816a784..6491081141b 100644 --- a/module/chunks/chunk_assigner.go +++ b/module/chunks/chunk_assigner.go @@ -3,8 +3,9 @@ package chunks import ( "fmt" - "github.com/onflow/flow-go/crypto/hash" - "github.com/onflow/flow-go/crypto/random" + "github.com/onflow/crypto/hash" + "github.com/onflow/crypto/random" + chunkmodels "github.com/onflow/flow-go/model/chunks" "github.com/onflow/flow-go/model/encoding/json" "github.com/onflow/flow-go/model/flow" diff --git a/module/dkg.go b/module/dkg.go index 7952fbbdc8d..0f6a83cc9a8 100644 --- a/module/dkg.go +++ b/module/dkg.go @@ -1,7 +1,8 @@ package module import ( - "github.com/onflow/flow-go/crypto" + "github.com/onflow/crypto" + "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/model/messages" ) diff --git a/module/dkg/broker.go b/module/dkg/broker.go index f94fbc981fe..62458613a1b 100644 --- a/module/dkg/broker.go +++ b/module/dkg/broker.go @@ -6,10 +6,10 @@ import ( "sync" "time" + "github.com/onflow/crypto" "github.com/rs/zerolog" "github.com/sethvargo/go-retry" - "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/engine" "github.com/onflow/flow-go/model/fingerprint" "github.com/onflow/flow-go/model/flow" diff --git a/module/dkg/client.go b/module/dkg/client.go index fbe7247ed46..391d1ec272e 100644 --- a/module/dkg/client.go +++ b/module/dkg/client.go @@ -7,15 +7,16 @@ import ( "strconv" "time" + "github.com/rs/zerolog" + "github.com/onflow/cadence" + "github.com/onflow/crypto" "github.com/onflow/flow-core-contracts/lib/go/templates" - "github.com/rs/zerolog" sdk "github.com/onflow/flow-go-sdk" sdkcrypto "github.com/onflow/flow-go-sdk/crypto" - "github.com/onflow/flow-go/model/flow" - "github.com/onflow/flow-go/crypto" + "github.com/onflow/flow-go/model/flow" model "github.com/onflow/flow-go/model/messages" "github.com/onflow/flow-go/module" "github.com/onflow/flow-go/module/epochs" @@ -126,7 +127,7 @@ func (c *Client) Broadcast(msg model.BroadcastDKGMessage) error { // construct transaction to send dkg whiteboard message to contract tx := sdk.NewTransaction(). SetScript(templates.GenerateSendDKGWhiteboardMessageScript(c.env)). - SetGasLimit(9999). + SetComputeLimit(9999). SetReferenceBlockID(latestBlock.ID). SetProposalKey(account.Address, int(c.AccountKeyIndex), account.Keys[int(c.AccountKeyIndex)].SequenceNumber). SetPayer(account.Address). @@ -192,7 +193,7 @@ func (c *Client) SubmitResult(groupPublicKey crypto.PublicKey, publicKeys []cryp tx := sdk.NewTransaction(). SetScript(templates.GenerateSendDKGFinalSubmissionScript(c.env)). - SetGasLimit(9999). + SetComputeLimit(9999). SetReferenceBlockID(latestBlock.ID). SetProposalKey(account.Address, int(c.AccountKeyIndex), account.Keys[int(c.AccountKeyIndex)].SequenceNumber). SetPayer(account.Address). diff --git a/module/dkg/controller.go b/module/dkg/controller.go index ae4b54ecb38..e37924c6e81 100644 --- a/module/dkg/controller.go +++ b/module/dkg/controller.go @@ -2,68 +2,15 @@ package dkg import ( "fmt" - "math" "sync" - "time" + "github.com/onflow/crypto" "github.com/rs/zerolog" - "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/module" - "github.com/onflow/flow-go/utils/rand" ) -const ( - - // DefaultBaseStartDelay is the default base delay to use when introducing - // random delay to the DKG start process. See preStartDelay for details. - DefaultBaseStartDelay = 500 * time.Microsecond - - // DefaultBaseHandleFirstBroadcastDelay is the default base to use when - // introducing random delay to processing the first DKG broadcast message. - // See preHandleFirstBroadcastDelay for details. - // - // For a 150-node DKG, we observe a cost of ~2.5s per message to process - // broadcast messages during phase 1, for a total of ~6m of total CPU time. - // We would like to target spreading this cost over a 30 minute period. - // With the default value for DefaultHandleSubsequentBroadcastDelay, this - // results in processing all phase 1 messages in 6m+6m=12m, so for a maximum - // total processing time of 30m, we sample the initial delay from [0,18m]. - // We use 50ms as the default because 50ms*150^2 = 18.75m - // - DefaultBaseHandleFirstBroadcastDelay = 50 * time.Millisecond - - // DefaultHandleSubsequentBroadcastDelay is the default delay to use before - // processing all DKG broadcasts after the first. - DefaultHandleSubsequentBroadcastDelay = 2500 * time.Millisecond -) - -// ControllerConfig defines configuration for the DKG Controller. These define -// how the DKG controller introduces delays to expensive DKG computations. -// -// We introduce delays for two reasons: -// 1. Avoid running long-running expensive DKG computations consecutively. -// 2. Avoid synchronizing expensive DKG computations across the DKG committee. -// -// Delays introduced prior to DKG start and prior to processing the FIRST broadcast -// message are sampled uniformly from [0,m), where m=b*n^2 -// -// b = base delay (from config) -// n = size of DKG committee -// -// Delays introduced prior to processing subsequent broadcast messages are constant. -type ControllerConfig struct { - // BaseStartDelay determines the maximum delay before starting the DKG. - BaseStartDelay time.Duration - // BaseHandleFirstBroadcastDelay determines the maximum delay before handling - // the first broadcast message. - BaseHandleFirstBroadcastDelay time.Duration - // HandleSubsequentBroadcastDelay determines the constant delay before handling - // all broadcast messages following the first. - HandleSubsequentBroadcastDelay time.Duration -} - // Controller implements the DKGController interface. It controls the execution // of a Joint Feldman DKG instance. A new Controller must be instantiated for // every epoch. @@ -101,8 +48,7 @@ type Controller struct { // artifactsLock protects access to artifacts artifactsLock sync.Mutex - config ControllerConfig - once *sync.Once + once *sync.Once } // NewController instantiates a new Joint Feldman DKG controller. @@ -112,7 +58,6 @@ func NewController( dkg crypto.DKGState, seed []byte, broker module.DKGBroker, - config ControllerConfig, ) *Controller { logger := log.With(). @@ -130,7 +75,6 @@ func NewController( endCh: make(chan struct{}), shutdownCh: make(chan struct{}), once: new(sync.Once), - config: config, } } @@ -293,32 +237,6 @@ func (c *Controller) doBackgroundWork() { case msg := <-broadcastMsgCh: - // before processing a broadcast message during phase 1, sleep for a - // random delay to avoid synchronizing this expensive operation across - // all consensus nodes - state := c.GetState() - if state == Phase1 { - - // introduce a large, uniformly sampled delay prior to processing - // the first message - isFirstMessage := false - c.once.Do(func() { - isFirstMessage = true - delay, err := c.preHandleFirstBroadcastDelay() - if err != nil { - c.log.Err(err).Msg("pre handle first broadcast delay failed") - } - c.log.Info().Msgf("sleeping for %s before processing first phase 1 broadcast message", delay) - time.Sleep(delay) - }) - - if !isFirstMessage { - // introduce a constant delay for all subsequent messages - c.log.Debug().Msgf("sleeping for %s before processing subsequent phase 1 broadcast message", c.config.HandleSubsequentBroadcastDelay) - time.Sleep(c.config.HandleSubsequentBroadcastDelay) - } - } - c.dkgLock.Lock() err := c.dkg.HandleBroadcastMsg(int(msg.CommitteeMemberIndex), msg.Data) c.dkgLock.Unlock() @@ -338,17 +256,8 @@ func (c *Controller) start() error { return fmt.Errorf("cannot execute start routine in state %s", state) } - // before starting the DKG, sleep for a random delay to avoid synchronizing - // this expensive operation across all consensus nodes - delay, err := c.preStartDelay() - if err != nil { - return fmt.Errorf("pre start delay failed: %w", err) - } - c.log.Debug().Msgf("sleeping for %s before starting DKG", delay) - time.Sleep(delay) - c.dkgLock.Lock() - err = c.dkg.Start(c.seed) + err := c.dkg.Start(c.seed) c.dkgLock.Unlock() if err != nil { return fmt.Errorf("Error starting DKG: %w", err) @@ -423,56 +332,3 @@ func (c *Controller) phase3() error { } } } - -// preStartDelay returns a duration to delay prior to starting the DKG process. -// This prevents synchronization of the DKG starting (an expensive operation) -// across the network, which can impact finalization. -func (c *Controller) preStartDelay() (time.Duration, error) { - return computePreprocessingDelay(c.config.BaseStartDelay, c.dkg.Size()) -} - -// preHandleFirstBroadcastDelay returns a duration to delay prior to handling -// the first broadcast message. This delay is used only during phase 1 of the DKG. -// This prevents synchronization of processing verification vectors (an -// expensive operation) across the network, which can impact finalization. -func (c *Controller) preHandleFirstBroadcastDelay() (time.Duration, error) { - return computePreprocessingDelay(c.config.BaseHandleFirstBroadcastDelay, c.dkg.Size()) -} - -// computePreprocessingDelay computes a random delay to introduce before an -// expensive operation. -// -// The maximum delay is m=b*n^2 where: -// * b is a configurable base delay -// * n is the size of the DKG committee -func computePreprocessingDelay(baseDelay time.Duration, dkgSize int) (time.Duration, error) { - - maxDelay := computePreprocessingDelayMax(baseDelay, dkgSize) - if maxDelay <= 0 { - return 0, nil - } - // select delay from [0,m) - r, err := rand.Uint64n(uint64(maxDelay.Nanoseconds())) - if err != nil { - return time.Duration(0), fmt.Errorf("delay generation failed %w", err) - } - return time.Duration(r), nil -} - -// computePreprocessingDelayMax computes the maximum dely for computePreprocessingDelay. -func computePreprocessingDelayMax(baseDelay time.Duration, dkgSize int) time.Duration { - // sanity checks - if baseDelay < 0 { - baseDelay = 0 - } - if dkgSize < 0 { - dkgSize = 0 - } - - // m=b*n^2 - maxDelay := time.Duration(math.Pow(float64(dkgSize), 2)) * baseDelay - if maxDelay <= 0 { - return 0 - } - return maxDelay -} diff --git a/module/dkg/controller_factory.go b/module/dkg/controller_factory.go index ae12219706e..b1e01b8e592 100644 --- a/module/dkg/controller_factory.go +++ b/module/dkg/controller_factory.go @@ -3,9 +3,9 @@ package dkg import ( "fmt" + "github.com/onflow/crypto" "github.com/rs/zerolog" - "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/module" "github.com/onflow/flow-go/module/signature" @@ -21,7 +21,6 @@ type ControllerFactory struct { me module.Local dkgContractClients []module.DKGContractClient tunnel *BrokerTunnel - config ControllerConfig } // NewControllerFactory creates a new factory that generates Controllers with @@ -31,14 +30,13 @@ func NewControllerFactory( me module.Local, dkgContractClients []module.DKGContractClient, tunnel *BrokerTunnel, - config ControllerConfig) *ControllerFactory { +) *ControllerFactory { return &ControllerFactory{ log: log, me: me, dkgContractClients: dkgContractClients, tunnel: tunnel, - config: config, } } @@ -77,7 +75,6 @@ func (f *ControllerFactory) Create( dkg, seed, broker, - f.config, ) return controller, nil diff --git a/module/dkg/controller_test.go b/module/dkg/controller_test.go index 3d9d1676a6a..9c92069ddf8 100644 --- a/module/dkg/controller_test.go +++ b/module/dkg/controller_test.go @@ -6,11 +6,10 @@ import ( "testing" "time" + "github.com/onflow/crypto" "github.com/rs/zerolog" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/model/flow" msg "github.com/onflow/flow-go/model/messages" "github.com/onflow/flow-go/module/signature" @@ -253,20 +252,12 @@ func initNodes(t *testing.T, n int, phase1Duration, phase2Duration, phase3Durati dkg, err := crypto.NewJointFeldman(n, signature.RandomBeaconThreshold(n), i, broker) require.NoError(t, err) - // create a config with no delays for tests - config := ControllerConfig{ - BaseStartDelay: 0, - BaseHandleFirstBroadcastDelay: 0, - HandleSubsequentBroadcastDelay: 0, - } - controller := NewController( logger, "dkg_test", dkg, seed, broker, - config, ) require.NoError(t, err) @@ -329,62 +320,3 @@ func checkArtifacts(t *testing.T, nodes []*node, totalNodes int) { } } } - -func TestDelay(t *testing.T) { - - t.Run("should return 0 delay for <=0 inputs", func(t *testing.T) { - delay, err := computePreprocessingDelay(0, 100) - require.NoError(t, err) - assert.Equal(t, delay, time.Duration(0)) - delay, err = computePreprocessingDelay(time.Hour, 0) - require.NoError(t, err) - assert.Equal(t, delay, time.Duration(0)) - delay, err = computePreprocessingDelay(time.Millisecond, -1) - require.NoError(t, err) - assert.Equal(t, delay, time.Duration(0)) - delay, err = computePreprocessingDelay(-time.Millisecond, 100) - require.NoError(t, err) - assert.Equal(t, delay, time.Duration(0)) - }) - - // NOTE: this is a probabilistic test. It will (extremely infrequently) fail. - t.Run("should return different values for same inputs", func(t *testing.T) { - d1, err := computePreprocessingDelay(time.Hour, 100) - require.NoError(t, err) - d2, err := computePreprocessingDelay(time.Hour, 100) - require.NoError(t, err) - assert.NotEqual(t, d1, d2) - }) - - t.Run("should return values in expected range", func(t *testing.T) { - baseDelay := time.Second - dkgSize := 100 - minDelay := time.Duration(0) - // m=b*n^2 - expectedMaxDelay := time.Duration(int64(baseDelay) * int64(dkgSize) * int64(dkgSize)) - - maxDelay := computePreprocessingDelayMax(baseDelay, dkgSize) - assert.Equal(t, expectedMaxDelay, maxDelay) - - delay, err := computePreprocessingDelay(baseDelay, dkgSize) - require.NoError(t, err) - assert.LessOrEqual(t, minDelay, delay) - assert.GreaterOrEqual(t, expectedMaxDelay, delay) - }) - - t.Run("should return values in expected range for defaults", func(t *testing.T) { - baseDelay := DefaultBaseHandleFirstBroadcastDelay - dkgSize := 150 - minDelay := time.Duration(0) - // m=b*n^2 - expectedMaxDelay := time.Duration(int64(baseDelay) * int64(dkgSize) * int64(dkgSize)) - - maxDelay := computePreprocessingDelayMax(baseDelay, dkgSize) - assert.Equal(t, expectedMaxDelay, maxDelay) - - delay, err := computePreprocessingDelay(baseDelay, dkgSize) - require.NoError(t, err) - assert.LessOrEqual(t, minDelay, delay) - assert.GreaterOrEqual(t, expectedMaxDelay, delay) - }) -} diff --git a/module/dkg/hasher.go b/module/dkg/hasher.go index 8f6cee27f57..f7fbc826282 100644 --- a/module/dkg/hasher.go +++ b/module/dkg/hasher.go @@ -1,7 +1,8 @@ package dkg import ( - "github.com/onflow/flow-go/crypto/hash" + "github.com/onflow/crypto/hash" + "github.com/onflow/flow-go/module/signature" ) diff --git a/module/dkg/mock_client.go b/module/dkg/mock_client.go index 0bb3e2f85fb..0f05f14f34d 100644 --- a/module/dkg/mock_client.go +++ b/module/dkg/mock_client.go @@ -1,9 +1,9 @@ package dkg import ( + "github.com/onflow/crypto" "github.com/rs/zerolog" - "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/model/flow" model "github.com/onflow/flow-go/model/messages" ) diff --git a/module/dkg_broker.go b/module/dkg_broker.go index 7e64353816e..7bc02e2fb99 100644 --- a/module/dkg_broker.go +++ b/module/dkg_broker.go @@ -1,7 +1,8 @@ package module import ( - "github.com/onflow/flow-go/crypto" + "github.com/onflow/crypto" + "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/model/messages" ) diff --git a/module/epochs/epoch_config.go b/module/epochs/epoch_config.go index 2fdb317c51f..6e6c350e70d 100644 --- a/module/epochs/epoch_config.go +++ b/module/epochs/epoch_config.go @@ -3,8 +3,8 @@ package epochs import ( "github.com/onflow/cadence" jsoncdc "github.com/onflow/cadence/encoding/json" + "github.com/onflow/crypto" - "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/model/flow" ) diff --git a/module/epochs/machine_account_test.go b/module/epochs/machine_account_test.go index f5cb3dd8dad..2fcdf25a70f 100644 --- a/module/epochs/machine_account_test.go +++ b/module/epochs/machine_account_test.go @@ -3,13 +3,14 @@ package epochs import ( "testing" - "github.com/onflow/cadence" "github.com/rs/zerolog" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - sdkcrypto "github.com/onflow/flow-go-sdk/crypto" - "github.com/onflow/flow-go/crypto" + "github.com/onflow/cadence" + "github.com/onflow/crypto" + "github.com/onflow/crypto/hash" + "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/utils/unittest" ) @@ -42,13 +43,13 @@ func TestMachineAccountChecking(t *testing.T) { }) t.Run("inconsistent hash algo", func(t *testing.T) { local, remote := unittest.MachineAccountFixture(t) - remote.Keys[0].HashAlgo = sdkcrypto.SHA2_384 + remote.Keys[0].HashAlgo = hash.SHA2_384 err := CheckMachineAccountInfo(zerolog.Nop(), conf, flow.RoleConsensus, local, remote) require.Error(t, err) }) t.Run("inconsistent sig algo", func(t *testing.T) { local, remote := unittest.MachineAccountFixture(t) - remote.Keys[0].SigAlgo = sdkcrypto.ECDSA_secp256k1 + remote.Keys[0].SigAlgo = crypto.ECDSASecp256k1 err := CheckMachineAccountInfo(zerolog.Nop(), conf, flow.RoleConsensus, local, remote) require.Error(t, err) }) @@ -141,8 +142,8 @@ func TestMachineAccountChecking(t *testing.T) { t.Run("local file deviates from defaults", func(t *testing.T) { t.Run("hash algo", func(t *testing.T) { local, remote := unittest.MachineAccountFixture(t) - local.HashAlgorithm = sdkcrypto.SHA3_384 // non-standard hash algo - remote.Keys[0].HashAlgo = sdkcrypto.SHA3_384 // consistent between local/remote + local.HashAlgorithm = hash.SHA3_384 // non-standard hash algo + remote.Keys[0].HashAlgo = hash.SHA3_384 // consistent between local/remote log, hook := unittest.HookedLogger() err := CheckMachineAccountInfo(log, conf, flow.RoleConsensus, local, remote) @@ -155,10 +156,10 @@ func TestMachineAccountChecking(t *testing.T) { // non-standard sig algo sk := unittest.PrivateKeyFixture(crypto.ECDSASecp256k1, unittest.DefaultSeedFixtureLength) local.EncodedPrivateKey = sk.Encode() - local.SigningAlgorithm = sdkcrypto.ECDSA_secp256k1 + local.SigningAlgorithm = crypto.ECDSASecp256k1 // consistent between local/remote remote.Keys[0].PublicKey = sk.PublicKey() - remote.Keys[0].SigAlgo = sdkcrypto.ECDSA_secp256k1 + remote.Keys[0].SigAlgo = crypto.ECDSASecp256k1 log, hook := unittest.HookedLogger() err := CheckMachineAccountInfo(log, conf, flow.RoleConsensus, local, remote) diff --git a/module/epochs/qc_client.go b/module/epochs/qc_client.go index 8bf675f4048..ed039f9ca58 100644 --- a/module/epochs/qc_client.go +++ b/module/epochs/qc_client.go @@ -104,7 +104,7 @@ func (c *QCContractClient) SubmitVote(ctx context.Context, vote *model.Vote) err seqNumber := account.Keys[int(c.AccountKeyIndex)].SequenceNumber tx := sdk.NewTransaction(). SetScript(templates.GenerateSubmitVoteScript(c.env)). - SetGasLimit(9999). + SetComputeLimit(9999). SetReferenceBlockID(latestBlock.ID). SetProposalKey(account.Address, int(c.AccountKeyIndex), seqNumber). SetPayer(account.Address). diff --git a/module/execution/scripts_test.go b/module/execution/scripts_test.go index b73b905eb8f..9088403770f 100644 --- a/module/execution/scripts_test.go +++ b/module/execution/scripts_test.go @@ -156,7 +156,7 @@ func (s *scriptTestSuite) SetupTest() { s.Require().NoError(err) s.registerIndex = pebbleRegisters - index, err := indexer.New(logger, metrics.NewNoopCollector(), nil, s.registerIndex, headers, nil, nil) + index, err := indexer.New(logger, metrics.NewNoopCollector(), nil, s.registerIndex, headers, nil, nil, func(originID flow.Identifier, entity flow.Entity) {}) s.Require().NoError(err) scripts, err := NewScripts( diff --git a/module/finalizedreader/finalizedreader.go b/module/finalizedreader/finalizedreader.go index 01b6e4ec5ce..19012407d30 100644 --- a/module/finalizedreader/finalizedreader.go +++ b/module/finalizedreader/finalizedreader.go @@ -32,12 +32,12 @@ func (r *FinalizedReader) FinalizedBlockIDAtHeight(height uint64) (flow.Identifi return flow.ZeroID, fmt.Errorf("height not finalized (%v): %w", height, storage.ErrNotFound) } - header, err := r.headers.ByHeight(height) + finalizedID, err := r.headers.BlockIDByHeight(height) if err != nil { return flow.ZeroID, err } - return header.ID(), nil + return finalizedID, nil } // BlockFinalized implements the protocol.Consumer interface, which allows FinalizedReader diff --git a/module/finalizer/consensus/finalizer.go b/module/finalizer/consensus/finalizer.go index d0f8bdda796..b5fd97de564 100644 --- a/module/finalizer/consensus/finalizer.go +++ b/module/finalizer/consensus/finalizer.go @@ -75,12 +75,12 @@ func (f *Finalizer) MakeFinal(blockID flow.Identifier) error { } if pending.Height <= finalized { - dup, err := f.headers.ByHeight(pending.Height) + dupID, err := f.headers.BlockIDByHeight(pending.Height) if err != nil { return fmt.Errorf("could not retrieve finalized equivalent: %w", err) } - if dup.ID() != blockID { - return fmt.Errorf("cannot finalize pending block conflicting with finalized state (height: %d, pending: %x, finalized: %x)", pending.Height, blockID, dup.ID()) + if dupID != blockID { + return fmt.Errorf("cannot finalize pending block conflicting with finalized state (height: %d, pending: %x, finalized: %x)", pending.Height, blockID, dupID) } return nil } diff --git a/module/local.go b/module/local.go index e1fa2f5fa45..7e3621acc70 100644 --- a/module/local.go +++ b/module/local.go @@ -3,8 +3,9 @@ package module import ( - "github.com/onflow/flow-go/crypto" - "github.com/onflow/flow-go/crypto/hash" + "github.com/onflow/crypto" + "github.com/onflow/crypto/hash" + "github.com/onflow/flow-go/model/flow" ) diff --git a/module/local/me.go b/module/local/me.go index 6a2f1ce117a..468681d4da9 100644 --- a/module/local/me.go +++ b/module/local/me.go @@ -5,8 +5,9 @@ package local import ( "fmt" - "github.com/onflow/flow-go/crypto" - "github.com/onflow/flow-go/crypto/hash" + "github.com/onflow/crypto" + "github.com/onflow/crypto/hash" + "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/model/flow/filter" ) diff --git a/module/local/me_nokey.go b/module/local/me_nokey.go index d9de4348dc1..3027184f1a1 100644 --- a/module/local/me_nokey.go +++ b/module/local/me_nokey.go @@ -3,8 +3,9 @@ package local import ( "fmt" - "github.com/onflow/flow-go/crypto" - "github.com/onflow/flow-go/crypto/hash" + "github.com/onflow/crypto" + "github.com/onflow/crypto/hash" + "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/model/flow/filter" ) diff --git a/module/mempool/README.md b/module/mempool/README.md new file mode 100644 index 00000000000..ac7d6efadbf --- /dev/null +++ b/module/mempool/README.md @@ -0,0 +1,26 @@ +# The `mempool` module + +The `mempool` module provides mempool implementations for the Flow blockchain, which +are in-memory data structures that are tasked with storing the `flow.Entity` objects. +`flow.Entity` objects are the fundamental data model of the Flow blockchain, and +every Flow primitives such as transactions, blocks, and collections are represented +as `flow.Entity` objects. + +Each mempool implementation is tasked for storing a specific type of `flow.Entity`. +As a convention, all mempools are built on top of the `stdmap.Backend` struct, which +provides a thread-safe cache implementation for storing and retrieving `flow.Entity` objects. +The primary responsibility of the `stdmap.Backend` struct is to provide thread-safety for its underlying +data model (i.e., `mempool.Backdata`) that is tasked with maintaining the actual `flow.Entity` objects. + +At the moment, the `mempool` module provides two implementations for the `mempool.Backdata`: +- `backdata.Backdata`: a map implementation for storing `flow.Entity` objects using native Go `map`s. +- `herocache.Cache`: a cache implementation for storing `flow.Entity` objects, which is a heap-optimized + cache implementation that is aims on minimizing the memory footprint of the mempool on the heap and + reducing the GC pressure. + +Note-1: by design the `mempool.Backdata` interface is **not thread-safe**. Therefore, it is the responsibility +of the `stdmap.Backend` struct to provide thread-safety for its underlying `mempool.Backdata` implementation. + +Note-2: The `herocache.Cache` implementation is several orders of magnitude faster than the `backdata.Backdata` on +high-throughput workloads. For the read or write-heavy workloads, the `herocache.Cache` implementation is recommended as +the underlying `mempool.Backdata` implementation. diff --git a/module/mempool/backData.go b/module/mempool/backData.go index dbee603299a..cb61654dce6 100644 --- a/module/mempool/backData.go +++ b/module/mempool/backData.go @@ -23,6 +23,28 @@ type BackData interface { // Returns a bool which indicates whether the entity was updated as well as the updated entity. Adjust(entityID flow.Identifier, f func(flow.Entity) flow.Entity) (flow.Entity, bool) + // AdjustWithInit adjusts the entity using the given function if the given identifier can be found. When the + // entity is not found, it initializes the entity using the given init function and then applies the adjust function. + // Args: + // - entityID: the identifier of the entity to adjust. + // - adjust: the function that adjusts the entity. + // - init: the function that initializes the entity when it is not found. + // Returns: + // - the adjusted entity. + // + // - a bool which indicates whether the entity was adjusted. + AdjustWithInit(entityID flow.Identifier, adjust func(flow.Entity) flow.Entity, init func() flow.Entity) (flow.Entity, bool) + + // GetWithInit returns the given entity from the backdata. If the entity does not exist, it creates a new entity + // using the factory function and stores it in the backdata. + // Args: + // - entityID: the identifier of the entity to get. + // - init: the function that initializes the entity when it is not found. + // Returns: + // - the entity. + // - a bool which indicates whether the entity was found (or created). + GetWithInit(entityID flow.Identifier, init func() flow.Entity) (flow.Entity, bool) + // ByID returns the given entity from the backdata. ByID(entityID flow.Identifier) (flow.Entity, bool) diff --git a/module/mempool/herocache/backdata/cache.go b/module/mempool/herocache/backdata/cache.go index b498022c825..ec7a915dea3 100644 --- a/module/mempool/herocache/backdata/cache.go +++ b/module/mempool/herocache/backdata/cache.go @@ -59,6 +59,7 @@ type slotBucket struct { } // Cache implements an array-based generic memory pool backed by a fixed total array. +// Note that this implementation is NOT thread-safe, and the higher-level Backend is responsible for concurrency management. type Cache struct { logger zerolog.Logger collector module.HeroCacheMetrics @@ -203,6 +204,45 @@ func (c *Cache) Adjust(entityID flow.Identifier, f func(flow.Entity) flow.Entity return newEntity, true } +// AdjustWithInit adjusts the entity using the given function if the given identifier can be found. When the +// entity is not found, it initializes the entity using the given init function and then applies the adjust function. +// Args: +// - entityID: the identifier of the entity to adjust. +// - adjust: the function that adjusts the entity. +// - init: the function that initializes the entity when it is not found. +// Returns: +// - the adjusted entity. +// +// - a bool which indicates whether the entity was adjusted. +func (c *Cache) AdjustWithInit(entityID flow.Identifier, adjust func(flow.Entity) flow.Entity, init func() flow.Entity) (flow.Entity, bool) { + defer c.logTelemetry() + + if c.Has(entityID) { + return c.Adjust(entityID, adjust) + } + c.put(entityID, init()) + return c.Adjust(entityID, adjust) +} + +// GetWithInit returns the given entity from the backdata. If the entity does not exist, it creates a new entity +// using the factory function and stores it in the backdata. +// Args: +// - entityID: the identifier of the entity to get. +// - init: the function that initializes the entity when it is not found. +// Returns: +// - the entity. +// +// - a bool which indicates whether the entity was found (or created). +func (c *Cache) GetWithInit(entityID flow.Identifier, init func() flow.Entity) (flow.Entity, bool) { + defer c.logTelemetry() + + if c.Has(entityID) { + return c.ByID(entityID) + } + c.put(entityID, init()) + return c.ByID(entityID) +} + // ByID returns the given entity from the backdata. func (c *Cache) ByID(entityID flow.Identifier) (flow.Entity, bool) { defer c.logTelemetry() diff --git a/module/mempool/herocache/backdata/cache_test.go b/module/mempool/herocache/backdata/cache_test.go index bf1d7b3c60e..fe7a3975284 100644 --- a/module/mempool/herocache/backdata/cache_test.go +++ b/module/mempool/herocache/backdata/cache_test.go @@ -126,6 +126,160 @@ func TestArrayBackData_Adjust(t *testing.T) { require.Equal(t, bd.Size(), uint(limit)) } +// TestArrayBackData_AdjustWitInit evaluates that AdjustWithInit method. It should initialize and then adjust the value of +// non-existing entity while preserving the integrity of BackData on just adjusting the value of existing entity. +func TestArrayBackData_AdjustWitInit(t *testing.T) { + limit := 100_000 + + bd := NewCache(uint32(limit), + 8, + heropool.LRUEjection, + unittest.Logger(), + metrics.NewNoopCollector()) + + entities := unittest.EntityListFixture(uint(limit)) + for _, e := range entities { + adjustedEntity, adjusted := bd.AdjustWithInit(e.ID(), func(entity flow.Entity) flow.Entity { + // adjust logic, increments the nonce of the entity + mockEntity, ok := entity.(*unittest.MockEntity) + require.True(t, ok) + mockEntity.Nonce++ + return mockEntity + }, func() flow.Entity { + return e // initialize with the entity + }) + require.True(t, adjusted) + require.Equal(t, e.ID(), adjustedEntity.ID()) + require.Equal(t, uint64(1), adjustedEntity.(*unittest.MockEntity).Nonce) + } + + // picks a random entity from BackData and adjusts its identifier to a new one. + entityIndex := rand.Int() % limit + // checking integrity of retrieving entity + oldEntity, ok := bd.ByID(entities[entityIndex].ID()) + require.True(t, ok) + oldEntityID := oldEntity.ID() + require.Equal(t, entities[entityIndex].ID(), oldEntityID) + require.Equal(t, entities[entityIndex], oldEntity) + + // picks a new identifier for the entity and makes sure it is different than its current one. + newEntityID := unittest.IdentifierFixture() + require.NotEqual(t, oldEntityID, newEntityID) + + // adjusts old entity to a new entity with a new identifier + newEntity, ok := bd.Adjust(oldEntity.ID(), func(entity flow.Entity) flow.Entity { + mockEntity, ok := entity.(*unittest.MockEntity) + require.True(t, ok) + // oldEntity must be passed to func parameter of adjust. + require.Equal(t, oldEntityID, mockEntity.ID()) + require.Equal(t, oldEntity, mockEntity) + + // adjust logic, adjsuts the nonce of the entity + return &unittest.MockEntity{Identifier: newEntityID, Nonce: 2} + }) + + // adjustment must be successful, and identifier must be updated. + require.True(t, ok) + require.Equal(t, newEntityID, newEntity.ID()) + require.Equal(t, uint64(2), newEntity.(*unittest.MockEntity).Nonce) + newMockEntity, ok := newEntity.(*unittest.MockEntity) + require.True(t, ok) + + // replaces new entity in the original reference list and + // retrieves all. + entities[entityIndex] = newMockEntity + testRetrievableFrom(t, bd, entities, 0) + + // re-adjusting old entity must fail, since its identifier must no longer exist + entity, ok := bd.Adjust(oldEntityID, func(entity flow.Entity) flow.Entity { + require.Fail(t, "function must not be invoked on a non-existing entity") + return entity + }) + require.False(t, ok) + require.Nil(t, entity) + + // similarly, retrieving old entity must fail + entity, ok = bd.ByID(oldEntityID) + require.False(t, ok) + require.Nil(t, entity) + + ok = bd.Has(oldEntityID) + require.False(t, ok) +} + +// TestArrayBackData_GetWithInit evaluates that GetWithInit method. It should initialize and then retrieve the value of +// non-existing entity while preserving the integrity of BackData on just retrieving the value of existing entity. +func TestArrayBackData_GetWithInit(t *testing.T) { + limit := 1000 + + bd := NewCache(uint32(limit), 8, heropool.LRUEjection, unittest.Logger(), metrics.NewNoopCollector()) + + entities := unittest.EntityListFixture(uint(limit)) + + // GetWithInit + for _, e := range entities { + // all entities must be initialized retrieved successfully + actual, ok := bd.GetWithInit(e.ID(), func() flow.Entity { + return e // initialize with the entity + }) + require.True(t, ok) + require.Equal(t, e, actual) + } + + // All + all := bd.All() + require.Equal(t, len(entities), len(all)) + for _, expected := range entities { + actual, ok := bd.ByID(expected.ID()) + require.True(t, ok) + require.Equal(t, expected, actual) + } + + // Identifiers + ids := bd.Identifiers() + require.Equal(t, len(entities), len(ids)) + for _, id := range ids { + require.True(t, bd.Has(id)) + } + + // Entities + actualEntities := bd.Entities() + require.Equal(t, len(entities), len(actualEntities)) + require.ElementsMatch(t, entities, actualEntities) + + // Adjust + for _, e := range entities { + // all entities must be adjusted successfully + actual, ok := bd.Adjust(e.ID(), func(entity flow.Entity) flow.Entity { + // increment nonce of the entity + entity.(*unittest.MockEntity).Nonce++ + return entity + }) + require.True(t, ok) + require.Equal(t, e, actual) + } + + // ByID; should return the latest version of the entity + for _, e := range entities { + // all entities must be retrieved successfully + actual, ok := bd.ByID(e.ID()) + require.True(t, ok) + require.Equal(t, e.ID(), actual.ID()) + require.Equal(t, uint64(1), actual.(*unittest.MockEntity).Nonce) + } + + // GetWithInit; should return the latest version of the entity, than increment the nonce + for _, e := range entities { + // all entities must be retrieved successfully + actual, ok := bd.GetWithInit(e.ID(), func() flow.Entity { + require.Fail(t, "should not be called") // entity has already been initialized + return e + }) + require.True(t, ok) + require.Equal(t, e.ID(), actual.ID()) + } +} + // TestArrayBackData_WriteHeavy evaluates correctness of Cache under the writing and retrieving // a heavy load of entities up to its limit. All data must be written successfully and then retrievable. func TestArrayBackData_WriteHeavy(t *testing.T) { diff --git a/module/mempool/mock/back_data.go b/module/mempool/mock/back_data.go index 68661aa9c23..a72f0d6da1f 100644 --- a/module/mempool/mock/back_data.go +++ b/module/mempool/mock/back_data.go @@ -53,6 +53,32 @@ func (_m *BackData) Adjust(entityID flow.Identifier, f func(flow.Entity) flow.En return r0, r1 } +// AdjustWithInit provides a mock function with given fields: entityID, adjust, init +func (_m *BackData) AdjustWithInit(entityID flow.Identifier, adjust func(flow.Entity) flow.Entity, init func() flow.Entity) (flow.Entity, bool) { + ret := _m.Called(entityID, adjust, init) + + var r0 flow.Entity + var r1 bool + if rf, ok := ret.Get(0).(func(flow.Identifier, func(flow.Entity) flow.Entity, func() flow.Entity) (flow.Entity, bool)); ok { + return rf(entityID, adjust, init) + } + if rf, ok := ret.Get(0).(func(flow.Identifier, func(flow.Entity) flow.Entity, func() flow.Entity) flow.Entity); ok { + r0 = rf(entityID, adjust, init) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(flow.Entity) + } + } + + if rf, ok := ret.Get(1).(func(flow.Identifier, func(flow.Entity) flow.Entity, func() flow.Entity) bool); ok { + r1 = rf(entityID, adjust, init) + } else { + r1 = ret.Get(1).(bool) + } + + return r0, r1 +} + // All provides a mock function with given fields: func (_m *BackData) All() map[flow.Identifier]flow.Entity { ret := _m.Called() @@ -116,6 +142,32 @@ func (_m *BackData) Entities() []flow.Entity { return r0 } +// GetWithInit provides a mock function with given fields: entityID, init +func (_m *BackData) GetWithInit(entityID flow.Identifier, init func() flow.Entity) (flow.Entity, bool) { + ret := _m.Called(entityID, init) + + var r0 flow.Entity + var r1 bool + if rf, ok := ret.Get(0).(func(flow.Identifier, func() flow.Entity) (flow.Entity, bool)); ok { + return rf(entityID, init) + } + if rf, ok := ret.Get(0).(func(flow.Identifier, func() flow.Entity) flow.Entity); ok { + r0 = rf(entityID, init) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(flow.Entity) + } + } + + if rf, ok := ret.Get(1).(func(flow.Identifier, func() flow.Entity) bool); ok { + r1 = rf(entityID, init) + } else { + r1 = ret.Get(1).(bool) + } + + return r0, r1 +} + // Has provides a mock function with given fields: entityID func (_m *BackData) Has(entityID flow.Identifier) bool { ret := _m.Called(entityID) diff --git a/module/mempool/stdmap/backDataHeapBenchmark_test.go b/module/mempool/stdmap/backDataHeapBenchmark_test.go index 1a3fdbc7e17..367f983120a 100644 --- a/module/mempool/stdmap/backDataHeapBenchmark_test.go +++ b/module/mempool/stdmap/backDataHeapBenchmark_test.go @@ -3,6 +3,7 @@ package stdmap_test import ( "runtime" "runtime/debug" + "sync" "testing" "time" @@ -110,6 +111,11 @@ func testAddEntities(t testing.TB, limit uint, b *stdmap.Backend, entities []*un type baselineLRU struct { c *lru.Cache // used to incorporate an LRU cache limit int + + // atomicAdjustMutex is used to synchronize concurrent access to the + // underlying LRU cache. This is needed because hashicorp LRU does not + // provide thread-safety for atomic adjust-with-init or get-with-init operations. + atomicAdjustMutex sync.Mutex } func newBaselineLRU(limit int) *baselineLRU { @@ -167,6 +173,40 @@ func (b *baselineLRU) Adjust(entityID flow.Identifier, f func(flow.Entity) flow. return newEntity, true } +// AdjustWithInit will adjust the value item using the given function if the given key can be found. +// If the key is not found, the init function will be called to create a new value. +// Returns a bool which indicates whether the value was updated as well as the updated value and +// a bool indicating whether the value was initialized. +// Note: this is a benchmark helper, hence, the adjust-with-init provides serializability w.r.t other concurrent adjust-with-init or get-with-init operations, +// and does not provide serializability w.r.t concurrent add, adjust or get operations. +func (b *baselineLRU) AdjustWithInit(entityID flow.Identifier, adjust func(flow.Entity) flow.Entity, init func() flow.Entity) (flow.Entity, bool) { + b.atomicAdjustMutex.Lock() + defer b.atomicAdjustMutex.Unlock() + + if b.Has(entityID) { + return b.Adjust(entityID, adjust) + } + added := b.Add(entityID, init()) + if !added { + return nil, false + } + return b.Adjust(entityID, adjust) +} + +// GetWithInit will retrieve the value item if the given key can be found. +// If the key is not found, the init function will be called to create a new value. +// Returns a bool which indicates whether the entity was found (or created). +func (b *baselineLRU) GetWithInit(entityID flow.Identifier, init func() flow.Entity) (flow.Entity, bool) { + newE := init() + e, ok, _ := b.c.PeekOrAdd(entityID, newE) + if !ok { + // if the entity was not found, it means that the new entity was added to the cache. + return newE, true + } + // if the entity was found, it means that the new entity was not added to the cache. + return e.(flow.Entity), true +} + // ByID returns the given item from the pool. func (b *baselineLRU) ByID(entityID flow.Identifier) (flow.Entity, bool) { e, ok := b.c.Get(entityID) @@ -182,12 +222,12 @@ func (b *baselineLRU) ByID(entityID flow.Identifier) (flow.Entity, bool) { } // Size will return the total of the backend. -func (b baselineLRU) Size() uint { +func (b *baselineLRU) Size() uint { return uint(b.c.Len()) } // All returns all entities from the pool. -func (b baselineLRU) All() map[flow.Identifier]flow.Entity { +func (b *baselineLRU) All() map[flow.Identifier]flow.Entity { all := make(map[flow.Identifier]flow.Entity) for _, entityID := range b.c.Keys() { id, ok := entityID.(flow.Identifier) @@ -205,7 +245,7 @@ func (b baselineLRU) All() map[flow.Identifier]flow.Entity { return all } -func (b baselineLRU) Identifiers() flow.IdentifierList { +func (b *baselineLRU) Identifiers() flow.IdentifierList { ids := make(flow.IdentifierList, b.c.Len()) entityIds := b.c.Keys() total := len(entityIds) @@ -219,7 +259,7 @@ func (b baselineLRU) Identifiers() flow.IdentifierList { return ids } -func (b baselineLRU) Entities() []flow.Entity { +func (b *baselineLRU) Entities() []flow.Entity { entities := make([]flow.Entity, b.c.Len()) entityIds := b.c.Keys() total := len(entityIds) diff --git a/module/mempool/stdmap/backdata/mapBackData.go b/module/mempool/stdmap/backdata/mapBackData.go index 887b6fca335..24c34d79eb0 100644 --- a/module/mempool/stdmap/backdata/mapBackData.go +++ b/module/mempool/stdmap/backdata/mapBackData.go @@ -5,6 +5,7 @@ import ( ) // MapBackData implements a map-based generic memory BackData backed by a Go map. +// Note that this implementation is NOT thread-safe, and the higher-level Backend is responsible for concurrency management. type MapBackData struct { // NOTE: as a BackData implementation, MapBackData must be non-blocking. // Concurrency management is done by overlay Backend. @@ -19,7 +20,7 @@ func NewMapBackData() *MapBackData { } // Has checks if backdata already contains the entity with the given identifier. -func (b MapBackData) Has(entityID flow.Identifier) bool { +func (b *MapBackData) Has(entityID flow.Identifier) bool { _, exists := b.entities[entityID] return exists } @@ -59,8 +60,42 @@ func (b *MapBackData) Adjust(entityID flow.Identifier, f func(flow.Entity) flow. return newentity, true } +// AdjustWithInit adjusts the entity using the given function if the given identifier can be found. When the +// entity is not found, it initializes the entity using the given init function and then applies the adjust function. +// Args: +// - entityID: the identifier of the entity to adjust. +// - adjust: the function that adjusts the entity. +// - init: the function that initializes the entity when it is not found. +// Returns: +// - the adjusted entity. +// +// - a bool which indicates whether the entity was adjusted. +func (b *MapBackData) AdjustWithInit(entityID flow.Identifier, adjust func(flow.Entity) flow.Entity, init func() flow.Entity) (flow.Entity, bool) { + if b.Has(entityID) { + return b.Adjust(entityID, adjust) + } + b.Add(entityID, init()) + return b.Adjust(entityID, adjust) +} + +// GetWithInit returns the given entity from the backdata. If the entity does not exist, it creates a new entity +// using the factory function and stores it in the backdata. +// Args: +// - entityID: the identifier of the entity to get. +// - init: the function that initializes the entity when it is not found. +// Returns: +// - the entity. +// - a bool which indicates whether the entity was found (or created). +func (b *MapBackData) GetWithInit(entityID flow.Identifier, init func() flow.Entity) (flow.Entity, bool) { + if b.Has(entityID) { + return b.ByID(entityID) + } + b.Add(entityID, init()) + return b.ByID(entityID) +} + // ByID returns the given entity from the backdata. -func (b MapBackData) ByID(entityID flow.Identifier) (flow.Entity, bool) { +func (b *MapBackData) ByID(entityID flow.Identifier) (flow.Entity, bool) { entity, exists := b.entities[entityID] if !exists { return nil, false @@ -69,12 +104,12 @@ func (b MapBackData) ByID(entityID flow.Identifier) (flow.Entity, bool) { } // Size returns the size of the backdata, i.e., total number of stored (entityId, entity) -func (b MapBackData) Size() uint { +func (b *MapBackData) Size() uint { return uint(len(b.entities)) } // All returns all entities stored in the backdata. -func (b MapBackData) All() map[flow.Identifier]flow.Entity { +func (b *MapBackData) All() map[flow.Identifier]flow.Entity { entities := make(map[flow.Identifier]flow.Entity) for entityID, entity := range b.entities { entities[entityID] = entity @@ -83,7 +118,7 @@ func (b MapBackData) All() map[flow.Identifier]flow.Entity { } // Identifiers returns the list of identifiers of entities stored in the backdata. -func (b MapBackData) Identifiers() flow.IdentifierList { +func (b *MapBackData) Identifiers() flow.IdentifierList { ids := make(flow.IdentifierList, len(b.entities)) i := 0 for entityID := range b.entities { @@ -94,7 +129,7 @@ func (b MapBackData) Identifiers() flow.IdentifierList { } // Entities returns the list of entities stored in the backdata. -func (b MapBackData) Entities() []flow.Entity { +func (b *MapBackData) Entities() []flow.Entity { entities := make([]flow.Entity, len(b.entities)) i := 0 for _, entity := range b.entities { diff --git a/module/mempool/stdmap/backdata/mapBackData_test.go b/module/mempool/stdmap/backdata/mapBackData_test.go index 7e5858d65ac..a5a13b70bb8 100644 --- a/module/mempool/stdmap/backdata/mapBackData_test.go +++ b/module/mempool/stdmap/backdata/mapBackData_test.go @@ -5,6 +5,7 @@ import ( "github.com/stretchr/testify/require" + "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/utils/unittest" ) @@ -47,3 +48,140 @@ func TestMapBackData_StoreAnd(t *testing.T) { require.Equal(t, len(entities), len(actualEntities)) require.ElementsMatch(t, entities, actualEntities) } + +// TestMapBackData_AdjustWithInit tests the AdjustWithInit method of the MapBackData. +// Note that as the backdata is not inherently thread-safe, this test is not concurrent. +func TestMapBackData_AdjustWithInit(t *testing.T) { + backData := NewMapBackData() + entities := unittest.EntityListFixture(100) + ids := flow.GetIDs(entities) + + // AdjustWithInit + for _, e := range entities { + // all entities must be adjusted successfully + actual, ok := backData.AdjustWithInit(e.ID(), func(entity flow.Entity) flow.Entity { + // increment nonce of the entity + entity.(*unittest.MockEntity).Nonce++ + return entity + }, func() flow.Entity { + return e + }) + require.True(t, ok) + require.Equal(t, e, actual) + } + + // All + all := backData.All() + require.Equal(t, len(entities), len(all)) + for _, expected := range entities { + actual, ok := backData.ByID(expected.ID()) + require.True(t, ok) + require.Equal(t, expected.ID(), actual.ID()) + require.Equal(t, uint64(1), actual.(*unittest.MockEntity).Nonce) + } + + // Identifiers + retriedIds := backData.Identifiers() + require.Equal(t, len(entities), len(retriedIds)) + require.ElementsMatch(t, ids, retriedIds) + for _, id := range retriedIds { + require.True(t, backData.Has(id)) + } + + // Entities + actualEntities := backData.Entities() + require.Equal(t, len(entities), len(actualEntities)) + require.ElementsMatch(t, entities, actualEntities) + + // ByID + for _, e := range entities { + // all entities must be retrieved successfully + actual, ok := backData.ByID(e.ID()) + require.True(t, ok) + require.Equal(t, e.ID(), actual.ID()) + require.Equal(t, uint64(1), actual.(*unittest.MockEntity).Nonce) + } + + // GetWithInit + for _, e := range entities { + // all entities must be retrieved successfully + actual, ok := backData.GetWithInit(e.ID(), func() flow.Entity { + require.Fail(t, "should not be called") // entity has already been initialized + return e + }) + require.True(t, ok) + require.Equal(t, e.ID(), actual.ID()) + require.Equal(t, uint64(1), actual.(*unittest.MockEntity).Nonce) + } +} + +// TestMapBackData_GetWithInit tests the GetWithInit method of the MapBackData. +// Note that as the backdata is not inherently thread-safe, this test is not concurrent. +func TestMapBackData_GetWithInit(t *testing.T) { + backData := NewMapBackData() + entities := unittest.EntityListFixture(100) + + // GetWithInit + for _, e := range entities { + // all entities must be initialized retrieved successfully + actual, ok := backData.GetWithInit(e.ID(), func() flow.Entity { + return e // initialize with the entity + }) + require.True(t, ok) + require.Equal(t, e, actual) + } + + // All + all := backData.All() + require.Equal(t, len(entities), len(all)) + for _, expected := range entities { + actual, ok := backData.ByID(expected.ID()) + require.True(t, ok) + require.Equal(t, expected, actual) + } + + // Identifiers + ids := backData.Identifiers() + require.Equal(t, len(entities), len(ids)) + for _, id := range ids { + require.True(t, backData.Has(id)) + } + + // Entities + actualEntities := backData.Entities() + require.Equal(t, len(entities), len(actualEntities)) + require.ElementsMatch(t, entities, actualEntities) + + // Adjust + for _, e := range entities { + // all entities must be adjusted successfully + actual, ok := backData.Adjust(e.ID(), func(entity flow.Entity) flow.Entity { + // increment nonce of the entity + entity.(*unittest.MockEntity).Nonce++ + return entity + }) + require.True(t, ok) + require.Equal(t, e, actual) + } + + // ByID; should return the latest version of the entity + for _, e := range entities { + // all entities must be retrieved successfully + actual, ok := backData.ByID(e.ID()) + require.True(t, ok) + require.Equal(t, e.ID(), actual.ID()) + require.Equal(t, uint64(1), actual.(*unittest.MockEntity).Nonce) + } + + // GetWithInit; should return the latest version of the entity, than increment the nonce + for _, e := range entities { + // all entities must be retrieved successfully + actual, ok := backData.GetWithInit(e.ID(), func() flow.Entity { + require.Fail(t, "should not be called") // entity has already been initialized + return e + }) + require.True(t, ok) + require.Equal(t, e.ID(), actual.ID()) + require.Equal(t, uint64(1), actual.(*unittest.MockEntity).Nonce) + } +} diff --git a/module/mempool/stdmap/backend.go b/module/mempool/stdmap/backend.go index fb42e5297d5..f7dfc7de323 100644 --- a/module/mempool/stdmap/backend.go +++ b/module/mempool/stdmap/backend.go @@ -12,7 +12,7 @@ import ( _ "github.com/onflow/flow-go/utils/binstat" ) -// Backend provides synchronized access to a backdata +// Backend is a wrapper around the backdata that provides concurrency-safe operations. type Backend struct { sync.RWMutex backData mempool.BackData @@ -40,12 +40,12 @@ func NewBackend(options ...OptionFunc) *Backend { // Has checks if we already contain the item with the given hash. func (b *Backend) Has(entityID flow.Identifier) bool { - //bs1 := binstat.EnterTime(binstat.BinStdmap + ".r_lock.(Backend)Has") + // bs1 := binstat.EnterTime(binstat.BinStdmap + ".r_lock.(Backend)Has") b.RLock() - //binstat.Leave(bs1) + // binstat.Leave(bs1) - //bs2 := binstat.EnterTime(binstat.BinStdmap + ".inlock.(Backend)Has") - //defer binstat.Leave(bs2) + // bs2 := binstat.EnterTime(binstat.BinStdmap + ".inlock.(Backend)Has") + // defer binstat.Leave(bs2) defer b.RUnlock() has := b.backData.Has(entityID) return has @@ -53,16 +53,16 @@ func (b *Backend) Has(entityID flow.Identifier) bool { // Add adds the given item to the pool. func (b *Backend) Add(entity flow.Entity) bool { - //bs0 := binstat.EnterTime(binstat.BinStdmap + ".< 0; num-- { go func() { + defer wg.Done() backend.Add(unittest.MockEntityFixture()) // creates and adds a fake item to the mempool - wg.Done() }() } unittest.RequireReturnsBefore(t, wg.Wait, 1*time.Second, "failed to add elements in time") diff --git a/module/metrics.go b/module/metrics.go index a33dfc0a094..4315d6c017b 100644 --- a/module/metrics.go +++ b/module/metrics.go @@ -12,6 +12,7 @@ import ( "github.com/onflow/flow-go/model/cluster" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/network/channels" + p2pmsg "github.com/onflow/flow-go/network/p2p/message" ) type EntriesFunc func() uint @@ -48,50 +49,127 @@ type NetworkSecurityMetrics interface { OnViolationReportSkipped() } -// GossipSubRouterMetrics encapsulates the metrics collectors for GossipSubRouter module of the networking layer. -// It mostly collects the metrics related to the control message exchange between nodes over the GossipSub protocol. -type GossipSubRouterMetrics interface { - // OnIncomingRpcAcceptedFully tracks the number of RPC messages received by the node that are fully accepted. - // An RPC may contain any number of control messages, i.e., IHAVE, IWANT, GRAFT, PRUNE, as well as the actual messages. - // A fully accepted RPC means that all the control messages are accepted and all the messages are accepted. - OnIncomingRpcAcceptedFully() +// GossipSubRpcInspectorMetrics encapsulates the metrics collectors for GossipSub RPC Inspector module of the networking layer. +// The RPC inspector is the entry point of the GossipSub protocol. It inspects the incoming RPC messages and decides +// whether to accept, prune, or reject the RPC message. +// The GossipSubRpcInspectorMetrics tracks the number of RPC messages received by the local node from other nodes over +// the GossipSub protocol. It also tracks the number of control messages included in the RPC messages, i.e., IHAVE, IWANT, +// GRAFT, PRUNE. It also tracks the number of actual messages included in the RPC messages. +// The GossipSubRpcInspectorMetrics differs from LocalGossipSubRouterMetrics in that the former tracks the messages +// received by the local node from other nodes over the GossipSub protocol but may not all be accepted by the local node, +// e.g., due to RPC pruning or throttling; while the latter tracks the local node's view of the GossipSub protocol, i.e., entirely +// containing the messages that are accepted by the local node (either as whole RPC or only for the control messages). +// Having this distinction is useful for debugging and troubleshooting the GossipSub protocol, for example, the number of +// messages received by the local node from other nodes over the GossipSub protocol may be much higher than the number +// of messages accepted by the local node, which may indicate that the local node is throttling the incoming messages. +type GossipSubRpcInspectorMetrics interface { + // OnIWantMessageIDsReceived tracks the number of message ids received by the node from other nodes on an RPC. + // Note: this function is called on each IWANT message received by the node, not on each message id received. + OnIWantMessageIDsReceived(msgIdCount int) + + // OnIHaveMessageIDsReceived tracks the number of message ids received by the node from other nodes on an iHave message. + // This function is called on each iHave message received by the node. + // Args: + // - channel: the channel on which the iHave message was received. + // - msgIdCount: the number of message ids received on the iHave message. + OnIHaveMessageIDsReceived(channel string, msgIdCount int) - // OnIncomingRpcAcceptedOnlyForControlMessages tracks the number of RPC messages received by the node that are accepted - // only for the control messages, i.e., only for the included IHAVE, IWANT, GRAFT, PRUNE. However, the actual messages - // included in the RPC are not accepted. - // This happens mostly when the validation pipeline of GossipSub is throttled, and cannot accept more actual messages for - // validation. - OnIncomingRpcAcceptedOnlyForControlMessages() + // OnIncomingRpcReceived tracks the number of RPC messages received by the node. + // Args: + // iHaveCount: the number of iHAVE messages included in the RPC. + // iWantCount: the number of iWANT messages included in the RPC. + // graftCount: the number of GRAFT messages included in the RPC. + // pruneCount: the number of PRUNE messages included in the RPC. + // msgCount: the number of publish messages included in the RPC. + OnIncomingRpcReceived(iHaveCount, iWantCount, graftCount, pruneCount, msgCount int) +} - // OnIncomingRpcRejected tracks the number of RPC messages received by the node that are rejected. - // This happens mostly when the RPC is coming from a low-scored peer based on the peer scoring module of GossipSub. - OnIncomingRpcRejected() +// LocalGossipSubRouterMetrics encapsulates the metrics collectors for GossipSub router of the local node. +// It gives a lens into the local GossipSub node's view of the GossipSub protocol. +// LocalGossipSubRouterMetrics differs from GossipSubRpcInspectorMetrics in that the former tracks the local node's view +// of the GossipSub protocol, while the latter tracks the messages received by the local node from other nodes over the +// GossipSub protocol but may not all be accepted by the local node, e.g., due to RPC pruning or throttling. +// Having this distinction is useful for debugging and troubleshooting the GossipSub protocol, for example, the number of +// messages received by the local node from other nodes over the GossipSub protocol may be much higher than the number +// of messages accepted by the local node, which may indicate that the local node is throttling the incoming messages. +type LocalGossipSubRouterMetrics interface { + // OnLocalMeshSizeUpdated tracks the size of the local mesh for a topic. + OnLocalMeshSizeUpdated(topic string, size int) - // OnIWantReceived tracks the number of IWANT messages received by the node from other nodes over an RPC message. - // iWant is a control message that is sent by a node to request a message that it has seen advertised in an iHAVE message. - OnIWantReceived(count int) + // OnPeerAddedToProtocol is called when the local node receives a stream from a peer on a gossipsub-related protocol. + // Args: + // protocol: the protocol name that the peer is connected to. + OnPeerAddedToProtocol(protocol string) - // OnIHaveReceived tracks the number of IHAVE messages received by the node from other nodes over an RPC message. - // iHave is a control message that is sent by a node to another node to indicate that it has a new gossiped message. - OnIHaveReceived(count int) + // OnPeerRemovedFromProtocol is called when the local considers a remote peer blacklisted or unavailable. + OnPeerRemovedFromProtocol() - // OnGraftReceived tracks the number of GRAFT messages received by the node from other nodes over an RPC message. - // GRAFT is a control message of GossipSub protocol that connects two nodes over a topic directly as gossip partners. - OnGraftReceived(count int) + // OnLocalPeerJoinedTopic is called when the local node subscribes to a gossipsub topic. + OnLocalPeerJoinedTopic() - // OnPruneReceived tracks the number of PRUNE messages received by the node from other nodes over an RPC message. - // PRUNE is a control message of GossipSub protocol that disconnects two nodes over a topic. - OnPruneReceived(count int) + // OnLocalPeerLeftTopic is called when the local node unsubscribes from a gossipsub topic. + OnLocalPeerLeftTopic() - // OnPublishedGossipMessagesReceived tracks the number of gossip messages received by the node from other nodes over an - // RPC message. - OnPublishedGossipMessagesReceived(count int) -} + // OnPeerGraftTopic is called when the local node receives a GRAFT message from a remote peer on a topic. + // Note: the received GRAFT at this point is considered passed the RPC inspection, and is accepted by the local node. + OnPeerGraftTopic(topic string) -// GossipSubLocalMeshMetrics encapsulates the metrics collectors for GossipSub mesh of the networking layer. -type GossipSubLocalMeshMetrics interface { - // OnLocalMeshSizeUpdated tracks the size of the local mesh for a topic. - OnLocalMeshSizeUpdated(topic string, size int) + // OnPeerPruneTopic is called when the local node receives a PRUNE message from a remote peer on a topic. + // Note: the received PRUNE at this point is considered passed the RPC inspection, and is accepted by the local node. + OnPeerPruneTopic(topic string) + + // OnMessageEnteredValidation is called when a received pubsub message enters the validation pipeline. It is the + // internal validation pipeline of GossipSub protocol. The message may be rejected or accepted by the validation + // pipeline. + OnMessageEnteredValidation(size int) + + // OnMessageRejected is called when a received pubsub message is rejected by the validation pipeline. + // Args: + // + // reason: the reason for rejection. + // size: the size of the message in bytes. + OnMessageRejected(size int, reason string) + + // OnMessageDuplicate is called when a received pubsub message is a duplicate of a previously received message, and + // is dropped. + // Args: + // size: the size of the message in bytes. + OnMessageDuplicate(size int) + + // OnPeerThrottled is called when a peer is throttled by the local node, i.e., the local node is not accepting any + // pubsub message from the peer but may still accept control messages. + OnPeerThrottled() + + // OnRpcReceived is called when an RPC message is received by the local node. The received RPC is considered + // passed the RPC inspection, and is accepted by the local node. + // Args: + // msgCount: the number of messages included in the RPC. + // iHaveCount: the number of iHAVE messages included in the RPC. + // iWantCount: the number of iWANT messages included in the RPC. + // graftCount: the number of GRAFT messages included in the RPC. + // pruneCount: the number of PRUNE messages included in the RPC. + OnRpcReceived(msgCount int, iHaveCount int, iWantCount int, graftCount int, pruneCount int) + + // OnRpcSent is called when an RPC message is sent by the local node. + // Note: the sent RPC is considered passed the RPC inspection, and is accepted by the local node. + // Args: + // msgCount: the number of messages included in the RPC. + // iHaveCount: the number of iHAVE messages included in the RPC. + // iWantCount: the number of iWANT messages included in the RPC. + // graftCount: the number of GRAFT messages included in the RPC. + // pruneCount: the number of PRUNE messages included in the RPC. + OnRpcSent(msgCount int, iHaveCount int, iWantCount int, graftCount int, pruneCount int) + + // OnOutboundRpcDropped is called when an outbound RPC message is dropped by the local node, typically because the local node + // outbound message queue is full; or the RPC is big and the local node cannot fragment it. + OnOutboundRpcDropped() + + // OnUndeliveredMessage is called when a message is not delivered at least one subscriber of the topic, for example when + // the subscriber is too slow to process the message. + OnUndeliveredMessage() + + // OnMessageDeliveredToAllSubscribers is called when a message is delivered to all subscribers of the topic. + OnMessageDeliveredToAllSubscribers(size int) } // UnicastManagerMetrics unicast manager metrics. @@ -128,8 +206,8 @@ type UnicastManagerMetrics interface { type GossipSubMetrics interface { GossipSubScoringMetrics - GossipSubRouterMetrics - GossipSubLocalMeshMetrics + GossipSubRpcInspectorMetrics + LocalGossipSubRouterMetrics GossipSubRpcValidationInspectorMetrics } @@ -169,11 +247,112 @@ type GossipSubScoringMetrics interface { // GossipSubRpcValidationInspectorMetrics encapsulates the metrics collectors for the gossipsub rpc validation control message inspectors. type GossipSubRpcValidationInspectorMetrics interface { + GossipSubRpcInspectorMetrics + // AsyncProcessingStarted increments the metric tracking the number of inspect message request being processed by workers in the rpc validator worker pool. AsyncProcessingStarted() // AsyncProcessingFinished tracks the time spent by a rpc validation inspector worker to process an inspect message request asynchronously and decrements the metric tracking // the number of inspect message requests being processed asynchronously by the rpc validation inspector workers. AsyncProcessingFinished(duration time.Duration) + + // OnIHaveControlMessageIdsTruncated tracks the number of times message ids on an iHave message were truncated. + // Note that this function is called only when the message ids are truncated from an iHave message, not when the iHave message itself is truncated. + // This is different from the OnControlMessagesTruncated function which is called when a slice of control messages truncated from an RPC with all their message ids. + // Args: + // + // diff: the number of actual messages truncated. + OnIHaveControlMessageIdsTruncated(diff int) + + // OnIWantControlMessageIdsTruncated tracks the number of times message ids on an iWant message were truncated. + // Note that this function is called only when the message ids are truncated from an iWant message, not when the iWant message itself is truncated. + // This is different from the OnControlMessagesTruncated function which is called when a slice of control messages truncated from an RPC with all their message ids. + // Args: + // diff: the number of actual messages truncated. + OnIWantControlMessageIdsTruncated(diff int) + + // OnControlMessagesTruncated tracks the number of times a slice of control messages is truncated from an RPC with all their included message ids. + // Args: + // + // messageType: the type of the control message that was truncated + // diff: the number of control messages truncated. + OnControlMessagesTruncated(messageType p2pmsg.ControlMessageType, diff int) + + // OnIWantMessagesInspected tracks the number of duplicate and cache miss message ids received by the node on iWant messages at the end of the async inspection iWants + // across one RPC, regardless of the result of the inspection. + // + // duplicateCount: the total number of duplicate message ids received by the node on the iWant messages at the end of the async inspection of the RPC. + // cacheMissCount: the total number of cache miss message ids received by the node on the iWant message at the end of the async inspection of the RPC. + OnIWantMessagesInspected(duplicateCount int, cacheMissCount int) + + // OnIWantDuplicateMessageIdsExceedThreshold tracks the number of times that async inspection of iWant messages failed due to the total number of duplicate message ids + // received by the node on the iWant messages of a single RPC exceeding the threshold, which results in a misbehaviour report. + OnIWantDuplicateMessageIdsExceedThreshold() + + // OnIWantCacheMissMessageIdsExceedThreshold tracks the number of times that async inspection of iWant messages failed due to the total + // number of cache miss message ids received by the node on the iWant messages of a single RPC exceeding the threshold, which results in a misbehaviour report. + OnIWantCacheMissMessageIdsExceedThreshold() + + // OnIHaveMessagesInspected is called at the end of the async inspection of iHave messages of a single RPC, regardless of the result of the inspection. + // It tracks the number of duplicate topic ids and duplicate message ids received by the node on the iHave messages of that single RPC at the end of the async inspection iHaves. + // Args: + // + // duplicateTopicIds: the total number of duplicate topic ids received by the node on the iHave messages at the end of the async inspection of the RPC. + // duplicateMessageIds: the number of duplicate message ids received by the node on the iHave messages at the end of the async inspection of the RPC. + OnIHaveMessagesInspected(duplicateTopicIds int, duplicateMessageIds int) + + // OnIHaveDuplicateTopicIdsExceedThreshold tracks the number of times that the async inspection of iHave messages of a single RPC failed due to the total number of duplicate topic ids + // received by the node on the iHave messages of that RPC exceeding the threshold, which results in a misbehaviour report. + OnIHaveDuplicateTopicIdsExceedThreshold() + + // OnIHaveDuplicateMessageIdsExceedThreshold tracks the number of times that the async inspection of iHave messages of a single RPC failed due to the total number of duplicate message ids + // received by the node on an iHave message exceeding the threshold, which results in a misbehaviour report. + OnIHaveDuplicateMessageIdsExceedThreshold() + + // OnInvalidTopicIdDetectedForControlMessage tracks the number of times that the async inspection of a control message type on a single RPC failed due to an invalid topic id. + // Args: + // - messageType: the type of the control message that was truncated. + OnInvalidTopicIdDetectedForControlMessage(messageType p2pmsg.ControlMessageType) + + // OnActiveClusterIDsNotSetErr tracks the number of times that the async inspection of a control message type on a single RPC failed due to active cluster ids not set inspection failure. + // This is not causing a misbehaviour report. + OnActiveClusterIDsNotSetErr() + + // OnUnstakedPeerInspectionFailed tracks the number of times that the async inspection of a control message type on a single RPC failed due to unstaked peer inspection failure. + // This is not causing a misbehaviour report. + OnUnstakedPeerInspectionFailed() + + // OnInvalidControlMessageNotificationSent tracks the number of times that the async inspection of a control message failed and resulted in dissemination of an invalid control message was sent. + OnInvalidControlMessageNotificationSent() + + // OnPublishMessagesInspectionErrorExceedsThreshold tracks the number of times that async inspection of publish messages failed due to the number of errors. + OnPublishMessagesInspectionErrorExceedsThreshold() + + // OnPruneDuplicateTopicIdsExceedThreshold tracks the number of times that the async inspection of prune messages for an RPC failed due to the number of duplicate topic ids + // received by the node on prune messages of the same RPC excesses threshold, which results in a misbehaviour report. + OnPruneDuplicateTopicIdsExceedThreshold() + + // OnPruneMessageInspected is called at the end of the async inspection of prune messages of the RPC, regardless of the result of the inspection. + // Args: + // duplicateTopicIds: the number of duplicate topic ids received by the node on the prune messages of the RPC at the end of the async inspection prunes. + OnPruneMessageInspected(duplicateTopicIds int) + + // OnGraftDuplicateTopicIdsExceedThreshold tracks the number of times that the async inspection of the graft messages of a single RPC failed due to the number of duplicate topic ids + // received by the node on graft messages of the same RPC excesses threshold, which results in a misbehaviour report. + OnGraftDuplicateTopicIdsExceedThreshold() + + // OnGraftMessageInspected is called at the end of the async inspection of graft messages of a single RPC, regardless of the result of the inspection. + // Args: + // duplicateTopicIds: the number of duplicate topic ids received by the node on the graft messages at the end of the async inspection of a single RPC. + OnGraftMessageInspected(duplicateTopicIds int) + + // OnPublishMessageInspected is called at the end of the async inspection of publish messages of a single RPC, regardless of the result of the inspection. + // It tracks the total number of errors detected during the async inspection of the rpc together with their individual breakdown. + // Args: + // - errCount: the number of errors that occurred during the async inspection of publish messages. + // - invalidTopicIdsCount: the number of times that an invalid topic id was detected during the async inspection of publish messages. + // - invalidSubscriptionsCount: the number of times that an invalid subscription was detected during the async inspection of publish messages. + // - invalidSendersCount: the number of times that an invalid sender was detected during the async inspection of publish messages. + OnPublishMessageInspected(totalErrCount int, invalidTopicIdsCount int, invalidSubscriptionsCount int, invalidSendersCount int) } // NetworkInboundQueueMetrics encapsulates the metrics collectors for the inbound queue of the networking layer. @@ -716,6 +895,9 @@ type ExecutionMetrics interface { // ExecutionLastExecutedBlockHeight reports last executed block height ExecutionLastExecutedBlockHeight(height uint64) + // ExecutionLastFinalizedExecutedBlockHeight reports last finalized and executed block height + ExecutionLastFinalizedExecutedBlockHeight(height uint64) + // ExecutionBlockExecuted reports the total time and computation spent on executing a block ExecutionBlockExecuted(dur time.Duration, stats ExecutionResultStats) diff --git a/module/metrics/execution.go b/module/metrics/execution.go index 94c3e70e107..90fc9ea27f4 100644 --- a/module/metrics/execution.go +++ b/module/metrics/execution.go @@ -8,6 +8,7 @@ import ( "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/module" + "github.com/onflow/flow-go/module/counters" ) type ExecutionCollector struct { @@ -18,6 +19,7 @@ type ExecutionCollector struct { totalExecutedScriptsCounter prometheus.Counter totalFailedTransactionsCounter prometheus.Counter lastExecutedBlockHeightGauge prometheus.Gauge + lastFinalizedExecutedBlockHeightGauge prometheus.Gauge stateStorageDiskTotal prometheus.Gauge storageStateCommitment prometheus.Gauge forestApproxMemorySize prometheus.Gauge @@ -80,6 +82,7 @@ type ExecutionCollector struct { stateSyncActive prometheus.Gauge blockDataUploadsInProgress prometheus.Gauge blockDataUploadsDuration prometheus.Histogram + maxCollectionHeightData counters.StrictMonotonousCounter maxCollectionHeight prometheus.Gauge computationResultUploadedCount prometheus.Counter computationResultUploadRetriedCount prometheus.Counter @@ -633,6 +636,13 @@ func NewExecutionCollector(tracer module.Tracer) *ExecutionCollector { Help: "the last height that was executed", }), + lastFinalizedExecutedBlockHeightGauge: promauto.NewGauge(prometheus.GaugeOpts{ + Namespace: namespaceExecution, + Subsystem: subsystemRuntime, + Name: "last_finalized_executed_block_height", + Help: "the last height that was finalized and executed", + }), + stateStorageDiskTotal: promauto.NewGauge(prometheus.GaugeOpts{ Namespace: namespaceExecution, Subsystem: subsystemStateStorage, @@ -675,6 +685,7 @@ func NewExecutionCollector(tracer module.Tracer) *ExecutionCollector { Help: "the number of times a program was found in the cache", }), + maxCollectionHeightData: counters.NewMonotonousCounter(0), maxCollectionHeight: prometheus.NewGauge(prometheus.GaugeOpts{ Name: "max_collection_height", Namespace: namespaceExecution, @@ -793,6 +804,11 @@ func (ec *ExecutionCollector) ExecutionLastExecutedBlockHeight(height uint64) { ec.lastExecutedBlockHeightGauge.Set(float64(height)) } +// ExecutionLastFinalizedExecutedBlockHeight reports last finalized executed block height +func (ec *ExecutionCollector) ExecutionLastFinalizedExecutedBlockHeight(height uint64) { + ec.lastFinalizedExecutedBlockHeightGauge.Set(float64(height)) +} + // ForestApproxMemorySize records approximate memory usage of forest (all in-memory trees) func (ec *ExecutionCollector) ForestApproxMemorySize(bytes uint64) { ec.forestApproxMemorySize.Set(float64(bytes)) @@ -937,7 +953,10 @@ func (ec *ExecutionCollector) RuntimeTransactionProgramsCacheHit() { } func (ec *ExecutionCollector) UpdateCollectionMaxHeight(height uint64) { - ec.maxCollectionHeight.Set(float64(height)) + updated := ec.maxCollectionHeightData.Set(height) + if updated { + ec.maxCollectionHeight.Set(float64(height)) + } } func (ec *ExecutionCollector) ExecutionComputationResultUploaded() { diff --git a/module/metrics/execution_state_indexer.go b/module/metrics/execution_state_indexer.go index 6c3f68b4d69..ffe7c15d215 100644 --- a/module/metrics/execution_state_indexer.go +++ b/module/metrics/execution_state_indexer.go @@ -83,7 +83,7 @@ func (c *ExecutionStateIndexerCollector) InitializeLatestHeight(height uint64) { } // BlockIndexed records metrics from indexing execution data from a single block. -func (c *ExecutionStateIndexerCollector) BlockIndexed(height uint64, duration time.Duration, registers, events, transactionResults int) { +func (c *ExecutionStateIndexerCollector) BlockIndexed(height uint64, duration time.Duration, events, registers, transactionResults int) { c.indexDuration.Observe(float64(duration.Milliseconds())) c.highestIndexedHeight.Set(float64(height)) c.indexedEvents.Add(float64(events)) diff --git a/module/metrics/gossipsub.go b/module/metrics/gossipsub.go index 5ba5369fa0d..39dfd1f1e36 100644 --- a/module/metrics/gossipsub.go +++ b/module/metrics/gossipsub.go @@ -7,173 +7,374 @@ import ( "github.com/onflow/flow-go/module" ) -type GossipSubMetrics struct { - receivedIHaveCount prometheus.Counter - receivedIWantCount prometheus.Counter - receivedGraftCount prometheus.Counter - receivedPruneCount prometheus.Counter - incomingRpcAcceptedFullyCount prometheus.Counter - incomingRpcAcceptedOnlyControlCount prometheus.Counter - incomingRpcRejectedCount prometheus.Counter - receivedPublishMessageCount prometheus.Counter +// LocalGossipSubRouterMetrics encapsulates the metrics collectors for GossipSub router of the local node. +// It gives a lens into the local node's view of the GossipSub protocol. +type LocalGossipSubRouterMetrics struct { + // localMeshSize is the number of peers in the local mesh of the node on each topic. + localMeshSize prometheus.GaugeVec - prefix string -} + // peerAddedOnProtocolCount is the number of peers added to the local gossipsub router on a gossipsub protocol. + peerAddedOnProtocolCount prometheus.CounterVec + + // peerRemovedFromProtocolCount is the number of peers removed from the local gossipsub router (i.e., blacklisted or unavailable). + peerRemovedFromProtocolCount prometheus.Counter + + // localPeerJoinedTopicCount is the number of times the local node joined (i.e., subscribed) to a topic. + localPeerJoinedTopicCount prometheus.Counter + + // localPeerLeftTopicCount is the number of times the local node left (i.e., unsubscribed) from a topic. + localPeerLeftTopicCount prometheus.Counter + + // peerGraftTopicCount is the number of peers grafted to a topic on the local mesh of the node, i.e., the local node + // is directly connected to the peer on the topic, and exchange messages directly. + peerGraftTopicCount prometheus.CounterVec + + // peerPruneTopicCount is the number of peers pruned from a topic on the local mesh of the node, i.e., the local node + // is no longer directly connected to the peer on the topic, and exchange messages indirectly. + peerPruneTopicCount prometheus.CounterVec + + // messageEnteredValidationCount is the number of incoming pubsub messages entered internal validation pipeline of gossipsub. + messageEnteredValidationCount prometheus.Counter + + // messageDeliveredSize is the size of messages delivered to all subscribers of the topic. + messageDeliveredSize prometheus.Histogram + + // messageRejectedSize is the size of inbound messages rejected by the validation pipeline; the rejection reason is also included. + messageRejectedSize prometheus.HistogramVec + + // messageDuplicateSize is the size of messages that are duplicates of already received messages. + messageDuplicateSize prometheus.Histogram + + // peerThrottledCount is the number of peers that are throttled by the local node, i.e., the local node is not accepting + // any pubsub message from the peer but may still accept control messages. + peerThrottledCount prometheus.Counter + + // rpcRcvCount is the number of rpc messages received and processed by the router (i.e., passed rpc inspection). + rpcRcvCount prometheus.Counter -var _ module.GossipSubRouterMetrics = (*GossipSubMetrics)(nil) + // iWantRcvCount is the number of iwant messages received by the router on rpcs. + iWantRcvCount prometheus.Counter -func NewGossipSubMetrics(prefix string) *GossipSubMetrics { - gs := &GossipSubMetrics{prefix: prefix} + // iHaveRcvCount is the number of ihave messages received by the router on rpcs. + iHaveRcvCount prometheus.Counter + + // graftRcvCount is the number of graft messages received by the router on rpcs. + graftRcvCount prometheus.Counter + + // pruneRcvCount is the number of prune messages received by the router on rpcs. + pruneRcvCount prometheus.Counter + + // pubsubMsgRcvCount is the number of pubsub messages received by the router. + pubsubMsgRcvCount prometheus.Counter + + // rpcSentCount is the number of rpc messages sent by the router. + rpcSentCount prometheus.Counter + + // iWantSentCount is the number of iwant messages sent by the router on rpcs. + iWantSentCount prometheus.Counter + + // iHaveSentCount is the number of ihave messages sent by the router on rpcs. + iHaveSentCount prometheus.Counter + + // graftSentCount is the number of graft messages sent by the router on rpcs. + graftSentCount prometheus.Counter + + // pruneSentCount is the number of prune messages sent by the router on rpcs. + pruneSentCount prometheus.Counter + + // pubsubMsgSentCount is the number of pubsub messages sent by the router. + pubsubMsgSentCount prometheus.Counter + + // outboundRpcDroppedCount is the number of outbound rpc messages dropped, typically because the outbound message queue is full. + outboundRpcDroppedCount prometheus.Counter + + // undeliveredOutboundMessageCount is the number of undelivered messages, i.e., messages that are not delivered to at least one subscriber. + undeliveredOutboundMessageCount prometheus.Counter +} - gs.receivedIHaveCount = promauto.NewCounter( - prometheus.CounterOpts{ +func NewGossipSubLocalMeshMetrics(prefix string) *LocalGossipSubRouterMetrics { + return &LocalGossipSubRouterMetrics{ + localMeshSize: *promauto.NewGaugeVec( + prometheus.GaugeOpts{ + Namespace: namespaceNetwork, + Subsystem: subsystemGossip, + Name: prefix + "gossipsub_local_mesh_size", + Help: "number of peers in the local mesh of the node", + }, + []string{LabelChannel}, + ), + peerAddedOnProtocolCount: *promauto.NewCounterVec(prometheus.CounterOpts{ Namespace: namespaceNetwork, Subsystem: subsystemGossip, - Name: gs.prefix + "gossipsub_received_ihave_total", - Help: "number of received ihave messages from gossipsub protocol", - }, - ) - - gs.receivedIWantCount = promauto.NewCounter( - prometheus.CounterOpts{ + Name: prefix + "gossipsub_added_peer_on_protocol_total", + Help: "number of peers added to the local gossipsub router on a gossipsub protocol", + }, []string{LabelProtocol}), + peerRemovedFromProtocolCount: prometheus.NewCounter(prometheus.CounterOpts{ Namespace: namespaceNetwork, Subsystem: subsystemGossip, - Name: gs.prefix + "gossipsub_received_iwant_total", - Help: "number of received iwant messages from gossipsub protocol", - }, - ) - - gs.receivedGraftCount = promauto.NewCounter( - prometheus.CounterOpts{ + Name: prefix + "gossipsub_removed_peer_total", + Help: "number of peers removed from the local gossipsub router on a gossipsub protocol due to unavailability or blacklisting", + }), + localPeerJoinedTopicCount: prometheus.NewCounter(prometheus.CounterOpts{ Namespace: namespaceNetwork, Subsystem: subsystemGossip, - Name: gs.prefix + "gossipsub_received_graft_total", - Help: "number of received graft messages from gossipsub protocol", - }, - ) - - gs.receivedPruneCount = promauto.NewCounter( - prometheus.CounterOpts{ + Name: prefix + "gossipsub_joined_topic_total", + Help: "number of times the local node joined (i.e., subscribed) to a topic", + }), + localPeerLeftTopicCount: prometheus.NewCounter(prometheus.CounterOpts{ Namespace: namespaceNetwork, Subsystem: subsystemGossip, - Name: gs.prefix + "gossipsub_received_prune_total", - Help: "number of received prune messages from gossipsub protocol", - }, - ) - - gs.incomingRpcAcceptedFullyCount = promauto.NewCounter( - prometheus.CounterOpts{ + Name: prefix + "gossipsub_left_topic_total", + Help: "number of times the local node left (i.e., unsubscribed) from a topic", + }), + peerGraftTopicCount: *promauto.NewCounterVec(prometheus.CounterOpts{ Namespace: namespaceNetwork, Subsystem: subsystemGossip, - Name: gs.prefix + "gossipsub_incoming_rpc_accepted_fully_total", - Help: "number of incoming rpc messages accepted fully by gossipsub protocol", - }, - ) - - gs.incomingRpcAcceptedOnlyControlCount = promauto.NewCounter( - prometheus.CounterOpts{ + Name: prefix + "gossipsub_graft_topic_total", + Help: "number of peers grafted to a topic on the local mesh of the node", + }, []string{LabelChannel}), + peerPruneTopicCount: *promauto.NewCounterVec(prometheus.CounterOpts{ Namespace: namespaceNetwork, Subsystem: subsystemGossip, - Name: gs.prefix + "gossipsub_incoming_rpc_accepted_only_control_total", - Help: "number of incoming rpc messages accepted only control messages by gossipsub protocol", - }, - ) - - gs.incomingRpcRejectedCount = promauto.NewCounter( - prometheus.CounterOpts{ + Name: prefix + "gossipsub_prune_topic_total", + Help: "number of peers pruned from a topic on the local mesh of the node", + }, []string{LabelChannel}), + messageEnteredValidationCount: prometheus.NewCounter(prometheus.CounterOpts{ Namespace: namespaceNetwork, Subsystem: subsystemGossip, - Name: gs.prefix + "gossipsub_incoming_rpc_rejected_total", - Help: "number of incoming rpc messages rejected by gossipsub protocol", - }, - ) - - gs.receivedPublishMessageCount = promauto.NewCounter( - prometheus.CounterOpts{ + Name: prefix + "gossipsub_message_entered_validation_total", + Help: "number of messages entered internal validation pipeline of gossipsub", + }), + messageDeliveredSize: prometheus.NewHistogram(prometheus.HistogramOpts{ + Namespace: namespaceNetwork, + Subsystem: subsystemGossip, + Buckets: []float64{KiB, 100 * KiB, 1 * MiB}, + Name: prefix + "gossipsub_message_delivered_size", + Help: "size of messages delivered to all subscribers of the topic", + }), + messageRejectedSize: *promauto.NewHistogramVec(prometheus.HistogramOpts{ + Namespace: namespaceNetwork, + Subsystem: subsystemGossip, + Name: prefix + "gossipsub_message_rejected_size_bytes", + Help: "size of messages rejected by the validation pipeline", + }, []string{LabelRejectionReason}), + messageDuplicateSize: prometheus.NewHistogram(prometheus.HistogramOpts{ + Namespace: namespaceNetwork, + Subsystem: subsystemGossip, + Buckets: []float64{KiB, 100 * KiB, 1 * MiB}, + Name: prefix + "gossipsub_duplicate_message_size_bytes", + Help: "size of messages that are duplicates of already received messages", + }), + peerThrottledCount: prometheus.NewCounter(prometheus.CounterOpts{ + Namespace: namespaceNetwork, + Subsystem: subsystemGossip, + Name: prefix + "gossipsub_peer_throttled_total", + Help: "number of peers that are throttled by the local node, i.e., the local node is not accepting any pubsub message from the peer but may still accept control messages", + }), + rpcRcvCount: prometheus.NewCounter(prometheus.CounterOpts{ + Namespace: namespaceNetwork, + Subsystem: subsystemGossip, + Name: prefix + "gossipsub_rpc_received_total", + Help: "number of rpc messages received and processed by the router (i.e., passed rpc inspection)", + }), + rpcSentCount: prometheus.NewCounter(prometheus.CounterOpts{ + Namespace: namespaceNetwork, + Subsystem: subsystemGossip, + Name: prefix + "gossipsub_rpc_sent_total", + Help: "number of rpc messages sent by the router", + }), + outboundRpcDroppedCount: prometheus.NewCounter(prometheus.CounterOpts{ + Namespace: namespaceNetwork, + Subsystem: subsystemGossip, + Name: prefix + "gossipsub_rpc_dropped_total", + Help: "number of outbound rpc messages dropped, typically because the outbound message queue is full", + }), + undeliveredOutboundMessageCount: prometheus.NewCounter(prometheus.CounterOpts{ + Namespace: namespaceNetwork, + Subsystem: subsystemGossip, + Name: prefix + "gossipsub_undelivered_message_total", + Help: "number of undelivered messages, i.e., messages that are not delivered to at least one subscriber", + }), + iHaveRcvCount: prometheus.NewCounter(prometheus.CounterOpts{ + Namespace: namespaceNetwork, + Subsystem: subsystemGossip, + Name: prefix + "gossipsub_ihave_received_total", + Help: "number of ihave messages received by the router on rpcs", + }), + iWantRcvCount: prometheus.NewCounter(prometheus.CounterOpts{ + Namespace: namespaceNetwork, + Subsystem: subsystemGossip, + Name: prefix + "gossipsub_iwant_received_total", + Help: "number of iwant messages received by the router on rpcs", + }), + graftRcvCount: prometheus.NewCounter(prometheus.CounterOpts{ + Namespace: namespaceNetwork, + Subsystem: subsystemGossip, + Name: prefix + "gossipsub_graft_received_total", + Help: "number of graft messages received by the router on rpcs", + }), + pruneRcvCount: prometheus.NewCounter(prometheus.CounterOpts{ + Namespace: namespaceNetwork, + Subsystem: subsystemGossip, + Name: prefix + "gossipsub_prune_received_total", + Help: "number of prune messages received by the router on rpcs", + }), + pubsubMsgRcvCount: prometheus.NewCounter(prometheus.CounterOpts{ + Namespace: namespaceNetwork, + Subsystem: subsystemGossip, + Name: prefix + "gossipsub_pubsub_message_received_total", + Help: "number of pubsub messages received by the router", + }), + iHaveSentCount: prometheus.NewCounter(prometheus.CounterOpts{ + Namespace: namespaceNetwork, + Subsystem: subsystemGossip, + Name: prefix + "gossipsub_ihave_sent_total", + Help: "number of ihave messages sent by the router on rpcs", + }), + iWantSentCount: prometheus.NewCounter(prometheus.CounterOpts{ + Namespace: namespaceNetwork, + Subsystem: subsystemGossip, + Name: prefix + "gossipsub_iwant_sent_total", + Help: "number of iwant messages sent by the router on rpcs", + }), + graftSentCount: prometheus.NewCounter(prometheus.CounterOpts{ + Namespace: namespaceNetwork, + Subsystem: subsystemGossip, + Name: prefix + "gossipsub_graft_sent_total", + Help: "number of graft messages sent by the router on rpcs", + }), + pruneSentCount: prometheus.NewCounter(prometheus.CounterOpts{ + Namespace: namespaceNetwork, + Subsystem: subsystemGossip, + Name: prefix + "gossipsub_prune_sent_total", + Help: "number of prune messages sent by the router on rpcs", + }), + pubsubMsgSentCount: prometheus.NewCounter(prometheus.CounterOpts{ Namespace: namespaceNetwork, Subsystem: subsystemGossip, - Name: gs.prefix + "gossipsub_received_publish_message_total", - Help: "number of received publish messages from gossipsub protocol", - }, - ) + Name: prefix + "gossipsub_pubsub_message_sent_total", + Help: "number of pubsub messages sent by the router", + }), + } +} - return gs +var _ module.LocalGossipSubRouterMetrics = (*LocalGossipSubRouterMetrics)(nil) + +// OnLocalMeshSizeUpdated updates the local mesh size metric. +func (g *LocalGossipSubRouterMetrics) OnLocalMeshSizeUpdated(topic string, size int) { + g.localMeshSize.WithLabelValues(topic).Set(float64(size)) } -// OnIWantReceived tracks the number of IWANT messages received by the node from other nodes. -// iWant is a control message that is sent by a node to request a message that it has seen advertised in an iHAVE message. -func (nc *GossipSubMetrics) OnIWantReceived(count int) { - nc.receivedIWantCount.Add(float64(count)) +// OnPeerAddedToProtocol is called when the local node receives a stream from a peer on a gossipsub-related protocol. +// Args: +// +// protocol: the protocol name that the peer is connected to. +func (g *LocalGossipSubRouterMetrics) OnPeerAddedToProtocol(protocol string) { + g.peerAddedOnProtocolCount.WithLabelValues(protocol).Inc() } -// OnIHaveReceived tracks the number of IHAVE messages received by the node from other nodes. -// iHave is a control message that is sent by a node to another node to indicate that it has a new gossiped message. -func (nc *GossipSubMetrics) OnIHaveReceived(count int) { - nc.receivedIHaveCount.Add(float64(count)) +// OnPeerRemovedFromProtocol is called when the local considers a remote peer blacklisted or unavailable. +func (g *LocalGossipSubRouterMetrics) OnPeerRemovedFromProtocol() { + g.peerRemovedFromProtocolCount.Inc() } -// OnGraftReceived tracks the number of GRAFT messages received by the node from other nodes. -// GRAFT is a control message of GossipSub protocol that connects two nodes over a topic directly as gossip partners. -func (nc *GossipSubMetrics) OnGraftReceived(count int) { - nc.receivedGraftCount.Add(float64(count)) +// OnLocalPeerJoinedTopic is called when the local node subscribes to a gossipsub topic. +// Args: +// +// topic: the topic that the local peer subscribed to. +func (g *LocalGossipSubRouterMetrics) OnLocalPeerJoinedTopic() { + g.localPeerJoinedTopicCount.Inc() } -// OnPruneReceived tracks the number of PRUNE messages received by the node from other nodes. -// PRUNE is a control message of GossipSub protocol that disconnects two nodes over a topic. -func (nc *GossipSubMetrics) OnPruneReceived(count int) { - nc.receivedPruneCount.Add(float64(count)) +// OnLocalPeerLeftTopic is called when the local node unsubscribes from a gossipsub topic. +// Args: +// +// topic: the topic that the local peer has unsubscribed from. +func (g *LocalGossipSubRouterMetrics) OnLocalPeerLeftTopic() { + g.localPeerLeftTopicCount.Inc() } -// OnIncomingRpcAcceptedFully tracks the number of RPC messages received by the node that are fully accepted. -// An RPC may contain any number of control messages, i.e., IHAVE, IWANT, GRAFT, PRUNE, as well as the actual messages. -// A fully accepted RPC means that all the control messages are accepted and all the messages are accepted. -func (nc *GossipSubMetrics) OnIncomingRpcAcceptedFully() { - nc.incomingRpcAcceptedFullyCount.Inc() +// OnPeerGraftTopic is called when the local node receives a GRAFT message from a remote peer on a topic. +// Note: the received GRAFT at this point is considered passed the RPC inspection, and is accepted by the local node. +func (g *LocalGossipSubRouterMetrics) OnPeerGraftTopic(topic string) { + g.peerGraftTopicCount.WithLabelValues(topic).Inc() } -// OnIncomingRpcAcceptedOnlyForControlMessages tracks the number of RPC messages received by the node that are accepted -// only for the control messages, i.e., only for the included IHAVE, IWANT, GRAFT, PRUNE. However, the actual messages -// included in the RPC are not accepted. -// This happens mostly when the validation pipeline of GossipSub is throttled, and cannot accept more actual messages for -// validation. -func (nc *GossipSubMetrics) OnIncomingRpcAcceptedOnlyForControlMessages() { - nc.incomingRpcAcceptedOnlyControlCount.Inc() +// OnPeerPruneTopic is called when the local node receives a PRUNE message from a remote peer on a topic. +// Note: the received PRUNE at this point is considered passed the RPC inspection, and is accepted by the local node. +func (g *LocalGossipSubRouterMetrics) OnPeerPruneTopic(topic string) { + g.peerPruneTopicCount.WithLabelValues(topic).Inc() } -// OnIncomingRpcRejected tracks the number of RPC messages received by the node that are rejected. -// This happens mostly when the RPC is coming from a low-scored peer based on the peer scoring module of GossipSub. -func (nc *GossipSubMetrics) OnIncomingRpcRejected() { - nc.incomingRpcRejectedCount.Inc() +// OnMessageEnteredValidation is called when a received pubsub message enters the validation pipeline. It is the +// internal validation pipeline of GossipSub protocol. The message may be rejected or accepted by the validation +// pipeline. +func (g *LocalGossipSubRouterMetrics) OnMessageEnteredValidation(int) { + g.messageEnteredValidationCount.Inc() } -// OnPublishedGossipMessagesReceived tracks the number of gossip messages received by the node from other nodes over an -// RPC message. -func (nc *GossipSubMetrics) OnPublishedGossipMessagesReceived(count int) { - nc.receivedPublishMessageCount.Add(float64(count)) +// OnMessageRejected is called when a received pubsub message is rejected by the validation pipeline. +// Args: +// +// reason: the reason for rejection. +// size: the size of the rejected message. +func (g *LocalGossipSubRouterMetrics) OnMessageRejected(size int, reason string) { + g.messageRejectedSize.WithLabelValues(reason).Observe(float64(size)) } -// GossipSubLocalMeshMetrics is a metrics collector for the local mesh of GossipSub protocol. -type GossipSubLocalMeshMetrics struct { - localMeshSize prometheus.GaugeVec +// OnMessageDuplicate is called when a received pubsub message is a duplicate of a previously received message, and +// is dropped. +// Args: +// +// size: the size of the duplicate message. +func (g *LocalGossipSubRouterMetrics) OnMessageDuplicate(size int) { + g.messageDuplicateSize.Observe(float64(size)) } -func NewGossipSubLocalMeshMetrics(prefix string) *GossipSubLocalMeshMetrics { - return &GossipSubLocalMeshMetrics{ - localMeshSize: *promauto.NewGaugeVec( - prometheus.GaugeOpts{ - Namespace: namespaceNetwork, - Subsystem: subsystemGossip, - Name: prefix + "gossipsub_local_mesh_size", - Help: "number of peers in the local mesh of the node", - }, - []string{LabelChannel}, - ), - } +// OnPeerThrottled is called when a peer is throttled by the local node, i.e., the local node is not accepting any +// pubsub message from the peer but may still accept control messages. +func (g *LocalGossipSubRouterMetrics) OnPeerThrottled() { + g.peerThrottledCount.Inc() } -var _ module.GossipSubLocalMeshMetrics = (*GossipSubLocalMeshMetrics)(nil) +// OnRpcReceived is called when an RPC message is received by the local node. The received RPC is considered +// passed the RPC inspection, and is accepted by the local node. +func (g *LocalGossipSubRouterMetrics) OnRpcReceived(msgCount int, iHaveCount int, iWantCount int, graftCount int, pruneCount int) { + g.rpcRcvCount.Inc() + g.pubsubMsgRcvCount.Add(float64(msgCount)) + g.iHaveRcvCount.Add(float64(iHaveCount)) + g.iWantRcvCount.Add(float64(iWantCount)) + g.graftRcvCount.Add(float64(graftCount)) + g.pruneRcvCount.Add(float64(pruneCount)) +} -// OnLocalMeshSizeUpdated updates the local mesh size metric. -func (g *GossipSubLocalMeshMetrics) OnLocalMeshSizeUpdated(topic string, size int) { - g.localMeshSize.WithLabelValues(topic).Set(float64(size)) +// OnRpcSent is called when an RPC message is sent by the local node. +// Note: the sent RPC is considered passed the RPC inspection, and is accepted by the local node. +func (g *LocalGossipSubRouterMetrics) OnRpcSent(msgCount int, iHaveCount int, iWantCount int, graftCount int, pruneCount int) { + g.rpcSentCount.Inc() + g.pubsubMsgSentCount.Add(float64(msgCount)) + g.iHaveSentCount.Add(float64(iHaveCount)) + g.iWantSentCount.Add(float64(iWantCount)) + g.graftSentCount.Add(float64(graftCount)) + g.pruneSentCount.Add(float64(pruneCount)) +} + +// OnOutboundRpcDropped is called when an outbound RPC message is dropped by the local node, typically because the local node +// outbound message queue is full; or the RPC is big and the local node cannot fragment it. +func (g *LocalGossipSubRouterMetrics) OnOutboundRpcDropped() { + g.outboundRpcDroppedCount.Inc() +} + +// OnUndeliveredMessage is called when a message is not delivered at least one subscriber of the topic, for example when +// the subscriber is too slow to process the message. +func (g *LocalGossipSubRouterMetrics) OnUndeliveredMessage() { + g.undeliveredOutboundMessageCount.Inc() +} + +// OnMessageDeliveredToAllSubscribers is called when a message is delivered to all subscribers of the topic. +// Args: +// +// size: the size of the delivered message. +func (g *LocalGossipSubRouterMetrics) OnMessageDeliveredToAllSubscribers(size int) { + g.messageDeliveredSize.Observe(float64(size)) } diff --git a/module/metrics/gossipsub_rpc_validation_inspector.go b/module/metrics/gossipsub_rpc_validation_inspector.go index 3556081ae71..6b79e8c477d 100644 --- a/module/metrics/gossipsub_rpc_validation_inspector.go +++ b/module/metrics/gossipsub_rpc_validation_inspector.go @@ -7,6 +7,12 @@ import ( "github.com/prometheus/client_golang/prometheus/promauto" "github.com/onflow/flow-go/module" + p2pmsg "github.com/onflow/flow-go/network/p2p/message" +) + +const ( + labelIHaveMessageIds = "ihave_message_ids" + labelIWantMessageIds = "iwant_message_ids" ) // GossipSubRpcValidationInspectorMetrics metrics collector for the gossipsub RPC validation inspector. @@ -14,6 +20,48 @@ type GossipSubRpcValidationInspectorMetrics struct { prefix string rpcCtrlMsgInAsyncPreProcessingGauge prometheus.Gauge rpcCtrlMsgAsyncProcessingTimeHistogram prometheus.Histogram + rpcCtrlMsgTruncation prometheus.HistogramVec + ctrlMsgInvalidTopicIdCount prometheus.CounterVec + receivedIWantMsgCount prometheus.Counter + receivedIWantMsgIDsHistogram prometheus.Histogram + receivedIHaveMsgCount prometheus.Counter + receivedIHaveMsgIDsHistogram prometheus.HistogramVec + receivedPruneCount prometheus.Counter + receivedGraftCount prometheus.Counter + receivedPublishMessageCount prometheus.Counter + incomingRpcCount prometheus.Counter + + // graft inspection + graftDuplicateTopicIdsHistogram prometheus.Histogram + graftDuplicateTopicIdsExceedThresholdCount prometheus.Counter + + // prune inspection + pruneDuplicateTopicIdsHistogram prometheus.Histogram + pruneDuplicateTopicIdsExceedThresholdCount prometheus.Counter + + // iHave inspection + iHaveDuplicateMessageIdHistogram prometheus.Histogram + iHaveDuplicateTopicIdHistogram prometheus.Histogram + iHaveDuplicateMessageIdExceedThresholdCount prometheus.Counter + iHaveDuplicateTopicIdExceedThresholdCount prometheus.Counter + + // iWant inspection + iWantDuplicateMessageIdHistogram prometheus.Histogram + iWantCacheMissHistogram prometheus.Histogram + iWantDuplicateMessageIdExceedThresholdCount prometheus.Counter + iWantCacheMissMessageIdExceedThresholdCount prometheus.Counter + + // inspection result + errActiveClusterIdsNotSetCount prometheus.Counter + errUnstakedPeerInspectionFailedCount prometheus.Counter + invalidControlMessageNotificationSentCount prometheus.Counter + + // publish messages + publishMessageInspectionErrExceedThresholdCount prometheus.Counter + publishMessageInvalidSenderCountHistogram prometheus.Histogram + publishMessageInvalidSubscriptionsHistogram prometheus.Histogram + publishMessageInvalidTopicIdHistogram prometheus.Histogram + publishMessageInspectedErrHistogram prometheus.Histogram } var _ module.GossipSubRpcValidationInspectorMetrics = (*GossipSubRpcValidationInspectorMetrics)(nil) @@ -39,6 +87,227 @@ func NewGossipSubRPCValidationInspectorMetrics(prefix string) *GossipSubRpcValid }, ) + gc.rpcCtrlMsgTruncation = *promauto.NewHistogramVec(prometheus.HistogramOpts{ + Namespace: namespaceNetwork, + Subsystem: subsystemGossip, + Name: gc.prefix + "rpc_control_message_truncation", + Help: "the number of times a control message was truncated", + Buckets: []float64{10, 100, 1000}, + }, []string{LabelMessage}) + + gc.receivedIHaveMsgCount = promauto.NewCounter(prometheus.CounterOpts{ + Namespace: namespaceNetwork, + Subsystem: subsystemGossip, + Name: gc.prefix + "gossipsub_received_ihave_total", + Help: "number of received ihave messages from gossipsub protocol", + }) + + gc.receivedIHaveMsgIDsHistogram = *promauto.NewHistogramVec(prometheus.HistogramOpts{ + Namespace: namespaceNetwork, + Subsystem: subsystemGossip, + Name: gc.prefix + "gossipsub_received_ihave_message_ids", + Help: "histogram of received ihave message ids from gossipsub protocol per channel", + }, []string{LabelChannel}) + + gc.receivedIWantMsgCount = promauto.NewCounter(prometheus.CounterOpts{ + Namespace: namespaceNetwork, + Subsystem: subsystemGossip, + Name: gc.prefix + "gossipsub_received_iwant_total", + Help: "total number of received iwant messages from gossipsub protocol", + }) + + gc.receivedIWantMsgIDsHistogram = promauto.NewHistogram(prometheus.HistogramOpts{ + Namespace: namespaceNetwork, + Subsystem: subsystemGossip, + Name: gc.prefix + "gossipsub_received_iwant_message_ids", + Help: "histogram of received iwant message ids from gossipsub protocol per channel", + }) + + gc.receivedGraftCount = promauto.NewCounter(prometheus.CounterOpts{ + Namespace: namespaceNetwork, + Subsystem: subsystemGossip, + Name: gc.prefix + "gossipsub_received_graft_total", + Help: "total number of received graft messages from gossipsub protocol", + }) + + gc.receivedPruneCount = promauto.NewCounter(prometheus.CounterOpts{ + Namespace: namespaceNetwork, + Subsystem: subsystemGossip, + Name: gc.prefix + "gossipsub_received_prune_total", + Help: "total number of received prune messages from gossipsub protocol", + }) + + gc.receivedPublishMessageCount = promauto.NewCounter(prometheus.CounterOpts{ + Namespace: namespaceNetwork, + Subsystem: subsystemGossip, + Name: gc.prefix + "gossipsub_received_publish_message_total", + Help: "total number of received publish messages from gossipsub protocol", + }) + + gc.incomingRpcCount = promauto.NewCounter(prometheus.CounterOpts{ + Namespace: namespaceNetwork, + Subsystem: subsystemGossip, + Name: gc.prefix + "gossipsub_incoming_rpc_total", + Help: "total number of incoming rpc messages from gossipsub protocol", + }) + + gc.iHaveDuplicateMessageIdHistogram = promauto.NewHistogram(prometheus.HistogramOpts{ + Namespace: namespaceNetwork, + Subsystem: subsystemGossip, + Buckets: []float64{1, 100, 1000}, + Name: gc.prefix + "rpc_inspection_ihave_duplicate_message_ids_count", + Help: "number of duplicate message ids received from gossipsub protocol during the async inspection of a single RPC", + }) + + gc.iHaveDuplicateTopicIdHistogram = promauto.NewHistogram(prometheus.HistogramOpts{ + Namespace: namespaceNetwork, + Subsystem: subsystemGossip, + Buckets: []float64{1, 100, 1000}, + Name: gc.prefix + "rpc_inspection_ihave_duplicate_topic_ids_count", + Help: "number of duplicate topic ids received from gossipsub protocol during the async inspection of a single RPC", + }) + + gc.iHaveDuplicateMessageIdExceedThresholdCount = promauto.NewCounter(prometheus.CounterOpts{ + Namespace: namespaceNetwork, + Subsystem: subsystemGossip, + Name: gc.prefix + "rpc_inspection_ihave_duplicate_message_ids_exceed_threshold_total", + Help: "total number of times that the async inspection of iHave messages failed due to the number of duplicate message ids exceeding the threshold", + }) + + gc.iHaveDuplicateTopicIdExceedThresholdCount = promauto.NewCounter(prometheus.CounterOpts{ + Namespace: namespaceNetwork, + Subsystem: subsystemGossip, + Name: gc.prefix + "rpc_inspection_ihave_duplicate_topic_ids_exceed_threshold_total", + Help: "total number of times that the async inspection of iHave messages failed due to the number of duplicate topic ids exceeding the threshold", + }) + + gc.iWantDuplicateMessageIdHistogram = promauto.NewHistogram(prometheus.HistogramOpts{ + Namespace: namespaceNetwork, + Subsystem: subsystemGossip, + Name: gc.prefix + "rpc_inspection_iwant_duplicate_message_ids_count", + Buckets: []float64{1, 100, 1000}, + Help: "number of duplicate message ids received from gossipsub protocol during the async inspection of a single RPC", + }) + + gc.iWantCacheMissHistogram = promauto.NewHistogram(prometheus.HistogramOpts{ + Namespace: namespaceNetwork, + Subsystem: subsystemGossip, + Name: gc.prefix + "rpc_inspection_iwant_cache_miss_message_ids_count", + Buckets: []float64{1, 100, 1000}, + Help: "total number of cache miss message ids received from gossipsub protocol during the async inspection of a single RPC", + }) + + gc.iWantDuplicateMessageIdExceedThresholdCount = promauto.NewCounter(prometheus.CounterOpts{ + Namespace: namespaceNetwork, + Subsystem: subsystemGossip, + Name: gc.prefix + "rpc_inspection_iwant_duplicate_message_ids_exceed_threshold_total", + Help: "total number of times that the async inspection of iWant messages failed due to the number of duplicate message ids ", + }) + + gc.iWantCacheMissMessageIdExceedThresholdCount = promauto.NewCounter(prometheus.CounterOpts{ + Namespace: namespaceNetwork, + Subsystem: subsystemGossip, + Name: gc.prefix + "rpc_inspection_iwant_cache_miss_message_ids_exceed_threshold_total", + Help: "total number of times that the async inspection of iWant messages failed due to the number of cache miss message ids ", + }) + + gc.ctrlMsgInvalidTopicIdCount = *promauto.NewCounterVec(prometheus.CounterOpts{ + Namespace: namespaceNetwork, + Subsystem: subsystemGossip, + Name: gc.prefix + "control_message_invalid_topic_id_total", + Help: "total number of control messages with invalid topic id received from gossipsub protocol during the async inspection", + }, []string{LabelMessage}) + + gc.errActiveClusterIdsNotSetCount = promauto.NewCounter(prometheus.CounterOpts{ + Namespace: namespaceNetwork, + Subsystem: subsystemGossip, + Name: gc.prefix + "active_cluster_ids_not_inspection_error_total", + Help: "total number of inspection errors due to active cluster ids not set inspection failure", + }) + + gc.errUnstakedPeerInspectionFailedCount = promauto.NewCounter(prometheus.CounterOpts{ + Namespace: namespaceNetwork, + Subsystem: subsystemGossip, + Name: gc.prefix + "unstaked_peer_inspection_error_total", + Help: "total number of inspection errors due to unstaked peer inspection failure", + }) + + gc.invalidControlMessageNotificationSentCount = promauto.NewCounter(prometheus.CounterOpts{ + Namespace: namespaceNetwork, + Subsystem: subsystemGossip, + Name: gc.prefix + "invalid_control_message_notification_sent_total", + Help: "number of invalid control message notifications (i.e., misbehavior report) sent due to async inspection of rpcs failure", + }) + + gc.graftDuplicateTopicIdsHistogram = promauto.NewHistogram(prometheus.HistogramOpts{ + Namespace: namespaceNetwork, + Subsystem: subsystemGossip, + Name: gc.prefix + "rpc_inspection_graft_duplicate_topic_ids_count", + Buckets: []float64{1, 100, 1000}, + Help: "number of duplicate topic ids on graft messages of a single RPC during the async inspection, regardless of the result of the inspection", + }) + + gc.graftDuplicateTopicIdsExceedThresholdCount = promauto.NewCounter(prometheus.CounterOpts{ + Namespace: namespaceNetwork, + Subsystem: subsystemGossip, + Name: gc.prefix + "rpc_inspection_graft_duplicate_topic_ids_exceed_threshold_total", + Help: "number of times that the async inspection of graft messages of an rpc failed due to the number of duplicate topic ids exceeding the threshold", + }) + + gc.pruneDuplicateTopicIdsHistogram = promauto.NewHistogram(prometheus.HistogramOpts{ + Namespace: namespaceNetwork, + Subsystem: subsystemGossip, + Buckets: []float64{1, 100, 1000}, + Name: gc.prefix + "rpc_inspection_prune_duplicate_topic_ids_count", + Help: "number of duplicate topic ids on prune messages of a single RPC during the async inspection, regardless of the result of the inspection", + }) + + gc.pruneDuplicateTopicIdsExceedThresholdCount = promauto.NewCounter(prometheus.CounterOpts{ + Namespace: namespaceNetwork, + Subsystem: subsystemGossip, + Name: gc.prefix + "rpc_inspection_prune_duplicate_topic_ids_exceed_threshold_total", + Help: "number of times that the async inspection of prune messages failed due to the number of duplicate topic ids exceeding the threshold", + }) + + gc.publishMessageInspectedErrHistogram = promauto.NewHistogram(prometheus.HistogramOpts{ + Namespace: namespaceNetwork, + Subsystem: subsystemGossip, + Name: gc.prefix + "publish_message_inspected_error_count", + Buckets: []float64{10, 100, 1000}, + Help: "number of errors that occurred during the async inspection of publish messages on a single RPC, regardless pof the result", + }) + + gc.publishMessageInvalidSenderCountHistogram = promauto.NewHistogram(prometheus.HistogramOpts{ + Namespace: namespaceNetwork, + Subsystem: subsystemGossip, + Name: gc.prefix + "rpc_inspection_publish_message_invalid_sender_count", + Buckets: []float64{1, 100, 1000}, + Help: "number of invalid senders observed during the async inspection of publish messages on a single RPC, regardless of the result", + }) + + gc.publishMessageInvalidTopicIdHistogram = promauto.NewHistogram(prometheus.HistogramOpts{ + Namespace: namespaceNetwork, + Subsystem: subsystemGossip, + Name: gc.prefix + "rpc_inspection_publish_message_invalid_topic_id_count", + Buckets: []float64{1, 100, 1000}, + Help: "number of invalid topic ids observed during the async inspection of publish messages on a single RPC, regardless of the result", + }) + + gc.publishMessageInvalidSubscriptionsHistogram = promauto.NewHistogram(prometheus.HistogramOpts{ + Namespace: namespaceNetwork, + Subsystem: subsystemGossip, + Name: gc.prefix + "rpc_inspection_publish_message_invalid_subscriptions_count", + Buckets: []float64{1, 100, 1000}, + Help: "number of invalid subscriptions observed during the async inspection of publish messages on a single RPC, regardless of the result", + }) + + gc.publishMessageInspectionErrExceedThresholdCount = promauto.NewCounter(prometheus.CounterOpts{ + Namespace: namespaceNetwork, + Subsystem: subsystemGossip, + Name: gc.prefix + "publish_message_inspection_err_exceed_threshold_total", + Help: "number of rpcs fail on inspection due to published message inspection errors exceeding the threshold", + }) + return gc } @@ -53,3 +322,177 @@ func (c *GossipSubRpcValidationInspectorMetrics) AsyncProcessingFinished(duratio c.rpcCtrlMsgInAsyncPreProcessingGauge.Dec() c.rpcCtrlMsgAsyncProcessingTimeHistogram.Observe(duration.Seconds()) } + +// OnControlMessageIDsTruncated tracks the number of times a control message was truncated. +// Args: +// +// messageType: the type of the control message that was truncated +// diff: the number of message ids truncated. +func (c *GossipSubRpcValidationInspectorMetrics) OnControlMessagesTruncated(messageType p2pmsg.ControlMessageType, diff int) { + c.rpcCtrlMsgTruncation.WithLabelValues(messageType.String()).Observe(float64(diff)) +} + +// OnIHaveControlMessageIdsTruncated tracks the number of times message ids on an iHave message were truncated. +// Note that this function is called only when the message ids are truncated from an iHave message, not when the iHave message itself is truncated. +// This is different from the OnControlMessagesTruncated function which is called when a slice of control messages truncated from an RPC with all their message ids. +// Args: +// +// diff: the number of actual messages truncated. +func (c *GossipSubRpcValidationInspectorMetrics) OnIHaveControlMessageIdsTruncated(diff int) { + c.OnControlMessagesTruncated(labelIHaveMessageIds, diff) +} + +// OnIWantControlMessageIdsTruncated tracks the number of times message ids on an iWant message were truncated. +// Note that this function is called only when the message ids are truncated from an iWant message, not when the iWant message itself is truncated. +// This is different from the OnControlMessagesTruncated function which is called when a slice of control messages truncated from an RPC with all their message ids. +// Args: +// +// diff: the number of actual messages truncated. +func (c *GossipSubRpcValidationInspectorMetrics) OnIWantControlMessageIdsTruncated(diff int) { + c.OnControlMessagesTruncated(labelIWantMessageIds, diff) +} + +// OnIWantMessageIDsReceived tracks the number of message ids received by the node from other nodes on an RPC. +// Note: this function is called on each IWANT message received by the node. +// Args: +// - msgIdCount: the number of message ids received on the IWANT message. +func (c *GossipSubRpcValidationInspectorMetrics) OnIWantMessageIDsReceived(msgIdCount int) { + c.receivedIWantMsgIDsHistogram.Observe(float64(msgIdCount)) +} + +// OnIHaveMessageIDsReceived tracks the number of message ids received by the node from other nodes on an iHave message. +// This function is called on each iHave message received by the node. +// Args: +// - channel: the channel on which the iHave message was received. +// - msgIdCount: the number of message ids received on the iHave message. +func (c *GossipSubRpcValidationInspectorMetrics) OnIHaveMessageIDsReceived(channel string, msgIdCount int) { + c.receivedIHaveMsgIDsHistogram.WithLabelValues(channel).Observe(float64(msgIdCount)) +} + +// OnIncomingRpcReceived tracks the number of incoming RPC messages received by the node. +func (c *GossipSubRpcValidationInspectorMetrics) OnIncomingRpcReceived(iHaveCount, iWantCount, graftCount, pruneCount, msgCount int) { + c.incomingRpcCount.Inc() + c.receivedPublishMessageCount.Add(float64(msgCount)) + c.receivedPruneCount.Add(float64(pruneCount)) + c.receivedGraftCount.Add(float64(graftCount)) + c.receivedIWantMsgCount.Add(float64(iWantCount)) + c.receivedIHaveMsgCount.Add(float64(iHaveCount)) +} + +// OnIWantMessagesInspected tracks the number of duplicate and cache miss message ids received by the node on iWant messages at the end of the async inspection iWants +// across one RPC, regardless of the result of the inspection. +// +// duplicateCount: the total number of duplicate message ids received by the node on the iWant messages at the end of the async inspection of the RPC. +// cacheMissCount: the total number of cache miss message ids received by the node on the iWant message at the end of the async inspection of the RPC. +func (c *GossipSubRpcValidationInspectorMetrics) OnIWantMessagesInspected(duplicateCount int, cacheMissCount int) { + c.iWantDuplicateMessageIdHistogram.Observe(float64(duplicateCount)) + c.iWantCacheMissHistogram.Observe(float64(cacheMissCount)) +} + +// OnIWantDuplicateMessageIdsExceedThreshold tracks the number of times that async inspection of iWant messages failed due to the total number of duplicate message ids +// received by the node on the iWant messages of a single RPC exceeding the threshold, which results in a misbehaviour report. +func (c *GossipSubRpcValidationInspectorMetrics) OnIWantDuplicateMessageIdsExceedThreshold() { + c.iWantDuplicateMessageIdExceedThresholdCount.Inc() +} + +// OnIWantCacheMissMessageIdsExceedThreshold tracks the number of times that async inspection of iWant messages failed due to the total +// number of cache miss message ids received by the node on the iWant messages of a single RPC exceeding the threshold, which results in a misbehaviour report. +func (c *GossipSubRpcValidationInspectorMetrics) OnIWantCacheMissMessageIdsExceedThreshold() { + c.iWantCacheMissMessageIdExceedThresholdCount.Inc() +} + +// OnIHaveMessagesInspected is called at the end of the async inspection of iHave messages of a single RPC, regardless of the result of the inspection. +// It tracks the number of duplicate topic ids and duplicate message ids received by the node on the iHave messages of that single RPC at the end of the async inspection iHaves. +// Args: +// +// duplicateTopicIds: the total number of duplicate topic ids received by the node on the iHave messages at the end of the async inspection of the RPC. +// duplicateMessageIds: the number of duplicate message ids received by the node on the iHave messages at the end of the async inspection of the RPC. +func (c *GossipSubRpcValidationInspectorMetrics) OnIHaveMessagesInspected(duplicateTopicIds int, duplicateMessageIds int) { + c.iHaveDuplicateTopicIdHistogram.Observe(float64(duplicateTopicIds)) + c.iHaveDuplicateMessageIdHistogram.Observe(float64(duplicateMessageIds)) +} + +// OnIHaveDuplicateTopicIdsExceedThreshold tracks the number of times that the async inspection of iHave messages of a single RPC failed due to the total number of duplicate topic ids +// received by the node on the iHave messages of that RPC exceeding the threshold, which results in a misbehaviour report. +func (c *GossipSubRpcValidationInspectorMetrics) OnIHaveDuplicateTopicIdsExceedThreshold() { + c.iHaveDuplicateTopicIdExceedThresholdCount.Inc() +} + +// OnIHaveDuplicateMessageIdsExceedThreshold tracks the number of times that the async inspection of iHave messages of a single RPC failed due to the total number of duplicate message ids +// received by the node on an iHave message exceeding the threshold, which results in a misbehaviour report. +func (c *GossipSubRpcValidationInspectorMetrics) OnIHaveDuplicateMessageIdsExceedThreshold() { + c.iHaveDuplicateMessageIdExceedThresholdCount.Inc() +} + +// OnInvalidTopicIdDetectedForControlMessage tracks the number of times that the async inspection of a control message type on a single RPC failed due to an invalid topic id. +// Args: +// - messageType: the type of the control message that was truncated. +func (c *GossipSubRpcValidationInspectorMetrics) OnInvalidTopicIdDetectedForControlMessage(messageType p2pmsg.ControlMessageType) { + c.ctrlMsgInvalidTopicIdCount.WithLabelValues(messageType.String()).Inc() +} + +// OnActiveClusterIDsNotSetErr tracks the number of times that the async inspection of a control message type on a single RPC failed due to active cluster ids not set inspection failure. +// This is not causing a misbehaviour report. +func (c *GossipSubRpcValidationInspectorMetrics) OnActiveClusterIDsNotSetErr() { + c.errActiveClusterIdsNotSetCount.Inc() +} + +// OnUnstakedPeerInspectionFailed tracks the number of times that the async inspection of a control message type on a single RPC failed due to unstaked peer inspection failure. +// This is not causing a misbehaviour report. +func (c *GossipSubRpcValidationInspectorMetrics) OnUnstakedPeerInspectionFailed() { + c.errUnstakedPeerInspectionFailedCount.Inc() +} + +// OnInvalidControlMessageNotificationSent tracks the number of times that the async inspection of a control message failed and resulted in dissemination of an invalid control message was sent (i.e., a +// misbehavior report). +func (c *GossipSubRpcValidationInspectorMetrics) OnInvalidControlMessageNotificationSent() { + c.invalidControlMessageNotificationSentCount.Inc() +} + +// OnPruneDuplicateTopicIdsExceedThreshold tracks the number of times that the async inspection of prune messages for an RPC failed due to the number of duplicate topic ids +// received by the node on prune messages of the same RPC excesses threshold, which results in a misbehaviour report. +func (c *GossipSubRpcValidationInspectorMetrics) OnPruneDuplicateTopicIdsExceedThreshold() { + c.pruneDuplicateTopicIdsExceedThresholdCount.Inc() +} + +// OnPruneMessageInspected is called at the end of the async inspection of prune messages of the RPC, regardless of the result of the inspection. +// Args: +// +// duplicateTopicIds: the number of duplicate topic ids received by the node on the prune messages of the RPC at the end of the async inspection prunes. +func (c *GossipSubRpcValidationInspectorMetrics) OnPruneMessageInspected(duplicateTopicIds int) { + c.pruneDuplicateTopicIdsHistogram.Observe(float64(duplicateTopicIds)) +} + +// OnGraftDuplicateTopicIdsExceedThreshold tracks the number of times that the async inspection of a graft message failed due to the number of duplicate topic ids. +// received by the node on graft messages of the same rpc excesses threshold, which results in a misbehaviour report. +func (c *GossipSubRpcValidationInspectorMetrics) OnGraftDuplicateTopicIdsExceedThreshold() { + c.graftDuplicateTopicIdsExceedThresholdCount.Inc() +} + +// OnGraftMessageInspected is called at the end of the async inspection of graft messages of a single RPC, regardless of the result of the inspection. +// Args: +// +// duplicateTopicIds: the number of duplicate topic ids received by the node on the graft messages at the end of the async inspection of a single RPC. +func (c *GossipSubRpcValidationInspectorMetrics) OnGraftMessageInspected(duplicateTopicIds int) { + c.graftDuplicateTopicIdsHistogram.Observe(float64(duplicateTopicIds)) +} + +// OnPublishMessageInspected is called at the end of the async inspection of publish messages of a single RPC, regardless of the result of the inspection. +// It tracks the total number of errors detected during the async inspection of the rpc together with their individual breakdown. +// Args: +// - errCount: the number of errors that occurred during the async inspection of publish messages. +// - invalidTopicIdsCount: the number of times that an invalid topic id was detected during the async inspection of publish messages. +// - invalidSubscriptionsCount: the number of times that an invalid subscription was detected during the async inspection of publish messages. +// - invalidSendersCount: the number of times that an invalid sender was detected during the async inspection of publish messages. +func (c *GossipSubRpcValidationInspectorMetrics) OnPublishMessageInspected(totalErrCount int, invalidTopicIdsCount int, invalidSubscriptionsCount int, invalidSendersCount int) { + c.publishMessageInspectedErrHistogram.Observe(float64(totalErrCount)) + c.publishMessageInvalidSenderCountHistogram.Observe(float64(invalidSendersCount)) + c.publishMessageInvalidSubscriptionsHistogram.Observe(float64(invalidSubscriptionsCount)) + c.publishMessageInvalidTopicIdHistogram.Observe(float64(invalidTopicIdsCount)) +} + +// OnPublishMessagesInspectionErrorExceedsThreshold tracks the number of times that async inspection of publish messages failed due to the number of errors exceeding the threshold. +// Note that it causes a misbehaviour report. +func (c *GossipSubRpcValidationInspectorMetrics) OnPublishMessagesInspectionErrorExceedsThreshold() { + c.publishMessageInspectionErrExceedThresholdCount.Inc() +} diff --git a/module/metrics/herocache.go b/module/metrics/herocache.go index 59ddb0f2f36..be9dc343488 100644 --- a/module/metrics/herocache.go +++ b/module/metrics/herocache.go @@ -72,8 +72,27 @@ func NetworkReceiveCacheMetricsFactory(f HeroCacheMetricsFactory, networkType ne return f(namespaceNetwork, r) } -func NewSubscriptionRecordCacheMetricsFactory(f HeroCacheMetricsFactory) module.HeroCacheMetrics { - return f(namespaceNetwork, ResourceNetworkingSubscriptionRecordsCache) +func NewSubscriptionRecordCacheMetricsFactory(f HeroCacheMetricsFactory, networkType network.NetworkingType) module.HeroCacheMetrics { + r := ResourceNetworkingSubscriptionRecordsCache + if networkType == network.PublicNetwork { + r = PrependPublicPrefix(r) + } + return f(namespaceNetwork, r) +} + +// NewGossipSubApplicationSpecificScoreCacheMetrics is the factory method for creating a new HeroCacheCollector for the +// application specific score cache of the GossipSub peer scoring module. The application specific score cache is used +// to keep track of the application specific score of peers in GossipSub. +// Args: +// - f: the HeroCacheMetricsFactory to create the collector +// Returns: +// - a HeroCacheMetrics for the application specific score cache +func NewGossipSubApplicationSpecificScoreCacheMetrics(f HeroCacheMetricsFactory, networkingType network.NetworkingType) module.HeroCacheMetrics { + r := ResourceNetworkingGossipSubApplicationSpecificScoreCache + if networkingType == network.PublicNetwork { + r = PrependPublicPrefix(r) + } + return f(namespaceNetwork, r) } // DisallowListCacheMetricsFactory is the factory method for creating a new HeroCacheCollector for the disallow list cache. @@ -91,6 +110,16 @@ func DisallowListCacheMetricsFactory(f HeroCacheMetricsFactory, networkingType n return f(namespaceNetwork, r) } +// GossipSubSpamRecordCacheMetricsFactory is the factory method for creating a new HeroCacheCollector for the spam record cache. +// The spam record cache is used to keep track of peers that are spamming the network and the reasons for it. +func GossipSubSpamRecordCacheMetricsFactory(f HeroCacheMetricsFactory, networkingType network.NetworkingType) module.HeroCacheMetrics { + r := ResourceNetworkingGossipSubSpamRecordCache + if networkingType == network.PublicNetwork { + r = PrependPublicPrefix(r) + } + return f(namespaceNetwork, r) +} + func NetworkDnsTxtCacheMetricsFactory(registrar prometheus.Registerer) *HeroCacheCollector { return NewHeroCacheCollector(namespaceNetwork, ResourceNetworkingDnsTxtCache, registrar) } @@ -140,15 +169,6 @@ func ApplicationLayerSpamRecordQueueMetricsFactory(f HeroCacheMetricsFactory, ne return f(namespaceNetwork, r) } -func GossipSubRPCMetricsObserverInspectorQueueMetricFactory(f HeroCacheMetricsFactory, networkType network.NetworkingType) module.HeroCacheMetrics { - // we don't use the public prefix for the metrics here for sake of backward compatibility of metric name. - r := ResourceNetworkingRpcMetricsObserverInspectorQueue - if networkType == network.PublicNetwork { - r = PrependPublicPrefix(r) - } - return f(namespaceNetwork, r) -} - func GossipSubRPCInspectorQueueMetricFactory(f HeroCacheMetricsFactory, networkType network.NetworkingType) module.HeroCacheMetrics { // we don't use the public prefix for the metrics here for sake of backward compatibility of metric name. r := ResourceNetworkingRpcValidationInspectorQueue @@ -193,6 +213,22 @@ func GossipSubRPCInspectorClusterPrefixedCacheMetricFactory(f HeroCacheMetricsFa return f(namespaceNetwork, r) } +// GossipSubAppSpecificScoreUpdateQueueMetricFactory is the factory method for creating a new HeroCacheCollector for the +// app-specific score update queue of the GossipSub peer scoring module. The app-specific score update queue is used to +// queue the update requests for the app-specific score of peers. The update requests are queued in a worker pool and +// processed asynchronously. +// Args: +// - f: the HeroCacheMetricsFactory to create the collector +// Returns: +// - a HeroCacheMetrics for the app-specific score update queue. +func GossipSubAppSpecificScoreUpdateQueueMetricFactory(f HeroCacheMetricsFactory, networkingType network.NetworkingType) module.HeroCacheMetrics { + r := ResourceNetworkingAppSpecificScoreUpdateQueue + if networkingType == network.PublicNetwork { + r = PrependPublicPrefix(r) + } + return f(namespaceNetwork, r) +} + func CollectionNodeTransactionsCacheMetrics(registrar prometheus.Registerer, epoch uint64) *HeroCacheCollector { return NewHeroCacheCollector(namespaceCollection, fmt.Sprintf("%s_%d", ResourceTransaction, epoch), registrar) } diff --git a/module/metrics/labels.go b/module/metrics/labels.go index e58610bec35..d9c46ff9704 100644 --- a/module/metrics/labels.go +++ b/module/metrics/labels.go @@ -23,6 +23,7 @@ const ( LabelStatusCode = "code" LabelMethod = "method" LabelService = "service" + LabelRejectionReason = "rejection_reason" ) const ( @@ -46,59 +47,61 @@ const ( ) const ( - ResourceUndefined = "undefined" - ResourceProposal = "proposal" - ResourceHeader = "header" - ResourceFinalizedHeight = "finalized_height" - ResourceIndex = "index" - ResourceIdentity = "identity" - ResourceGuarantee = "guarantee" - ResourceResult = "result" - ResourceResultApprovals = "result_approvals" - ResourceReceipt = "receipt" - ResourceQC = "qc" - ResourceMyReceipt = "my_receipt" - ResourceCollection = "collection" - ResourceApproval = "approval" - ResourceSeal = "seal" - ResourcePendingIncorporatedSeal = "pending_incorporated_seal" - ResourceCommit = "commit" - ResourceTransaction = "transaction" - ResourceClusterPayload = "cluster_payload" - ResourceClusterProposal = "cluster_proposal" - ResourceProcessedResultID = "processed_result_id" // verification node, finder engine // TODO: remove finder engine labels - ResourceDiscardedResultID = "discarded_result_id" // verification node, finder engine - ResourcePendingReceipt = "pending_receipt" // verification node, finder engine - ResourceReceiptIDsByResult = "receipt_ids_by_result" // verification node, finder engine - ResourcePendingReceiptIDsByBlock = "pending_receipt_ids_by_block" // verification node, finder engine - ResourcePendingResult = "pending_result" // verification node, match engine - ResourceChunkIDsByResult = "chunk_ids_by_result" // verification node, match engine - ResourcePendingChunk = "pending_chunk" // verification node, match engine - ResourcePendingBlock = "pending_block" // verification node, match engine - ResourceCachedReceipt = "cached_receipt" // verification node, finder engine - ResourceCachedBlockID = "cached_block_id" // verification node, finder engine - ResourceChunkStatus = "chunk_status" // verification node, fetcher engine - ResourceChunkRequest = "chunk_request" // verification node, requester engine - ResourceChunkConsumer = "chunk_consumer_jobs" // verification node - ResourceBlockConsumer = "block_consumer_jobs" // verification node - ResourceEpochSetup = "epoch_setup" - ResourceEpochCommit = "epoch_commit" - ResourceEpochStatus = "epoch_status" - ResourceNetworkingReceiveCache = "networking_received_message" // networking layer - ResourceNetworkingSubscriptionRecordsCache = "subscription_records_cache" // networking layer - ResourceNetworkingDnsIpCache = "networking_dns_ip_cache" // networking layer - ResourceNetworkingDnsTxtCache = "networking_dns_txt_cache" // networking layer - ResourceNetworkingDisallowListNotificationQueue = "networking_disallow_list_notification_queue" - ResourceNetworkingRpcInspectorNotificationQueue = "networking_rpc_inspector_notification_queue" - ResourceNetworkingRpcValidationInspectorQueue = "networking_rpc_validation_inspector_queue" - ResourceNetworkingRpcMetricsObserverInspectorQueue = "networking_rpc_metrics_observer_inspector_queue" - ResourceNetworkingApplicationLayerSpamRecordCache = "application_layer_spam_record_cache" - ResourceNetworkingApplicationLayerSpamReportQueue = "application_layer_spam_report_queue" - ResourceNetworkingRpcClusterPrefixReceivedCache = "rpc_cluster_prefixed_received_cache" - ResourceNetworkingDisallowListCache = "disallow_list_cache" - ResourceNetworkingRPCSentTrackerCache = "gossipsub_rpc_sent_tracker_cache" - ResourceNetworkingRPCSentTrackerQueue = "gossipsub_rpc_sent_tracker_queue" - ResourceNetworkingUnicastDialConfigCache = "unicast_dial_config_cache" + ResourceUndefined = "undefined" + ResourceProposal = "proposal" + ResourceHeader = "header" + ResourceFinalizedHeight = "finalized_height" + ResourceIndex = "index" + ResourceIdentity = "identity" + ResourceGuarantee = "guarantee" + ResourceResult = "result" + ResourceResultApprovals = "result_approvals" + ResourceReceipt = "receipt" + ResourceQC = "qc" + ResourceMyReceipt = "my_receipt" + ResourceCollection = "collection" + ResourceApproval = "approval" + ResourceSeal = "seal" + ResourcePendingIncorporatedSeal = "pending_incorporated_seal" + ResourceCommit = "commit" + ResourceTransaction = "transaction" + ResourceClusterPayload = "cluster_payload" + ResourceClusterProposal = "cluster_proposal" + ResourceProcessedResultID = "processed_result_id" // verification node, finder engine // TODO: remove finder engine labels + ResourceDiscardedResultID = "discarded_result_id" // verification node, finder engine + ResourcePendingReceipt = "pending_receipt" // verification node, finder engine + ResourceReceiptIDsByResult = "receipt_ids_by_result" // verification node, finder engine + ResourcePendingReceiptIDsByBlock = "pending_receipt_ids_by_block" // verification node, finder engine + ResourcePendingResult = "pending_result" // verification node, match engine + ResourceChunkIDsByResult = "chunk_ids_by_result" // verification node, match engine + ResourcePendingChunk = "pending_chunk" // verification node, match engine + ResourcePendingBlock = "pending_block" // verification node, match engine + ResourceCachedReceipt = "cached_receipt" // verification node, finder engine + ResourceCachedBlockID = "cached_block_id" // verification node, finder engine + ResourceChunkStatus = "chunk_status" // verification node, fetcher engine + ResourceChunkRequest = "chunk_request" // verification node, requester engine + ResourceChunkConsumer = "chunk_consumer_jobs" // verification node + ResourceBlockConsumer = "block_consumer_jobs" // verification node + ResourceEpochSetup = "epoch_setup" + ResourceEpochCommit = "epoch_commit" + ResourceEpochStatus = "epoch_status" + ResourceNetworkingReceiveCache = "networking_received_message" // networking layer + ResourceNetworkingSubscriptionRecordsCache = "subscription_records_cache" // networking layer + ResourceNetworkingDnsIpCache = "networking_dns_ip_cache" // networking layer + ResourceNetworkingDnsTxtCache = "networking_dns_txt_cache" // networking layer + ResourceNetworkingDisallowListNotificationQueue = "networking_disallow_list_notification_queue" + ResourceNetworkingRpcInspectorNotificationQueue = "networking_rpc_inspector_notification_queue" + ResourceNetworkingRpcValidationInspectorQueue = "networking_rpc_validation_inspector_queue" + ResourceNetworkingApplicationLayerSpamRecordCache = "application_layer_spam_record_cache" + ResourceNetworkingApplicationLayerSpamReportQueue = "application_layer_spam_report_queue" + ResourceNetworkingRpcClusterPrefixReceivedCache = "rpc_cluster_prefixed_received_cache" + ResourceNetworkingAppSpecificScoreUpdateQueue = "gossipsub_app_specific_score_update_queue" + ResourceNetworkingGossipSubApplicationSpecificScoreCache = "gossipsub_application_specific_score_cache" + ResourceNetworkingGossipSubSpamRecordCache = "gossipsub_spam_record_cache" + ResourceNetworkingDisallowListCache = "disallow_list_cache" + ResourceNetworkingRPCSentTrackerCache = "gossipsub_rpc_sent_tracker_cache" + ResourceNetworkingRPCSentTrackerQueue = "gossipsub_rpc_sent_tracker_queue" + ResourceNetworkingUnicastDialConfigCache = "unicast_dial_config_cache" ResourceFollowerPendingBlocksCache = "follower_pending_block_cache" // follower engine ResourceFollowerLoopCertifiedBlocksChannel = "follower_loop_certified_blocks_channel" // follower loop, certified blocks buffered channel diff --git a/module/metrics/libp2p_resource_manager.go b/module/metrics/libp2p_resource_manager.go index 4effd90d5e5..fd8ec8aa5ff 100644 --- a/module/metrics/libp2p_resource_manager.go +++ b/module/metrics/libp2p_resource_manager.go @@ -11,7 +11,7 @@ import ( "github.com/prometheus/client_golang/prometheus/promauto" "github.com/rs/zerolog" - "github.com/onflow/flow-go/network/p2p/p2plogging" + p2plogging "github.com/onflow/flow-go/network/p2p/logging" "github.com/onflow/flow-go/utils/logging" ) diff --git a/module/metrics/network.go b/module/metrics/network.go index 97f4af4d42e..eae2678a26e 100644 --- a/module/metrics/network.go +++ b/module/metrics/network.go @@ -10,7 +10,7 @@ import ( "github.com/rs/zerolog" "github.com/onflow/flow-go/module" - "github.com/onflow/flow-go/network/p2p/p2plogging" + logging2 "github.com/onflow/flow-go/network/p2p/logging" "github.com/onflow/flow-go/utils/logging" ) @@ -24,9 +24,8 @@ const ( type NetworkCollector struct { *UnicastManagerMetrics *LibP2PResourceManagerMetrics - *GossipSubMetrics *GossipSubScoreMetrics - *GossipSubLocalMeshMetrics + *LocalGossipSubRouterMetrics *GossipSubRpcValidationInspectorMetrics *AlspMetrics outboundMessageSize *prometheus.HistogramVec @@ -75,8 +74,7 @@ func NewNetworkCollector(logger zerolog.Logger, opts ...NetworkCollectorOpt) *Ne nc.UnicastManagerMetrics = NewUnicastManagerMetrics(nc.prefix) nc.LibP2PResourceManagerMetrics = NewLibP2PResourceManagerMetrics(logger, nc.prefix) - nc.GossipSubLocalMeshMetrics = NewGossipSubLocalMeshMetrics(nc.prefix) - nc.GossipSubMetrics = NewGossipSubMetrics(nc.prefix) + nc.LocalGossipSubRouterMetrics = NewGossipSubLocalMeshMetrics(nc.prefix) nc.GossipSubScoreMetrics = NewGossipSubScoreMetrics(nc.prefix) nc.GossipSubRpcValidationInspectorMetrics = NewGossipSubRPCValidationInspectorMetrics(nc.prefix) nc.AlspMetrics = NewAlspMetrics() @@ -360,7 +358,7 @@ func (nc *NetworkCollector) OnUnauthorizedMessage(role, msgType, topic, offense // OnRateLimitedPeer tracks the number of rate limited messages seen on the network. func (nc *NetworkCollector) OnRateLimitedPeer(peerID peer.ID, role, msgType, topic, reason string) { nc.logger.Warn(). - Str("peer_id", p2plogging.PeerId(peerID)). + Str("peer_id", logging2.PeerId(peerID)). Str("role", role). Str("message_type", msgType). Str("topic", topic). diff --git a/module/metrics/noop.go b/module/metrics/noop.go index 941f99d9b9f..14ce9bb3994 100644 --- a/module/metrics/noop.go +++ b/module/metrics/noop.go @@ -16,6 +16,7 @@ import ( "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/module" "github.com/onflow/flow-go/network/channels" + p2pmsg "github.com/onflow/flow-go/network/p2p/message" ) type NoopCollector struct{} @@ -162,6 +163,7 @@ func (nc *NoopCollector) FinishBlockReceivedToExecuted(blockID flow.Identifier) func (nc *NoopCollector) ExecutionComputationUsedPerBlock(computation uint64) {} func (nc *NoopCollector) ExecutionStorageStateCommitment(bytes int64) {} func (nc *NoopCollector) ExecutionLastExecutedBlockHeight(height uint64) {} +func (nc *NoopCollector) ExecutionLastFinalizedExecutedBlockHeight(height uint64) {} func (nc *NoopCollector) ExecutionBlockExecuted(_ time.Duration, _ module.ExecutionResultStats) {} func (nc *NoopCollector) ExecutionCollectionExecuted(_ time.Duration, _ module.ExecutionResultStats) { } @@ -271,18 +273,28 @@ func (nc *NoopCollector) OnDialRetryBudgetResetToDefault() func (nc *NoopCollector) OnStreamCreationRetryBudgetResetToDefault() {} var _ module.HeroCacheMetrics = (*NoopCollector)(nil) -var _ module.NetworkMetrics = (*NoopCollector)(nil) -func (nc *NoopCollector) OnRateLimitedUnicastMessage(role, msgType, topic, reason string) {} -func (nc *NoopCollector) OnIWantReceived(int) {} -func (nc *NoopCollector) OnIHaveReceived(int) {} -func (nc *NoopCollector) OnGraftReceived(int) {} -func (nc *NoopCollector) OnPruneReceived(int) {} -func (nc *NoopCollector) OnIncomingRpcAcceptedFully() {} -func (nc *NoopCollector) OnIncomingRpcAcceptedOnlyForControlMessages() {} -func (nc *NoopCollector) OnIncomingRpcRejected() {} -func (nc *NoopCollector) OnPublishedGossipMessagesReceived(int) {} -func (nc *NoopCollector) OnLocalMeshSizeUpdated(string, int) {} +func (nc *NoopCollector) OnIWantControlMessageIdsTruncated(diff int) {} +func (nc *NoopCollector) OnIWantMessageIDsReceived(msgIdCount int) {} +func (nc *NoopCollector) OnIHaveMessageIDsReceived(channel string, msgIdCount int) {} +func (nc *NoopCollector) OnLocalMeshSizeUpdated(string, int) {} +func (nc *NoopCollector) OnPeerAddedToProtocol(protocol string) {} +func (nc *NoopCollector) OnPeerRemovedFromProtocol() {} +func (nc *NoopCollector) OnLocalPeerJoinedTopic() {} +func (nc *NoopCollector) OnLocalPeerLeftTopic() {} +func (nc *NoopCollector) OnPeerGraftTopic(topic string) {} +func (nc *NoopCollector) OnPeerPruneTopic(topic string) {} +func (nc *NoopCollector) OnMessageEnteredValidation(size int) {} +func (nc *NoopCollector) OnMessageRejected(size int, reason string) {} +func (nc *NoopCollector) OnMessageDuplicate(size int) {} +func (nc *NoopCollector) OnPeerThrottled() {} +func (nc *NoopCollector) OnRpcReceived(msgCount int, iHaveCount int, iWantCount int, graftCount int, pruneCount int) { +} +func (nc *NoopCollector) OnRpcSent(msgCount int, iHaveCount int, iWantCount int, graftCount int, pruneCount int) { +} +func (nc *NoopCollector) OnOutboundRpcDropped() {} +func (nc *NoopCollector) OnUndeliveredMessage() {} +func (nc *NoopCollector) OnMessageDeliveredToAllSubscribers(size int) {} func (nc *NoopCollector) AllowConn(network.Direction, bool) {} func (nc *NoopCollector) BlockConn(network.Direction, bool) {} func (nc *NoopCollector) AllowStream(peer.ID, network.Direction) {} @@ -306,9 +318,31 @@ func (nc *NoopCollector) OnBehaviourPenaltyUpdated(f float64) func (nc *NoopCollector) OnIPColocationFactorUpdated(f float64) {} func (nc *NoopCollector) OnAppSpecificScoreUpdated(f float64) {} func (nc *NoopCollector) OnOverallPeerScoreUpdated(f float64) {} - -func (nc *NoopCollector) AsyncProcessingStarted() {} -func (nc *NoopCollector) AsyncProcessingFinished(time.Duration) {} +func (nc *NoopCollector) OnIHaveControlMessageIdsTruncated(diff int) {} +func (nc *NoopCollector) OnControlMessagesTruncated(messageType p2pmsg.ControlMessageType, diff int) { +} +func (nc *NoopCollector) OnIncomingRpcReceived(iHaveCount, iWantCount, graftCount, pruneCount, msgCount int) { +} +func (nc *NoopCollector) AsyncProcessingStarted() {} +func (nc *NoopCollector) AsyncProcessingFinished(time.Duration) {} +func (nc *NoopCollector) OnIWantMessagesInspected(duplicateCount int, cacheMissCount int) {} +func (nc *NoopCollector) OnIWantDuplicateMessageIdsExceedThreshold() {} +func (nc *NoopCollector) OnIWantCacheMissMessageIdsExceedThreshold() {} +func (nc *NoopCollector) OnIHaveMessagesInspected(duplicateTopicIds int, duplicateMessageIds int) {} +func (nc *NoopCollector) OnIHaveDuplicateTopicIdsExceedThreshold() {} +func (nc *NoopCollector) OnIHaveDuplicateMessageIdsExceedThreshold() {} +func (nc *NoopCollector) OnInvalidTopicIdDetectedForControlMessage(messageType p2pmsg.ControlMessageType) { +} +func (nc *NoopCollector) OnActiveClusterIDsNotSetErr() {} +func (nc *NoopCollector) OnUnstakedPeerInspectionFailed() {} +func (nc *NoopCollector) OnInvalidControlMessageNotificationSent() {} +func (nc *NoopCollector) OnPublishMessagesInspectionErrorExceedsThreshold() {} +func (nc *NoopCollector) OnPruneDuplicateTopicIdsExceedThreshold() {} +func (nc *NoopCollector) OnPruneMessageInspected(duplicateTopicIds int) {} +func (nc *NoopCollector) OnGraftDuplicateTopicIdsExceedThreshold() {} +func (nc *NoopCollector) OnGraftMessageInspected(duplicateTopicIds int) {} +func (nc *NoopCollector) OnPublishMessageInspected(totalErrCount int, invalidTopicIdsCount int, invalidSubscriptionsCount int, invalidSendersCount int) { +} func (nc *NoopCollector) OnMisbehaviorReported(string, string) {} func (nc *NoopCollector) OnViolationReportSkipped() {} diff --git a/module/mock/dkg_broker.go b/module/mock/dkg_broker.go index 788da3bbc1d..f2b6372519c 100644 --- a/module/mock/dkg_broker.go +++ b/module/mock/dkg_broker.go @@ -3,7 +3,7 @@ package mock import ( - crypto "github.com/onflow/flow-go/crypto" + crypto "github.com/onflow/crypto" flow "github.com/onflow/flow-go/model/flow" messages "github.com/onflow/flow-go/model/messages" diff --git a/module/mock/dkg_contract_client.go b/module/mock/dkg_contract_client.go index 7bcfa5eddbf..3a685819a34 100644 --- a/module/mock/dkg_contract_client.go +++ b/module/mock/dkg_contract_client.go @@ -3,7 +3,7 @@ package mock import ( - crypto "github.com/onflow/flow-go/crypto" + crypto "github.com/onflow/crypto" flow "github.com/onflow/flow-go/model/flow" messages "github.com/onflow/flow-go/model/messages" diff --git a/module/mock/dkg_controller.go b/module/mock/dkg_controller.go index 90d88cd362b..cf751c602f6 100644 --- a/module/mock/dkg_controller.go +++ b/module/mock/dkg_controller.go @@ -3,7 +3,7 @@ package mock import ( - crypto "github.com/onflow/flow-go/crypto" + crypto "github.com/onflow/crypto" flow "github.com/onflow/flow-go/model/flow" mock "github.com/stretchr/testify/mock" diff --git a/module/mock/execution_metrics.go b/module/mock/execution_metrics.go index 9abc39fdb3b..bca785e7e75 100644 --- a/module/mock/execution_metrics.go +++ b/module/mock/execution_metrics.go @@ -81,6 +81,11 @@ func (_m *ExecutionMetrics) ExecutionLastExecutedBlockHeight(height uint64) { _m.Called(height) } +// ExecutionLastFinalizedExecutedBlockHeight provides a mock function with given fields: height +func (_m *ExecutionMetrics) ExecutionLastFinalizedExecutedBlockHeight(height uint64) { + _m.Called(height) +} + // ExecutionScriptExecuted provides a mock function with given fields: dur, compUsed, memoryUsed, memoryEstimate func (_m *ExecutionMetrics) ExecutionScriptExecuted(dur time.Duration, compUsed uint64, memoryUsed uint64, memoryEstimate uint64) { _m.Called(dur, compUsed, memoryUsed, memoryEstimate) diff --git a/module/mock/gossip_sub_local_mesh_metrics.go b/module/mock/gossip_sub_local_mesh_metrics.go deleted file mode 100644 index aa14978a00e..00000000000 --- a/module/mock/gossip_sub_local_mesh_metrics.go +++ /dev/null @@ -1,30 +0,0 @@ -// Code generated by mockery v2.21.4. DO NOT EDIT. - -package mock - -import mock "github.com/stretchr/testify/mock" - -// GossipSubLocalMeshMetrics is an autogenerated mock type for the GossipSubLocalMeshMetrics type -type GossipSubLocalMeshMetrics struct { - mock.Mock -} - -// OnLocalMeshSizeUpdated provides a mock function with given fields: topic, size -func (_m *GossipSubLocalMeshMetrics) OnLocalMeshSizeUpdated(topic string, size int) { - _m.Called(topic, size) -} - -type mockConstructorTestingTNewGossipSubLocalMeshMetrics interface { - mock.TestingT - Cleanup(func()) -} - -// NewGossipSubLocalMeshMetrics creates a new instance of GossipSubLocalMeshMetrics. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewGossipSubLocalMeshMetrics(t mockConstructorTestingTNewGossipSubLocalMeshMetrics) *GossipSubLocalMeshMetrics { - mock := &GossipSubLocalMeshMetrics{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/module/mock/gossip_sub_metrics.go b/module/mock/gossip_sub_metrics.go index 393ff1ac555..f7e057ea5ba 100644 --- a/module/mock/gossip_sub_metrics.go +++ b/module/mock/gossip_sub_metrics.go @@ -6,6 +6,8 @@ import ( channels "github.com/onflow/flow-go/network/channels" mock "github.com/stretchr/testify/mock" + p2pmsg "github.com/onflow/flow-go/network/p2p/message" + time "time" ) @@ -24,6 +26,11 @@ func (_m *GossipSubMetrics) AsyncProcessingStarted() { _m.Called() } +// OnActiveClusterIDsNotSetErr provides a mock function with given fields: +func (_m *GossipSubMetrics) OnActiveClusterIDsNotSetErr() { + _m.Called() +} + // OnAppSpecificScoreUpdated provides a mock function with given fields: _a0 func (_m *GossipSubMetrics) OnAppSpecificScoreUpdated(_a0 float64) { _m.Called(_a0) @@ -34,19 +41,49 @@ func (_m *GossipSubMetrics) OnBehaviourPenaltyUpdated(_a0 float64) { _m.Called(_a0) } +// OnControlMessagesTruncated provides a mock function with given fields: messageType, diff +func (_m *GossipSubMetrics) OnControlMessagesTruncated(messageType p2pmsg.ControlMessageType, diff int) { + _m.Called(messageType, diff) +} + // OnFirstMessageDeliveredUpdated provides a mock function with given fields: _a0, _a1 func (_m *GossipSubMetrics) OnFirstMessageDeliveredUpdated(_a0 channels.Topic, _a1 float64) { _m.Called(_a0, _a1) } -// OnGraftReceived provides a mock function with given fields: count -func (_m *GossipSubMetrics) OnGraftReceived(count int) { - _m.Called(count) +// OnGraftDuplicateTopicIdsExceedThreshold provides a mock function with given fields: +func (_m *GossipSubMetrics) OnGraftDuplicateTopicIdsExceedThreshold() { + _m.Called() +} + +// OnGraftMessageInspected provides a mock function with given fields: duplicateTopicIds +func (_m *GossipSubMetrics) OnGraftMessageInspected(duplicateTopicIds int) { + _m.Called(duplicateTopicIds) +} + +// OnIHaveControlMessageIdsTruncated provides a mock function with given fields: diff +func (_m *GossipSubMetrics) OnIHaveControlMessageIdsTruncated(diff int) { + _m.Called(diff) +} + +// OnIHaveDuplicateMessageIdsExceedThreshold provides a mock function with given fields: +func (_m *GossipSubMetrics) OnIHaveDuplicateMessageIdsExceedThreshold() { + _m.Called() +} + +// OnIHaveDuplicateTopicIdsExceedThreshold provides a mock function with given fields: +func (_m *GossipSubMetrics) OnIHaveDuplicateTopicIdsExceedThreshold() { + _m.Called() +} + +// OnIHaveMessageIDsReceived provides a mock function with given fields: channel, msgIdCount +func (_m *GossipSubMetrics) OnIHaveMessageIDsReceived(channel string, msgIdCount int) { + _m.Called(channel, msgIdCount) } -// OnIHaveReceived provides a mock function with given fields: count -func (_m *GossipSubMetrics) OnIHaveReceived(count int) { - _m.Called(count) +// OnIHaveMessagesInspected provides a mock function with given fields: duplicateTopicIds, duplicateMessageIds +func (_m *GossipSubMetrics) OnIHaveMessagesInspected(duplicateTopicIds int, duplicateMessageIds int) { + _m.Called(duplicateTopicIds, duplicateMessageIds) } // OnIPColocationFactorUpdated provides a mock function with given fields: _a0 @@ -54,23 +91,38 @@ func (_m *GossipSubMetrics) OnIPColocationFactorUpdated(_a0 float64) { _m.Called(_a0) } -// OnIWantReceived provides a mock function with given fields: count -func (_m *GossipSubMetrics) OnIWantReceived(count int) { - _m.Called(count) +// OnIWantCacheMissMessageIdsExceedThreshold provides a mock function with given fields: +func (_m *GossipSubMetrics) OnIWantCacheMissMessageIdsExceedThreshold() { + _m.Called() } -// OnIncomingRpcAcceptedFully provides a mock function with given fields: -func (_m *GossipSubMetrics) OnIncomingRpcAcceptedFully() { - _m.Called() +// OnIWantControlMessageIdsTruncated provides a mock function with given fields: diff +func (_m *GossipSubMetrics) OnIWantControlMessageIdsTruncated(diff int) { + _m.Called(diff) } -// OnIncomingRpcAcceptedOnlyForControlMessages provides a mock function with given fields: -func (_m *GossipSubMetrics) OnIncomingRpcAcceptedOnlyForControlMessages() { +// OnIWantDuplicateMessageIdsExceedThreshold provides a mock function with given fields: +func (_m *GossipSubMetrics) OnIWantDuplicateMessageIdsExceedThreshold() { _m.Called() } -// OnIncomingRpcRejected provides a mock function with given fields: -func (_m *GossipSubMetrics) OnIncomingRpcRejected() { +// OnIWantMessageIDsReceived provides a mock function with given fields: msgIdCount +func (_m *GossipSubMetrics) OnIWantMessageIDsReceived(msgIdCount int) { + _m.Called(msgIdCount) +} + +// OnIWantMessagesInspected provides a mock function with given fields: duplicateCount, cacheMissCount +func (_m *GossipSubMetrics) OnIWantMessagesInspected(duplicateCount int, cacheMissCount int) { + _m.Called(duplicateCount, cacheMissCount) +} + +// OnIncomingRpcReceived provides a mock function with given fields: iHaveCount, iWantCount, graftCount, pruneCount, msgCount +func (_m *GossipSubMetrics) OnIncomingRpcReceived(iHaveCount int, iWantCount int, graftCount int, pruneCount int, msgCount int) { + _m.Called(iHaveCount, iWantCount, graftCount, pruneCount, msgCount) +} + +// OnInvalidControlMessageNotificationSent provides a mock function with given fields: +func (_m *GossipSubMetrics) OnInvalidControlMessageNotificationSent() { _m.Called() } @@ -79,29 +131,114 @@ func (_m *GossipSubMetrics) OnInvalidMessageDeliveredUpdated(_a0 channels.Topic, _m.Called(_a0, _a1) } +// OnInvalidTopicIdDetectedForControlMessage provides a mock function with given fields: messageType +func (_m *GossipSubMetrics) OnInvalidTopicIdDetectedForControlMessage(messageType p2pmsg.ControlMessageType) { + _m.Called(messageType) +} + // OnLocalMeshSizeUpdated provides a mock function with given fields: topic, size func (_m *GossipSubMetrics) OnLocalMeshSizeUpdated(topic string, size int) { _m.Called(topic, size) } +// OnLocalPeerJoinedTopic provides a mock function with given fields: +func (_m *GossipSubMetrics) OnLocalPeerJoinedTopic() { + _m.Called() +} + +// OnLocalPeerLeftTopic provides a mock function with given fields: +func (_m *GossipSubMetrics) OnLocalPeerLeftTopic() { + _m.Called() +} + // OnMeshMessageDeliveredUpdated provides a mock function with given fields: _a0, _a1 func (_m *GossipSubMetrics) OnMeshMessageDeliveredUpdated(_a0 channels.Topic, _a1 float64) { _m.Called(_a0, _a1) } +// OnMessageDeliveredToAllSubscribers provides a mock function with given fields: size +func (_m *GossipSubMetrics) OnMessageDeliveredToAllSubscribers(size int) { + _m.Called(size) +} + +// OnMessageDuplicate provides a mock function with given fields: size +func (_m *GossipSubMetrics) OnMessageDuplicate(size int) { + _m.Called(size) +} + +// OnMessageEnteredValidation provides a mock function with given fields: size +func (_m *GossipSubMetrics) OnMessageEnteredValidation(size int) { + _m.Called(size) +} + +// OnMessageRejected provides a mock function with given fields: size, reason +func (_m *GossipSubMetrics) OnMessageRejected(size int, reason string) { + _m.Called(size, reason) +} + +// OnOutboundRpcDropped provides a mock function with given fields: +func (_m *GossipSubMetrics) OnOutboundRpcDropped() { + _m.Called() +} + // OnOverallPeerScoreUpdated provides a mock function with given fields: _a0 func (_m *GossipSubMetrics) OnOverallPeerScoreUpdated(_a0 float64) { _m.Called(_a0) } -// OnPruneReceived provides a mock function with given fields: count -func (_m *GossipSubMetrics) OnPruneReceived(count int) { - _m.Called(count) +// OnPeerAddedToProtocol provides a mock function with given fields: protocol +func (_m *GossipSubMetrics) OnPeerAddedToProtocol(protocol string) { + _m.Called(protocol) } -// OnPublishedGossipMessagesReceived provides a mock function with given fields: count -func (_m *GossipSubMetrics) OnPublishedGossipMessagesReceived(count int) { - _m.Called(count) +// OnPeerGraftTopic provides a mock function with given fields: topic +func (_m *GossipSubMetrics) OnPeerGraftTopic(topic string) { + _m.Called(topic) +} + +// OnPeerPruneTopic provides a mock function with given fields: topic +func (_m *GossipSubMetrics) OnPeerPruneTopic(topic string) { + _m.Called(topic) +} + +// OnPeerRemovedFromProtocol provides a mock function with given fields: +func (_m *GossipSubMetrics) OnPeerRemovedFromProtocol() { + _m.Called() +} + +// OnPeerThrottled provides a mock function with given fields: +func (_m *GossipSubMetrics) OnPeerThrottled() { + _m.Called() +} + +// OnPruneDuplicateTopicIdsExceedThreshold provides a mock function with given fields: +func (_m *GossipSubMetrics) OnPruneDuplicateTopicIdsExceedThreshold() { + _m.Called() +} + +// OnPruneMessageInspected provides a mock function with given fields: duplicateTopicIds +func (_m *GossipSubMetrics) OnPruneMessageInspected(duplicateTopicIds int) { + _m.Called(duplicateTopicIds) +} + +// OnPublishMessageInspected provides a mock function with given fields: totalErrCount, invalidTopicIdsCount, invalidSubscriptionsCount, invalidSendersCount +func (_m *GossipSubMetrics) OnPublishMessageInspected(totalErrCount int, invalidTopicIdsCount int, invalidSubscriptionsCount int, invalidSendersCount int) { + _m.Called(totalErrCount, invalidTopicIdsCount, invalidSubscriptionsCount, invalidSendersCount) +} + +// OnPublishMessagesInspectionErrorExceedsThreshold provides a mock function with given fields: +func (_m *GossipSubMetrics) OnPublishMessagesInspectionErrorExceedsThreshold() { + _m.Called() +} + +// OnRpcReceived provides a mock function with given fields: msgCount, iHaveCount, iWantCount, graftCount, pruneCount +func (_m *GossipSubMetrics) OnRpcReceived(msgCount int, iHaveCount int, iWantCount int, graftCount int, pruneCount int) { + _m.Called(msgCount, iHaveCount, iWantCount, graftCount, pruneCount) +} + +// OnRpcSent provides a mock function with given fields: msgCount, iHaveCount, iWantCount, graftCount, pruneCount +func (_m *GossipSubMetrics) OnRpcSent(msgCount int, iHaveCount int, iWantCount int, graftCount int, pruneCount int) { + _m.Called(msgCount, iHaveCount, iWantCount, graftCount, pruneCount) } // OnTimeInMeshUpdated provides a mock function with given fields: _a0, _a1 @@ -109,6 +246,16 @@ func (_m *GossipSubMetrics) OnTimeInMeshUpdated(_a0 channels.Topic, _a1 time.Dur _m.Called(_a0, _a1) } +// OnUndeliveredMessage provides a mock function with given fields: +func (_m *GossipSubMetrics) OnUndeliveredMessage() { + _m.Called() +} + +// OnUnstakedPeerInspectionFailed provides a mock function with given fields: +func (_m *GossipSubMetrics) OnUnstakedPeerInspectionFailed() { + _m.Called() +} + // SetWarningStateCount provides a mock function with given fields: _a0 func (_m *GossipSubMetrics) SetWarningStateCount(_a0 uint) { _m.Called(_a0) diff --git a/module/mock/gossip_sub_router_metrics.go b/module/mock/gossip_sub_router_metrics.go deleted file mode 100644 index a320a11fffc..00000000000 --- a/module/mock/gossip_sub_router_metrics.go +++ /dev/null @@ -1,65 +0,0 @@ -// Code generated by mockery v2.21.4. DO NOT EDIT. - -package mock - -import mock "github.com/stretchr/testify/mock" - -// GossipSubRouterMetrics is an autogenerated mock type for the GossipSubRouterMetrics type -type GossipSubRouterMetrics struct { - mock.Mock -} - -// OnGraftReceived provides a mock function with given fields: count -func (_m *GossipSubRouterMetrics) OnGraftReceived(count int) { - _m.Called(count) -} - -// OnIHaveReceived provides a mock function with given fields: count -func (_m *GossipSubRouterMetrics) OnIHaveReceived(count int) { - _m.Called(count) -} - -// OnIWantReceived provides a mock function with given fields: count -func (_m *GossipSubRouterMetrics) OnIWantReceived(count int) { - _m.Called(count) -} - -// OnIncomingRpcAcceptedFully provides a mock function with given fields: -func (_m *GossipSubRouterMetrics) OnIncomingRpcAcceptedFully() { - _m.Called() -} - -// OnIncomingRpcAcceptedOnlyForControlMessages provides a mock function with given fields: -func (_m *GossipSubRouterMetrics) OnIncomingRpcAcceptedOnlyForControlMessages() { - _m.Called() -} - -// OnIncomingRpcRejected provides a mock function with given fields: -func (_m *GossipSubRouterMetrics) OnIncomingRpcRejected() { - _m.Called() -} - -// OnPruneReceived provides a mock function with given fields: count -func (_m *GossipSubRouterMetrics) OnPruneReceived(count int) { - _m.Called(count) -} - -// OnPublishedGossipMessagesReceived provides a mock function with given fields: count -func (_m *GossipSubRouterMetrics) OnPublishedGossipMessagesReceived(count int) { - _m.Called(count) -} - -type mockConstructorTestingTNewGossipSubRouterMetrics interface { - mock.TestingT - Cleanup(func()) -} - -// NewGossipSubRouterMetrics creates a new instance of GossipSubRouterMetrics. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewGossipSubRouterMetrics(t mockConstructorTestingTNewGossipSubRouterMetrics) *GossipSubRouterMetrics { - mock := &GossipSubRouterMetrics{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/module/mock/gossip_sub_rpc_inspector_metrics.go b/module/mock/gossip_sub_rpc_inspector_metrics.go new file mode 100644 index 00000000000..d972e9193e0 --- /dev/null +++ b/module/mock/gossip_sub_rpc_inspector_metrics.go @@ -0,0 +1,40 @@ +// Code generated by mockery v2.21.4. DO NOT EDIT. + +package mock + +import mock "github.com/stretchr/testify/mock" + +// GossipSubRpcInspectorMetrics is an autogenerated mock type for the GossipSubRpcInspectorMetrics type +type GossipSubRpcInspectorMetrics struct { + mock.Mock +} + +// OnIHaveMessageIDsReceived provides a mock function with given fields: channel, msgIdCount +func (_m *GossipSubRpcInspectorMetrics) OnIHaveMessageIDsReceived(channel string, msgIdCount int) { + _m.Called(channel, msgIdCount) +} + +// OnIWantMessageIDsReceived provides a mock function with given fields: msgIdCount +func (_m *GossipSubRpcInspectorMetrics) OnIWantMessageIDsReceived(msgIdCount int) { + _m.Called(msgIdCount) +} + +// OnIncomingRpcReceived provides a mock function with given fields: iHaveCount, iWantCount, graftCount, pruneCount, msgCount +func (_m *GossipSubRpcInspectorMetrics) OnIncomingRpcReceived(iHaveCount int, iWantCount int, graftCount int, pruneCount int, msgCount int) { + _m.Called(iHaveCount, iWantCount, graftCount, pruneCount, msgCount) +} + +type mockConstructorTestingTNewGossipSubRpcInspectorMetrics interface { + mock.TestingT + Cleanup(func()) +} + +// NewGossipSubRpcInspectorMetrics creates a new instance of GossipSubRpcInspectorMetrics. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewGossipSubRpcInspectorMetrics(t mockConstructorTestingTNewGossipSubRpcInspectorMetrics) *GossipSubRpcInspectorMetrics { + mock := &GossipSubRpcInspectorMetrics{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/module/mock/gossip_sub_rpc_validation_inspector_metrics.go b/module/mock/gossip_sub_rpc_validation_inspector_metrics.go index 782383fc3dc..84eef02f7ea 100644 --- a/module/mock/gossip_sub_rpc_validation_inspector_metrics.go +++ b/module/mock/gossip_sub_rpc_validation_inspector_metrics.go @@ -5,6 +5,8 @@ package mock import ( mock "github.com/stretchr/testify/mock" + p2pmsg "github.com/onflow/flow-go/network/p2p/message" + time "time" ) @@ -23,6 +25,116 @@ func (_m *GossipSubRpcValidationInspectorMetrics) AsyncProcessingStarted() { _m.Called() } +// OnActiveClusterIDsNotSetErr provides a mock function with given fields: +func (_m *GossipSubRpcValidationInspectorMetrics) OnActiveClusterIDsNotSetErr() { + _m.Called() +} + +// OnControlMessagesTruncated provides a mock function with given fields: messageType, diff +func (_m *GossipSubRpcValidationInspectorMetrics) OnControlMessagesTruncated(messageType p2pmsg.ControlMessageType, diff int) { + _m.Called(messageType, diff) +} + +// OnGraftDuplicateTopicIdsExceedThreshold provides a mock function with given fields: +func (_m *GossipSubRpcValidationInspectorMetrics) OnGraftDuplicateTopicIdsExceedThreshold() { + _m.Called() +} + +// OnGraftMessageInspected provides a mock function with given fields: duplicateTopicIds +func (_m *GossipSubRpcValidationInspectorMetrics) OnGraftMessageInspected(duplicateTopicIds int) { + _m.Called(duplicateTopicIds) +} + +// OnIHaveControlMessageIdsTruncated provides a mock function with given fields: diff +func (_m *GossipSubRpcValidationInspectorMetrics) OnIHaveControlMessageIdsTruncated(diff int) { + _m.Called(diff) +} + +// OnIHaveDuplicateMessageIdsExceedThreshold provides a mock function with given fields: +func (_m *GossipSubRpcValidationInspectorMetrics) OnIHaveDuplicateMessageIdsExceedThreshold() { + _m.Called() +} + +// OnIHaveDuplicateTopicIdsExceedThreshold provides a mock function with given fields: +func (_m *GossipSubRpcValidationInspectorMetrics) OnIHaveDuplicateTopicIdsExceedThreshold() { + _m.Called() +} + +// OnIHaveMessageIDsReceived provides a mock function with given fields: channel, msgIdCount +func (_m *GossipSubRpcValidationInspectorMetrics) OnIHaveMessageIDsReceived(channel string, msgIdCount int) { + _m.Called(channel, msgIdCount) +} + +// OnIHaveMessagesInspected provides a mock function with given fields: duplicateTopicIds, duplicateMessageIds +func (_m *GossipSubRpcValidationInspectorMetrics) OnIHaveMessagesInspected(duplicateTopicIds int, duplicateMessageIds int) { + _m.Called(duplicateTopicIds, duplicateMessageIds) +} + +// OnIWantCacheMissMessageIdsExceedThreshold provides a mock function with given fields: +func (_m *GossipSubRpcValidationInspectorMetrics) OnIWantCacheMissMessageIdsExceedThreshold() { + _m.Called() +} + +// OnIWantControlMessageIdsTruncated provides a mock function with given fields: diff +func (_m *GossipSubRpcValidationInspectorMetrics) OnIWantControlMessageIdsTruncated(diff int) { + _m.Called(diff) +} + +// OnIWantDuplicateMessageIdsExceedThreshold provides a mock function with given fields: +func (_m *GossipSubRpcValidationInspectorMetrics) OnIWantDuplicateMessageIdsExceedThreshold() { + _m.Called() +} + +// OnIWantMessageIDsReceived provides a mock function with given fields: msgIdCount +func (_m *GossipSubRpcValidationInspectorMetrics) OnIWantMessageIDsReceived(msgIdCount int) { + _m.Called(msgIdCount) +} + +// OnIWantMessagesInspected provides a mock function with given fields: duplicateCount, cacheMissCount +func (_m *GossipSubRpcValidationInspectorMetrics) OnIWantMessagesInspected(duplicateCount int, cacheMissCount int) { + _m.Called(duplicateCount, cacheMissCount) +} + +// OnIncomingRpcReceived provides a mock function with given fields: iHaveCount, iWantCount, graftCount, pruneCount, msgCount +func (_m *GossipSubRpcValidationInspectorMetrics) OnIncomingRpcReceived(iHaveCount int, iWantCount int, graftCount int, pruneCount int, msgCount int) { + _m.Called(iHaveCount, iWantCount, graftCount, pruneCount, msgCount) +} + +// OnInvalidControlMessageNotificationSent provides a mock function with given fields: +func (_m *GossipSubRpcValidationInspectorMetrics) OnInvalidControlMessageNotificationSent() { + _m.Called() +} + +// OnInvalidTopicIdDetectedForControlMessage provides a mock function with given fields: messageType +func (_m *GossipSubRpcValidationInspectorMetrics) OnInvalidTopicIdDetectedForControlMessage(messageType p2pmsg.ControlMessageType) { + _m.Called(messageType) +} + +// OnPruneDuplicateTopicIdsExceedThreshold provides a mock function with given fields: +func (_m *GossipSubRpcValidationInspectorMetrics) OnPruneDuplicateTopicIdsExceedThreshold() { + _m.Called() +} + +// OnPruneMessageInspected provides a mock function with given fields: duplicateTopicIds +func (_m *GossipSubRpcValidationInspectorMetrics) OnPruneMessageInspected(duplicateTopicIds int) { + _m.Called(duplicateTopicIds) +} + +// OnPublishMessageInspected provides a mock function with given fields: totalErrCount, invalidTopicIdsCount, invalidSubscriptionsCount, invalidSendersCount +func (_m *GossipSubRpcValidationInspectorMetrics) OnPublishMessageInspected(totalErrCount int, invalidTopicIdsCount int, invalidSubscriptionsCount int, invalidSendersCount int) { + _m.Called(totalErrCount, invalidTopicIdsCount, invalidSubscriptionsCount, invalidSendersCount) +} + +// OnPublishMessagesInspectionErrorExceedsThreshold provides a mock function with given fields: +func (_m *GossipSubRpcValidationInspectorMetrics) OnPublishMessagesInspectionErrorExceedsThreshold() { + _m.Called() +} + +// OnUnstakedPeerInspectionFailed provides a mock function with given fields: +func (_m *GossipSubRpcValidationInspectorMetrics) OnUnstakedPeerInspectionFailed() { + _m.Called() +} + type mockConstructorTestingTNewGossipSubRpcValidationInspectorMetrics interface { mock.TestingT Cleanup(func()) diff --git a/module/mock/lib_p2_p_metrics.go b/module/mock/lib_p2_p_metrics.go index c4551e77d8e..f91d247d6bf 100644 --- a/module/mock/lib_p2_p_metrics.go +++ b/module/mock/lib_p2_p_metrics.go @@ -8,6 +8,8 @@ import ( network "github.com/libp2p/go-libp2p/core/network" + p2pmsg "github.com/onflow/flow-go/network/p2p/message" + peer "github.com/libp2p/go-libp2p/core/peer" protocol "github.com/libp2p/go-libp2p/core/protocol" @@ -110,6 +112,11 @@ func (_m *LibP2PMetrics) InboundConnections(connectionCount uint) { _m.Called(connectionCount) } +// OnActiveClusterIDsNotSetErr provides a mock function with given fields: +func (_m *LibP2PMetrics) OnActiveClusterIDsNotSetErr() { + _m.Called() +} + // OnAppSpecificScoreUpdated provides a mock function with given fields: _a0 func (_m *LibP2PMetrics) OnAppSpecificScoreUpdated(_a0 float64) { _m.Called(_a0) @@ -120,6 +127,11 @@ func (_m *LibP2PMetrics) OnBehaviourPenaltyUpdated(_a0 float64) { _m.Called(_a0) } +// OnControlMessagesTruncated provides a mock function with given fields: messageType, diff +func (_m *LibP2PMetrics) OnControlMessagesTruncated(messageType p2pmsg.ControlMessageType, diff int) { + _m.Called(messageType, diff) +} + // OnDNSCacheHit provides a mock function with given fields: func (_m *LibP2PMetrics) OnDNSCacheHit() { _m.Called() @@ -160,14 +172,39 @@ func (_m *LibP2PMetrics) OnFirstMessageDeliveredUpdated(_a0 channels.Topic, _a1 _m.Called(_a0, _a1) } -// OnGraftReceived provides a mock function with given fields: count -func (_m *LibP2PMetrics) OnGraftReceived(count int) { - _m.Called(count) +// OnGraftDuplicateTopicIdsExceedThreshold provides a mock function with given fields: +func (_m *LibP2PMetrics) OnGraftDuplicateTopicIdsExceedThreshold() { + _m.Called() +} + +// OnGraftMessageInspected provides a mock function with given fields: duplicateTopicIds +func (_m *LibP2PMetrics) OnGraftMessageInspected(duplicateTopicIds int) { + _m.Called(duplicateTopicIds) +} + +// OnIHaveControlMessageIdsTruncated provides a mock function with given fields: diff +func (_m *LibP2PMetrics) OnIHaveControlMessageIdsTruncated(diff int) { + _m.Called(diff) +} + +// OnIHaveDuplicateMessageIdsExceedThreshold provides a mock function with given fields: +func (_m *LibP2PMetrics) OnIHaveDuplicateMessageIdsExceedThreshold() { + _m.Called() +} + +// OnIHaveDuplicateTopicIdsExceedThreshold provides a mock function with given fields: +func (_m *LibP2PMetrics) OnIHaveDuplicateTopicIdsExceedThreshold() { + _m.Called() } -// OnIHaveReceived provides a mock function with given fields: count -func (_m *LibP2PMetrics) OnIHaveReceived(count int) { - _m.Called(count) +// OnIHaveMessageIDsReceived provides a mock function with given fields: channel, msgIdCount +func (_m *LibP2PMetrics) OnIHaveMessageIDsReceived(channel string, msgIdCount int) { + _m.Called(channel, msgIdCount) +} + +// OnIHaveMessagesInspected provides a mock function with given fields: duplicateTopicIds, duplicateMessageIds +func (_m *LibP2PMetrics) OnIHaveMessagesInspected(duplicateTopicIds int, duplicateMessageIds int) { + _m.Called(duplicateTopicIds, duplicateMessageIds) } // OnIPColocationFactorUpdated provides a mock function with given fields: _a0 @@ -175,23 +212,38 @@ func (_m *LibP2PMetrics) OnIPColocationFactorUpdated(_a0 float64) { _m.Called(_a0) } -// OnIWantReceived provides a mock function with given fields: count -func (_m *LibP2PMetrics) OnIWantReceived(count int) { - _m.Called(count) +// OnIWantCacheMissMessageIdsExceedThreshold provides a mock function with given fields: +func (_m *LibP2PMetrics) OnIWantCacheMissMessageIdsExceedThreshold() { + _m.Called() } -// OnIncomingRpcAcceptedFully provides a mock function with given fields: -func (_m *LibP2PMetrics) OnIncomingRpcAcceptedFully() { - _m.Called() +// OnIWantControlMessageIdsTruncated provides a mock function with given fields: diff +func (_m *LibP2PMetrics) OnIWantControlMessageIdsTruncated(diff int) { + _m.Called(diff) } -// OnIncomingRpcAcceptedOnlyForControlMessages provides a mock function with given fields: -func (_m *LibP2PMetrics) OnIncomingRpcAcceptedOnlyForControlMessages() { +// OnIWantDuplicateMessageIdsExceedThreshold provides a mock function with given fields: +func (_m *LibP2PMetrics) OnIWantDuplicateMessageIdsExceedThreshold() { _m.Called() } -// OnIncomingRpcRejected provides a mock function with given fields: -func (_m *LibP2PMetrics) OnIncomingRpcRejected() { +// OnIWantMessageIDsReceived provides a mock function with given fields: msgIdCount +func (_m *LibP2PMetrics) OnIWantMessageIDsReceived(msgIdCount int) { + _m.Called(msgIdCount) +} + +// OnIWantMessagesInspected provides a mock function with given fields: duplicateCount, cacheMissCount +func (_m *LibP2PMetrics) OnIWantMessagesInspected(duplicateCount int, cacheMissCount int) { + _m.Called(duplicateCount, cacheMissCount) +} + +// OnIncomingRpcReceived provides a mock function with given fields: iHaveCount, iWantCount, graftCount, pruneCount, msgCount +func (_m *LibP2PMetrics) OnIncomingRpcReceived(iHaveCount int, iWantCount int, graftCount int, pruneCount int, msgCount int) { + _m.Called(iHaveCount, iWantCount, graftCount, pruneCount, msgCount) +} + +// OnInvalidControlMessageNotificationSent provides a mock function with given fields: +func (_m *LibP2PMetrics) OnInvalidControlMessageNotificationSent() { _m.Called() } @@ -200,21 +252,66 @@ func (_m *LibP2PMetrics) OnInvalidMessageDeliveredUpdated(_a0 channels.Topic, _a _m.Called(_a0, _a1) } +// OnInvalidTopicIdDetectedForControlMessage provides a mock function with given fields: messageType +func (_m *LibP2PMetrics) OnInvalidTopicIdDetectedForControlMessage(messageType p2pmsg.ControlMessageType) { + _m.Called(messageType) +} + // OnLocalMeshSizeUpdated provides a mock function with given fields: topic, size func (_m *LibP2PMetrics) OnLocalMeshSizeUpdated(topic string, size int) { _m.Called(topic, size) } +// OnLocalPeerJoinedTopic provides a mock function with given fields: +func (_m *LibP2PMetrics) OnLocalPeerJoinedTopic() { + _m.Called() +} + +// OnLocalPeerLeftTopic provides a mock function with given fields: +func (_m *LibP2PMetrics) OnLocalPeerLeftTopic() { + _m.Called() +} + // OnMeshMessageDeliveredUpdated provides a mock function with given fields: _a0, _a1 func (_m *LibP2PMetrics) OnMeshMessageDeliveredUpdated(_a0 channels.Topic, _a1 float64) { _m.Called(_a0, _a1) } +// OnMessageDeliveredToAllSubscribers provides a mock function with given fields: size +func (_m *LibP2PMetrics) OnMessageDeliveredToAllSubscribers(size int) { + _m.Called(size) +} + +// OnMessageDuplicate provides a mock function with given fields: size +func (_m *LibP2PMetrics) OnMessageDuplicate(size int) { + _m.Called(size) +} + +// OnMessageEnteredValidation provides a mock function with given fields: size +func (_m *LibP2PMetrics) OnMessageEnteredValidation(size int) { + _m.Called(size) +} + +// OnMessageRejected provides a mock function with given fields: size, reason +func (_m *LibP2PMetrics) OnMessageRejected(size int, reason string) { + _m.Called(size, reason) +} + +// OnOutboundRpcDropped provides a mock function with given fields: +func (_m *LibP2PMetrics) OnOutboundRpcDropped() { + _m.Called() +} + // OnOverallPeerScoreUpdated provides a mock function with given fields: _a0 func (_m *LibP2PMetrics) OnOverallPeerScoreUpdated(_a0 float64) { _m.Called(_a0) } +// OnPeerAddedToProtocol provides a mock function with given fields: _a0 +func (_m *LibP2PMetrics) OnPeerAddedToProtocol(_a0 string) { + _m.Called(_a0) +} + // OnPeerDialFailure provides a mock function with given fields: duration, attempts func (_m *LibP2PMetrics) OnPeerDialFailure(duration time.Duration, attempts int) { _m.Called(duration, attempts) @@ -225,14 +322,54 @@ func (_m *LibP2PMetrics) OnPeerDialed(duration time.Duration, attempts int) { _m.Called(duration, attempts) } -// OnPruneReceived provides a mock function with given fields: count -func (_m *LibP2PMetrics) OnPruneReceived(count int) { - _m.Called(count) +// OnPeerGraftTopic provides a mock function with given fields: topic +func (_m *LibP2PMetrics) OnPeerGraftTopic(topic string) { + _m.Called(topic) +} + +// OnPeerPruneTopic provides a mock function with given fields: topic +func (_m *LibP2PMetrics) OnPeerPruneTopic(topic string) { + _m.Called(topic) } -// OnPublishedGossipMessagesReceived provides a mock function with given fields: count -func (_m *LibP2PMetrics) OnPublishedGossipMessagesReceived(count int) { - _m.Called(count) +// OnPeerRemovedFromProtocol provides a mock function with given fields: +func (_m *LibP2PMetrics) OnPeerRemovedFromProtocol() { + _m.Called() +} + +// OnPeerThrottled provides a mock function with given fields: +func (_m *LibP2PMetrics) OnPeerThrottled() { + _m.Called() +} + +// OnPruneDuplicateTopicIdsExceedThreshold provides a mock function with given fields: +func (_m *LibP2PMetrics) OnPruneDuplicateTopicIdsExceedThreshold() { + _m.Called() +} + +// OnPruneMessageInspected provides a mock function with given fields: duplicateTopicIds +func (_m *LibP2PMetrics) OnPruneMessageInspected(duplicateTopicIds int) { + _m.Called(duplicateTopicIds) +} + +// OnPublishMessageInspected provides a mock function with given fields: totalErrCount, invalidTopicIdsCount, invalidSubscriptionsCount, invalidSendersCount +func (_m *LibP2PMetrics) OnPublishMessageInspected(totalErrCount int, invalidTopicIdsCount int, invalidSubscriptionsCount int, invalidSendersCount int) { + _m.Called(totalErrCount, invalidTopicIdsCount, invalidSubscriptionsCount, invalidSendersCount) +} + +// OnPublishMessagesInspectionErrorExceedsThreshold provides a mock function with given fields: +func (_m *LibP2PMetrics) OnPublishMessagesInspectionErrorExceedsThreshold() { + _m.Called() +} + +// OnRpcReceived provides a mock function with given fields: msgCount, iHaveCount, iWantCount, graftCount, pruneCount +func (_m *LibP2PMetrics) OnRpcReceived(msgCount int, iHaveCount int, iWantCount int, graftCount int, pruneCount int) { + _m.Called(msgCount, iHaveCount, iWantCount, graftCount, pruneCount) +} + +// OnRpcSent provides a mock function with given fields: msgCount, iHaveCount, iWantCount, graftCount, pruneCount +func (_m *LibP2PMetrics) OnRpcSent(msgCount int, iHaveCount int, iWantCount int, graftCount int, pruneCount int) { + _m.Called(msgCount, iHaveCount, iWantCount, graftCount, pruneCount) } // OnStreamCreated provides a mock function with given fields: duration, attempts @@ -265,6 +402,16 @@ func (_m *LibP2PMetrics) OnTimeInMeshUpdated(_a0 channels.Topic, _a1 time.Durati _m.Called(_a0, _a1) } +// OnUndeliveredMessage provides a mock function with given fields: +func (_m *LibP2PMetrics) OnUndeliveredMessage() { + _m.Called() +} + +// OnUnstakedPeerInspectionFailed provides a mock function with given fields: +func (_m *LibP2PMetrics) OnUnstakedPeerInspectionFailed() { + _m.Called() +} + // OutboundConnections provides a mock function with given fields: connectionCount func (_m *LibP2PMetrics) OutboundConnections(connectionCount uint) { _m.Called(connectionCount) diff --git a/module/mock/local.go b/module/mock/local.go index 37a980da0cd..673dc0b91a3 100644 --- a/module/mock/local.go +++ b/module/mock/local.go @@ -3,10 +3,10 @@ package mock import ( - crypto "github.com/onflow/flow-go/crypto" + crypto "github.com/onflow/crypto" flow "github.com/onflow/flow-go/model/flow" - hash "github.com/onflow/flow-go/crypto/hash" + hash "github.com/onflow/crypto/hash" mock "github.com/stretchr/testify/mock" ) diff --git a/module/mock/local_gossip_sub_router_metrics.go b/module/mock/local_gossip_sub_router_metrics.go new file mode 100644 index 00000000000..491d542f1f2 --- /dev/null +++ b/module/mock/local_gossip_sub_router_metrics.go @@ -0,0 +1,105 @@ +// Code generated by mockery v2.21.4. DO NOT EDIT. + +package mock + +import mock "github.com/stretchr/testify/mock" + +// LocalGossipSubRouterMetrics is an autogenerated mock type for the LocalGossipSubRouterMetrics type +type LocalGossipSubRouterMetrics struct { + mock.Mock +} + +// OnLocalMeshSizeUpdated provides a mock function with given fields: topic, size +func (_m *LocalGossipSubRouterMetrics) OnLocalMeshSizeUpdated(topic string, size int) { + _m.Called(topic, size) +} + +// OnLocalPeerJoinedTopic provides a mock function with given fields: +func (_m *LocalGossipSubRouterMetrics) OnLocalPeerJoinedTopic() { + _m.Called() +} + +// OnLocalPeerLeftTopic provides a mock function with given fields: +func (_m *LocalGossipSubRouterMetrics) OnLocalPeerLeftTopic() { + _m.Called() +} + +// OnMessageDeliveredToAllSubscribers provides a mock function with given fields: size +func (_m *LocalGossipSubRouterMetrics) OnMessageDeliveredToAllSubscribers(size int) { + _m.Called(size) +} + +// OnMessageDuplicate provides a mock function with given fields: size +func (_m *LocalGossipSubRouterMetrics) OnMessageDuplicate(size int) { + _m.Called(size) +} + +// OnMessageEnteredValidation provides a mock function with given fields: size +func (_m *LocalGossipSubRouterMetrics) OnMessageEnteredValidation(size int) { + _m.Called(size) +} + +// OnMessageRejected provides a mock function with given fields: size, reason +func (_m *LocalGossipSubRouterMetrics) OnMessageRejected(size int, reason string) { + _m.Called(size, reason) +} + +// OnOutboundRpcDropped provides a mock function with given fields: +func (_m *LocalGossipSubRouterMetrics) OnOutboundRpcDropped() { + _m.Called() +} + +// OnPeerAddedToProtocol provides a mock function with given fields: protocol +func (_m *LocalGossipSubRouterMetrics) OnPeerAddedToProtocol(protocol string) { + _m.Called(protocol) +} + +// OnPeerGraftTopic provides a mock function with given fields: topic +func (_m *LocalGossipSubRouterMetrics) OnPeerGraftTopic(topic string) { + _m.Called(topic) +} + +// OnPeerPruneTopic provides a mock function with given fields: topic +func (_m *LocalGossipSubRouterMetrics) OnPeerPruneTopic(topic string) { + _m.Called(topic) +} + +// OnPeerRemovedFromProtocol provides a mock function with given fields: +func (_m *LocalGossipSubRouterMetrics) OnPeerRemovedFromProtocol() { + _m.Called() +} + +// OnPeerThrottled provides a mock function with given fields: +func (_m *LocalGossipSubRouterMetrics) OnPeerThrottled() { + _m.Called() +} + +// OnRpcReceived provides a mock function with given fields: msgCount, iHaveCount, iWantCount, graftCount, pruneCount +func (_m *LocalGossipSubRouterMetrics) OnRpcReceived(msgCount int, iHaveCount int, iWantCount int, graftCount int, pruneCount int) { + _m.Called(msgCount, iHaveCount, iWantCount, graftCount, pruneCount) +} + +// OnRpcSent provides a mock function with given fields: msgCount, iHaveCount, iWantCount, graftCount, pruneCount +func (_m *LocalGossipSubRouterMetrics) OnRpcSent(msgCount int, iHaveCount int, iWantCount int, graftCount int, pruneCount int) { + _m.Called(msgCount, iHaveCount, iWantCount, graftCount, pruneCount) +} + +// OnUndeliveredMessage provides a mock function with given fields: +func (_m *LocalGossipSubRouterMetrics) OnUndeliveredMessage() { + _m.Called() +} + +type mockConstructorTestingTNewLocalGossipSubRouterMetrics interface { + mock.TestingT + Cleanup(func()) +} + +// NewLocalGossipSubRouterMetrics creates a new instance of LocalGossipSubRouterMetrics. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewLocalGossipSubRouterMetrics(t mockConstructorTestingTNewLocalGossipSubRouterMetrics) *LocalGossipSubRouterMetrics { + mock := &LocalGossipSubRouterMetrics{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/module/mock/network_metrics.go b/module/mock/network_metrics.go index 35a38e60e62..e86d63fb03a 100644 --- a/module/mock/network_metrics.go +++ b/module/mock/network_metrics.go @@ -8,6 +8,8 @@ import ( network "github.com/libp2p/go-libp2p/core/network" + p2pmsg "github.com/onflow/flow-go/network/p2p/message" + peer "github.com/libp2p/go-libp2p/core/peer" protocol "github.com/libp2p/go-libp2p/core/protocol" @@ -140,6 +142,11 @@ func (_m *NetworkMetrics) MessageRemoved(priority int) { _m.Called(priority) } +// OnActiveClusterIDsNotSetErr provides a mock function with given fields: +func (_m *NetworkMetrics) OnActiveClusterIDsNotSetErr() { + _m.Called() +} + // OnAppSpecificScoreUpdated provides a mock function with given fields: _a0 func (_m *NetworkMetrics) OnAppSpecificScoreUpdated(_a0 float64) { _m.Called(_a0) @@ -150,6 +157,11 @@ func (_m *NetworkMetrics) OnBehaviourPenaltyUpdated(_a0 float64) { _m.Called(_a0) } +// OnControlMessagesTruncated provides a mock function with given fields: messageType, diff +func (_m *NetworkMetrics) OnControlMessagesTruncated(messageType p2pmsg.ControlMessageType, diff int) { + _m.Called(messageType, diff) +} + // OnDNSCacheHit provides a mock function with given fields: func (_m *NetworkMetrics) OnDNSCacheHit() { _m.Called() @@ -190,14 +202,39 @@ func (_m *NetworkMetrics) OnFirstMessageDeliveredUpdated(_a0 channels.Topic, _a1 _m.Called(_a0, _a1) } -// OnGraftReceived provides a mock function with given fields: count -func (_m *NetworkMetrics) OnGraftReceived(count int) { - _m.Called(count) +// OnGraftDuplicateTopicIdsExceedThreshold provides a mock function with given fields: +func (_m *NetworkMetrics) OnGraftDuplicateTopicIdsExceedThreshold() { + _m.Called() +} + +// OnGraftMessageInspected provides a mock function with given fields: duplicateTopicIds +func (_m *NetworkMetrics) OnGraftMessageInspected(duplicateTopicIds int) { + _m.Called(duplicateTopicIds) +} + +// OnIHaveControlMessageIdsTruncated provides a mock function with given fields: diff +func (_m *NetworkMetrics) OnIHaveControlMessageIdsTruncated(diff int) { + _m.Called(diff) +} + +// OnIHaveDuplicateMessageIdsExceedThreshold provides a mock function with given fields: +func (_m *NetworkMetrics) OnIHaveDuplicateMessageIdsExceedThreshold() { + _m.Called() +} + +// OnIHaveDuplicateTopicIdsExceedThreshold provides a mock function with given fields: +func (_m *NetworkMetrics) OnIHaveDuplicateTopicIdsExceedThreshold() { + _m.Called() } -// OnIHaveReceived provides a mock function with given fields: count -func (_m *NetworkMetrics) OnIHaveReceived(count int) { - _m.Called(count) +// OnIHaveMessageIDsReceived provides a mock function with given fields: channel, msgIdCount +func (_m *NetworkMetrics) OnIHaveMessageIDsReceived(channel string, msgIdCount int) { + _m.Called(channel, msgIdCount) +} + +// OnIHaveMessagesInspected provides a mock function with given fields: duplicateTopicIds, duplicateMessageIds +func (_m *NetworkMetrics) OnIHaveMessagesInspected(duplicateTopicIds int, duplicateMessageIds int) { + _m.Called(duplicateTopicIds, duplicateMessageIds) } // OnIPColocationFactorUpdated provides a mock function with given fields: _a0 @@ -205,23 +242,38 @@ func (_m *NetworkMetrics) OnIPColocationFactorUpdated(_a0 float64) { _m.Called(_a0) } -// OnIWantReceived provides a mock function with given fields: count -func (_m *NetworkMetrics) OnIWantReceived(count int) { - _m.Called(count) +// OnIWantCacheMissMessageIdsExceedThreshold provides a mock function with given fields: +func (_m *NetworkMetrics) OnIWantCacheMissMessageIdsExceedThreshold() { + _m.Called() +} + +// OnIWantControlMessageIdsTruncated provides a mock function with given fields: diff +func (_m *NetworkMetrics) OnIWantControlMessageIdsTruncated(diff int) { + _m.Called(diff) } -// OnIncomingRpcAcceptedFully provides a mock function with given fields: -func (_m *NetworkMetrics) OnIncomingRpcAcceptedFully() { +// OnIWantDuplicateMessageIdsExceedThreshold provides a mock function with given fields: +func (_m *NetworkMetrics) OnIWantDuplicateMessageIdsExceedThreshold() { _m.Called() } -// OnIncomingRpcAcceptedOnlyForControlMessages provides a mock function with given fields: -func (_m *NetworkMetrics) OnIncomingRpcAcceptedOnlyForControlMessages() { - _m.Called() +// OnIWantMessageIDsReceived provides a mock function with given fields: msgIdCount +func (_m *NetworkMetrics) OnIWantMessageIDsReceived(msgIdCount int) { + _m.Called(msgIdCount) +} + +// OnIWantMessagesInspected provides a mock function with given fields: duplicateCount, cacheMissCount +func (_m *NetworkMetrics) OnIWantMessagesInspected(duplicateCount int, cacheMissCount int) { + _m.Called(duplicateCount, cacheMissCount) } -// OnIncomingRpcRejected provides a mock function with given fields: -func (_m *NetworkMetrics) OnIncomingRpcRejected() { +// OnIncomingRpcReceived provides a mock function with given fields: iHaveCount, iWantCount, graftCount, pruneCount, msgCount +func (_m *NetworkMetrics) OnIncomingRpcReceived(iHaveCount int, iWantCount int, graftCount int, pruneCount int, msgCount int) { + _m.Called(iHaveCount, iWantCount, graftCount, pruneCount, msgCount) +} + +// OnInvalidControlMessageNotificationSent provides a mock function with given fields: +func (_m *NetworkMetrics) OnInvalidControlMessageNotificationSent() { _m.Called() } @@ -230,26 +282,71 @@ func (_m *NetworkMetrics) OnInvalidMessageDeliveredUpdated(_a0 channels.Topic, _ _m.Called(_a0, _a1) } +// OnInvalidTopicIdDetectedForControlMessage provides a mock function with given fields: messageType +func (_m *NetworkMetrics) OnInvalidTopicIdDetectedForControlMessage(messageType p2pmsg.ControlMessageType) { + _m.Called(messageType) +} + // OnLocalMeshSizeUpdated provides a mock function with given fields: topic, size func (_m *NetworkMetrics) OnLocalMeshSizeUpdated(topic string, size int) { _m.Called(topic, size) } +// OnLocalPeerJoinedTopic provides a mock function with given fields: +func (_m *NetworkMetrics) OnLocalPeerJoinedTopic() { + _m.Called() +} + +// OnLocalPeerLeftTopic provides a mock function with given fields: +func (_m *NetworkMetrics) OnLocalPeerLeftTopic() { + _m.Called() +} + // OnMeshMessageDeliveredUpdated provides a mock function with given fields: _a0, _a1 func (_m *NetworkMetrics) OnMeshMessageDeliveredUpdated(_a0 channels.Topic, _a1 float64) { _m.Called(_a0, _a1) } +// OnMessageDeliveredToAllSubscribers provides a mock function with given fields: size +func (_m *NetworkMetrics) OnMessageDeliveredToAllSubscribers(size int) { + _m.Called(size) +} + +// OnMessageDuplicate provides a mock function with given fields: size +func (_m *NetworkMetrics) OnMessageDuplicate(size int) { + _m.Called(size) +} + +// OnMessageEnteredValidation provides a mock function with given fields: size +func (_m *NetworkMetrics) OnMessageEnteredValidation(size int) { + _m.Called(size) +} + +// OnMessageRejected provides a mock function with given fields: size, reason +func (_m *NetworkMetrics) OnMessageRejected(size int, reason string) { + _m.Called(size, reason) +} + // OnMisbehaviorReported provides a mock function with given fields: channel, misbehaviorType func (_m *NetworkMetrics) OnMisbehaviorReported(channel string, misbehaviorType string) { _m.Called(channel, misbehaviorType) } +// OnOutboundRpcDropped provides a mock function with given fields: +func (_m *NetworkMetrics) OnOutboundRpcDropped() { + _m.Called() +} + // OnOverallPeerScoreUpdated provides a mock function with given fields: _a0 func (_m *NetworkMetrics) OnOverallPeerScoreUpdated(_a0 float64) { _m.Called(_a0) } +// OnPeerAddedToProtocol provides a mock function with given fields: _a0 +func (_m *NetworkMetrics) OnPeerAddedToProtocol(_a0 string) { + _m.Called(_a0) +} + // OnPeerDialFailure provides a mock function with given fields: duration, attempts func (_m *NetworkMetrics) OnPeerDialFailure(duration time.Duration, attempts int) { _m.Called(duration, attempts) @@ -260,14 +357,44 @@ func (_m *NetworkMetrics) OnPeerDialed(duration time.Duration, attempts int) { _m.Called(duration, attempts) } -// OnPruneReceived provides a mock function with given fields: count -func (_m *NetworkMetrics) OnPruneReceived(count int) { - _m.Called(count) +// OnPeerGraftTopic provides a mock function with given fields: topic +func (_m *NetworkMetrics) OnPeerGraftTopic(topic string) { + _m.Called(topic) +} + +// OnPeerPruneTopic provides a mock function with given fields: topic +func (_m *NetworkMetrics) OnPeerPruneTopic(topic string) { + _m.Called(topic) +} + +// OnPeerRemovedFromProtocol provides a mock function with given fields: +func (_m *NetworkMetrics) OnPeerRemovedFromProtocol() { + _m.Called() +} + +// OnPeerThrottled provides a mock function with given fields: +func (_m *NetworkMetrics) OnPeerThrottled() { + _m.Called() +} + +// OnPruneDuplicateTopicIdsExceedThreshold provides a mock function with given fields: +func (_m *NetworkMetrics) OnPruneDuplicateTopicIdsExceedThreshold() { + _m.Called() +} + +// OnPruneMessageInspected provides a mock function with given fields: duplicateTopicIds +func (_m *NetworkMetrics) OnPruneMessageInspected(duplicateTopicIds int) { + _m.Called(duplicateTopicIds) } -// OnPublishedGossipMessagesReceived provides a mock function with given fields: count -func (_m *NetworkMetrics) OnPublishedGossipMessagesReceived(count int) { - _m.Called(count) +// OnPublishMessageInspected provides a mock function with given fields: totalErrCount, invalidTopicIdsCount, invalidSubscriptionsCount, invalidSendersCount +func (_m *NetworkMetrics) OnPublishMessageInspected(totalErrCount int, invalidTopicIdsCount int, invalidSubscriptionsCount int, invalidSendersCount int) { + _m.Called(totalErrCount, invalidTopicIdsCount, invalidSubscriptionsCount, invalidSendersCount) +} + +// OnPublishMessagesInspectionErrorExceedsThreshold provides a mock function with given fields: +func (_m *NetworkMetrics) OnPublishMessagesInspectionErrorExceedsThreshold() { + _m.Called() } // OnRateLimitedPeer provides a mock function with given fields: pid, role, msgType, topic, reason @@ -275,6 +402,16 @@ func (_m *NetworkMetrics) OnRateLimitedPeer(pid peer.ID, role string, msgType st _m.Called(pid, role, msgType, topic, reason) } +// OnRpcReceived provides a mock function with given fields: msgCount, iHaveCount, iWantCount, graftCount, pruneCount +func (_m *NetworkMetrics) OnRpcReceived(msgCount int, iHaveCount int, iWantCount int, graftCount int, pruneCount int) { + _m.Called(msgCount, iHaveCount, iWantCount, graftCount, pruneCount) +} + +// OnRpcSent provides a mock function with given fields: msgCount, iHaveCount, iWantCount, graftCount, pruneCount +func (_m *NetworkMetrics) OnRpcSent(msgCount int, iHaveCount int, iWantCount int, graftCount int, pruneCount int) { + _m.Called(msgCount, iHaveCount, iWantCount, graftCount, pruneCount) +} + // OnStreamCreated provides a mock function with given fields: duration, attempts func (_m *NetworkMetrics) OnStreamCreated(duration time.Duration, attempts int) { _m.Called(duration, attempts) @@ -310,6 +447,16 @@ func (_m *NetworkMetrics) OnUnauthorizedMessage(role string, msgType string, top _m.Called(role, msgType, topic, offense) } +// OnUndeliveredMessage provides a mock function with given fields: +func (_m *NetworkMetrics) OnUndeliveredMessage() { + _m.Called() +} + +// OnUnstakedPeerInspectionFailed provides a mock function with given fields: +func (_m *NetworkMetrics) OnUnstakedPeerInspectionFailed() { + _m.Called() +} + // OnViolationReportSkipped provides a mock function with given fields: func (_m *NetworkMetrics) OnViolationReportSkipped() { _m.Called() diff --git a/module/mock/public_key.go b/module/mock/public_key.go index 6b9c8432aca..8a8e35f80d4 100644 --- a/module/mock/public_key.go +++ b/module/mock/public_key.go @@ -3,9 +3,8 @@ package mock import ( - crypto "github.com/onflow/flow-go/crypto" - hash "github.com/onflow/flow-go/crypto/hash" - + crypto "github.com/onflow/crypto" + hash "github.com/onflow/crypto/hash" mock "github.com/stretchr/testify/mock" ) diff --git a/module/mock/random_beacon_key_store.go b/module/mock/random_beacon_key_store.go index e1719fd4019..b247339bf96 100644 --- a/module/mock/random_beacon_key_store.go +++ b/module/mock/random_beacon_key_store.go @@ -3,7 +3,7 @@ package mock import ( - crypto "github.com/onflow/flow-go/crypto" + crypto "github.com/onflow/crypto" mock "github.com/stretchr/testify/mock" ) diff --git a/module/mocks/network.go b/module/mocks/network.go index 3788efaf45a..3bab7657423 100644 --- a/module/mocks/network.go +++ b/module/mocks/network.go @@ -8,8 +8,8 @@ import ( reflect "reflect" gomock "github.com/golang/mock/gomock" - crypto "github.com/onflow/flow-go/crypto" - hash "github.com/onflow/flow-go/crypto/hash" + crypto "github.com/onflow/crypto" + hash "github.com/onflow/crypto/hash" flow "github.com/onflow/flow-go/model/flow" ) diff --git a/module/signature/aggregation.go b/module/signature/aggregation.go index 76101ee3805..d56651d2221 100644 --- a/module/signature/aggregation.go +++ b/module/signature/aggregation.go @@ -4,8 +4,8 @@ import ( "fmt" "sync" - "github.com/onflow/flow-go/crypto" - "github.com/onflow/flow-go/crypto/hash" + "github.com/onflow/crypto" + "github.com/onflow/crypto/hash" ) // SignatureAggregatorSameMessage aggregates BLS signatures of the same message from different signers. diff --git a/module/signature/aggregation_test.go b/module/signature/aggregation_test.go index 87a31561753..a891416ba6e 100644 --- a/module/signature/aggregation_test.go +++ b/module/signature/aggregation_test.go @@ -7,10 +7,9 @@ import ( "testing" "time" + "github.com/onflow/crypto" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - - "github.com/onflow/flow-go/crypto" ) func getPRG(t *testing.T) *mrand.Rand { diff --git a/module/signature/signer_indices_test.go b/module/signature/signer_indices_test.go index 0bd7aaee34e..4f1595d06c5 100644 --- a/module/signature/signer_indices_test.go +++ b/module/signature/signer_indices_test.go @@ -12,7 +12,6 @@ import ( "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/model/flow/filter" "github.com/onflow/flow-go/model/flow/filter/id" - "github.com/onflow/flow-go/model/flow/order" "github.com/onflow/flow-go/module/signature" "github.com/onflow/flow-go/utils/unittest" ) @@ -110,7 +109,7 @@ func Test_EncodeSignerToIndicesAndSigType(t *testing.T) { numRandomBeaconSigners := rapid.IntRange(0, committeeSize-numStakingSigners).Draw(t, "numRandomBeaconSigners").(int) // create committee - committeeIdentities := unittest.IdentityListFixture(committeeSize, unittest.WithRole(flow.RoleConsensus)).Sort(order.Canonical) + committeeIdentities := unittest.IdentityListFixture(committeeSize, unittest.WithRole(flow.RoleConsensus)).Sort(flow.Canonical) committee := committeeIdentities.NodeIDs() stakingSigners, beaconSigners := sampleSigners(t, committee, numStakingSigners, numRandomBeaconSigners) @@ -148,7 +147,7 @@ func Test_DecodeSigTypeToStakingAndBeaconSigners(t *testing.T) { numRandomBeaconSigners := rapid.IntRange(0, committeeSize-numStakingSigners).Draw(t, "numRandomBeaconSigners").(int) // create committee - committeeIdentities := unittest.IdentityListFixture(committeeSize, unittest.WithRole(flow.RoleConsensus)).Sort(order.Canonical) + committeeIdentities := unittest.IdentityListFixture(committeeSize, unittest.WithRole(flow.RoleConsensus)).Sort(flow.Canonical) committee := committeeIdentities.NodeIDs() stakingSigners, beaconSigners := sampleSigners(t, committee, numStakingSigners, numRandomBeaconSigners) @@ -274,7 +273,7 @@ func Test_EncodeSignersToIndices(t *testing.T) { numSigners := rapid.IntRange(0, committeeSize).Draw(t, "numSigners").(int) // create committee - identities := unittest.IdentityListFixture(committeeSize, unittest.WithRole(flow.RoleConsensus)).Sort(order.Canonical) + identities := unittest.IdentityListFixture(committeeSize, unittest.WithRole(flow.RoleConsensus)).Sort(flow.Canonical) committee := identities.NodeIDs() signers, err := committee.Sample(uint(numSigners)) require.NoError(t, err) @@ -304,7 +303,7 @@ func Test_DecodeSignerIndicesToIdentifiers(t *testing.T) { numSigners := rapid.IntRange(0, committeeSize).Draw(t, "numSigners").(int) // create committee - identities := unittest.IdentityListFixture(committeeSize, unittest.WithRole(flow.RoleConsensus)).Sort(order.Canonical) + identities := unittest.IdentityListFixture(committeeSize, unittest.WithRole(flow.RoleConsensus)).Sort(flow.Canonical) committee := identities.NodeIDs() signers, err := committee.Sample(uint(numSigners)) require.NoError(t, err) @@ -341,7 +340,7 @@ func Test_DecodeSignerIndicesToIdentities(t *testing.T) { numSigners := rapid.IntRange(0, committeeSize).Draw(t, "numSigners").(int) // create committee - identities := unittest.IdentityListFixture(committeeSize, unittest.WithRole(flow.RoleConsensus)).Sort(order.Canonical) + identities := unittest.IdentityListFixture(committeeSize, unittest.WithRole(flow.RoleConsensus)).Sort(flow.Canonical) signers, err := identities.Sample(uint(numSigners)) require.NoError(t, err) @@ -352,7 +351,7 @@ func Test_DecodeSignerIndicesToIdentities(t *testing.T) { // decode and verify decodedSigners, err := signature.DecodeSignerIndicesToIdentities(identities, signerIndices) require.NoError(t, err) - require.Equal(t, signers.Sort(order.Canonical), decodedSigners.Sort(order.Canonical)) + require.Equal(t, signers.Sort(flow.Canonical), decodedSigners.Sort(flow.Canonical)) }) } diff --git a/module/signature/signing_tags.go b/module/signature/signing_tags.go index 0e60ef1cfc1..f2d142b4253 100644 --- a/module/signature/signing_tags.go +++ b/module/signature/signing_tags.go @@ -1,8 +1,8 @@ package signature import ( - "github.com/onflow/flow-go/crypto" - "github.com/onflow/flow-go/crypto/hash" + "github.com/onflow/crypto" + "github.com/onflow/crypto/hash" ) // List of domain separation tags for protocol signatures. diff --git a/module/signature/type_encoder.go b/module/signature/type_encoder.go index 11241c0fa57..fe253146b49 100644 --- a/module/signature/type_encoder.go +++ b/module/signature/type_encoder.go @@ -3,7 +3,8 @@ package signature import ( "fmt" - "github.com/onflow/flow-go/crypto" + "github.com/onflow/crypto" + "github.com/onflow/flow-go/model/encoding" ) diff --git a/module/signer.go b/module/signer.go index b02514c06ee..8438a00aa01 100644 --- a/module/signer.go +++ b/module/signer.go @@ -3,7 +3,7 @@ package module import ( "errors" - "github.com/onflow/flow-go/crypto" + "github.com/onflow/crypto" ) var ( diff --git a/module/state_synchronization/indexer/indexer_core.go b/module/state_synchronization/indexer/indexer_core.go index 00a67ff8ca8..fdeb76b62d7 100644 --- a/module/state_synchronization/indexer/indexer_core.go +++ b/module/state_synchronization/indexer/indexer_core.go @@ -8,6 +8,7 @@ import ( "github.com/rs/zerolog" "golang.org/x/sync/errgroup" + "github.com/onflow/flow-go/engine/common/requester" "github.com/onflow/flow-go/ledger" "github.com/onflow/flow-go/ledger/common/convert" "github.com/onflow/flow-go/model/flow" @@ -28,6 +29,8 @@ type IndexerCore struct { events storage.Events results storage.LightTransactionResults batcher bstorage.BatchBuilder + + collectionHandler requester.HandleFunc } // New execution state indexer used to ingest block execution data and index it by height. @@ -41,6 +44,7 @@ func New( headers storage.Headers, events storage.Events, results storage.LightTransactionResults, + collectionHandler requester.HandleFunc, ) (*IndexerCore, error) { log = log.With().Str("component", "execution_indexer").Logger() metrics.InitializeLatestHeight(registers.LatestHeight()) @@ -58,6 +62,8 @@ func New( headers: headers, events: events, results: results, + + collectionHandler: collectionHandler, }, nil } @@ -68,13 +74,14 @@ func New( // - storage.ErrHeightNotIndexed if the given height was not indexed yet or lower than the first indexed height. func (c *IndexerCore) RegisterValue(ID flow.RegisterID, height uint64) (flow.RegisterValue, error) { value, err := c.registers.Get(ID, height) - // only return an error if the error doesn't match the not found error, since we have - // to gracefully handle not found values and instead assign nil, that is because the script executor - // expects that behaviour - if errors.Is(err, storage.ErrNotFound) { - return nil, nil - } if err != nil { + // only return an error if the error doesn't match the not found error, since we have + // to gracefully handle not found values and instead assign nil, that is because the script executor + // expects that behaviour + if errors.Is(err, storage.ErrNotFound) { + return nil, nil + } + return nil, err } @@ -103,6 +110,7 @@ func (c *IndexerCore) IndexBlockData(data *execution_data.BlockExecutionDataEnti if block.Height != latest+1 && block.Height != latest { return fmt.Errorf("must index block data with the next height %d, but got %d", latest+1, block.Height) } + // allow rerunning the indexer for same height since we are fetching height from register storage, but there are other storages // for indexing resources which might fail to update the values, so this enables rerunning and reindexing those resources if block.Height == latest { @@ -111,14 +119,8 @@ func (c *IndexerCore) IndexBlockData(data *execution_data.BlockExecutionDataEnti } start := time.Now() - - // concurrently process indexing of block data g := errgroup.Group{} - // TODO: collections are currently indexed using the ingestion engine. In many cases, they are - // downloaded and indexed before the block is sealed. However, when a node is catching up, it - // may download the execution data first. In that case, we should index the collections here. - var eventCount, resultCount, registerCount int g.Go(func() error { start := time.Now() @@ -159,6 +161,31 @@ func (c *IndexerCore) IndexBlockData(data *execution_data.BlockExecutionDataEnti return nil }) + g.Go(func() error { + start := time.Now() + + // index all collections except the system chunk + // Note: the access ingestion engine also indexes collections, starting when the block is + // finalized. This process can fall behind due to the node being offline, resource issues + // or network congestion. This indexer ensures that collections are never farther behind + // than the latest indexed block. Calling the collection handler with a collection that + // has already been indexed is a noop. + indexedCount := 0 + if len(data.ChunkExecutionDatas) > 0 { + for _, chunk := range data.ChunkExecutionDatas[0 : len(data.ChunkExecutionDatas)-1] { + c.collectionHandler(flow.ZeroID, chunk.Collection) + indexedCount++ + } + } + + lg.Debug(). + Int("collection_count", indexedCount). + Dur("duration_ms", time.Since(start)). + Msg("indexed collections") + + return nil + }) + g.Go(func() error { start := time.Now() @@ -203,7 +230,7 @@ func (c *IndexerCore) IndexBlockData(data *execution_data.BlockExecutionDataEnti return fmt.Errorf("failed to index block data at height %d: %w", block.Height, err) } - c.metrics.BlockIndexed(block.Height, time.Since(start), registerCount, eventCount, resultCount) + c.metrics.BlockIndexed(block.Height, time.Since(start), eventCount, registerCount, resultCount) lg.Debug(). Dur("duration_ms", time.Since(start)). Msg("indexed block data") diff --git a/module/state_synchronization/indexer/indexer_core_test.go b/module/state_synchronization/indexer/indexer_core_test.go index 7adb771db3e..73c0174105f 100644 --- a/module/state_synchronization/indexer/indexer_core_test.go +++ b/module/state_synchronization/indexer/indexer_core_test.go @@ -14,6 +14,7 @@ import ( mocks "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + "github.com/onflow/flow-go/engine/common/requester" "github.com/onflow/flow-go/ledger" "github.com/onflow/flow-go/ledger/common/convert" "github.com/onflow/flow-go/ledger/common/pathfinder" @@ -28,21 +29,24 @@ import ( "github.com/onflow/flow-go/utils/unittest" ) +var noopHandlerFunc requester.HandleFunc = func(originID flow.Identifier, entity flow.Entity) {} + type indexCoreTest struct { - t *testing.T - indexer *IndexerCore - registers *storagemock.RegisterIndex - events *storagemock.Events - results *storagemock.LightTransactionResults - headers *storagemock.Headers - ctx context.Context - blocks []*flow.Block - data *execution_data.BlockExecutionDataEntity - lastHeightStore func(t *testing.T) uint64 - firstHeightStore func(t *testing.T) uint64 - registersStore func(t *testing.T, entries flow.RegisterEntries, height uint64) error - eventsStore func(t *testing.T, ID flow.Identifier, events []flow.EventsList) error - registersGet func(t *testing.T, IDs flow.RegisterID, height uint64) (flow.RegisterValue, error) + t *testing.T + indexer *IndexerCore + registers *storagemock.RegisterIndex + events *storagemock.Events + results *storagemock.LightTransactionResults + headers *storagemock.Headers + ctx context.Context + blocks []*flow.Block + collectionHandler requester.HandleFunc + data *execution_data.BlockExecutionDataEntity + lastHeightStore func(t *testing.T) uint64 + firstHeightStore func(t *testing.T) uint64 + registersStore func(t *testing.T, entries flow.RegisterEntries, height uint64) error + eventsStore func(t *testing.T, ID flow.Identifier, events []flow.EventsList) error + registersGet func(t *testing.T, IDs flow.RegisterID, height uint64) (flow.RegisterValue, error) } func newIndexCoreTest( @@ -59,6 +63,13 @@ func newIndexCoreTest( ctx: context.Background(), data: exeData, headers: newBlockHeadersStorage(blocks).(*storagemock.Headers), // convert it back to mock type for tests + collectionHandler: func(originID flow.Identifier, entity flow.Entity) { + // collectionHandler is always called. by default, assert the value passed was empty + // to enforce the test writer handles the collection when it's tested. + // this will never happen in production. + assert.Equal(t, flow.ZeroID, originID) + assert.Nil(t, entity) + }, } } @@ -85,6 +96,7 @@ func (i *indexCoreTest) setLastHeight(f func(t *testing.T) uint64) *indexCoreTes }) return i } + func (i *indexCoreTest) useDefaultHeights() *indexCoreTest { i.registers. On("FirstHeight"). @@ -137,6 +149,11 @@ func (i *indexCoreTest) setGetRegisters(f func(t *testing.T, ID flow.RegisterID, return i } +func (i *indexCoreTest) setOnCollection(fn requester.HandleFunc) *indexCoreTest { + i.collectionHandler = fn + return i +} + func (i *indexCoreTest) useDefaultEvents() *indexCoreTest { i.events. On("BatchStore", mock.AnythingOfType("flow.Identifier"), mock.AnythingOfType("[]flow.EventsList"), mock.Anything). @@ -160,7 +177,11 @@ func (i *indexCoreTest) initIndexer() *indexCoreTest { i.useDefaultHeights() - indexer, err := New(zerolog.New(os.Stdout), metrics.NewNoopCollector(), db, i.registers, i.headers, i.events, i.results) + onCollection := func(originID flow.Identifier, entity flow.Entity) { + i.collectionHandler(originID, entity) + } + + indexer, err := New(zerolog.New(os.Stdout), metrics.NewNoopCollector(), db, i.registers, i.headers, i.events, i.results, onCollection) require.NoError(i.t, err) i.indexer = indexer return i @@ -340,9 +361,55 @@ func TestExecutionState_IndexBlockData(t *testing.T) { assert.NoError(t, err) }) + t.Run("Index Collections", func(t *testing.T) { + expectedCollections := unittest.CollectionListFixture(2) + ed := &execution_data.BlockExecutionData{ + BlockID: block.ID(), + ChunkExecutionDatas: []*execution_data.ChunkExecutionData{ + {Collection: expectedCollections[0]}, + {Collection: expectedCollections[1]}, + }, + } + execData := execution_data.NewBlockExecutionDataEntity(block.ID(), ed) + collectionsHandled := 0 + err := newIndexCoreTest(t, blocks, execData). + initIndexer(). + // make sure an empty set of events were stored + setStoreEvents(func(t *testing.T, actualBlockID flow.Identifier, actualEvents []flow.EventsList) error { + assert.Equal(t, block.ID(), actualBlockID) + require.Len(t, actualEvents, 1) + require.Len(t, actualEvents[0], 0) + return nil + }). + // make sure an empty set of transaction results were stored + setStoreTransactionResults(func(t *testing.T, actualBlockID flow.Identifier, actualResults []flow.LightTransactionResult) error { + assert.Equal(t, block.ID(), actualBlockID) + require.Len(t, actualResults, 0) + return nil + }). + setOnCollection(func(_ flow.Identifier, entity flow.Entity) { + require.Less(t, collectionsHandled, len(expectedCollections), "more collections handled than expected") + + actual, ok := entity.(*flow.Collection) + require.True(t, ok) + assert.Equal(t, expectedCollections[collectionsHandled], actual) + collectionsHandled++ + }). + // make sure an empty set of register entries was stored + setStoreRegisters(func(t *testing.T, entries flow.RegisterEntries, height uint64) error { + assert.Equal(t, height, block.Header.Height) + assert.Equal(t, 0, entries.Len()) + return nil + }). + runIndexBlockData() + + assert.NoError(t, err) + }) + t.Run("Index AllTheThings", func(t *testing.T) { expectedEvents := unittest.EventsFixture(20) expectedResults := unittest.LightTransactionResultsFixture(20) + expectedCollections := unittest.CollectionListFixture(2) expectedTries := []*ledger.TrieUpdate{trieUpdateFixture(t), trieUpdateFixture(t)} expectedPayloads := make([]*ledger.Payload, 0) for _, trie := range expectedTries { @@ -353,11 +420,13 @@ func TestExecutionState_IndexBlockData(t *testing.T) { BlockID: block.ID(), ChunkExecutionDatas: []*execution_data.ChunkExecutionData{ { + Collection: expectedCollections[0], Events: expectedEvents[:10], TransactionResults: expectedResults[:10], TrieUpdate: expectedTries[0], }, { + Collection: expectedCollections[1], TransactionResults: expectedResults[10:], Events: expectedEvents[10:], TrieUpdate: expectedTries[1], @@ -365,7 +434,7 @@ func TestExecutionState_IndexBlockData(t *testing.T) { }, } execData := execution_data.NewBlockExecutionDataEntity(block.ID(), ed) - + collectionsHandled := 0 err := newIndexCoreTest(t, blocks, execData). initIndexer(). // make sure all events are stored at once in order @@ -387,6 +456,14 @@ func TestExecutionState_IndexBlockData(t *testing.T) { } return nil }). + setOnCollection(func(_ flow.Identifier, entity flow.Entity) { + require.Less(t, collectionsHandled, len(expectedCollections), "more collections handled than expected") + + actual, ok := entity.(*flow.Collection) + require.True(t, ok) + assert.Equal(t, expectedCollections[collectionsHandled], actual) + collectionsHandled++ + }). // make sure update registers match in length and are same as block data ledger payloads setStoreRegisters(func(t *testing.T, entries flow.RegisterEntries, actualHeight uint64) error { assert.Equal(t, actualHeight, block.Header.Height) @@ -541,9 +618,10 @@ func trieRegistersPayloadComparer(t *testing.T, triePayloads []*ledger.Payload, } func TestIndexerIntegration_StoreAndGet(t *testing.T) { - regOwner := "f8d6e0586b0a20c7" + regOwnerAddress := unittest.RandomAddressFixture() + regOwner := string(regOwnerAddress.Bytes()) regKey := "code" - registerID := flow.NewRegisterID(regOwner, regKey) + registerID := flow.NewRegisterID(regOwnerAddress, regKey) db, dbDir := unittest.TempBadgerDB(t) t.Cleanup(func() { @@ -556,18 +634,18 @@ func TestIndexerIntegration_StoreAndGet(t *testing.T) { // this test makes sure index values for a single register are correctly updated and always last value is returned t.Run("Single Index Value Changes", func(t *testing.T) { pebbleStorage.RunWithRegistersStorageAtInitialHeights(t, 0, 0, func(registers *pebbleStorage.Registers) { - index, err := New(logger, metrics, db, registers, nil, nil, nil) + index, err := New(logger, metrics, db, registers, nil, nil, nil, noopHandlerFunc) require.NoError(t, err) values := [][]byte{[]byte("1"), []byte("1"), []byte("2"), []byte("3"), []byte("4")} for i, val := range values { - testDesc := fmt.Sprintf("test itteration number %d failed with test value %s", i, val) + testDesc := fmt.Sprintf("test iteration number %d failed with test value %s", i, val) height := uint64(i + 1) err := storeRegisterWithValue(index, height, regOwner, regKey, val) - assert.NoError(t, err) + require.NoError(t, err) results, err := index.RegisterValue(registerID, height) - require.Nil(t, err, testDesc) + require.NoError(t, err, testDesc) assert.Equal(t, val, results) } }) @@ -577,7 +655,7 @@ func TestIndexerIntegration_StoreAndGet(t *testing.T) { // up to the specification script executor requires t.Run("Missing Register", func(t *testing.T) { pebbleStorage.RunWithRegistersStorageAtInitialHeights(t, 0, 0, func(registers *pebbleStorage.Registers) { - index, err := New(logger, metrics, db, registers, nil, nil, nil) + index, err := New(logger, metrics, db, registers, nil, nil, nil, noopHandlerFunc) require.NoError(t, err) value, err := index.RegisterValue(registerID, 0) @@ -591,7 +669,7 @@ func TestIndexerIntegration_StoreAndGet(t *testing.T) { // e.g. we index A{h(1) -> X}, A{h(2) -> Y}, when we request h(4) we get value Y t.Run("Single Index Value At Later Heights", func(t *testing.T) { pebbleStorage.RunWithRegistersStorageAtInitialHeights(t, 0, 0, func(registers *pebbleStorage.Registers) { - index, err := New(logger, metrics, db, registers, nil, nil, nil) + index, err := New(logger, metrics, db, registers, nil, nil, nil, noopHandlerFunc) require.NoError(t, err) storeValues := [][]byte{[]byte("1"), []byte("2")} @@ -622,7 +700,7 @@ func TestIndexerIntegration_StoreAndGet(t *testing.T) { // this test makes sure we correctly handle weird payloads t.Run("Empty and Nil Payloads", func(t *testing.T) { pebbleStorage.RunWithRegistersStorageAtInitialHeights(t, 0, 0, func(registers *pebbleStorage.Registers) { - index, err := New(logger, metrics, db, registers, nil, nil, nil) + index, err := New(logger, metrics, db, registers, nil, nil, nil, noopHandlerFunc) require.NoError(t, err) require.NoError(t, index.indexRegisters(map[ledger.Path]*ledger.Payload{}, 1)) diff --git a/module/util/log.go b/module/util/log.go index 13060d9f017..fa50f6ac479 100644 --- a/module/util/log.go +++ b/module/util/log.go @@ -1,25 +1,149 @@ package util import ( + "sync" + "sync/atomic" + "time" + "github.com/rs/zerolog" ) -// LogProgress takes a total and return function such that when called with a 0-based index -// it prints the progress from 0% to 100% to indicate the index from 0 to (total - 1) has been -// processed. -// useful to report the progress of processing the index from 0 to (total - 1) -func LogProgress(msg string, total int, logger zerolog.Logger) func(currentIndex int) { - logThreshold := float64(0) - return func(currentIndex int) { +// LogProgressFunc is a function that can be called to add to the progress +type LogProgressFunc func(addProgress int) + +type LogProgressConfig struct { + Message string + Total int + Sampler zerolog.Sampler +} + +func DefaultLogProgressConfig( + message string, + total int, +) LogProgressConfig { + nth := uint32(total / 10) // sample every 10% by default + if nth == 0 { + nth = 1 + } + + sampler := newProgressLogsSampler(nth, 60*time.Second) + return NewLogProgressConfig( + message, + total, + sampler, + ) +} + +func NewLogProgressConfig( + message string, + total int, + sampler zerolog.Sampler) LogProgressConfig { + return LogProgressConfig{ + Message: message, + Total: total, + Sampler: sampler, + } + +} + +type LogProgressOption func(config *LogProgressConfig) + +// LogProgress takes a total and return function such that when called adds the given +// number to the progress and logs the progress every 10% or every 60 seconds whichever +// comes first. +// The returned function can be called concurrently. +// An eta is also logged, but it assumes that the progress is linear. +func LogProgress( + log zerolog.Logger, + config LogProgressConfig, +) LogProgressFunc { + sampler := log.Sample(config.Sampler) + + start := time.Now() + currentIndex := uint64(0) + return func(add int) { + current := atomic.AddUint64(¤tIndex, uint64(add)) + percentage := float64(100) - if total > 0 { - percentage = (float64(currentIndex+1) / float64(total)) * 100. // currentIndex+1 assuming zero based indexing + if config.Total > 0 { + percentage = (float64(current) / float64(config.Total)) * 100. } + elapsed := time.Since(start) + elapsedString := elapsed.Round(1 * time.Second).String() - // report every 10 percent - if percentage >= logThreshold { - logger.Info().Msgf("%s progress: %v percent", msg, logThreshold) - logThreshold += 10 + etaString := "unknown" + if percentage > 0 { + eta := time.Duration(float64(elapsed) / percentage * (100 - percentage)) + if eta < 0 { + eta = 0 + } + etaString = eta.Round(1 * time.Second).String() + + } + + if current != uint64(config.Total) { + sampler.Info().Msgf("%s progress %d/%d (%.1f%%) elapsed: %s, eta %s", config.Message, current, config.Total, percentage, elapsedString, etaString) + } else { + log.Info().Msgf("%s progress %d/%d (%.1f%%) total time %s", config.Message, current, config.Total, percentage, elapsedString) } } } + +type TimedSampler struct { + start time.Time + Duration time.Duration + mu sync.Mutex +} + +var _ zerolog.Sampler = (*TimedSampler)(nil) + +func NewTimedSampler(duration time.Duration) *TimedSampler { + return &TimedSampler{ + start: time.Now(), + Duration: duration, + mu: sync.Mutex{}, + } +} + +func (s *TimedSampler) Sample(_ zerolog.Level) bool { + s.mu.Lock() + defer s.mu.Unlock() + + if time.Since(s.start) > s.Duration { + s.start = time.Now() + return true + } + return false +} + +func (s *TimedSampler) Reset() { + s.mu.Lock() + defer s.mu.Unlock() + + s.start = time.Now() +} + +type progressLogsSampler struct { + basicSampler *zerolog.BasicSampler + timedSampler *TimedSampler +} + +var _ zerolog.Sampler = (*progressLogsSampler)(nil) + +// newProgressLogsSampler returns a sampler that samples every nth log +// and also samples a log if the last log was more than duration ago +func newProgressLogsSampler(nth uint32, duration time.Duration) zerolog.Sampler { + return &progressLogsSampler{ + basicSampler: &zerolog.BasicSampler{N: nth}, + timedSampler: NewTimedSampler(duration), + } +} + +func (s *progressLogsSampler) Sample(lvl zerolog.Level) bool { + sample := s.basicSampler.Sample(lvl) + if sample { + s.timedSampler.Reset() + return true + } + return s.timedSampler.Sample(lvl) +} diff --git a/module/util/log_test.go b/module/util/log_test.go index 76f74b2a016..f35864dbdb8 100644 --- a/module/util/log_test.go +++ b/module/util/log_test.go @@ -2,7 +2,11 @@ package util import ( "bytes" + "fmt" + "strings" + "sync" "testing" + "time" "github.com/rs/zerolog" "github.com/stretchr/testify/require" @@ -12,48 +16,141 @@ func TestLogProgress40(t *testing.T) { buf := bytes.NewBufferString("") lg := zerolog.New(buf) total := 40 - logger := LogProgress("test", total, lg) + logger := LogProgress( + lg, + DefaultLogProgressConfig( + "test", + total, + ), + ) for i := 0; i < total; i++ { - logger(i) - } - - expectedLogs := - `{"level":"info","message":"test progress: 0 percent"} -{"level":"info","message":"test progress: 10 percent"} -{"level":"info","message":"test progress: 20 percent"} -{"level":"info","message":"test progress: 30 percent"} -{"level":"info","message":"test progress: 40 percent"} -{"level":"info","message":"test progress: 50 percent"} -{"level":"info","message":"test progress: 60 percent"} -{"level":"info","message":"test progress: 70 percent"} -{"level":"info","message":"test progress: 80 percent"} -{"level":"info","message":"test progress: 90 percent"} -{"level":"info","message":"test progress: 100 percent"} -` - require.Equal(t, expectedLogs, buf.String()) + logger(1) + } + + expectedLogs := []string{ + `test progress 1/40 (2.5%)`, + `test progress 5/40 (12.5%)`, + `test progress 9/40 (22.5%)`, + `test progress 13/40 (32.5%)`, + `test progress 17/40 (42.5%)`, + `test progress 21/40 (52.5%)`, + `test progress 25/40 (62.5%)`, + `test progress 29/40 (72.5%)`, + `test progress 33/40 (82.5%)`, + `test progress 37/40 (92.5%)`, + `test progress 40/40 (100.0%)`, + } + + for _, log := range expectedLogs { + require.Contains(t, buf.String(), log, total) + } } func TestLogProgress1000(t *testing.T) { for total := 11; total < 1000; total++ { buf := bytes.NewBufferString("") lg := zerolog.New(buf) - logger := LogProgress("test", total, lg) + logger := LogProgress( + lg, + DefaultLogProgressConfig( + "test", + total, + ), + ) + for i := 0; i < total; i++ { - logger(i) + logger(1) + } + + expectedLogs := []string{ + fmt.Sprintf(`test progress 1/%d`, total), + fmt.Sprintf(`test progress %d/%d (100.0%%)`, total, total), + } + + for _, log := range expectedLogs { + require.Contains(t, buf.String(), log, total) + } + } +} + +func TestLogProgressMultipleGoroutines(t *testing.T) { + total := 1000 + + buf := bytes.NewBufferString("") + lg := zerolog.New(buf) + logger := LogProgress( + lg, + DefaultLogProgressConfig( + "test", + total, + ), + ) + + wg := sync.WaitGroup{} + for i := 0; i < 10; i++ { + wg.Add(1) + go func() { + defer wg.Done() + for j := 0; j < 100; j++ { + logger(1) + } + }() + } + + wg.Wait() + + expectedLogs := []string{ + fmt.Sprintf(`test progress 1/%d`, total), + fmt.Sprintf(`test progress %d/%d (100.0%%)`, total, total), + } + + lines := strings.Count(buf.String(), "\n") + // every 10% + 1 for the final log + require.Equal(t, 11, lines) + + for _, log := range expectedLogs { + require.Contains(t, buf.String(), log, total) + } +} + +func TestLogProgressCustomSampler(t *testing.T) { + total := 1000 + + nth := uint32(total / 10) // sample every 10% by default + if nth == 0 { + nth = 1 + } + sampler := newProgressLogsSampler(nth, 10*time.Millisecond) + + buf := bytes.NewBufferString("") + lg := zerolog.New(buf) + logger := LogProgress( + lg, + NewLogProgressConfig( + "test", + total, + sampler, + ), + ) + + for i := 0; i < total; i++ { + if i == 7 || i == 77 || i == 777 { + // s + time.Sleep(20 * time.Millisecond) } + logger(1) + } + + expectedLogs := []string{ + fmt.Sprintf(`test progress 1/%d`, total), + fmt.Sprintf(`test progress %d/%d (100.0%%)`, total, total), + } + + lines := strings.Count(buf.String(), "\n") + // every 10% + 1 for the final log + 3 for the custom sampler + require.Equal(t, 14, lines) - expectedLogs := `{"level":"info","message":"test progress: 0 percent"} -{"level":"info","message":"test progress: 10 percent"} -{"level":"info","message":"test progress: 20 percent"} -{"level":"info","message":"test progress: 30 percent"} -{"level":"info","message":"test progress: 40 percent"} -{"level":"info","message":"test progress: 50 percent"} -{"level":"info","message":"test progress: 60 percent"} -{"level":"info","message":"test progress: 70 percent"} -{"level":"info","message":"test progress: 80 percent"} -{"level":"info","message":"test progress: 90 percent"} -{"level":"info","message":"test progress: 100 percent"} -` - require.Equal(t, expectedLogs, buf.String(), total) + for _, log := range expectedLogs { + require.Contains(t, buf.String(), log, total) } } diff --git a/module/validation/receipt_validator.go b/module/validation/receipt_validator.go index dae906a982a..95a0cb7bb03 100644 --- a/module/validation/receipt_validator.go +++ b/module/validation/receipt_validator.go @@ -4,9 +4,11 @@ import ( "errors" "fmt" - "github.com/onflow/flow-go/crypto/hash" + "github.com/onflow/crypto/hash" + "github.com/onflow/flow-go/engine" "github.com/onflow/flow-go/model/flow" + "github.com/onflow/flow-go/module" "github.com/onflow/flow-go/module/signature" "github.com/onflow/flow-go/state" "github.com/onflow/flow-go/state/fork" @@ -15,7 +17,7 @@ import ( ) // receiptValidator holds all needed context for checking -// receipt validity against current protocol state. +// receipt validity against the current protocol state. type receiptValidator struct { headers storage.Headers seals storage.Seals @@ -25,12 +27,14 @@ type receiptValidator struct { signatureHasher hash.Hasher } +var _ module.ReceiptValidator = (*receiptValidator)(nil) + func NewReceiptValidator(state protocol.State, headers storage.Headers, index storage.Index, results storage.ExecutionResults, seals storage.Seals, -) *receiptValidator { +) module.ReceiptValidator { rv := &receiptValidator{ state: state, headers: headers, @@ -39,7 +43,6 @@ func NewReceiptValidator(state protocol.State, signatureHasher: signature.NewBLSHasher(signature.ExecutionReceiptTag), seals: seals, } - return rv } @@ -272,12 +275,22 @@ func (v *receiptValidator) ValidatePayload(candidate *flow.Block) error { return fmt.Errorf("internal error while traversing the ancestor fork of unsealed blocks: %w", err) } - // first validate all results that were included into payload - // if one of results is invalid we fail the whole check because it could be violating - // parent-children relationship + // tracks the number of receipts committing to each result. + // it's ok to only index receipts at this point, because we will perform + // all needed checks after we have validated all results. + receiptsByResult := payload.Receipts.GroupByResultID() + + // validate all results that are incorporated into the payload. If one is malformed, the entire block is invalid. for i, result := range payload.Results { resultID := result.ID() + // Every included result must be accompanied by a receipt with a corresponding `ResultID`, in the same block. + // If a result is included without a corresponding receipt, it cannot be attributed to any executor. + receiptsForResult := uint(len(receiptsByResult.GetGroup(resultID))) + if receiptsForResult == 0 { + return engine.NewInvalidInputErrorf("no receipts for result %v at index %d", resultID, i) + } + // check for duplicated results if _, isDuplicate := executionTree[resultID]; isDuplicate { return engine.NewInvalidInputErrorf("duplicate result %v at index %d", resultID, i) diff --git a/module/validation/receipt_validator_test.go b/module/validation/receipt_validator_test.go index ec6a31d9475..81a4bed321a 100644 --- a/module/validation/receipt_validator_test.go +++ b/module/validation/receipt_validator_test.go @@ -29,7 +29,13 @@ func (s *ReceiptValidationSuite) SetupTest() { s.SetupChain() s.publicKey = &fmock.PublicKey{} s.Identities[s.ExeID].StakingPubKey = s.publicKey - s.receiptValidator = NewReceiptValidator(s.State, s.HeadersDB, s.IndexDB, s.ResultsDB, s.SealsDB) + s.receiptValidator = NewReceiptValidator( + s.State, + s.HeadersDB, + s.IndexDB, + s.ResultsDB, + s.SealsDB, + ) } // TestReceiptValid try submitting valid receipt @@ -745,3 +751,102 @@ func (s *ReceiptValidationSuite) TestValidateReceiptAfterBootstrap() { err := s.receiptValidator.ValidatePayload(candidate) s.Require().NoError(err) } + +// TestValidateReceiptResultWithoutReceipt tests a case when a malicious leader incorporates a made-up execution result +// into their proposal. ReceiptValidator must ensure that for each result included in the block, there must be +// at least one receipt included in that block as well. +func (s *ReceiptValidationSuite) TestValidateReceiptResultWithoutReceipt() { + // assuming signatures are all good + s.publicKey.On("Verify", mock.Anything, mock.Anything, mock.Anything).Return(true, nil) + + // G <- A <- B + blocks, result0, seal := unittest.ChainFixture(2) + s.SealsIndex[blocks[0].ID()] = seal + + receipts := unittest.ReceiptChainFor(blocks, result0) + blockA, blockB := blocks[1], blocks[2] + receiptA, receiptB := receipts[1], receipts[2] + + blockA.Payload.Receipts = []*flow.ExecutionReceiptMeta{} + blockB.Payload.Receipts = []*flow.ExecutionReceiptMeta{receiptA.Meta()} + blockB.Payload.Results = []*flow.ExecutionResult{&receiptA.ExecutionResult} + // update block header so that blocks are chained together + unittest.ReconnectBlocksAndReceipts(blocks, receipts) + // assuming all receipts are executed by the correct executor + for _, r := range receipts { + r.ExecutorID = s.ExeID + } + + for _, b := range blocks { + s.Extend(b) + } + s.PersistedResults[result0.ID()] = result0 + + candidate := unittest.BlockWithParentFixture(blockB.Header) + candidate.Payload = &flow.Payload{ + Receipts: []*flow.ExecutionReceiptMeta{}, + Results: []*flow.ExecutionResult{&receiptB.ExecutionResult}, + } + + err := s.receiptValidator.ValidatePayload(candidate) + s.Require().Error(err) + s.Require().True(engine.IsInvalidInputError(err)) +} + +// TestValidateReceiptResultHasEnoughReceipts tests the happy path of a block proposal, where a leader +// includes multiple Execution Receipts that commit to the same result. In this case, the Flow protocol +// prescribes that +// - the Execution Result is only incorporated once +// - from each Receipt the `ExecutionReceiptMeta` is to be included. +// +// The validator is expected to accept such payload as valid. +func (s *ReceiptValidationSuite) TestValidateReceiptResultHasEnoughReceipts() { + k := uint(5) + // assuming signatures are all good + s.publicKey.On("Verify", mock.Anything, mock.Anything, mock.Anything).Return(true, nil) + + // G <- A <- B + blocks, result0, seal := unittest.ChainFixture(2) + s.SealsIndex[blocks[0].ID()] = seal + + receipts := unittest.ReceiptChainFor(blocks, result0) + blockA, blockB := blocks[1], blocks[2] + receiptA, receiptB := receipts[1], receipts[2] + + blockA.Payload.Receipts = []*flow.ExecutionReceiptMeta{} + blockB.Payload.Receipts = []*flow.ExecutionReceiptMeta{receiptA.Meta()} + blockB.Payload.Results = []*flow.ExecutionResult{&receiptA.ExecutionResult} + // update block header so that blocks are chained together + unittest.ReconnectBlocksAndReceipts(blocks, receipts) + // assuming all receipts are executed by the correct executor + for _, r := range receipts { + r.ExecutorID = s.ExeID + } + + for _, b := range blocks { + s.Extend(b) + } + s.PersistedResults[result0.ID()] = result0 + + candidateReceipts := []*flow.ExecutionReceiptMeta{receiptB.Meta()} + // add k-1 more receipts for the same execution result + for i := uint(1); i < k; i++ { + // use base receipt and change the executor ID, we don't care about signatures since we are not validating them + receipt := *receiptB.Meta() + // create a mock executor which submitted the receipt + executor := unittest.IdentityFixture(unittest.WithRole(flow.RoleExecution), unittest.WithStakingPubKey(s.publicKey)) + receipt.ExecutorID = executor.NodeID + // update local identity table so the receipt is considered valid + s.Identities[executor.NodeID] = executor + candidateReceipts = append(candidateReceipts, &receipt) + } + + candidate := unittest.BlockWithParentFixture(blockB.Header) + candidate.Payload = &flow.Payload{ + Receipts: candidateReceipts, + Results: []*flow.ExecutionResult{&receiptB.ExecutionResult}, + } + + err := s.receiptValidator.ValidatePayload(candidate) + s.Require().NoError(err) +} diff --git a/module/validation/seal_validator.go b/module/validation/seal_validator.go index 19649abf31f..eef47535e54 100644 --- a/module/validation/seal_validator.go +++ b/module/validation/seal_validator.go @@ -3,7 +3,8 @@ package validation import ( "fmt" - "github.com/onflow/flow-go/crypto/hash" + "github.com/onflow/crypto/hash" + "github.com/onflow/flow-go/engine" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/module" diff --git a/network/alsp/cache.go b/network/alsp/cache.go index eeab8fee302..be75043cfa0 100644 --- a/network/alsp/cache.go +++ b/network/alsp/cache.go @@ -12,7 +12,7 @@ type SpamRecordCache interface { // Returns the Penalty value of the record after the adjustment. // It returns an error if the adjustFunc returns an error or if the record does not exist. // Assuming that adjust is always called when the record exists, the error is irrecoverable and indicates a bug. - Adjust(originId flow.Identifier, adjustFunc model.RecordAdjustFunc) (float64, error) + AdjustWithInit(originId flow.Identifier, adjustFunc model.RecordAdjustFunc) (float64, error) // Identities returns the list of identities of the nodes that have a spam record in the cache. Identities() []flow.Identifier diff --git a/network/alsp/internal/cache.go b/network/alsp/internal/cache.go index 6bc6f361593..321bdb3baf7 100644 --- a/network/alsp/internal/cache.go +++ b/network/alsp/internal/cache.go @@ -14,8 +14,6 @@ import ( "github.com/onflow/flow-go/network/alsp/model" ) -var ErrSpamRecordNotFound = fmt.Errorf("spam record not found") - // SpamRecordCache is a cache that stores spam records at the protocol layer for ALSP. type SpamRecordCache struct { recordFactory model.SpamRecordFactoryFunc // recordFactory is a factory function that creates a new spam record. @@ -39,11 +37,7 @@ var _ alsp.SpamRecordCache = (*SpamRecordCache)(nil) func NewSpamRecordCache(sizeLimit uint32, logger zerolog.Logger, collector module.HeroCacheMetrics, recordFactory model.SpamRecordFactoryFunc) *SpamRecordCache { backData := herocache.NewCache(sizeLimit, herocache.DefaultOversizeFactor, - // this cache is supposed to keep the spam record for the authorized (staked) nodes. Since the number of such nodes is - // expected to be small, we do not eject any records from the cache. The cache size must be large enough to hold all - // the spam records of the authorized nodes. Also, this cache is keeping at most one record per origin id, so the - // size of the cache must be at least the number of authorized nodes. - heropool.NoEjection, + heropool.LRUEjection, logger.With().Str("mempool", "aslp-spam-records").Logger(), collector) @@ -53,73 +47,21 @@ func NewSpamRecordCache(sizeLimit uint32, logger zerolog.Logger, collector modul } } -// init initializes the spam record cache for the given origin id if it does not exist. -// Returns true if the record is initialized, false otherwise (i.e., the record already exists). -// Args: -// - originId: the origin id of the spam record. -// Returns: -// - true if the record is initialized, false otherwise (i.e., the record already exists). -// Note that if Init is called multiple times for the same origin id, the record is initialized only once, and the -// subsequent calls return false and do not change the record (i.e., the record is not re-initialized). -func (s *SpamRecordCache) init(originId flow.Identifier) bool { - return s.c.Add(ProtocolSpamRecordEntity{s.recordFactory(originId)}) -} - -// Adjust applies the given adjust function to the spam record of the given origin id. +// AdjustWithInit applies the given adjust function to the spam record of the given origin id. // Returns the Penalty value of the record after the adjustment. // It returns an error if the adjustFunc returns an error or if the record does not exist. -// Note that if the record the Adjust is called when the record does not exist, the record is initialized and the -// adjust function is applied to the initialized record again. In this case, the adjust function should not return an error. +// Note that if the record does not exist, the record is initialized and the +// adjust function is applied to the initialized record again. // Args: // - originId: the origin id of the spam record. // - adjustFunc: the function that adjusts the spam record. // Returns: // - Penalty value of the record after the adjustment. // - error any returned error should be considered as an irrecoverable error and indicates a bug. -func (s *SpamRecordCache) Adjust(originId flow.Identifier, adjustFunc model.RecordAdjustFunc) (float64, error) { - // first, we try to optimistically adjust the record assuming that the record already exists. - penalty, err := s.adjust(originId, adjustFunc) - - switch { - case err == ErrSpamRecordNotFound: - // if the record does not exist, we initialize the record and try to adjust it again. - // Note: there is an edge case where the record is initialized by another goroutine between the two calls. - // In this case, the init function is invoked twice, but it is not a problem because the underlying - // cache is thread-safe. Hence, we do not need to synchronize the two calls. In such cases, one of the - // two calls returns false, and the other call returns true. We do not care which call returns false, hence, - // we ignore the return value of the init function. - _ = s.init(originId) - // as the record is initialized, the adjust function should not return an error, and any returned error - // is an irrecoverable error and indicates a bug. - return s.adjust(originId, adjustFunc) - case err != nil: - // if the adjust function returns an unexpected error on the first attempt, we return the error directly. - return 0, err - default: - // if the adjust function returns no error, we return the penalty value. - return penalty, nil - } -} - -// adjust applies the given adjust function to the spam record of the given origin id. -// Returns the Penalty value of the record after the adjustment. -// It returns an error if the adjustFunc returns an error or if the record does not exist. -// Args: -// - originId: the origin id of the spam record. -// - adjustFunc: the function that adjusts the spam record. -// Returns: -// - Penalty value of the record after the adjustment. -// - error if the adjustFunc returns an error or if the record does not exist (ErrSpamRecordNotFound). Except the ErrSpamRecordNotFound, -// any other error should be treated as an irrecoverable error and indicates a bug. -func (s *SpamRecordCache) adjust(originId flow.Identifier, adjustFunc model.RecordAdjustFunc) (float64, error) { +func (s *SpamRecordCache) AdjustWithInit(originId flow.Identifier, adjustFunc model.RecordAdjustFunc) (float64, error) { var rErr error - adjustedEntity, adjusted := s.c.Adjust(originId, func(entity flow.Entity) flow.Entity { - record, ok := entity.(ProtocolSpamRecordEntity) - if !ok { - // sanity check - // This should never happen, because the cache only contains ProtocolSpamRecordEntity entities. - panic(fmt.Sprintf("invalid entity type, expected ProtocolSpamRecordEntity type, got: %T", entity)) - } + wrapAdjustFunc := func(entity flow.Entity) flow.Entity { + record := mustBeProtocolSpamRecordEntity(entity) // Adjust the record. adjustedRecord, err := adjustFunc(record.ProtocolSpamRecord) @@ -130,17 +72,22 @@ func (s *SpamRecordCache) adjust(originId flow.Identifier, adjustFunc model.Reco // Return the adjusted record. return ProtocolSpamRecordEntity{adjustedRecord} - }) + } + initFunc := func() flow.Entity { + return ProtocolSpamRecordEntity{s.recordFactory(originId)} + } + adjustedEntity, adjusted := s.c.AdjustWithInit(originId, wrapAdjustFunc, initFunc) if rErr != nil { return 0, fmt.Errorf("failed to adjust record: %w", rErr) } if !adjusted { - return 0, ErrSpamRecordNotFound + return 0, fmt.Errorf("adjustment failed for origin id %s", originId) } - return adjustedEntity.(ProtocolSpamRecordEntity).Penalty, nil + record := mustBeProtocolSpamRecordEntity(adjustedEntity) + return record.Penalty, nil } // Get returns the spam record of the given origin id. @@ -156,12 +103,7 @@ func (s *SpamRecordCache) Get(originId flow.Identifier) (*model.ProtocolSpamReco return nil, false } - record, ok := entity.(ProtocolSpamRecordEntity) - if !ok { - // sanity check - // This should never happen, because the cache only contains ProtocolSpamRecordEntity entities. - panic(fmt.Sprintf("invalid entity type, expected ProtocolSpamRecordEntity type, got: %T", entity)) - } + record := mustBeProtocolSpamRecordEntity(entity) // return a copy of the record (we do not want the caller to modify the record). return &model.ProtocolSpamRecord{ @@ -192,3 +134,19 @@ func (s *SpamRecordCache) Remove(originId flow.Identifier) bool { func (s *SpamRecordCache) Size() uint { return s.c.Size() } + +// mustBeProtocolSpamRecordEntity returns the given entity as a ProtocolSpamRecordEntity. +// It panics if the given entity is not a ProtocolSpamRecordEntity. +// Args: +// - entity: the entity to be converted. +// Returns: +// - ProtocolSpamRecordEntity, the converted entity. +func mustBeProtocolSpamRecordEntity(entity flow.Entity) ProtocolSpamRecordEntity { + record, ok := entity.(ProtocolSpamRecordEntity) + if !ok { + // sanity check + // This should never happen, because the cache only contains ProtocolSpamRecordEntity entities. + panic(fmt.Sprintf("invalid entity type, expected ProtocolSpamRecordEntity type, got: %T", entity)) + } + return record +} diff --git a/network/alsp/internal/cache_test.go b/network/alsp/internal/cache_test.go index 22efd456a8e..4837bd6bd1c 100644 --- a/network/alsp/internal/cache_test.go +++ b/network/alsp/internal/cache_test.go @@ -73,7 +73,7 @@ func TestSpamRecordCache_Adjust_Init(t *testing.T) { originID2 := unittest.IdentifierFixture() // adjusting a spam record for an origin ID that does not exist in the cache should initialize the record. - initializedPenalty, err := cache.Adjust(originID1, adjustFuncIncrement) + initializedPenalty, err := cache.AdjustWithInit(originID1, adjustFuncIncrement) require.NoError(t, err, "expected no error") require.Equal(t, float64(1), initializedPenalty, "expected initialized penalty to be 1") @@ -86,7 +86,7 @@ func TestSpamRecordCache_Adjust_Init(t *testing.T) { // adjusting a spam record for an origin ID that already exists in the cache should not initialize the record, // but should apply the adjust function to the existing record. - initializedPenalty, err = cache.Adjust(originID1, adjustFuncIncrement) + initializedPenalty, err = cache.AdjustWithInit(originID1, adjustFuncIncrement) require.NoError(t, err, "expected no error") require.Equal(t, float64(2), initializedPenalty, "expected initialized penalty to be 2") record1Again, ok := cache.Get(originID1) @@ -98,7 +98,7 @@ func TestSpamRecordCache_Adjust_Init(t *testing.T) { // adjusting a spam record for a different origin ID should initialize the record. // this is to ensure that the record factory is called only once. - initializedPenalty, err = cache.Adjust(originID2, adjustFuncIncrement) + initializedPenalty, err = cache.AdjustWithInit(originID2, adjustFuncIncrement) require.NoError(t, err, "expected no error") require.Equal(t, float64(1), initializedPenalty, "expected initialized penalty to be 1") record2, ok := cache.Get(originID2) @@ -131,10 +131,10 @@ func TestSpamRecordCache_Adjust_Error(t *testing.T) { originID2 := unittest.IdentifierFixture() // initialize spam records for originID1 and originID2 - penalty, err := cache.Adjust(originID1, adjustFnNoOp) + penalty, err := cache.AdjustWithInit(originID1, adjustFnNoOp) require.NoError(t, err, "expected no error") require.Equal(t, 0.0, penalty, "expected penalty to be 0") - penalty, err = cache.Adjust(originID2, adjustFnNoOp) + penalty, err = cache.AdjustWithInit(originID2, adjustFnNoOp) require.NoError(t, err, "expected no error") require.Equal(t, 0.0, penalty, "expected penalty to be 0") @@ -143,7 +143,7 @@ func TestSpamRecordCache_Adjust_Error(t *testing.T) { record.Penalty -= 10 return record, nil } - penalty, err = cache.Adjust(originID1, adjustFunc) + penalty, err = cache.AdjustWithInit(originID1, adjustFunc) require.NoError(t, err) require.Equal(t, -10.0, penalty) @@ -156,7 +156,7 @@ func TestSpamRecordCache_Adjust_Error(t *testing.T) { adjustFuncError := func(record model.ProtocolSpamRecord) (model.ProtocolSpamRecord, error) { return record, errors.New("adjustment error") } - _, err = cache.Adjust(originID1, adjustFuncError) + _, err = cache.AdjustWithInit(originID1, adjustFuncError) require.Error(t, err) // even though the adjustFunc returned an error, the record should be intact. @@ -189,11 +189,11 @@ func TestSpamRecordCache_Identities(t *testing.T) { originID3 := unittest.IdentifierFixture() // initialize spam records for a few origin IDs - _, err := cache.Adjust(originID1, adjustFnNoOp) + _, err := cache.AdjustWithInit(originID1, adjustFnNoOp) require.NoError(t, err) - _, err = cache.Adjust(originID2, adjustFnNoOp) + _, err = cache.AdjustWithInit(originID2, adjustFnNoOp) require.NoError(t, err) - _, err = cache.Adjust(originID3, adjustFnNoOp) + _, err = cache.AdjustWithInit(originID3, adjustFnNoOp) require.NoError(t, err) // check if the Identities method returns the correct set of origin IDs @@ -235,11 +235,11 @@ func TestSpamRecordCache_Remove(t *testing.T) { originID3 := unittest.IdentifierFixture() // initialize spam records for a few origin IDs - _, err := cache.Adjust(originID1, adjustFnNoOp) + _, err := cache.AdjustWithInit(originID1, adjustFnNoOp) require.NoError(t, err) - _, err = cache.Adjust(originID2, adjustFnNoOp) + _, err = cache.AdjustWithInit(originID2, adjustFnNoOp) require.NoError(t, err) - _, err = cache.Adjust(originID3, adjustFnNoOp) + _, err = cache.AdjustWithInit(originID3, adjustFnNoOp) require.NoError(t, err) // remove originID1 and check if the record is removed @@ -280,14 +280,14 @@ func TestSpamRecordCache_EdgeCasesAndInvalidInputs(t *testing.T) { // 1. initializing a spam record multiple times originID1 := unittest.IdentifierFixture() - _, err := cache.Adjust(originID1, adjustFnNoOp) + _, err := cache.AdjustWithInit(originID1, adjustFnNoOp) require.NoError(t, err) - _, err = cache.Adjust(originID1, adjustFnNoOp) + _, err = cache.AdjustWithInit(originID1, adjustFnNoOp) require.NoError(t, err) // 2. Test adjusting a non-existent spam record originID2 := unittest.IdentifierFixture() - initialPenalty, err := cache.Adjust(originID2, func(record model.ProtocolSpamRecord) (model.ProtocolSpamRecord, error) { + initialPenalty, err := cache.AdjustWithInit(originID2, func(record model.ProtocolSpamRecord) (model.ProtocolSpamRecord, error) { record.Penalty -= 10 return record, nil }) @@ -296,7 +296,7 @@ func TestSpamRecordCache_EdgeCasesAndInvalidInputs(t *testing.T) { // 3. Test removing a spam record multiple times originID3 := unittest.IdentifierFixture() - _, err = cache.Adjust(originID3, adjustFnNoOp) + _, err = cache.AdjustWithInit(originID3, adjustFnNoOp) require.NoError(t, err) require.True(t, cache.Remove(originID3)) require.False(t, cache.Remove(originID3)) @@ -328,7 +328,7 @@ func TestSpamRecordCache_ConcurrentInitialization(t *testing.T) { for _, originID := range originIDs { go func(id flow.Identifier) { defer wg.Done() - penalty, err := cache.Adjust(id, adjustFnNoOp) + penalty, err := cache.AdjustWithInit(id, adjustFnNoOp) require.NoError(t, err) require.Equal(t, float64(0), penalty) }(originID) @@ -376,7 +376,7 @@ func TestSpamRecordCache_ConcurrentSameRecordAdjust(t *testing.T) { for i := 0; i < concurrentAttempts; i++ { go func() { defer wg.Done() - penalty, err := cache.Adjust(originID, adjustFn) + penalty, err := cache.AdjustWithInit(originID, adjustFn) require.NoError(t, err) require.Less(t, penalty, 0.0) // penalty should be negative }() @@ -415,7 +415,7 @@ func TestSpamRecordCache_ConcurrentRemoval(t *testing.T) { originIDs := unittest.IdentifierListFixture(10) for _, originID := range originIDs { - penalty, err := cache.Adjust(originID, adjustFnNoOp) + penalty, err := cache.AdjustWithInit(originID, adjustFnNoOp) require.NoError(t, err) require.Equal(t, float64(0), penalty) } @@ -464,7 +464,7 @@ func TestSpamRecordCache_ConcurrentUpdatesAndReads(t *testing.T) { originIDs := unittest.IdentifierListFixture(10) for _, originID := range originIDs { - penalty, err := cache.Adjust(originID, adjustFnNoOp) + penalty, err := cache.AdjustWithInit(originID, adjustFnNoOp) require.NoError(t, err) require.Equal(t, float64(0), penalty) } @@ -481,7 +481,7 @@ func TestSpamRecordCache_ConcurrentUpdatesAndReads(t *testing.T) { // adjust spam records concurrently go func(id flow.Identifier) { defer wg.Done() - _, err := cache.Adjust(id, adjustFunc) + _, err := cache.AdjustWithInit(id, adjustFunc) require.NoError(t, err) }(originID) @@ -529,7 +529,7 @@ func TestSpamRecordCache_ConcurrentInitAndRemove(t *testing.T) { originIDsToRemove := originIDs[10:] for _, originID := range originIDsToRemove { - penalty, err := cache.Adjust(originID, adjustFnNoOp) + penalty, err := cache.AdjustWithInit(originID, adjustFnNoOp) require.NoError(t, err) require.Equal(t, float64(0), penalty) } @@ -542,7 +542,7 @@ func TestSpamRecordCache_ConcurrentInitAndRemove(t *testing.T) { originID := originID // capture range variable go func() { defer wg.Done() - penalty, err := cache.Adjust(originID, adjustFnNoOp) + penalty, err := cache.AdjustWithInit(originID, adjustFnNoOp) require.NoError(t, err) require.Equal(t, float64(0), penalty) }() @@ -597,7 +597,7 @@ func TestSpamRecordCache_ConcurrentInitRemoveAdjust(t *testing.T) { originIDsToAdjust := originIDs[20:] for _, originID := range originIDsToRemove { - penalty, err := cache.Adjust(originID, adjustFnNoOp) + penalty, err := cache.AdjustWithInit(originID, adjustFnNoOp) require.NoError(t, err) require.Equal(t, float64(0), penalty) } @@ -615,7 +615,7 @@ func TestSpamRecordCache_ConcurrentInitRemoveAdjust(t *testing.T) { originID := originID // capture range variable go func() { defer wg.Done() - penalty, err := cache.Adjust(originID, adjustFnNoOp) + penalty, err := cache.AdjustWithInit(originID, adjustFnNoOp) require.NoError(t, err) require.Equal(t, float64(0), penalty) }() @@ -633,7 +633,7 @@ func TestSpamRecordCache_ConcurrentInitRemoveAdjust(t *testing.T) { for _, originID := range originIDsToAdjust { go func(id flow.Identifier) { defer wg.Done() - _, _ = cache.Adjust(id, adjustFunc) + _, _ = cache.AdjustWithInit(id, adjustFunc) }(originID) } @@ -668,13 +668,13 @@ func TestSpamRecordCache_ConcurrentInitRemoveAndAdjust(t *testing.T) { originIDsToAdjust := originIDs[20:] for _, originID := range originIDsToRemove { - penalty, err := cache.Adjust(originID, adjustFnNoOp) + penalty, err := cache.AdjustWithInit(originID, adjustFnNoOp) require.NoError(t, err) require.Equal(t, float64(0), penalty) } for _, originID := range originIDsToAdjust { - penalty, err := cache.Adjust(originID, adjustFnNoOp) + penalty, err := cache.AdjustWithInit(originID, adjustFnNoOp) require.NoError(t, err) require.Equal(t, float64(0), penalty) } @@ -687,7 +687,7 @@ func TestSpamRecordCache_ConcurrentInitRemoveAndAdjust(t *testing.T) { originID := originID go func() { defer wg.Done() - penalty, err := cache.Adjust(originID, adjustFnNoOp) + penalty, err := cache.AdjustWithInit(originID, adjustFnNoOp) require.NoError(t, err) require.Equal(t, float64(0), penalty) }() @@ -707,7 +707,7 @@ func TestSpamRecordCache_ConcurrentInitRemoveAndAdjust(t *testing.T) { originID := originID go func() { defer wg.Done() - _, err := cache.Adjust(originID, func(record model.ProtocolSpamRecord) (model.ProtocolSpamRecord, error) { + _, err := cache.AdjustWithInit(originID, func(record model.ProtocolSpamRecord) (model.ProtocolSpamRecord, error) { record.Penalty -= 1 return record, nil }) @@ -763,7 +763,7 @@ func TestSpamRecordCache_ConcurrentIdentitiesAndOperations(t *testing.T) { originIDsToRemove := originIDs[10:20] for _, originID := range originIDsToRemove { - penalty, err := cache.Adjust(originID, adjustFnNoOp) + penalty, err := cache.AdjustWithInit(originID, adjustFnNoOp) require.NoError(t, err) require.Equal(t, float64(0), penalty) } @@ -776,7 +776,7 @@ func TestSpamRecordCache_ConcurrentIdentitiesAndOperations(t *testing.T) { originID := originID go func() { defer wg.Done() - penalty, err := cache.Adjust(originID, adjustFnNoOp) + penalty, err := cache.AdjustWithInit(originID, adjustFnNoOp) require.NoError(t, err) require.Equal(t, float64(0), penalty) retrieved, ok := cache.Get(originID) diff --git a/network/alsp/manager/manager.go b/network/alsp/manager/manager.go index f29fbc694b4..10d9be5b866 100644 --- a/network/alsp/manager/manager.go +++ b/network/alsp/manager/manager.go @@ -300,7 +300,7 @@ func (m *MisbehaviorReportManager) onHeartbeat() error { for _, id := range allIds { m.logger.Trace().Hex("identifier", logging.ID(id)).Msg("onHeartbeat - looping through spam records") - penalty, err := m.cache.Adjust(id, func(record model.ProtocolSpamRecord) (model.ProtocolSpamRecord, error) { + penalty, err := m.cache.AdjustWithInit(id, func(record model.ProtocolSpamRecord) (model.ProtocolSpamRecord, error) { if record.Penalty > 0 { // sanity check; this should never happen. return record, fmt.Errorf("illegal state: spam record %x has positive penalty %f", id, record.Penalty) @@ -423,7 +423,7 @@ func (m *MisbehaviorReportManager) processMisbehaviorReport(report internal.Repo // a spam record for the peer first and then applies the penalty. In other words, Adjust uses an optimistic update by // first assuming that the spam record exists and then initializing it if it does not exist. In this way, we avoid // acquiring the lock twice per misbehavior report, reducing the contention on the lock and improving the performance. - updatedPenalty, err := m.cache.Adjust(report.OriginId, func(record model.ProtocolSpamRecord) (model.ProtocolSpamRecord, error) { + updatedPenalty, err := m.cache.AdjustWithInit(report.OriginId, func(record model.ProtocolSpamRecord) (model.ProtocolSpamRecord, error) { if report.Penalty > 0 { // this should never happen, unless there is a bug in the misbehavior report handling logic. // we should crash the node in this case to prevent further misbehavior reports from being lost and fix the bug. diff --git a/network/alsp/manager/manager_test.go b/network/alsp/manager/manager_test.go index d2178f359f6..1f6b90c2cfe 100644 --- a/network/alsp/manager/manager_test.go +++ b/network/alsp/manager/manager_test.go @@ -30,9 +30,9 @@ import ( "github.com/onflow/flow-go/network/internal/testutils" "github.com/onflow/flow-go/network/mocknetwork" "github.com/onflow/flow-go/network/p2p" - "github.com/onflow/flow-go/network/p2p/p2pnet" p2ptest "github.com/onflow/flow-go/network/p2p/test" "github.com/onflow/flow-go/network/slashing" + "github.com/onflow/flow-go/network/underlay" "github.com/onflow/flow-go/utils/unittest" ) @@ -60,7 +60,7 @@ func TestNetworkPassesReportedMisbehavior(t *testing.T) { ids, nodes := testutils.LibP2PNodeForNetworkFixture(t, sporkId, 1) idProvider := id.NewFixedIdentityProvider(ids) networkCfg := testutils.NetworkConfigFixture(t, *ids[0], idProvider, sporkId, nodes[0]) - net, err := p2pnet.NewNetwork(networkCfg, p2pnet.WithAlspManager(misbehaviorReportManger)) + net, err := underlay.NewNetwork(networkCfg, underlay.WithAlspManager(misbehaviorReportManger)) require.NoError(t, err) ctx, cancel := context.WithCancel(context.Background()) @@ -117,8 +117,8 @@ func TestHandleReportedMisbehavior_Cache_Integration(t *testing.T) { sporkId := unittest.IdentifierFixture() ids, nodes := testutils.LibP2PNodeForNetworkFixture(t, sporkId, 1) idProvider := id.NewFixedIdentityProvider(ids) - networkCfg := testutils.NetworkConfigFixture(t, *ids[0], idProvider, sporkId, nodes[0], p2pnet.WithAlspConfig(cfg)) - net, err := p2pnet.NewNetwork(networkCfg) + networkCfg := testutils.NetworkConfigFixture(t, *ids[0], idProvider, sporkId, nodes[0], underlay.WithAlspConfig(cfg)) + net, err := underlay.NewNetwork(networkCfg) require.NoError(t, err) ctx, cancel := context.WithCancel(context.Background()) @@ -217,8 +217,8 @@ func TestHandleReportedMisbehavior_And_DisallowListing_Integration(t *testing.T) p2ptest.WithPeerManagerEnabled(p2ptest.PeerManagerConfigFixture(), nil)) idProvider := id.NewFixedIdentityProvider(ids) - networkCfg := testutils.NetworkConfigFixture(t, *ids[0], idProvider, sporkId, nodes[0], p2pnet.WithAlspConfig(cfg)) - victimNetwork, err := p2pnet.NewNetwork(networkCfg) + networkCfg := testutils.NetworkConfigFixture(t, *ids[0], idProvider, sporkId, nodes[0], underlay.WithAlspConfig(cfg)) + victimNetwork, err := underlay.NewNetwork(networkCfg) require.NoError(t, err) // index of the victim node in the nodes slice. @@ -310,9 +310,9 @@ func TestHandleReportedMisbehavior_And_DisallowListing_RepeatOffender_Integratio ids, nodes := testutils.LibP2PNodeForNetworkFixture(t, sporkId, 3, p2ptest.WithPeerManagerEnabled(p2ptest.PeerManagerConfigFixture(p2ptest.WithZeroJitterAndZeroBackoff(t)), nil)) idProvider := unittest.NewUpdatableIDProvider(ids) - networkCfg := testutils.NetworkConfigFixture(t, *ids[0], idProvider, sporkId, nodes[0], p2pnet.WithAlspConfig(cfg)) + networkCfg := testutils.NetworkConfigFixture(t, *ids[0], idProvider, sporkId, nodes[0], underlay.WithAlspConfig(cfg)) - victimNetwork, err := p2pnet.NewNetwork(networkCfg) + victimNetwork, err := underlay.NewNetwork(networkCfg) require.NoError(t, err) // index of the victim node in the nodes slice. @@ -470,12 +470,12 @@ func TestHandleReportedMisbehavior_And_SlashingViolationsConsumer_Integration(t idProvider, sporkId, nodes[0], - p2pnet.WithAlspConfig(managerCfgFixture(t)), - p2pnet.WithSlashingViolationConsumerFactory(func(adapter network.ConduitAdapter) network.ViolationsConsumer { + underlay.WithAlspConfig(managerCfgFixture(t)), + underlay.WithSlashingViolationConsumerFactory(func(adapter network.ConduitAdapter) network.ViolationsConsumer { violationsConsumer = slashing.NewSlashingViolationsConsumer(unittest.Logger(), metrics.NewNoopCollector(), adapter) return violationsConsumer })) - victimNetwork, err := p2pnet.NewNetwork(networkCfg) + victimNetwork, err := underlay.NewNetwork(networkCfg) require.NoError(t, err) ctx, cancel := context.WithCancel(context.Background()) @@ -563,8 +563,8 @@ func TestMisbehaviorReportMetrics(t *testing.T) { ids, nodes := testutils.LibP2PNodeForNetworkFixture(t, sporkId, 1) idProvider := id.NewFixedIdentityProvider(ids) - networkCfg := testutils.NetworkConfigFixture(t, *ids[0], idProvider, sporkId, nodes[0], p2pnet.WithAlspConfig(cfg)) - net, err := p2pnet.NewNetwork(networkCfg) + networkCfg := testutils.NetworkConfigFixture(t, *ids[0], idProvider, sporkId, nodes[0], underlay.WithAlspConfig(cfg)) + net, err := underlay.NewNetwork(networkCfg) require.NoError(t, err) ctx, cancel := context.WithCancel(context.Background()) @@ -1602,7 +1602,7 @@ func TestDecayMisbehaviorPenalty_DecayToZero_AllowListing(t *testing.T) { // simulates a disallow-listed peer in cache. originId := unittest.IdentifierFixture() - penalty, err := cache.Adjust(originId, func(record model.ProtocolSpamRecord) (model.ProtocolSpamRecord, error) { + penalty, err := cache.AdjustWithInit(originId, func(record model.ProtocolSpamRecord) (model.ProtocolSpamRecord, error) { record.Penalty = -10 // set the penalty to -10 to simulate that the penalty has already been decayed for a while. record.CutoffCounter = 1 record.DisallowListed = true @@ -1740,7 +1740,7 @@ func TestDisallowListNotification(t *testing.T) { }, 2*time.Second, 10*time.Millisecond, "ALSP manager did not handle the misbehavior report") } -////////////////////////////// TEST HELPERS /////////////////////////////////////////////////////////////////////////////// +// //////////////////////////// TEST HELPERS /////////////////////////////////////////////////////////////////////////////// // The following functions are helpers for the tests. It wasn't feasible to put them in a helper file in the alspmgr_test // package because that would break encapsulation of the ALSP manager and require making some fields exportable. // Putting them in alspmgr package would cause a circular import cycle. Therefore, they are put in the internal test package here. diff --git a/network/alsp/mock/spam_record_cache.go b/network/alsp/mock/spam_record_cache.go index ecc9f4ae1a5..f9c7a6952bd 100644 --- a/network/alsp/mock/spam_record_cache.go +++ b/network/alsp/mock/spam_record_cache.go @@ -14,8 +14,8 @@ type SpamRecordCache struct { mock.Mock } -// Adjust provides a mock function with given fields: originId, adjustFunc -func (_m *SpamRecordCache) Adjust(originId flow.Identifier, adjustFunc model.RecordAdjustFunc) (float64, error) { +// AdjustWithInit provides a mock function with given fields: originId, adjustFunc +func (_m *SpamRecordCache) AdjustWithInit(originId flow.Identifier, adjustFunc model.RecordAdjustFunc) (float64, error) { ret := _m.Called(originId, adjustFunc) var r0 float64 diff --git a/network/cache/rcvcache.go b/network/cache/rcvcache.go index bdab2ad894a..f929a18a523 100644 --- a/network/cache/rcvcache.go +++ b/network/cache/rcvcache.go @@ -61,6 +61,6 @@ func (r *ReceiveCache) Add(eventID []byte) bool { return r.c.Add(receiveCacheEntry{eventID: flow.HashToID(eventID)}) // ignore eviction status } -func (r ReceiveCache) Size() uint { +func (r *ReceiveCache) Size() uint { return r.c.Size() } diff --git a/network/cache/rcvcache_test.go b/network/cache/rcvcache_test.go index 28c65c52f72..36cc7b600bc 100644 --- a/network/cache/rcvcache_test.go +++ b/network/cache/rcvcache_test.go @@ -14,7 +14,7 @@ import ( "github.com/onflow/flow-go/network/channels" - "github.com/onflow/flow-go/crypto/hash" + "github.com/onflow/crypto/hash" "github.com/onflow/flow-go/module/metrics" netcache "github.com/onflow/flow-go/network/cache" diff --git a/network/errors.go b/network/errors.go index 5c4485324e2..f9edc2d291a 100644 --- a/network/errors.go +++ b/network/errors.go @@ -6,7 +6,7 @@ import ( "github.com/libp2p/go-libp2p/core/peer" - "github.com/onflow/flow-go/network/p2p/p2plogging" + p2plogging "github.com/onflow/flow-go/network/p2p/logging" ) var ( diff --git a/network/internal/p2pfixtures/fixtures.go b/network/internal/p2pfixtures/fixtures.go index d2c79966c1f..7e8f053676f 100644 --- a/network/internal/p2pfixtures/fixtures.go +++ b/network/internal/p2pfixtures/fixtures.go @@ -17,11 +17,11 @@ import ( "github.com/libp2p/go-libp2p/core/routing" "github.com/multiformats/go-multiaddr" manet "github.com/multiformats/go-multiaddr/net" + "github.com/onflow/crypto" "github.com/rs/zerolog" "github.com/stretchr/testify/require" "github.com/onflow/flow-go/config" - "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/module/id" "github.com/onflow/flow-go/module/metrics" @@ -30,10 +30,9 @@ import ( "github.com/onflow/flow-go/network/internal/p2putils" "github.com/onflow/flow-go/network/message" "github.com/onflow/flow-go/network/p2p" + p2pbuilder "github.com/onflow/flow-go/network/p2p/builder" + p2pbuilderconfig "github.com/onflow/flow-go/network/p2p/builder/config" p2pdht "github.com/onflow/flow-go/network/p2p/dht" - "github.com/onflow/flow-go/network/p2p/p2pbuilder" - p2pconfig "github.com/onflow/flow-go/network/p2p/p2pbuilder/config" - "github.com/onflow/flow-go/network/p2p/tracer" "github.com/onflow/flow-go/network/p2p/unicast/protocols" "github.com/onflow/flow-go/network/p2p/utils" "github.com/onflow/flow-go/utils/unittest" @@ -100,21 +99,10 @@ func CreateNode(t *testing.T, networkKey crypto.PrivateKey, sporkID flow.Identif defaultFlowConfig, err := config.DefaultConfig() require.NoError(t, err) - meshTracerCfg := &tracer.GossipSubMeshTracerConfig{ - Logger: logger, - Metrics: metrics.NewNoopCollector(), - IDProvider: idProvider, - LoggerInterval: defaultFlowConfig.NetworkConfig.GossipSubConfig.LocalMeshLogInterval, - RpcSentTrackerCacheSize: defaultFlowConfig.NetworkConfig.GossipSubConfig.RPCSentTrackerCacheSize, - RpcSentTrackerWorkerQueueCacheSize: defaultFlowConfig.NetworkConfig.GossipSubConfig.RPCSentTrackerQueueCacheSize, - RpcSentTrackerNumOfWorkers: defaultFlowConfig.NetworkConfig.GossipSubConfig.RpcSentTrackerNumOfWorkers, - HeroCacheMetricsFactory: metrics.NewNoopHeroCacheMetricsFactory(), - NetworkingType: flownet.PublicNetwork, - } - meshTracer := tracer.NewGossipSubMeshTracer(meshTracerCfg) - - builder := p2pbuilder.NewNodeBuilder(logger, - &p2pconfig.MetricsConfig{ + builder := p2pbuilder.NewNodeBuilder( + logger, + &defaultFlowConfig.NetworkConfig.GossipSub, + &p2pbuilderconfig.MetricsConfig{ HeroCacheFactory: metrics.NewNoopHeroCacheMetricsFactory(), Metrics: metrics.NewNoopCollector(), }, @@ -123,24 +111,19 @@ func CreateNode(t *testing.T, networkKey crypto.PrivateKey, sporkID flow.Identif networkKey, sporkID, idProvider, - defaultFlowConfig.NetworkConfig.GossipSubConfig.GossipSubScoringRegistryConfig, &defaultFlowConfig.NetworkConfig.ResourceManager, - &defaultFlowConfig.NetworkConfig.GossipSubConfig, - p2pconfig.PeerManagerDisableConfig(), + p2pbuilderconfig.PeerManagerDisableConfig(), &p2p.DisallowListCacheConfig{ MaxSize: uint32(1000), Metrics: metrics.NewNoopCollector(), }, - meshTracer, - &p2pconfig.UnicastConfig{ - UnicastConfig: defaultFlowConfig.NetworkConfig.UnicastConfig, + &p2pbuilderconfig.UnicastConfig{ + Unicast: defaultFlowConfig.NetworkConfig.Unicast, }). SetRoutingSystem(func(c context.Context, h host.Host) (routing.Routing, error) { return p2pdht.NewDHT(c, h, protocols.FlowDHTProtocolID(sporkID), zerolog.Nop(), metrics.NewNoopCollector()) }). - SetResourceManager(&network.NullResourceManager{}). - SetGossipSubTracer(meshTracer). - SetGossipSubScoreTracerInterval(defaultFlowConfig.NetworkConfig.GossipSubConfig.ScoreTracerInterval) + SetResourceManager(&network.NullResourceManager{}) for _, opt := range opts { opt(builder) @@ -246,7 +229,7 @@ func EnsureMessageExchangeOverUnicast(t *testing.T, ctx context.Context, nodes [ if this == other { continue } - err := this.OpenProtectedStream(ctx, other.ID(), t.Name(), func(stream network.Stream) error { + err := this.OpenAndWriteOnStream(ctx, other.ID(), t.Name(), func(stream network.Stream) error { rw := bufio.NewReadWriter(bufio.NewReader(stream), bufio.NewWriter(stream)) _, err := rw.WriteString(msg) require.NoError(t, err) @@ -305,7 +288,7 @@ func EnsureNoStreamCreation(t *testing.T, ctx context.Context, from []p2p.LibP2P } require.Empty(t, other.Host().Network().ConnsToPeer(thisId)) - err := this.OpenProtectedStream(ctx, otherId, t.Name(), func(stream network.Stream) error { + err := this.OpenAndWriteOnStream(ctx, otherId, t.Name(), func(stream network.Stream) error { // no-op as the stream is never created. return nil }) @@ -330,7 +313,7 @@ func EnsureStreamCreation(t *testing.T, ctx context.Context, from []p2p.LibP2PNo require.Fail(t, "node is in both from and to lists") } // stream creation should pass without error - err := this.OpenProtectedStream(ctx, other.ID(), t.Name(), func(stream network.Stream) error { + err := this.OpenAndWriteOnStream(ctx, other.ID(), t.Name(), func(stream network.Stream) error { require.NotNil(t, stream) return nil }) diff --git a/network/internal/p2putils/utils.go b/network/internal/p2putils/utils.go index 0ec8b8aba11..37df9e4794c 100644 --- a/network/internal/p2putils/utils.go +++ b/network/internal/p2putils/utils.go @@ -14,7 +14,7 @@ import ( "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/network/p2p/keyutils" - "github.com/onflow/flow-go/network/p2p/p2plogging" + p2plogging "github.com/onflow/flow-go/network/p2p/logging" "github.com/onflow/flow-go/network/p2p/unicast/protocols" ) diff --git a/network/internal/testutils/testUtil.go b/network/internal/testutils/testUtil.go index c817fba98f8..a08af0b218e 100644 --- a/network/internal/testutils/testUtil.go +++ b/network/internal/testutils/testUtil.go @@ -31,9 +31,9 @@ import ( "github.com/onflow/flow-go/network/p2p" "github.com/onflow/flow-go/network/p2p/conduit" "github.com/onflow/flow-go/network/p2p/connection" - "github.com/onflow/flow-go/network/p2p/p2pnet" p2ptest "github.com/onflow/flow-go/network/p2p/test" "github.com/onflow/flow-go/network/p2p/translator" + "github.com/onflow/flow-go/network/underlay" "github.com/onflow/flow-go/utils/unittest" ) @@ -96,7 +96,7 @@ func (tw *TagWatchingConnManager) Unprotect(id peer.ID, tag string) bool { } // NewTagWatchingConnManager creates a new TagWatchingConnManager with the given config. It returns an error if the config is invalid. -func NewTagWatchingConnManager(log zerolog.Logger, metrics module.LibP2PConnectionMetrics, config *netconf.ConnectionManagerConfig) (*TagWatchingConnManager, error) { +func NewTagWatchingConnManager(log zerolog.Logger, metrics module.LibP2PConnectionMetrics, config *netconf.ConnectionManager) (*TagWatchingConnManager, error) { cm, err := connection.NewConnManager(log, metrics, config) if err != nil { return nil, fmt.Errorf("could not create connection manager: %w", err) @@ -149,10 +149,10 @@ func NetworksFixture(t *testing.T, sporkId flow.Identifier, ids flow.IdentityList, libp2pNodes []p2p.LibP2PNode, - configOpts ...func(*p2pnet.NetworkConfig)) ([]*p2pnet.Network, []*unittest.UpdatableIDProvider) { + configOpts ...func(*underlay.NetworkConfig)) ([]*underlay.Network, []*unittest.UpdatableIDProvider) { count := len(ids) - nets := make([]*p2pnet.Network, 0) + nets := make([]*underlay.Network, 0) idProviders := make([]*unittest.UpdatableIDProvider, 0) for i := 0; i < count; i++ { @@ -163,7 +163,7 @@ func NetworksFixture(t *testing.T, opt(params) } - net, err := p2pnet.NewNetwork(params) + net, err := underlay.NewNetwork(params) require.NoError(t, err) nets = append(nets, net) @@ -179,7 +179,7 @@ func NetworkConfigFixture( idProvider module.IdentityProvider, sporkId flow.Identifier, libp2pNode p2p.LibP2PNode, - opts ...p2pnet.NetworkConfigOption) *p2pnet.NetworkConfig { + opts ...underlay.NetworkConfigOption) *underlay.NetworkConfig { me := mock.NewLocal(t) me.On("NodeID").Return(myId.NodeID).Maybe() @@ -193,7 +193,7 @@ func NetworkConfigFixture( defaultFlowConfig.NetworkConfig.NetworkReceivedMessageCacheSize, unittest.Logger(), metrics.NewNoopCollector()) - params := &p2pnet.NetworkConfig{ + params := &underlay.NetworkConfig{ Logger: unittest.Logger(), Codec: unittest.NetworkCodec(), Libp2pNode: libp2pNode, @@ -205,7 +205,7 @@ func NetworkConfigFixture( ReceiveCache: receiveCache, ConduitFactory: conduit.NewDefaultConduitFactory(), SporkId: sporkId, - UnicastMessageTimeout: p2pnet.DefaultUnicastTimeout, + UnicastMessageTimeout: underlay.DefaultUnicastTimeout, IdentityTranslator: translator.NewIdentityProviderIDTranslator(idProvider), AlspCfg: &alspmgr.MisbehaviorReportManagerConfig{ Logger: unittest.Logger(), diff --git a/network/message/message.pb.go b/network/message/message.pb.go index 9618db05eef..a9cb83c279f 100644 --- a/network/message/message.pb.go +++ b/network/message/message.pb.go @@ -5,10 +5,11 @@ package message import ( fmt "fmt" - proto "github.com/golang/protobuf/proto" io "io" math "math" math_bits "math/bits" + + proto "github.com/golang/protobuf/proto" ) // Reference imports to suppress errors if they are not otherwise used. diff --git a/network/message/message_scope.go b/network/message/message_scope.go index fa4b0d667ef..0b93de7af19 100644 --- a/network/message/message_scope.go +++ b/network/message/message_scope.go @@ -4,7 +4,8 @@ import ( "fmt" "strings" - "github.com/onflow/flow-go/crypto/hash" + "github.com/onflow/crypto/hash" + "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/network/channels" ) diff --git a/network/message/ping.pb.go b/network/message/ping.pb.go index 9f86dc7ee73..521e7293909 100644 --- a/network/message/ping.pb.go +++ b/network/message/ping.pb.go @@ -5,10 +5,11 @@ package message import ( fmt "fmt" - proto "github.com/golang/protobuf/proto" io "io" math "math" math_bits "math/bits" + + proto "github.com/golang/protobuf/proto" ) // Reference imports to suppress errors if they are not otherwise used. diff --git a/network/mocknetwork/disallow_list_oracle.go b/network/mocknetwork/disallow_list_oracle.go index 3bae6e851f3..ed7ae6f6b6b 100644 --- a/network/mocknetwork/disallow_list_oracle.go +++ b/network/mocknetwork/disallow_list_oracle.go @@ -3,9 +3,10 @@ package mocknetwork import ( - network "github.com/onflow/flow-go/network" mock "github.com/stretchr/testify/mock" + network "github.com/onflow/flow-go/network" + peer "github.com/libp2p/go-libp2p/core/peer" ) diff --git a/network/mocknetwork/middleware.go b/network/mocknetwork/middleware.go index f2552d768f1..aaa6816b77a 100644 --- a/network/mocknetwork/middleware.go +++ b/network/mocknetwork/middleware.go @@ -3,9 +3,10 @@ package mocknetwork import ( - channels "github.com/onflow/flow-go/network/channels" mock "github.com/stretchr/testify/mock" + channels "github.com/onflow/flow-go/network/channels" + network "github.com/onflow/flow-go/network" ) diff --git a/network/mocknetwork/network.go b/network/mocknetwork/network.go index 95891793892..bf21a78fbd4 100644 --- a/network/mocknetwork/network.go +++ b/network/mocknetwork/network.go @@ -4,6 +4,7 @@ package mocknetwork import ( datastore "github.com/ipfs/go-datastore" + channels "github.com/onflow/flow-go/network/channels" irrecoverable "github.com/onflow/flow-go/module/irrecoverable" diff --git a/network/mocknetwork/overlay.go b/network/mocknetwork/overlay.go index 7eab360e012..35bb90c11e6 100644 --- a/network/mocknetwork/overlay.go +++ b/network/mocknetwork/overlay.go @@ -3,9 +3,10 @@ package mocknetwork import ( - flow "github.com/onflow/flow-go/model/flow" mock "github.com/stretchr/testify/mock" + flow "github.com/onflow/flow-go/model/flow" + network "github.com/onflow/flow-go/network" peer "github.com/libp2p/go-libp2p/core/peer" diff --git a/network/netconf/config.go b/network/netconf/config.go index b9df868d281..a14933a0498 100644 --- a/network/netconf/config.go +++ b/network/netconf/config.go @@ -3,17 +3,23 @@ package netconf import ( "time" - "github.com/onflow/flow-go/network/p2p/p2pconf" + p2pconfig "github.com/onflow/flow-go/network/p2p/config" +) + +const ( + gossipsubKey = "gossipsub" + unicastKey = "unicast" + connectionManagerKey = "connection-manager" ) // Config encapsulation of configuration structs for all components related to the Flow network. type Config struct { - UnicastConfig `mapstructure:",squash"` - ResourceManager p2pconf.ResourceManagerConfig `mapstructure:"libp2p-resource-manager"` - ConnectionManagerConfig `mapstructure:",squash"` - // GossipSubConfig core gossipsub configuration. - p2pconf.GossipSubConfig `mapstructure:",squash"` - AlspConfig `mapstructure:",squash"` + Unicast Unicast `mapstructure:"unicast"` + ResourceManager p2pconfig.ResourceManagerConfig `mapstructure:"libp2p-resource-manager"` + ConnectionManager ConnectionManager `mapstructure:"connection-manager"` + // GossipSub core gossipsub configuration. + GossipSub p2pconfig.GossipSubParameters `mapstructure:"gossipsub"` + AlspConfig `mapstructure:",squash"` // NetworkConnectionPruning determines whether connections to nodes // that are not part of protocol state should be trimmed @@ -23,54 +29,12 @@ type Config struct { PreferredUnicastProtocols []string `mapstructure:"preferred-unicast-protocols"` NetworkReceivedMessageCacheSize uint32 `validate:"gt=0" mapstructure:"received-message-cache-size"` PeerUpdateInterval time.Duration `validate:"gt=0s" mapstructure:"peerupdate-interval"` - UnicastMessageTimeout time.Duration `validate:"gt=0s" mapstructure:"unicast-message-timeout"` DNSCacheTTL time.Duration `validate:"gt=0s" mapstructure:"dns-cache-ttl"` // DisallowListNotificationCacheSize size of the queue for notifications about new peers in the disallow list. DisallowListNotificationCacheSize uint32 `validate:"gt=0" mapstructure:"disallow-list-notification-cache-size"` } -type UnicastConfig struct { - // UnicastRateLimitersConfig configuration for all unicast rate limiters. - UnicastRateLimitersConfig `mapstructure:",squash"` - - // CreateStreamBackoffDelay initial delay used in the exponential backoff for create stream retries. - CreateStreamBackoffDelay time.Duration `validate:"gt=0s" mapstructure:"unicast-create-stream-retry-delay"` - - // StreamZeroRetryResetThreshold is the threshold that determines when to reset the stream creation retry budget to the default value. - // - // For example the default value of 100 means that if the stream creation retry budget is decreased to 0, then it will be reset to default value - // when the number of consecutive successful streams reaches 100. - // - // This is to prevent the retry budget from being reset too frequently, as the retry budget is used to gauge the reliability of the stream creation. - // When the stream creation retry budget is reset to the default value, it means that the stream creation is reliable enough to be trusted again. - // This parameter mandates when the stream creation is reliable enough to be trusted again; i.e., when the number of consecutive successful streams reaches this threshold. - // Note that the counter is reset to 0 when the stream creation fails, so the value of for example 100 means that the stream creation is reliable enough that the recent - // 100 stream creations are all successful. - StreamZeroRetryResetThreshold uint64 `validate:"gt=0" mapstructure:"unicast-stream-zero-retry-reset-threshold"` - - // MaxStreamCreationRetryAttemptTimes is the maximum number of attempts to be made to create a stream to a remote node over a direct unicast (1:1) connection before we give up. - MaxStreamCreationRetryAttemptTimes uint64 `validate:"gt=1" mapstructure:"unicast-max-stream-creation-retry-attempt-times"` - - // ConfigCacheSize is the cache size of the dial config cache that keeps the individual dial config for each peer. - ConfigCacheSize uint32 `validate:"gt=0" mapstructure:"unicast-dial-config-cache-size"` -} - -// UnicastRateLimitersConfig unicast rate limiter configuration for the message and bandwidth rate limiters. -type UnicastRateLimitersConfig struct { - // DryRun setting this to true will disable connection disconnects and gating when unicast rate limiters are configured - DryRun bool `mapstructure:"unicast-dry-run"` - // LockoutDuration the number of seconds a peer will be forced to wait before being allowed to successfully reconnect to the node - // after being rate limited. - LockoutDuration time.Duration `validate:"gte=0" mapstructure:"unicast-lockout-duration"` - // MessageRateLimit amount of unicast messages that can be sent by a peer per second. - MessageRateLimit int `validate:"gte=0" mapstructure:"unicast-message-rate-limit"` - // BandwidthRateLimit bandwidth size in bytes a peer is allowed to send via unicast streams per second. - BandwidthRateLimit int `validate:"gte=0" mapstructure:"unicast-bandwidth-rate-limit"` - // BandwidthBurstLimit bandwidth size in bytes a peer is allowed to send via unicast streams at once. - BandwidthBurstLimit int `validate:"gte=0" mapstructure:"unicast-bandwidth-burst-limit"` -} - // AlspConfig is the config for the Application Layer Spam Prevention (ALSP) protocol. type AlspConfig struct { // Size of the cache for spam records. There is at most one spam record per authorized (i.e., staked) node. diff --git a/network/netconf/config_test.go b/network/netconf/config_test.go index 3a0a21b10b9..d6a062cdb2f 100644 --- a/network/netconf/config_test.go +++ b/network/netconf/config_test.go @@ -42,10 +42,34 @@ func TestSetAliases(t *testing.T) { } } -// TestCrossReferenceFlagsAndConfigs ensures every network configuration in the config file has a corresponding CLI flag. -func TestCrossReferenceFlagsAndConfigs(t *testing.T) { +// TestCrossReferenceFlagsWithConfigs ensures that each flag is cross-referenced with the config file, i.e., that each +// flag has a corresponding config key. +func TestCrossReferenceFlagsWithConfigs(t *testing.T) { // reads the default config file c := config.RawViperConfig() err := netconf.SetAliases(c) require.NoError(t, err) } + +// TestCrossReferenceConfigsWithFlags ensures that each config is cross-referenced with the flags, i.e., that each config +// key has a corresponding flag. +func TestCrossReferenceConfigsWithFlags(t *testing.T) { + c := config.RawViperConfig() + // keeps all flag names + m := make(map[string]struct{}) + + // each flag name should correspond to exactly one key in our config store after it is loaded with the default config + for _, flagName := range netconf.AllFlagNames() { + m[flagName] = struct{}{} + } + + for _, key := range c.AllKeys() { + s := strings.Split(key, ".") + flag := strings.Join(s[1:], "-") + if len(flag) == 0 { + continue + } + _, ok := m[flag] + require.Truef(t, ok, "config key %s does not have a corresponding flag", flag) + } +} diff --git a/network/netconf/connection_manager.go b/network/netconf/connection_manager.go index 333a3f8c6e5..2533e5c38fc 100644 --- a/network/netconf/connection_manager.go +++ b/network/netconf/connection_manager.go @@ -2,7 +2,14 @@ package netconf import "time" -type ConnectionManagerConfig struct { +const ( + highWatermarkKey = "high-watermark" + lowWatermarkKey = "low-watermark" + silencePeriodKey = "silence-period" + gracePeriodKey = "grace-period" +) + +type ConnectionManager struct { // HighWatermark and LowWatermark govern the number of connections are maintained by the ConnManager. // When the peer count exceeds the HighWatermark, as many peers will be pruned (and // their connections terminated) until LowWatermark peers remain. In other words, whenever the @@ -15,11 +22,12 @@ type ConnectionManagerConfig struct { // will be pruned. If both peers have incoming connections, and there are still ties, one of the peers will be // pruned at random. // Algorithm implementation is in https://github.com/libp2p/go-libp2p/blob/master/p2p/net/connmgr/connmgr.go#L262-L318 - HighWatermark int `mapstructure:"libp2p-high-watermark"` // naming from libp2p - LowWatermark int `mapstructure:"libp2p-low-watermark"` // naming from libp2p + HighWatermark int `mapstructure:"high-watermark"` // naming from libp2p + LowWatermark int `mapstructure:"low-watermark"` // naming from libp2p - // SilencePeriod is the time to wait before start pruning connections. - SilencePeriod time.Duration `mapstructure:"libp2p-silence-period"` // naming from libp2p - // GracePeriod is the time to wait before pruning a new connection. - GracePeriod time.Duration `mapstructure:"libp2p-grace-period"` // naming from libp2p + // SilencePeriod is a regular interval that the connection manager will check for pruning peers if the number of peers exceeds the high-watermark. + // It is a regular interval. + SilencePeriod time.Duration `mapstructure:"silence-period"` // naming from libp2p + // GracePeriod is the time to wait before a new connection is considered for pruning. + GracePeriod time.Duration `mapstructure:"grace-period"` // naming from libp2p } diff --git a/network/netconf/flags.go b/network/netconf/flags.go index 823772f5b03..8597b5e0721 100644 --- a/network/netconf/flags.go +++ b/network/netconf/flags.go @@ -7,29 +7,18 @@ import ( "github.com/spf13/pflag" "github.com/spf13/viper" - "github.com/onflow/flow-go/network/p2p/p2pconf" + p2pconfig "github.com/onflow/flow-go/network/p2p/config" ) const ( // All constant strings are used for CLI flag names and corresponding keys for config values. // network configuration - networkingConnectionPruning = "networking-connection-pruning" - preferredUnicastsProtocols = "preferred-unicast-protocols" - receivedMessageCacheSize = "received-message-cache-size" - peerUpdateInterval = "peerupdate-interval" - unicastMessageTimeout = "unicast-message-timeout" - unicastCreateStreamRetryDelay = "unicast-create-stream-retry-delay" - unicastStreamZeroRetryResetThreshold = "unicast-stream-zero-retry-reset-threshold" - unicastMaxStreamCreationRetryAttemptTimes = "unicast-max-stream-creation-retry-attempt-times" - unicastDialConfigCacheSize = "unicast-dial-config-cache-size" - dnsCacheTTL = "dns-cache-ttl" - disallowListNotificationCacheSize = "disallow-list-notification-cache-size" - // unicast rate limiters config - dryRun = "unicast-dry-run" - lockoutDuration = "unicast-lockout-duration" - messageRateLimit = "unicast-message-rate-limit" - bandwidthRateLimit = "unicast-bandwidth-rate-limit" - bandwidthBurstLimit = "unicast-bandwidth-burst-limit" + networkingConnectionPruning = "networking-connection-pruning" + preferredUnicastsProtocols = "preferred-unicast-protocols" + receivedMessageCacheSize = "received-message-cache-size" + peerUpdateInterval = "peerupdate-interval" + dnsCacheTTL = "dns-cache-ttl" + disallowListNotificationCacheSize = "disallow-list-notification-cache-size" // resource manager config rootResourceManagerPrefix = "libp2p-resource-manager" memoryLimitRatioPrefix = "memory-limit-ratio" @@ -47,53 +36,10 @@ const ( fileDescriptorsLimit = "fd" memoryLimitBytes = "memory-bytes" - // connection manager - highWatermark = "libp2p-high-watermark" - lowWatermark = "libp2p-low-watermark" - gracePeriod = "libp2p-grace-period" - silencePeriod = "libp2p-silence-period" - // gossipsub - peerScoring = "gossipsub-peer-scoring-enabled" - localMeshLogInterval = "gossipsub-local-mesh-logging-interval" - rpcSentTrackerCacheSize = "gossipsub-rpc-sent-tracker-cache-size" - rpcSentTrackerQueueCacheSize = "gossipsub-rpc-sent-tracker-queue-cache-size" - rpcSentTrackerNumOfWorkers = "gossipsub-rpc-sent-tracker-workers" - scoreTracerInterval = "gossipsub-score-tracer-interval" - - gossipSubSubscriptionProviderUpdateInterval = "gossipsub-subscription-provider-update-interval" - gossipSubSubscriptionProviderCacheSize = "gossipsub-subscription-provider-cache-size" - - // gossipsub validation inspector - gossipSubRPCInspectorNotificationCacheSize = "gossipsub-rpc-inspector-notification-cache-size" - validationInspectorNumberOfWorkers = "gossipsub-rpc-validation-inspector-workers" - validationInspectorInspectMessageQueueCacheSize = "gossipsub-rpc-validation-inspector-queue-cache-size" - validationInspectorClusterPrefixedTopicsReceivedCacheSize = "gossipsub-cluster-prefix-tracker-cache-size" - validationInspectorClusterPrefixedTopicsReceivedCacheDecay = "gossipsub-cluster-prefix-tracker-cache-decay" - validationInspectorClusterPrefixHardThreshold = "gossipsub-rpc-cluster-prefixed-hard-threshold" - - ihaveMaxSampleSize = "gossipsub-rpc-ihave-max-sample-size" - ihaveMaxMessageIDSampleSize = "gossipsub-rpc-ihave-max-message-id-sample-size" - controlMessageMaxSampleSize = "gossipsub-rpc-graft-and-prune-message-max-sample-size" - iwantMaxSampleSize = "gossipsub-rpc-iwant-max-sample-size" - iwantMaxMessageIDSampleSize = "gossipsub-rpc-iwant-max-message-id-sample-size" - iwantCacheMissThreshold = "gossipsub-rpc-iwant-cache-miss-threshold" - iwantCacheMissCheckSize = "gossipsub-rpc-iwant-cache-miss-check-size" - iwantDuplicateMsgIDThreshold = "gossipsub-rpc-iwant-duplicate-message-id-threshold" - rpcMessageMaxSampleSize = "gossipsub-rpc-message-max-sample-size" - rpcMessageErrorThreshold = "gossipsub-rpc-message-error-threshold" - // gossipsub metrics inspector - metricsInspectorNumberOfWorkers = "gossipsub-rpc-metrics-inspector-workers" - metricsInspectorCacheSize = "gossipsub-rpc-metrics-inspector-cache-size" - - // gossipsub scoring registry - scoringRegistrySlowerDecayThreshold = "gossipsub-app-specific-penalty-decay-slowdown-threshold" - scoringRegistryDecayRateDecrement = "gossipsub-app-specific-penalty-decay-rate-reduction-factor" - scoringRegistryDecayAdjustInterval = "gossipsub-app-specific-penalty-decay-evaluation-period" - alspDisabled = "alsp-disable-penalty" - alspSpamRecordCacheSize = "alsp-spam-record-cache-size" - alspSpamRecordQueueSize = "alsp-spam-report-queue-size" - alspHearBeatInterval = "alsp-heart-beat-interval" - + alspDisabled = "alsp-disable-penalty" + alspSpamRecordCacheSize = "alsp-spam-record-cache-size" + alspSpamRecordQueueSize = "alsp-spam-report-queue-size" + alspHearBeatInterval = "alsp-heart-beat-interval" alspSyncEngineBatchRequestBaseProb = "alsp-sync-engine-batch-request-base-prob" alspSyncEngineRangeRequestBaseProb = "alsp-sync-engine-range-request-base-prob" alspSyncEngineSyncRequestProb = "alsp-sync-engine-sync-request-prob" @@ -105,39 +51,25 @@ func AllFlagNames() []string { preferredUnicastsProtocols, receivedMessageCacheSize, peerUpdateInterval, - unicastMessageTimeout, - unicastCreateStreamRetryDelay, - unicastStreamZeroRetryResetThreshold, - unicastMaxStreamCreationRetryAttemptTimes, - unicastDialConfigCacheSize, + BuildFlagName(unicastKey, MessageTimeoutKey), + BuildFlagName(unicastKey, unicastManagerKey, createStreamBackoffDelayKey), + BuildFlagName(unicastKey, unicastManagerKey, streamZeroRetryResetThresholdKey), + BuildFlagName(unicastKey, unicastManagerKey, maxStreamCreationRetryAttemptTimesKey), + BuildFlagName(unicastKey, unicastManagerKey, configCacheSizeKey), dnsCacheTTL, disallowListNotificationCacheSize, - dryRun, - lockoutDuration, - messageRateLimit, - bandwidthRateLimit, - bandwidthBurstLimit, - rootResourceManagerPrefix + "-" + memoryLimitRatioPrefix, - rootResourceManagerPrefix + "-" + fileDescriptorsRatioPrefix, - highWatermark, - lowWatermark, - gracePeriod, - silencePeriod, - peerScoring, - localMeshLogInterval, - rpcSentTrackerCacheSize, - rpcSentTrackerQueueCacheSize, - rpcSentTrackerNumOfWorkers, - scoreTracerInterval, - gossipSubRPCInspectorNotificationCacheSize, - validationInspectorNumberOfWorkers, - validationInspectorInspectMessageQueueCacheSize, - validationInspectorClusterPrefixedTopicsReceivedCacheSize, - validationInspectorClusterPrefixedTopicsReceivedCacheDecay, - validationInspectorClusterPrefixHardThreshold, - ihaveMaxSampleSize, - metricsInspectorNumberOfWorkers, - metricsInspectorCacheSize, + BuildFlagName(unicastKey, rateLimiterKey, messageRateLimitKey), + BuildFlagName(unicastKey, rateLimiterKey, BandwidthRateLimitKey), + BuildFlagName(unicastKey, rateLimiterKey, BandwidthBurstLimitKey), + BuildFlagName(unicastKey, rateLimiterKey, LockoutDurationKey), + BuildFlagName(unicastKey, rateLimiterKey, DryRunKey), + BuildFlagName(unicastKey, enableStreamProtectionKey), + BuildFlagName(rootResourceManagerPrefix, memoryLimitRatioPrefix), + BuildFlagName(rootResourceManagerPrefix, fileDescriptorsRatioPrefix), + BuildFlagName(connectionManagerKey, highWatermarkKey), + BuildFlagName(connectionManagerKey, lowWatermarkKey), + BuildFlagName(connectionManagerKey, silencePeriodKey), + BuildFlagName(connectionManagerKey, gracePeriodKey), alspDisabled, alspSpamRecordCacheSize, alspSpamRecordQueueSize, @@ -145,18 +77,103 @@ func AllFlagNames() []string { alspSyncEngineBatchRequestBaseProb, alspSyncEngineRangeRequestBaseProb, alspSyncEngineSyncRequestProb, - iwantMaxSampleSize, - iwantMaxMessageIDSampleSize, - ihaveMaxMessageIDSampleSize, - iwantCacheMissThreshold, - controlMessageMaxSampleSize, - iwantDuplicateMsgIDThreshold, - iwantCacheMissCheckSize, - scoringRegistrySlowerDecayThreshold, - scoringRegistryDecayRateDecrement, - rpcMessageMaxSampleSize, - rpcMessageErrorThreshold, - scoringRegistryDecayAdjustInterval, + + BuildFlagName(gossipsubKey, p2pconfig.PeerScoringEnabledKey), + BuildFlagName(gossipsubKey, p2pconfig.RpcTracerKey, p2pconfig.LocalMeshLogIntervalKey), + BuildFlagName(gossipsubKey, p2pconfig.RpcTracerKey, p2pconfig.ScoreTracerIntervalKey), + BuildFlagName(gossipsubKey, p2pconfig.RpcTracerKey, p2pconfig.RPCSentTrackerCacheSizeKey), + BuildFlagName(gossipsubKey, p2pconfig.RpcTracerKey, p2pconfig.RPCSentTrackerQueueCacheSizeKey), + BuildFlagName(gossipsubKey, p2pconfig.RpcTracerKey, p2pconfig.RPCSentTrackerNumOfWorkersKey), + BuildFlagName(gossipsubKey, p2pconfig.RpcInspectorKey, p2pconfig.ValidationConfigKey, p2pconfig.InspectionQueueConfigKey, p2pconfig.NumberOfWorkersKey), + BuildFlagName(gossipsubKey, p2pconfig.RpcInspectorKey, p2pconfig.ValidationConfigKey, p2pconfig.InspectionQueueConfigKey, p2pconfig.QueueSizeKey), + BuildFlagName(gossipsubKey, p2pconfig.RpcInspectorKey, p2pconfig.ValidationConfigKey, p2pconfig.ClusterPrefixedMessageConfigKey, p2pconfig.TrackerCacheSizeKey), + BuildFlagName(gossipsubKey, p2pconfig.RpcInspectorKey, p2pconfig.ValidationConfigKey, p2pconfig.ClusterPrefixedMessageConfigKey, p2pconfig.TrackerCacheDecayKey), + BuildFlagName(gossipsubKey, p2pconfig.RpcInspectorKey, p2pconfig.ValidationConfigKey, p2pconfig.ClusterPrefixedMessageConfigKey, p2pconfig.HardThresholdKey), + BuildFlagName(gossipsubKey, p2pconfig.RpcInspectorKey, p2pconfig.NotificationCacheSizeKey), + + BuildFlagName(gossipsubKey, p2pconfig.RpcInspectorKey, p2pconfig.ValidationConfigKey, p2pconfig.ProcessKey, p2pconfig.InspectionKey, p2pconfig.DisabledKey), + BuildFlagName(gossipsubKey, p2pconfig.RpcInspectorKey, p2pconfig.ValidationConfigKey, p2pconfig.ProcessKey, p2pconfig.InspectionKey, p2pconfig.EnableKey, p2pconfig.GraftKey), + BuildFlagName(gossipsubKey, p2pconfig.RpcInspectorKey, p2pconfig.ValidationConfigKey, p2pconfig.ProcessKey, p2pconfig.InspectionKey, p2pconfig.EnableKey, p2pconfig.PruneKey), + BuildFlagName(gossipsubKey, p2pconfig.RpcInspectorKey, p2pconfig.ValidationConfigKey, p2pconfig.ProcessKey, p2pconfig.InspectionKey, p2pconfig.EnableKey, p2pconfig.IHaveKey), + BuildFlagName(gossipsubKey, p2pconfig.RpcInspectorKey, p2pconfig.ValidationConfigKey, p2pconfig.ProcessKey, p2pconfig.InspectionKey, p2pconfig.EnableKey, p2pconfig.IWantKey), + BuildFlagName(gossipsubKey, p2pconfig.RpcInspectorKey, p2pconfig.ValidationConfigKey, p2pconfig.ProcessKey, p2pconfig.InspectionKey, p2pconfig.EnableKey, p2pconfig.PublishKey), + + BuildFlagName(gossipsubKey, p2pconfig.RpcInspectorKey, p2pconfig.ValidationConfigKey, p2pconfig.ProcessKey, p2pconfig.TruncationKey, p2pconfig.DisabledKey), + BuildFlagName(gossipsubKey, p2pconfig.RpcInspectorKey, p2pconfig.ValidationConfigKey, p2pconfig.ProcessKey, p2pconfig.TruncationKey, p2pconfig.EnableKey, p2pconfig.GraftKey), + BuildFlagName(gossipsubKey, p2pconfig.RpcInspectorKey, p2pconfig.ValidationConfigKey, p2pconfig.ProcessKey, p2pconfig.TruncationKey, p2pconfig.EnableKey, p2pconfig.PruneKey), + BuildFlagName(gossipsubKey, p2pconfig.RpcInspectorKey, p2pconfig.ValidationConfigKey, p2pconfig.ProcessKey, p2pconfig.TruncationKey, p2pconfig.EnableKey, p2pconfig.IHaveKey), + BuildFlagName(gossipsubKey, p2pconfig.RpcInspectorKey, p2pconfig.ValidationConfigKey, p2pconfig.ProcessKey, p2pconfig.TruncationKey, p2pconfig.EnableKey, p2pconfig.IHaveKey, p2pconfig.MessageIDKey), + BuildFlagName(gossipsubKey, p2pconfig.RpcInspectorKey, p2pconfig.ValidationConfigKey, p2pconfig.ProcessKey, p2pconfig.TruncationKey, p2pconfig.EnableKey, p2pconfig.IWantConfigKey), + BuildFlagName(gossipsubKey, p2pconfig.RpcInspectorKey, p2pconfig.ValidationConfigKey, p2pconfig.ProcessKey, p2pconfig.TruncationKey, p2pconfig.EnableKey, p2pconfig.IWantKey, p2pconfig.MessageIDKey), + + BuildFlagName(gossipsubKey, p2pconfig.RpcInspectorKey, p2pconfig.ValidationConfigKey, p2pconfig.IHaveConfigKey, p2pconfig.MessageCountThreshold), + BuildFlagName(gossipsubKey, p2pconfig.RpcInspectorKey, p2pconfig.ValidationConfigKey, p2pconfig.IHaveConfigKey, p2pconfig.MessageIdCountThreshold), + BuildFlagName(gossipsubKey, p2pconfig.RpcInspectorKey, p2pconfig.ValidationConfigKey, p2pconfig.IHaveConfigKey, p2pconfig.DuplicateTopicIdThresholdKey), + BuildFlagName(gossipsubKey, p2pconfig.RpcInspectorKey, p2pconfig.ValidationConfigKey, p2pconfig.IHaveConfigKey, p2pconfig.DuplicateMessageIdThresholdKey), + BuildFlagName(gossipsubKey, p2pconfig.RpcInspectorKey, p2pconfig.ValidationConfigKey, p2pconfig.GraftPruneKey, p2pconfig.DuplicateTopicIdThresholdKey), + BuildFlagName(gossipsubKey, p2pconfig.RpcInspectorKey, p2pconfig.ValidationConfigKey, p2pconfig.GraftPruneKey, p2pconfig.MessageCountThreshold), + BuildFlagName(gossipsubKey, p2pconfig.RpcInspectorKey, p2pconfig.ValidationConfigKey, p2pconfig.IWantConfigKey, p2pconfig.MessageCountThreshold), + BuildFlagName(gossipsubKey, p2pconfig.RpcInspectorKey, p2pconfig.ValidationConfigKey, p2pconfig.IWantConfigKey, p2pconfig.MessageIdCountThreshold), + BuildFlagName(gossipsubKey, p2pconfig.RpcInspectorKey, p2pconfig.ValidationConfigKey, p2pconfig.IWantConfigKey, p2pconfig.CacheMissThresholdKey), + BuildFlagName(gossipsubKey, p2pconfig.RpcInspectorKey, p2pconfig.ValidationConfigKey, p2pconfig.IWantConfigKey, p2pconfig.DuplicateMsgIDThresholdKey), + BuildFlagName(gossipsubKey, p2pconfig.RpcInspectorKey, p2pconfig.ValidationConfigKey, p2pconfig.PublishMessagesConfigKey, p2pconfig.MaxSampleSizeKey), + BuildFlagName(gossipsubKey, p2pconfig.RpcInspectorKey, p2pconfig.ValidationConfigKey, p2pconfig.PublishMessagesConfigKey, p2pconfig.MessageErrorThresholdKey), + BuildFlagName(gossipsubKey, p2pconfig.SubscriptionProviderKey, p2pconfig.UpdateIntervalKey), + BuildFlagName(gossipsubKey, p2pconfig.SubscriptionProviderKey, p2pconfig.CacheSizeKey), + + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.AppSpecificScoreWeightKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.DecayIntervalKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.DecayToZeroKey), + + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.TopicKey, p2pconfig.SkipAtomicValidationKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.TopicKey, p2pconfig.InvalidMessageDeliveriesWeightKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.TopicKey, p2pconfig.InvalidMessageDeliveriesDecayKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.TopicKey, p2pconfig.TimeInMeshQuantumKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.TopicKey, p2pconfig.TopicWeightKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.TopicKey, p2pconfig.MeshMessageDeliveriesDecayKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.TopicKey, p2pconfig.MeshMessageDeliveriesCapKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.TopicKey, p2pconfig.MeshMessageDeliveryThresholdKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.TopicKey, p2pconfig.MeshDeliveriesWeightKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.TopicKey, p2pconfig.MeshMessageDeliveriesWindowKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.TopicKey, p2pconfig.MeshMessageDeliveryActivationKey), + + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.ThresholdsKey, p2pconfig.GossipThresholdKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.ThresholdsKey, p2pconfig.PublishKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.ThresholdsKey, p2pconfig.GraylistThresholdKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.ThresholdsKey, p2pconfig.AcceptPXThresholdKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.ThresholdsKey, p2pconfig.OpportunisticGraftThresholdKey), + + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.BehaviourKey, p2pconfig.BehaviourPenaltyThresholdKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.BehaviourKey, p2pconfig.BehaviourPenaltyWeightKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.BehaviourKey, p2pconfig.BehaviourPenaltyDecayKey), + + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.ProtocolKey, p2pconfig.MaxDebugLogsKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.ProtocolKey, p2pconfig.AppSpecificKey, p2pconfig.MaxAppSpecificKey, p2pconfig.PenaltyKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.ProtocolKey, p2pconfig.AppSpecificKey, p2pconfig.MinAppSpecificKey, p2pconfig.PenaltyKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.ProtocolKey, p2pconfig.AppSpecificKey, p2pconfig.UnknownIdentityKey, p2pconfig.PenaltyKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.ProtocolKey, p2pconfig.AppSpecificKey, p2pconfig.InvalidSubscriptionKey, p2pconfig.PenaltyKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.ProtocolKey, p2pconfig.AppSpecificKey, p2pconfig.MaxAppSpecificKey, p2pconfig.RewardKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.ProtocolKey, p2pconfig.AppSpecificKey, p2pconfig.StakedIdentityKey, p2pconfig.RewardKey), + + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.StartupSilenceDurationKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.AppSpecificScoreRegistryKey, p2pconfig.ScoreUpdateWorkerNumKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.AppSpecificScoreRegistryKey, p2pconfig.ScoreUpdateRequestQueueSizeKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.AppSpecificScoreRegistryKey, p2pconfig.ScoreTTLKey), + + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.SpamRecordCacheKey, p2pconfig.CacheSizeKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.SpamRecordCacheKey, p2pconfig.DecayKey, p2pconfig.PenaltyDecaySlowdownThresholdKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.SpamRecordCacheKey, p2pconfig.DecayKey, p2pconfig.DecayRateReductionFactorKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.SpamRecordCacheKey, p2pconfig.DecayKey, p2pconfig.PenaltyDecayEvaluationPeriodKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.SpamRecordCacheKey, p2pconfig.DecayKey, p2pconfig.MinimumSpamPenaltyDecayFactorKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.SpamRecordCacheKey, p2pconfig.DecayKey, p2pconfig.MaximumSpamPenaltyDecayFactorKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.SpamRecordCacheKey, p2pconfig.DecayKey, p2pconfig.SkipDecayThresholdKey), + + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.MisbehaviourPenaltiesKey, p2pconfig.GraftKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.MisbehaviourPenaltiesKey, p2pconfig.PruneKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.MisbehaviourPenaltiesKey, p2pconfig.IHaveKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.MisbehaviourPenaltiesKey, p2pconfig.IWantKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.MisbehaviourPenaltiesKey, p2pconfig.PublishKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.MisbehaviourPenaltiesKey, p2pconfig.ClusterPrefixedReductionFactorKey), } for _, scope := range []string{systemScope, transientScope, protocolScope, peerScope, peerProtocolScope} { @@ -189,82 +206,66 @@ func InitializeNetworkFlags(flags *pflag.FlagSet, config *Config) { config.DisallowListNotificationCacheSize, "cache size for notification events from disallow list") flags.Duration(peerUpdateInterval, config.PeerUpdateInterval, "how often to refresh the peer connections for the node") - flags.Duration(unicastMessageTimeout, config.UnicastMessageTimeout, "how long a unicast transmission can take to complete") - // unicast manager options - flags.Duration(unicastCreateStreamRetryDelay, - config.UnicastConfig.CreateStreamBackoffDelay, + flags.Duration(BuildFlagName(unicastKey, MessageTimeoutKey), config.Unicast.MessageTimeout, "how long a unicast transmission can take to complete") + flags.Duration(BuildFlagName(unicastKey, unicastManagerKey, createStreamBackoffDelayKey), config.Unicast.UnicastManager.CreateStreamBackoffDelay, "initial backoff delay between failing to establish a connection with another node and retrying, "+ "this delay increases exponentially with the number of subsequent failures to establish a connection.") - flags.Uint64(unicastStreamZeroRetryResetThreshold, - config.UnicastConfig.StreamZeroRetryResetThreshold, + flags.Uint64(BuildFlagName(unicastKey, unicastManagerKey, streamZeroRetryResetThresholdKey), config.Unicast.UnicastManager.StreamZeroRetryResetThreshold, "reset stream creation retry budget from zero to the maximum after consecutive successful streams reach this threshold.") - flags.Uint64(unicastMaxStreamCreationRetryAttemptTimes, config.UnicastConfig.MaxStreamCreationRetryAttemptTimes, "max attempts to create a unicast stream.") - flags.Uint32(unicastDialConfigCacheSize, - config.UnicastConfig.ConfigCacheSize, + flags.Uint64(BuildFlagName(unicastKey, unicastManagerKey, maxStreamCreationRetryAttemptTimesKey), + config.Unicast.UnicastManager.MaxStreamCreationRetryAttemptTimes, + "max attempts to create a unicast stream.") + flags.Uint32(BuildFlagName(unicastKey, unicastManagerKey, configCacheSizeKey), config.Unicast.UnicastManager.ConfigCacheSize, "cache size of the dial config cache, recommended to be big enough to accommodate the entire nodes in the network.") // unicast stream handler rate limits - flags.Int(messageRateLimit, config.UnicastConfig.UnicastRateLimitersConfig.MessageRateLimit, "maximum number of unicast messages that a peer can send per second") - flags.Int(bandwidthRateLimit, - config.UnicastConfig.UnicastRateLimitersConfig.BandwidthRateLimit, + flags.Int(BuildFlagName(unicastKey, rateLimiterKey, messageRateLimitKey), config.Unicast.RateLimiter.MessageRateLimit, "maximum number of unicast messages that a peer can send per second") + flags.Int(BuildFlagName(unicastKey, rateLimiterKey, BandwidthRateLimitKey), config.Unicast.RateLimiter.BandwidthRateLimit, "bandwidth size in bytes a peer is allowed to send via unicast streams per second") - flags.Int(bandwidthBurstLimit, config.UnicastConfig.UnicastRateLimitersConfig.BandwidthBurstLimit, "bandwidth size in bytes a peer is allowed to send at one time") - flags.Duration(lockoutDuration, - config.UnicastConfig.UnicastRateLimitersConfig.LockoutDuration, + flags.Int(BuildFlagName(unicastKey, rateLimiterKey, BandwidthBurstLimitKey), config.Unicast.RateLimiter.BandwidthBurstLimit, "bandwidth size in bytes a peer is allowed to send at one time") + flags.Duration(BuildFlagName(unicastKey, rateLimiterKey, LockoutDurationKey), config.Unicast.RateLimiter.LockoutDuration, "the number of seconds a peer will be forced to wait before being allowed to successful reconnect to the node after being rate limited") - flags.Bool(dryRun, config.UnicastConfig.UnicastRateLimitersConfig.DryRun, "disable peer disconnects and connections gating when rate limiting peers") + flags.Bool(BuildFlagName(unicastKey, rateLimiterKey, DryRunKey), config.Unicast.RateLimiter.DryRun, "disable peer disconnects and connections gating when rate limiting peers") + flags.Bool(BuildFlagName(unicastKey, enableStreamProtectionKey), + config.Unicast.EnableStreamProtection, + "enable stream protection for unicast streams, when enabled, all connections that are being established or have been already established for unicast streams are protected") LoadLibP2PResourceManagerFlags(flags, config) - // connection manager - flags.Int(lowWatermark, config.ConnectionManagerConfig.LowWatermark, "low watermarking for libp2p connection manager") - flags.Int(highWatermark, config.ConnectionManagerConfig.HighWatermark, "high watermarking for libp2p connection manager") - flags.Duration(gracePeriod, config.ConnectionManagerConfig.GracePeriod, "grace period for libp2p connection manager") - flags.Duration(silencePeriod, config.ConnectionManagerConfig.SilencePeriod, "silence period for libp2p connection manager") - flags.Bool(peerScoring, config.GossipSubConfig.PeerScoring, "enabling peer scoring on pubsub network") - flags.Duration(localMeshLogInterval, config.GossipSubConfig.LocalMeshLogInterval, "logging interval for local mesh in gossipsub") - flags.Duration( - scoreTracerInterval, - config.GossipSubConfig.ScoreTracerInterval, + flags.Int(BuildFlagName(connectionManagerKey, lowWatermarkKey), config.ConnectionManager.LowWatermark, "low watermarking for libp2p connection manager") + flags.Int(BuildFlagName(connectionManagerKey, highWatermarkKey), config.ConnectionManager.HighWatermark, "high watermarking for libp2p connection manager") + flags.Duration(BuildFlagName(connectionManagerKey, gracePeriodKey), config.ConnectionManager.GracePeriod, "grace period for libp2p connection manager") + flags.Duration(BuildFlagName(connectionManagerKey, silencePeriodKey), config.ConnectionManager.SilencePeriod, "silence period for libp2p connection manager") + flags.Bool(BuildFlagName(gossipsubKey, p2pconfig.PeerScoringEnabledKey), config.GossipSub.PeerScoringEnabled, "enabling peer scoring on pubsub network") + flags.Duration(BuildFlagName(gossipsubKey, p2pconfig.RpcTracerKey, p2pconfig.LocalMeshLogIntervalKey), + config.GossipSub.RpcTracer.LocalMeshLogInterval, + "logging interval for local mesh in gossipsub tracer") + flags.Duration(BuildFlagName(gossipsubKey, p2pconfig.RpcTracerKey, p2pconfig.ScoreTracerIntervalKey), config.GossipSub.RpcTracer.ScoreTracerInterval, "logging interval for peer score tracer in gossipsub, set to 0 to disable") - flags.Uint32( - rpcSentTrackerCacheSize, - config.GossipSubConfig.RPCSentTrackerCacheSize, + flags.Uint32(BuildFlagName(gossipsubKey, p2pconfig.RpcTracerKey, p2pconfig.RPCSentTrackerCacheSizeKey), config.GossipSub.RpcTracer.RPCSentTrackerCacheSize, "cache size of the rpc sent tracker used by the gossipsub mesh tracer.") - flags.Uint32( - rpcSentTrackerQueueCacheSize, - config.GossipSubConfig.RPCSentTrackerQueueCacheSize, + flags.Uint32(BuildFlagName(gossipsubKey, p2pconfig.RpcTracerKey, p2pconfig.RPCSentTrackerQueueCacheSizeKey), config.GossipSub.RpcTracer.RPCSentTrackerQueueCacheSize, "cache size of the rpc sent tracker worker queue.") - flags.Int( - rpcSentTrackerNumOfWorkers, - config.GossipSubConfig.RpcSentTrackerNumOfWorkers, + flags.Int(BuildFlagName(gossipsubKey, p2pconfig.RpcTracerKey, p2pconfig.RPCSentTrackerNumOfWorkersKey), config.GossipSub.RpcTracer.RpcSentTrackerNumOfWorkers, "number of workers for the rpc sent tracker worker pool.") // gossipsub RPC control message validation limits used for validation configuration and rate limiting - flags.Int(validationInspectorNumberOfWorkers, - config.GossipSubConfig.GossipSubRPCInspectorsConfig.GossipSubRPCValidationInspectorConfigs.NumberOfWorkers, - "number of gossupsub RPC control message validation inspector component workers") - flags.Uint32(validationInspectorInspectMessageQueueCacheSize, - config.GossipSubConfig.GossipSubRPCInspectorsConfig.GossipSubRPCValidationInspectorConfigs.CacheSize, - "cache size for gossipsub RPC validation inspector events worker pool queue.") - flags.Uint32(validationInspectorClusterPrefixedTopicsReceivedCacheSize, - config.GossipSubConfig.GossipSubRPCInspectorsConfig.GossipSubRPCValidationInspectorConfigs.ClusterPrefixedControlMsgsReceivedCacheSize, + flags.Int(BuildFlagName(gossipsubKey, p2pconfig.RpcInspectorKey, p2pconfig.ValidationConfigKey, p2pconfig.InspectionQueueConfigKey, p2pconfig.NumberOfWorkersKey), + config.GossipSub.RpcInspector.Validation.InspectionQueue.NumberOfWorkers, + "number of gossipsub RPC control message validation inspector component workers") + flags.Uint32(BuildFlagName(gossipsubKey, p2pconfig.RpcInspectorKey, p2pconfig.ValidationConfigKey, p2pconfig.InspectionQueueConfigKey, p2pconfig.QueueSizeKey), + config.GossipSub.RpcInspector.Validation.InspectionQueue.Size, + "queue size for gossipsub RPC validation inspector events worker pool queue.") + flags.Uint32(BuildFlagName(gossipsubKey, p2pconfig.RpcInspectorKey, p2pconfig.ValidationConfigKey, p2pconfig.ClusterPrefixedMessageConfigKey, p2pconfig.TrackerCacheSizeKey), + config.GossipSub.RpcInspector.Validation.ClusterPrefixedMessage.ControlMsgsReceivedCacheSize, "cache size for gossipsub RPC validation inspector cluster prefix received tracker.") - flags.Float64(validationInspectorClusterPrefixedTopicsReceivedCacheDecay, - config.GossipSubConfig.GossipSubRPCInspectorsConfig.GossipSubRPCValidationInspectorConfigs.ClusterPrefixedControlMsgsReceivedCacheDecay, + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.RpcInspectorKey, p2pconfig.ValidationConfigKey, p2pconfig.ClusterPrefixedMessageConfigKey, p2pconfig.TrackerCacheDecayKey), + config.GossipSub.RpcInspector.Validation.ClusterPrefixedMessage.ControlMsgsReceivedCacheDecay, "the decay value used to decay cluster prefix received topics received cached counters.") - flags.Float64(validationInspectorClusterPrefixHardThreshold, - config.GossipSubConfig.GossipSubRPCInspectorsConfig.GossipSubRPCValidationInspectorConfigs.ClusterPrefixHardThreshold, + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.RpcInspectorKey, p2pconfig.ValidationConfigKey, p2pconfig.ClusterPrefixedMessageConfigKey, p2pconfig.HardThresholdKey), + config.GossipSub.RpcInspector.Validation.ClusterPrefixedMessage.HardThreshold, "the maximum number of cluster-prefixed control messages allowed to be processed when the active cluster id is unset or a mismatch is detected, exceeding this threshold will result in node penalization by gossipsub.") - // gossipsub RPC control message metrics observer inspector configuration - flags.Int(metricsInspectorNumberOfWorkers, - config.GossipSubConfig.GossipSubRPCInspectorsConfig.GossipSubRPCMetricsInspectorConfigs.NumberOfWorkers, - "cache size for gossipsub RPC metrics inspector events worker pool queue.") - flags.Uint32(metricsInspectorCacheSize, - config.GossipSubConfig.GossipSubRPCInspectorsConfig.GossipSubRPCMetricsInspectorConfigs.CacheSize, - "cache size for gossipsub RPC metrics inspector events worker pool.") // networking event notifications - flags.Uint32(gossipSubRPCInspectorNotificationCacheSize, - config.GossipSubConfig.GossipSubRPCInspectorsConfig.GossipSubRPCInspectorNotificationCacheSize, + flags.Uint32(BuildFlagName(gossipsubKey, p2pconfig.RpcInspectorKey, p2pconfig.NotificationCacheSizeKey), config.GossipSub.RpcInspector.NotificationCacheSize, "cache size for notification events from gossipsub rpc inspector") // application layer spam prevention (alsp) protocol flags.Bool(alspDisabled, config.AlspConfig.DisablePenalty, "disable the penalty mechanism of the alsp protocol. default value (recommended) is false") @@ -281,50 +282,245 @@ func InitializeNetworkFlags(flags *pflag.FlagSet, config *Config) { "base probability of creating a misbehavior report for a range request message") flags.Float32(alspSyncEngineSyncRequestProb, config.AlspConfig.SyncEngine.SyncRequestProb, "probability of creating a misbehavior report for a sync request message") - flags.Float64(scoringRegistrySlowerDecayThreshold, - config.GossipSubConfig.GossipSubScoringRegistryConfig.PenaltyDecaySlowdownThreshold, - "the penalty level at which the decay rate is reduced by --gossipsub-app-specific-penalty-decay-rate-reduction-factor") - flags.Float64(scoringRegistryDecayRateDecrement, - config.GossipSubConfig.GossipSubScoringRegistryConfig.DecayRateReductionFactor, - "defines the value by which the decay rate is decreased every time the penalty is below the --gossipsub-app-specific-penalty-decay-slowdown-threshold.") - flags.Duration(scoringRegistryDecayAdjustInterval, - config.GossipSubConfig.GossipSubScoringRegistryConfig.PenaltyDecayEvaluationPeriod, - "defines the period at which the decay for a spam record is okay to be adjusted.") + flags.Bool(BuildFlagName(gossipsubKey, p2pconfig.RpcInspectorKey, p2pconfig.ValidationConfigKey, p2pconfig.ProcessKey, p2pconfig.InspectionKey, p2pconfig.DisabledKey), + config.GossipSub.RpcInspector.Validation.InspectionProcess.Inspect.Disabled, + "disable rpc inspection for all control message types") + flags.Bool(BuildFlagName(gossipsubKey, p2pconfig.RpcInspectorKey, p2pconfig.ValidationConfigKey, p2pconfig.ProcessKey, p2pconfig.InspectionKey, p2pconfig.EnableKey, p2pconfig.GraftKey), + config.GossipSub.RpcInspector.Validation.InspectionProcess.Inspect.EnableGraft, + "disable graft control message inspection") + flags.Bool(BuildFlagName(gossipsubKey, p2pconfig.RpcInspectorKey, p2pconfig.ValidationConfigKey, p2pconfig.ProcessKey, p2pconfig.InspectionKey, p2pconfig.EnableKey, p2pconfig.PruneKey), + config.GossipSub.RpcInspector.Validation.InspectionProcess.Inspect.EnablePrune, + "disable prune control message inspection") + flags.Bool(BuildFlagName(gossipsubKey, p2pconfig.RpcInspectorKey, p2pconfig.ValidationConfigKey, p2pconfig.ProcessKey, p2pconfig.InspectionKey, p2pconfig.EnableKey, p2pconfig.IHaveKey), + config.GossipSub.RpcInspector.Validation.InspectionProcess.Inspect.EnableIHave, + "disable ihave control message inspection") + flags.Bool(BuildFlagName(gossipsubKey, p2pconfig.RpcInspectorKey, p2pconfig.ValidationConfigKey, p2pconfig.ProcessKey, p2pconfig.InspectionKey, p2pconfig.EnableKey, p2pconfig.IWantKey), + config.GossipSub.RpcInspector.Validation.InspectionProcess.Inspect.EnableIWant, + "disable iwant control message inspection") + flags.Bool(BuildFlagName(gossipsubKey, p2pconfig.RpcInspectorKey, p2pconfig.ValidationConfigKey, p2pconfig.ProcessKey, p2pconfig.InspectionKey, p2pconfig.EnableKey, p2pconfig.PublishKey), + config.GossipSub.RpcInspector.Validation.InspectionProcess.Inspect.EnablePublish, + "disable rpc publish message inspection") - flags.Int(ihaveMaxSampleSize, - config.GossipSubConfig.GossipSubRPCInspectorsConfig.GossipSubRPCValidationInspectorConfigs.IHaveRPCInspectionConfig.MaxSampleSize, - "max number of ihaves to sample when performing validation") - flags.Int(ihaveMaxMessageIDSampleSize, - config.GossipSubConfig.GossipSubRPCInspectorsConfig.GossipSubRPCValidationInspectorConfigs.IHaveRPCInspectionConfig.MaxMessageIDSampleSize, - "max number of message ids to sample when performing validation per ihave") - flags.Int(controlMessageMaxSampleSize, - config.GossipSubConfig.GossipSubRPCInspectorsConfig.GossipSubRPCValidationInspectorConfigs.GraftPruneMessageMaxSampleSize, - "max number of control messages to sample when performing validation on GRAFT and PRUNE message types") - flags.Uint(iwantMaxSampleSize, - config.GossipSubConfig.GossipSubRPCInspectorsConfig.GossipSubRPCValidationInspectorConfigs.IWantRPCInspectionConfig.MaxSampleSize, - "max number of iwants to sample when performing validation") - flags.Int(iwantMaxMessageIDSampleSize, - config.GossipSubConfig.GossipSubRPCInspectorsConfig.GossipSubRPCValidationInspectorConfigs.IWantRPCInspectionConfig.MaxMessageIDSampleSize, - "max number of message ids to sample when performing validation per iwant") - flags.Float64(iwantCacheMissThreshold, - config.GossipSubConfig.GossipSubRPCInspectorsConfig.GossipSubRPCValidationInspectorConfigs.IWantRPCInspectionConfig.CacheMissThreshold, - "max number of iwants to sample when performing validation") - flags.Int(iwantCacheMissCheckSize, - config.GossipSubConfig.GossipSubRPCInspectorsConfig.GossipSubRPCValidationInspectorConfigs.IWantRPCInspectionConfig.CacheMissCheckSize, - "the iWants size at which message id cache misses will be checked") - flags.Float64(iwantDuplicateMsgIDThreshold, - config.GossipSubConfig.GossipSubRPCInspectorsConfig.GossipSubRPCValidationInspectorConfigs.IWantRPCInspectionConfig.DuplicateMsgIDThreshold, - "max allowed duplicate message IDs in a single iWant control message") - - flags.Int(rpcMessageMaxSampleSize, config.GossipSubConfig.GossipSubRPCInspectorsConfig.GossipSubRPCValidationInspectorConfigs.RpcMessageMaxSampleSize, "the max sample size used for RPC message validation. If the total number of RPC messages exceeds this value a sample will be taken but messages will not be truncated") - flags.Int(rpcMessageErrorThreshold, config.GossipSubConfig.GossipSubRPCInspectorsConfig.GossipSubRPCValidationInspectorConfigs.RpcMessageErrorThreshold, "the threshold at which an error will be returned if the number of invalid RPC messages exceeds this value") - flags.Duration( - gossipSubSubscriptionProviderUpdateInterval, config.GossipSubConfig.SubscriptionProviderConfig.SubscriptionUpdateInterval, + flags.Bool(BuildFlagName(gossipsubKey, p2pconfig.RpcInspectorKey, p2pconfig.ValidationConfigKey, p2pconfig.ProcessKey, p2pconfig.TruncationKey, p2pconfig.DisabledKey), + config.GossipSub.RpcInspector.Validation.InspectionProcess.Truncate.Disabled, + "disable rpc truncation for all control message types") + flags.Bool(BuildFlagName(gossipsubKey, p2pconfig.RpcInspectorKey, p2pconfig.ValidationConfigKey, p2pconfig.ProcessKey, p2pconfig.TruncationKey, p2pconfig.EnableKey, p2pconfig.GraftKey), + config.GossipSub.RpcInspector.Validation.InspectionProcess.Truncate.EnableGraft, + "disable graft control message truncation") + flags.Bool(BuildFlagName(gossipsubKey, p2pconfig.RpcInspectorKey, p2pconfig.ValidationConfigKey, p2pconfig.ProcessKey, p2pconfig.TruncationKey, p2pconfig.EnableKey, p2pconfig.PruneKey), + config.GossipSub.RpcInspector.Validation.InspectionProcess.Truncate.EnablePrune, + "disable prune control message truncation") + flags.Bool(BuildFlagName(gossipsubKey, p2pconfig.RpcInspectorKey, p2pconfig.ValidationConfigKey, p2pconfig.ProcessKey, p2pconfig.TruncationKey, p2pconfig.EnableKey, p2pconfig.IHaveKey), + config.GossipSub.RpcInspector.Validation.InspectionProcess.Truncate.EnableIHave, + "disable ihave control message truncation") + flags.Bool(BuildFlagName(gossipsubKey, p2pconfig.RpcInspectorKey, p2pconfig.ValidationConfigKey, p2pconfig.ProcessKey, p2pconfig.TruncationKey, p2pconfig.EnableKey, p2pconfig.IHaveKey, p2pconfig.MessageIDKey), + config.GossipSub.RpcInspector.Validation.InspectionProcess.Truncate.EnableIHaveMessageIds, + "disable ihave message id truncation") + flags.Bool(BuildFlagName(gossipsubKey, p2pconfig.RpcInspectorKey, p2pconfig.ValidationConfigKey, p2pconfig.ProcessKey, p2pconfig.TruncationKey, p2pconfig.EnableKey, p2pconfig.IWantKey), + config.GossipSub.RpcInspector.Validation.InspectionProcess.Truncate.EnableIWant, + "disable iwant control message truncation") + flags.Bool(BuildFlagName(gossipsubKey, p2pconfig.RpcInspectorKey, p2pconfig.ValidationConfigKey, p2pconfig.ProcessKey, p2pconfig.TruncationKey, p2pconfig.EnableKey, p2pconfig.IWantKey, p2pconfig.MessageIDKey), + config.GossipSub.RpcInspector.Validation.InspectionProcess.Truncate.EnableIWantMessageIds, + "disable iwant message id truncation") + + flags.Int(BuildFlagName(gossipsubKey, p2pconfig.RpcInspectorKey, p2pconfig.ValidationConfigKey, p2pconfig.IHaveConfigKey, p2pconfig.MessageCountThreshold), + config.GossipSub.RpcInspector.Validation.IHave.MessageCountThreshold, + "threshold for the number of ihave control messages to accept on a single RPC message, if exceeded the RPC message will be sampled and truncated") + flags.Int(BuildFlagName(gossipsubKey, p2pconfig.RpcInspectorKey, p2pconfig.ValidationConfigKey, p2pconfig.IHaveConfigKey, p2pconfig.MessageIdCountThreshold), + config.GossipSub.RpcInspector.Validation.IHave.MessageIdCountThreshold, + "threshold for the number of message ids on a single ihave control message to accept, if exceeded the RPC message ids will be sampled and truncated") + flags.Int(BuildFlagName(gossipsubKey, p2pconfig.RpcInspectorKey, p2pconfig.ValidationConfigKey, p2pconfig.IHaveConfigKey, p2pconfig.DuplicateTopicIdThresholdKey), + config.GossipSub.RpcInspector.Validation.IHave.DuplicateTopicIdThreshold, + "the max allowed duplicate topic IDs across all ihave control messages in a single RPC message, if exceeded a misbehavior report will be created") + flags.Int(BuildFlagName(gossipsubKey, p2pconfig.RpcInspectorKey, p2pconfig.ValidationConfigKey, p2pconfig.IHaveConfigKey, p2pconfig.DuplicateMessageIdThresholdKey), + config.GossipSub.RpcInspector.Validation.IHave.DuplicateMessageIdThreshold, + "the max allowed duplicate message IDs in a single ihave control message, if exceeded a misbehavior report will be created") + flags.Int(BuildFlagName(gossipsubKey, p2pconfig.RpcInspectorKey, p2pconfig.ValidationConfigKey, p2pconfig.GraftPruneKey, p2pconfig.MessageCountThreshold), + config.GossipSub.RpcInspector.Validation.GraftPrune.MessageCountThreshold, + "threshold for the number of graft or prune control messages to accept on a single RPC message, if exceeded the RPC message will be sampled and truncated") + flags.Uint(BuildFlagName(gossipsubKey, p2pconfig.RpcInspectorKey, p2pconfig.ValidationConfigKey, p2pconfig.IWantConfigKey, p2pconfig.MessageCountThreshold), + config.GossipSub.RpcInspector.Validation.IWant.MessageCountThreshold, + "threshold for the number of iwant control messages to accept on a single RPC message, if exceeded the RPC message will be sampled and truncated") + flags.Int(BuildFlagName(gossipsubKey, p2pconfig.RpcInspectorKey, p2pconfig.ValidationConfigKey, p2pconfig.IWantConfigKey, p2pconfig.MessageIdCountThreshold), + config.GossipSub.RpcInspector.Validation.IWant.MessageIdCountThreshold, + "threshold for the number of message ids on a single iwant control message to accept, if exceeded the RPC message ids will be sampled and truncated") + flags.Int(BuildFlagName(gossipsubKey, p2pconfig.RpcInspectorKey, p2pconfig.ValidationConfigKey, p2pconfig.IWantConfigKey, p2pconfig.CacheMissThresholdKey), + config.GossipSub.RpcInspector.Validation.IWant.CacheMissThreshold, + "max number of cache misses (untracked) allowed in a single iWant control message, if exceeded a misbehavior report will be created") + flags.Int(BuildFlagName(gossipsubKey, p2pconfig.RpcInspectorKey, p2pconfig.ValidationConfigKey, p2pconfig.IWantConfigKey, p2pconfig.DuplicateMsgIDThresholdKey), + config.GossipSub.RpcInspector.Validation.IWant.DuplicateMsgIdThreshold, + "max allowed duplicate message IDs in a single iWant control message, if exceeded a misbehavior report will be created") + flags.Int(BuildFlagName(gossipsubKey, p2pconfig.RpcInspectorKey, p2pconfig.ValidationConfigKey, p2pconfig.PublishMessagesConfigKey, p2pconfig.MaxSampleSizeKey), + config.GossipSub.RpcInspector.Validation.PublishMessages.MaxSampleSize, + "the max sample size for async validation of publish messages, if exceeded the message will be sampled for inspection, but is not truncated") + flags.Int(BuildFlagName(gossipsubKey, p2pconfig.RpcInspectorKey, p2pconfig.ValidationConfigKey, p2pconfig.PublishMessagesConfigKey, p2pconfig.MessageErrorThresholdKey), + config.GossipSub.RpcInspector.Validation.PublishMessages.ErrorThreshold, + "the max number of errors allowed in a (sampled) set of publish messages on a single rpc, if exceeded a misbehavior report will be created") + flags.Int(BuildFlagName(gossipsubKey, p2pconfig.RpcInspectorKey, p2pconfig.ValidationConfigKey, p2pconfig.GraftPruneKey, p2pconfig.DuplicateTopicIdThresholdKey), + config.GossipSub.RpcInspector.Validation.GraftPrune.DuplicateTopicIdThreshold, + "the max allowed duplicate topic IDs across all graft or prune control messages in a single RPC message, if exceeded a misbehavior report will be created") + + flags.Duration(BuildFlagName(gossipsubKey, p2pconfig.SubscriptionProviderKey, p2pconfig.UpdateIntervalKey), + config.GossipSub.SubscriptionProvider.UpdateInterval, "interval for updating the list of subscribed topics for all peers in the gossipsub, recommended value is a few minutes") - flags.Uint32( - gossipSubSubscriptionProviderCacheSize, - config.GossipSubConfig.SubscriptionProviderConfig.CacheSize, + flags.Uint32(BuildFlagName(gossipsubKey, p2pconfig.SubscriptionProviderKey, p2pconfig.CacheSizeKey), + config.GossipSub.SubscriptionProvider.CacheSize, "size of the cache that keeps the list of topics each peer has subscribed to, recommended size is 10x the number of authorized nodes") + + flags.Duration(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.DecayIntervalKey), + config.GossipSub.ScoringParameters.PeerScoring.Internal.DecayInterval, + "interval at which the counters associated with a peer behavior in GossipSub system are decayed, recommended value is one minute") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.AppSpecificScoreWeightKey), + config.GossipSub.ScoringParameters.PeerScoring.Internal.AppSpecificScoreWeight, + "the weight for app-specific scores") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.DecayToZeroKey), + config.GossipSub.ScoringParameters.PeerScoring.Internal.DecayToZero, + "the maximum value below which a peer scoring counter is reset to zero") + + flags.Bool(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.TopicKey, p2pconfig.SkipAtomicValidationKey), + config.GossipSub.ScoringParameters.PeerScoring.Internal.TopicParameters.SkipAtomicValidation, + "the default value for the skip atomic validation flag for topics") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.TopicKey, p2pconfig.InvalidMessageDeliveriesWeightKey), + config.GossipSub.ScoringParameters.PeerScoring.Internal.TopicParameters.InvalidMessageDeliveriesWeight, + "this value is applied to the square of the number of invalid message deliveries on a topic") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.TopicKey, p2pconfig.InvalidMessageDeliveriesDecayKey), + config.GossipSub.ScoringParameters.PeerScoring.Internal.TopicParameters.InvalidMessageDeliveriesDecay, + "the decay factor used to decay the number of invalid message deliveries") + flags.Duration(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.TopicKey, p2pconfig.TimeInMeshQuantumKey), + config.GossipSub.ScoringParameters.PeerScoring.Internal.TopicParameters.TimeInMeshQuantum, + "the time in mesh quantum for the GossipSub scoring system") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.TopicKey, p2pconfig.TopicWeightKey), + config.GossipSub.ScoringParameters.PeerScoring.Internal.TopicParameters.TopicWeight, + "the weight of a topic in the GossipSub scoring system") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.TopicKey, p2pconfig.MeshMessageDeliveriesDecayKey), + config.GossipSub.ScoringParameters.PeerScoring.Internal.TopicParameters.MeshMessageDeliveriesDecay, + "this is applied to the number of actual message deliveries in a topic mesh at each decay interval (i.e., DecayInterval)") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.TopicKey, p2pconfig.MeshMessageDeliveriesCapKey), + config.GossipSub.ScoringParameters.PeerScoring.Internal.TopicParameters.MeshMessageDeliveriesCap, + "The maximum number of actual message deliveries in a topic mesh that is used to calculate the score of a peer in that topic mesh") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.TopicKey, p2pconfig.MeshMessageDeliveryThresholdKey), + config.GossipSub.ScoringParameters.PeerScoring.Internal.TopicParameters.MeshMessageDeliveryThreshold, + "The threshold for the number of actual message deliveries in a topic mesh that is used to calculate the score of a peer in that topic mesh") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.TopicKey, p2pconfig.MeshDeliveriesWeightKey), + config.GossipSub.ScoringParameters.PeerScoring.Internal.TopicParameters.MeshDeliveriesWeight, + "the weight for applying penalty when a peer is under-performing in a topic mesh") + flags.Duration(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.TopicKey, p2pconfig.MeshMessageDeliveriesWindowKey), + config.GossipSub.ScoringParameters.PeerScoring.Internal.TopicParameters.MeshMessageDeliveriesWindow, + "the window size is time interval that we count a delivery of an already seen message towards the score of a peer in a topic mesh. The delivery is counted by GossipSub only if the previous sender of the message is different from the current sender") + flags.Duration(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.TopicKey, p2pconfig.MeshMessageDeliveryActivationKey), + config.GossipSub.ScoringParameters.PeerScoring.Internal.TopicParameters.MeshMessageDeliveryActivation, + "the time interval that we wait for a new peer that joins a topic mesh till start counting the number of actual message deliveries of that peer in that topic mesh") + + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.ThresholdsKey, p2pconfig.GossipThresholdKey), + config.GossipSub.ScoringParameters.PeerScoring.Internal.Thresholds.Gossip, + "the threshold when a peer's penalty drops below this threshold, no gossip is emitted towards that peer and gossip from that peer is ignored") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.ThresholdsKey, p2pconfig.PublishKey), + config.GossipSub.ScoringParameters.PeerScoring.Internal.Thresholds.Publish, + "the threshold when a peer's penalty drops below this threshold, self-published messages are not propagated towards this peer") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.ThresholdsKey, p2pconfig.GraylistThresholdKey), + config.GossipSub.ScoringParameters.PeerScoring.Internal.Thresholds.Graylist, + "the threshold when a peer's penalty drops below this threshold, the peer is graylisted, i.e., incoming RPCs from the peer are ignored") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.ThresholdsKey, p2pconfig.AcceptPXThresholdKey), + config.GossipSub.ScoringParameters.PeerScoring.Internal.Thresholds.AcceptPX, + "the threshold when a peer sends us PX information with a prune, we only accept it and connect to the supplied peers if the originating peer's penalty exceeds this threshold") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.ThresholdsKey, p2pconfig.OpportunisticGraftThresholdKey), + config.GossipSub.ScoringParameters.PeerScoring.Internal.Thresholds.OpportunisticGraft, + "the threshold when the median peer penalty in the mesh drops below this value, the peer may select more peers with penalty above the median to opportunistically graft on the mesh") + + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.BehaviourKey, p2pconfig.BehaviourPenaltyThresholdKey), + config.GossipSub.ScoringParameters.PeerScoring.Internal.Behaviour.PenaltyThreshold, + "the threshold when the behavior of a peer is considered as bad by GossipSub") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.BehaviourKey, p2pconfig.BehaviourPenaltyWeightKey), + config.GossipSub.ScoringParameters.PeerScoring.Internal.Behaviour.PenaltyWeight, + "the weight for applying penalty when a peer misbehavior goes beyond the threshold") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.BehaviourKey, p2pconfig.BehaviourPenaltyDecayKey), + config.GossipSub.ScoringParameters.PeerScoring.Internal.Behaviour.PenaltyDecay, + "the decay interval for the misbehavior counter of a peer. The misbehavior counter is incremented by GossipSub for iHave broken promises or the GRAFT flooding attacks (i.e., each GRAFT received from a remote peer while that peer is on a PRUNE backoff)") + + flags.Uint32(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.ProtocolKey, p2pconfig.MaxDebugLogsKey), + config.GossipSub.ScoringParameters.PeerScoring.Protocol.MaxDebugLogs, + "the max number of debug/trace log events per second. Logs emitted above this threshold are dropped") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.ProtocolKey, p2pconfig.AppSpecificKey, p2pconfig.MaxAppSpecificKey, p2pconfig.PenaltyKey), + config.GossipSub.ScoringParameters.PeerScoring.Protocol.AppSpecificScore.MaxAppSpecificPenalty, + "the maximum penalty for sever offenses that we apply to a remote node score") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.ProtocolKey, p2pconfig.AppSpecificKey, p2pconfig.MinAppSpecificKey, p2pconfig.PenaltyKey), + config.GossipSub.ScoringParameters.PeerScoring.Protocol.AppSpecificScore.MinAppSpecificPenalty, + "the minimum penalty for sever offenses that we apply to a remote node score") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.ProtocolKey, p2pconfig.AppSpecificKey, p2pconfig.UnknownIdentityKey, p2pconfig.PenaltyKey), + config.GossipSub.ScoringParameters.PeerScoring.Protocol.AppSpecificScore.UnknownIdentityPenalty, + "the penalty for unknown identity. It is applied to the peer's score when the peer is not in the identity list") + flags.Float64(BuildFlagName(gossipsubKey, + p2pconfig.ScoreParamsKey, + p2pconfig.PeerScoringKey, + p2pconfig.ProtocolKey, + p2pconfig.AppSpecificKey, + p2pconfig.InvalidSubscriptionKey, + p2pconfig.PenaltyKey), + config.GossipSub.ScoringParameters.PeerScoring.Protocol.AppSpecificScore.InvalidSubscriptionPenalty, + "the penalty for invalid subscription. It is applied to the peer's score when the peer subscribes to a topic that it is not authorized to subscribe to") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.ProtocolKey, p2pconfig.AppSpecificKey, p2pconfig.MaxAppSpecificKey, p2pconfig.RewardKey), + config.GossipSub.ScoringParameters.PeerScoring.Protocol.AppSpecificScore.MaxAppSpecificReward, + "the reward for well-behaving staked peers") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.ProtocolKey, p2pconfig.AppSpecificKey, p2pconfig.StakedIdentityKey, p2pconfig.RewardKey), + config.GossipSub.ScoringParameters.PeerScoring.Protocol.AppSpecificScore.StakedIdentityReward, + "the reward for staking peers") + + flags.Duration(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.StartupSilenceDurationKey), + config.GossipSub.ScoringParameters.ScoringRegistryParameters.StartupSilenceDuration, + "the duration of time, after the node startup, during which the scoring registry remains inactive before penalizing nodes.") + flags.Int(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.AppSpecificScoreRegistryKey, p2pconfig.ScoreUpdateWorkerNumKey), + config.GossipSub.ScoringParameters.ScoringRegistryParameters.AppSpecificScore.ScoreUpdateWorkerNum, + "number of workers for the app specific score update worker pool") + flags.Uint32(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.AppSpecificScoreRegistryKey, p2pconfig.ScoreUpdateRequestQueueSizeKey), + config.GossipSub.ScoringParameters.ScoringRegistryParameters.AppSpecificScore.ScoreUpdateRequestQueueSize, + "size of the app specific score update worker pool queue") + flags.Duration(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.AppSpecificScoreRegistryKey, p2pconfig.ScoreTTLKey), + config.GossipSub.ScoringParameters.ScoringRegistryParameters.AppSpecificScore.ScoreTTL, + "time to live for app specific scores; when expired a new request will be sent to the score update worker pool; till then the expired score will be used") + + flags.Uint32(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.SpamRecordCacheKey, p2pconfig.CacheSizeKey), + config.GossipSub.ScoringParameters.ScoringRegistryParameters.SpamRecordCache.CacheSize, + "size of the spam record cache, recommended size is 10x the number of authorized nodes") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.SpamRecordCacheKey, p2pconfig.DecayKey, p2pconfig.PenaltyDecaySlowdownThresholdKey), + config.GossipSub.ScoringParameters.ScoringRegistryParameters.SpamRecordCache.Decay.PenaltyDecaySlowdownThreshold, + fmt.Sprintf("the penalty level at which the decay rate is reduced by --%s", + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.SpamRecordCacheKey, p2pconfig.DecayKey, p2pconfig.DecayRateReductionFactorKey))) + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.SpamRecordCacheKey, p2pconfig.DecayKey, p2pconfig.DecayRateReductionFactorKey), + config.GossipSub.ScoringParameters.ScoringRegistryParameters.SpamRecordCache.Decay.DecayRateReductionFactor, + fmt.Sprintf("defines the value by which the decay rate is decreased every time the penalty is below the --%s", + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.SpamRecordCacheKey, p2pconfig.DecayKey, p2pconfig.PenaltyDecaySlowdownThresholdKey))) + flags.Duration(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.SpamRecordCacheKey, p2pconfig.DecayKey, p2pconfig.PenaltyDecayEvaluationPeriodKey), + config.GossipSub.ScoringParameters.ScoringRegistryParameters.SpamRecordCache.Decay.PenaltyDecayEvaluationPeriod, + "defines the period at which the decay for a spam record is okay to be adjusted.") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.SpamRecordCacheKey, p2pconfig.DecayKey, p2pconfig.MinimumSpamPenaltyDecayFactorKey), + config.GossipSub.ScoringParameters.ScoringRegistryParameters.SpamRecordCache.Decay.MinimumSpamPenaltyDecayFactor, + "the minimum speed at which the spam penalty value of a peer is decayed") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.SpamRecordCacheKey, p2pconfig.DecayKey, p2pconfig.MaximumSpamPenaltyDecayFactorKey), + config.GossipSub.ScoringParameters.ScoringRegistryParameters.SpamRecordCache.Decay.MaximumSpamPenaltyDecayFactor, + "the maximum rate at which the spam penalty value of a peer decays") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.SpamRecordCacheKey, p2pconfig.DecayKey, p2pconfig.SkipDecayThresholdKey), + config.GossipSub.ScoringParameters.ScoringRegistryParameters.SpamRecordCache.Decay.SkipDecayThreshold, + "the threshold for which when the negative penalty is above this value, the decay function will not be called") + + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.MisbehaviourPenaltiesKey, p2pconfig.GraftKey), + config.GossipSub.ScoringParameters.ScoringRegistryParameters.MisbehaviourPenalties.GraftMisbehaviour, + "the penalty applied to the application specific penalty when a peer conducts a graft misbehaviour") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.MisbehaviourPenaltiesKey, p2pconfig.PruneKey), + config.GossipSub.ScoringParameters.ScoringRegistryParameters.MisbehaviourPenalties.PruneMisbehaviour, + "the penalty applied to the application specific penalty when a peer conducts a prune misbehaviour") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.MisbehaviourPenaltiesKey, p2pconfig.IHaveKey), + config.GossipSub.ScoringParameters.ScoringRegistryParameters.MisbehaviourPenalties.IHaveMisbehaviour, + "the penalty applied to the application specific penalty when a peer conducts a iHave misbehaviour") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.MisbehaviourPenaltiesKey, p2pconfig.IWantKey), + config.GossipSub.ScoringParameters.ScoringRegistryParameters.MisbehaviourPenalties.IWantMisbehaviour, + "the penalty applied to the application specific penalty when a peer conducts a iWant misbehaviour") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.MisbehaviourPenaltiesKey, p2pconfig.PublishKey), + config.GossipSub.ScoringParameters.ScoringRegistryParameters.MisbehaviourPenalties.PublishMisbehaviour, + "the penalty applied to the application specific penalty when a peer conducts a rpc publish message misbehaviour") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.MisbehaviourPenaltiesKey, p2pconfig.ClusterPrefixedReductionFactorKey), + config.GossipSub.ScoringParameters.ScoringRegistryParameters.MisbehaviourPenalties.ClusterPrefixedReductionFactor, + "the factor used to reduce the penalty for control message misbehaviours on cluster prefixed topics") + } // LoadLibP2PResourceManagerFlags loads all CLI flags for the libp2p resource manager configuration on the provided pflag set. @@ -350,7 +546,7 @@ func LoadLibP2PResourceManagerFlags(flags *pflag.FlagSet, config *Config) { // *p2pconf.ResourceScope: the resource scope to load flags for. // *pflag.FlagSet: the pflag set of the Flow node. // *Config: the default network config used to set default values on the flags. -func loadLibP2PResourceManagerFlagsForScope(scope p2pconf.ResourceScope, flags *pflag.FlagSet, override *p2pconf.ResourceManagerOverrideLimit) { +func loadLibP2PResourceManagerFlagsForScope(scope p2pconfig.ResourceScope, flags *pflag.FlagSet, override *p2pconfig.ResourceManagerOverrideLimit) { flags.Int(fmt.Sprintf("%s-%s-%s-%s", rootResourceManagerPrefix, limitsOverridePrefix, scope, inboundStreamLimit), override.StreamsInbound, fmt.Sprintf("the limit on the number of inbound streams at %s scope, 0 means use the default value", scope)) @@ -372,10 +568,10 @@ func loadLibP2PResourceManagerFlagsForScope(scope p2pconf.ResourceScope, flags * } // SetAliases this func sets an aliases for each CLI flag defined for network config overrides to it's corresponding -// full key in the viper config store. This is required because in our config.yml file all configuration values for the +// full key in the viper config store. This is required because in our p2pconfig.yml file all configuration values for the // Flow network are stored one level down on the network-config property. When the default config is bootstrapped viper will -// store these values with the "network-config." prefix on the config key, because we do not want to use CLI flags like --network-config.networking-connection-pruning -// to override default values we instead use cleans flags like --networking-connection-pruning and create an alias from networking-connection-pruning -> network-config.networking-connection-pruning +// store these values with the "network-p2pconfig." prefix on the config key, because we do not want to use CLI flags like --network-p2pconfig.networking-connection-pruning +// to override default values we instead use cleans flags like --networking-connection-pruning and create an alias from networking-connection-pruning -> network-p2pconfig.networking-connection-pruning // to ensure overrides happen as expected. // Args: // *viper.Viper: instance of the viper store to register network config aliases on. @@ -384,24 +580,28 @@ func loadLibP2PResourceManagerFlagsForScope(scope p2pconf.ResourceScope, flags * func SetAliases(conf *viper.Viper) error { m := make(map[string]string) // create map of key -> full pathkey - // ie: "networking-connection-pruning" -> "network-config.networking-connection-pruning" + // ie: "networking-connection-pruning" -> "network-p2pconfig.networking-connection-pruning" for _, key := range conf.AllKeys() { s := strings.Split(key, ".") - // Each networking config has the format of network-config.key1.key2.key3... in the config file + // Each networking config has the format of network-p2pconfig.key1.key2.key3... in the config file // which is translated to key1-key2-key3... in the CLI flags // Hence, we map the CLI flag name to the full key in the config store // TODO: all networking flags should also be prefixed with "network-config". Hence, this - // mapping should be from network-config.key1.key2.key3... to network-config-key1-key2-key3... + // mapping should be from network-p2pconfig.key1.key2.key3... to network-config-key1-key2-key3... m[strings.Join(s[1:], "-")] = key } // each flag name should correspond to exactly one key in our config store after it is loaded with the default config for _, flagName := range AllFlagNames() { fullKey, ok := m[flagName] if !ok { - return fmt.Errorf( - "invalid network configuration missing configuration key flag name %s check config file and cli flags", flagName) + return fmt.Errorf("invalid network configuration missing configuration key flag name %s check config file and cli flags", flagName) } + conf.RegisterAlias(fullKey, flagName) } return nil } + +func BuildFlagName(keys ...string) string { + return strings.Join(keys, "-") +} diff --git a/network/netconf/flags_test.go b/network/netconf/flags_test.go new file mode 100644 index 00000000000..984dc68936e --- /dev/null +++ b/network/netconf/flags_test.go @@ -0,0 +1,51 @@ +package netconf_test + +import ( + "testing" + + "github.com/onflow/flow-go/network/netconf" +) + +// TestBuildFlagName tests the BuildFlagName function for various cases +func TestBuildFlagName(t *testing.T) { + tests := []struct { + name string + keys []string + expected string + }{ + { + name: "Single key", + keys: []string{"key1"}, + expected: "key1", + }, + { + name: "Two keys", + keys: []string{"key1", "key2"}, + expected: "key1-key2", + }, + { + name: "Multiple keys", + keys: []string{"key1", "key2", "key3"}, + expected: "key1-key2-key3", + }, + { + name: "No keys", + keys: []string{}, + expected: "", + }, + { + name: "Key with spaces", + keys: []string{"key 1", "key 2"}, + expected: "key 1-key 2", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := netconf.BuildFlagName(tt.keys...) + if result != tt.expected { + t.Errorf("BuildFlagName(%v) = %v, want %v", tt.keys, result, tt.expected) + } + }) + } +} diff --git a/network/netconf/unicast.go b/network/netconf/unicast.go new file mode 100644 index 00000000000..0dfd31f857f --- /dev/null +++ b/network/netconf/unicast.go @@ -0,0 +1,79 @@ +package netconf + +import "time" + +const ( + rateLimiterKey = "rate-limiter" + unicastManagerKey = "manager" + enableStreamProtectionKey = "enable-stream-protection" + MessageTimeoutKey = "message-timeout" +) + +// Unicast configuration parameters for the unicast protocol. +type Unicast struct { + // RateLimiter configuration for all unicast rate limiters. + RateLimiter RateLimiter `mapstructure:"rate-limiter"` + + // UnicastManager configuration for the unicast manager. The unicast manager is responsible for establishing unicast streams. + UnicastManager UnicastManager `mapstructure:"manager"` + + // EnableStreamProtection enables stream protection for unicast streams. When enabled, all connections that are being established or + // have been already established for unicast streams are protected, meaning that they won't be closed by the connection manager. + // This is useful for preventing the connection manager from closing unicast streams that are being used by the application layer. + // However, it may interfere with the resource manager of libp2p, i.e., the connection manager may not be able to close connections + // that are not being used by the application layer while at the same time the node is running out of resources for new connections. + EnableStreamProtection bool `mapstructure:"enable-stream-protection"` + + MessageTimeout time.Duration `validate:"gt=0s" mapstructure:"message-timeout"` +} + +const ( + DryRunKey = "dry-run" + LockoutDurationKey = "lockout-duration" + messageRateLimitKey = "message-rate-limit" + BandwidthRateLimitKey = "bandwidth-rate-limit" + BandwidthBurstLimitKey = "bandwidth-burst-limit" +) + +// RateLimiter unicast rate limiter configuration for the message and bandwidth rate limiters. +type RateLimiter struct { + // DryRun setting this to true will disable connection disconnects and gating when unicast rate limiters are configured + DryRun bool `mapstructure:"dry-run"` + // LockoutDuration the number of seconds a peer will be forced to wait before being allowed to successfully reconnect to the node + // after being rate limited. + LockoutDuration time.Duration `validate:"gte=0" mapstructure:"lockout-duration"` + // MessageRateLimit amount of unicast messages that can be sent by a peer per second. + MessageRateLimit int `validate:"gte=0" mapstructure:"message-rate-limit"` + // BandwidthRateLimit bandwidth size in bytes a peer is allowed to send via unicast streams per second. + BandwidthRateLimit int `validate:"gte=0" mapstructure:"bandwidth-rate-limit"` + // BandwidthBurstLimit bandwidth size in bytes a peer is allowed to send via unicast streams at once. + BandwidthBurstLimit int `validate:"gte=0" mapstructure:"bandwidth-burst-limit"` +} + +const ( + createStreamBackoffDelayKey = "create-stream-retry-delay" + streamZeroRetryResetThresholdKey = "stream-zero-retry-reset-threshold" + maxStreamCreationRetryAttemptTimesKey = "max-stream-creation-retry-attempt-times" + configCacheSizeKey = "dial-config-cache-size" +) + +// UnicastManager configuration for the unicast manager. The unicast manager is responsible for establishing unicast streams. +type UnicastManager struct { + // CreateStreamBackoffDelay initial delay used in the exponential backoff for create stream retries. + CreateStreamBackoffDelay time.Duration `validate:"gt=0s" mapstructure:"create-stream-retry-delay"` + // StreamZeroRetryResetThreshold is the threshold that determines when to reset the stream creation retry budget to the default value. + // + // For example the default value of 100 means that if the stream creation retry budget is decreased to 0, then it will be reset to default value + // when the number of consecutive successful streams reaches 100. + // + // This is to prevent the retry budget from being reset too frequently, as the retry budget is used to gauge the reliability of the stream creation. + // When the stream creation retry budget is reset to the default value, it means that the stream creation is reliable enough to be trusted again. + // This parameter mandates when the stream creation is reliable enough to be trusted again; i.e., when the number of consecutive successful streams reaches this threshold. + // Note that the counter is reset to 0 when the stream creation fails, so the value of for example 100 means that the stream creation is reliable enough that the recent + // 100 stream creations are all successful. + StreamZeroRetryResetThreshold uint64 `validate:"gt=0" mapstructure:"stream-zero-retry-reset-threshold"` + // MaxStreamCreationRetryAttemptTimes is the maximum number of attempts to be made to create a stream to a remote node over a direct unicast (1:1) connection before we give up. + MaxStreamCreationRetryAttemptTimes uint64 `validate:"gt=1" mapstructure:"max-stream-creation-retry-attempt-times"` + // ConfigCacheSize is the cache size of the dial config cache that keeps the individual dial config for each peer. + ConfigCacheSize uint32 `validate:"gt=0" mapstructure:"dial-config-cache-size"` +} diff --git a/network/p2p/blob/blob_service.go b/network/p2p/blob/blob_service.go index 7f8d06e56c1..459c26913f2 100644 --- a/network/p2p/blob/blob_service.go +++ b/network/p2p/blob/blob_service.go @@ -30,7 +30,7 @@ import ( "github.com/onflow/flow-go/module/irrecoverable" "github.com/onflow/flow-go/module/metrics" "github.com/onflow/flow-go/network" - "github.com/onflow/flow-go/network/p2p/p2plogging" + p2plogging "github.com/onflow/flow-go/network/p2p/logging" "github.com/onflow/flow-go/utils/logging" ipld "github.com/ipfs/go-ipld-format" diff --git a/network/p2p/builder.go b/network/p2p/builder.go index 31a7da024f5..cbc71475511 100644 --- a/network/p2p/builder.go +++ b/network/p2p/builder.go @@ -2,7 +2,6 @@ package p2p import ( "context" - "time" pubsub "github.com/libp2p/go-libp2p-pubsub" "github.com/libp2p/go-libp2p/core/connmgr" @@ -19,11 +18,18 @@ import ( "github.com/onflow/flow-go/module/metrics" flownet "github.com/onflow/flow-go/network" "github.com/onflow/flow-go/network/channels" - "github.com/onflow/flow-go/network/p2p/p2pconf" + p2pconfig "github.com/onflow/flow-go/network/p2p/config" ) type GossipSubFactoryFunc func(context.Context, zerolog.Logger, host.Host, PubSubAdapterConfig, CollectionClusterChangesConsumer) (PubSubAdapter, error) -type CreateNodeFunc func(zerolog.Logger, host.Host, ProtocolPeerCache, PeerManager, *DisallowListCacheConfig) LibP2PNode + +// NodeConstructor is a function that creates a new libp2p node. +// Args: +// - config: configuration for the node +// Returns: +// - LibP2PNode: new libp2p node +// - error: error if any, any returned error is irrecoverable. +type NodeConstructor func(config *NodeConfig) (LibP2PNode, error) type GossipSubAdapterConfigFunc func(*BasePubSubAdapterConfig) PubSubAdapterConfig // GossipSubBuilder provides a builder pattern for creating a GossipSub pubsub system. @@ -55,14 +61,6 @@ type GossipSubBuilder interface { // none EnableGossipSubScoringWithOverride(*PeerScoringConfigOverride) - // SetGossipSubScoreTracerInterval sets the gossipsub score tracer interval of the builder. - // If the gossipsub score tracer interval has already been set, a fatal error is logged. - SetGossipSubScoreTracerInterval(time.Duration) - - // SetGossipSubTracer sets the gossipsub tracer of the builder. - // If the gossipsub tracer has already been set, a fatal error is logged. - SetGossipSubTracer(PubSubTracer) - // SetRoutingSystem sets the routing system of the builder. // If the routing system has already been set, a fatal error is logged. SetRoutingSystem(routing.Routing) @@ -105,7 +103,7 @@ type GossipSubRpcInspectorSuiteFactoryFunc func( irrecoverable.SignalerContext, zerolog.Logger, flow.Identifier, - *p2pconf.GossipSubRPCInspectorsConfig, + *p2pconfig.RpcInspectorParameters, module.GossipSubMetrics, metrics.HeroCacheMetricsFactory, flownet.NetworkingType, @@ -122,20 +120,28 @@ type NodeBuilder interface { SetConnectionGater(ConnectionGater) NodeBuilder SetRoutingSystem(func(context.Context, host.Host) (routing.Routing, error)) NodeBuilder - // EnableGossipSubScoringWithOverride enables peer scoring for the GossipSub pubsub system with the given override. + // OverrideGossipSubScoringConfig overrides the default peer scoring config for the GossipSub protocol. + // Note that it does not enable peer scoring. The peer scoring is enabled directly by setting the `peer-scoring-enabled` flag to true in `default-config.yaml`, or + // by setting the `gossipsub-peer-scoring-enabled` runtime flag to true. This function only overrides the default peer scoring config which takes effect + // only if the peer scoring is enabled (mostly for testing purposes). // Any existing peer scoring config attribute that is set in the override will override the default peer scoring config. // Anything that is left to nil or zero value in the override will be ignored and the default value will be used. // Note: it is not recommended to override the default peer scoring config in production unless you know what you are doing. - // Production Tip: use PeerScoringConfigNoOverride as the argument to this function to enable peer scoring without any override. // Args: // - PeerScoringConfigOverride: override for the peer scoring config- Recommended to use PeerScoringConfigNoOverride for production. // Returns: // none - EnableGossipSubScoringWithOverride(*PeerScoringConfigOverride) NodeBuilder - SetCreateNode(CreateNodeFunc) NodeBuilder + OverrideGossipSubScoringConfig(*PeerScoringConfigOverride) NodeBuilder + + // OverrideNodeConstructor overrides the default node constructor, i.e., the function that creates a new libp2p node. + // The purpose of override is to allow the node to provide a custom node constructor for sake of testing or experimentation. + // It is NOT recommended to override the default node constructor in production unless you know what you are doing. + // Args: + // - NodeConstructor: custom node constructor + // Returns: + // none + OverrideNodeConstructor(NodeConstructor) NodeBuilder SetGossipSubFactory(GossipSubFactoryFunc, GossipSubAdapterConfigFunc) NodeBuilder - SetGossipSubTracer(PubSubTracer) NodeBuilder - SetGossipSubScoreTracerInterval(time.Duration) NodeBuilder OverrideDefaultRpcInspectorSuiteFactory(GossipSubRpcInspectorSuiteFactoryFunc) NodeBuilder Build() (LibP2PNode, error) } @@ -155,16 +161,21 @@ type PeerScoringConfigOverride struct { // Override criteria: if the function is not nil, it will override the default application specific score parameters. // If the function is nil, the default application specific score parameters are used. AppSpecificScoreParams func(peer.ID) float64 +} - // DecayInterval is the interval over which we decay the effect of past behavior, so that - // a good or bad behavior will not have a permanent effect on the penalty. It is also the interval - // that GossipSub uses to refresh the scores of all peers. - // Override criteria: if the value is not zero, it will override the default decay interval. - // If the value is zero, the default decay interval is used. - DecayInterval time.Duration +// NodeParameters are the numerical values that are used to configure the libp2p node. +type NodeParameters struct { + EnableProtectedStreams bool `validate:"required"` } -// PeerScoringConfigNoOverride is a default peer scoring configuration for a GossipSub pubsub system. -// It is set to nil, which means that no override is done to the default peer scoring configuration. -// It is the recommended way to use the default peer scoring configuration. -var PeerScoringConfigNoOverride = (*PeerScoringConfigOverride)(nil) +// NodeConfig is the configuration for the libp2p node, it contains the parameters as well as the essential components for setting up the node. +// It is used to create a new libp2p node. +type NodeConfig struct { + Parameters *NodeParameters `validate:"required"` + // logger used to provide logging + Logger zerolog.Logger `validate:"required"` + // reference to the libp2p host (https://godoc.org/github.com/libp2p/go-libp2p/core/host) + Host host.Host `validate:"required"` + PeerManager PeerManager + DisallowListCacheCfg *DisallowListCacheConfig `validate:"required"` +} diff --git a/network/p2p/p2pbuilder/config/config.go b/network/p2p/builder/config/config.go similarity index 97% rename from network/p2p/p2pbuilder/config/config.go rename to network/p2p/builder/config/config.go index dc6bb447e88..63d819d0248 100644 --- a/network/p2p/p2pbuilder/config/config.go +++ b/network/p2p/builder/config/config.go @@ -1,4 +1,4 @@ -package p2pconfig +package p2pbuilderconfig import ( "time" @@ -9,7 +9,7 @@ import ( // UnicastConfig configuration parameters for the unicast protocol. type UnicastConfig struct { - netconf.UnicastConfig + netconf.Unicast // RateLimiterDistributor distributor that distributes notifications whenever a peer is rate limited to all consumers. RateLimiterDistributor p2p.UnicastRateLimiterDistributor diff --git a/network/p2p/p2pbuilder/config/metrics.go b/network/p2p/builder/config/metrics.go similarity index 96% rename from network/p2p/p2pbuilder/config/metrics.go rename to network/p2p/builder/config/metrics.go index 1283035e5a6..510bd65e0bf 100644 --- a/network/p2p/p2pbuilder/config/metrics.go +++ b/network/p2p/builder/config/metrics.go @@ -1,4 +1,4 @@ -package p2pconfig +package p2pbuilderconfig import ( "github.com/onflow/flow-go/module" diff --git a/network/p2p/p2pbuilder/gossipsub/gossipSubBuilder.go b/network/p2p/builder/gossipsub/gossipSubBuilder.go similarity index 67% rename from network/p2p/p2pbuilder/gossipsub/gossipSubBuilder.go rename to network/p2p/builder/gossipsub/gossipSubBuilder.go index 8237944f13f..7c0b7508cb4 100644 --- a/network/p2p/p2pbuilder/gossipsub/gossipSubBuilder.go +++ b/network/p2p/builder/gossipsub/gossipSubBuilder.go @@ -3,7 +3,6 @@ package gossipsubbuilder import ( "context" "fmt" - "time" pubsub "github.com/libp2p/go-libp2p-pubsub" "github.com/libp2p/go-libp2p/core/host" @@ -17,13 +16,12 @@ import ( "github.com/onflow/flow-go/module/metrics" "github.com/onflow/flow-go/network" "github.com/onflow/flow-go/network/p2p" + p2pbuilderconfig "github.com/onflow/flow-go/network/p2p/builder/config" + inspectorbuilder "github.com/onflow/flow-go/network/p2p/builder/inspector" + p2pconfig "github.com/onflow/flow-go/network/p2p/config" "github.com/onflow/flow-go/network/p2p/distributor" - "github.com/onflow/flow-go/network/p2p/inspector" "github.com/onflow/flow-go/network/p2p/inspector/validation" - p2pconfig "github.com/onflow/flow-go/network/p2p/p2pbuilder/config" - inspectorbuilder "github.com/onflow/flow-go/network/p2p/p2pbuilder/inspector" - "github.com/onflow/flow-go/network/p2p/p2pconf" - "github.com/onflow/flow-go/network/p2p/p2pnode" + p2pnode "github.com/onflow/flow-go/network/p2p/node" "github.com/onflow/flow-go/network/p2p/scoring" "github.com/onflow/flow-go/network/p2p/tracer" "github.com/onflow/flow-go/network/p2p/utils" @@ -32,26 +30,22 @@ import ( // The Builder struct is used to configure and create a new GossipSub pubsub system. type Builder struct { - networkType network.NetworkingType - sporkId flow.Identifier - logger zerolog.Logger - metricsCfg *p2pconfig.MetricsConfig - h host.Host - subscriptionFilter pubsub.SubscriptionFilter - gossipSubFactory p2p.GossipSubFactoryFunc - gossipSubConfigFunc p2p.GossipSubAdapterConfigFunc - gossipSubPeerScoring bool // whether to enable gossipsub peer scoring - scoringRegistryConfig p2pconf.GossipSubScoringRegistryConfig - gossipSubScoreTracerInterval time.Duration // the interval at which the gossipsub score tracer logs the peer scores. + networkType network.NetworkingType + sporkId flow.Identifier + logger zerolog.Logger + metricsCfg *p2pbuilderconfig.MetricsConfig + h host.Host + subscriptionFilter pubsub.SubscriptionFilter + gossipSubFactory p2p.GossipSubFactoryFunc + gossipSubConfigFunc p2p.GossipSubAdapterConfigFunc // gossipSubTracer is a callback interface that is called by the gossipsub implementation upon // certain events. Currently, we use it to log and observe the local mesh of the node. - gossipSubTracer p2p.PubSubTracer - scoreOptionConfig *scoring.ScoreOptionConfig - subscriptionProviderParam *p2pconf.SubscriptionProviderParameters - idProvider module.IdentityProvider - routingSystem routing.Routing - rpcInspectorConfig *p2pconf.GossipSubRPCInspectorsConfig - rpcInspectorSuiteFactory p2p.GossipSubRpcInspectorSuiteFactoryFunc + gossipSubTracer p2p.PubSubTracer + scoreOptionConfig *scoring.ScoreOptionConfig + idProvider module.IdentityProvider + routingSystem routing.Routing + rpcInspectorSuiteFactory p2p.GossipSubRpcInspectorSuiteFactoryFunc + gossipSubCfg *p2pconfig.GossipSubParameters } var _ p2p.GossipSubBuilder = (*Builder)(nil) @@ -103,7 +97,7 @@ func (g *Builder) SetGossipSubConfigFunc(gossipSubConfigFunc p2p.GossipSubAdapte // Returns: // none func (g *Builder) EnableGossipSubScoringWithOverride(override *p2p.PeerScoringConfigOverride) { - g.gossipSubPeerScoring = true // TODO: we should enable peer scoring by default. + g.gossipSubCfg.PeerScoringEnabled = true // TODO: we should enable peer scoring by default. if override == nil { return } @@ -122,33 +116,6 @@ func (g *Builder) EnableGossipSubScoringWithOverride(override *p2p.PeerScoringCo g.scoreOptionConfig.OverrideTopicScoreParams(topic, params) } } - if override.DecayInterval > 0 { - g.logger.Warn(). - Str(logging.KeyNetworkingSecurity, "true"). - Dur("decay_interval", override.DecayInterval). - Msg("overriding decay interval for gossipsub") - g.scoreOptionConfig.OverrideDecayInterval(override.DecayInterval) - } -} - -// SetGossipSubScoreTracerInterval sets the gossipsub score tracer interval of the builder. -// If the gossipsub score tracer interval has already been set, a fatal error is logged. -func (g *Builder) SetGossipSubScoreTracerInterval(gossipSubScoreTracerInterval time.Duration) { - if g.gossipSubScoreTracerInterval != time.Duration(0) { - g.logger.Fatal().Msg("gossipsub score tracer interval has already been set") - return - } - g.gossipSubScoreTracerInterval = gossipSubScoreTracerInterval -} - -// SetGossipSubTracer sets the gossipsub tracer of the builder. -// If the gossipsub tracer has already been set, a fatal error is logged. -func (g *Builder) SetGossipSubTracer(gossipSubTracer p2p.PubSubTracer) { - if g.gossipSubTracer != nil { - g.logger.Fatal().Msg("gossipsub tracer has already been set") - return - } - g.gossipSubTracer = gossipSubTracer } // SetRoutingSystem sets the routing system of the builder. @@ -179,33 +146,42 @@ func (g *Builder) OverrideDefaultRpcInspectorSuiteFactory(factory p2p.GossipSubR // Returns: // - a new gossipsub builder. // Note: the builder is not thread-safe. It should only be used in the main thread. -func NewGossipSubBuilder( - logger zerolog.Logger, - metricsCfg *p2pconfig.MetricsConfig, +func NewGossipSubBuilder(logger zerolog.Logger, + metricsCfg *p2pbuilderconfig.MetricsConfig, + gossipSubCfg *p2pconfig.GossipSubParameters, networkType network.NetworkingType, sporkId flow.Identifier, - idProvider module.IdentityProvider, - scoringRegistryConfig p2pconf.GossipSubScoringRegistryConfig, - rpcInspectorConfig *p2pconf.GossipSubRPCInspectorsConfig, - subscriptionProviderPrams *p2pconf.SubscriptionProviderParameters, - rpcTracker p2p.RpcControlTracking) *Builder { + idProvider module.IdentityProvider) *Builder { lg := logger.With(). Str("component", "gossipsub"). Str("network-type", networkType.String()). Logger() + meshTracerCfg := &tracer.GossipSubMeshTracerConfig{ + Logger: lg, + Metrics: metricsCfg.Metrics, + IDProvider: idProvider, + LoggerInterval: gossipSubCfg.RpcTracer.LocalMeshLogInterval, + RpcSentTrackerCacheSize: gossipSubCfg.RpcTracer.RPCSentTrackerCacheSize, + RpcSentTrackerWorkerQueueCacheSize: gossipSubCfg.RpcTracer.RPCSentTrackerQueueCacheSize, + RpcSentTrackerNumOfWorkers: gossipSubCfg.RpcTracer.RpcSentTrackerNumOfWorkers, + HeroCacheMetricsFactory: metricsCfg.HeroCacheFactory, + NetworkingType: networkType, + } + meshTracer := tracer.NewGossipSubMeshTracer(meshTracerCfg) + b := &Builder{ - logger: lg, - metricsCfg: metricsCfg, - sporkId: sporkId, - networkType: networkType, - idProvider: idProvider, - gossipSubFactory: defaultGossipSubFactory(), - gossipSubConfigFunc: defaultGossipSubAdapterConfig(), - scoreOptionConfig: scoring.NewScoreOptionConfig(lg, idProvider), - rpcInspectorConfig: rpcInspectorConfig, - rpcInspectorSuiteFactory: defaultInspectorSuite(rpcTracker), - subscriptionProviderParam: subscriptionProviderPrams, + logger: lg, + metricsCfg: metricsCfg, + sporkId: sporkId, + networkType: networkType, + idProvider: idProvider, + gossipSubFactory: defaultGossipSubFactory(), + gossipSubConfigFunc: defaultGossipSubAdapterConfig(), + scoreOptionConfig: scoring.NewScoreOptionConfig(lg, gossipSubCfg.ScoringParameters, metricsCfg.HeroCacheFactory, idProvider, networkType), + rpcInspectorSuiteFactory: defaultInspectorSuite(meshTracer), + gossipSubTracer: meshTracer, + gossipSubCfg: gossipSubCfg, } return b @@ -214,12 +190,7 @@ func NewGossipSubBuilder( // defaultGossipSubFactory returns the default gossipsub factory function. It is used to create the default gossipsub factory. // Note: always use the default gossipsub factory function to create the gossipsub factory (unless you know what you are doing). func defaultGossipSubFactory() p2p.GossipSubFactoryFunc { - return func( - ctx context.Context, - logger zerolog.Logger, - h host.Host, - cfg p2p.PubSubAdapterConfig, - clusterChangeConsumer p2p.CollectionClusterChangesConsumer) (p2p.PubSubAdapter, error) { + return func(ctx context.Context, logger zerolog.Logger, h host.Host, cfg p2p.PubSubAdapterConfig, clusterChangeConsumer p2p.CollectionClusterChangesConsumer) (p2p.PubSubAdapter, error) { return p2pnode.NewGossipSubAdapter(ctx, logger, h, cfg, clusterChangeConsumer) } } @@ -235,37 +206,26 @@ func defaultGossipSubAdapterConfig() p2p.GossipSubAdapterConfigFunc { // defaultInspectorSuite returns the default inspector suite factory function. It is used to create the default inspector suite. // Inspector suite is utilized to inspect the incoming gossipsub rpc messages from different perspectives. // Note: always use the default inspector suite factory function to create the inspector suite (unless you know what you are doing). +// todo: this function can be simplified. func defaultInspectorSuite(rpcTracker p2p.RpcControlTracking) p2p.GossipSubRpcInspectorSuiteFactoryFunc { - return func( - ctx irrecoverable.SignalerContext, + return func(ctx irrecoverable.SignalerContext, logger zerolog.Logger, sporkId flow.Identifier, - inspectorCfg *p2pconf.GossipSubRPCInspectorsConfig, + inspectorCfg *p2pconfig.RpcInspectorParameters, gossipSubMetrics module.GossipSubMetrics, heroCacheMetricsFactory metrics.HeroCacheMetricsFactory, networkType network.NetworkingType, idProvider module.IdentityProvider, topicProvider func() p2p.TopicProvider) (p2p.GossipSubInspectorSuite, error) { - metricsInspector := inspector.NewControlMsgMetricsInspector( - logger, - p2pnode.NewGossipSubControlMessageMetrics(gossipSubMetrics, logger), - inspectorCfg.GossipSubRPCMetricsInspectorConfigs.NumberOfWorkers, - []queue.HeroStoreConfigOption{ - queue.WithHeroStoreSizeLimit(inspectorCfg.GossipSubRPCMetricsInspectorConfigs.CacheSize), - queue.WithHeroStoreCollector( - metrics.GossipSubRPCMetricsObserverInspectorQueueMetricFactory( - heroCacheMetricsFactory, - networkType)), - }...) - notificationDistributor := distributor.DefaultGossipSubInspectorNotificationDistributor( - logger, []queue.HeroStoreConfigOption{ - queue.WithHeroStoreSizeLimit(inspectorCfg.GossipSubRPCInspectorNotificationCacheSize), - queue.WithHeroStoreCollector(metrics.RpcInspectorNotificationQueueMetricFactory(heroCacheMetricsFactory, networkType))}...) + + notificationDistributor := distributor.DefaultGossipSubInspectorNotificationDistributor(logger, []queue.HeroStoreConfigOption{ + queue.WithHeroStoreSizeLimit(inspectorCfg.NotificationCacheSize), + queue.WithHeroStoreCollector(metrics.RpcInspectorNotificationQueueMetricFactory(heroCacheMetricsFactory, networkType))}...) params := &validation.InspectorParams{ Logger: logger, SporkID: sporkId, - Config: &inspectorCfg.GossipSubRPCValidationInspectorConfigs, + Config: &inspectorCfg.Validation, Distributor: notificationDistributor, HeroCacheMetricsFactory: heroCacheMetricsFactory, IdProvider: idProvider, @@ -278,7 +238,7 @@ func defaultInspectorSuite(rpcTracker p2p.RpcControlTracking) p2p.GossipSubRpcIn if err != nil { return nil, fmt.Errorf("failed to create new control message valiadation inspector: %w", err) } - return inspectorbuilder.NewGossipSubInspectorSuite(metricsInspector, rpcValidationInspector, notificationDistributor), nil + return inspectorbuilder.NewGossipSubInspectorSuite(rpcValidationInspector, notificationDistributor), nil } } @@ -297,10 +257,9 @@ func (g *Builder) Build(ctx irrecoverable.SignalerContext) (p2p.PubSubAdapter, e // before it is created). var gossipSub p2p.PubSubAdapter - gossipSubConfigs := g.gossipSubConfigFunc( - &p2p.BasePubSubAdapterConfig{ - MaxMessageSize: p2pnode.DefaultMaxPubSubMsgSize, - }) + gossipSubConfigs := g.gossipSubConfigFunc(&p2p.BasePubSubAdapterConfig{ + MaxMessageSize: p2pnode.DefaultMaxPubSubMsgSize, + }) gossipSubConfigs.WithMessageIdFunction(utils.MessageID) if g.routingSystem != nil { @@ -311,11 +270,10 @@ func (g *Builder) Build(ctx irrecoverable.SignalerContext) (p2p.PubSubAdapter, e gossipSubConfigs.WithSubscriptionFilter(g.subscriptionFilter) } - inspectorSuite, err := g.rpcInspectorSuiteFactory( - ctx, + inspectorSuite, err := g.rpcInspectorSuiteFactory(ctx, g.logger, g.sporkId, - g.rpcInspectorConfig, + &g.gossipSubCfg.RpcInspector, g.metricsCfg.Metrics, g.metricsCfg.HeroCacheFactory, g.networkType, @@ -330,7 +288,8 @@ func (g *Builder) Build(ctx irrecoverable.SignalerContext) (p2p.PubSubAdapter, e var scoreOpt *scoring.ScoreOption var scoreTracer p2p.PeerScoreTracer - if g.gossipSubPeerScoring { + // currently, peer scoring is not supported for public networks. + if g.gossipSubCfg.PeerScoringEnabled && g.networkType != network.PublicNetwork { // wires the gossipsub score option to the subscription provider. subscriptionProvider, err := scoring.NewSubscriptionProvider(&scoring.SubscriptionProviderConfig{ Logger: g.logger, @@ -341,19 +300,23 @@ func (g *Builder) Build(ctx irrecoverable.SignalerContext) (p2p.PubSubAdapter, e return gossipSub }, IdProvider: g.idProvider, - Params: g.subscriptionProviderParam, + Params: &g.gossipSubCfg.SubscriptionProvider, HeroCacheMetricsFactory: g.metricsCfg.HeroCacheFactory, + NetworkingType: g.networkType, }) if err != nil { return nil, fmt.Errorf("could not create subscription provider: %w", err) } g.scoreOptionConfig.SetRegisterNotificationConsumerFunc(inspectorSuite.AddInvalidControlMessageConsumer) - scoreOpt = scoring.NewScoreOption(g.scoringRegistryConfig, g.scoreOptionConfig, subscriptionProvider) + scoreOpt, err = scoring.NewScoreOption(g.scoreOptionConfig, subscriptionProvider) + if err != nil { + return nil, fmt.Errorf("could not create gossipsub score option: %w", err) + } gossipSubConfigs.WithScoreOption(scoreOpt) - if g.gossipSubScoreTracerInterval > 0 { - scoreTracer = tracer.NewGossipSubScoreTracer(g.logger, g.idProvider, g.metricsCfg.Metrics, g.gossipSubScoreTracerInterval) + if g.gossipSubCfg.RpcTracer.ScoreTracerInterval > 0 { + scoreTracer = tracer.NewGossipSubScoreTracer(g.logger, g.idProvider, g.metricsCfg.Metrics, g.gossipSubCfg.RpcTracer.ScoreTracerInterval) gossipSubConfigs.WithScoreTracer(scoreTracer) } } else { diff --git a/network/p2p/p2pbuilder/inspector/aggregate.go b/network/p2p/builder/inspector/aggregate.go similarity index 100% rename from network/p2p/p2pbuilder/inspector/aggregate.go rename to network/p2p/builder/inspector/aggregate.go diff --git a/network/p2p/p2pbuilder/inspector/suite.go b/network/p2p/builder/inspector/suite.go similarity index 94% rename from network/p2p/p2pbuilder/inspector/suite.go rename to network/p2p/builder/inspector/suite.go index 8fe6a1c4547..b1b35d8bc2c 100644 --- a/network/p2p/p2pbuilder/inspector/suite.go +++ b/network/p2p/builder/inspector/suite.go @@ -8,7 +8,6 @@ import ( "github.com/onflow/flow-go/module/component" "github.com/onflow/flow-go/module/irrecoverable" "github.com/onflow/flow-go/network/p2p" - "github.com/onflow/flow-go/network/p2p/inspector" "github.com/onflow/flow-go/network/p2p/inspector/validation" ) @@ -21,6 +20,7 @@ type GossipSubInspectorSuite struct { ctrlMsgInspectDistributor p2p.GossipSubInspectorNotifDistributor } +// TODO: this can be simplified as there is no more need for the aggregated inspector. var _ p2p.GossipSubInspectorSuite = (*GossipSubInspectorSuite)(nil) // NewGossipSubInspectorSuite creates a new GossipSubInspectorSuite. @@ -36,10 +36,10 @@ var _ p2p.GossipSubInspectorSuite = (*GossipSubInspectorSuite)(nil) // regarding gossipsub control messages is detected. // Returns: // - the new GossipSubInspectorSuite. -func NewGossipSubInspectorSuite(metricsInspector *inspector.ControlMsgMetricsInspector, +func NewGossipSubInspectorSuite( validationInspector *validation.ControlMsgValidationInspector, ctrlMsgInspectDistributor p2p.GossipSubInspectorNotifDistributor) *GossipSubInspectorSuite { - inspectors := []p2p.GossipSubRPCInspector{metricsInspector, validationInspector} + inspectors := []p2p.GossipSubRPCInspector{validationInspector} s := &GossipSubInspectorSuite{ ctrlMsgInspectDistributor: ctrlMsgInspectDistributor, validationInspector: validationInspector, diff --git a/network/p2p/p2pbuilder/libp2pNodeBuilder.go b/network/p2p/builder/libp2pNodeBuilder.go similarity index 67% rename from network/p2p/p2pbuilder/libp2pNodeBuilder.go rename to network/p2p/builder/libp2pNodeBuilder.go index 3bba1a0b10d..7d9709ae457 100644 --- a/network/p2p/p2pbuilder/libp2pNodeBuilder.go +++ b/network/p2p/builder/libp2pNodeBuilder.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "net" - "time" "github.com/libp2p/go-libp2p" pubsub "github.com/libp2p/go-libp2p-pubsub" @@ -19,9 +18,9 @@ import ( "github.com/libp2p/go-libp2p/p2p/transport/tcp" "github.com/multiformats/go-multiaddr" madns "github.com/multiformats/go-multiaddr-dns" + fcrypto "github.com/onflow/crypto" "github.com/rs/zerolog" - fcrypto "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/module" "github.com/onflow/flow-go/module/component" @@ -30,16 +29,15 @@ import ( flownet "github.com/onflow/flow-go/network" "github.com/onflow/flow-go/network/netconf" "github.com/onflow/flow-go/network/p2p" + p2pbuilderconfig "github.com/onflow/flow-go/network/p2p/builder/config" + gossipsubbuilder "github.com/onflow/flow-go/network/p2p/builder/gossipsub" + p2pconfig "github.com/onflow/flow-go/network/p2p/config" "github.com/onflow/flow-go/network/p2p/connection" "github.com/onflow/flow-go/network/p2p/dht" "github.com/onflow/flow-go/network/p2p/keyutils" - p2pconfig "github.com/onflow/flow-go/network/p2p/p2pbuilder/config" - gossipsubbuilder "github.com/onflow/flow-go/network/p2p/p2pbuilder/gossipsub" - "github.com/onflow/flow-go/network/p2p/p2pconf" - "github.com/onflow/flow-go/network/p2p/p2plogging" - "github.com/onflow/flow-go/network/p2p/p2pnode" + p2plogging "github.com/onflow/flow-go/network/p2p/logging" + p2pnode "github.com/onflow/flow-go/network/p2p/node" "github.com/onflow/flow-go/network/p2p/subscription" - "github.com/onflow/flow-go/network/p2p/tracer" "github.com/onflow/flow-go/network/p2p/unicast" unicastcache "github.com/onflow/flow-go/network/p2p/unicast/cache" "github.com/onflow/flow-go/network/p2p/unicast/protocols" @@ -60,56 +58,51 @@ type LibP2PNodeBuilder struct { address string networkKey fcrypto.PrivateKey logger zerolog.Logger - metricsConfig *p2pconfig.MetricsConfig + metricsConfig *p2pbuilderconfig.MetricsConfig basicResolver madns.BasicResolver resourceManager network.ResourceManager - resourceManagerCfg *p2pconf.ResourceManagerConfig + resourceManagerCfg *p2pconfig.ResourceManagerConfig connManager connmgr.ConnManager connGater p2p.ConnectionGater routingFactory func(context.Context, host.Host) (routing.Routing, error) - peerManagerConfig *p2pconfig.PeerManagerConfig - createNode p2p.CreateNodeFunc - gossipSubTracer p2p.PubSubTracer + peerManagerConfig *p2pbuilderconfig.PeerManagerConfig + createNode p2p.NodeConstructor disallowListCacheCfg *p2p.DisallowListCacheConfig - unicastConfig *p2pconfig.UnicastConfig + unicastConfig *p2pbuilderconfig.UnicastConfig networkingType flownet.NetworkingType // whether the node is running in private (staked) or public (unstaked) network } func NewNodeBuilder( logger zerolog.Logger, - metricsConfig *p2pconfig.MetricsConfig, + gossipSubCfg *p2pconfig.GossipSubParameters, + metricsConfig *p2pbuilderconfig.MetricsConfig, networkingType flownet.NetworkingType, address string, networkKey fcrypto.PrivateKey, sporkId flow.Identifier, idProvider module.IdentityProvider, - scoringRegistryConfig p2pconf.GossipSubScoringRegistryConfig, - rCfg *p2pconf.ResourceManagerConfig, - gossipCfg *p2pconf.GossipSubConfig, - peerManagerConfig *p2pconfig.PeerManagerConfig, + rCfg *p2pconfig.ResourceManagerConfig, + peerManagerConfig *p2pbuilderconfig.PeerManagerConfig, disallowListCacheCfg *p2p.DisallowListCacheConfig, - rpcTracker p2p.RpcControlTracking, - unicastConfig *p2pconfig.UnicastConfig) *LibP2PNodeBuilder { + unicastConfig *p2pbuilderconfig.UnicastConfig, +) *LibP2PNodeBuilder { return &LibP2PNodeBuilder{ logger: logger, sporkId: sporkId, address: address, networkKey: networkKey, - createNode: DefaultCreateNodeFunc, + createNode: func(cfg *p2p.NodeConfig) (p2p.LibP2PNode, error) { return p2pnode.NewNode(cfg) }, metricsConfig: metricsConfig, resourceManagerCfg: rCfg, disallowListCacheCfg: disallowListCacheCfg, networkingType: networkingType, gossipSubBuilder: gossipsubbuilder.NewGossipSubBuilder(logger, metricsConfig, + gossipSubCfg, networkingType, sporkId, - idProvider, - scoringRegistryConfig, - &gossipCfg.GossipSubRPCInspectorsConfig, - &gossipCfg.SubscriptionProviderConfig, - rpcTracker), + idProvider), peerManagerConfig: peerManagerConfig, unicastConfig: unicastConfig, } @@ -159,36 +152,34 @@ func (builder *LibP2PNodeBuilder) SetGossipSubFactory(gf p2p.GossipSubFactoryFun return builder } -// EnableGossipSubScoringWithOverride enables peer scoring for the GossipSub pubsub system with the given override. +// OverrideGossipSubScoringConfig overrides the default peer scoring config for the GossipSub protocol. +// Note that it does not enable peer scoring. The peer scoring is enabled directly by setting the `peer-scoring-enabled` flag to true in `default-config.yaml`, or +// by setting the `gossipsub-peer-scoring-enabled` runtime flag to true. This function only overrides the default peer scoring config which takes effect +// only if the peer scoring is enabled (mostly for testing purposes). // Any existing peer scoring config attribute that is set in the override will override the default peer scoring config. // Anything that is left to nil or zero value in the override will be ignored and the default value will be used. // Note: it is not recommended to override the default peer scoring config in production unless you know what you are doing. -// Production Tip: use PeerScoringConfigNoOverride as the argument to this function to enable peer scoring without any override. // Args: // - PeerScoringConfigOverride: override for the peer scoring config- Recommended to use PeerScoringConfigNoOverride for production. // Returns: // none -func (builder *LibP2PNodeBuilder) EnableGossipSubScoringWithOverride(config *p2p.PeerScoringConfigOverride) p2p.NodeBuilder { +func (builder *LibP2PNodeBuilder) OverrideGossipSubScoringConfig(config *p2p.PeerScoringConfigOverride) p2p.NodeBuilder { builder.gossipSubBuilder.EnableGossipSubScoringWithOverride(config) return builder } -func (builder *LibP2PNodeBuilder) SetGossipSubTracer(tracer p2p.PubSubTracer) p2p.NodeBuilder { - builder.gossipSubBuilder.SetGossipSubTracer(tracer) - builder.gossipSubTracer = tracer - return builder -} - -func (builder *LibP2PNodeBuilder) SetCreateNode(f p2p.CreateNodeFunc) p2p.NodeBuilder { +// OverrideNodeConstructor overrides the default node constructor, i.e., the function that creates a new libp2p node. +// The purpose of override is to allow the node to provide a custom node constructor for sake of testing or experimentation. +// It is NOT recommended to override the default node constructor in production unless you know what you are doing. +// Args: +// - NodeConstructor: custom node constructor +// Returns: +// none +func (builder *LibP2PNodeBuilder) OverrideNodeConstructor(f p2p.NodeConstructor) p2p.NodeBuilder { builder.createNode = f return builder } -func (builder *LibP2PNodeBuilder) SetGossipSubScoreTracerInterval(interval time.Duration) p2p.NodeBuilder { - builder.gossipSubBuilder.SetGossipSubScoreTracerInterval(interval) - return builder -} - func (builder *LibP2PNodeBuilder) OverrideDefaultRpcInspectorSuiteFactory(factory p2p.GossipSubRpcInspectorSuiteFactoryFunc) p2p.NodeBuilder { builder.gossipSubBuilder.OverrideDefaultRpcInspectorSuiteFactory(factory) return builder @@ -240,12 +231,7 @@ func (builder *LibP2PNodeBuilder) Build() (p2p.LibP2PNode, error) { return nil, err } builder.gossipSubBuilder.SetHost(h) - lg := builder.logger.With().Str("local_peer_id", p2plogging.PeerId(h.ID())).Logger() - - pCache, err := p2pnode.NewProtocolPeerCache(builder.logger, h) - if err != nil { - return nil, err - } + builder.logger = builder.logger.With().Str("local_peer_id", p2plogging.PeerId(h.ID())).Logger() var peerManager p2p.PeerManager if builder.peerManagerConfig.UpdateInterval > 0 { @@ -256,7 +242,7 @@ func (builder *LibP2PNodeBuilder) Build() (p2p.LibP2PNode, error) { peerUpdater, err := connection.NewPeerUpdater( &connection.PeerUpdaterConfig{ PruneConnections: builder.peerManagerConfig.ConnectionPruning, - Logger: lg, + Logger: builder.logger, Host: connection.NewConnectorHost(h), Connector: connector, }) @@ -264,30 +250,38 @@ func (builder *LibP2PNodeBuilder) Build() (p2p.LibP2PNode, error) { return nil, fmt.Errorf("failed to create libp2p connector: %w", err) } - peerManager = connection.NewPeerManager(lg, builder.peerManagerConfig.UpdateInterval, peerUpdater) + peerManager = connection.NewPeerManager(builder.logger, builder.peerManagerConfig.UpdateInterval, peerUpdater) if builder.unicastConfig.RateLimiterDistributor != nil { builder.unicastConfig.RateLimiterDistributor.AddConsumer(peerManager) } } - node := builder.createNode(lg, h, pCache, peerManager, builder.disallowListCacheCfg) + node, err := builder.createNode(&p2p.NodeConfig{ + Parameters: &p2p.NodeParameters{ + EnableProtectedStreams: builder.unicastConfig.EnableStreamProtection, + }, + Logger: builder.logger, + Host: h, + PeerManager: peerManager, + DisallowListCacheCfg: builder.disallowListCacheCfg, + }) + if err != nil { + return nil, fmt.Errorf("could not create libp2p node: %w", err) + } if builder.connGater != nil { builder.connGater.SetDisallowListOracle(node) } unicastManager, err := unicast.NewUnicastManager(&unicast.ManagerConfig{ - Logger: lg, - StreamFactory: stream.NewLibP2PStreamFactory(h), - SporkId: builder.sporkId, - CreateStreamBackoffDelay: builder.unicastConfig.CreateStreamBackoffDelay, - Metrics: builder.metricsConfig.Metrics, - StreamZeroRetryResetThreshold: builder.unicastConfig.StreamZeroRetryResetThreshold, - MaxStreamCreationRetryAttemptTimes: builder.unicastConfig.MaxStreamCreationRetryAttemptTimes, + Logger: builder.logger, + StreamFactory: stream.NewLibP2PStreamFactory(h), + SporkId: builder.sporkId, + Metrics: builder.metricsConfig.Metrics, + Parameters: &builder.unicastConfig.UnicastManager, UnicastConfigCacheFactory: func(configFactory func() unicast.Config) unicast.ConfigCache { - return unicastcache.NewUnicastConfigCache(builder.unicastConfig.ConfigCacheSize, - lg, + return unicastcache.NewUnicastConfigCache(builder.unicastConfig.UnicastManager.ConfigCacheSize, builder.logger, metrics.DialConfigCacheMetricFactory(builder.metricsConfig.HeroCacheFactory, builder.networkingType), configFactory) }, @@ -298,45 +292,43 @@ func (builder *LibP2PNodeBuilder) Build() (p2p.LibP2PNode, error) { node.SetUnicastManager(unicastManager) cm := component.NewComponentManagerBuilder(). - AddWorker( - func(ctx irrecoverable.SignalerContext, ready component.ReadyFunc) { - if builder.routingFactory != nil { - routingSystem, err := builder.routingFactory(ctx, h) - if err != nil { - ctx.Throw(fmt.Errorf("could not create routing system: %w", err)) - } - if err := node.SetRouting(routingSystem); err != nil { - ctx.Throw(fmt.Errorf("could not set routing system: %w", err)) - } - builder.gossipSubBuilder.SetRoutingSystem(routingSystem) - lg.Debug().Msg("routing system created") - } - // gossipsub is created here, because it needs to be created during the node startup. - gossipSub, err := builder.gossipSubBuilder.Build(ctx) + AddWorker(func(ctx irrecoverable.SignalerContext, ready component.ReadyFunc) { + if builder.routingFactory != nil { + routingSystem, err := builder.routingFactory(ctx, h) if err != nil { - ctx.Throw(fmt.Errorf("could not create gossipsub: %w", err)) + ctx.Throw(fmt.Errorf("could not create routing system: %w", err)) } - node.SetPubSub(gossipSub) - gossipSub.Start(ctx) - ready() - - <-gossipSub.Done() - }). - AddWorker( - func(ctx irrecoverable.SignalerContext, ready component.ReadyFunc) { - // encapsulates shutdown logic for the libp2p node. - ready() - <-ctx.Done() - // we wait till the context is done, and then we stop the libp2p node. - - err = node.Stop() - if err != nil { - // ignore context cancellation errors - if !errors.Is(err, context.Canceled) && !errors.Is(err, context.DeadlineExceeded) { - ctx.Throw(fmt.Errorf("could not stop libp2p node: %w", err)) - } + if err := node.SetRouting(routingSystem); err != nil { + ctx.Throw(fmt.Errorf("could not set routing system: %w", err)) } - }) + builder.gossipSubBuilder.SetRoutingSystem(routingSystem) + builder.logger.Debug().Msg("routing system created") + } + // gossipsub is created here, because it needs to be created during the node startup. + gossipSub, err := builder.gossipSubBuilder.Build(ctx) + if err != nil { + ctx.Throw(fmt.Errorf("could not create gossipsub: %w", err)) + } + node.SetPubSub(gossipSub) + gossipSub.Start(ctx) + ready() + + <-gossipSub.Done() + }). + AddWorker(func(ctx irrecoverable.SignalerContext, ready component.ReadyFunc) { + // encapsulates shutdown logic for the libp2p node. + ready() + <-ctx.Done() + // we wait till the context is done, and then we stop the libp2p node. + + err = node.Stop() + if err != nil { + // ignore context cancellation errors + if !errors.Is(err, context.Canceled) && !errors.Is(err, context.DeadlineExceeded) { + ctx.Throw(fmt.Errorf("could not stop libp2p node: %w", err)) + } + } + }) node.SetComponentManager(cm.Build()) @@ -385,10 +377,9 @@ func defaultLibP2POptions(address string, key fcrypto.PrivateKey) ([]config.Opti // While this sounds great, it intermittently causes a 'broken pipe' error // as the 1-k discovery process and the 1-1 messaging both sometimes attempt to open connection to the same target // As of now there is no requirement of client sockets to be a well-known port, so disabling port reuse all together. - t := libp2p.Transport( - func(u transport.Upgrader) (*tcp.TcpTransport, error) { - return tcp.NewTCPTransport(u, nil, tcp.DisableReuseport()) - }) + t := libp2p.Transport(func(u transport.Upgrader) (*tcp.TcpTransport, error) { + return tcp.NewTCPTransport(u, nil, tcp.DisableReuseport()) + }) // gather all the options for the libp2p node options := []config.Option{ @@ -400,17 +391,6 @@ func defaultLibP2POptions(address string, key fcrypto.PrivateKey) ([]config.Opti return options, nil } -// DefaultCreateNodeFunc returns new libP2P node. -func DefaultCreateNodeFunc( - logger zerolog.Logger, - host host.Host, - pCache p2p.ProtocolPeerCache, - peerManager p2p.PeerManager, - disallowListCacheCfg *p2p.DisallowListCacheConfig, -) p2p.LibP2PNode { - return p2pnode.NewNode(logger, host, pCache, peerManager, disallowListCacheCfg) -} - // DefaultNodeBuilder returns a node builder. func DefaultNodeBuilder( logger zerolog.Logger, @@ -419,15 +399,15 @@ func DefaultNodeBuilder( flowKey fcrypto.PrivateKey, sporkId flow.Identifier, idProvider module.IdentityProvider, - metricsCfg *p2pconfig.MetricsConfig, + metricsCfg *p2pbuilderconfig.MetricsConfig, resolver madns.BasicResolver, role string, - connGaterCfg *p2pconfig.ConnectionGaterConfig, - peerManagerCfg *p2pconfig.PeerManagerConfig, - gossipCfg *p2pconf.GossipSubConfig, - rCfg *p2pconf.ResourceManagerConfig, - uniCfg *p2pconfig.UnicastConfig, - connMgrConfig *netconf.ConnectionManagerConfig, + connGaterCfg *p2pbuilderconfig.ConnectionGaterConfig, + peerManagerCfg *p2pbuilderconfig.PeerManagerConfig, + gossipCfg *p2pconfig.GossipSubParameters, + rCfg *p2pconfig.ResourceManagerConfig, + uniCfg *p2pbuilderconfig.UnicastConfig, + connMgrConfig *netconf.ConnectionManager, disallowListCacheCfg *p2p.DisallowListCacheConfig, dhtSystemActivation DhtSystemActivation, ) (p2p.NodeBuilder, error) { @@ -447,45 +427,20 @@ func DefaultNodeBuilder( connection.WithOnInterceptPeerDialFilters(append(peerFilters, connGaterCfg.InterceptPeerDialFilters...)), connection.WithOnInterceptSecuredFilters(append(peerFilters, connGaterCfg.InterceptSecuredFilters...))) - meshTracerCfg := &tracer.GossipSubMeshTracerConfig{ - Logger: logger, - Metrics: metricsCfg.Metrics, - IDProvider: idProvider, - LoggerInterval: gossipCfg.LocalMeshLogInterval, - RpcSentTrackerCacheSize: gossipCfg.RPCSentTrackerCacheSize, - RpcSentTrackerWorkerQueueCacheSize: gossipCfg.RPCSentTrackerQueueCacheSize, - RpcSentTrackerNumOfWorkers: gossipCfg.RpcSentTrackerNumOfWorkers, - HeroCacheMetricsFactory: metricsCfg.HeroCacheFactory, - NetworkingType: flownet.PrivateNetwork, - } - meshTracer := tracer.NewGossipSubMeshTracer(meshTracerCfg) - builder := NewNodeBuilder(logger, + gossipCfg, metricsCfg, networkingType, address, flowKey, sporkId, idProvider, - gossipCfg.GossipSubScoringRegistryConfig, - rCfg, - gossipCfg, - peerManagerCfg, + rCfg, peerManagerCfg, disallowListCacheCfg, - meshTracer, uniCfg). SetBasicResolver(resolver). SetConnectionManager(connManager). - SetConnectionGater(connGater). - SetCreateNode(DefaultCreateNodeFunc) - - if gossipCfg.PeerScoring { - // In production, we never override the default scoring config. - builder.EnableGossipSubScoringWithOverride(p2p.PeerScoringConfigNoOverride) - } - - builder.SetGossipSubTracer(meshTracer) - builder.SetGossipSubScoreTracerInterval(gossipCfg.ScoreTracerInterval) + SetConnectionGater(connGater) if role != "ghost" { r, err := flow.ParseRole(role) diff --git a/network/p2p/p2pbuilder/libp2pscaler.go b/network/p2p/builder/libp2pscaler.go similarity index 100% rename from network/p2p/p2pbuilder/libp2pscaler.go rename to network/p2p/builder/libp2pscaler.go diff --git a/network/p2p/p2pbuilder/libp2pscaler_test.go b/network/p2p/builder/libp2pscaler_test.go similarity index 94% rename from network/p2p/p2pbuilder/libp2pscaler_test.go rename to network/p2p/builder/libp2pscaler_test.go index 1fb9a8539af..4fd853dbd1c 100644 --- a/network/p2p/p2pbuilder/libp2pscaler_test.go +++ b/network/p2p/builder/libp2pscaler_test.go @@ -9,7 +9,7 @@ import ( "github.com/stretchr/testify/require" "github.com/onflow/flow-go/config" - "github.com/onflow/flow-go/network/p2p/p2pconf" + p2pconfig "github.com/onflow/flow-go/network/p2p/config" "github.com/onflow/flow-go/utils/unittest" ) @@ -125,7 +125,7 @@ func TestApplyResourceLimitOverride(t *testing.T) { libp2p.SetDefaultServiceLimits(&limits) scaled := limits.Scale(mem, fd) - systemOverride := p2pconf.ResourceManagerOverrideLimit{ + systemOverride := p2pconfig.ResourceManagerOverrideLimit{ StreamsInbound: 0, // should not be overridden. StreamsOutbound: 456, ConnectionsInbound: 789, @@ -134,7 +134,7 @@ func TestApplyResourceLimitOverride(t *testing.T) { Memory: 7890, } - peerOverride := p2pconf.ResourceManagerOverrideLimit{ + peerOverride := p2pconfig.ResourceManagerOverrideLimit{ StreamsInbound: 321, StreamsOutbound: 0, // should not be overridden. ConnectionsInbound: 987, @@ -144,8 +144,8 @@ func TestApplyResourceLimitOverride(t *testing.T) { } partial := rcmgr.PartialLimitConfig{} - partial.System = ApplyResourceLimitOverride(unittest.Logger(), p2pconf.ResourceScopeSystem, scaled.ToPartialLimitConfig().System, systemOverride) - partial.PeerDefault = ApplyResourceLimitOverride(unittest.Logger(), p2pconf.ResourceScopePeer, scaled.ToPartialLimitConfig().PeerDefault, peerOverride) + partial.System = ApplyResourceLimitOverride(unittest.Logger(), p2pconfig.ResourceScopeSystem, scaled.ToPartialLimitConfig().System, systemOverride) + partial.PeerDefault = ApplyResourceLimitOverride(unittest.Logger(), p2pconfig.ResourceScopePeer, scaled.ToPartialLimitConfig().PeerDefault, peerOverride) final := partial.Build(scaled).ToPartialLimitConfig() require.Equal(t, 456, int(final.System.StreamsOutbound)) // should be overridden. @@ -176,7 +176,7 @@ func TestBuildLibp2pResourceManagerLimits(t *testing.T) { require.NoError(t, err) // this function will evaluate that the limits are override correctly on each scope using the override config. - requireEqual := func(t *testing.T, override p2pconf.ResourceManagerOverrideLimit, actual rcmgr.ResourceLimits) { + requireEqual := func(t *testing.T, override p2pconfig.ResourceManagerOverrideLimit, actual rcmgr.ResourceLimits) { require.Equal(t, override.StreamsInbound, int(actual.StreamsInbound)) require.Equal(t, override.StreamsOutbound, int(actual.StreamsOutbound)) require.Equal(t, override.ConnectionsInbound, int(actual.ConnsInbound)) diff --git a/network/p2p/p2pbuilder/resourceLimit.go b/network/p2p/builder/resourceLimit.go similarity index 84% rename from network/p2p/p2pbuilder/resourceLimit.go rename to network/p2p/builder/resourceLimit.go index e80f185cbfd..56a804d1258 100644 --- a/network/p2p/p2pbuilder/resourceLimit.go +++ b/network/p2p/builder/resourceLimit.go @@ -7,7 +7,7 @@ import ( rcmgr "github.com/libp2p/go-libp2p/p2p/host/resource-manager" "github.com/rs/zerolog" - "github.com/onflow/flow-go/network/p2p/p2pconf" + p2pconfig "github.com/onflow/flow-go/network/p2p/config" ) // BuildLibp2pResourceManagerLimits builds the resource manager limits for the libp2p node. @@ -20,7 +20,7 @@ import ( // // - the resource manager limits. // - any error encountered, all returned errors are irrecoverable (we cannot continue without the resource manager limits). -func BuildLibp2pResourceManagerLimits(logger zerolog.Logger, config *p2pconf.ResourceManagerConfig) (*rcmgr.ConcreteLimitConfig, error) { +func BuildLibp2pResourceManagerLimits(logger zerolog.Logger, config *p2pconfig.ResourceManagerConfig) (*rcmgr.ConcreteLimitConfig, error) { defaultLimits := rcmgr.DefaultLimits libp2p.SetDefaultServiceLimits(&defaultLimits) @@ -36,11 +36,11 @@ func BuildLibp2pResourceManagerLimits(logger zerolog.Logger, config *p2pconf.Res scaled := defaultLimits.Scale(mem, fd) scaledP := scaled.ToPartialLimitConfig() override := rcmgr.PartialLimitConfig{} - override.System = ApplyResourceLimitOverride(logger, p2pconf.ResourceScopeSystem, scaledP.System, config.Override.System) - override.Transient = ApplyResourceLimitOverride(logger, p2pconf.ResourceScopeTransient, scaledP.Transient, config.Override.Transient) - override.ProtocolDefault = ApplyResourceLimitOverride(logger, p2pconf.ResourceScopeProtocol, scaledP.ProtocolDefault, config.Override.Protocol) - override.PeerDefault = ApplyResourceLimitOverride(logger, p2pconf.ResourceScopePeer, scaledP.PeerDefault, config.Override.Peer) - override.ProtocolPeerDefault = ApplyResourceLimitOverride(logger, p2pconf.ResourceScopePeerProtocol, scaledP.ProtocolPeerDefault, config.Override.PeerProtocol) + override.System = ApplyResourceLimitOverride(logger, p2pconfig.ResourceScopeSystem, scaledP.System, config.Override.System) + override.Transient = ApplyResourceLimitOverride(logger, p2pconfig.ResourceScopeTransient, scaledP.Transient, config.Override.Transient) + override.ProtocolDefault = ApplyResourceLimitOverride(logger, p2pconfig.ResourceScopeProtocol, scaledP.ProtocolDefault, config.Override.Protocol) + override.PeerDefault = ApplyResourceLimitOverride(logger, p2pconfig.ResourceScopePeer, scaledP.PeerDefault, config.Override.Peer) + override.ProtocolPeerDefault = ApplyResourceLimitOverride(logger, p2pconfig.ResourceScopePeerProtocol, scaledP.ProtocolPeerDefault, config.Override.PeerProtocol) limits := override.Build(scaled) logger.Info(). @@ -66,9 +66,9 @@ func BuildLibp2pResourceManagerLimits(logger zerolog.Logger, config *p2pconf.Res // // the overridden limit. func ApplyResourceLimitOverride(logger zerolog.Logger, - resourceScope p2pconf.ResourceScope, + resourceScope p2pconfig.ResourceScope, original rcmgr.ResourceLimits, - override p2pconf.ResourceManagerOverrideLimit) rcmgr.ResourceLimits { + override p2pconfig.ResourceManagerOverrideLimit) rcmgr.ResourceLimits { lg := logger.With().Logger() if override.StreamsInbound > 0 { diff --git a/network/p2p/p2pbuilder/utils.go b/network/p2p/builder/utils.go similarity index 98% rename from network/p2p/p2pbuilder/utils.go rename to network/p2p/builder/utils.go index 1c51fe70d3c..b43531e5151 100644 --- a/network/p2p/p2pbuilder/utils.go +++ b/network/p2p/builder/utils.go @@ -10,7 +10,7 @@ import ( "github.com/onflow/flow-go/module" "github.com/onflow/flow-go/network/p2p" - "github.com/onflow/flow-go/network/p2p/p2plogging" + p2plogging "github.com/onflow/flow-go/network/p2p/logging" ) const keyResourceManagerLimit = "libp2p_resource_manager_limit" diff --git a/network/p2p/cache.go b/network/p2p/cache.go index 3f3bbadc00c..226617c7a6d 100644 --- a/network/p2p/cache.go +++ b/network/p2p/cache.go @@ -36,15 +36,6 @@ type UpdateFunction func(record GossipSubSpamRecord) GossipSubSpamRecord // // Implementation must be thread-safe. type GossipSubSpamRecordCache interface { - // Add adds the GossipSubSpamRecord of a peer to the cache. - // Args: - // - peerID: the peer ID of the peer in the GossipSub protocol. - // - record: the GossipSubSpamRecord of the peer. - // - // Returns: - // - bool: true if the record was added successfully, false otherwise. - Add(peerId peer.ID, record GossipSubSpamRecord) bool - // Get returns the GossipSubSpamRecord of a peer from the cache. // Args: // - peerID: the peer ID of the peer in the GossipSub protocol. @@ -54,14 +45,16 @@ type GossipSubSpamRecordCache interface { // - bool: true if the record was retrieved successfully, false otherwise. Get(peerID peer.ID) (*GossipSubSpamRecord, error, bool) - // Update updates the GossipSub spam penalty of a peer in the cache using the given adjust function. + // Adjust updates the GossipSub spam penalty of a peer in the cache. If the peer does not have a record in the cache, a new record is created. + // The order of the pre-processing functions is the same as the order in which they were added to the cache. // Args: // - peerID: the peer ID of the peer in the GossipSub protocol. - // - adjustFn: the adjust function to be applied to the record. + // - updateFn: the update function to be applied to the record. // Returns: // - *GossipSubSpamRecord: the updated record. // - error on failure to update the record. The returned error is irrecoverable and indicates an exception. - Update(peerID peer.ID, updateFunc UpdateFunction) (*GossipSubSpamRecord, error) + // Note that if any of the pre-processing functions returns an error, the record is reverted to its original state (prior to applying the update function). + Adjust(peerID peer.ID, updateFunc UpdateFunction) (*GossipSubSpamRecord, error) // Has returns true if the cache contains the GossipSubSpamRecord of the given peer. // Args: @@ -71,6 +64,33 @@ type GossipSubSpamRecordCache interface { Has(peerID peer.ID) bool } +// GossipSubApplicationSpecificScoreCache is a cache for storing the application specific score of peers. +// The application specific score of a peer is used to calculate the GossipSub score of the peer; it contains the spam penalty of the peer, staking score, and subscription penalty. +// Note that none of the application specific scores, spam penalties, staking scores, and subscription penalties are shared publicly with other peers. +// Rather they are solely used by the current peer to select the peers to which it will connect on a topic mesh. +// The cache is expected to have an eject policy to remove the least recently used record when the cache is full. +// Implementation must be thread-safe, but can be blocking. +type GossipSubApplicationSpecificScoreCache interface { + // Get returns the application specific score of a peer from the cache. + // Args: + // - peerID: the peer ID of the peer in the GossipSub protocol. + // Returns: + // - float64: the application specific score of the peer. + // - time.Time: the time at which the score was last updated. + // - bool: true if the score was retrieved successfully, false otherwise. + Get(peerID peer.ID) (float64, time.Time, bool) + + // AdjustWithInit adds the application specific score of a peer to the cache. + // If the peer already has a score in the cache, the score is updated. + // Args: + // - peerID: the peer ID of the peer in the GossipSub protocol. + // - score: the application specific score of the peer. + // - time: the time at which the score was last updated. + // Returns: + // - error on failure to add the score. The returned error is irrecoverable and indicates an exception. + AdjustWithInit(peerID peer.ID, score float64, time time.Time) error +} + // GossipSubSpamRecord represents spam record of a peer in the GossipSub protocol. // It acts as a penalty card for a peer in the GossipSub protocol that keeps the // spam penalty of the peer as well as its decay factor. diff --git a/network/p2p/cache/gossipsub_spam_records.go b/network/p2p/cache/gossipsub_spam_records.go index 265c2befbb7..2b99666a49b 100644 --- a/network/p2p/cache/gossipsub_spam_records.go +++ b/network/p2p/cache/gossipsub_spam_records.go @@ -13,7 +13,7 @@ import ( "github.com/onflow/flow-go/module/mempool/herocache/backdata/heropool" "github.com/onflow/flow-go/module/mempool/stdmap" "github.com/onflow/flow-go/network/p2p" - "github.com/onflow/flow-go/network/p2p/p2plogging" + p2plogging "github.com/onflow/flow-go/network/p2p/logging" ) // GossipSubSpamRecordCache is a cache for storing the gossipsub spam records of peers. It is thread-safe. @@ -30,6 +30,9 @@ type GossipSubSpamRecordCache struct { // Primary use case is to perform decay operations on the record before reading or updating it. In this way, a // record is only decayed when it is read or updated without the need to explicitly iterating over the cache. preprocessFns []PreprocessorFunc + + // initFn is a function that is called to initialize a new record in the cache. + initFn func() p2p.GossipSubSpamRecord } var _ p2p.GossipSubSpamRecordCache = (*GossipSubSpamRecordCache)(nil) @@ -60,41 +63,24 @@ type PreprocessorFunc func(record p2p.GossipSubSpamRecord, lastUpdated time.Time // Returns: // // *GossipSubSpamRecordCache: the newly created cache with a HeroCache-based backend. -func NewGossipSubSpamRecordCache(sizeLimit uint32, logger zerolog.Logger, collector module.HeroCacheMetrics, prFns ...PreprocessorFunc) *GossipSubSpamRecordCache { +func NewGossipSubSpamRecordCache(sizeLimit uint32, + logger zerolog.Logger, + collector module.HeroCacheMetrics, + initFn func() p2p.GossipSubSpamRecord, + prFns ...PreprocessorFunc) *GossipSubSpamRecordCache { backData := herocache.NewCache(sizeLimit, herocache.DefaultOversizeFactor, - // we should not evict any record from the cache, - // eviction will open the node to spam attacks by malicious peers to erase their application specific penalty. - heropool.NoEjection, + heropool.LRUEjection, logger.With().Str("mempool", "gossipsub-app-Penalty-cache").Logger(), collector) return &GossipSubSpamRecordCache{ c: stdmap.NewBackend(stdmap.WithBackData(backData)), preprocessFns: prFns, + initFn: initFn, } } -// Add adds the GossipSubSpamRecord of a peer to the cache. -// Args: -// - peerID: the peer ID of the peer in the GossipSub protocol. -// - record: the GossipSubSpamRecord of the peer. -// -// Returns: -// - bool: true if the record was added successfully, false otherwise. -// Note that a record is added successfully if the cache has enough space to store the record and no record exists for the peer in the cache. -// In other words, the entries are deduplicated by the peer ID. -func (a *GossipSubSpamRecordCache) Add(peerId peer.ID, record p2p.GossipSubSpamRecord) bool { - entityId := flow.HashToID([]byte(peerId)) // HeroCache uses hash of peer.ID as the unique identifier of the record. - return a.c.Add(gossipsubSpamRecordEntity{ - entityId: entityId, - peerID: peerId, - lastUpdated: time.Now(), - GossipSubSpamRecord: record, - }) -} - -// Update updates the GossipSub spam penalty of a peer in the cache. It assumes that a record already exists for the peer in the cache. -// It first reads the record from the cache, applies the pre-processing functions to the record, and then applies the update function to the record. +// Adjust updates the GossipSub spam penalty of a peer in the cache. If the peer does not have a record in the cache, a new record is created. // The order of the pre-processing functions is the same as the order in which they were added to the cache. // Args: // - peerID: the peer ID of the peer in the GossipSub protocol. @@ -103,16 +89,12 @@ func (a *GossipSubSpamRecordCache) Add(peerId peer.ID, record p2p.GossipSubSpamR // - *GossipSubSpamRecord: the updated record. // - error on failure to update the record. The returned error is irrecoverable and indicates an exception. // Note that if any of the pre-processing functions returns an error, the record is reverted to its original state (prior to applying the update function). -func (a *GossipSubSpamRecordCache) Update(peerID peer.ID, updateFn p2p.UpdateFunction) (*p2p.GossipSubSpamRecord, error) { - // HeroCache uses flow.Identifier for keys, so reformat of the peer.ID - entityId := flow.HashToID([]byte(peerID)) - if !a.c.Has(entityId) { - return nil, fmt.Errorf("could not update spam records for peer %s, record not found", peerID.String()) - } +func (a *GossipSubSpamRecordCache) Adjust(peerID peer.ID, updateFn p2p.UpdateFunction) (*p2p.GossipSubSpamRecord, error) { + entityId := entityIdOf(peerID) var err error - record, updated := a.c.Adjust(entityId, func(entry flow.Entity) flow.Entity { - e := entry.(gossipsubSpamRecordEntity) + adjustFunc := func(entity flow.Entity) flow.Entity { + e := entity.(gossipsubSpamRecordEntity) currentRecord := e.GossipSubSpamRecord // apply the pre-processing functions to the record. @@ -131,16 +113,25 @@ func (a *GossipSubSpamRecordCache) Update(peerID peer.ID, updateFn p2p.UpdateFun e.lastUpdated = time.Now() } return e - }) + } + + initFunc := func() flow.Entity { + return gossipsubSpamRecordEntity{ + entityId: entityId, + peerID: peerID, + GossipSubSpamRecord: a.initFn(), + } + } + + adjustedEntity, adjusted := a.c.AdjustWithInit(entityId, adjustFunc, initFunc) if err != nil { - return nil, fmt.Errorf("could not update spam records for peer %s, error: %w", peerID.String(), err) + return nil, fmt.Errorf("error while applying pre-processing functions to cache record for peer %s: %w", p2plogging.PeerId(peerID), err) } - if !updated { - // this happens when the underlying HeroCache fails to update the record. - return nil, fmt.Errorf("internal cache error for updating %s", peerID.String()) + if !adjusted { + return nil, fmt.Errorf("could not adjust cache record for peer %s", p2plogging.PeerId(peerID)) } - r := record.(gossipsubSpamRecordEntity).GossipSubSpamRecord + r := adjustedEntity.(gossipsubSpamRecordEntity).GossipSubSpamRecord return &r, nil } @@ -150,7 +141,7 @@ func (a *GossipSubSpamRecordCache) Update(peerID peer.ID, updateFn p2p.UpdateFun // Returns: // - true if the gossipsub spam record of the peer is found in the cache, false otherwise. func (a *GossipSubSpamRecordCache) Has(peerID peer.ID) bool { - entityId := flow.HashToID([]byte(peerID)) // HeroCache uses hash of peer.ID as the unique identifier of the record. + entityId := entityIdOf(peerID) return a.c.Has(entityId) } @@ -165,14 +156,14 @@ func (a *GossipSubSpamRecordCache) Has(peerID peer.ID) bool { // the caller is advised to crash the node. // - true if the record is found in the cache, false otherwise. func (a *GossipSubSpamRecordCache) Get(peerID peer.ID) (*p2p.GossipSubSpamRecord, error, bool) { - entityId := flow.HashToID([]byte(peerID)) // HeroCache uses hash of peer.ID as the unique identifier of the record. + entityId := entityIdOf(peerID) if !a.c.Has(entityId) { return nil, nil, false } var err error - record, updated := a.c.Adjust(entityId, func(entry flow.Entity) flow.Entity { - e := entry.(gossipsubSpamRecordEntity) + record, updated := a.c.Adjust(entityId, func(entity flow.Entity) flow.Entity { + e := mustBeGossipSubSpamRecordEntity(entity) currentRecord := e.GossipSubSpamRecord for _, apply := range a.preprocessFns { @@ -194,7 +185,7 @@ func (a *GossipSubSpamRecordCache) Get(peerID peer.ID) (*p2p.GossipSubSpamRecord return nil, fmt.Errorf("could not decay cache record for peer %s", p2plogging.PeerId(peerID)), false } - r := record.(gossipsubSpamRecordEntity).GossipSubSpamRecord + r := mustBeGossipSubSpamRecordEntity(record).GossipSubSpamRecord return &r, nil, true } @@ -224,3 +215,31 @@ func (a gossipsubSpamRecordEntity) ID() flow.Identifier { func (a gossipsubSpamRecordEntity) Checksum() flow.Identifier { return a.entityId } + +// entityIdOf converts a peer ID to a flow ID by taking the hash of the peer ID. +// This is used to convert the peer ID in a notion that is compatible with HeroCache. +// This is not a protocol-level conversion, and is only used internally by the cache, MUST NOT be exposed outside the cache. +// Args: +// - peerId: the peer ID of the peer in the GossipSub protocol. +// Returns: +// - flow.Identifier: the flow ID of the peer. +func entityIdOf(peerId peer.ID) flow.Identifier { + return flow.MakeID(peerId) +} + +// mustBeGossipSubSpamRecordEntity converts a flow.Entity to a gossipsubSpamRecordEntity. +// This is used to convert the flow.Entity returned by HeroCache to a gossipsubSpamRecordEntity. +// If the conversion fails, it panics. +// Args: +// - entity: the flow.Entity to be converted. +// Returns: +// - gossipsubSpamRecordEntity: the converted gossipsubSpamRecordEntity. +func mustBeGossipSubSpamRecordEntity(entity flow.Entity) gossipsubSpamRecordEntity { + record, ok := entity.(gossipsubSpamRecordEntity) + if !ok { + // sanity check + // This should never happen, because the cache only contains gossipsubSpamRecordEntity entities. + panic(fmt.Sprintf("invalid entity type, expected gossipsubSpamRecordEntity type, got: %T", entity)) + } + return record +} diff --git a/network/p2p/cache/gossipsub_spam_records_test.go b/network/p2p/cache/gossipsub_spam_records_test.go index 166776b93ba..bee5ecdc26e 100644 --- a/network/p2p/cache/gossipsub_spam_records_test.go +++ b/network/p2p/cache/gossipsub_spam_records_test.go @@ -7,7 +7,6 @@ import ( "time" "github.com/libp2p/go-libp2p/core/peer" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/atomic" @@ -21,58 +20,65 @@ import ( // adding a new record to the cache. func TestGossipSubSpamRecordCache_Add(t *testing.T) { // create a new instance of GossipSubSpamRecordCache. - cache := netcache.NewGossipSubSpamRecordCache(100, unittest.Logger(), metrics.NewNoopCollector()) + cache := netcache.NewGossipSubSpamRecordCache(100, unittest.Logger(), metrics.NewNoopCollector(), func() p2p.GossipSubSpamRecord { + return p2p.GossipSubSpamRecord{ + Decay: 0, + Penalty: 0, + LastDecayAdjustment: time.Now(), + } + }) - // tests adding a new record to the cache. - require.True(t, cache.Add("peer0", p2p.GossipSubSpamRecord{ - Decay: 0.1, - Penalty: 0.5, - })) + adjustedEntity, err := cache.Adjust("peer0", func(record p2p.GossipSubSpamRecord) p2p.GossipSubSpamRecord { + record.Decay = 0.1 + record.Penalty = 0.5 - // tests updating an existing record in the cache. - require.False(t, cache.Add("peer0", p2p.GossipSubSpamRecord{ - Decay: 0.1, - Penalty: 0.5, - })) + return record + }) + require.NoError(t, err) + require.Equal(t, 0.1, adjustedEntity.Decay) + require.Equal(t, 0.5, adjustedEntity.Penalty) // makes the cache full. - for i := 1; i < 100; i++ { - require.True(t, cache.Add(peer.ID(fmt.Sprintf("peer%d", i)), p2p.GossipSubSpamRecord{ - Decay: 0.1, - Penalty: 0.5, - })) - } + for i := 1; i <= 100; i++ { + adjustedEntity, err := cache.Adjust(peer.ID(fmt.Sprintf("peer%d", i)), func(record p2p.GossipSubSpamRecord) p2p.GossipSubSpamRecord { + record.Decay = 0.1 + record.Penalty = 0.5 - // adding a new record to the cache should fail. - require.False(t, cache.Add("peer101", p2p.GossipSubSpamRecord{ - Decay: 0.1, - Penalty: 0.5, - })) + return record + }) + + require.NoError(t, err) + require.Equal(t, 0.1, adjustedEntity.Decay) + } // retrieving an existing record should work. - for i := 0; i < 100; i++ { + for i := 1; i <= 100; i++ { record, err, ok := cache.Get(peer.ID(fmt.Sprintf("peer%d", i))) - require.True(t, ok) + require.True(t, ok, fmt.Sprintf("record for peer%d should exist", i)) require.NoError(t, err) require.Equal(t, 0.1, record.Decay) require.Equal(t, 0.5, record.Penalty) } - // yet attempting on adding an existing record should fail. - require.False(t, cache.Add("peer1", p2p.GossipSubSpamRecord{ - Decay: 0.2, - Penalty: 0.8, - })) + // since cache is LRU, the first record should be evicted. + _, err, ok := cache.Get("peer0") + require.False(t, ok) + require.NoError(t, err) } -// TestGossipSubSpamRecordCache_Concurrent_Add tests if the cache can be added and retrieved concurrently. -// It updates the cache with a number of records concurrently and then checks if the cache -// can retrieve all records. -func TestGossipSubSpamRecordCache_Concurrent_Add(t *testing.T) { - cache := netcache.NewGossipSubSpamRecordCache(200, unittest.Logger(), metrics.NewNoopCollector()) +// TestGossipSubSpamRecordCache_Concurrent_Adjust tests if the cache can be adjusted and retrieved concurrently. +// It adjusts the cache with a number of records concurrently and then checks if the cache can retrieve all records. +func TestGossipSubSpamRecordCache_Concurrent_Adjust(t *testing.T) { + cache := netcache.NewGossipSubSpamRecordCache(200, unittest.Logger(), metrics.NewNoopCollector(), func() p2p.GossipSubSpamRecord { + return p2p.GossipSubSpamRecord{ + Decay: 0, + Penalty: 0, + LastDecayAdjustment: time.Now(), + } + }) - // defines the number of records to update. + // defines the number of records to be adjusted. numRecords := 100 // uses a wait group to wait for all goroutines to finish. @@ -84,15 +90,20 @@ func TestGossipSubSpamRecordCache_Concurrent_Add(t *testing.T) { go func(num int) { defer wg.Done() peerID := fmt.Sprintf("peer%d", num) - added := cache.Add(peer.ID(peerID), p2p.GossipSubSpamRecord{ - Decay: 0.1 * float64(num), - Penalty: float64(num), + adjustedEntity, err := cache.Adjust(peer.ID(peerID), func(record p2p.GossipSubSpamRecord) p2p.GossipSubSpamRecord { + record.Decay = 0.1 * float64(num) + record.Penalty = float64(num) + + return record }) - require.True(t, added) + + require.NoError(t, err) + require.Equal(t, 0.1*float64(num), adjustedEntity.Decay) + require.Equal(t, float64(num), adjustedEntity.Penalty) }(i) } - unittest.RequireReturnsBefore(t, wg.Wait, 100*time.Millisecond, "could not update all records concurrently on time") + unittest.RequireReturnsBefore(t, wg.Wait, 100*time.Millisecond, "could not adjust all records concurrently on time") // checks if the cache can retrieve all records. for i := 0; i < numRecords; i++ { @@ -110,96 +121,51 @@ func TestGossipSubSpamRecordCache_Concurrent_Add(t *testing.T) { } } -// TestGossipSubSpamRecordCache_Update tests the Update method of the GossipSubSpamRecordCache. It tests if the cache can update -// the penalty of an existing record and fail to update the penalty of a non-existing record. -func TestGossipSubSpamRecordCache_Update(t *testing.T) { - cache := netcache.NewGossipSubSpamRecordCache(200, unittest.Logger(), metrics.NewNoopCollector()) +// TestGossipSubSpamRecordCache_Adjust tests the Adjust method of the GossipSubSpamRecordCache. It tests if the cache can adjust +// the penalty of an existing record and add a new record. +func TestGossipSubSpamRecordCache_Adjust(t *testing.T) { + cache := netcache.NewGossipSubSpamRecordCache(200, unittest.Logger(), metrics.NewNoopCollector(), func() p2p.GossipSubSpamRecord { + return p2p.GossipSubSpamRecord{ + Decay: 0, + Penalty: 0, + LastDecayAdjustment: time.Now(), + } + }) peerID := "peer1" - // tests updateing the penalty of an existing record. - require.True(t, cache.Add(peer.ID(peerID), p2p.GossipSubSpamRecord{ - Decay: 0.1, - Penalty: 0.5, - })) - record, err := cache.Update(peer.ID(peerID), func(record p2p.GossipSubSpamRecord) p2p.GossipSubSpamRecord { + // test adjusting a non-existing record. + record, err := cache.Adjust(peer.ID(peerID), func(record p2p.GossipSubSpamRecord) p2p.GossipSubSpamRecord { record.Penalty = 0.7 return record }) require.NoError(t, err) - require.Equal(t, 0.7, record.Penalty) // checks if the penalty is updateed correctly. + require.Equal(t, 0.7, record.Penalty) // checks if the penalty is adjusted correctly. - // tests updating the penalty of a non-existing record. - record, err = cache.Update(peer.ID("peer2"), func(record p2p.GossipSubSpamRecord) p2p.GossipSubSpamRecord { - require.Fail(t, "the function should not be called for a non-existing record") + // test adjusting an existing record. + record, err = cache.Adjust(peer.ID(peerID), func(record p2p.GossipSubSpamRecord) p2p.GossipSubSpamRecord { + record.Penalty = 0.8 return record }) - require.Error(t, err) - require.Nil(t, record) -} - -// TestGossipSubSpamRecordCache_Concurrent_Update tests if the cache can be updated concurrently. It updates the cache -// with a number of records concurrently and then checks if the cache can retrieve all records. -func TestGossipSubSpamRecordCache_Concurrent_Update(t *testing.T) { - cache := netcache.NewGossipSubSpamRecordCache(200, unittest.Logger(), metrics.NewNoopCollector()) - - // defines the number of records to update. - numRecords := 100 - - // adds all records to the cache, sequentially. - for i := 0; i < numRecords; i++ { - peerID := fmt.Sprintf("peer%d", i) - err := cache.Add(peer.ID(peerID), p2p.GossipSubSpamRecord{ - Decay: 0.1 * float64(i), - Penalty: float64(i), - }) - require.True(t, err) - } - - // uses a wait group to wait for all goroutines to finish. - var wg sync.WaitGroup - wg.Add(numRecords) - - // updates the records concurrently. - for i := 0; i < numRecords; i++ { - go func(num int) { - defer wg.Done() - peerID := fmt.Sprintf("peer%d", num) - _, err := cache.Update(peer.ID(peerID), func(record p2p.GossipSubSpamRecord) p2p.GossipSubSpamRecord { - record.Penalty = 0.7 * float64(num) - record.Decay = 0.1 * float64(num) - return record - }) - require.NoError(t, err) - }(i) - } - - unittest.RequireReturnsBefore(t, wg.Wait, 100*time.Millisecond, "could not update all records concurrently on time") - - // checks if the cache can retrieve all records. - for i := 0; i < numRecords; i++ { - peerID := fmt.Sprintf("peer%d", i) - record, err, found := cache.Get(peer.ID(peerID)) - require.True(t, found) - require.NoError(t, err) - - expectedPenalty := 0.7 * float64(i) - require.Equal(t, expectedPenalty, record.Penalty, - "Get() returned incorrect Penalty for record %s: expected %f, got %f", peerID, expectedPenalty, record.Penalty) - expectedDecay := 0.1 * float64(i) - require.Equal(t, expectedDecay, record.Decay, - "Get() returned incorrect Decay for record %s: expected %f, got %f", peerID, expectedDecay, record.Decay) - } + require.NoError(t, err) + require.Equal(t, 0.8, record.Penalty) // checks if the penalty is adjusted correctly. } -// TestGossipSubSpamRecordCache_Update_With_Preprocess tests Update method of the GossipSubSpamRecordCache when the cache +// TestGossipSubSpamRecordCache_Adjust_With_Preprocess tests Adjust method of the GossipSubSpamRecordCache when the cache // has preprocessor functions. -// It tests when the cache has preprocessor functions, all preprocessor functions are called prior to the update function. +// It tests when the cache has preprocessor functions, all preprocessor functions are called prior to the adjust function. // Also, it tests if the pre-processor functions are called in the order they are added. -func TestGossipSubSpamRecordCache_Update_With_Preprocess(t *testing.T) { +func TestGossipSubSpamRecordCache_Adjust_With_Preprocess(t *testing.T) { cache := netcache.NewGossipSubSpamRecordCache(200, unittest.Logger(), metrics.NewNoopCollector(), + func() p2p.GossipSubSpamRecord { + return p2p.GossipSubSpamRecord{ + Decay: 0, + Penalty: 0, + LastDecayAdjustment: time.Now(), + } + }, func(record p2p.GossipSubSpamRecord, lastUpdated time.Time) (p2p.GossipSubSpamRecord, error) { record.Penalty += 1.5 return record, nil @@ -209,14 +175,19 @@ func TestGossipSubSpamRecordCache_Update_With_Preprocess(t *testing.T) { }) peerID := "peer1" - // adds a record to the cache. - require.True(t, cache.Add(peer.ID(peerID), p2p.GossipSubSpamRecord{ - Decay: 0.1, - Penalty: 0.5, - })) + + // test adjusting a non-existing record. + record, err := cache.Adjust(peer.ID(peerID), func(record p2p.GossipSubSpamRecord) p2p.GossipSubSpamRecord { + record.Penalty = 0.5 + record.Decay = 0.1 + return record + }) + require.NoError(t, err) + require.Equal(t, 0.5, record.Penalty) // checks if the penalty is adjusted correctly. + require.Equal(t, 0.1, record.Decay) // checks if the decay is adjusted correctly. // tests updating the penalty of an existing record. - record, err := cache.Update(peer.ID(peerID), func(record p2p.GossipSubSpamRecord) p2p.GossipSubSpamRecord { + record, err = cache.Adjust(peer.ID(peerID), func(record p2p.GossipSubSpamRecord) p2p.GossipSubSpamRecord { record.Penalty += 0.7 return record }) @@ -225,46 +196,61 @@ func TestGossipSubSpamRecordCache_Update_With_Preprocess(t *testing.T) { require.Equal(t, 0.1, record.Decay) // checks if the decay is not changed. } -// TestGossipSubSpamRecordCache_Update_Preprocess_Error tests the Update method of the GossipSubSpamRecordCache. -// It tests if any of the preprocessor functions returns an error, the update function effect +// TestGossipSubSpamRecordCache_Adjust_Preprocess_Error tests the Adjust method of the GossipSubSpamRecordCache. +// It tests if any of the preprocessor functions returns an error, the Adjust function effect // is reverted, and the error is returned. -func TestGossipSubSpamRecordCache_Update_Preprocess_Error(t *testing.T) { - secondPreprocessorCalled := false +func TestGossipSubSpamRecordCache_Adjust_Preprocess_Error(t *testing.T) { + secondPreprocessorCalled := 0 cache := netcache.NewGossipSubSpamRecordCache(200, unittest.Logger(), metrics.NewNoopCollector(), + func() p2p.GossipSubSpamRecord { + return p2p.GossipSubSpamRecord{ + Decay: 0, + Penalty: 0, + LastDecayAdjustment: time.Now(), + } + }, // the first preprocessor function does not return an error. func(record p2p.GossipSubSpamRecord, lastUpdated time.Time) (p2p.GossipSubSpamRecord, error) { return record, nil }, - // the second preprocessor function returns an error on the first call and nil on the second call onwards. + // the second preprocessor function returns an error on the second call, and does not return an error on any other call. + // this means that adjustment should be successful on the first call, and should fail on the second call. func(record p2p.GossipSubSpamRecord, lastUpdated time.Time) (p2p.GossipSubSpamRecord, error) { - if !secondPreprocessorCalled { - secondPreprocessorCalled = true - return record, fmt.Errorf("error") + secondPreprocessorCalled++ + if secondPreprocessorCalled == 2 { + return record, fmt.Errorf("some error") } return record, nil + }) - peerID := "peer1" - // adds a record to the cache. - require.True(t, cache.Add(peer.ID(peerID), p2p.GossipSubSpamRecord{ - Decay: 0.1, - Penalty: 0.5, - })) + peerID := unittest.PeerIdFixture(t) - // tests updating the penalty of an existing record. - record, err := cache.Update(peer.ID(peerID), func(record p2p.GossipSubSpamRecord) p2p.GossipSubSpamRecord { + // tests adjusting the penalty of a non-existing record; the record should be initiated and the penalty should be adjusted. + record, err := cache.Adjust(peerID, func(record p2p.GossipSubSpamRecord) p2p.GossipSubSpamRecord { + record.Penalty = 0.5 + record.Decay = 0.1 + return record + }) + require.NoError(t, err) + require.NotNil(t, record) + require.Equal(t, 0.5, record.Penalty) // checks if the penalty is not changed. + require.Equal(t, 0.1, record.Decay) // checks if the decay is not changed. + + // tests adjusting the penalty of an existing record. + record, err = cache.Adjust(peerID, func(record p2p.GossipSubSpamRecord) p2p.GossipSubSpamRecord { record.Penalty = 0.7 return record }) - // since the second preprocessor function returns an error, the update function effect should be reverted. + // since the second preprocessor function returns an error, the adjust function effect should be reverted. // the error should be returned. require.Error(t, err) require.Nil(t, record) // checks if the record is not changed. - record, err, found := cache.Get(peer.ID(peerID)) + record, err, found := cache.Get(peerID) require.True(t, found) require.NoError(t, err) require.Equal(t, 0.5, record.Penalty) // checks if the penalty is not changed. @@ -272,22 +258,33 @@ func TestGossipSubSpamRecordCache_Update_Preprocess_Error(t *testing.T) { } // TestGossipSubSpamRecordCache_ByValue tests if the cache stores the GossipSubSpamRecord by value. -// It updates the cache with a record and then modifies the record externally. +// It adjusts the cache with a record and then modifies the record externally. // It then checks if the record in the cache is still the original record. // This is a desired behavior that is guaranteed by the underlying HeroCache library. -// In other words, we don't desire the records to be externally mutable after they are added to the cache (unless by a subsequent call to Update). +// In other words, we don't desire the records to be externally mutable after they are added to the cache (unless by a subsequent call to Adjust). func TestGossipSubSpamRecordCache_ByValue(t *testing.T) { - cache := netcache.NewGossipSubSpamRecordCache(200, unittest.Logger(), metrics.NewNoopCollector()) + cache := netcache.NewGossipSubSpamRecordCache(200, unittest.Logger(), metrics.NewNoopCollector(), func() p2p.GossipSubSpamRecord { + return p2p.GossipSubSpamRecord{ + Decay: 0, + Penalty: 0, + LastDecayAdjustment: time.Now(), + } + }) - peerID := "peer1" - added := cache.Add(peer.ID(peerID), p2p.GossipSubSpamRecord{ - Decay: 0.1, - Penalty: 0.5, + peerID := unittest.PeerIdFixture(t) + // adjusts a non-existing record, which should initiate the record. + record, err := cache.Adjust(peerID, func(record p2p.GossipSubSpamRecord) p2p.GossipSubSpamRecord { + record.Penalty = 0.5 + record.Decay = 0.1 + return record }) - require.True(t, added) + require.NoError(t, err) + require.NotNil(t, record) + require.Equal(t, 0.5, record.Penalty) // checks if the penalty is not changed. + require.Equal(t, 0.1, record.Decay) // checks if the decay is not changed. // get the record from the cache - record, err, found := cache.Get(peer.ID(peerID)) + record, err, found := cache.Get(peerID) require.True(t, found) require.NoError(t, err) @@ -296,7 +293,7 @@ func TestGossipSubSpamRecordCache_ByValue(t *testing.T) { record.Penalty = 0.8 // get the record from the cache again - record, err, found = cache.Get(peer.ID(peerID)) + record, err, found = cache.Get(peerID) require.True(t, found) require.NoError(t, err) @@ -305,10 +302,16 @@ func TestGossipSubSpamRecordCache_ByValue(t *testing.T) { require.Equal(t, 0.5, record.Penalty) } -// TestGossipSubSpamRecordCache_Get_With_Preprocessors tests if the cache applies the preprocessors to the records -// before returning them. +// TestGossipSubSpamRecordCache_Get_With_Preprocessors tests if the cache applies the preprocessors to the records before returning them. func TestGossipSubSpamRecordCache_Get_With_Preprocessors(t *testing.T) { cache := netcache.NewGossipSubSpamRecordCache(10, unittest.Logger(), metrics.NewNoopCollector(), + func() p2p.GossipSubSpamRecord { + return p2p.GossipSubSpamRecord{ + Decay: 0, + Penalty: 0, + LastDecayAdjustment: time.Now(), + } + }, // first preprocessor: adds 1 to the penalty. func(record p2p.GossipSubSpamRecord, lastUpdated time.Time) (p2p.GossipSubSpamRecord, error) { record.Penalty++ @@ -321,22 +324,24 @@ func TestGossipSubSpamRecordCache_Get_With_Preprocessors(t *testing.T) { }, ) - record := p2p.GossipSubSpamRecord{ - Decay: 0.5, - Penalty: 1, - } - added := cache.Add("peerA", record) - assert.True(t, added) + peerId := unittest.PeerIdFixture(t) + adjustedRecord, err := cache.Adjust(peerId, func(record p2p.GossipSubSpamRecord) p2p.GossipSubSpamRecord { + record.Penalty = 1 + record.Decay = 0.5 + return record + }) + require.NoError(t, err) + require.Equal(t, 1.0, adjustedRecord.Penalty) - // verifies that the preprocessors were called and the record was updated accordingly. - cachedRecord, err, ok := cache.Get("peerA") - assert.NoError(t, err) - assert.True(t, ok) + // verifies that the preprocessors were called and the record was adjusted accordingly. + cachedRecord, err, ok := cache.Get(peerId) + require.NoError(t, err) + require.True(t, ok) // expected penalty is 4: the first preprocessor adds 1 to the penalty and the second preprocessor multiplies the penalty by 2. // (1 + 1) * 2 = 4 - assert.Equal(t, 4.0, cachedRecord.Penalty) // penalty should be updated - assert.Equal(t, 0.5, cachedRecord.Decay) // decay should not be modified + require.Equal(t, 4.0, cachedRecord.Penalty) // penalty should be adjusted + require.Equal(t, 0.5, cachedRecord.Decay) // decay should not be modified } // TestGossipSubSpamRecordCache_Get_Preprocessor_Error tests if the cache returns an error if one of the preprocessors returns an error upon a Get. @@ -349,15 +354,22 @@ func TestGossipSubSpamRecordCache_Get_Preprocessor_Error(t *testing.T) { thirdPreprocessorCalledCount := 0 cache := netcache.NewGossipSubSpamRecordCache(10, unittest.Logger(), metrics.NewNoopCollector(), + func() p2p.GossipSubSpamRecord { + return p2p.GossipSubSpamRecord{ + Decay: 0, + Penalty: 0, + LastDecayAdjustment: time.Now(), + } + }, // first preprocessor: adds 1 to the penalty. func(record p2p.GossipSubSpamRecord, lastUpdated time.Time) (p2p.GossipSubSpamRecord, error) { record.Penalty++ return record, nil }, - // second preprocessor: multiplies the penalty by 2 (this preprocessor returns an error on the second call) + // second preprocessor: multiplies the penalty by 2 (this preprocessor returns an error on the third call and forward) func(record p2p.GossipSubSpamRecord, lastUpdated time.Time) (p2p.GossipSubSpamRecord, error) { secondPreprocessorCalledCount++ - if secondPreprocessorCalledCount < 2 { + if secondPreprocessorCalledCount < 3 { // on the first call, the preprocessor is successful return record, nil } else { @@ -365,109 +377,153 @@ func TestGossipSubSpamRecordCache_Get_Preprocessor_Error(t *testing.T) { return p2p.GossipSubSpamRecord{}, fmt.Errorf("error in preprocessor") } }, - // since second preprocessor returns an error on the second call, the third preprocessor should not be called more than once.. + // since second preprocessor returns an error on the second call, the third preprocessor should not be called more than once. func(record p2p.GossipSubSpamRecord, lastUpdated time.Time) (p2p.GossipSubSpamRecord, error) { thirdPreprocessorCalledCount++ - require.Less(t, secondPreprocessorCalledCount, 2) + require.Less(t, secondPreprocessorCalledCount, 3) return record, nil }, ) - record := p2p.GossipSubSpamRecord{ - Decay: 0.5, - Penalty: 1, - } - added := cache.Add("peerA", record) - assert.True(t, added) + peerId := unittest.PeerIdFixture(t) + adjustedRecord, err := cache.Adjust(peerId, func(record p2p.GossipSubSpamRecord) p2p.GossipSubSpamRecord { + record.Penalty = 1 + record.Decay = 0.5 + return record + }) + require.NoError(t, err) + require.Equal(t, 1.0, adjustedRecord.Penalty) + require.Equal(t, 0.5, adjustedRecord.Decay) - // verifies that the preprocessors were called and the penalty was updated accordingly. - cachedRecord, err, ok := cache.Get("peerA") + // verifies that the preprocessors were called and the penalty was adjusted accordingly. + cachedRecord, err, ok := cache.Get(peerId) require.NoError(t, err) - assert.True(t, ok) - assert.Equal(t, 2.0, cachedRecord.Penalty) // penalty should be updated by the first preprocessor (1 + 1 = 2) - assert.Equal(t, 0.5, cachedRecord.Decay) + require.True(t, ok) + require.Equal(t, 2.0, cachedRecord.Penalty) // penalty should be adjusted by the first preprocessor (1 + 1 = 2) + require.Equal(t, 0.5, cachedRecord.Decay) // query the cache again that should trigger the second preprocessor to return an error. - cachedRecord, err, ok = cache.Get("peerA") + cachedRecord, err, ok = cache.Get(peerId) require.Error(t, err) - assert.False(t, ok) - assert.Nil(t, cachedRecord) + require.False(t, ok) + require.Nil(t, cachedRecord) - // verifies that the third preprocessor was not called. - assert.Equal(t, 1, thirdPreprocessorCalledCount) - // verifies that the second preprocessor was called only twice (one success, and one failure). - assert.Equal(t, 2, secondPreprocessorCalledCount) + // verifies that the third preprocessor was called only twice (two success calls). + require.Equal(t, 2, thirdPreprocessorCalledCount) + // verifies that the second preprocessor was called three times (two success calls and one failure call). + require.Equal(t, 3, secondPreprocessorCalledCount) } // TestGossipSubSpamRecordCache_Get_Without_Preprocessors tests when no preprocessors are provided to the cache constructor // that the cache returns the original record without any modifications. func TestGossipSubSpamRecordCache_Get_Without_Preprocessors(t *testing.T) { - cache := netcache.NewGossipSubSpamRecordCache(10, unittest.Logger(), metrics.NewNoopCollector()) + cache := netcache.NewGossipSubSpamRecordCache(10, unittest.Logger(), metrics.NewNoopCollector(), func() p2p.GossipSubSpamRecord { + return p2p.GossipSubSpamRecord{ + Decay: 0, + Penalty: 0, + LastDecayAdjustment: time.Now(), + } + }) - record := p2p.GossipSubSpamRecord{ - Decay: 0.5, - Penalty: 1, - } - added := cache.Add("peerA", record) - assert.True(t, added) - - // verifies that no preprocessors were called and the record was not updated. - cachedRecord, err, ok := cache.Get("peerA") - assert.NoError(t, err) - assert.True(t, ok) - assert.Equal(t, 1.0, cachedRecord.Penalty) - assert.Equal(t, 0.5, cachedRecord.Decay) + peerId := unittest.PeerIdFixture(t) + adjustedRecord, err := cache.Adjust(peerId, func(record p2p.GossipSubSpamRecord) p2p.GossipSubSpamRecord { + record.Penalty = 1 + record.Decay = 0.5 + return record + }) + require.NoError(t, err) + require.Equal(t, 1.0, adjustedRecord.Penalty) + require.Equal(t, 0.5, adjustedRecord.Decay) + + // verifies that no preprocessors were called and the record was not adjusted. + cachedRecord, err, ok := cache.Get(peerId) + require.NoError(t, err) + require.True(t, ok) + require.Equal(t, 1.0, cachedRecord.Penalty) + require.Equal(t, 0.5, cachedRecord.Decay) } -// TestGossipSubSpamRecordCache_Duplicate_Add_Sequential tests if the cache returns false when a duplicate record is added to the cache. +// TestGossipSubSpamRecordCache_Duplicate_Adjust_Sequential tests if the cache returns false when a duplicate record is added to the cache. // This test evaluates that the cache de-duplicates the records based on their peer id and not content, and hence // each peer id can only be added once to the cache. -func TestGossipSubSpamRecordCache_Duplicate_Add_Sequential(t *testing.T) { - cache := netcache.NewGossipSubSpamRecordCache(10, unittest.Logger(), metrics.NewNoopCollector()) +func TestGossipSubSpamRecordCache_Duplicate_Adjust_Sequential(t *testing.T) { + cache := netcache.NewGossipSubSpamRecordCache(10, unittest.Logger(), metrics.NewNoopCollector(), func() p2p.GossipSubSpamRecord { + return p2p.GossipSubSpamRecord{ + Decay: 0, + Penalty: 0, + LastDecayAdjustment: time.Now(), + } + }) - record := p2p.GossipSubSpamRecord{ - Decay: 0.5, - Penalty: 1, - } - added := cache.Add("peerA", record) - assert.True(t, added) + peerId := unittest.PeerIdFixture(t) + adjustedRecord, err := cache.Adjust(peerId, func(record p2p.GossipSubSpamRecord) p2p.GossipSubSpamRecord { + record.Penalty = 1 + record.Decay = 0.5 + return record + }) + require.NoError(t, err) + require.Equal(t, 1.0, adjustedRecord.Penalty) + require.Equal(t, 0.5, adjustedRecord.Decay) - // verifies that the cache returns false when a duplicate record is added. - added = cache.Add("peerA", record) - assert.False(t, added) + // duplicate adjust should return the same record. + adjustedRecord, err = cache.Adjust(peerId, func(record p2p.GossipSubSpamRecord) p2p.GossipSubSpamRecord { + record.Penalty = 1 + record.Decay = 0.5 + return record + }) + require.NoError(t, err) + require.Equal(t, 1.0, adjustedRecord.Penalty) + require.Equal(t, 0.5, adjustedRecord.Decay) // verifies that the cache deduplicates the records based on their peer id and not content. - record.Penalty = 2 - added = cache.Add("peerA", record) - assert.False(t, added) + adjustedRecord, err = cache.Adjust(peerId, func(record p2p.GossipSubSpamRecord) p2p.GossipSubSpamRecord { + record.Penalty = 3 + record.Decay = 2 + return record + }) + require.NoError(t, err) + require.Equal(t, 3.0, adjustedRecord.Penalty) + require.Equal(t, 2.0, adjustedRecord.Decay) } -// TestGossipSubSpamRecordCache_Duplicate_Add_Concurrent tests if the cache returns false when a duplicate record is added to the cache. -// Test is the concurrent version of TestAppScoreCache_DuplicateAdd_Sequential. -func TestGossipSubSpamRecordCache_Duplicate_Add_Concurrent(t *testing.T) { - cache := netcache.NewGossipSubSpamRecordCache(10, unittest.Logger(), metrics.NewNoopCollector()) +// TestGossipSubSpamRecordCache_Duplicate_Adjust_Concurrent tests if the cache returns false when a duplicate record is added to the cache. +// Test is the concurrent version of TestAppScoreCache_Duplicate_Adjust_Sequential. +func TestGossipSubSpamRecordCache_Duplicate_Adjust_Concurrent(t *testing.T) { + cache := netcache.NewGossipSubSpamRecordCache(10, unittest.Logger(), metrics.NewNoopCollector(), func() p2p.GossipSubSpamRecord { + return p2p.GossipSubSpamRecord{ + Decay: 0, + Penalty: 0, + LastDecayAdjustment: time.Now(), + } + }) successAdd := atomic.Int32{} successAdd.Store(0) record1 := p2p.GossipSubSpamRecord{ - Decay: 0.5, + Decay: 1, Penalty: 1, } record2 := p2p.GossipSubSpamRecord{ - Decay: 0.5, + Decay: 1, Penalty: 2, } wg := sync.WaitGroup{} // wait group to wait for all goroutines to finish. wg.Add(2) + peerId := unittest.PeerIdFixture(t) // adds a record to the cache concurrently. - add := func(record p2p.GossipSubSpamRecord) { - added := cache.Add("peerA", record) - if added { - successAdd.Inc() - } + add := func(newRecord p2p.GossipSubSpamRecord) { + _, err := cache.Adjust(peerId, func(record p2p.GossipSubSpamRecord) p2p.GossipSubSpamRecord { + record.Penalty = newRecord.Penalty + record.Decay = newRecord.Decay + record.LastDecayAdjustment = newRecord.LastDecayAdjustment + return record + }) + require.NoError(t, err) + successAdd.Inc() + wg.Done() } @@ -476,6 +532,12 @@ func TestGossipSubSpamRecordCache_Duplicate_Add_Concurrent(t *testing.T) { unittest.RequireReturnsBefore(t, wg.Wait, 100*time.Millisecond, "could not add records to the cache") - // verifies that only one of the records was added to the cache. - assert.Equal(t, int32(1), successAdd.Load()) + // verifies that both of the records was added to the cache. + require.Equal(t, int32(2), successAdd.Load()) + + // verifies that the record is adjusted to one of the records. + cachedRecord, err, ok := cache.Get(peerId) + require.NoError(t, err) + require.True(t, ok) + require.True(t, cachedRecord.Penalty == 1 && cachedRecord.Decay == 1 || cachedRecord.Penalty == 2 && cachedRecord.Decay == 1) } diff --git a/network/p2p/cache/node_blocklist_wrapper_test.go b/network/p2p/cache/node_blocklist_wrapper_test.go index bdeb50ffd27..cf05dd71e73 100644 --- a/network/p2p/cache/node_blocklist_wrapper_test.go +++ b/network/p2p/cache/node_blocklist_wrapper_test.go @@ -17,7 +17,7 @@ import ( "github.com/onflow/flow-go/network" "github.com/onflow/flow-go/network/mocknetwork" "github.com/onflow/flow-go/network/p2p/cache" - "github.com/onflow/flow-go/network/p2p/p2pnet" + "github.com/onflow/flow-go/network/underlay" "github.com/onflow/flow-go/utils/unittest" ) @@ -177,7 +177,7 @@ func (s *NodeDisallowListWrapperTestSuite) TestDisallowListNode() { s.provider.On("Identities", mock.Anything).Return(combinedIdentities) - identities := s.wrapper.Identities(p2pnet.NotEjectedFilter) + identities := s.wrapper.Identities(underlay.NotEjectedFilter) require.Equal(s.T(), len(honestIdentities), len(identities)) // expected only honest nodes to be returned for _, i := range identities { diff --git a/network/p2p/p2pconf/errors.go b/network/p2p/config/errors.go similarity index 97% rename from network/p2p/p2pconf/errors.go rename to network/p2p/config/errors.go index 88417ced914..ff5f989fe21 100644 --- a/network/p2p/p2pconf/errors.go +++ b/network/p2p/config/errors.go @@ -1,4 +1,4 @@ -package p2pconf +package p2pconfig import ( "errors" diff --git a/network/p2p/p2pconf/errors_test.go b/network/p2p/config/errors_test.go similarity index 98% rename from network/p2p/p2pconf/errors_test.go rename to network/p2p/config/errors_test.go index 681d839a5fa..58db000e1be 100644 --- a/network/p2p/p2pconf/errors_test.go +++ b/network/p2p/config/errors_test.go @@ -1,4 +1,4 @@ -package p2pconf +package p2pconfig import ( "fmt" diff --git a/network/p2p/p2pconf/gossipsub.go b/network/p2p/config/gossipsub.go similarity index 67% rename from network/p2p/p2pconf/gossipsub.go rename to network/p2p/config/gossipsub.go index c5d9a6b3bfd..31b69dd221b 100644 --- a/network/p2p/p2pconf/gossipsub.go +++ b/network/p2p/config/gossipsub.go @@ -1,4 +1,4 @@ -package p2pconf +package p2pconfig import ( "time" @@ -54,58 +54,80 @@ type ResourceManagerOverrideLimit struct { Memory int `validate:"gte=0" mapstructure:"memory-bytes"` } -// GossipSubConfig is the configuration for the GossipSub pubsub implementation. -type GossipSubConfig struct { - // GossipSubRPCInspectorsConfig configuration for all gossipsub RPC control message inspectors. - GossipSubRPCInspectorsConfig `mapstructure:",squash"` +// GossipSubParameters keys. +const ( + RpcInspectorKey = "rpc-inspector" + RpcTracerKey = "rpc-tracer" + PeerScoringEnabledKey = "peer-scoring-enabled" + ScoreParamsKey = "scoring-parameters" + SubscriptionProviderKey = "subscription-provider" +) - // GossipSubTracerConfig is the configuration for the gossipsub tracer. GossipSub tracer is used to trace the local mesh events and peer scores. - GossipSubTracerConfig `mapstructure:",squash"` +// GossipSubParameters is the configuration for the GossipSub pubsub implementation. +type GossipSubParameters struct { + // RpcInspectorParameters configuration for all gossipsub RPC control message inspectors. + RpcInspector RpcInspectorParameters `mapstructure:"rpc-inspector"` // GossipSubScoringRegistryConfig is the configuration for the GossipSub score registry. - GossipSubScoringRegistryConfig `mapstructure:",squash"` + // GossipSubTracerParameters is the configuration for the gossipsub tracer. GossipSub tracer is used to trace the local mesh events and peer scores. + RpcTracer GossipSubTracerParameters `mapstructure:"rpc-tracer"` + + // ScoringParameters is whether to enable GossipSub peer scoring. + PeerScoringEnabled bool `mapstructure:"peer-scoring-enabled"` + SubscriptionProvider SubscriptionProviderParameters `mapstructure:"subscription-provider"` + ScoringParameters ScoringParameters `mapstructure:"scoring-parameters"` +} - // PeerScoring is whether to enable GossipSub peer scoring. - PeerScoring bool `mapstructure:"gossipsub-peer-scoring-enabled"` +const ( + DecayIntervalKey = "decay-interval" +) - SubscriptionProviderConfig SubscriptionProviderParameters `mapstructure:",squash"` +// ScoringParameters are the parameters for the score option. +// Parameters are "numerical values" that are used to compute or build components that compute the score of a peer in GossipSub system. +type ScoringParameters struct { + PeerScoring PeerScoringParameters `validate:"required" mapstructure:"peer-scoring"` + ScoringRegistryParameters ScoringRegistryParameters `validate:"required" mapstructure:"scoring-registry"` } +// SubscriptionProviderParameters keys. +const ( + UpdateIntervalKey = "update-interval" + CacheSizeKey = "cache-size" +) + type SubscriptionProviderParameters struct { - // SubscriptionUpdateInterval is the interval for updating the list of topics the node have subscribed to; as well as the list of all + // UpdateInterval is the interval for updating the list of topics the node have subscribed to; as well as the list of all // peers subscribed to each of those topics. This is used to penalize peers that have an invalid subscription based on their role. - SubscriptionUpdateInterval time.Duration `validate:"gt=0s" mapstructure:"gossipsub-subscription-provider-update-interval"` + UpdateInterval time.Duration `validate:"gt=0s" mapstructure:"update-interval"` // CacheSize is the size of the cache that keeps the list of peers subscribed to each topic as the local node. // This is the local view of the current node towards the subscription status of other nodes in the system. // The cache must be large enough to accommodate the maximum number of nodes in the system, otherwise the view of the local node will be incomplete // due to cache eviction. - CacheSize uint32 `validate:"gt=0" mapstructure:"gossipsub-subscription-provider-cache-size"` + CacheSize uint32 `validate:"gt=0" mapstructure:"cache-size"` } -// GossipSubScoringRegistryConfig is the configuration for the GossipSub score registry. -type GossipSubScoringRegistryConfig struct { - // PenaltyDecaySlowdownThreshold defines the penalty level which the decay rate is reduced by `DecayRateReductionFactor` every time the penalty of a node falls below the threshold, thereby slowing down the decay process. - // This mechanism ensures that malicious nodes experience longer decay periods, while honest nodes benefit from quicker decay. - PenaltyDecaySlowdownThreshold float64 `validate:"lt=0" mapstructure:"gossipsub-app-specific-penalty-decay-slowdown-threshold"` - // DecayRateReductionFactor defines the value by which the decay rate is decreased every time the penalty is below the PenaltyDecaySlowdownThreshold. A reduced decay rate extends the time it takes for penalties to diminish. - DecayRateReductionFactor float64 `validate:"gt=0,lt=1" mapstructure:"gossipsub-app-specific-penalty-decay-rate-reduction-factor"` - // PenaltyDecayEvaluationPeriod defines the interval at which the decay for a spam record is okay to be adjusted. - PenaltyDecayEvaluationPeriod time.Duration `validate:"gt=0" mapstructure:"gossipsub-app-specific-penalty-decay-evaluation-period"` -} +// GossipSubTracerParameters keys. +const ( + LocalMeshLogIntervalKey = "local-mesh-logging-interval" + ScoreTracerIntervalKey = "score-tracer-interval" + RPCSentTrackerCacheSizeKey = "rpc-sent-tracker-cache-size" + RPCSentTrackerQueueCacheSizeKey = "rpc-sent-tracker-queue-cache-size" + RPCSentTrackerNumOfWorkersKey = "rpc-sent-tracker-workers" +) -// GossipSubTracerConfig is the config for the gossipsub tracer. GossipSub tracer is used to trace the local mesh events and peer scores. -type GossipSubTracerConfig struct { +// GossipSubTracerParameters is the config for the gossipsub tracer. GossipSub tracer is used to trace the local mesh events and peer scores. +type GossipSubTracerParameters struct { // LocalMeshLogInterval is the interval at which the local mesh is logged. - LocalMeshLogInterval time.Duration `validate:"gt=0s" mapstructure:"gossipsub-local-mesh-logging-interval"` + LocalMeshLogInterval time.Duration `validate:"gt=0s" mapstructure:"local-mesh-logging-interval"` // ScoreTracerInterval is the interval at which the score tracer logs the peer scores. - ScoreTracerInterval time.Duration `validate:"gt=0s" mapstructure:"gossipsub-score-tracer-interval"` + ScoreTracerInterval time.Duration `validate:"gt=0s" mapstructure:"score-tracer-interval"` // RPCSentTrackerCacheSize cache size of the rpc sent tracker used by the gossipsub mesh tracer. - RPCSentTrackerCacheSize uint32 `validate:"gt=0" mapstructure:"gossipsub-rpc-sent-tracker-cache-size"` + RPCSentTrackerCacheSize uint32 `validate:"gt=0" mapstructure:"rpc-sent-tracker-cache-size"` // RPCSentTrackerQueueCacheSize cache size of the rpc sent tracker queue used for async tracking. - RPCSentTrackerQueueCacheSize uint32 `validate:"gt=0" mapstructure:"gossipsub-rpc-sent-tracker-queue-cache-size"` + RPCSentTrackerQueueCacheSize uint32 `validate:"gt=0" mapstructure:"rpc-sent-tracker-queue-cache-size"` // RpcSentTrackerNumOfWorkers number of workers for rpc sent tracker worker pool. - RpcSentTrackerNumOfWorkers int `validate:"gt=0" mapstructure:"gossipsub-rpc-sent-tracker-workers"` + RpcSentTrackerNumOfWorkers int `validate:"gt=0" mapstructure:"rpc-sent-tracker-workers"` } // ResourceScope is the scope of the resource, e.g., system, transient, protocol, peer, peer-protocol. diff --git a/network/p2p/config/gossipsub_rpc_inspectors.go b/network/p2p/config/gossipsub_rpc_inspectors.go new file mode 100644 index 00000000000..a2e4b9f180e --- /dev/null +++ b/network/p2p/config/gossipsub_rpc_inspectors.go @@ -0,0 +1,237 @@ +package p2pconfig + +// RpcInspectorParameters keys. +const ( + ValidationConfigKey = "validation" + MetricsConfigKey = "metrics" + NotificationCacheSizeKey = "notification-cache-size" +) + +// RpcInspectorParameters contains the "numerical values" for the gossipsub RPC control message inspectors parameters. +type RpcInspectorParameters struct { + // RpcValidationInspector control message validation inspector validation configuration and limits. + Validation RpcValidationInspector `mapstructure:"validation"` + // NotificationCacheSize size of the queue for notifications about invalid RPC messages. + NotificationCacheSize uint32 `mapstructure:"notification-cache-size"` +} + +// RpcValidationInspectorParameters keys. +const ( + ProcessKey = "process" + ClusterPrefixedMessageConfigKey = "cluster-prefixed-messages" + IWantConfigKey = "iwant" + IHaveConfigKey = "ihave" + GraftPruneKey = "graft-and-prune" + PublishMessagesConfigKey = "publish-messages" + InspectionQueueConfigKey = "inspection-queue" +) + +// RpcValidationInspector rpc control message validation inspector configuration. +type RpcValidationInspector struct { + ClusterPrefixedMessage ClusterPrefixedMessageInspectionParameters `mapstructure:"cluster-prefixed-messages"` + IWant IWantRpcInspectionParameters `mapstructure:"iwant"` + IHave IHaveRpcInspectionParameters `mapstructure:"ihave"` + GraftPrune GraftPruneRpcInspectionParameters `mapstructure:"graft-and-prune"` + PublishMessages PublishMessageInspectionParameters `mapstructure:"publish-messages"` + InspectionQueue InspectionQueueParameters `mapstructure:"inspection-queue"` + // InspectionProcess configuration that controls which aspects of rpc inspection are enabled and disabled during inspect message request processing. + InspectionProcess InspectionProcess `mapstructure:"process"` +} + +// InspectionProcess configuration that controls which aspects of rpc inspection are enabled and disabled during inspect message request processing. +type InspectionProcess struct { + Inspect Inspect `validate:"required" mapstructure:"inspection"` + Truncate Truncate `validate:"required" mapstructure:"truncation"` +} + +const ( + InspectionKey = "inspection" + TruncationKey = "truncation" + EnableKey = "enable" + DisabledKey = "disabled" + MessageIDKey = "message-id" +) + +// Inspect configuration to enable/disable RPC inspection for a particular control message type. +type Inspect struct { + // Disabled serves as a fail-safe mechanism to globally deactivate inspection logic. When this fail-safe is activated it disables all + // aspects of the inspection logic, irrespective of individual configurations like inspection.enable-graft, inspection.enable-prune, etc. + // Consequently, all metrics collection and logging related to the rpc and inspection will also be disabled. + // It is important to note that activating this fail-safe results in a comprehensive deactivation inspection features. + // Please use this setting judiciously, considering its broad impact on the behavior of control message handling. + Disabled bool `mapstructure:"disabled"` + // EnableGraft enable graft control message inspection. + EnableGraft bool `mapstructure:"enable-graft"` + // EnablePrune enable prune control message inspection. + EnablePrune bool `mapstructure:"enable-prune"` + // EnableIHave enable iHave control message inspection. + EnableIHave bool `mapstructure:"enable-ihave"` + // EnableIWant enable iWant control message inspection. + EnableIWant bool `mapstructure:"enable-iwant"` + // EnablePublish enable publish message inspection. + EnablePublish bool `mapstructure:"enable-publish"` +} + +// Truncate configuration to enable/disable RPC truncation for a particular control message type. +type Truncate struct { + // Disabled serves as a fail-safe mechanism to globally deactivate truncation logic. When this fail-safe is activated it disables all + // aspects of the truncation logic, irrespective of individual configurations like truncation.enable-graft, truncation.enable-prune, etc. + // Consequently, all metrics collection and logging related to the rpc and inspection will also be disabled. + // It is important to note that activating this fail-safe results in a comprehensive deactivation truncation features. + // Please use this setting judiciously, considering its broad impact on the behavior of control message handling. + Disabled bool `mapstructure:"disabled"` + // EnableGraft enable graft control message truncation. + EnableGraft bool `mapstructure:"enable-graft"` + // EnablePrune enable prune control message truncation. + EnablePrune bool `mapstructure:"enable-prune"` + // EnableIHave enable iHave control message truncation. + EnableIHave bool `mapstructure:"enable-ihave"` + // EnableIHaveMessageIds enable iHave message id truncation. + EnableIHaveMessageIds bool `mapstructure:"enable-ihave-message-id"` + // EnableIWant enable iWant control message truncation. + EnableIWant bool `mapstructure:"enable-iwant"` + // EnableIWantMessageIds enable iWant message id truncation. + EnableIWantMessageIds bool `mapstructure:"enable-iwant-message-id"` +} + +const ( + QueueSizeKey = "queue-size" +) + +// InspectionQueueParameters contains the "numerical values" for the control message validation inspector. +// Incoming GossipSub RPCs are queued for async inspection by a worker pool. This worker pool is configured +// by the parameters in this struct. +// Each RPC has a number of "publish messages" accompanied by control messages. +type InspectionQueueParameters struct { + // NumberOfWorkers number of worker pool workers. + NumberOfWorkers int `validate:"gte=1" mapstructure:"workers"` + // Size size of the queue used by worker pool for the control message validation inspector. + Size uint32 `validate:"gte=100" mapstructure:"queue-size"` +} + +const ( + MaxSampleSizeKey = "max-sample-size" + MessageErrorThresholdKey = "error-threshold" +) + +// PublishMessageInspectionParameters contains the "numerical values" for the publish control message inspection. +// Each RPC has a number of "publish messages" accompanied by control messages. This struct contains the limits +// for the inspection of these publish messages. +type PublishMessageInspectionParameters struct { + // MaxSampleSize is the maximum number of messages in a single RPC message that are randomly sampled for async inspection. + // When the size of a single RPC message exceeds this threshold, a random sample is taken for inspection, but the RPC message is not truncated. + MaxSampleSize int `validate:"gte=0" mapstructure:"max-sample-size"` + // ErrorThreshold the threshold at which an error will be returned if the number of invalid RPC messages exceeds this value. + ErrorThreshold int `validate:"gte=0" mapstructure:"error-threshold"` +} + +// GraftPruneRpcInspectionParameters contains the "numerical values" for the graft and prune control message inspection. +// Each RPC has a number of "publish messages" accompanied by control messages. This struct contains the limits +// for the inspection of these graft and prune control messages. +type GraftPruneRpcInspectionParameters struct { + // MessageCountThreshold is the maximum number of GRAFT or PRUNE messages in a single RPC message. + // When the total number of GRAFT or PRUNE messages in a single RPC message exceeds this threshold, + // a random sample of GRAFT or PRUNE messages will be taken and the RPC message will be truncated to this sample size. + MessageCountThreshold int `validate:"gte=0" mapstructure:"message-count-threshold"` + + // DuplicateTopicIdThreshold is the tolerance threshold for having duplicate topics in a single GRAFT or PRUNE message under inspection. + // Ideally, a GRAFT or PRUNE message should not have any duplicate topics, hence a topic ID is counted as a duplicate only if it is repeated more than once. + // When the total number of duplicate topic ids in a single GRAFT or PRUNE message exceeds this threshold, the inspection of message will fail. + DuplicateTopicIdThreshold int `validate:"gte=0" mapstructure:"duplicate-topic-id-threshold"` +} + +const ( + MessageCountThreshold = "message-count-threshold" + MessageIdCountThreshold = "message-id-count-threshold" + CacheMissThresholdKey = "cache-miss-threshold" + DuplicateMsgIDThresholdKey = "duplicate-message-id-threshold" +) + +// IWantRpcInspectionParameters contains the "numerical values" for iwant rpc control inspection. +// Each RPC has a number of "publish messages" accompanied by control messages. This struct contains the limits +// for the inspection of the iwant control messages. +type IWantRpcInspectionParameters struct { + // MessageCountThreshold is the maximum allowed number of iWant messages in a single RPC message. + // Each iWant message represents the list of message ids. When the total number of iWant messages + // in a single RPC message exceeds this threshold, a random sample of iWant messages will be taken and the RPC message will be truncated to this sample size. + // The sample size is equal to the configured MessageCountThreshold. + MessageCountThreshold uint `validate:"gt=0" mapstructure:"message-count-threshold"` + // MessageIdCountThreshold is the maximum allowed number of message ids in a single iWant message. + // Each iWant message represents the list of message ids for a specific topic, and this parameter controls the maximum number of message ids + // that can be included in a single iWant message. When the total number of message ids in a single iWant message exceeds this threshold, + // a random sample of message ids will be taken and the iWant message will be truncated to this sample size. + // The sample size is equal to the configured MessageIdCountThreshold. + MessageIdCountThreshold int `validate:"gte=0" mapstructure:"message-id-count-threshold"` + // CacheMissThreshold is the threshold of tolerance for the total cache misses in all iWant messages in a single RPC message. + // When the total number of cache misses in all iWant messages in a single RPC message exceeds this threshold, the inspection of message will fail. + // An iWant message is considered a cache miss if it contains a message id that is not present in the local cache for iHave messages, i.e., the node + // does not have a record of an iHave message for this message id. + // When the total number of cache misses in all iWant messages in a single RPC message exceeds this threshold, the inspection of message will fail, and + // a single misbehavior notification will be reported. + CacheMissThreshold int `validate:"gt=0" mapstructure:"cache-miss-threshold"` + // DuplicateMsgIdThreshold is the maximum allowed number of duplicate message ids in a all iWant messages in a single RPC message. + // Each iWant message represents the list of message ids, and this parameter controls the maximum number of duplicate message ids + // that can be included in all iWant messages in a single RPC message. When the total number of duplicate message ids in a single iWant message exceeds this threshold, + // a single misbehavior notification will be reported, and the inspection of message will fail. + DuplicateMsgIdThreshold int `validate:"gt=0" mapstructure:"duplicate-message-id-threshold"` +} + +const ( + DuplicateTopicIdThresholdKey = "duplicate-topic-id-threshold" + DuplicateMessageIdThresholdKey = "duplicate-message-id-threshold" +) + +// IHaveRpcInspectionParameters contains the "numerical values" for ihave rpc control inspection. +// Each RPC has a number of "publish messages" accompanied by control messages. This struct contains the limits +// for the inspection of the ihave control messages. +type IHaveRpcInspectionParameters struct { + // MessageCountThreshold is the maximum allowed number of iHave messages in a single RPC message. + // Each iHave message represents the list of message ids for a specific topic. When the total number of iHave messages + // in a single RPC message exceeds this threshold, a random sample of iHave messages will be taken and the RPC message will be truncated to this sample size. + // The sample size is equal to the configured MessageCountThreshold. + MessageCountThreshold int `validate:"gte=0" mapstructure:"message-count-threshold"` + // MessageIdCountThreshold is the maximum allowed number of message ids in a single iHave message. + // Each iHave message represents the list of message ids for a specific topic, and this parameter controls the maximum number of message ids + // that can be included in a single iHave message. When the total number of message ids in a single iHave message exceeds this threshold, + // a random sample of message ids will be taken and the iHave message will be truncated to this sample size. + // The sample size is equal to the configured MessageIdCountThreshold. + MessageIdCountThreshold int `validate:"gte=0" mapstructure:"message-id-count-threshold"` + + // DuplicateTopicIdThreshold is the tolerance threshold for having duplicate topics in an iHave message under inspection. + // When the total number of duplicate topic ids in a single iHave message exceeds this threshold, the inspection of message will fail. + // Note that a topic ID is counted as a duplicate only if it is repeated more than DuplicateTopicIdThreshold times. + DuplicateTopicIdThreshold int `validate:"gte=0" mapstructure:"duplicate-topic-id-threshold"` + + // DuplicateMessageIdThreshold is the threshold of tolerance for having duplicate message IDs in a single iHave message under inspection. + // When the total number of duplicate message ids in a single iHave message exceeds this threshold, the inspection of message will fail. + // Ideally, an iHave message should not have any duplicate message IDs, hence a message id is considered duplicate when it is repeated more than once + // within the same iHave message. When the total number of duplicate message ids in a single iHave message exceeds this threshold, the inspection of message will fail. + DuplicateMessageIdThreshold int `validate:"gte=0" mapstructure:"duplicate-message-id-threshold"` +} + +const ( + HardThresholdKey = "hard-threshold" + TrackerCacheSizeKey = "tracker-cache-size" + TrackerCacheDecayKey = "tracker-cache-decay" +) + +// ClusterPrefixedMessageInspectionParameters contains the "numerical values" for cluster prefixed control message inspection. +// Each RPC has a number of "publish messages" accompanied by control messages. This struct contains the limits for the inspection +// of messages (publish messages and control messages) that belongs to cluster prefixed topics. +// Cluster-prefixed topics are topics that are prefixed with the cluster ID of the node that published the message. +type ClusterPrefixedMessageInspectionParameters struct { + // HardThreshold the upper bound on the amount of cluster prefixed control messages that will be processed + // before a node starts to get penalized. This allows LN nodes to process some cluster prefixed control messages during startup + // when the cluster ID's provider is set asynchronously. It also allows processing of some stale messages that may be sent by nodes + // that fall behind in the protocol. After the amount of cluster prefixed control messages processed exceeds this threshold the node + // will be pushed to the edge of the network mesh. + HardThreshold float64 `validate:"gte=0" mapstructure:"hard-threshold"` + // ControlMsgsReceivedCacheSize size of the cache used to track the amount of cluster prefixed topics received by peers. + ControlMsgsReceivedCacheSize uint32 `validate:"gt=0" mapstructure:"tracker-cache-size"` + // ControlMsgsReceivedCacheDecay decay val used for the geometric decay of cache counters used to keep track of cluster prefixed topics received by peers. + ControlMsgsReceivedCacheDecay float64 `validate:"gt=0" mapstructure:"tracker-cache-decay"` +} + +const ( + NumberOfWorkersKey = "workers" +) diff --git a/network/p2p/config/peer_scoring.go b/network/p2p/config/peer_scoring.go new file mode 100644 index 00000000000..d31c9031bb1 --- /dev/null +++ b/network/p2p/config/peer_scoring.go @@ -0,0 +1,232 @@ +package p2pconfig + +import "time" + +const ( + PeerScoringKey = "peer-scoring" + InternalKey = "internal" + ProtocolKey = "protocol" +) + +// PeerScoringParameters encapsulates the parameters of the GossipSub scoring system. +type PeerScoringParameters struct { + // Internal is the internal parameters of the GossipSub scoring system that are hosted by + // the GossipSub system, and are not exposed to the Flow protocol. + // The internal parameters are hosted by the GossipSub system. + Internal InternalGossipSubScoreParams `validate:"required" mapstructure:"internal"` + // Protocol is the protocol parameters of the peer scoring system that is hosted by the Flow protocol. + Protocol ProtocolLevelGossipSubScoreParams `validate:"required" mapstructure:"protocol"` +} + +const ( + AppSpecificScoreWeightKey = "app-specific-score-weight" + DecayToZeroKey = "decay-to-zero" + ThresholdsKey = "thresholds" + BehaviourKey = "behaviour" + TopicKey = "topic" +) + +type InternalGossipSubScoreParams struct { + // AppSpecificScoreWeight is the weight for app-specific scores. It is used to scale the app-specific + // scores to the same range as the other scores. At the current version, we don't distinguish between the app-specific + // scores and the other scores, so we set it to 1. + AppSpecificScoreWeight float64 `validate:"gt=0,lte=1" mapstructure:"app-specific-score-weight"` + // DecayInterval is the decay interval for the overall score of a peer at the GossipSub scoring + // system. We set it to 1 minute so that it is not too short so that a malicious node can recover from a penalty + // and is not too long so that a well-behaved node can't recover from a penalty. + DecayInterval time.Duration `validate:"gte=1m" mapstructure:"decay-interval"` + // DecayToZero is the decay to zero for the overall score of a peer at the GossipSub scoring system. + // It defines the maximum value below which a peer scoring counter is reset to zero. + // This is to prevent the counter from decaying to a very small value. + // The value is 0.01, which means that a counter will be reset to zero if it decays to 0.01. + // When a counter hits the DecayToZero threshold, it means that the peer did not exhibit the behavior + // for a long time, and we can reset the counter. + DecayToZero float64 `validate:"required" mapstructure:"decay-to-zero"` + TopicParameters TopicScoringParameters `validate:"required" mapstructure:"topic"` + Thresholds InternalScoringThresholds `validate:"required" mapstructure:"thresholds"` + Behaviour InternalScoringBehavioural `validate:"required" mapstructure:"behaviour"` +} + +const ( + MaxDebugLogsKey = "max-debug-logs" + AppSpecificKey = "application-specific" +) + +type ProtocolLevelGossipSubScoreParams struct { + MaxDebugLogs uint32 `validate:"lte=50" mapstructure:"max-debug-logs"` + AppSpecificScore ApplicationSpecificScoreParameters `validate:"required" mapstructure:"application-specific"` +} + +const ( + MaxAppSpecificKey = "max-app-specific" + MinAppSpecificKey = "min-app-specific" + UnknownIdentityKey = "unknown-identity" + InvalidSubscriptionKey = "invalid-subscription" + StakedIdentityKey = "staked-identity" + RewardKey = "reward" + PenaltyKey = "penalty" +) + +type ApplicationSpecificScoreParameters struct { + // MaxAppSpecificPenalty the maximum penalty for sever offenses that we apply to a remote node score. The score + // mechanism of GossipSub in Flow is designed in a way that all other infractions are penalized with a fraction of + // this value. We have also set the other parameters such as DefaultGraylistThreshold, DefaultGossipThreshold and DefaultPublishThreshold to + // be a bit higher than this, i.e., MaxAppSpecificPenalty + 1. This ensures that a node with a score of MaxAppSpecificPenalty + // will be graylisted (i.e., all incoming and outgoing RPCs are rejected) and will not be able to publish or gossip any messages. + MaxAppSpecificPenalty float64 `validate:"lt=0" mapstructure:"max-app-specific-penalty"` + // MinAppSpecificPenalty the minimum penalty for sever offenses that we apply to a remote node score. + MinAppSpecificPenalty float64 `validate:"lt=0" mapstructure:"min-app-specific-penalty"` + // UnknownIdentityPenalty is the penalty for unknown identity. It is applied to the peer's score when + // the peer is not in the identity list. + UnknownIdentityPenalty float64 `validate:"lt=0" mapstructure:"unknown-identity-penalty"` + // InvalidSubscriptionPenalty is the penalty for invalid subscription. It is applied to the peer's score when + // the peer subscribes to a topic that it is not authorized to subscribe to. + InvalidSubscriptionPenalty float64 `validate:"lt=0" mapstructure:"invalid-subscription-penalty"` + // MaxAppSpecificReward is the reward for well-behaving staked peers. If a peer does not have + // any misbehavior record, e.g., invalid subscription, invalid message, etc., it will be rewarded with this score. + MaxAppSpecificReward float64 `validate:"gt=0" mapstructure:"max-app-specific-reward"` + // StakedIdentityReward is the reward for staking peers. It is applied to the peer's score when + // the peer does not have any misbehavior record, e.g., invalid subscription, invalid message, etc. + // The purpose is to reward the staking peers for their contribution to the network and prioritize them in neighbor selection. + StakedIdentityReward float64 `validate:"gt=0" mapstructure:"staked-identity-reward"` +} + +const ( + GossipThresholdKey = "gossip" + GraylistThresholdKey = "graylist" + AcceptPXThresholdKey = "accept-px" + OpportunisticGraftThresholdKey = "opportunistic-graft" +) + +// InternalScoringThresholds score option threshold configuration parameters. +type InternalScoringThresholds struct { + // Gossip when a peer's penalty drops below this threshold, + // no gossip is emitted towards that peer and gossip from that peer is ignored. + Gossip float64 `validate:"lt=0" mapstructure:"gossip"` + // Publish when a peer's penalty drops below this threshold, + // self-published messages are not propagated towards this peer. + Publish float64 `validate:"lt=0" mapstructure:"publish"` + // Graylist when a peer's penalty drops below this threshold, the peer is graylisted, i.e., + // incoming RPCs from the peer are ignored. + Graylist float64 `validate:"lt=0" mapstructure:"graylist"` + // AcceptPX when a peer sends us PX information with a prune, we only accept it and connect to the supplied + // peers if the originating peer's penalty exceeds this threshold. + AcceptPX float64 `validate:"gt=0" mapstructure:"accept-px"` + // OpportunisticGraft when the median peer penalty in the mesh drops below this value, + // the peer may select more peers with penalty above the median to opportunistically graft on the mesh. + OpportunisticGraft float64 `validate:"gt=0" mapstructure:"opportunistic-graft"` +} + +const ( + BehaviourPenaltyThresholdKey = "penalty-threshold" + BehaviourPenaltyWeightKey = "penalty-weight" + BehaviourPenaltyDecayKey = "penalty-decay" +) + +// InternalScoringBehavioural score option behaviour configuration parameters. +type InternalScoringBehavioural struct { + // PenaltyThreshold is the threshold when the behavior of a peer is considered as bad by GossipSub. + // Currently, the misbehavior is defined as advertising an iHave without responding to the iWants (iHave broken promises), as well as attempting + // on GRAFT when the peer is considered for a PRUNE backoff, i.e., the local peer does not allow the peer to join the local topic mesh + // for a while, and the remote peer keep attempting on GRAFT (aka GRAFT flood). + // When the misbehavior counter of a peer goes beyond this threshold, the peer is penalized by BehaviorPenaltyWeight (see below) for the excess misbehavior. + // + // An iHave broken promise means that a peer advertises an iHave for a message, but does not respond to the iWant requests for that message. + // For iHave broken promises, the gossipsub scoring works as follows: + // It samples ONLY A SINGLE iHave out of the entire RPC. + // If that iHave is not followed by an actual message within the next 3 seconds, the peer misbehavior counter is incremented by 1. + // + // The counter is also decayed by (0.99) every decay interval (DecayInterval) i.e., every minute. + // Note that misbehaviors are counted by GossipSub across all topics (and is different from the Application Layer Misbehaviors that we count through + // the ALSP system). + PenaltyThreshold float64 `validate:"gt=0" mapstructure:"penalty-threshold"` + // PenaltyWeight is the weight for applying penalty when a peer misbehavior goes beyond the threshold. + // Misbehavior of a peer at gossipsub layer is defined as advertising an iHave without responding to the iWants (broken promises), as well as attempting + // on GRAFT when the peer is considered for a PRUNE backoff, i.e., the local peer does not allow the peer to join the local topic mesh + // This is detected by the GossipSub scoring system, and the peer is penalized by BehaviorPenaltyWeight. + // + // An iHave broken promise means that a peer advertises an iHave for a message, but does not respond to the iWant requests for that message. + // For iHave broken promises, the gossipsub scoring works as follows: + // It samples ONLY A SINGLE iHave out of the entire RPC. + // If that iHave is not followed by an actual message within the next 3 seconds, the peer misbehavior counter is incremented by 1. + PenaltyWeight float64 `validate:"lt=0" mapstructure:"penalty-weight"` + // PenaltyDecay is the decay interval for the misbehavior counter of a peer. The misbehavior counter is + // incremented by GossipSub for iHave broken promises or the GRAFT flooding attacks (i.e., each GRAFT received from a remote peer while that peer is on a PRUNE backoff). + // + // An iHave broken promise means that a peer advertises an iHave for a message, but does not respond to the iWant requests for that message. + // For iHave broken promises, the gossipsub scoring works as follows: + // It samples ONLY A SINGLE iHave out of the entire RPC. + // If that iHave is not followed by an actual message within the next 3 seconds, the peer misbehavior counter is incremented by 1. + // This means that regardless of how many iHave broken promises an RPC contains, the misbehavior counter is incremented by 1. + // That is why we decay the misbehavior counter very slow, as this counter indicates a severe misbehavior. + // The misbehavior counter is decayed per decay interval (i.e., DecayInterval = 1 minute) by GossipSub. + // + // Note that misbehaviors are counted by GossipSub across all topics (and is different from the Application Layer Misbehaviors that we count through + // the ALSP system that is based on the engines report). + PenaltyDecay float64 `validate:"gt=0,lt=1" mapstructure:"penalty-decay"` +} + +const ( + SkipAtomicValidationKey = "skip-atomic-validation" + InvalidMessageDeliveriesWeightKey = "invalid-message-deliveries-weight" + InvalidMessageDeliveriesDecayKey = "invalid-message-deliveries-decay" + TimeInMeshQuantumKey = "time-in-mesh-quantum" + TopicWeightKey = "topic-weight" + MeshMessageDeliveriesDecayKey = "mesh-message-deliveries-decay" + MeshMessageDeliveriesCapKey = "mesh-message-deliveries-cap" + MeshMessageDeliveryThresholdKey = "mesh-message-deliveries-threshold" + MeshDeliveriesWeightKey = "mesh-deliveries-weight" + MeshMessageDeliveriesWindowKey = "mesh-message-deliveries-window" + MeshMessageDeliveryActivationKey = "mesh-message-delivery-activation" +) + +// TopicScoringParameters score option topic validation configuration parameters. +type TopicScoringParameters struct { + // SkipAtomicValidation is the value for the skip atomic validation flag for topics. + // If set it to true, the gossipsub parameter validation will not fail if we leave some of the + // topic parameters at their values, i.e., zero. + SkipAtomicValidation bool `validate:"required" mapstructure:"skip-atomic-validation"` + // InvalidMessageDeliveriesWeight this value is applied to the square of the number of invalid message deliveries on a topic. + // It is used to penalize peers that send invalid messages. By an invalid message, we mean a message that is not signed by the + // publisher, or a message that is not signed by the peer that sent it. + InvalidMessageDeliveriesWeight float64 `validate:"lt=0" mapstructure:"invalid-message-deliveries-weight"` + // InvalidMessageDeliveriesDecay decay factor used to decay the number of invalid message deliveries. + // The total number of invalid message deliveries is multiplied by this factor at each heartbeat interval to + // decay the number of invalid message deliveries, and prevent the peer from being disconnected if it stops + // sending invalid messages. + InvalidMessageDeliveriesDecay float64 `validate:"gt=0,lt=1" mapstructure:"invalid-message-deliveries-decay"` + // TimeInMeshQuantum is the time in mesh quantum for the GossipSub scoring system. It is used to gauge + // a discrete time interval for the time in mesh counter. + TimeInMeshQuantum time.Duration `validate:"gte=1h" mapstructure:"time-in-mesh-quantum"` + // Weight is the weight of a topic in the GossipSub scoring system. + // The overall score of a peer in a topic mesh is multiplied by the weight of the topic when calculating the overall score of the peer. + TopicWeight float64 `validate:"gt=0" mapstructure:"topic-weight"` + // MeshMessageDeliveriesDecay is applied to the number of actual message deliveries in a topic mesh + // at each decay interval (i.e., DecayInterval). + // It is used to decay the number of actual message deliveries, and prevents past message + // deliveries from affecting the current score of the peer. + MeshMessageDeliveriesDecay float64 `validate:"gt=0" mapstructure:"mesh-message-deliveries-decay"` + // MeshMessageDeliveriesCap is the maximum number of actual message deliveries in a topic + // mesh that is used to calculate the score of a peer in that topic mesh. + MeshMessageDeliveriesCap float64 `validate:"gt=0" mapstructure:"mesh-message-deliveries-cap"` + // MeshMessageDeliveryThreshold is the threshold for the number of actual message deliveries in a + // topic mesh that is used to calculate the score of a peer in that topic mesh. + // If the number of actual message deliveries in a topic mesh is less than this value, + // the peer will be penalized by square of the difference between the actual message deliveries and the threshold, + // i.e., -w * (actual - threshold)^2 where `actual` and `threshold` are the actual message deliveries and the + // threshold, respectively, and `w` is the weight (i.e., MeshMessageDeliveriesWeight). + MeshMessageDeliveryThreshold float64 `validate:"gt=0" mapstructure:"mesh-message-deliveries-threshold"` + // MeshDeliveriesWeight is the weight for applying penalty when a peer is under-performing in a topic mesh. + // Upon every decay interval, if the number of actual message deliveries is less than the topic mesh message deliveries threshold + // (i.e., MeshMessageDeliveriesThreshold), the peer will be penalized by square of the difference between the actual + // message deliveries and the threshold, multiplied by this weight, i.e., -w * (actual - threshold)^2 where w is the weight, and + // `actual` and `threshold` are the actual message deliveries and the threshold, respectively. + MeshDeliveriesWeight float64 `validate:"lt=0" mapstructure:"mesh-deliveries-weight"` + // MeshMessageDeliveriesWindow is the window size is time interval that we count a delivery of an already + // seen message towards the score of a peer in a topic mesh. The delivery is counted + // by GossipSub only if the previous sender of the message is different from the current sender. + MeshMessageDeliveriesWindow time.Duration `validate:"gte=1m" mapstructure:"mesh-message-deliveries-window"` + // MeshMessageDeliveryActivation is the time interval that we wait for a new peer that joins a topic mesh + // till start counting the number of actual message deliveries of that peer in that topic mesh. + MeshMessageDeliveryActivation time.Duration `validate:"gte=2m" mapstructure:"mesh-message-delivery-activation"` +} diff --git a/network/p2p/config/score_registry.go b/network/p2p/config/score_registry.go new file mode 100644 index 00000000000..aa4f8596d24 --- /dev/null +++ b/network/p2p/config/score_registry.go @@ -0,0 +1,103 @@ +package p2pconfig + +import "time" + +const ( + SpamRecordCacheKey = "spam-record-cache" + ScoringRegistryKey = "scoring-registry" + AppSpecificScoreRegistryKey = "app-specific-score" + StartupSilenceDurationKey = "startup-silence-duration" +) + +type ScoringRegistryParameters struct { + // StartupSilenceDuration defines the duration of time, after the node startup, + // during which the scoring registry remains inactive before penalizing nodes. + // Throughout this startup silence period, the application-specific penalty + // for all nodes will be set to 0, and any invalid control message notifications + // will be ignored. + // + // This configuration allows nodes to stabilize and initialize before + // applying penalties or responding processing invalid control message notifications. + StartupSilenceDuration time.Duration `validate:"gt=10m" mapstructure:"startup-silence-duration"` + AppSpecificScore AppSpecificScoreParameters `validate:"required" mapstructure:"app-specific-score"` + SpamRecordCache SpamRecordCacheParameters `validate:"required" mapstructure:"spam-record-cache"` + MisbehaviourPenalties MisbehaviourPenalties `validate:"required" mapstructure:"misbehaviour-penalties"` +} + +const ( + ScoreUpdateWorkerNumKey = "score-update-worker-num" + ScoreUpdateRequestQueueSizeKey = "score-update-request-queue-size" + ScoreTTLKey = "score-ttl" +) + +// AppSpecificScoreParameters is the parameters for the GossipSubAppSpecificScoreRegistry. +// Parameters are "numerical values" that are used to compute or build components that compute or maintain the application specific score of peers. +type AppSpecificScoreParameters struct { + // ScoreUpdateWorkerNum is the number of workers in the worker pool for handling the application specific score update of peers in a non-blocking way. + ScoreUpdateWorkerNum int `validate:"gt=0" mapstructure:"score-update-worker-num"` + + // ScoreUpdateRequestQueueSize is the size of the worker pool for handling the application specific score update of peers in a non-blocking way. + ScoreUpdateRequestQueueSize uint32 `validate:"gt=0" mapstructure:"score-update-request-queue-size"` + + // ScoreTTL is the time to live of the application specific score of a peer; the registry keeps a cached copy of the + // application specific score of a peer for this duration. When the duration expires, the application specific score + // of the peer is updated asynchronously. As long as the update is in progress, the cached copy of the application + // specific score of the peer is used even if it is expired. + ScoreTTL time.Duration `validate:"required" mapstructure:"score-ttl"` +} + +const ( + DecayKey = "decay" +) + +type SpamRecordCacheParameters struct { + // CacheSize is size of the cache used to store the spam records of peers. + // The spam records are used to penalize peers that send invalid messages. + CacheSize uint32 `validate:"gt=0" mapstructure:"cache-size"` + Decay SpamRecordCacheDecay `validate:"required" mapstructure:"decay"` +} + +const ( + PenaltyDecaySlowdownThresholdKey = "penalty-decay-slowdown-threshold" + DecayRateReductionFactorKey = "penalty-decay-rate-reduction-factor" + PenaltyDecayEvaluationPeriodKey = "penalty-decay-evaluation-period" + MinimumSpamPenaltyDecayFactorKey = "minimum-spam-penalty-decay-factor" + MaximumSpamPenaltyDecayFactorKey = "maximum-spam-penalty-decay-factor" + SkipDecayThresholdKey = "skip-decay-threshold" +) + +type SpamRecordCacheDecay struct { + // PenaltyDecaySlowdownThreshold defines the penalty level which the decay rate is reduced by `DecayRateReductionFactor` every time the penalty of a node falls below the threshold, thereby slowing down the decay process. + // This mechanism ensures that malicious nodes experience longer decay periods, while honest nodes benefit from quicker decay. + PenaltyDecaySlowdownThreshold float64 `validate:"lt=0" mapstructure:"penalty-decay-slowdown-threshold"` + + // DecayRateReductionFactor defines the value by which the decay rate is decreased every time the penalty is below the PenaltyDecaySlowdownThreshold. A reduced decay rate extends the time it takes for penalties to diminish. + DecayRateReductionFactor float64 `validate:"gt=0,lt=1" mapstructure:"penalty-decay-rate-reduction-factor"` + + // PenaltyDecayEvaluationPeriod defines the interval at which the decay for a spam record is okay to be adjusted. + PenaltyDecayEvaluationPeriod time.Duration `validate:"gt=0" mapstructure:"penalty-decay-evaluation-period"` + + SkipDecayThreshold float64 `validate:"gt=-1,lt=0" mapstructure:"skip-decay-threshold"` + + MinimumSpamPenaltyDecayFactor float64 `validate:"gt=0,lte=1" mapstructure:"minimum-spam-penalty-decay-factor"` + MaximumSpamPenaltyDecayFactor float64 `validate:"gt=0,lte=1" mapstructure:"maximum-spam-penalty-decay-factor"` +} + +const ( + MisbehaviourPenaltiesKey = "misbehaviour-penalties" + GraftKey = "graft" + PruneKey = "prune" + IWantKey = "iwant" + IHaveKey = "ihave" + PublishKey = "publish" + ClusterPrefixedReductionFactorKey = "cluster-prefixed-reduction-factor" +) + +type MisbehaviourPenalties struct { + GraftMisbehaviour float64 `validate:"lt=0" mapstructure:"graft"` + PruneMisbehaviour float64 `validate:"lt=0" mapstructure:"prune"` + IHaveMisbehaviour float64 `validate:"lt=0" mapstructure:"ihave"` + IWantMisbehaviour float64 `validate:"lt=0" mapstructure:"iwant"` + PublishMisbehaviour float64 `validate:"lt=0" mapstructure:"publish"` + ClusterPrefixedReductionFactor float64 `validate:"gt=0,lte=1" mapstructure:"cluster-prefixed-reduction-factor"` +} diff --git a/network/p2p/connection/connManager.go b/network/p2p/connection/connManager.go index 54d85175cce..38f6773843f 100644 --- a/network/p2p/connection/connManager.go +++ b/network/p2p/connection/connManager.go @@ -31,7 +31,7 @@ var _ connmgr.ConnManager = (*ConnManager)(nil) // It errors if creating the basic connection manager of libp2p fails. // The error is not benign, and we should crash the node if it happens. // It is a malpractice to start the node without connection manager. -func NewConnManager(logger zerolog.Logger, metric module.LibP2PConnectionMetrics, cfg *netconf.ConnectionManagerConfig) (*ConnManager, error) { +func NewConnManager(logger zerolog.Logger, metric module.LibP2PConnectionMetrics, cfg *netconf.ConnectionManager) (*ConnManager, error) { basic, err := libp2pconnmgr.NewConnManager( cfg.LowWatermark, cfg.HighWatermark, diff --git a/network/p2p/connection/connManager_test.go b/network/p2p/connection/connManager_test.go index 4416055c39d..115192a67f8 100644 --- a/network/p2p/connection/connManager_test.go +++ b/network/p2p/connection/connManager_test.go @@ -59,7 +59,7 @@ func TestConnectionManagerProtection(t *testing.T) { flowConfig, err := config.DefaultConfig() require.NoError(t, err) noopMetrics := metrics.NewNoopCollector() - connManager, err := connection.NewConnManager(log, noopMetrics, &flowConfig.NetworkConfig.ConnectionManagerConfig) + connManager, err := connection.NewConnManager(log, noopMetrics, &flowConfig.NetworkConfig.ConnectionManager) require.NoError(t, err) testCases := [][]fun{ @@ -106,7 +106,7 @@ func TestConnectionManager_Watermarking(t *testing.T) { signalerCtx := irrecoverable.NewMockSignalerContext(t, ctx) defer cancel() - cfg := &netconf.ConnectionManagerConfig{ + cfg := &netconf.ConnectionManager{ HighWatermark: 4, // whenever the number of connections exceeds 4, connection manager prune connections. LowWatermark: 2, // connection manager prune connections until the number of connections is 2. GracePeriod: 500 * time.Millisecond, // extra connections will be pruned if they are older than a second (just for testing). diff --git a/network/p2p/connection/connection_gater.go b/network/p2p/connection/connection_gater.go index a3013af8385..140f92cb87b 100644 --- a/network/p2p/connection/connection_gater.go +++ b/network/p2p/connection/connection_gater.go @@ -12,7 +12,7 @@ import ( "github.com/onflow/flow-go/module" "github.com/onflow/flow-go/network/p2p" - "github.com/onflow/flow-go/network/p2p/p2plogging" + p2plogging "github.com/onflow/flow-go/network/p2p/logging" "github.com/onflow/flow-go/utils/logging" ) diff --git a/network/p2p/connection/connection_gater_test.go b/network/p2p/connection/connection_gater_test.go index 59ef138758d..2addb8b35ed 100644 --- a/network/p2p/connection/connection_gater_test.go +++ b/network/p2p/connection/connection_gater_test.go @@ -18,10 +18,10 @@ import ( "github.com/onflow/flow-go/network/channels" "github.com/onflow/flow-go/network/internal/p2pfixtures" "github.com/onflow/flow-go/network/p2p" + p2pbuilderconfig "github.com/onflow/flow-go/network/p2p/builder/config" "github.com/onflow/flow-go/network/p2p/connection" + p2plogging "github.com/onflow/flow-go/network/p2p/logging" mockp2p "github.com/onflow/flow-go/network/p2p/mock" - p2pconfig "github.com/onflow/flow-go/network/p2p/p2pbuilder/config" - "github.com/onflow/flow-go/network/p2p/p2plogging" p2ptest "github.com/onflow/flow-go/network/p2p/test" "github.com/onflow/flow-go/network/p2p/unicast/stream" "github.com/onflow/flow-go/utils/unittest" @@ -259,7 +259,7 @@ func TestConnectionGater_InterceptUpgrade(t *testing.T) { p2ptest.WithRole(flow.RoleConsensus), p2ptest.WithDefaultStreamHandler(handler), // enable peer manager, with a 1-second refresh rate, and connection pruning enabled. - p2ptest.WithPeerManagerEnabled(&p2pconfig.PeerManagerConfig{ + p2ptest.WithPeerManagerEnabled(&p2pbuilderconfig.PeerManagerConfig{ ConnectionPruning: true, UpdateInterval: 1 * time.Second, ConnectorFactory: connection.DefaultLibp2pBackoffConnectorFactory(), @@ -342,7 +342,7 @@ func TestConnectionGater_Disallow_Integration(t *testing.T) { p2ptest.WithRole(flow.RoleConsensus), p2ptest.WithDefaultStreamHandler(handler), // enable peer manager, with a 1-second refresh rate, and connection pruning enabled. - p2ptest.WithPeerManagerEnabled(&p2pconfig.PeerManagerConfig{ + p2ptest.WithPeerManagerEnabled(&p2pbuilderconfig.PeerManagerConfig{ ConnectionPruning: true, UpdateInterval: 1 * time.Second, ConnectorFactory: connection.DefaultLibp2pBackoffConnectorFactory(), diff --git a/network/p2p/connection/connector.go b/network/p2p/connection/connector.go index 2e59c595bf7..f551cff7c10 100644 --- a/network/p2p/connection/connector.go +++ b/network/p2p/connection/connector.go @@ -8,7 +8,7 @@ import ( "github.com/onflow/flow-go/network/internal/p2putils" "github.com/onflow/flow-go/network/p2p" - "github.com/onflow/flow-go/network/p2p/p2plogging" + p2plogging "github.com/onflow/flow-go/network/p2p/logging" "github.com/onflow/flow-go/utils/logging" "github.com/onflow/flow-go/utils/rand" ) diff --git a/network/p2p/connection/connector_factory.go b/network/p2p/connection/connector_factory.go index 10003895953..b00a1016e64 100644 --- a/network/p2p/connection/connector_factory.go +++ b/network/p2p/connection/connector_factory.go @@ -7,8 +7,8 @@ import ( "github.com/libp2p/go-libp2p/core/host" discoveryBackoff "github.com/libp2p/go-libp2p/p2p/discovery/backoff" + "github.com/onflow/crypto/random" - "github.com/onflow/flow-go/crypto/random" "github.com/onflow/flow-go/network/p2p" ) diff --git a/network/p2p/connection/internal/loggerNotifiee.go b/network/p2p/connection/internal/loggerNotifiee.go index 244223a0c1e..26a38db4491 100644 --- a/network/p2p/connection/internal/loggerNotifiee.go +++ b/network/p2p/connection/internal/loggerNotifiee.go @@ -6,7 +6,7 @@ import ( "github.com/rs/zerolog" "github.com/onflow/flow-go/module" - "github.com/onflow/flow-go/network/p2p/p2plogging" + p2plogging "github.com/onflow/flow-go/network/p2p/logging" ) type LoggerNotifiee struct { diff --git a/network/p2p/connection/peerManager.go b/network/p2p/connection/peerManager.go index d8e323813fd..737ef3ad0e6 100644 --- a/network/p2p/connection/peerManager.go +++ b/network/p2p/connection/peerManager.go @@ -12,7 +12,7 @@ import ( "github.com/onflow/flow-go/module/component" "github.com/onflow/flow-go/module/irrecoverable" "github.com/onflow/flow-go/network/p2p" - "github.com/onflow/flow-go/network/p2p/p2plogging" + p2plogging "github.com/onflow/flow-go/network/p2p/logging" "github.com/onflow/flow-go/utils/logging" "github.com/onflow/flow-go/utils/rand" ) diff --git a/network/p2p/dht/dht.go b/network/p2p/dht/dht.go index 930df0e2251..e4104397160 100644 --- a/network/p2p/dht/dht.go +++ b/network/p2p/dht/dht.go @@ -10,7 +10,7 @@ import ( "github.com/rs/zerolog" "github.com/onflow/flow-go/module" - "github.com/onflow/flow-go/network/p2p/p2plogging" + p2plogging "github.com/onflow/flow-go/network/p2p/logging" ) // This produces a new IPFS DHT diff --git a/network/p2p/dht/dht_test.go b/network/p2p/dht/dht_test.go index a0d5fe2fb22..ef23ccb9901 100644 --- a/network/p2p/dht/dht_test.go +++ b/network/p2p/dht/dht_test.go @@ -104,7 +104,7 @@ func TestFindPeerWithDHT(t *testing.T) { // lookup since client i does not know client j's address. unittest.RequireReturnsBefore( t, func() { - err = dhtClientNodes[i].OpenProtectedStream( + err = dhtClientNodes[i].OpenAndWriteOnStream( ctx, dhtClientNodes[j].ID(), t.Name(), func(stream network.Stream) error { // do nothing require.NotNil(t, stream) diff --git a/network/p2p/distributor/gossipsub_inspector.go b/network/p2p/distributor/gossipsub_inspector.go index e61ce744143..d466bf5a134 100644 --- a/network/p2p/distributor/gossipsub_inspector.go +++ b/network/p2p/distributor/gossipsub_inspector.go @@ -11,7 +11,7 @@ import ( "github.com/onflow/flow-go/module/mempool/queue" "github.com/onflow/flow-go/module/metrics" "github.com/onflow/flow-go/network/p2p" - "github.com/onflow/flow-go/network/p2p/p2plogging" + p2plogging "github.com/onflow/flow-go/network/p2p/logging" ) const ( diff --git a/network/p2p/inspector/control_message_metrics.go b/network/p2p/inspector/control_message_metrics.go deleted file mode 100644 index 9047d0f9484..00000000000 --- a/network/p2p/inspector/control_message_metrics.go +++ /dev/null @@ -1,101 +0,0 @@ -package inspector - -import ( - "fmt" - - pubsub "github.com/libp2p/go-libp2p-pubsub" - "github.com/libp2p/go-libp2p/core/peer" - "github.com/rs/zerolog" - - "github.com/onflow/flow-go/engine/common/worker" - "github.com/onflow/flow-go/module/component" - "github.com/onflow/flow-go/module/mempool/queue" - "github.com/onflow/flow-go/module/metrics" - "github.com/onflow/flow-go/network/p2p" - "github.com/onflow/flow-go/network/p2p/inspector/internal" -) - -const ( - // DefaultControlMsgMetricsInspectorNumberOfWorkers default number of workers for the inspector component. - DefaultControlMsgMetricsInspectorNumberOfWorkers = 1 - // DefaultControlMsgMetricsInspectorQueueCacheSize is the default size of the message queue. - DefaultControlMsgMetricsInspectorQueueCacheSize = 100 - // rpcInspectorComponentName the rpc inspector component name. - rpcInspectorComponentName = "gossipsub_rpc_metrics_observer_inspector" -) - -// ObserveRPCMetricsRequest represents a request to capture metrics for the provided RPC -type ObserveRPCMetricsRequest struct { - // Nonce adds random value so that when msg req is stored on hero store a unique ID can be created from the struct fields. - Nonce []byte - // From the sender of the RPC. - From peer.ID - // rpc the rpc message. - rpc *pubsub.RPC -} - -// ControlMsgMetricsInspector a GossipSub RPC inspector that will observe incoming RPC's and collect metrics related to control messages. -type ControlMsgMetricsInspector struct { - component.Component - logger zerolog.Logger - // NumberOfWorkers number of component workers. - NumberOfWorkers int - // workerPool queue that stores *ObserveRPCMetricsRequest that will be processed by component workers. - workerPool *worker.Pool[*ObserveRPCMetricsRequest] - metrics p2p.GossipSubControlMetricsObserver -} - -var _ p2p.GossipSubRPCInspector = (*ControlMsgMetricsInspector)(nil) - -// Inspect submits a request to the worker pool to observe metrics for the rpc. -// All errors returned from this function can be considered benign. -func (c *ControlMsgMetricsInspector) Inspect(from peer.ID, rpc *pubsub.RPC) error { - nonce, err := internal.Nonce() - if err != nil { - return fmt.Errorf("failed to get observe rpc metrics request nonce: %w", err) - } - c.workerPool.Submit(&ObserveRPCMetricsRequest{Nonce: nonce, From: from, rpc: rpc}) - return nil -} - -// ObserveRPC collects metrics for the rpc. -// No error is ever returned from this func. -func (c *ControlMsgMetricsInspector) ObserveRPC(req *ObserveRPCMetricsRequest) error { - c.metrics.ObserveRPC(req.From, req.rpc) - return nil -} - -// Name returns the name of the rpc inspector. -func (c *ControlMsgMetricsInspector) Name() string { - return rpcInspectorComponentName -} - -// NewControlMsgMetricsInspector returns a new *ControlMsgMetricsInspector -func NewControlMsgMetricsInspector(logger zerolog.Logger, metricsObserver p2p.GossipSubControlMetricsObserver, numberOfWorkers int, heroStoreOpts ...queue.HeroStoreConfigOption) *ControlMsgMetricsInspector { - lg := logger.With().Str("component", "gossip_sub_rpc_metrics_observer_inspector").Logger() - c := &ControlMsgMetricsInspector{ - logger: lg, - NumberOfWorkers: numberOfWorkers, - metrics: metricsObserver, - } - - cfg := &queue.HeroStoreConfig{ - SizeLimit: DefaultControlMsgMetricsInspectorQueueCacheSize, - Collector: metrics.NewNoopCollector(), - } - - for _, opt := range heroStoreOpts { - opt(cfg) - } - store := queue.NewHeroStore(cfg.SizeLimit, logger, cfg.Collector) - pool := worker.NewWorkerPoolBuilder[*ObserveRPCMetricsRequest](c.logger, store, c.ObserveRPC).Build() - c.workerPool = pool - - builder := component.NewComponentManagerBuilder() - for i := 0; i < c.NumberOfWorkers; i++ { - builder.AddWorker(pool.WorkerLogic()) - } - c.Component = builder.Build() - - return c -} diff --git a/network/p2p/inspector/internal/cache/cache.go b/network/p2p/inspector/internal/cache/cache.go index 82d8f781a98..d64418d636f 100644 --- a/network/p2p/inspector/internal/cache/cache.go +++ b/network/p2p/inspector/internal/cache/cache.go @@ -51,11 +51,7 @@ type RecordCache struct { func NewRecordCache(config *RecordCacheConfig, recordEntityFactory recordEntityFactory) (*RecordCache, error) { backData := herocache.NewCache(config.sizeLimit, herocache.DefaultOversizeFactor, - // this cache is supposed to keep the cluster prefix control messages received record for the authorized (staked) nodes. Since the number of such nodes is - // expected to be small, we do not eject any records from the cache. The cache size must be large enough to hold all - // the records of the authorized nodes. Also, this cache is keeping at most one record per peer id, so the - // size of the cache must be at least the number of authorized nodes. - heropool.NoEjection, + heropool.LRUEjection, config.logger.With().Str("mempool", "gossipsub=cluster-prefix-control-messages-received-records").Logger(), config.collector) return &RecordCache{ @@ -65,19 +61,6 @@ func NewRecordCache(config *RecordCacheConfig, recordEntityFactory recordEntityF }, nil } -// Init initializes the record cache for the given peer id if it does not exist. -// Returns true if the record is initialized, false otherwise (i.e.: the record already exists). -// Args: -// - nodeID: the node ID of the sender of the control message. -// Returns: -// - true if the record is initialized, false otherwise (i.e.: the record already exists). -// Note that if Init is called multiple times for the same peer id, the record is initialized only once, and the -// subsequent calls return false and do not change the record (i.e.: the record is not re-initialized). -func (r *RecordCache) Init(nodeID flow.Identifier) bool { - entity := r.recordEntityFactory(nodeID) - return r.c.Add(entity) -} - // ReceivedClusterPrefixedMessage applies an adjustment that increments the number of cluster prefixed control messages received by a peer. // Returns number of cluster prefix control messages received after the adjustment. The record is initialized before // the adjustment func is applied that will increment the Gauge. @@ -88,35 +71,32 @@ func (r *RecordCache) Init(nodeID flow.Identifier) bool { // - exception only in cases of internal data inconsistency or bugs. No errors are expected. func (r *RecordCache) ReceivedClusterPrefixedMessage(nodeID flow.Identifier) (float64, error) { var err error - optimisticAdjustFunc := func() (flow.Entity, bool) { - return r.c.Adjust(nodeID, func(entity flow.Entity) flow.Entity { - entity, err = r.decayAdjustment(entity) // first decay the record - if err != nil { - return entity - } - return r.incrementAdjustment(entity) // then increment the record - }) + adjustFunc := func(entity flow.Entity) flow.Entity { + entity, err = r.decayAdjustment(entity) // first decay the record + if err != nil { + return entity + } + return r.incrementAdjustment(entity) // then increment the record } - // optimisticAdjustFunc is called assuming the record exists; if the record does not exist, - // it means the record was not initialized. In this case, initialize the record and call optimisticAdjustFunc again. - // If the record was initialized, optimisticAdjustFunc will be called only once. - adjustedEntity, adjusted := optimisticAdjustFunc() + adjustedEntity, adjusted := r.c.AdjustWithInit(nodeID, adjustFunc, func() flow.Entity { + return r.recordEntityFactory(nodeID) + }) + if err != nil { - return 0, fmt.Errorf("unexpected error while applying decay adjustment for node %s: %w", nodeID, err) + return 0, fmt.Errorf("unexpected error while applying decay and increment adjustments for node %s: %w", nodeID, err) } + if !adjusted { - r.Init(nodeID) - adjustedEntity, adjusted = optimisticAdjustFunc() - if !adjusted { - return 0, fmt.Errorf("unexpected record not found for node ID %s, even after an init attempt", nodeID) - } + return 0, fmt.Errorf("adjustment failed for node %s", nodeID) } - return adjustedEntity.(ClusterPrefixedMessagesReceivedRecord).Gauge, nil + record := mustBeClusterPrefixedMessageReceivedRecordEntity(adjustedEntity) + + return record.Gauge, nil } -// Get returns the current number of cluster prefixed control messages received from a peer. +// GetWithInit returns the current number of cluster prefixed control messages received from a peer. // The record is initialized before the count is returned. // Before the control messages received gauge value is returned it is decayed using the configured decay function. // Returns the record and true if the record exists, nil and false otherwise. @@ -125,30 +105,24 @@ func (r *RecordCache) ReceivedClusterPrefixedMessage(nodeID flow.Identifier) (fl // Returns: // - The cluster prefixed control messages received gauge value after the decay and true if the record exists, 0 and false otherwise. // No errors are expected during normal operation. -func (r *RecordCache) Get(nodeID flow.Identifier) (float64, bool, error) { - if r.Init(nodeID) { - return 0, true, nil - } - +func (r *RecordCache) GetWithInit(nodeID flow.Identifier) (float64, bool, error) { var err error - adjustedEntity, adjusted := r.c.Adjust(nodeID, func(entity flow.Entity) flow.Entity { + adjustLogic := func(entity flow.Entity) flow.Entity { // perform decay on gauge value entity, err = r.decayAdjustment(entity) return entity + } + adjustedEntity, adjusted := r.c.AdjustWithInit(nodeID, adjustLogic, func() flow.Entity { + return r.recordEntityFactory(nodeID) }) if err != nil { return 0, false, fmt.Errorf("unexpected error while applying decay adjustment for node %s: %w", nodeID, err) } if !adjusted { - return 0, false, fmt.Errorf("unexpected error record not found for node ID %s, even after an init attempt", nodeID) + return 0, false, fmt.Errorf("decay adjustment failed for node %s", nodeID) } - record, ok := adjustedEntity.(ClusterPrefixedMessagesReceivedRecord) - if !ok { - // sanity check - // This should never happen, because the cache only contains ClusterPrefixedMessagesReceivedRecord entities. - panic(fmt.Sprintf("invalid entity type, expected ClusterPrefixedMessagesReceivedRecord type, got: %T", adjustedEntity)) - } + record := mustBeClusterPrefixedMessageReceivedRecordEntity(adjustedEntity) return record.Gauge, true, nil } @@ -225,3 +199,19 @@ func defaultDecayFunction(decay float64) decayFunc { return recordEntity, nil } } + +// mustBeClusterPrefixedMessageReceivedRecordEntity is a helper function for type assertion of the flow.Entity to ClusterPrefixedMessagesReceivedRecord. +// It panics if the type assertion fails. +// Args: +// - entity: the flow.Entity to be type asserted. +// Returns: +// - the ClusterPrefixedMessagesReceivedRecord entity. +func mustBeClusterPrefixedMessageReceivedRecordEntity(entity flow.Entity) ClusterPrefixedMessagesReceivedRecord { + c, ok := entity.(ClusterPrefixedMessagesReceivedRecord) + if !ok { + // sanity check + // This should never happen, because the cache only contains ClusterPrefixedMessagesReceivedRecord entities. + panic(fmt.Sprintf("invalid entity type, expected ClusterPrefixedMessagesReceivedRecord type, got: %T", entity)) + } + return c +} diff --git a/network/p2p/inspector/internal/cache/cache_test.go b/network/p2p/inspector/internal/cache/cache_test.go index 2be9d4f2517..d6f5ffad908 100644 --- a/network/p2p/inspector/internal/cache/cache_test.go +++ b/network/p2p/inspector/internal/cache/cache_test.go @@ -8,7 +8,6 @@ import ( "github.com/rs/zerolog" "github.com/stretchr/testify/require" - "go.uber.org/atomic" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/module" @@ -28,18 +27,14 @@ func TestRecordCache_Init(t *testing.T) { nodeID2 := unittest.IdentifierFixture() // test initializing a record for an node ID that doesn't exist in the cache - initialized := cache.Init(nodeID1) - require.True(t, initialized, "expected record to be initialized") - gauge, ok, err := cache.Get(nodeID1) + gauge, ok, err := cache.GetWithInit(nodeID1) require.NoError(t, err) require.True(t, ok, "expected record to exist") require.Zerof(t, gauge, "expected gauge to be 0") require.Equal(t, uint(1), cache.Size(), "expected cache to have one additional record") // test initializing a record for an node ID that already exists in the cache - initialized = cache.Init(nodeID1) - require.False(t, initialized, "expected record not to be initialized") - gaugeAgain, ok, err := cache.Get(nodeID1) + gaugeAgain, ok, err := cache.GetWithInit(nodeID1) require.NoError(t, err) require.True(t, ok, "expected record to still exist") require.Zerof(t, gaugeAgain, "expected same gauge to be 0") @@ -47,9 +42,7 @@ func TestRecordCache_Init(t *testing.T) { require.Equal(t, uint(1), cache.Size(), "expected cache to still have one additional record") // test initializing a record for another node ID - initialized = cache.Init(nodeID2) - require.True(t, initialized, "expected record to be initialized") - gauge2, ok, err := cache.Get(nodeID2) + gauge2, ok, err := cache.GetWithInit(nodeID2) require.NoError(t, err) require.True(t, ok, "expected record to exist") require.Zerof(t, gauge2, "expected second gauge to be 0") @@ -71,26 +64,21 @@ func TestRecordCache_ConcurrentInit(t *testing.T) { for _, nodeID := range nodeIDs { go func(id flow.Identifier) { defer wg.Done() - cache.Init(id) + gauge, found, err := cache.GetWithInit(id) + require.NoError(t, err) + require.True(t, found) + require.Zerof(t, gauge, "expected all gauge values to be initialized to 0") }(nodeID) } unittest.RequireReturnsBefore(t, wg.Wait, 100*time.Millisecond, "timed out waiting for goroutines to finish") - - // ensure that all records are correctly initialized - for _, nodeID := range nodeIDs { - gauge, found, err := cache.Get(nodeID) - require.NoError(t, err) - require.True(t, found) - require.Zerof(t, gauge, "expected all gauge values to be initialized to 0") - } } // TestRecordCache_ConcurrentSameRecordInit tests the concurrent initialization of the same record. // The test covers the following scenarios: // 1. Multiple goroutines attempting to initialize the same record concurrently. // 2. Only one goroutine successfully initializes the record, and others receive false on initialization. -// 3. The record is correctly initialized in the cache and can be retrieved using the Get method. +// 3. The record is correctly initialized in the cache and can be retrieved using the GetWithInit method. func TestRecordCache_ConcurrentSameRecordInit(t *testing.T) { cache := cacheFixture(t, 100, defaultDecay, zerolog.Nop(), metrics.NewNoopCollector()) @@ -100,28 +88,20 @@ func TestRecordCache_ConcurrentSameRecordInit(t *testing.T) { var wg sync.WaitGroup wg.Add(concurrentAttempts) - successGauge := atomic.Int32{} - for i := 0; i < concurrentAttempts; i++ { go func() { defer wg.Done() - initSuccess := cache.Init(nodeID) - if initSuccess { - successGauge.Inc() - } + gauge, found, err := cache.GetWithInit(nodeID) + require.NoError(t, err) + require.True(t, found) + require.Zero(t, gauge) }() } unittest.RequireReturnsBefore(t, wg.Wait, 100*time.Millisecond, "timed out waiting for goroutines to finish") // ensure that only one goroutine successfully initialized the record - require.Equal(t, int32(1), successGauge.Load()) - - // ensure that the record is correctly initialized in the cache - gauge, found, err := cache.Get(nodeID) - require.NoError(t, err) - require.True(t, found) - require.Zero(t, gauge) + require.Equal(t, uint(1), cache.Size()) } // TestRecordCache_ReceivedClusterPrefixedMessage tests the ReceivedClusterPrefixedMessage method of the RecordCache. @@ -135,22 +115,22 @@ func TestRecordCache_ReceivedClusterPrefixedMessage(t *testing.T) { nodeID1 := unittest.IdentifierFixture() nodeID2 := unittest.IdentifierFixture() - // initialize spam records for nodeID1 and nodeID2 - require.True(t, cache.Init(nodeID1)) - require.True(t, cache.Init(nodeID2)) - gauge, err := cache.ReceivedClusterPrefixedMessage(nodeID1) require.NoError(t, err) require.Equal(t, float64(1), gauge) // get will apply a slightl decay resulting // in a gauge value less than gauge which is 1 but greater than 0.9 - currentGauge, ok, err := cache.Get(nodeID1) + currentGauge, ok, err := cache.GetWithInit(nodeID1) require.NoError(t, err) require.True(t, ok) require.LessOrEqual(t, currentGauge, gauge) require.Greater(t, currentGauge, 0.9) + _, ok, err = cache.GetWithInit(nodeID2) + require.NoError(t, err) + require.True(t, ok) + // test adjusting the spam record for a non-existing node ID nodeID3 := unittest.IdentifierFixture() gauge3, err := cache.ReceivedClusterPrefixedMessage(nodeID3) @@ -172,11 +152,10 @@ func TestRecordCache_Decay(t *testing.T) { nodeID1 := unittest.IdentifierFixture() // initialize spam records for nodeID1 and nodeID2 - require.True(t, cache.Init(nodeID1)) gauge, err := cache.ReceivedClusterPrefixedMessage(nodeID1) require.Equal(t, float64(1), gauge) require.NoError(t, err) - gauge, ok, err := cache.Get(nodeID1) + gauge, ok, err := cache.GetWithInit(nodeID1) require.True(t, ok) require.NoError(t, err) // gauge should have been delayed slightly @@ -184,7 +163,7 @@ func TestRecordCache_Decay(t *testing.T) { time.Sleep(time.Second) - gauge, ok, err = cache.Get(nodeID1) + gauge, ok, err = cache.GetWithInit(nodeID1) require.True(t, ok) require.NoError(t, err) // gauge should have been delayed slightly, but closer to 0 @@ -203,9 +182,15 @@ func TestRecordCache_Identities(t *testing.T) { nodeID2 := unittest.IdentifierFixture() nodeID3 := unittest.IdentifierFixture() - require.True(t, cache.Init(nodeID1)) - require.True(t, cache.Init(nodeID2)) - require.True(t, cache.Init(nodeID3)) + _, ok, err := cache.GetWithInit(nodeID1) + require.NoError(t, err) + require.True(t, ok) + _, ok, err = cache.GetWithInit(nodeID2) + require.NoError(t, err) + require.True(t, ok) + _, ok, err = cache.GetWithInit(nodeID3) + require.NoError(t, err) + require.True(t, ok) // check if the NodeIDs method returns the correct set of node IDs identities := cache.NodeIDs() @@ -227,9 +212,15 @@ func TestRecordCache_Remove(t *testing.T) { nodeID2 := unittest.IdentifierFixture() nodeID3 := unittest.IdentifierFixture() - require.True(t, cache.Init(nodeID1)) - require.True(t, cache.Init(nodeID2)) - require.True(t, cache.Init(nodeID3)) + _, ok, err := cache.GetWithInit(nodeID1) + require.NoError(t, err) + require.True(t, ok) + _, ok, err = cache.GetWithInit(nodeID2) + require.NoError(t, err) + require.True(t, ok) + _, ok, err = cache.GetWithInit(nodeID3) + require.NoError(t, err) + require.True(t, ok) numOfIds := uint(3) require.Equal(t, numOfIds, cache.Size(), fmt.Sprintf("expected size of the cache to be %d", numOfIds)) @@ -238,10 +229,10 @@ func TestRecordCache_Remove(t *testing.T) { require.NotContains(t, nodeID1, cache.NodeIDs()) // check if the other node IDs are still in the cache - _, exists, err := cache.Get(nodeID2) + _, exists, err := cache.GetWithInit(nodeID2) require.NoError(t, err) require.True(t, exists) - _, exists, err = cache.Get(nodeID3) + _, exists, err = cache.GetWithInit(nodeID3) require.NoError(t, err) require.True(t, exists) @@ -259,7 +250,9 @@ func TestRecordCache_ConcurrentRemove(t *testing.T) { nodeIDs := unittest.IdentifierListFixture(10) for _, nodeID := range nodeIDs { - cache.Init(nodeID) + _, ok, err := cache.GetWithInit(nodeID) + require.NoError(t, err) + require.True(t, ok) } var wg sync.WaitGroup @@ -289,7 +282,9 @@ func TestRecordCache_ConcurrentUpdatesAndReads(t *testing.T) { nodeIDs := unittest.IdentifierListFixture(10) for _, nodeID := range nodeIDs { - cache.Init(nodeID) + _, ok, err := cache.GetWithInit(nodeID) + require.NoError(t, err) + require.True(t, ok) } var wg sync.WaitGroup @@ -306,7 +301,7 @@ func TestRecordCache_ConcurrentUpdatesAndReads(t *testing.T) { // get spam records concurrently go func(id flow.Identifier) { defer wg.Done() - _, found, err := cache.Get(id) + _, found, err := cache.GetWithInit(id) require.NoError(t, err) require.True(t, found) }(nodeID) @@ -316,7 +311,7 @@ func TestRecordCache_ConcurrentUpdatesAndReads(t *testing.T) { // ensure that the records are correctly updated in the cache for _, nodeID := range nodeIDs { - gauge, found, err := cache.Get(nodeID) + gauge, found, err := cache.GetWithInit(nodeID) require.NoError(t, err) require.True(t, found) // slight decay will result in 0.9 < gauge < 1 @@ -339,7 +334,9 @@ func TestRecordCache_ConcurrentInitAndRemove(t *testing.T) { nodeIDsToRemove := nodeIDs[10:] for _, nodeID := range nodeIDsToRemove { - cache.Init(nodeID) + _, ok, err := cache.GetWithInit(nodeID) + require.NoError(t, err) + require.True(t, ok) } var wg sync.WaitGroup @@ -349,7 +346,9 @@ func TestRecordCache_ConcurrentInitAndRemove(t *testing.T) { for _, nodeID := range nodeIDsToAdd { go func(id flow.Identifier) { defer wg.Done() - cache.Init(id) + _, ok, err := cache.GetWithInit(id) + require.NoError(t, err) + require.True(t, ok) }(nodeID) } @@ -383,7 +382,9 @@ func TestRecordCache_ConcurrentInitRemoveUpdate(t *testing.T) { nodeIDsToAdjust := nodeIDs[20:] for _, nodeID := range nodeIDsToRemove { - cache.Init(nodeID) + _, ok, err := cache.GetWithInit(nodeID) + require.NoError(t, err) + require.True(t, ok) } var wg sync.WaitGroup @@ -393,7 +394,9 @@ func TestRecordCache_ConcurrentInitRemoveUpdate(t *testing.T) { for _, nodeID := range nodeIDsToAdd { go func(id flow.Identifier) { defer wg.Done() - cache.Init(id) + _, ok, err := cache.GetWithInit(id) + require.NoError(t, err) + require.True(t, ok) }(nodeID) } @@ -431,7 +434,9 @@ func TestRecordCache_EdgeCasesAndInvalidInputs(t *testing.T) { nodeIDsToRemove := nodeIDs[10:20] for _, nodeID := range nodeIDsToRemove { - cache.Init(nodeID) + _, ok, err := cache.GetWithInit(nodeID) + require.NoError(t, err) + require.True(t, ok) } var wg sync.WaitGroup @@ -441,8 +446,7 @@ func TestRecordCache_EdgeCasesAndInvalidInputs(t *testing.T) { for _, nodeID := range nodeIDsToAdd { go func(id flow.Identifier) { defer wg.Done() - require.True(t, cache.Init(id)) - retrieved, ok, err := cache.Get(id) + retrieved, ok, err := cache.GetWithInit(id) require.NoError(t, err) require.True(t, ok) require.Zero(t, retrieved) diff --git a/network/p2p/inspector/internal/cache/cluster_prefixed_received_tracker.go b/network/p2p/inspector/internal/cache/cluster_prefixed_received_tracker.go index b112b7d7a7c..99e40884e1f 100644 --- a/network/p2p/inspector/internal/cache/cluster_prefixed_received_tracker.go +++ b/network/p2p/inspector/internal/cache/cluster_prefixed_received_tracker.go @@ -18,7 +18,8 @@ type ClusterPrefixedMessagesReceivedTracker struct { } // NewClusterPrefixedMessagesReceivedTracker returns a new *ClusterPrefixedMessagesReceivedTracker. -func NewClusterPrefixedMessagesReceivedTracker(logger zerolog.Logger, sizeLimit uint32, clusterPrefixedCacheCollector module.HeroCacheMetrics, decay float64) (*ClusterPrefixedMessagesReceivedTracker, error) { +func NewClusterPrefixedMessagesReceivedTracker(logger zerolog.Logger, sizeLimit uint32, clusterPrefixedCacheCollector module.HeroCacheMetrics, decay float64) (*ClusterPrefixedMessagesReceivedTracker, + error) { config := &RecordCacheConfig{ sizeLimit: sizeLimit, logger: logger, @@ -45,7 +46,7 @@ func (c *ClusterPrefixedMessagesReceivedTracker) Inc(nodeID flow.Identifier) (fl // Load loads the current number of cluster prefixed control messages received by a peer. // All errors returned from this func are unexpected and irrecoverable. func (c *ClusterPrefixedMessagesReceivedTracker) Load(nodeID flow.Identifier) (float64, error) { - count, _, err := c.cache.Get(nodeID) + count, _, err := c.cache.GetWithInit(nodeID) if err != nil { return 0, fmt.Errorf("failed to get cluster prefixed received tracker gauge value for peer %s: %w", nodeID, err) } diff --git a/network/p2p/inspector/validation/control_message_validation_inspector.go b/network/p2p/inspector/validation/control_message_validation_inspector.go index 177de5a630d..c42d4aae0e9 100644 --- a/network/p2p/inspector/validation/control_message_validation_inspector.go +++ b/network/p2p/inspector/validation/control_message_validation_inspector.go @@ -21,16 +21,36 @@ import ( "github.com/onflow/flow-go/network" "github.com/onflow/flow-go/network/channels" "github.com/onflow/flow-go/network/p2p" + p2pconfig "github.com/onflow/flow-go/network/p2p/config" "github.com/onflow/flow-go/network/p2p/inspector/internal/cache" + p2plogging "github.com/onflow/flow-go/network/p2p/logging" p2pmsg "github.com/onflow/flow-go/network/p2p/message" - "github.com/onflow/flow-go/network/p2p/p2pconf" - "github.com/onflow/flow-go/network/p2p/p2plogging" "github.com/onflow/flow-go/state/protocol" "github.com/onflow/flow-go/state/protocol/events" "github.com/onflow/flow-go/utils/logging" flowrand "github.com/onflow/flow-go/utils/rand" ) +const ( + RPCInspectionDisabledWarning = "rpc inspection disabled for all control message types, skipping inspection" + GraftInspectionDisabledWarning = "rpc graft inspection disabled skipping" + PruneInspectionDisabledWarning = "rpc prune inspection disabled skipping" + IWantInspectionDisabledWarning = "rpc iwant inspection disabled skipping" + IHaveInspectionDisabledWarning = "rpc ihave inspection disabled skipping" + PublishInspectionDisabledWarning = "rpc publish message inspection disabled skipping" + + RPCTruncationDisabledWarning = "rpc truncation disabled for all control message types, skipping truncation" + GraftTruncationDisabledWarning = "rpc graft truncation disabled skipping" + PruneTruncationDisabledWarning = "rpc prune truncation disabled skipping" + IHaveTruncationDisabledWarning = "rpc ihave truncation disabled skipping" + IHaveMessageIDTruncationDisabledWarning = "ihave message ids truncation disabled skipping" + IWantTruncationDisabledWarning = "rpc iwant truncation disabled skipping" + IWantMessageIDTruncationDisabledWarning = "iwant message ids truncation disabled skipping" + + // rpcInspectorComponentName the rpc inspector component name. + rpcInspectorComponentName = "gossipsub_rpc_validation_inspector" +) + // ControlMsgValidationInspector RPC message inspector that inspects control messages and performs some validation on them, // when some validation rule is broken feedback is given via the Peer scoring notifier. type ControlMsgValidationInspector struct { @@ -41,7 +61,7 @@ type ControlMsgValidationInspector struct { sporkID flow.Identifier metrics module.GossipSubRpcValidationInspectorMetrics // config control message validation configurations. - config *p2pconf.GossipSubRPCValidationInspectorConfigs + config *p2pconfig.RpcValidationInspector // distributor used to disseminate invalid RPC message notifications. distributor p2p.GossipSubInspectorNotifDistributor // workerPool queue that stores *InspectRPCRequest that will be processed by component workers. @@ -69,7 +89,7 @@ type InspectorParams struct { // SporkID the current spork ID. SporkID flow.Identifier `validate:"required"` // Config inspector configuration. - Config *p2pconf.GossipSubRPCValidationInspectorConfigs `validate:"required"` + Config *p2pconfig.RpcValidationInspector `validate:"required"` // Distributor gossipsub inspector notification distributor. Distributor p2p.GossipSubInspectorNotifDistributor `validate:"required"` // HeroCacheMetricsFactory the metrics factory. @@ -109,17 +129,17 @@ func NewControlMsgValidationInspector(params *InspectorParams) (*ControlMsgValid clusterPrefixedCacheCollector := metrics.GossipSubRPCInspectorClusterPrefixedCacheMetricFactory(params.HeroCacheMetricsFactory, params.NetworkingType) clusterPrefixedTracker, err := cache.NewClusterPrefixedMessagesReceivedTracker(params.Logger, - params.Config.ClusterPrefixedControlMsgsReceivedCacheSize, + params.Config.ClusterPrefixedMessage.ControlMsgsReceivedCacheSize, clusterPrefixedCacheCollector, - params.Config.ClusterPrefixedControlMsgsReceivedCacheDecay) + params.Config.ClusterPrefixedMessage.ControlMsgsReceivedCacheDecay) if err != nil { return nil, fmt.Errorf("failed to create cluster prefix topics received tracker") } - if params.Config.RpcMessageMaxSampleSize < params.Config.RpcMessageErrorThreshold { + if params.Config.PublishMessages.MaxSampleSize < params.Config.PublishMessages.ErrorThreshold { return nil, fmt.Errorf("rpc message max sample size must be greater than or equal to rpc message error threshold, got %d and %d respectively", - params.Config.RpcMessageMaxSampleSize, - params.Config.RpcMessageErrorThreshold) + params.Config.PublishMessages.MaxSampleSize, + params.Config.PublishMessages.ErrorThreshold) } c := &ControlMsgValidationInspector{ @@ -135,7 +155,7 @@ func NewControlMsgValidationInspector(params *InspectorParams) (*ControlMsgValid topicOracle: params.TopicOracle, } - store := queue.NewHeroStore(params.Config.CacheSize, params.Logger, inspectMsgQueueCacheCollector) + store := queue.NewHeroStore(params.Config.InspectionQueue.Size, params.Logger, inspectMsgQueueCacheCollector) pool := worker.NewWorkerPoolBuilder[*InspectRPCRequest](lg, store, c.processInspectRPCReq).Build() @@ -158,7 +178,7 @@ func NewControlMsgValidationInspector(params *InspectorParams) (*ControlMsgValid <-c.distributor.Done() c.logger.Debug().Msg("rpc inspector distributor shutdown complete") }) - for i := 0; i < c.config.NumberOfWorkers; i++ { + for i := 0; i < c.config.InspectionQueue.NumberOfWorkers; i++ { builder.AddWorker(pool.WorkerLogic()) } c.Component = builder.Build() @@ -191,8 +211,19 @@ func (c *ControlMsgValidationInspector) ActiveClustersChanged(clusterIDList flow // Returns: // - error: if a new inspect rpc request cannot be created, all errors returned are considered irrecoverable. func (c *ControlMsgValidationInspector) Inspect(from peer.ID, rpc *pubsub.RPC) error { + if c.config.InspectionProcess.Inspect.Disabled { + c.logger. + Trace(). + Str("peer_id", p2plogging.PeerId(from)). + Bool(logging.KeyNetworkingSecurity, true). + Msg(RPCInspectionDisabledWarning) + return nil + } + + // first truncate the rpc to the configured max sample size; if needed c.truncateRPC(from, rpc) - // queue further async inspection + + // second, queue further async inspection req, err := NewInspectRPCRequest(from, rpc) if err != nil { c.logger.Error(). @@ -203,9 +234,39 @@ func (c *ControlMsgValidationInspector) Inspect(from peer.ID, rpc *pubsub.RPC) e return fmt.Errorf("failed to get inspect RPC request: %w", err) } c.workerPool.Submit(req) + return nil } +// updateMetrics updates the metrics for the received RPC. +// Args: +// - from: the sender. +// +// - rpc: the control message RPC. +func (c *ControlMsgValidationInspector) updateMetrics(from peer.ID, rpc *pubsub.RPC) { + includedMessages := len(rpc.GetPublish()) + iHaveCount, iWantCount, graftCount, pruneCount := 0, 0, 0, 0 + ctl := rpc.GetControl() + if ctl != nil { + iHaveCount = len(ctl.GetIhave()) + iWantCount = len(ctl.GetIwant()) + graftCount = len(ctl.GetGraft()) + pruneCount = len(ctl.GetPrune()) + } + c.metrics.OnIncomingRpcReceived(iHaveCount, iWantCount, graftCount, pruneCount, includedMessages) + if c.logger.GetLevel() > zerolog.TraceLevel { + return // skip logging if trace level is not enabled + } + c.logger.Trace(). + Str("peer_id", p2plogging.PeerId(from)). + Int("iHaveCount", iHaveCount). + Int("iWantCount", iWantCount). + Int("graftCount", graftCount). + Int("pruneCount", pruneCount). + Int("included_message_count", includedMessages). + Msg("received rpc with control messages") +} + // processInspectRPCReq func used by component workers to perform further inspection of RPC control messages that will validate ensure all control message // types are valid in the RPC. // Args: @@ -214,6 +275,7 @@ func (c *ControlMsgValidationInspector) Inspect(from peer.ID, rpc *pubsub.RPC) e // Returns: // - error: no error is expected to be returned from this func as they are logged and distributed in invalid control message notifications. func (c *ControlMsgValidationInspector) processInspectRPCReq(req *InspectRPCRequest) error { + c.updateMetrics(req.Peer, req.rpc) c.metrics.AsyncProcessingStarted() start := time.Now() defer func() { @@ -293,15 +355,37 @@ func (c *ControlMsgValidationInspector) checkPubsubMessageSender(message *pubsub // - error: if any error occurs while sampling or validating topics, all returned errors are benign and should not cause the node to crash. // - bool: true if an error is returned and the topic that failed validation was a cluster prefixed topic, false otherwise. func (c *ControlMsgValidationInspector) inspectGraftMessages(from peer.ID, grafts []*pubsub_pb.ControlGraft, activeClusterIDS flow.ChainIDList) (error, p2p.CtrlMsgTopicType) { - tracker := make(duplicateStrTracker) + if !c.config.InspectionProcess.Inspect.EnableGraft { + c.logger. + Trace(). + Str("peer_id", p2plogging.PeerId(from)). + Bool(logging.KeyNetworkingSecurity, true). + Msg(GraftInspectionDisabledWarning) + return nil, p2p.CtrlMsgNonClusterTopicType + } + + duplicateTopicTracker := make(duplicateStrTracker) + totalDuplicateTopicIds := 0 + defer func() { + // regardless of inspection result, update metrics + c.metrics.OnGraftMessageInspected(totalDuplicateTopicIds) + }() + for _, graft := range grafts { topic := channels.Topic(graft.GetTopicID()) - if tracker.isDuplicate(topic.String()) { - return NewDuplicateTopicErr(topic.String(), p2pmsg.CtrlMsgGraft), p2p.CtrlMsgNonClusterTopicType + if duplicateTopicTracker.track(topic.String()) > 1 { + // ideally, a GRAFT message should not have any duplicate topics, hence a topic ID is counted as a duplicate only if it is repeated more than once. + totalDuplicateTopicIds++ + // check if the total number of duplicates exceeds the configured threshold. + if totalDuplicateTopicIds > c.config.GraftPrune.DuplicateTopicIdThreshold { + c.metrics.OnGraftDuplicateTopicIdsExceedThreshold() + return NewDuplicateTopicErr(topic.String(), totalDuplicateTopicIds, p2pmsg.CtrlMsgGraft), p2p.CtrlMsgNonClusterTopicType + } } - tracker.set(topic.String()) err, ctrlMsgType := c.validateTopic(from, topic, activeClusterIDS) if err != nil { + // TODO: consider adding a threshold for this error similar to the duplicate topic id threshold. + c.metrics.OnInvalidTopicIdDetectedForControlMessage(p2pmsg.CtrlMsgGraft) return err, ctrlMsgType } } @@ -319,15 +403,35 @@ func (c *ControlMsgValidationInspector) inspectGraftMessages(from peer.ID, graft // - error: if any error occurs while sampling or validating topics, all returned errors are benign and should not cause the node to crash. // - bool: true if an error is returned and the topic that failed validation was a cluster prefixed topic, false otherwise. func (c *ControlMsgValidationInspector) inspectPruneMessages(from peer.ID, prunes []*pubsub_pb.ControlPrune, activeClusterIDS flow.ChainIDList) (error, p2p.CtrlMsgTopicType) { + if !c.config.InspectionProcess.Inspect.EnablePrune { + c.logger. + Trace(). + Str("peer_id", p2plogging.PeerId(from)). + Bool(logging.KeyNetworkingSecurity, true). + Msg(PruneInspectionDisabledWarning) + return nil, p2p.CtrlMsgNonClusterTopicType + } tracker := make(duplicateStrTracker) + totalDuplicateTopicIds := 0 + defer func() { + // regardless of inspection result, update metrics + c.metrics.OnPruneMessageInspected(totalDuplicateTopicIds) + }() for _, prune := range prunes { topic := channels.Topic(prune.GetTopicID()) - if tracker.isDuplicate(topic.String()) { - return NewDuplicateTopicErr(topic.String(), p2pmsg.CtrlMsgPrune), p2p.CtrlMsgNonClusterTopicType + if tracker.track(topic.String()) > 1 { + // ideally, a PRUNE message should not have any duplicate topics, hence a topic ID is counted as a duplicate only if it is repeated more than once. + totalDuplicateTopicIds++ + // check if the total number of duplicates exceeds the configured threshold. + if totalDuplicateTopicIds > c.config.GraftPrune.DuplicateTopicIdThreshold { + c.metrics.OnPruneDuplicateTopicIdsExceedThreshold() + return NewDuplicateTopicErr(topic.String(), totalDuplicateTopicIds, p2pmsg.CtrlMsgPrune), p2p.CtrlMsgNonClusterTopicType + } } - tracker.set(topic.String()) err, ctrlMsgType := c.validateTopic(from, topic, activeClusterIDS) if err != nil { + // TODO: consider adding a threshold for this error similar to the duplicate topic id threshold. + c.metrics.OnInvalidTopicIdDetectedForControlMessage(p2pmsg.CtrlMsgPrune) return err, ctrlMsgType } } @@ -345,38 +449,70 @@ func (c *ControlMsgValidationInspector) inspectPruneMessages(from peer.ID, prune // - error: if any error occurs while sampling or validating topics, all returned errors are benign and should not cause the node to crash. // - bool: true if an error is returned and the topic that failed validation was a cluster prefixed topic, false otherwise. func (c *ControlMsgValidationInspector) inspectIHaveMessages(from peer.ID, ihaves []*pubsub_pb.ControlIHave, activeClusterIDS flow.ChainIDList) (error, p2p.CtrlMsgTopicType) { + if !c.config.InspectionProcess.Inspect.EnableIHave { + c.logger. + Trace(). + Str("peer_id", p2plogging.PeerId(from)). + Bool(logging.KeyNetworkingSecurity, true). + Msg(IHaveInspectionDisabledWarning) + return nil, p2p.CtrlMsgNonClusterTopicType + } + if len(ihaves) == 0 { return nil, p2p.CtrlMsgNonClusterTopicType } lg := c.logger.With(). Str("peer_id", p2plogging.PeerId(from)). Int("sample_size", len(ihaves)). - Int("max_sample_size", c.config.IHaveRPCInspectionConfig.MaxSampleSize). + Int("max_sample_size", c.config.IHave.MessageCountThreshold). Logger() duplicateTopicTracker := make(duplicateStrTracker) duplicateMessageIDTracker := make(duplicateStrTracker) totalMessageIds := 0 + totalDuplicateTopicIds := 0 + totalDuplicateMessageIds := 0 + defer func() { + // regardless of inspection result, update metrics + c.metrics.OnIHaveMessagesInspected(totalDuplicateTopicIds, totalDuplicateMessageIds) + }() for _, ihave := range ihaves { messageIds := ihave.GetMessageIDs() topic := ihave.GetTopicID() - if duplicateTopicTracker.isDuplicate(topic) { - return NewDuplicateTopicErr(topic, p2pmsg.CtrlMsgIHave), p2p.CtrlMsgNonClusterTopicType - } - duplicateTopicTracker.set(topic) + totalMessageIds += len(messageIds) + + // first check if the topic is valid, fail fast if it is not err, ctrlMsgType := c.validateTopic(from, channels.Topic(topic), activeClusterIDS) if err != nil { + // TODO: consider adding a threshold for this error similar to the duplicate topic id threshold. + c.metrics.OnInvalidTopicIdDetectedForControlMessage(p2pmsg.CtrlMsgIHave) return err, ctrlMsgType } + // then track the topic ensuring it is not beyond a duplicate threshold. + if duplicateTopicTracker.track(topic) > 1 { + totalDuplicateTopicIds++ + // the topic is duplicated, check if the total number of duplicates exceeds the configured threshold + if totalDuplicateTopicIds > c.config.IHave.DuplicateTopicIdThreshold { + c.metrics.OnIHaveDuplicateTopicIdsExceedThreshold() + return NewDuplicateTopicErr(topic, totalDuplicateTopicIds, p2pmsg.CtrlMsgIHave), p2p.CtrlMsgNonClusterTopicType + } + } + for _, messageID := range messageIds { - if duplicateMessageIDTracker.isDuplicate(messageID) { - return NewDuplicateTopicErr(messageID, p2pmsg.CtrlMsgIHave), p2p.CtrlMsgNonClusterTopicType + if duplicateMessageIDTracker.track(messageID) > 1 { + totalDuplicateMessageIds++ + // the message is duplicated, check if the total number of duplicates exceeds the configured threshold + if totalDuplicateMessageIds > c.config.IHave.DuplicateMessageIdThreshold { + c.metrics.OnIHaveDuplicateMessageIdsExceedThreshold() + return NewDuplicateMessageIDErr(messageID, totalDuplicateMessageIds, p2pmsg.CtrlMsgIHave), p2p.CtrlMsgNonClusterTopicType + } } - duplicateMessageIDTracker.set(messageID) } } lg.Debug(). Int("total_message_ids", totalMessageIds). + Int("total_duplicate_topic_ids", totalDuplicateTopicIds). + Int("total_duplicate_message_ids", totalDuplicateMessageIds). Msg("ihave control message validation complete") return nil, p2p.CtrlMsgNonClusterTopicType } @@ -393,26 +529,36 @@ func (c *ControlMsgValidationInspector) inspectIHaveMessages(from peer.ID, ihave // - DuplicateTopicErr: if there are any duplicate message ids found in any of the iWants. // - IWantCacheMissThresholdErr: if the rate of cache misses exceeds the configured allowed threshold. func (c *ControlMsgValidationInspector) inspectIWantMessages(from peer.ID, iWants []*pubsub_pb.ControlIWant) error { + if !c.config.InspectionProcess.Inspect.EnableIWant { + c.logger. + Trace(). + Str("peer_id", p2plogging.PeerId(from)). + Bool(logging.KeyNetworkingSecurity, true). + Msg(IWantInspectionDisabledWarning) + return nil + } + if len(iWants) == 0 { return nil } lastHighest := c.rpcTracker.LastHighestIHaveRPCSize() lg := c.logger.With(). Str("peer_id", p2plogging.PeerId(from)). - Uint("max_sample_size", c.config.IWantRPCInspectionConfig.MaxSampleSize). + Uint("max_sample_size", c.config.IWant.MessageCountThreshold). Int64("last_highest_ihave_rpc_size", lastHighest). Logger() - sampleSize := uint(len(iWants)) - tracker := make(duplicateStrTracker) + duplicateMsgIdTracker := make(duplicateStrTracker) cacheMisses := 0 - allowedCacheMissesThreshold := float64(sampleSize) * c.config.IWantRPCInspectionConfig.CacheMissThreshold - duplicates := 0 - allowedDuplicatesThreshold := float64(sampleSize) * c.config.IWantRPCInspectionConfig.DuplicateMsgIDThreshold - checkCacheMisses := len(iWants) >= c.config.IWantRPCInspectionConfig.CacheMissCheckSize + duplicateMessageIds := 0 + defer func() { + // regardless of inspection result, update metrics + c.metrics.OnIWantMessagesInspected(duplicateMessageIds, cacheMisses) + }() + lg = lg.With(). - Uint("iwant_sample_size", sampleSize). - Float64("allowed_cache_misses_threshold", allowedCacheMissesThreshold). - Float64("allowed_duplicates_threshold", allowedDuplicatesThreshold).Logger() + Int("iwant_msg_count", len(iWants)). + Int("cache_misses_threshold", c.config.IWant.CacheMissThreshold). + Int("duplicates_threshold", c.config.IWant.DuplicateMsgIdThreshold).Logger() lg.Trace().Msg("validating sample of message ids from iwant control message") @@ -422,22 +568,23 @@ func (c *ControlMsgValidationInspector) inspectIWantMessages(from peer.ID, iWant messageIDCount := uint(len(messageIds)) for _, messageID := range messageIds { // check duplicate allowed threshold - if tracker.isDuplicate(messageID) { - duplicates++ - if float64(duplicates) > allowedDuplicatesThreshold { - return NewIWantDuplicateMsgIDThresholdErr(duplicates, messageIDCount, c.config.IWantRPCInspectionConfig.DuplicateMsgIDThreshold) + if duplicateMsgIdTracker.track(messageID) > 1 { + // ideally, an iWant message should not have any duplicate message IDs, hence a message id is considered duplicate when it is repeated more than once. + duplicateMessageIds++ + if duplicateMessageIds > c.config.IWant.DuplicateMsgIdThreshold { + c.metrics.OnIWantDuplicateMessageIdsExceedThreshold() + return NewIWantDuplicateMsgIDThresholdErr(duplicateMessageIds, messageIDCount, c.config.IWant.DuplicateMsgIdThreshold) } } // check cache miss threshold if !c.rpcTracker.WasIHaveRPCSent(messageID) { cacheMisses++ - if checkCacheMisses { - if float64(cacheMisses) > allowedCacheMissesThreshold { - return NewIWantCacheMissThresholdErr(cacheMisses, messageIDCount, c.config.IWantRPCInspectionConfig.CacheMissThreshold) - } + if cacheMisses > c.config.IWant.CacheMissThreshold { + c.metrics.OnIWantCacheMissMessageIdsExceedThreshold() + return NewIWantCacheMissThresholdErr(cacheMisses, messageIDCount, c.config.IWant.CacheMissThreshold) } } - tracker.set(messageID) + duplicateMsgIdTracker.track(messageID) totalMessageIds++ } } @@ -445,7 +592,7 @@ func (c *ControlMsgValidationInspector) inspectIWantMessages(from peer.ID, iWant lg.Debug(). Int("total_message_ids", totalMessageIds). Int("cache_misses", cacheMisses). - Int("duplicates", duplicates). + Int("total_duplicate_message_ids", duplicateMessageIds). Msg("iwant control message validation complete") return nil @@ -464,11 +611,20 @@ func (c *ControlMsgValidationInspector) inspectIWantMessages(from peer.ID, iWant // - InvalidRpcPublishMessagesErr: if the amount of invalid messages exceeds the configured RPCMessageErrorThreshold. // - int: the number of invalid pubsub messages func (c *ControlMsgValidationInspector) inspectRpcPublishMessages(from peer.ID, messages []*pubsub_pb.Message, activeClusterIDS flow.ChainIDList) (error, uint64) { + if !c.config.InspectionProcess.Inspect.EnablePublish { + c.logger. + Trace(). + Str("peer_id", p2plogging.PeerId(from)). + Bool(logging.KeyNetworkingSecurity, true). + Msg(PublishInspectionDisabledWarning) + return nil, 0 + } + totalMessages := len(messages) if totalMessages == 0 { return nil, 0 } - sampleSize := c.config.RpcMessageMaxSampleSize + sampleSize := c.config.PublishMessages.MaxSampleSize if sampleSize > totalMessages { sampleSize = totalMessages } @@ -486,10 +642,22 @@ func (c *ControlMsgValidationInspector) inspectRpcPublishMessages(from peer.ID, return false } var errs *multierror.Error + invalidTopicIdsCount := 0 + invalidSubscriptionsCount := 0 + invalidSendersCount := 0 + defer func() { + // regardless of inspection result, update metrics + errCnt := 0 + if errs != nil { + errCnt = errs.Len() + } + c.metrics.OnPublishMessageInspected(errCnt, invalidTopicIdsCount, invalidSubscriptionsCount, invalidSendersCount) + }() for _, message := range messages[:sampleSize] { if c.networkingType == network.PrivateNetwork { err := c.checkPubsubMessageSender(message) if err != nil { + invalidSendersCount++ errs = multierror.Append(errs, err) continue } @@ -501,17 +669,20 @@ func (c *ControlMsgValidationInspector) inspectRpcPublishMessages(from peer.ID, err, _ := c.validateTopic(from, topic, activeClusterIDS) if err != nil { // we can skip checking for subscription of topic that failed validation and continue + invalidTopicIdsCount++ errs = multierror.Append(errs, err) continue } if !hasSubscription(topic.String()) { + invalidSubscriptionsCount++ errs = multierror.Append(errs, fmt.Errorf("subscription for topic %s not found", topic)) } } // return an error when we exceed the error threshold - if errs != nil && errs.Len() > c.config.RpcMessageErrorThreshold { + if errs != nil && errs.Len() > c.config.PublishMessages.ErrorThreshold { + c.metrics.OnPublishMessagesInspectionErrorExceedsThreshold() return NewInvalidRpcPublishMessagesErr(errs.ErrorOrNil(), errs.Len()), uint64(errs.Len()) } @@ -523,16 +694,27 @@ func (c *ControlMsgValidationInspector) inspectRpcPublishMessages(from peer.ID, // - from: peer ID of the sender. // - rpc: the pubsub RPC. func (c *ControlMsgValidationInspector) truncateRPC(from peer.ID, rpc *pubsub.RPC) { + if c.config.InspectionProcess.Truncate.Disabled { + c.logger. + Trace(). + Str("peer_id", p2plogging.PeerId(from)). + Bool(logging.KeyNetworkingSecurity, true). + Msg(RPCTruncationDisabledWarning) + return + } + for _, ctlMsgType := range p2pmsg.ControlMessageTypes() { switch ctlMsgType { case p2pmsg.CtrlMsgGraft: - c.truncateGraftMessages(rpc) + c.truncateGraftMessages(from, rpc) case p2pmsg.CtrlMsgPrune: - c.truncatePruneMessages(rpc) + c.truncatePruneMessages(from, rpc) case p2pmsg.CtrlMsgIHave: - c.truncateIHaveMessages(rpc) + c.truncateIHaveMessages(from, rpc) + c.truncateIHaveMessageIds(from, rpc) case p2pmsg.CtrlMsgIWant: c.truncateIWantMessages(from, rpc) + c.truncateIWantMessageIds(from, rpc) default: // sanity check this should never happen c.logAndThrowError(fmt.Errorf("unknown control message type encountered during RPC truncation")) @@ -544,138 +726,205 @@ func (c *ControlMsgValidationInspector) truncateRPC(from peer.ID, rpc *pubsub.RP // GraftPruneMessageMaxSampleSize the list of Grafts will be truncated. // Args: // - rpc: the rpc message to truncate. -func (c *ControlMsgValidationInspector) truncateGraftMessages(rpc *pubsub.RPC) { - grafts := rpc.GetControl().GetGraft() - totalGrafts := len(grafts) - if totalGrafts == 0 { +func (c *ControlMsgValidationInspector) truncateGraftMessages(from peer.ID, rpc *pubsub.RPC) { + if !c.config.InspectionProcess.Truncate.EnableGraft { + c.logger. + Trace(). + Str("peer_id", p2plogging.PeerId(from)). + Bool(logging.KeyNetworkingSecurity, true). + Msg(GraftTruncationDisabledWarning) return } - sampleSize := c.config.GraftPruneMessageMaxSampleSize - if sampleSize > totalGrafts { - sampleSize = totalGrafts + + grafts := rpc.GetControl().GetGraft() + originalGraftSize := len(grafts) + if originalGraftSize <= c.config.GraftPrune.MessageCountThreshold { + return // nothing to truncate } - c.performSample(p2pmsg.CtrlMsgGraft, uint(totalGrafts), uint(sampleSize), func(i, j uint) { + + // truncate grafts and update metrics + sampleSize := c.config.GraftPrune.MessageCountThreshold + c.performSample(p2pmsg.CtrlMsgGraft, uint(originalGraftSize), uint(sampleSize), func(i, j uint) { grafts[i], grafts[j] = grafts[j], grafts[i] }) rpc.Control.Graft = grafts[:sampleSize] + c.metrics.OnControlMessagesTruncated(p2pmsg.CtrlMsgGraft, originalGraftSize-len(rpc.Control.Graft)) } // truncatePruneMessages truncates the Prune control messages in the RPC. If the total number of Prunes in the RPC exceeds the configured // GraftPruneMessageMaxSampleSize the list of Prunes will be truncated. // Args: // - rpc: the rpc message to truncate. -func (c *ControlMsgValidationInspector) truncatePruneMessages(rpc *pubsub.RPC) { - prunes := rpc.GetControl().GetPrune() - totalPrunes := len(prunes) - if totalPrunes == 0 { +func (c *ControlMsgValidationInspector) truncatePruneMessages(from peer.ID, rpc *pubsub.RPC) { + if !c.config.InspectionProcess.Truncate.EnablePrune { + c.logger. + Trace(). + Str("peer_id", p2plogging.PeerId(from)). + Bool(logging.KeyNetworkingSecurity, true). + Msg(PruneTruncationDisabledWarning) return } - sampleSize := c.config.GraftPruneMessageMaxSampleSize - if sampleSize > totalPrunes { - sampleSize = totalPrunes + + prunes := rpc.GetControl().GetPrune() + originalPruneSize := len(prunes) + if originalPruneSize <= c.config.GraftPrune.MessageCountThreshold { + return // nothing to truncate } - c.performSample(p2pmsg.CtrlMsgPrune, uint(totalPrunes), uint(sampleSize), func(i, j uint) { + + sampleSize := c.config.GraftPrune.MessageCountThreshold + c.performSample(p2pmsg.CtrlMsgPrune, uint(originalPruneSize), uint(sampleSize), func(i, j uint) { prunes[i], prunes[j] = prunes[j], prunes[i] }) rpc.Control.Prune = prunes[:sampleSize] + c.metrics.OnControlMessagesTruncated(p2pmsg.CtrlMsgPrune, originalPruneSize-len(rpc.Control.Prune)) } // truncateIHaveMessages truncates the iHaves control messages in the RPC. If the total number of iHaves in the RPC exceeds the configured -// MaxSampleSize the list of iHaves will be truncated. +// MessageCountThreshold the list of iHaves will be truncated. // Args: // - rpc: the rpc message to truncate. -func (c *ControlMsgValidationInspector) truncateIHaveMessages(rpc *pubsub.RPC) { - ihaves := rpc.GetControl().GetIhave() - totalIHaves := len(ihaves) - if totalIHaves == 0 { +func (c *ControlMsgValidationInspector) truncateIHaveMessages(from peer.ID, rpc *pubsub.RPC) { + if !c.config.InspectionProcess.Truncate.EnableIHave { + c.logger. + Trace(). + Str("peer_id", p2plogging.PeerId(from)). + Bool(logging.KeyNetworkingSecurity, true). + Msg(IHaveTruncationDisabledWarning) return } - sampleSize := c.config.IHaveRPCInspectionConfig.MaxSampleSize - if sampleSize > totalIHaves { - sampleSize = totalIHaves + + ihaves := rpc.GetControl().GetIhave() + originalIHaveCount := len(ihaves) + if originalIHaveCount == 0 { + return } - c.performSample(p2pmsg.CtrlMsgIHave, uint(totalIHaves), uint(sampleSize), func(i, j uint) { - ihaves[i], ihaves[j] = ihaves[j], ihaves[i] - }) - rpc.Control.Ihave = ihaves[:sampleSize] - c.truncateIHaveMessageIds(rpc) + if originalIHaveCount > c.config.IHave.MessageCountThreshold { + // truncate ihaves and update metrics + sampleSize := c.config.IHave.MessageCountThreshold + if sampleSize > originalIHaveCount { + sampleSize = originalIHaveCount + } + c.performSample(p2pmsg.CtrlMsgIHave, uint(originalIHaveCount), uint(sampleSize), func(i, j uint) { + ihaves[i], ihaves[j] = ihaves[j], ihaves[i] + }) + rpc.Control.Ihave = ihaves[:sampleSize] + c.metrics.OnControlMessagesTruncated(p2pmsg.CtrlMsgIHave, originalIHaveCount-len(rpc.Control.Ihave)) + } } // truncateIHaveMessageIds truncates the message ids for each iHave control message in the RPC. If the total number of message ids in a single iHave exceeds the configured -// MaxMessageIDSampleSize the list of message ids will be truncated. Before message ids are truncated the iHave control messages should have been truncated themselves. +// MessageIdCountThreshold the list of message ids will be truncated. Before message ids are truncated the iHave control messages should have been truncated themselves. // Args: // - rpc: the rpc message to truncate. -func (c *ControlMsgValidationInspector) truncateIHaveMessageIds(rpc *pubsub.RPC) { +func (c *ControlMsgValidationInspector) truncateIHaveMessageIds(from peer.ID, rpc *pubsub.RPC) { + if !c.config.InspectionProcess.Truncate.EnableIHaveMessageIds { + c.logger. + Trace(). + Str("peer_id", p2plogging.PeerId(from)). + Bool(logging.KeyNetworkingSecurity, true). + Msg(IHaveMessageIDTruncationDisabledWarning) + return + } + for _, ihave := range rpc.GetControl().GetIhave() { messageIDs := ihave.GetMessageIDs() - totalMessageIDs := len(messageIDs) - if totalMessageIDs == 0 { - return + originalMessageIdCount := len(messageIDs) + if originalMessageIdCount == 0 { + continue // nothing to truncate; skip } - sampleSize := c.config.IHaveRPCInspectionConfig.MaxMessageIDSampleSize - if sampleSize > totalMessageIDs { - sampleSize = totalMessageIDs + + if originalMessageIdCount > c.config.IHave.MessageIdCountThreshold { + sampleSize := c.config.IHave.MessageIdCountThreshold + if sampleSize > originalMessageIdCount { + sampleSize = originalMessageIdCount + } + c.performSample(p2pmsg.CtrlMsgIHave, uint(originalMessageIdCount), uint(sampleSize), func(i, j uint) { + messageIDs[i], messageIDs[j] = messageIDs[j], messageIDs[i] + }) + ihave.MessageIDs = messageIDs[:sampleSize] + c.metrics.OnIHaveControlMessageIdsTruncated(originalMessageIdCount - len(ihave.MessageIDs)) } - c.performSample(p2pmsg.CtrlMsgIHave, uint(totalMessageIDs), uint(sampleSize), func(i, j uint) { - messageIDs[i], messageIDs[j] = messageIDs[j], messageIDs[i] - }) - ihave.MessageIDs = messageIDs[:sampleSize] + c.metrics.OnIHaveMessageIDsReceived(ihave.GetTopicID(), len(ihave.MessageIDs)) } } // truncateIWantMessages truncates the iWant control messages in the RPC. If the total number of iWants in the RPC exceeds the configured -// MaxSampleSize the list of iWants will be truncated. +// MessageCountThreshold the list of iWants will be truncated. // Args: // - rpc: the rpc message to truncate. func (c *ControlMsgValidationInspector) truncateIWantMessages(from peer.ID, rpc *pubsub.RPC) { + if !c.config.InspectionProcess.Truncate.EnableIWant { + c.logger. + Trace(). + Str("peer_id", p2plogging.PeerId(from)). + Bool(logging.KeyNetworkingSecurity, true). + Msg(IWantTruncationDisabledWarning) + return + } + iWants := rpc.GetControl().GetIwant() - totalIWants := uint(len(iWants)) - if totalIWants == 0 { + originalIWantCount := uint(len(iWants)) + if originalIWantCount == 0 { return } - sampleSize := c.config.IWantRPCInspectionConfig.MaxSampleSize - if sampleSize > totalIWants { - sampleSize = totalIWants + + if originalIWantCount > c.config.IWant.MessageCountThreshold { + // truncate iWants and update metrics + sampleSize := c.config.IWant.MessageCountThreshold + if sampleSize > originalIWantCount { + sampleSize = originalIWantCount + } + c.performSample(p2pmsg.CtrlMsgIWant, originalIWantCount, sampleSize, func(i, j uint) { + iWants[i], iWants[j] = iWants[j], iWants[i] + }) + rpc.Control.Iwant = iWants[:sampleSize] + c.metrics.OnControlMessagesTruncated(p2pmsg.CtrlMsgIWant, int(originalIWantCount)-len(rpc.Control.Iwant)) } - c.performSample(p2pmsg.CtrlMsgIWant, totalIWants, sampleSize, func(i, j uint) { - iWants[i], iWants[j] = iWants[j], iWants[i] - }) - rpc.Control.Iwant = iWants[:sampleSize] - c.truncateIWantMessageIds(from, rpc) } // truncateIWantMessageIds truncates the message ids for each iWant control message in the RPC. If the total number of message ids in a single iWant exceeds the configured -// MaxMessageIDSampleSize the list of message ids will be truncated. Before message ids are truncated the iWant control messages should have been truncated themselves. +// MessageIdCountThreshold the list of message ids will be truncated. Before message ids are truncated the iWant control messages should have been truncated themselves. // Args: // - rpc: the rpc message to truncate. func (c *ControlMsgValidationInspector) truncateIWantMessageIds(from peer.ID, rpc *pubsub.RPC) { + if !c.config.InspectionProcess.Truncate.EnableIWantMessageIds { + c.logger. + Trace(). + Str("peer_id", p2plogging.PeerId(from)). + Bool(logging.KeyNetworkingSecurity, true). + Msg(IWantMessageIDTruncationDisabledWarning) + return + } + lastHighest := c.rpcTracker.LastHighestIHaveRPCSize() lg := c.logger.With(). Str("peer_id", p2plogging.PeerId(from)). - Uint("max_sample_size", c.config.IWantRPCInspectionConfig.MaxSampleSize). + Uint("max_sample_size", c.config.IWant.MessageCountThreshold). Int64("last_highest_ihave_rpc_size", lastHighest). Logger() sampleSize := int(10 * lastHighest) - if sampleSize == 0 || sampleSize > c.config.IWantRPCInspectionConfig.MaxMessageIDSampleSize { + if sampleSize == 0 || sampleSize > c.config.IWant.MessageIdCountThreshold { // invalid or 0 sample size is suspicious lg.Warn().Str(logging.KeySuspicious, "true").Msg("zero or invalid sample size, using default max sample size") - sampleSize = c.config.IWantRPCInspectionConfig.MaxMessageIDSampleSize + sampleSize = c.config.IWant.MessageIdCountThreshold } for _, iWant := range rpc.GetControl().GetIwant() { messageIDs := iWant.GetMessageIDs() - totalMessageIDs := len(messageIDs) - if totalMessageIDs == 0 { - return + totalMessageIdCount := len(messageIDs) + if totalMessageIdCount == 0 { + continue // nothing to truncate; skip } - if sampleSize > totalMessageIDs { - sampleSize = totalMessageIDs + + if totalMessageIdCount > sampleSize { + c.performSample(p2pmsg.CtrlMsgIWant, uint(totalMessageIdCount), uint(sampleSize), func(i, j uint) { + messageIDs[i], messageIDs[j] = messageIDs[j], messageIDs[i] + }) + iWant.MessageIDs = messageIDs[:sampleSize] + c.metrics.OnIWantControlMessageIdsTruncated(totalMessageIdCount - len(iWant.MessageIDs)) } - c.performSample(p2pmsg.CtrlMsgIWant, uint(totalMessageIDs), uint(sampleSize), func(i, j uint) { - messageIDs[i], messageIDs[j] = messageIDs[j], messageIDs[i] - }) - iWant.MessageIDs = messageIDs[:sampleSize] + c.metrics.OnIWantMessageIDsReceived(len(iWant.MessageIDs)) } } @@ -722,7 +971,7 @@ func (c *ControlMsgValidationInspector) validateTopic(from peer.ID, topic channe // - channels.UnknownClusterIDErr: if the topic contains a cluster ID prefix that is not in the active cluster IDs list. // // In the case where an ErrActiveClusterIdsNotSet or UnknownClusterIDErr is encountered and the cluster prefixed topic received -// tracker for the peer is less than or equal to the configured ClusterPrefixHardThreshold an error will only be logged and not returned. +// tracker for the peer is less than or equal to the configured HardThreshold an error will only be logged and not returned. // At the point where the hard threshold is crossed the error will be returned and the sender will start to be penalized. // Any errors encountered while incrementing or loading the cluster prefixed control message gauge for a peer will result in an irrecoverable error being thrown, these // errors are unexpected and irrecoverable indicating a bug. @@ -794,7 +1043,7 @@ func (c *ControlMsgValidationInspector) getFlowIdentifier(peerID peer.ID) (flow. } // checkClusterPrefixHardThreshold returns true if the cluster prefix received tracker count is less than -// the configured ClusterPrefixHardThreshold, false otherwise. +// the configured HardThreshold, false otherwise. // If any error is encountered while loading from the tracker this func will throw an error on the signaler context, these errors // are unexpected and irrecoverable indicating a bug. func (c *ControlMsgValidationInspector) checkClusterPrefixHardThreshold(nodeID flow.Identifier) bool { @@ -803,7 +1052,7 @@ func (c *ControlMsgValidationInspector) checkClusterPrefixHardThreshold(nodeID f // irrecoverable error encountered c.logAndThrowError(fmt.Errorf("cluster prefixed control message gauge during hard threshold check failed for node %s: %w", nodeID, err)) } - return gauge <= c.config.ClusterPrefixHardThreshold + return gauge <= c.config.ClusterPrefixedMessage.HardThreshold } // logAndDistributeErr logs the provided error and attempts to disseminate an invalid control message validation notification for the error. @@ -826,8 +1075,10 @@ func (c *ControlMsgValidationInspector) logAndDistributeAsyncInspectErrs(req *In switch { case IsErrActiveClusterIDsNotSet(err): + c.metrics.OnActiveClusterIDsNotSetErr() lg.Warn().Msg("active cluster ids not set") case IsErrUnstakedPeer(err): + c.metrics.OnUnstakedPeerInspectionFailed() lg.Warn().Msg("control message received from unstaked peer") default: distErr := c.distributor.Distribute(p2p.NewInvalidControlMessageNotification(req.Peer, ctlMsgType, err, count, topicType)) @@ -835,8 +1086,10 @@ func (c *ControlMsgValidationInspector) logAndDistributeAsyncInspectErrs(req *In lg.Error(). Err(distErr). Msg("failed to distribute invalid control message notification") + return } lg.Error().Msg("rpc control message async inspection failed") + c.metrics.OnInvalidControlMessageNotificationSent() } } diff --git a/network/p2p/inspector/validation/control_message_validation_inspector_test.go b/network/p2p/inspector/validation/control_message_validation_inspector_test.go index e8cd3560281..77b8413123f 100644 --- a/network/p2p/inspector/validation/control_message_validation_inspector_test.go +++ b/network/p2p/inspector/validation/control_message_validation_inspector_test.go @@ -4,13 +4,18 @@ import ( "context" "fmt" "math/rand" + "os" + "sync" "testing" "time" + pubsub "github.com/libp2p/go-libp2p-pubsub" pubsub_pb "github.com/libp2p/go-libp2p-pubsub/pb" "github.com/libp2p/go-libp2p/core/peer" + "github.com/rs/zerolog" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + "go.uber.org/atomic" "github.com/onflow/flow-go/config" "github.com/onflow/flow-go/model/flow" @@ -29,6 +34,7 @@ import ( ) func TestNewControlMsgValidationInspector(t *testing.T) { + t.Run("should create validation inspector without error", func(t *testing.T) { sporkID := unittest.IdentifierFixture() flowConfig, err := config.DefaultConfig() @@ -39,7 +45,7 @@ func TestNewControlMsgValidationInspector(t *testing.T) { inspector, err := validation.NewControlMsgValidationInspector(&validation.InspectorParams{ Logger: unittest.Logger(), SporkID: sporkID, - Config: &flowConfig.NetworkConfig.GossipSubRPCValidationInspectorConfigs, + Config: &flowConfig.NetworkConfig.GossipSub.RpcInspector.Validation, Distributor: distributor, IdProvider: idProvider, HeroCacheMetricsFactory: metrics.NewNoopHeroCacheMetricsFactory(), @@ -83,16 +89,18 @@ func TestNewControlMsgValidationInspector(t *testing.T) { // Message truncation for each control message type occurs when the count of control // messages exceeds the configured maximum sample size for that control message type. func TestControlMessageValidationInspector_truncateRPC(t *testing.T) { - t.Run("truncateGraftMessages should truncate graft messages as expected", func(t *testing.T) { + t.Run("graft truncation", func(t *testing.T) { graftPruneMessageMaxSampleSize := 1000 inspector, signalerCtx, cancel, distributor, rpcTracker, _, _, _ := inspectorFixture(t, func(params *validation.InspectorParams) { - params.Config.GraftPruneMessageMaxSampleSize = graftPruneMessageMaxSampleSize + params.Config.GraftPrune.MessageCountThreshold = graftPruneMessageMaxSampleSize }) // topic validation is ignored set any topic oracle distributor.On("Distribute", mock.AnythingOfType("*p2p.InvCtrlMsgNotif")).Return(nil).Maybe() rpcTracker.On("LastHighestIHaveRPCSize").Return(int64(100)).Maybe() rpcTracker.On("WasIHaveRPCSent", mock.AnythingOfType("string")).Return(true).Maybe() inspector.Start(signalerCtx) + unittest.RequireComponentsReadyBefore(t, 1*time.Second, inspector) + // topic validation not performed so we can use random strings graftsGreaterThanMaxSampleSize := unittest.P2PRPCFixture(unittest.WithGrafts(unittest.P2PRPCGraftFixtures(unittest.IdentifierListFixture(2000).Strings()...)...)) require.Greater(t, len(graftsGreaterThanMaxSampleSize.GetControl().GetGraft()), graftPruneMessageMaxSampleSize) @@ -109,13 +117,14 @@ func TestControlMessageValidationInspector_truncateRPC(t *testing.T) { shouldNotBeTruncated := len(graftsLessThanMaxSampleSize.GetControl().GetGraft()) == 50 return shouldBeTruncated && shouldNotBeTruncated }, time.Second, 500*time.Millisecond) - stopInspector(t, cancel, inspector) + cancel() + unittest.RequireCloseBefore(t, inspector.Done(), 5*time.Second, "inspector did not stop") }) - t.Run("truncatePruneMessages should truncate prune messages as expected", func(t *testing.T) { + t.Run("prune truncation", func(t *testing.T) { graftPruneMessageMaxSampleSize := 1000 inspector, signalerCtx, cancel, distributor, rpcTracker, _, _, _ := inspectorFixture(t, func(params *validation.InspectorParams) { - params.Config.GraftPruneMessageMaxSampleSize = graftPruneMessageMaxSampleSize + params.Config.GraftPrune.MessageCountThreshold = graftPruneMessageMaxSampleSize }) // topic validation is ignored set any topic oracle rpcTracker.On("LastHighestIHaveRPCSize").Return(int64(100)).Maybe() @@ -123,6 +132,8 @@ func TestControlMessageValidationInspector_truncateRPC(t *testing.T) { distributor.On("Distribute", mock.AnythingOfType("*p2p.InvCtrlMsgNotif")).Return(nil).Twice() inspector.Start(signalerCtx) + unittest.RequireComponentsReadyBefore(t, 1*time.Second, inspector) + // unittest.RequireCloseBefore(t, inspector.Ready(), 100*time.Millisecond, "inspector did not start") // topic validation not performed, so we can use random strings prunesGreaterThanMaxSampleSize := unittest.P2PRPCFixture(unittest.WithPrunes(unittest.P2PRPCPruneFixtures(unittest.IdentifierListFixture(2000).Strings()...)...)) @@ -139,19 +150,21 @@ func TestControlMessageValidationInspector_truncateRPC(t *testing.T) { shouldNotBeTruncated := len(prunesLessThanMaxSampleSize.GetControl().GetPrune()) == 50 return shouldBeTruncated && shouldNotBeTruncated }, time.Second, 500*time.Millisecond) - stopInspector(t, cancel, inspector) + cancel() + unittest.RequireCloseBefore(t, inspector.Done(), 5*time.Second, "inspector did not stop") }) - t.Run("truncateIHaveMessages should truncate iHave messages as expected", func(t *testing.T) { + t.Run("ihave message id truncation", func(t *testing.T) { maxSampleSize := 1000 inspector, signalerCtx, cancel, distributor, rpcTracker, _, _, _ := inspectorFixture(t, func(params *validation.InspectorParams) { - params.Config.IHaveRPCInspectionConfig.MaxSampleSize = maxSampleSize + params.Config.IHave.MessageCountThreshold = maxSampleSize }) // topic validation is ignored set any topic oracle rpcTracker.On("LastHighestIHaveRPCSize").Return(int64(100)).Maybe() rpcTracker.On("WasIHaveRPCSent", mock.AnythingOfType("string")).Return(true).Maybe() distributor.On("Distribute", mock.AnythingOfType("*p2p.InvCtrlMsgNotif")).Return(nil).Twice() inspector.Start(signalerCtx) + unittest.RequireComponentsReadyBefore(t, 1*time.Second, inspector) // topic validation not performed so we can use random strings iHavesGreaterThanMaxSampleSize := unittest.P2PRPCFixture(unittest.WithIHaves(unittest.P2PRPCIHaveFixtures(2000, unittest.IdentifierListFixture(2000).Strings()...)...)) @@ -163,25 +176,27 @@ func TestControlMessageValidationInspector_truncateRPC(t *testing.T) { require.NoError(t, inspector.Inspect(from, iHavesGreaterThanMaxSampleSize)) require.NoError(t, inspector.Inspect(from, iHavesLessThanMaxSampleSize)) require.Eventually(t, func() bool { - // rpc with iHaves greater than configured max sample size should be truncated to MaxSampleSize + // rpc with iHaves greater than configured max sample size should be truncated to MessageCountThreshold shouldBeTruncated := len(iHavesGreaterThanMaxSampleSize.GetControl().GetIhave()) == maxSampleSize - // rpc with iHaves less than MaxSampleSize should not be truncated + // rpc with iHaves less than MessageCountThreshold should not be truncated shouldNotBeTruncated := len(iHavesLessThanMaxSampleSize.GetControl().GetIhave()) == 50 return shouldBeTruncated && shouldNotBeTruncated }, time.Second, 500*time.Millisecond) - stopInspector(t, cancel, inspector) + cancel() + unittest.RequireCloseBefore(t, inspector.Done(), 5*time.Second, "inspector did not stop") }) - t.Run("truncateIHaveMessageIds should truncate iHave message ids as expected", func(t *testing.T) { + t.Run("ihave message ids truncation", func(t *testing.T) { maxMessageIDSampleSize := 1000 inspector, signalerCtx, cancel, distributor, rpcTracker, _, _, _ := inspectorFixture(t, func(params *validation.InspectorParams) { - params.Config.IHaveRPCInspectionConfig.MaxMessageIDSampleSize = maxMessageIDSampleSize + params.Config.IHave.MessageIdCountThreshold = maxMessageIDSampleSize }) // topic validation is ignored set any topic oracle rpcTracker.On("LastHighestIHaveRPCSize").Return(int64(100)).Maybe() rpcTracker.On("WasIHaveRPCSent", mock.AnythingOfType("string")).Return(true).Maybe() distributor.On("Distribute", mock.AnythingOfType("*p2p.InvCtrlMsgNotif")).Return(nil).Twice() inspector.Start(signalerCtx) + unittest.RequireComponentsReadyBefore(t, 1*time.Second, inspector) // topic validation not performed so we can use random strings iHavesGreaterThanMaxSampleSize := unittest.P2PRPCFixture(unittest.WithIHaves(unittest.P2PRPCIHaveFixtures(2000, unittest.IdentifierListFixture(10).Strings()...)...)) @@ -192,32 +207,35 @@ func TestControlMessageValidationInspector_truncateRPC(t *testing.T) { require.NoError(t, inspector.Inspect(from, iHavesLessThanMaxSampleSize)) require.Eventually(t, func() bool { for _, iHave := range iHavesGreaterThanMaxSampleSize.GetControl().GetIhave() { - // rpc with iHaves message ids greater than configured max sample size should be truncated to MaxSampleSize + // rpc with iHaves message ids greater than configured max sample size should be truncated to MessageCountThreshold if len(iHave.GetMessageIDs()) != maxMessageIDSampleSize { return false } } for _, iHave := range iHavesLessThanMaxSampleSize.GetControl().GetIhave() { - // rpc with iHaves message ids less than MaxSampleSize should not be truncated + // rpc with iHaves message ids less than MessageCountThreshold should not be truncated if len(iHave.GetMessageIDs()) != 50 { return false } } return true }, time.Second, 500*time.Millisecond) - stopInspector(t, cancel, inspector) + cancel() + unittest.RequireCloseBefore(t, inspector.Done(), 5*time.Second, "inspector did not stop") }) - t.Run("truncateIWantMessages should truncate iWant messages as expected", func(t *testing.T) { + t.Run("iwant message truncation", func(t *testing.T) { maxSampleSize := uint(100) inspector, signalerCtx, cancel, distributor, rpcTracker, _, _, _ := inspectorFixture(t, func(params *validation.InspectorParams) { - params.Config.IWantRPCInspectionConfig.MaxSampleSize = maxSampleSize + params.Config.IWant.MessageCountThreshold = maxSampleSize }) // topic validation is ignored set any topic oracle rpcTracker.On("LastHighestIHaveRPCSize").Return(int64(100)).Maybe() rpcTracker.On("WasIHaveRPCSent", mock.AnythingOfType("string")).Return(true).Maybe() distributor.On("Distribute", mock.AnythingOfType("*p2p.InvCtrlMsgNotif")).Return(nil).Maybe() inspector.Start(signalerCtx) + unittest.RequireComponentsReadyBefore(t, 1*time.Second, inspector) + iWantsGreaterThanMaxSampleSize := unittest.P2PRPCFixture(unittest.WithIWants(unittest.P2PRPCIWantFixtures(200, 200)...)) require.Greater(t, uint(len(iWantsGreaterThanMaxSampleSize.GetControl().GetIwant())), maxSampleSize) iWantsLessThanMaxSampleSize := unittest.P2PRPCFixture(unittest.WithIWants(unittest.P2PRPCIWantFixtures(50, 200)...)) @@ -227,25 +245,28 @@ func TestControlMessageValidationInspector_truncateRPC(t *testing.T) { require.NoError(t, inspector.Inspect(from, iWantsGreaterThanMaxSampleSize)) require.NoError(t, inspector.Inspect(from, iWantsLessThanMaxSampleSize)) require.Eventually(t, func() bool { - // rpc with iWants greater than configured max sample size should be truncated to MaxSampleSize + // rpc with iWants greater than configured max sample size should be truncated to MessageCountThreshold shouldBeTruncated := len(iWantsGreaterThanMaxSampleSize.GetControl().GetIwant()) == int(maxSampleSize) - // rpc with iWants less than MaxSampleSize should not be truncated + // rpc with iWants less than MessageCountThreshold should not be truncated shouldNotBeTruncated := len(iWantsLessThanMaxSampleSize.GetControl().GetIwant()) == 50 return shouldBeTruncated && shouldNotBeTruncated }, time.Second, 500*time.Millisecond) - stopInspector(t, cancel, inspector) + cancel() + unittest.RequireCloseBefore(t, inspector.Done(), 5*time.Second, "inspector did not stop") }) - t.Run("truncateIWantMessageIds should truncate iWant message ids as expected", func(t *testing.T) { + t.Run("iwant message id truncation", func(t *testing.T) { maxMessageIDSampleSize := 1000 inspector, signalerCtx, cancel, distributor, rpcTracker, _, _, _ := inspectorFixture(t, func(params *validation.InspectorParams) { - params.Config.IWantRPCInspectionConfig.MaxMessageIDSampleSize = maxMessageIDSampleSize + params.Config.IWant.MessageIdCountThreshold = maxMessageIDSampleSize }) // topic validation is ignored set any topic oracle rpcTracker.On("LastHighestIHaveRPCSize").Return(int64(100)).Maybe() rpcTracker.On("WasIHaveRPCSent", mock.AnythingOfType("string")).Return(true).Maybe() distributor.On("Distribute", mock.AnythingOfType("*p2p.InvCtrlMsgNotif")).Return(nil).Maybe() inspector.Start(signalerCtx) + unittest.RequireComponentsReadyBefore(t, 1*time.Second, inspector) + iWantsGreaterThanMaxSampleSize := unittest.P2PRPCFixture(unittest.WithIWants(unittest.P2PRPCIWantFixtures(10, 2000)...)) iWantsLessThanMaxSampleSize := unittest.P2PRPCFixture(unittest.WithIWants(unittest.P2PRPCIWantFixtures(10, 50)...)) @@ -254,442 +275,762 @@ func TestControlMessageValidationInspector_truncateRPC(t *testing.T) { require.NoError(t, inspector.Inspect(from, iWantsLessThanMaxSampleSize)) require.Eventually(t, func() bool { for _, iWant := range iWantsGreaterThanMaxSampleSize.GetControl().GetIwant() { - // rpc with iWants message ids greater than configured max sample size should be truncated to MaxSampleSize + // rpc with iWants message ids greater than configured max sample size should be truncated to MessageCountThreshold if len(iWant.GetMessageIDs()) != maxMessageIDSampleSize { return false } } for _, iWant := range iWantsLessThanMaxSampleSize.GetControl().GetIwant() { - // rpc with iWants less than MaxSampleSize should not be truncated + // rpc with iWants less than MessageCountThreshold should not be truncated if len(iWant.GetMessageIDs()) != 50 { return false } } return true }, time.Second, 500*time.Millisecond) - stopInspector(t, cancel, inspector) + cancel() + unittest.RequireCloseBefore(t, inspector.Done(), 5*time.Second, "inspector did not stop") }) } -// TestControlMessageValidationInspector_processInspectRPCReq verifies the correct behavior of control message validation. -// It ensures that valid RPC control messages do not trigger erroneous invalid control message notifications, -// while all types of invalid control messages trigger expected notifications. -func TestControlMessageValidationInspector_processInspectRPCReq(t *testing.T) { - t.Run("processInspectRPCReq should not disseminate any invalid notification errors for valid RPC's", func(t *testing.T) { - inspector, signalerCtx, cancel, distributor, rpcTracker, sporkID, _, topicProviderOracle := inspectorFixture(t) - defer distributor.AssertNotCalled(t, "Distribute") +// TestControlMessageInspection_ValidRpc ensures inspector does not disseminate invalid control message notifications for a valid RPC. +func TestControlMessageInspection_ValidRpc(t *testing.T) { + inspector, signalerCtx, cancel, distributor, rpcTracker, sporkID, _, topicProviderOracle := inspectorFixture(t) + defer distributor.AssertNotCalled(t, "Distribute") - topics := []string{ - fmt.Sprintf("%s/%s", channels.TestNetworkChannel, sporkID), - fmt.Sprintf("%s/%s", channels.PushBlocks, sporkID), - fmt.Sprintf("%s/%s", channels.SyncCommittee, sporkID), - fmt.Sprintf("%s/%s", channels.RequestChunks, sporkID), + topics := []string{ + fmt.Sprintf("%s/%s", channels.TestNetworkChannel, sporkID), + fmt.Sprintf("%s/%s", channels.PushBlocks, sporkID), + fmt.Sprintf("%s/%s", channels.SyncCommittee, sporkID), + fmt.Sprintf("%s/%s", channels.RequestChunks, sporkID), + } + // avoid unknown topics errors + topicProviderOracle.UpdateTopics(topics) + inspector.Start(signalerCtx) + unittest.RequireComponentsReadyBefore(t, 1*time.Second, inspector) + + grafts := unittest.P2PRPCGraftFixtures(topics...) + prunes := unittest.P2PRPCPruneFixtures(topics...) + ihaves := unittest.P2PRPCIHaveFixtures(50, topics...) + iwants := unittest.P2PRPCIWantFixtures(2, 50) + pubsubMsgs := unittest.GossipSubMessageFixtures(10, topics[0]) + + rpc := unittest.P2PRPCFixture( + unittest.WithGrafts(grafts...), + unittest.WithPrunes(prunes...), + unittest.WithIHaves(ihaves...), + unittest.WithIWants(iwants...), + unittest.WithPubsubMessages(pubsubMsgs...)) + rpcTracker.On("LastHighestIHaveRPCSize").Return(int64(100)).Maybe() + rpcTracker.On("WasIHaveRPCSent", mock.AnythingOfType("string")).Return(true).Run(func(args mock.Arguments) { + id, ok := args[0].(string) + require.True(t, ok) + for _, iwant := range iwants { + for _, messageID := range iwant.GetMessageIDs() { + if id == messageID { + return + } + } } - // avoid unknown topics errors - topicProviderOracle.UpdateTopics(topics) - inspector.Start(signalerCtx) - grafts := unittest.P2PRPCGraftFixtures(topics...) - prunes := unittest.P2PRPCPruneFixtures(topics...) - ihaves := unittest.P2PRPCIHaveFixtures(50, topics...) - iwants := unittest.P2PRPCIWantFixtures(2, 5) - pubsubMsgs := unittest.GossipSubMessageFixtures(10, topics[0]) - - // avoid cache misses for iwant messages. - iwants[0].MessageIDs = ihaves[0].MessageIDs[:10] - iwants[1].MessageIDs = ihaves[1].MessageIDs[11:20] - expectedMsgIds := make([]string, 0) - expectedMsgIds = append(expectedMsgIds, ihaves[0].MessageIDs...) - expectedMsgIds = append(expectedMsgIds, ihaves[1].MessageIDs...) - rpc := unittest.P2PRPCFixture( - unittest.WithGrafts(grafts...), - unittest.WithPrunes(prunes...), - unittest.WithIHaves(ihaves...), - unittest.WithIWants(iwants...), - unittest.WithPubsubMessages(pubsubMsgs...)) - rpcTracker.On("LastHighestIHaveRPCSize").Return(int64(100)).Maybe() - rpcTracker.On("WasIHaveRPCSent", mock.AnythingOfType("string")).Return(true).Run(func(args mock.Arguments) { - id, ok := args[0].(string) - require.True(t, ok) - require.Contains(t, expectedMsgIds, id) - }) - - from := unittest.PeerIdFixture(t) - require.NoError(t, inspector.Inspect(from, rpc)) - // sleep for 1 second to ensure rpc is processed - time.Sleep(time.Second) - stopInspector(t, cancel, inspector) + require.Fail(t, "message id not found in iwant messages") }) - t.Run("processInspectRPCReq should disseminate invalid control message notification for control messages with duplicate topics", func(t *testing.T) { - inspector, signalerCtx, cancel, distributor, _, sporkID, _, topicProviderOracle := inspectorFixture(t) - duplicateTopic := fmt.Sprintf("%s/%s", channels.TestNetworkChannel, sporkID) - // avoid unknown topics errors - topicProviderOracle.UpdateTopics([]string{duplicateTopic}) - // create control messages with duplicate topic - grafts := []*pubsub_pb.ControlGraft{unittest.P2PRPCGraftFixture(&duplicateTopic), unittest.P2PRPCGraftFixture(&duplicateTopic)} - prunes := []*pubsub_pb.ControlPrune{unittest.P2PRPCPruneFixture(&duplicateTopic), unittest.P2PRPCPruneFixture(&duplicateTopic)} - ihaves := []*pubsub_pb.ControlIHave{unittest.P2PRPCIHaveFixture(&duplicateTopic, unittest.IdentifierListFixture(20).Strings()...), - unittest.P2PRPCIHaveFixture(&duplicateTopic, unittest.IdentifierListFixture(20).Strings()...)} - from := unittest.PeerIdFixture(t) - duplicateTopicGraftsRpc := unittest.P2PRPCFixture(unittest.WithGrafts(grafts...)) - duplicateTopicPrunesRpc := unittest.P2PRPCFixture(unittest.WithPrunes(prunes...)) - duplicateTopicIHavesRpc := unittest.P2PRPCFixture(unittest.WithIHaves(ihaves...)) - distributor.On("Distribute", mock.AnythingOfType("*p2p.InvCtrlMsgNotif")).Return(nil).Times(3).Run(func(args mock.Arguments) { - notification, ok := args[0].(*p2p.InvCtrlMsgNotif) - require.True(t, ok) - require.Equal(t, notification.TopicType, p2p.CtrlMsgNonClusterTopicType, "expected p2p.CtrlMsgNonClusterTopicType notification type, no RPC with cluster prefixed topic sent in this test") - require.Equal(t, from, notification.PeerID) - require.Contains(t, []p2pmsg.ControlMessageType{p2pmsg.CtrlMsgGraft, p2pmsg.CtrlMsgPrune, p2pmsg.CtrlMsgIHave}, notification.MsgType) - require.True(t, validation.IsDuplicateTopicErr(notification.Error)) - }) + from := unittest.PeerIdFixture(t) + require.NoError(t, inspector.Inspect(from, rpc)) + // sleep for 1 second to ensure rpc is processed + time.Sleep(time.Second) + cancel() + unittest.RequireCloseBefore(t, inspector.Done(), 5*time.Second, "inspector did not stop") +} - inspector.Start(signalerCtx) +// TestGraftInspection_InvalidTopic ensures inspector disseminates an invalid control message notification for +// graft messages when the topic is invalid. +func TestGraftInspection_InvalidTopic(t *testing.T) { + inspector, signalerCtx, cancel, distributor, rpcTracker, sporkID, _, topicProviderOracle := inspectorFixture(t) + // create unknown topic + unknownTopic, malformedTopic, invalidSporkIDTopic := invalidTopics(t, sporkID) + // avoid unknown topics errors + topicProviderOracle.UpdateTopics([]string{unknownTopic, malformedTopic, invalidSporkIDTopic}) + unknownTopicGraft := unittest.P2PRPCGraftFixture(&unknownTopic) + malformedTopicGraft := unittest.P2PRPCGraftFixture(&malformedTopic) + invalidSporkIDTopicGraft := unittest.P2PRPCGraftFixture(&invalidSporkIDTopic) - require.NoError(t, inspector.Inspect(from, duplicateTopicGraftsRpc)) - require.NoError(t, inspector.Inspect(from, duplicateTopicPrunesRpc)) - require.NoError(t, inspector.Inspect(from, duplicateTopicIHavesRpc)) - // sleep for 1 second to ensure rpc's is processed - time.Sleep(time.Second) - stopInspector(t, cancel, inspector) - }) + unknownTopicReq := unittest.P2PRPCFixture(unittest.WithGrafts(unknownTopicGraft)) + malformedTopicReq := unittest.P2PRPCFixture(unittest.WithGrafts(malformedTopicGraft)) + invalidSporkIDTopicReq := unittest.P2PRPCFixture(unittest.WithGrafts(invalidSporkIDTopicGraft)) - t.Run("inspectGraftMessages should disseminate invalid control message notification for invalid graft messages as expected", func(t *testing.T) { - inspector, signalerCtx, cancel, distributor, _, sporkID, _, topicProviderOracle := inspectorFixture(t) - // create unknown topic - unknownTopic, malformedTopic, invalidSporkIDTopic := invalidTopics(t, sporkID) - // avoid unknown topics errors - topicProviderOracle.UpdateTopics([]string{unknownTopic, malformedTopic, invalidSporkIDTopic}) - unknownTopicGraft := unittest.P2PRPCGraftFixture(&unknownTopic) - malformedTopicGraft := unittest.P2PRPCGraftFixture(&malformedTopic) - invalidSporkIDTopicGraft := unittest.P2PRPCGraftFixture(&invalidSporkIDTopic) + from := unittest.PeerIdFixture(t) + checkNotification := checkNotificationFunc(t, from, p2pmsg.CtrlMsgGraft, channels.IsInvalidTopicErr, p2p.CtrlMsgNonClusterTopicType) + rpcTracker.On("LastHighestIHaveRPCSize").Return(int64(100)).Maybe() + distributor.On("Distribute", mock.AnythingOfType("*p2p.InvCtrlMsgNotif")).Return(nil).Times(3).Run(checkNotification) - unknownTopicReq := unittest.P2PRPCFixture(unittest.WithGrafts(unknownTopicGraft)) - malformedTopicReq := unittest.P2PRPCFixture(unittest.WithGrafts(malformedTopicGraft)) - invalidSporkIDTopicReq := unittest.P2PRPCFixture(unittest.WithGrafts(invalidSporkIDTopicGraft)) + inspector.Start(signalerCtx) + unittest.RequireComponentsReadyBefore(t, 1*time.Second, inspector) - from := unittest.PeerIdFixture(t) - checkNotification := checkNotificationFunc(t, from, p2pmsg.CtrlMsgGraft, channels.IsInvalidTopicErr, p2p.CtrlMsgNonClusterTopicType) - distributor.On("Distribute", mock.AnythingOfType("*p2p.InvCtrlMsgNotif")).Return(nil).Times(3).Run(checkNotification) + require.NoError(t, inspector.Inspect(from, unknownTopicReq)) + require.NoError(t, inspector.Inspect(from, malformedTopicReq)) + require.NoError(t, inspector.Inspect(from, invalidSporkIDTopicReq)) + // sleep for 1 second to ensure rpc's is processed + time.Sleep(time.Second) + cancel() + unittest.RequireCloseBefore(t, inspector.Done(), 5*time.Second, "inspector did not stop") +} - inspector.Start(signalerCtx) +// TestGraftInspection_DuplicateTopicIds_BelowThreshold ensures inspector does not disseminate invalid control message notifications +// for a valid RPC with duplicate graft topic ids below the threshold. +func TestGraftInspection_DuplicateTopicIds_BelowThreshold(t *testing.T) { + inspector, signalerCtx, cancel, distributor, rpcTracker, sporkID, _, topicProviderOracle := inspectorFixture(t) + duplicateTopic := fmt.Sprintf("%s/%s", channels.TestNetworkChannel, sporkID) + // avoid unknown topics errors + topicProviderOracle.UpdateTopics([]string{duplicateTopic}) + var grafts []*pubsub_pb.ControlGraft + cfg, err := config.DefaultConfig() + require.NoError(t, err) + for i := 0; i < cfg.NetworkConfig.GossipSub.RpcInspector.Validation.GraftPrune.DuplicateTopicIdThreshold; i++ { + grafts = append(grafts, unittest.P2PRPCGraftFixture(&duplicateTopic)) + } + from := unittest.PeerIdFixture(t) + rpc := unittest.P2PRPCFixture(unittest.WithGrafts(grafts...)) + rpcTracker.On("LastHighestIHaveRPCSize").Return(int64(100)).Maybe() + // no notification should be disseminated for valid messages as long as the number of duplicates is below the threshold + distributor.AssertNotCalled(t, "Distribute", mock.AnythingOfType("*p2p.InvCtrlMsgNotif")) - require.NoError(t, inspector.Inspect(from, unknownTopicReq)) - require.NoError(t, inspector.Inspect(from, malformedTopicReq)) - require.NoError(t, inspector.Inspect(from, invalidSporkIDTopicReq)) - // sleep for 1 second to ensure rpc's is processed - time.Sleep(time.Second) - stopInspector(t, cancel, inspector) + inspector.Start(signalerCtx) + unittest.RequireComponentsReadyBefore(t, 100*time.Millisecond, inspector) + + require.NoError(t, inspector.Inspect(from, rpc)) + // sleep for 1 second to ensure rpc's is processed + time.Sleep(time.Second) + cancel() + unittest.RequireCloseBefore(t, inspector.Done(), 5*time.Second, "inspector did not stop") +} + +func TestGraftInspection_DuplicateTopicIds_AboveThreshold(t *testing.T) { + inspector, signalerCtx, cancel, distributor, rpcTracker, sporkID, _, topicProviderOracle := inspectorFixture(t) + duplicateTopic := fmt.Sprintf("%s/%s", channels.TestNetworkChannel, sporkID) + // avoid unknown topics errors + topicProviderOracle.UpdateTopics([]string{duplicateTopic}) + var grafts []*pubsub_pb.ControlGraft + cfg, err := config.DefaultConfig() + require.NoError(t, err) + for i := 0; i < cfg.NetworkConfig.GossipSub.RpcInspector.Validation.GraftPrune.DuplicateTopicIdThreshold+2; i++ { + grafts = append(grafts, unittest.P2PRPCGraftFixture(&duplicateTopic)) + } + from := unittest.PeerIdFixture(t) + rpc := unittest.P2PRPCFixture(unittest.WithGrafts(grafts...)) + rpcTracker.On("LastHighestIHaveRPCSize").Return(int64(100)).Maybe() + + distributor.On("Distribute", mock.AnythingOfType("*p2p.InvCtrlMsgNotif")).Return(nil).Once().Run(func(args mock.Arguments) { + notification, ok := args[0].(*p2p.InvCtrlMsgNotif) + require.True(t, ok) + require.Equal(t, notification.TopicType, p2p.CtrlMsgNonClusterTopicType, "expected p2p.CtrlMsgNonClusterTopicType notification type, no RPC with cluster prefixed topic sent in this test") + require.Equal(t, from, notification.PeerID) + require.Equal(t, p2pmsg.CtrlMsgGraft, notification.MsgType) + require.True(t, validation.IsDuplicateTopicErr(notification.Error)) }) - t.Run("inspectPruneMessages should disseminate invalid control message notification for invalid prune messages as expected", func(t *testing.T) { - inspector, signalerCtx, cancel, distributor, _, sporkID, _, topicProviderOracle := inspectorFixture(t) - // create unknown topic - unknownTopic, malformedTopic, invalidSporkIDTopic := invalidTopics(t, sporkID) - unknownTopicPrune := unittest.P2PRPCPruneFixture(&unknownTopic) - malformedTopicPrune := unittest.P2PRPCPruneFixture(&malformedTopic) - invalidSporkIDTopicPrune := unittest.P2PRPCPruneFixture(&invalidSporkIDTopic) - // avoid unknown topics errors - topicProviderOracle.UpdateTopics([]string{unknownTopic, malformedTopic, invalidSporkIDTopic}) - unknownTopicRpc := unittest.P2PRPCFixture(unittest.WithPrunes(unknownTopicPrune)) - malformedTopicRpc := unittest.P2PRPCFixture(unittest.WithPrunes(malformedTopicPrune)) - invalidSporkIDTopicRpc := unittest.P2PRPCFixture(unittest.WithPrunes(invalidSporkIDTopicPrune)) + inspector.Start(signalerCtx) + unittest.RequireComponentsReadyBefore(t, 100*time.Millisecond, inspector) - from := unittest.PeerIdFixture(t) - checkNotification := checkNotificationFunc(t, from, p2pmsg.CtrlMsgPrune, channels.IsInvalidTopicErr, p2p.CtrlMsgNonClusterTopicType) - distributor.On("Distribute", mock.AnythingOfType("*p2p.InvCtrlMsgNotif")).Return(nil).Times(3).Run(checkNotification) + require.NoError(t, inspector.Inspect(from, rpc)) + // sleep for 1 second to ensure rpc's is processed + time.Sleep(time.Second) + cancel() + unittest.RequireCloseBefore(t, inspector.Done(), 5*time.Second, "inspector did not stop") +} - inspector.Start(signalerCtx) +// TestPruneInspection_DuplicateTopicIds_AboveThreshold ensures inspector disseminates an invalid control message notification for +// prune messages when the number of duplicate topic ids is above the threshold. +func TestPruneInspection_DuplicateTopicIds_AboveThreshold(t *testing.T) { + inspector, signalerCtx, cancel, distributor, rpcTracker, sporkID, _, topicProviderOracle := inspectorFixture(t) + duplicateTopic := fmt.Sprintf("%s/%s", channels.TestNetworkChannel, sporkID) + // avoid unknown topics errors + topicProviderOracle.UpdateTopics([]string{duplicateTopic}) + var prunes []*pubsub_pb.ControlPrune + cfg, err := config.DefaultConfig() + require.NoError(t, err) + // we need threshold + 1 to trigger the invalid control message notification; as the first duplicate topic id is not counted + for i := 0; i < cfg.NetworkConfig.GossipSub.RpcInspector.Validation.GraftPrune.DuplicateTopicIdThreshold+2; i++ { + prunes = append(prunes, unittest.P2PRPCPruneFixture(&duplicateTopic)) + } + from := unittest.PeerIdFixture(t) + rpc := unittest.P2PRPCFixture(unittest.WithPrunes(prunes...)) + rpcTracker.On("LastHighestIHaveRPCSize").Return(int64(100)).Maybe() - require.NoError(t, inspector.Inspect(from, unknownTopicRpc)) - require.NoError(t, inspector.Inspect(from, malformedTopicRpc)) - require.NoError(t, inspector.Inspect(from, invalidSporkIDTopicRpc)) - // sleep for 1 second to ensure rpc's is processed - time.Sleep(time.Second) - stopInspector(t, cancel, inspector) + distributor.On("Distribute", mock.AnythingOfType("*p2p.InvCtrlMsgNotif")).Return(nil).Once().Run(func(args mock.Arguments) { + notification, ok := args[0].(*p2p.InvCtrlMsgNotif) + require.True(t, ok) + require.Equal(t, notification.TopicType, p2p.CtrlMsgNonClusterTopicType, "expected p2p.CtrlMsgNonClusterTopicType notification type, no RPC with cluster prefixed topic sent in this test") + require.Equal(t, from, notification.PeerID) + require.Equal(t, p2pmsg.CtrlMsgPrune, notification.MsgType) + require.True(t, validation.IsDuplicateTopicErr(notification.Error)) }) - t.Run("inspectIHaveMessages should disseminate invalid control message notification for iHave messages with invalid topics as expected", func(t *testing.T) { - inspector, signalerCtx, cancel, distributor, _, sporkID, _, topicProviderOracle := inspectorFixture(t) - // create unknown topic - unknownTopic, malformedTopic, invalidSporkIDTopic := invalidTopics(t, sporkID) - // avoid unknown topics errors - topicProviderOracle.UpdateTopics([]string{unknownTopic, malformedTopic, invalidSporkIDTopic}) - unknownTopicIhave := unittest.P2PRPCIHaveFixture(&unknownTopic, unittest.IdentifierListFixture(5).Strings()...) - malformedTopicIhave := unittest.P2PRPCIHaveFixture(&malformedTopic, unittest.IdentifierListFixture(5).Strings()...) - invalidSporkIDTopicIhave := unittest.P2PRPCIHaveFixture(&invalidSporkIDTopic, unittest.IdentifierListFixture(5).Strings()...) + inspector.Start(signalerCtx) + unittest.RequireComponentsReadyBefore(t, 100*time.Millisecond, inspector) - unknownTopicRpc := unittest.P2PRPCFixture(unittest.WithIHaves(unknownTopicIhave)) - malformedTopicRpc := unittest.P2PRPCFixture(unittest.WithIHaves(malformedTopicIhave)) - invalidSporkIDTopicRpc := unittest.P2PRPCFixture(unittest.WithIHaves(invalidSporkIDTopicIhave)) + require.NoError(t, inspector.Inspect(from, rpc)) + // sleep for 1 second to ensure rpc's is processed + time.Sleep(time.Second) + cancel() + unittest.RequireCloseBefore(t, inspector.Done(), 5*time.Second, "inspector did not stop") +} - from := unittest.PeerIdFixture(t) - checkNotification := checkNotificationFunc(t, from, p2pmsg.CtrlMsgIHave, channels.IsInvalidTopicErr, p2p.CtrlMsgNonClusterTopicType) - distributor.On("Distribute", mock.AnythingOfType("*p2p.InvCtrlMsgNotif")).Return(nil).Times(3).Run(checkNotification) - inspector.Start(signalerCtx) +// TestPruneInspection_DuplicateTopicIds_BelowThreshold ensures inspector does not disseminate invalid control message notifications +// for a valid RPC with duplicate prune topic ids below the threshold. +func TestPrueInspection_DuplicateTopicIds_BelowThreshold(t *testing.T) { + inspector, signalerCtx, cancel, distributor, rpcTracker, sporkID, _, topicProviderOracle := inspectorFixture(t) + duplicateTopic := fmt.Sprintf("%s/%s", channels.TestNetworkChannel, sporkID) + // avoid unknown topics errors + topicProviderOracle.UpdateTopics([]string{duplicateTopic}) + var prunes []*pubsub_pb.ControlPrune + cfg, err := config.DefaultConfig() + require.NoError(t, err) + for i := 0; i < cfg.NetworkConfig.GossipSub.RpcInspector.Validation.GraftPrune.DuplicateTopicIdThreshold; i++ { + prunes = append(prunes, unittest.P2PRPCPruneFixture(&duplicateTopic)) + } + from := unittest.PeerIdFixture(t) + rpc := unittest.P2PRPCFixture(unittest.WithPrunes(prunes...)) + rpcTracker.On("LastHighestIHaveRPCSize").Return(int64(100)).Maybe() - require.NoError(t, inspector.Inspect(from, unknownTopicRpc)) - require.NoError(t, inspector.Inspect(from, malformedTopicRpc)) - require.NoError(t, inspector.Inspect(from, invalidSporkIDTopicRpc)) - // sleep for 1 second to ensure rpc's is processed - time.Sleep(time.Second) - stopInspector(t, cancel, inspector) - }) + // no notification should be disseminated for valid messages as long as the number of duplicates is below the threshold + distributor.AssertNotCalled(t, "Distribute", mock.AnythingOfType("*p2p.InvCtrlMsgNotif")) - t.Run("inspectIHaveMessages should disseminate invalid control message notification for iHave messages with duplicate message ids as expected", func(t *testing.T) { - inspector, signalerCtx, cancel, distributor, _, sporkID, _, topicProviderOracle := inspectorFixture(t) - validTopic := fmt.Sprintf("%s/%s", channels.PushBlocks.String(), sporkID) - // avoid unknown topics errors - topicProviderOracle.UpdateTopics([]string{validTopic}) - duplicateMsgID := unittest.IdentifierFixture() - msgIds := flow.IdentifierList{duplicateMsgID, duplicateMsgID, duplicateMsgID} - duplicateMsgIDIHave := unittest.P2PRPCIHaveFixture(&validTopic, append(msgIds, unittest.IdentifierListFixture(5)...).Strings()...) - duplicateMsgIDRpc := unittest.P2PRPCFixture(unittest.WithIHaves(duplicateMsgIDIHave)) - from := unittest.PeerIdFixture(t) - checkNotification := checkNotificationFunc(t, from, p2pmsg.CtrlMsgIHave, validation.IsDuplicateTopicErr, p2p.CtrlMsgNonClusterTopicType) - distributor.On("Distribute", mock.AnythingOfType("*p2p.InvCtrlMsgNotif")).Return(nil).Once().Run(checkNotification) - inspector.Start(signalerCtx) + inspector.Start(signalerCtx) + unittest.RequireComponentsReadyBefore(t, 100*time.Millisecond, inspector) - require.NoError(t, inspector.Inspect(from, duplicateMsgIDRpc)) - // sleep for 1 second to ensure rpc's is processed - time.Sleep(time.Second) - stopInspector(t, cancel, inspector) - }) + require.NoError(t, inspector.Inspect(from, rpc)) + // sleep for 1 second to ensure rpc's is processed + time.Sleep(time.Second) + cancel() + unittest.RequireCloseBefore(t, inspector.Done(), 5*time.Second, "inspector did not stop") +} - t.Run("inspectIWantMessages should disseminate invalid control message notification for iWant messages when duplicate message ids exceeds the allowed threshold", func(t *testing.T) { - inspector, signalerCtx, cancel, distributor, rpcTracker, _, _, _ := inspectorFixture(t) - // oracle must be set even though iWant messages do not have topic IDs - duplicateMsgID := unittest.IdentifierFixture() - duplicates := flow.IdentifierList{duplicateMsgID, duplicateMsgID} - msgIds := append(duplicates, unittest.IdentifierListFixture(5)...).Strings() - duplicateMsgIDIWant := unittest.P2PRPCIWantFixture(msgIds...) +// TestPruneInspection_InvalidTopic ensures inspector disseminates an invalid control message notification for +// prune messages when the topic is invalid. +func TestPruneInspection_InvalidTopic(t *testing.T) { + inspector, signalerCtx, cancel, distributor, rpcTracker, sporkID, _, topicProviderOracle := inspectorFixture(t) + // create unknown topic + unknownTopic, malformedTopic, invalidSporkIDTopic := invalidTopics(t, sporkID) + unknownTopicPrune := unittest.P2PRPCPruneFixture(&unknownTopic) + malformedTopicPrune := unittest.P2PRPCPruneFixture(&malformedTopic) + invalidSporkIDTopicPrune := unittest.P2PRPCPruneFixture(&invalidSporkIDTopic) + // avoid unknown topics errors + topicProviderOracle.UpdateTopics([]string{unknownTopic, malformedTopic, invalidSporkIDTopic}) + unknownTopicRpc := unittest.P2PRPCFixture(unittest.WithPrunes(unknownTopicPrune)) + malformedTopicRpc := unittest.P2PRPCFixture(unittest.WithPrunes(malformedTopicPrune)) + invalidSporkIDTopicRpc := unittest.P2PRPCFixture(unittest.WithPrunes(invalidSporkIDTopicPrune)) - duplicateMsgIDRpc := unittest.P2PRPCFixture(unittest.WithIWants(duplicateMsgIDIWant)) + from := unittest.PeerIdFixture(t) + checkNotification := checkNotificationFunc(t, from, p2pmsg.CtrlMsgPrune, channels.IsInvalidTopicErr, p2p.CtrlMsgNonClusterTopicType) + rpcTracker.On("LastHighestIHaveRPCSize").Return(int64(100)).Maybe() - from := unittest.PeerIdFixture(t) - checkNotification := checkNotificationFunc(t, from, p2pmsg.CtrlMsgIWant, validation.IsIWantDuplicateMsgIDThresholdErr, p2p.CtrlMsgNonClusterTopicType) - distributor.On("Distribute", mock.AnythingOfType("*p2p.InvCtrlMsgNotif")).Return(nil).Once().Run(checkNotification) - rpcTracker.On("LastHighestIHaveRPCSize").Return(int64(100)).Maybe() - rpcTracker.On("WasIHaveRPCSent", mock.AnythingOfType("string")).Return(true).Run(func(args mock.Arguments) { - id, ok := args[0].(string) - require.True(t, ok) - require.Contains(t, msgIds, id) - }) + distributor.On("Distribute", mock.AnythingOfType("*p2p.InvCtrlMsgNotif")).Return(nil).Times(3).Run(checkNotification) - inspector.Start(signalerCtx) + inspector.Start(signalerCtx) + unittest.RequireComponentsReadyBefore(t, 1*time.Second, inspector) - require.NoError(t, inspector.Inspect(from, duplicateMsgIDRpc)) - // sleep for 1 second to ensure rpc's is processed - time.Sleep(time.Second) - stopInspector(t, cancel, inspector) - }) + require.NoError(t, inspector.Inspect(from, unknownTopicRpc)) + require.NoError(t, inspector.Inspect(from, malformedTopicRpc)) + require.NoError(t, inspector.Inspect(from, invalidSporkIDTopicRpc)) + // sleep for 1 second to ensure rpc's is processed + time.Sleep(time.Second) + cancel() + unittest.RequireCloseBefore(t, inspector.Done(), 5*time.Second, "inspector did not stop") +} - t.Run("inspectIWantMessages should disseminate invalid control message notification for iWant messages when cache misses exceeds allowed threshold", func(t *testing.T) { - cacheMissCheckSize := 1000 - inspector, signalerCtx, cancel, distributor, rpcTracker, _, _, _ := inspectorFixture(t, func(params *validation.InspectorParams) { - params.Config.CacheMissCheckSize = cacheMissCheckSize - // set high cache miss threshold to ensure we only disseminate notification when it is exceeded - params.Config.IWantRPCInspectionConfig.CacheMissThreshold = .9 - }) - // oracle must be set even though iWant messages do not have topic IDs - inspectMsgRpc := unittest.P2PRPCFixture(unittest.WithIWants(unittest.P2PRPCIWantFixtures(cacheMissCheckSize+1, 100)...)) +// TestIHaveInspection_InvalidTopic ensures inspector disseminates an invalid control message notification for +// iHave messages when the topic is invalid. +func TestIHaveInspection_InvalidTopic(t *testing.T) { + inspector, signalerCtx, cancel, distributor, rpcTracker, sporkID, _, topicProviderOracle := inspectorFixture(t) + // create unknown topic + unknownTopic, malformedTopic, invalidSporkIDTopic := invalidTopics(t, sporkID) + // avoid unknown topics errors + topicProviderOracle.UpdateTopics([]string{unknownTopic, malformedTopic, invalidSporkIDTopic}) + unknownTopicIhave := unittest.P2PRPCIHaveFixture(&unknownTopic, unittest.IdentifierListFixture(5).Strings()...) + malformedTopicIhave := unittest.P2PRPCIHaveFixture(&malformedTopic, unittest.IdentifierListFixture(5).Strings()...) + invalidSporkIDTopicIhave := unittest.P2PRPCIHaveFixture(&invalidSporkIDTopic, unittest.IdentifierListFixture(5).Strings()...) - from := unittest.PeerIdFixture(t) - checkNotification := checkNotificationFunc(t, from, p2pmsg.CtrlMsgIWant, validation.IsIWantCacheMissThresholdErr, p2p.CtrlMsgNonClusterTopicType) - distributor.On("Distribute", mock.AnythingOfType("*p2p.InvCtrlMsgNotif")).Return(nil).Once().Run(checkNotification) - rpcTracker.On("LastHighestIHaveRPCSize").Return(int64(100)).Maybe() - // return false each time to eventually force a notification to be disseminated when the cache miss count finally exceeds the 90% threshold - rpcTracker.On("WasIHaveRPCSent", mock.AnythingOfType("string")).Return(false).Run(func(args mock.Arguments) { - id, ok := args[0].(string) - require.True(t, ok) - found := false - for _, iwant := range inspectMsgRpc.GetControl().GetIwant() { - for _, messageID := range iwant.GetMessageIDs() { - if id == messageID { - found = true - } + unknownTopicRpc := unittest.P2PRPCFixture(unittest.WithIHaves(unknownTopicIhave)) + malformedTopicRpc := unittest.P2PRPCFixture(unittest.WithIHaves(malformedTopicIhave)) + invalidSporkIDTopicRpc := unittest.P2PRPCFixture(unittest.WithIHaves(invalidSporkIDTopicIhave)) + + from := unittest.PeerIdFixture(t) + checkNotification := checkNotificationFunc(t, from, p2pmsg.CtrlMsgIHave, channels.IsInvalidTopicErr, p2p.CtrlMsgNonClusterTopicType) + rpcTracker.On("LastHighestIHaveRPCSize").Return(int64(100)).Maybe() + + distributor.On("Distribute", mock.AnythingOfType("*p2p.InvCtrlMsgNotif")).Return(nil).Times(3).Run(checkNotification) + inspector.Start(signalerCtx) + unittest.RequireComponentsReadyBefore(t, 1*time.Second, inspector) + + require.NoError(t, inspector.Inspect(from, unknownTopicRpc)) + require.NoError(t, inspector.Inspect(from, malformedTopicRpc)) + require.NoError(t, inspector.Inspect(from, invalidSporkIDTopicRpc)) + // sleep for 1 second to ensure rpc's is processed + time.Sleep(time.Second) + cancel() + unittest.RequireCloseBefore(t, inspector.Done(), 5*time.Second, "inspector did not stop") +} + +// TestIHaveInspection_DuplicateTopicIds_BelowThreshold ensures inspector does not disseminate an invalid control message notification for +// iHave messages when duplicate topic ids are below allowed threshold. +func TestIHaveInspection_DuplicateTopicIds_BelowThreshold(t *testing.T) { + inspector, signalerCtx, cancel, distributor, rpcTracker, sporkID, _, topicProviderOracle := inspectorFixture(t) + validTopic := fmt.Sprintf("%s/%s", channels.PushBlocks.String(), sporkID) + // avoid unknown topics errors + topicProviderOracle.UpdateTopics([]string{validTopic}) + + cfg, err := config.DefaultConfig() + require.NoError(t, err) + validTopicIHave := unittest.P2PRPCIHaveFixture(&validTopic, unittest.IdentifierListFixture(5).Strings()...) + ihaves := []*pubsub_pb.ControlIHave{validTopicIHave} + // duplicate the valid topic id on other iHave messages but with different message ids + for i := 0; i < cfg.NetworkConfig.GossipSub.RpcInspector.Validation.IHave.DuplicateTopicIdThreshold-1; i++ { + ihaves = append(ihaves, unittest.P2PRPCIHaveFixture(&validTopic, unittest.IdentifierListFixture(5).Strings()...)) + } + // creates an RPC with duplicate topic ids but different message ids + duplicateMsgIDRpc := unittest.P2PRPCFixture(unittest.WithIHaves(ihaves...)) + from := unittest.PeerIdFixture(t) + + rpcTracker.On("LastHighestIHaveRPCSize").Return(int64(100)).Maybe() + + // no notification should be disseminated for valid messages as long as the number of duplicates is below the threshold + distributor.AssertNotCalled(t, "Distribute", mock.AnythingOfType("*p2p.InvCtrlMsgNotif")) + inspector.Start(signalerCtx) + unittest.RequireComponentsReadyBefore(t, 1*time.Second, inspector) + + require.NoError(t, inspector.Inspect(from, duplicateMsgIDRpc)) + // TODO: this sleeps should be replaced with a queue size checker. + time.Sleep(time.Second) + cancel() + unittest.RequireCloseBefore(t, inspector.Done(), 5*time.Second, "inspector did not stop") +} + +// TestIHaveInspection_DuplicateTopicIds_AboveThreshold ensures inspector disseminate an invalid control message notification for +// iHave messages when duplicate topic ids are above allowed threshold. +func TestIHaveInspection_DuplicateTopicIds_AboveThreshold(t *testing.T) { + inspector, signalerCtx, cancel, distributor, rpcTracker, sporkID, _, topicProviderOracle := inspectorFixture(t) + validTopic := fmt.Sprintf("%s/%s", channels.PushBlocks.String(), sporkID) + // avoid unknown topics errors + topicProviderOracle.UpdateTopics([]string{validTopic}) + + cfg, err := config.DefaultConfig() + require.NoError(t, err) + validTopicIHave := unittest.P2PRPCIHaveFixture(&validTopic, unittest.IdentifierListFixture(5).Strings()...) + ihaves := []*pubsub_pb.ControlIHave{validTopicIHave} + // duplicate the valid topic id on other iHave messages but with different message ids up to the threshold + for i := 0; i < cfg.NetworkConfig.GossipSub.RpcInspector.Validation.IHave.DuplicateTopicIdThreshold+2; i++ { + ihaves = append(ihaves, unittest.P2PRPCIHaveFixture(&validTopic, unittest.IdentifierListFixture(5).Strings()...)) + } + // creates an RPC with duplicate topic ids but different message ids + duplicateMsgIDRpc := unittest.P2PRPCFixture(unittest.WithIHaves(ihaves...)) + from := unittest.PeerIdFixture(t) + rpcTracker.On("LastHighestIHaveRPCSize").Return(int64(100)).Maybe() + + // one notification should be disseminated for invalid messages when the number of duplicates exceeds the threshold + checkNotification := checkNotificationFunc(t, from, p2pmsg.CtrlMsgIHave, validation.IsDuplicateTopicErr, p2p.CtrlMsgNonClusterTopicType) + distributor.On("Distribute", mock.AnythingOfType("*p2p.InvCtrlMsgNotif")).Return(nil).Once().Run(checkNotification) + inspector.Start(signalerCtx) + unittest.RequireComponentsReadyBefore(t, 1*time.Second, inspector) + + require.NoError(t, inspector.Inspect(from, duplicateMsgIDRpc)) + // TODO: this sleeps should be replaced with a queue size checker. + time.Sleep(time.Second) + cancel() + unittest.RequireCloseBefore(t, inspector.Done(), 5*time.Second, "inspector did not stop") +} + +// TestIHaveInspection_DuplicateMessageIds_BelowThreshold ensures inspector does not disseminate an invalid control message notification for +// iHave messages when duplicate message ids are below allowed threshold. +func TestIHaveInspection_DuplicateMessageIds_BelowThreshold(t *testing.T) { + inspector, signalerCtx, cancel, distributor, rpcTracker, sporkID, _, topicProviderOracle := inspectorFixture(t) + validTopic := fmt.Sprintf("%s/%s", channels.PushBlocks.String(), sporkID) + // avoid unknown topics errors + topicProviderOracle.UpdateTopics([]string{validTopic}) + duplicateMsgID := unittest.IdentifierFixture() + + cfg, err := config.DefaultConfig() + require.NoError(t, err) + msgIds := flow.IdentifierList{} + // includes as many duplicates as allowed by the threshold + for i := 0; i < cfg.NetworkConfig.GossipSub.RpcInspector.Validation.IHave.DuplicateMessageIdThreshold; i++ { + msgIds = append(msgIds, duplicateMsgID) + } + duplicateMsgIDIHave := unittest.P2PRPCIHaveFixture(&validTopic, append(msgIds, unittest.IdentifierListFixture(5)...).Strings()...) + duplicateMsgIDRpc := unittest.P2PRPCFixture(unittest.WithIHaves(duplicateMsgIDIHave)) + from := unittest.PeerIdFixture(t) + rpcTracker.On("LastHighestIHaveRPCSize").Return(int64(100)).Maybe() + + // no notification should be disseminated for valid messages as long as the number of duplicates is below the threshold + distributor.AssertNotCalled(t, "Distribute", mock.AnythingOfType("*p2p.InvCtrlMsgNotif")) + inspector.Start(signalerCtx) + unittest.RequireComponentsReadyBefore(t, 1*time.Second, inspector) + + require.NoError(t, inspector.Inspect(from, duplicateMsgIDRpc)) + // TODO: this sleeps should be replaced with a queue size checker. + time.Sleep(time.Second) + cancel() + unittest.RequireCloseBefore(t, inspector.Done(), 5*time.Second, "inspector did not stop") +} + +// TestIHaveInspection_DuplicateMessageIds_AboveThreshold ensures inspector disseminates an invalid control message notification for +// iHave messages when duplicate message ids are above allowed threshold. +func TestIHaveInspection_DuplicateMessageIds_AboveThreshold(t *testing.T) { + inspector, signalerCtx, cancel, distributor, rpcTracker, sporkID, _, topicProviderOracle := inspectorFixture(t) + validTopic := fmt.Sprintf("%s/%s", channels.PushBlocks.String(), sporkID) + // avoid unknown topics errors + topicProviderOracle.UpdateTopics([]string{validTopic}) + duplicateMsgID := unittest.IdentifierFixture() + + cfg, err := config.DefaultConfig() + require.NoError(t, err) + msgIds := flow.IdentifierList{} + // includes as many duplicates as beyond the threshold + for i := 0; i < cfg.NetworkConfig.GossipSub.RpcInspector.Validation.IHave.DuplicateMessageIdThreshold+2; i++ { + msgIds = append(msgIds, duplicateMsgID) + } + duplicateMsgIDIHave := unittest.P2PRPCIHaveFixture(&validTopic, append(msgIds, unittest.IdentifierListFixture(5)...).Strings()...) + duplicateMsgIDRpc := unittest.P2PRPCFixture(unittest.WithIHaves(duplicateMsgIDIHave)) + from := unittest.PeerIdFixture(t) + rpcTracker.On("LastHighestIHaveRPCSize").Return(int64(100)).Maybe() + + // one notification should be disseminated for invalid messages when the number of duplicates exceeds the threshold + checkNotification := checkNotificationFunc(t, from, p2pmsg.CtrlMsgIHave, validation.IsDuplicateMessageIDErr, p2p.CtrlMsgNonClusterTopicType) + distributor.On("Distribute", mock.AnythingOfType("*p2p.InvCtrlMsgNotif")).Return(nil).Once().Run(checkNotification) + inspector.Start(signalerCtx) + unittest.RequireComponentsReadyBefore(t, 1*time.Second, inspector) + + require.NoError(t, inspector.Inspect(from, duplicateMsgIDRpc)) + // TODO: this sleeps should be replaced with a queue size checker. + time.Sleep(time.Second) + cancel() + unittest.RequireCloseBefore(t, inspector.Done(), 5*time.Second, "inspector did not stop") +} + +// TestIWantInspection_DuplicateMessageIds_BelowThreshold ensures inspector does not disseminate an invalid control message notification for +// iWant messages when duplicate message ids are below allowed threshold. +func TestIWantInspection_DuplicateMessageIds_BelowThreshold(t *testing.T) { + inspector, signalerCtx, cancel, distributor, rpcTracker, _, _, _ := inspectorFixture(t) + // oracle must be set even though iWant messages do not have topic IDs + duplicateMsgID := unittest.IdentifierFixture() + duplicates := flow.IdentifierList{} + cfg, err := config.DefaultConfig() + require.NoError(t, err) + // includes as many duplicates as allowed by the threshold + for i := 0; i < int(cfg.NetworkConfig.GossipSub.RpcInspector.Validation.IWant.DuplicateMsgIdThreshold)-2; i++ { + duplicates = append(duplicates, duplicateMsgID) + } + msgIds := append(duplicates, unittest.IdentifierListFixture(5)...).Strings() + duplicateMsgIDIWant := unittest.P2PRPCIWantFixture(msgIds...) + + duplicateMsgIDRpc := unittest.P2PRPCFixture(unittest.WithIWants(duplicateMsgIDIWant)) + + from := unittest.PeerIdFixture(t) + distributor.AssertNotCalled(t, "Distribute", mock.AnythingOfType("*p2p.InvCtrlMsgNotif")) + rpcTracker.On("LastHighestIHaveRPCSize").Return(int64(100)).Maybe() + rpcTracker.On("WasIHaveRPCSent", mock.AnythingOfType("string")).Return(true).Run(func(args mock.Arguments) { + id, ok := args[0].(string) + require.True(t, ok) + require.Contains(t, msgIds, id) + }).Maybe() // if iwant message ids count are not bigger than cache miss check size, this method is not called, anyway in this test we do not care about this method. + + inspector.Start(signalerCtx) + unittest.RequireComponentsReadyBefore(t, 1*time.Second, inspector) + + require.NoError(t, inspector.Inspect(from, duplicateMsgIDRpc)) + // sleep for 1 second to ensure rpc's is processed + time.Sleep(time.Second) + cancel() + unittest.RequireCloseBefore(t, inspector.Done(), 5*time.Second, "inspector did not stop") +} + +// TestIWantInspection_DuplicateMessageIds_AboveThreshold ensures inspector disseminates invalid control message notifications for iWant messages when duplicate message ids exceeds allowed threshold. +func TestIWantInspection_DuplicateMessageIds_AboveThreshold(t *testing.T) { + inspector, signalerCtx, cancel, distributor, rpcTracker, _, _, _ := inspectorFixture(t) + // oracle must be set even though iWant messages do not have topic IDs + duplicateMsgID := unittest.IdentifierFixture() + duplicates := flow.IdentifierList{} + cfg, err := config.DefaultConfig() + require.NoError(t, err) + // includes as many duplicates as allowed by the threshold + for i := 0; i < int(cfg.NetworkConfig.GossipSub.RpcInspector.Validation.IWant.DuplicateMsgIdThreshold)+2; i++ { + duplicates = append(duplicates, duplicateMsgID) + } + msgIds := append(duplicates, unittest.IdentifierListFixture(5)...).Strings() + duplicateMsgIDIWant := unittest.P2PRPCIWantFixture(msgIds...) + + duplicateMsgIDRpc := unittest.P2PRPCFixture(unittest.WithIWants(duplicateMsgIDIWant)) + + from := unittest.PeerIdFixture(t) + checkNotification := checkNotificationFunc(t, from, p2pmsg.CtrlMsgIWant, validation.IsIWantDuplicateMsgIDThresholdErr, p2p.CtrlMsgNonClusterTopicType) + distributor.On("Distribute", mock.AnythingOfType("*p2p.InvCtrlMsgNotif")).Return(nil).Once().Run(checkNotification) + rpcTracker.On("LastHighestIHaveRPCSize").Return(int64(100)).Maybe() + rpcTracker.On("WasIHaveRPCSent", mock.AnythingOfType("string")).Return(true).Run(func(args mock.Arguments) { + id, ok := args[0].(string) + require.True(t, ok) + require.Contains(t, msgIds, id) + }).Maybe() // if iwant message ids count are not bigger than cache miss check size, this method is not called, anyway in this test we do not care about this method. + + inspector.Start(signalerCtx) + unittest.RequireComponentsReadyBefore(t, 1*time.Second, inspector) + + require.NoError(t, inspector.Inspect(from, duplicateMsgIDRpc)) + // sleep for 1 second to ensure rpc's is processed + time.Sleep(time.Second) + cancel() + unittest.RequireCloseBefore(t, inspector.Done(), 5*time.Second, "inspector did not stop") +} + +// TestIWantInspection_CacheMiss_AboveThreshold ensures inspector disseminates invalid control message notifications for iWant messages when cache misses exceeds allowed threshold. +func TestIWantInspection_CacheMiss_AboveThreshold(t *testing.T) { + inspector, signalerCtx, cancel, distributor, rpcTracker, _, _, _ := inspectorFixture(t, func(params *validation.InspectorParams) { + // set high cache miss threshold to ensure we only disseminate notification when it is exceeded + params.Config.IWant.CacheMissThreshold = 900 + }) + // 10 iwant messages, each with 100 message ids; total of 1000 message ids, which when imitated as cache misses should trigger notification dissemination. + inspectMsgRpc := unittest.P2PRPCFixture(unittest.WithIWants(unittest.P2PRPCIWantFixtures(10, 100)...)) + + from := unittest.PeerIdFixture(t) + checkNotification := checkNotificationFunc(t, from, p2pmsg.CtrlMsgIWant, validation.IsIWantCacheMissThresholdErr, p2p.CtrlMsgNonClusterTopicType) + distributor.On("Distribute", mock.AnythingOfType("*p2p.InvCtrlMsgNotif")).Return(nil).Once().Run(checkNotification) + rpcTracker.On("LastHighestIHaveRPCSize").Return(int64(100)).Maybe() + // return false each time to eventually force a notification to be disseminated when the cache miss count finally exceeds the 90% threshold + allIwantsChecked := sync.WaitGroup{} + allIwantsChecked.Add(901) // 901 message ids + rpcTracker.On("WasIHaveRPCSent", mock.AnythingOfType("string")).Return(false).Run(func(args mock.Arguments) { + defer allIwantsChecked.Done() + + id, ok := args[0].(string) + require.True(t, ok) + found := false + for _, iwant := range inspectMsgRpc.GetControl().GetIwant() { + for _, messageID := range iwant.GetMessageIDs() { + if id == messageID { + found = true } } - require.True(t, found) - }) + } + require.True(t, found) + }) - inspector.Start(signalerCtx) + inspector.Start(signalerCtx) + unittest.RequireComponentsReadyBefore(t, 1*time.Second, inspector) - require.NoError(t, inspector.Inspect(from, inspectMsgRpc)) - // sleep for 1 second to ensure rpc's is processed - time.Sleep(time.Second) - stopInspector(t, cancel, inspector) + require.NoError(t, inspector.Inspect(from, inspectMsgRpc)) + unittest.RequireReturnsBefore(t, allIwantsChecked.Wait, 1*time.Second, "all iwant messages should be checked for cache misses") + + // sleep for 1 second to ensure rpc's is processed + time.Sleep(time.Second) + cancel() + unittest.RequireCloseBefore(t, inspector.Done(), 5*time.Second, "inspector did not stop") +} + +func TestIWantInspection_CacheMiss_BelowThreshold(t *testing.T) { + inspector, signalerCtx, cancel, distributor, rpcTracker, _, _, _ := inspectorFixture(t, func(params *validation.InspectorParams) { + // set high cache miss threshold to ensure that we do not disseminate notification in this test + params.Config.IWant.CacheMissThreshold = 99 }) + // oracle must be set even though iWant messages do not have topic IDs + defer distributor.AssertNotCalled(t, "Distribute") - t.Run("inspectIWantMessages should not disseminate invalid control message notification for iWant messages when cache misses exceeds allowed threshold if cache miss check size not exceeded", - func(t *testing.T) { - inspector, signalerCtx, cancel, distributor, rpcTracker, _, _, _ := inspectorFixture(t, func(params *validation.InspectorParams) { - // if size of iwants not greater than 10 cache misses will not be checked - params.Config.CacheMissCheckSize = 10 - // set high cache miss threshold to ensure we only disseminate notification when it is exceeded - params.Config.IWantRPCInspectionConfig.CacheMissThreshold = .9 - }) - // oracle must be set even though iWant messages do not have topic IDs - defer distributor.AssertNotCalled(t, "Distribute") - - msgIds := unittest.IdentifierListFixture(100).Strings() - inspectMsgRpc := unittest.P2PRPCFixture(unittest.WithIWants(unittest.P2PRPCIWantFixture(msgIds...))) - rpcTracker.On("LastHighestIHaveRPCSize").Return(int64(100)).Maybe() - // return false each time to eventually force a notification to be disseminated when the cache miss count finally exceeds the 90% threshold - rpcTracker.On("WasIHaveRPCSent", mock.AnythingOfType("string")).Return(false).Run(func(args mock.Arguments) { - id, ok := args[0].(string) - require.True(t, ok) - require.Contains(t, msgIds, id) - }) - - from := unittest.PeerIdFixture(t) - inspector.Start(signalerCtx) + msgIds := unittest.IdentifierListFixture(98).Strings() // one less than cache miss threshold + inspectMsgRpc := unittest.P2PRPCFixture(unittest.WithIWants(unittest.P2PRPCIWantFixture(msgIds...))) + rpcTracker.On("LastHighestIHaveRPCSize").Return(int64(100)).Maybe() - require.NoError(t, inspector.Inspect(from, inspectMsgRpc)) - // sleep for 1 second to ensure rpc's is processed - time.Sleep(time.Second) - stopInspector(t, cancel, inspector) - }) + allIwantsChecked := sync.WaitGroup{} + allIwantsChecked.Add(len(msgIds)) + // returns false each time to imitate cache misses; however, since the number of cache misses is below the threshold, no notification should be disseminated. + rpcTracker.On("WasIHaveRPCSent", mock.AnythingOfType("string")).Return(false).Run(func(args mock.Arguments) { + defer allIwantsChecked.Done() + id, ok := args[0].(string) + require.True(t, ok) + require.Contains(t, msgIds, id) + }) - t.Run("inspectRpcPublishMessages should disseminate invalid control message notification when invalid pubsub messages count greater than configured RpcMessageErrorThreshold", func(t *testing.T) { - errThreshold := 500 - inspector, signalerCtx, cancel, distributor, _, sporkID, _, topicProviderOracle := inspectorFixture(t, func(params *validation.InspectorParams) { - params.Config.RpcMessageErrorThreshold = errThreshold - }) - // create unknown topic - unknownTopic := channels.Topic(fmt.Sprintf("%s/%s", unittest.IdentifierFixture(), sporkID)).String() - // create malformed topic - malformedTopic := channels.Topic("!@#$%^&**((").String() - // a topics spork ID is considered invalid if it does not match the current spork ID - invalidSporkIDTopic := channels.Topic(fmt.Sprintf("%s/%s", channels.PushBlocks, unittest.IdentifierFixture())).String() - // create 10 normal messages - pubsubMsgs := unittest.GossipSubMessageFixtures(50, fmt.Sprintf("%s/%s", channels.TestNetworkChannel, sporkID)) - // add 550 invalid messages to force notification dissemination - invalidMessageFixtures := []*pubsub_pb.Message{ - {Topic: &unknownTopic}, - {Topic: &malformedTopic}, - {Topic: &invalidSporkIDTopic}, - } - for i := 0; i < errThreshold+1; i++ { - pubsubMsgs = append(pubsubMsgs, invalidMessageFixtures[rand.Intn(len(invalidMessageFixtures))]) - } - rpc := unittest.P2PRPCFixture(unittest.WithPubsubMessages(pubsubMsgs...)) - topics := make([]string, len(pubsubMsgs)) - for i, msg := range pubsubMsgs { - topics[i] = *msg.Topic - } - // set topic oracle to return list of topics to avoid hasSubscription errors and force topic validation - topicProviderOracle.UpdateTopics(topics) - from := unittest.PeerIdFixture(t) - checkNotification := checkNotificationFunc(t, from, p2pmsg.RpcPublishMessage, validation.IsInvalidRpcPublishMessagesErr, p2p.CtrlMsgNonClusterTopicType) - distributor.On("Distribute", mock.AnythingOfType("*p2p.InvCtrlMsgNotif")).Return(nil).Once().Run(checkNotification) + from := unittest.PeerIdFixture(t) + inspector.Start(signalerCtx) + unittest.RequireComponentsReadyBefore(t, 1*time.Second, inspector) - inspector.Start(signalerCtx) + require.NoError(t, inspector.Inspect(from, inspectMsgRpc)) + unittest.RequireReturnsBefore(t, allIwantsChecked.Wait, 1*time.Second, "all iwant messages should be checked for cache misses") - require.NoError(t, inspector.Inspect(from, rpc)) - // sleep for 1 second to ensure rpc's is processed - time.Sleep(time.Second) - stopInspector(t, cancel, inspector) - }) - t.Run("inspectRpcPublishMessages should disseminate invalid control message notification when subscription missing for topic", func(t *testing.T) { - errThreshold := 500 - inspector, signalerCtx, cancel, distributor, _, sporkID, _, _ := inspectorFixture(t, func(params *validation.InspectorParams) { - params.Config.RpcMessageErrorThreshold = errThreshold - }) - pubsubMsgs := unittest.GossipSubMessageFixtures(errThreshold+1, fmt.Sprintf("%s/%s", channels.TestNetworkChannel, sporkID)) - from := unittest.PeerIdFixture(t) - rpc := unittest.P2PRPCFixture(unittest.WithPubsubMessages(pubsubMsgs...)) - checkNotification := checkNotificationFunc(t, from, p2pmsg.RpcPublishMessage, validation.IsInvalidRpcPublishMessagesErr, p2p.CtrlMsgNonClusterTopicType) - distributor.On("Distribute", mock.AnythingOfType("*p2p.InvCtrlMsgNotif")).Return(nil).Once().Run(checkNotification) - inspector.Start(signalerCtx) - require.NoError(t, inspector.Inspect(from, rpc)) - // sleep for 1 second to ensure rpc's is processed - time.Sleep(time.Second) - stopInspector(t, cancel, inspector) + // waits one more second to ensure no notification is disseminated + time.Sleep(1 * time.Second) + cancel() + unittest.RequireCloseBefore(t, inspector.Done(), 5*time.Second, "inspector did not stop") +} + +// TestControlMessageInspection_ExceedingErrThreshold ensures inspector disseminates invalid control message notifications for RPCs that exceed the configured error threshold. +func TestPublishMessageInspection_ExceedingErrThreshold(t *testing.T) { + errThreshold := 500 + inspector, signalerCtx, cancel, distributor, rpcTracker, sporkID, _, topicProviderOracle := inspectorFixture(t, func(params *validation.InspectorParams) { + params.Config.PublishMessages.ErrorThreshold = errThreshold }) + // create unknown topic + unknownTopic := channels.Topic(fmt.Sprintf("%s/%s", unittest.IdentifierFixture(), sporkID)).String() + // create malformed topic + malformedTopic := channels.Topic("!@#$%^&**((").String() + // a topics spork ID is considered invalid if it does not match the current spork ID + invalidSporkIDTopic := channels.Topic(fmt.Sprintf("%s/%s", channels.PushBlocks, unittest.IdentifierFixture())).String() + // create 10 normal messages + pubsubMsgs := unittest.GossipSubMessageFixtures(50, fmt.Sprintf("%s/%s", channels.TestNetworkChannel, sporkID)) + // add 550 invalid messages to force notification dissemination + invalidMessageFixtures := []*pubsub_pb.Message{ + {Topic: &unknownTopic}, + {Topic: &malformedTopic}, + {Topic: &invalidSporkIDTopic}, + } + for i := 0; i < errThreshold+1; i++ { + pubsubMsgs = append(pubsubMsgs, invalidMessageFixtures[rand.Intn(len(invalidMessageFixtures))]) + } + rpc := unittest.P2PRPCFixture(unittest.WithPubsubMessages(pubsubMsgs...)) + topics := make([]string, len(pubsubMsgs)) + for i, msg := range pubsubMsgs { + topics[i] = *msg.Topic + } + // set topic oracle to return list of topics to avoid hasSubscription errors and force topic validation + topicProviderOracle.UpdateTopics(topics) + from := unittest.PeerIdFixture(t) + rpcTracker.On("LastHighestIHaveRPCSize").Return(int64(100)).Maybe() - t.Run("inspectRpcPublishMessages should disseminate invalid control message notification when publish messages contain no topic", func(t *testing.T) { - errThreshold := 500 - inspector, signalerCtx, cancel, distributor, _, _, _, _ := inspectorFixture(t, func(params *validation.InspectorParams) { - // 5 invalid pubsub messages will force notification dissemination - params.Config.RpcMessageErrorThreshold = errThreshold - }) - pubsubMsgs := unittest.GossipSubMessageFixtures(errThreshold+1, "") - rpc := unittest.P2PRPCFixture(unittest.WithPubsubMessages(pubsubMsgs...)) - topics := make([]string, len(pubsubMsgs)) - for i, msg := range pubsubMsgs { - topics[i] = *msg.Topic - } - // set topic oracle to return list of topics excluding first topic sent - from := unittest.PeerIdFixture(t) - checkNotification := checkNotificationFunc(t, from, p2pmsg.RpcPublishMessage, validation.IsInvalidRpcPublishMessagesErr, p2p.CtrlMsgNonClusterTopicType) - distributor.On("Distribute", mock.AnythingOfType("*p2p.InvCtrlMsgNotif")).Return(nil).Once().Run(checkNotification) - inspector.Start(signalerCtx) - require.NoError(t, inspector.Inspect(from, rpc)) - // sleep for 1 second to ensure rpc's is processed - time.Sleep(time.Second) - stopInspector(t, cancel, inspector) + checkNotification := checkNotificationFunc(t, from, p2pmsg.RpcPublishMessage, validation.IsInvalidRpcPublishMessagesErr, p2p.CtrlMsgNonClusterTopicType) + distributor.On("Distribute", mock.AnythingOfType("*p2p.InvCtrlMsgNotif")).Return(nil).Once().Run(checkNotification) + + inspector.Start(signalerCtx) + unittest.RequireComponentsReadyBefore(t, 1*time.Second, inspector) + + require.NoError(t, inspector.Inspect(from, rpc)) + // sleep for 1 second to ensure rpc's is processed + time.Sleep(time.Second) + cancel() + unittest.RequireCloseBefore(t, inspector.Done(), 5*time.Second, "inspector did not stop") +} + +// TestControlMessageInspection_MissingSubscription ensures inspector disseminates invalid control message notifications for RPCs that the peer is not subscribed to. +func TestPublishMessageInspection_MissingSubscription(t *testing.T) { + errThreshold := 500 + inspector, signalerCtx, cancel, distributor, rpcTracker, sporkID, _, _ := inspectorFixture(t, func(params *validation.InspectorParams) { + params.Config.PublishMessages.ErrorThreshold = errThreshold }) - t.Run("inspectRpcPublishMessages should not inspect pubsub message sender on public networks", func(t *testing.T) { - inspector, signalerCtx, cancel, _, _, sporkID, idProvider, topicProviderOracle := inspectorFixture(t) - from := unittest.PeerIdFixture(t) - defer idProvider.AssertNotCalled(t, "ByPeerID", from) - topic := fmt.Sprintf("%s/%s", channels.TestNetworkChannel, sporkID) - topicProviderOracle.UpdateTopics([]string{topic}) - pubsubMsgs := unittest.GossipSubMessageFixtures(10, topic, unittest.WithFrom(from)) - rpc := unittest.P2PRPCFixture(unittest.WithPubsubMessages(pubsubMsgs...)) - inspector.Start(signalerCtx) - require.NoError(t, inspector.Inspect(from, rpc)) - // sleep for 1 second to ensure rpc's is processed - time.Sleep(time.Second) - stopInspector(t, cancel, inspector) + pubsubMsgs := unittest.GossipSubMessageFixtures(errThreshold+1, fmt.Sprintf("%s/%s", channels.TestNetworkChannel, sporkID)) + from := unittest.PeerIdFixture(t) + rpc := unittest.P2PRPCFixture(unittest.WithPubsubMessages(pubsubMsgs...)) + checkNotification := checkNotificationFunc(t, from, p2pmsg.RpcPublishMessage, validation.IsInvalidRpcPublishMessagesErr, p2p.CtrlMsgNonClusterTopicType) + rpcTracker.On("LastHighestIHaveRPCSize").Return(int64(100)).Maybe() + + distributor.On("Distribute", mock.AnythingOfType("*p2p.InvCtrlMsgNotif")).Return(nil).Once().Run(checkNotification) + inspector.Start(signalerCtx) + unittest.RequireComponentsReadyBefore(t, 1*time.Second, inspector) + + require.NoError(t, inspector.Inspect(from, rpc)) + // sleep for 1 second to ensure rpc's is processed + time.Sleep(time.Second) + cancel() + unittest.RequireCloseBefore(t, inspector.Done(), 5*time.Second, "inspector did not stop") +} + +// TestPublishMessageInspection_MissingTopic ensures inspector disseminates invalid control message notifications for published messages with missing topics. +func TestPublishMessageInspection_MissingTopic(t *testing.T) { + errThreshold := 500 + inspector, signalerCtx, cancel, distributor, rpcTracker, _, _, _ := inspectorFixture(t, func(params *validation.InspectorParams) { + // 5 invalid pubsub messages will force notification dissemination + params.Config.PublishMessages.ErrorThreshold = errThreshold }) - t.Run("inspectRpcPublishMessages should disseminate invalid control message notification when message is from unstaked peer", func(t *testing.T) { - inspector, signalerCtx, cancel, distributor, _, sporkID, idProvider, topicProviderOracle := inspectorFixture(t, func(params *validation.InspectorParams) { - // override the inspector and params, run the inspector in private mode - params.NetworkingType = network.PrivateNetwork - }) - from := unittest.PeerIdFixture(t) - topic := fmt.Sprintf("%s/%s", channels.TestNetworkChannel, sporkID) - topicProviderOracle.UpdateTopics([]string{topic}) - // default RpcMessageErrorThreshold is 500, 501 messages should trigger a notification - pubsubMsgs := unittest.GossipSubMessageFixtures(501, topic, unittest.WithFrom(from)) - idProvider.On("ByPeerID", from).Return(nil, false).Times(501) - rpc := unittest.P2PRPCFixture(unittest.WithPubsubMessages(pubsubMsgs...)) - checkNotification := checkNotificationFunc(t, from, p2pmsg.RpcPublishMessage, validation.IsInvalidRpcPublishMessagesErr, p2p.CtrlMsgNonClusterTopicType) - distributor.On("Distribute", mock.AnythingOfType("*p2p.InvCtrlMsgNotif")).Return(nil).Once().Run(checkNotification) - inspector.Start(signalerCtx) - require.NoError(t, inspector.Inspect(from, rpc)) - // sleep for 1 second to ensure rpc's is processed - time.Sleep(time.Second) - stopInspector(t, cancel, inspector) + pubsubMsgs := unittest.GossipSubMessageFixtures(errThreshold+1, "") + rpc := unittest.P2PRPCFixture(unittest.WithPubsubMessages(pubsubMsgs...)) + for _, msg := range pubsubMsgs { + msg.Topic = nil + } + from := unittest.PeerIdFixture(t) + checkNotification := checkNotificationFunc(t, from, p2pmsg.RpcPublishMessage, validation.IsInvalidRpcPublishMessagesErr, p2p.CtrlMsgNonClusterTopicType) + rpcTracker.On("LastHighestIHaveRPCSize").Return(int64(100)).Maybe() + distributor.On("Distribute", mock.AnythingOfType("*p2p.InvCtrlMsgNotif")).Return(nil).Once().Run(checkNotification) + inspector.Start(signalerCtx) + unittest.RequireComponentsReadyBefore(t, 1*time.Second, inspector) + + require.NoError(t, inspector.Inspect(from, rpc)) + // sleep for 1 second to ensure rpc's is processed + time.Sleep(time.Second) + cancel() + unittest.RequireCloseBefore(t, inspector.Done(), 5*time.Second, "inspector did not stop") +} + +// TestRpcInspectionDeactivatedOnPublicNetwork ensures inspector does not inspect RPCs on public networks. +func TestRpcInspectionDeactivatedOnPublicNetwork(t *testing.T) { + inspector, signalerCtx, cancel, _, rpcTracker, sporkID, idProvider, topicProviderOracle := inspectorFixture(t) + from := unittest.PeerIdFixture(t) + defer idProvider.AssertNotCalled(t, "ByPeerID", from) + topic := fmt.Sprintf("%s/%s", channels.TestNetworkChannel, sporkID) + topicProviderOracle.UpdateTopics([]string{topic}) + pubsubMsgs := unittest.GossipSubMessageFixtures(10, topic, unittest.WithFrom(from)) + rpc := unittest.P2PRPCFixture(unittest.WithPubsubMessages(pubsubMsgs...)) + inspector.Start(signalerCtx) + unittest.RequireComponentsReadyBefore(t, 1*time.Second, inspector) + rpcTracker.On("LastHighestIHaveRPCSize").Return(int64(100)).Maybe() + require.NoError(t, inspector.Inspect(from, rpc)) + // sleep for 1 second to ensure rpc's is processed + time.Sleep(time.Second) + cancel() + unittest.RequireCloseBefore(t, inspector.Done(), 5*time.Second, "inspector did not stop") +} + +// TestControlMessageInspection_Unstaked_From ensures inspector disseminates invalid control message notifications for published messages from unstaked peers. +func TestPublishMessageInspection_Unstaked_From(t *testing.T) { + inspector, signalerCtx, cancel, distributor, rpcTracker, sporkID, idProvider, topicProviderOracle := inspectorFixture(t, func(params *validation.InspectorParams) { + // override the inspector and params, run the inspector in private mode + params.NetworkingType = network.PrivateNetwork }) - t.Run("inspectRpcPublishMessages should disseminate invalid control message notification when message is from ejected peer", func(t *testing.T) { - inspector, signalerCtx, cancel, distributor, _, sporkID, idProvider, topicProviderOracle := inspectorFixture(t, func(params *validation.InspectorParams) { - // override the inspector and params, run the inspector in private mode - params.NetworkingType = network.PrivateNetwork - }) - from := unittest.PeerIdFixture(t) - id := unittest.IdentityFixture() - id.Ejected = true - topic := fmt.Sprintf("%s/%s", channels.TestNetworkChannel, sporkID) - topicProviderOracle.UpdateTopics([]string{topic}) - pubsubMsgs := unittest.GossipSubMessageFixtures(501, topic, unittest.WithFrom(from)) - idProvider.On("ByPeerID", from).Return(id, true).Times(501) - rpc := unittest.P2PRPCFixture(unittest.WithPubsubMessages(pubsubMsgs...)) - checkNotification := checkNotificationFunc(t, from, p2pmsg.RpcPublishMessage, validation.IsInvalidRpcPublishMessagesErr, p2p.CtrlMsgNonClusterTopicType) - distributor.On("Distribute", mock.AnythingOfType("*p2p.InvCtrlMsgNotif")).Return(nil).Once().Run(checkNotification) - inspector.Start(signalerCtx) - require.NoError(t, inspector.Inspect(from, rpc)) - // sleep for 1 second to ensure rpc's is processed - time.Sleep(time.Second) - stopInspector(t, cancel, inspector) + from := unittest.PeerIdFixture(t) + topic := fmt.Sprintf("%s/%s", channels.TestNetworkChannel, sporkID) + topicProviderOracle.UpdateTopics([]string{topic}) + // default RpcMessageErrorThreshold is 500, 501 messages should trigger a notification + pubsubMsgs := unittest.GossipSubMessageFixtures(501, topic, unittest.WithFrom(from)) + idProvider.On("ByPeerID", from).Return(nil, false).Times(501) + rpc := unittest.P2PRPCFixture(unittest.WithPubsubMessages(pubsubMsgs...)) + checkNotification := checkNotificationFunc(t, from, p2pmsg.RpcPublishMessage, validation.IsInvalidRpcPublishMessagesErr, p2p.CtrlMsgNonClusterTopicType) + rpcTracker.On("LastHighestIHaveRPCSize").Return(int64(100)).Maybe() + distributor.On("Distribute", mock.AnythingOfType("*p2p.InvCtrlMsgNotif")).Return(nil).Once().Run(checkNotification) + inspector.Start(signalerCtx) + unittest.RequireComponentsReadyBefore(t, 1*time.Second, inspector) + + require.NoError(t, inspector.Inspect(from, rpc)) + // sleep for 1 second to ensure rpc's is processed + time.Sleep(time.Second) + cancel() + unittest.RequireCloseBefore(t, inspector.Done(), 5*time.Second, "inspector did not stop") +} + +// TestControlMessageInspection_Ejected_From ensures inspector disseminates invalid control message notifications for published messages from ejected peers. +func TestPublishMessageInspection_Ejected_From(t *testing.T) { + inspector, signalerCtx, cancel, distributor, rpcTracker, sporkID, idProvider, topicProviderOracle := inspectorFixture(t, func(params *validation.InspectorParams) { + // override the inspector and params, run the inspector in private mode + params.NetworkingType = network.PrivateNetwork }) + from := unittest.PeerIdFixture(t) + id := unittest.IdentityFixture() + id.Ejected = true + topic := fmt.Sprintf("%s/%s", channels.TestNetworkChannel, sporkID) + topicProviderOracle.UpdateTopics([]string{topic}) + pubsubMsgs := unittest.GossipSubMessageFixtures(501, topic, unittest.WithFrom(from)) + idProvider.On("ByPeerID", from).Return(id, true).Times(501) + rpc := unittest.P2PRPCFixture(unittest.WithPubsubMessages(pubsubMsgs...)) + checkNotification := checkNotificationFunc(t, from, p2pmsg.RpcPublishMessage, validation.IsInvalidRpcPublishMessagesErr, p2p.CtrlMsgNonClusterTopicType) + rpcTracker.On("LastHighestIHaveRPCSize").Return(int64(100)).Maybe() + distributor.On("Distribute", mock.AnythingOfType("*p2p.InvCtrlMsgNotif")).Return(nil).Once().Run(checkNotification) + inspector.Start(signalerCtx) + unittest.RequireComponentsReadyBefore(t, 1*time.Second, inspector) + + require.NoError(t, inspector.Inspect(from, rpc)) + // sleep for 1 second to ensure rpc's is processed + time.Sleep(time.Second) + cancel() + unittest.RequireCloseBefore(t, inspector.Done(), 5*time.Second, "inspector did not stop") } // TestNewControlMsgValidationInspector_validateClusterPrefixedTopic ensures cluster prefixed topics are validated as expected. func TestNewControlMsgValidationInspector_validateClusterPrefixedTopic(t *testing.T) { t.Run("validateClusterPrefixedTopic should not return an error for valid cluster prefixed topics", func(t *testing.T) { - inspector, signalerCtx, cancel, distributor, _, sporkID, idProvider, topicProviderOracle := inspectorFixture(t) + inspector, signalerCtx, cancel, distributor, rpcTracker, sporkID, idProvider, topicProviderOracle := inspectorFixture(t) defer distributor.AssertNotCalled(t, "Distribute") clusterID := flow.ChainID(unittest.IdentifierFixture().String()) clusterPrefixedTopic := channels.Topic(fmt.Sprintf("%s/%s", channels.SyncCluster(clusterID), sporkID)).String() @@ -697,18 +1038,22 @@ func TestNewControlMsgValidationInspector_validateClusterPrefixedTopic(t *testin from := unittest.PeerIdFixture(t) idProvider.On("ByPeerID", from).Return(unittest.IdentityFixture(), true).Once() inspectMsgRpc := unittest.P2PRPCFixture(unittest.WithGrafts(unittest.P2PRPCGraftFixture(&clusterPrefixedTopic))) + rpcTracker.On("LastHighestIHaveRPCSize").Return(int64(100)).Maybe() inspector.ActiveClustersChanged(flow.ChainIDList{clusterID, flow.ChainID(unittest.IdentifierFixture().String()), flow.ChainID(unittest.IdentifierFixture().String())}) inspector.Start(signalerCtx) + unittest.RequireComponentsReadyBefore(t, 1*time.Second, inspector) + require.NoError(t, inspector.Inspect(from, inspectMsgRpc)) // sleep for 1 second to ensure rpc's is processed time.Sleep(time.Second) - stopInspector(t, cancel, inspector) + cancel() + unittest.RequireCloseBefore(t, inspector.Done(), 5*time.Second, "inspector did not stop") }) t.Run("validateClusterPrefixedTopic should not return error if cluster prefixed hard threshold not exceeded for unknown cluster ids", func(t *testing.T) { - inspector, signalerCtx, cancel, distributor, _, sporkID, idProvider, _ := inspectorFixture(t, func(params *validation.InspectorParams) { + inspector, signalerCtx, cancel, distributor, rpcTracker, sporkID, idProvider, _ := inspectorFixture(t, func(params *validation.InspectorParams) { // set hard threshold to small number , ensure that a single unknown cluster prefix id does not cause a notification to be disseminated - params.Config.ClusterPrefixHardThreshold = 2 + params.Config.ClusterPrefixedMessage.HardThreshold = 2 }) defer distributor.AssertNotCalled(t, "Distribute") clusterID := flow.ChainID(unittest.IdentifierFixture().String()) @@ -717,16 +1062,19 @@ func TestNewControlMsgValidationInspector_validateClusterPrefixedTopic(t *testin inspectMsgRpc := unittest.P2PRPCFixture(unittest.WithGrafts(unittest.P2PRPCGraftFixture(&clusterPrefixedTopic))) id := unittest.IdentityFixture() idProvider.On("ByPeerID", from).Return(id, true).Once() + rpcTracker.On("LastHighestIHaveRPCSize").Return(int64(100)).Maybe() inspector.Start(signalerCtx) + unittest.RequireComponentsReadyBefore(t, 1*time.Second, inspector) + require.NoError(t, inspector.Inspect(from, inspectMsgRpc)) // sleep for 1 second to ensure rpc's is processed time.Sleep(time.Second) - stopInspector(t, cancel, inspector) - stopInspector(t, cancel, inspector) + cancel() + unittest.RequireCloseBefore(t, inspector.Done(), 5*time.Second, "inspector did not stop") }) t.Run("validateClusterPrefixedTopic should return an error when sender is unstaked", func(t *testing.T) { - inspector, signalerCtx, cancel, distributor, _, sporkID, idProvider, topicProviderOracle := inspectorFixture(t) + inspector, signalerCtx, cancel, distributor, rpcTracker, sporkID, idProvider, topicProviderOracle := inspectorFixture(t) defer distributor.AssertNotCalled(t, "Distribute") clusterID := flow.ChainID(unittest.IdentifierFixture().String()) clusterPrefixedTopic := channels.Topic(fmt.Sprintf("%s/%s", channels.SyncCluster(clusterID), sporkID)).String() @@ -736,16 +1084,19 @@ func TestNewControlMsgValidationInspector_validateClusterPrefixedTopic(t *testin inspectMsgRpc := unittest.P2PRPCFixture(unittest.WithGrafts(unittest.P2PRPCGraftFixture(&clusterPrefixedTopic))) inspector.ActiveClustersChanged(flow.ChainIDList{flow.ChainID(unittest.IdentifierFixture().String())}) inspector.Start(signalerCtx) + unittest.RequireComponentsReadyBefore(t, 1*time.Second, inspector) + rpcTracker.On("LastHighestIHaveRPCSize").Return(int64(100)).Maybe() require.NoError(t, inspector.Inspect(from, inspectMsgRpc)) // sleep for 1 second to ensure rpc's is processed time.Sleep(time.Second) - stopInspector(t, cancel, inspector) + cancel() + unittest.RequireCloseBefore(t, inspector.Done(), 5*time.Second, "inspector did not stop") }) t.Run("validateClusterPrefixedTopic should return error if cluster prefixed hard threshold exceeded for unknown cluster ids", func(t *testing.T) { - inspector, signalerCtx, cancel, distributor, _, sporkID, idProvider, topicProviderOracle := inspectorFixture(t, func(params *validation.InspectorParams) { + inspector, signalerCtx, cancel, distributor, rpcTracker, sporkID, idProvider, topicProviderOracle := inspectorFixture(t, func(params *validation.InspectorParams) { // the 11th unknown cluster ID error should cause an error - params.Config.ClusterPrefixHardThreshold = 10 + params.Config.ClusterPrefixedMessage.HardThreshold = 10 }) clusterID := flow.ChainID(unittest.IdentifierFixture().String()) clusterPrefixedTopic := channels.Topic(fmt.Sprintf("%s/%s", channels.SyncCluster(clusterID), sporkID)).String() @@ -756,20 +1107,24 @@ func TestNewControlMsgValidationInspector_validateClusterPrefixedTopic(t *testin checkNotification := checkNotificationFunc(t, from, p2pmsg.CtrlMsgGraft, channels.IsUnknownClusterIDErr, p2p.CtrlMsgTopicTypeClusterPrefixed) inspectMsgRpc := unittest.P2PRPCFixture(unittest.WithGrafts(unittest.P2PRPCGraftFixture(&clusterPrefixedTopic))) inspector.ActiveClustersChanged(flow.ChainIDList{flow.ChainID(unittest.IdentifierFixture().String())}) + rpcTracker.On("LastHighestIHaveRPCSize").Return(int64(100)).Maybe() distributor.On("Distribute", mock.AnythingOfType("*p2p.InvCtrlMsgNotif")).Return(nil).Once().Run(checkNotification) inspector.Start(signalerCtx) + unittest.RequireComponentsReadyBefore(t, 1*time.Second, inspector) + for i := 0; i < 11; i++ { require.NoError(t, inspector.Inspect(from, inspectMsgRpc)) } // sleep for 1 second to ensure rpc's is processed time.Sleep(time.Second) - stopInspector(t, cancel, inspector) + cancel() + unittest.RequireCloseBefore(t, inspector.Done(), 5*time.Second, "inspector did not stop") }) } // TestControlMessageValidationInspector_ActiveClustersChanged validates the expected update of the active cluster IDs list. func TestControlMessageValidationInspector_ActiveClustersChanged(t *testing.T) { - inspector, signalerCtx, cancel, distributor, _, sporkID, idProvider, _ := inspectorFixture(t) + inspector, signalerCtx, cancel, distributor, rpcTracker, sporkID, idProvider, _ := inspectorFixture(t) defer distributor.AssertNotCalled(t, "Distribute") identity := unittest.IdentityFixture() idProvider.On("ByPeerID", mock.AnythingOfType("peer.ID")).Return(identity, true).Times(5) @@ -779,6 +1134,8 @@ func TestControlMessageValidationInspector_ActiveClustersChanged(t *testing.T) { } inspector.ActiveClustersChanged(activeClusterIds) inspector.Start(signalerCtx) + unittest.RequireComponentsReadyBefore(t, 1*time.Second, inspector) + rpcTracker.On("LastHighestIHaveRPCSize").Return(int64(100)).Maybe() from := unittest.PeerIdFixture(t) for _, id := range activeClusterIds { topic := channels.Topic(fmt.Sprintf("%s/%s", channels.SyncCluster(id), sporkID)).String() @@ -787,7 +1144,207 @@ func TestControlMessageValidationInspector_ActiveClustersChanged(t *testing.T) { } // sleep for 1 second to ensure rpc's is processed time.Sleep(time.Second) - stopInspector(t, cancel, inspector) + cancel() + unittest.RequireCloseBefore(t, inspector.Done(), 5*time.Second, "inspector did not stop") +} + +// TestControlMessageValidationInspector_TruncationConfigToggle ensures that rpc's are not truncated when truncation is disabled through configs. +func TestControlMessageValidationInspector_TruncationConfigToggle(t *testing.T) { + t.Run("should not perform truncation when disabled is set to true", func(t *testing.T) { + numOfMsgs := 5000 + + // we expected a single warning for the entire RPC + expectedWarningLogs := int64(1) + expectedLogStrs := map[string]struct{}{validation.RPCTruncationDisabledWarning: {}} + logCounter := atomic.NewInt64(0) + logger := hookedLogger(logCounter, zerolog.TraceLevel, expectedLogStrs) + inspector, signalerCtx, cancel, distributor, rpcTracker, _, _, _ := inspectorFixture(t, func(params *validation.InspectorParams) { + params.Config.GraftPrune.MessageCountThreshold = numOfMsgs + params.Logger = logger + // disable truncation for all control message types + params.Config.InspectionProcess.Truncate.Disabled = true + }) + + // topic validation is ignored set any topic oracle + distributor.On("Distribute", mock.AnythingOfType("*p2p.InvCtrlMsgNotif")).Return(nil).Maybe() + rpcTracker.On("LastHighestIHaveRPCSize").Return(int64(100)).Maybe() + rpcTracker.On("WasIHaveRPCSent", mock.AnythingOfType("string")).Return(true).Maybe() + inspector.Start(signalerCtx) + + rpc := unittest.P2PRPCFixture( + unittest.WithGrafts(unittest.P2PRPCGraftFixtures(unittest.IdentifierListFixture(numOfMsgs).Strings()...)...), + unittest.WithPrunes(unittest.P2PRPCPruneFixtures(unittest.IdentifierListFixture(numOfMsgs).Strings()...)...), + unittest.WithIHaves(unittest.P2PRPCIHaveFixtures(numOfMsgs, unittest.IdentifierListFixture(numOfMsgs).Strings()...)...), + unittest.WithIWants(unittest.P2PRPCIWantFixtures(numOfMsgs, numOfMsgs)...), + ) + + from := unittest.PeerIdFixture(t) + require.NoError(t, inspector.Inspect(from, rpc)) + + require.Eventually(t, func() bool { + return logCounter.Load() == expectedWarningLogs + }, time.Second, 500*time.Millisecond) + + // ensure truncation not performed + require.Len(t, rpc.GetControl().GetGraft(), numOfMsgs) + require.Len(t, rpc.GetControl().GetPrune(), numOfMsgs) + require.Len(t, rpc.GetControl().GetIhave(), numOfMsgs) + ensureMessageIdsLen(t, p2pmsg.CtrlMsgIHave, rpc, numOfMsgs) + require.Len(t, rpc.GetControl().GetIwant(), numOfMsgs) + ensureMessageIdsLen(t, p2pmsg.CtrlMsgIWant, rpc, numOfMsgs) + + time.Sleep(time.Second) + cancel() + unittest.RequireCloseBefore(t, inspector.Done(), 5*time.Second, "inspector did not stop") + }) + + t.Run("should not perform truncation when disabled for each individual control message type directly", func(t *testing.T) { + numOfMsgs := 5000 + + expectedLogStrs := map[string]struct{}{ + validation.GraftTruncationDisabledWarning: {}, + validation.PruneTruncationDisabledWarning: {}, + validation.IHaveTruncationDisabledWarning: {}, + validation.IHaveMessageIDTruncationDisabledWarning: {}, + validation.IWantTruncationDisabledWarning: {}, + validation.IWantMessageIDTruncationDisabledWarning: {}, + } + logCounter := atomic.NewInt64(0) + logger := hookedLogger(logCounter, zerolog.TraceLevel, expectedLogStrs) + inspector, signalerCtx, cancel, distributor, rpcTracker, _, _, _ := inspectorFixture(t, func(params *validation.InspectorParams) { + params.Config.GraftPrune.MessageCountThreshold = numOfMsgs + params.Logger = logger + // disable truncation for all control message types individually + params.Config.InspectionProcess.Truncate.EnableGraft = false + params.Config.InspectionProcess.Truncate.EnablePrune = false + params.Config.InspectionProcess.Truncate.EnableIHave = false + params.Config.InspectionProcess.Truncate.EnableIHaveMessageIds = false + params.Config.InspectionProcess.Truncate.EnableIWant = false + params.Config.InspectionProcess.Truncate.EnableIWantMessageIds = false + }) + + // topic validation is ignored set any topic oracle + distributor.On("Distribute", mock.AnythingOfType("*p2p.InvCtrlMsgNotif")).Return(nil).Maybe() + rpcTracker.On("LastHighestIHaveRPCSize").Return(int64(100)).Maybe() + rpcTracker.On("WasIHaveRPCSent", mock.AnythingOfType("string")).Return(true).Maybe() + inspector.Start(signalerCtx) + + rpc := unittest.P2PRPCFixture( + unittest.WithGrafts(unittest.P2PRPCGraftFixtures(unittest.IdentifierListFixture(numOfMsgs).Strings()...)...), + unittest.WithPrunes(unittest.P2PRPCPruneFixtures(unittest.IdentifierListFixture(numOfMsgs).Strings()...)...), + unittest.WithIHaves(unittest.P2PRPCIHaveFixtures(numOfMsgs, unittest.IdentifierListFixture(numOfMsgs).Strings()...)...), + unittest.WithIWants(unittest.P2PRPCIWantFixtures(numOfMsgs, numOfMsgs)...), + ) + + from := unittest.PeerIdFixture(t) + require.NoError(t, inspector.Inspect(from, rpc)) + + require.Eventually(t, func() bool { + return logCounter.Load() == int64(len(expectedLogStrs)) + }, time.Second, 500*time.Millisecond) + + // ensure truncation not performed + require.Len(t, rpc.GetControl().GetGraft(), numOfMsgs) + require.Len(t, rpc.GetControl().GetPrune(), numOfMsgs) + require.Len(t, rpc.GetControl().GetIhave(), numOfMsgs) + ensureMessageIdsLen(t, p2pmsg.CtrlMsgIHave, rpc, numOfMsgs) + require.Len(t, rpc.GetControl().GetIwant(), numOfMsgs) + ensureMessageIdsLen(t, p2pmsg.CtrlMsgIWant, rpc, numOfMsgs) + + time.Sleep(time.Second) + cancel() + unittest.RequireCloseBefore(t, inspector.Done(), 5*time.Second, "inspector did not stop") + }) +} + +// TestControlMessageValidationInspector_InspectionConfigToggle ensures that rpc's are not inspected when inspection is disabled through configs. +func TestControlMessageValidationInspector_InspectionConfigToggle(t *testing.T) { + t.Run("should not perform inspection when disabled is set to true", func(t *testing.T) { + numOfMsgs := 5000 + + // we expected a single warning for the entire RPC + expectedWarningLogs := int64(1) + expectedLogStrs := map[string]struct{}{validation.RPCInspectionDisabledWarning: {}} + logCounter := atomic.NewInt64(0) + logger := hookedLogger(logCounter, zerolog.TraceLevel, expectedLogStrs) + inspector, signalerCtx, cancel, distributor, rpcTracker, _, _, _ := inspectorFixture(t, func(params *validation.InspectorParams) { + params.Logger = logger + // disable inspector for all control message types + params.Config.InspectionProcess.Inspect.Disabled = true + }) + + // distribute should never be called when inspection is disabled + defer distributor.AssertNotCalled(t, "Distribute") + rpcTracker.On("LastHighestIHaveRPCSize").Return(int64(100)).Maybe() + rpcTracker.On("WasIHaveRPCSent", mock.AnythingOfType("string")).Return(true).Maybe() + inspector.Start(signalerCtx) + + rpc := unittest.P2PRPCFixture( + unittest.WithGrafts(unittest.P2PRPCGraftFixtures(unittest.IdentifierListFixture(numOfMsgs).Strings()...)...), + unittest.WithPrunes(unittest.P2PRPCPruneFixtures(unittest.IdentifierListFixture(numOfMsgs).Strings()...)...), + unittest.WithIHaves(unittest.P2PRPCIHaveFixtures(numOfMsgs, unittest.IdentifierListFixture(numOfMsgs).Strings()...)...), + unittest.WithIWants(unittest.P2PRPCIWantFixtures(numOfMsgs, numOfMsgs)...), + ) + + from := unittest.PeerIdFixture(t) + require.NoError(t, inspector.Inspect(from, rpc)) + + require.Eventually(t, func() bool { + return logCounter.Load() == expectedWarningLogs + }, time.Second, 500*time.Millisecond) + time.Sleep(time.Second) + cancel() + unittest.RequireCloseBefore(t, inspector.Done(), 5*time.Second, "inspector did not stop") + }) + + t.Run("should not perform inspection when disabled for each individual control message type directly", func(t *testing.T) { + numOfMsgs := 5000 + + expectedLogStrs := map[string]struct{}{ + validation.GraftInspectionDisabledWarning: {}, + validation.PruneInspectionDisabledWarning: {}, + validation.IHaveInspectionDisabledWarning: {}, + validation.IWantInspectionDisabledWarning: {}, + validation.PublishInspectionDisabledWarning: {}, + } + logCounter := atomic.NewInt64(0) + logger := hookedLogger(logCounter, zerolog.TraceLevel, expectedLogStrs) + inspector, signalerCtx, cancel, distributor, rpcTracker, _, _, _ := inspectorFixture(t, func(params *validation.InspectorParams) { + params.Config.GraftPrune.MessageCountThreshold = numOfMsgs + params.Logger = logger + // disable inspection for all control message types individually + params.Config.InspectionProcess.Inspect.EnableGraft = false + params.Config.InspectionProcess.Inspect.EnablePrune = false + params.Config.InspectionProcess.Inspect.EnableIHave = false + params.Config.InspectionProcess.Inspect.EnableIWant = false + params.Config.InspectionProcess.Inspect.EnablePublish = false + }) + + // distribute should never be called when inspection is disabled + defer distributor.AssertNotCalled(t, "Distribute") + rpcTracker.On("LastHighestIHaveRPCSize").Return(int64(100)).Maybe() + rpcTracker.On("WasIHaveRPCSent", mock.AnythingOfType("string")).Return(true).Maybe() + inspector.Start(signalerCtx) + + rpc := unittest.P2PRPCFixture( + unittest.WithGrafts(unittest.P2PRPCGraftFixtures(unittest.IdentifierListFixture(numOfMsgs).Strings()...)...), + unittest.WithPrunes(unittest.P2PRPCPruneFixtures(unittest.IdentifierListFixture(numOfMsgs).Strings()...)...), + unittest.WithIHaves(unittest.P2PRPCIHaveFixtures(numOfMsgs, unittest.IdentifierListFixture(numOfMsgs).Strings()...)...), + unittest.WithIWants(unittest.P2PRPCIWantFixtures(numOfMsgs, numOfMsgs)...), + unittest.WithPubsubMessages(unittest.GossipSubMessageFixtures(numOfMsgs, unittest.RandomStringFixture(t, 100), unittest.WithFrom(unittest.PeerIdFixture(t)))...), + ) + + from := unittest.PeerIdFixture(t) + require.NoError(t, inspector.Inspect(from, rpc)) + + require.Eventually(t, func() bool { + return logCounter.Load() == int64(len(expectedLogStrs)) + }, time.Second, 500*time.Millisecond) + + time.Sleep(time.Second) + cancel() + unittest.RequireCloseBefore(t, inspector.Done(), 5*time.Second, "inspector did not stop") + }) } // invalidTopics returns 3 invalid topics. @@ -839,7 +1396,7 @@ func inspectorFixture(t *testing.T, opts ...func(params *validation.InspectorPar params := &validation.InspectorParams{ Logger: unittest.Logger(), SporkID: sporkID, - Config: &flowConfig.NetworkConfig.GossipSubRPCValidationInspectorConfigs, + Config: &flowConfig.NetworkConfig.GossipSub.RpcInspector.Validation, Distributor: distributor, IdProvider: idProvider, HeroCacheMetricsFactory: metrics.NewNoopHeroCacheMetricsFactory(), @@ -860,11 +1417,28 @@ func inspectorFixture(t *testing.T, opts ...func(params *validation.InspectorPar return validationInspector, signalerCtx, cancel, distributor, rpcTracker, sporkID, idProvider, topicProviderOracle } -func stopInspector(t *testing.T, cancel context.CancelFunc, inspector *validation.ControlMsgValidationInspector) { - cancel() - unittest.RequireCloseBefore(t, inspector.Done(), 5*time.Second, "inspector did not stop") +// utility function to track the number of logs expected logs for the expected log level. +func hookedLogger(counter *atomic.Int64, expectedLogLevel zerolog.Level, expectedLogs map[string]struct{}) zerolog.Logger { + hook := zerolog.HookFunc(func(e *zerolog.Event, level zerolog.Level, message string) { + if _, ok := expectedLogs[message]; ok && level == expectedLogLevel { + counter.Inc() + } + }) + return zerolog.New(os.Stdout).Level(expectedLogLevel).Hook(hook) } -func defaultTopicOracle() []string { - return []string{} +// ensureMessageIdsLen ensures RPC IHave and IWant message ids are the expected len. +func ensureMessageIdsLen(t *testing.T, msgType p2pmsg.ControlMessageType, rpc *pubsub.RPC, expectedLen int) { + switch msgType { + case p2pmsg.CtrlMsgIHave: + for _, ihave := range rpc.GetControl().GetIhave() { + require.Len(t, ihave.GetMessageIDs(), expectedLen) + } + case p2pmsg.CtrlMsgIWant: + for _, iwant := range rpc.GetControl().GetIwant() { + require.Len(t, iwant.GetMessageIDs(), expectedLen) + } + default: + require.Fail(t, "control message type provided does not contain message ids expected ihave or iwant") + } } diff --git a/network/p2p/inspector/validation/errors.go b/network/p2p/inspector/validation/errors.go index 99d4dad2b61..bb73f9cba9b 100644 --- a/network/p2p/inspector/validation/errors.go +++ b/network/p2p/inspector/validation/errors.go @@ -12,15 +12,15 @@ import ( type IWantDuplicateMsgIDThresholdErr struct { duplicates int sampleSize uint - threshold float64 + threshold int } func (e IWantDuplicateMsgIDThresholdErr) Error() string { - return fmt.Sprintf("%d/%d iWant duplicate message ids exceeds the allowed threshold: %f", e.duplicates, e.sampleSize, e.threshold) + return fmt.Sprintf("%d/%d iWant duplicate message ids exceeds the allowed threshold: %d", e.duplicates, e.sampleSize, e.threshold) } // NewIWantDuplicateMsgIDThresholdErr returns a new IWantDuplicateMsgIDThresholdErr. -func NewIWantDuplicateMsgIDThresholdErr(duplicates int, sampleSize uint, threshold float64) IWantDuplicateMsgIDThresholdErr { +func NewIWantDuplicateMsgIDThresholdErr(duplicates int, sampleSize uint, threshold int) IWantDuplicateMsgIDThresholdErr { return IWantDuplicateMsgIDThresholdErr{duplicates, sampleSize, threshold} } @@ -34,15 +34,15 @@ func IsIWantDuplicateMsgIDThresholdErr(err error) bool { type IWantCacheMissThresholdErr struct { cacheMissCount int // total iwant cache misses sampleSize uint - threshold float64 + threshold int } func (e IWantCacheMissThresholdErr) Error() string { - return fmt.Sprintf("%d/%d iWant cache misses exceeds the allowed threshold: %f", e.cacheMissCount, e.sampleSize, e.threshold) + return fmt.Sprintf("%d/%d iWant cache misses exceeds the allowed threshold: %d", e.cacheMissCount, e.sampleSize, e.threshold) } // NewIWantCacheMissThresholdErr returns a new IWantCacheMissThresholdErr. -func NewIWantCacheMissThresholdErr(cacheMissCount int, sampleSize uint, threshold float64) IWantCacheMissThresholdErr { +func NewIWantCacheMissThresholdErr(cacheMissCount int, sampleSize uint, threshold int) IWantCacheMissThresholdErr { return IWantCacheMissThresholdErr{cacheMissCount, sampleSize, threshold} } @@ -54,17 +54,28 @@ func IsIWantCacheMissThresholdErr(err error) bool { // DuplicateTopicErr error that indicates a duplicate has been detected. This can be duplicate topic or message ID tracking. type DuplicateTopicErr struct { - topic string - msgType p2pmsg.ControlMessageType + topic string // the topic that is duplicated + count int // the number of times the topic has been duplicated + msgType p2pmsg.ControlMessageType // the control message type that the topic was found in } func (e DuplicateTopicErr) Error() string { - return fmt.Sprintf("duplicate topic foud in %s control message type: %s", e.msgType, e.topic) + return fmt.Sprintf("duplicate topic found in %s control message type: %s", e.msgType, e.topic) } // NewDuplicateTopicErr returns a new DuplicateTopicErr. -func NewDuplicateTopicErr(topic string, msgType p2pmsg.ControlMessageType) DuplicateTopicErr { - return DuplicateTopicErr{topic, msgType} +// Args: +// +// topic: the topic that is duplicated +// count: the number of times the topic has been duplicated +// msgType: the control message type that the topic was found in +// +// Returns: +// +// A new DuplicateTopicErr. +func NewDuplicateTopicErr(topic string, count int, msgType p2pmsg.ControlMessageType) DuplicateTopicErr { + + return DuplicateTopicErr{topic, count, msgType} } // IsDuplicateTopicErr returns true if an error is DuplicateTopicErr. @@ -75,8 +86,9 @@ func IsDuplicateTopicErr(err error) bool { // DuplicateMessageIDErr error that indicates a duplicate message ID has been detected in a IHAVE or IWANT control message. type DuplicateMessageIDErr struct { - id string - msgType p2pmsg.ControlMessageType + id string // id of the message that is duplicated + count int // the number of times the message ID has been duplicated + msgType p2pmsg.ControlMessageType // the control message type that the message ID was found in } func (e DuplicateMessageIDErr) Error() string { @@ -84,8 +96,13 @@ func (e DuplicateMessageIDErr) Error() string { } // NewDuplicateMessageIDErr returns a new DuplicateMessageIDErr. -func NewDuplicateMessageIDErr(id string, msgType p2pmsg.ControlMessageType) DuplicateMessageIDErr { - return DuplicateMessageIDErr{id, msgType} +// Args: +// +// id: id of the message that is duplicated +// count: the number of times the message ID has been duplicated +// msgType: the control message type that the message ID was found in. +func NewDuplicateMessageIDErr(id string, count int, msgType p2pmsg.ControlMessageType) DuplicateMessageIDErr { + return DuplicateMessageIDErr{id, count, msgType} } // IsDuplicateMessageIDErr returns true if an error is DuplicateMessageIDErr. diff --git a/network/p2p/inspector/validation/errors_test.go b/network/p2p/inspector/validation/errors_test.go index bc43ca38771..29072fbd5f7 100644 --- a/network/p2p/inspector/validation/errors_test.go +++ b/network/p2p/inspector/validation/errors_test.go @@ -29,8 +29,8 @@ func TestErrActiveClusterIDsNotSetRoundTrip(t *testing.T) { // TestErrDuplicateTopicRoundTrip ensures correct error formatting for DuplicateTopicErr. func TestDuplicateTopicErrRoundTrip(t *testing.T) { - expectedErrorMsg := fmt.Sprintf("duplicate topic foud in %s control message type: %s", p2pmsg.CtrlMsgGraft, channels.TestNetworkChannel) - err := NewDuplicateTopicErr(channels.TestNetworkChannel.String(), p2pmsg.CtrlMsgGraft) + expectedErrorMsg := fmt.Sprintf("duplicate topic found in %s control message type: %s", p2pmsg.CtrlMsgGraft, channels.TestNetworkChannel) + err := NewDuplicateTopicErr(channels.TestNetworkChannel.String(), 1, p2pmsg.CtrlMsgGraft) assert.Equal(t, expectedErrorMsg, err.Error(), "the error message should be correctly formatted") // tests the IsDuplicateTopicErr function. assert.True(t, IsDuplicateTopicErr(err), "IsDuplicateTopicErr should return true for DuplicateTopicErr error") @@ -44,11 +44,11 @@ func TestDuplicateMessageIDErrRoundTrip(t *testing.T) { msgID := "flow-1804flkjnafo" expectedErrMsg1 := fmt.Sprintf("duplicate message ID foud in %s control message type: %s", p2pmsg.CtrlMsgIHave, msgID) expectedErrMsg2 := fmt.Sprintf("duplicate message ID foud in %s control message type: %s", p2pmsg.CtrlMsgIWant, msgID) - err := NewDuplicateMessageIDErr(msgID, p2pmsg.CtrlMsgIHave) + err := NewDuplicateMessageIDErr(msgID, 1, p2pmsg.CtrlMsgIHave) assert.Equal(t, expectedErrMsg1, err.Error(), "the error message should be correctly formatted") // tests the IsDuplicateTopicErr function. assert.True(t, IsDuplicateMessageIDErr(err), "IsDuplicateMessageIDErr should return true for DuplicateMessageIDErr error") - err = NewDuplicateMessageIDErr(msgID, p2pmsg.CtrlMsgIWant) + err = NewDuplicateMessageIDErr(msgID, 1, p2pmsg.CtrlMsgIWant) assert.Equal(t, expectedErrMsg2, err.Error(), "the error message should be correctly formatted") // tests the IsDuplicateTopicErr function. assert.True(t, IsDuplicateMessageIDErr(err), "IsDuplicateMessageIDErr should return true for DuplicateMessageIDErr error") @@ -59,10 +59,10 @@ func TestDuplicateMessageIDErrRoundTrip(t *testing.T) { // TestIWantCacheMissThresholdErrRoundTrip ensures correct error formatting for IWantCacheMissThresholdErr. func TestIWantCacheMissThresholdErrRoundTrip(t *testing.T) { - err := NewIWantCacheMissThresholdErr(5, 10, .4) + err := NewIWantCacheMissThresholdErr(5, 10, 5) // tests the error message formatting. - expectedErrMsg := "5/10 iWant cache misses exceeds the allowed threshold: 0.400000" + expectedErrMsg := "5/10 iWant cache misses exceeds the allowed threshold: 5" assert.Equal(t, expectedErrMsg, err.Error(), "the error message should be correctly formatted") // tests the IsIWantCacheMissThresholdErr function. @@ -75,10 +75,10 @@ func TestIWantCacheMissThresholdErrRoundTrip(t *testing.T) { // TestIWantDuplicateMsgIDThresholdErrRoundTrip ensures correct error formatting for IWantDuplicateMsgIDThresholdErr. func TestIWantDuplicateMsgIDThresholdErrRoundTrip(t *testing.T) { - err := NewIWantDuplicateMsgIDThresholdErr(5, 10, .4) + err := NewIWantDuplicateMsgIDThresholdErr(5, 10, 5) // tests the error message formatting. - expectedErrMsg := "5/10 iWant duplicate message ids exceeds the allowed threshold: 0.400000" + expectedErrMsg := "5/10 iWant duplicate message ids exceeds the allowed threshold: 5" assert.Equal(t, expectedErrMsg, err.Error(), "the error message should be correctly formatted") // tests the IsIWantDuplicateMsgIDThresholdErr function. diff --git a/network/p2p/inspector/validation/utils.go b/network/p2p/inspector/validation/utils.go index 0e57a9dd345..d84bdd0cbf2 100644 --- a/network/p2p/inspector/validation/utils.go +++ b/network/p2p/inspector/validation/utils.go @@ -1,12 +1,24 @@ package validation -type duplicateStrTracker map[string]struct{} +// duplicateStrTracker is a map of strings to the number of times they have been tracked. +// It is a non-concurrent map, so it should only be used in a single goroutine. +// It is used to track duplicate strings. +type duplicateStrTracker map[string]int -func (d duplicateStrTracker) set(s string) { - d[s] = struct{}{} -} +// track stores the string and returns the number of times it has been tracked and whether it is a duplicate. +// If the string has not been tracked before, it is stored with a count of 1. +// If the string has been tracked before, the count is incremented. +// Args: +// +// s: the string to track +// +// Returns: +// The number of times this string has been tracked, e.g., 1 if it is the first time, 2 if it is the second time, etc. +func (d duplicateStrTracker) track(s string) int { + if _, ok := d[s]; !ok { + d[s] = 0 + } + d[s]++ -func (d duplicateStrTracker) isDuplicate(s string) bool { - _, ok := d[s] - return ok + return d[s] } diff --git a/network/p2p/inspector/validation/utils_test.go b/network/p2p/inspector/validation/utils_test.go index 14015dcc621..81f0ffca936 100644 --- a/network/p2p/inspector/validation/utils_test.go +++ b/network/p2p/inspector/validation/utils_test.go @@ -6,11 +6,17 @@ import ( "github.com/stretchr/testify/require" ) -// TestDuplicateStrTracker ensures duplicateStrTracker performs simple set and returns expected result for duplicate str. -func TestDuplicateStrTracker(t *testing.T) { +// TestDuplicateStringTracker tests the duplicateStrTracker.track function. +func TestDuplicateStringTracker(t *testing.T) { tracker := make(duplicateStrTracker) - s := "hello world" - require.False(t, tracker.isDuplicate(s)) - tracker.set(s) - require.True(t, tracker.isDuplicate(s)) + require.Equal(t, 1, tracker.track("test1")) + require.Equal(t, 2, tracker.track("test1")) + + // tracking a new string, 3 times + require.Equal(t, 1, tracker.track("test2")) + require.Equal(t, 2, tracker.track("test2")) + require.Equal(t, 3, tracker.track("test2")) + + // tracking an empty string + require.Equal(t, 1, tracker.track("")) } diff --git a/network/p2p/inspector/validation/validation_inspector_config.go b/network/p2p/inspector/validation/validation_inspector_config.go deleted file mode 100644 index ccad4018c04..00000000000 --- a/network/p2p/inspector/validation/validation_inspector_config.go +++ /dev/null @@ -1,14 +0,0 @@ -package validation - -const ( - // DefaultNumberOfWorkers default number of workers for the inspector component. - DefaultNumberOfWorkers = 5 - // DefaultControlMsgValidationInspectorQueueCacheSize is the default size of the inspect message queue. - DefaultControlMsgValidationInspectorQueueCacheSize = 100 - // DefaultClusterPrefixedControlMsgsReceivedCacheSize is the default size of the cluster prefixed topics received record cache. - DefaultClusterPrefixedControlMsgsReceivedCacheSize = 150 - // DefaultClusterPrefixedControlMsgsReceivedCacheDecay the default cache decay value for cluster prefixed topics received cached counters. - DefaultClusterPrefixedControlMsgsReceivedCacheDecay = 0.99 - // rpcInspectorComponentName the rpc inspector component name. - rpcInspectorComponentName = "gossipsub_rpc_validation_inspector" -) diff --git a/network/p2p/keyutils/keyTranslator.go b/network/p2p/keyutils/keyTranslator.go index 3dd3eab8088..2e29faee401 100644 --- a/network/p2p/keyutils/keyTranslator.go +++ b/network/p2p/keyutils/keyTranslator.go @@ -10,9 +10,7 @@ import ( lcrypto "github.com/libp2p/go-libp2p/core/crypto" lcrypto_pb "github.com/libp2p/go-libp2p/core/crypto/pb" "github.com/libp2p/go-libp2p/core/peer" - - "github.com/onflow/flow-go/crypto" - fcrypto "github.com/onflow/flow-go/crypto" + "github.com/onflow/crypto" ) // This module is meant to help libp2p <-> flow public key conversions @@ -46,7 +44,7 @@ func setPubKey(c elliptic.Curve, x *big.Int, y *big.Int) *goecdsa.PublicKey { // These utility functions convert a Flow crypto key to a LibP2P key (Flow --> LibP2P) // PeerIDFromFlowPublicKey converts a Flow public key to a LibP2P peer ID. -func PeerIDFromFlowPublicKey(networkPubKey fcrypto.PublicKey) (pid peer.ID, err error) { +func PeerIDFromFlowPublicKey(networkPubKey crypto.PublicKey) (pid peer.ID, err error) { pk, err := LibP2PPublicKeyFromFlow(networkPubKey) if err != nil { err = fmt.Errorf("failed to convert Flow key to LibP2P key: %w", err) @@ -63,7 +61,7 @@ func PeerIDFromFlowPublicKey(networkPubKey fcrypto.PublicKey) (pid peer.ID, err } // LibP2PPrivKeyFromFlow converts a Flow private key to a LibP2P Private key -func LibP2PPrivKeyFromFlow(fpk fcrypto.PrivateKey) (lcrypto.PrivKey, error) { +func LibP2PPrivKeyFromFlow(fpk crypto.PrivateKey) (lcrypto.PrivKey, error) { // get the signature algorithm keyType, err := keyType(fpk.Algorithm()) if err != nil { @@ -94,7 +92,7 @@ func LibP2PPrivKeyFromFlow(fpk fcrypto.PrivateKey) (lcrypto.PrivKey, error) { } // LibP2PPublicKeyFromFlow converts a Flow public key to a LibP2P public key -func LibP2PPublicKeyFromFlow(fpk fcrypto.PublicKey) (lcrypto.PubKey, error) { +func LibP2PPublicKeyFromFlow(fpk crypto.PublicKey) (lcrypto.PubKey, error) { keyType, err := keyType(fpk.Algorithm()) if err != nil { return nil, err @@ -130,7 +128,7 @@ func LibP2PPublicKeyFromFlow(fpk fcrypto.PublicKey) (lcrypto.PubKey, error) { // This converts some libp2p PubKeys to a flow PublicKey // - the supported key types are ECDSA P-256 and ECDSA Secp256k1 public keys, // - libp2p also supports RSA and Ed25519 keys, which Flow doesn't, their conversion will return an error. -func FlowPublicKeyFromLibP2P(lpk lcrypto.PubKey) (fcrypto.PublicKey, error) { +func FlowPublicKeyFromLibP2P(lpk lcrypto.PubKey) (crypto.PublicKey, error) { switch ktype := lpk.Type(); ktype { case lcrypto_pb.KeyType_ECDSA: @@ -178,11 +176,11 @@ func FlowPublicKeyFromLibP2P(lpk lcrypto.PubKey) (fcrypto.PublicKey, error) { } // keyType translates Flow signing algorithm constants to the corresponding LibP2P constants -func keyType(sa fcrypto.SigningAlgorithm) (lcrypto_pb.KeyType, error) { +func keyType(sa crypto.SigningAlgorithm) (lcrypto_pb.KeyType, error) { switch sa { - case fcrypto.ECDSAP256: + case crypto.ECDSAP256: return lcrypto_pb.KeyType_ECDSA, nil - case fcrypto.ECDSASecp256k1: + case crypto.ECDSASecp256k1: return lcrypto_pb.KeyType_Secp256k1, nil default: return -1, lcrypto.ErrBadKeyType diff --git a/network/p2p/keyutils/keyTranslator_test.go b/network/p2p/keyutils/keyTranslator_test.go index e8cc10599a5..4f630b1ffb4 100644 --- a/network/p2p/keyutils/keyTranslator_test.go +++ b/network/p2p/keyutils/keyTranslator_test.go @@ -12,7 +12,7 @@ import ( "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" - fcrypto "github.com/onflow/flow-go/crypto" + fcrypto "github.com/onflow/crypto" ) // KeyTranslatorTestSuite tests key conversion from Flow keys to LibP2P keys diff --git a/network/p2p/libp2pNode.go b/network/p2p/libp2pNode.go index 17295341bde..aa4b1a3a408 100644 --- a/network/p2p/libp2pNode.go +++ b/network/p2p/libp2pNode.go @@ -105,7 +105,7 @@ type UnicastManagement interface { // error: An error, if any occurred during the process. This includes failure in creating the stream, setting the write // deadline, executing the writing logic, resetting the stream if the writing logic fails, or closing the stream. // All returned errors during this process can be considered benign. - OpenProtectedStream(ctx context.Context, peerID peer.ID, protectionTag string, writingLogic func(stream libp2pnet.Stream) error) error + OpenAndWriteOnStream(ctx context.Context, peerID peer.ID, protectionTag string, writingLogic func(stream libp2pnet.Stream) error) error // WithDefaultUnicastProtocol overrides the default handler of the unicast manager and registers all preferred protocols. WithDefaultUnicastProtocol(defaultHandler libp2pnet.StreamHandler, preferred []protocols.ProtocolName) error } @@ -114,13 +114,20 @@ type UnicastManagement interface { type PubSub interface { // Subscribe subscribes the node to the given topic and returns the subscription Subscribe(topic channels.Topic, topicValidator TopicValidatorFunc) (Subscription, error) - // UnSubscribe cancels the subscriber and closes the topic. + // Unsubscribe cancels the subscriber and closes the topic. Unsubscribe(topic channels.Topic) error // Publish publishes the given payload on the topic. Publish(ctx context.Context, messageScope flownet.OutgoingMessageScope) error // SetPubSub sets the node's pubsub implementation. // SetPubSub may be called at most once. SetPubSub(ps PubSubAdapter) + + // GetLocalMeshPeers returns the list of peers in the local mesh for the given topic. + // Args: + // - topic: the topic. + // Returns: + // - []peer.ID: the list of peers in the local mesh for the given topic. + GetLocalMeshPeers(topic channels.Topic) []peer.ID } // LibP2PNode represents a Flow libp2p node. It provides the network layer with the necessary interface to diff --git a/network/p2p/p2plogging/internal/peerIdCache.go b/network/p2p/logging/internal/peerIdCache.go similarity index 100% rename from network/p2p/p2plogging/internal/peerIdCache.go rename to network/p2p/logging/internal/peerIdCache.go diff --git a/network/p2p/p2plogging/internal/peerIdCache_test.go b/network/p2p/logging/internal/peerIdCache_test.go similarity index 98% rename from network/p2p/p2plogging/internal/peerIdCache_test.go rename to network/p2p/logging/internal/peerIdCache_test.go index e4e799d9d62..8e03efa7fbb 100644 --- a/network/p2p/p2plogging/internal/peerIdCache_test.go +++ b/network/p2p/logging/internal/peerIdCache_test.go @@ -5,7 +5,7 @@ import ( "github.com/stretchr/testify/assert" - "github.com/onflow/flow-go/network/p2p/p2plogging/internal" + "github.com/onflow/flow-go/network/p2p/logging/internal" "github.com/onflow/flow-go/utils/unittest" ) diff --git a/network/p2p/p2plogging/logging.go b/network/p2p/logging/logging.go similarity index 92% rename from network/p2p/p2plogging/logging.go rename to network/p2p/logging/logging.go index 324e7677dd3..2ab4345aa6b 100644 --- a/network/p2p/p2plogging/logging.go +++ b/network/p2p/logging/logging.go @@ -3,7 +3,7 @@ package p2plogging import ( "github.com/libp2p/go-libp2p/core/peer" - "github.com/onflow/flow-go/network/p2p/p2plogging/internal" + "github.com/onflow/flow-go/network/p2p/logging/internal" ) // peerIdCache is a global cache of peer ids, it is used to avoid expensive base58 encoding of peer ids. diff --git a/network/p2p/p2plogging/logging_test.go b/network/p2p/logging/logging_test.go similarity index 96% rename from network/p2p/p2plogging/logging_test.go rename to network/p2p/logging/logging_test.go index 9740db7574d..1dc80e3af4d 100644 --- a/network/p2p/p2plogging/logging_test.go +++ b/network/p2p/logging/logging_test.go @@ -6,7 +6,7 @@ import ( "github.com/libp2p/go-libp2p/core/peer" "github.com/stretchr/testify/require" - "github.com/onflow/flow-go/network/p2p/p2plogging" + p2plogging "github.com/onflow/flow-go/network/p2p/logging" "github.com/onflow/flow-go/utils/unittest" ) diff --git a/network/p2p/mock/adjust_function.go b/network/p2p/mock/adjust_function.go index 675dddb2efd..6016fbfa04b 100644 --- a/network/p2p/mock/adjust_function.go +++ b/network/p2p/mock/adjust_function.go @@ -3,8 +3,9 @@ package mockp2p import ( - p2p "github.com/onflow/flow-go/network/p2p" mock "github.com/stretchr/testify/mock" + + p2p "github.com/onflow/flow-go/network/p2p" ) // AdjustFunction is an autogenerated mock type for the AdjustFunction type diff --git a/network/p2p/mock/gossip_sub_application_specific_score_cache.go b/network/p2p/mock/gossip_sub_application_specific_score_cache.go new file mode 100644 index 00000000000..d4eb9c6cc32 --- /dev/null +++ b/network/p2p/mock/gossip_sub_application_specific_score_cache.go @@ -0,0 +1,76 @@ +// Code generated by mockery v2.21.4. DO NOT EDIT. + +package mockp2p + +import ( + mock "github.com/stretchr/testify/mock" + + peer "github.com/libp2p/go-libp2p/core/peer" + + time "time" +) + +// GossipSubApplicationSpecificScoreCache is an autogenerated mock type for the GossipSubApplicationSpecificScoreCache type +type GossipSubApplicationSpecificScoreCache struct { + mock.Mock +} + +// AdjustWithInit provides a mock function with given fields: peerID, score, _a2 +func (_m *GossipSubApplicationSpecificScoreCache) AdjustWithInit(peerID peer.ID, score float64, _a2 time.Time) error { + ret := _m.Called(peerID, score, _a2) + + var r0 error + if rf, ok := ret.Get(0).(func(peer.ID, float64, time.Time) error); ok { + r0 = rf(peerID, score, _a2) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Get provides a mock function with given fields: peerID +func (_m *GossipSubApplicationSpecificScoreCache) Get(peerID peer.ID) (float64, time.Time, bool) { + ret := _m.Called(peerID) + + var r0 float64 + var r1 time.Time + var r2 bool + if rf, ok := ret.Get(0).(func(peer.ID) (float64, time.Time, bool)); ok { + return rf(peerID) + } + if rf, ok := ret.Get(0).(func(peer.ID) float64); ok { + r0 = rf(peerID) + } else { + r0 = ret.Get(0).(float64) + } + + if rf, ok := ret.Get(1).(func(peer.ID) time.Time); ok { + r1 = rf(peerID) + } else { + r1 = ret.Get(1).(time.Time) + } + + if rf, ok := ret.Get(2).(func(peer.ID) bool); ok { + r2 = rf(peerID) + } else { + r2 = ret.Get(2).(bool) + } + + return r0, r1, r2 +} + +type mockConstructorTestingTNewGossipSubApplicationSpecificScoreCache interface { + mock.TestingT + Cleanup(func()) +} + +// NewGossipSubApplicationSpecificScoreCache creates a new instance of GossipSubApplicationSpecificScoreCache. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewGossipSubApplicationSpecificScoreCache(t mockConstructorTestingTNewGossipSubApplicationSpecificScoreCache) *GossipSubApplicationSpecificScoreCache { + mock := &GossipSubApplicationSpecificScoreCache{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/network/p2p/mock/gossip_sub_builder.go b/network/p2p/mock/gossip_sub_builder.go index 08d82bd03c6..13342243e3b 100644 --- a/network/p2p/mock/gossip_sub_builder.go +++ b/network/p2p/mock/gossip_sub_builder.go @@ -13,8 +13,6 @@ import ( pubsub "github.com/libp2p/go-libp2p-pubsub" routing "github.com/libp2p/go-libp2p/core/routing" - - time "time" ) // GossipSubBuilder is an autogenerated mock type for the GossipSubBuilder type @@ -68,16 +66,6 @@ func (_m *GossipSubBuilder) SetGossipSubFactory(_a0 p2p.GossipSubFactoryFunc) { _m.Called(_a0) } -// SetGossipSubScoreTracerInterval provides a mock function with given fields: _a0 -func (_m *GossipSubBuilder) SetGossipSubScoreTracerInterval(_a0 time.Duration) { - _m.Called(_a0) -} - -// SetGossipSubTracer provides a mock function with given fields: _a0 -func (_m *GossipSubBuilder) SetGossipSubTracer(_a0 p2p.PubSubTracer) { - _m.Called(_a0) -} - // SetHost provides a mock function with given fields: _a0 func (_m *GossipSubBuilder) SetHost(_a0 host.Host) { _m.Called(_a0) diff --git a/network/p2p/mock/gossip_sub_control_metrics_observer.go b/network/p2p/mock/gossip_sub_control_metrics_observer.go deleted file mode 100644 index 333bb990c6c..00000000000 --- a/network/p2p/mock/gossip_sub_control_metrics_observer.go +++ /dev/null @@ -1,36 +0,0 @@ -// Code generated by mockery v2.21.4. DO NOT EDIT. - -package mockp2p - -import ( - mock "github.com/stretchr/testify/mock" - - peer "github.com/libp2p/go-libp2p/core/peer" - - pubsub "github.com/libp2p/go-libp2p-pubsub" -) - -// GossipSubControlMetricsObserver is an autogenerated mock type for the GossipSubControlMetricsObserver type -type GossipSubControlMetricsObserver struct { - mock.Mock -} - -// ObserveRPC provides a mock function with given fields: _a0, _a1 -func (_m *GossipSubControlMetricsObserver) ObserveRPC(_a0 peer.ID, _a1 *pubsub.RPC) { - _m.Called(_a0, _a1) -} - -type mockConstructorTestingTNewGossipSubControlMetricsObserver interface { - mock.TestingT - Cleanup(func()) -} - -// NewGossipSubControlMetricsObserver creates a new instance of GossipSubControlMetricsObserver. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewGossipSubControlMetricsObserver(t mockConstructorTestingTNewGossipSubControlMetricsObserver) *GossipSubControlMetricsObserver { - mock := &GossipSubControlMetricsObserver{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/network/p2p/mock/gossip_sub_inspector_notification_distributor.go b/network/p2p/mock/gossip_sub_inspector_notification_distributor.go index 757cd8fa363..f44f7a2c480 100644 --- a/network/p2p/mock/gossip_sub_inspector_notification_distributor.go +++ b/network/p2p/mock/gossip_sub_inspector_notification_distributor.go @@ -3,9 +3,10 @@ package mockp2p import ( - irrecoverable "github.com/onflow/flow-go/module/irrecoverable" mock "github.com/stretchr/testify/mock" + irrecoverable "github.com/onflow/flow-go/module/irrecoverable" + p2p "github.com/onflow/flow-go/network/p2p" ) diff --git a/network/p2p/mock/gossip_sub_invalid_control_message_notification_consumer.go b/network/p2p/mock/gossip_sub_invalid_control_message_notification_consumer.go index 8df3aae5870..fa1471614b6 100644 --- a/network/p2p/mock/gossip_sub_invalid_control_message_notification_consumer.go +++ b/network/p2p/mock/gossip_sub_invalid_control_message_notification_consumer.go @@ -3,8 +3,9 @@ package mockp2p import ( - p2p "github.com/onflow/flow-go/network/p2p" mock "github.com/stretchr/testify/mock" + + p2p "github.com/onflow/flow-go/network/p2p" ) // GossipSubInvalidControlMessageNotificationConsumer is an autogenerated mock type for the GossipSubInvalidControlMessageNotificationConsumer type diff --git a/network/p2p/mock/gossip_sub_rpc_inspector_suite_factory_func.go b/network/p2p/mock/gossip_sub_rpc_inspector_suite_factory_func.go index 24c253b70d2..7b419f29c48 100644 --- a/network/p2p/mock/gossip_sub_rpc_inspector_suite_factory_func.go +++ b/network/p2p/mock/gossip_sub_rpc_inspector_suite_factory_func.go @@ -16,7 +16,7 @@ import ( p2p "github.com/onflow/flow-go/network/p2p" - p2pconf "github.com/onflow/flow-go/network/p2p/p2pconf" + p2pconfig "github.com/onflow/flow-go/network/p2p/config" zerolog "github.com/rs/zerolog" ) @@ -27,15 +27,15 @@ type GossipSubRpcInspectorSuiteFactoryFunc struct { } // Execute provides a mock function with given fields: _a0, _a1, _a2, _a3, _a4, _a5, _a6, _a7, _a8 -func (_m *GossipSubRpcInspectorSuiteFactoryFunc) Execute(_a0 irrecoverable.SignalerContext, _a1 zerolog.Logger, _a2 flow.Identifier, _a3 *p2pconf.GossipSubRPCInspectorsConfig, _a4 module.GossipSubMetrics, _a5 metrics.HeroCacheMetricsFactory, _a6 network.NetworkingType, _a7 module.IdentityProvider, _a8 func() p2p.TopicProvider) (p2p.GossipSubInspectorSuite, error) { +func (_m *GossipSubRpcInspectorSuiteFactoryFunc) Execute(_a0 irrecoverable.SignalerContext, _a1 zerolog.Logger, _a2 flow.Identifier, _a3 *p2pconfig.RpcInspectorParameters, _a4 module.GossipSubMetrics, _a5 metrics.HeroCacheMetricsFactory, _a6 network.NetworkingType, _a7 module.IdentityProvider, _a8 func() p2p.TopicProvider) (p2p.GossipSubInspectorSuite, error) { ret := _m.Called(_a0, _a1, _a2, _a3, _a4, _a5, _a6, _a7, _a8) var r0 p2p.GossipSubInspectorSuite var r1 error - if rf, ok := ret.Get(0).(func(irrecoverable.SignalerContext, zerolog.Logger, flow.Identifier, *p2pconf.GossipSubRPCInspectorsConfig, module.GossipSubMetrics, metrics.HeroCacheMetricsFactory, network.NetworkingType, module.IdentityProvider, func() p2p.TopicProvider) (p2p.GossipSubInspectorSuite, error)); ok { + if rf, ok := ret.Get(0).(func(irrecoverable.SignalerContext, zerolog.Logger, flow.Identifier, *p2pconfig.RpcInspectorParameters, module.GossipSubMetrics, metrics.HeroCacheMetricsFactory, network.NetworkingType, module.IdentityProvider, func() p2p.TopicProvider) (p2p.GossipSubInspectorSuite, error)); ok { return rf(_a0, _a1, _a2, _a3, _a4, _a5, _a6, _a7, _a8) } - if rf, ok := ret.Get(0).(func(irrecoverable.SignalerContext, zerolog.Logger, flow.Identifier, *p2pconf.GossipSubRPCInspectorsConfig, module.GossipSubMetrics, metrics.HeroCacheMetricsFactory, network.NetworkingType, module.IdentityProvider, func() p2p.TopicProvider) p2p.GossipSubInspectorSuite); ok { + if rf, ok := ret.Get(0).(func(irrecoverable.SignalerContext, zerolog.Logger, flow.Identifier, *p2pconfig.RpcInspectorParameters, module.GossipSubMetrics, metrics.HeroCacheMetricsFactory, network.NetworkingType, module.IdentityProvider, func() p2p.TopicProvider) p2p.GossipSubInspectorSuite); ok { r0 = rf(_a0, _a1, _a2, _a3, _a4, _a5, _a6, _a7, _a8) } else { if ret.Get(0) != nil { @@ -43,7 +43,7 @@ func (_m *GossipSubRpcInspectorSuiteFactoryFunc) Execute(_a0 irrecoverable.Signa } } - if rf, ok := ret.Get(1).(func(irrecoverable.SignalerContext, zerolog.Logger, flow.Identifier, *p2pconf.GossipSubRPCInspectorsConfig, module.GossipSubMetrics, metrics.HeroCacheMetricsFactory, network.NetworkingType, module.IdentityProvider, func() p2p.TopicProvider) error); ok { + if rf, ok := ret.Get(1).(func(irrecoverable.SignalerContext, zerolog.Logger, flow.Identifier, *p2pconfig.RpcInspectorParameters, module.GossipSubMetrics, metrics.HeroCacheMetricsFactory, network.NetworkingType, module.IdentityProvider, func() p2p.TopicProvider) error); ok { r1 = rf(_a0, _a1, _a2, _a3, _a4, _a5, _a6, _a7, _a8) } else { r1 = ret.Error(1) diff --git a/network/p2p/mock/gossip_sub_spam_record_cache.go b/network/p2p/mock/gossip_sub_spam_record_cache.go index 35e674fdffb..2fe7a692f89 100644 --- a/network/p2p/mock/gossip_sub_spam_record_cache.go +++ b/network/p2p/mock/gossip_sub_spam_record_cache.go @@ -14,18 +14,30 @@ type GossipSubSpamRecordCache struct { mock.Mock } -// Add provides a mock function with given fields: peerId, record -func (_m *GossipSubSpamRecordCache) Add(peerId peer.ID, record p2p.GossipSubSpamRecord) bool { - ret := _m.Called(peerId, record) +// Adjust provides a mock function with given fields: peerID, updateFunc +func (_m *GossipSubSpamRecordCache) Adjust(peerID peer.ID, updateFunc p2p.UpdateFunction) (*p2p.GossipSubSpamRecord, error) { + ret := _m.Called(peerID, updateFunc) - var r0 bool - if rf, ok := ret.Get(0).(func(peer.ID, p2p.GossipSubSpamRecord) bool); ok { - r0 = rf(peerId, record) + var r0 *p2p.GossipSubSpamRecord + var r1 error + if rf, ok := ret.Get(0).(func(peer.ID, p2p.UpdateFunction) (*p2p.GossipSubSpamRecord, error)); ok { + return rf(peerID, updateFunc) + } + if rf, ok := ret.Get(0).(func(peer.ID, p2p.UpdateFunction) *p2p.GossipSubSpamRecord); ok { + r0 = rf(peerID, updateFunc) } else { - r0 = ret.Get(0).(bool) + if ret.Get(0) != nil { + r0 = ret.Get(0).(*p2p.GossipSubSpamRecord) + } } - return r0 + if rf, ok := ret.Get(1).(func(peer.ID, p2p.UpdateFunction) error); ok { + r1 = rf(peerID, updateFunc) + } else { + r1 = ret.Error(1) + } + + return r0, r1 } // Get provides a mock function with given fields: peerID @@ -75,32 +87,6 @@ func (_m *GossipSubSpamRecordCache) Has(peerID peer.ID) bool { return r0 } -// Update provides a mock function with given fields: peerID, updateFunc -func (_m *GossipSubSpamRecordCache) Update(peerID peer.ID, updateFunc p2p.UpdateFunction) (*p2p.GossipSubSpamRecord, error) { - ret := _m.Called(peerID, updateFunc) - - var r0 *p2p.GossipSubSpamRecord - var r1 error - if rf, ok := ret.Get(0).(func(peer.ID, p2p.UpdateFunction) (*p2p.GossipSubSpamRecord, error)); ok { - return rf(peerID, updateFunc) - } - if rf, ok := ret.Get(0).(func(peer.ID, p2p.UpdateFunction) *p2p.GossipSubSpamRecord); ok { - r0 = rf(peerID, updateFunc) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*p2p.GossipSubSpamRecord) - } - } - - if rf, ok := ret.Get(1).(func(peer.ID, p2p.UpdateFunction) error); ok { - r1 = rf(peerID, updateFunc) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - type mockConstructorTestingTNewGossipSubSpamRecordCache interface { mock.TestingT Cleanup(func()) diff --git a/network/p2p/mock/lib_p2_p_node.go b/network/p2p/mock/lib_p2_p_node.go index bbcd4c226cb..e522cb8f225 100644 --- a/network/p2p/mock/lib_p2_p_node.go +++ b/network/p2p/mock/lib_p2_p_node.go @@ -104,6 +104,22 @@ func (_m *LibP2PNode) GetIPPort() (string, string, error) { return r0, r1, r2 } +// GetLocalMeshPeers provides a mock function with given fields: topic +func (_m *LibP2PNode) GetLocalMeshPeers(topic channels.Topic) []peer.ID { + ret := _m.Called(topic) + + var r0 []peer.ID + if rf, ok := ret.Get(0).(func(channels.Topic) []peer.ID); ok { + r0 = rf(topic) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]peer.ID) + } + } + + return r0 +} + // GetPeersForProtocol provides a mock function with given fields: pid func (_m *LibP2PNode) GetPeersForProtocol(pid protocol.ID) peer.IDSlice { ret := _m.Called(pid) @@ -240,8 +256,8 @@ func (_m *LibP2PNode) OnDisallowListNotification(id peer.ID, cause network.Disal _m.Called(id, cause) } -// OpenProtectedStream provides a mock function with given fields: ctx, peerID, protectionTag, writingLogic -func (_m *LibP2PNode) OpenProtectedStream(ctx context.Context, peerID peer.ID, protectionTag string, writingLogic func(corenetwork.Stream) error) error { +// OpenAndWriteOnStream provides a mock function with given fields: ctx, peerID, protectionTag, writingLogic +func (_m *LibP2PNode) OpenAndWriteOnStream(ctx context.Context, peerID peer.ID, protectionTag string, writingLogic func(corenetwork.Stream) error) error { ret := _m.Called(ctx, peerID, protectionTag, writingLogic) var r0 error diff --git a/network/p2p/mock/network_config_option.go b/network/p2p/mock/network_config_option.go index 8474d84d9a9..7b3c1440e0b 100644 --- a/network/p2p/mock/network_config_option.go +++ b/network/p2p/mock/network_config_option.go @@ -3,7 +3,7 @@ package mockp2p import ( - p2p "github.com/onflow/flow-go/network/p2p/p2pnet" + p2p "github.com/onflow/flow-go/network/underlay" mock "github.com/stretchr/testify/mock" ) diff --git a/network/p2p/mock/network_opt_function.go b/network/p2p/mock/network_opt_function.go index 260368afe50..68f00f7620c 100644 --- a/network/p2p/mock/network_opt_function.go +++ b/network/p2p/mock/network_opt_function.go @@ -3,7 +3,7 @@ package mockp2p import ( - p2p "github.com/onflow/flow-go/network/p2p/p2pnet" + p2p "github.com/onflow/flow-go/network/underlay" mock "github.com/stretchr/testify/mock" ) diff --git a/network/p2p/mock/network_option.go b/network/p2p/mock/network_option.go index 6b36ba7399f..c1c742bbe91 100644 --- a/network/p2p/mock/network_option.go +++ b/network/p2p/mock/network_option.go @@ -3,7 +3,7 @@ package mockp2p import ( - p2p "github.com/onflow/flow-go/network/p2p/p2pnet" + p2p "github.com/onflow/flow-go/network/underlay" mock "github.com/stretchr/testify/mock" ) diff --git a/network/p2p/mock/network_param_option.go b/network/p2p/mock/network_param_option.go index 28f3c79b367..8f4bcc26f5f 100644 --- a/network/p2p/mock/network_param_option.go +++ b/network/p2p/mock/network_param_option.go @@ -3,7 +3,7 @@ package mockp2p import ( - p2p "github.com/onflow/flow-go/network/p2p/p2pnet" + p2p "github.com/onflow/flow-go/network/underlay" mock "github.com/stretchr/testify/mock" ) diff --git a/network/p2p/mock/node_block_list_consumer.go b/network/p2p/mock/node_block_list_consumer.go index 41a5b05751d..a60105b4c17 100644 --- a/network/p2p/mock/node_block_list_consumer.go +++ b/network/p2p/mock/node_block_list_consumer.go @@ -3,8 +3,9 @@ package mockp2p import ( - flow "github.com/onflow/flow-go/model/flow" mock "github.com/stretchr/testify/mock" + + flow "github.com/onflow/flow-go/model/flow" ) // NodeBlockListConsumer is an autogenerated mock type for the NodeBlockListConsumer type diff --git a/network/p2p/mock/node_builder.go b/network/p2p/mock/node_builder.go index 331290f90c6..3b10dcfb0c8 100644 --- a/network/p2p/mock/node_builder.go +++ b/network/p2p/mock/node_builder.go @@ -20,8 +20,6 @@ import ( pubsub "github.com/libp2p/go-libp2p-pubsub" routing "github.com/libp2p/go-libp2p/core/routing" - - time "time" ) // NodeBuilder is an autogenerated mock type for the NodeBuilder type @@ -55,12 +53,12 @@ func (_m *NodeBuilder) Build() (p2p.LibP2PNode, error) { return r0, r1 } -// EnableGossipSubScoringWithOverride provides a mock function with given fields: _a0 -func (_m *NodeBuilder) EnableGossipSubScoringWithOverride(_a0 *p2p.PeerScoringConfigOverride) p2p.NodeBuilder { +// OverrideDefaultRpcInspectorSuiteFactory provides a mock function with given fields: _a0 +func (_m *NodeBuilder) OverrideDefaultRpcInspectorSuiteFactory(_a0 p2p.GossipSubRpcInspectorSuiteFactoryFunc) p2p.NodeBuilder { ret := _m.Called(_a0) var r0 p2p.NodeBuilder - if rf, ok := ret.Get(0).(func(*p2p.PeerScoringConfigOverride) p2p.NodeBuilder); ok { + if rf, ok := ret.Get(0).(func(p2p.GossipSubRpcInspectorSuiteFactoryFunc) p2p.NodeBuilder); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { @@ -71,12 +69,12 @@ func (_m *NodeBuilder) EnableGossipSubScoringWithOverride(_a0 *p2p.PeerScoringCo return r0 } -// OverrideDefaultRpcInspectorSuiteFactory provides a mock function with given fields: _a0 -func (_m *NodeBuilder) OverrideDefaultRpcInspectorSuiteFactory(_a0 p2p.GossipSubRpcInspectorSuiteFactoryFunc) p2p.NodeBuilder { +// OverrideGossipSubScoringConfig provides a mock function with given fields: _a0 +func (_m *NodeBuilder) OverrideGossipSubScoringConfig(_a0 *p2p.PeerScoringConfigOverride) p2p.NodeBuilder { ret := _m.Called(_a0) var r0 p2p.NodeBuilder - if rf, ok := ret.Get(0).(func(p2p.GossipSubRpcInspectorSuiteFactoryFunc) p2p.NodeBuilder); ok { + if rf, ok := ret.Get(0).(func(*p2p.PeerScoringConfigOverride) p2p.NodeBuilder); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { @@ -87,12 +85,12 @@ func (_m *NodeBuilder) OverrideDefaultRpcInspectorSuiteFactory(_a0 p2p.GossipSub return r0 } -// SetBasicResolver provides a mock function with given fields: _a0 -func (_m *NodeBuilder) SetBasicResolver(_a0 madns.BasicResolver) p2p.NodeBuilder { +// OverrideNodeConstructor provides a mock function with given fields: _a0 +func (_m *NodeBuilder) OverrideNodeConstructor(_a0 p2p.NodeConstructor) p2p.NodeBuilder { ret := _m.Called(_a0) var r0 p2p.NodeBuilder - if rf, ok := ret.Get(0).(func(madns.BasicResolver) p2p.NodeBuilder); ok { + if rf, ok := ret.Get(0).(func(p2p.NodeConstructor) p2p.NodeBuilder); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { @@ -103,12 +101,12 @@ func (_m *NodeBuilder) SetBasicResolver(_a0 madns.BasicResolver) p2p.NodeBuilder return r0 } -// SetConnectionGater provides a mock function with given fields: _a0 -func (_m *NodeBuilder) SetConnectionGater(_a0 p2p.ConnectionGater) p2p.NodeBuilder { +// SetBasicResolver provides a mock function with given fields: _a0 +func (_m *NodeBuilder) SetBasicResolver(_a0 madns.BasicResolver) p2p.NodeBuilder { ret := _m.Called(_a0) var r0 p2p.NodeBuilder - if rf, ok := ret.Get(0).(func(p2p.ConnectionGater) p2p.NodeBuilder); ok { + if rf, ok := ret.Get(0).(func(madns.BasicResolver) p2p.NodeBuilder); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { @@ -119,12 +117,12 @@ func (_m *NodeBuilder) SetConnectionGater(_a0 p2p.ConnectionGater) p2p.NodeBuild return r0 } -// SetConnectionManager provides a mock function with given fields: _a0 -func (_m *NodeBuilder) SetConnectionManager(_a0 connmgr.ConnManager) p2p.NodeBuilder { +// SetConnectionGater provides a mock function with given fields: _a0 +func (_m *NodeBuilder) SetConnectionGater(_a0 p2p.ConnectionGater) p2p.NodeBuilder { ret := _m.Called(_a0) var r0 p2p.NodeBuilder - if rf, ok := ret.Get(0).(func(connmgr.ConnManager) p2p.NodeBuilder); ok { + if rf, ok := ret.Get(0).(func(p2p.ConnectionGater) p2p.NodeBuilder); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { @@ -135,12 +133,12 @@ func (_m *NodeBuilder) SetConnectionManager(_a0 connmgr.ConnManager) p2p.NodeBui return r0 } -// SetCreateNode provides a mock function with given fields: _a0 -func (_m *NodeBuilder) SetCreateNode(_a0 p2p.CreateNodeFunc) p2p.NodeBuilder { +// SetConnectionManager provides a mock function with given fields: _a0 +func (_m *NodeBuilder) SetConnectionManager(_a0 connmgr.ConnManager) p2p.NodeBuilder { ret := _m.Called(_a0) var r0 p2p.NodeBuilder - if rf, ok := ret.Get(0).(func(p2p.CreateNodeFunc) p2p.NodeBuilder); ok { + if rf, ok := ret.Get(0).(func(connmgr.ConnManager) p2p.NodeBuilder); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { @@ -167,38 +165,6 @@ func (_m *NodeBuilder) SetGossipSubFactory(_a0 p2p.GossipSubFactoryFunc, _a1 p2p return r0 } -// SetGossipSubScoreTracerInterval provides a mock function with given fields: _a0 -func (_m *NodeBuilder) SetGossipSubScoreTracerInterval(_a0 time.Duration) p2p.NodeBuilder { - ret := _m.Called(_a0) - - var r0 p2p.NodeBuilder - if rf, ok := ret.Get(0).(func(time.Duration) p2p.NodeBuilder); ok { - r0 = rf(_a0) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(p2p.NodeBuilder) - } - } - - return r0 -} - -// SetGossipSubTracer provides a mock function with given fields: _a0 -func (_m *NodeBuilder) SetGossipSubTracer(_a0 p2p.PubSubTracer) p2p.NodeBuilder { - ret := _m.Called(_a0) - - var r0 p2p.NodeBuilder - if rf, ok := ret.Get(0).(func(p2p.PubSubTracer) p2p.NodeBuilder); ok { - r0 = rf(_a0) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(p2p.NodeBuilder) - } - } - - return r0 -} - // SetResourceManager provides a mock function with given fields: _a0 func (_m *NodeBuilder) SetResourceManager(_a0 network.ResourceManager) p2p.NodeBuilder { ret := _m.Called(_a0) diff --git a/network/p2p/mock/node_constructor.go b/network/p2p/mock/node_constructor.go new file mode 100644 index 00000000000..5dcf02911c4 --- /dev/null +++ b/network/p2p/mock/node_constructor.go @@ -0,0 +1,54 @@ +// Code generated by mockery v2.21.4. DO NOT EDIT. + +package mockp2p + +import ( + p2p "github.com/onflow/flow-go/network/p2p" + mock "github.com/stretchr/testify/mock" +) + +// NodeConstructor is an autogenerated mock type for the NodeConstructor type +type NodeConstructor struct { + mock.Mock +} + +// Execute provides a mock function with given fields: config +func (_m *NodeConstructor) Execute(config *p2p.NodeConfig) (p2p.LibP2PNode, error) { + ret := _m.Called(config) + + var r0 p2p.LibP2PNode + var r1 error + if rf, ok := ret.Get(0).(func(*p2p.NodeConfig) (p2p.LibP2PNode, error)); ok { + return rf(config) + } + if rf, ok := ret.Get(0).(func(*p2p.NodeConfig) p2p.LibP2PNode); ok { + r0 = rf(config) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(p2p.LibP2PNode) + } + } + + if rf, ok := ret.Get(1).(func(*p2p.NodeConfig) error); ok { + r1 = rf(config) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +type mockConstructorTestingTNewNodeConstructor interface { + mock.TestingT + Cleanup(func()) +} + +// NewNodeConstructor creates a new instance of NodeConstructor. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewNodeConstructor(t mockConstructorTestingTNewNodeConstructor) *NodeConstructor { + mock := &NodeConstructor{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/network/p2p/mock/peer_scoring_builder.go b/network/p2p/mock/peer_scoring_builder.go index 51a7e2c68fb..d5c1fd35d94 100644 --- a/network/p2p/mock/peer_scoring_builder.go +++ b/network/p2p/mock/peer_scoring_builder.go @@ -3,9 +3,10 @@ package mockp2p import ( - channels "github.com/onflow/flow-go/network/channels" mock "github.com/stretchr/testify/mock" + channels "github.com/onflow/flow-go/network/channels" + peer "github.com/libp2p/go-libp2p/core/peer" pubsub "github.com/libp2p/go-libp2p-pubsub" diff --git a/network/p2p/mock/pub_sub.go b/network/p2p/mock/pub_sub.go index b035607ef57..e80a4009598 100644 --- a/network/p2p/mock/pub_sub.go +++ b/network/p2p/mock/pub_sub.go @@ -12,6 +12,8 @@ import ( network "github.com/onflow/flow-go/network" p2p "github.com/onflow/flow-go/network/p2p" + + peer "github.com/libp2p/go-libp2p/core/peer" ) // PubSub is an autogenerated mock type for the PubSub type @@ -19,6 +21,22 @@ type PubSub struct { mock.Mock } +// GetLocalMeshPeers provides a mock function with given fields: topic +func (_m *PubSub) GetLocalMeshPeers(topic channels.Topic) []peer.ID { + ret := _m.Called(topic) + + var r0 []peer.ID + if rf, ok := ret.Get(0).(func(channels.Topic) []peer.ID); ok { + r0 = rf(topic) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]peer.ID) + } + } + + return r0 +} + // Publish provides a mock function with given fields: ctx, messageScope func (_m *PubSub) Publish(ctx context.Context, messageScope network.OutgoingMessageScope) error { ret := _m.Called(ctx, messageScope) diff --git a/network/p2p/mock/pub_sub_adapter.go b/network/p2p/mock/pub_sub_adapter.go index ec05980dea1..159b149f183 100644 --- a/network/p2p/mock/pub_sub_adapter.go +++ b/network/p2p/mock/pub_sub_adapter.go @@ -4,6 +4,8 @@ package mockp2p import ( flow "github.com/onflow/flow-go/model/flow" + channels "github.com/onflow/flow-go/network/channels" + irrecoverable "github.com/onflow/flow-go/module/irrecoverable" mock "github.com/stretchr/testify/mock" @@ -39,6 +41,22 @@ func (_m *PubSubAdapter) Done() <-chan struct{} { return r0 } +// GetLocalMeshPeers provides a mock function with given fields: topic +func (_m *PubSubAdapter) GetLocalMeshPeers(topic channels.Topic) []peer.ID { + ret := _m.Called(topic) + + var r0 []peer.ID + if rf, ok := ret.Get(0).(func(channels.Topic) []peer.ID); ok { + r0 = rf(topic) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]peer.ID) + } + } + + return r0 +} + // GetTopics provides a mock function with given fields: func (_m *PubSubAdapter) GetTopics() []string { ret := _m.Called() diff --git a/network/p2p/mock/pub_sub_tracer.go b/network/p2p/mock/pub_sub_tracer.go index 26e37611bc2..9dc380aed65 100644 --- a/network/p2p/mock/pub_sub_tracer.go +++ b/network/p2p/mock/pub_sub_tracer.go @@ -4,6 +4,8 @@ package mockp2p import ( irrecoverable "github.com/onflow/flow-go/module/irrecoverable" + channels "github.com/onflow/flow-go/network/channels" + mock "github.com/stretchr/testify/mock" peer "github.com/libp2p/go-libp2p/core/peer" @@ -54,6 +56,22 @@ func (_m *PubSubTracer) DuplicateMessage(msg *pubsub.Message) { _m.Called(msg) } +// GetLocalMeshPeers provides a mock function with given fields: topic +func (_m *PubSubTracer) GetLocalMeshPeers(topic channels.Topic) []peer.ID { + ret := _m.Called(topic) + + var r0 []peer.ID + if rf, ok := ret.Get(0).(func(channels.Topic) []peer.ID); ok { + r0 = rf(topic) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]peer.ID) + } + } + + return r0 +} + // Graft provides a mock function with given fields: p, topic func (_m *PubSubTracer) Graft(p peer.ID, topic string) { _m.Called(p, topic) diff --git a/network/p2p/mock/unicast_management.go b/network/p2p/mock/unicast_management.go index e45531c1a25..96bb43bfd60 100644 --- a/network/p2p/mock/unicast_management.go +++ b/network/p2p/mock/unicast_management.go @@ -18,8 +18,8 @@ type UnicastManagement struct { mock.Mock } -// OpenProtectedStream provides a mock function with given fields: ctx, peerID, protectionTag, writingLogic -func (_m *UnicastManagement) OpenProtectedStream(ctx context.Context, peerID peer.ID, protectionTag string, writingLogic func(network.Stream) error) error { +// OpenAndWriteOnStream provides a mock function with given fields: ctx, peerID, protectionTag, writingLogic +func (_m *UnicastManagement) OpenAndWriteOnStream(ctx context.Context, peerID peer.ID, protectionTag string, writingLogic func(network.Stream) error) error { ret := _m.Called(ctx, peerID, protectionTag, writingLogic) var r0 error diff --git a/network/p2p/p2pnode/disallow_listing_test.go b/network/p2p/node/disallow_listing_test.go similarity index 96% rename from network/p2p/p2pnode/disallow_listing_test.go rename to network/p2p/node/disallow_listing_test.go index 4dd0249c9f4..2e9faf43ce3 100644 --- a/network/p2p/p2pnode/disallow_listing_test.go +++ b/network/p2p/node/disallow_listing_test.go @@ -12,8 +12,8 @@ import ( mockmodule "github.com/onflow/flow-go/module/mock" "github.com/onflow/flow-go/network" "github.com/onflow/flow-go/network/p2p" + p2pbuilderconfig "github.com/onflow/flow-go/network/p2p/builder/config" "github.com/onflow/flow-go/network/p2p/connection" - p2pconfig "github.com/onflow/flow-go/network/p2p/p2pbuilder/config" p2ptest "github.com/onflow/flow-go/network/p2p/test" "github.com/onflow/flow-go/utils/unittest" ) @@ -35,7 +35,7 @@ func TestDisconnectingFromDisallowListedNode(t *testing.T) { sporkID, t.Name(), idProvider, - p2ptest.WithPeerManagerEnabled(&p2pconfig.PeerManagerConfig{ + p2ptest.WithPeerManagerEnabled(&p2pbuilderconfig.PeerManagerConfig{ ConnectionPruning: true, UpdateInterval: connection.DefaultPeerUpdateInterval, ConnectorFactory: connection.DefaultLibp2pBackoffConnectorFactory(), diff --git a/network/p2p/p2pnode/gossipSubAdapter.go b/network/p2p/node/gossipSubAdapter.go similarity index 94% rename from network/p2p/p2pnode/gossipSubAdapter.go rename to network/p2p/node/gossipSubAdapter.go index f2d1296b588..d1acf5af376 100644 --- a/network/p2p/p2pnode/gossipSubAdapter.go +++ b/network/p2p/node/gossipSubAdapter.go @@ -12,6 +12,7 @@ import ( "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/module/component" "github.com/onflow/flow-go/module/irrecoverable" + "github.com/onflow/flow-go/network/channels" "github.com/onflow/flow-go/network/p2p" "github.com/onflow/flow-go/network/p2p/utils" "github.com/onflow/flow-go/utils/logging" @@ -31,6 +32,7 @@ type GossipSubAdapter struct { topicScoreParamFunc func(topic *pubsub.Topic) *pubsub.TopicScoreParams logger zerolog.Logger peerScoreExposer p2p.PeerScoreExposer + localMeshTracer p2p.PubSubTracer // clusterChangeConsumer is a callback that is invoked when the set of active clusters of collection nodes changes. // This callback is implemented by the rpc inspector suite of the GossipSubAdapter, and consumes the cluster changes // to update the rpc inspector state of the recent topics (i.e., channels). @@ -107,6 +109,7 @@ func NewGossipSubAdapter(ctx context.Context, <-tracer.Done() a.logger.Info().Msg("pubsub tracer stopped") }) + a.localMeshTracer = tracer } if inspectorSuite := gossipSubConfig.InspectorSuiteComponent(); inspectorSuite != nil { @@ -211,6 +214,15 @@ func (g *GossipSubAdapter) ListPeers(topic string) []peer.ID { return g.gossipSub.ListPeers(topic) } +// GetLocalMeshPeers returns the list of peers in the local mesh for the given topic. +// Args: +// - topic: the topic. +// Returns: +// - []peer.ID: the list of peers in the local mesh for the given topic. +func (g *GossipSubAdapter) GetLocalMeshPeers(topic channels.Topic) []peer.ID { + return g.localMeshTracer.GetLocalMeshPeers(topic) +} + // PeerScoreExposer returns the peer score exposer for the gossipsub adapter. The exposer is a read-only interface // for querying peer scores and returns the local scoring table of the underlying gossipsub node. // The exposer is only available if the gossipsub adapter was configured with a score tracer. diff --git a/network/p2p/p2pnode/gossipSubAdapterConfig.go b/network/p2p/node/gossipSubAdapterConfig.go similarity index 100% rename from network/p2p/p2pnode/gossipSubAdapterConfig.go rename to network/p2p/node/gossipSubAdapterConfig.go diff --git a/network/p2p/p2pnode/gossipSubTopic.go b/network/p2p/node/gossipSubTopic.go similarity index 100% rename from network/p2p/p2pnode/gossipSubTopic.go rename to network/p2p/node/gossipSubTopic.go diff --git a/network/p2p/p2pnode/internal/cache.go b/network/p2p/node/internal/cache.go similarity index 64% rename from network/p2p/p2pnode/internal/cache.go rename to network/p2p/node/internal/cache.go index 6d8952e6628..e2a49ccca51 100644 --- a/network/p2p/p2pnode/internal/cache.go +++ b/network/p2p/node/internal/cache.go @@ -34,11 +34,7 @@ type DisallowListCache struct { func NewDisallowListCache(sizeLimit uint32, logger zerolog.Logger, collector module.HeroCacheMetrics) *DisallowListCache { backData := herocache.NewCache(sizeLimit, herocache.DefaultOversizeFactor, - // this cache is supposed to keep the disallow-list causes for the authorized (staked) nodes. Since the number of such nodes is - // expected to be small, we do not eject any records from the cache. The cache size must be large enough to hold all - // the spam records of the authorized nodes. Also, this cache is keeping at most one record per peer id, so the - // size of the cache must be at least the number of authorized nodes. - heropool.NoEjection, + heropool.LRUEjection, logger.With().Str("mempool", "disallow-list-records").Logger(), collector) @@ -72,20 +68,6 @@ func (d *DisallowListCache) IsDisallowListed(peerID peer.ID) ([]network.Disallow return causes, true } -// init initializes the disallow-list cache entity for the peerID. -// Args: -// - peerID: the peerID of the peer to be disallow-listed. -// Returns: -// - bool: true if the entity is successfully added to the cache. -// false if the entity already exists in the cache. -func (d *DisallowListCache) init(peerID peer.ID) bool { - return d.c.Add(&disallowListCacheEntity{ - peerID: peerID, - causes: make(map[network.DisallowListedCause]struct{}), - id: makeId(peerID), - }) -} - // DisallowFor disallow-lists a peer for a cause. // Args: // - peerID: the peerID of the peer to be disallow-listed. @@ -94,51 +76,26 @@ func (d *DisallowListCache) init(peerID peer.ID) bool { // - []network.DisallowListedCause: the list of causes for which the peer is disallow-listed. // - error: if the operation fails, error is irrecoverable. func (d *DisallowListCache) DisallowFor(peerID peer.ID, cause network.DisallowListedCause) ([]network.DisallowListedCause, error) { - // first, we try to optimistically add the peer to the disallow list. - causes, err := d.disallowListFor(peerID, cause) - switch { - case err == nil: - return causes, nil - case err == ErrDisallowCacheEntityNotFound: - // if the entity not exist, we initialize it and try again. - // Note: there is an edge case where the entity is initialized by another goroutine between the two calls. - // In this case, the init function is invoked twice, but it is not a problem because the underlying - // cache is thread-safe. Hence, we do not need to synchronize the two calls. In such cases, one of the - // two calls returns false, and the other call returns true. We do not care which call returns false, hence, - // we ignore the return value of the init function. - _ = d.init(peerID) - causes, err = d.disallowListFor(peerID, cause) - if err != nil { - // any error after the init is irrecoverable. - return nil, fmt.Errorf("failed to disallow list peer %s for cause %s: %w", peerID, cause, err) + entityId := makeId(peerID) + initLogic := func() flow.Entity { + return &disallowListCacheEntity{ + peerID: peerID, + causes: make(map[network.DisallowListedCause]struct{}), + entityId: entityId, } - return causes, nil - default: - return nil, fmt.Errorf("failed to disallow list peer %s for cause %s: %w", peerID, cause, err) } -} - -// disallowListFor is a helper function for disallowing a peer for a cause. -// It adds the cause to the disallow list cache entity for the peerID and returns the updated list of causes for the peer. -// Args: -// - peerID: the peerID of the peer to be disallow-listed. -// - cause: the cause for disallow-listing the peer. -// Returns: -// - the updated list of causes for the peer. -// - error if the entity for the peerID is not found in the cache it returns ErrDisallowCacheEntityNotFound, which is a benign error. -func (d *DisallowListCache) disallowListFor(peerID peer.ID, cause network.DisallowListedCause) ([]network.DisallowListedCause, error) { - adjustedEntity, adjusted := d.c.Adjust(makeId(peerID), func(entity flow.Entity) flow.Entity { + adjustLogic := func(entity flow.Entity) flow.Entity { dEntity := mustBeDisallowListEntity(entity) dEntity.causes[cause] = struct{}{} return dEntity - }) - + } + adjustedEntity, adjusted := d.c.AdjustWithInit(entityId, adjustLogic, initLogic) if !adjusted { - // if the entity is not found in the cache, we return a benign error. - return nil, ErrDisallowCacheEntityNotFound + return nil, fmt.Errorf("failed to disallow list peer %s for cause %s", peerID, cause) } dEntity := mustBeDisallowListEntity(adjustedEntity) + // returning a deep copy of causes (to avoid being mutated externally). updatedCauses := make([]network.DisallowListedCause, 0, len(dEntity.causes)) for c := range dEntity.causes { updatedCauses = append(updatedCauses, c) diff --git a/network/p2p/p2pnode/internal/cacheEntity.go b/network/p2p/node/internal/cacheEntity.go similarity index 96% rename from network/p2p/p2pnode/internal/cacheEntity.go rename to network/p2p/node/internal/cacheEntity.go index e55b0d127b5..119a5b42c1d 100644 --- a/network/p2p/p2pnode/internal/cacheEntity.go +++ b/network/p2p/node/internal/cacheEntity.go @@ -16,7 +16,7 @@ type disallowListCacheEntity struct { causes map[network.DisallowListedCause]struct{} // id is the hash of the peerID which is used as the key for storing the entity in the cache. // we cache it internally to avoid hashing the peerID multiple times. - id flow.Identifier + entityId flow.Identifier } var _ flow.Entity = (*disallowListCacheEntity)(nil) @@ -25,7 +25,7 @@ var _ flow.Entity = (*disallowListCacheEntity)(nil) // Returns: // - the hash of the peerID as a flow.Identifier. func (d *disallowListCacheEntity) ID() flow.Identifier { - return d.id + return d.entityId } // Checksum returns the hash of the peerID, there is no use for this method in the cache. It is implemented to satisfy @@ -33,7 +33,7 @@ func (d *disallowListCacheEntity) ID() flow.Identifier { // Returns: // - the hash of the peerID as a flow.Identifier. func (d *disallowListCacheEntity) Checksum() flow.Identifier { - return d.id + return d.entityId } // makeId is a helper function for creating the id field of the disallowListCacheEntity by hashing the peerID. diff --git a/network/p2p/p2pnode/internal/cache_test.go b/network/p2p/node/internal/cache_test.go similarity index 99% rename from network/p2p/p2pnode/internal/cache_test.go rename to network/p2p/node/internal/cache_test.go index d4ab02d5c9b..b5fc0533034 100644 --- a/network/p2p/p2pnode/internal/cache_test.go +++ b/network/p2p/node/internal/cache_test.go @@ -12,7 +12,7 @@ import ( "github.com/onflow/flow-go/module/metrics" "github.com/onflow/flow-go/network" - "github.com/onflow/flow-go/network/p2p/p2pnode/internal" + "github.com/onflow/flow-go/network/p2p/node/internal" "github.com/onflow/flow-go/utils/unittest" ) diff --git a/network/p2p/p2pnode/protocolPeerCache.go b/network/p2p/node/internal/protocolPeerCache.go similarity index 97% rename from network/p2p/p2pnode/protocolPeerCache.go rename to network/p2p/node/internal/protocolPeerCache.go index 125d9aa3b37..8cfe67220c4 100644 --- a/network/p2p/p2pnode/protocolPeerCache.go +++ b/network/p2p/node/internal/protocolPeerCache.go @@ -1,4 +1,4 @@ -package p2pnode +package internal import ( "fmt" @@ -11,7 +11,7 @@ import ( "github.com/libp2p/go-libp2p/core/protocol" "github.com/rs/zerolog" - "github.com/onflow/flow-go/network/p2p/p2plogging" + p2plogging "github.com/onflow/flow-go/network/p2p/logging" ) // ProtocolPeerCache store a mapping from protocol ID to peers who support that protocol diff --git a/network/p2p/p2pnode/protocolPeerCache_test.go b/network/p2p/node/internal/protocolPeerCache_test.go similarity index 85% rename from network/p2p/p2pnode/protocolPeerCache_test.go rename to network/p2p/node/internal/protocolPeerCache_test.go index cc15d6cfc87..ffb7eaf2828 100644 --- a/network/p2p/p2pnode/protocolPeerCache_test.go +++ b/network/p2p/node/internal/protocolPeerCache_test.go @@ -1,4 +1,4 @@ -package p2pnode_test +package internal_test import ( "context" @@ -8,13 +8,13 @@ import ( "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/network" "github.com/libp2p/go-libp2p/core/protocol" + "github.com/onflow/crypto" "github.com/rs/zerolog" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - fcrypto "github.com/onflow/flow-go/crypto" - "github.com/onflow/flow-go/network/p2p/p2pbuilder" - "github.com/onflow/flow-go/network/p2p/p2pnode" + p2pbuilder "github.com/onflow/flow-go/network/p2p/builder" + "github.com/onflow/flow-go/network/p2p/node/internal" "github.com/onflow/flow-go/utils/unittest" ) @@ -23,13 +23,13 @@ func TestProtocolPeerCache(t *testing.T) { defer cancel() // create three hosts, and a pcache for the first - h1, err := p2pbuilder.DefaultLibP2PHost(unittest.DefaultAddress, unittest.KeyFixture(fcrypto.ECDSASecp256k1)) + h1, err := p2pbuilder.DefaultLibP2PHost(unittest.DefaultAddress, unittest.KeyFixture(crypto.ECDSASecp256k1)) require.NoError(t, err) - pcache, err := p2pnode.NewProtocolPeerCache(zerolog.Nop(), h1) + pcache, err := internal.NewProtocolPeerCache(zerolog.Nop(), h1) require.NoError(t, err) - h2, err := p2pbuilder.DefaultLibP2PHost(unittest.DefaultAddress, unittest.KeyFixture(fcrypto.ECDSASecp256k1)) + h2, err := p2pbuilder.DefaultLibP2PHost(unittest.DefaultAddress, unittest.KeyFixture(crypto.ECDSASecp256k1)) require.NoError(t, err) - h3, err := p2pbuilder.DefaultLibP2PHost(unittest.DefaultAddress, unittest.KeyFixture(fcrypto.ECDSASecp256k1)) + h3, err := p2pbuilder.DefaultLibP2PHost(unittest.DefaultAddress, unittest.KeyFixture(crypto.ECDSASecp256k1)) require.NoError(t, err) // register each host on a separate protocol diff --git a/network/p2p/p2pnode/libp2pNode.go b/network/p2p/node/libp2pNode.go similarity index 90% rename from network/p2p/p2pnode/libp2pNode.go rename to network/p2p/node/libp2pNode.go index e22d3dbaa44..c65455c0d88 100644 --- a/network/p2p/p2pnode/libp2pNode.go +++ b/network/p2p/node/libp2pNode.go @@ -8,6 +8,7 @@ import ( "sync" "time" + "github.com/go-playground/validator/v10" "github.com/hashicorp/go-multierror" dht "github.com/libp2p/go-libp2p-kad-dht" kbucket "github.com/libp2p/go-libp2p-kbucket" @@ -25,8 +26,8 @@ import ( "github.com/onflow/flow-go/network/channels" "github.com/onflow/flow-go/network/internal/p2putils" "github.com/onflow/flow-go/network/p2p" - "github.com/onflow/flow-go/network/p2p/p2plogging" - "github.com/onflow/flow-go/network/p2p/p2pnode/internal" + p2plogging "github.com/onflow/flow-go/network/p2p/logging" + nodeinternal "github.com/onflow/flow-go/network/p2p/node/internal" "github.com/onflow/flow-go/network/p2p/unicast/protocols" "github.com/onflow/flow-go/utils/logging" ) @@ -64,29 +65,42 @@ type Node struct { // Cache of temporary disallow-listed peers, when a peer is disallow-listed, the connections to that peer // are closed and further connections are not allowed till the peer is removed from the disallow-list. disallowListedCache p2p.DisallowListCache + parameters *p2p.NodeParameters } // NewNode creates a new libp2p node and sets its parameters. -func NewNode( - logger zerolog.Logger, - host host.Host, - pCache p2p.ProtocolPeerCache, - peerManager p2p.PeerManager, - disallowLstCacheCfg *p2p.DisallowListCacheConfig, -) *Node { +// Args: +// - cfg: The configuration for the libp2p node. +// +// Returns: +// - *Node: The created libp2p node. +// +// - error: An error, if any occurred during the process. This includes failure in creating the node. The returned error is irrecoverable, and the node cannot be used. +func NewNode(cfg *p2p.NodeConfig) (*Node, error) { + err := validator.New().Struct(cfg) + if err != nil { + return nil, fmt.Errorf("invalid config: %w", err) + } + + pCache, err := nodeinternal.NewProtocolPeerCache(cfg.Logger, cfg.Host) + if err != nil { + return nil, fmt.Errorf("failed to create protocol peer cache: %w", err) + } + return &Node{ - host: host, - logger: logger.With().Str("component", "libp2p-node").Logger(), + host: cfg.Host, + logger: cfg.Logger.With().Str("component", "libp2p-node").Logger(), topics: make(map[channels.Topic]p2p.Topic), subs: make(map[channels.Topic]p2p.Subscription), pCache: pCache, - peerManager: peerManager, - disallowListedCache: internal.NewDisallowListCache( - disallowLstCacheCfg.MaxSize, - logger.With().Str("module", "disallow-list-cache").Logger(), - disallowLstCacheCfg.Metrics, + peerManager: cfg.PeerManager, + parameters: cfg.Parameters, + disallowListedCache: nodeinternal.NewDisallowListCache( + cfg.DisallowListCacheCfg.MaxSize, + cfg.Logger.With().Str("module", "disallow-list-cache").Logger(), + cfg.DisallowListCacheCfg.Metrics, ), - } + }, nil } func (n *Node) Start(ctx irrecoverable.SignalerContext) { @@ -176,8 +190,7 @@ func (n *Node) GetPeersForProtocol(pid protocol.ID) peer.IDSlice { return peers } -// OpenProtectedStream opens a new stream to a peer with a protection tag. The protection tag can be used to ensure -// that the connection to the peer is maintained for a particular purpose. The stream is opened to the given peerID +// OpenAndWriteOnStream opens a new stream to a peer. The stream is opened to the given peerID // and writingLogic is executed on the stream. The created stream does not need to be reused and can be inexpensively // created for each send. Moreover, the stream creation does not incur a round-trip time as the stream negotiation happens // on an existing connection. @@ -194,9 +207,14 @@ func (n *Node) GetPeersForProtocol(pid protocol.ID) peer.IDSlice { // error: An error, if any occurred during the process. This includes failure in creating the stream, setting the write // deadline, executing the writing logic, resetting the stream if the writing logic fails, or closing the stream. // All returned errors during this process can be considered benign. -func (n *Node) OpenProtectedStream(ctx context.Context, peerID peer.ID, protectionTag string, writingLogic func(stream libp2pnet.Stream) error) error { - n.host.ConnManager().Protect(peerID, protectionTag) - defer n.host.ConnManager().Unprotect(peerID, protectionTag) +func (n *Node) OpenAndWriteOnStream(ctx context.Context, peerID peer.ID, protectionTag string, writingLogic func(stream libp2pnet.Stream) error) error { + lg := n.logger.With().Str("remote_peer_id", p2plogging.PeerId(peerID)).Logger() + if n.parameters.EnableProtectedStreams { + n.host.ConnManager().Protect(peerID, protectionTag) + defer n.host.ConnManager().Unprotect(peerID, protectionTag) + lg = lg.With().Str("protection_tag", protectionTag).Logger() + lg.Trace().Msg("attempting to create protected stream") + } // streams don't need to be reused and are fairly inexpensive to be created for each send. // A stream creation does NOT incur an RTT as stream negotiation happens on an existing connection. @@ -204,12 +222,14 @@ func (n *Node) OpenProtectedStream(ctx context.Context, peerID peer.ID, protecti if err != nil { return fmt.Errorf("failed to create stream for %s: %w", peerID, err) } + lg.Trace().Msg("successfully created stream") deadline, _ := ctx.Deadline() err = s.SetWriteDeadline(deadline) if err != nil { return fmt.Errorf("failed to set write deadline for stream: %w", err) } + lg.Trace().Msg("successfully set write deadline on stream") err = writingLogic(s) if err != nil { @@ -224,12 +244,14 @@ func (n *Node) OpenProtectedStream(ctx context.Context, peerID peer.ID, protecti return fmt.Errorf("writing logic failed for %s: %w", peerID, err) } + lg.Trace().Msg("successfully wrote on stream") // close the stream immediately err = s.Close() if err != nil { return fmt.Errorf("failed to close the stream for %s: %w", peerID, err) } + lg.Trace().Msg("successfully closed stream") return nil } @@ -558,6 +580,15 @@ func (n *Node) SetPubSub(ps p2p.PubSubAdapter) { n.pubSub = ps } +// GetLocalMeshPeers returns the list of peers in the local mesh for the given topic. +// Args: +// - topic: the topic. +// Returns: +// - []peer.ID: the list of peers in the local mesh for the given topic. +func (n *Node) GetLocalMeshPeers(topic channels.Topic) []peer.ID { + return n.pubSub.GetLocalMeshPeers(topic) +} + // SetComponentManager sets the component manager for the node. // SetComponentManager may be called at most once. func (n *Node) SetComponentManager(cm *component.ComponentManager) { diff --git a/network/p2p/p2pnode/libp2pNode_test.go b/network/p2p/node/libp2pNode_test.go similarity index 95% rename from network/p2p/p2pnode/libp2pNode_test.go rename to network/p2p/node/libp2pNode_test.go index 9ec66082a0e..d301cf48733 100644 --- a/network/p2p/p2pnode/libp2pNode_test.go +++ b/network/p2p/node/libp2pNode_test.go @@ -20,7 +20,7 @@ import ( "github.com/onflow/flow-go/network/internal/p2pfixtures" "github.com/onflow/flow-go/network/internal/p2putils" "github.com/onflow/flow-go/network/p2p" - "github.com/onflow/flow-go/network/p2p/p2plogging" + p2plogging "github.com/onflow/flow-go/network/p2p/logging" p2ptest "github.com/onflow/flow-go/network/p2p/test" "github.com/onflow/flow-go/network/p2p/utils" validator "github.com/onflow/flow-go/network/validator/pubsub" @@ -193,27 +193,27 @@ func TestConnGater(t *testing.T) { node1.Host().Peerstore().AddAddrs(node2Info.ID, node2Info.Addrs, peerstore.PermanentAddrTTL) node2.Host().Peerstore().AddAddrs(node1Info.ID, node1Info.Addrs, peerstore.PermanentAddrTTL) - err = node1.OpenProtectedStream(ctx, node2Info.ID, t.Name(), func(stream network.Stream) error { + err = node1.OpenAndWriteOnStream(ctx, node2Info.ID, t.Name(), func(stream network.Stream) error { // no-op, as the connection should not be possible return nil }) require.ErrorContains(t, err, "target node is not on the approved list of nodes") - err = node2.OpenProtectedStream(ctx, node1Info.ID, t.Name(), func(stream network.Stream) error { + err = node2.OpenAndWriteOnStream(ctx, node1Info.ID, t.Name(), func(stream network.Stream) error { // no-op, as the connection should not be possible return nil }) require.ErrorContains(t, err, "target node is not on the approved list of nodes") node1Peers.Add(node2Info.ID, struct{}{}) - err = node1.OpenProtectedStream(ctx, node2Info.ID, t.Name(), func(stream network.Stream) error { + err = node1.OpenAndWriteOnStream(ctx, node2Info.ID, t.Name(), func(stream network.Stream) error { // no-op, as the connection should not be possible return nil }) require.Error(t, err) node2Peers.Add(node1Info.ID, struct{}{}) - err = node1.OpenProtectedStream(ctx, node2Info.ID, t.Name(), func(stream network.Stream) error { + err = node1.OpenAndWriteOnStream(ctx, node2Info.ID, t.Name(), func(stream network.Stream) error { // no-op, as the connection should not be possible return nil }) @@ -303,7 +303,7 @@ func createConcurrentStreams(t *testing.T, ctx context.Context, nodes []p2p.LibP wg.Add(1) go func(sender p2p.LibP2PNode) { defer wg.Done() - err := sender.OpenProtectedStream(ctx, pInfo.ID, t.Name(), func(stream network.Stream) error { + err := sender.OpenAndWriteOnStream(ctx, pInfo.ID, t.Name(), func(stream network.Stream) error { streams <- stream // wait for the done signal to close the stream diff --git a/network/p2p/p2pnode/libp2pStream_test.go b/network/p2p/node/libp2pStream_test.go similarity index 96% rename from network/p2p/p2pnode/libp2pStream_test.go rename to network/p2p/node/libp2pStream_test.go index 8131864b41d..46d291f3af5 100644 --- a/network/p2p/p2pnode/libp2pStream_test.go +++ b/network/p2p/node/libp2pStream_test.go @@ -58,7 +58,7 @@ func TestStreamClosing(t *testing.T) { go func(i int) { // Create stream from node 1 to node 2 (reuse if one already exists) nodes[0].Host().Peerstore().AddAddrs(nodeInfo1.ID, nodeInfo1.Addrs, peerstore.AddressTTL) - err := nodes[0].OpenProtectedStream(ctx, nodeInfo1.ID, t.Name(), func(s network.Stream) error { + err := nodes[0].OpenAndWriteOnStream(ctx, nodeInfo1.ID, t.Name(), func(s network.Stream) error { w := bufio.NewWriter(s) // Send message from node 1 to 2 @@ -165,7 +165,7 @@ func testCreateStream(t *testing.T, sporkId flow.Identifier, unicasts []protocol require.NoError(t, err) nodes[0].Host().Peerstore().AddAddrs(pInfo.ID, pInfo.Addrs, peerstore.AddressTTL) go func() { - err := nodes[0].OpenProtectedStream(ctx, pInfo.ID, t.Name(), func(stream network.Stream) error { + err := nodes[0].OpenAndWriteOnStream(ctx, pInfo.ID, t.Name(), func(stream network.Stream) error { require.NotNil(t, stream) streams = append(streams, stream) // if we return this function, the stream will be closed, but we need to keep it open for the test @@ -242,7 +242,7 @@ func TestCreateStream_FallBack(t *testing.T) { // a new stream must be created go func() { - err = thisNode.OpenProtectedStream(ctx, pInfo.ID, t.Name(), func(stream network.Stream) error { + err = thisNode.OpenAndWriteOnStream(ctx, pInfo.ID, t.Name(), func(stream network.Stream) error { require.NotNil(t, stream) streams = append(streams, stream) @@ -300,7 +300,7 @@ func TestCreateStreamIsConcurrencySafe(t *testing.T) { createStream := func() { <-gate nodes[0].Host().Peerstore().AddAddrs(nodeInfo1.ID, nodeInfo1.Addrs, peerstore.AddressTTL) - err := nodes[0].OpenProtectedStream(ctx, nodeInfo1.ID, t.Name(), func(stream network.Stream) error { + err := nodes[0].OpenAndWriteOnStream(ctx, nodeInfo1.ID, t.Name(), func(stream network.Stream) error { // no-op stream writer, we just check that the stream was created return nil }) @@ -358,7 +358,7 @@ func TestNoBackoffWhenCreatingStream(t *testing.T) { cfg, err := config.DefaultConfig() require.NoError(t, err) - maxTimeToWait := time.Duration(cfg.NetworkConfig.UnicastConfig.MaxStreamCreationRetryAttemptTimes) * unicast.MaxRetryJitter * time.Millisecond + maxTimeToWait := time.Duration(cfg.NetworkConfig.Unicast.UnicastManager.MaxStreamCreationRetryAttemptTimes) * unicast.MaxRetryJitter * time.Millisecond // need to add some buffer time so that RequireReturnsBefore waits slightly longer than maxTimeToWait to avoid // a race condition @@ -384,7 +384,7 @@ func TestNoBackoffWhenCreatingStream(t *testing.T) { ctx, cancel := context.WithTimeout(ctx, maxTimeToWait) unittest.RequireReturnsBefore(t, func() { - err = node1.OpenProtectedStream(ctx, pInfo.ID, t.Name(), func(stream network.Stream) error { + err = node1.OpenAndWriteOnStream(ctx, pInfo.ID, t.Name(), func(stream network.Stream) error { // do nothing, this is a no-op stream writer, we just check that the stream was created return nil }) @@ -523,7 +523,7 @@ func TestCreateStreamTimeoutWithUnresponsiveNode(t *testing.T) { unittest.AssertReturnsBefore(t, func() { nodes[0].Host().Peerstore().AddAddrs(silentNodeInfo.ID, silentNodeInfo.Addrs, peerstore.AddressTTL) - err = nodes[0].OpenProtectedStream(tctx, silentNodeInfo.ID, t.Name(), func(stream network.Stream) error { + err = nodes[0].OpenAndWriteOnStream(tctx, silentNodeInfo.ID, t.Name(), func(stream network.Stream) error { // do nothing, this is a no-op stream writer, we just check that the stream was created return nil }) @@ -564,7 +564,7 @@ func TestCreateStreamIsConcurrent(t *testing.T) { func() { goodNodes[0].Host().Peerstore().AddAddrs(silentNodeInfo.ID, silentNodeInfo.Addrs, peerstore.AddressTTL) // the subsequent call will be blocked - _ = goodNodes[0].OpenProtectedStream(ctx, silentNodeInfo.ID, t.Name(), func(stream network.Stream) error { + _ = goodNodes[0].OpenAndWriteOnStream(ctx, silentNodeInfo.ID, t.Name(), func(stream network.Stream) error { // do nothing, the stream creation will be blocked so this should never be called require.Fail(t, "this should never be called") return nil @@ -575,7 +575,7 @@ func TestCreateStreamIsConcurrent(t *testing.T) { unittest.RequireReturnsBefore(t, func() { goodNodes[0].Host().Peerstore().AddAddrs(goodNodeInfo1.ID, goodNodeInfo1.Addrs, peerstore.AddressTTL) - err := goodNodes[0].OpenProtectedStream(ctx, goodNodeInfo1.ID, t.Name(), func(stream network.Stream) error { + err := goodNodes[0].OpenAndWriteOnStream(ctx, goodNodeInfo1.ID, t.Name(), func(stream network.Stream) error { // do nothing, this is a no-op stream writer, we just check that the stream was created return nil }) diff --git a/network/p2p/p2pnode/libp2pUtils_test.go b/network/p2p/node/libp2pUtils_test.go similarity index 98% rename from network/p2p/p2pnode/libp2pUtils_test.go rename to network/p2p/node/libp2pUtils_test.go index 7d4d676c66d..e06a46f61e6 100644 --- a/network/p2p/p2pnode/libp2pUtils_test.go +++ b/network/p2p/node/libp2pUtils_test.go @@ -6,16 +6,14 @@ import ( "github.com/libp2p/go-libp2p/core/peer" "github.com/multiformats/go-multiaddr" + "github.com/onflow/crypto" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" - "github.com/onflow/flow-go/network/p2p/utils" - - "github.com/onflow/flow-go/crypto" - "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/network/p2p/keyutils" + "github.com/onflow/flow-go/network/p2p/utils" "github.com/onflow/flow-go/utils/unittest" ) diff --git a/network/p2p/p2pnode/resourceManager_test.go b/network/p2p/node/resourceManager_test.go similarity index 97% rename from network/p2p/p2pnode/resourceManager_test.go rename to network/p2p/node/resourceManager_test.go index e2b49b1c8b7..42a53c3b5b4 100644 --- a/network/p2p/p2pnode/resourceManager_test.go +++ b/network/p2p/node/resourceManager_test.go @@ -13,6 +13,7 @@ import ( rcmgr "github.com/libp2p/go-libp2p/p2p/host/resource-manager" "github.com/stretchr/testify/require" + "github.com/onflow/flow-go/config" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/module/irrecoverable" mockmodule "github.com/onflow/flow-go/module/mock" @@ -33,6 +34,10 @@ func TestCreateStream_InboundConnResourceLimit(t *testing.T) { defer cancel() signalerCtx := irrecoverable.NewMockSignalerContext(t, ctx) + cfg, err := config.DefaultConfig() + require.NoError(t, err) + cfg.NetworkConfig.Unicast.UnicastManager.CreateStreamBackoffDelay = 10 * time.Millisecond + sporkID := unittest.IdentifierFixture() sender, id1 := p2ptest.NodeFixture( @@ -41,7 +46,7 @@ func TestCreateStream_InboundConnResourceLimit(t *testing.T) { t.Name(), idProvider, p2ptest.WithDefaultResourceManager(), - p2ptest.WithCreateStreamRetryDelay(10*time.Millisecond)) + p2ptest.OverrideFlowConfig(cfg)) receiver, id2 := p2ptest.NodeFixture( t, @@ -49,7 +54,7 @@ func TestCreateStream_InboundConnResourceLimit(t *testing.T) { t.Name(), idProvider, p2ptest.WithDefaultResourceManager(), - p2ptest.WithCreateStreamRetryDelay(10*time.Millisecond)) + p2ptest.OverrideFlowConfig(cfg)) idProvider.On("ByPeerID", sender.ID()).Return(&id1, true).Maybe() idProvider.On("ByPeerID", receiver.ID()).Return(&id2, true).Maybe() @@ -284,9 +289,12 @@ func testCreateStreamInboundStreamResourceLimits(t *testing.T, cfg *testPeerLimi ctx, cancel := context.WithCancel(context.Background()) defer cancel() signalerCtx := irrecoverable.NewMockSignalerContext(t, ctx) - sporkID := unittest.IdentifierFixture() + flowCfg, err := config.DefaultConfig() + require.NoError(t, err) + flowCfg.NetworkConfig.Unicast.UnicastManager.CreateStreamBackoffDelay = 10 * time.Millisecond + // sender nodes will have infinite stream limit to ensure that they can create as many streams as they want. resourceManagerSnd, err := rcmgr.NewResourceManager(rcmgr.NewFixedLimiter(rcmgr.InfiniteLimits)) require.NoError(t, err) @@ -295,7 +303,7 @@ func testCreateStreamInboundStreamResourceLimits(t *testing.T, cfg *testPeerLimi t.Name(), cfg.nodeCount, idProvider, p2ptest.WithResourceManager(resourceManagerSnd), - p2ptest.WithCreateStreamRetryDelay(10*time.Millisecond)) + p2ptest.OverrideFlowConfig(flowCfg)) // receiver node will run with default limits and no scaling. limits := rcmgr.DefaultLimits @@ -334,7 +342,7 @@ func testCreateStreamInboundStreamResourceLimits(t *testing.T, cfg *testPeerLimi t.Name(), idProvider, p2ptest.WithResourceManager(resourceManagerRcv), - p2ptest.WithCreateStreamRetryDelay(10*time.Millisecond)) + p2ptest.OverrideFlowConfig(flowCfg)) for i, sender := range senders { idProvider.On("ByPeerID", sender.ID()).Return(senderIds[i], true).Maybe() diff --git a/network/p2p/p2pconf/gossipsub_rpc_inspectors.go b/network/p2p/p2pconf/gossipsub_rpc_inspectors.go deleted file mode 100644 index 3d3cea79b21..00000000000 --- a/network/p2p/p2pconf/gossipsub_rpc_inspectors.go +++ /dev/null @@ -1,79 +0,0 @@ -package p2pconf - -// GossipSubRPCInspectorsConfig encompasses configuration related to gossipsub RPC message inspectors. -type GossipSubRPCInspectorsConfig struct { - // GossipSubRPCValidationInspectorConfigs control message validation inspector validation configuration and limits. - GossipSubRPCValidationInspectorConfigs `mapstructure:",squash"` - // GossipSubRPCMetricsInspectorConfigs control message metrics inspector configuration. - GossipSubRPCMetricsInspectorConfigs `mapstructure:",squash"` - // GossipSubRPCInspectorNotificationCacheSize size of the queue for notifications about invalid RPC messages. - GossipSubRPCInspectorNotificationCacheSize uint32 `mapstructure:"gossipsub-rpc-inspector-notification-cache-size"` -} - -// GossipSubRPCValidationInspectorConfigs validation limits used for gossipsub RPC control message inspection. -type GossipSubRPCValidationInspectorConfigs struct { - ClusterPrefixedMessageConfig `mapstructure:",squash"` - IWantRPCInspectionConfig `mapstructure:",squash"` - IHaveRPCInspectionConfig `mapstructure:",squash"` - // NumberOfWorkers number of worker pool workers. - NumberOfWorkers int `validate:"gte=1" mapstructure:"gossipsub-rpc-validation-inspector-workers"` - // CacheSize size of the queue used by worker pool for the control message validation inspector. - CacheSize uint32 `validate:"gte=100" mapstructure:"gossipsub-rpc-validation-inspector-queue-cache-size"` - // GraftPruneMessageMaxSampleSize the max sample size used for control message validation of GRAFT and PRUNE. If the total number of control messages (GRAFT or PRUNE) - // exceeds this max sample size then the respective message will be truncated to this value before being processed. - GraftPruneMessageMaxSampleSize int `validate:"gte=1000" mapstructure:"gossipsub-rpc-graft-and-prune-message-max-sample-size"` - // RPCMessageMaxSampleSize the max sample size used for RPC message validation. If the total number of RPC messages exceeds this value a sample will be taken but messages will not be truncated. - RpcMessageMaxSampleSize int `validate:"gte=1000" mapstructure:"gossipsub-rpc-message-max-sample-size"` - // RPCMessageErrorThreshold the threshold at which an error will be returned if the number of invalid RPC messages exceeds this value. - RpcMessageErrorThreshold int `validate:"gte=500" mapstructure:"gossipsub-rpc-message-error-threshold"` -} - -// IWantRPCInspectionConfig validation configuration for iWANT RPC control messages. -type IWantRPCInspectionConfig struct { - // MaxSampleSize max inspection sample size to use. If the total number of iWant control messages - // exceeds this max sample size then the respective message will be truncated before being processed. - MaxSampleSize uint `validate:"gt=0" mapstructure:"gossipsub-rpc-iwant-max-sample-size"` - // MaxMessageIDSampleSize max inspection sample size to use for iWant message ids. Each iWant message includes a list of message ids - // each, if the size of this list exceeds the configured max message id sample size the list of message ids will be truncated. - MaxMessageIDSampleSize int `validate:"gte=1000" mapstructure:"gossipsub-rpc-iwant-max-message-id-sample-size"` - // CacheMissThreshold the threshold of missing corresponding iHave messages for iWant messages received before an invalid control message notification is disseminated. - // If the cache miss threshold is exceeded an invalid control message notification is disseminated and the sender will be penalized. - CacheMissThreshold float64 `validate:"gt=0" mapstructure:"gossipsub-rpc-iwant-cache-miss-threshold"` - // CacheMissCheckSize the iWants size at which message id cache misses will be checked. - CacheMissCheckSize int `validate:"gt=0" mapstructure:"gossipsub-rpc-iwant-cache-miss-check-size"` - // DuplicateMsgIDThreshold maximum allowed duplicate message IDs in a single iWant control message. - // If the duplicate message threshold is exceeded an invalid control message notification is disseminated and the sender will be penalized. - DuplicateMsgIDThreshold float64 `validate:"gt=0" mapstructure:"gossipsub-rpc-iwant-duplicate-message-id-threshold"` -} - -// IHaveRPCInspectionConfig validation configuration for iHave RPC control messages. -type IHaveRPCInspectionConfig struct { - // MaxSampleSize max inspection sample size to use. If the number of ihave messages exceeds this configured value - // the control message ihaves will be truncated to the max sample size. This sample is randomly selected. - MaxSampleSize int `validate:"gte=1000" mapstructure:"gossipsub-rpc-ihave-max-sample-size"` - // MaxMessageIDSampleSize max inspection sample size to use for iHave message ids. Each ihave message includes a list of message ids - // each, if the size of this list exceeds the configured max message id sample size the list of message ids will be truncated. - MaxMessageIDSampleSize int `validate:"gte=1000" mapstructure:"gossipsub-rpc-ihave-max-message-id-sample-size"` -} - -// ClusterPrefixedMessageConfig configuration values for cluster prefixed control message validation. -type ClusterPrefixedMessageConfig struct { - // ClusterPrefixHardThreshold the upper bound on the amount of cluster prefixed control messages that will be processed - // before a node starts to get penalized. This allows LN nodes to process some cluster prefixed control messages during startup - // when the cluster ID's provider is set asynchronously. It also allows processing of some stale messages that may be sent by nodes - // that fall behind in the protocol. After the amount of cluster prefixed control messages processed exceeds this threshold the node - // will be pushed to the edge of the network mesh. - ClusterPrefixHardThreshold float64 `validate:"gte=0" mapstructure:"gossipsub-rpc-cluster-prefixed-hard-threshold"` - // ClusterPrefixedControlMsgsReceivedCacheSize size of the cache used to track the amount of cluster prefixed topics received by peers. - ClusterPrefixedControlMsgsReceivedCacheSize uint32 `validate:"gt=0" mapstructure:"gossipsub-cluster-prefix-tracker-cache-size"` - // ClusterPrefixedControlMsgsReceivedCacheDecay decay val used for the geometric decay of cache counters used to keep track of cluster prefixed topics received by peers. - ClusterPrefixedControlMsgsReceivedCacheDecay float64 `validate:"gt=0" mapstructure:"gossipsub-cluster-prefix-tracker-cache-decay"` -} - -// GossipSubRPCMetricsInspectorConfigs rpc metrics observer inspector configuration. -type GossipSubRPCMetricsInspectorConfigs struct { - // NumberOfWorkers number of worker pool workers. - NumberOfWorkers int `validate:"gte=1" mapstructure:"gossipsub-rpc-metrics-inspector-workers"` - // CacheSize size of the queue used by worker pool for the control message metrics inspector. - CacheSize uint32 `validate:"gt=0" mapstructure:"gossipsub-rpc-metrics-inspector-cache-size"` -} diff --git a/network/p2p/p2pnode/gossipsubMetrics.go b/network/p2p/p2pnode/gossipsubMetrics.go deleted file mode 100644 index 4a06b7e6e7a..00000000000 --- a/network/p2p/p2pnode/gossipsubMetrics.go +++ /dev/null @@ -1,58 +0,0 @@ -package p2pnode - -import ( - pubsub "github.com/libp2p/go-libp2p-pubsub" - "github.com/libp2p/go-libp2p/core/peer" - "github.com/rs/zerolog" - - "github.com/onflow/flow-go/module" - "github.com/onflow/flow-go/network/p2p" - "github.com/onflow/flow-go/network/p2p/p2plogging" -) - -// GossipSubControlMessageMetrics is a metrics and observability wrapper component for the incoming RPCs to a -// GossipSub router. It records metrics on the number of control messages received in each RPC. -type GossipSubControlMessageMetrics struct { - metrics module.GossipSubRouterMetrics - logger zerolog.Logger -} - -var _ p2p.GossipSubControlMetricsObserver = (*GossipSubControlMessageMetrics)(nil) - -func NewGossipSubControlMessageMetrics(metrics module.GossipSubRouterMetrics, logger zerolog.Logger) *GossipSubControlMessageMetrics { - return &GossipSubControlMessageMetrics{ - logger: logger.With().Str("module", "gossipsub-control-message-metrics").Logger(), - metrics: metrics, - } -} - -// ObserveRPC is invoked to record metrics on incoming RPC messages. -func (o *GossipSubControlMessageMetrics) ObserveRPC(from peer.ID, rpc *pubsub.RPC) { - lg := o.logger.With().Str("peer_id", p2plogging.PeerId(from)).Logger() - includedMessages := len(rpc.GetPublish()) - - ctl := rpc.GetControl() - if ctl == nil && includedMessages == 0 { - lg.Trace().Msg("received rpc with no control message and no publish messages") - return - } - - iHaveCount := len(ctl.GetIhave()) - iWantCount := len(ctl.GetIwant()) - graftCount := len(ctl.GetGraft()) - pruneCount := len(ctl.GetPrune()) - - lg.Trace(). - Int("iHaveCount", iHaveCount). - Int("iWantCount", iWantCount). - Int("graftCount", graftCount). - Int("pruneCount", pruneCount). - Int("included_message_count", includedMessages). - Msg("received rpc with control messages") - - o.metrics.OnIHaveReceived(iHaveCount) - o.metrics.OnIWantReceived(iWantCount) - o.metrics.OnGraftReceived(graftCount) - o.metrics.OnPruneReceived(pruneCount) - o.metrics.OnPublishedGossipMessagesReceived(includedMessages) -} diff --git a/network/p2p/pubsub.go b/network/p2p/pubsub.go index d0ceb33fe8c..7b4833736bb 100644 --- a/network/p2p/pubsub.go +++ b/network/p2p/pubsub.go @@ -12,6 +12,7 @@ import ( "github.com/onflow/flow-go/engine/collection" "github.com/onflow/flow-go/module/component" + "github.com/onflow/flow-go/network/channels" ) type ValidationResult int @@ -54,6 +55,13 @@ type PubSubAdapter interface { // subscribed peers for topics A and B, and querying for topic C will return an empty list. ListPeers(topic string) []peer.ID + // GetLocalMeshPeers returns the list of peers in the local mesh for the given topic. + // Args: + // - topic: the topic. + // Returns: + // - []peer.ID: the list of peers in the local mesh for the given topic. + GetLocalMeshPeers(topic channels.Topic) []peer.ID + // PeerScoreExposer returns the peer score exposer for the gossipsub adapter. The exposer is a read-only interface // for querying peer scores and returns the local scoring table of the underlying gossipsub node. // The exposer is only available if the gossipsub adapter was configured with a score tracer. @@ -78,11 +86,6 @@ type PubSubAdapterConfig interface { WithInspectorSuite(GossipSubInspectorSuite) } -// GossipSubControlMetricsObserver funcs used to observe gossipsub related metrics. -type GossipSubControlMetricsObserver interface { - ObserveRPC(peer.ID, *pubsub.RPC) -} - // GossipSubRPCInspector app specific RPC inspector used to inspect and validate incoming RPC messages before they are processed by libp2p. // Implementations must: // - be concurrency safe @@ -172,6 +175,12 @@ type PubSubTracer interface { component.Component pubsub.RawTracer RpcControlTracking + // GetLocalMeshPeers returns the list of peers in the mesh for the given topic. + // Args: + // - topic: the topic. + // Returns: + // - []peer.ID: the list of peers in the mesh for the given topic. + GetLocalMeshPeers(topic channels.Topic) []peer.ID } // RpcControlTracking is the abstraction of the underlying libp2p control message tracker used to track message ids advertised by the iHave control messages. diff --git a/network/p2p/scoring/README.md b/network/p2p/scoring/README.md index 38a758db439..dda1cd7cb0b 100644 --- a/network/p2p/scoring/README.md +++ b/network/p2p/scoring/README.md @@ -258,4 +258,38 @@ This option is passed to the GossipSub at the time of initialization. ```go flowPubSubOption := scoreOption.BuildFlowPubSubScoreOption() gossipSubOption := scoreOption.BuildGossipSubScoreOption() +``` + +# Caching Application Specific Score +![app-specific-score-cache.png](app-specific-score-cache.png) +The application-specific score of a peer is part of its overall score in the GossipSub protocol. In contrast to the rest of the GossipSub score of the peer that is computed +internally by the GossipSub protocol, the application-specific score of a peer is computed externally by the application, i.e., the Flow protocol-level semantics. +As the figure above illustrates, GossipSub's peer scoring mechanism invokes the application-specific scoring function on a peer id upon receiving a gossip message from that peer. +This means that the application-specific score of a peer is computed every time a gossip message is received from that peer. +This can be computationally expensive, especially when the network is large and the number of gossip messages is high. +As shown by the figure above, each time the application-specific score of a peer is computed, the score is computed from scratch by +computing the spam penalty, staking score and subscription penalty. Each of these computations involves a cache lookup and a computation. +Hence, a single computation of the application-specific score of a peer involves 3 cache lookups and 3 computations. +As the application-specific score of a peer is not expected to change frequently, we can cache the score of a peer and reuse it for a certain period of time. +This can significantly reduce the computational overhead of the scoring mechanism. +By caching the application-specific score of a peer, we can reduce the number of cache lookups and computations from 3 to 1 per computation of the application-specific score of a peer, which +results in a 66% reduction in the computational overhead of the scoring mechanism. +The caching mechanism is implemented in the `GossipSubAppSpecificScoreRegistry` struct. Each time the application-specific score of a peer is requested by the GossipSub protocol, the registry +checks if the score of the peer is cached. If the score is cached, the cached score is returned. Otherwise, a score of zero is returned, and a request for the application-specific score of the peer is +queued to the `appScoreUpdateWorkerPool` to be computed asynchronously. Once the score is computed, it is cached and the score is updated in the `appScoreCache`. +Each score record in the cache is associated with a TTL (time-to-live) value, which is the duration for which the score is valid. +When the retrieved score is expired, the expired score is still returned to the GossipSub protocol, but the score is updated asynchronously in the background by submitting a request to the `appScoreUpdateWorkerPool`. +The app-specific score configuration values are configurable through the following parameters in the `default-config.yaml` file: +```yaml + scoring-parameters: + app-specific-score: + # number of workers that asynchronously update the app specific score requests when they are expired. + score-update-worker-num: 5 + # size of the queue used by the worker pool for the app specific score update requests. The queue is used to buffer the app specific score update requests + # before they are processed by the worker pool. The queue size must be larger than total number of peers in the network. + # The queue is deduplicated based on the peer ids ensuring that there is only one app specific score update request per peer in the queue. + score-update-request-queue-size: 10_000 + # score ttl is the time to live for the app specific score. Once the score is expired; a new request will be sent to the app specific score provider to update the score. + # until the score is updated, the previous score will be used. + score-ttl: 1m ``` \ No newline at end of file diff --git a/network/p2p/scoring/app-specific-score-cache.png b/network/p2p/scoring/app-specific-score-cache.png new file mode 100644 index 00000000000..fe5b24dcbe4 Binary files /dev/null and b/network/p2p/scoring/app-specific-score-cache.png differ diff --git a/network/p2p/scoring/app_score_test.go b/network/p2p/scoring/app_score_test.go index 39a0a405e8e..e373cd6ac0e 100644 --- a/network/p2p/scoring/app_score_test.go +++ b/network/p2p/scoring/app_score_test.go @@ -13,15 +13,13 @@ import ( "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/module/id" "github.com/onflow/flow-go/module/irrecoverable" - "github.com/onflow/flow-go/module/metrics" "github.com/onflow/flow-go/module/mock" "github.com/onflow/flow-go/network/channels" "github.com/onflow/flow-go/network/internal/p2pfixtures" "github.com/onflow/flow-go/network/message" "github.com/onflow/flow-go/network/p2p" - "github.com/onflow/flow-go/network/p2p/scoring" + p2pconfig "github.com/onflow/flow-go/network/p2p/config" p2ptest "github.com/onflow/flow-go/network/p2p/test" - "github.com/onflow/flow-go/network/p2p/tracer" flowpubsub "github.com/onflow/flow-go/network/validator/pubsub" "github.com/onflow/flow-go/utils/unittest" ) @@ -38,16 +36,13 @@ func TestFullGossipSubConnectivity(t *testing.T) { // two groups of non-access nodes and one group of access nodes. groupOneNodes, groupOneIds := p2ptest.NodesFixture(t, sporkId, t.Name(), 5, idProvider, - p2ptest.WithRole(flow.RoleConsensus), - p2ptest.EnablePeerScoringWithOverride(p2p.PeerScoringConfigNoOverride)) + p2ptest.WithRole(flow.RoleConsensus)) groupTwoNodes, groupTwoIds := p2ptest.NodesFixture(t, sporkId, t.Name(), 5, idProvider, - p2ptest.WithRole(flow.RoleCollection), - p2ptest.EnablePeerScoringWithOverride(p2p.PeerScoringConfigNoOverride)) + p2ptest.WithRole(flow.RoleCollection)) accessNodeGroup, accessNodeIds := p2ptest.NodesFixture(t, sporkId, t.Name(), 5, idProvider, - p2ptest.WithRole(flow.RoleAccess), - p2ptest.EnablePeerScoringWithOverride(p2p.PeerScoringConfigNoOverride)) + p2ptest.WithRole(flow.RoleAccess)) ids := append(append(groupOneIds, groupTwoIds...), accessNodeIds...) nodes := append(append(groupOneNodes, groupTwoNodes...), accessNodeGroup...) @@ -131,27 +126,13 @@ func TestFullGossipSubConnectivityAmongHonestNodesWithMaliciousMajority(t *testi sporkId := unittest.IdentifierFixture() idProvider := mock.NewIdentityProvider(t) - // two (honest) consensus nodes - opts := []p2ptest.NodeFixtureParameterOption{p2ptest.WithRole(flow.RoleConsensus)} - opts = append(opts, p2ptest.EnablePeerScoringWithOverride(p2p.PeerScoringConfigNoOverride)) - defaultConfig, err := config.DefaultConfig() require.NoError(t, err) - meshTracerCfg := &tracer.GossipSubMeshTracerConfig{ - Logger: unittest.Logger(), - Metrics: metrics.NewNoopCollector(), - IDProvider: idProvider, - LoggerInterval: time.Second, - HeroCacheMetricsFactory: metrics.NewNoopHeroCacheMetricsFactory(), - RpcSentTrackerCacheSize: defaultConfig.NetworkConfig.GossipSubConfig.RPCSentTrackerCacheSize, - RpcSentTrackerWorkerQueueCacheSize: defaultConfig.NetworkConfig.GossipSubConfig.RPCSentTrackerQueueCacheSize, - RpcSentTrackerNumOfWorkers: defaultConfig.NetworkConfig.GossipSubConfig.RpcSentTrackerNumOfWorkers, - } + // override the default config to make the mesh tracer log more frequently + defaultConfig.NetworkConfig.GossipSub.RpcTracer.LocalMeshLogInterval = time.Second - con1NodeTracer := tracer.NewGossipSubMeshTracer(meshTracerCfg) // mesh tracer for con1 - con2NodeTracer := tracer.NewGossipSubMeshTracer(meshTracerCfg) // mesh tracer for con2 - con1Node, con1Id := p2ptest.NodeFixture(t, sporkId, t.Name(), idProvider, append(opts, p2ptest.WithGossipSubTracer(con1NodeTracer))...) - con2Node, con2Id := p2ptest.NodeFixture(t, sporkId, t.Name(), idProvider, append(opts, p2ptest.WithGossipSubTracer(con2NodeTracer))...) + con1Node, con1Id := p2ptest.NodeFixture(t, sporkId, t.Name(), idProvider, p2ptest.WithRole(flow.RoleConsensus), p2ptest.OverrideFlowConfig(defaultConfig)) + con2Node, con2Id := p2ptest.NodeFixture(t, sporkId, t.Name(), idProvider, p2ptest.WithRole(flow.RoleConsensus), p2ptest.OverrideFlowConfig(defaultConfig)) // create > 2 * 12 malicious access nodes // 12 is the maximum size of default GossipSub mesh. @@ -161,7 +142,7 @@ func TestFullGossipSubConnectivityAmongHonestNodesWithMaliciousMajority(t *testi p2ptest.WithRole(flow.RoleAccess), // overrides the default peer scoring parameters to mute GossipSub traffic from/to honest nodes. p2ptest.EnablePeerScoringWithOverride(&p2p.PeerScoringConfigOverride{ - AppSpecificScoreParams: maliciousAppSpecificScore(flow.IdentityList{&con1Id, &con2Id}), + AppSpecificScoreParams: maliciousAppSpecificScore(flow.IdentityList{&con1Id, &con2Id}, defaultConfig.NetworkConfig.GossipSub.ScoringParameters.PeerScoring.Protocol), }), ) @@ -214,7 +195,7 @@ func TestFullGossipSubConnectivityAmongHonestNodesWithMaliciousMajority(t *testi for { select { case <-ticker.C: - con1BlockTopicPeers := con1NodeTracer.GetMeshPeers(blockTopic.String()) + con1BlockTopicPeers := con1Node.GetLocalMeshPeers(blockTopic) for _, p := range con1BlockTopicPeers { if p == con2Node.ID() { con2HasCon1 = true @@ -222,7 +203,7 @@ func TestFullGossipSubConnectivityAmongHonestNodesWithMaliciousMajority(t *testi } } - con2BlockTopicPeers := con2NodeTracer.GetMeshPeers(blockTopic.String()) + con2BlockTopicPeers := con2Node.GetLocalMeshPeers(blockTopic) for _, p := range con2BlockTopicPeers { if p == con1Node.ID() { con1HasCon2 = true @@ -242,14 +223,14 @@ func TestFullGossipSubConnectivityAmongHonestNodesWithMaliciousMajority(t *testi // maliciousAppSpecificScore returns a malicious app specific penalty function that rewards the malicious node and // punishes the honest nodes. -func maliciousAppSpecificScore(honestIds flow.IdentityList) func(peer.ID) float64 { +func maliciousAppSpecificScore(honestIds flow.IdentityList, optionCfg p2pconfig.ProtocolLevelGossipSubScoreParams) func(peer.ID) float64 { honestIdProvider := id.NewFixedIdentityProvider(honestIds) return func(p peer.ID) float64 { _, isHonest := honestIdProvider.ByPeerID(p) if isHonest { - return scoring.MaxAppSpecificPenalty + return optionCfg.AppSpecificScore.MaxAppSpecificPenalty } - return scoring.MaxAppSpecificReward + return optionCfg.AppSpecificScore.MaxAppSpecificReward } } diff --git a/network/p2p/scoring/decay_test.go b/network/p2p/scoring/decay_test.go index 281ed194f15..643d22fba83 100644 --- a/network/p2p/scoring/decay_test.go +++ b/network/p2p/scoring/decay_test.go @@ -287,7 +287,7 @@ func TestDefaultDecayFunction(t *testing.T) { record: p2p.GossipSubSpamRecord{ Penalty: -100, Decay: 0.8, - LastDecayAdjustment: time.Now().Add(-flowConfig.NetworkConfig.GossipSubConfig.GossipSubScoringRegistryConfig.PenaltyDecayEvaluationPeriod), + LastDecayAdjustment: time.Now().Add(-flowConfig.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.SpamRecordCache.Decay.PenaltyDecayEvaluationPeriod), }, lastUpdated: time.Now(), }, @@ -299,8 +299,9 @@ func TestDefaultDecayFunction(t *testing.T) { }, }, } - scoringRegistryConfig := flowConfig.NetworkConfig.GossipSubConfig.GossipSubScoringRegistryConfig - decayFunc := scoring.DefaultDecayFunction(scoringRegistryConfig.PenaltyDecaySlowdownThreshold, scoringRegistryConfig.DecayRateReductionFactor, scoringRegistryConfig.PenaltyDecayEvaluationPeriod) + scoringRegistryConfig := flowConfig.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters + decayFunc := scoring.DefaultDecayFunction(scoringRegistryConfig.SpamRecordCache.Decay) + for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := decayFunc(tt.args.record, tt.args.lastUpdated) diff --git a/network/p2p/scoring/internal/appSpecificScoreCache.go b/network/p2p/scoring/internal/appSpecificScoreCache.go new file mode 100644 index 00000000000..a818046b06c --- /dev/null +++ b/network/p2p/scoring/internal/appSpecificScoreCache.go @@ -0,0 +1,94 @@ +package internal + +import ( + "fmt" + "time" + + "github.com/libp2p/go-libp2p/core/peer" + "github.com/rs/zerolog" + + "github.com/onflow/flow-go/model/flow" + "github.com/onflow/flow-go/module" + herocache "github.com/onflow/flow-go/module/mempool/herocache/backdata" + "github.com/onflow/flow-go/module/mempool/herocache/backdata/heropool" + "github.com/onflow/flow-go/module/mempool/stdmap" + "github.com/onflow/flow-go/network/p2p" +) + +// AppSpecificScoreCache is a cache that stores the application specific score of peers. +// The application specific score of a peer is used to calculate the GossipSub score of the peer. +// Note that the application specific score and the GossipSub score are solely used by the current peer to select the peers +// to which it will connect on a topic mesh. +type AppSpecificScoreCache struct { + c *stdmap.Backend +} + +var _ p2p.GossipSubApplicationSpecificScoreCache = (*AppSpecificScoreCache)(nil) + +// NewAppSpecificScoreCache creates a new application specific score cache with the given size limit. +// The cache has an LRU eviction policy. +// Args: +// - sizeLimit: the size limit of the cache. +// - logger: the logger to use for logging. +// - collector: the metrics collector to use for collecting metrics. +// Returns: +// - *AppSpecificScoreCache: the created cache. +func NewAppSpecificScoreCache(sizeLimit uint32, logger zerolog.Logger, collector module.HeroCacheMetrics) *AppSpecificScoreCache { + backData := herocache.NewCache(sizeLimit, + herocache.DefaultOversizeFactor, + heropool.LRUEjection, + logger.With().Str("mempool", "gossipsub-app-specific-score-cache").Logger(), + collector) + + return &AppSpecificScoreCache{ + c: stdmap.NewBackend(stdmap.WithBackData(backData)), + } +} + +// Get returns the application specific score of a peer from the cache. +// Args: +// - peerID: the peer ID of the peer in the GossipSub protocol. +// Returns: +// - float64: the application specific score of the peer. +// - time.Time: the time at which the score was last updated. +// - bool: true if the score was retrieved successfully, false otherwise. +func (a *AppSpecificScoreCache) Get(peerID peer.ID) (float64, time.Time, bool) { + e, ok := a.c.ByID(entityIdOf(peerID)) + if !ok { + return 0, time.Time{}, false + } + return e.(appSpecificScoreRecordEntity).Score, e.(appSpecificScoreRecordEntity).LastUpdated, true +} + +// AdjustWithInit adds the application specific score of a peer to the cache. +// If the peer already has a score in the cache, the score is updated. +// Args: +// - peerID: the peer ID of the peer in the GossipSub protocol. +// - score: the application specific score of the peer. +// - time: the time at which the score was last updated. +// Returns: +// - error on failure to add the score. The returned error is irrecoverable and indicates an exception. +func (a *AppSpecificScoreCache) AdjustWithInit(peerID peer.ID, score float64, time time.Time) error { + entityId := entityIdOf(peerID) + + initLogic := func() flow.Entity { + return appSpecificScoreRecordEntity{ + entityId: entityId, + PeerID: peerID, + Score: score, + LastUpdated: time, + } + } + adjustLogic := func(entity flow.Entity) flow.Entity { + r := entity.(appSpecificScoreRecordEntity) + r.Score = score + r.LastUpdated = time + return r + } + _, adjusted := a.c.AdjustWithInit(entityId, adjustLogic, initLogic) + if !adjusted { + return fmt.Errorf("failed to adjust app specific score for peer %s", peerID) + } + + return nil +} diff --git a/network/p2p/scoring/internal/appSpecificScoreCache_test.go b/network/p2p/scoring/internal/appSpecificScoreCache_test.go new file mode 100644 index 00000000000..bea5f355833 --- /dev/null +++ b/network/p2p/scoring/internal/appSpecificScoreCache_test.go @@ -0,0 +1,166 @@ +package internal_test + +import ( + "sync" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/onflow/flow-go/module/metrics" + "github.com/onflow/flow-go/network/p2p/scoring/internal" + "github.com/onflow/flow-go/utils/unittest" +) + +// TestAppSpecificScoreCache tests the functionality of AppSpecificScoreCache; +// specifically, it tests the Add and Get methods. +// It does not test the eviction policy of the cache. +func TestAppSpecificScoreCache(t *testing.T) { + cache := internal.NewAppSpecificScoreCache(10, unittest.Logger(), metrics.NewNoopCollector()) + require.NotNil(t, cache, "failed to create AppSpecificScoreCache") + + peerID := unittest.PeerIdFixture(t) + score := 5.0 + updateTime := time.Now() + + err := cache.AdjustWithInit(peerID, score, updateTime) + require.Nil(t, err, "failed to add score to cache") + + // retrieve score from cache + retrievedScore, lastUpdated, found := cache.Get(peerID) + require.True(t, found, "failed to find score in cache") + require.Equal(t, score, retrievedScore, "retrieved score does not match expected") + require.Equal(t, updateTime, lastUpdated, "retrieved update time does not match expected") + + // test cache update + newScore := 10.0 + err = cache.AdjustWithInit(peerID, newScore, updateTime.Add(time.Minute)) + require.Nil(t, err, "Failed to update score in cache") + + // retrieve updated score + updatedScore, updatedTime, found := cache.Get(peerID) + require.True(t, found, "failed to find updated score in cache") + require.Equal(t, newScore, updatedScore, "updated score does not match expected") + require.Equal(t, updateTime.Add(time.Minute), updatedTime, "updated time does not match expected") +} + +// TestAppSpecificScoreCache_Concurrent_Add_Get_Update tests the concurrent functionality of AppSpecificScoreCache; +// specifically, it tests the Add and Get methods under concurrent access. +func TestAppSpecificScoreCache_Concurrent_Add_Get_Update(t *testing.T) { + cache := internal.NewAppSpecificScoreCache(10, unittest.Logger(), metrics.NewNoopCollector()) + require.NotNil(t, cache, "failed to create AppSpecificScoreCache") + + peerId1 := unittest.PeerIdFixture(t) + score1 := 5.0 + lastUpdated1 := time.Now() + + peerId2 := unittest.PeerIdFixture(t) + score2 := 10.0 + lastUpdated2 := time.Now().Add(time.Minute) + + wg := sync.WaitGroup{} + wg.Add(2) + go func() { + defer wg.Done() + err := cache.AdjustWithInit(peerId1, score1, lastUpdated1) + require.Nil(t, err, "failed to add score1 to cache") + }() + + go func() { + defer wg.Done() + err := cache.AdjustWithInit(peerId2, score2, lastUpdated2) + require.Nil(t, err, "failed to add score2 to cache") + }() + + unittest.RequireReturnsBefore(t, wg.Wait, 100*time.Millisecond, "failed to add scores to cache") + + // retrieve scores concurrently + wg.Add(2) + go func() { + defer wg.Done() + retrievedScore, lastUpdated, found := cache.Get(peerId1) + require.True(t, found, "failed to find score1 in cache") + require.Equal(t, score1, retrievedScore, "retrieved score1 does not match expected") + require.Equal(t, lastUpdated1, lastUpdated, "retrieved update time1 does not match expected") + }() + + go func() { + defer wg.Done() + retrievedScore, lastUpdated, found := cache.Get(peerId2) + require.True(t, found, "failed to find score2 in cache") + require.Equal(t, score2, retrievedScore, "retrieved score2 does not match expected") + require.Equal(t, lastUpdated2, lastUpdated, "retrieved update time2 does not match expected") + }() + + unittest.RequireReturnsBefore(t, wg.Wait, 100*time.Millisecond, "failed to retrieve scores from cache") + + // test cache update + newScore1 := 15.0 + newScore2 := 20.0 + lastUpdated1 = time.Now().Add(time.Minute) + lastUpdated2 = time.Now().Add(time.Minute) + + wg.Add(2) + go func() { + defer wg.Done() + err := cache.AdjustWithInit(peerId1, newScore1, lastUpdated1) + require.Nil(t, err, "failed to update score1 in cache") + }() + + go func() { + defer wg.Done() + err := cache.AdjustWithInit(peerId2, newScore2, lastUpdated2) + require.Nil(t, err, "failed to update score2 in cache") + }() + + unittest.RequireReturnsBefore(t, wg.Wait, 100*time.Millisecond, "failed to update scores in cache") + + // retrieve updated scores concurrently + wg.Add(2) + + go func() { + defer wg.Done() + updatedScore, updatedTime, found := cache.Get(peerId1) + require.True(t, found, "failed to find updated score1 in cache") + require.Equal(t, newScore1, updatedScore, "updated score1 does not match expected") + require.Equal(t, lastUpdated1, updatedTime, "updated time1 does not match expected") + }() + + go func() { + defer wg.Done() + updatedScore, updatedTime, found := cache.Get(peerId2) + require.True(t, found, "failed to find updated score2 in cache") + require.Equal(t, newScore2, updatedScore, "updated score2 does not match expected") + require.Equal(t, lastUpdated2, updatedTime, "updated time2 does not match expected") + }() + + unittest.RequireReturnsBefore(t, wg.Wait, 100*time.Millisecond, "failed to retrieve updated scores from cache") +} + +// TestAppSpecificScoreCache_Eviction tests the eviction policy of AppSpecificScoreCache; +// specifically, it tests that the cache evicts the least recently used record when the cache is full. +func TestAppSpecificScoreCache_Eviction(t *testing.T) { + cache := internal.NewAppSpecificScoreCache(10, unittest.Logger(), metrics.NewNoopCollector()) + require.NotNil(t, cache, "failed to create AppSpecificScoreCache") + + peerIds := unittest.PeerIdFixtures(t, 11) + scores := []float64{5.0, 10.0, 15.0, 20.0, 25.0, 30.0, 35.0, -1, -2, -3, -4} + require.Equal(t, len(peerIds), len(scores), "peer ids and scores must have the same length") + + // add scores to cache + for i := 0; i < len(peerIds); i++ { + err := cache.AdjustWithInit(peerIds[i], scores[i], time.Now()) + require.Nil(t, err, "failed to add score to cache") + } + + // retrieve scores from cache; the first score should have been evicted + for i := 1; i < len(peerIds); i++ { + retrievedScore, _, found := cache.Get(peerIds[i]) + require.True(t, found, "failed to find score in cache") + require.Equal(t, scores[i], retrievedScore, "retrieved score does not match expected") + } + + // the first score should not be in the cache + _, _, found := cache.Get(peerIds[0]) + require.False(t, found, "score should not be in cache") +} diff --git a/network/p2p/scoring/internal/appSpecificScoreRecord.go b/network/p2p/scoring/internal/appSpecificScoreRecord.go new file mode 100644 index 00000000000..5abd11d8922 --- /dev/null +++ b/network/p2p/scoring/internal/appSpecificScoreRecord.go @@ -0,0 +1,39 @@ +package internal + +import ( + "time" + + "github.com/libp2p/go-libp2p/core/peer" + + "github.com/onflow/flow-go/model/flow" +) + +// appSpecificScoreRecordEntity is an entity that represents the application specific score of a peer. +type appSpecificScoreRecordEntity struct { + // entityId is the key of the entity in the cache. It is the hash of the peer id. + // It is intentionally encoded as part of the struct to avoid recomputing it. + entityId flow.Identifier + + // PeerID is the peer id of the peer that is the owner of the subscription. + PeerID peer.ID + + // Score is the application specific score of the peer. + Score float64 + + // LastUpdated is the last time the score was updated. + LastUpdated time.Time +} + +var _ flow.Entity = (*appSpecificScoreRecordEntity)(nil) + +// ID returns the entity id of the subscription record, which is the hash of the peer id. +// The AppSpecificScoreCache uses the entity id as the key in the cache. +func (a appSpecificScoreRecordEntity) ID() flow.Identifier { + return a.entityId +} + +// Checksum returns the entity id of the subscription record, which is the hash of the peer id. +// It is of no use in the cache, but it is implemented to satisfy the flow.Entity interface. +func (a appSpecificScoreRecordEntity) Checksum() flow.Identifier { + return a.entityId +} diff --git a/network/p2p/scoring/internal/subscriptionCache.go b/network/p2p/scoring/internal/subscriptionCache.go index 95acafdd422..8aed3878753 100644 --- a/network/p2p/scoring/internal/subscriptionCache.go +++ b/network/p2p/scoring/internal/subscriptionCache.go @@ -1,7 +1,6 @@ package internal import ( - "errors" "fmt" "github.com/libp2p/go-libp2p/core/peer" @@ -15,8 +14,6 @@ import ( "github.com/onflow/flow-go/module/mempool/stdmap" ) -var ErrTopicRecordNotFound = fmt.Errorf("topic record not found") - // SubscriptionRecordCache manages the subscription records of peers in a network. // It uses a currentCycle counter to track the update cycles of the cache, ensuring the relevance of subscription data. type SubscriptionRecordCache struct { @@ -61,7 +58,7 @@ func NewSubscriptionRecordCache(sizeLimit uint32, // - []string: the list of topics the peer is subscribed to. // - bool: true if there is a record for the peer, false otherwise. func (s *SubscriptionRecordCache) GetSubscribedTopics(pid peer.ID) ([]string, bool) { - e, ok := s.c.ByID(flow.MakeID(pid)) + e, ok := s.c.ByID(entityIdOf(pid)) if !ok { return nil, false } @@ -87,7 +84,7 @@ func (s *SubscriptionRecordCache) MoveToNextUpdateCycle() uint64 { return s.currentCycle.Load() } -// AddTopicForPeer appends a topic to the list of topics a peer is subscribed to. If the peer is not subscribed to any +// AddWithInitTopicForPeer appends a topic to the list of topics a peer is subscribed to. If the peer is not subscribed to any // topics yet, a new record is created. // If the last update cycle is older than the current cycle, the list of topics for the peer is first cleared, and then // the topic is added to the list. This is to ensure that the list of topics for a peer is always up to date. @@ -98,40 +95,18 @@ func (s *SubscriptionRecordCache) MoveToNextUpdateCycle() uint64 { // - []string: the list of topics the peer is subscribed to after the update. // - error: an error if the update failed; any returned error is an irrecoverable error and indicates a bug or misconfiguration. // Implementation must be thread-safe. -func (s *SubscriptionRecordCache) AddTopicForPeer(pid peer.ID, topic string) ([]string, error) { - // first, we try to optimistically adjust the record assuming that the record already exists. - entityId := flow.MakeID(pid) - topics, err := s.addTopicForPeer(entityId, topic) - - switch { - case errors.Is(err, ErrTopicRecordNotFound): - // if the record does not exist, we initialize the record and try to adjust it again. - // Note: there is an edge case where the record is initialized by another goroutine between the two calls. - // In this case, the init function is invoked twice, but it is not a problem because the underlying - // cache is thread-safe. Hence, we do not need to synchronize the two calls. In such cases, one of the - // two calls returns false, and the other call returns true. We do not care which call returns false, hence, - // we ignore the return value of the init function. - _ = s.c.Add(SubscriptionRecordEntity{ +func (s *SubscriptionRecordCache) AddWithInitTopicForPeer(pid peer.ID, topic string) ([]string, error) { + entityId := entityIdOf(pid) + initLogic := func() flow.Entity { + return SubscriptionRecordEntity{ entityId: entityId, PeerID: pid, Topics: make([]string, 0), LastUpdatedCycle: s.currentCycle.Load(), - }) - // as the record is initialized, the adjust attempt should not return an error, and any returned error - // is an irrecoverable error and indicates a bug. - return s.addTopicForPeer(entityId, topic) - case err != nil: - // if the adjust function returns an unexpected error on the first attempt, we return the error directly. - return nil, err - default: - // if the adjust function returns no error, we return the updated list of topics. - return topics, nil + } } -} - -func (s *SubscriptionRecordCache) addTopicForPeer(entityId flow.Identifier, topic string) ([]string, error) { var rErr error - updatedEntity, adjusted := s.c.Adjust(entityId, func(entity flow.Entity) flow.Entity { + adjustLogic := func(entity flow.Entity) flow.Entity { record, ok := entity.(SubscriptionRecordEntity) if !ok { // sanity check @@ -162,15 +137,25 @@ func (s *SubscriptionRecordCache) addTopicForPeer(entityId flow.Identifier, topi // Return the adjusted record. return record - }) - + } + adjustedEntity, adjusted := s.c.AdjustWithInit(entityId, adjustLogic, initLogic) if rErr != nil { - return nil, fmt.Errorf("failed to adjust record: %w", rErr) + return nil, fmt.Errorf("failed to adjust record with error: %w", rErr) } - if !adjusted { - return nil, ErrTopicRecordNotFound + return nil, fmt.Errorf("failed to adjust record, entity not found") } - return updatedEntity.(SubscriptionRecordEntity).Topics, nil + return adjustedEntity.(SubscriptionRecordEntity).Topics, nil +} + +// entityIdOf converts a peer ID to a flow ID by taking the hash of the peer ID. +// This is used to convert the peer ID in a notion that is compatible with HeroCache. +// This is not a protocol-level conversion, and is only used internally by the cache, MUST NOT be exposed outside the cache. +// Args: +// - peerId: the peer ID of the peer in the GossipSub protocol. +// Returns: +// - flow.Identifier: the flow ID of the peer. +func entityIdOf(pid peer.ID) flow.Identifier { + return flow.MakeID(pid) } diff --git a/network/p2p/scoring/internal/subscriptionCache_test.go b/network/p2p/scoring/internal/subscriptionCache_test.go index a333c18bdd8..54b88707702 100644 --- a/network/p2p/scoring/internal/subscriptionCache_test.go +++ b/network/p2p/scoring/internal/subscriptionCache_test.go @@ -8,6 +8,7 @@ import ( "github.com/stretchr/testify/require" "github.com/onflow/flow-go/module/metrics" + "github.com/onflow/flow-go/network" "github.com/onflow/flow-go/network/p2p/scoring/internal" "github.com/onflow/flow-go/utils/unittest" ) @@ -19,7 +20,7 @@ func TestNewSubscriptionRecordCache(t *testing.T) { cache := internal.NewSubscriptionRecordCache( sizeLimit, unittest.Logger(), - metrics.NewSubscriptionRecordCacheMetricsFactory(metrics.NewNoopHeroCacheMetricsFactory())) + metrics.NewSubscriptionRecordCacheMetricsFactory(metrics.NewNoopHeroCacheMetricsFactory(), network.PrivateNetwork)) require.NotNil(t, cache, "cache should not be nil") require.IsType(t, &internal.SubscriptionRecordCache{}, cache, "cache should be of type *SubscriptionRecordCache") @@ -31,17 +32,17 @@ func TestSubscriptionCache_GetSubscribedTopics(t *testing.T) { cache := internal.NewSubscriptionRecordCache( sizeLimit, unittest.Logger(), - metrics.NewSubscriptionRecordCacheMetricsFactory(metrics.NewNoopHeroCacheMetricsFactory())) + metrics.NewSubscriptionRecordCacheMetricsFactory(metrics.NewNoopHeroCacheMetricsFactory(), network.PrivateNetwork)) // create a dummy peer ID peerID := unittest.PeerIdFixture(t) // case when the peer has a subscription topics := []string{"topic1", "topic2"} - updatedTopics, err := cache.AddTopicForPeer(peerID, topics[0]) + updatedTopics, err := cache.AddWithInitTopicForPeer(peerID, topics[0]) require.NoError(t, err, "adding topic 1 should not produce an error") require.Equal(t, topics[:1], updatedTopics, "updated topics should match the added topic") - updatedTopics, err = cache.AddTopicForPeer(peerID, topics[1]) + updatedTopics, err = cache.AddWithInitTopicForPeer(peerID, topics[1]) require.NoError(t, err, "adding topic 2 should not produce an error") require.Equal(t, topics, updatedTopics, "updated topics should match the added topic") @@ -63,7 +64,7 @@ func TestSubscriptionCache_MoveToNextUpdateCycle(t *testing.T) { cache := internal.NewSubscriptionRecordCache( sizeLimit, unittest.Logger(), - metrics.NewSubscriptionRecordCacheMetricsFactory(metrics.NewNoopHeroCacheMetricsFactory())) + metrics.NewSubscriptionRecordCacheMetricsFactory(metrics.NewNoopHeroCacheMetricsFactory(), network.PrivateNetwork)) // initial cycle should be 0, so first increment sets it to 1 firstCycle := cache.MoveToNextUpdateCycle() @@ -80,7 +81,7 @@ func TestSubscriptionCache_TestAddTopicForPeer(t *testing.T) { cache := internal.NewSubscriptionRecordCache( sizeLimit, unittest.Logger(), - metrics.NewSubscriptionRecordCacheMetricsFactory(metrics.NewNoopHeroCacheMetricsFactory())) + metrics.NewSubscriptionRecordCacheMetricsFactory(metrics.NewNoopHeroCacheMetricsFactory(), network.PrivateNetwork)) // case when adding a topic to an existing peer existingPeerID := unittest.PeerIdFixture(t) @@ -88,11 +89,11 @@ func TestSubscriptionCache_TestAddTopicForPeer(t *testing.T) { secondTopic := "topic2" // add first topic to the existing peer - _, err := cache.AddTopicForPeer(existingPeerID, firstTopic) + _, err := cache.AddWithInitTopicForPeer(existingPeerID, firstTopic) require.NoError(t, err, "adding first topic to existing peer should not produce an error") // add second topic to the same peer - updatedTopics, err := cache.AddTopicForPeer(existingPeerID, secondTopic) + updatedTopics, err := cache.AddWithInitTopicForPeer(existingPeerID, secondTopic) require.NoError(t, err, "adding second topic to existing peer should not produce an error") require.ElementsMatch(t, []string{firstTopic, secondTopic}, updatedTopics, "updated topics should match the added topics") @@ -101,7 +102,7 @@ func TestSubscriptionCache_TestAddTopicForPeer(t *testing.T) { newTopic := "newTopic" // add a topic to the new peer - updatedTopics, err = cache.AddTopicForPeer(newPeerID, newTopic) + updatedTopics, err = cache.AddWithInitTopicForPeer(newPeerID, newTopic) require.NoError(t, err, "adding topic to new peer should not produce an error") require.Equal(t, []string{newTopic}, updatedTopics, "updated topics for new peer should match the added topic") @@ -117,31 +118,31 @@ func TestSubscriptionCache_DuplicateTopics(t *testing.T) { cache := internal.NewSubscriptionRecordCache( sizeLimit, unittest.Logger(), - metrics.NewSubscriptionRecordCacheMetricsFactory(metrics.NewNoopHeroCacheMetricsFactory())) + metrics.NewSubscriptionRecordCacheMetricsFactory(metrics.NewNoopHeroCacheMetricsFactory(), network.PrivateNetwork)) peerID := unittest.PeerIdFixture(t) topic := "topic1" // add first topic to the existing peer - _, err := cache.AddTopicForPeer(peerID, topic) + _, err := cache.AddWithInitTopicForPeer(peerID, topic) require.NoError(t, err, "adding first topic to existing peer should not produce an error") // add second topic to the same peer - updatedTopics, err := cache.AddTopicForPeer(peerID, topic) + updatedTopics, err := cache.AddWithInitTopicForPeer(peerID, topic) require.NoError(t, err, "adding duplicate topic to existing peer should not produce an error") require.Equal(t, []string{topic}, updatedTopics, "duplicate topic should not be added") } -// TestSubscriptionCache_MoveUpdateCycle tests that (1) within one update cycle, "AddTopicForPeer" calls append the topics to the list of -// subscribed topics for peer, (2) as long as there is no "AddTopicForPeer" call, moving to the next update cycle -// does not change the subscribed topics for a peer, and (3) calling "AddTopicForPeer" after moving to the next update +// TestSubscriptionCache_MoveUpdateCycle tests that (1) within one update cycle, "AddWithInitTopicForPeer" calls append the topics to the list of +// subscribed topics for peer, (2) as long as there is no "AddWithInitTopicForPeer" call, moving to the next update cycle +// does not change the subscribed topics for a peer, and (3) calling "AddWithInitTopicForPeer" after moving to the next update // cycle clears the subscribed topics for a peer and adds the new topic. func TestSubscriptionCache_MoveUpdateCycle(t *testing.T) { sizeLimit := uint32(100) cache := internal.NewSubscriptionRecordCache( sizeLimit, unittest.Logger(), - metrics.NewSubscriptionRecordCacheMetricsFactory(metrics.NewNoopHeroCacheMetricsFactory())) + metrics.NewSubscriptionRecordCacheMetricsFactory(metrics.NewNoopHeroCacheMetricsFactory(), network.PrivateNetwork)) peerID := unittest.PeerIdFixture(t) topic1 := "topic1" @@ -150,13 +151,13 @@ func TestSubscriptionCache_MoveUpdateCycle(t *testing.T) { topic4 := "topic4" // adds topic1, topic2, and topic3 to the peer - topics, err := cache.AddTopicForPeer(peerID, topic1) + topics, err := cache.AddWithInitTopicForPeer(peerID, topic1) require.NoError(t, err, "adding first topic to existing peer should not produce an error") require.Equal(t, []string{topic1}, topics, "updated topics should match the added topic") - topics, err = cache.AddTopicForPeer(peerID, topic2) + topics, err = cache.AddWithInitTopicForPeer(peerID, topic2) require.NoError(t, err, "adding second topic to existing peer should not produce an error") require.Equal(t, []string{topic1, topic2}, topics, "updated topics should match the added topics") - topics, err = cache.AddTopicForPeer(peerID, topic3) + topics, err = cache.AddWithInitTopicForPeer(peerID, topic3) require.NoError(t, err, "adding third topic to existing peer should not produce an error") require.Equal(t, []string{topic1, topic2, topic3}, topics, "updated topics should match the added topics") @@ -168,7 +169,7 @@ func TestSubscriptionCache_MoveUpdateCycle(t *testing.T) { // add topic4 to the peer; since we moved to the next update cycle, the topics for the peer should be cleared // and topic4 should be the only topic for the peer - topics, err = cache.AddTopicForPeer(peerID, topic4) + topics, err = cache.AddWithInitTopicForPeer(peerID, topic4) require.NoError(t, err, "adding fourth topic to existing peer should not produce an error") require.Equal(t, []string{topic4}, topics, "updated topics should match the added topic") @@ -188,7 +189,7 @@ func TestSubscriptionCache_MoveUpdateCycleWithDifferentPeers(t *testing.T) { cache := internal.NewSubscriptionRecordCache( sizeLimit, unittest.Logger(), - metrics.NewSubscriptionRecordCacheMetricsFactory(metrics.NewNoopHeroCacheMetricsFactory())) + metrics.NewSubscriptionRecordCacheMetricsFactory(metrics.NewNoopHeroCacheMetricsFactory(), network.PrivateNetwork)) peer1 := unittest.PeerIdFixture(t) peer2 := unittest.PeerIdFixture(t) @@ -196,12 +197,12 @@ func TestSubscriptionCache_MoveUpdateCycleWithDifferentPeers(t *testing.T) { topic2 := "topic2" // add topic1 to peer1 - topics, err := cache.AddTopicForPeer(peer1, topic1) + topics, err := cache.AddWithInitTopicForPeer(peer1, topic1) require.NoError(t, err, "adding first topic to peer1 should not produce an error") require.Equal(t, []string{topic1}, topics, "updated topics should match the added topic") // add topic2 to peer2 - topics, err = cache.AddTopicForPeer(peer2, topic2) + topics, err = cache.AddWithInitTopicForPeer(peer2, topic2) require.NoError(t, err, "adding first topic to peer2 should not produce an error") require.Equal(t, []string{topic2}, topics, "updated topics should match the added topic") @@ -218,7 +219,7 @@ func TestSubscriptionCache_MoveUpdateCycleWithDifferentPeers(t *testing.T) { require.ElementsMatch(t, []string{topic2}, topics, "retrieved topics should match the added topics") // now add topic2 to peer1; it should overwrite the previous topics for peer1, but not affect the topics for peer2 - topics, err = cache.AddTopicForPeer(peer1, topic2) + topics, err = cache.AddWithInitTopicForPeer(peer1, topic2) require.NoError(t, err, "adding second topic to peer1 should not produce an error") require.Equal(t, []string{topic2}, topics, "updated topics should match the added topic") @@ -229,12 +230,11 @@ func TestSubscriptionCache_MoveUpdateCycleWithDifferentPeers(t *testing.T) { // TestSubscriptionCache_ConcurrentUpdate tests subscription cache update in a concurrent environment. func TestSubscriptionCache_ConcurrentUpdate(t *testing.T) { - unittest.SkipUnless(t, unittest.TEST_TODO, "this test requires atomic AdjustOrGet method to be implemented for backend") sizeLimit := uint32(100) cache := internal.NewSubscriptionRecordCache( sizeLimit, unittest.Logger(), - metrics.NewSubscriptionRecordCacheMetricsFactory(metrics.NewNoopHeroCacheMetricsFactory())) + metrics.NewSubscriptionRecordCacheMetricsFactory(metrics.NewNoopHeroCacheMetricsFactory(), network.PrivateNetwork)) peerIds := unittest.PeerIdFixtures(t, 100) topics := []string{"topic1", "topic2", "topic3"} @@ -247,7 +247,7 @@ func TestSubscriptionCache_ConcurrentUpdate(t *testing.T) { allUpdatesDone.Add(1) go func() { defer allUpdatesDone.Done() - _, err := cache.AddTopicForPeer(pid, topic) + _, err := cache.AddWithInitTopicForPeer(pid, topic) require.NoError(t, err, "adding topic to peer should not produce an error") }() } @@ -277,7 +277,7 @@ func TestSubscriptionCache_TestSizeLimit(t *testing.T) { cache := internal.NewSubscriptionRecordCache( sizeLimit, unittest.Logger(), - metrics.NewSubscriptionRecordCacheMetricsFactory(metrics.NewNoopHeroCacheMetricsFactory())) + metrics.NewSubscriptionRecordCacheMetricsFactory(metrics.NewNoopHeroCacheMetricsFactory(), network.PrivateNetwork)) peerIds := unittest.PeerIdFixtures(t, 100) topics := []string{"topic1", "topic2", "topic3"} @@ -285,7 +285,7 @@ func TestSubscriptionCache_TestSizeLimit(t *testing.T) { // add topics to peers for _, pid := range peerIds { for _, topic := range topics { - _, err := cache.AddTopicForPeer(pid, topic) + _, err := cache.AddWithInitTopicForPeer(pid, topic) require.NoError(t, err, "adding topic to peer should not produce an error") } } @@ -299,7 +299,7 @@ func TestSubscriptionCache_TestSizeLimit(t *testing.T) { // add one more peer and verify that the first peer is evicted newPeerID := unittest.PeerIdFixture(t) - _, err := cache.AddTopicForPeer(newPeerID, topics[0]) + _, err := cache.AddWithInitTopicForPeer(newPeerID, topics[0]) require.NoError(t, err, "adding topic to peer should not produce an error") _, found := cache.GetSubscribedTopics(peerIds[0]) diff --git a/network/p2p/scoring/registry.go b/network/p2p/scoring/registry.go index 0b4ac5c707e..4b56de3754e 100644 --- a/network/p2p/scoring/registry.go +++ b/network/p2p/scoring/registry.go @@ -5,91 +5,34 @@ import ( "math" "time" + "github.com/go-playground/validator/v10" "github.com/libp2p/go-libp2p/core/peer" "github.com/rs/zerolog" + "go.uber.org/atomic" + "github.com/onflow/flow-go/engine/common/worker" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/module" "github.com/onflow/flow-go/module/component" "github.com/onflow/flow-go/module/irrecoverable" + "github.com/onflow/flow-go/module/mempool/queue" + "github.com/onflow/flow-go/module/metrics" + "github.com/onflow/flow-go/network" "github.com/onflow/flow-go/network/p2p" netcache "github.com/onflow/flow-go/network/p2p/cache" + p2pconfig "github.com/onflow/flow-go/network/p2p/config" + p2plogging "github.com/onflow/flow-go/network/p2p/logging" p2pmsg "github.com/onflow/flow-go/network/p2p/message" - "github.com/onflow/flow-go/network/p2p/p2plogging" "github.com/onflow/flow-go/utils/logging" ) const ( - // MinimumSpamPenaltyDecayFactor is minimum speed at which the spam penalty value of a peer is decayed. - // Spam record will be initialized with a decay value between .5 , .7 and this value will then be decayed up to .99 on consecutive misbehavior's, - // The maximum decay value decays the penalty by 1% every second. The decay is applied geometrically, i.e., `newPenalty = oldPenalty * decay`, hence, the higher decay value - // indicates a lower decay speed, i.e., it takes more heartbeat intervals to decay a penalty back to zero when the decay value is high. - // assume: - // penalty = -100 (the maximum application specific penalty is -100) - // skipDecayThreshold = -0.1 - // it takes around 459 seconds for the penalty to decay to reach greater than -0.1 and turn into 0. - // x * 0.99 ^ n > -0.1 (assuming negative x). - // 0.99 ^ n > -0.1 / x - // Now we can take the logarithm of both sides (with any base, but let's use base 10 for simplicity). - // log( 0.99 ^ n ) < log( 0.1 / x ) - // Using the properties of logarithms, we can bring down the exponent: - // n * log( 0.99 ) < log( -0.1 / x ) - // And finally, we can solve for n: - // n > log( -0.1 / x ) / log( 0.99 ) - // We can plug in x = -100: - // n > log( -0.1 / -100 ) / log( 0.99 ) - // n > log( 0.001 ) / log( 0.99 ) - // n > -3 / log( 0.99 ) - // n > 458.22 - MinimumSpamPenaltyDecayFactor = 0.99 - // MaximumSpamPenaltyDecayFactor represents the maximum rate at which the spam penalty value of a peer decays. Decay speeds increase - // during sustained malicious activity, leading to a slower recovery of the app-specific score for the penalized node. Conversely, - // decay speeds decrease, allowing faster recoveries, when nodes exhibit fleeting misbehavior. - MaximumSpamPenaltyDecayFactor = 0.8 - // skipDecayThreshold is the threshold for which when the negative penalty is above this value, the decay function will not be called. - // instead, the penalty will be set to 0. This is to prevent the penalty from keeping a small negative value for a long time. - skipDecayThreshold = -0.1 - // graftMisbehaviourPenalty is the penalty applied to the application specific penalty when a peer conducts a graft misbehaviour. - graftMisbehaviourPenalty = -10 - // pruneMisbehaviourPenalty is the penalty applied to the application specific penalty when a peer conducts a prune misbehaviour. - pruneMisbehaviourPenalty = -10 - // iHaveMisbehaviourPenalty is the penalty applied to the application specific penalty when a peer conducts a iHave misbehaviour. - iHaveMisbehaviourPenalty = -10 - // iWantMisbehaviourPenalty is the penalty applied to the application specific penalty when a peer conducts a iWant misbehaviour. - iWantMisbehaviourPenalty = -10 - // clusterPrefixedPenaltyReductionFactor factor used to reduce the penalty for control message misbehaviours on cluster prefixed topics. This allows a more lenient punishment for nodes - // that fall behind and may need to request old data. - clusterPrefixedPenaltyReductionFactor = .5 - // rpcPublishMessageMisbehaviourPenalty is the penalty applied to the application specific penalty when a peer conducts a RpcPublishMessageMisbehaviourPenalty misbehaviour. - rpcPublishMessageMisbehaviourPenalty = -10 + // NotificationSilencedMsg log messages for silenced notifications + NotificationSilencedMsg = "ignoring invalid control message notification for peer during silence period" ) type SpamRecordInitFunc func() p2p.GossipSubSpamRecord -// GossipSubCtrlMsgPenaltyValue is the penalty value for each control message type. -type GossipSubCtrlMsgPenaltyValue struct { - Graft float64 // penalty value for an individual graft message misbehaviour. - Prune float64 // penalty value for an individual prune message misbehaviour. - IHave float64 // penalty value for an individual iHave message misbehaviour. - IWant float64 // penalty value for an individual iWant message misbehaviour. - // ClusterPrefixedPenaltyReductionFactor factor used to reduce the penalty for control message misbehaviours on cluster prefixed topics. This is allows a more lenient punishment for nodes - // that fall behind and may need to request old data. - ClusterPrefixedPenaltyReductionFactor float64 - RpcPublishMessage float64 // penalty value for an individual RpcPublishMessage message misbehaviour. -} - -// DefaultGossipSubCtrlMsgPenaltyValue returns the default penalty value for each control message type. -func DefaultGossipSubCtrlMsgPenaltyValue() GossipSubCtrlMsgPenaltyValue { - return GossipSubCtrlMsgPenaltyValue{ - Graft: graftMisbehaviourPenalty, - Prune: pruneMisbehaviourPenalty, - IHave: iHaveMisbehaviourPenalty, - IWant: iWantMisbehaviourPenalty, - ClusterPrefixedPenaltyReductionFactor: clusterPrefixedPenaltyReductionFactor, - RpcPublishMessage: rpcPublishMessageMisbehaviourPenalty, - } -} - // GossipSubAppSpecificScoreRegistry is the registry for the application specific score of peers in the GossipSub protocol. // The application specific score is part of the overall score of a peer, and is used to determine the peer's score based // on its behavior related to the application (Flow protocol). @@ -101,37 +44,74 @@ type GossipSubAppSpecificScoreRegistry struct { component.Component logger zerolog.Logger idProvider module.IdentityProvider + // spamScoreCache currently only holds the control message misbehaviour penalty (spam related penalty). spamScoreCache p2p.GossipSubSpamRecordCache - penalty GossipSubCtrlMsgPenaltyValue - // initial application specific penalty record, used to initialize the penalty cache entry. - init SpamRecordInitFunc + + penalty p2pconfig.MisbehaviourPenalties + validator p2p.SubscriptionValidator + + // scoreTTL is the time to live of the application specific score of a peer; the registry keeps a cached copy of the + // application specific score of a peer for this duration. When the duration expires, the application specific score + // of the peer is updated asynchronously. As long as the update is in progress, the cached copy of the application + // specific score of the peer is used even if it is expired. + scoreTTL time.Duration + + // appScoreCache is a cache that stores the application specific score of peers. + appScoreCache p2p.GossipSubApplicationSpecificScoreCache + + // appScoreUpdateWorkerPool is the worker pool for handling the application specific score update of peers in a non-blocking way. + appScoreUpdateWorkerPool *worker.Pool[peer.ID] + + // silencePeriodDuration duration that the startup silence period will last, during which nodes will not be penalized + silencePeriodDuration time.Duration + // silencePeriodStartTime time that the silence period begins, this is the time that the registry is started by the node. + silencePeriodStartTime time.Time + // silencePeriodElapsed atomic bool that stores a bool flag which indicates if the silence period is over or not. + silencePeriodElapsed *atomic.Bool + + unknownIdentityPenalty float64 + minAppSpecificPenalty float64 + stakedIdentityReward float64 + invalidSubscriptionPenalty float64 } // GossipSubAppSpecificScoreRegistryConfig is the configuration for the GossipSubAppSpecificScoreRegistry. -// The configuration is used to initialize the registry. +// Configurations are the "union of parameters and other components" that are used to compute or build components that compute or maintain the application specific score of peers. type GossipSubAppSpecificScoreRegistryConfig struct { - Logger zerolog.Logger + Parameters p2pconfig.AppSpecificScoreParameters `validate:"required"` + + Logger zerolog.Logger `validate:"required"` // Validator is the subscription validator used to validate the subscriptions of peers, and determine if a peer is // authorized to subscribe to a topic. - Validator p2p.SubscriptionValidator + Validator p2p.SubscriptionValidator `validate:"required"` // Penalty encapsulates the penalty unit for each control message type misbehaviour. - Penalty GossipSubCtrlMsgPenaltyValue + Penalty p2pconfig.MisbehaviourPenalties `validate:"required"` // IdProvider is the identity provider used to translate peer ids at the networking layer to Flow identifiers (if // an authorized peer is found). - IdProvider module.IdentityProvider + IdProvider module.IdentityProvider `validate:"required"` - // Init is a factory function that returns a new GossipSubSpamRecord. It is used to initialize the spam record of - // a peer when the peer is first observed by the local peer. - Init SpamRecordInitFunc - - // CacheFactory is a factory function that returns a new GossipSubSpamRecordCache. It is used to initialize the spamScoreCache. + // SpamRecordCacheFactory is a factory function that returns a new GossipSubSpamRecordCache. It is used to initialize the spamScoreCache. // The cache is used to store the application specific penalty of peers. - CacheFactory func() p2p.GossipSubSpamRecordCache + SpamRecordCacheFactory func() p2p.GossipSubSpamRecordCache `validate:"required"` + + // AppScoreCacheFactory is a factory function that returns a new GossipSubApplicationSpecificScoreCache. It is used to initialize the appScoreCache. + // The cache is used to store the application specific score of peers. + AppScoreCacheFactory func() p2p.GossipSubApplicationSpecificScoreCache `validate:"required"` + + HeroCacheMetricsFactory metrics.HeroCacheMetricsFactory `validate:"required"` + + NetworkingType network.NetworkingType `validate:"required"` + + // ScoringRegistryStartupSilenceDuration defines the duration of time, after the node startup, + // during which the scoring registry remains inactive before penalizing nodes. + ScoringRegistryStartupSilenceDuration time.Duration + + AppSpecificScoreParams p2pconfig.ApplicationSpecificScoreParameters `validate:"required"` } // NewGossipSubAppSpecificScoreRegistry returns a new GossipSubAppSpecificScoreRegistry. @@ -142,16 +122,38 @@ type GossipSubAppSpecificScoreRegistryConfig struct { // Returns: // // a new GossipSubAppSpecificScoreRegistry. -func NewGossipSubAppSpecificScoreRegistry(config *GossipSubAppSpecificScoreRegistryConfig) *GossipSubAppSpecificScoreRegistry { +// +// error: if the configuration is invalid, an error is returned; any returned error is an irrecoverable error and indicates a bug or misconfiguration. +func NewGossipSubAppSpecificScoreRegistry(config *GossipSubAppSpecificScoreRegistryConfig) (*GossipSubAppSpecificScoreRegistry, error) { + if err := validator.New().Struct(config); err != nil { + return nil, fmt.Errorf("invalid config: %w", err) + } + + lg := config.Logger.With().Str("module", "app_score_registry").Logger() + store := queue.NewHeroStore(config.Parameters.ScoreUpdateRequestQueueSize, + lg.With().Str("component", "app_specific_score_update").Logger(), + metrics.GossipSubAppSpecificScoreUpdateQueueMetricFactory(config.HeroCacheMetricsFactory, config.NetworkingType)) + reg := &GossipSubAppSpecificScoreRegistry{ - logger: config.Logger.With().Str("module", "app_score_registry").Logger(), - spamScoreCache: config.CacheFactory(), - penalty: config.Penalty, - init: config.Init, - validator: config.Validator, - idProvider: config.IdProvider, + logger: config.Logger.With().Str("module", "app_score_registry").Logger(), + spamScoreCache: config.SpamRecordCacheFactory(), + appScoreCache: config.AppScoreCacheFactory(), + penalty: config.Penalty, + validator: config.Validator, + idProvider: config.IdProvider, + scoreTTL: config.Parameters.ScoreTTL, + silencePeriodDuration: config.ScoringRegistryStartupSilenceDuration, + silencePeriodElapsed: atomic.NewBool(false), + unknownIdentityPenalty: config.AppSpecificScoreParams.UnknownIdentityPenalty, + minAppSpecificPenalty: config.AppSpecificScoreParams.MinAppSpecificPenalty, + stakedIdentityReward: config.AppSpecificScoreParams.StakedIdentityReward, + invalidSubscriptionPenalty: config.AppSpecificScoreParams.InvalidSubscriptionPenalty, } + reg.appScoreUpdateWorkerPool = worker.NewWorkerPoolBuilder[peer.ID](lg.With().Str("component", "app_specific_score_update_worker_pool").Logger(), + store, + reg.processAppSpecificScoreUpdateWork).Build() + builder := component.NewComponentManagerBuilder() builder.AddWorker(func(ctx irrecoverable.SignalerContext, ready component.ReadyFunc) { reg.logger.Info().Msg("starting subscription validator") @@ -164,72 +166,154 @@ func NewGossipSubAppSpecificScoreRegistry(config *GossipSubAppSpecificScoreRegis ready() reg.logger.Info().Msg("subscription validator is ready") } - <-ctx.Done() reg.logger.Info().Msg("stopping subscription validator") <-reg.validator.Done() reg.logger.Info().Msg("subscription validator stopped") + }).AddWorker(func(parent irrecoverable.SignalerContext, ready component.ReadyFunc) { + if !reg.silencePeriodStartTime.IsZero() { + parent.Throw(fmt.Errorf("gossipsub scoring registry started more than once")) + } + reg.silencePeriodStartTime = time.Now() + ready() }) + + for i := 0; i < config.Parameters.ScoreUpdateWorkerNum; i++ { + builder.AddWorker(reg.appScoreUpdateWorkerPool.WorkerLogic()) + } + reg.Component = builder.Build() - return reg + return reg, nil } var _ p2p.GossipSubInvCtrlMsgNotifConsumer = (*GossipSubAppSpecificScoreRegistry)(nil) -// AppSpecificScoreFunc returns the application specific penalty function that is called by the GossipSub protocol to determine the application specific penalty of a peer. +// AppSpecificScoreFunc returns the application specific score function that is called by the GossipSub protocol to determine the application specific score of a peer. +// The application specific score is part of the overall score of a peer, and is used to determine the peer's score based +// This function reads the application specific score of a peer from the cache, and if the penalty is not found in the cache, it computes it. +// If the score is not found in the cache, it is computed and added to the cache. +// Also if the score is expired, it is computed and added to the cache. +// Returns: +// - func(peer.ID) float64: the application specific score function. +// Implementation must be thread-safe. func (r *GossipSubAppSpecificScoreRegistry) AppSpecificScoreFunc() func(peer.ID) float64 { return func(pid peer.ID) float64 { - appSpecificScore := float64(0) - lg := r.logger.With().Str("remote_peer_id", p2plogging.PeerId(pid)).Logger() - // (1) spam penalty: the penalty is applied to the application specific penalty when a peer conducts a spamming misbehaviour. - spamRecord, err, spamRecordExists := r.spamScoreCache.Get(pid) - if err != nil { - // the error is considered fatal as it means the cache is not working properly. - // we should not continue with the execution as it may lead to routing attack vulnerability. - r.logger.Fatal().Str("peer_id", p2plogging.PeerId(pid)).Err(err).Msg("could not get application specific penalty for peer") - return appSpecificScore // unreachable, but added to avoid proceeding with the execution if log level is changed. - } - if spamRecordExists { - lg = lg.With().Float64("spam_penalty", spamRecord.Penalty).Logger() - appSpecificScore += spamRecord.Penalty + // during startup silence period avoid penalizing nodes + if !r.afterSilencePeriod() { + lg.Trace().Msg("returning 0 app specific score penalty for node during silence period") + return 0 } - // (2) staking score: for staked peers, a default positive reward is applied only if the peer has no penalty on spamming and subscription. - // for unknown peers a negative penalty is applied. - stakingScore, flowId, role := r.stakingScore(pid) - if stakingScore < 0 { - lg = lg.With().Float64("staking_penalty", stakingScore).Logger() - // staking penalty is applied right away. - appSpecificScore += stakingScore + appSpecificScore, lastUpdated, ok := r.appScoreCache.Get(pid) + switch { + case !ok: + // record not found in the cache, or expired; submit a worker to update it. + submitted := r.appScoreUpdateWorkerPool.Submit(pid) + lg.Trace(). + Bool("worker_submitted", submitted). + Msg("application specific score not found in cache, submitting worker to update it") + return 0 // in the mean time, return 0, which is a neutral score. + case time.Since(lastUpdated) > r.scoreTTL: + // record found in the cache, but expired; submit a worker to update it. + submitted := r.appScoreUpdateWorkerPool.Submit(pid) + lg.Trace(). + Bool("worker_submitted", submitted). + Float64("app_specific_score", appSpecificScore). + Dur("score_ttl", r.scoreTTL). + Msg("application specific score expired, submitting worker to update it") + return appSpecificScore // in the mean time, return the expired score. + default: + // record found in the cache. + r.logger.Trace(). + Float64("app_specific_score", appSpecificScore). + Msg("application specific score found in cache") + return appSpecificScore } + } +} - if stakingScore >= 0 { - // (3) subscription penalty: the subscription penalty is applied to the application specific penalty when a - // peer is subscribed to a topic that it is not allowed to subscribe to based on its role. - // Note: subscription penalty can be considered only for staked peers, for non-staked peers, we cannot - // determine the role of the peer. - subscriptionPenalty := r.subscriptionPenalty(pid, flowId, role) - lg = lg.With().Float64("subscription_penalty", subscriptionPenalty).Logger() - if subscriptionPenalty < 0 { - appSpecificScore += subscriptionPenalty - } - } +// computeAppSpecificScore computes the application specific score of a peer. +// The application specific score is computed based on the spam penalty, staking score, and subscription penalty. +// The spam penalty is the penalty applied to the application specific score when a peer conducts a spamming misbehaviour. +// The staking score is the reward/penalty applied to the application specific score when a peer is staked/unstaked. +// The subscription penalty is the penalty applied to the application specific score when a peer is subscribed to a topic that it is not allowed to subscribe to based on its role. +// Args: +// - pid: the peer ID of the peer in the GossipSub protocol. +// Returns: +// - float64: the application specific score of the peer. +func (r *GossipSubAppSpecificScoreRegistry) computeAppSpecificScore(pid peer.ID) float64 { + appSpecificScore := float64(0) + + lg := r.logger.With().Str("peer_id", p2plogging.PeerId(pid)).Logger() + // (1) spam penalty: the penalty is applied to the application specific penalty when a peer conducts a spamming misbehaviour. + spamRecord, err, spamRecordExists := r.spamScoreCache.Get(pid) + if err != nil { + // the error is considered fatal as it means the cache is not working properly. + // we should not continue with the execution as it may lead to routing attack vulnerability. + r.logger.Fatal().Str("peer_id", p2plogging.PeerId(pid)).Err(err).Msg("could not get application specific penalty for peer") + return appSpecificScore // unreachable, but added to avoid proceeding with the execution if log level is changed. + } + + if spamRecordExists { + lg = lg.With().Float64("spam_penalty", spamRecord.Penalty).Logger() + appSpecificScore += spamRecord.Penalty + } + + // (2) staking score: for staked peers, a default positive reward is applied only if the peer has no penalty on spamming and subscription. + // for unknown peers a negative penalty is applied. + stakingScore, flowId, role := r.stakingScore(pid) + if stakingScore < 0 { + lg = lg.With().Float64("staking_penalty", stakingScore).Logger() + // staking penalty is applied right away. + appSpecificScore += stakingScore + } - // (4) staking reward: for staked peers, a default positive reward is applied only if the peer has no penalty on spamming and subscription. - if stakingScore > 0 && appSpecificScore == float64(0) { - lg = lg.With().Float64("staking_reward", stakingScore).Logger() - appSpecificScore += stakingScore + if stakingScore >= 0 { + // (3) subscription penalty: the subscription penalty is applied to the application specific penalty when a + // peer is subscribed to a topic that it is not allowed to subscribe to based on its role. + // Note: subscription penalty can be considered only for staked peers, for non-staked peers, we cannot + // determine the role of the peer. + subscriptionPenalty := r.subscriptionPenalty(pid, flowId, role) + lg = lg.With().Float64("subscription_penalty", subscriptionPenalty).Logger() + if subscriptionPenalty < 0 { + appSpecificScore += subscriptionPenalty } + } + + // (4) staking reward: for staked peers, a default positive reward is applied only if the peer has no penalty on spamming and subscription. + if stakingScore > 0 && appSpecificScore == float64(0) { + lg = lg.With().Float64("staking_reward", stakingScore).Logger() + appSpecificScore += stakingScore + } + + lg.Trace(). + Float64("total_app_specific_score", appSpecificScore). + Msg("application specific score computed") - lg.Debug(). - Float64("total_app_specific_score", appSpecificScore). - Msg("application specific penalty computed") + return appSpecificScore +} - return appSpecificScore +// processMisbehaviorReport is the worker function that is called by the worker pool to update the application specific score of a peer. +// The function is called in a non-blocking way, and the worker pool is used to limit the number of concurrent executions of the function. +// Args: +// - pid: the peer ID of the peer in the GossipSub protocol. +// Returns: +// - error: an error if the update failed; any returned error is an irrecoverable error and indicates a bug or misconfiguration. +func (r *GossipSubAppSpecificScoreRegistry) processAppSpecificScoreUpdateWork(p peer.ID) error { + appSpecificScore := r.computeAppSpecificScore(p) + err := r.appScoreCache.AdjustWithInit(p, appSpecificScore, time.Now()) + if err != nil { + // the error is considered fatal as it means the cache is not working properly. + return fmt.Errorf("could not add application specific score %f for peer to cache: %w", appSpecificScore, err) } + r.logger.Trace(). + Str("remote_peer_id", p2plogging.PeerId(p)). + Float64("app_specific_score", appSpecificScore). + Msg("application specific score computed and cache updated") + return nil } func (r *GossipSubAppSpecificScoreRegistry) stakingScore(pid peer.ID) (float64, flow.Identifier, flow.Role) { @@ -242,7 +326,7 @@ func (r *GossipSubAppSpecificScoreRegistry) stakingScore(pid peer.ID) (float64, Err(err). Bool(logging.KeySuspicious, true). Msg("invalid peer identity, penalizing peer") - return DefaultUnknownIdentityPenalty, flow.Identifier{}, 0 + return r.unknownIdentityPenalty, flow.Identifier{}, 0 } lg = lg.With(). @@ -255,24 +339,25 @@ func (r *GossipSubAppSpecificScoreRegistry) stakingScore(pid peer.ID) (float64, if flowId.Role == flow.RoleAccess { lg.Trace(). Msg("pushing access node to edge by penalizing with minimum penalty value") - return MinAppSpecificPenalty, flowId.NodeID, flowId.Role + return r.minAppSpecificPenalty, flowId.NodeID, flowId.Role } lg.Trace(). Msg("rewarding well-behaved non-access node peer with maximum reward value") - return DefaultStakedIdentityReward, flowId.NodeID, flowId.Role + return r.stakedIdentityReward, flowId.NodeID, flowId.Role } func (r *GossipSubAppSpecificScoreRegistry) subscriptionPenalty(pid peer.ID, flowId flow.Identifier, role flow.Role) float64 { // checks if peer has any subscription violation. if err := r.validator.CheckSubscribedToAllowedTopics(pid, role); err != nil { - r.logger.Err(err). + r.logger.Warn(). + Err(err). Str("peer_id", p2plogging.PeerId(pid)). Hex("flow_id", logging.ID(flowId)). Bool(logging.KeySuspicious, true). Msg("invalid subscription detected, penalizing peer") - return DefaultInvalidSubscriptionPenalty + return r.invalidSubscriptionPenalty } return 0 @@ -289,28 +374,25 @@ func (r *GossipSubAppSpecificScoreRegistry) OnInvalidControlMessageNotification( Str("peer_id", p2plogging.PeerId(notification.PeerID)). Str("misbehavior_type", notification.MsgType.String()).Logger() - // try initializing the application specific penalty for the peer if it is not yet initialized. - // this is done to avoid the case where the peer is not yet cached and the application specific penalty is not yet initialized. - // initialization is successful only if the peer is not yet cached. If any error is occurred during initialization we log a fatal error - initRecord := r.init() - initialized := r.spamScoreCache.Add(notification.PeerID, initRecord) - if initialized { - lg.Trace().Str("peer_id", p2plogging.PeerId(notification.PeerID)).Msg("application specific penalty initialized for peer") + // during startup silence period avoid penalizing nodes, ignore all notifications + if !r.afterSilencePeriod() { + lg.Trace().Msg("ignoring invalid control message notification for peer during silence period") + return } - record, err := r.spamScoreCache.Update(notification.PeerID, func(record p2p.GossipSubSpamRecord) p2p.GossipSubSpamRecord { + record, err := r.spamScoreCache.Adjust(notification.PeerID, func(record p2p.GossipSubSpamRecord) p2p.GossipSubSpamRecord { penalty := 0.0 switch notification.MsgType { case p2pmsg.CtrlMsgGraft: - penalty += r.penalty.Graft + penalty += r.penalty.GraftMisbehaviour case p2pmsg.CtrlMsgPrune: - penalty += r.penalty.Prune + penalty += r.penalty.PruneMisbehaviour case p2pmsg.CtrlMsgIHave: - penalty += r.penalty.IHave + penalty += r.penalty.IHaveMisbehaviour case p2pmsg.CtrlMsgIWant: - penalty += r.penalty.IWant + penalty += r.penalty.IWantMisbehaviour case p2pmsg.RpcPublishMessage: - penalty += r.penalty.RpcPublishMessage + penalty += r.penalty.PublishMisbehaviour default: // the error is considered fatal as it means that we have an unsupported misbehaviour type, we should crash the node to prevent routing attack vulnerability. lg.Fatal().Str("misbehavior_type", notification.MsgType.String()).Msg("unknown misbehaviour type") @@ -318,7 +400,7 @@ func (r *GossipSubAppSpecificScoreRegistry) OnInvalidControlMessageNotification( // reduce penalty for cluster prefixed topics allowing nodes that are potentially behind to catch up if notification.TopicType == p2p.CtrlMsgTopicTypeClusterPrefixed { - penalty *= r.penalty.ClusterPrefixedPenaltyReductionFactor + penalty *= r.penalty.ClusterPrefixedReductionFactor } record.Penalty += penalty @@ -331,14 +413,26 @@ func (r *GossipSubAppSpecificScoreRegistry) OnInvalidControlMessageNotification( } lg.Debug(). - Float64("app_specific_score", record.Penalty). + Float64("spam_record_penalty", record.Penalty). Msg("applied misbehaviour penalty and updated application specific penalty") } +// afterSilencePeriod returns true if registry silence period is over, false otherwise. +func (r *GossipSubAppSpecificScoreRegistry) afterSilencePeriod() bool { + if !r.silencePeriodElapsed.Load() { + if time.Since(r.silencePeriodStartTime) > r.silencePeriodDuration { + r.silencePeriodElapsed.Store(true) + return true + } + return false + } + return true +} + // DefaultDecayFunction is the default decay function that is used to decay the application specific penalty of a peer. // It is used if no decay function is provided in the configuration. // It decays the application specific penalty of a peer if it is negative. -func DefaultDecayFunction(slowerDecayPenaltyThreshold, decayRateDecrement float64, decayAdjustInterval time.Duration) netcache.PreprocessorFunc { +func DefaultDecayFunction(cfg p2pconfig.SpamRecordCacheDecay) netcache.PreprocessorFunc { return func(record p2p.GossipSubSpamRecord, lastUpdated time.Time) (p2p.GossipSubSpamRecord, error) { if record.Penalty >= 0 { // no need to decay the penalty if it is positive, the reason is currently the app specific penalty @@ -347,10 +441,10 @@ func DefaultDecayFunction(slowerDecayPenaltyThreshold, decayRateDecrement float6 return record, nil } - if record.Penalty > skipDecayThreshold { + if record.Penalty > cfg.SkipDecayThreshold { // penalty is negative but greater than the threshold, we set it to 0. record.Penalty = 0 - record.Decay = MaximumSpamPenaltyDecayFactor + record.Decay = cfg.MaximumSpamPenaltyDecayFactor record.LastDecayAdjustment = time.Time{} return record, nil } @@ -362,10 +456,10 @@ func DefaultDecayFunction(slowerDecayPenaltyThreshold, decayRateDecrement float6 } record.Penalty = penalty - if record.Penalty <= slowerDecayPenaltyThreshold { - if time.Since(record.LastDecayAdjustment) > decayAdjustInterval || record.LastDecayAdjustment.IsZero() { + if record.Penalty <= cfg.PenaltyDecaySlowdownThreshold { + if time.Since(record.LastDecayAdjustment) > cfg.PenaltyDecayEvaluationPeriod || record.LastDecayAdjustment.IsZero() { // reduces the decay speed flooring at MinimumSpamRecordDecaySpeed - record.Decay = math.Min(record.Decay+decayRateDecrement, MinimumSpamPenaltyDecayFactor) + record.Decay = math.Min(record.Decay+cfg.DecayRateReductionFactor, cfg.MinimumSpamPenaltyDecayFactor) record.LastDecayAdjustment = time.Now() } } @@ -373,13 +467,15 @@ func DefaultDecayFunction(slowerDecayPenaltyThreshold, decayRateDecrement float6 } } -// InitAppScoreRecordState initializes the gossipsub spam record state for a peer. +// InitAppScoreRecordStateFunc returns a callback that initializes the gossipsub spam record state for a peer. // Returns: -// - a gossipsub spam record with the default decay value and 0 penalty. -func InitAppScoreRecordState() p2p.GossipSubSpamRecord { - return p2p.GossipSubSpamRecord{ - Decay: MaximumSpamPenaltyDecayFactor, - Penalty: 0, - LastDecayAdjustment: time.Now(), +// - a func that returns a gossipsub spam record with the default decay value and 0 penalty. +func InitAppScoreRecordStateFunc(maximumSpamPenaltyDecayFactor float64) func() p2p.GossipSubSpamRecord { + return func() p2p.GossipSubSpamRecord { + return p2p.GossipSubSpamRecord{ + Decay: maximumSpamPenaltyDecayFactor, + Penalty: 0, + LastDecayAdjustment: time.Now(), + } } } diff --git a/network/p2p/scoring/registry_test.go b/network/p2p/scoring/registry_test.go index 92d93942afa..ce5a522c17c 100644 --- a/network/p2p/scoring/registry_test.go +++ b/network/p2p/scoring/registry_test.go @@ -1,87 +1,165 @@ package scoring_test import ( + "context" "fmt" "math" + "os" "sync" "testing" "time" "github.com/libp2p/go-libp2p/core/peer" + "github.com/rs/zerolog" "github.com/stretchr/testify/assert" testifymock "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + "go.uber.org/atomic" "github.com/onflow/flow-go/config" + "github.com/onflow/flow-go/model/flow" + "github.com/onflow/flow-go/module/irrecoverable" "github.com/onflow/flow-go/module/metrics" "github.com/onflow/flow-go/module/mock" + "github.com/onflow/flow-go/network" "github.com/onflow/flow-go/network/p2p" netcache "github.com/onflow/flow-go/network/p2p/cache" + p2pconfig "github.com/onflow/flow-go/network/p2p/config" p2pmsg "github.com/onflow/flow-go/network/p2p/message" mockp2p "github.com/onflow/flow-go/network/p2p/mock" - "github.com/onflow/flow-go/network/p2p/p2pconf" "github.com/onflow/flow-go/network/p2p/scoring" + "github.com/onflow/flow-go/network/p2p/scoring/internal" "github.com/onflow/flow-go/utils/unittest" ) -// TestNoPenaltyRecord tests that if there is no penalty record for a peer id, the app specific score should be the max -// app specific reward. This is the default reward for a staked peer that has valid subscriptions and has not been -// penalized. -func TestNoPenaltyRecord(t *testing.T) { - peerID := unittest.PeerIdFixture(t) - reg, spamRecords := newGossipSubAppSpecificScoreRegistry( - t, - withStakedIdentity(peerID), +// TestScoreRegistry_FreshStart tests the app specific score computation of the node when there is no spam record for the peer id upon fresh start of the registry. +// It tests the state that a staked peer with a valid role and valid subscriptions has no spam records; hence it should "eventually" be rewarded with the default reward +// for its GossipSub app specific score. The "eventually" comes from the fact that the app specific score is updated asynchronously in the cache, and the cache is +// updated when the app specific score function is called by GossipSub. +func TestScoreRegistry_FreshStart(t *testing.T) { + peerID := peer.ID("peer-1") + + cfg, err := config.DefaultConfig() + require.NoError(t, err) + // refresh cached app-specific score every 100 milliseconds to speed up the test. + cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.AppSpecificScore.ScoreTTL = 100 * time.Millisecond + + reg, spamRecords, appScoreCache := newGossipSubAppSpecificScoreRegistry(t, + cfg.NetworkConfig.GossipSub.ScoringParameters, + scoring.InitAppScoreRecordStateFunc(cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.SpamRecordCache.Decay.MaximumSpamPenaltyDecayFactor), + withStakedIdentities(peerID), withValidSubscriptions(peerID)) + ctx, cancel := context.WithCancel(context.Background()) + signalerCtx := irrecoverable.NewMockSignalerContext(t, ctx) + reg.Start(signalerCtx) + unittest.RequireCloseBefore(t, reg.Ready(), 1*time.Second, "registry did not start in time") - // initially, the spamRecords should not have the peer id. - assert.False(t, spamRecords.Has(peerID)) + defer stopRegistry(t, cancel, reg) - score := reg.AppSpecificScoreFunc()(peerID) - // since the peer id does not have a spam record, the app specific score should be the max app specific reward, which - // is the default reward for a staked peer that has valid subscriptions. - assert.Equal(t, scoring.MaxAppSpecificReward, score) + // initially, the spamRecords should not have the peer id, and there should be no app-specific score in the cache. + require.False(t, spamRecords.Has(peerID)) + score, updated, exists := appScoreCache.Get(peerID) // get the score from the cache. + require.False(t, exists) + require.Equal(t, time.Time{}, updated) + require.Equal(t, float64(0), score) + + maxAppSpecificReward := cfg.NetworkConfig.GossipSub.ScoringParameters.PeerScoring.Protocol.AppSpecificScore.MaxAppSpecificReward + + queryTime := time.Now() + require.Eventually(t, func() bool { + // calling the app specific score function when there is no app specific score in the cache should eventually update the cache. + score := reg.AppSpecificScoreFunc()(peerID) + // since the peer id does not have a spam record, the app specific score should be the max app specific reward, which + // is the default reward for a staked peer that has valid subscriptions. + return score == maxAppSpecificReward + }, 5*time.Second, 100*time.Millisecond) // still the spamRecords should not have the peer id (as there is no spam record for the peer id). - assert.False(t, spamRecords.Has(peerID)) + require.False(t, spamRecords.Has(peerID)) + + // however, the app specific score should be updated in the cache. + score, updated, exists = appScoreCache.Get(peerID) // get the score from the cache. + require.True(t, exists) + require.True(t, updated.After(queryTime)) + require.Equal(t, maxAppSpecificReward, score) + + // stop the registry. + cancel() + unittest.RequireCloseBefore(t, reg.Done(), 1*time.Second, "failed to stop GossipSubAppSpecificScoreRegistry") } -// TestPeerWithSpamRecord tests the app specific penalty computation of the node when there is a spam record for the peer id. -// It tests the state that a staked peer with a valid role and valid subscriptions has spam records. -// Since the peer has spam records, it should be deprived of the default reward for its staked role, and only have the -// penalty value as the app specific score. -func TestPeerWithSpamRecord(t *testing.T) { +// TestScoreRegistry_PeerWithSpamRecord is a test suite designed to assess the app-specific penalty computation +// in a scenario where a peer with a staked identity and valid subscriptions has a spam record. The suite runs multiple +// sub-tests, each targeting a specific type of control message (graft, prune, ihave, iwant, RpcPublishMessage). The focus +// is on the impact of spam records on the app-specific score, specifically how such records negate the default reward +// a staked peer would otherwise receive, leaving only the penalty as the app-specific score. This testing reflects the +// asynchronous nature of app-specific score updates in GossipSub's cache. +func TestScoreRegistry_PeerWithSpamRecord(t *testing.T) { t.Run("graft", func(t *testing.T) { - testPeerWithSpamRecord(t, p2pmsg.CtrlMsgGraft, penaltyValueFixtures().Graft) + testScoreRegistryPeerWithSpamRecord(t, p2pmsg.CtrlMsgGraft, penaltyValueFixtures().GraftMisbehaviour) }) t.Run("prune", func(t *testing.T) { - testPeerWithSpamRecord(t, p2pmsg.CtrlMsgPrune, penaltyValueFixtures().Prune) + testScoreRegistryPeerWithSpamRecord(t, p2pmsg.CtrlMsgPrune, penaltyValueFixtures().PruneMisbehaviour) }) t.Run("ihave", func(t *testing.T) { - testPeerWithSpamRecord(t, p2pmsg.CtrlMsgIHave, penaltyValueFixtures().IHave) + testScoreRegistryPeerWithSpamRecord(t, p2pmsg.CtrlMsgIHave, penaltyValueFixtures().IHaveMisbehaviour) }) t.Run("iwant", func(t *testing.T) { - testPeerWithSpamRecord(t, p2pmsg.CtrlMsgIWant, penaltyValueFixtures().IWant) + testScoreRegistryPeerWithSpamRecord(t, p2pmsg.CtrlMsgIWant, penaltyValueFixtures().IWantMisbehaviour) }) t.Run("RpcPublishMessage", func(t *testing.T) { - testPeerWithSpamRecord(t, p2pmsg.RpcPublishMessage, penaltyValueFixtures().RpcPublishMessage) + testScoreRegistryPeerWithSpamRecord(t, p2pmsg.RpcPublishMessage, penaltyValueFixtures().PublishMisbehaviour) }) } -func testPeerWithSpamRecord(t *testing.T, messageType p2pmsg.ControlMessageType, expectedPenalty float64) { - peerID := unittest.PeerIdFixture(t) - reg, spamRecords := newGossipSubAppSpecificScoreRegistry( - t, - withStakedIdentity(peerID), +// testScoreRegistryPeerWithSpamRecord conducts an individual test within the TestScoreRegistry_PeerWithSpamRecord suite. +// It evaluates the ScoreRegistry's handling of a staked peer with valid subscriptions when a spam record is present for +// the peer ID. The function simulates the process of starting the registry, recording a misbehavior, and then verifying the +// updates to the spam records and app-specific score cache based on the type of control message received. +// Parameters: +// - t *testing.T: The test context. +// - messageType p2pmsg.ControlMessageType: The type of control message being tested. +// - expectedPenalty float64: The expected penalty value for the given control message type. +// This function specifically tests how the ScoreRegistry updates a peer's app-specific score in response to spam records, +// emphasizing the removal of the default reward for staked peers with valid roles and focusing on the asynchronous update +// mechanism of the app-specific score in the cache. +func testScoreRegistryPeerWithSpamRecord(t *testing.T, messageType p2pmsg.ControlMessageType, expectedPenalty float64) { + peerID := peer.ID("peer-1") + + cfg, err := config.DefaultConfig() + require.NoError(t, err) + // refresh cached app-specific score every 100 milliseconds to speed up the test. + cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.AppSpecificScore.ScoreTTL = 10 * time.Millisecond + + reg, spamRecords, appScoreCache := newGossipSubAppSpecificScoreRegistry(t, + cfg.NetworkConfig.GossipSub.ScoringParameters, + scoring.InitAppScoreRecordStateFunc(cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.SpamRecordCache.Decay.MaximumSpamPenaltyDecayFactor), + withStakedIdentities(peerID), withValidSubscriptions(peerID)) + ctx, cancel := context.WithCancel(context.Background()) + signalerCtx := irrecoverable.NewMockSignalerContext(t, ctx) + reg.Start(signalerCtx) + unittest.RequireCloseBefore(t, reg.Ready(), 1*time.Second, "registry did not start in time") - // initially, the spamRecords should not have the peer id. - assert.False(t, spamRecords.Has(peerID)) + defer stopRegistry(t, cancel, reg) - // since the peer id does not have a spam record, the app specific score should be the max app specific reward, which - // is the default reward for a staked peer that has valid subscriptions. - score := reg.AppSpecificScoreFunc()(peerID) - assert.Equal(t, scoring.MaxAppSpecificReward, score) + // initially, the spamRecords should not have the peer id; also the app specific score record should not be in the cache. + require.False(t, spamRecords.Has(peerID)) + score, updated, exists := appScoreCache.Get(peerID) // get the score from the cache. + require.False(t, exists) + require.Equal(t, time.Time{}, updated) + require.Equal(t, float64(0), score) + + scoreOptParameters := cfg.NetworkConfig.GossipSub.ScoringParameters.PeerScoring.Protocol.AppSpecificScore + + // eventually, the app specific score should be updated in the cache. + require.Eventually(t, func() bool { + // calling the app specific score function when there is no app specific score in the cache should eventually update the cache. + score := reg.AppSpecificScoreFunc()(peerID) + // since the peer id does not have a spam record, the app specific score should be the max app specific reward, which + // is the default reward for a staked peer that has valid subscriptions. + return scoreOptParameters.MaxAppSpecificReward == score + }, 5*time.Second, 100*time.Millisecond) // report a misbehavior for the peer id. reg.OnInvalidControlMessageNotification(&p2p.InvCtrlMsgNotif{ @@ -93,49 +171,100 @@ func testPeerWithSpamRecord(t *testing.T, messageType p2pmsg.ControlMessageType, record, err, ok := spamRecords.Get(peerID) // get the record from the spamRecords. assert.True(t, ok) assert.NoError(t, err) - assert.Less(t, math.Abs(expectedPenalty-record.Penalty), 10e-3) // penalty should be updated to -10. - assert.Equal(t, scoring.InitAppScoreRecordState().Decay, record.Decay) // decay should be initialized to the initial state. + assert.Less(t, math.Abs(expectedPenalty-record.Penalty), 10e-3) // penalty should be updated to -10. + assert.Equal(t, scoring.InitAppScoreRecordStateFunc(cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.SpamRecordCache.Decay.MaximumSpamPenaltyDecayFactor)().Decay, record.Decay) // decay should be initialized to the initial state. - // this peer has a spam record, with no subscription penalty. Hence, the app specific score should only be the spam penalty, - // and the peer should be deprived of the default reward for its valid staked role. - score = reg.AppSpecificScoreFunc()(peerID) - assert.Less(t, math.Abs(expectedPenalty-score), 10e-3) + queryTime := time.Now() + // eventually, the app specific score should be updated in the cache. + require.Eventually(t, func() bool { + // calling the app specific score function when there is no app specific score in the cache should eventually update the cache. + score := reg.AppSpecificScoreFunc()(peerID) + // this peer has a spam record, with no subscription penalty. Hence, the app specific score should only be the spam penalty, + // and the peer should be deprived of the default reward for its valid staked role. + // As the app specific score in the cache and spam penalty in the spamRecords are updated at different times, we account for 0.1% error. + return math.Abs(expectedPenalty-score)/math.Max(expectedPenalty, score) < 0.001 + }, 5*time.Second, 100*time.Millisecond) + + // the app specific score should now be updated in the cache. + score, updated, exists = appScoreCache.Get(peerID) // get the score from the cache. + require.True(t, exists) + require.True(t, updated.After(queryTime)) + require.True(t, math.Abs(expectedPenalty-score)/math.Max(expectedPenalty, score) < 0.001) + + // stop the registry. + cancel() + unittest.RequireCloseBefore(t, reg.Done(), 1*time.Second, "failed to stop GossipSubAppSpecificScoreRegistry") } -func TestSpamRecord_With_UnknownIdentity(t *testing.T) { +// TestScoreRegistry_SpamRecordWithUnknownIdentity is a test suite for verifying the behavior of the ScoreRegistry +// when handling spam records associated with unknown identities. It tests various scenarios based on different control +// message types, including graft, prune, ihave, iwant, and RpcPublishMessage. Each sub-test validates the app-specific +// penalty computation and updates to the score registry when a peer with an unknown identity sends these control messages. +func TestScoreRegistry_SpamRecordWithUnknownIdentity(t *testing.T) { t.Run("graft", func(t *testing.T) { - testSpamRecordWithUnknownIdentity(t, p2pmsg.CtrlMsgGraft, penaltyValueFixtures().Graft) + testScoreRegistrySpamRecordWithUnknownIdentity(t, p2pmsg.CtrlMsgGraft, penaltyValueFixtures().GraftMisbehaviour) }) t.Run("prune", func(t *testing.T) { - testSpamRecordWithUnknownIdentity(t, p2pmsg.CtrlMsgPrune, penaltyValueFixtures().Prune) + testScoreRegistrySpamRecordWithUnknownIdentity(t, p2pmsg.CtrlMsgPrune, penaltyValueFixtures().PruneMisbehaviour) }) t.Run("ihave", func(t *testing.T) { - testSpamRecordWithUnknownIdentity(t, p2pmsg.CtrlMsgIHave, penaltyValueFixtures().IHave) + testScoreRegistrySpamRecordWithUnknownIdentity(t, p2pmsg.CtrlMsgIHave, penaltyValueFixtures().IHaveMisbehaviour) }) t.Run("iwant", func(t *testing.T) { - testSpamRecordWithUnknownIdentity(t, p2pmsg.CtrlMsgIWant, penaltyValueFixtures().IWant) + testScoreRegistrySpamRecordWithUnknownIdentity(t, p2pmsg.CtrlMsgIWant, penaltyValueFixtures().IWantMisbehaviour) }) t.Run("RpcPublishMessage", func(t *testing.T) { - testSpamRecordWithUnknownIdentity(t, p2pmsg.RpcPublishMessage, penaltyValueFixtures().RpcPublishMessage) + testScoreRegistrySpamRecordWithUnknownIdentity(t, p2pmsg.RpcPublishMessage, penaltyValueFixtures().PublishMisbehaviour) }) } -// testSpamRecordWithUnknownIdentity tests the app specific penalty computation of the node when there is a spam record for the peer id and -// the peer id has an unknown identity. -func testSpamRecordWithUnknownIdentity(t *testing.T, messageType p2pmsg.ControlMessageType, expectedPenalty float64) { - peerID := unittest.PeerIdFixture(t) - reg, spamRecords := newGossipSubAppSpecificScoreRegistry( - t, +// testScoreRegistrySpamRecordWithUnknownIdentity tests the app-specific penalty computation of the node when there +// is a spam record for a peer ID with an unknown identity. It examines the functionality of the GossipSubAppSpecificScoreRegistry +// under various conditions, including the initialization state, spam record creation, and the impact of different control message types. +// Parameters: +// - t *testing.T: The testing context. +// - messageType p2pmsg.ControlMessageType: The type of control message being tested. +// - expectedPenalty float64: The expected penalty value for the given control message type. +// The function simulates the process of starting the registry, reporting a misbehavior for the peer ID, and verifying the +// updates to the spam records and app-specific score cache. It ensures that the penalties are correctly computed and applied +// based on the given control message type and the state of the peer ID (unknown identity and spam record presence). +func testScoreRegistrySpamRecordWithUnknownIdentity(t *testing.T, messageType p2pmsg.ControlMessageType, expectedPenalty float64) { + peerID := peer.ID("peer-1") + cfg, err := config.DefaultConfig() + require.NoError(t, err) + // refresh cached app-specific score every 100 milliseconds to speed up the test. + cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.AppSpecificScore.ScoreTTL = 100 * time.Millisecond + + reg, spamRecords, appScoreCache := newGossipSubAppSpecificScoreRegistry(t, + cfg.NetworkConfig.GossipSub.ScoringParameters, + scoring.InitAppScoreRecordStateFunc(cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.SpamRecordCache.Decay.MaximumSpamPenaltyDecayFactor), withUnknownIdentity(peerID), withValidSubscriptions(peerID)) + ctx, cancel := context.WithCancel(context.Background()) + signalerCtx := irrecoverable.NewMockSignalerContext(t, ctx) + reg.Start(signalerCtx) + unittest.RequireCloseBefore(t, reg.Ready(), 1*time.Second, "registry did not start in time") + + defer stopRegistry(t, cancel, reg) + + // initially, the spamRecords should not have the peer id; also the app specific score record should not be in the cache. + require.False(t, spamRecords.Has(peerID)) + score, updated, exists := appScoreCache.Get(peerID) // get the score from the cache. + require.False(t, exists) + require.Equal(t, time.Time{}, updated) + require.Equal(t, float64(0), score) - // initially, the spamRecords should not have the peer id. - assert.False(t, spamRecords.Has(peerID)) + scoreOptParameters := cfg.NetworkConfig.GossipSub.ScoringParameters.PeerScoring.Protocol.AppSpecificScore - // peer does not have spam record, but has an unknown identity. Hence, the app specific score should be the staking penalty. - score := reg.AppSpecificScoreFunc()(peerID) - require.Equal(t, scoring.DefaultUnknownIdentityPenalty, score) + // eventually the app specific score should be updated in the cache to the penalty value for unknown identity. + require.Eventually(t, func() bool { + // calling the app specific score function when there is no app specific score in the cache should eventually update the cache. + score := reg.AppSpecificScoreFunc()(peerID) + // peer does not have spam record, but has an unknown identity. Hence, the app specific score should be the staking penalty. + return scoreOptParameters.UnknownIdentityPenalty == score + }, 5*time.Second, 100*time.Millisecond) + // queryTime := time.Now() // report a misbehavior for the peer id. reg.OnInvalidControlMessageNotification(&p2p.InvCtrlMsgNotif{ PeerID: peerID, @@ -144,49 +273,104 @@ func testSpamRecordWithUnknownIdentity(t *testing.T, messageType p2pmsg.ControlM // the penalty should now be updated. record, err, ok := spamRecords.Get(peerID) // get the record from the spamRecords. - assert.True(t, ok) - assert.NoError(t, err) - assert.Less(t, math.Abs(expectedPenalty-record.Penalty), 10e-3) // penalty should be updated to -10, we account for decay. - assert.Equal(t, scoring.InitAppScoreRecordState().Decay, record.Decay) // decay should be initialized to the initial state. - // the peer has spam record as well as an unknown identity. Hence, the app specific score should be the spam penalty - // and the staking penalty. - score = reg.AppSpecificScoreFunc()(peerID) - assert.Less(t, math.Abs(expectedPenalty+scoring.DefaultUnknownIdentityPenalty-score), 10e-3) + require.True(t, ok) + require.NoError(t, err) + require.Less(t, math.Abs(expectedPenalty-record.Penalty), 10e-3) // penalty should be updated to -10, we account for decay. + require.Equal(t, scoring.InitAppScoreRecordStateFunc(cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.SpamRecordCache.Decay.MaximumSpamPenaltyDecayFactor)().Decay, record.Decay) // decay should be initialized to the initial state. + + queryTime := time.Now() + // eventually, the app specific score should be updated in the cache. + require.Eventually(t, func() bool { + // calling the app specific score function when there is no app specific score in the cache should eventually update the cache. + score := reg.AppSpecificScoreFunc()(peerID) + // the peer has spam record as well as an unknown identity. Hence, the app specific score should be the spam penalty + // and the staking penalty. + // As the app specific score in the cache and spam penalty in the spamRecords are updated at different times, we account for 0.1% error. + return unittest.AreNumericallyClose(expectedPenalty+scoreOptParameters.UnknownIdentityPenalty, score, 0.01) + }, 5*time.Second, 10*time.Millisecond) + + // the app specific score should now be updated in the cache. + score, updated, exists = appScoreCache.Get(peerID) // get the score from the cache. + require.True(t, exists) + require.True(t, updated.After(queryTime)) + + unittest.RequireNumericallyClose(t, expectedPenalty+scoreOptParameters.UnknownIdentityPenalty, score, 0.01) + assert.Equal(t, scoring.InitAppScoreRecordStateFunc(cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.SpamRecordCache.Decay.MaximumSpamPenaltyDecayFactor)().Decay, record.Decay) // decay should be initialized to the initial state. + + // stop the registry. + cancel() + unittest.RequireCloseBefore(t, reg.Done(), 1*time.Second, "failed to stop GossipSubAppSpecificScoreRegistry") } -func TestSpamRecord_With_SubscriptionPenalty(t *testing.T) { +// TestScoreRegistry_SpamRecordWithSubscriptionPenalty is a test suite for verifying the behavior of the ScoreRegistry +// in handling spam records associated with invalid subscriptions. It encompasses a series of sub-tests, each focusing on +// a different control message type: graft, prune, ihave, iwant, and RpcPublishMessage. These sub-tests are designed to +// validate the appropriate application of penalties in the ScoreRegistry when a peer with an invalid subscription is involved +// in spam activities, as indicated by these control messages. +func TestScoreRegistry_SpamRecordWithSubscriptionPenalty(t *testing.T) { t.Run("graft", func(t *testing.T) { - testSpamRecordWithSubscriptionPenalty(t, p2pmsg.CtrlMsgGraft, penaltyValueFixtures().Graft) + testScoreRegistrySpamRecordWithSubscriptionPenalty(t, p2pmsg.CtrlMsgGraft, penaltyValueFixtures().GraftMisbehaviour) }) t.Run("prune", func(t *testing.T) { - testSpamRecordWithSubscriptionPenalty(t, p2pmsg.CtrlMsgPrune, penaltyValueFixtures().Prune) + testScoreRegistrySpamRecordWithSubscriptionPenalty(t, p2pmsg.CtrlMsgPrune, penaltyValueFixtures().PruneMisbehaviour) }) t.Run("ihave", func(t *testing.T) { - testSpamRecordWithSubscriptionPenalty(t, p2pmsg.CtrlMsgIHave, penaltyValueFixtures().IHave) + testScoreRegistrySpamRecordWithSubscriptionPenalty(t, p2pmsg.CtrlMsgIHave, penaltyValueFixtures().IHaveMisbehaviour) }) t.Run("iwant", func(t *testing.T) { - testSpamRecordWithSubscriptionPenalty(t, p2pmsg.CtrlMsgIWant, penaltyValueFixtures().IWant) + testScoreRegistrySpamRecordWithSubscriptionPenalty(t, p2pmsg.CtrlMsgIWant, penaltyValueFixtures().IWantMisbehaviour) }) t.Run("RpcPublishMessage", func(t *testing.T) { - testSpamRecordWithSubscriptionPenalty(t, p2pmsg.RpcPublishMessage, penaltyValueFixtures().RpcPublishMessage) + testScoreRegistrySpamRecordWithSubscriptionPenalty(t, p2pmsg.RpcPublishMessage, penaltyValueFixtures().PublishMisbehaviour) }) } -// testSpamRecordWithUnknownIdentity tests the app specific penalty computation of the node when there is a spam record for the peer id and -// the peer id has an invalid subscription as well. -func testSpamRecordWithSubscriptionPenalty(t *testing.T, messageType p2pmsg.ControlMessageType, expectedPenalty float64) { - peerID := unittest.PeerIdFixture(t) - reg, spamRecords := newGossipSubAppSpecificScoreRegistry( - t, - withStakedIdentity(peerID), +// testScoreRegistrySpamRecordWithSubscriptionPenalty tests the application-specific penalty computation in the ScoreRegistry +// when a spam record exists for a peer ID that also has an invalid subscription. The function simulates the process of +// initializing the registry, handling spam records, and updating penalties based on various control message types. +// Parameters: +// - t *testing.T: The testing context. +// - messageType p2pmsg.ControlMessageType: The type of control message being tested. +// - expectedPenalty float64: The expected penalty value for the given control message type. +// The function focuses on evaluating the registry's response to spam activities (as represented by control messages) from a +// peer with invalid subscriptions. It verifies that penalties are accurately computed and applied, taking into account both +// the spam record and the invalid subscription status of the peer. +func testScoreRegistrySpamRecordWithSubscriptionPenalty(t *testing.T, messageType p2pmsg.ControlMessageType, expectedPenalty float64) { + peerID := peer.ID("peer-1") + cfg, err := config.DefaultConfig() + require.NoError(t, err) + // refresh cached app-specific score every 100 milliseconds to speed up the test. + cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.AppSpecificScore.ScoreTTL = 100 * time.Millisecond + + reg, spamRecords, appScoreCache := newGossipSubAppSpecificScoreRegistry(t, + cfg.NetworkConfig.GossipSub.ScoringParameters, + scoring.InitAppScoreRecordStateFunc(cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.SpamRecordCache.Decay.MaximumSpamPenaltyDecayFactor), + withStakedIdentities(peerID), withInvalidSubscriptions(peerID)) + ctx, cancel := context.WithCancel(context.Background()) + signalerCtx := irrecoverable.NewMockSignalerContext(t, ctx) + reg.Start(signalerCtx) + unittest.RequireCloseBefore(t, reg.Ready(), 1*time.Second, "registry did not start in time") + + defer stopRegistry(t, cancel, reg) - // initially, the spamRecords should not have the peer id. - assert.False(t, spamRecords.Has(peerID)) + // initially, the spamRecords should not have the peer id; also the app specific score record should not be in the cache. + require.False(t, spamRecords.Has(peerID)) + score, updated, exists := appScoreCache.Get(peerID) // get the score from the cache. + require.False(t, exists) + require.Equal(t, time.Time{}, updated) + require.Equal(t, float64(0), score) + + scoreOptParameters := cfg.NetworkConfig.GossipSub.ScoringParameters.PeerScoring.Protocol.AppSpecificScore // peer does not have spam record, but has invalid subscription. Hence, the app specific score should be subscription penalty. - score := reg.AppSpecificScoreFunc()(peerID) - require.Equal(t, scoring.DefaultInvalidSubscriptionPenalty, score) + // eventually the app specific score should be updated in the cache to the penalty value for subscription penalty. + require.Eventually(t, func() bool { + // calling the app specific score function when there is no app specific score in the cache should eventually update the cache. + score := reg.AppSpecificScoreFunc()(peerID) + // peer does not have spam record, but has an invalid subscription penalty. + return scoreOptParameters.InvalidSubscriptionPenalty == score + }, 5*time.Second, 100*time.Millisecond) // report a misbehavior for the peer id. reg.OnInvalidControlMessageNotification(&p2p.InvCtrlMsgNotif{ @@ -199,19 +383,49 @@ func testSpamRecordWithSubscriptionPenalty(t *testing.T, messageType p2pmsg.Cont assert.True(t, ok) assert.NoError(t, err) assert.Less(t, math.Abs(expectedPenalty-record.Penalty), 10e-3) - assert.Equal(t, scoring.InitAppScoreRecordState().Decay, record.Decay) // decay should be initialized to the initial state. - // the peer has spam record as well as an unknown identity. Hence, the app specific score should be the spam penalty - // and the staking penalty. - score = reg.AppSpecificScoreFunc()(peerID) - assert.Less(t, math.Abs(expectedPenalty+scoring.DefaultInvalidSubscriptionPenalty-score), 10e-3) + assert.Equal(t, scoring.InitAppScoreRecordStateFunc(cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.SpamRecordCache.Decay.MaximumSpamPenaltyDecayFactor)().Decay, record.Decay) // decay should be initialized to the initial state. + + queryTime := time.Now() + // eventually, the app specific score should be updated in the cache. + require.Eventually(t, func() bool { + // calling the app specific score function when there is no app specific score in the cache should eventually update the cache. + score := reg.AppSpecificScoreFunc()(peerID) + // the peer has spam record as well as an unknown identity. Hence, the app specific score should be the spam penalty + // and the staking penalty. + // As the app specific score in the cache and spam penalty in the spamRecords are updated at different times, we account for 0.1% error. + return unittest.AreNumericallyClose(expectedPenalty+scoreOptParameters.InvalidSubscriptionPenalty, score, 0.01) + }, 5*time.Second, 10*time.Millisecond) + + // the app specific score should now be updated in the cache. + score, updated, exists = appScoreCache.Get(peerID) // get the score from the cache. + require.True(t, exists) + require.True(t, updated.After(queryTime)) + unittest.RequireNumericallyClose(t, expectedPenalty+scoreOptParameters.InvalidSubscriptionPenalty, score, 0.01) + + // stop the registry. + cancel() + unittest.RequireCloseBefore(t, reg.Done(), 1*time.Second, "failed to stop GossipSubAppSpecificScoreRegistry") } // TestSpamPenaltyDecaysInCache tests that the spam penalty records decay over time in the cache. -func TestSpamPenaltyDecaysInCache(t *testing.T) { - peerID := unittest.PeerIdFixture(t) - reg, _ := newGossipSubAppSpecificScoreRegistry(t, - withStakedIdentity(peerID), +func TestScoreRegistry_SpamPenaltyDecaysInCache(t *testing.T) { + peerID := peer.ID("peer-1") + cfg, err := config.DefaultConfig() + require.NoError(t, err) + // refresh cached app-specific score every 100 milliseconds to speed up the test. + cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.AppSpecificScore.ScoreTTL = 100 * time.Millisecond + + reg, _, _ := newGossipSubAppSpecificScoreRegistry(t, + cfg.NetworkConfig.GossipSub.ScoringParameters, + scoring.InitAppScoreRecordStateFunc(cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.SpamRecordCache.Decay.MaximumSpamPenaltyDecayFactor), + withStakedIdentities(peerID), withValidSubscriptions(peerID)) + ctx, cancel := context.WithCancel(context.Background()) + signalerCtx := irrecoverable.NewMockSignalerContext(t, ctx) + reg.Start(signalerCtx) + unittest.RequireCloseBefore(t, reg.Ready(), 1*time.Second, "registry did not start in time") + + defer stopRegistry(t, cancel, reg) // report a misbehavior for the peer id. reg.OnInvalidControlMessageNotification(&p2p.InvCtrlMsgNotif{ @@ -249,40 +463,61 @@ func TestSpamPenaltyDecaysInCache(t *testing.T) { time.Sleep(1 * time.Second) // wait for the penalty to decay. - // when the app specific penalty function is called for the first time, the decay functionality should be kicked in - // the cache, and the penalty should be updated. Note that since the penalty values are negative, the default staked identity - // reward is not applied. Hence, the penalty is only comprised of the penalties. - score := reg.AppSpecificScoreFunc()(peerID) // the upper bound is the sum of the penalties without decay. - scoreUpperBound := penaltyValueFixtures().Prune + - penaltyValueFixtures().Graft + - penaltyValueFixtures().IHave + - penaltyValueFixtures().IWant + - penaltyValueFixtures().RpcPublishMessage + scoreUpperBound := penaltyValueFixtures().PruneMisbehaviour + + penaltyValueFixtures().GraftMisbehaviour + + penaltyValueFixtures().IHaveMisbehaviour + + penaltyValueFixtures().IWantMisbehaviour + + penaltyValueFixtures().PublishMisbehaviour // the lower bound is the sum of the penalties with decay assuming the decay is applied 4 times to the sum of the penalties. // in reality, the decay is applied 4 times to the first penalty, then 3 times to the second penalty, and so on. - r := scoring.InitAppScoreRecordState() + r := scoring.InitAppScoreRecordStateFunc(cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.SpamRecordCache.Decay.MaximumSpamPenaltyDecayFactor)() scoreLowerBound := scoreUpperBound * math.Pow(r.Decay, 4) - // with decay, the penalty should be between the upper and lower bounds. - assert.Greater(t, score, scoreUpperBound) - assert.Less(t, score, scoreLowerBound) + // eventually, the app specific score should be updated in the cache. + require.Eventually(t, func() bool { + // when the app specific penalty function is called for the first time, the decay functionality should be kicked in + // the cache, and the penalty should be updated. Note that since the penalty values are negative, the default staked identity + // reward is not applied. Hence, the penalty is only comprised of the penalties. + score := reg.AppSpecificScoreFunc()(peerID) + // with decay, the penalty should be between the upper and lower bounds. + return score > scoreUpperBound && score < scoreLowerBound + }, 5*time.Second, 100*time.Millisecond) + + // stop the registry. + cancel() + unittest.RequireCloseBefore(t, reg.Done(), 1*time.Second, "failed to stop GossipSubAppSpecificScoreRegistry") } // TestSpamPenaltyDecayToZero tests that the spam penalty decays to zero over time, and when the spam penalty of // a peer is set back to zero, its app specific penalty is also reset to the initial state. -func TestSpamPenaltyDecayToZero(t *testing.T) { - peerID := unittest.PeerIdFixture(t) - reg, spamRecords := newGossipSubAppSpecificScoreRegistry( - t, - withStakedIdentity(peerID), - withValidSubscriptions(peerID), - withInitFunction(func() p2p.GossipSubSpamRecord { +func TestScoreRegistry_SpamPenaltyDecayToZero(t *testing.T) { + peerID := peer.ID("peer-1") + cfg, err := config.DefaultConfig() + require.NoError(t, err) + // refresh cached app-specific score every 100 milliseconds to speed up the test. + cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.AppSpecificScore.ScoreTTL = 100 * time.Millisecond + + reg, spamRecords, _ := newGossipSubAppSpecificScoreRegistry(t, + cfg.NetworkConfig.GossipSub.ScoringParameters, + func() p2p.GossipSubSpamRecord { return p2p.GossipSubSpamRecord{ Decay: 0.02, // we choose a small decay value to speed up the test. Penalty: 0, } - })) + }, + withStakedIdentities(peerID), + withValidSubscriptions(peerID)) + + // starts the registry. + ctx, cancel := context.WithCancel(context.Background()) + signalerCtx := irrecoverable.NewMockSignalerContext(t, ctx) + reg.Start(signalerCtx) + unittest.RequireCloseBefore(t, reg.Ready(), 1*time.Second, "registry did not start in time") + + defer stopRegistry(t, cancel, reg) + + scoreOptParameters := cfg.NetworkConfig.GossipSub.ScoringParameters.PeerScoring.Protocol.AppSpecificScore // report a misbehavior for the peer id. reg.OnInvalidControlMessageNotification(&p2p.InvCtrlMsgNotif{ @@ -293,9 +528,11 @@ func TestSpamPenaltyDecayToZero(t *testing.T) { // decays happen every second, so we wait for 1 second to make sure the penalty is updated. time.Sleep(1 * time.Second) // the penalty should now be updated, it should be still negative but greater than the penalty value (due to decay). - score := reg.AppSpecificScoreFunc()(peerID) - require.Less(t, score, float64(0)) // the penalty should be less than zero. - require.Greater(t, score, penaltyValueFixtures().Graft) // the penalty should be less than the penalty value due to decay. + require.Eventually(t, func() bool { + score := reg.AppSpecificScoreFunc()(peerID) + // the penalty should be less than zero and greater than the penalty value (due to decay). + return score < 0 && score > penaltyValueFixtures().GraftMisbehaviour + }, 5*time.Second, 100*time.Millisecond) require.Eventually(t, func() bool { // the spam penalty should eventually decay to zero. @@ -305,7 +542,7 @@ func TestSpamPenaltyDecayToZero(t *testing.T) { require.Eventually(t, func() bool { // when the spam penalty is decayed to zero, the app specific penalty of the node should reset back to default staking reward. - return reg.AppSpecificScoreFunc()(peerID) == scoring.DefaultStakedIdentityReward + return reg.AppSpecificScoreFunc()(peerID) == scoreOptParameters.StakedIdentityReward }, 5*time.Second, 100*time.Millisecond) // the penalty should now be zero. @@ -313,25 +550,48 @@ func TestSpamPenaltyDecayToZero(t *testing.T) { assert.True(t, ok) assert.NoError(t, err) assert.Equal(t, 0.0, record.Penalty) // penalty should be zero. + + // stop the registry. + cancel() + unittest.RequireCloseBefore(t, reg.Done(), 1*time.Second, "failed to stop GossipSubAppSpecificScoreRegistry") } // TestPersistingUnknownIdentityPenalty tests that even though the spam penalty is decayed to zero, the unknown identity penalty // is persisted. This is because the unknown identity penalty is not decayed. -func TestPersistingUnknownIdentityPenalty(t *testing.T) { - peerID := unittest.PeerIdFixture(t) - reg, spamRecords := newGossipSubAppSpecificScoreRegistry( - t, - withUnknownIdentity(peerID), // the peer id has an unknown identity. - withValidSubscriptions(peerID), - withInitFunction(func() p2p.GossipSubSpamRecord { +func TestScoreRegistry_PersistingUnknownIdentityPenalty(t *testing.T) { + peerID := peer.ID("peer-1") + + cfg, err := config.DefaultConfig() + require.NoError(t, err) + // refresh cached app-specific score every 100 milliseconds to speed up the test. + cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.AppSpecificScore.ScoreTTL = 100 * time.Millisecond + + reg, spamRecords, _ := newGossipSubAppSpecificScoreRegistry(t, + cfg.NetworkConfig.GossipSub.ScoringParameters, + func() p2p.GossipSubSpamRecord { return p2p.GossipSubSpamRecord{ Decay: 0.02, // we choose a small decay value to speed up the test. Penalty: 0, } - })) + }, + withUnknownIdentity(peerID), // the peer id has an unknown identity. + withValidSubscriptions(peerID)) + + // starts the registry. + ctx, cancel := context.WithCancel(context.Background()) + signalerCtx := irrecoverable.NewMockSignalerContext(t, ctx) + reg.Start(signalerCtx) + unittest.RequireCloseBefore(t, reg.Ready(), 1*time.Second, "registry did not start in time") + + defer stopRegistry(t, cancel, reg) + + scoreOptParameters := cfg.NetworkConfig.GossipSub.ScoringParameters.PeerScoring.Protocol.AppSpecificScore // initially, the app specific score should be the default unknown identity penalty. - require.Equal(t, scoring.DefaultUnknownIdentityPenalty, reg.AppSpecificScoreFunc()(peerID)) + require.Eventually(t, func() bool { + score := reg.AppSpecificScoreFunc()(peerID) + return score == scoreOptParameters.UnknownIdentityPenalty + }, 5*time.Second, 100*time.Millisecond) // report a misbehavior for the peer id. reg.OnInvalidControlMessageNotification(&p2p.InvCtrlMsgNotif{ @@ -339,20 +599,17 @@ func TestPersistingUnknownIdentityPenalty(t *testing.T) { MsgType: p2pmsg.CtrlMsgGraft, }) - // with reported spam, the app specific score should be the default unknown identity + the spam penalty. - diff := math.Abs(scoring.DefaultUnknownIdentityPenalty + penaltyValueFixtures().Graft - reg.AppSpecificScoreFunc()(peerID)) - normalizedDiff := diff / (scoring.DefaultUnknownIdentityPenalty + penaltyValueFixtures().Graft) - require.NotZero(t, normalizedDiff, "difference between the expected and actual app specific score should not be zero") - require.Less(t, - normalizedDiff, - 0.01, "normalized difference between the expected and actual app specific score should be less than 1%") - // decays happen every second, so we wait for 1 second to make sure the penalty is updated. time.Sleep(1 * time.Second) + // the penalty should now be updated, it should be still negative but greater than the penalty value (due to decay). - score := reg.AppSpecificScoreFunc()(peerID) - require.Less(t, score, float64(0)) // the penalty should be less than zero. - require.Greater(t, score, penaltyValueFixtures().Graft+scoring.DefaultUnknownIdentityPenalty) // the penalty should be less than the penalty value due to decay. + require.Eventually(t, func() bool { + score := reg.AppSpecificScoreFunc()(peerID) + // Ideally, the score should be the sum of the default invalid subscription penalty and the graft penalty, however, + // due to exponential decay of the spam penalty and asynchronous update the app specific score; score should be in the range of [scoring. + // (scoring.DefaultUnknownIdentityPenalty+penaltyValueFixtures().GraftMisbehaviour, scoring.DefaultUnknownIdentityPenalty). + return score < scoreOptParameters.UnknownIdentityPenalty && score > scoreOptParameters.UnknownIdentityPenalty+penaltyValueFixtures().GraftMisbehaviour + }, 5*time.Second, 100*time.Millisecond) require.Eventually(t, func() bool { // the spam penalty should eventually decay to zero. @@ -362,7 +619,7 @@ func TestPersistingUnknownIdentityPenalty(t *testing.T) { require.Eventually(t, func() bool { // when the spam penalty is decayed to zero, the app specific penalty of the node should only contain the unknown identity penalty. - return reg.AppSpecificScoreFunc()(peerID) == scoring.DefaultUnknownIdentityPenalty + return reg.AppSpecificScoreFunc()(peerID) == scoreOptParameters.UnknownIdentityPenalty }, 5*time.Second, 100*time.Millisecond) // the spam penalty should now be zero in spamRecords. @@ -370,25 +627,46 @@ func TestPersistingUnknownIdentityPenalty(t *testing.T) { assert.True(t, ok) assert.NoError(t, err) assert.Equal(t, 0.0, record.Penalty) // penalty should be zero. + + // stop the registry. + cancel() + unittest.RequireCloseBefore(t, reg.Done(), 1*time.Second, "failed to stop GossipSubAppSpecificScoreRegistry") } // TestPersistingInvalidSubscriptionPenalty tests that even though the spam penalty is decayed to zero, the invalid subscription penalty // is persisted. This is because the invalid subscription penalty is not decayed. -func TestPersistingInvalidSubscriptionPenalty(t *testing.T) { - peerID := unittest.PeerIdFixture(t) - reg, spamRecords := newGossipSubAppSpecificScoreRegistry( - t, - withStakedIdentity(peerID), - withInvalidSubscriptions(peerID), // the peer id has an invalid subscription. - withInitFunction(func() p2p.GossipSubSpamRecord { +func TestScoreRegistry_PersistingInvalidSubscriptionPenalty(t *testing.T) { + peerID := peer.ID("peer-1") + + cfg, err := config.DefaultConfig() + require.NoError(t, err) + // refresh cached app-specific score every 100 milliseconds to speed up the test. + cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.AppSpecificScore.ScoreTTL = 100 * time.Millisecond + + reg, spamRecords, _ := newGossipSubAppSpecificScoreRegistry(t, + cfg.NetworkConfig.GossipSub.ScoringParameters, + func() p2p.GossipSubSpamRecord { return p2p.GossipSubSpamRecord{ Decay: 0.02, // we choose a small decay value to speed up the test. Penalty: 0, } - })) + }, + withStakedIdentities(peerID), + withInvalidSubscriptions(peerID)) // the peer id has an invalid subscription + + // starts the registry. + ctx, cancel := context.WithCancel(context.Background()) + signalerCtx := irrecoverable.NewMockSignalerContext(t, ctx) + reg.Start(signalerCtx) + unittest.RequireCloseBefore(t, reg.Ready(), 1*time.Second, "failed to start GossipSubAppSpecificScoreRegistry") + + scoreOptParameters := cfg.NetworkConfig.GossipSub.ScoringParameters.PeerScoring.Protocol.AppSpecificScore // initially, the app specific score should be the default invalid subscription penalty. - require.Equal(t, scoring.DefaultUnknownIdentityPenalty, reg.AppSpecificScoreFunc()(peerID)) + require.Eventually(t, func() bool { + score := reg.AppSpecificScoreFunc()(peerID) + return score == scoreOptParameters.InvalidSubscriptionPenalty + }, 5*time.Second, 100*time.Millisecond) // report a misbehavior for the peer id. reg.OnInvalidControlMessageNotification(&p2p.InvCtrlMsgNotif{ @@ -397,14 +675,13 @@ func TestPersistingInvalidSubscriptionPenalty(t *testing.T) { }) // with reported spam, the app specific score should be the default invalid subscription penalty + the spam penalty. - require.Less(t, math.Abs(scoring.DefaultInvalidSubscriptionPenalty+penaltyValueFixtures().Graft-reg.AppSpecificScoreFunc()(peerID)), 10e-3) - - // decays happen every second, so we wait for 1 second to make sure the penalty is updated. - time.Sleep(1 * time.Second) - // the penalty should now be updated, it should be still negative but greater than the penalty value (due to decay). - score := reg.AppSpecificScoreFunc()(peerID) - require.Less(t, score, float64(0)) // the penalty should be less than zero. - require.Greater(t, score, penaltyValueFixtures().Graft+scoring.DefaultInvalidSubscriptionPenalty) // the penalty should be less than the penalty value due to decay. + require.Eventually(t, func() bool { + score := reg.AppSpecificScoreFunc()(peerID) + // Ideally, the score should be the sum of the default invalid subscription penalty and the graft penalty, however, + // due to exponential decay of the spam penalty and asynchronous update the app specific score; score should be in the range of [scoring. + // (DefaultInvalidSubscriptionPenalty+penaltyValueFixtures().GraftMisbehaviour, scoring.DefaultInvalidSubscriptionPenalty). + return score < scoreOptParameters.InvalidSubscriptionPenalty && score > scoreOptParameters.InvalidSubscriptionPenalty+penaltyValueFixtures().GraftMisbehaviour + }, 5*time.Second, 100*time.Millisecond) require.Eventually(t, func() bool { // the spam penalty should eventually decay to zero. @@ -414,7 +691,7 @@ func TestPersistingInvalidSubscriptionPenalty(t *testing.T) { require.Eventually(t, func() bool { // when the spam penalty is decayed to zero, the app specific penalty of the node should only contain the default invalid subscription penalty. - return reg.AppSpecificScoreFunc()(peerID) == scoring.DefaultUnknownIdentityPenalty + return reg.AppSpecificScoreFunc()(peerID) == scoreOptParameters.UnknownIdentityPenalty }, 5*time.Second, 100*time.Millisecond) // the spam penalty should now be zero in spamRecords. @@ -422,39 +699,52 @@ func TestPersistingInvalidSubscriptionPenalty(t *testing.T) { assert.True(t, ok) assert.NoError(t, err) assert.Equal(t, 0.0, record.Penalty) // penalty should be zero. + + // stop the registry. + cancel() + unittest.RequireCloseBefore(t, reg.Done(), 1*time.Second, "failed to stop GossipSubAppSpecificScoreRegistry") } -// TestSpamRecordDecayAdjustment ensures that spam record decay is increased each time a peers score reaches the scoring.IncreaseDecayThreshold eventually +// TestScoreRegistry_TestSpamRecordDecayAdjustment ensures that spam record decay is increased each time a peers score reaches the scoring.IncreaseDecayThreshold eventually // sustained misbehavior will result in the spam record decay reaching the minimum decay speed .99, and the decay speed is reset to the max decay speed .8. -func TestSpamRecordDecayAdjustment(t *testing.T) { - flowConfig, err := config.DefaultConfig() +func TestScoreRegistry_TestSpamRecordDecayAdjustment(t *testing.T) { + cfg, err := config.DefaultConfig() require.NoError(t, err) - scoringRegistryConfig := flowConfig.NetworkConfig.GossipSubConfig.GossipSubScoringRegistryConfig + // refresh cached app-specific score every 100 milliseconds to speed up the test. + cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.AppSpecificScore.ScoreTTL = 100 * time.Millisecond // increase configured DecayRateReductionFactor so that the decay time is increased faster - scoringRegistryConfig.DecayRateReductionFactor = .1 - scoringRegistryConfig.PenaltyDecayEvaluationPeriod = time.Second + cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.SpamRecordCache.Decay.DecayRateReductionFactor = .1 + cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.SpamRecordCache.Decay.PenaltyDecayEvaluationPeriod = time.Second peer1 := unittest.PeerIdFixture(t) peer2 := unittest.PeerIdFixture(t) - reg, spamRecords := newScoringRegistry( - t, - scoringRegistryConfig, - withStakedIdentity(peer1), - withValidSubscriptions(peer1), - withStakedIdentity(peer2), - withValidSubscriptions(peer2)) + reg, spamRecords, _ := newGossipSubAppSpecificScoreRegistry(t, + cfg.NetworkConfig.GossipSub.ScoringParameters, + scoring.InitAppScoreRecordStateFunc(cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.SpamRecordCache.Decay.MaximumSpamPenaltyDecayFactor), + withStakedIdentities(peer1, peer2), + withValidSubscriptions(peer1, peer2)) + + ctx, cancel := context.WithCancel(context.Background()) + signalerCtx := irrecoverable.NewMockSignalerContext(t, ctx) + reg.Start(signalerCtx) + unittest.RequireCloseBefore(t, reg.Ready(), 1*time.Second, "failed to start GossipSubAppSpecificScoreRegistry") // initially, the spamRecords should not have the peer ids. assert.False(t, spamRecords.Has(peer1)) assert.False(t, spamRecords.Has(peer2)) + + scoreOptParameters := cfg.NetworkConfig.GossipSub.ScoringParameters.PeerScoring.Protocol.AppSpecificScore + scoringRegistryParameters := cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters // since the both peers do not have a spam record, their app specific score should be the max app specific reward, which // is the default reward for a staked peer that has valid subscriptions. - assert.Equal(t, scoring.MaxAppSpecificReward, reg.AppSpecificScoreFunc()(peer1)) - assert.Equal(t, scoring.MaxAppSpecificReward, reg.AppSpecificScoreFunc()(peer2)) + require.Eventually(t, func() bool { + // when the spam penalty is decayed to zero, the app specific penalty of the node should only contain the unknown identity penalty. + return scoreOptParameters.MaxAppSpecificReward == reg.AppSpecificScoreFunc()(peer1) && scoreOptParameters.MaxAppSpecificReward == reg.AppSpecificScoreFunc()(peer2) + }, 5*time.Second, 100*time.Millisecond) // simulate sustained malicious activity from peer1, eventually the decay speed // for a spam record should be reduced to the MinimumSpamPenaltyDecayFactor - prevDecay := scoring.MaximumSpamPenaltyDecayFactor + prevDecay := scoringRegistryParameters.SpamRecordCache.Decay.MaximumSpamPenaltyDecayFactor tolerance := 0.1 require.Eventually(t, func() bool { reg.OnInvalidControlMessageNotification(&p2p.InvCtrlMsgNotif{ @@ -466,7 +756,7 @@ func TestSpamRecordDecayAdjustment(t *testing.T) { require.True(t, ok) assert.Less(t, math.Abs(prevDecay-record.Decay), tolerance) prevDecay = record.Decay - return record.Decay == scoring.MinimumSpamPenaltyDecayFactor + return record.Decay == scoringRegistryParameters.SpamRecordCache.Decay.MinimumSpamPenaltyDecayFactor }, 5*time.Second, 500*time.Millisecond) // initialize a spam record for peer2 @@ -475,23 +765,23 @@ func TestSpamRecordDecayAdjustment(t *testing.T) { MsgType: p2pmsg.CtrlMsgPrune, }) // reduce penalty and increase Decay to scoring.MinimumSpamPenaltyDecayFactor - record, err := spamRecords.Update(peer2, func(record p2p.GossipSubSpamRecord) p2p.GossipSubSpamRecord { + record, err := spamRecords.Adjust(peer2, func(record p2p.GossipSubSpamRecord) p2p.GossipSubSpamRecord { record.Penalty = -.1 - record.Decay = scoring.MinimumSpamPenaltyDecayFactor + record.Decay = scoringRegistryParameters.SpamRecordCache.Decay.MinimumSpamPenaltyDecayFactor return record }) require.NoError(t, err) - require.True(t, record.Decay == scoring.MinimumSpamPenaltyDecayFactor) + require.True(t, record.Decay == scoringRegistryParameters.SpamRecordCache.Decay.MinimumSpamPenaltyDecayFactor) require.True(t, record.Penalty == -.1) // simulate sustained good behavior from peer 2, each time the spam record is read from the cache // using Get method the record penalty will be decayed until it is eventually reset to // 0 at this point the decay speed for the record should be reset to MaximumSpamPenaltyDecayFactor - // eventually after penalty reaches the skipDecaThreshold the record decay will be reset to scoring.MaximumSpamPenaltyDecayFactor + // eventually after penalty reaches the skipDecaThreshold the record decay will be reset to scoringRegistryParameters.MaximumSpamPenaltyDecayFactor require.Eventually(t, func() bool { record, err, ok := spamRecords.Get(peer2) require.NoError(t, err) require.True(t, ok) - return record.Decay == scoring.MaximumSpamPenaltyDecayFactor && + return record.Decay == scoringRegistryParameters.SpamRecordCache.Decay.MaximumSpamPenaltyDecayFactor && record.Penalty == 0 && record.LastDecayAdjustment.IsZero() }, 5*time.Second, time.Second) @@ -505,8 +795,12 @@ func TestSpamRecordDecayAdjustment(t *testing.T) { record, err, ok := spamRecords.Get(peer1) require.NoError(t, err) require.True(t, ok) - return record.Decay == scoring.MinimumSpamPenaltyDecayFactor + return record.Decay == scoringRegistryParameters.SpamRecordCache.Decay.MinimumSpamPenaltyDecayFactor }, 5*time.Second, 500*time.Millisecond) + + // stop the registry. + cancel() + unittest.RequireCloseBefore(t, reg.Done(), 1*time.Second, "failed to stop GossipSubAppSpecificScoreRegistry") } // TestPeerSpamPenaltyClusterPrefixed evaluates the application-specific penalty calculation for a node when a spam record is present @@ -516,19 +810,39 @@ func TestSpamRecordDecayAdjustment(t *testing.T) { func TestPeerSpamPenaltyClusterPrefixed(t *testing.T) { ctlMsgTypes := p2pmsg.ControlMessageTypes() peerIds := unittest.PeerIdFixtures(t, len(ctlMsgTypes)) - opts := make([]scoringRegistryParamsOpt, 0) - for _, peerID := range peerIds { - opts = append(opts, withStakedIdentity(peerID), withValidSubscriptions(peerID)) - } - reg, spamRecords := newGossipSubAppSpecificScoreRegistry(t, opts...) + + cfg, err := config.DefaultConfig() + require.NoError(t, err) + // refresh cached app-specific score every 100 milliseconds to speed up the test. + cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.AppSpecificScore.ScoreTTL = 100 * time.Millisecond + + reg, spamRecords, _ := newGossipSubAppSpecificScoreRegistry(t, + cfg.NetworkConfig.GossipSub.ScoringParameters, + scoring.InitAppScoreRecordStateFunc(cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.SpamRecordCache.Decay.MaximumSpamPenaltyDecayFactor), + withStakedIdentities(peerIds...), + withValidSubscriptions(peerIds...)) + + // starts the registry. + ctx, cancel := context.WithCancel(context.Background()) + signalerCtx := irrecoverable.NewMockSignalerContext(t, ctx) + reg.Start(signalerCtx) + unittest.RequireCloseBefore(t, reg.Ready(), 1*time.Second, "failed to start GossipSubAppSpecificScoreRegistry") + + scoreOptParameters := cfg.NetworkConfig.GossipSub.ScoringParameters.PeerScoring.Protocol.AppSpecificScore for _, peerID := range peerIds { // initially, the spamRecords should not have the peer id. assert.False(t, spamRecords.Has(peerID)) - // since the peer id does not have a spam record, the app specific score should be the max app specific reward, which + // since the peer id does not have a spam record, the app specific score should (eventually, due to caching) be the max app specific reward, which // is the default reward for a staked peer that has valid subscriptions. - score := reg.AppSpecificScoreFunc()(peerID) - assert.Equal(t, scoring.MaxAppSpecificReward, score) + require.Eventually(t, func() bool { + // calling the app specific score function when there is no app specific score in the cache should eventually update the cache. + score := reg.AppSpecificScoreFunc()(peerID) + // since the peer id does not have a spam record, the app specific score should be the max app specific reward, which + // is the default reward for a staked peer that has valid subscriptions. + return score == scoreOptParameters.MaxAppSpecificReward + }, 5*time.Second, 100*time.Millisecond) + } // Report consecutive misbehavior's for the specified peer ID. Two misbehavior's are reported concurrently: @@ -556,15 +870,15 @@ func TestPeerSpamPenaltyClusterPrefixed(t *testing.T) { }() unittest.RequireReturnsBefore(t, wg.Wait, 100*time.Millisecond, "timed out waiting for goroutines to finish") - // expected penalty should be penaltyValueFixtures().Graft * (1 + clusterReductionFactor) - expectedPenalty := penaltyValueFixture(ctlMsgType) * (1 + penaltyValueFixtures().ClusterPrefixedPenaltyReductionFactor) + // expected penalty should be penaltyValueFixtures().GraftMisbehaviour * (1 + clusterReductionFactor) + expectedPenalty := penaltyValueFixture(ctlMsgType) * (1 + penaltyValueFixtures().ClusterPrefixedReductionFactor) // the penalty should now be updated in the spamRecords record, err, ok := spamRecords.Get(peerID) // get the record from the spamRecords. assert.True(t, ok) assert.NoError(t, err) assert.Less(t, math.Abs(expectedPenalty-record.Penalty), 10e-3) - assert.Equal(t, scoring.InitAppScoreRecordState().Decay, record.Decay) + assert.Equal(t, scoring.InitAppScoreRecordStateFunc(cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.SpamRecordCache.Decay.MaximumSpamPenaltyDecayFactor)().Decay, record.Decay) // this peer has a spam record, with no subscription penalty. Hence, the app specific score should only be the spam penalty, // and the peer should be deprived of the default reward for its valid staked role. score := reg.AppSpecificScoreFunc()(peerID) @@ -575,21 +889,143 @@ func TestPeerSpamPenaltyClusterPrefixed(t *testing.T) { assert.Less(t, math.Abs(expectedPenalty-score)/expectedPenalty, tolerance) } } + + // stop the registry. + cancel() + unittest.RequireCloseBefore(t, reg.Done(), 1*time.Second, "failed to stop GossipSubAppSpecificScoreRegistry") +} + +// TestScoringRegistrySilencePeriod ensures that the scoring registry does not penalize nodes during the silence period, and +// starts to penalize nodes only after the silence period is over. +func TestScoringRegistrySilencePeriod(t *testing.T) { + peerID := unittest.PeerIdFixture(t) + silenceDuration := 5 * time.Second + silencedNotificationLogs := atomic.NewInt32(0) + hook := zerolog.HookFunc(func(e *zerolog.Event, level zerolog.Level, message string) { + if level == zerolog.TraceLevel { + if message == scoring.NotificationSilencedMsg { + silencedNotificationLogs.Inc() + } + } + }) + logger := zerolog.New(os.Stdout).Level(zerolog.TraceLevel).Hook(hook) + + cfg, err := config.DefaultConfig() + require.NoError(t, err) + // refresh cached app-specific score every 100 milliseconds to speed up the test. + cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.AppSpecificScore.ScoreTTL = 100 * time.Millisecond + maximumSpamPenaltyDecayFactor := cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.SpamRecordCache.Decay.MaximumSpamPenaltyDecayFactor + reg, spamRecords, _ := newGossipSubAppSpecificScoreRegistry(t, + cfg.NetworkConfig.GossipSub.ScoringParameters, + scoring.InitAppScoreRecordStateFunc(maximumSpamPenaltyDecayFactor), + withUnknownIdentity(peerID), + withInvalidSubscriptions(peerID), + func(cfg *scoring.GossipSubAppSpecificScoreRegistryConfig) { + // we set the scoring registry silence duration 10 seconds + // the peer is not expected to be penalized for the first 5 seconds of the test + // after that an invalid control message notification is processed and the peer + // should be penalized + cfg.ScoringRegistryStartupSilenceDuration = silenceDuration + // hooked logger will capture the number of logs related to ignored notifications + cfg.Logger = logger + }) + + ctx, cancel := context.WithCancel(context.Background()) + signalerCtx := irrecoverable.NewMockSignalerContext(t, ctx) + defer stopRegistry(t, cancel, reg) + // capture approximate registry start time + reg.Start(signalerCtx) + unittest.RequireCloseBefore(t, reg.Ready(), 1*time.Second, "registry did not start in time") + + registryStartTime := time.Now() + expectedNumOfSilencedNotif := 0 + // while we are in the silence period all notifications should be ignored and the + // invalid subscription penalty should not be applied to the app specific score + // we ensure we stay within the silence duration by iterating only up until 1 second + // before silence period is over + for time.Since(registryStartTime) < (silenceDuration - time.Second) { + // report a misbehavior for the peer id. + reg.OnInvalidControlMessageNotification(&p2p.InvCtrlMsgNotif{ + PeerID: peerID, + MsgType: p2pmsg.CtrlMsgGraft, + }) + expectedNumOfSilencedNotif++ + // spam records should not be created during the silence period + _, err, ok := spamRecords.Get(peerID) + assert.False(t, ok) + assert.NoError(t, err) + // initially, the app specific score should be the default invalid subscription penalty. + require.Equal(t, float64(0), reg.AppSpecificScoreFunc()(peerID)) + } + + invalidSubscriptionPenalty := cfg.NetworkConfig.GossipSub.ScoringParameters.PeerScoring.Protocol.AppSpecificScore.InvalidSubscriptionPenalty + + require.Eventually(t, func() bool { + // we expect to have logged a debug message for all notifications ignored. + require.Equal(t, int32(expectedNumOfSilencedNotif), silencedNotificationLogs.Load()) + // after silence period the invalid subscription penalty should be applied to the app specific score + return invalidSubscriptionPenalty == reg.AppSpecificScoreFunc()(peerID) + }, 2*time.Second, 200*time.Millisecond) + + // after silence period the peer has spam record as well as an unknown identity. Hence, the app specific score should be the spam penalty + // and the staking penalty. + reg.OnInvalidControlMessageNotification(&p2p.InvCtrlMsgNotif{ + PeerID: peerID, + MsgType: p2pmsg.CtrlMsgGraft, + }) + // the penalty should now be applied and spam records created. + record, err, ok := spamRecords.Get(peerID) + assert.True(t, ok) + assert.NoError(t, err) + expectedPenalty := penaltyValueFixtures().GraftMisbehaviour + assert.Less(t, math.Abs(expectedPenalty-record.Penalty), 10e-3) + assert.Equal(t, scoring.InitAppScoreRecordStateFunc(maximumSpamPenaltyDecayFactor)().Decay, record.Decay) // decay should be initialized to the initial state. + + require.Eventually(t, func() bool { + // we expect to have logged a debug message for all notifications ignored. + require.Equal(t, int32(expectedNumOfSilencedNotif), silencedNotificationLogs.Load()) + // after silence period the invalid subscription penalty should be applied to the app specific score + return invalidSubscriptionPenalty+expectedPenalty-reg.AppSpecificScoreFunc()(peerID) < 0.1 + }, 2*time.Second, 200*time.Millisecond) } -// withStakedIdentity returns a function that sets the identity provider to return an staked identity for the given peer id. +// withStakedIdentities returns a function that sets the identity provider to return staked identities for the given peer ids. // It is used for testing purposes, and causes the given peer id to benefit from the staked identity reward in GossipSub. -func withStakedIdentity(peerId peer.ID) func(cfg *scoring.GossipSubAppSpecificScoreRegistryConfig) { +func withStakedIdentities(peerIds ...peer.ID) func(cfg *scoring.GossipSubAppSpecificScoreRegistryConfig) { return func(cfg *scoring.GossipSubAppSpecificScoreRegistryConfig) { - cfg.IdProvider.(*mock.IdentityProvider).On("ByPeerID", peerId).Return(unittest.IdentityFixture(), true).Maybe() + cfg.IdProvider.(*mock.IdentityProvider).On("ByPeerID", testifymock.AnythingOfType("peer.ID")). + Return(func(pid peer.ID) *flow.Identity { + for _, peerID := range peerIds { + if peerID == pid { + return unittest.IdentityFixture() + } + } + return nil + }, func(pid peer.ID) bool { + for _, peerID := range peerIds { + if peerID == pid { + return true + } + } + return false + }).Maybe() } } -// withValidSubscriptions returns a function that sets the subscription validator to return nil for the given peer id. +// withValidSubscriptions returns a function that sets the subscription validator to return nil for the given peer ids. // It is used for testing purposes and causes the given peer id to never be penalized for subscribing to invalid topics. -func withValidSubscriptions(peer peer.ID) func(cfg *scoring.GossipSubAppSpecificScoreRegistryConfig) { +func withValidSubscriptions(peerIds ...peer.ID) func(cfg *scoring.GossipSubAppSpecificScoreRegistryConfig) { return func(cfg *scoring.GossipSubAppSpecificScoreRegistryConfig) { - cfg.Validator.(*mockp2p.SubscriptionValidator).On("CheckSubscribedToAllowedTopics", peer, testifymock.Anything).Return(nil).Maybe() + cfg.Validator.(*mockp2p.SubscriptionValidator). + On("CheckSubscribedToAllowedTopics", testifymock.AnythingOfType("peer.ID"), testifymock.Anything). + Return(func(pid peer.ID, _ flow.Role) error { + for _, peerID := range peerIds { + if peerID == pid { + return nil + } + } + return fmt.Errorf("invalid subscriptions") + }).Maybe() } } @@ -605,61 +1041,95 @@ func withUnknownIdentity(peer peer.ID) func(cfg *scoring.GossipSubAppSpecificSco // It is used for testing purposes and causes the given peer id to be penalized for subscribing to invalid topics. func withInvalidSubscriptions(peer peer.ID) func(cfg *scoring.GossipSubAppSpecificScoreRegistryConfig) { return func(cfg *scoring.GossipSubAppSpecificScoreRegistryConfig) { - cfg.Validator.(*mockp2p.SubscriptionValidator).On("CheckSubscribedToAllowedTopics", peer, testifymock.Anything).Return(fmt.Errorf("invalid subscriptions")).Maybe() - } -} - -func withInitFunction(initFunction scoring.SpamRecordInitFunc) func(cfg *scoring.GossipSubAppSpecificScoreRegistryConfig) { - return func(cfg *scoring.GossipSubAppSpecificScoreRegistryConfig) { - cfg.Init = initFunction + cfg.Validator.(*mockp2p.SubscriptionValidator).On("CheckSubscribedToAllowedTopics", + peer, + testifymock.Anything).Return(fmt.Errorf("invalid subscriptions")).Maybe() } } -type scoringRegistryParamsOpt func(*scoring.GossipSubAppSpecificScoreRegistryConfig) - -// newGossipSubAppSpecificScoreRegistry returns a new instance of GossipSubAppSpecificScoreRegistry with default values -// for the testing purposes. -func newGossipSubAppSpecificScoreRegistry(t *testing.T, opts ...scoringRegistryParamsOpt) (*scoring.GossipSubAppSpecificScoreRegistry, *netcache.GossipSubSpamRecordCache) { - flowConfig, err := config.DefaultConfig() - require.NoError(t, err) - scoringRegistryConfig := flowConfig.NetworkConfig.GossipSubConfig.GossipSubScoringRegistryConfig - return newScoringRegistry(t, scoringRegistryConfig, opts...) -} - -func newScoringRegistry(t *testing.T, config p2pconf.GossipSubScoringRegistryConfig, opts ...scoringRegistryParamsOpt) (*scoring.GossipSubAppSpecificScoreRegistry, *netcache.GossipSubSpamRecordCache) { - cache := netcache.NewGossipSubSpamRecordCache( - 100, +// newGossipSubAppSpecificScoreRegistry creates a new instance of GossipSubAppSpecificScoreRegistry along with its associated +// GossipSubSpamRecordCache and AppSpecificScoreCache. This function is primarily used in testing scenarios to set up a controlled +// environment for evaluating the behavior of the GossipSub scoring mechanism. +// +// The function accepts a variable number of options to configure the GossipSubAppSpecificScoreRegistryConfig, allowing for +// customization of the registry's behavior in tests. These options can modify various aspects of the configuration, such as +// penalty values, identity providers, validators, and caching mechanisms. +// +// Parameters: +// - t *testing.T: The test context, used for asserting the absence of errors during the setup. +// - params p2pconfig.ScoringParameters: The scoring parameters used to configure the registry. +// - initFunction scoring.SpamRecordInitFunc: The function used to initialize the spam records. +// - opts ...func(*scoring.GossipSubAppSpecificScoreRegistryConfig): A variadic set of functions that modify the registry's configuration. +// +// Returns: +// - *scoring.GossipSubAppSpecificScoreRegistry: The configured GossipSub application-specific score registry. +// - *netcache.GossipSubSpamRecordCache: The cache used for storing spam records. +// - *internal.AppSpecificScoreCache: The cache for storing application-specific scores. +// +// This function initializes and configures the scoring registry with default and test-specific settings. It sets up a spam record cache +// and an application-specific score cache with predefined sizes and functionalities. The function also configures the scoring parameters +// with test-specific values, particularly modifying the ScoreTTL value for the purpose of the tests. The creation and configuration of +// the GossipSubAppSpecificScoreRegistry are validated to ensure no errors occur during the process. +func newGossipSubAppSpecificScoreRegistry(t *testing.T, + params p2pconfig.ScoringParameters, + initFunction scoring.SpamRecordInitFunc, + opts ...func(*scoring.GossipSubAppSpecificScoreRegistryConfig)) (*scoring.GossipSubAppSpecificScoreRegistry, + *netcache.GossipSubSpamRecordCache, + *internal.AppSpecificScoreCache) { + cache := netcache.NewGossipSubSpamRecordCache(100, unittest.Logger(), metrics.NewNoopCollector(), - scoring.DefaultDecayFunction(config.PenaltyDecaySlowdownThreshold, config.DecayRateReductionFactor, config.PenaltyDecayEvaluationPeriod), - ) + initFunction, + scoring.DefaultDecayFunction(params.ScoringRegistryParameters.SpamRecordCache.Decay)) + appSpecificScoreCache := internal.NewAppSpecificScoreCache(100, unittest.Logger(), metrics.NewNoopCollector()) + + validator := mockp2p.NewSubscriptionValidator(t) + validator.On("Start", testifymock.Anything).Return().Maybe() + done := make(chan struct{}) + close(done) + f := func() <-chan struct{} { + return done + } + validator.On("Ready").Return(f()).Maybe() + validator.On("Done").Return(f()).Maybe() cfg := &scoring.GossipSubAppSpecificScoreRegistryConfig{ Logger: unittest.Logger(), - Init: scoring.InitAppScoreRecordState, Penalty: penaltyValueFixtures(), IdProvider: mock.NewIdentityProvider(t), - Validator: mockp2p.NewSubscriptionValidator(t), - CacheFactory: func() p2p.GossipSubSpamRecordCache { + Validator: validator, + AppScoreCacheFactory: func() p2p.GossipSubApplicationSpecificScoreCache { + return appSpecificScoreCache + }, + SpamRecordCacheFactory: func() p2p.GossipSubSpamRecordCache { return cache }, + Parameters: params.ScoringRegistryParameters.AppSpecificScore, + HeroCacheMetricsFactory: metrics.NewNoopHeroCacheMetricsFactory(), + NetworkingType: network.PrivateNetwork, + AppSpecificScoreParams: params.PeerScoring.Protocol.AppSpecificScore, + ScoringRegistryStartupSilenceDuration: 0, // turn off silence period by default } for _, opt := range opts { opt(cfg) } - return scoring.NewGossipSubAppSpecificScoreRegistry(cfg), cache + + reg, err := scoring.NewGossipSubAppSpecificScoreRegistry(cfg) + require.NoError(t, err, "failed to create GossipSubAppSpecificScoreRegistry") + + return reg, cache, appSpecificScoreCache } // penaltyValueFixtures returns a set of penalty values for testing purposes. // The values are not realistic. The important thing is that they are different from each other. This is to make sure // that the tests are not passing because of the default values. -func penaltyValueFixtures() scoring.GossipSubCtrlMsgPenaltyValue { - return scoring.GossipSubCtrlMsgPenaltyValue{ - Graft: -100, - Prune: -50, - IHave: -20, - IWant: -10, - ClusterPrefixedPenaltyReductionFactor: .5, - RpcPublishMessage: -10, +func penaltyValueFixtures() p2pconfig.MisbehaviourPenalties { + return p2pconfig.MisbehaviourPenalties{ + GraftMisbehaviour: -100, + PruneMisbehaviour: -50, + IHaveMisbehaviour: -20, + IWantMisbehaviour: -10, + ClusterPrefixedReductionFactor: .5, + PublishMisbehaviour: -10, } } @@ -668,16 +1138,21 @@ func penaltyValueFixture(msgType p2pmsg.ControlMessageType) float64 { penaltyValues := penaltyValueFixtures() switch msgType { case p2pmsg.CtrlMsgGraft: - return penaltyValues.Graft + return penaltyValues.GraftMisbehaviour case p2pmsg.CtrlMsgPrune: - return penaltyValues.Prune + return penaltyValues.PruneMisbehaviour case p2pmsg.CtrlMsgIHave: - return penaltyValues.IHave + return penaltyValues.IHaveMisbehaviour case p2pmsg.CtrlMsgIWant: - return penaltyValues.IWant + return penaltyValues.IWantMisbehaviour case p2pmsg.RpcPublishMessage: - return penaltyValues.RpcPublishMessage + return penaltyValues.PublishMisbehaviour default: - return penaltyValues.ClusterPrefixedPenaltyReductionFactor + return penaltyValues.ClusterPrefixedReductionFactor } } + +func stopRegistry(t *testing.T, cancel context.CancelFunc, registry *scoring.GossipSubAppSpecificScoreRegistry) { + cancel() + unittest.RequireCloseBefore(t, registry.Done(), 5*time.Second, "registry did not stop") +} diff --git a/network/p2p/scoring/score_option.go b/network/p2p/scoring/score_option.go index f3955700479..07b948d975e 100644 --- a/network/p2p/scoring/score_option.go +++ b/network/p2p/scoring/score_option.go @@ -12,336 +12,63 @@ import ( "github.com/onflow/flow-go/module/component" "github.com/onflow/flow-go/module/irrecoverable" "github.com/onflow/flow-go/module/metrics" + "github.com/onflow/flow-go/network" "github.com/onflow/flow-go/network/channels" "github.com/onflow/flow-go/network/p2p" netcache "github.com/onflow/flow-go/network/p2p/cache" - "github.com/onflow/flow-go/network/p2p/p2pconf" + p2pconfig "github.com/onflow/flow-go/network/p2p/config" + "github.com/onflow/flow-go/network/p2p/scoring/internal" "github.com/onflow/flow-go/network/p2p/utils" "github.com/onflow/flow-go/utils/logging" ) -const ( - // DefaultAppSpecificScoreWeight is the default weight for app-specific scores. It is used to scale the app-specific - // scores to the same range as the other scores. At the current version, we don't distinguish between the app-specific - // scores and the other scores, so we set it to 1. - DefaultAppSpecificScoreWeight = 1 - - // MaxAppSpecificReward is the default reward for well-behaving staked peers. If a peer does not have - // any misbehavior record, e.g., invalid subscription, invalid message, etc., it will be rewarded with this score. - MaxAppSpecificReward = float64(100) - - // MaxAppSpecificPenalty is the maximum penalty for sever offenses that we apply to a remote node score. The score - // mechanism of GossipSub in Flow is designed in a way that all other infractions are penalized with a fraction of - // this value. We have also set the other parameters such as DefaultGraylistThreshold, DefaultGossipThreshold and DefaultPublishThreshold to - // be a bit higher than this, i.e., MaxAppSpecificPenalty + 1. This ensures that a node with a score of MaxAppSpecificPenalty - // will be graylisted (i.e., all incoming and outgoing RPCs are rejected) and will not be able to publish or gossip any messages. - MaxAppSpecificPenalty = -1 * MaxAppSpecificReward - MinAppSpecificPenalty = -1 - - // DefaultStakedIdentityReward is the default reward for staking peers. It is applied to the peer's score when - // the peer does not have any misbehavior record, e.g., invalid subscription, invalid message, etc. - // The purpose is to reward the staking peers for their contribution to the network and prioritize them in neighbor selection. - DefaultStakedIdentityReward = MaxAppSpecificReward - - // DefaultUnknownIdentityPenalty is the default penalty for unknown identity. It is applied to the peer's score when - // the peer is not in the identity list. - DefaultUnknownIdentityPenalty = MaxAppSpecificPenalty - - // DefaultInvalidSubscriptionPenalty is the default penalty for invalid subscription. It is applied to the peer's score when - // the peer subscribes to a topic that it is not authorized to subscribe to. - DefaultInvalidSubscriptionPenalty = MaxAppSpecificPenalty - - // DefaultGossipThreshold when a peer's penalty drops below this threshold, - // no gossip is emitted towards that peer and gossip from that peer is ignored. - // - // Validation Constraint: GossipThreshold >= PublishThreshold && GossipThreshold < 0 - // - // How we use it: - // As current max penalty is -100, we set the threshold to -99 so that all gossips - // to and from peers with penalty -100 are ignored. - DefaultGossipThreshold = MaxAppSpecificPenalty + 1 - - // DefaultPublishThreshold when a peer's penalty drops below this threshold, - // self-published messages are not propagated towards this peer. - // - // Validation Constraint: - // PublishThreshold >= GraylistThreshold && PublishThreshold <= GossipThreshold && PublishThreshold < 0. - // - // How we use it: - // As current max penalty is -100, we set the threshold to -99 so that all penalized peers are deprived of - // receiving any published messages. - DefaultPublishThreshold = MaxAppSpecificPenalty + 1 - - // DefaultGraylistThreshold when a peer's penalty drops below this threshold, the peer is graylisted, i.e., - // incoming RPCs from the peer are ignored. - // - // Validation Constraint: - // GraylistThreshold =< PublishThreshold && GraylistThreshold =< GossipThreshold && GraylistThreshold < 0 - // - // How we use it: - // As current max penalty is -100, we set the threshold to -99 so that all penalized peers are graylisted. - DefaultGraylistThreshold = MaxAppSpecificPenalty + 1 - - // DefaultAcceptPXThreshold when a peer sends us PX information with a prune, we only accept it and connect to the supplied - // peers if the originating peer's penalty exceeds this threshold. - // - // Validation Constraint: must be non-negative. - // - // How we use it: - // As current max reward is 100, we set the threshold to 99 so that we only receive supplied peers from - // well-behaved peers. - DefaultAcceptPXThreshold = MaxAppSpecificReward - 1 - - // DefaultOpportunisticGraftThreshold when the median peer penalty in the mesh drops below this value, - // the peer may select more peers with penalty above the median to opportunistically graft on the mesh. - // - // Validation Constraint: must be non-negative. - // - // How we use it: - // We set it to the MaxAppSpecificReward + 1 so that we only opportunistically graft peers that are not access nodes (i.e., with MinAppSpecificPenalty), - // or penalized peers (i.e., with MaxAppSpecificPenalty). - DefaultOpportunisticGraftThreshold = MaxAppSpecificReward + 1 - - // MaxDebugLogs sets the max number of debug/trace log events per second. Logs emitted above - // this threshold are dropped. - MaxDebugLogs = 50 - - // defaultScoreCacheSize is the default size of the cache used to store the app specific penalty of peers. - defaultScoreCacheSize = 1000 - - // defaultDecayInterval is the default decay interval for the overall score of a peer at the GossipSub scoring - // system. We set it to 1 minute so that it is not too short so that a malicious node can recover from a penalty - // and is not too long so that a well-behaved node can't recover from a penalty. - defaultDecayInterval = 1 * time.Minute - - // defaultDecayToZero is the default decay to zero for the overall score of a peer at the GossipSub scoring system. - // It defines the maximum value below which a peer scoring counter is reset to zero. - // This is to prevent the counter from decaying to a very small value. - // The default value is 0.01, which means that a counter will be reset to zero if it decays to 0.01. - // When a counter hits the DecayToZero threshold, it means that the peer did not exhibit the behavior - // for a long time, and we can reset the counter. - defaultDecayToZero = 0.01 - - // defaultTopicSkipAtomicValidation is the default value for the skip atomic validation flag for topics. - // We set it to true, which means gossipsub parameter validation will not fail if we leave some of the - // topic parameters at their default values, i.e., zero. This is because we are not setting all - // topic parameters at the current implementation. - defaultTopicSkipAtomicValidation = true - - // defaultTopicInvalidMessageDeliveriesWeight this value is applied to the square of the number of invalid message deliveries on a topic. - // It is used to penalize peers that send invalid messages. By an invalid message, we mean a message that is not signed by the - // publisher, or a message that is not signed by the peer that sent it. We set it to -1.0, which means that with around 14 invalid - // message deliveries within a gossipsub heartbeat interval, the peer will be disconnected. - // The supporting math is as follows: - // - each staked (i.e., authorized) peer is rewarded by the fixed reward of 100 (i.e., DefaultStakedIdentityReward). - // - x invalid message deliveries will result in a penalty of x^2 * DefaultTopicInvalidMessageDeliveriesWeight, i.e., -x^2. - // - the peer will be disconnected when its penalty reaches -100 (i.e., MaxAppSpecificPenalty). - // - so, the maximum number of invalid message deliveries that a peer can have before being disconnected is sqrt(200/DefaultTopicInvalidMessageDeliveriesWeight) ~ 14. - defaultTopicInvalidMessageDeliveriesWeight = -1.0 - - // defaultTopicInvalidMessageDeliveriesDecay decay factor used to decay the number of invalid message deliveries. - // The total number of invalid message deliveries is multiplied by this factor at each heartbeat interval to - // decay the number of invalid message deliveries, and prevent the peer from being disconnected if it stops - // sending invalid messages. We set it to 0.99, which means that the number of invalid message deliveries will - // decay by 1% at each heartbeat interval. - // The decay heartbeats are defined by the heartbeat interval of the gossipsub scoring system, which is 1 Minute (defaultDecayInterval). - defaultTopicInvalidMessageDeliveriesDecay = .99 - - // defaultTopicTimeInMeshQuantum is the default time in mesh quantum for the GossipSub scoring system. It is used to gauge - // a discrete time interval for the time in mesh counter. We set it to 1 hour, which means that every one complete hour a peer is - // in a topic mesh, the time in mesh counter will be incremented by 1 and is counted towards the availability score of the peer in that topic mesh. - // The reason of setting it to 1 hour is that we want to reward peers that are in a topic mesh for a long time, and we want to avoid rewarding peers that - // are churners, i.e., peers that join and leave a topic mesh frequently. - defaultTopicTimeInMesh = time.Hour - - // defaultTopicWeight is the default weight of a topic in the GossipSub scoring system. - // The overall score of a peer in a topic mesh is multiplied by the weight of the topic when calculating the overall score of the peer. - // We set it to 1.0, which means that the overall score of a peer in a topic mesh is not affected by the weight of the topic. - defaultTopicWeight = 1.0 - - // defaultTopicMeshMessageDeliveriesDecay is applied to the number of actual message deliveries in a topic mesh - // at each decay interval (i.e., defaultDecayInterval). - // It is used to decay the number of actual message deliveries, and prevents past message - // deliveries from affecting the current score of the peer. - // As the decay interval is 1 minute, we set it to 0.5, which means that the number of actual message - // deliveries will decay by 50% at each decay interval. - defaultTopicMeshMessageDeliveriesDecay = .5 - - // defaultTopicMeshMessageDeliveriesCap is the maximum number of actual message deliveries in a topic - // mesh that is used to calculate the score of a peer in that topic mesh. - // We set it to 1000, which means that the maximum number of actual message deliveries in a - // topic mesh that is used to calculate the score of a peer in that topic mesh is 1000. - // This is to prevent the score of a peer in a topic mesh from being affected by a large number of actual - // message deliveries and also affect the score of the peer in other topic meshes. - // When the total delivered messages in a topic mesh exceeds this value, the score of the peer in that topic - // mesh will not be affected by the actual message deliveries in that topic mesh. - // Moreover, this does not allow the peer to accumulate a large number of actual message deliveries in a topic mesh - // and then start under-performing in that topic mesh without being penalized. - defaultTopicMeshMessageDeliveriesCap = 1000 - - // defaultTopicMeshMessageDeliveriesThreshold is the threshold for the number of actual message deliveries in a - // topic mesh that is used to calculate the score of a peer in that topic mesh. - // If the number of actual message deliveries in a topic mesh is less than this value, - // the peer will be penalized by square of the difference between the actual message deliveries and the threshold, - // i.e., -w * (actual - threshold)^2 where `actual` and `threshold` are the actual message deliveries and the - // threshold, respectively, and `w` is the weight (i.e., defaultTopicMeshMessageDeliveriesWeight). - // We set it to 0.1 * defaultTopicMeshMessageDeliveriesCap, which means that if a peer delivers less tha 10% of the - // maximum number of actual message deliveries in a topic mesh, it will be considered as an under-performing peer - // in that topic mesh. - defaultTopicMeshMessageDeliveryThreshold = 0.1 * defaultTopicMeshMessageDeliveriesCap - - // defaultTopicMeshDeliveriesWeight is the weight for applying penalty when a peer is under-performing in a topic mesh. - // Upon every decay interval, if the number of actual message deliveries is less than the topic mesh message deliveries threshold - // (i.e., defaultTopicMeshMessageDeliveriesThreshold), the peer will be penalized by square of the difference between the actual - // message deliveries and the threshold, multiplied by this weight, i.e., -w * (actual - threshold)^2 where w is the weight, and - // `actual` and `threshold` are the actual message deliveries and the threshold, respectively. - // We set this value to be - 0.05 MaxAppSpecificReward / (defaultTopicMeshMessageDeliveriesThreshold^2). This guarantees that even if a peer - // is not delivering any message in a topic mesh, it will not be disconnected. - // Rather, looses part of the MaxAppSpecificReward that is awarded by our app-specific scoring function to all staked - // nodes by default will be withdrawn, and the peer will be slightly penalized. In other words, under-performing in a topic mesh - // will drop the overall score of a peer by 5% of the MaxAppSpecificReward that is awarded by our app-specific scoring function. - // It means that under-performing in a topic mesh will not cause a peer to be disconnected, but it will cause the peer to lose - // its MaxAppSpecificReward that is awarded by our app-specific scoring function. - // At this point, we do not want to disconnect a peer only because it is under-performing in a topic mesh as it might be - // causing a false positive network partition. - // TODO: we must increase the penalty for under-performing in a topic mesh in the future, and disconnect the peer if it is under-performing. - defaultTopicMeshMessageDeliveriesWeight = -0.05 * MaxAppSpecificReward / (defaultTopicMeshMessageDeliveryThreshold * defaultTopicMeshMessageDeliveryThreshold) - - // defaultMeshMessageDeliveriesWindow is the window size is time interval that we count a delivery of an already - // seen message towards the score of a peer in a topic mesh. The delivery is counted - // by GossipSub only if the previous sender of the message is different from the current sender. - // We set it to the decay interval of the GossipSub scoring system, which is 1 minute. - // It means that if a peer delivers a message that it has already seen less than one minute ago, - // the delivery will be counted towards the score of the peer in a topic mesh only if the previous sender of the message. - // This also prevents replay attacks of messages that are older than one minute. As replayed messages will not - // be counted towards the actual message deliveries of a peer in a topic mesh. - defaultMeshMessageDeliveriesWindow = defaultDecayInterval - - // defaultMeshMessageDeliveryActivation is the time interval that we wait for a new peer that joins a topic mesh - // till start counting the number of actual message deliveries of that peer in that topic mesh. - // We set it to 2 * defaultDecayInterval, which means that we wait for 2 decay intervals before start counting - // the number of actual message deliveries of a peer in a topic mesh. - // With a default decay interval of 1 minute, it means that we wait for 2 minutes before start counting the - // number of actual message deliveries of a peer in a topic mesh. This is to account for - // the time that it takes for a peer to start up and receive messages from other peers in the topic mesh. - defaultMeshMessageDeliveriesActivation = 2 * defaultDecayInterval - - // defaultBehaviorPenaltyThreshold is the threshold when the behavior of a peer is considered as bad by GossipSub. - // Currently, the misbehavior is defined as advertising an iHave without responding to the iWants (iHave broken promises), as well as attempting - // on GRAFT when the peer is considered for a PRUNE backoff, i.e., the local peer does not allow the peer to join the local topic mesh - // for a while, and the remote peer keep attempting on GRAFT (aka GRAFT flood). - // When the misbehavior counter of a peer goes beyond this threshold, the peer is penalized by defaultBehaviorPenaltyWeight (see below) for the excess misbehavior. - // - // An iHave broken promise means that a peer advertises an iHave for a message, but does not respond to the iWant requests for that message. - // For iHave broken promises, the gossipsub scoring works as follows: - // It samples ONLY A SINGLE iHave out of the entire RPC. - // If that iHave is not followed by an actual message within the next 3 seconds, the peer misbehavior counter is incremented by 1. - // - // We set it to 10, meaning that we at most tolerate 10 of such RPCs containing iHave broken promises. After that, the peer is penalized for every - // excess RPC containing iHave broken promises. - // The counter is also decayed by (0.99) every decay interval (defaultDecayInterval) i.e., every minute. - // Note that misbehaviors are counted by GossipSub across all topics (and is different from the Application Layer Misbehaviors that we count through - // the ALSP system). - defaultBehaviourPenaltyThreshold = 10 - - // defaultBehaviorPenaltyWeight is the weight for applying penalty when a peer misbehavior goes beyond the threshold. - // Misbehavior of a peer at gossipsub layer is defined as advertising an iHave without responding to the iWants (broken promises), as well as attempting - // on GRAFT when the peer is considered for a PRUNE backoff, i.e., the local peer does not allow the peer to join the local topic mesh - // This is detected by the GossipSub scoring system, and the peer is penalized by defaultBehaviorPenaltyWeight. - // - // An iHave broken promise means that a peer advertises an iHave for a message, but does not respond to the iWant requests for that message. - // For iHave broken promises, the gossipsub scoring works as follows: - // It samples ONLY A SINGLE iHave out of the entire RPC. - // If that iHave is not followed by an actual message within the next 3 seconds, the peer misbehavior counter is incremented by 1. - // - // The penalty is applied to the square of the difference between the misbehavior counter and the threshold, i.e., -|w| * (misbehavior counter - threshold)^2. - // We set it to 0.01 * MaxAppSpecificPenalty, which means that misbehaving 10 times more than the threshold (i.e., 10 + 10) will cause the peer to lose - // its entire AppSpecificReward that is awarded by our app-specific scoring function to all staked (i.e., authorized) nodes by default. - // Moreover, as the MaxAppSpecificPenalty is -MaxAppSpecificReward, misbehaving sqrt(2) * 10 times more than the threshold will cause the peer score - // to be dropped below the MaxAppSpecificPenalty, which is also below the GraylistThreshold, and the peer will be graylisted (i.e., disconnected). - // - // The math is as follows: -|w| * (misbehavior - threshold)^2 = 0.01 * MaxAppSpecificPenalty * (misbehavior - threshold)^2 < 2 * MaxAppSpecificPenalty - // if misbehavior > threshold + sqrt(2) * 10. - // As shown above, with this choice of defaultBehaviorPenaltyWeight, misbehaving sqrt(2) * 10 times more than the threshold will cause the peer score - // to be dropped below the MaxAppSpecificPenalty, which is also below the GraylistThreshold, and the peer will be graylisted (i.e., disconnected). This weight - // is chosen in a way that with almost a few misbehaviors more than the threshold, the peer will be graylisted. The rationale relies on the fact that - // the misbehavior counter is incremented by 1 for each RPC containing one or more broken promises. Hence, it is per RPC, and not per broken promise. - // Having sqrt(2) * 10 broken promises RPC is a blatant misbehavior, and the peer should be graylisted. With decay interval of 1 minute, and decay value of - // 0.99 we expect a graylisted node due to borken promises to get back in about 527 minutes, i.e., (0.99)^x * (sqrt(2) * 10)^2 * MaxAppSpecificPenalty > GraylistThreshold - // where x is the number of decay intervals that the peer is graylisted. As MaxAppSpecificPenalty and GraylistThresholds are close, we can simplify the inequality - // to (0.99)^x * (sqrt(2) * 10)^2 > 1 --> (0.99)^x * 200 > 1 --> (0.99)^x > 1/200 --> x > log(1/200) / log(0.99) --> x > 527.17 decay intervals, i.e., 527 minutes. - // Note that misbehaviors are counted by GossipSub across all topics (and is different from the Application Layer Misbehaviors that we count through - // the ALSP system that are reported by the engines). - defaultBehaviourPenaltyWeight = 0.01 * MaxAppSpecificPenalty - - // defaultBehaviorPenaltyDecay is the decay interval for the misbehavior counter of a peer. The misbehavior counter is - // incremented by GossipSub for iHave broken promises or the GRAFT flooding attacks (i.e., each GRAFT received from a remote peer while that peer is on a PRUNE backoff). - // - // An iHave broken promise means that a peer advertises an iHave for a message, but does not respond to the iWant requests for that message. - // For iHave broken promises, the gossipsub scoring works as follows: - // It samples ONLY A SINGLE iHave out of the entire RPC. - // If that iHave is not followed by an actual message within the next 3 seconds, the peer misbehavior counter is incremented by 1. - // This means that regardless of how many iHave broken promises an RPC contains, the misbehavior counter is incremented by 1. - // That is why we decay the misbehavior counter very slow, as this counter indicates a severe misbehavior. - // - // The misbehavior counter is decayed per decay interval (i.e., defaultDecayInterval = 1 minute) by GossipSub. - // We set it to 0.99, which means that the misbehavior counter is decayed by 1% per decay interval. - // With the generous threshold that we set (i.e., defaultBehaviourPenaltyThreshold = 10), we take the peers going beyond the threshold as persistent misbehaviors, - // We expect honest peers never to go beyond the threshold, and if they do, we expect them to go back below the threshold quickly. - // - // Note that misbehaviors are counted by GossipSub across all topics (and is different from the Application Layer Misbehaviors that we count through - // the ALSP system that is based on the engines report). - defaultBehaviourPenaltyDecay = 0.99 -) - // ScoreOption is a functional option for configuring the peer scoring system. // TODO: rename it to ScoreManager. type ScoreOption struct { component.Component logger zerolog.Logger - peerScoreParams *pubsub.PeerScoreParams - peerThresholdParams *pubsub.PeerScoreThresholds - validator p2p.SubscriptionValidator - appScoreFunc func(peer.ID) float64 + peerScoreParams *pubsub.PeerScoreParams + peerThresholdParams *pubsub.PeerScoreThresholds + defaultTopicScoreParams *pubsub.TopicScoreParams + validator p2p.SubscriptionValidator + appScoreFunc func(peer.ID) float64 } type ScoreOptionConfig struct { logger zerolog.Logger + params p2pconfig.ScoringParameters provider module.IdentityProvider - cacheSize uint32 - cacheMetrics module.HeroCacheMetrics + heroCacheMetricsFactory metrics.HeroCacheMetricsFactory appScoreFunc func(peer.ID) float64 - decayInterval time.Duration // the decay interval, when is set to 0, the default value will be used. topicParams []func(map[string]*pubsub.TopicScoreParams) registerNotificationConsumerFunc func(p2p.GossipSubInvCtrlMsgNotifConsumer) + networkingType network.NetworkingType } -func NewScoreOptionConfig(logger zerolog.Logger, idProvider module.IdentityProvider) *ScoreOptionConfig { +// NewScoreOptionConfig creates a new configuration for the GossipSub peer scoring option. +// Args: +// - logger: the logger to use. +// - hcMetricsFactory: HeroCache metrics factory to create metrics for the scoring-related caches. +// - idProvider: the identity provider to use. +// - networkingType: the networking type to use, public or private. +// Returns: +// - a new configuration for the GossipSub peer scoring option. +func NewScoreOptionConfig(logger zerolog.Logger, + params p2pconfig.ScoringParameters, + hcMetricsFactory metrics.HeroCacheMetricsFactory, + idProvider module.IdentityProvider, + networkingType network.NetworkingType) *ScoreOptionConfig { return &ScoreOptionConfig{ - logger: logger, - provider: idProvider, - cacheSize: defaultScoreCacheSize, - cacheMetrics: metrics.NewNoopCollector(), // no metrics by default - topicParams: make([]func(map[string]*pubsub.TopicScoreParams), 0), + logger: logger.With().Str("module", "pubsub_score_option").Logger(), + provider: idProvider, + params: params, + heroCacheMetricsFactory: hcMetricsFactory, + topicParams: make([]func(map[string]*pubsub.TopicScoreParams), 0), + networkingType: networkingType, } } -// SetCacheSize sets the size of the cache used to store the app specific penalty of peers. -// If the cache size is not set, the default value will be used. -// It is safe to call this method multiple times, the last call will be used. -func (c *ScoreOptionConfig) SetCacheSize(size uint32) { - c.cacheSize = size -} - -// SetCacheMetrics sets the cache metrics collector for the penalty option. -// It is used to collect metrics for the app specific penalty cache. If the cache metrics collector is not set, -// a no-op collector will be used. -// It is safe to call this method multiple times, the last call will be used. -func (c *ScoreOptionConfig) SetCacheMetrics(metrics module.HeroCacheMetrics) { - c.cacheMetrics = metrics -} - // OverrideAppSpecificScoreFunction sets the app specific penalty function for the penalty option. // It is used to calculate the app specific penalty of a peer. // If the app specific penalty function is not set, the default one is used. @@ -367,86 +94,120 @@ func (c *ScoreOptionConfig) SetRegisterNotificationConsumerFunc(f func(p2p.Gossi c.registerNotificationConsumerFunc = f } -// OverrideDecayInterval overrides the decay interval for the penalty option. It is used to override the default -// decay interval for the penalty option. The decay interval is the time interval that the decay values are applied and -// peer scores are updated. -// Note: It is always recommended to use the default value unless you know what you are doing. Hence, calling this method -// is not recommended in production. -// Args: -// -// interval: the decay interval. -// -// Returns: -// none -func (c *ScoreOptionConfig) OverrideDecayInterval(interval time.Duration) { - c.decayInterval = interval -} - // NewScoreOption creates a new penalty option with the given configuration. -func NewScoreOption(scoreRegistryConfig p2pconf.GossipSubScoringRegistryConfig, scoreOptionConfig *ScoreOptionConfig, provider p2p.SubscriptionProvider) *ScoreOption { - throttledSampler := logging.BurstSampler(MaxDebugLogs, time.Second) - logger := scoreOptionConfig.logger.With(). +func NewScoreOption(cfg *ScoreOptionConfig, provider p2p.SubscriptionProvider) (*ScoreOption, error) { + throttledSampler := logging.BurstSampler(cfg.params.PeerScoring.Protocol.MaxDebugLogs, time.Second) + logger := cfg.logger.With(). Str("module", "pubsub_score_option"). Logger(). Sample(zerolog.LevelSampler{ TraceSampler: throttledSampler, DebugSampler: throttledSampler, }) - validator := NewSubscriptionValidator(scoreOptionConfig.logger, provider) - scoreRegistry := NewGossipSubAppSpecificScoreRegistry(&GossipSubAppSpecificScoreRegistryConfig{ - Logger: logger, - Penalty: DefaultGossipSubCtrlMsgPenaltyValue(), - Validator: validator, - Init: InitAppScoreRecordState, - IdProvider: scoreOptionConfig.provider, - CacheFactory: func() p2p.GossipSubSpamRecordCache { - return netcache.NewGossipSubSpamRecordCache( - scoreOptionConfig.cacheSize, - scoreOptionConfig.logger, - scoreOptionConfig.cacheMetrics, - DefaultDecayFunction( - scoreRegistryConfig.PenaltyDecaySlowdownThreshold, - scoreRegistryConfig.DecayRateReductionFactor, - scoreRegistryConfig.PenaltyDecayEvaluationPeriod, - )) + + validator := NewSubscriptionValidator(cfg.logger, provider) + scoreRegistry, err := NewGossipSubAppSpecificScoreRegistry(&GossipSubAppSpecificScoreRegistryConfig{ + Logger: logger, + Penalty: cfg.params.ScoringRegistryParameters.MisbehaviourPenalties, + Validator: validator, + IdProvider: cfg.provider, + HeroCacheMetricsFactory: cfg.heroCacheMetricsFactory, + AppScoreCacheFactory: func() p2p.GossipSubApplicationSpecificScoreCache { + collector := metrics.NewGossipSubApplicationSpecificScoreCacheMetrics(cfg.heroCacheMetricsFactory, cfg.networkingType) + return internal.NewAppSpecificScoreCache(cfg.params.ScoringRegistryParameters.SpamRecordCache.CacheSize, cfg.logger, collector) }, + SpamRecordCacheFactory: func() p2p.GossipSubSpamRecordCache { + collector := metrics.GossipSubSpamRecordCacheMetricsFactory(cfg.heroCacheMetricsFactory, cfg.networkingType) + return netcache.NewGossipSubSpamRecordCache(cfg.params.ScoringRegistryParameters.SpamRecordCache.CacheSize, cfg.logger, collector, + InitAppScoreRecordStateFunc(cfg.params.ScoringRegistryParameters.SpamRecordCache.Decay.MaximumSpamPenaltyDecayFactor), + DefaultDecayFunction(cfg.params.ScoringRegistryParameters.SpamRecordCache.Decay)) + }, + Parameters: cfg.params.ScoringRegistryParameters.AppSpecificScore, + NetworkingType: cfg.networkingType, + AppSpecificScoreParams: cfg.params.PeerScoring.Protocol.AppSpecificScore, }) + if err != nil { + return nil, fmt.Errorf("failed to create gossipsub app specific score registry: %w", err) + } + s := &ScoreOption{ - logger: logger, - validator: validator, - peerScoreParams: defaultPeerScoreParams(), - appScoreFunc: scoreRegistry.AppSpecificScoreFunc(), + logger: logger, + validator: validator, + peerScoreParams: &pubsub.PeerScoreParams{ + Topics: make(map[string]*pubsub.TopicScoreParams), + // we don't set all the parameters, so we skip the atomic validation. + // atomic validation fails initialization if any parameter is not set. + SkipAtomicValidation: cfg.params.PeerScoring.Internal.TopicParameters.SkipAtomicValidation, + // DecayInterval is the interval over which we decay the effect of past behavior, so that + // a good or bad behavior will not have a permanent effect on the penalty. It is also the interval + // that GossipSub uses to refresh the scores of all peers. + DecayInterval: cfg.params.PeerScoring.Internal.DecayInterval, + // DecayToZero defines the maximum value below which a peer scoring counter is reset to zero. + // This is to prevent the counter from decaying to a very small value. + // When a counter hits the DecayToZero threshold, it means that the peer did not exhibit the behavior + // for a long time, and we can reset the counter. + DecayToZero: cfg.params.PeerScoring.Internal.DecayToZero, + // AppSpecificWeight is the weight of the application specific penalty. + AppSpecificWeight: cfg.params.PeerScoring.Internal.AppSpecificScoreWeight, + // PenaltyThreshold is the threshold above which a peer is penalized for GossipSub-level misbehaviors. + BehaviourPenaltyThreshold: cfg.params.PeerScoring.Internal.Behaviour.PenaltyThreshold, + // PenaltyWeight is the weight of the GossipSub-level penalty. + BehaviourPenaltyWeight: cfg.params.PeerScoring.Internal.Behaviour.PenaltyWeight, + // PenaltyDecay is the decay of the GossipSub-level penalty (applied every decay interval). + BehaviourPenaltyDecay: cfg.params.PeerScoring.Internal.Behaviour.PenaltyDecay, + }, + peerThresholdParams: &pubsub.PeerScoreThresholds{ + GossipThreshold: cfg.params.PeerScoring.Internal.Thresholds.Gossip, + PublishThreshold: cfg.params.PeerScoring.Internal.Thresholds.Publish, + GraylistThreshold: cfg.params.PeerScoring.Internal.Thresholds.Graylist, + AcceptPXThreshold: cfg.params.PeerScoring.Internal.Thresholds.AcceptPX, + OpportunisticGraftThreshold: cfg.params.PeerScoring.Internal.Thresholds.OpportunisticGraft, + }, + defaultTopicScoreParams: &pubsub.TopicScoreParams{ + TopicWeight: cfg.params.PeerScoring.Internal.TopicParameters.TopicWeight, + SkipAtomicValidation: cfg.params.PeerScoring.Internal.TopicParameters.SkipAtomicValidation, + InvalidMessageDeliveriesWeight: cfg.params.PeerScoring.Internal.TopicParameters.InvalidMessageDeliveriesWeight, + InvalidMessageDeliveriesDecay: cfg.params.PeerScoring.Internal.TopicParameters.InvalidMessageDeliveriesDecay, + TimeInMeshQuantum: cfg.params.PeerScoring.Internal.TopicParameters.TimeInMeshQuantum, + MeshMessageDeliveriesWeight: cfg.params.PeerScoring.Internal.TopicParameters.MeshDeliveriesWeight, + MeshMessageDeliveriesDecay: cfg.params.PeerScoring.Internal.TopicParameters.MeshMessageDeliveriesDecay, + MeshMessageDeliveriesCap: cfg.params.PeerScoring.Internal.TopicParameters.MeshMessageDeliveriesCap, + MeshMessageDeliveriesThreshold: cfg.params.PeerScoring.Internal.TopicParameters.MeshMessageDeliveryThreshold, + MeshMessageDeliveriesWindow: cfg.params.PeerScoring.Internal.TopicParameters.MeshMessageDeliveriesWindow, + MeshMessageDeliveriesActivation: cfg.params.PeerScoring.Internal.TopicParameters.MeshMessageDeliveryActivation, + }, + appScoreFunc: scoreRegistry.AppSpecificScoreFunc(), } // set the app specific penalty function for the penalty option // if the app specific penalty function is not set, use the default one - if scoreOptionConfig.appScoreFunc != nil { - s.appScoreFunc = scoreOptionConfig.appScoreFunc + if cfg.appScoreFunc != nil { + s.appScoreFunc = cfg.appScoreFunc s.logger. Warn(). Str(logging.KeyNetworkingSecurity, "true"). Msg("app specific score function is overridden, should never happen in production") } - if scoreOptionConfig.decayInterval > 0 { + if cfg.params.PeerScoring.Internal.DecayInterval > 0 && cfg.params.PeerScoring.Internal.DecayInterval != s.peerScoreParams.DecayInterval { // overrides the default decay interval if the decay interval is set. - s.peerScoreParams.DecayInterval = scoreOptionConfig.decayInterval + s.peerScoreParams.DecayInterval = cfg.params.PeerScoring.Internal.DecayInterval s.logger. Warn(). Str(logging.KeyNetworkingSecurity, "true"). - Dur("decay_interval_ms", scoreOptionConfig.decayInterval). + Dur("decay_interval_ms", cfg.params.PeerScoring.Internal.DecayInterval). Msg("decay interval is overridden, should never happen in production") } // registers the score registry as the consumer of the invalid control message notifications - if scoreOptionConfig.registerNotificationConsumerFunc != nil { - scoreOptionConfig.registerNotificationConsumerFunc(scoreRegistry) + if cfg.registerNotificationConsumerFunc != nil { + cfg.registerNotificationConsumerFunc(scoreRegistry) } s.peerScoreParams.AppSpecificScore = s.appScoreFunc // apply the topic penalty parameters if any. - for _, topicParams := range scoreOptionConfig.topicParams { + for _, topicParams := range cfg.topicParams { topicParams(s.peerScoreParams.Topics) } @@ -468,12 +229,10 @@ func NewScoreOption(scoreRegistryConfig p2pconf.GossipSubScoringRegistryConfig, s.logger.Info().Msg("score registry stopped") }).Build() - return s + return s, nil } func (s *ScoreOption) BuildFlowPubSubScoreOption() (*pubsub.PeerScoreParams, *pubsub.PeerScoreThresholds) { - s.preparePeerScoreThresholds() - s.logger.Info(). Float64("gossip_threshold", s.peerThresholdParams.GossipThreshold). Float64("publish_threshold", s.peerThresholdParams.PublishThreshold). @@ -491,16 +250,6 @@ func (s *ScoreOption) BuildFlowPubSubScoreOption() (*pubsub.PeerScoreParams, *pu return s.peerScoreParams, s.peerThresholdParams } -func (s *ScoreOption) preparePeerScoreThresholds() { - s.peerThresholdParams = &pubsub.PeerScoreThresholds{ - GossipThreshold: DefaultGossipThreshold, - PublishThreshold: DefaultPublishThreshold, - GraylistThreshold: DefaultGraylistThreshold, - AcceptPXThreshold: DefaultAcceptPXThreshold, - OpportunisticGraftThreshold: DefaultOpportunisticGraftThreshold, - } -} - // TopicScoreParams returns the topic score parameters for the given topic. If the topic // score parameters are not set, it returns the default topic score parameters. // The custom topic parameters are set at the initialization of the score option. @@ -512,59 +261,7 @@ func (s *ScoreOption) preparePeerScoreThresholds() { func (s *ScoreOption) TopicScoreParams(topic *pubsub.Topic) *pubsub.TopicScoreParams { params, exists := s.peerScoreParams.Topics[topic.String()] if !exists { - return DefaultTopicScoreParams() + return s.defaultTopicScoreParams } return params } - -func defaultPeerScoreParams() *pubsub.PeerScoreParams { - // DO NOT CHANGE THE DEFAULT VALUES, THEY ARE TUNED FOR THE BEST SECURITY PRACTICES. - return &pubsub.PeerScoreParams{ - Topics: make(map[string]*pubsub.TopicScoreParams), - // we don't set all the parameters, so we skip the atomic validation. - // atomic validation fails initialization if any parameter is not set. - SkipAtomicValidation: true, - // DecayInterval is the interval over which we decay the effect of past behavior, so that - // a good or bad behavior will not have a permanent effect on the penalty. It is also the interval - // that GossipSub uses to refresh the scores of all peers. - DecayInterval: defaultDecayInterval, - // DecayToZero defines the maximum value below which a peer scoring counter is reset to zero. - // This is to prevent the counter from decaying to a very small value. - // When a counter hits the DecayToZero threshold, it means that the peer did not exhibit the behavior - // for a long time, and we can reset the counter. - DecayToZero: defaultDecayToZero, - // AppSpecificWeight is the weight of the application specific penalty. - AppSpecificWeight: DefaultAppSpecificScoreWeight, - // BehaviourPenaltyThreshold is the threshold above which a peer is penalized for GossipSub-level misbehaviors. - BehaviourPenaltyThreshold: defaultBehaviourPenaltyThreshold, - // BehaviourPenaltyWeight is the weight of the GossipSub-level penalty. - BehaviourPenaltyWeight: defaultBehaviourPenaltyWeight, - // BehaviourPenaltyDecay is the decay of the GossipSub-level penalty (applied every decay interval). - BehaviourPenaltyDecay: defaultBehaviourPenaltyDecay, - } -} - -// DefaultTopicScoreParams returns the default score params for topics. -func DefaultTopicScoreParams() *pubsub.TopicScoreParams { - // DO NOT CHANGE THE DEFAULT VALUES, THEY ARE TUNED FOR THE BEST SECURITY PRACTICES. - p := &pubsub.TopicScoreParams{ - TopicWeight: defaultTopicWeight, - SkipAtomicValidation: defaultTopicSkipAtomicValidation, - InvalidMessageDeliveriesWeight: defaultTopicInvalidMessageDeliveriesWeight, - InvalidMessageDeliveriesDecay: defaultTopicInvalidMessageDeliveriesDecay, - TimeInMeshQuantum: defaultTopicTimeInMesh, - MeshMessageDeliveriesWeight: defaultTopicMeshMessageDeliveriesWeight, - MeshMessageDeliveriesDecay: defaultTopicMeshMessageDeliveriesDecay, - MeshMessageDeliveriesCap: defaultTopicMeshMessageDeliveriesCap, - MeshMessageDeliveriesThreshold: defaultTopicMeshMessageDeliveryThreshold, - MeshMessageDeliveriesWindow: defaultMeshMessageDeliveriesWindow, - MeshMessageDeliveriesActivation: defaultMeshMessageDeliveriesActivation, - } - - if p.MeshMessageDeliveriesWeight >= 0 { - // GossipSub also does a validation, but we want to panic as early as possible. - panic(fmt.Sprintf("invalid mesh message deliveries weight %f", p.MeshMessageDeliveriesWeight)) - } - - return p -} diff --git a/network/p2p/scoring/scoring_test.go b/network/p2p/scoring/scoring_test.go index 8e55e231195..cee819d3c85 100644 --- a/network/p2p/scoring/scoring_test.go +++ b/network/p2p/scoring/scoring_test.go @@ -5,6 +5,7 @@ import ( "fmt" "math/rand" "testing" + "time" pubsub "github.com/libp2p/go-libp2p-pubsub" "github.com/libp2p/go-libp2p/core/peer" @@ -12,6 +13,7 @@ import ( mocktestify "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + "github.com/onflow/flow-go/config" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/module" "github.com/onflow/flow-go/module/component" @@ -22,8 +24,8 @@ import ( flownet "github.com/onflow/flow-go/network" "github.com/onflow/flow-go/network/channels" "github.com/onflow/flow-go/network/p2p" + p2pconfig "github.com/onflow/flow-go/network/p2p/config" p2pmsg "github.com/onflow/flow-go/network/p2p/message" - "github.com/onflow/flow-go/network/p2p/p2pconf" p2ptest "github.com/onflow/flow-go/network/p2p/test" "github.com/onflow/flow-go/utils/unittest" ) @@ -89,7 +91,7 @@ func TestInvalidCtrlMsgScoringIntegration(t *testing.T) { irrecoverable.SignalerContext, zerolog.Logger, flow.Identifier, - *p2pconf.GossipSubRPCInspectorsConfig, + *p2pconfig.RpcInspectorParameters, module.GossipSubMetrics, metrics.HeroCacheMetricsFactory, flownet.NetworkingType, @@ -98,13 +100,19 @@ func TestInvalidCtrlMsgScoringIntegration(t *testing.T) { // override the gossipsub rpc inspector suite factory to return the mock inspector suite return inspectorSuite1, nil } + + cfg, err := config.DefaultConfig() + require.NoError(t, err) + + cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.AppSpecificScore.ScoreTTL = 10 * time.Millisecond // speed up the test + node1, id1 := p2ptest.NodeFixture( t, sporkId, t.Name(), idProvider, p2ptest.WithRole(flow.RoleConsensus), - p2ptest.EnablePeerScoringWithOverride(p2p.PeerScoringConfigNoOverride), + p2ptest.OverrideFlowConfig(cfg), p2ptest.OverrideGossipSubRpcInspectorSuiteFactory(factory)) node2, id2 := p2ptest.NodeFixture( @@ -113,7 +121,7 @@ func TestInvalidCtrlMsgScoringIntegration(t *testing.T) { t.Name(), idProvider, p2ptest.WithRole(flow.RoleConsensus), - p2ptest.EnablePeerScoringWithOverride(p2p.PeerScoringConfigNoOverride)) + p2ptest.OverrideFlowConfig(cfg)) ids := flow.IdentityList{&id1, &id2} nodes := []p2p.LibP2PNode{node1, node2} @@ -132,7 +140,7 @@ func TestInvalidCtrlMsgScoringIntegration(t *testing.T) { p2ptest.LetNodesDiscoverEachOther(t, ctx, nodes, ids) blockTopic := channels.TopicFromChannel(channels.PushBlocks, sporkId) - // checks end-to-end message delivery works on GossipSub + // checks end-to-end message delivery works on GossipSub. p2ptest.EnsurePubsubMessageExchange(t, ctx, nodes, blockTopic, 1, func() interface{} { return unittest.ProposalFixture() }) @@ -147,6 +155,8 @@ func TestInvalidCtrlMsgScoringIntegration(t *testing.T) { }) } + time.Sleep(1 * time.Second) // wait for app-specific score to be updated in the cache (remember that we need at least 100 ms for the score to be updated (ScoreTTL)) + // checks no GossipSub message exchange should no longer happen between node1 and node2. p2ptest.EnsureNoPubsubExchangeBetweenGroups( t, diff --git a/network/p2p/scoring/subscriptionCache.go b/network/p2p/scoring/subscriptionCache.go index 8eae60bd385..a58ab79db5c 100644 --- a/network/p2p/scoring/subscriptionCache.go +++ b/network/p2p/scoring/subscriptionCache.go @@ -31,5 +31,5 @@ type SubscriptionCache interface { // - []string: the list of topics the peer is subscribed to after the update. // - error: an error if the update failed; any returned error is an irrecoverable error and indicates a bug or misconfiguration. // Implementation must be thread-safe. - AddTopicForPeer(pid peer.ID, topic string) ([]string, error) + AddWithInitTopicForPeer(pid peer.ID, topic string) ([]string, error) } diff --git a/network/p2p/scoring/subscription_provider.go b/network/p2p/scoring/subscription_provider.go index 4f6918a81a0..2bfd43bb870 100644 --- a/network/p2p/scoring/subscription_provider.go +++ b/network/p2p/scoring/subscription_provider.go @@ -13,9 +13,10 @@ import ( "github.com/onflow/flow-go/module/component" "github.com/onflow/flow-go/module/irrecoverable" "github.com/onflow/flow-go/module/metrics" + "github.com/onflow/flow-go/network" "github.com/onflow/flow-go/network/p2p" - "github.com/onflow/flow-go/network/p2p/p2pconf" - "github.com/onflow/flow-go/network/p2p/p2plogging" + p2pconfig "github.com/onflow/flow-go/network/p2p/config" + p2plogging "github.com/onflow/flow-go/network/p2p/logging" "github.com/onflow/flow-go/network/p2p/scoring/internal" "github.com/onflow/flow-go/utils/logging" ) @@ -39,11 +40,12 @@ type SubscriptionProvider struct { } type SubscriptionProviderConfig struct { - Logger zerolog.Logger `validate:"required"` - TopicProviderOracle func() p2p.TopicProvider `validate:"required"` - IdProvider module.IdentityProvider `validate:"required"` - HeroCacheMetricsFactory metrics.HeroCacheMetricsFactory `validate:"required"` - Params *p2pconf.SubscriptionProviderParameters `validate:"required"` + Logger zerolog.Logger `validate:"required"` + TopicProviderOracle func() p2p.TopicProvider `validate:"required"` + IdProvider module.IdentityProvider `validate:"required"` + HeroCacheMetricsFactory metrics.HeroCacheMetricsFactory `validate:"required"` + Params *p2pconfig.SubscriptionProviderParameters `validate:"required"` + NetworkingType network.NetworkingType `validate:"required"` } var _ p2p.SubscriptionProvider = (*SubscriptionProvider)(nil) @@ -53,13 +55,13 @@ func NewSubscriptionProvider(cfg *SubscriptionProviderConfig) (*SubscriptionProv return nil, fmt.Errorf("invalid subscription provider config: %w", err) } - cacheMetrics := metrics.NewSubscriptionRecordCacheMetricsFactory(cfg.HeroCacheMetricsFactory) + cacheMetrics := metrics.NewSubscriptionRecordCacheMetricsFactory(cfg.HeroCacheMetricsFactory, cfg.NetworkingType) cache := internal.NewSubscriptionRecordCache(cfg.Params.CacheSize, cfg.Logger, cacheMetrics) p := &SubscriptionProvider{ logger: cfg.Logger.With().Str("module", "subscription_provider").Logger(), topicProviderOracle: cfg.TopicProviderOracle, - allTopicsUpdateInterval: cfg.Params.SubscriptionUpdateInterval, + allTopicsUpdateInterval: cfg.Params.UpdateInterval, idProvider: cfg.IdProvider, cache: cache, } @@ -69,7 +71,7 @@ func NewSubscriptionProvider(cfg *SubscriptionProviderConfig) (*SubscriptionProv func(ctx irrecoverable.SignalerContext, ready component.ReadyFunc) { ready() p.logger.Debug(). - Float64("update_interval_seconds", cfg.Params.SubscriptionUpdateInterval.Seconds()). + Float64("update_interval_seconds", cfg.Params.UpdateInterval.Seconds()). Msg("subscription provider started; starting update topics loop") p.updateTopicsLoop(ctx) @@ -132,7 +134,7 @@ func (s *SubscriptionProvider) updateTopics() error { continue } - updatedTopics, err := s.cache.AddTopicForPeer(p, topic) + updatedTopics, err := s.cache.AddWithInitTopicForPeer(p, topic) if err != nil { // this is an irrecoverable error; hence, we crash the node. return fmt.Errorf("failed to update topics for peer %s: %w", p, err) diff --git a/network/p2p/scoring/subscription_provider_test.go b/network/p2p/scoring/subscription_provider_test.go index cb3b45ecbd1..84f5aeb6896 100644 --- a/network/p2p/scoring/subscription_provider_test.go +++ b/network/p2p/scoring/subscription_provider_test.go @@ -15,6 +15,7 @@ import ( "github.com/onflow/flow-go/module/irrecoverable" "github.com/onflow/flow-go/module/metrics" "github.com/onflow/flow-go/module/mock" + "github.com/onflow/flow-go/network" "github.com/onflow/flow-go/network/p2p" mockp2p "github.com/onflow/flow-go/network/p2p/mock" "github.com/onflow/flow-go/network/p2p/scoring" @@ -31,16 +32,17 @@ func TestSubscriptionProvider_GetSubscribedTopics(t *testing.T) { idProvider := mock.NewIdentityProvider(t) // set a low update interval to speed up the test - cfg.NetworkConfig.SubscriptionProviderConfig.SubscriptionUpdateInterval = 100 * time.Millisecond + cfg.NetworkConfig.GossipSub.SubscriptionProvider.UpdateInterval = 100 * time.Millisecond sp, err := scoring.NewSubscriptionProvider(&scoring.SubscriptionProviderConfig{ Logger: unittest.Logger(), TopicProviderOracle: func() p2p.TopicProvider { return tp }, - Params: &cfg.NetworkConfig.SubscriptionProviderConfig, + Params: &cfg.NetworkConfig.GossipSub.SubscriptionProvider, HeroCacheMetricsFactory: metrics.NewNoopHeroCacheMetricsFactory(), IdProvider: idProvider, + NetworkingType: network.PrivateNetwork, }) require.NoError(t, err) @@ -92,16 +94,17 @@ func TestSubscriptionProvider_GetSubscribedTopics_SkippingUnknownPeers(t *testin idProvider := mock.NewIdentityProvider(t) // set a low update interval to speed up the test - cfg.NetworkConfig.SubscriptionProviderConfig.SubscriptionUpdateInterval = 100 * time.Millisecond + cfg.NetworkConfig.GossipSub.SubscriptionProvider.UpdateInterval = 100 * time.Millisecond sp, err := scoring.NewSubscriptionProvider(&scoring.SubscriptionProviderConfig{ Logger: unittest.Logger(), TopicProviderOracle: func() p2p.TopicProvider { return tp }, - Params: &cfg.NetworkConfig.SubscriptionProviderConfig, + Params: &cfg.NetworkConfig.GossipSub.SubscriptionProvider, HeroCacheMetricsFactory: metrics.NewNoopHeroCacheMetricsFactory(), IdProvider: idProvider, + NetworkingType: network.PrivateNetwork, }) require.NoError(t, err) diff --git a/network/p2p/scoring/subscription_validator.go b/network/p2p/scoring/subscription_validator.go index 8c3fc048168..94a08c0e19c 100644 --- a/network/p2p/scoring/subscription_validator.go +++ b/network/p2p/scoring/subscription_validator.go @@ -8,7 +8,7 @@ import ( "github.com/onflow/flow-go/module/component" "github.com/onflow/flow-go/module/irrecoverable" "github.com/onflow/flow-go/network/p2p" - "github.com/onflow/flow-go/network/p2p/p2plogging" + p2plogging "github.com/onflow/flow-go/network/p2p/logging" p2putils "github.com/onflow/flow-go/network/p2p/utils" ) diff --git a/network/p2p/scoring/subscription_validator_test.go b/network/p2p/scoring/subscription_validator_test.go index 770f74cf146..1a6a4b6bfcb 100644 --- a/network/p2p/scoring/subscription_validator_test.go +++ b/network/p2p/scoring/subscription_validator_test.go @@ -164,13 +164,15 @@ func TestSubscriptionValidator_InvalidSubscriptions(t *testing.T) { // 4. Verification node also publishes a chunk request on the RequestChunks channel. // 5. Test checks that consensus node does not receive the chunk request while the other verification node does. func TestSubscriptionValidator_Integration(t *testing.T) { + unittest.SkipUnless(t, unittest.TEST_FLAKY, "flaky test") ctx, cancel := context.WithCancel(context.Background()) signalerCtx := irrecoverable.NewMockSignalerContext(t, ctx) cfg, err := config.DefaultConfig() require.NoError(t, err) // set a low update interval to speed up the test - cfg.NetworkConfig.SubscriptionProviderConfig.SubscriptionUpdateInterval = 100 * time.Millisecond + cfg.NetworkConfig.GossipSub.SubscriptionProvider.UpdateInterval = 10 * time.Millisecond + cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.AppSpecificScore.ScoreTTL = 10 * time.Millisecond sporkId := unittest.IdentifierFixture() @@ -180,7 +182,6 @@ func TestSubscriptionValidator_Integration(t *testing.T) { idProvider, p2ptest.WithLogger(unittest.Logger()), p2ptest.OverrideFlowConfig(cfg), - p2ptest.EnablePeerScoringWithOverride(p2p.PeerScoringConfigNoOverride), p2ptest.WithRole(flow.RoleConsensus)) // two verification node. @@ -188,16 +189,22 @@ func TestSubscriptionValidator_Integration(t *testing.T) { idProvider, p2ptest.WithLogger(unittest.Logger()), p2ptest.OverrideFlowConfig(cfg), - p2ptest.EnablePeerScoringWithOverride(p2p.PeerScoringConfigNoOverride), p2ptest.WithRole(flow.RoleVerification)) verNode2, verId2 := p2ptest.NodeFixture(t, sporkId, t.Name(), idProvider, p2ptest.WithLogger(unittest.Logger()), p2ptest.OverrideFlowConfig(cfg), - p2ptest.EnablePeerScoringWithOverride(p2p.PeerScoringConfigNoOverride), p2ptest.WithRole(flow.RoleVerification)) + // suppress peer provider error + peerProvider := func() peer.IDSlice { + return []peer.ID{conNode.ID(), verNode1.ID(), verNode2.ID()} + } + verNode1.WithPeersProvider(peerProvider) + verNode2.WithPeersProvider(peerProvider) + conNode.WithPeersProvider(peerProvider) + ids := flow.IdentityList{&conId, &verId1, &verId2} nodes := []p2p.LibP2PNode{conNode, verNode1, verNode2} diff --git a/network/p2p/test/fixtures.go b/network/p2p/test/fixtures.go index 0384773214c..037bf5d4d14 100644 --- a/network/p2p/test/fixtures.go +++ b/network/p2p/test/fixtures.go @@ -17,13 +17,13 @@ import ( "github.com/libp2p/go-libp2p/core/protocol" "github.com/libp2p/go-libp2p/core/routing" discoveryBackoff "github.com/libp2p/go-libp2p/p2p/discovery/backoff" + "github.com/onflow/crypto" "github.com/rs/zerolog" mockery "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "golang.org/x/exp/rand" "github.com/onflow/flow-go/config" - "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/module" "github.com/onflow/flow-go/module/irrecoverable" @@ -33,12 +33,11 @@ import ( "github.com/onflow/flow-go/network/internal/p2pfixtures" "github.com/onflow/flow-go/network/message" "github.com/onflow/flow-go/network/p2p" + p2pbuilder "github.com/onflow/flow-go/network/p2p/builder" + p2pbuilderconfig "github.com/onflow/flow-go/network/p2p/builder/config" "github.com/onflow/flow-go/network/p2p/connection" p2pdht "github.com/onflow/flow-go/network/p2p/dht" mockp2p "github.com/onflow/flow-go/network/p2p/mock" - "github.com/onflow/flow-go/network/p2p/p2pbuilder" - p2pconfig "github.com/onflow/flow-go/network/p2p/p2pbuilder/config" - "github.com/onflow/flow-go/network/p2p/tracer" "github.com/onflow/flow-go/network/p2p/unicast/protocols" "github.com/onflow/flow-go/network/p2p/utils" validator "github.com/onflow/flow-go/network/validator/pubsub" @@ -81,49 +80,32 @@ func NodeFixture(t *testing.T, dhtPrefix string, idProvider module.IdentityProvider, opts ...NodeFixtureParameterOption) (p2p.LibP2PNode, flow.Identity) { + defaultFlowConfig, err := config.DefaultConfig() require.NoError(t, err) - - logger := unittest.Logger() require.NotNil(t, idProvider) connectionGater := NewConnectionGater(idProvider, func(p peer.ID) error { return nil }) require.NotNil(t, connectionGater) - meshTracerCfg := &tracer.GossipSubMeshTracerConfig{ - Logger: unittest.Logger(), - Metrics: metrics.NewNoopCollector(), - IDProvider: idProvider, - LoggerInterval: time.Second, - HeroCacheMetricsFactory: metrics.NewNoopHeroCacheMetricsFactory(), - RpcSentTrackerCacheSize: defaultFlowConfig.NetworkConfig.GossipSubConfig.RPCSentTrackerCacheSize, - RpcSentTrackerWorkerQueueCacheSize: defaultFlowConfig.NetworkConfig.GossipSubConfig.RPCSentTrackerQueueCacheSize, - RpcSentTrackerNumOfWorkers: defaultFlowConfig.NetworkConfig.GossipSubConfig.RpcSentTrackerNumOfWorkers, - } - parameters := &NodeFixtureParameters{ NetworkingType: flownet.PrivateNetwork, HandlerFunc: func(network.Stream) {}, Unicasts: nil, Key: NetworkingKeyFixtures(t), Address: unittest.DefaultAddress, - Logger: logger, + Logger: unittest.Logger().Level(zerolog.WarnLevel), Role: flow.RoleCollection, IdProvider: idProvider, - MetricsCfg: &p2pconfig.MetricsConfig{ + MetricsCfg: &p2pbuilderconfig.MetricsConfig{ HeroCacheFactory: metrics.NewNoopHeroCacheMetricsFactory(), Metrics: metrics.NewNoopCollector(), }, - ResourceManager: &network.NullResourceManager{}, - GossipSubPeerScoreTracerInterval: defaultFlowConfig.NetworkConfig.GossipSubConfig.ScoreTracerInterval, - ConnGater: connectionGater, - PeerManagerConfig: PeerManagerConfigFixture(), // disabled by default - FlowConfig: defaultFlowConfig, - PubSubTracer: tracer.NewGossipSubMeshTracer(meshTracerCfg), - UnicastConfig: &p2pconfig.UnicastConfig{ - UnicastConfig: defaultFlowConfig.NetworkConfig.UnicastConfig, - }, + ResourceManager: &network.NullResourceManager{}, + ConnGater: connectionGater, + PeerManagerConfig: PeerManagerConfigFixture(), // disabled by default + FlowConfig: defaultFlowConfig, } for _, opt := range opts { @@ -134,31 +116,31 @@ func NodeFixture(t *testing.T, unittest.WithAddress(parameters.Address), unittest.WithRole(parameters.Role)) - logger = parameters.Logger.With().Hex("node_id", logging.ID(identity.NodeID)).Logger() + logger := parameters.Logger.With().Hex("node_id", logging.ID(identity.NodeID)).Logger() - connManager, err := connection.NewConnManager(logger, parameters.MetricsCfg.Metrics, ¶meters.FlowConfig.NetworkConfig.ConnectionManagerConfig) + connManager, err := connection.NewConnManager(logger, parameters.MetricsCfg.Metrics, ¶meters.FlowConfig.NetworkConfig.ConnectionManager) require.NoError(t, err) builder := p2pbuilder.NewNodeBuilder( logger, + ¶meters.FlowConfig.NetworkConfig.GossipSub, parameters.MetricsCfg, parameters.NetworkingType, parameters.Address, parameters.Key, sporkID, parameters.IdProvider, - defaultFlowConfig.NetworkConfig.GossipSubConfig.GossipSubScoringRegistryConfig, ¶meters.FlowConfig.NetworkConfig.ResourceManager, - ¶meters.FlowConfig.NetworkConfig.GossipSubConfig, parameters.PeerManagerConfig, &p2p.DisallowListCacheConfig{ MaxSize: uint32(1000), Metrics: metrics.NewNoopCollector(), }, - parameters.PubSubTracer, - parameters.UnicastConfig). + &p2pbuilderconfig.UnicastConfig{ + Unicast: parameters.FlowConfig.NetworkConfig.Unicast, + RateLimiterDistributor: parameters.UnicastRateLimiterDistributor, + }). SetConnectionManager(connManager). - SetCreateNode(p2pbuilder.DefaultCreateNodeFunc). SetResourceManager(parameters.ResourceManager) if parameters.DhtOptions != nil && (parameters.Role != flow.RoleAccess && parameters.Role != flow.RoleExecution) { @@ -192,7 +174,7 @@ func NodeFixture(t *testing.T, } if parameters.PeerScoringEnabled { - builder.EnableGossipSubScoringWithOverride(parameters.PeerScoringConfigOverride) + builder.OverrideGossipSubScoringConfig(parameters.PeerScoringConfigOverride) } if parameters.GossipSubFactory != nil && parameters.GossipSubConfig != nil { @@ -203,12 +185,6 @@ func NodeFixture(t *testing.T, builder.SetConnectionManager(parameters.ConnManager) } - if parameters.PubSubTracer != nil { - builder.SetGossipSubTracer(parameters.PubSubTracer) - } - - builder.SetGossipSubScoreTracerInterval(parameters.GossipSubPeerScoreTracerInterval) - n, err := builder.Build() require.NoError(t, err) @@ -229,13 +205,32 @@ func NodeFixture(t *testing.T, return n, *identity } +// RegisterPeerProviders registers the peer provider for all the nodes in the input slice. +// All node ids are registered as the peers provider for all the nodes. +// This means that every node will be connected to every other node by the peer manager. +// This is useful for suppressing the "peer provider not set" verbose warning logs in tests scenarios where +// it is desirable to have all nodes connected to each other. +// Args: +// - t: testing.T- the test object; not used, but included in the signature to defensively prevent misuse of the test utility in production. +// - nodes: nodes to register the peer provider for, each node will be connected to all other nodes. +func RegisterPeerProviders(_ *testing.T, nodes []p2p.LibP2PNode) { + ids := peer.IDSlice{} + for _, node := range nodes { + ids = append(ids, node.ID()) + } + for _, node := range nodes { + node.WithPeersProvider(func() peer.IDSlice { + return ids + }) + } +} + type NodeFixtureParameterOption func(*NodeFixtureParameters) type NodeFixtureParameters struct { HandlerFunc network.StreamHandler NetworkingType flownet.NetworkingType Unicasts []protocols.ProtocolName - UnicastConfig *p2pconfig.UnicastConfig Key crypto.PrivateKey Address string DhtOptions []dht.Option @@ -244,23 +239,22 @@ type NodeFixtureParameters struct { PeerScoringEnabled bool IdProvider module.IdentityProvider PeerScoringConfigOverride *p2p.PeerScoringConfigOverride - PeerManagerConfig *p2pconfig.PeerManagerConfig + PeerManagerConfig *p2pbuilderconfig.PeerManagerConfig PeerProvider p2p.PeersProvider // peer manager parameter ConnGater p2p.ConnectionGater ConnManager connmgr.ConnManager GossipSubFactory p2p.GossipSubFactoryFunc GossipSubConfig p2p.GossipSubAdapterConfigFunc - MetricsCfg *p2pconfig.MetricsConfig + MetricsCfg *p2pbuilderconfig.MetricsConfig ResourceManager network.ResourceManager - PubSubTracer p2p.PubSubTracer - GossipSubPeerScoreTracerInterval time.Duration // intervals at which the peer score is updated and logged. GossipSubRpcInspectorSuiteFactory p2p.GossipSubRpcInspectorSuiteFactoryFunc FlowConfig *config.FlowConfig + UnicastRateLimiterDistributor p2p.UnicastRateLimiterDistributor } func WithUnicastRateLimitDistributor(distributor p2p.UnicastRateLimiterDistributor) NodeFixtureParameterOption { return func(p *NodeFixtureParameters) { - p.UnicastConfig.RateLimiterDistributor = distributor + p.UnicastRateLimiterDistributor = distributor } } @@ -276,12 +270,6 @@ func OverrideFlowConfig(cfg *config.FlowConfig) NodeFixtureParameterOption { } } -func WithCreateStreamRetryDelay(delay time.Duration) NodeFixtureParameterOption { - return func(p *NodeFixtureParameters) { - p.UnicastConfig.CreateStreamBackoffDelay = delay - } -} - // EnablePeerScoringWithOverride enables peer scoring for the GossipSub pubsub system with the given override. // Any existing peer scoring config attribute that is set in the override will override the default peer scoring config. // Anything that is left to nil or zero value in the override will be ignored and the default value will be used. @@ -300,19 +288,13 @@ func EnablePeerScoringWithOverride(override *p2p.PeerScoringConfigOverride) Node } } -func WithGossipSubTracer(tracer p2p.PubSubTracer) NodeFixtureParameterOption { - return func(p *NodeFixtureParameters) { - p.PubSubTracer = tracer - } -} - func WithDefaultStreamHandler(handler network.StreamHandler) NodeFixtureParameterOption { return func(p *NodeFixtureParameters) { p.HandlerFunc = handler } } -func WithPeerManagerEnabled(cfg *p2pconfig.PeerManagerConfig, peerProvider p2p.PeersProvider) NodeFixtureParameterOption { +func WithPeerManagerEnabled(cfg *p2pbuilderconfig.PeerManagerConfig, peerProvider p2p.PeersProvider) NodeFixtureParameterOption { return func(p *NodeFixtureParameters) { p.PeerManagerConfig = cfg p.PeerProvider = peerProvider @@ -379,12 +361,6 @@ func WithMetricsCollector(metrics module.NetworkMetrics) NodeFixtureParameterOpt } } -func WithPeerScoreTracerInterval(interval time.Duration) NodeFixtureParameterOption { - return func(p *NodeFixtureParameters) { - p.GossipSubPeerScoreTracerInterval = interval - } -} - // WithDefaultResourceManager sets the resource manager to nil, which will cause the node to use the default resource manager. // Otherwise, it uses the resource manager provided by the test (the infinite resource manager). func WithDefaultResourceManager() NodeFixtureParameterOption { @@ -408,8 +384,8 @@ func WithUnicastHandlerFunc(handler network.StreamHandler) NodeFixtureParameterO } // PeerManagerConfigFixture is a test fixture that sets the default config for the peer manager. -func PeerManagerConfigFixture(opts ...func(*p2pconfig.PeerManagerConfig)) *p2pconfig.PeerManagerConfig { - cfg := &p2pconfig.PeerManagerConfig{ +func PeerManagerConfigFixture(opts ...func(*p2pbuilderconfig.PeerManagerConfig)) *p2pbuilderconfig.PeerManagerConfig { + cfg := &p2pbuilderconfig.PeerManagerConfig{ ConnectionPruning: true, UpdateInterval: 1 * time.Second, ConnectorFactory: connection.DefaultLibp2pBackoffConnectorFactory(), @@ -422,8 +398,8 @@ func PeerManagerConfigFixture(opts ...func(*p2pconfig.PeerManagerConfig)) *p2pco // WithZeroJitterAndZeroBackoff is a test fixture that sets the default config for the peer manager. // It uses a backoff connector with zero jitter and zero backoff. -func WithZeroJitterAndZeroBackoff(t *testing.T) func(*p2pconfig.PeerManagerConfig) { - return func(cfg *p2pconfig.PeerManagerConfig) { +func WithZeroJitterAndZeroBackoff(t *testing.T) func(*p2pbuilderconfig.PeerManagerConfig) { + return func(cfg *p2pbuilderconfig.PeerManagerConfig) { cfg.ConnectorFactory = func(host host.Host) (p2p.Connector, error) { cacheSize := 100 dialTimeout := time.Minute * 2 @@ -616,7 +592,7 @@ func EnsureStreamCreationInBothDirections(t *testing.T, ctx context.Context, nod continue } // stream creation should pass without error - err := this.OpenProtectedStream(ctx, other.ID(), t.Name(), func(stream network.Stream) error { + err := this.OpenAndWriteOnStream(ctx, other.ID(), t.Name(), func(stream network.Stream) error { // do nothing require.NotNil(t, stream) return nil @@ -849,6 +825,22 @@ func MockInspectorNotificationDistributorReadyDoneAware(d *mockp2p.GossipSubInsp }()).Maybe() } +// MockScoringRegistrySubscriptionValidatorReadyDoneAware mocks the Ready and Done methods of the subscription validator to return a channel that is already closed, +// so that the distributor is considered ready and done when the test needs. +func MockScoringRegistrySubscriptionValidatorReadyDoneAware(s *mockp2p.SubscriptionValidator) { + s.On("Start", mockery.Anything).Return().Maybe() + s.On("Ready").Return(func() <-chan struct{} { + ch := make(chan struct{}) + close(ch) + return ch + }()).Maybe() + s.On("Done").Return(func() <-chan struct{} { + ch := make(chan struct{}) + close(ch) + return ch + }()).Maybe() +} + // GossipSubRpcFixtures returns a slice of random message IDs for testing. // Args: // - t: *testing.T instance @@ -941,6 +933,18 @@ func WithIHave(msgCount, msgIDCount int, topicId string) GossipSubCtrlOption { } } +// WithIHaveMessageIDs adds iHave control messages with the given message IDs to the control message. +func WithIHaveMessageIDs(msgIDs []string, topicId string) GossipSubCtrlOption { + return func(msg *pb.ControlMessage) { + msg.Ihave = []*pb.ControlIHave{ + { + TopicID: &topicId, + MessageIDs: msgIDs, + }, + } + } +} + // WithIWant adds iWant control messages of the given size and number to the control message. // The message IDs are generated randomly. // Args: diff --git a/network/p2p/test/sporking_test.go b/network/p2p/test/sporking_test.go index c49773d7214..e1e8d8cb7b6 100644 --- a/network/p2p/test/sporking_test.go +++ b/network/p2p/test/sporking_test.go @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/onflow/flow-go/config" "github.com/onflow/flow-go/model/flow" libp2pmessage "github.com/onflow/flow-go/model/libp2p/message" "github.com/onflow/flow-go/network/message" @@ -88,7 +89,7 @@ func TestCrosstalkPreventionOnNetworkKeyChange(t *testing.T) { // create stream from node 1 to node 2 node1.Host().Peerstore().AddAddrs(peerInfo2.ID, peerInfo2.Addrs, peerstore.AddressTTL) - err = node1.OpenProtectedStream(context.Background(), peerInfo2.ID, t.Name(), func(stream network.Stream) error { + err = node1.OpenAndWriteOnStream(context.Background(), peerInfo2.ID, t.Name(), func(stream network.Stream) error { require.NotNil(t, stream) return nil }) @@ -157,7 +158,7 @@ func TestOneToOneCrosstalkPrevention(t *testing.T) { // create stream from node 1 to node 2 node2.Host().Peerstore().AddAddrs(peerInfo1.ID, peerInfo1.Addrs, peerstore.AddressTTL) - err = node2.OpenProtectedStream(context.Background(), peerInfo1.ID, t.Name(), func(stream network.Stream) error { + err = node2.OpenAndWriteOnStream(context.Background(), peerInfo1.ID, t.Name(), func(stream network.Stream) error { assert.NotNil(t, stream) return nil }) @@ -204,15 +205,23 @@ func TestOneToKCrosstalkPrevention(t *testing.T) { previousSporkId := unittest.IdentifierFixture() // create and start node 1 on localhost and random port + cfg, err := config.DefaultConfig() + require.NoError(t, err) + // cross-talk prevention is intrinsically tied to how we encode topics, peer scoring adds another layer of protection by preventing unknown identifiers + // from joining the mesh. As this test simulates the scenario where a node is moved from the old chain to the new chain, we disable peer scoring + // to allow the node to join the mesh on the new chain, otherwise the node will be disconnected from the mesh due to peer scoring penalty for unknown identifiers. + cfg.NetworkConfig.GossipSub.PeerScoringEnabled = false node1, id1 := p2ptest.NodeFixture(t, previousSporkId, "test_one_to_k_crosstalk_prevention", idProvider, + p2ptest.OverrideFlowConfig(cfg), ) p2ptest.StartNode(t, signalerCtx1, node1) defer p2ptest.StopNode(t, node1, cancel1) idProvider.SetIdentities(flow.IdentityList{&id1}) + // create and start node 2 on localhost and random port with the same root block ID node2, id2 := p2ptest.NodeFixture(t, previousSporkId, @@ -315,7 +324,7 @@ func testOneToOneMessagingFails(t *testing.T, sourceNode p2p.LibP2PNode, peerInf sourceNode.Host().Peerstore().AddAddrs(peerInfo.ID, peerInfo.Addrs, peerstore.AddressTTL) ctx, cancel := context.WithCancel(context.Background()) defer cancel() - err := sourceNode.OpenProtectedStream(ctx, peerInfo.ID, t.Name(), func(stream network.Stream) error { + err := sourceNode.OpenAndWriteOnStream(ctx, peerInfo.ID, t.Name(), func(stream network.Stream) error { // this callback should never be called assert.Fail(t, "stream creation should have failed") return nil diff --git a/network/p2p/test/topic_validator_test.go b/network/p2p/test/topic_validator_test.go index 21fd328cf1a..45c4bc296d8 100644 --- a/network/p2p/test/topic_validator_test.go +++ b/network/p2p/test/topic_validator_test.go @@ -24,7 +24,7 @@ import ( "github.com/onflow/flow-go/network/message" "github.com/onflow/flow-go/network/mocknetwork" "github.com/onflow/flow-go/network/p2p" - "github.com/onflow/flow-go/network/p2p/p2plogging" + p2plogging "github.com/onflow/flow-go/network/p2p/logging" p2ptest "github.com/onflow/flow-go/network/p2p/test" "github.com/onflow/flow-go/network/p2p/translator" "github.com/onflow/flow-go/network/p2p/utils" @@ -55,7 +55,7 @@ func TestTopicValidator_Unstaked(t *testing.T) { channel := channels.ConsensusCommittee topic := channels.TopicFromChannel(channel, sporkId) - //NOTE: identity2 is not in the ids list simulating an un-staked node + // NOTE: identity2 is not in the ids list simulating an un-staked node ids := flow.IdentityList{&identity1} translatorFixture, err := translator.NewFixedTableIdentityTranslator(ids) require.NoError(t, err) diff --git a/network/p2p/tracer/gossipSubMeshTracer.go b/network/p2p/tracer/gossipSubMeshTracer.go index d7d602c59fe..7e5324b01dd 100644 --- a/network/p2p/tracer/gossipSubMeshTracer.go +++ b/network/p2p/tracer/gossipSubMeshTracer.go @@ -8,6 +8,7 @@ import ( pubsub "github.com/libp2p/go-libp2p-pubsub" "github.com/libp2p/go-libp2p/core/peer" + "github.com/libp2p/go-libp2p/core/protocol" "github.com/rs/zerolog" "github.com/onflow/flow-go/module" @@ -15,8 +16,9 @@ import ( "github.com/onflow/flow-go/module/irrecoverable" "github.com/onflow/flow-go/module/metrics" "github.com/onflow/flow-go/network" + "github.com/onflow/flow-go/network/channels" "github.com/onflow/flow-go/network/p2p" - "github.com/onflow/flow-go/network/p2p/p2plogging" + p2plogging "github.com/onflow/flow-go/network/p2p/logging" "github.com/onflow/flow-go/network/p2p/tracer/internal" "github.com/onflow/flow-go/utils/logging" ) @@ -45,14 +47,13 @@ const ( // Additionally, it allows users to configure the logging interval. type GossipSubMeshTracer struct { component.Component - pubsub.RawTracer topicMeshMu sync.RWMutex // to protect topicMeshMap topicMeshMap map[string]map[peer.ID]struct{} // map of local mesh peers by topic. logger zerolog.Logger idProvider module.IdentityProvider loggerInterval time.Duration - metrics module.GossipSubLocalMeshMetrics + metrics module.LocalGossipSubRouterMetrics rpcSentTracker *internal.RPCSentTracker } @@ -62,7 +63,7 @@ type GossipSubMeshTracerConfig struct { network.NetworkingType metrics.HeroCacheMetricsFactory Logger zerolog.Logger - Metrics module.GossipSubLocalMeshMetrics + Metrics module.LocalGossipSubRouterMetrics IDProvider module.IdentityProvider LoggerInterval time.Duration RpcSentTrackerCacheSize uint32 @@ -87,7 +88,6 @@ func NewGossipSubMeshTracer(config *GossipSubMeshTracerConfig) *GossipSubMeshTra LastHighestIhavesSentResetInterval: defaultLastHighestIHaveRPCSizeResetInterval, }) g := &GossipSubMeshTracer{ - RawTracer: NewGossipSubNoopTracer(), topicMeshMap: make(map[string]map[peer.ID]struct{}), idProvider: config.IDProvider, metrics: config.Metrics, @@ -115,20 +115,25 @@ func NewGossipSubMeshTracer(config *GossipSubMeshTracerConfig) *GossipSubMeshTra return g } -// GetMeshPeers returns the local mesh peers for the given topic. -func (t *GossipSubMeshTracer) GetMeshPeers(topic string) []peer.ID { +// GetLocalMeshPeers returns the local mesh peers for the given topic. +// Args: +// - topic: the topic. +// Returns: +// - []peer.ID: the local mesh peers for the given topic. +func (t *GossipSubMeshTracer) GetLocalMeshPeers(topic channels.Topic) []peer.ID { t.topicMeshMu.RLock() defer t.topicMeshMu.RUnlock() - peers := make([]peer.ID, 0, len(t.topicMeshMap[topic])) - for p := range t.topicMeshMap[topic] { + peers := make([]peer.ID, 0, len(t.topicMeshMap[topic.String()])) + for p := range t.topicMeshMap[topic.String()] { peers = append(peers, p) } return peers } -// Graft is called when a peer is added to a topic mesh. The tracer uses this to track the mesh peers. +// Graft is called by GossipSub when a peer is added to a topic mesh. The tracer uses this to track the mesh peers. func (t *GossipSubMeshTracer) Graft(p peer.ID, topic string) { + t.metrics.OnPeerGraftTopic(topic) t.topicMeshMu.Lock() defer t.topicMeshMu.Unlock() @@ -154,8 +159,9 @@ func (t *GossipSubMeshTracer) Graft(p peer.ID, topic string) { lg.Debug().Hex("flow_id", logging.ID(id.NodeID)).Str("role", id.Role.String()).Msg("grafted peer") } -// Prune is called when a peer is removed from a topic mesh. The tracer uses this to track the mesh peers. +// Prune is called by GossipSub when a peer is removed from a topic mesh. The tracer uses this to track the mesh peers. func (t *GossipSubMeshTracer) Prune(p peer.ID, topic string) { + t.metrics.OnPeerPruneTopic(topic) t.topicMeshMu.Lock() defer t.topicMeshMu.Unlock() @@ -182,13 +188,256 @@ func (t *GossipSubMeshTracer) Prune(p peer.ID, topic string) { lg.Debug().Hex("flow_id", logging.ID(id.NodeID)).Str("role", id.Role.String()).Msg("pruned peer") } -// SendRPC is called when a RPC is sent. Currently, the GossipSubMeshTracer tracks iHave RPC messages that have been sent. +// SendRPC is called by GossipSub when a RPC is sent. Currently, the GossipSubMeshTracer tracks iHave RPC messages that have been sent. // This function can be updated to track other control messages in the future as required. -func (t *GossipSubMeshTracer) SendRPC(rpc *pubsub.RPC, _ peer.ID) { +func (t *GossipSubMeshTracer) SendRPC(rpc *pubsub.RPC, p peer.ID) { err := t.rpcSentTracker.Track(rpc) if err != nil { t.logger.Err(err).Bool(logging.KeyNetworkingSecurity, true).Msg("failed to track sent pubsbub rpc") } + + msgCount, ihaveCount, iwantCount, graftCount, pruneCount := 0, 0, 0, 0, 0 + if rpc.Control != nil { + ihaveCount = len(rpc.Control.Ihave) + iwantCount = len(rpc.Control.Iwant) + graftCount = len(rpc.Control.Graft) + pruneCount = len(rpc.Control.Prune) + } + msgCount = len(rpc.Publish) + t.metrics.OnRpcReceived(msgCount, ihaveCount, iwantCount, graftCount, pruneCount) + if t.logger.GetLevel() == zerolog.TraceLevel { + t.logger.Trace(). + Str("remote_peer_id", p2plogging.PeerId(p)). + Int("subscription_option_count", len(rpc.Subscriptions)). + Int("publish_message_count", msgCount). + Int("ihave_size", ihaveCount). + Int("iwant_size", iwantCount). + Int("graft_size", graftCount). + Int("prune_size", pruneCount). + Msg("sent pubsub rpc") + } + + t.metrics.OnRpcSent(msgCount, ihaveCount, iwantCount, graftCount, pruneCount) +} + +// AddPeer is called by GossipSub as a callback when a peer is added to the local node on a protocol, i.e., the local node is connected to the peer on a protocol. +// The peer may or may not be subscribed to any topic. +func (t *GossipSubMeshTracer) AddPeer(p peer.ID, proto protocol.ID) { + if t.logger.GetLevel() == zerolog.TraceLevel { + t.logger.Trace(). + Str("local_peer_id", p2plogging.PeerId(p)). + Str("protocol", string(proto)). + Msg("peer added") + } + t.metrics.OnPeerAddedToProtocol(string(proto)) +} + +// RemovePeer is called by GossipSub as a callback when a peer is removed from the local node, +// i.e., the local node is no longer connected to the peer. +func (t *GossipSubMeshTracer) RemovePeer(p peer.ID) { + t.metrics.OnPeerRemovedFromProtocol() + if t.logger.GetLevel() == zerolog.TraceLevel { + t.logger.Trace(). + Str("local_peer_id", p2plogging.PeerId(p)). + Msg("peer removed") + } +} + +// Join is called by GossipSub as a callback when the local node joins a topic. +func (t *GossipSubMeshTracer) Join(topic string) { + t.metrics.OnLocalPeerJoinedTopic() + if t.logger.GetLevel() == zerolog.TraceLevel { + t.logger.Trace(). + Str("topic", topic). + Msg("local peer joined topic") + } +} + +// Leave is called by GossipSub as a callback when the local node leaves a topic. +func (t *GossipSubMeshTracer) Leave(topic string) { + t.metrics.OnLocalPeerLeftTopic() + if t.logger.GetLevel() == zerolog.TraceLevel { + t.logger.Trace(). + Str("topic", topic). + Msg("local peer left topic") + } +} + +// ValidateMessage is called by GossipSub as a callback when a message is received by the local node and entered the validation phase. +// As the result of the validation, the message may be rejected or passed to the application (i.e., Flow protocol). +func (t *GossipSubMeshTracer) ValidateMessage(msg *pubsub.Message) { + size := len(msg.Data) + t.metrics.OnMessageEnteredValidation(size) + + if t.logger.GetLevel() > zerolog.TraceLevel { + return // return fast if we are not logging at trace level + } + + lg := t.logger.With().Logger() + if msg.Topic != nil { + lg = lg.With().Str("topic", *msg.Topic).Logger() + } + from, err := peer.IDFromBytes(msg.From) + if err == nil { + lg = lg.With().Str("remote_peer_id", p2plogging.PeerId(from)).Logger() + } + + lg.Trace(). + Str("received_from", p2plogging.PeerId(msg.ReceivedFrom)). + Int("message_size", size). + Msg("received pubsub message entered validation phase") +} + +// DeliverMessage is called by GossipSub as a callback when the local node delivers a message to all subscribers of the topic. +func (t *GossipSubMeshTracer) DeliverMessage(msg *pubsub.Message) { + size := len(msg.Data) + t.metrics.OnMessageDeliveredToAllSubscribers(size) + + if t.logger.GetLevel() > zerolog.TraceLevel { + return // return fast if we are not logging at trace level + } + + lg := t.logger.With().Logger() + if msg.Topic != nil { + lg = lg.With().Str("topic", *msg.Topic).Logger() + } + from, err := peer.IDFromBytes(msg.From) + if err == nil { + lg = lg.With().Str("remote_peer_id", p2plogging.PeerId(from)).Logger() + } + + lg.Trace(). + Str("received_from", p2plogging.PeerId(msg.ReceivedFrom)). + Int("message_size", len(msg.Data)). + Msg("delivered pubsub message to all subscribers") +} + +// RejectMessage is called by GossipSub as a callback when a message is rejected by the local node. +// The message may be rejected for a variety of reasons, but the most common reason is that the message is invalid with respect to signature. +// Any message that arrives at the local node should contain the peer id of the source (i.e., the peer that created the message), the +// networking public key of the source, and the signature of the message. The local node uses this information to verify the message. +// If any of the information is missing or invalid, the message is rejected. +func (t *GossipSubMeshTracer) RejectMessage(msg *pubsub.Message, reason string) { + size := len(msg.Data) + t.metrics.OnMessageRejected(size, reason) + + if t.logger.GetLevel() > zerolog.TraceLevel { + return // return fast if we are not logging at trace level + } + + lg := t.logger.With().Logger() + if msg.Topic != nil { + lg = lg.With().Str("topic", *msg.Topic).Logger() + } + from, err := peer.IDFromBytes(msg.From) + if err == nil { + lg = lg.With().Str("remote_peer_id", p2plogging.PeerId(from)).Logger() + } + + lg.Trace(). + Str("received_from", p2plogging.PeerId(msg.ReceivedFrom)). + Int("message_size", size). + Msg("rejected pubsub message") + +} + +// DuplicateMessage is called by GossipSub as a callback when a duplicate message is received by the local node. +func (t *GossipSubMeshTracer) DuplicateMessage(msg *pubsub.Message) { + size := len(msg.Data) + t.metrics.OnMessageDuplicate(size) + + if t.logger.GetLevel() > zerolog.TraceLevel { + return // return fast if we are not logging at trace level + } + + lg := t.logger.With().Logger() + if msg.Topic != nil { + lg = lg.With().Str("topic", *msg.Topic).Logger() + } + from, err := peer.IDFromBytes(msg.From) + if err == nil { + lg = lg.With().Str("remote_peer_id", p2plogging.PeerId(from)).Logger() + } + + lg.Trace(). + Str("received_from", p2plogging.PeerId(msg.ReceivedFrom)). + Int("message_size", size). + Msg("received duplicate pubsub message") +} + +// ThrottlePeer is called by GossipSub when a peer is throttled by the local node, i.e., the local node is not accepting any +// pubsub message from the peer but may still accept control messages. +func (t *GossipSubMeshTracer) ThrottlePeer(p peer.ID) { + t.logger.Warn(). + Bool(logging.KeyNetworkingSecurity, true). + Str("remote_peer_id", p2plogging.PeerId(p)). + Msg("throttled peer; no longer accepting pubsub messages from peer, but may still accept control messages") + t.metrics.OnPeerThrottled() +} + +// RecvRPC is called by GossipSub as a callback when an inbound RPC message is received by the local node, +// note that the RPC already passed the RPC inspection, hence its statistics may be different from the RPC inspector metrics, as +// the RPC inspector metrics are updated before the RPC inspection, and the RPC may gone through truncation or rejection. +// This callback tracks the RPC messages as they are completely received by the local GossipSub router. +func (t *GossipSubMeshTracer) RecvRPC(rpc *pubsub.RPC) { + msgCount, ihaveCount, iwantCount, graftCount, pruneCount := 0, 0, 0, 0, 0 + if rpc.Control != nil { + ihaveCount = len(rpc.Control.Ihave) + iwantCount = len(rpc.Control.Iwant) + graftCount = len(rpc.Control.Graft) + pruneCount = len(rpc.Control.Prune) + } + msgCount = len(rpc.Publish) + t.metrics.OnRpcReceived(msgCount, ihaveCount, iwantCount, graftCount, pruneCount) + if t.logger.GetLevel() == zerolog.TraceLevel { + t.logger.Trace(). + Int("subscription_option_count", len(rpc.Subscriptions)). + Int("publish_message_count", msgCount). + Int("ihave_size", ihaveCount). + Int("iwant_size", iwantCount). + Int("graft_size", graftCount). + Int("prune_size", pruneCount). + Msg("received pubsub rpc") + } +} + +// DropRPC is called by GossipSub as a callback when an outbound RPC message is dropped by the local node, typically because the local node +// outbound message queue is full; or the RPC is big and the local node cannot fragment it. +func (t *GossipSubMeshTracer) DropRPC(rpc *pubsub.RPC, p peer.ID) { + msgCount, ihaveCount, iwantCount, graftCount, pruneCount := 0, 0, 0, 0, 0 + if rpc.Control != nil { + ihaveCount = len(rpc.Control.Ihave) + iwantCount = len(rpc.Control.Iwant) + graftCount = len(rpc.Control.Graft) + pruneCount = len(rpc.Control.Prune) + } + msgCount = len(rpc.Publish) + t.metrics.OnRpcReceived(msgCount, ihaveCount, iwantCount, graftCount, pruneCount) + if t.logger.GetLevel() == zerolog.TraceLevel { + t.logger.Warn(). + Bool(logging.KeyNetworkingSecurity, true). + Str("remote_peer_id", p2plogging.PeerId(p)). + Int("subscription_option_count", len(rpc.Subscriptions)). + Int("publish_message_count", msgCount). + Int("ihave_size", ihaveCount). + Int("iwant_size", iwantCount). + Int("graft_size", graftCount). + Int("prune_size", pruneCount). + Msg("outbound rpc dropped") + } + t.metrics.OnOutboundRpcDropped() +} + +// UndeliverableMessage is called by GossipSub as a callback when a message is dropped by the local node, typically because the local node +// outbound message queue is full; or the message is big and the local node cannot fragment it. +func (t *GossipSubMeshTracer) UndeliverableMessage(msg *pubsub.Message) { + t.logger.Warn(). + Bool(logging.KeyNetworkingSecurity, true). + Str("topic", *msg.Topic). + Str("remote_peer_id", p2plogging.PeerId(msg.ReceivedFrom)). + Int("message_size", len(msg.Data)). + Msg("undeliverable pubsub message") + t.metrics.OnUndeliveredMessage() } // WasIHaveRPCSent returns true if an iHave control message for the messageID was sent, otherwise false. diff --git a/network/p2p/tracer/gossipSubMeshTracer_test.go b/network/p2p/tracer/gossipSubMeshTracer_test.go index 842fa3dfe72..aaf6419cec7 100644 --- a/network/p2p/tracer/gossipSubMeshTracer_test.go +++ b/network/p2p/tracer/gossipSubMeshTracer_test.go @@ -66,24 +66,22 @@ func TestGossipSubMeshTracer(t *testing.T) { // creates one node with a gossipsub mesh meshTracer, and the other nodes without a gossipsub mesh meshTracer. // we only need one node with a meshTracer to test the meshTracer. // meshTracer logs at 1 second intervals for sake of testing. - collector := mockmodule.NewGossipSubLocalMeshMetrics(t) - meshTracerCfg := &tracer.GossipSubMeshTracerConfig{ - Logger: logger, - Metrics: collector, - IDProvider: idProvider, - LoggerInterval: time.Second, - HeroCacheMetricsFactory: metrics.NewNoopHeroCacheMetricsFactory(), - RpcSentTrackerCacheSize: defaultConfig.NetworkConfig.GossipSubConfig.RPCSentTrackerCacheSize, - RpcSentTrackerWorkerQueueCacheSize: defaultConfig.NetworkConfig.GossipSubConfig.RPCSentTrackerQueueCacheSize, - RpcSentTrackerNumOfWorkers: defaultConfig.NetworkConfig.GossipSubConfig.RpcSentTrackerNumOfWorkers, - } - meshTracer := tracer.NewGossipSubMeshTracer(meshTracerCfg) + // creates one node with a gossipsub mesh meshTracer, and the other nodes without a gossipsub mesh meshTracer. + // we only need one node with a meshTracer to test the meshTracer. + // meshTracer logs at 1 second intervals for sake of testing. + collector := newLocalMeshTracerMetricsCollector(t) + // set the meshTracer to log at 1 second intervals for sake of testing. + defaultConfig.NetworkConfig.GossipSub.RpcTracer.LocalMeshLogInterval = 1 * time.Second + // disables peer scoring for sake of testing; so that unknown peers are not penalized and could be detected by the meshTracer. + defaultConfig.NetworkConfig.GossipSub.PeerScoringEnabled = false tracerNode, tracerId := p2ptest.NodeFixture( t, sporkId, t.Name(), idProvider, - p2ptest.WithGossipSubTracer(meshTracer), + p2ptest.WithLogger(logger), + p2ptest.OverrideFlowConfig(defaultConfig), + p2ptest.WithMetricsCollector(collector), p2ptest.WithRole(flow.RoleConsensus)) idProvider.On("ByPeerID", tracerNode.ID()).Return(&tracerId, true).Maybe() @@ -116,6 +114,7 @@ func TestGossipSubMeshTracer(t *testing.T) { nodes := []p2p.LibP2PNode{tracerNode, otherNode1, otherNode2, unknownNode} ids := flow.IdentityList{&tracerId, &otherId1, &otherId2, &unknownId} + p2ptest.RegisterPeerProviders(t, nodes) p2ptest.StartNodes(t, signalerCtx, nodes) defer p2ptest.StopNodes(t, nodes, cancel) @@ -123,9 +122,9 @@ func TestGossipSubMeshTracer(t *testing.T) { // all nodes subscribe to topic1 // for topic 1 expect the meshTracer to be notified of the local mesh size being 1, 2, and 3 (when unknownNode, otherNode1, and otherNode2 join the mesh). - collector.On("OnLocalMeshSizeUpdated", topic1.String(), 1).Twice() // 1 for the first subscription, 1 for the first leave - collector.On("OnLocalMeshSizeUpdated", topic1.String(), 2).Twice() // 1 for the second subscription, 1 for the second leave - collector.On("OnLocalMeshSizeUpdated", topic1.String(), 3).Once() // 3 for the third subscription. + collector.l.On("OnLocalMeshSizeUpdated", topic1.String(), 1).Twice() // 1 for the first subscription, 1 for the first leave + collector.l.On("OnLocalMeshSizeUpdated", topic1.String(), 2).Twice() // 1 for the second subscription, 1 for the second leave + collector.l.On("OnLocalMeshSizeUpdated", topic1.String(), 3).Once() // 3 for the third subscription. for _, node := range nodes { _, err := node.Subscribe( @@ -138,7 +137,7 @@ func TestGossipSubMeshTracer(t *testing.T) { // the tracerNode and otherNode1 subscribe to topic2 // for topic 2 expect the meshTracer to be notified of the local mesh size being 1 (when otherNode1 join the mesh). - collector.On("OnLocalMeshSizeUpdated", topic2.String(), 1).Once() + collector.l.On("OnLocalMeshSizeUpdated", topic2.String(), 1).Once() for _, node := range []p2p.LibP2PNode{tracerNode, otherNode1} { _, err := node.Subscribe( @@ -152,15 +151,15 @@ func TestGossipSubMeshTracer(t *testing.T) { // eventually, the meshTracer should have the other nodes in its mesh. assert.Eventually(t, func() bool { topic1MeshSize := 0 - for _, peer := range meshTracer.GetMeshPeers(topic1.String()) { - if peer == otherNode1.ID() || peer == otherNode2.ID() { + for _, peerId := range tracerNode.GetLocalMeshPeers(topic1) { + if peerId == otherNode1.ID() || peerId == otherNode2.ID() { topic1MeshSize++ } } topic2MeshSize := 0 - for _, peer := range meshTracer.GetMeshPeers(topic2.String()) { - if peer == otherNode1.ID() { + for _, peerId := range tracerNode.GetLocalMeshPeers(topic2) { + if peerId == otherNode1.ID() { topic2MeshSize++ } } @@ -174,7 +173,7 @@ func TestGossipSubMeshTracer(t *testing.T) { }, 2*time.Second, 10*time.Millisecond) // expect the meshTracer to be notified of the local mesh size being (when all nodes leave the mesh). - collector.On("OnLocalMeshSizeUpdated", topic1.String(), 0).Once() + collector.l.On("OnLocalMeshSizeUpdated", topic1.String(), 0).Once() // all nodes except the tracerNode unsubscribe from the topic1, which triggers sending a PRUNE to the tracerNode for each unsubscription. // We expect the tracerNode to remove the otherNode1, otherNode2, and unknownNode from its mesh. @@ -184,18 +183,36 @@ func TestGossipSubMeshTracer(t *testing.T) { assert.Eventually(t, func() bool { // eventually, the tracerNode should not have the other node in its mesh for topic1. - for _, peer := range meshTracer.GetMeshPeers(topic1.String()) { - if peer == otherNode1.ID() || peer == otherNode2.ID() || peer == unknownNode.ID() { + for _, peerId := range tracerNode.GetLocalMeshPeers(topic1) { + if peerId == otherNode1.ID() || peerId == otherNode2.ID() || peerId == unknownNode.ID() { return false } } // but the tracerNode should still have the otherNode1 in its mesh for topic2. - for _, peer := range meshTracer.GetMeshPeers(topic2.String()) { - if peer != otherNode1.ID() { + for _, peerId := range tracerNode.GetLocalMeshPeers(topic2) { + if peerId != otherNode1.ID() { return false } } return true }, 2*time.Second, 10*time.Millisecond) } + +// localMeshTracerMetricsCollector is a mock metrics that can be mocked for GossipSubLocalMeshMetrics while acting as a NoopCollector for other metrics. +type localMeshTracerMetricsCollector struct { + *metrics.NoopCollector + l *mockmodule.LocalGossipSubRouterMetrics +} + +func newLocalMeshTracerMetricsCollector(t *testing.T) *localMeshTracerMetricsCollector { + return &localMeshTracerMetricsCollector{ + l: mockmodule.NewLocalGossipSubRouterMetrics(t), + NoopCollector: metrics.NewNoopCollector(), + } +} + +func (c *localMeshTracerMetricsCollector) OnLocalMeshSizeUpdated(topic string, size int) { + // calls the mock method to assert the metrics. + c.l.OnLocalMeshSizeUpdated(topic, size) +} diff --git a/network/p2p/tracer/gossipSubScoreTracer.go b/network/p2p/tracer/gossipSubScoreTracer.go index a4f7364a05d..8fc58dc81db 100644 --- a/network/p2p/tracer/gossipSubScoreTracer.go +++ b/network/p2p/tracer/gossipSubScoreTracer.go @@ -12,7 +12,7 @@ import ( "github.com/onflow/flow-go/module/irrecoverable" "github.com/onflow/flow-go/network/channels" "github.com/onflow/flow-go/network/p2p" - "github.com/onflow/flow-go/network/p2p/p2plogging" + p2plogging "github.com/onflow/flow-go/network/p2p/logging" "github.com/onflow/flow-go/utils/logging" ) diff --git a/network/p2p/tracer/gossipSubScoreTracer_test.go b/network/p2p/tracer/gossipSubScoreTracer_test.go index 1592f182299..dab069024c9 100644 --- a/network/p2p/tracer/gossipSubScoreTracer_test.go +++ b/network/p2p/tracer/gossipSubScoreTracer_test.go @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/require" "go.uber.org/atomic" + "github.com/onflow/flow-go/config" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/module" "github.com/onflow/flow-go/module/irrecoverable" @@ -72,6 +73,14 @@ func TestGossipSubScoreTracer(t *testing.T) { topic1 := channels.TopicFromChannel(channels.PushBlocks, sporkId) // 3. Creates three nodes with different roles and sets their roles as consensus, access, and tracer, respectively. + cfg, err := config.DefaultConfig() + require.NoError(t, err) + // tracer will update the score and local mesh every 1 second (for testing purposes) + cfg.NetworkConfig.GossipSub.RpcTracer.LocalMeshLogInterval = 1 * time.Second + cfg.NetworkConfig.GossipSub.RpcTracer.ScoreTracerInterval = 1 * time.Second + // the libp2p node updates the subscription list as well as the app-specific score every 10 milliseconds (for testing purposes) + cfg.NetworkConfig.GossipSub.SubscriptionProvider.UpdateInterval = 10 * time.Millisecond + cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.AppSpecificScore.ScoreTTL = 10 * time.Millisecond tracerNode, tracerId := p2ptest.NodeFixture( t, sporkId, @@ -82,7 +91,7 @@ func TestGossipSubScoreTracer(t *testing.T) { c: scoreMetrics, }), p2ptest.WithLogger(logger), - p2ptest.WithPeerScoreTracerInterval(1*time.Second), // set the peer score log interval to 1 second for sake of testing. + p2ptest.OverrideFlowConfig(cfg), p2ptest.EnablePeerScoringWithOverride(&p2p.PeerScoringConfigOverride{ AppSpecificScoreParams: func(pid peer.ID) float64 { id, ok := idProvider.ByPeerID(pid) @@ -163,7 +172,7 @@ func TestGossipSubScoreTracer(t *testing.T) { scoreMetrics.On("SetWarningStateCount", uint(0)).Return() // 6. Subscribes the nodes to a common topic. - _, err := tracerNode.Subscribe( + _, err = tracerNode.Subscribe( topic1, validator.TopicValidator( unittest.Logger(), @@ -186,7 +195,7 @@ func TestGossipSubScoreTracer(t *testing.T) { // 7. Expects the tracer node to have the correct app scores, a non-zero score, an existing behaviour score, an existing // IP score, and an existing mesh score. - assert.Eventually(t, func() bool { + require.Eventually(t, func() bool { // we expect the tracerNode to have the consensusNodes and accessNodes with the correct app scores. exposer := tracerNode.PeerScoreExposer() score, ok := exposer.GetAppScore(consensusNode.ID()) diff --git a/network/p2p/tracer/internal/rpc_sent_tracker_test.go b/network/p2p/tracer/internal/rpc_sent_tracker_test.go index 208bbc42940..938a998cf46 100644 --- a/network/p2p/tracer/internal/rpc_sent_tracker_test.go +++ b/network/p2p/tracer/internal/rpc_sent_tracker_test.go @@ -228,10 +228,10 @@ func mockTracker(t *testing.T, lastHighestIhavesSentResetInterval time.Duration) require.NoError(t, err) tracker := NewRPCSentTracker(&RPCSentTrackerConfig{ Logger: zerolog.Nop(), - RPCSentCacheSize: cfg.NetworkConfig.GossipSubConfig.RPCSentTrackerCacheSize, + RPCSentCacheSize: cfg.NetworkConfig.GossipSub.RpcTracer.RPCSentTrackerCacheSize, RPCSentCacheCollector: metrics.NewNoopCollector(), WorkerQueueCacheCollector: metrics.NewNoopCollector(), - WorkerQueueCacheSize: cfg.NetworkConfig.GossipSubConfig.RPCSentTrackerQueueCacheSize, + WorkerQueueCacheSize: cfg.NetworkConfig.GossipSub.RpcTracer.RPCSentTrackerQueueCacheSize, NumOfWorkers: 1, LastHighestIhavesSentResetInterval: lastHighestIhavesSentResetInterval, }) diff --git a/network/p2p/translator/identity_provider_translator.go b/network/p2p/translator/identity_provider_translator.go index 8156f2e22a2..d1dd643415a 100644 --- a/network/p2p/translator/identity_provider_translator.go +++ b/network/p2p/translator/identity_provider_translator.go @@ -10,7 +10,7 @@ import ( "github.com/onflow/flow-go/module" "github.com/onflow/flow-go/network/p2p" "github.com/onflow/flow-go/network/p2p/keyutils" - "github.com/onflow/flow-go/network/p2p/p2plogging" + p2plogging "github.com/onflow/flow-go/network/p2p/logging" ) // IdentityProviderIDTranslator implements an `p2p.IDTranslator` which provides ID diff --git a/network/p2p/translator/unstaked_translator_test.go b/network/p2p/translator/unstaked_translator_test.go index 939e2eb2441..d8ef5f82137 100644 --- a/network/p2p/translator/unstaked_translator_test.go +++ b/network/p2p/translator/unstaked_translator_test.go @@ -5,13 +5,11 @@ import ( "testing" "github.com/libp2p/go-libp2p/core/peer" + fcrypto "github.com/onflow/crypto" "github.com/stretchr/testify/require" - "github.com/onflow/flow-go/network/p2p/translator" - - fcrypto "github.com/onflow/flow-go/crypto" - "github.com/onflow/flow-go/network/p2p/keyutils" + "github.com/onflow/flow-go/network/p2p/translator" ) // For these test, refer to https://github.com/libp2p/specs/blob/master/peer-ids/peer-ids.md for libp2p diff --git a/network/p2p/unicast/cache/unicastConfigCache.go b/network/p2p/unicast/cache/unicastConfigCache.go index 13c000110fe..52845d7f33a 100644 --- a/network/p2p/unicast/cache/unicastConfigCache.go +++ b/network/p2p/unicast/cache/unicastConfigCache.go @@ -2,7 +2,6 @@ package unicastcache import ( "fmt" - "sync" "github.com/libp2p/go-libp2p/core/peer" "github.com/rs/zerolog" @@ -15,13 +14,7 @@ import ( "github.com/onflow/flow-go/network/p2p/unicast" ) -// ErrUnicastConfigNotFound is a benign error that indicates that the unicast config does not exist in the cache. It is not a fatal error. -var ErrUnicastConfigNotFound = fmt.Errorf("unicast config not found") - type UnicastConfigCache struct { - // mutex is temporarily protect the edge case in HeroCache that optimistic adjustment causes the cache to be full. - // TODO: remove this mutex after the HeroCache is fixed. - mutex sync.RWMutex peerCache *stdmap.Backend cfgFactory func() unicast.Config // factory function that creates a new unicast config. } @@ -56,7 +49,7 @@ func NewUnicastConfigCache( } } -// Adjust applies the given adjust function to the unicast config of the given peer ID, and stores the adjusted config in the cache. +// AdjustWithInit applies the given adjust function to the unicast config of the given peer ID, and stores the adjusted config in the cache. // It returns an error if the adjustFunc returns an error. // Note that if the Adjust is called when the config does not exist, the config is initialized and the // adjust function is applied to the initialized config again. In this case, the adjust function should not return an error. @@ -65,51 +58,11 @@ func NewUnicastConfigCache( // - adjustFunc: the function that adjusts the unicast config. // Returns: // - error any returned error should be considered as an irrecoverable error and indicates a bug. -func (d *UnicastConfigCache) Adjust(peerID peer.ID, adjustFunc unicast.UnicastConfigAdjustFunc) (*unicast.Config, error) { - d.mutex.Lock() // making optimistic adjustment atomic. - defer d.mutex.Unlock() - - // first we translate the peer id to a flow id (taking - peerIdHash := PeerIdToFlowId(peerID) - adjustedUnicastCfg, err := d.adjust(peerIdHash, adjustFunc) - if err != nil { - if err == ErrUnicastConfigNotFound { - // if the config does not exist, we initialize the config and try to adjust it again. - // Note: there is an edge case where the config is initialized by another goroutine between the two calls. - // In this case, the init function is invoked twice, but it is not a problem because the underlying - // cache is thread-safe. Hence, we do not need to synchronize the two calls. In such cases, one of the - // two calls returns false, and the other call returns true. We do not care which call returns false, hence, - // we ignore the return value of the init function. - e := UnicastConfigEntity{ - PeerId: peerID, - Config: d.cfgFactory(), - } - - _ = d.peerCache.Add(e) - - // as the config is initialized, the adjust function should not return an error, and any returned error - // is an irrecoverable error and indicates a bug. - return d.adjust(peerIdHash, adjustFunc) - } - // if the adjust function returns an unexpected error on the first attempt, we return the error directly. - // any returned error should be considered as an irrecoverable error and indicates a bug. - return nil, fmt.Errorf("failed to adjust unicast config: %w", err) - } - // if the adjust function returns no error on the first attempt, we return the adjusted config. - return adjustedUnicastCfg, nil -} - -// adjust applies the given adjust function to the unicast config of the given origin id. -// It returns an error if the adjustFunc returns an error or if the config does not exist. -// Args: -// - peerIDHash: the hash value of the peer id of the unicast config (i.e., the ID of the unicast config entity). -// - adjustFunc: the function that adjusts the unicast config. -// Returns: -// - error if the adjustFunc returns an error or if the config does not exist (ErrUnicastConfigNotFound). Except the ErrUnicastConfigNotFound, -// any other error should be treated as an irrecoverable error and indicates a bug. -func (d *UnicastConfigCache) adjust(peerIdHash flow.Identifier, adjustFunc unicast.UnicastConfigAdjustFunc) (*unicast.Config, error) { +func (d *UnicastConfigCache) AdjustWithInit(peerID peer.ID, adjustFunc unicast.UnicastConfigAdjustFunc) (*unicast.Config, error) { + entityId := entityIdOf(peerID) var rErr error - adjustedEntity, adjusted := d.peerCache.Adjust(peerIdHash, func(entity flow.Entity) flow.Entity { + // wraps external adjust function to adjust the unicast config. + wrapAdjustFunc := func(entity flow.Entity) flow.Entity { cfgEntity, ok := entity.(UnicastConfigEntity) if !ok { // sanity check @@ -127,14 +80,23 @@ func (d *UnicastConfigCache) adjust(peerIdHash flow.Identifier, adjustFunc unica // Return the adjusted config. cfgEntity.Config = adjustedCfg return cfgEntity - }) + } + initFunc := func() flow.Entity { + return UnicastConfigEntity{ + PeerId: peerID, + Config: d.cfgFactory(), + EntityId: entityId, + } + } + + adjustedEntity, adjusted := d.peerCache.AdjustWithInit(entityId, wrapAdjustFunc, initFunc) if rErr != nil { - return nil, fmt.Errorf("failed to adjust config: %w", rErr) + return nil, fmt.Errorf("adjust operation aborted with an error: %w", rErr) } if !adjusted { - return nil, ErrUnicastConfigNotFound + return nil, fmt.Errorf("adjust operation aborted, entity not found") } return &unicast.Config{ @@ -143,37 +105,27 @@ func (d *UnicastConfigCache) adjust(peerIdHash flow.Identifier, adjustFunc unica }, nil } -// GetOrInit returns the unicast config for the given peer id. If the config does not exist, it creates a new config +// GetWithInit returns the unicast config for the given peer id. If the config does not exist, it creates a new config // using the factory function and stores it in the cache. // Args: // - peerID: the peer id of the unicast config. // Returns: // - *Config, the unicast config for the given peer id. // - error if the factory function returns an error. Any error should be treated as an irrecoverable error and indicates a bug. -func (d *UnicastConfigCache) GetOrInit(peerID peer.ID) (*unicast.Config, error) { - // first we translate the peer id to a flow id (taking - flowPeerId := PeerIdToFlowId(peerID) - cfg, ok := d.get(flowPeerId) - if !ok { - _ = d.peerCache.Add(UnicastConfigEntity{ - PeerId: peerID, - Config: d.cfgFactory(), - }) - cfg, ok = d.get(flowPeerId) - if !ok { - return nil, fmt.Errorf("failed to initialize unicast config for peer %s", peerID) +func (d *UnicastConfigCache) GetWithInit(peerID peer.ID) (*unicast.Config, error) { + // ensuring that the init-and-get operation is atomic. + entityId := entityIdOf(peerID) + initFunc := func() flow.Entity { + return UnicastConfigEntity{ + PeerId: peerID, + Config: d.cfgFactory(), + EntityId: entityId, } } - return cfg, nil -} - -// Get returns the unicast config of the given peer ID. -func (d *UnicastConfigCache) get(peerIDHash flow.Identifier) (*unicast.Config, bool) { - entity, ok := d.peerCache.ByID(peerIDHash) + entity, ok := d.peerCache.GetWithInit(entityId, initFunc) if !ok { - return nil, false + return nil, fmt.Errorf("get or init for unicast config for peer %s failed", peerID) } - cfg, ok := entity.(UnicastConfigEntity) if !ok { // sanity check @@ -185,7 +137,7 @@ func (d *UnicastConfigCache) get(peerIDHash flow.Identifier) (*unicast.Config, b return &unicast.Config{ StreamCreationRetryAttemptBudget: cfg.StreamCreationRetryAttemptBudget, ConsecutiveSuccessfulStream: cfg.ConsecutiveSuccessfulStream, - }, true + }, nil } // Size returns the number of unicast configs in the cache. diff --git a/network/p2p/unicast/cache/unicastConfigCache_test.go b/network/p2p/unicast/cache/unicastConfigCache_test.go index 4d07c9980d2..23d83f1a354 100644 --- a/network/p2p/unicast/cache/unicastConfigCache_test.go +++ b/network/p2p/unicast/cache/unicastConfigCache_test.go @@ -60,9 +60,9 @@ func TestUnicastConfigCache_Adjust_Init(t *testing.T) { peerID1 := unittest.PeerIdFixture(t) peerID2 := unittest.PeerIdFixture(t) - // Initializing the unicast config for peerID1 through GetOrInit. - // unicast config for peerID1 does not exist in the cache, so it must be initialized when using GetOrInit. - cfg, err := cache.GetOrInit(peerID1) + // Initializing the unicast config for peerID1 through GetWithInit. + // unicast config for peerID1 does not exist in the cache, so it must be initialized when using GetWithInit. + cfg, err := cache.GetWithInit(peerID1) require.NoError(t, err) require.NotNil(t, cfg, "unicast config must not be nil") require.Equal(t, unicastConfigFixture(), *cfg, "unicast config must be initialized with the default values") @@ -70,15 +70,15 @@ func TestUnicastConfigCache_Adjust_Init(t *testing.T) { // Initializing and adjusting the unicast config for peerID2 through Adjust. // unicast config for peerID2 does not exist in the cache, so it must be initialized when using Adjust. - cfg, err = cache.Adjust(peerID2, adjustFuncIncrement) + cfg, err = cache.AdjustWithInit(peerID2, adjustFuncIncrement) require.NoError(t, err) // adjusting a non-existing unicast config must not initialize the config. require.Equal(t, uint(2), cache.Size(), "cache size must be 2") require.Equal(t, cfg.StreamCreationRetryAttemptBudget, unicastConfigFixture().StreamCreationRetryAttemptBudget+1, "stream backoff must be 2") - // Retrieving the unicast config of peerID2 through GetOrInit. + // Retrieving the unicast config of peerID2 through GetWithInit. // retrieve the unicast config for peerID2 and assert than it is initialized with the default values; and the adjust function is applied. - cfg, err = cache.GetOrInit(peerID2) + cfg, err = cache.GetWithInit(peerID2) require.NoError(t, err, "unicast config must exist in the cache") require.NotNil(t, cfg, "unicast config must not be nil") // retrieving an existing unicast config must not change the cache size. @@ -88,7 +88,7 @@ func TestUnicastConfigCache_Adjust_Init(t *testing.T) { // Adjusting the unicast config of peerID1 through Adjust. // unicast config for peerID1 already exists in the cache, so it must be adjusted when using Adjust. - cfg, err = cache.Adjust(peerID1, adjustFuncIncrement) + cfg, err = cache.AdjustWithInit(peerID1, adjustFuncIncrement) require.NoError(t, err) // adjusting an existing unicast config must not change the cache size. require.Equal(t, uint(2), cache.Size(), "cache size must be 2") @@ -96,7 +96,7 @@ func TestUnicastConfigCache_Adjust_Init(t *testing.T) { // Recurring adjustment of the unicast config of peerID1 through Adjust. // unicast config for peerID1 already exists in the cache, so it must be adjusted when using Adjust. - cfg, err = cache.Adjust(peerID1, adjustFuncIncrement) + cfg, err = cache.AdjustWithInit(peerID1, adjustFuncIncrement) require.NoError(t, err) // adjusting an existing unicast config must not change the cache size. require.Equal(t, uint(2), cache.Size(), "cache size must be 2") @@ -130,7 +130,7 @@ func TestUnicastConfigCache_Concurrent_Adjust(t *testing.T) { wg.Add(1) go func(peerId peer.ID) { defer wg.Done() - _, err := cache.Adjust(peerId, func(cfg unicast.Config) (unicast.Config, error) { + _, err := cache.AdjustWithInit(peerId, func(cfg unicast.Config) (unicast.Config, error) { cfg.StreamCreationRetryAttemptBudget++ return cfg, nil }) @@ -151,7 +151,7 @@ func TestUnicastConfigCache_Concurrent_Adjust(t *testing.T) { wg.Done() peerID := peerIds[j] - cfg, err := cache.GetOrInit(peerID) + cfg, err := cache.GetWithInit(peerID) require.NoError(t, err) require.Equal(t, uint64(j+1), @@ -182,7 +182,7 @@ func TestConcurrent_Adjust_And_Get_Is_Safe(t *testing.T) { go func() { defer wg.Done() peerId := unittest.PeerIdFixture(t) - updatedConfig, err := cache.Adjust(peerId, func(cfg unicast.Config) (unicast.Config, error) { + updatedConfig, err := cache.AdjustWithInit(peerId, func(cfg unicast.Config) (unicast.Config, error) { cfg.StreamCreationRetryAttemptBudget = 2 // some random adjustment cfg.ConsecutiveSuccessfulStream = 3 // some random adjustment return cfg, nil @@ -199,7 +199,7 @@ func TestConcurrent_Adjust_And_Get_Is_Safe(t *testing.T) { go func() { wg.Done() peerId := unittest.PeerIdFixture(t) - cfg, err := cache.GetOrInit(peerId) + cfg, err := cache.GetWithInit(peerId) require.NoError(t, err) // concurrent retrieval must not fail. require.Equal(t, unicastConfigFixture().StreamCreationRetryAttemptBudget, cfg.StreamCreationRetryAttemptBudget) require.Equal(t, uint64(0), cfg.ConsecutiveSuccessfulStream) @@ -229,7 +229,7 @@ func TestUnicastConfigCache_LRU_Eviction(t *testing.T) { peerIds[i] = peerId } for i := 0; i < int(sizeLimit+1); i++ { - updatedConfig, err := cache.Adjust(peerIds[i], func(cfg unicast.Config) (unicast.Config, error) { + updatedConfig, err := cache.AdjustWithInit(peerIds[i], func(cfg unicast.Config) (unicast.Config, error) { cfg.StreamCreationRetryAttemptBudget = 2 // some random adjustment cfg.ConsecutiveSuccessfulStream = 3 // some random adjustment return cfg, nil @@ -241,7 +241,7 @@ func TestUnicastConfigCache_LRU_Eviction(t *testing.T) { // except the first peer id, all other peer ids should stay intact in the cache. for i := 1; i < int(sizeLimit+1); i++ { - cfg, err := cache.GetOrInit(peerIds[i]) + cfg, err := cache.GetWithInit(peerIds[i]) require.NoError(t, err) require.Equal(t, uint64(2), cfg.StreamCreationRetryAttemptBudget) require.Equal(t, uint64(3), cfg.ConsecutiveSuccessfulStream) @@ -251,7 +251,7 @@ func TestUnicastConfigCache_LRU_Eviction(t *testing.T) { // querying the first peer id should return a fresh unicast config, // since it should be evicted due to LRU eviction, and the initiated with the default values. - cfg, err := cache.GetOrInit(peerIds[0]) + cfg, err := cache.GetWithInit(peerIds[0]) require.NoError(t, err) require.Equal(t, unicastConfigFixture().StreamCreationRetryAttemptBudget, cfg.StreamCreationRetryAttemptBudget) require.Equal(t, uint64(0), cfg.ConsecutiveSuccessfulStream) diff --git a/network/p2p/unicast/cache/unicastConfigEntity.go b/network/p2p/unicast/cache/unicastConfigEntity.go index c1db31523fe..7e84b89deab 100644 --- a/network/p2p/unicast/cache/unicastConfigEntity.go +++ b/network/p2p/unicast/cache/unicastConfigEntity.go @@ -11,18 +11,15 @@ import ( // It implements the flow.Entity interface. type UnicastConfigEntity struct { unicast.Config - PeerId peer.ID // remote peer id; used as the "key" in the unicast config cache. - id flow.Identifier // cache the id for fast lookup (HeroCache). + PeerId peer.ID // remote peer id; used as the "key" in the unicast config cache. + EntityId flow.Identifier // cache the id for fast lookup (HeroCache). } var _ flow.Entity = (*UnicastConfigEntity)(nil) // ID returns the ID of the unicast config entity; it is hash value of the peer id. func (d UnicastConfigEntity) ID() flow.Identifier { - if d.id == flow.ZeroID { - d.id = PeerIdToFlowId(d.PeerId) - } - return d.id + return d.EntityId } // Checksum acts the same as ID. @@ -30,7 +27,13 @@ func (d UnicastConfigEntity) Checksum() flow.Identifier { return d.ID() } -// PeerIdToFlowId converts a peer id to a flow id (hash value of the peer id). -func PeerIdToFlowId(pid peer.ID) flow.Identifier { - return flow.MakeIDFromFingerPrint([]byte(pid)) +// entityIdOf converts a peer ID to a flow ID by taking the hash of the peer ID. +// This is used to convert the peer ID in a notion that is compatible with HeroCache. +// This is not a protocol-level conversion, and is only used internally by the cache, MUST NOT be exposed outside the cache. +// Args: +// - peerId: the peer ID of the peer in the GossipSub protocol. +// Returns: +// - flow.Identifier: the flow ID of the peer. +func entityIdOf(pid peer.ID) flow.Identifier { + return flow.MakeID(pid) } diff --git a/network/p2p/unicast/cache/unicastConfigEntity_test.go b/network/p2p/unicast/cache/unicastConfigEntity_test.go index d7bad635c04..a8994b4375d 100644 --- a/network/p2p/unicast/cache/unicastConfigEntity_test.go +++ b/network/p2p/unicast/cache/unicastConfigEntity_test.go @@ -5,6 +5,7 @@ import ( "github.com/stretchr/testify/require" + "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/network/p2p/unicast" unicastcache "github.com/onflow/flow-go/network/p2p/unicast/cache" "github.com/onflow/flow-go/utils/unittest" @@ -20,12 +21,13 @@ func TestUnicastConfigEntity(t *testing.T) { StreamCreationRetryAttemptBudget: 20, ConsecutiveSuccessfulStream: 30, }, + EntityId: flow.MakeID(peerID), } t.Run( "Test ID and Checksum", func(t *testing.T) { // id and checksum methods must return the same value as expected. - expectedID := unicastcache.PeerIdToFlowId(peerID) + expectedID := flow.MakeID(peerID) require.Equal(t, expectedID, d.ID()) require.Equal(t, expectedID, d.Checksum()) @@ -36,9 +38,11 @@ func TestUnicastConfigEntity(t *testing.T) { ) t.Run("ID is only calculated from peer.ID", func(t *testing.T) { + peerId := unittest.PeerIdFixture(t) d2 := &unicastcache.UnicastConfigEntity{ - PeerId: unittest.PeerIdFixture(t), - Config: d.Config, + PeerId: peerId, + Config: d.Config, + EntityId: flow.MakeID(peerId), } require.NotEqual(t, d.ID(), d2.ID()) // different peer id, different id. @@ -47,6 +51,7 @@ func TestUnicastConfigEntity(t *testing.T) { Config: unicast.Config{ StreamCreationRetryAttemptBudget: 200, }, + EntityId: d.EntityId, } require.Equal(t, d.ID(), d3.ID()) // same peer id, same id, even though the unicast config is different. }) diff --git a/network/p2p/unicast/dialConfigCache.go b/network/p2p/unicast/dialConfigCache.go index fc4c3199b5b..9696e3dcc14 100644 --- a/network/p2p/unicast/dialConfigCache.go +++ b/network/p2p/unicast/dialConfigCache.go @@ -7,14 +7,14 @@ import ( // ConfigCache is a thread-safe cache for dial configs. It is used by the unicast service to store // the dial configs for peers. type ConfigCache interface { - // GetOrInit returns the dial config for the given peer id. If the config does not exist, it creates a new config + // GetWithInit returns the dial config for the given peer id. If the config does not exist, it creates a new config // using the factory function and stores it in the cache. // Args: // - peerID: the peer id of the dial config. // Returns: // - *Config, the dial config for the given peer id. // - error if the factory function returns an error. Any error should be treated as an irrecoverable error and indicates a bug. - GetOrInit(peerID peer.ID) (*Config, error) + GetWithInit(peerID peer.ID) (*Config, error) // Adjust adjusts the dial config for the given peer id using the given adjustFunc. // It returns an error if the adjustFunc returns an error. @@ -23,7 +23,7 @@ type ConfigCache interface { // - adjustFunc: the function that adjusts the dial config. // Returns: // - error if the adjustFunc returns an error. Any error should be treated as an irrecoverable error and indicates a bug. - Adjust(peerID peer.ID, adjustFunc UnicastConfigAdjustFunc) (*Config, error) + AdjustWithInit(peerID peer.ID, adjustFunc UnicastConfigAdjustFunc) (*Config, error) // Size returns the number of dial configs in the cache. Size() uint diff --git a/network/p2p/unicast/manager.go b/network/p2p/unicast/manager.go index 16b4dce703b..e51c3d0fc42 100644 --- a/network/p2p/unicast/manager.go +++ b/network/p2p/unicast/manager.go @@ -18,7 +18,7 @@ import ( "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/module" "github.com/onflow/flow-go/network/p2p" - "github.com/onflow/flow-go/network/p2p/p2plogging" + p2plogging "github.com/onflow/flow-go/network/p2p/logging" "github.com/onflow/flow-go/network/p2p/unicast/protocols" "github.com/onflow/flow-go/network/p2p/unicast/stream" "github.com/onflow/flow-go/utils/logging" @@ -85,21 +85,21 @@ func NewUnicastManager(cfg *ManagerConfig) (*Manager, error) { logger: cfg.Logger.With().Str("module", "unicast-manager").Logger(), dialConfigCache: cfg.UnicastConfigCacheFactory(func() Config { return Config{ - StreamCreationRetryAttemptBudget: cfg.MaxStreamCreationRetryAttemptTimes, + StreamCreationRetryAttemptBudget: cfg.Parameters.MaxStreamCreationRetryAttemptTimes, } }), streamFactory: cfg.StreamFactory, sporkId: cfg.SporkId, metrics: cfg.Metrics, - createStreamBackoffDelay: cfg.CreateStreamBackoffDelay, - streamZeroBackoffResetThreshold: cfg.StreamZeroRetryResetThreshold, - maxStreamCreationAttemptTimes: cfg.MaxStreamCreationRetryAttemptTimes, + createStreamBackoffDelay: cfg.Parameters.CreateStreamBackoffDelay, + streamZeroBackoffResetThreshold: cfg.Parameters.StreamZeroRetryResetThreshold, + maxStreamCreationAttemptTimes: cfg.Parameters.MaxStreamCreationRetryAttemptTimes, } m.logger.Info(). Hex("spork_id", logging.ID(cfg.SporkId)). - Dur("create_stream_backoff_delay", cfg.CreateStreamBackoffDelay). - Uint64("stream_zero_backoff_reset_threshold", cfg.StreamZeroRetryResetThreshold). + Dur("create_stream_backoff_delay", cfg.Parameters.CreateStreamBackoffDelay). + Uint64("stream_zero_backoff_reset_threshold", cfg.Parameters.StreamZeroRetryResetThreshold). Msg("unicast manager created") return m, nil @@ -234,7 +234,7 @@ func (m *Manager) createStream(ctx context.Context, peerID peer.ID, protocol pro return nil, fmt.Errorf("failed to upgrade raw stream: %w", err) } - updatedConfig, err := m.dialConfigCache.Adjust(peerID, func(config Config) (Config, error) { + updatedConfig, err := m.dialConfigCache.AdjustWithInit(peerID, func(config Config) (Config, error) { config.ConsecutiveSuccessfulStream++ // increase consecutive successful stream count. return config, nil }) @@ -353,7 +353,7 @@ func retryFailedError(dialAttempts, maxAttempts uint64, err error) error { // - dial config for the given peer id. // - error if the dial config cannot be retrieved or adjusted; any error is irrecoverable and indicates a fatal error. func (m *Manager) getDialConfig(peerID peer.ID) (*Config, error) { - dialCfg, err := m.dialConfigCache.GetOrInit(peerID) + dialCfg, err := m.dialConfigCache.GetWithInit(peerID) if err != nil { return nil, fmt.Errorf("failed to get or init dial config for peer id: %w", err) } @@ -361,7 +361,7 @@ func (m *Manager) getDialConfig(peerID peer.ID) (*Config, error) { if dialCfg.StreamCreationRetryAttemptBudget == uint64(0) && dialCfg.ConsecutiveSuccessfulStream >= m.streamZeroBackoffResetThreshold { // reset the stream creation backoff budget to the default value if the number of consecutive successful streams reaches the threshold, // as the stream creation is reliable enough to be trusted again. - dialCfg, err = m.dialConfigCache.Adjust(peerID, func(config Config) (Config, error) { + dialCfg, err = m.dialConfigCache.AdjustWithInit(peerID, func(config Config) (Config, error) { config.StreamCreationRetryAttemptBudget = m.maxStreamCreationAttemptTimes m.metrics.OnStreamCreationRetryBudgetUpdated(config.StreamCreationRetryAttemptBudget) m.metrics.OnStreamCreationRetryBudgetResetToDefault() @@ -385,7 +385,7 @@ func (m *Manager) getDialConfig(peerID peer.ID) (*Config, error) { // - connected indicates whether there is a connection to the peer. // - error if the dial config cannot be adjusted; any error is irrecoverable and indicates a fatal error. func (m *Manager) adjustUnsuccessfulStreamAttempt(peerID peer.ID) (*Config, error) { - updatedCfg, err := m.dialConfigCache.Adjust(peerID, func(config Config) (Config, error) { + updatedCfg, err := m.dialConfigCache.AdjustWithInit(peerID, func(config Config) (Config, error) { // consecutive successful stream count is reset to 0 if we fail to create a stream or connection to the peer. config.ConsecutiveSuccessfulStream = 0 diff --git a/network/p2p/unicast/manager_config.go b/network/p2p/unicast/manager_config.go index eac00c76611..ea40c4f97bb 100644 --- a/network/p2p/unicast/manager_config.go +++ b/network/p2p/unicast/manager_config.go @@ -1,12 +1,11 @@ package unicast import ( - "time" - "github.com/rs/zerolog" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/module" + "github.com/onflow/flow-go/network/netconf" "github.com/onflow/flow-go/network/p2p" ) @@ -16,23 +15,7 @@ type ManagerConfig struct { SporkId flow.Identifier `validate:"required"` Metrics module.UnicastManagerMetrics `validate:"required"` - // CreateStreamBackoffDelay is the backoff delay between retrying stream creations to the same peer. - CreateStreamBackoffDelay time.Duration `validate:"gt=0"` - - // StreamZeroRetryResetThreshold is the threshold that determines when to reset the stream creation retry budget to the default value. - // - // For example the default value of 100 means that if the stream creation retry budget is decreased to 0, then it will be reset to default value - // when the number of consecutive successful streams reaches 100. - // - // This is to prevent the retry budget from being reset too frequently, as the retry budget is used to gauge the reliability of the stream creation. - // When the stream creation retry budget is reset to the default value, it means that the stream creation is reliable enough to be trusted again. - // This parameter mandates when the stream creation is reliable enough to be trusted again; i.e., when the number of consecutive successful streams reaches this threshold. - // Note that the counter is reset to 0 when the stream creation fails, so the value of for example 100 means that the stream creation is reliable enough that the recent - // 100 stream creations are all successful. - StreamZeroRetryResetThreshold uint64 `validate:"gt=0"` - - // MaxStreamCreationRetryAttemptTimes is the maximum number of attempts to be made to create a stream to a remote node over a direct unicast (1:1) connection before we give up. - MaxStreamCreationRetryAttemptTimes uint64 `validate:"gt=0"` + Parameters *netconf.UnicastManager `validate:"required"` // UnicastConfigCacheFactory is a factory function to create a new dial config cache. UnicastConfigCacheFactory DialConfigCacheFactory `validate:"required"` diff --git a/network/p2p/unicast/manager_test.go b/network/p2p/unicast/manager_test.go index 32b35cd9dfc..1ab85e16cd8 100644 --- a/network/p2p/unicast/manager_test.go +++ b/network/p2p/unicast/manager_test.go @@ -28,23 +28,21 @@ func unicastManagerFixture(t *testing.T) (*unicast.Manager, *mockp2p.StreamFacto cfg, err := config.DefaultConfig() require.NoError(t, err) - unicastConfigCache := unicastcache.NewUnicastConfigCache(cfg.NetworkConfig.UnicastConfig.ConfigCacheSize, + unicastConfigCache := unicastcache.NewUnicastConfigCache(cfg.NetworkConfig.Unicast.UnicastManager.ConfigCacheSize, unittest.Logger(), metrics.NewNoopCollector(), func() unicast.Config { return unicast.Config{ - StreamCreationRetryAttemptBudget: cfg.NetworkConfig.UnicastConfig.MaxStreamCreationRetryAttemptTimes, + StreamCreationRetryAttemptBudget: cfg.NetworkConfig.Unicast.UnicastManager.MaxStreamCreationRetryAttemptTimes, } }) mgr, err := unicast.NewUnicastManager(&unicast.ManagerConfig{ - Logger: unittest.Logger(), - StreamFactory: streamFactory, - SporkId: unittest.IdentifierFixture(), - CreateStreamBackoffDelay: cfg.NetworkConfig.UnicastConfig.CreateStreamBackoffDelay, - Metrics: metrics.NewNoopCollector(), - StreamZeroRetryResetThreshold: cfg.NetworkConfig.UnicastConfig.StreamZeroRetryResetThreshold, - MaxStreamCreationRetryAttemptTimes: cfg.NetworkConfig.UnicastConfig.MaxStreamCreationRetryAttemptTimes, + Logger: unittest.Logger(), + StreamFactory: streamFactory, + SporkId: unittest.IdentifierFixture(), + Metrics: metrics.NewNoopCollector(), + Parameters: &cfg.NetworkConfig.Unicast.UnicastManager, UnicastConfigCacheFactory: func(func() unicast.Config) unicast.ConfigCache { return unicastConfigCache }, @@ -62,20 +60,18 @@ func TestManagerConfigValidation(t *testing.T) { require.NoError(t, err) validConfig := unicast.ManagerConfig{ - Logger: unittest.Logger(), - StreamFactory: mockp2p.NewStreamFactory(t), - SporkId: unittest.IdentifierFixture(), - CreateStreamBackoffDelay: cfg.NetworkConfig.UnicastConfig.CreateStreamBackoffDelay, - Metrics: metrics.NewNoopCollector(), - StreamZeroRetryResetThreshold: cfg.NetworkConfig.UnicastConfig.StreamZeroRetryResetThreshold, - MaxStreamCreationRetryAttemptTimes: cfg.NetworkConfig.UnicastConfig.MaxStreamCreationRetryAttemptTimes, + Logger: unittest.Logger(), + StreamFactory: mockp2p.NewStreamFactory(t), + SporkId: unittest.IdentifierFixture(), + Parameters: &cfg.NetworkConfig.Unicast.UnicastManager, + Metrics: metrics.NewNoopCollector(), UnicastConfigCacheFactory: func(func() unicast.Config) unicast.ConfigCache { - return unicastcache.NewUnicastConfigCache(cfg.NetworkConfig.UnicastConfig.ConfigCacheSize, + return unicastcache.NewUnicastConfigCache(cfg.NetworkConfig.Unicast.UnicastManager.ConfigCacheSize, unittest.Logger(), metrics.NewNoopCollector(), func() unicast.Config { return unicast.Config{ - StreamCreationRetryAttemptBudget: cfg.NetworkConfig.UnicastConfig.MaxStreamCreationRetryAttemptTimes, + StreamCreationRetryAttemptBudget: cfg.NetworkConfig.Unicast.UnicastManager.MaxStreamCreationRetryAttemptTimes, } }) }, @@ -94,25 +90,9 @@ func TestManagerConfigValidation(t *testing.T) { require.Nil(t, mgr) }) - t.Run("Invalid CreateStreamBackoffDelay", func(t *testing.T) { + t.Run("Nil Parameters", func(t *testing.T) { cfg := validConfig - cfg.CreateStreamBackoffDelay = 0 - mgr, err := unicast.NewUnicastManager(&cfg) - require.Error(t, err) - require.Nil(t, mgr) - }) - - t.Run("Invalid StreamZeroRetryResetThreshold", func(t *testing.T) { - cfg := validConfig - cfg.StreamZeroRetryResetThreshold = 0 - mgr, err := unicast.NewUnicastManager(&cfg) - require.Error(t, err) - require.Nil(t, mgr) - }) - - t.Run("Invalid MaxStreamCreationRetryAttemptTimes", func(t *testing.T) { - cfg := validConfig - cfg.MaxStreamCreationRetryAttemptTimes = 0 + cfg.Parameters = nil mgr, err := unicast.NewUnicastManager(&cfg) require.Error(t, err) require.Nil(t, mgr) @@ -162,12 +142,10 @@ func TestUnicastManager_SuccessfulStream(t *testing.T) { require.NotNil(t, s) // The unicast config must be updated with the backoff budget decremented. - unicastCfg, err := configCache.GetOrInit(peerID) + unicastCfg, err := configCache.GetWithInit(peerID) require.NoError(t, err) - require.Equal(t, - cfg.NetworkConfig.UnicastConfig.MaxStreamCreationRetryAttemptTimes, - unicastCfg.StreamCreationRetryAttemptBudget) // stream backoff budget must remain intact. - require.Equal(t, uint64(1), unicastCfg.ConsecutiveSuccessfulStream) // consecutive successful stream must incremented. + require.Equal(t, cfg.NetworkConfig.Unicast.UnicastManager.MaxStreamCreationRetryAttemptTimes, unicastCfg.StreamCreationRetryAttemptBudget) // stream backoff budget must remain intact. + require.Equal(t, uint64(1), unicastCfg.ConsecutiveSuccessfulStream) // consecutive successful stream must incremented. } // TestUnicastManager_StreamBackoff tests the backoff mechanism of the unicast manager for stream creation. @@ -184,7 +162,7 @@ func TestUnicastManager_StreamBackoff(t *testing.T) { // mocks that it attempts to create a stream some number of times, before giving up. streamFactory.On("NewStream", mock.Anything, peerID, mock.Anything). Return(nil, fmt.Errorf("some error")). - Times(int(cfg.NetworkConfig.UnicastConfig.MaxStreamCreationRetryAttemptTimes + 1)) + Times(int(cfg.NetworkConfig.Unicast.UnicastManager.MaxStreamCreationRetryAttemptTimes + 1)) ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -194,10 +172,10 @@ func TestUnicastManager_StreamBackoff(t *testing.T) { require.Nil(t, s) // The unicast config must be updated with the backoff budget decremented. - unicastCfg, err := configCache.GetOrInit(peerID) + unicastCfg, err := configCache.GetWithInit(peerID) require.NoError(t, err) // stream backoff budget must be decremented by 1 since all budget is used up. - require.Equal(t, cfg.NetworkConfig.UnicastConfig.MaxStreamCreationRetryAttemptTimes-1, unicastCfg.StreamCreationRetryAttemptBudget) + require.Equal(t, cfg.NetworkConfig.Unicast.UnicastManager.MaxStreamCreationRetryAttemptTimes-1, unicastCfg.StreamCreationRetryAttemptBudget) // consecutive successful stream must be reset to zero, since the stream creation failed. require.Equal(t, uint64(0), unicastCfg.ConsecutiveSuccessfulStream) } @@ -215,7 +193,7 @@ func TestUnicastManager_StreamFactory_StreamBackoff(t *testing.T) { // mocks that it attempts to create a stream some number of times, before giving up. streamFactory.On("NewStream", mock.Anything, peerID, mock.Anything). Return(nil, fmt.Errorf("some error")). - Times(int(cfg.NetworkConfig.UnicastConfig.MaxStreamCreationRetryAttemptTimes + 1)) + Times(int(cfg.NetworkConfig.Unicast.UnicastManager.MaxStreamCreationRetryAttemptTimes + 1)) ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -224,10 +202,10 @@ func TestUnicastManager_StreamFactory_StreamBackoff(t *testing.T) { require.Nil(t, s) // The unicast config must be updated with the stream backoff budget decremented. - unicastCfg, err := unicastConfigCache.GetOrInit(peerID) + unicastCfg, err := unicastConfigCache.GetWithInit(peerID) require.NoError(t, err) // stream backoff budget must be decremented by 1. - require.Equal(t, cfg.NetworkConfig.UnicastConfig.MaxStreamCreationRetryAttemptTimes-1, unicastCfg.StreamCreationRetryAttemptBudget) + require.Equal(t, cfg.NetworkConfig.Unicast.UnicastManager.MaxStreamCreationRetryAttemptTimes-1, unicastCfg.StreamCreationRetryAttemptBudget) // consecutive successful stream must be zero as we have not created a successful stream yet. require.Equal(t, uint64(0), unicastCfg.ConsecutiveSuccessfulStream) } @@ -256,10 +234,10 @@ func TestUnicastManager_Stream_ConsecutiveStreamCreation_Increment(t *testing.T) require.NotNil(t, s) // The unicast config must be updated with the stream backoff budget decremented. - unicastCfg, err := unicastConfigCache.GetOrInit(peerID) + unicastCfg, err := unicastConfigCache.GetWithInit(peerID) require.NoError(t, err) // stream backoff budget must be intact (all stream creation attempts are successful). - require.Equal(t, cfg.NetworkConfig.UnicastConfig.MaxStreamCreationRetryAttemptTimes, unicastCfg.StreamCreationRetryAttemptBudget) + require.Equal(t, cfg.NetworkConfig.Unicast.UnicastManager.MaxStreamCreationRetryAttemptTimes, unicastCfg.StreamCreationRetryAttemptBudget) // consecutive successful stream must be incremented. require.Equal(t, uint64(i+1), unicastCfg.ConsecutiveSuccessfulStream) } @@ -276,7 +254,7 @@ func TestUnicastManager_Stream_ConsecutiveStreamCreation_Reset(t *testing.T) { Return(nil, fmt.Errorf("some error")). Once() - adjustedUnicastConfig, err := unicastConfigCache.Adjust(peerID, func(unicastConfig unicast.Config) (unicast.Config, error) { + adjustedUnicastConfig, err := unicastConfigCache.AdjustWithInit(peerID, func(unicastConfig unicast.Config) (unicast.Config, error) { // sets the consecutive successful stream to 5 meaning that the last 5 stream creation attempts were successful. unicastConfig.ConsecutiveSuccessfulStream = 5 // sets the stream back budget to 0 meaning that the stream backoff budget is exhausted. @@ -295,7 +273,7 @@ func TestUnicastManager_Stream_ConsecutiveStreamCreation_Reset(t *testing.T) { require.Nil(t, s) // The unicast config must be updated with the stream backoff budget decremented. - unicastCfg, err := unicastConfigCache.GetOrInit(peerID) + unicastCfg, err := unicastConfigCache.GetWithInit(peerID) require.NoError(t, err) // stream backoff budget must be intact (we can't decrement it below 0). @@ -342,11 +320,11 @@ func TestUnicastManager_StreamFactory_ErrNoAddresses(t *testing.T) { require.Error(t, err) require.Nil(t, s) - unicastCfg, err := unicastConfigCache.GetOrInit(peerID) + unicastCfg, err := unicastConfigCache.GetWithInit(peerID) require.NoError(t, err) // stream backoff budget must be reduced by 1 due to failed stream creation. - require.Equal(t, cfg.NetworkConfig.UnicastConfig.MaxStreamCreationRetryAttemptTimes-1, unicastCfg.StreamCreationRetryAttemptBudget) + require.Equal(t, cfg.NetworkConfig.Unicast.UnicastManager.MaxStreamCreationRetryAttemptTimes-1, unicastCfg.StreamCreationRetryAttemptBudget) // consecutive successful stream must be set to zero. require.Equal(t, uint64(0), unicastCfg.ConsecutiveSuccessfulStream) } @@ -371,10 +349,10 @@ func TestUnicastManager_Stream_ErrSecurityProtocolNegotiationFailed(t *testing.T require.Error(t, err) require.Nil(t, s) - unicastCfg, err := unicastConfigCache.GetOrInit(peerID) + unicastCfg, err := unicastConfigCache.GetWithInit(peerID) require.NoError(t, err) // stream retry budget must be decremented by 1 (since we didn't have a successful stream creation, the budget is decremented). - require.Equal(t, cfg.NetworkConfig.UnicastConfig.MaxStreamCreationRetryAttemptTimes-1, unicastCfg.StreamCreationRetryAttemptBudget) + require.Equal(t, cfg.NetworkConfig.Unicast.UnicastManager.MaxStreamCreationRetryAttemptTimes-1, unicastCfg.StreamCreationRetryAttemptBudget) // consecutive successful stream must be set to zero. require.Equal(t, uint64(0), unicastCfg.ConsecutiveSuccessfulStream) } @@ -398,10 +376,10 @@ func TestUnicastManager_StreamFactory_ErrGaterDisallowedConnection(t *testing.T) require.Error(t, err) require.Nil(t, s) - unicastCfg, err := unicastConfigCache.GetOrInit(peerID) + unicastCfg, err := unicastConfigCache.GetWithInit(peerID) require.NoError(t, err) // stream backoff budget must be reduced by 1 due to failed stream creation. - require.Equal(t, cfg.NetworkConfig.UnicastConfig.MaxStreamCreationRetryAttemptTimes-1, unicastCfg.StreamCreationRetryAttemptBudget) + require.Equal(t, cfg.NetworkConfig.Unicast.UnicastManager.MaxStreamCreationRetryAttemptTimes-1, unicastCfg.StreamCreationRetryAttemptBudget) // consecutive successful stream must be set to zero. require.Equal(t, uint64(0), unicastCfg.ConsecutiveSuccessfulStream) } @@ -420,7 +398,7 @@ func TestUnicastManager_Stream_BackoffBudgetDecremented(t *testing.T) { // Let's consider x = unicastmodel.MaxStreamCreationRetryAttemptTimes + 1. Then the test tries x times CreateStream. With dynamic backoffs, // the first CreateStream call will try to NewStream x times, the second CreateStream call will try to NewStream x-1 times, // and so on. So the total number of Connect calls is x + (x-1) + (x-2) + ... + 1 = x(x+1)/2. - maxStreamRetryBudget := cfg.NetworkConfig.UnicastConfig.MaxStreamCreationRetryAttemptTimes + maxStreamRetryBudget := cfg.NetworkConfig.Unicast.UnicastManager.MaxStreamCreationRetryAttemptTimes maxStreamAttempt := maxStreamRetryBudget + 1 // 1 attempt + retry times totalAttempts := maxStreamAttempt * (maxStreamAttempt + 1) / 2 @@ -435,7 +413,7 @@ func TestUnicastManager_Stream_BackoffBudgetDecremented(t *testing.T) { require.Error(t, err) require.Nil(t, s) - unicastCfg, err := unicastConfigCache.GetOrInit(peerID) + unicastCfg, err := unicastConfigCache.GetWithInit(peerID) require.NoError(t, err) if i == int(maxStreamRetryBudget)-1 { @@ -445,7 +423,7 @@ func TestUnicastManager_Stream_BackoffBudgetDecremented(t *testing.T) { } } // At this time the backoff budget for connection must be 0. - unicastCfg, err := unicastConfigCache.GetOrInit(peerID) + unicastCfg, err := unicastConfigCache.GetWithInit(peerID) require.NoError(t, err) require.Equal(t, uint64(0), unicastCfg.StreamCreationRetryAttemptBudget) @@ -454,7 +432,7 @@ func TestUnicastManager_Stream_BackoffBudgetDecremented(t *testing.T) { require.Error(t, err) require.Nil(t, s) - unicastCfg, err = unicastConfigCache.GetOrInit(peerID) + unicastCfg, err = unicastConfigCache.GetWithInit(peerID) require.NoError(t, err) require.Equal(t, uint64(0), unicastCfg.StreamCreationRetryAttemptBudget) } @@ -472,14 +450,14 @@ func TestUnicastManager_Stream_BackoffBudgetResetToDefault(t *testing.T) { streamFactory.On("NewStream", mock.Anything, peerID, mock.Anything).Return(&p2ptest.MockStream{}, nil).Once() // update the unicast config of the peer to have a zero stream backoff budget but a consecutive successful stream counter above the reset threshold. - adjustedCfg, err := unicastConfigCache.Adjust(peerID, func(unicastConfig unicast.Config) (unicast.Config, error) { + adjustedCfg, err := unicastConfigCache.AdjustWithInit(peerID, func(unicastConfig unicast.Config) (unicast.Config, error) { unicastConfig.StreamCreationRetryAttemptBudget = 0 - unicastConfig.ConsecutiveSuccessfulStream = cfg.NetworkConfig.UnicastConfig.StreamZeroRetryResetThreshold + 1 + unicastConfig.ConsecutiveSuccessfulStream = cfg.NetworkConfig.Unicast.UnicastManager.StreamZeroRetryResetThreshold + 1 return unicastConfig, nil }) require.NoError(t, err) require.Equal(t, uint64(0), adjustedCfg.StreamCreationRetryAttemptBudget) - require.Equal(t, cfg.NetworkConfig.UnicastConfig.StreamZeroRetryResetThreshold+1, adjustedCfg.ConsecutiveSuccessfulStream) + require.Equal(t, cfg.NetworkConfig.Unicast.UnicastManager.StreamZeroRetryResetThreshold+1, adjustedCfg.ConsecutiveSuccessfulStream) ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -488,12 +466,12 @@ func TestUnicastManager_Stream_BackoffBudgetResetToDefault(t *testing.T) { require.NoError(t, err) require.NotNil(t, s) - unicastCfg, err := unicastConfigCache.GetOrInit(peerID) + unicastCfg, err := unicastConfigCache.GetWithInit(peerID) require.NoError(t, err) // stream backoff budget must reset to default. - require.Equal(t, cfg.NetworkConfig.UnicastConfig.MaxStreamCreationRetryAttemptTimes, unicastCfg.StreamCreationRetryAttemptBudget) + require.Equal(t, cfg.NetworkConfig.Unicast.UnicastManager.MaxStreamCreationRetryAttemptTimes, unicastCfg.StreamCreationRetryAttemptBudget) // consecutive successful stream must increment by 1 (it was threshold + 1 before). - require.Equal(t, cfg.NetworkConfig.UnicastConfig.StreamZeroRetryResetThreshold+1+1, unicastCfg.ConsecutiveSuccessfulStream) + require.Equal(t, cfg.NetworkConfig.Unicast.UnicastManager.StreamZeroRetryResetThreshold+1+1, unicastCfg.ConsecutiveSuccessfulStream) } // TestUnicastManager_Stream_NoBackoff_When_Budget_Is_Zero tests that when the stream backoff budget is zero and the consecutive successful stream counter is not above the @@ -505,7 +483,7 @@ func TestUnicastManager_Stream_NoBackoff_When_Budget_Is_Zero(t *testing.T) { // mocks that it attempts to create a stream once and fails, and does not retry. streamFactory.On("NewStream", mock.Anything, peerID, mock.Anything).Return(nil, fmt.Errorf("some error")).Once() - adjustedCfg, err := unicastConfigCache.Adjust(peerID, func(unicastConfig unicast.Config) (unicast.Config, error) { + adjustedCfg, err := unicastConfigCache.AdjustWithInit(peerID, func(unicastConfig unicast.Config) (unicast.Config, error) { unicastConfig.ConsecutiveSuccessfulStream = 2 // set the consecutive successful stream to 2, which is below the reset threshold. unicastConfig.StreamCreationRetryAttemptBudget = 0 // set the stream backoff budget to 0, meaning that the stream backoff budget is exhausted. return unicastConfig, nil @@ -521,7 +499,7 @@ func TestUnicastManager_Stream_NoBackoff_When_Budget_Is_Zero(t *testing.T) { require.Error(t, err) require.Nil(t, s) - unicastCfg, err := unicastConfigCache.GetOrInit(peerID) + unicastCfg, err := unicastConfigCache.GetWithInit(peerID) require.NoError(t, err) require.Equal(t, uint64(0), unicastCfg.StreamCreationRetryAttemptBudget) // stream backoff budget must remain zero. require.Equal(t, uint64(0), unicastCfg.ConsecutiveSuccessfulStream) // consecutive successful stream must be set to zero. diff --git a/network/p2p/unicast/stream/errors.go b/network/p2p/unicast/stream/errors.go index 725b5e45247..844617b27e3 100644 --- a/network/p2p/unicast/stream/errors.go +++ b/network/p2p/unicast/stream/errors.go @@ -7,7 +7,7 @@ import ( "github.com/libp2p/go-libp2p/core/peer" "github.com/libp2p/go-libp2p/core/protocol" - "github.com/onflow/flow-go/network/p2p/p2plogging" + p2plogging "github.com/onflow/flow-go/network/p2p/logging" ) // ErrSecurityProtocolNegotiationFailed indicates security protocol negotiation failed during the stream factory connect attempt. diff --git a/network/p2p/utils/p2putils.go b/network/p2p/utils/p2putils.go index 552aa5c99a6..1779cdc34f9 100644 --- a/network/p2p/utils/p2putils.go +++ b/network/p2p/utils/p2putils.go @@ -6,8 +6,8 @@ import ( "github.com/libp2p/go-libp2p/core/peer" "github.com/multiformats/go-multiaddr" + "github.com/onflow/crypto/hash" - "github.com/onflow/flow-go/crypto/hash" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/network/channels" "github.com/onflow/flow-go/network/internal/p2putils" diff --git a/network/stub/hash.go b/network/stub/hash.go index fd13f0906f8..4730e4b096d 100644 --- a/network/stub/hash.go +++ b/network/stub/hash.go @@ -4,7 +4,8 @@ import ( "encoding/hex" "fmt" - "github.com/onflow/flow-go/crypto/hash" + "github.com/onflow/crypto/hash" + "github.com/onflow/flow-go/model/encoding/json" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/network/channels" diff --git a/network/test/cohort1/meshengine_test.go b/network/test/cohort1/meshengine_test.go index 02870ea57e0..9e25cd84eae 100644 --- a/network/test/cohort1/meshengine_test.go +++ b/network/test/cohort1/meshengine_test.go @@ -29,9 +29,9 @@ import ( "github.com/onflow/flow-go/network/channels" "github.com/onflow/flow-go/network/internal/testutils" "github.com/onflow/flow-go/network/p2p" - "github.com/onflow/flow-go/network/p2p/p2pnet" - "github.com/onflow/flow-go/network/p2p/p2pnode" + p2pnode "github.com/onflow/flow-go/network/p2p/node" p2ptest "github.com/onflow/flow-go/network/p2p/test" + "github.com/onflow/flow-go/network/underlay" "github.com/onflow/flow-go/utils/unittest" ) @@ -39,11 +39,11 @@ import ( // of engines over a complete graph type MeshEngineTestSuite struct { suite.Suite - testutils.ConduitWrapper // used as a wrapper around conduit methods - networks []*p2pnet.Network // used to keep track of the networks - libp2pNodes []p2p.LibP2PNode // used to keep track of the libp2p nodes - ids flow.IdentityList // used to keep track of the identifiers associated with networks - obs chan string // used to keep track of Protect events tagged by pubsub messages + testutils.ConduitWrapper // used as a wrapper around conduit methods + networks []*underlay.Network // used to keep track of the networks + libp2pNodes []p2p.LibP2PNode // used to keep track of the libp2p nodes + ids flow.IdentityList // used to keep track of the identifiers associated with networks + obs chan string // used to keep track of Protect events tagged by pubsub messages cancel context.CancelFunc } @@ -96,7 +96,7 @@ func (suite *MeshEngineTestSuite) SetupTest() { connManager, err := testutils.NewTagWatchingConnManager( unittest.Logger(), metrics.NewNoopCollector(), - &defaultFlowConfig.NetworkConfig.ConnectionManagerConfig) + &defaultFlowConfig.NetworkConfig.ConnectionManager) require.NoError(suite.T(), err) opts = append(opts, p2ptest.WithConnectionManager(connManager)) @@ -175,7 +175,7 @@ func (suite *MeshEngineTestSuite) TestTargetedValidators_Publish() { // TestMaxMessageSize_Unicast evaluates the messageSizeScenario scenario using // the Unicast method of conduits. func (suite *MeshEngineTestSuite) TestMaxMessageSize_Unicast() { - suite.messageSizeScenario(suite.Unicast, p2pnet.DefaultMaxUnicastMsgSize) + suite.messageSizeScenario(suite.Unicast, underlay.DefaultMaxUnicastMsgSize) } // TestMaxMessageSize_Multicast evaluates the messageSizeScenario scenario using diff --git a/network/test/cohort1/network_test.go b/network/test/cohort1/network_test.go index 8f8716308ba..bffd3ac52b7 100644 --- a/network/test/cohort1/network_test.go +++ b/network/test/cohort1/network_test.go @@ -35,11 +35,11 @@ import ( "github.com/onflow/flow-go/network/message" "github.com/onflow/flow-go/network/mocknetwork" "github.com/onflow/flow-go/network/p2p" - "github.com/onflow/flow-go/network/p2p/p2pnet" - "github.com/onflow/flow-go/network/p2p/p2pnode" + p2pnode "github.com/onflow/flow-go/network/p2p/node" p2ptest "github.com/onflow/flow-go/network/p2p/test" "github.com/onflow/flow-go/network/p2p/unicast/ratelimit" "github.com/onflow/flow-go/network/p2p/utils/ratelimiter" + "github.com/onflow/flow-go/network/underlay" "github.com/onflow/flow-go/utils/unittest" ) @@ -89,7 +89,7 @@ type NetworkTestSuite struct { sync.RWMutex size int // used to determine number of networks under test libP2PNodes []p2p.LibP2PNode - networks []*p2pnet.Network + networks []*underlay.Network obs chan string // used to keep track of Protect events tagged by pubsub messages ids []*flow.Identity metrics *metrics.NoopCollector // no-op performance monitoring simulation @@ -128,6 +128,7 @@ func (suite *NetworkTestSuite) SetupTest() { idProvider := unittest.NewUpdatableIDProvider(flow.IdentityList{}) defaultFlowConfig, err := config.DefaultConfig() require.NoError(suite.T(), err) + defaultFlowConfig.NetworkConfig.Unicast.UnicastManager.CreateStreamBackoffDelay = 1 * time.Millisecond opts := []p2ptest.NodeFixtureParameterOption{p2ptest.WithUnicastHandlerFunc(nil)} @@ -135,13 +136,13 @@ func (suite *NetworkTestSuite) SetupTest() { connManager, err := testutils.NewTagWatchingConnManager( unittest.Logger(), metrics.NewNoopCollector(), - &defaultFlowConfig.NetworkConfig.ConnectionManagerConfig) + &defaultFlowConfig.NetworkConfig.ConnectionManager) require.NoError(suite.T(), err) opts = append(opts, p2ptest.WithConnectionManager(connManager), p2ptest.WithRole(flow.RoleExecution), - p2ptest.WithCreateStreamRetryDelay(1*time.Millisecond)) // to suppress exponential backoff + p2ptest.OverrideFlowConfig(defaultFlowConfig)) // to suppress exponential backoff node, nodeId := p2ptest.NodeFixture(suite.T(), suite.sporkId, suite.T().Name(), @@ -204,7 +205,7 @@ func (suite *NetworkTestSuite) TestUpdateNodeAddresses() { idProvider, suite.sporkId, libP2PNodes[0]) - newNet, err := p2pnet.NewNetwork(networkCfg) + newNet, err := underlay.NewNetwork(networkCfg) require.NoError(suite.T(), err) require.Len(suite.T(), ids, 1) newId := ids[0] @@ -279,12 +280,16 @@ func (suite *NetworkTestSuite) TestUnicastRateLimit_Messages() { opts := []ratelimit.RateLimitersOption{ratelimit.WithMessageRateLimiter(messageRateLimiter), ratelimit.WithNotifier(distributor), ratelimit.WithDisabledRateLimiting(false)} rateLimiters := ratelimit.NewRateLimiters(opts...) + defaultFlowConfig, err := config.DefaultConfig() + require.NoError(suite.T(), err) + defaultFlowConfig.NetworkConfig.Unicast.UnicastManager.CreateStreamBackoffDelay = 1 * time.Millisecond + idProvider := unittest.NewUpdatableIDProvider(suite.ids) ids, libP2PNodes := testutils.LibP2PNodeForNetworkFixture(suite.T(), suite.sporkId, 1, p2ptest.WithUnicastRateLimitDistributor(distributor), - p2ptest.WithCreateStreamRetryDelay(1*time.Millisecond), // to suppress exponential backoff + p2ptest.OverrideFlowConfig(defaultFlowConfig), // to suppress exponential backoff p2ptest.WithConnectionGater(p2ptest.NewConnectionGater(idProvider, func(pid peer.ID) error { if messageRateLimiter.IsRateLimited(pid) { return fmt.Errorf("rate-limited peer") @@ -299,10 +304,10 @@ func (suite *NetworkTestSuite) TestUnicastRateLimit_Messages() { idProvider, suite.sporkId, libP2PNodes[0]) - newNet, err := p2pnet.NewNetwork( + newNet, err := underlay.NewNetwork( netCfg, - p2pnet.WithUnicastRateLimiters(rateLimiters), - p2pnet.WithPeerManagerFilters(testutils.IsRateLimitedPeerFilter(messageRateLimiter))) + underlay.WithUnicastRateLimiters(rateLimiters), + underlay.WithPeerManagerFilters(testutils.IsRateLimitedPeerFilter(messageRateLimiter))) require.NoError(suite.T(), err) require.Len(suite.T(), ids, 1) @@ -417,13 +422,17 @@ func (suite *NetworkTestSuite) TestUnicastRateLimit_Bandwidth() { opts := []ratelimit.RateLimitersOption{ratelimit.WithBandwidthRateLimiter(bandwidthRateLimiter), ratelimit.WithNotifier(distributor), ratelimit.WithDisabledRateLimiting(false)} rateLimiters := ratelimit.NewRateLimiters(opts...) + defaultFlowConfig, err := config.DefaultConfig() + require.NoError(suite.T(), err) + defaultFlowConfig.NetworkConfig.Unicast.UnicastManager.CreateStreamBackoffDelay = 1 * time.Millisecond + idProvider := unittest.NewUpdatableIDProvider(suite.ids) // create a new staked identity ids, libP2PNodes := testutils.LibP2PNodeForNetworkFixture(suite.T(), suite.sporkId, 1, p2ptest.WithUnicastRateLimitDistributor(distributor), - p2ptest.WithCreateStreamRetryDelay(1*time.Millisecond), // to suppress exponential backoff + p2ptest.OverrideFlowConfig(defaultFlowConfig), // to suppress exponential backoff p2ptest.WithConnectionGater(p2ptest.NewConnectionGater(idProvider, func(pid peer.ID) error { // create connection gater, connection gater will refuse connections from rate limited nodes if bandwidthRateLimiter.IsRateLimited(pid) { @@ -441,10 +450,10 @@ func (suite *NetworkTestSuite) TestUnicastRateLimit_Bandwidth() { idProvider, suite.sporkId, libP2PNodes[0]) - newNet, err := p2pnet.NewNetwork( + newNet, err := underlay.NewNetwork( netCfg, - p2pnet.WithUnicastRateLimiters(rateLimiters), - p2pnet.WithPeerManagerFilters(testutils.IsRateLimitedPeerFilter(bandwidthRateLimiter))) + underlay.WithUnicastRateLimiters(rateLimiters), + underlay.WithPeerManagerFilters(testutils.IsRateLimitedPeerFilter(bandwidthRateLimiter))) require.NoError(suite.T(), err) require.Len(suite.T(), ids, 1) newId := ids[0] @@ -752,7 +761,7 @@ func (suite *NetworkTestSuite) TestMaxMessageSize_Unicast() { // so the generated payload is 1000 bytes below the maximum unicast message size. // We hence add up 1000 bytes to the input of network payload fixture to make // sure that payload is beyond the permissible size. - payload := testutils.NetworkPayloadFixture(suite.T(), uint(p2pnet.DefaultMaxUnicastMsgSize)+1000) + payload := testutils.NetworkPayloadFixture(suite.T(), uint(underlay.DefaultMaxUnicastMsgSize)+1000) event := &libp2pmessage.TestMessage{ Text: string(payload), } @@ -771,7 +780,7 @@ func (suite *NetworkTestSuite) TestLargeMessageSize_SendDirect() { targetId := suite.ids[targetIndex].NodeID // creates a network payload with a size greater than the default max size using a known large message type - targetSize := uint64(p2pnet.DefaultMaxUnicastMsgSize) + 1000 + targetSize := uint64(underlay.DefaultMaxUnicastMsgSize) + 1000 event := unittest.ChunkDataResponseMsgFixture(unittest.IdentifierFixture(), unittest.WithApproximateSize(targetSize)) // expect one message to be received by the target @@ -909,7 +918,7 @@ func TestChunkDataPackMaxMessageSize(t *testing.T) { require.NoError(t, err) // get the max message size for the message - size, err := p2pnet.UnicastMaxMsgSizeByCode(msg.Proto().Payload) + size, err := underlay.UnicastMaxMsgSizeByCode(msg.Proto().Payload) require.NoError(t, err) - require.Equal(t, p2pnet.LargeMsgMaxUnicastMsgSize, size) + require.Equal(t, underlay.LargeMsgMaxUnicastMsgSize, size) } diff --git a/network/test/cohort2/blob_service_test.go b/network/test/cohort2/blob_service_test.go index b3f3d6921ae..c6ab06e399d 100644 --- a/network/test/cohort2/blob_service_test.go +++ b/network/test/cohort2/blob_service_test.go @@ -14,11 +14,11 @@ import ( "github.com/stretchr/testify/suite" "go.uber.org/atomic" + p2pbuilderconfig "github.com/onflow/flow-go/network/p2p/builder/config" "github.com/onflow/flow-go/network/p2p/connection" "github.com/onflow/flow-go/network/p2p/dht" - p2pconfig "github.com/onflow/flow-go/network/p2p/p2pbuilder/config" - "github.com/onflow/flow-go/network/p2p/p2pnet" p2ptest "github.com/onflow/flow-go/network/p2p/test" + "github.com/onflow/flow-go/network/underlay" "github.com/onflow/flow-go/utils/unittest" "github.com/onflow/flow-go/model/flow" @@ -51,7 +51,7 @@ type BlobServiceTestSuite struct { suite.Suite cancel context.CancelFunc - networks []*p2pnet.Network + networks []*underlay.Network blobServices []network.BlobService datastores []datastore.Batching blobCids []cid.Cid @@ -90,7 +90,7 @@ func (suite *BlobServiceTestSuite) SetupTest() { p2ptest.WithRole(flow.RoleExecution), p2ptest.WithDHTOptions(dht.AsServer()), p2ptest.WithPeerManagerEnabled( - &p2pconfig.PeerManagerConfig{ + &p2pbuilderconfig.PeerManagerConfig{ UpdateInterval: 1 * time.Second, ConnectionPruning: true, ConnectorFactory: connection.DefaultLibp2pBackoffConnectorFactory(), diff --git a/network/test/cohort2/echoengine_test.go b/network/test/cohort2/echoengine_test.go index 81d807be180..6ae724bf6a8 100644 --- a/network/test/cohort2/echoengine_test.go +++ b/network/test/cohort2/echoengine_test.go @@ -20,7 +20,7 @@ import ( "github.com/onflow/flow-go/network/channels" "github.com/onflow/flow-go/network/internal/testutils" "github.com/onflow/flow-go/network/p2p" - "github.com/onflow/flow-go/network/p2p/p2pnet" + "github.com/onflow/flow-go/network/underlay" "github.com/onflow/flow-go/utils/unittest" ) @@ -29,10 +29,10 @@ import ( // single message from one engine to the other one through different scenarios. type EchoEngineTestSuite struct { suite.Suite - testutils.ConduitWrapper // used as a wrapper around conduit methods - networks []*p2pnet.Network // used to keep track of the networks - libp2pNodes []p2p.LibP2PNode // used to keep track of the libp2p nodes - ids flow.IdentityList // used to keep track of the identifiers associated with networks + testutils.ConduitWrapper // used as a wrapper around conduit methods + networks []*underlay.Network // used to keep track of the networks + libp2pNodes []p2p.LibP2PNode // used to keep track of the libp2p nodes + ids flow.IdentityList // used to keep track of the identifiers associated with networks cancel context.CancelFunc } diff --git a/network/test/cohort2/epochtransition_test.go b/network/test/cohort2/epochtransition_test.go index 22972e44557..6b33664cf65 100644 --- a/network/test/cohort2/epochtransition_test.go +++ b/network/test/cohort2/epochtransition_test.go @@ -26,7 +26,7 @@ import ( "github.com/onflow/flow-go/network/channels" "github.com/onflow/flow-go/network/internal/testutils" "github.com/onflow/flow-go/network/p2p" - "github.com/onflow/flow-go/network/p2p/p2pnet" + "github.com/onflow/flow-go/network/underlay" mockprotocol "github.com/onflow/flow-go/state/protocol/mock" "github.com/onflow/flow-go/utils/unittest" ) @@ -52,7 +52,7 @@ type MutableIdentityTableSuite struct { type testNode struct { id *flow.Identity libp2pNode p2p.LibP2PNode - network *p2pnet.Network + network *underlay.Network engine *testutils.MeshEngine } diff --git a/network/test/cohort2/unicast_authorization_test.go b/network/test/cohort2/unicast_authorization_test.go index faffb5e2307..26d5da48849 100644 --- a/network/test/cohort2/unicast_authorization_test.go +++ b/network/test/cohort2/unicast_authorization_test.go @@ -23,8 +23,8 @@ import ( "github.com/onflow/flow-go/network/message" "github.com/onflow/flow-go/network/mocknetwork" "github.com/onflow/flow-go/network/p2p" - "github.com/onflow/flow-go/network/p2p/p2plogging" - "github.com/onflow/flow-go/network/p2p/p2pnet" + p2plogging "github.com/onflow/flow-go/network/p2p/logging" + "github.com/onflow/flow-go/network/underlay" "github.com/onflow/flow-go/network/validator" "github.com/onflow/flow-go/utils/unittest" ) @@ -54,7 +54,7 @@ type UnicastAuthorizationTestSuite struct { cancel context.CancelFunc sporkId flow.Identifier // waitCh is the channel used to wait for the networks to perform authorization and invoke the slashing - //violation's consumer before making mock assertions and cleaning up resources + // violation's consumer before making mock assertions and cleaning up resources waitCh chan struct{} } @@ -84,8 +84,8 @@ func (u *UnicastAuthorizationTestSuite) setupNetworks(slashingViolationsConsumer u.sporkId, ids, libP2PNodes, - p2pnet.WithCodec(u.codec), - p2pnet.WithSlashingViolationConsumerFactory(func(_ network.ConduitAdapter) network.ViolationsConsumer { + underlay.WithCodec(u.codec), + underlay.WithSlashingViolationConsumerFactory(func(_ network.ConduitAdapter) network.ViolationsConsumer { return slashingViolationsConsumer })) require.Len(u.T(), ids, 2) @@ -168,7 +168,7 @@ func (u *UnicastAuthorizationTestSuite) TestUnicastAuthorization_UnstakedPeer() func (u *UnicastAuthorizationTestSuite) TestUnicastAuthorization_EjectedPeer() { slashingViolationsConsumer := mocknetwork.NewViolationsConsumer(u.T()) u.setupNetworks(slashingViolationsConsumer) - //NOTE: setup ejected identity + // NOTE: setup ejected identity u.senderID.Ejected = true // overriding the identity provide of the receiver node to return the ejected identity so that the @@ -446,7 +446,7 @@ func (u *UnicastAuthorizationTestSuite) TestUnicastAuthorization_ReceiverHasNoSu MsgType: "*message.TestMessage", Channel: channels.TestNetworkChannel, Protocol: message.ProtocolTypeUnicast, - Err: p2pnet.ErrUnicastMsgWithoutSub, + Err: underlay.ErrUnicastMsgWithoutSub, } slashingViolationsConsumer.On("OnUnauthorizedUnicastOnChannel", expectedViolation). diff --git a/network/p2p/p2pnet/internal/readSubscription.go b/network/underlay/internal/readSubscription.go similarity index 100% rename from network/p2p/p2pnet/internal/readSubscription.go rename to network/underlay/internal/readSubscription.go diff --git a/network/p2p/p2pnet/network.go b/network/underlay/network.go similarity index 99% rename from network/p2p/p2pnet/network.go rename to network/underlay/network.go index 3b280ecaeae..d7a1b3f277b 100644 --- a/network/p2p/p2pnet/network.go +++ b/network/underlay/network.go @@ -1,4 +1,4 @@ -package p2pnet +package underlay import ( "bufio" @@ -31,14 +31,14 @@ import ( "github.com/onflow/flow-go/network/message" "github.com/onflow/flow-go/network/p2p" "github.com/onflow/flow-go/network/p2p/blob" - "github.com/onflow/flow-go/network/p2p/p2plogging" - "github.com/onflow/flow-go/network/p2p/p2pnet/internal" + p2plogging "github.com/onflow/flow-go/network/p2p/logging" "github.com/onflow/flow-go/network/p2p/ping" "github.com/onflow/flow-go/network/p2p/subscription" "github.com/onflow/flow-go/network/p2p/unicast/protocols" "github.com/onflow/flow-go/network/p2p/unicast/ratelimit" "github.com/onflow/flow-go/network/p2p/utils" "github.com/onflow/flow-go/network/queue" + "github.com/onflow/flow-go/network/underlay/internal" "github.com/onflow/flow-go/network/validator" flowpubsub "github.com/onflow/flow-go/network/validator/pubsub" _ "github.com/onflow/flow-go/utils/binstat" @@ -469,7 +469,8 @@ func (n *Network) handleRegisterEngineRequest(parent irrecoverable.SignalerConte return newConduit, nil } -func (n *Network) handleRegisterBlobServiceRequest(parent irrecoverable.SignalerContext, channel channels.Channel, ds datastore.Batching, opts []network.BlobServiceOption) (network.BlobService, error) { +func (n *Network) handleRegisterBlobServiceRequest(parent irrecoverable.SignalerContext, channel channels.Channel, ds datastore.Batching, opts []network.BlobServiceOption) (network.BlobService, + error) { bs := blob.NewBlobService(n.libP2PNode.Host(), n.libP2PNode.Routing(), channel.String(), ds, n.bitswapMetrics, n.logger, opts...) // start the blob service using the network's context @@ -643,7 +644,7 @@ func (n *Network) UnicastOnChannel(channel channels.Channel, payload interface{} } streamProtectionTag := fmt.Sprintf("%v:%v", channel, msg.PayloadType()) - err = n.libP2PNode.OpenProtectedStream(ctx, peerID, streamProtectionTag, func(stream libp2pnet.Stream) error { + err = n.libP2PNode.OpenAndWriteOnStream(ctx, peerID, streamProtectionTag, func(stream libp2pnet.Stream) error { bufw := bufio.NewWriter(stream) writer := ggio.NewDelimitedWriter(bufw) diff --git a/network/validator/authorized_sender_validator.go b/network/validator/authorized_sender_validator.go index 69d925661a1..1a9a9d9a44f 100644 --- a/network/validator/authorized_sender_validator.go +++ b/network/validator/authorized_sender_validator.go @@ -13,7 +13,7 @@ import ( "github.com/onflow/flow-go/network/codec" "github.com/onflow/flow-go/network/message" "github.com/onflow/flow-go/network/p2p" - "github.com/onflow/flow-go/network/p2p/p2plogging" + p2plogging "github.com/onflow/flow-go/network/p2p/logging" ) var ( diff --git a/network/validator/pubsub/topic_validator.go b/network/validator/pubsub/topic_validator.go index 078f9272b12..c55689468d8 100644 --- a/network/validator/pubsub/topic_validator.go +++ b/network/validator/pubsub/topic_validator.go @@ -12,7 +12,7 @@ import ( "github.com/onflow/flow-go/network/channels" "github.com/onflow/flow-go/network/message" "github.com/onflow/flow-go/network/p2p" - "github.com/onflow/flow-go/network/p2p/p2plogging" + p2plogging "github.com/onflow/flow-go/network/p2p/logging" "github.com/onflow/flow-go/network/validator" _ "github.com/onflow/flow-go/utils/binstat" "github.com/onflow/flow-go/utils/logging" @@ -73,9 +73,9 @@ func TopicValidator(log zerolog.Logger, peerFilter func(peer.ID) error, validato return func(ctx context.Context, receivedFrom peer.ID, rawMsg *pubsub.Message) p2p.ValidationResult { var msg message.Message // convert the incoming raw message payload to Message type - //bs := binstat.EnterTimeVal(binstat.BinNet+":wire>1protobuf2message", int64(len(rawMsg.Data))) + // bs := binstat.EnterTimeVal(binstat.BinNet+":wire>1protobuf2message", int64(len(rawMsg.Data))) err := msg.Unmarshal(rawMsg.Data) - //binstat.Leave(bs) + // binstat.Leave(bs) if err != nil { return p2p.ValidationReject } diff --git a/state/protocol/badger/mutator_test.go b/state/protocol/badger/mutator_test.go index 53ecd1a6e79..788408b881e 100644 --- a/state/protocol/badger/mutator_test.go +++ b/state/protocol/badger/mutator_test.go @@ -11,16 +11,15 @@ import ( "time" "github.com/dgraph-io/badger/v2" + "github.com/onflow/crypto" "github.com/rs/zerolog" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/engine" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/model/flow/filter" - "github.com/onflow/flow-go/model/flow/order" "github.com/onflow/flow-go/module/metrics" mockmodule "github.com/onflow/flow-go/module/mock" "github.com/onflow/flow-go/module/signature" @@ -868,7 +867,7 @@ func TestExtendEpochTransitionValid(t *testing.T) { // add a participant for the next epoch epoch2NewParticipant := unittest.IdentityFixture(unittest.WithRole(flow.RoleVerification)) - epoch2Participants := append(participants, epoch2NewParticipant).Sort(order.Canonical) + epoch2Participants := append(participants, epoch2NewParticipant).Sort(flow.Canonical) // create the epoch setup event for the second epoch epoch2Setup := unittest.EpochSetupFixture( @@ -1319,7 +1318,7 @@ func TestExtendEpochSetupInvalid(t *testing.T) { // add a participant for the next epoch epoch2NewParticipant := unittest.IdentityFixture(unittest.WithRole(flow.RoleVerification)) - epoch2Participants := append(participants, epoch2NewParticipant).Sort(order.Canonical) + epoch2Participants := append(participants, epoch2NewParticipant).Sort(flow.Canonical) // this function will return a VALID setup event and seal, we will modify // in different ways in each test case @@ -1437,7 +1436,7 @@ func TestExtendEpochCommitInvalid(t *testing.T) { epoch2Participants := append( participants.Filter(filter.Not(filter.HasRole(flow.RoleConsensus))), epoch2NewParticipant, - ).Sort(order.Canonical) + ).Sort(flow.Canonical) // factory method to create a valid EpochSetup method w.r.t. the generated state createSetup := func(block *flow.Block) (*flow.EpochSetup, *flow.ExecutionReceipt, *flow.Seal) { @@ -1620,7 +1619,7 @@ func TestExtendEpochTransitionWithoutCommit(t *testing.T) { // add a participant for the next epoch epoch2NewParticipant := unittest.IdentityFixture(unittest.WithRole(flow.RoleVerification)) - epoch2Participants := append(participants, epoch2NewParticipant).Sort(order.Canonical) + epoch2Participants := append(participants, epoch2NewParticipant).Sort(flow.Canonical) // create the epoch setup event for the second epoch epoch2Setup := unittest.EpochSetupFixture( @@ -1761,7 +1760,7 @@ func TestEmergencyEpochFallback(t *testing.T) { // add a participant for the next epoch epoch2NewParticipant := unittest.IdentityFixture(unittest.WithRole(flow.RoleVerification)) - epoch2Participants := append(participants, epoch2NewParticipant).Sort(order.Canonical) + epoch2Participants := append(participants, epoch2NewParticipant).Sort(flow.Canonical) // create the epoch setup event for the second epoch epoch2Setup := unittest.EpochSetupFixture( @@ -1851,7 +1850,7 @@ func TestEmergencyEpochFallback(t *testing.T) { // add a participant for the next epoch epoch2NewParticipant := unittest.IdentityFixture(unittest.WithRole(flow.RoleVerification)) - epoch2Participants := append(participants, epoch2NewParticipant).Sort(order.Canonical) + epoch2Participants := append(participants, epoch2NewParticipant).Sort(flow.Canonical) // create the epoch setup event for the second epoch // this event is invalid because it used a non-contiguous first view diff --git a/state/protocol/badger/snapshot.go b/state/protocol/badger/snapshot.go index 1a121e81748..90cdebc6db9 100644 --- a/state/protocol/badger/snapshot.go +++ b/state/protocol/badger/snapshot.go @@ -12,7 +12,6 @@ import ( "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/model/flow/filter" "github.com/onflow/flow-go/model/flow/mapfunc" - "github.com/onflow/flow-go/model/flow/order" "github.com/onflow/flow-go/state/fork" "github.com/onflow/flow-go/state/protocol" "github.com/onflow/flow-go/state/protocol/inmem" @@ -111,7 +110,7 @@ func (s *Snapshot) Identities(selector flow.IdentityFilter) (flow.IdentityList, } // sort the identities so the 'IsCached' binary search works - identities := setup.Participants.Sort(order.Canonical) + identities := setup.Participants.Sort(flow.Canonical) // get identities that are in either last/next epoch but NOT in the current epoch var otherEpochIdentities flow.IdentityList @@ -173,7 +172,7 @@ func (s *Snapshot) Identities(selector flow.IdentityFilter) (flow.IdentityList, identities = identities.Filter(selector) // apply a deterministic sort to the participants - identities = identities.Sort(order.Canonical) + identities = identities.Sort(flow.Canonical) return identities, nil } diff --git a/state/protocol/badger/validity.go b/state/protocol/badger/validity.go index 264831512ec..acece515f64 100644 --- a/state/protocol/badger/validity.go +++ b/state/protocol/badger/validity.go @@ -10,7 +10,6 @@ import ( "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/model/flow/factory" "github.com/onflow/flow-go/model/flow/filter" - "github.com/onflow/flow-go/model/flow/order" "github.com/onflow/flow-go/state/protocol" ) @@ -87,7 +86,7 @@ func verifyEpochSetup(setup *flow.EpochSetup, verifyNetworkAddress bool) error { } // the participants must be listed in canonical order - if !setup.Participants.Sorted(order.Canonical) { + if !flow.IsIdentityListCanonical(setup.Participants) { return fmt.Errorf("participants are not canonically ordered") } @@ -235,7 +234,7 @@ func IsValidRootSnapshot(snap protocol.Snapshot, verifyResultID bool) error { if err != nil { return fmt.Errorf("could not get identities for root snapshot: %w", err) } - if !identities.Sorted(order.Canonical) { + if !flow.IsIdentityListCanonical(identities) { return fmt.Errorf("identities are not canonically ordered") } diff --git a/state/protocol/badger/validity_test.go b/state/protocol/badger/validity_test.go index 53a044770c2..f5349531841 100644 --- a/state/protocol/badger/validity_test.go +++ b/state/protocol/badger/validity_test.go @@ -3,9 +3,9 @@ package badger import ( "testing" + "github.com/onflow/crypto" "github.com/stretchr/testify/require" - "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/model/flow/filter" "github.com/onflow/flow-go/state/protocol" diff --git a/state/protocol/convert.go b/state/protocol/convert.go index 8f8630b2230..34a7dc97921 100644 --- a/state/protocol/convert.go +++ b/state/protocol/convert.go @@ -3,11 +3,11 @@ package protocol import ( "fmt" - "github.com/onflow/flow-go/module/signature" + "github.com/onflow/crypto" - "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/model/flow/filter" + "github.com/onflow/flow-go/module/signature" ) // ToEpochSetup converts an Epoch interface instance to the underlying concrete diff --git a/state/protocol/dkg.go b/state/protocol/dkg.go index 88d79c96f39..0bd726a8d46 100644 --- a/state/protocol/dkg.go +++ b/state/protocol/dkg.go @@ -1,7 +1,8 @@ package protocol import ( - "github.com/onflow/flow-go/crypto" + "github.com/onflow/crypto" + "github.com/onflow/flow-go/model/flow" ) diff --git a/state/protocol/inmem/dkg.go b/state/protocol/inmem/dkg.go index 59431dc5420..54b3d682337 100644 --- a/state/protocol/inmem/dkg.go +++ b/state/protocol/inmem/dkg.go @@ -1,7 +1,8 @@ package inmem import ( - "github.com/onflow/flow-go/crypto" + "github.com/onflow/crypto" + "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/state/protocol" ) diff --git a/state/protocol/mock/cluster_events.go b/state/protocol/mock/cluster_events.go index a17e4db4a9a..2e5a2bead49 100644 --- a/state/protocol/mock/cluster_events.go +++ b/state/protocol/mock/cluster_events.go @@ -3,8 +3,9 @@ package mock import ( - flow "github.com/onflow/flow-go/model/flow" mock "github.com/stretchr/testify/mock" + + flow "github.com/onflow/flow-go/model/flow" ) // ClusterEvents is an autogenerated mock type for the ClusterEvents type diff --git a/state/protocol/mock/cluster_id_update_consumer.go b/state/protocol/mock/cluster_id_update_consumer.go index 3594d504c0f..a18339c6376 100644 --- a/state/protocol/mock/cluster_id_update_consumer.go +++ b/state/protocol/mock/cluster_id_update_consumer.go @@ -3,8 +3,9 @@ package mock import ( - flow "github.com/onflow/flow-go/model/flow" mock "github.com/stretchr/testify/mock" + + flow "github.com/onflow/flow-go/model/flow" ) // ClusterIDUpdateConsumer is an autogenerated mock type for the ClusterIDUpdateConsumer type diff --git a/state/protocol/mock/dkg.go b/state/protocol/mock/dkg.go index 207719bd1ad..86e13439dda 100644 --- a/state/protocol/mock/dkg.go +++ b/state/protocol/mock/dkg.go @@ -3,7 +3,7 @@ package mock import ( - crypto "github.com/onflow/flow-go/crypto" + crypto "github.com/onflow/crypto" flow "github.com/onflow/flow-go/model/flow" mock "github.com/stretchr/testify/mock" diff --git a/state/protocol/prg/prg.go b/state/protocol/prg/prg.go index 30ec995059a..36b3b77751d 100644 --- a/state/protocol/prg/prg.go +++ b/state/protocol/prg/prg.go @@ -5,8 +5,8 @@ import ( "golang.org/x/crypto/sha3" - "github.com/onflow/flow-go/crypto" - "github.com/onflow/flow-go/crypto/random" + "github.com/onflow/crypto" + "github.com/onflow/crypto/random" ) const RandomSourceLength = crypto.SignatureLenBLSBLS12381 diff --git a/state/protocol/snapshot.go b/state/protocol/snapshot.go index f557bc59fbc..ca781b6e6cb 100644 --- a/state/protocol/snapshot.go +++ b/state/protocol/snapshot.go @@ -50,7 +50,7 @@ type Snapshot interface { // epoch. At the end of an epoch, this includes identities scheduled to join // in the next epoch but are not active yet. // - // Identities are guaranteed to be returned in canonical order (order.Canonical). + // Identities are guaranteed to be returned in canonical order (flow.Canonical). // // It allows us to provide optional upfront filters which can be used by the // implementation to speed up database lookups. diff --git a/storage/badger/blocks.go b/storage/badger/blocks.go index cc0a35e3acd..9d3b64a1ffc 100644 --- a/storage/badger/blocks.go +++ b/storage/badger/blocks.go @@ -109,6 +109,7 @@ func (b *Blocks) IndexBlockForCollections(blockID flow.Identifier, collIDs []flo } // InsertLastFullBlockHeightIfNotExists inserts the last full block height +// Calling this function multiple times is a no-op and returns no expected errors. func (b *Blocks) InsertLastFullBlockHeightIfNotExists(height uint64) error { return operation.RetryOnConflict(b.db.Update, func(tx *badger.Txn) error { err := operation.InsertLastCompleteBlockHeightIfNotExists(height)(tx) diff --git a/storage/badger/dkg_state.go b/storage/badger/dkg_state.go index 73e2b3e8133..53915aa9122 100644 --- a/storage/badger/dkg_state.go +++ b/storage/badger/dkg_state.go @@ -4,8 +4,8 @@ import ( "fmt" "github.com/dgraph-io/badger/v2" + "github.com/onflow/crypto" - "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/model/encodable" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/module" diff --git a/storage/badger/operation/events_test.go b/storage/badger/operation/events_test.go index ed2395e69d7..9896c02fd69 100644 --- a/storage/badger/operation/events_test.go +++ b/storage/badger/operation/events_test.go @@ -2,9 +2,10 @@ package operation import ( "bytes" - "sort" "testing" + "golang.org/x/exp/slices" + "github.com/dgraph-io/badger/v2" "github.com/stretchr/testify/require" @@ -114,24 +115,14 @@ func TestRetrieveEventByBlockIDTxID(t *testing.T) { }) } -// Event retrieval does not guarantee any order, hence a sort function to help compare expected and actual events +// Event retrieval does not guarantee any order, +// Hence, we a sort the events for comparing the expected and actual events. func sortEvent(events []flow.Event) { - sort.Slice(events, func(i, j int) bool { - - tComp := bytes.Compare(events[i].TransactionID[:], events[j].TransactionID[:]) - if tComp < 0 { - return true + slices.SortFunc(events, func(i, j flow.Event) int { + tComp := bytes.Compare(i.TransactionID[:], j.TransactionID[:]) + if tComp != 0 { + return tComp } - if tComp > 0 { - return false - } - - txIndex := events[i].TransactionIndex == events[j].TransactionIndex - if !txIndex { - return events[i].TransactionIndex < events[j].TransactionIndex - } - - return events[i].EventIndex < events[j].EventIndex - + return int(i.EventIndex) - int(j.EventIndex) }) } diff --git a/storage/badger/operation/guarantees_test.go b/storage/badger/operation/guarantees_test.go index 3045799db58..146fc862fbf 100644 --- a/storage/badger/operation/guarantees_test.go +++ b/storage/badger/operation/guarantees_test.go @@ -4,10 +4,10 @@ import ( "testing" "github.com/dgraph-io/badger/v2" + "github.com/onflow/crypto" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/utils/unittest" ) diff --git a/storage/badger/operation/headers_test.go b/storage/badger/operation/headers_test.go index 089ecea3848..60270cd24ca 100644 --- a/storage/badger/operation/headers_test.go +++ b/storage/badger/operation/headers_test.go @@ -7,10 +7,10 @@ import ( "time" "github.com/dgraph-io/badger/v2" + "github.com/onflow/crypto" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/utils/unittest" ) diff --git a/storage/badger/operation/interactions_test.go b/storage/badger/operation/interactions_test.go index fd334c3a6b8..b976a2dafd1 100644 --- a/storage/badger/operation/interactions_test.go +++ b/storage/badger/operation/interactions_test.go @@ -18,15 +18,15 @@ func TestStateInteractionsInsertCheckRetrieve(t *testing.T) { unittest.RunWithBadgerDB(t, func(db *badger.DB) { id1 := flow.NewRegisterID( - string([]byte("\x89krg\u007fBN\x1d\xf5\xfb\xb8r\xbc4\xbd\x98Õ¼\xf1\xd0twU\xbf\x16N\xb4?,\xa0&;")), + flow.BytesToAddress([]byte("\x89krg\u007fBN\x1d\xf5\xfb\xb8r\xbc4\xbd\x98Õ¼\xf1\xd0twU\xbf\x16N\xb4?,\xa0&;")), "") - id2 := flow.NewRegisterID(string([]byte{2}), "") - id3 := flow.NewRegisterID(string([]byte{3}), "") + id2 := flow.NewRegisterID(flow.BytesToAddress([]byte{2}), "") + id3 := flow.NewRegisterID(flow.BytesToAddress([]byte{3}), "") executionSnapshot := &snapshot.ExecutionSnapshot{ ReadSet: map[flow.RegisterID]struct{}{ - id2: struct{}{}, - id3: struct{}{}, + id2: {}, + id3: {}, }, WriteSet: map[flow.RegisterID]flow.RegisterValue{ id1: []byte("zażółć gęślÄ… jaźń"), @@ -36,7 +36,7 @@ func TestStateInteractionsInsertCheckRetrieve(t *testing.T) { interactions := []*snapshot.ExecutionSnapshot{ executionSnapshot, - &snapshot.ExecutionSnapshot{}, + {}, } blockID := unittest.IdentifierFixture() diff --git a/storage/dkg.go b/storage/dkg.go index 3f38212461c..9c35e79b6d1 100644 --- a/storage/dkg.go +++ b/storage/dkg.go @@ -1,7 +1,8 @@ package storage import ( - "github.com/onflow/flow-go/crypto" + "github.com/onflow/crypto" + "github.com/onflow/flow-go/model/flow" ) diff --git a/storage/mock/beacon_private_keys.go b/storage/mock/beacon_private_keys.go index 9dc1ec2fe69..63f1e60c7d0 100644 --- a/storage/mock/beacon_private_keys.go +++ b/storage/mock/beacon_private_keys.go @@ -3,8 +3,9 @@ package mock import ( - encodable "github.com/onflow/flow-go/model/encodable" mock "github.com/stretchr/testify/mock" + + encodable "github.com/onflow/flow-go/model/encodable" ) // BeaconPrivateKeys is an autogenerated mock type for the BeaconPrivateKeys type diff --git a/storage/mock/dkg_state.go b/storage/mock/dkg_state.go index e9092a66dd9..18a3e808a0c 100644 --- a/storage/mock/dkg_state.go +++ b/storage/mock/dkg_state.go @@ -3,7 +3,7 @@ package mock import ( - crypto "github.com/onflow/flow-go/crypto" + crypto "github.com/onflow/crypto" flow "github.com/onflow/flow-go/model/flow" mock "github.com/stretchr/testify/mock" diff --git a/storage/mock/epoch_states.go b/storage/mock/epoch_states.go index 2106bd07076..c7a8916e81f 100644 --- a/storage/mock/epoch_states.go +++ b/storage/mock/epoch_states.go @@ -4,6 +4,7 @@ package mock import ( badger "github.com/dgraph-io/badger/v2" + flow "github.com/onflow/flow-go/model/flow" mock "github.com/stretchr/testify/mock" diff --git a/storage/mock/job.go b/storage/mock/job.go index dabff1d99ac..fd53c461d4d 100644 --- a/storage/mock/job.go +++ b/storage/mock/job.go @@ -3,8 +3,9 @@ package mock import ( - module "github.com/onflow/flow-go/module" mock "github.com/stretchr/testify/mock" + + module "github.com/onflow/flow-go/module" ) // Job is an autogenerated mock type for the Job type diff --git a/storage/mock/safe_beacon_keys.go b/storage/mock/safe_beacon_keys.go index 5d4ff0b511b..fecfdc4c81e 100644 --- a/storage/mock/safe_beacon_keys.go +++ b/storage/mock/safe_beacon_keys.go @@ -3,7 +3,7 @@ package mock import ( - crypto "github.com/onflow/flow-go/crypto" + crypto "github.com/onflow/crypto" mock "github.com/stretchr/testify/mock" ) diff --git a/storage/pebble/bootstrap.go b/storage/pebble/bootstrap.go index 54a3ab182b3..f00d4991cda 100644 --- a/storage/pebble/bootstrap.go +++ b/storage/pebble/bootstrap.go @@ -12,6 +12,7 @@ import ( "go.uber.org/atomic" "golang.org/x/sync/errgroup" + "github.com/onflow/flow-go/ledger" "github.com/onflow/flow-go/ledger/common/convert" "github.com/onflow/flow-go/ledger/complete/wal" ) @@ -26,6 +27,7 @@ type RegisterBootstrap struct { checkpointFileName string leafNodeChan chan *wal.LeafNode rootHeight uint64 + rootHash ledger.RootHash registerCount *atomic.Uint64 } @@ -36,6 +38,7 @@ func NewRegisterBootstrap( db *pebble.DB, checkpointFile string, rootHeight uint64, + rootHash ledger.RootHash, log zerolog.Logger, ) (*RegisterBootstrap, error) { // check for pre-populated heights, fail if it is populated @@ -57,6 +60,7 @@ func NewRegisterBootstrap( checkpointFileName: checkpointFileName, leafNodeChan: make(chan *wal.LeafNode, checkpointLeafNodeBufSize), rootHeight: rootHeight, + rootHash: rootHash, registerCount: atomic.NewUint64(0), }, nil } @@ -131,6 +135,12 @@ func (b *RegisterBootstrap) IndexCheckpointFile(ctx context.Context, workerCount cct, cancel := context.WithCancel(ctx) defer cancel() + // validate the checkpoint has correct root hash + err := wal.CheckpointHasRootHash(b.log, b.checkpointDir, b.checkpointFileName, b.rootHash) + if err != nil { + return fmt.Errorf("the root checkpoint to have the trie root hash %v does not match with the root state commitment: %w", b.rootHash, err) + } + g, gCtx := errgroup.WithContext(cct) start := time.Now() @@ -141,7 +151,7 @@ func (b *RegisterBootstrap) IndexCheckpointFile(ctx context.Context, workerCount }) } - err := wal.OpenAndReadLeafNodesFromCheckpointV6(b.leafNodeChan, b.checkpointDir, b.checkpointFileName, b.log) + err = wal.OpenAndReadLeafNodesFromCheckpointV6(b.leafNodeChan, b.checkpointDir, b.checkpointFileName, b.log) if err != nil { return fmt.Errorf("error reading leaf node: %w", err) } diff --git a/storage/pebble/bootstrap_test.go b/storage/pebble/bootstrap_test.go index 5e35ac156a2..bf35ec915b3 100644 --- a/storage/pebble/bootstrap_test.go +++ b/storage/pebble/bootstrap_test.go @@ -28,13 +28,14 @@ func TestRegisterBootstrap_NewBootstrap(t *testing.T) { t.Parallel() unittest.RunWithTempDir(t, func(dir string) { rootHeight := uint64(1) + rootHash := ledger.RootHash(unittest.StateCommitmentFixture()) log := zerolog.New(io.Discard) p, err := OpenRegisterPebbleDB(dir) require.NoError(t, err) // set heights require.NoError(t, initHeights(p, rootHeight)) // errors if FirstHeight or LastHeight are populated - _, err = NewRegisterBootstrap(p, dir, rootHeight, log) + _, err = NewRegisterBootstrap(p, dir, rootHeight, rootHash, log) require.ErrorIs(t, err, ErrAlreadyBootstrapped) }) } @@ -45,12 +46,13 @@ func TestRegisterBootstrap_IndexCheckpointFile_Happy(t *testing.T) { rootHeight := uint64(10000) unittest.RunWithTempDir(t, func(dir string) { tries, registerIDs := simpleTrieWithValidRegisterIDs(t) + rootHash := tries[0].RootHash() fileName := "simple-checkpoint" require.NoErrorf(t, wal.StoreCheckpointV6Concurrently(tries, dir, fileName, log), "fail to store checkpoint") checkpointFile := path.Join(dir, fileName) pb, dbDir := createPebbleForTest(t) - bootstrap, err := NewRegisterBootstrap(pb, checkpointFile, rootHeight, log) + bootstrap, err := NewRegisterBootstrap(pb, checkpointFile, rootHeight, rootHash, log) require.NoError(t, err) err = bootstrap.IndexCheckpointFile(context.Background(), workerCount) require.NoError(t, err) @@ -79,12 +81,13 @@ func TestRegisterBootstrap_IndexCheckpointFile_Empty(t *testing.T) { rootHeight := uint64(10000) unittest.RunWithTempDir(t, func(dir string) { tries := []*trie.MTrie{trie.NewEmptyMTrie()} + rootHash := tries[0].RootHash() fileName := "empty-checkpoint" require.NoErrorf(t, wal.StoreCheckpointV6Concurrently(tries, dir, fileName, log), "fail to store checkpoint") checkpointFile := path.Join(dir, fileName) pb, dbDir := createPebbleForTest(t) - bootstrap, err := NewRegisterBootstrap(pb, checkpointFile, rootHeight, log) + bootstrap, err := NewRegisterBootstrap(pb, checkpointFile, rootHeight, rootHash, log) require.NoError(t, err) err = bootstrap.IndexCheckpointFile(context.Background(), workerCount) require.NoError(t, err) @@ -113,6 +116,7 @@ func TestRegisterBootstrap_IndexCheckpointFile_FormatIssue(t *testing.T) { emptyTrie := trie.NewEmptyMTrie() trieWithInvalidEntry, _, err := trie.NewTrieWithUpdatedRegisters(emptyTrie, paths, payloads, true) require.NoError(t, err) + rootHash := trieWithInvalidEntry.RootHash() log := zerolog.New(io.Discard) unittest.RunWithTempDir(t, func(dir string) { @@ -122,7 +126,7 @@ func TestRegisterBootstrap_IndexCheckpointFile_FormatIssue(t *testing.T) { checkpointFile := path.Join(dir, fileName) pb, dbDir := createPebbleForTest(t) - bootstrap, err := NewRegisterBootstrap(pb, checkpointFile, rootHeight, log) + bootstrap, err := NewRegisterBootstrap(pb, checkpointFile, rootHeight, rootHash, log) require.NoError(t, err) err = bootstrap.IndexCheckpointFile(context.Background(), workerCount) require.ErrorContains(t, err, "unexpected ledger key format") @@ -138,6 +142,7 @@ func TestRegisterBootstrap_IndexCheckpointFile_CorruptedCheckpointFile(t *testin log := zerolog.New(io.Discard) unittest.RunWithTempDir(t, func(dir string) { tries, _ := largeTrieWithValidRegisterIDs(t) + rootHash := tries[0].RootHash() checkpointFileName := "large-checkpoint-incomplete" require.NoErrorf(t, wal.StoreCheckpointV6Concurrently(tries, dir, checkpointFileName, log), "fail to store checkpoint") // delete 2nd part of the file (2nd subtrie) @@ -145,7 +150,7 @@ func TestRegisterBootstrap_IndexCheckpointFile_CorruptedCheckpointFile(t *testin err := os.RemoveAll(fileToDelete) require.NoError(t, err) pb, dbDir := createPebbleForTest(t) - bootstrap, err := NewRegisterBootstrap(pb, checkpointFileName, rootHeight, log) + bootstrap, err := NewRegisterBootstrap(pb, checkpointFileName, rootHeight, rootHash, log) require.NoError(t, err) err = bootstrap.IndexCheckpointFile(context.Background(), workerCount) require.ErrorIs(t, err, os.ErrNotExist) @@ -159,11 +164,12 @@ func TestRegisterBootstrap_IndexCheckpointFile_MultipleBatch(t *testing.T) { rootHeight := uint64(10000) unittest.RunWithTempDir(t, func(dir string) { tries, registerIDs := largeTrieWithValidRegisterIDs(t) + rootHash := tries[0].RootHash() fileName := "large-checkpoint" require.NoErrorf(t, wal.StoreCheckpointV6Concurrently(tries, dir, fileName, log), "fail to store checkpoint") checkpointFile := path.Join(dir, fileName) pb, dbDir := createPebbleForTest(t) - bootstrap, err := NewRegisterBootstrap(pb, checkpointFile, rootHeight, log) + bootstrap, err := NewRegisterBootstrap(pb, checkpointFile, rootHeight, rootHash, log) require.NoError(t, err) err = bootstrap.IndexCheckpointFile(context.Background(), workerCount) require.NoError(t, err) diff --git a/storage/pebble/registers.go b/storage/pebble/registers.go index a1f3bc20bed..40a4657d4af 100644 --- a/storage/pebble/registers.go +++ b/storage/pebble/registers.go @@ -59,9 +59,12 @@ func (s *Registers) Get( fmt.Sprintf("height %d not indexed, indexed range is [%d-%d]", height, s.firstHeight, latestHeight), ) } - iter := s.db.NewIter(&pebble.IterOptions{ + iter, err := s.db.NewIter(&pebble.IterOptions{ UseL6Filters: true, }) + if err != nil { + return nil, err + } defer iter.Close() diff --git a/storage/pebble/registers_test.go b/storage/pebble/registers_test.go index 0e598e0ce45..34fd26ad602 100644 --- a/storage/pebble/registers_test.go +++ b/storage/pebble/registers_test.go @@ -300,12 +300,13 @@ func Benchmark_PayloadStorage(b *testing.B) { require.NoError(b, err) require.NotNil(b, s) - batchSizeKey := flow.NewRegisterID("batch", "size") + owner := unittest.RandomAddressFixture() + batchSizeKey := flow.NewRegisterID(owner, "size") const maxBatchSize = 1024 var totalBatchSize int keyForBatchSize := func(i int) flow.RegisterID { - return flow.NewRegisterID("batch", strconv.Itoa(i)) + return flow.NewRegisterID(owner, strconv.Itoa(i)) } valueForHeightAndKey := func(i, j int) []byte { return []byte(fmt.Sprintf("%d-%d", i, j)) diff --git a/tools/test_monitor/common/testdata/test_data.go b/tools/test_monitor/common/testdata/test_data.go index 28d8c58ba6b..f324427b702 100644 --- a/tools/test_monitor/common/testdata/test_data.go +++ b/tools/test_monitor/common/testdata/test_data.go @@ -9,7 +9,7 @@ import ( const COMMIT_DATE = "2021-09-21T18:06:25-07:00" const COMMIT_SHA = "46baf6c6be29af9c040bc14195e195848598bbae" const JOB_STARTED = "2021-09-21T21:06:25-07:00" -const CRYPTO_HASH_PACKAGE = "github.com/onflow/flow-go/crypto/hash" +const CRYPTO_HASH_PACKAGE = "github.com/onflow/crypto/hash" const RUN_ID = "12345" // Level1TestData is used by tests to store what the expected test result should be and what the raw diff --git a/tools/test_monitor/level2/process_summary2_results.go b/tools/test_monitor/level2/process_summary2_results.go index a8491ac5853..8d40c2cb711 100644 --- a/tools/test_monitor/level2/process_summary2_results.go +++ b/tools/test_monitor/level2/process_summary2_results.go @@ -107,8 +107,8 @@ func saveExceptionMessage(testResult common.Level1TestResult) { // for each failed / exception test, we want to save the raw output message as a text file // there could be multiple failures / exceptions of the same test, so we want to save each failed / exception message in a separate text file // each test with failures / exceptions will have a uniquely named (based on test name and package) subdirectory where failed / exception messages are saved -// e.g. "failures/TestSanitySha3_256+github.com-onflow-flow-go-crypto-hash" will store failed messages text files -// from test TestSanitySha3_256 from the "github.com/onflow/flow-go/crypto/hash" package +// e.g. "failures/TestSanitySha3_256+github.com-onflow-crypto-hash" will store failed messages text files +// from test TestSanitySha3_256 from the "github.com/onflow/crypto/hash" package // failure and exception messages are saved in a similar way so this helper function // handles saving both types of messages func saveMessageHelper(testResult common.Level1TestResult, messagesDir string, messageFileStem string) { diff --git a/tools/test_monitor/testdata/summary1/raw/test-result-crypto-hash-1-count-fail.json b/tools/test_monitor/testdata/summary1/raw/test-result-crypto-hash-1-count-fail.json index c9f23234e69..04cda7b0b10 100644 --- a/tools/test_monitor/testdata/summary1/raw/test-result-crypto-hash-1-count-fail.json +++ b/tools/test_monitor/testdata/summary1/raw/test-result-crypto-hash-1-count-fail.json @@ -1,55 +1,55 @@ -{"Time":"2021-09-24T11:51:27.764908-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256"} -{"Time":"2021-09-24T11:51:27.767125-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Output":"=== RUN TestSanitySha3_256\n"} -{"Time":"2021-09-24T11:51:27.767166-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Output":" hash_test.go:21: \n"} -{"Time":"2021-09-24T11:51:27.76718-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Output":" \tError Trace:\thash_test.go:21\n"} -{"Time":"2021-09-24T11:51:27.767187-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Output":" \tError: \tNot equal: \n"} -{"Time":"2021-09-24T11:51:27.767194-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Output":" \t \texpected: hash.Hash{0x36, 0xf0, 0x28, 0x58, 0xb, 0xb0, 0x2c, 0xc8, 0x27, 0x2a, 0x9a, 0x2, 0xf, 0x42, 0x0, 0xe3, 0x46, 0xe2, 0x76, 0xae, 0x66, 0x4e, 0x45, 0xee, 0x80, 0x74, 0x55, 0x74, 0xe2, 0xf5, 0xab, 0x81}\n"} -{"Time":"2021-09-24T11:51:27.767226-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Output":" \t \tactual : hash.Hash{0x36, 0xf0, 0x28, 0x58, 0xb, 0xb0, 0x2c, 0xc8, 0x27, 0x2a, 0x9a, 0x2, 0xf, 0x42, 0x0, 0xe3, 0x46, 0xe2, 0x76, 0xae, 0x66, 0x4e, 0x45, 0xee, 0x80, 0x74, 0x55, 0x74, 0xe2, 0xf5, 0xab, 0x80}\n"} -{"Time":"2021-09-24T11:51:27.767234-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Output":" \t \t\n"} -{"Time":"2021-09-24T11:51:27.76724-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Output":" \t \tDiff:\n"} -{"Time":"2021-09-24T11:51:27.76745-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Output":" \t \t--- Expected\n"} -{"Time":"2021-09-24T11:51:27.767484-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Output":" \t \t+++ Actual\n"} -{"Time":"2021-09-24T11:51:27.767497-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Output":" \t \t@@ -1,2 +1,2 @@\n"} -{"Time":"2021-09-24T11:51:27.76751-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Output":" \t \t-(hash.Hash) (len=32) 0x36f028580bb02cc8272a9a020f4200e346e276ae664e45ee80745574e2f5ab81\n"} -{"Time":"2021-09-24T11:51:27.767522-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Output":" \t \t+(hash.Hash) (len=32) 0x36f028580bb02cc8272a9a020f4200e346e276ae664e45ee80745574e2f5ab80\n"} -{"Time":"2021-09-24T11:51:27.767533-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Output":" \t \t \n"} -{"Time":"2021-09-24T11:51:27.767544-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Output":" \tTest: \tTestSanitySha3_256\n"} -{"Time":"2021-09-24T11:51:27.76759-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Output":"--- FAIL: TestSanitySha3_256 (0.00s)\n"} -{"Time":"2021-09-24T11:51:27.767602-04:00","Action":"fail","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Elapsed":0} -{"Time":"2021-09-24T11:51:27.767632-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384"} -{"Time":"2021-09-24T11:51:27.767642-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Output":"=== RUN TestSanitySha3_384\n"} -{"Time":"2021-09-24T11:51:27.767654-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Output":"--- PASS: TestSanitySha3_384 (0.00s)\n"} -{"Time":"2021-09-24T11:51:27.767663-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Elapsed":0} -{"Time":"2021-09-24T11:51:27.767672-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256"} -{"Time":"2021-09-24T11:51:27.767681-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":"=== RUN TestSanitySha2_256\n"} -{"Time":"2021-09-24T11:51:27.767692-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":"--- PASS: TestSanitySha2_256 (0.00s)\n"} -{"Time":"2021-09-24T11:51:27.767702-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Elapsed":0} -{"Time":"2021-09-24T11:51:27.767712-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384"} -{"Time":"2021-09-24T11:51:27.767721-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Output":"=== RUN TestSanitySha2_384\n"} -{"Time":"2021-09-24T11:51:27.767732-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Output":"--- PASS: TestSanitySha2_384 (0.00s)\n"} -{"Time":"2021-09-24T11:51:27.767743-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Elapsed":0} -{"Time":"2021-09-24T11:51:27.767752-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128"} -{"Time":"2021-09-24T11:51:27.767791-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Output":"=== RUN TestSanityKmac128\n"} -{"Time":"2021-09-24T11:51:27.767816-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Output":"--- PASS: TestSanityKmac128 (0.00s)\n"} -{"Time":"2021-09-24T11:51:27.767824-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Elapsed":0} -{"Time":"2021-09-24T11:51:27.767831-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI"} -{"Time":"2021-09-24T11:51:27.767837-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":"=== RUN TestHashersAPI\n"} -{"Time":"2021-09-24T11:51:27.767843-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":" hash_test.go:114: math rand seed is 1632498687765218000\n"} -{"Time":"2021-09-24T11:51:27.767866-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":"--- PASS: TestHashersAPI (0.00s)\n"} -{"Time":"2021-09-24T11:51:27.767875-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Elapsed":0} -{"Time":"2021-09-24T11:51:27.767884-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3"} -{"Time":"2021-09-24T11:51:27.7679-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":"=== RUN TestSha3\n"} -{"Time":"2021-09-24T11:51:27.767909-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":" hash_test.go:158: math rand seed is 1632498687765661000\n"} -{"Time":"2021-09-24T11:51:27.767965-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256"} -{"Time":"2021-09-24T11:51:27.767986-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Output":"=== RUN TestSha3/SHA3_256\n"} -{"Time":"2021-09-24T11:51:27.875316-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384"} -{"Time":"2021-09-24T11:51:27.875342-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Output":"=== RUN TestSha3/SHA3_384\n"} -{"Time":"2021-09-24T11:51:27.99617-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":"--- PASS: TestSha3 (0.23s)\n"} -{"Time":"2021-09-24T11:51:27.996209-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Output":" --- PASS: TestSha3/SHA3_256 (0.11s)\n"} -{"Time":"2021-09-24T11:51:27.99622-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Elapsed":0.11} -{"Time":"2021-09-24T11:51:27.996229-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Output":" --- PASS: TestSha3/SHA3_384 (0.12s)\n"} -{"Time":"2021-09-24T11:51:27.996233-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Elapsed":0.12} -{"Time":"2021-09-24T11:51:27.996236-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Elapsed":0.23} -{"Time":"2021-09-24T11:51:27.996241-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Output":"FAIL\n"} -{"Time":"2021-09-24T11:51:27.997332-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Output":"FAIL\tgithub.com/onflow/flow-go/crypto/hash\t0.483s\n"} -{"Time":"2021-09-24T11:51:27.997383-04:00","Action":"fail","Package":"github.com/onflow/flow-go/crypto/hash","Elapsed":0.483} \ No newline at end of file +{"Time":"2021-09-24T11:51:27.764908-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256"} +{"Time":"2021-09-24T11:51:27.767125-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Output":"=== RUN TestSanitySha3_256\n"} +{"Time":"2021-09-24T11:51:27.767166-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Output":" hash_test.go:21: \n"} +{"Time":"2021-09-24T11:51:27.76718-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Output":" \tError Trace:\thash_test.go:21\n"} +{"Time":"2021-09-24T11:51:27.767187-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Output":" \tError: \tNot equal: \n"} +{"Time":"2021-09-24T11:51:27.767194-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Output":" \t \texpected: hash.Hash{0x36, 0xf0, 0x28, 0x58, 0xb, 0xb0, 0x2c, 0xc8, 0x27, 0x2a, 0x9a, 0x2, 0xf, 0x42, 0x0, 0xe3, 0x46, 0xe2, 0x76, 0xae, 0x66, 0x4e, 0x45, 0xee, 0x80, 0x74, 0x55, 0x74, 0xe2, 0xf5, 0xab, 0x81}\n"} +{"Time":"2021-09-24T11:51:27.767226-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Output":" \t \tactual : hash.Hash{0x36, 0xf0, 0x28, 0x58, 0xb, 0xb0, 0x2c, 0xc8, 0x27, 0x2a, 0x9a, 0x2, 0xf, 0x42, 0x0, 0xe3, 0x46, 0xe2, 0x76, 0xae, 0x66, 0x4e, 0x45, 0xee, 0x80, 0x74, 0x55, 0x74, 0xe2, 0xf5, 0xab, 0x80}\n"} +{"Time":"2021-09-24T11:51:27.767234-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Output":" \t \t\n"} +{"Time":"2021-09-24T11:51:27.76724-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Output":" \t \tDiff:\n"} +{"Time":"2021-09-24T11:51:27.76745-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Output":" \t \t--- Expected\n"} +{"Time":"2021-09-24T11:51:27.767484-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Output":" \t \t+++ Actual\n"} +{"Time":"2021-09-24T11:51:27.767497-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Output":" \t \t@@ -1,2 +1,2 @@\n"} +{"Time":"2021-09-24T11:51:27.76751-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Output":" \t \t-(hash.Hash) (len=32) 0x36f028580bb02cc8272a9a020f4200e346e276ae664e45ee80745574e2f5ab81\n"} +{"Time":"2021-09-24T11:51:27.767522-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Output":" \t \t+(hash.Hash) (len=32) 0x36f028580bb02cc8272a9a020f4200e346e276ae664e45ee80745574e2f5ab80\n"} +{"Time":"2021-09-24T11:51:27.767533-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Output":" \t \t \n"} +{"Time":"2021-09-24T11:51:27.767544-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Output":" \tTest: \tTestSanitySha3_256\n"} +{"Time":"2021-09-24T11:51:27.76759-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Output":"--- FAIL: TestSanitySha3_256 (0.00s)\n"} +{"Time":"2021-09-24T11:51:27.767602-04:00","Action":"fail","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Elapsed":0} +{"Time":"2021-09-24T11:51:27.767632-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384"} +{"Time":"2021-09-24T11:51:27.767642-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Output":"=== RUN TestSanitySha3_384\n"} +{"Time":"2021-09-24T11:51:27.767654-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Output":"--- PASS: TestSanitySha3_384 (0.00s)\n"} +{"Time":"2021-09-24T11:51:27.767663-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Elapsed":0} +{"Time":"2021-09-24T11:51:27.767672-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256"} +{"Time":"2021-09-24T11:51:27.767681-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":"=== RUN TestSanitySha2_256\n"} +{"Time":"2021-09-24T11:51:27.767692-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":"--- PASS: TestSanitySha2_256 (0.00s)\n"} +{"Time":"2021-09-24T11:51:27.767702-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Elapsed":0} +{"Time":"2021-09-24T11:51:27.767712-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384"} +{"Time":"2021-09-24T11:51:27.767721-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Output":"=== RUN TestSanitySha2_384\n"} +{"Time":"2021-09-24T11:51:27.767732-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Output":"--- PASS: TestSanitySha2_384 (0.00s)\n"} +{"Time":"2021-09-24T11:51:27.767743-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Elapsed":0} +{"Time":"2021-09-24T11:51:27.767752-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128"} +{"Time":"2021-09-24T11:51:27.767791-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Output":"=== RUN TestSanityKmac128\n"} +{"Time":"2021-09-24T11:51:27.767816-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Output":"--- PASS: TestSanityKmac128 (0.00s)\n"} +{"Time":"2021-09-24T11:51:27.767824-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Elapsed":0} +{"Time":"2021-09-24T11:51:27.767831-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI"} +{"Time":"2021-09-24T11:51:27.767837-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":"=== RUN TestHashersAPI\n"} +{"Time":"2021-09-24T11:51:27.767843-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":" hash_test.go:114: math rand seed is 1632498687765218000\n"} +{"Time":"2021-09-24T11:51:27.767866-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":"--- PASS: TestHashersAPI (0.00s)\n"} +{"Time":"2021-09-24T11:51:27.767875-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Elapsed":0} +{"Time":"2021-09-24T11:51:27.767884-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3"} +{"Time":"2021-09-24T11:51:27.7679-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":"=== RUN TestSha3\n"} +{"Time":"2021-09-24T11:51:27.767909-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":" hash_test.go:158: math rand seed is 1632498687765661000\n"} +{"Time":"2021-09-24T11:51:27.767965-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256"} +{"Time":"2021-09-24T11:51:27.767986-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Output":"=== RUN TestSha3/SHA3_256\n"} +{"Time":"2021-09-24T11:51:27.875316-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384"} +{"Time":"2021-09-24T11:51:27.875342-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Output":"=== RUN TestSha3/SHA3_384\n"} +{"Time":"2021-09-24T11:51:27.99617-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":"--- PASS: TestSha3 (0.23s)\n"} +{"Time":"2021-09-24T11:51:27.996209-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Output":" --- PASS: TestSha3/SHA3_256 (0.11s)\n"} +{"Time":"2021-09-24T11:51:27.99622-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Elapsed":0.11} +{"Time":"2021-09-24T11:51:27.996229-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Output":" --- PASS: TestSha3/SHA3_384 (0.12s)\n"} +{"Time":"2021-09-24T11:51:27.996233-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Elapsed":0.12} +{"Time":"2021-09-24T11:51:27.996236-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Elapsed":0.23} +{"Time":"2021-09-24T11:51:27.996241-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Output":"FAIL\n"} +{"Time":"2021-09-24T11:51:27.997332-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Output":"FAIL\tgithub.com/onflow/crypto/hash\t0.483s\n"} +{"Time":"2021-09-24T11:51:27.997383-04:00","Action":"fail","Package":"github.com/onflow/crypto/hash","Elapsed":0.483} \ No newline at end of file diff --git a/tools/test_monitor/testdata/summary1/raw/test-result-crypto-hash-1-count-pass.json b/tools/test_monitor/testdata/summary1/raw/test-result-crypto-hash-1-count-pass.json index 4fe74ee9c9d..386e6b733ae 100644 --- a/tools/test_monitor/testdata/summary1/raw/test-result-crypto-hash-1-count-pass.json +++ b/tools/test_monitor/testdata/summary1/raw/test-result-crypto-hash-1-count-pass.json @@ -1,41 +1,41 @@ -{"Time":"2021-09-24T11:27:29.121674-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256"} -{"Time":"2021-09-24T11:27:29.121944-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Output":"=== RUN TestSanitySha3_256\n"} -{"Time":"2021-09-24T11:27:29.121969-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Output":"--- PASS: TestSanitySha3_256 (0.00s)\n"} -{"Time":"2021-09-24T11:27:29.121973-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Elapsed":0} -{"Time":"2021-09-24T11:27:29.121993-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384"} -{"Time":"2021-09-24T11:27:29.121996-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Output":"=== RUN TestSanitySha3_384\n"} -{"Time":"2021-09-24T11:27:29.122-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Output":"--- PASS: TestSanitySha3_384 (0.00s)\n"} -{"Time":"2021-09-24T11:27:29.122003-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Elapsed":0} -{"Time":"2021-09-24T11:27:29.122006-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256"} -{"Time":"2021-09-24T11:27:29.122126-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":"=== RUN TestSanitySha2_256\n"} -{"Time":"2021-09-24T11:27:29.122143-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":"--- PASS: TestSanitySha2_256 (0.00s)\n"} -{"Time":"2021-09-24T11:27:29.122155-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Elapsed":0} -{"Time":"2021-09-24T11:27:29.122161-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384"} -{"Time":"2021-09-24T11:27:29.122168-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Output":"=== RUN TestSanitySha2_384\n"} -{"Time":"2021-09-24T11:27:29.122175-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Output":"--- PASS: TestSanitySha2_384 (0.00s)\n"} -{"Time":"2021-09-24T11:27:29.122184-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Elapsed":0} -{"Time":"2021-09-24T11:27:29.122189-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128"} -{"Time":"2021-09-24T11:27:29.122194-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Output":"=== RUN TestSanityKmac128\n"} -{"Time":"2021-09-24T11:27:29.122205-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Output":"--- PASS: TestSanityKmac128 (0.00s)\n"} -{"Time":"2021-09-24T11:27:29.122212-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Elapsed":0} -{"Time":"2021-09-24T11:27:29.122216-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI"} -{"Time":"2021-09-24T11:27:29.122273-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":"=== RUN TestHashersAPI\n"} -{"Time":"2021-09-24T11:27:29.122293-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":" hash_test.go:114: math rand seed is 1632497249121800000\n"} -{"Time":"2021-09-24T11:27:29.122332-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":"--- PASS: TestHashersAPI (0.00s)\n"} -{"Time":"2021-09-24T11:27:29.122339-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Elapsed":0} -{"Time":"2021-09-24T11:27:29.122344-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3"} -{"Time":"2021-09-24T11:27:29.122351-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":"=== RUN TestSha3\n"} -{"Time":"2021-09-24T11:27:29.122663-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":" hash_test.go:158: math rand seed is 1632497249122032000\n"} -{"Time":"2021-09-24T11:27:29.122678-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256"} -{"Time":"2021-09-24T11:27:29.122724-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Output":"=== RUN TestSha3/SHA3_256\n"} -{"Time":"2021-09-24T11:27:29.223618-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384"} -{"Time":"2021-09-24T11:27:29.223646-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Output":"=== RUN TestSha3/SHA3_384\n"} -{"Time":"2021-09-24T11:27:29.347325-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":"--- PASS: TestSha3 (0.23s)\n"} -{"Time":"2021-09-24T11:27:29.347372-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Output":" --- PASS: TestSha3/SHA3_256 (0.10s)\n"} -{"Time":"2021-09-24T11:27:29.347376-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Elapsed":0.1} -{"Time":"2021-09-24T11:27:29.347401-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Output":" --- PASS: TestSha3/SHA3_384 (0.12s)\n"} -{"Time":"2021-09-24T11:27:29.347405-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Elapsed":0.12} -{"Time":"2021-09-24T11:27:29.347408-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Elapsed":0.23} -{"Time":"2021-09-24T11:27:29.347412-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Output":"PASS\n"} -{"Time":"2021-09-24T11:27:29.348371-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Output":"ok \tgithub.com/onflow/flow-go/crypto/hash\t0.349s\n"} -{"Time":"2021-09-24T11:27:29.348417-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Elapsed":0.349} \ No newline at end of file +{"Time":"2021-09-24T11:27:29.121674-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256"} +{"Time":"2021-09-24T11:27:29.121944-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Output":"=== RUN TestSanitySha3_256\n"} +{"Time":"2021-09-24T11:27:29.121969-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Output":"--- PASS: TestSanitySha3_256 (0.00s)\n"} +{"Time":"2021-09-24T11:27:29.121973-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Elapsed":0} +{"Time":"2021-09-24T11:27:29.121993-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384"} +{"Time":"2021-09-24T11:27:29.121996-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Output":"=== RUN TestSanitySha3_384\n"} +{"Time":"2021-09-24T11:27:29.122-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Output":"--- PASS: TestSanitySha3_384 (0.00s)\n"} +{"Time":"2021-09-24T11:27:29.122003-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Elapsed":0} +{"Time":"2021-09-24T11:27:29.122006-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256"} +{"Time":"2021-09-24T11:27:29.122126-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":"=== RUN TestSanitySha2_256\n"} +{"Time":"2021-09-24T11:27:29.122143-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":"--- PASS: TestSanitySha2_256 (0.00s)\n"} +{"Time":"2021-09-24T11:27:29.122155-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Elapsed":0} +{"Time":"2021-09-24T11:27:29.122161-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384"} +{"Time":"2021-09-24T11:27:29.122168-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Output":"=== RUN TestSanitySha2_384\n"} +{"Time":"2021-09-24T11:27:29.122175-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Output":"--- PASS: TestSanitySha2_384 (0.00s)\n"} +{"Time":"2021-09-24T11:27:29.122184-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Elapsed":0} +{"Time":"2021-09-24T11:27:29.122189-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128"} +{"Time":"2021-09-24T11:27:29.122194-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Output":"=== RUN TestSanityKmac128\n"} +{"Time":"2021-09-24T11:27:29.122205-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Output":"--- PASS: TestSanityKmac128 (0.00s)\n"} +{"Time":"2021-09-24T11:27:29.122212-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Elapsed":0} +{"Time":"2021-09-24T11:27:29.122216-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI"} +{"Time":"2021-09-24T11:27:29.122273-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":"=== RUN TestHashersAPI\n"} +{"Time":"2021-09-24T11:27:29.122293-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":" hash_test.go:114: math rand seed is 1632497249121800000\n"} +{"Time":"2021-09-24T11:27:29.122332-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":"--- PASS: TestHashersAPI (0.00s)\n"} +{"Time":"2021-09-24T11:27:29.122339-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Elapsed":0} +{"Time":"2021-09-24T11:27:29.122344-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3"} +{"Time":"2021-09-24T11:27:29.122351-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":"=== RUN TestSha3\n"} +{"Time":"2021-09-24T11:27:29.122663-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":" hash_test.go:158: math rand seed is 1632497249122032000\n"} +{"Time":"2021-09-24T11:27:29.122678-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256"} +{"Time":"2021-09-24T11:27:29.122724-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Output":"=== RUN TestSha3/SHA3_256\n"} +{"Time":"2021-09-24T11:27:29.223618-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384"} +{"Time":"2021-09-24T11:27:29.223646-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Output":"=== RUN TestSha3/SHA3_384\n"} +{"Time":"2021-09-24T11:27:29.347325-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":"--- PASS: TestSha3 (0.23s)\n"} +{"Time":"2021-09-24T11:27:29.347372-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Output":" --- PASS: TestSha3/SHA3_256 (0.10s)\n"} +{"Time":"2021-09-24T11:27:29.347376-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Elapsed":0.1} +{"Time":"2021-09-24T11:27:29.347401-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Output":" --- PASS: TestSha3/SHA3_384 (0.12s)\n"} +{"Time":"2021-09-24T11:27:29.347405-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Elapsed":0.12} +{"Time":"2021-09-24T11:27:29.347408-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Elapsed":0.23} +{"Time":"2021-09-24T11:27:29.347412-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Output":"PASS\n"} +{"Time":"2021-09-24T11:27:29.348371-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Output":"ok \tgithub.com/onflow/crypto/hash\t0.349s\n"} +{"Time":"2021-09-24T11:27:29.348417-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Elapsed":0.349} \ No newline at end of file diff --git a/tools/test_monitor/testdata/summary1/raw/test-result-crypto-hash-1-count-skip-pass.json b/tools/test_monitor/testdata/summary1/raw/test-result-crypto-hash-1-count-skip-pass.json index 0236cfc342b..5f27a6040b7 100644 --- a/tools/test_monitor/testdata/summary1/raw/test-result-crypto-hash-1-count-skip-pass.json +++ b/tools/test_monitor/testdata/summary1/raw/test-result-crypto-hash-1-count-skip-pass.json @@ -1,42 +1,42 @@ -{"Time":"2021-10-06T07:11:37.589443-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256"} -{"Time":"2021-10-06T07:11:37.58988-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Output":"=== RUN TestSanitySha3_256\n"} -{"Time":"2021-10-06T07:11:37.589928-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Output":"--- PASS: TestSanitySha3_256 (0.00s)\n"} -{"Time":"2021-10-06T07:11:37.589942-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Elapsed":0} -{"Time":"2021-10-06T07:11:37.589967-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384"} -{"Time":"2021-10-06T07:11:37.589977-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Output":"=== RUN TestSanitySha3_384\n"} -{"Time":"2021-10-06T07:11:37.589987-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Output":"--- PASS: TestSanitySha3_384 (0.00s)\n"} -{"Time":"2021-10-06T07:11:37.589999-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Elapsed":0} -{"Time":"2021-10-06T07:11:37.590008-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256"} -{"Time":"2021-10-06T07:11:37.590017-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":"=== RUN TestSanitySha2_256\n"} -{"Time":"2021-10-06T07:11:37.590029-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" hash_test.go:36: SKIP [TEST_TODO]: skip for testing\n"} -{"Time":"2021-10-06T07:11:37.590039-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":"--- SKIP: TestSanitySha2_256 (0.00s)\n"} -{"Time":"2021-10-06T07:11:37.590049-04:00","Action":"skip","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Elapsed":0} -{"Time":"2021-10-06T07:11:37.590061-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384"} -{"Time":"2021-10-06T07:11:37.590073-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Output":"=== RUN TestSanitySha2_384\n"} -{"Time":"2021-10-06T07:11:37.590087-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Output":"--- PASS: TestSanitySha2_384 (0.00s)\n"} -{"Time":"2021-10-06T07:11:37.5901-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Elapsed":0} -{"Time":"2021-10-06T07:11:37.590114-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128"} -{"Time":"2021-10-06T07:11:37.590137-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Output":"=== RUN TestSanityKmac128\n"} -{"Time":"2021-10-06T07:11:37.590159-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Output":"--- PASS: TestSanityKmac128 (0.00s)\n"} -{"Time":"2021-10-06T07:11:37.590169-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Elapsed":0} -{"Time":"2021-10-06T07:11:37.590178-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI"} -{"Time":"2021-10-06T07:11:37.590186-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":"=== RUN TestHashersAPI\n"} -{"Time":"2021-10-06T07:11:37.5902-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":" hash_test.go:96: SKIP [TEST_TODO]: skip for testing\n"} -{"Time":"2021-10-06T07:11:37.590209-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":"--- SKIP: TestHashersAPI (0.00s)\n"} -{"Time":"2021-10-06T07:11:37.590246-04:00","Action":"skip","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Elapsed":0} -{"Time":"2021-10-06T07:11:37.590253-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3"} -{"Time":"2021-10-06T07:11:37.590261-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":"=== RUN TestSha3\n"} -{"Time":"2021-10-06T07:11:37.590268-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":" hash_test.go:160: math rand seed is 1633518697589650000\n"} -{"Time":"2021-10-06T07:11:37.590283-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256"} -{"Time":"2021-10-06T07:11:37.590289-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Output":"=== RUN TestSha3/SHA3_256\n"} -{"Time":"2021-10-06T07:11:37.701578-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384"} -{"Time":"2021-10-06T07:11:37.701615-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Output":"=== RUN TestSha3/SHA3_384\n"} -{"Time":"2021-10-06T07:11:37.828418-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":"--- PASS: TestSha3 (0.24s)\n"} -{"Time":"2021-10-06T07:11:37.828497-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Output":" --- PASS: TestSha3/SHA3_256 (0.11s)\n"} -{"Time":"2021-10-06T07:11:37.828512-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Elapsed":0.11} -{"Time":"2021-10-06T07:11:37.82853-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Output":" --- PASS: TestSha3/SHA3_384 (0.13s)\n"} -{"Time":"2021-10-06T07:11:37.828545-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Elapsed":0.13} -{"Time":"2021-10-06T07:11:37.828557-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Elapsed":0.24} -{"Time":"2021-10-06T07:11:37.828563-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Output":"PASS\n"} -{"Time":"2021-10-06T07:11:37.829672-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Output":"ok \tgithub.com/onflow/flow-go/crypto/hash\t0.446s\n"} -{"Time":"2021-10-06T07:11:37.829738-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Elapsed":0.446} +{"Time":"2021-10-06T07:11:37.589443-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256"} +{"Time":"2021-10-06T07:11:37.58988-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Output":"=== RUN TestSanitySha3_256\n"} +{"Time":"2021-10-06T07:11:37.589928-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Output":"--- PASS: TestSanitySha3_256 (0.00s)\n"} +{"Time":"2021-10-06T07:11:37.589942-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Elapsed":0} +{"Time":"2021-10-06T07:11:37.589967-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384"} +{"Time":"2021-10-06T07:11:37.589977-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Output":"=== RUN TestSanitySha3_384\n"} +{"Time":"2021-10-06T07:11:37.589987-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Output":"--- PASS: TestSanitySha3_384 (0.00s)\n"} +{"Time":"2021-10-06T07:11:37.589999-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Elapsed":0} +{"Time":"2021-10-06T07:11:37.590008-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256"} +{"Time":"2021-10-06T07:11:37.590017-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":"=== RUN TestSanitySha2_256\n"} +{"Time":"2021-10-06T07:11:37.590029-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" hash_test.go:36: SKIP [TEST_TODO]: skip for testing\n"} +{"Time":"2021-10-06T07:11:37.590039-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":"--- SKIP: TestSanitySha2_256 (0.00s)\n"} +{"Time":"2021-10-06T07:11:37.590049-04:00","Action":"skip","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Elapsed":0} +{"Time":"2021-10-06T07:11:37.590061-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384"} +{"Time":"2021-10-06T07:11:37.590073-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Output":"=== RUN TestSanitySha2_384\n"} +{"Time":"2021-10-06T07:11:37.590087-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Output":"--- PASS: TestSanitySha2_384 (0.00s)\n"} +{"Time":"2021-10-06T07:11:37.5901-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Elapsed":0} +{"Time":"2021-10-06T07:11:37.590114-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128"} +{"Time":"2021-10-06T07:11:37.590137-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Output":"=== RUN TestSanityKmac128\n"} +{"Time":"2021-10-06T07:11:37.590159-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Output":"--- PASS: TestSanityKmac128 (0.00s)\n"} +{"Time":"2021-10-06T07:11:37.590169-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Elapsed":0} +{"Time":"2021-10-06T07:11:37.590178-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI"} +{"Time":"2021-10-06T07:11:37.590186-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":"=== RUN TestHashersAPI\n"} +{"Time":"2021-10-06T07:11:37.5902-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":" hash_test.go:96: SKIP [TEST_TODO]: skip for testing\n"} +{"Time":"2021-10-06T07:11:37.590209-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":"--- SKIP: TestHashersAPI (0.00s)\n"} +{"Time":"2021-10-06T07:11:37.590246-04:00","Action":"skip","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Elapsed":0} +{"Time":"2021-10-06T07:11:37.590253-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3"} +{"Time":"2021-10-06T07:11:37.590261-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":"=== RUN TestSha3\n"} +{"Time":"2021-10-06T07:11:37.590268-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":" hash_test.go:160: math rand seed is 1633518697589650000\n"} +{"Time":"2021-10-06T07:11:37.590283-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256"} +{"Time":"2021-10-06T07:11:37.590289-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Output":"=== RUN TestSha3/SHA3_256\n"} +{"Time":"2021-10-06T07:11:37.701578-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384"} +{"Time":"2021-10-06T07:11:37.701615-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Output":"=== RUN TestSha3/SHA3_384\n"} +{"Time":"2021-10-06T07:11:37.828418-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":"--- PASS: TestSha3 (0.24s)\n"} +{"Time":"2021-10-06T07:11:37.828497-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Output":" --- PASS: TestSha3/SHA3_256 (0.11s)\n"} +{"Time":"2021-10-06T07:11:37.828512-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Elapsed":0.11} +{"Time":"2021-10-06T07:11:37.82853-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Output":" --- PASS: TestSha3/SHA3_384 (0.13s)\n"} +{"Time":"2021-10-06T07:11:37.828545-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Elapsed":0.13} +{"Time":"2021-10-06T07:11:37.828557-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Elapsed":0.24} +{"Time":"2021-10-06T07:11:37.828563-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Output":"PASS\n"} +{"Time":"2021-10-06T07:11:37.829672-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Output":"ok \tgithub.com/onflow/crypto/hash\t0.446s\n"} +{"Time":"2021-10-06T07:11:37.829738-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Elapsed":0.446} diff --git a/tools/test_monitor/testdata/summary1/raw/test-result-crypto-hash-10-count-fail.json b/tools/test_monitor/testdata/summary1/raw/test-result-crypto-hash-10-count-fail.json index 7c1203f336c..a594c74fb0c 100644 --- a/tools/test_monitor/testdata/summary1/raw/test-result-crypto-hash-10-count-fail.json +++ b/tools/test_monitor/testdata/summary1/raw/test-result-crypto-hash-10-count-fail.json @@ -1,523 +1,523 @@ -{"Time":"2021-09-27T06:48:02.184192-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256"} -{"Time":"2021-09-27T06:48:02.184694-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Output":"=== RUN TestSanitySha3_256\n"} -{"Time":"2021-09-27T06:48:02.18473-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Output":"--- PASS: TestSanitySha3_256 (0.00s)\n"} -{"Time":"2021-09-27T06:48:02.184738-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Elapsed":0} -{"Time":"2021-09-27T06:48:02.184759-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384"} -{"Time":"2021-09-27T06:48:02.184765-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Output":"=== RUN TestSanitySha3_384\n"} -{"Time":"2021-09-27T06:48:02.184772-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Output":"--- PASS: TestSanitySha3_384 (0.00s)\n"} -{"Time":"2021-09-27T06:48:02.184778-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Elapsed":0} -{"Time":"2021-09-27T06:48:02.184784-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256"} -{"Time":"2021-09-27T06:48:02.184913-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":"=== RUN TestSanitySha2_256\n"} -{"Time":"2021-09-27T06:48:02.184947-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" hash_test.go:41: \n"} -{"Time":"2021-09-27T06:48:02.184971-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \tError Trace:\thash_test.go:41\n"} -{"Time":"2021-09-27T06:48:02.184993-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \tError: \tNot equal: \n"} -{"Time":"2021-09-27T06:48:02.185002-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \texpected: hash.Hash{0x9f, 0x86, 0xd0, 0x81, 0x88, 0x4c, 0x7d, 0x65, 0x9a, 0x2f, 0xea, 0xa0, 0xc5, 0x5a, 0xd0, 0x15, 0xa3, 0xbf, 0x4f, 0x1b, 0x2b, 0xb, 0x82, 0x2c, 0xd1, 0x5d, 0x6c, 0x15, 0xb0, 0xf0, 0xa, 0x9}\n"} -{"Time":"2021-09-27T06:48:02.185025-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \tactual : hash.Hash{0x9f, 0x86, 0xd0, 0x81, 0x88, 0x4c, 0x7d, 0x65, 0x9a, 0x2f, 0xea, 0xa0, 0xc5, 0x5a, 0xd0, 0x15, 0xa3, 0xbf, 0x4f, 0x1b, 0x2b, 0xb, 0x82, 0x2c, 0xd1, 0x5d, 0x6c, 0x15, 0xb0, 0xf0, 0xa, 0x8}\n"} -{"Time":"2021-09-27T06:48:02.185037-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t\n"} -{"Time":"2021-09-27T06:48:02.185046-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \tDiff:\n"} -{"Time":"2021-09-27T06:48:02.185055-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t--- Expected\n"} -{"Time":"2021-09-27T06:48:02.185063-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t+++ Actual\n"} -{"Time":"2021-09-27T06:48:02.185101-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t@@ -1,2 +1,2 @@\n"} -{"Time":"2021-09-27T06:48:02.185122-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t-(hash.Hash) (len=32) 0x9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a09\n"} -{"Time":"2021-09-27T06:48:02.185145-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t+(hash.Hash) (len=32) 0x9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08\n"} -{"Time":"2021-09-27T06:48:02.185155-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t \n"} -{"Time":"2021-09-27T06:48:02.185164-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \tTest: \tTestSanitySha2_256\n"} -{"Time":"2021-09-27T06:48:02.185182-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":"--- FAIL: TestSanitySha2_256 (0.00s)\n"} -{"Time":"2021-09-27T06:48:02.185192-04:00","Action":"fail","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Elapsed":0} -{"Time":"2021-09-27T06:48:02.185199-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384"} -{"Time":"2021-09-27T06:48:02.185208-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Output":"=== RUN TestSanitySha2_384\n"} -{"Time":"2021-09-27T06:48:02.185218-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Output":"--- PASS: TestSanitySha2_384 (0.00s)\n"} -{"Time":"2021-09-27T06:48:02.185239-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Elapsed":0} -{"Time":"2021-09-27T06:48:02.185245-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128"} -{"Time":"2021-09-27T06:48:02.185261-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Output":"=== RUN TestSanityKmac128\n"} -{"Time":"2021-09-27T06:48:02.185271-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Output":"--- PASS: TestSanityKmac128 (0.00s)\n"} -{"Time":"2021-09-27T06:48:02.18528-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Elapsed":0} -{"Time":"2021-09-27T06:48:02.185289-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI"} -{"Time":"2021-09-27T06:48:02.185297-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":"=== RUN TestHashersAPI\n"} -{"Time":"2021-09-27T06:48:02.185307-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":" hash_test.go:114: math rand seed is 1632739682184421000\n"} -{"Time":"2021-09-27T06:48:02.185335-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":"--- PASS: TestHashersAPI (0.00s)\n"} -{"Time":"2021-09-27T06:48:02.185346-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Elapsed":0} -{"Time":"2021-09-27T06:48:02.185354-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3"} -{"Time":"2021-09-27T06:48:02.185376-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":"=== RUN TestSha3\n"} -{"Time":"2021-09-27T06:48:02.185386-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":" hash_test.go:158: math rand seed is 1632739682184858000\n"} -{"Time":"2021-09-27T06:48:02.185396-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256"} -{"Time":"2021-09-27T06:48:02.185404-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Output":"=== RUN TestSha3/SHA3_256\n"} -{"Time":"2021-09-27T06:48:02.294081-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384"} -{"Time":"2021-09-27T06:48:02.294138-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Output":"=== RUN TestSha3/SHA3_384\n"} -{"Time":"2021-09-27T06:48:02.415091-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":"--- PASS: TestSha3 (0.23s)\n"} -{"Time":"2021-09-27T06:48:02.415117-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Output":" --- PASS: TestSha3/SHA3_256 (0.11s)\n"} -{"Time":"2021-09-27T06:48:02.415121-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Elapsed":0.11} -{"Time":"2021-09-27T06:48:02.415206-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Output":" --- PASS: TestSha3/SHA3_384 (0.12s)\n"} -{"Time":"2021-09-27T06:48:02.41522-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Elapsed":0.12} -{"Time":"2021-09-27T06:48:02.415224-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Elapsed":0.23} -{"Time":"2021-09-27T06:48:02.415227-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256"} -{"Time":"2021-09-27T06:48:02.41523-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Output":"=== RUN TestSanitySha3_256\n"} -{"Time":"2021-09-27T06:48:02.415234-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Output":"--- PASS: TestSanitySha3_256 (0.00s)\n"} -{"Time":"2021-09-27T06:48:02.415237-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Elapsed":0} -{"Time":"2021-09-27T06:48:02.415322-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384"} -{"Time":"2021-09-27T06:48:02.415329-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Output":"=== RUN TestSanitySha3_384\n"} -{"Time":"2021-09-27T06:48:02.415335-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Output":"--- PASS: TestSanitySha3_384 (0.00s)\n"} -{"Time":"2021-09-27T06:48:02.415344-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Elapsed":0} -{"Time":"2021-09-27T06:48:02.415349-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256"} -{"Time":"2021-09-27T06:48:02.415354-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":"=== RUN TestSanitySha2_256\n"} -{"Time":"2021-09-27T06:48:02.415361-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" hash_test.go:41: \n"} -{"Time":"2021-09-27T06:48:02.415364-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \tError Trace:\thash_test.go:41\n"} -{"Time":"2021-09-27T06:48:02.415368-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \tError: \tNot equal: \n"} -{"Time":"2021-09-27T06:48:02.415371-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \texpected: hash.Hash{0x9f, 0x86, 0xd0, 0x81, 0x88, 0x4c, 0x7d, 0x65, 0x9a, 0x2f, 0xea, 0xa0, 0xc5, 0x5a, 0xd0, 0x15, 0xa3, 0xbf, 0x4f, 0x1b, 0x2b, 0xb, 0x82, 0x2c, 0xd1, 0x5d, 0x6c, 0x15, 0xb0, 0xf0, 0xa, 0x9}\n"} -{"Time":"2021-09-27T06:48:02.415406-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \tactual : hash.Hash{0x9f, 0x86, 0xd0, 0x81, 0x88, 0x4c, 0x7d, 0x65, 0x9a, 0x2f, 0xea, 0xa0, 0xc5, 0x5a, 0xd0, 0x15, 0xa3, 0xbf, 0x4f, 0x1b, 0x2b, 0xb, 0x82, 0x2c, 0xd1, 0x5d, 0x6c, 0x15, 0xb0, 0xf0, 0xa, 0x8}\n"} -{"Time":"2021-09-27T06:48:02.41542-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t\n"} -{"Time":"2021-09-27T06:48:02.415436-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \tDiff:\n"} -{"Time":"2021-09-27T06:48:02.415442-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t--- Expected\n"} -{"Time":"2021-09-27T06:48:02.415454-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t+++ Actual\n"} -{"Time":"2021-09-27T06:48:02.415467-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t@@ -1,2 +1,2 @@\n"} -{"Time":"2021-09-27T06:48:02.415476-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t-(hash.Hash) (len=32) 0x9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a09\n"} -{"Time":"2021-09-27T06:48:02.415481-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t+(hash.Hash) (len=32) 0x9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08\n"} -{"Time":"2021-09-27T06:48:02.415487-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t \n"} -{"Time":"2021-09-27T06:48:02.415492-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \tTest: \tTestSanitySha2_256\n"} -{"Time":"2021-09-27T06:48:02.415498-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":"--- FAIL: TestSanitySha2_256 (0.00s)\n"} -{"Time":"2021-09-27T06:48:02.415504-04:00","Action":"fail","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Elapsed":0} -{"Time":"2021-09-27T06:48:02.415507-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384"} -{"Time":"2021-09-27T06:48:02.41551-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Output":"=== RUN TestSanitySha2_384\n"} -{"Time":"2021-09-27T06:48:02.415514-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Output":"--- PASS: TestSanitySha2_384 (0.00s)\n"} -{"Time":"2021-09-27T06:48:02.415517-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Elapsed":0} -{"Time":"2021-09-27T06:48:02.41552-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128"} -{"Time":"2021-09-27T06:48:02.415548-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Output":"=== RUN TestSanityKmac128\n"} -{"Time":"2021-09-27T06:48:02.415557-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Output":"--- PASS: TestSanityKmac128 (0.00s)\n"} -{"Time":"2021-09-27T06:48:02.415563-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Elapsed":0} -{"Time":"2021-09-27T06:48:02.415568-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI"} -{"Time":"2021-09-27T06:48:02.415573-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":"=== RUN TestHashersAPI\n"} -{"Time":"2021-09-27T06:48:02.415578-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":" hash_test.go:114: math rand seed is 1632739682415309000\n"} -{"Time":"2021-09-27T06:48:02.415609-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":"--- PASS: TestHashersAPI (0.00s)\n"} -{"Time":"2021-09-27T06:48:02.415656-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Elapsed":0} -{"Time":"2021-09-27T06:48:02.415675-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3"} -{"Time":"2021-09-27T06:48:02.41568-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":"=== RUN TestSha3\n"} -{"Time":"2021-09-27T06:48:02.415697-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":" hash_test.go:158: math rand seed is 1632739682415616000\n"} -{"Time":"2021-09-27T06:48:02.415702-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256"} -{"Time":"2021-09-27T06:48:02.415706-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Output":"=== RUN TestSha3/SHA3_256\n"} -{"Time":"2021-09-27T06:48:02.51693-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384"} -{"Time":"2021-09-27T06:48:02.516956-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Output":"=== RUN TestSha3/SHA3_384\n"} -{"Time":"2021-09-27T06:48:02.636991-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":"--- PASS: TestSha3 (0.22s)\n"} -{"Time":"2021-09-27T06:48:02.637017-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Output":" --- PASS: TestSha3/SHA3_256 (0.10s)\n"} -{"Time":"2021-09-27T06:48:02.637022-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Elapsed":0.1} -{"Time":"2021-09-27T06:48:02.637027-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Output":" --- PASS: TestSha3/SHA3_384 (0.12s)\n"} -{"Time":"2021-09-27T06:48:02.637031-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Elapsed":0.12} -{"Time":"2021-09-27T06:48:02.637034-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Elapsed":0.22} -{"Time":"2021-09-27T06:48:02.637037-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256"} -{"Time":"2021-09-27T06:48:02.637059-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Output":"=== RUN TestSanitySha3_256\n"} -{"Time":"2021-09-27T06:48:02.637075-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Output":"--- PASS: TestSanitySha3_256 (0.00s)\n"} -{"Time":"2021-09-27T06:48:02.637095-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Elapsed":0} -{"Time":"2021-09-27T06:48:02.6371-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384"} -{"Time":"2021-09-27T06:48:02.637103-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Output":"=== RUN TestSanitySha3_384\n"} -{"Time":"2021-09-27T06:48:02.637117-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Output":"--- PASS: TestSanitySha3_384 (0.00s)\n"} -{"Time":"2021-09-27T06:48:02.637177-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Elapsed":0} -{"Time":"2021-09-27T06:48:02.637187-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256"} -{"Time":"2021-09-27T06:48:02.63719-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":"=== RUN TestSanitySha2_256\n"} -{"Time":"2021-09-27T06:48:02.637196-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" hash_test.go:41: \n"} -{"Time":"2021-09-27T06:48:02.6372-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \tError Trace:\thash_test.go:41\n"} -{"Time":"2021-09-27T06:48:02.637203-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \tError: \tNot equal: \n"} -{"Time":"2021-09-27T06:48:02.637231-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \texpected: hash.Hash{0x9f, 0x86, 0xd0, 0x81, 0x88, 0x4c, 0x7d, 0x65, 0x9a, 0x2f, 0xea, 0xa0, 0xc5, 0x5a, 0xd0, 0x15, 0xa3, 0xbf, 0x4f, 0x1b, 0x2b, 0xb, 0x82, 0x2c, 0xd1, 0x5d, 0x6c, 0x15, 0xb0, 0xf0, 0xa, 0x9}\n"} -{"Time":"2021-09-27T06:48:02.637241-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \tactual : hash.Hash{0x9f, 0x86, 0xd0, 0x81, 0x88, 0x4c, 0x7d, 0x65, 0x9a, 0x2f, 0xea, 0xa0, 0xc5, 0x5a, 0xd0, 0x15, 0xa3, 0xbf, 0x4f, 0x1b, 0x2b, 0xb, 0x82, 0x2c, 0xd1, 0x5d, 0x6c, 0x15, 0xb0, 0xf0, 0xa, 0x8}\n"} -{"Time":"2021-09-27T06:48:02.637248-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t\n"} -{"Time":"2021-09-27T06:48:02.637265-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \tDiff:\n"} -{"Time":"2021-09-27T06:48:02.63729-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t--- Expected\n"} -{"Time":"2021-09-27T06:48:02.637294-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t+++ Actual\n"} -{"Time":"2021-09-27T06:48:02.637298-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t@@ -1,2 +1,2 @@\n"} -{"Time":"2021-09-27T06:48:02.637301-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t-(hash.Hash) (len=32) 0x9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a09\n"} -{"Time":"2021-09-27T06:48:02.637305-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t+(hash.Hash) (len=32) 0x9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08\n"} -{"Time":"2021-09-27T06:48:02.637309-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t \n"} -{"Time":"2021-09-27T06:48:02.637334-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \tTest: \tTestSanitySha2_256\n"} -{"Time":"2021-09-27T06:48:02.63734-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":"--- FAIL: TestSanitySha2_256 (0.00s)\n"} -{"Time":"2021-09-27T06:48:02.637343-04:00","Action":"fail","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Elapsed":0} -{"Time":"2021-09-27T06:48:02.637346-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384"} -{"Time":"2021-09-27T06:48:02.637352-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Output":"=== RUN TestSanitySha2_384\n"} -{"Time":"2021-09-27T06:48:02.637358-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Output":"--- PASS: TestSanitySha2_384 (0.00s)\n"} -{"Time":"2021-09-27T06:48:02.637376-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Elapsed":0} -{"Time":"2021-09-27T06:48:02.63738-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128"} -{"Time":"2021-09-27T06:48:02.637383-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Output":"=== RUN TestSanityKmac128\n"} -{"Time":"2021-09-27T06:48:02.637386-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Output":"--- PASS: TestSanityKmac128 (0.00s)\n"} -{"Time":"2021-09-27T06:48:02.63739-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Elapsed":0} -{"Time":"2021-09-27T06:48:02.637392-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI"} -{"Time":"2021-09-27T06:48:02.637404-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":"=== RUN TestHashersAPI\n"} -{"Time":"2021-09-27T06:48:02.637408-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":" hash_test.go:114: math rand seed is 1632739682637108000\n"} -{"Time":"2021-09-27T06:48:02.637414-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":"--- PASS: TestHashersAPI (0.00s)\n"} -{"Time":"2021-09-27T06:48:02.637417-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Elapsed":0} -{"Time":"2021-09-27T06:48:02.63742-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3"} -{"Time":"2021-09-27T06:48:02.637423-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":"=== RUN TestSha3\n"} -{"Time":"2021-09-27T06:48:02.637426-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":" hash_test.go:158: math rand seed is 1632739682637311000\n"} -{"Time":"2021-09-27T06:48:02.637466-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256"} -{"Time":"2021-09-27T06:48:02.637491-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Output":"=== RUN TestSha3/SHA3_256\n"} -{"Time":"2021-09-27T06:48:02.736502-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384"} -{"Time":"2021-09-27T06:48:02.736566-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Output":"=== RUN TestSha3/SHA3_384\n"} -{"Time":"2021-09-27T06:48:02.857319-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":"--- PASS: TestSha3 (0.22s)\n"} -{"Time":"2021-09-27T06:48:02.85738-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Output":" --- PASS: TestSha3/SHA3_256 (0.10s)\n"} -{"Time":"2021-09-27T06:48:02.857386-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Elapsed":0.1} -{"Time":"2021-09-27T06:48:02.8574-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Output":" --- PASS: TestSha3/SHA3_384 (0.12s)\n"} -{"Time":"2021-09-27T06:48:02.857404-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Elapsed":0.12} -{"Time":"2021-09-27T06:48:02.857408-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Elapsed":0.22} -{"Time":"2021-09-27T06:48:02.857413-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256"} -{"Time":"2021-09-27T06:48:02.857417-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Output":"=== RUN TestSanitySha3_256\n"} -{"Time":"2021-09-27T06:48:02.857426-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Output":"--- PASS: TestSanitySha3_256 (0.00s)\n"} -{"Time":"2021-09-27T06:48:02.85743-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Elapsed":0} -{"Time":"2021-09-27T06:48:02.857433-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384"} -{"Time":"2021-09-27T06:48:02.857437-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Output":"=== RUN TestSanitySha3_384\n"} -{"Time":"2021-09-27T06:48:02.857441-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Output":"--- PASS: TestSanitySha3_384 (0.00s)\n"} -{"Time":"2021-09-27T06:48:02.857541-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Elapsed":0} -{"Time":"2021-09-27T06:48:02.857552-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256"} -{"Time":"2021-09-27T06:48:02.857557-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":"=== RUN TestSanitySha2_256\n"} -{"Time":"2021-09-27T06:48:02.857565-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" hash_test.go:41: \n"} -{"Time":"2021-09-27T06:48:02.857568-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \tError Trace:\thash_test.go:41\n"} -{"Time":"2021-09-27T06:48:02.857572-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \tError: \tNot equal: \n"} -{"Time":"2021-09-27T06:48:02.857588-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \texpected: hash.Hash{0x9f, 0x86, 0xd0, 0x81, 0x88, 0x4c, 0x7d, 0x65, 0x9a, 0x2f, 0xea, 0xa0, 0xc5, 0x5a, 0xd0, 0x15, 0xa3, 0xbf, 0x4f, 0x1b, 0x2b, 0xb, 0x82, 0x2c, 0xd1, 0x5d, 0x6c, 0x15, 0xb0, 0xf0, 0xa, 0x9}\n"} -{"Time":"2021-09-27T06:48:02.857594-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \tactual : hash.Hash{0x9f, 0x86, 0xd0, 0x81, 0x88, 0x4c, 0x7d, 0x65, 0x9a, 0x2f, 0xea, 0xa0, 0xc5, 0x5a, 0xd0, 0x15, 0xa3, 0xbf, 0x4f, 0x1b, 0x2b, 0xb, 0x82, 0x2c, 0xd1, 0x5d, 0x6c, 0x15, 0xb0, 0xf0, 0xa, 0x8}\n"} -{"Time":"2021-09-27T06:48:02.857613-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t\n"} -{"Time":"2021-09-27T06:48:02.857617-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \tDiff:\n"} -{"Time":"2021-09-27T06:48:02.85762-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t--- Expected\n"} -{"Time":"2021-09-27T06:48:02.857624-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t+++ Actual\n"} -{"Time":"2021-09-27T06:48:02.857639-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t@@ -1,2 +1,2 @@\n"} -{"Time":"2021-09-27T06:48:02.857644-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t-(hash.Hash) (len=32) 0x9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a09\n"} -{"Time":"2021-09-27T06:48:02.857647-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t+(hash.Hash) (len=32) 0x9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08\n"} -{"Time":"2021-09-27T06:48:02.857651-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t \n"} -{"Time":"2021-09-27T06:48:02.85766-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \tTest: \tTestSanitySha2_256\n"} -{"Time":"2021-09-27T06:48:02.857665-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":"--- FAIL: TestSanitySha2_256 (0.00s)\n"} -{"Time":"2021-09-27T06:48:02.857668-04:00","Action":"fail","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Elapsed":0} -{"Time":"2021-09-27T06:48:02.857671-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384"} -{"Time":"2021-09-27T06:48:02.857674-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Output":"=== RUN TestSanitySha2_384\n"} -{"Time":"2021-09-27T06:48:02.857678-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Output":"--- PASS: TestSanitySha2_384 (0.00s)\n"} -{"Time":"2021-09-27T06:48:02.857699-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Elapsed":0} -{"Time":"2021-09-27T06:48:02.857744-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128"} -{"Time":"2021-09-27T06:48:02.85775-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Output":"=== RUN TestSanityKmac128\n"} -{"Time":"2021-09-27T06:48:02.857757-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Output":"--- PASS: TestSanityKmac128 (0.00s)\n"} -{"Time":"2021-09-27T06:48:02.857782-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Elapsed":0} -{"Time":"2021-09-27T06:48:02.857789-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI"} -{"Time":"2021-09-27T06:48:02.857793-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":"=== RUN TestHashersAPI\n"} -{"Time":"2021-09-27T06:48:02.857796-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":" hash_test.go:114: math rand seed is 1632739682857435000\n"} -{"Time":"2021-09-27T06:48:02.857838-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":"--- PASS: TestHashersAPI (0.00s)\n"} -{"Time":"2021-09-27T06:48:02.857843-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Elapsed":0} -{"Time":"2021-09-27T06:48:02.857846-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3"} -{"Time":"2021-09-27T06:48:02.857849-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":"=== RUN TestSha3\n"} -{"Time":"2021-09-27T06:48:02.857852-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":" hash_test.go:158: math rand seed is 1632739682857668000\n"} -{"Time":"2021-09-27T06:48:02.857855-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256"} -{"Time":"2021-09-27T06:48:02.857859-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Output":"=== RUN TestSha3/SHA3_256\n"} -{"Time":"2021-09-27T06:48:02.956791-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384"} -{"Time":"2021-09-27T06:48:02.956849-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Output":"=== RUN TestSha3/SHA3_384\n"} -{"Time":"2021-09-27T06:48:03.076829-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":"--- PASS: TestSha3 (0.22s)\n"} -{"Time":"2021-09-27T06:48:03.076855-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Output":" --- PASS: TestSha3/SHA3_256 (0.10s)\n"} -{"Time":"2021-09-27T06:48:03.076859-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Elapsed":0.1} -{"Time":"2021-09-27T06:48:03.076866-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Output":" --- PASS: TestSha3/SHA3_384 (0.12s)\n"} -{"Time":"2021-09-27T06:48:03.076871-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Elapsed":0.12} -{"Time":"2021-09-27T06:48:03.076876-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Elapsed":0.22} -{"Time":"2021-09-27T06:48:03.07688-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256"} -{"Time":"2021-09-27T06:48:03.077032-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Output":"=== RUN TestSanitySha3_256\n"} -{"Time":"2021-09-27T06:48:03.077046-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Output":"--- PASS: TestSanitySha3_256 (0.00s)\n"} -{"Time":"2021-09-27T06:48:03.077053-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Elapsed":0} -{"Time":"2021-09-27T06:48:03.077056-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384"} -{"Time":"2021-09-27T06:48:03.077059-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Output":"=== RUN TestSanitySha3_384\n"} -{"Time":"2021-09-27T06:48:03.077063-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Output":"--- PASS: TestSanitySha3_384 (0.00s)\n"} -{"Time":"2021-09-27T06:48:03.077123-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Elapsed":0} -{"Time":"2021-09-27T06:48:03.07713-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256"} -{"Time":"2021-09-27T06:48:03.077133-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":"=== RUN TestSanitySha2_256\n"} -{"Time":"2021-09-27T06:48:03.077137-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" hash_test.go:41: \n"} -{"Time":"2021-09-27T06:48:03.07714-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \tError Trace:\thash_test.go:41\n"} -{"Time":"2021-09-27T06:48:03.077143-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \tError: \tNot equal: \n"} -{"Time":"2021-09-27T06:48:03.077156-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \texpected: hash.Hash{0x9f, 0x86, 0xd0, 0x81, 0x88, 0x4c, 0x7d, 0x65, 0x9a, 0x2f, 0xea, 0xa0, 0xc5, 0x5a, 0xd0, 0x15, 0xa3, 0xbf, 0x4f, 0x1b, 0x2b, 0xb, 0x82, 0x2c, 0xd1, 0x5d, 0x6c, 0x15, 0xb0, 0xf0, 0xa, 0x9}\n"} -{"Time":"2021-09-27T06:48:03.07716-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \tactual : hash.Hash{0x9f, 0x86, 0xd0, 0x81, 0x88, 0x4c, 0x7d, 0x65, 0x9a, 0x2f, 0xea, 0xa0, 0xc5, 0x5a, 0xd0, 0x15, 0xa3, 0xbf, 0x4f, 0x1b, 0x2b, 0xb, 0x82, 0x2c, 0xd1, 0x5d, 0x6c, 0x15, 0xb0, 0xf0, 0xa, 0x8}\n"} -{"Time":"2021-09-27T06:48:03.077172-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t\n"} -{"Time":"2021-09-27T06:48:03.077195-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \tDiff:\n"} -{"Time":"2021-09-27T06:48:03.077199-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t--- Expected\n"} -{"Time":"2021-09-27T06:48:03.077203-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t+++ Actual\n"} -{"Time":"2021-09-27T06:48:03.077207-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t@@ -1,2 +1,2 @@\n"} -{"Time":"2021-09-27T06:48:03.077212-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t-(hash.Hash) (len=32) 0x9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a09\n"} -{"Time":"2021-09-27T06:48:03.077224-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t+(hash.Hash) (len=32) 0x9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08\n"} -{"Time":"2021-09-27T06:48:03.077229-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t \n"} -{"Time":"2021-09-27T06:48:03.077232-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \tTest: \tTestSanitySha2_256\n"} -{"Time":"2021-09-27T06:48:03.077236-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":"--- FAIL: TestSanitySha2_256 (0.00s)\n"} -{"Time":"2021-09-27T06:48:03.077239-04:00","Action":"fail","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Elapsed":0} -{"Time":"2021-09-27T06:48:03.077273-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384"} -{"Time":"2021-09-27T06:48:03.077286-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Output":"=== RUN TestSanitySha2_384\n"} -{"Time":"2021-09-27T06:48:03.077294-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Output":"--- PASS: TestSanitySha2_384 (0.00s)\n"} -{"Time":"2021-09-27T06:48:03.077299-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Elapsed":0} -{"Time":"2021-09-27T06:48:03.077304-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128"} -{"Time":"2021-09-27T06:48:03.077308-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Output":"=== RUN TestSanityKmac128\n"} -{"Time":"2021-09-27T06:48:03.077313-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Output":"--- PASS: TestSanityKmac128 (0.00s)\n"} -{"Time":"2021-09-27T06:48:03.077326-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Elapsed":0} -{"Time":"2021-09-27T06:48:03.077338-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI"} -{"Time":"2021-09-27T06:48:03.077344-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":"=== RUN TestHashersAPI\n"} -{"Time":"2021-09-27T06:48:03.077349-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":" hash_test.go:114: math rand seed is 1632739683077064000\n"} -{"Time":"2021-09-27T06:48:03.077357-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":"--- PASS: TestHashersAPI (0.00s)\n"} -{"Time":"2021-09-27T06:48:03.07736-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Elapsed":0} -{"Time":"2021-09-27T06:48:03.077363-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3"} -{"Time":"2021-09-27T06:48:03.077366-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":"=== RUN TestSha3\n"} -{"Time":"2021-09-27T06:48:03.07737-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":" hash_test.go:158: math rand seed is 1632739683077268000\n"} -{"Time":"2021-09-27T06:48:03.077808-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256"} -{"Time":"2021-09-27T06:48:03.077826-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Output":"=== RUN TestSha3/SHA3_256\n"} -{"Time":"2021-09-27T06:48:03.176088-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384"} -{"Time":"2021-09-27T06:48:03.176175-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Output":"=== RUN TestSha3/SHA3_384\n"} -{"Time":"2021-09-27T06:48:03.297398-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":"--- PASS: TestSha3 (0.22s)\n"} -{"Time":"2021-09-27T06:48:03.297426-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Output":" --- PASS: TestSha3/SHA3_256 (0.10s)\n"} -{"Time":"2021-09-27T06:48:03.297431-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Elapsed":0.1} -{"Time":"2021-09-27T06:48:03.297439-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Output":" --- PASS: TestSha3/SHA3_384 (0.12s)\n"} -{"Time":"2021-09-27T06:48:03.297442-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Elapsed":0.12} -{"Time":"2021-09-27T06:48:03.297445-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Elapsed":0.22} -{"Time":"2021-09-27T06:48:03.297448-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256"} -{"Time":"2021-09-27T06:48:03.297467-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Output":"=== RUN TestSanitySha3_256\n"} -{"Time":"2021-09-27T06:48:03.297477-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Output":"--- PASS: TestSanitySha3_256 (0.00s)\n"} -{"Time":"2021-09-27T06:48:03.297482-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Elapsed":0} -{"Time":"2021-09-27T06:48:03.297485-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384"} -{"Time":"2021-09-27T06:48:03.297488-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Output":"=== RUN TestSanitySha3_384\n"} -{"Time":"2021-09-27T06:48:03.297491-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Output":"--- PASS: TestSanitySha3_384 (0.00s)\n"} -{"Time":"2021-09-27T06:48:03.297591-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Elapsed":0} -{"Time":"2021-09-27T06:48:03.297618-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256"} -{"Time":"2021-09-27T06:48:03.297624-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":"=== RUN TestSanitySha2_256\n"} -{"Time":"2021-09-27T06:48:03.297632-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" hash_test.go:41: \n"} -{"Time":"2021-09-27T06:48:03.297635-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \tError Trace:\thash_test.go:41\n"} -{"Time":"2021-09-27T06:48:03.297639-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \tError: \tNot equal: \n"} -{"Time":"2021-09-27T06:48:03.297642-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \texpected: hash.Hash{0x9f, 0x86, 0xd0, 0x81, 0x88, 0x4c, 0x7d, 0x65, 0x9a, 0x2f, 0xea, 0xa0, 0xc5, 0x5a, 0xd0, 0x15, 0xa3, 0xbf, 0x4f, 0x1b, 0x2b, 0xb, 0x82, 0x2c, 0xd1, 0x5d, 0x6c, 0x15, 0xb0, 0xf0, 0xa, 0x9}\n"} -{"Time":"2021-09-27T06:48:03.297646-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \tactual : hash.Hash{0x9f, 0x86, 0xd0, 0x81, 0x88, 0x4c, 0x7d, 0x65, 0x9a, 0x2f, 0xea, 0xa0, 0xc5, 0x5a, 0xd0, 0x15, 0xa3, 0xbf, 0x4f, 0x1b, 0x2b, 0xb, 0x82, 0x2c, 0xd1, 0x5d, 0x6c, 0x15, 0xb0, 0xf0, 0xa, 0x8}\n"} -{"Time":"2021-09-27T06:48:03.297678-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t\n"} -{"Time":"2021-09-27T06:48:03.297687-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \tDiff:\n"} -{"Time":"2021-09-27T06:48:03.297704-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t--- Expected\n"} -{"Time":"2021-09-27T06:48:03.297709-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t+++ Actual\n"} -{"Time":"2021-09-27T06:48:03.297714-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t@@ -1,2 +1,2 @@\n"} -{"Time":"2021-09-27T06:48:03.297738-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t-(hash.Hash) (len=32) 0x9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a09\n"} -{"Time":"2021-09-27T06:48:03.297751-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t+(hash.Hash) (len=32) 0x9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08\n"} -{"Time":"2021-09-27T06:48:03.297755-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t \n"} -{"Time":"2021-09-27T06:48:03.297759-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \tTest: \tTestSanitySha2_256\n"} -{"Time":"2021-09-27T06:48:03.297763-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":"--- FAIL: TestSanitySha2_256 (0.00s)\n"} -{"Time":"2021-09-27T06:48:03.297767-04:00","Action":"fail","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Elapsed":0} -{"Time":"2021-09-27T06:48:03.29777-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384"} -{"Time":"2021-09-27T06:48:03.297773-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Output":"=== RUN TestSanitySha2_384\n"} -{"Time":"2021-09-27T06:48:03.297776-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Output":"--- PASS: TestSanitySha2_384 (0.00s)\n"} -{"Time":"2021-09-27T06:48:03.297795-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Elapsed":0} -{"Time":"2021-09-27T06:48:03.2978-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128"} -{"Time":"2021-09-27T06:48:03.297802-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Output":"=== RUN TestSanityKmac128\n"} -{"Time":"2021-09-27T06:48:03.297806-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Output":"--- PASS: TestSanityKmac128 (0.00s)\n"} -{"Time":"2021-09-27T06:48:03.29781-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Elapsed":0} -{"Time":"2021-09-27T06:48:03.297813-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI"} -{"Time":"2021-09-27T06:48:03.297816-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":"=== RUN TestHashersAPI\n"} -{"Time":"2021-09-27T06:48:03.297819-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":" hash_test.go:114: math rand seed is 1632739683297507000\n"} -{"Time":"2021-09-27T06:48:03.297825-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":"--- PASS: TestHashersAPI (0.00s)\n"} -{"Time":"2021-09-27T06:48:03.297833-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Elapsed":0} -{"Time":"2021-09-27T06:48:03.297837-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3"} -{"Time":"2021-09-27T06:48:03.29784-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":"=== RUN TestSha3\n"} -{"Time":"2021-09-27T06:48:03.297843-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":" hash_test.go:158: math rand seed is 1632739683297711000\n"} -{"Time":"2021-09-27T06:48:03.297847-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256"} -{"Time":"2021-09-27T06:48:03.297855-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Output":"=== RUN TestSha3/SHA3_256\n"} -{"Time":"2021-09-27T06:48:03.39759-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384"} -{"Time":"2021-09-27T06:48:03.397615-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Output":"=== RUN TestSha3/SHA3_384\n"} -{"Time":"2021-09-27T06:48:03.518378-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":"--- PASS: TestSha3 (0.22s)\n"} -{"Time":"2021-09-27T06:48:03.518404-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Output":" --- PASS: TestSha3/SHA3_256 (0.10s)\n"} -{"Time":"2021-09-27T06:48:03.518408-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Elapsed":0.1} -{"Time":"2021-09-27T06:48:03.518413-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Output":" --- PASS: TestSha3/SHA3_384 (0.12s)\n"} -{"Time":"2021-09-27T06:48:03.518417-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Elapsed":0.12} -{"Time":"2021-09-27T06:48:03.51842-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Elapsed":0.22} -{"Time":"2021-09-27T06:48:03.518423-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256"} -{"Time":"2021-09-27T06:48:03.518426-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Output":"=== RUN TestSanitySha3_256\n"} -{"Time":"2021-09-27T06:48:03.518429-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Output":"--- PASS: TestSanitySha3_256 (0.00s)\n"} -{"Time":"2021-09-27T06:48:03.518432-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Elapsed":0} -{"Time":"2021-09-27T06:48:03.518453-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384"} -{"Time":"2021-09-27T06:48:03.518456-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Output":"=== RUN TestSanitySha3_384\n"} -{"Time":"2021-09-27T06:48:03.51846-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Output":"--- PASS: TestSanitySha3_384 (0.00s)\n"} -{"Time":"2021-09-27T06:48:03.518629-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Elapsed":0} -{"Time":"2021-09-27T06:48:03.518642-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256"} -{"Time":"2021-09-27T06:48:03.518648-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":"=== RUN TestSanitySha2_256\n"} -{"Time":"2021-09-27T06:48:03.518658-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" hash_test.go:41: \n"} -{"Time":"2021-09-27T06:48:03.518683-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \tError Trace:\thash_test.go:41\n"} -{"Time":"2021-09-27T06:48:03.518689-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \tError: \tNot equal: \n"} -{"Time":"2021-09-27T06:48:03.518693-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \texpected: hash.Hash{0x9f, 0x86, 0xd0, 0x81, 0x88, 0x4c, 0x7d, 0x65, 0x9a, 0x2f, 0xea, 0xa0, 0xc5, 0x5a, 0xd0, 0x15, 0xa3, 0xbf, 0x4f, 0x1b, 0x2b, 0xb, 0x82, 0x2c, 0xd1, 0x5d, 0x6c, 0x15, 0xb0, 0xf0, 0xa, 0x9}\n"} -{"Time":"2021-09-27T06:48:03.518697-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \tactual : hash.Hash{0x9f, 0x86, 0xd0, 0x81, 0x88, 0x4c, 0x7d, 0x65, 0x9a, 0x2f, 0xea, 0xa0, 0xc5, 0x5a, 0xd0, 0x15, 0xa3, 0xbf, 0x4f, 0x1b, 0x2b, 0xb, 0x82, 0x2c, 0xd1, 0x5d, 0x6c, 0x15, 0xb0, 0xf0, 0xa, 0x8}\n"} -{"Time":"2021-09-27T06:48:03.518702-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t\n"} -{"Time":"2021-09-27T06:48:03.518705-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \tDiff:\n"} -{"Time":"2021-09-27T06:48:03.518708-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t--- Expected\n"} -{"Time":"2021-09-27T06:48:03.518711-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t+++ Actual\n"} -{"Time":"2021-09-27T06:48:03.518747-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t@@ -1,2 +1,2 @@\n"} -{"Time":"2021-09-27T06:48:03.51876-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t-(hash.Hash) (len=32) 0x9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a09\n"} -{"Time":"2021-09-27T06:48:03.518772-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t+(hash.Hash) (len=32) 0x9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08\n"} -{"Time":"2021-09-27T06:48:03.518778-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t \n"} -{"Time":"2021-09-27T06:48:03.51879-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \tTest: \tTestSanitySha2_256\n"} -{"Time":"2021-09-27T06:48:03.518811-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":"--- FAIL: TestSanitySha2_256 (0.00s)\n"} -{"Time":"2021-09-27T06:48:03.518816-04:00","Action":"fail","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Elapsed":0} -{"Time":"2021-09-27T06:48:03.51882-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384"} -{"Time":"2021-09-27T06:48:03.518822-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Output":"=== RUN TestSanitySha2_384\n"} -{"Time":"2021-09-27T06:48:03.518826-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Output":"--- PASS: TestSanitySha2_384 (0.00s)\n"} -{"Time":"2021-09-27T06:48:03.518835-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Elapsed":0} -{"Time":"2021-09-27T06:48:03.518838-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128"} -{"Time":"2021-09-27T06:48:03.51884-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Output":"=== RUN TestSanityKmac128\n"} -{"Time":"2021-09-27T06:48:03.518844-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Output":"--- PASS: TestSanityKmac128 (0.00s)\n"} -{"Time":"2021-09-27T06:48:03.518847-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Elapsed":0} -{"Time":"2021-09-27T06:48:03.518849-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI"} -{"Time":"2021-09-27T06:48:03.518852-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":"=== RUN TestHashersAPI\n"} -{"Time":"2021-09-27T06:48:03.518881-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":" hash_test.go:114: math rand seed is 1632739683518492000\n"} -{"Time":"2021-09-27T06:48:03.518905-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":"--- PASS: TestHashersAPI (0.00s)\n"} -{"Time":"2021-09-27T06:48:03.518912-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Elapsed":0} -{"Time":"2021-09-27T06:48:03.518917-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3"} -{"Time":"2021-09-27T06:48:03.518922-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":"=== RUN TestSha3\n"} -{"Time":"2021-09-27T06:48:03.518927-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":" hash_test.go:158: math rand seed is 1632739683518781000\n"} -{"Time":"2021-09-27T06:48:03.518935-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256"} -{"Time":"2021-09-27T06:48:03.51894-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Output":"=== RUN TestSha3/SHA3_256\n"} -{"Time":"2021-09-27T06:48:03.618338-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384"} -{"Time":"2021-09-27T06:48:03.618361-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Output":"=== RUN TestSha3/SHA3_384\n"} -{"Time":"2021-09-27T06:48:03.740052-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":"--- PASS: TestSha3 (0.22s)\n"} -{"Time":"2021-09-27T06:48:03.740148-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Output":" --- PASS: TestSha3/SHA3_256 (0.10s)\n"} -{"Time":"2021-09-27T06:48:03.740153-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Elapsed":0.1} -{"Time":"2021-09-27T06:48:03.740158-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Output":" --- PASS: TestSha3/SHA3_384 (0.12s)\n"} -{"Time":"2021-09-27T06:48:03.740161-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Elapsed":0.12} -{"Time":"2021-09-27T06:48:03.740164-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Elapsed":0.22} -{"Time":"2021-09-27T06:48:03.740167-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256"} -{"Time":"2021-09-27T06:48:03.740292-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Output":"=== RUN TestSanitySha3_256\n"} -{"Time":"2021-09-27T06:48:03.740318-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Output":"--- PASS: TestSanitySha3_256 (0.00s)\n"} -{"Time":"2021-09-27T06:48:03.740325-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Elapsed":0} -{"Time":"2021-09-27T06:48:03.74033-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384"} -{"Time":"2021-09-27T06:48:03.740335-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Output":"=== RUN TestSanitySha3_384\n"} -{"Time":"2021-09-27T06:48:03.740341-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Output":"--- PASS: TestSanitySha3_384 (0.00s)\n"} -{"Time":"2021-09-27T06:48:03.740417-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Elapsed":0} -{"Time":"2021-09-27T06:48:03.740429-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256"} -{"Time":"2021-09-27T06:48:03.740434-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":"=== RUN TestSanitySha2_256\n"} -{"Time":"2021-09-27T06:48:03.74045-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" hash_test.go:41: \n"} -{"Time":"2021-09-27T06:48:03.740455-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \tError Trace:\thash_test.go:41\n"} -{"Time":"2021-09-27T06:48:03.740461-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \tError: \tNot equal: \n"} -{"Time":"2021-09-27T06:48:03.740465-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \texpected: hash.Hash{0x9f, 0x86, 0xd0, 0x81, 0x88, 0x4c, 0x7d, 0x65, 0x9a, 0x2f, 0xea, 0xa0, 0xc5, 0x5a, 0xd0, 0x15, 0xa3, 0xbf, 0x4f, 0x1b, 0x2b, 0xb, 0x82, 0x2c, 0xd1, 0x5d, 0x6c, 0x15, 0xb0, 0xf0, 0xa, 0x9}\n"} -{"Time":"2021-09-27T06:48:03.740471-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \tactual : hash.Hash{0x9f, 0x86, 0xd0, 0x81, 0x88, 0x4c, 0x7d, 0x65, 0x9a, 0x2f, 0xea, 0xa0, 0xc5, 0x5a, 0xd0, 0x15, 0xa3, 0xbf, 0x4f, 0x1b, 0x2b, 0xb, 0x82, 0x2c, 0xd1, 0x5d, 0x6c, 0x15, 0xb0, 0xf0, 0xa, 0x8}\n"} -{"Time":"2021-09-27T06:48:03.740505-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t\n"} -{"Time":"2021-09-27T06:48:03.740515-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \tDiff:\n"} -{"Time":"2021-09-27T06:48:03.74052-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t--- Expected\n"} -{"Time":"2021-09-27T06:48:03.740524-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t+++ Actual\n"} -{"Time":"2021-09-27T06:48:03.740536-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t@@ -1,2 +1,2 @@\n"} -{"Time":"2021-09-27T06:48:03.74054-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t-(hash.Hash) (len=32) 0x9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a09\n"} -{"Time":"2021-09-27T06:48:03.740544-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t+(hash.Hash) (len=32) 0x9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08\n"} -{"Time":"2021-09-27T06:48:03.740547-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t \n"} -{"Time":"2021-09-27T06:48:03.74055-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \tTest: \tTestSanitySha2_256\n"} -{"Time":"2021-09-27T06:48:03.740592-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":"--- FAIL: TestSanitySha2_256 (0.00s)\n"} -{"Time":"2021-09-27T06:48:03.740606-04:00","Action":"fail","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Elapsed":0} -{"Time":"2021-09-27T06:48:03.740611-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384"} -{"Time":"2021-09-27T06:48:03.74062-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Output":"=== RUN TestSanitySha2_384\n"} -{"Time":"2021-09-27T06:48:03.740682-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Output":"--- PASS: TestSanitySha2_384 (0.00s)\n"} -{"Time":"2021-09-27T06:48:03.740696-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Elapsed":0} -{"Time":"2021-09-27T06:48:03.7407-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128"} -{"Time":"2021-09-27T06:48:03.740704-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Output":"=== RUN TestSanityKmac128\n"} -{"Time":"2021-09-27T06:48:03.740728-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Output":"--- PASS: TestSanityKmac128 (0.00s)\n"} -{"Time":"2021-09-27T06:48:03.74074-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Elapsed":0} -{"Time":"2021-09-27T06:48:03.740752-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI"} -{"Time":"2021-09-27T06:48:03.740757-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":"=== RUN TestHashersAPI\n"} -{"Time":"2021-09-27T06:48:03.740764-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":" hash_test.go:114: math rand seed is 1632739683740724000\n"} -{"Time":"2021-09-27T06:48:03.740982-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":"--- PASS: TestHashersAPI (0.00s)\n"} -{"Time":"2021-09-27T06:48:03.740995-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Elapsed":0} -{"Time":"2021-09-27T06:48:03.74102-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3"} -{"Time":"2021-09-27T06:48:03.741028-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":"=== RUN TestSha3\n"} -{"Time":"2021-09-27T06:48:03.741037-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":" hash_test.go:158: math rand seed is 1632739683740970000\n"} -{"Time":"2021-09-27T06:48:03.741063-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256"} -{"Time":"2021-09-27T06:48:03.741078-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Output":"=== RUN TestSha3/SHA3_256\n"} -{"Time":"2021-09-27T06:48:03.858998-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384"} -{"Time":"2021-09-27T06:48:03.859054-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Output":"=== RUN TestSha3/SHA3_384\n"} -{"Time":"2021-09-27T06:48:03.979881-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":"--- PASS: TestSha3 (0.24s)\n"} -{"Time":"2021-09-27T06:48:03.97994-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Output":" --- PASS: TestSha3/SHA3_256 (0.12s)\n"} -{"Time":"2021-09-27T06:48:03.979945-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Elapsed":0.12} -{"Time":"2021-09-27T06:48:03.97995-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Output":" --- PASS: TestSha3/SHA3_384 (0.12s)\n"} -{"Time":"2021-09-27T06:48:03.979953-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Elapsed":0.12} -{"Time":"2021-09-27T06:48:03.979956-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Elapsed":0.24} -{"Time":"2021-09-27T06:48:03.979959-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256"} -{"Time":"2021-09-27T06:48:03.979975-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Output":"=== RUN TestSanitySha3_256\n"} -{"Time":"2021-09-27T06:48:03.979988-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Output":"--- PASS: TestSanitySha3_256 (0.00s)\n"} -{"Time":"2021-09-27T06:48:03.979992-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Elapsed":0} -{"Time":"2021-09-27T06:48:03.979995-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384"} -{"Time":"2021-09-27T06:48:03.979998-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Output":"=== RUN TestSanitySha3_384\n"} -{"Time":"2021-09-27T06:48:03.980002-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Output":"--- PASS: TestSanitySha3_384 (0.00s)\n"} -{"Time":"2021-09-27T06:48:03.980104-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Elapsed":0} -{"Time":"2021-09-27T06:48:03.980129-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256"} -{"Time":"2021-09-27T06:48:03.980135-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":"=== RUN TestSanitySha2_256\n"} -{"Time":"2021-09-27T06:48:03.980142-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" hash_test.go:41: \n"} -{"Time":"2021-09-27T06:48:03.980152-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \tError Trace:\thash_test.go:41\n"} -{"Time":"2021-09-27T06:48:03.980156-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \tError: \tNot equal: \n"} -{"Time":"2021-09-27T06:48:03.980159-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \texpected: hash.Hash{0x9f, 0x86, 0xd0, 0x81, 0x88, 0x4c, 0x7d, 0x65, 0x9a, 0x2f, 0xea, 0xa0, 0xc5, 0x5a, 0xd0, 0x15, 0xa3, 0xbf, 0x4f, 0x1b, 0x2b, 0xb, 0x82, 0x2c, 0xd1, 0x5d, 0x6c, 0x15, 0xb0, 0xf0, 0xa, 0x9}\n"} -{"Time":"2021-09-27T06:48:03.980163-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \tactual : hash.Hash{0x9f, 0x86, 0xd0, 0x81, 0x88, 0x4c, 0x7d, 0x65, 0x9a, 0x2f, 0xea, 0xa0, 0xc5, 0x5a, 0xd0, 0x15, 0xa3, 0xbf, 0x4f, 0x1b, 0x2b, 0xb, 0x82, 0x2c, 0xd1, 0x5d, 0x6c, 0x15, 0xb0, 0xf0, 0xa, 0x8}\n"} -{"Time":"2021-09-27T06:48:03.980167-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t\n"} -{"Time":"2021-09-27T06:48:03.980192-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \tDiff:\n"} -{"Time":"2021-09-27T06:48:03.980205-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t--- Expected\n"} -{"Time":"2021-09-27T06:48:03.980211-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t+++ Actual\n"} -{"Time":"2021-09-27T06:48:03.980216-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t@@ -1,2 +1,2 @@\n"} -{"Time":"2021-09-27T06:48:03.980221-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t-(hash.Hash) (len=32) 0x9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a09\n"} -{"Time":"2021-09-27T06:48:03.980244-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t+(hash.Hash) (len=32) 0x9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08\n"} -{"Time":"2021-09-27T06:48:03.980257-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t \n"} -{"Time":"2021-09-27T06:48:03.980261-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \tTest: \tTestSanitySha2_256\n"} -{"Time":"2021-09-27T06:48:03.980266-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":"--- FAIL: TestSanitySha2_256 (0.00s)\n"} -{"Time":"2021-09-27T06:48:03.980271-04:00","Action":"fail","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Elapsed":0} -{"Time":"2021-09-27T06:48:03.980274-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384"} -{"Time":"2021-09-27T06:48:03.980277-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Output":"=== RUN TestSanitySha2_384\n"} -{"Time":"2021-09-27T06:48:03.98028-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Output":"--- PASS: TestSanitySha2_384 (0.00s)\n"} -{"Time":"2021-09-27T06:48:03.980284-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Elapsed":0} -{"Time":"2021-09-27T06:48:03.980286-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128"} -{"Time":"2021-09-27T06:48:03.980307-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Output":"=== RUN TestSanityKmac128\n"} -{"Time":"2021-09-27T06:48:03.980317-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Output":"--- PASS: TestSanityKmac128 (0.00s)\n"} -{"Time":"2021-09-27T06:48:03.980323-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Elapsed":0} -{"Time":"2021-09-27T06:48:03.98033-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI"} -{"Time":"2021-09-27T06:48:03.980335-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":"=== RUN TestHashersAPI\n"} -{"Time":"2021-09-27T06:48:03.98034-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":" hash_test.go:114: math rand seed is 1632739683980033000\n"} -{"Time":"2021-09-27T06:48:03.980388-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":"--- PASS: TestHashersAPI (0.00s)\n"} -{"Time":"2021-09-27T06:48:03.980399-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Elapsed":0} -{"Time":"2021-09-27T06:48:03.980403-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3"} -{"Time":"2021-09-27T06:48:03.980408-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":"=== RUN TestSha3\n"} -{"Time":"2021-09-27T06:48:03.980411-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":" hash_test.go:158: math rand seed is 1632739683980266000\n"} -{"Time":"2021-09-27T06:48:03.980416-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256"} -{"Time":"2021-09-27T06:48:03.98042-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Output":"=== RUN TestSha3/SHA3_256\n"} -{"Time":"2021-09-27T06:48:04.080285-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384"} -{"Time":"2021-09-27T06:48:04.080313-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Output":"=== RUN TestSha3/SHA3_384\n"} -{"Time":"2021-09-27T06:48:04.200352-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":"--- PASS: TestSha3 (0.22s)\n"} -{"Time":"2021-09-27T06:48:04.20038-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Output":" --- PASS: TestSha3/SHA3_256 (0.10s)\n"} -{"Time":"2021-09-27T06:48:04.200385-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Elapsed":0.1} -{"Time":"2021-09-27T06:48:04.20039-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Output":" --- PASS: TestSha3/SHA3_384 (0.12s)\n"} -{"Time":"2021-09-27T06:48:04.200393-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Elapsed":0.12} -{"Time":"2021-09-27T06:48:04.200397-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Elapsed":0.22} -{"Time":"2021-09-27T06:48:04.200401-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256"} -{"Time":"2021-09-27T06:48:04.200405-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Output":"=== RUN TestSanitySha3_256\n"} -{"Time":"2021-09-27T06:48:04.200422-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Output":"--- PASS: TestSanitySha3_256 (0.00s)\n"} -{"Time":"2021-09-27T06:48:04.200426-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Elapsed":0} -{"Time":"2021-09-27T06:48:04.200429-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384"} -{"Time":"2021-09-27T06:48:04.200433-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Output":"=== RUN TestSanitySha3_384\n"} -{"Time":"2021-09-27T06:48:04.200437-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Output":"--- PASS: TestSanitySha3_384 (0.00s)\n"} -{"Time":"2021-09-27T06:48:04.200554-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Elapsed":0} -{"Time":"2021-09-27T06:48:04.200568-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256"} -{"Time":"2021-09-27T06:48:04.200572-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":"=== RUN TestSanitySha2_256\n"} -{"Time":"2021-09-27T06:48:04.200579-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" hash_test.go:41: \n"} -{"Time":"2021-09-27T06:48:04.200583-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \tError Trace:\thash_test.go:41\n"} -{"Time":"2021-09-27T06:48:04.200586-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \tError: \tNot equal: \n"} -{"Time":"2021-09-27T06:48:04.20059-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \texpected: hash.Hash{0x9f, 0x86, 0xd0, 0x81, 0x88, 0x4c, 0x7d, 0x65, 0x9a, 0x2f, 0xea, 0xa0, 0xc5, 0x5a, 0xd0, 0x15, 0xa3, 0xbf, 0x4f, 0x1b, 0x2b, 0xb, 0x82, 0x2c, 0xd1, 0x5d, 0x6c, 0x15, 0xb0, 0xf0, 0xa, 0x9}\n"} -{"Time":"2021-09-27T06:48:04.200596-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \tactual : hash.Hash{0x9f, 0x86, 0xd0, 0x81, 0x88, 0x4c, 0x7d, 0x65, 0x9a, 0x2f, 0xea, 0xa0, 0xc5, 0x5a, 0xd0, 0x15, 0xa3, 0xbf, 0x4f, 0x1b, 0x2b, 0xb, 0x82, 0x2c, 0xd1, 0x5d, 0x6c, 0x15, 0xb0, 0xf0, 0xa, 0x8}\n"} -{"Time":"2021-09-27T06:48:04.200609-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t\n"} -{"Time":"2021-09-27T06:48:04.200613-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \tDiff:\n"} -{"Time":"2021-09-27T06:48:04.200616-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t--- Expected\n"} -{"Time":"2021-09-27T06:48:04.20062-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t+++ Actual\n"} -{"Time":"2021-09-27T06:48:04.200634-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t@@ -1,2 +1,2 @@\n"} -{"Time":"2021-09-27T06:48:04.200651-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t-(hash.Hash) (len=32) 0x9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a09\n"} -{"Time":"2021-09-27T06:48:04.200657-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t+(hash.Hash) (len=32) 0x9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08\n"} -{"Time":"2021-09-27T06:48:04.200662-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t \n"} -{"Time":"2021-09-27T06:48:04.200666-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":" \tTest: \tTestSanitySha2_256\n"} -{"Time":"2021-09-27T06:48:04.200671-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":"--- FAIL: TestSanitySha2_256 (0.00s)\n"} -{"Time":"2021-09-27T06:48:04.200677-04:00","Action":"fail","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Elapsed":0} -{"Time":"2021-09-27T06:48:04.200682-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384"} -{"Time":"2021-09-27T06:48:04.200687-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Output":"=== RUN TestSanitySha2_384\n"} -{"Time":"2021-09-27T06:48:04.200695-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Output":"--- PASS: TestSanitySha2_384 (0.00s)\n"} -{"Time":"2021-09-27T06:48:04.200698-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Elapsed":0} -{"Time":"2021-09-27T06:48:04.200701-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128"} -{"Time":"2021-09-27T06:48:04.200704-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Output":"=== RUN TestSanityKmac128\n"} -{"Time":"2021-09-27T06:48:04.200707-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Output":"--- PASS: TestSanityKmac128 (0.00s)\n"} -{"Time":"2021-09-27T06:48:04.20071-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Elapsed":0} -{"Time":"2021-09-27T06:48:04.200713-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI"} -{"Time":"2021-09-27T06:48:04.200716-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":"=== RUN TestHashersAPI\n"} -{"Time":"2021-09-27T06:48:04.200722-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":" hash_test.go:114: math rand seed is 1632739684200452000\n"} -{"Time":"2021-09-27T06:48:04.200737-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":"--- PASS: TestHashersAPI (0.00s)\n"} -{"Time":"2021-09-27T06:48:04.200741-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Elapsed":0} -{"Time":"2021-09-27T06:48:04.200745-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3"} -{"Time":"2021-09-27T06:48:04.20075-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":"=== RUN TestSha3\n"} -{"Time":"2021-09-27T06:48:04.200755-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":" hash_test.go:158: math rand seed is 1632739684200658000\n"} -{"Time":"2021-09-27T06:48:04.20076-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256"} -{"Time":"2021-09-27T06:48:04.200769-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Output":"=== RUN TestSha3/SHA3_256\n"} -{"Time":"2021-09-27T06:48:04.301997-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384"} -{"Time":"2021-09-27T06:48:04.302032-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Output":"=== RUN TestSha3/SHA3_384\n"} -{"Time":"2021-09-27T06:48:04.425552-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":"--- PASS: TestSha3 (0.22s)\n"} -{"Time":"2021-09-27T06:48:04.425588-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Output":" --- PASS: TestSha3/SHA3_256 (0.10s)\n"} -{"Time":"2021-09-27T06:48:04.425594-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Elapsed":0.1} -{"Time":"2021-09-27T06:48:04.425599-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Output":" --- PASS: TestSha3/SHA3_384 (0.12s)\n"} -{"Time":"2021-09-27T06:48:04.425602-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Elapsed":0.12} -{"Time":"2021-09-27T06:48:04.425605-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Elapsed":0.22} -{"Time":"2021-09-27T06:48:04.425609-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Output":"FAIL\n"} -{"Time":"2021-09-27T06:48:04.426907-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Output":"FAIL\tgithub.com/onflow/flow-go/crypto/hash\t2.445s\n"} -{"Time":"2021-09-27T06:48:04.42693-04:00","Action":"fail","Package":"github.com/onflow/flow-go/crypto/hash","Elapsed":2.445} \ No newline at end of file +{"Time":"2021-09-27T06:48:02.184192-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256"} +{"Time":"2021-09-27T06:48:02.184694-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Output":"=== RUN TestSanitySha3_256\n"} +{"Time":"2021-09-27T06:48:02.18473-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Output":"--- PASS: TestSanitySha3_256 (0.00s)\n"} +{"Time":"2021-09-27T06:48:02.184738-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Elapsed":0} +{"Time":"2021-09-27T06:48:02.184759-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384"} +{"Time":"2021-09-27T06:48:02.184765-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Output":"=== RUN TestSanitySha3_384\n"} +{"Time":"2021-09-27T06:48:02.184772-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Output":"--- PASS: TestSanitySha3_384 (0.00s)\n"} +{"Time":"2021-09-27T06:48:02.184778-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Elapsed":0} +{"Time":"2021-09-27T06:48:02.184784-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256"} +{"Time":"2021-09-27T06:48:02.184913-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":"=== RUN TestSanitySha2_256\n"} +{"Time":"2021-09-27T06:48:02.184947-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" hash_test.go:41: \n"} +{"Time":"2021-09-27T06:48:02.184971-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \tError Trace:\thash_test.go:41\n"} +{"Time":"2021-09-27T06:48:02.184993-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \tError: \tNot equal: \n"} +{"Time":"2021-09-27T06:48:02.185002-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \texpected: hash.Hash{0x9f, 0x86, 0xd0, 0x81, 0x88, 0x4c, 0x7d, 0x65, 0x9a, 0x2f, 0xea, 0xa0, 0xc5, 0x5a, 0xd0, 0x15, 0xa3, 0xbf, 0x4f, 0x1b, 0x2b, 0xb, 0x82, 0x2c, 0xd1, 0x5d, 0x6c, 0x15, 0xb0, 0xf0, 0xa, 0x9}\n"} +{"Time":"2021-09-27T06:48:02.185025-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \tactual : hash.Hash{0x9f, 0x86, 0xd0, 0x81, 0x88, 0x4c, 0x7d, 0x65, 0x9a, 0x2f, 0xea, 0xa0, 0xc5, 0x5a, 0xd0, 0x15, 0xa3, 0xbf, 0x4f, 0x1b, 0x2b, 0xb, 0x82, 0x2c, 0xd1, 0x5d, 0x6c, 0x15, 0xb0, 0xf0, 0xa, 0x8}\n"} +{"Time":"2021-09-27T06:48:02.185037-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t\n"} +{"Time":"2021-09-27T06:48:02.185046-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \tDiff:\n"} +{"Time":"2021-09-27T06:48:02.185055-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t--- Expected\n"} +{"Time":"2021-09-27T06:48:02.185063-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t+++ Actual\n"} +{"Time":"2021-09-27T06:48:02.185101-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t@@ -1,2 +1,2 @@\n"} +{"Time":"2021-09-27T06:48:02.185122-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t-(hash.Hash) (len=32) 0x9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a09\n"} +{"Time":"2021-09-27T06:48:02.185145-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t+(hash.Hash) (len=32) 0x9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08\n"} +{"Time":"2021-09-27T06:48:02.185155-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t \n"} +{"Time":"2021-09-27T06:48:02.185164-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \tTest: \tTestSanitySha2_256\n"} +{"Time":"2021-09-27T06:48:02.185182-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":"--- FAIL: TestSanitySha2_256 (0.00s)\n"} +{"Time":"2021-09-27T06:48:02.185192-04:00","Action":"fail","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Elapsed":0} +{"Time":"2021-09-27T06:48:02.185199-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384"} +{"Time":"2021-09-27T06:48:02.185208-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Output":"=== RUN TestSanitySha2_384\n"} +{"Time":"2021-09-27T06:48:02.185218-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Output":"--- PASS: TestSanitySha2_384 (0.00s)\n"} +{"Time":"2021-09-27T06:48:02.185239-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Elapsed":0} +{"Time":"2021-09-27T06:48:02.185245-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128"} +{"Time":"2021-09-27T06:48:02.185261-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Output":"=== RUN TestSanityKmac128\n"} +{"Time":"2021-09-27T06:48:02.185271-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Output":"--- PASS: TestSanityKmac128 (0.00s)\n"} +{"Time":"2021-09-27T06:48:02.18528-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Elapsed":0} +{"Time":"2021-09-27T06:48:02.185289-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI"} +{"Time":"2021-09-27T06:48:02.185297-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":"=== RUN TestHashersAPI\n"} +{"Time":"2021-09-27T06:48:02.185307-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":" hash_test.go:114: math rand seed is 1632739682184421000\n"} +{"Time":"2021-09-27T06:48:02.185335-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":"--- PASS: TestHashersAPI (0.00s)\n"} +{"Time":"2021-09-27T06:48:02.185346-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Elapsed":0} +{"Time":"2021-09-27T06:48:02.185354-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3"} +{"Time":"2021-09-27T06:48:02.185376-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":"=== RUN TestSha3\n"} +{"Time":"2021-09-27T06:48:02.185386-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":" hash_test.go:158: math rand seed is 1632739682184858000\n"} +{"Time":"2021-09-27T06:48:02.185396-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256"} +{"Time":"2021-09-27T06:48:02.185404-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Output":"=== RUN TestSha3/SHA3_256\n"} +{"Time":"2021-09-27T06:48:02.294081-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384"} +{"Time":"2021-09-27T06:48:02.294138-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Output":"=== RUN TestSha3/SHA3_384\n"} +{"Time":"2021-09-27T06:48:02.415091-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":"--- PASS: TestSha3 (0.23s)\n"} +{"Time":"2021-09-27T06:48:02.415117-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Output":" --- PASS: TestSha3/SHA3_256 (0.11s)\n"} +{"Time":"2021-09-27T06:48:02.415121-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Elapsed":0.11} +{"Time":"2021-09-27T06:48:02.415206-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Output":" --- PASS: TestSha3/SHA3_384 (0.12s)\n"} +{"Time":"2021-09-27T06:48:02.41522-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Elapsed":0.12} +{"Time":"2021-09-27T06:48:02.415224-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Elapsed":0.23} +{"Time":"2021-09-27T06:48:02.415227-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256"} +{"Time":"2021-09-27T06:48:02.41523-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Output":"=== RUN TestSanitySha3_256\n"} +{"Time":"2021-09-27T06:48:02.415234-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Output":"--- PASS: TestSanitySha3_256 (0.00s)\n"} +{"Time":"2021-09-27T06:48:02.415237-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Elapsed":0} +{"Time":"2021-09-27T06:48:02.415322-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384"} +{"Time":"2021-09-27T06:48:02.415329-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Output":"=== RUN TestSanitySha3_384\n"} +{"Time":"2021-09-27T06:48:02.415335-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Output":"--- PASS: TestSanitySha3_384 (0.00s)\n"} +{"Time":"2021-09-27T06:48:02.415344-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Elapsed":0} +{"Time":"2021-09-27T06:48:02.415349-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256"} +{"Time":"2021-09-27T06:48:02.415354-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":"=== RUN TestSanitySha2_256\n"} +{"Time":"2021-09-27T06:48:02.415361-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" hash_test.go:41: \n"} +{"Time":"2021-09-27T06:48:02.415364-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \tError Trace:\thash_test.go:41\n"} +{"Time":"2021-09-27T06:48:02.415368-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \tError: \tNot equal: \n"} +{"Time":"2021-09-27T06:48:02.415371-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \texpected: hash.Hash{0x9f, 0x86, 0xd0, 0x81, 0x88, 0x4c, 0x7d, 0x65, 0x9a, 0x2f, 0xea, 0xa0, 0xc5, 0x5a, 0xd0, 0x15, 0xa3, 0xbf, 0x4f, 0x1b, 0x2b, 0xb, 0x82, 0x2c, 0xd1, 0x5d, 0x6c, 0x15, 0xb0, 0xf0, 0xa, 0x9}\n"} +{"Time":"2021-09-27T06:48:02.415406-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \tactual : hash.Hash{0x9f, 0x86, 0xd0, 0x81, 0x88, 0x4c, 0x7d, 0x65, 0x9a, 0x2f, 0xea, 0xa0, 0xc5, 0x5a, 0xd0, 0x15, 0xa3, 0xbf, 0x4f, 0x1b, 0x2b, 0xb, 0x82, 0x2c, 0xd1, 0x5d, 0x6c, 0x15, 0xb0, 0xf0, 0xa, 0x8}\n"} +{"Time":"2021-09-27T06:48:02.41542-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t\n"} +{"Time":"2021-09-27T06:48:02.415436-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \tDiff:\n"} +{"Time":"2021-09-27T06:48:02.415442-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t--- Expected\n"} +{"Time":"2021-09-27T06:48:02.415454-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t+++ Actual\n"} +{"Time":"2021-09-27T06:48:02.415467-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t@@ -1,2 +1,2 @@\n"} +{"Time":"2021-09-27T06:48:02.415476-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t-(hash.Hash) (len=32) 0x9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a09\n"} +{"Time":"2021-09-27T06:48:02.415481-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t+(hash.Hash) (len=32) 0x9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08\n"} +{"Time":"2021-09-27T06:48:02.415487-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t \n"} +{"Time":"2021-09-27T06:48:02.415492-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \tTest: \tTestSanitySha2_256\n"} +{"Time":"2021-09-27T06:48:02.415498-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":"--- FAIL: TestSanitySha2_256 (0.00s)\n"} +{"Time":"2021-09-27T06:48:02.415504-04:00","Action":"fail","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Elapsed":0} +{"Time":"2021-09-27T06:48:02.415507-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384"} +{"Time":"2021-09-27T06:48:02.41551-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Output":"=== RUN TestSanitySha2_384\n"} +{"Time":"2021-09-27T06:48:02.415514-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Output":"--- PASS: TestSanitySha2_384 (0.00s)\n"} +{"Time":"2021-09-27T06:48:02.415517-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Elapsed":0} +{"Time":"2021-09-27T06:48:02.41552-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128"} +{"Time":"2021-09-27T06:48:02.415548-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Output":"=== RUN TestSanityKmac128\n"} +{"Time":"2021-09-27T06:48:02.415557-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Output":"--- PASS: TestSanityKmac128 (0.00s)\n"} +{"Time":"2021-09-27T06:48:02.415563-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Elapsed":0} +{"Time":"2021-09-27T06:48:02.415568-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI"} +{"Time":"2021-09-27T06:48:02.415573-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":"=== RUN TestHashersAPI\n"} +{"Time":"2021-09-27T06:48:02.415578-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":" hash_test.go:114: math rand seed is 1632739682415309000\n"} +{"Time":"2021-09-27T06:48:02.415609-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":"--- PASS: TestHashersAPI (0.00s)\n"} +{"Time":"2021-09-27T06:48:02.415656-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Elapsed":0} +{"Time":"2021-09-27T06:48:02.415675-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3"} +{"Time":"2021-09-27T06:48:02.41568-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":"=== RUN TestSha3\n"} +{"Time":"2021-09-27T06:48:02.415697-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":" hash_test.go:158: math rand seed is 1632739682415616000\n"} +{"Time":"2021-09-27T06:48:02.415702-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256"} +{"Time":"2021-09-27T06:48:02.415706-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Output":"=== RUN TestSha3/SHA3_256\n"} +{"Time":"2021-09-27T06:48:02.51693-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384"} +{"Time":"2021-09-27T06:48:02.516956-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Output":"=== RUN TestSha3/SHA3_384\n"} +{"Time":"2021-09-27T06:48:02.636991-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":"--- PASS: TestSha3 (0.22s)\n"} +{"Time":"2021-09-27T06:48:02.637017-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Output":" --- PASS: TestSha3/SHA3_256 (0.10s)\n"} +{"Time":"2021-09-27T06:48:02.637022-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Elapsed":0.1} +{"Time":"2021-09-27T06:48:02.637027-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Output":" --- PASS: TestSha3/SHA3_384 (0.12s)\n"} +{"Time":"2021-09-27T06:48:02.637031-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Elapsed":0.12} +{"Time":"2021-09-27T06:48:02.637034-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Elapsed":0.22} +{"Time":"2021-09-27T06:48:02.637037-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256"} +{"Time":"2021-09-27T06:48:02.637059-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Output":"=== RUN TestSanitySha3_256\n"} +{"Time":"2021-09-27T06:48:02.637075-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Output":"--- PASS: TestSanitySha3_256 (0.00s)\n"} +{"Time":"2021-09-27T06:48:02.637095-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Elapsed":0} +{"Time":"2021-09-27T06:48:02.6371-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384"} +{"Time":"2021-09-27T06:48:02.637103-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Output":"=== RUN TestSanitySha3_384\n"} +{"Time":"2021-09-27T06:48:02.637117-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Output":"--- PASS: TestSanitySha3_384 (0.00s)\n"} +{"Time":"2021-09-27T06:48:02.637177-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Elapsed":0} +{"Time":"2021-09-27T06:48:02.637187-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256"} +{"Time":"2021-09-27T06:48:02.63719-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":"=== RUN TestSanitySha2_256\n"} +{"Time":"2021-09-27T06:48:02.637196-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" hash_test.go:41: \n"} +{"Time":"2021-09-27T06:48:02.6372-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \tError Trace:\thash_test.go:41\n"} +{"Time":"2021-09-27T06:48:02.637203-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \tError: \tNot equal: \n"} +{"Time":"2021-09-27T06:48:02.637231-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \texpected: hash.Hash{0x9f, 0x86, 0xd0, 0x81, 0x88, 0x4c, 0x7d, 0x65, 0x9a, 0x2f, 0xea, 0xa0, 0xc5, 0x5a, 0xd0, 0x15, 0xa3, 0xbf, 0x4f, 0x1b, 0x2b, 0xb, 0x82, 0x2c, 0xd1, 0x5d, 0x6c, 0x15, 0xb0, 0xf0, 0xa, 0x9}\n"} +{"Time":"2021-09-27T06:48:02.637241-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \tactual : hash.Hash{0x9f, 0x86, 0xd0, 0x81, 0x88, 0x4c, 0x7d, 0x65, 0x9a, 0x2f, 0xea, 0xa0, 0xc5, 0x5a, 0xd0, 0x15, 0xa3, 0xbf, 0x4f, 0x1b, 0x2b, 0xb, 0x82, 0x2c, 0xd1, 0x5d, 0x6c, 0x15, 0xb0, 0xf0, 0xa, 0x8}\n"} +{"Time":"2021-09-27T06:48:02.637248-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t\n"} +{"Time":"2021-09-27T06:48:02.637265-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \tDiff:\n"} +{"Time":"2021-09-27T06:48:02.63729-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t--- Expected\n"} +{"Time":"2021-09-27T06:48:02.637294-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t+++ Actual\n"} +{"Time":"2021-09-27T06:48:02.637298-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t@@ -1,2 +1,2 @@\n"} +{"Time":"2021-09-27T06:48:02.637301-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t-(hash.Hash) (len=32) 0x9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a09\n"} +{"Time":"2021-09-27T06:48:02.637305-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t+(hash.Hash) (len=32) 0x9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08\n"} +{"Time":"2021-09-27T06:48:02.637309-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t \n"} +{"Time":"2021-09-27T06:48:02.637334-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \tTest: \tTestSanitySha2_256\n"} +{"Time":"2021-09-27T06:48:02.63734-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":"--- FAIL: TestSanitySha2_256 (0.00s)\n"} +{"Time":"2021-09-27T06:48:02.637343-04:00","Action":"fail","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Elapsed":0} +{"Time":"2021-09-27T06:48:02.637346-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384"} +{"Time":"2021-09-27T06:48:02.637352-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Output":"=== RUN TestSanitySha2_384\n"} +{"Time":"2021-09-27T06:48:02.637358-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Output":"--- PASS: TestSanitySha2_384 (0.00s)\n"} +{"Time":"2021-09-27T06:48:02.637376-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Elapsed":0} +{"Time":"2021-09-27T06:48:02.63738-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128"} +{"Time":"2021-09-27T06:48:02.637383-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Output":"=== RUN TestSanityKmac128\n"} +{"Time":"2021-09-27T06:48:02.637386-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Output":"--- PASS: TestSanityKmac128 (0.00s)\n"} +{"Time":"2021-09-27T06:48:02.63739-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Elapsed":0} +{"Time":"2021-09-27T06:48:02.637392-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI"} +{"Time":"2021-09-27T06:48:02.637404-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":"=== RUN TestHashersAPI\n"} +{"Time":"2021-09-27T06:48:02.637408-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":" hash_test.go:114: math rand seed is 1632739682637108000\n"} +{"Time":"2021-09-27T06:48:02.637414-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":"--- PASS: TestHashersAPI (0.00s)\n"} +{"Time":"2021-09-27T06:48:02.637417-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Elapsed":0} +{"Time":"2021-09-27T06:48:02.63742-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3"} +{"Time":"2021-09-27T06:48:02.637423-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":"=== RUN TestSha3\n"} +{"Time":"2021-09-27T06:48:02.637426-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":" hash_test.go:158: math rand seed is 1632739682637311000\n"} +{"Time":"2021-09-27T06:48:02.637466-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256"} +{"Time":"2021-09-27T06:48:02.637491-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Output":"=== RUN TestSha3/SHA3_256\n"} +{"Time":"2021-09-27T06:48:02.736502-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384"} +{"Time":"2021-09-27T06:48:02.736566-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Output":"=== RUN TestSha3/SHA3_384\n"} +{"Time":"2021-09-27T06:48:02.857319-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":"--- PASS: TestSha3 (0.22s)\n"} +{"Time":"2021-09-27T06:48:02.85738-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Output":" --- PASS: TestSha3/SHA3_256 (0.10s)\n"} +{"Time":"2021-09-27T06:48:02.857386-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Elapsed":0.1} +{"Time":"2021-09-27T06:48:02.8574-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Output":" --- PASS: TestSha3/SHA3_384 (0.12s)\n"} +{"Time":"2021-09-27T06:48:02.857404-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Elapsed":0.12} +{"Time":"2021-09-27T06:48:02.857408-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Elapsed":0.22} +{"Time":"2021-09-27T06:48:02.857413-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256"} +{"Time":"2021-09-27T06:48:02.857417-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Output":"=== RUN TestSanitySha3_256\n"} +{"Time":"2021-09-27T06:48:02.857426-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Output":"--- PASS: TestSanitySha3_256 (0.00s)\n"} +{"Time":"2021-09-27T06:48:02.85743-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Elapsed":0} +{"Time":"2021-09-27T06:48:02.857433-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384"} +{"Time":"2021-09-27T06:48:02.857437-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Output":"=== RUN TestSanitySha3_384\n"} +{"Time":"2021-09-27T06:48:02.857441-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Output":"--- PASS: TestSanitySha3_384 (0.00s)\n"} +{"Time":"2021-09-27T06:48:02.857541-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Elapsed":0} +{"Time":"2021-09-27T06:48:02.857552-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256"} +{"Time":"2021-09-27T06:48:02.857557-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":"=== RUN TestSanitySha2_256\n"} +{"Time":"2021-09-27T06:48:02.857565-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" hash_test.go:41: \n"} +{"Time":"2021-09-27T06:48:02.857568-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \tError Trace:\thash_test.go:41\n"} +{"Time":"2021-09-27T06:48:02.857572-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \tError: \tNot equal: \n"} +{"Time":"2021-09-27T06:48:02.857588-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \texpected: hash.Hash{0x9f, 0x86, 0xd0, 0x81, 0x88, 0x4c, 0x7d, 0x65, 0x9a, 0x2f, 0xea, 0xa0, 0xc5, 0x5a, 0xd0, 0x15, 0xa3, 0xbf, 0x4f, 0x1b, 0x2b, 0xb, 0x82, 0x2c, 0xd1, 0x5d, 0x6c, 0x15, 0xb0, 0xf0, 0xa, 0x9}\n"} +{"Time":"2021-09-27T06:48:02.857594-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \tactual : hash.Hash{0x9f, 0x86, 0xd0, 0x81, 0x88, 0x4c, 0x7d, 0x65, 0x9a, 0x2f, 0xea, 0xa0, 0xc5, 0x5a, 0xd0, 0x15, 0xa3, 0xbf, 0x4f, 0x1b, 0x2b, 0xb, 0x82, 0x2c, 0xd1, 0x5d, 0x6c, 0x15, 0xb0, 0xf0, 0xa, 0x8}\n"} +{"Time":"2021-09-27T06:48:02.857613-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t\n"} +{"Time":"2021-09-27T06:48:02.857617-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \tDiff:\n"} +{"Time":"2021-09-27T06:48:02.85762-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t--- Expected\n"} +{"Time":"2021-09-27T06:48:02.857624-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t+++ Actual\n"} +{"Time":"2021-09-27T06:48:02.857639-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t@@ -1,2 +1,2 @@\n"} +{"Time":"2021-09-27T06:48:02.857644-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t-(hash.Hash) (len=32) 0x9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a09\n"} +{"Time":"2021-09-27T06:48:02.857647-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t+(hash.Hash) (len=32) 0x9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08\n"} +{"Time":"2021-09-27T06:48:02.857651-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t \n"} +{"Time":"2021-09-27T06:48:02.85766-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \tTest: \tTestSanitySha2_256\n"} +{"Time":"2021-09-27T06:48:02.857665-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":"--- FAIL: TestSanitySha2_256 (0.00s)\n"} +{"Time":"2021-09-27T06:48:02.857668-04:00","Action":"fail","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Elapsed":0} +{"Time":"2021-09-27T06:48:02.857671-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384"} +{"Time":"2021-09-27T06:48:02.857674-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Output":"=== RUN TestSanitySha2_384\n"} +{"Time":"2021-09-27T06:48:02.857678-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Output":"--- PASS: TestSanitySha2_384 (0.00s)\n"} +{"Time":"2021-09-27T06:48:02.857699-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Elapsed":0} +{"Time":"2021-09-27T06:48:02.857744-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128"} +{"Time":"2021-09-27T06:48:02.85775-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Output":"=== RUN TestSanityKmac128\n"} +{"Time":"2021-09-27T06:48:02.857757-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Output":"--- PASS: TestSanityKmac128 (0.00s)\n"} +{"Time":"2021-09-27T06:48:02.857782-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Elapsed":0} +{"Time":"2021-09-27T06:48:02.857789-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI"} +{"Time":"2021-09-27T06:48:02.857793-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":"=== RUN TestHashersAPI\n"} +{"Time":"2021-09-27T06:48:02.857796-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":" hash_test.go:114: math rand seed is 1632739682857435000\n"} +{"Time":"2021-09-27T06:48:02.857838-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":"--- PASS: TestHashersAPI (0.00s)\n"} +{"Time":"2021-09-27T06:48:02.857843-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Elapsed":0} +{"Time":"2021-09-27T06:48:02.857846-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3"} +{"Time":"2021-09-27T06:48:02.857849-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":"=== RUN TestSha3\n"} +{"Time":"2021-09-27T06:48:02.857852-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":" hash_test.go:158: math rand seed is 1632739682857668000\n"} +{"Time":"2021-09-27T06:48:02.857855-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256"} +{"Time":"2021-09-27T06:48:02.857859-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Output":"=== RUN TestSha3/SHA3_256\n"} +{"Time":"2021-09-27T06:48:02.956791-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384"} +{"Time":"2021-09-27T06:48:02.956849-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Output":"=== RUN TestSha3/SHA3_384\n"} +{"Time":"2021-09-27T06:48:03.076829-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":"--- PASS: TestSha3 (0.22s)\n"} +{"Time":"2021-09-27T06:48:03.076855-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Output":" --- PASS: TestSha3/SHA3_256 (0.10s)\n"} +{"Time":"2021-09-27T06:48:03.076859-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Elapsed":0.1} +{"Time":"2021-09-27T06:48:03.076866-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Output":" --- PASS: TestSha3/SHA3_384 (0.12s)\n"} +{"Time":"2021-09-27T06:48:03.076871-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Elapsed":0.12} +{"Time":"2021-09-27T06:48:03.076876-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Elapsed":0.22} +{"Time":"2021-09-27T06:48:03.07688-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256"} +{"Time":"2021-09-27T06:48:03.077032-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Output":"=== RUN TestSanitySha3_256\n"} +{"Time":"2021-09-27T06:48:03.077046-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Output":"--- PASS: TestSanitySha3_256 (0.00s)\n"} +{"Time":"2021-09-27T06:48:03.077053-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Elapsed":0} +{"Time":"2021-09-27T06:48:03.077056-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384"} +{"Time":"2021-09-27T06:48:03.077059-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Output":"=== RUN TestSanitySha3_384\n"} +{"Time":"2021-09-27T06:48:03.077063-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Output":"--- PASS: TestSanitySha3_384 (0.00s)\n"} +{"Time":"2021-09-27T06:48:03.077123-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Elapsed":0} +{"Time":"2021-09-27T06:48:03.07713-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256"} +{"Time":"2021-09-27T06:48:03.077133-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":"=== RUN TestSanitySha2_256\n"} +{"Time":"2021-09-27T06:48:03.077137-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" hash_test.go:41: \n"} +{"Time":"2021-09-27T06:48:03.07714-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \tError Trace:\thash_test.go:41\n"} +{"Time":"2021-09-27T06:48:03.077143-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \tError: \tNot equal: \n"} +{"Time":"2021-09-27T06:48:03.077156-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \texpected: hash.Hash{0x9f, 0x86, 0xd0, 0x81, 0x88, 0x4c, 0x7d, 0x65, 0x9a, 0x2f, 0xea, 0xa0, 0xc5, 0x5a, 0xd0, 0x15, 0xa3, 0xbf, 0x4f, 0x1b, 0x2b, 0xb, 0x82, 0x2c, 0xd1, 0x5d, 0x6c, 0x15, 0xb0, 0xf0, 0xa, 0x9}\n"} +{"Time":"2021-09-27T06:48:03.07716-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \tactual : hash.Hash{0x9f, 0x86, 0xd0, 0x81, 0x88, 0x4c, 0x7d, 0x65, 0x9a, 0x2f, 0xea, 0xa0, 0xc5, 0x5a, 0xd0, 0x15, 0xa3, 0xbf, 0x4f, 0x1b, 0x2b, 0xb, 0x82, 0x2c, 0xd1, 0x5d, 0x6c, 0x15, 0xb0, 0xf0, 0xa, 0x8}\n"} +{"Time":"2021-09-27T06:48:03.077172-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t\n"} +{"Time":"2021-09-27T06:48:03.077195-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \tDiff:\n"} +{"Time":"2021-09-27T06:48:03.077199-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t--- Expected\n"} +{"Time":"2021-09-27T06:48:03.077203-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t+++ Actual\n"} +{"Time":"2021-09-27T06:48:03.077207-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t@@ -1,2 +1,2 @@\n"} +{"Time":"2021-09-27T06:48:03.077212-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t-(hash.Hash) (len=32) 0x9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a09\n"} +{"Time":"2021-09-27T06:48:03.077224-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t+(hash.Hash) (len=32) 0x9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08\n"} +{"Time":"2021-09-27T06:48:03.077229-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t \n"} +{"Time":"2021-09-27T06:48:03.077232-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \tTest: \tTestSanitySha2_256\n"} +{"Time":"2021-09-27T06:48:03.077236-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":"--- FAIL: TestSanitySha2_256 (0.00s)\n"} +{"Time":"2021-09-27T06:48:03.077239-04:00","Action":"fail","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Elapsed":0} +{"Time":"2021-09-27T06:48:03.077273-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384"} +{"Time":"2021-09-27T06:48:03.077286-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Output":"=== RUN TestSanitySha2_384\n"} +{"Time":"2021-09-27T06:48:03.077294-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Output":"--- PASS: TestSanitySha2_384 (0.00s)\n"} +{"Time":"2021-09-27T06:48:03.077299-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Elapsed":0} +{"Time":"2021-09-27T06:48:03.077304-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128"} +{"Time":"2021-09-27T06:48:03.077308-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Output":"=== RUN TestSanityKmac128\n"} +{"Time":"2021-09-27T06:48:03.077313-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Output":"--- PASS: TestSanityKmac128 (0.00s)\n"} +{"Time":"2021-09-27T06:48:03.077326-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Elapsed":0} +{"Time":"2021-09-27T06:48:03.077338-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI"} +{"Time":"2021-09-27T06:48:03.077344-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":"=== RUN TestHashersAPI\n"} +{"Time":"2021-09-27T06:48:03.077349-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":" hash_test.go:114: math rand seed is 1632739683077064000\n"} +{"Time":"2021-09-27T06:48:03.077357-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":"--- PASS: TestHashersAPI (0.00s)\n"} +{"Time":"2021-09-27T06:48:03.07736-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Elapsed":0} +{"Time":"2021-09-27T06:48:03.077363-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3"} +{"Time":"2021-09-27T06:48:03.077366-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":"=== RUN TestSha3\n"} +{"Time":"2021-09-27T06:48:03.07737-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":" hash_test.go:158: math rand seed is 1632739683077268000\n"} +{"Time":"2021-09-27T06:48:03.077808-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256"} +{"Time":"2021-09-27T06:48:03.077826-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Output":"=== RUN TestSha3/SHA3_256\n"} +{"Time":"2021-09-27T06:48:03.176088-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384"} +{"Time":"2021-09-27T06:48:03.176175-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Output":"=== RUN TestSha3/SHA3_384\n"} +{"Time":"2021-09-27T06:48:03.297398-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":"--- PASS: TestSha3 (0.22s)\n"} +{"Time":"2021-09-27T06:48:03.297426-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Output":" --- PASS: TestSha3/SHA3_256 (0.10s)\n"} +{"Time":"2021-09-27T06:48:03.297431-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Elapsed":0.1} +{"Time":"2021-09-27T06:48:03.297439-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Output":" --- PASS: TestSha3/SHA3_384 (0.12s)\n"} +{"Time":"2021-09-27T06:48:03.297442-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Elapsed":0.12} +{"Time":"2021-09-27T06:48:03.297445-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Elapsed":0.22} +{"Time":"2021-09-27T06:48:03.297448-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256"} +{"Time":"2021-09-27T06:48:03.297467-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Output":"=== RUN TestSanitySha3_256\n"} +{"Time":"2021-09-27T06:48:03.297477-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Output":"--- PASS: TestSanitySha3_256 (0.00s)\n"} +{"Time":"2021-09-27T06:48:03.297482-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Elapsed":0} +{"Time":"2021-09-27T06:48:03.297485-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384"} +{"Time":"2021-09-27T06:48:03.297488-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Output":"=== RUN TestSanitySha3_384\n"} +{"Time":"2021-09-27T06:48:03.297491-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Output":"--- PASS: TestSanitySha3_384 (0.00s)\n"} +{"Time":"2021-09-27T06:48:03.297591-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Elapsed":0} +{"Time":"2021-09-27T06:48:03.297618-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256"} +{"Time":"2021-09-27T06:48:03.297624-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":"=== RUN TestSanitySha2_256\n"} +{"Time":"2021-09-27T06:48:03.297632-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" hash_test.go:41: \n"} +{"Time":"2021-09-27T06:48:03.297635-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \tError Trace:\thash_test.go:41\n"} +{"Time":"2021-09-27T06:48:03.297639-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \tError: \tNot equal: \n"} +{"Time":"2021-09-27T06:48:03.297642-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \texpected: hash.Hash{0x9f, 0x86, 0xd0, 0x81, 0x88, 0x4c, 0x7d, 0x65, 0x9a, 0x2f, 0xea, 0xa0, 0xc5, 0x5a, 0xd0, 0x15, 0xa3, 0xbf, 0x4f, 0x1b, 0x2b, 0xb, 0x82, 0x2c, 0xd1, 0x5d, 0x6c, 0x15, 0xb0, 0xf0, 0xa, 0x9}\n"} +{"Time":"2021-09-27T06:48:03.297646-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \tactual : hash.Hash{0x9f, 0x86, 0xd0, 0x81, 0x88, 0x4c, 0x7d, 0x65, 0x9a, 0x2f, 0xea, 0xa0, 0xc5, 0x5a, 0xd0, 0x15, 0xa3, 0xbf, 0x4f, 0x1b, 0x2b, 0xb, 0x82, 0x2c, 0xd1, 0x5d, 0x6c, 0x15, 0xb0, 0xf0, 0xa, 0x8}\n"} +{"Time":"2021-09-27T06:48:03.297678-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t\n"} +{"Time":"2021-09-27T06:48:03.297687-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \tDiff:\n"} +{"Time":"2021-09-27T06:48:03.297704-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t--- Expected\n"} +{"Time":"2021-09-27T06:48:03.297709-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t+++ Actual\n"} +{"Time":"2021-09-27T06:48:03.297714-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t@@ -1,2 +1,2 @@\n"} +{"Time":"2021-09-27T06:48:03.297738-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t-(hash.Hash) (len=32) 0x9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a09\n"} +{"Time":"2021-09-27T06:48:03.297751-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t+(hash.Hash) (len=32) 0x9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08\n"} +{"Time":"2021-09-27T06:48:03.297755-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t \n"} +{"Time":"2021-09-27T06:48:03.297759-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \tTest: \tTestSanitySha2_256\n"} +{"Time":"2021-09-27T06:48:03.297763-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":"--- FAIL: TestSanitySha2_256 (0.00s)\n"} +{"Time":"2021-09-27T06:48:03.297767-04:00","Action":"fail","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Elapsed":0} +{"Time":"2021-09-27T06:48:03.29777-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384"} +{"Time":"2021-09-27T06:48:03.297773-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Output":"=== RUN TestSanitySha2_384\n"} +{"Time":"2021-09-27T06:48:03.297776-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Output":"--- PASS: TestSanitySha2_384 (0.00s)\n"} +{"Time":"2021-09-27T06:48:03.297795-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Elapsed":0} +{"Time":"2021-09-27T06:48:03.2978-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128"} +{"Time":"2021-09-27T06:48:03.297802-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Output":"=== RUN TestSanityKmac128\n"} +{"Time":"2021-09-27T06:48:03.297806-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Output":"--- PASS: TestSanityKmac128 (0.00s)\n"} +{"Time":"2021-09-27T06:48:03.29781-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Elapsed":0} +{"Time":"2021-09-27T06:48:03.297813-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI"} +{"Time":"2021-09-27T06:48:03.297816-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":"=== RUN TestHashersAPI\n"} +{"Time":"2021-09-27T06:48:03.297819-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":" hash_test.go:114: math rand seed is 1632739683297507000\n"} +{"Time":"2021-09-27T06:48:03.297825-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":"--- PASS: TestHashersAPI (0.00s)\n"} +{"Time":"2021-09-27T06:48:03.297833-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Elapsed":0} +{"Time":"2021-09-27T06:48:03.297837-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3"} +{"Time":"2021-09-27T06:48:03.29784-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":"=== RUN TestSha3\n"} +{"Time":"2021-09-27T06:48:03.297843-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":" hash_test.go:158: math rand seed is 1632739683297711000\n"} +{"Time":"2021-09-27T06:48:03.297847-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256"} +{"Time":"2021-09-27T06:48:03.297855-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Output":"=== RUN TestSha3/SHA3_256\n"} +{"Time":"2021-09-27T06:48:03.39759-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384"} +{"Time":"2021-09-27T06:48:03.397615-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Output":"=== RUN TestSha3/SHA3_384\n"} +{"Time":"2021-09-27T06:48:03.518378-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":"--- PASS: TestSha3 (0.22s)\n"} +{"Time":"2021-09-27T06:48:03.518404-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Output":" --- PASS: TestSha3/SHA3_256 (0.10s)\n"} +{"Time":"2021-09-27T06:48:03.518408-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Elapsed":0.1} +{"Time":"2021-09-27T06:48:03.518413-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Output":" --- PASS: TestSha3/SHA3_384 (0.12s)\n"} +{"Time":"2021-09-27T06:48:03.518417-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Elapsed":0.12} +{"Time":"2021-09-27T06:48:03.51842-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Elapsed":0.22} +{"Time":"2021-09-27T06:48:03.518423-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256"} +{"Time":"2021-09-27T06:48:03.518426-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Output":"=== RUN TestSanitySha3_256\n"} +{"Time":"2021-09-27T06:48:03.518429-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Output":"--- PASS: TestSanitySha3_256 (0.00s)\n"} +{"Time":"2021-09-27T06:48:03.518432-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Elapsed":0} +{"Time":"2021-09-27T06:48:03.518453-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384"} +{"Time":"2021-09-27T06:48:03.518456-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Output":"=== RUN TestSanitySha3_384\n"} +{"Time":"2021-09-27T06:48:03.51846-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Output":"--- PASS: TestSanitySha3_384 (0.00s)\n"} +{"Time":"2021-09-27T06:48:03.518629-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Elapsed":0} +{"Time":"2021-09-27T06:48:03.518642-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256"} +{"Time":"2021-09-27T06:48:03.518648-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":"=== RUN TestSanitySha2_256\n"} +{"Time":"2021-09-27T06:48:03.518658-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" hash_test.go:41: \n"} +{"Time":"2021-09-27T06:48:03.518683-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \tError Trace:\thash_test.go:41\n"} +{"Time":"2021-09-27T06:48:03.518689-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \tError: \tNot equal: \n"} +{"Time":"2021-09-27T06:48:03.518693-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \texpected: hash.Hash{0x9f, 0x86, 0xd0, 0x81, 0x88, 0x4c, 0x7d, 0x65, 0x9a, 0x2f, 0xea, 0xa0, 0xc5, 0x5a, 0xd0, 0x15, 0xa3, 0xbf, 0x4f, 0x1b, 0x2b, 0xb, 0x82, 0x2c, 0xd1, 0x5d, 0x6c, 0x15, 0xb0, 0xf0, 0xa, 0x9}\n"} +{"Time":"2021-09-27T06:48:03.518697-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \tactual : hash.Hash{0x9f, 0x86, 0xd0, 0x81, 0x88, 0x4c, 0x7d, 0x65, 0x9a, 0x2f, 0xea, 0xa0, 0xc5, 0x5a, 0xd0, 0x15, 0xa3, 0xbf, 0x4f, 0x1b, 0x2b, 0xb, 0x82, 0x2c, 0xd1, 0x5d, 0x6c, 0x15, 0xb0, 0xf0, 0xa, 0x8}\n"} +{"Time":"2021-09-27T06:48:03.518702-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t\n"} +{"Time":"2021-09-27T06:48:03.518705-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \tDiff:\n"} +{"Time":"2021-09-27T06:48:03.518708-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t--- Expected\n"} +{"Time":"2021-09-27T06:48:03.518711-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t+++ Actual\n"} +{"Time":"2021-09-27T06:48:03.518747-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t@@ -1,2 +1,2 @@\n"} +{"Time":"2021-09-27T06:48:03.51876-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t-(hash.Hash) (len=32) 0x9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a09\n"} +{"Time":"2021-09-27T06:48:03.518772-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t+(hash.Hash) (len=32) 0x9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08\n"} +{"Time":"2021-09-27T06:48:03.518778-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t \n"} +{"Time":"2021-09-27T06:48:03.51879-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \tTest: \tTestSanitySha2_256\n"} +{"Time":"2021-09-27T06:48:03.518811-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":"--- FAIL: TestSanitySha2_256 (0.00s)\n"} +{"Time":"2021-09-27T06:48:03.518816-04:00","Action":"fail","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Elapsed":0} +{"Time":"2021-09-27T06:48:03.51882-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384"} +{"Time":"2021-09-27T06:48:03.518822-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Output":"=== RUN TestSanitySha2_384\n"} +{"Time":"2021-09-27T06:48:03.518826-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Output":"--- PASS: TestSanitySha2_384 (0.00s)\n"} +{"Time":"2021-09-27T06:48:03.518835-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Elapsed":0} +{"Time":"2021-09-27T06:48:03.518838-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128"} +{"Time":"2021-09-27T06:48:03.51884-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Output":"=== RUN TestSanityKmac128\n"} +{"Time":"2021-09-27T06:48:03.518844-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Output":"--- PASS: TestSanityKmac128 (0.00s)\n"} +{"Time":"2021-09-27T06:48:03.518847-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Elapsed":0} +{"Time":"2021-09-27T06:48:03.518849-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI"} +{"Time":"2021-09-27T06:48:03.518852-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":"=== RUN TestHashersAPI\n"} +{"Time":"2021-09-27T06:48:03.518881-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":" hash_test.go:114: math rand seed is 1632739683518492000\n"} +{"Time":"2021-09-27T06:48:03.518905-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":"--- PASS: TestHashersAPI (0.00s)\n"} +{"Time":"2021-09-27T06:48:03.518912-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Elapsed":0} +{"Time":"2021-09-27T06:48:03.518917-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3"} +{"Time":"2021-09-27T06:48:03.518922-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":"=== RUN TestSha3\n"} +{"Time":"2021-09-27T06:48:03.518927-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":" hash_test.go:158: math rand seed is 1632739683518781000\n"} +{"Time":"2021-09-27T06:48:03.518935-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256"} +{"Time":"2021-09-27T06:48:03.51894-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Output":"=== RUN TestSha3/SHA3_256\n"} +{"Time":"2021-09-27T06:48:03.618338-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384"} +{"Time":"2021-09-27T06:48:03.618361-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Output":"=== RUN TestSha3/SHA3_384\n"} +{"Time":"2021-09-27T06:48:03.740052-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":"--- PASS: TestSha3 (0.22s)\n"} +{"Time":"2021-09-27T06:48:03.740148-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Output":" --- PASS: TestSha3/SHA3_256 (0.10s)\n"} +{"Time":"2021-09-27T06:48:03.740153-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Elapsed":0.1} +{"Time":"2021-09-27T06:48:03.740158-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Output":" --- PASS: TestSha3/SHA3_384 (0.12s)\n"} +{"Time":"2021-09-27T06:48:03.740161-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Elapsed":0.12} +{"Time":"2021-09-27T06:48:03.740164-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Elapsed":0.22} +{"Time":"2021-09-27T06:48:03.740167-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256"} +{"Time":"2021-09-27T06:48:03.740292-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Output":"=== RUN TestSanitySha3_256\n"} +{"Time":"2021-09-27T06:48:03.740318-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Output":"--- PASS: TestSanitySha3_256 (0.00s)\n"} +{"Time":"2021-09-27T06:48:03.740325-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Elapsed":0} +{"Time":"2021-09-27T06:48:03.74033-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384"} +{"Time":"2021-09-27T06:48:03.740335-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Output":"=== RUN TestSanitySha3_384\n"} +{"Time":"2021-09-27T06:48:03.740341-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Output":"--- PASS: TestSanitySha3_384 (0.00s)\n"} +{"Time":"2021-09-27T06:48:03.740417-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Elapsed":0} +{"Time":"2021-09-27T06:48:03.740429-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256"} +{"Time":"2021-09-27T06:48:03.740434-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":"=== RUN TestSanitySha2_256\n"} +{"Time":"2021-09-27T06:48:03.74045-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" hash_test.go:41: \n"} +{"Time":"2021-09-27T06:48:03.740455-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \tError Trace:\thash_test.go:41\n"} +{"Time":"2021-09-27T06:48:03.740461-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \tError: \tNot equal: \n"} +{"Time":"2021-09-27T06:48:03.740465-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \texpected: hash.Hash{0x9f, 0x86, 0xd0, 0x81, 0x88, 0x4c, 0x7d, 0x65, 0x9a, 0x2f, 0xea, 0xa0, 0xc5, 0x5a, 0xd0, 0x15, 0xa3, 0xbf, 0x4f, 0x1b, 0x2b, 0xb, 0x82, 0x2c, 0xd1, 0x5d, 0x6c, 0x15, 0xb0, 0xf0, 0xa, 0x9}\n"} +{"Time":"2021-09-27T06:48:03.740471-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \tactual : hash.Hash{0x9f, 0x86, 0xd0, 0x81, 0x88, 0x4c, 0x7d, 0x65, 0x9a, 0x2f, 0xea, 0xa0, 0xc5, 0x5a, 0xd0, 0x15, 0xa3, 0xbf, 0x4f, 0x1b, 0x2b, 0xb, 0x82, 0x2c, 0xd1, 0x5d, 0x6c, 0x15, 0xb0, 0xf0, 0xa, 0x8}\n"} +{"Time":"2021-09-27T06:48:03.740505-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t\n"} +{"Time":"2021-09-27T06:48:03.740515-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \tDiff:\n"} +{"Time":"2021-09-27T06:48:03.74052-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t--- Expected\n"} +{"Time":"2021-09-27T06:48:03.740524-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t+++ Actual\n"} +{"Time":"2021-09-27T06:48:03.740536-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t@@ -1,2 +1,2 @@\n"} +{"Time":"2021-09-27T06:48:03.74054-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t-(hash.Hash) (len=32) 0x9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a09\n"} +{"Time":"2021-09-27T06:48:03.740544-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t+(hash.Hash) (len=32) 0x9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08\n"} +{"Time":"2021-09-27T06:48:03.740547-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t \n"} +{"Time":"2021-09-27T06:48:03.74055-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \tTest: \tTestSanitySha2_256\n"} +{"Time":"2021-09-27T06:48:03.740592-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":"--- FAIL: TestSanitySha2_256 (0.00s)\n"} +{"Time":"2021-09-27T06:48:03.740606-04:00","Action":"fail","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Elapsed":0} +{"Time":"2021-09-27T06:48:03.740611-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384"} +{"Time":"2021-09-27T06:48:03.74062-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Output":"=== RUN TestSanitySha2_384\n"} +{"Time":"2021-09-27T06:48:03.740682-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Output":"--- PASS: TestSanitySha2_384 (0.00s)\n"} +{"Time":"2021-09-27T06:48:03.740696-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Elapsed":0} +{"Time":"2021-09-27T06:48:03.7407-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128"} +{"Time":"2021-09-27T06:48:03.740704-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Output":"=== RUN TestSanityKmac128\n"} +{"Time":"2021-09-27T06:48:03.740728-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Output":"--- PASS: TestSanityKmac128 (0.00s)\n"} +{"Time":"2021-09-27T06:48:03.74074-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Elapsed":0} +{"Time":"2021-09-27T06:48:03.740752-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI"} +{"Time":"2021-09-27T06:48:03.740757-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":"=== RUN TestHashersAPI\n"} +{"Time":"2021-09-27T06:48:03.740764-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":" hash_test.go:114: math rand seed is 1632739683740724000\n"} +{"Time":"2021-09-27T06:48:03.740982-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":"--- PASS: TestHashersAPI (0.00s)\n"} +{"Time":"2021-09-27T06:48:03.740995-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Elapsed":0} +{"Time":"2021-09-27T06:48:03.74102-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3"} +{"Time":"2021-09-27T06:48:03.741028-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":"=== RUN TestSha3\n"} +{"Time":"2021-09-27T06:48:03.741037-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":" hash_test.go:158: math rand seed is 1632739683740970000\n"} +{"Time":"2021-09-27T06:48:03.741063-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256"} +{"Time":"2021-09-27T06:48:03.741078-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Output":"=== RUN TestSha3/SHA3_256\n"} +{"Time":"2021-09-27T06:48:03.858998-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384"} +{"Time":"2021-09-27T06:48:03.859054-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Output":"=== RUN TestSha3/SHA3_384\n"} +{"Time":"2021-09-27T06:48:03.979881-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":"--- PASS: TestSha3 (0.24s)\n"} +{"Time":"2021-09-27T06:48:03.97994-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Output":" --- PASS: TestSha3/SHA3_256 (0.12s)\n"} +{"Time":"2021-09-27T06:48:03.979945-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Elapsed":0.12} +{"Time":"2021-09-27T06:48:03.97995-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Output":" --- PASS: TestSha3/SHA3_384 (0.12s)\n"} +{"Time":"2021-09-27T06:48:03.979953-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Elapsed":0.12} +{"Time":"2021-09-27T06:48:03.979956-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Elapsed":0.24} +{"Time":"2021-09-27T06:48:03.979959-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256"} +{"Time":"2021-09-27T06:48:03.979975-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Output":"=== RUN TestSanitySha3_256\n"} +{"Time":"2021-09-27T06:48:03.979988-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Output":"--- PASS: TestSanitySha3_256 (0.00s)\n"} +{"Time":"2021-09-27T06:48:03.979992-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Elapsed":0} +{"Time":"2021-09-27T06:48:03.979995-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384"} +{"Time":"2021-09-27T06:48:03.979998-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Output":"=== RUN TestSanitySha3_384\n"} +{"Time":"2021-09-27T06:48:03.980002-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Output":"--- PASS: TestSanitySha3_384 (0.00s)\n"} +{"Time":"2021-09-27T06:48:03.980104-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Elapsed":0} +{"Time":"2021-09-27T06:48:03.980129-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256"} +{"Time":"2021-09-27T06:48:03.980135-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":"=== RUN TestSanitySha2_256\n"} +{"Time":"2021-09-27T06:48:03.980142-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" hash_test.go:41: \n"} +{"Time":"2021-09-27T06:48:03.980152-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \tError Trace:\thash_test.go:41\n"} +{"Time":"2021-09-27T06:48:03.980156-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \tError: \tNot equal: \n"} +{"Time":"2021-09-27T06:48:03.980159-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \texpected: hash.Hash{0x9f, 0x86, 0xd0, 0x81, 0x88, 0x4c, 0x7d, 0x65, 0x9a, 0x2f, 0xea, 0xa0, 0xc5, 0x5a, 0xd0, 0x15, 0xa3, 0xbf, 0x4f, 0x1b, 0x2b, 0xb, 0x82, 0x2c, 0xd1, 0x5d, 0x6c, 0x15, 0xb0, 0xf0, 0xa, 0x9}\n"} +{"Time":"2021-09-27T06:48:03.980163-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \tactual : hash.Hash{0x9f, 0x86, 0xd0, 0x81, 0x88, 0x4c, 0x7d, 0x65, 0x9a, 0x2f, 0xea, 0xa0, 0xc5, 0x5a, 0xd0, 0x15, 0xa3, 0xbf, 0x4f, 0x1b, 0x2b, 0xb, 0x82, 0x2c, 0xd1, 0x5d, 0x6c, 0x15, 0xb0, 0xf0, 0xa, 0x8}\n"} +{"Time":"2021-09-27T06:48:03.980167-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t\n"} +{"Time":"2021-09-27T06:48:03.980192-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \tDiff:\n"} +{"Time":"2021-09-27T06:48:03.980205-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t--- Expected\n"} +{"Time":"2021-09-27T06:48:03.980211-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t+++ Actual\n"} +{"Time":"2021-09-27T06:48:03.980216-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t@@ -1,2 +1,2 @@\n"} +{"Time":"2021-09-27T06:48:03.980221-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t-(hash.Hash) (len=32) 0x9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a09\n"} +{"Time":"2021-09-27T06:48:03.980244-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t+(hash.Hash) (len=32) 0x9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08\n"} +{"Time":"2021-09-27T06:48:03.980257-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t \n"} +{"Time":"2021-09-27T06:48:03.980261-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \tTest: \tTestSanitySha2_256\n"} +{"Time":"2021-09-27T06:48:03.980266-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":"--- FAIL: TestSanitySha2_256 (0.00s)\n"} +{"Time":"2021-09-27T06:48:03.980271-04:00","Action":"fail","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Elapsed":0} +{"Time":"2021-09-27T06:48:03.980274-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384"} +{"Time":"2021-09-27T06:48:03.980277-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Output":"=== RUN TestSanitySha2_384\n"} +{"Time":"2021-09-27T06:48:03.98028-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Output":"--- PASS: TestSanitySha2_384 (0.00s)\n"} +{"Time":"2021-09-27T06:48:03.980284-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Elapsed":0} +{"Time":"2021-09-27T06:48:03.980286-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128"} +{"Time":"2021-09-27T06:48:03.980307-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Output":"=== RUN TestSanityKmac128\n"} +{"Time":"2021-09-27T06:48:03.980317-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Output":"--- PASS: TestSanityKmac128 (0.00s)\n"} +{"Time":"2021-09-27T06:48:03.980323-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Elapsed":0} +{"Time":"2021-09-27T06:48:03.98033-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI"} +{"Time":"2021-09-27T06:48:03.980335-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":"=== RUN TestHashersAPI\n"} +{"Time":"2021-09-27T06:48:03.98034-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":" hash_test.go:114: math rand seed is 1632739683980033000\n"} +{"Time":"2021-09-27T06:48:03.980388-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":"--- PASS: TestHashersAPI (0.00s)\n"} +{"Time":"2021-09-27T06:48:03.980399-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Elapsed":0} +{"Time":"2021-09-27T06:48:03.980403-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3"} +{"Time":"2021-09-27T06:48:03.980408-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":"=== RUN TestSha3\n"} +{"Time":"2021-09-27T06:48:03.980411-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":" hash_test.go:158: math rand seed is 1632739683980266000\n"} +{"Time":"2021-09-27T06:48:03.980416-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256"} +{"Time":"2021-09-27T06:48:03.98042-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Output":"=== RUN TestSha3/SHA3_256\n"} +{"Time":"2021-09-27T06:48:04.080285-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384"} +{"Time":"2021-09-27T06:48:04.080313-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Output":"=== RUN TestSha3/SHA3_384\n"} +{"Time":"2021-09-27T06:48:04.200352-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":"--- PASS: TestSha3 (0.22s)\n"} +{"Time":"2021-09-27T06:48:04.20038-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Output":" --- PASS: TestSha3/SHA3_256 (0.10s)\n"} +{"Time":"2021-09-27T06:48:04.200385-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Elapsed":0.1} +{"Time":"2021-09-27T06:48:04.20039-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Output":" --- PASS: TestSha3/SHA3_384 (0.12s)\n"} +{"Time":"2021-09-27T06:48:04.200393-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Elapsed":0.12} +{"Time":"2021-09-27T06:48:04.200397-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Elapsed":0.22} +{"Time":"2021-09-27T06:48:04.200401-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256"} +{"Time":"2021-09-27T06:48:04.200405-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Output":"=== RUN TestSanitySha3_256\n"} +{"Time":"2021-09-27T06:48:04.200422-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Output":"--- PASS: TestSanitySha3_256 (0.00s)\n"} +{"Time":"2021-09-27T06:48:04.200426-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Elapsed":0} +{"Time":"2021-09-27T06:48:04.200429-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384"} +{"Time":"2021-09-27T06:48:04.200433-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Output":"=== RUN TestSanitySha3_384\n"} +{"Time":"2021-09-27T06:48:04.200437-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Output":"--- PASS: TestSanitySha3_384 (0.00s)\n"} +{"Time":"2021-09-27T06:48:04.200554-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Elapsed":0} +{"Time":"2021-09-27T06:48:04.200568-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256"} +{"Time":"2021-09-27T06:48:04.200572-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":"=== RUN TestSanitySha2_256\n"} +{"Time":"2021-09-27T06:48:04.200579-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" hash_test.go:41: \n"} +{"Time":"2021-09-27T06:48:04.200583-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \tError Trace:\thash_test.go:41\n"} +{"Time":"2021-09-27T06:48:04.200586-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \tError: \tNot equal: \n"} +{"Time":"2021-09-27T06:48:04.20059-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \texpected: hash.Hash{0x9f, 0x86, 0xd0, 0x81, 0x88, 0x4c, 0x7d, 0x65, 0x9a, 0x2f, 0xea, 0xa0, 0xc5, 0x5a, 0xd0, 0x15, 0xa3, 0xbf, 0x4f, 0x1b, 0x2b, 0xb, 0x82, 0x2c, 0xd1, 0x5d, 0x6c, 0x15, 0xb0, 0xf0, 0xa, 0x9}\n"} +{"Time":"2021-09-27T06:48:04.200596-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \tactual : hash.Hash{0x9f, 0x86, 0xd0, 0x81, 0x88, 0x4c, 0x7d, 0x65, 0x9a, 0x2f, 0xea, 0xa0, 0xc5, 0x5a, 0xd0, 0x15, 0xa3, 0xbf, 0x4f, 0x1b, 0x2b, 0xb, 0x82, 0x2c, 0xd1, 0x5d, 0x6c, 0x15, 0xb0, 0xf0, 0xa, 0x8}\n"} +{"Time":"2021-09-27T06:48:04.200609-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t\n"} +{"Time":"2021-09-27T06:48:04.200613-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \tDiff:\n"} +{"Time":"2021-09-27T06:48:04.200616-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t--- Expected\n"} +{"Time":"2021-09-27T06:48:04.20062-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t+++ Actual\n"} +{"Time":"2021-09-27T06:48:04.200634-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t@@ -1,2 +1,2 @@\n"} +{"Time":"2021-09-27T06:48:04.200651-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t-(hash.Hash) (len=32) 0x9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a09\n"} +{"Time":"2021-09-27T06:48:04.200657-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t+(hash.Hash) (len=32) 0x9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08\n"} +{"Time":"2021-09-27T06:48:04.200662-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \t \t \n"} +{"Time":"2021-09-27T06:48:04.200666-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":" \tTest: \tTestSanitySha2_256\n"} +{"Time":"2021-09-27T06:48:04.200671-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":"--- FAIL: TestSanitySha2_256 (0.00s)\n"} +{"Time":"2021-09-27T06:48:04.200677-04:00","Action":"fail","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Elapsed":0} +{"Time":"2021-09-27T06:48:04.200682-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384"} +{"Time":"2021-09-27T06:48:04.200687-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Output":"=== RUN TestSanitySha2_384\n"} +{"Time":"2021-09-27T06:48:04.200695-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Output":"--- PASS: TestSanitySha2_384 (0.00s)\n"} +{"Time":"2021-09-27T06:48:04.200698-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Elapsed":0} +{"Time":"2021-09-27T06:48:04.200701-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128"} +{"Time":"2021-09-27T06:48:04.200704-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Output":"=== RUN TestSanityKmac128\n"} +{"Time":"2021-09-27T06:48:04.200707-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Output":"--- PASS: TestSanityKmac128 (0.00s)\n"} +{"Time":"2021-09-27T06:48:04.20071-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Elapsed":0} +{"Time":"2021-09-27T06:48:04.200713-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI"} +{"Time":"2021-09-27T06:48:04.200716-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":"=== RUN TestHashersAPI\n"} +{"Time":"2021-09-27T06:48:04.200722-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":" hash_test.go:114: math rand seed is 1632739684200452000\n"} +{"Time":"2021-09-27T06:48:04.200737-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":"--- PASS: TestHashersAPI (0.00s)\n"} +{"Time":"2021-09-27T06:48:04.200741-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Elapsed":0} +{"Time":"2021-09-27T06:48:04.200745-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3"} +{"Time":"2021-09-27T06:48:04.20075-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":"=== RUN TestSha3\n"} +{"Time":"2021-09-27T06:48:04.200755-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":" hash_test.go:158: math rand seed is 1632739684200658000\n"} +{"Time":"2021-09-27T06:48:04.20076-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256"} +{"Time":"2021-09-27T06:48:04.200769-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Output":"=== RUN TestSha3/SHA3_256\n"} +{"Time":"2021-09-27T06:48:04.301997-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384"} +{"Time":"2021-09-27T06:48:04.302032-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Output":"=== RUN TestSha3/SHA3_384\n"} +{"Time":"2021-09-27T06:48:04.425552-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":"--- PASS: TestSha3 (0.22s)\n"} +{"Time":"2021-09-27T06:48:04.425588-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Output":" --- PASS: TestSha3/SHA3_256 (0.10s)\n"} +{"Time":"2021-09-27T06:48:04.425594-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Elapsed":0.1} +{"Time":"2021-09-27T06:48:04.425599-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Output":" --- PASS: TestSha3/SHA3_384 (0.12s)\n"} +{"Time":"2021-09-27T06:48:04.425602-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Elapsed":0.12} +{"Time":"2021-09-27T06:48:04.425605-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Elapsed":0.22} +{"Time":"2021-09-27T06:48:04.425609-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Output":"FAIL\n"} +{"Time":"2021-09-27T06:48:04.426907-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Output":"FAIL\tgithub.com/onflow/crypto/hash\t2.445s\n"} +{"Time":"2021-09-27T06:48:04.42693-04:00","Action":"fail","Package":"github.com/onflow/crypto/hash","Elapsed":2.445} \ No newline at end of file diff --git a/tools/test_monitor/testdata/summary1/raw/test-result-crypto-hash-10-count-pass.json b/tools/test_monitor/testdata/summary1/raw/test-result-crypto-hash-10-count-pass.json index 4b1a17de6d5..72aab10fe2b 100644 --- a/tools/test_monitor/testdata/summary1/raw/test-result-crypto-hash-10-count-pass.json +++ b/tools/test_monitor/testdata/summary1/raw/test-result-crypto-hash-10-count-pass.json @@ -1,383 +1,383 @@ -{"Time":"2021-09-27T06:45:52.470262-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256"} -{"Time":"2021-09-27T06:45:52.470682-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Output":"=== RUN TestSanitySha3_256\n"} -{"Time":"2021-09-27T06:45:52.470718-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Output":"--- PASS: TestSanitySha3_256 (0.00s)\n"} -{"Time":"2021-09-27T06:45:52.470724-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Elapsed":0} -{"Time":"2021-09-27T06:45:52.470741-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384"} -{"Time":"2021-09-27T06:45:52.470745-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Output":"=== RUN TestSanitySha3_384\n"} -{"Time":"2021-09-27T06:45:52.470751-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Output":"--- PASS: TestSanitySha3_384 (0.00s)\n"} -{"Time":"2021-09-27T06:45:52.470756-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Elapsed":0} -{"Time":"2021-09-27T06:45:52.47076-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256"} -{"Time":"2021-09-27T06:45:52.470876-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":"=== RUN TestSanitySha2_256\n"} -{"Time":"2021-09-27T06:45:52.470895-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":"--- PASS: TestSanitySha2_256 (0.00s)\n"} -{"Time":"2021-09-27T06:45:52.470901-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Elapsed":0} -{"Time":"2021-09-27T06:45:52.470907-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384"} -{"Time":"2021-09-27T06:45:52.470911-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Output":"=== RUN TestSanitySha2_384\n"} -{"Time":"2021-09-27T06:45:52.470917-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Output":"--- PASS: TestSanitySha2_384 (0.00s)\n"} -{"Time":"2021-09-27T06:45:52.47093-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Elapsed":0} -{"Time":"2021-09-27T06:45:52.470935-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128"} -{"Time":"2021-09-27T06:45:52.470943-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Output":"=== RUN TestSanityKmac128\n"} -{"Time":"2021-09-27T06:45:52.470949-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Output":"--- PASS: TestSanityKmac128 (0.00s)\n"} -{"Time":"2021-09-27T06:45:52.470953-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Elapsed":0} -{"Time":"2021-09-27T06:45:52.470957-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI"} -{"Time":"2021-09-27T06:45:52.470981-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":"=== RUN TestHashersAPI\n"} -{"Time":"2021-09-27T06:45:52.470992-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":" hash_test.go:114: math rand seed is 1632739552470379000\n"} -{"Time":"2021-09-27T06:45:52.471024-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":"--- PASS: TestHashersAPI (0.00s)\n"} -{"Time":"2021-09-27T06:45:52.471029-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Elapsed":0} -{"Time":"2021-09-27T06:45:52.471034-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3"} -{"Time":"2021-09-27T06:45:52.471042-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":"=== RUN TestSha3\n"} -{"Time":"2021-09-27T06:45:52.471046-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":" hash_test.go:158: math rand seed is 1632739552470723000\n"} -{"Time":"2021-09-27T06:45:52.471051-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256"} -{"Time":"2021-09-27T06:45:52.471055-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Output":"=== RUN TestSha3/SHA3_256\n"} -{"Time":"2021-09-27T06:45:52.575573-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384"} -{"Time":"2021-09-27T06:45:52.575605-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Output":"=== RUN TestSha3/SHA3_384\n"} -{"Time":"2021-09-27T06:45:52.696742-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":"--- PASS: TestSha3 (0.23s)\n"} -{"Time":"2021-09-27T06:45:52.696768-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Output":" --- PASS: TestSha3/SHA3_256 (0.10s)\n"} -{"Time":"2021-09-27T06:45:52.696773-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Elapsed":0.1} -{"Time":"2021-09-27T06:45:52.696784-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Output":" --- PASS: TestSha3/SHA3_384 (0.12s)\n"} -{"Time":"2021-09-27T06:45:52.696787-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Elapsed":0.12} -{"Time":"2021-09-27T06:45:52.69679-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Elapsed":0.23} -{"Time":"2021-09-27T06:45:52.696793-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256"} -{"Time":"2021-09-27T06:45:52.696796-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Output":"=== RUN TestSanitySha3_256\n"} -{"Time":"2021-09-27T06:45:52.6968-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Output":"--- PASS: TestSanitySha3_256 (0.00s)\n"} -{"Time":"2021-09-27T06:45:52.696803-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Elapsed":0} -{"Time":"2021-09-27T06:45:52.696806-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384"} -{"Time":"2021-09-27T06:45:52.696809-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Output":"=== RUN TestSanitySha3_384\n"} -{"Time":"2021-09-27T06:45:52.697192-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Output":"--- PASS: TestSanitySha3_384 (0.00s)\n"} -{"Time":"2021-09-27T06:45:52.697219-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Elapsed":0} -{"Time":"2021-09-27T06:45:52.697226-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256"} -{"Time":"2021-09-27T06:45:52.697243-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":"=== RUN TestSanitySha2_256\n"} -{"Time":"2021-09-27T06:45:52.697259-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":"--- PASS: TestSanitySha2_256 (0.00s)\n"} -{"Time":"2021-09-27T06:45:52.697266-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Elapsed":0} -{"Time":"2021-09-27T06:45:52.697276-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384"} -{"Time":"2021-09-27T06:45:52.697281-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Output":"=== RUN TestSanitySha2_384\n"} -{"Time":"2021-09-27T06:45:52.697286-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Output":"--- PASS: TestSanitySha2_384 (0.00s)\n"} -{"Time":"2021-09-27T06:45:52.69729-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Elapsed":0} -{"Time":"2021-09-27T06:45:52.697295-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128"} -{"Time":"2021-09-27T06:45:52.6973-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Output":"=== RUN TestSanityKmac128\n"} -{"Time":"2021-09-27T06:45:52.697305-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Output":"--- PASS: TestSanityKmac128 (0.00s)\n"} -{"Time":"2021-09-27T06:45:52.697309-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Elapsed":0} -{"Time":"2021-09-27T06:45:52.697314-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI"} -{"Time":"2021-09-27T06:45:52.697318-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":"=== RUN TestHashersAPI\n"} -{"Time":"2021-09-27T06:45:52.697322-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":" hash_test.go:114: math rand seed is 1632739552696815000\n"} -{"Time":"2021-09-27T06:45:52.697325-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":"--- PASS: TestHashersAPI (0.00s)\n"} -{"Time":"2021-09-27T06:45:52.697328-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Elapsed":0} -{"Time":"2021-09-27T06:45:52.697331-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3"} -{"Time":"2021-09-27T06:45:52.697335-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":"=== RUN TestSha3\n"} -{"Time":"2021-09-27T06:45:52.697348-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":" hash_test.go:158: math rand seed is 1632739552697024000\n"} -{"Time":"2021-09-27T06:45:52.697359-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256"} -{"Time":"2021-09-27T06:45:52.697362-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Output":"=== RUN TestSha3/SHA3_256\n"} -{"Time":"2021-09-27T06:45:52.796063-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384"} -{"Time":"2021-09-27T06:45:52.79612-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Output":"=== RUN TestSha3/SHA3_384\n"} -{"Time":"2021-09-27T06:45:52.917391-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":"--- PASS: TestSha3 (0.22s)\n"} -{"Time":"2021-09-27T06:45:52.917417-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Output":" --- PASS: TestSha3/SHA3_256 (0.10s)\n"} -{"Time":"2021-09-27T06:45:52.917421-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Elapsed":0.1} -{"Time":"2021-09-27T06:45:52.917427-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Output":" --- PASS: TestSha3/SHA3_384 (0.12s)\n"} -{"Time":"2021-09-27T06:45:52.917431-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Elapsed":0.12} -{"Time":"2021-09-27T06:45:52.917436-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Elapsed":0.22} -{"Time":"2021-09-27T06:45:52.917441-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256"} -{"Time":"2021-09-27T06:45:52.917446-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Output":"=== RUN TestSanitySha3_256\n"} -{"Time":"2021-09-27T06:45:52.917449-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Output":"--- PASS: TestSanitySha3_256 (0.00s)\n"} -{"Time":"2021-09-27T06:45:52.917452-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Elapsed":0} -{"Time":"2021-09-27T06:45:52.917455-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384"} -{"Time":"2021-09-27T06:45:52.917458-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Output":"=== RUN TestSanitySha3_384\n"} -{"Time":"2021-09-27T06:45:52.91757-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Output":"--- PASS: TestSanitySha3_384 (0.00s)\n"} -{"Time":"2021-09-27T06:45:52.917587-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Elapsed":0} -{"Time":"2021-09-27T06:45:52.917591-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256"} -{"Time":"2021-09-27T06:45:52.917595-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":"=== RUN TestSanitySha2_256\n"} -{"Time":"2021-09-27T06:45:52.917599-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":"--- PASS: TestSanitySha2_256 (0.00s)\n"} -{"Time":"2021-09-27T06:45:52.917602-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Elapsed":0} -{"Time":"2021-09-27T06:45:52.917605-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384"} -{"Time":"2021-09-27T06:45:52.917608-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Output":"=== RUN TestSanitySha2_384\n"} -{"Time":"2021-09-27T06:45:52.917611-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Output":"--- PASS: TestSanitySha2_384 (0.00s)\n"} -{"Time":"2021-09-27T06:45:52.91762-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Elapsed":0} -{"Time":"2021-09-27T06:45:52.917624-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128"} -{"Time":"2021-09-27T06:45:52.917631-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Output":"=== RUN TestSanityKmac128\n"} -{"Time":"2021-09-27T06:45:52.917635-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Output":"--- PASS: TestSanityKmac128 (0.00s)\n"} -{"Time":"2021-09-27T06:45:52.917647-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Elapsed":0} -{"Time":"2021-09-27T06:45:52.91765-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI"} -{"Time":"2021-09-27T06:45:52.917653-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":"=== RUN TestHashersAPI\n"} -{"Time":"2021-09-27T06:45:52.917658-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":" hash_test.go:114: math rand seed is 1632739552917474000\n"} -{"Time":"2021-09-27T06:45:52.917717-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":"--- PASS: TestHashersAPI (0.00s)\n"} -{"Time":"2021-09-27T06:45:52.917729-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Elapsed":0} -{"Time":"2021-09-27T06:45:52.917733-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3"} -{"Time":"2021-09-27T06:45:52.917737-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":"=== RUN TestSha3\n"} -{"Time":"2021-09-27T06:45:52.917742-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":" hash_test.go:158: math rand seed is 1632739552917708000\n"} -{"Time":"2021-09-27T06:45:52.917753-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256"} -{"Time":"2021-09-27T06:45:52.917756-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Output":"=== RUN TestSha3/SHA3_256\n"} -{"Time":"2021-09-27T06:45:53.018094-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384"} -{"Time":"2021-09-27T06:45:53.018182-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Output":"=== RUN TestSha3/SHA3_384\n"} -{"Time":"2021-09-27T06:45:53.140382-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":"--- PASS: TestSha3 (0.22s)\n"} -{"Time":"2021-09-27T06:45:53.140409-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Output":" --- PASS: TestSha3/SHA3_256 (0.10s)\n"} -{"Time":"2021-09-27T06:45:53.140413-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Elapsed":0.1} -{"Time":"2021-09-27T06:45:53.140425-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Output":" --- PASS: TestSha3/SHA3_384 (0.12s)\n"} -{"Time":"2021-09-27T06:45:53.140428-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Elapsed":0.12} -{"Time":"2021-09-27T06:45:53.140431-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Elapsed":0.22} -{"Time":"2021-09-27T06:45:53.140434-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256"} -{"Time":"2021-09-27T06:45:53.140447-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Output":"=== RUN TestSanitySha3_256\n"} -{"Time":"2021-09-27T06:45:53.140452-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Output":"--- PASS: TestSanitySha3_256 (0.00s)\n"} -{"Time":"2021-09-27T06:45:53.140455-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Elapsed":0} -{"Time":"2021-09-27T06:45:53.140458-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384"} -{"Time":"2021-09-27T06:45:53.14046-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Output":"=== RUN TestSanitySha3_384\n"} -{"Time":"2021-09-27T06:45:53.140464-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Output":"--- PASS: TestSanitySha3_384 (0.00s)\n"} -{"Time":"2021-09-27T06:45:53.140578-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Elapsed":0} -{"Time":"2021-09-27T06:45:53.140591-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256"} -{"Time":"2021-09-27T06:45:53.140599-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":"=== RUN TestSanitySha2_256\n"} -{"Time":"2021-09-27T06:45:53.14062-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":"--- PASS: TestSanitySha2_256 (0.00s)\n"} -{"Time":"2021-09-27T06:45:53.140635-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Elapsed":0} -{"Time":"2021-09-27T06:45:53.140641-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384"} -{"Time":"2021-09-27T06:45:53.140646-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Output":"=== RUN TestSanitySha2_384\n"} -{"Time":"2021-09-27T06:45:53.140651-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Output":"--- PASS: TestSanitySha2_384 (0.00s)\n"} -{"Time":"2021-09-27T06:45:53.140656-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Elapsed":0} -{"Time":"2021-09-27T06:45:53.14066-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128"} -{"Time":"2021-09-27T06:45:53.140689-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Output":"=== RUN TestSanityKmac128\n"} -{"Time":"2021-09-27T06:45:53.1407-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Output":"--- PASS: TestSanityKmac128 (0.00s)\n"} -{"Time":"2021-09-27T06:45:53.140705-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Elapsed":0} -{"Time":"2021-09-27T06:45:53.14071-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI"} -{"Time":"2021-09-27T06:45:53.140718-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":"=== RUN TestHashersAPI\n"} -{"Time":"2021-09-27T06:45:53.140724-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":" hash_test.go:114: math rand seed is 1632739553140451000\n"} -{"Time":"2021-09-27T06:45:53.140754-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":"--- PASS: TestHashersAPI (0.00s)\n"} -{"Time":"2021-09-27T06:45:53.140765-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Elapsed":0} -{"Time":"2021-09-27T06:45:53.140768-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3"} -{"Time":"2021-09-27T06:45:53.140772-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":"=== RUN TestSha3\n"} -{"Time":"2021-09-27T06:45:53.140775-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":" hash_test.go:158: math rand seed is 1632739553140702000\n"} -{"Time":"2021-09-27T06:45:53.140779-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256"} -{"Time":"2021-09-27T06:45:53.141538-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Output":"=== RUN TestSha3/SHA3_256\n"} -{"Time":"2021-09-27T06:45:53.240746-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384"} -{"Time":"2021-09-27T06:45:53.24077-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Output":"=== RUN TestSha3/SHA3_384\n"} -{"Time":"2021-09-27T06:45:53.362197-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":"--- PASS: TestSha3 (0.22s)\n"} -{"Time":"2021-09-27T06:45:53.362225-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Output":" --- PASS: TestSha3/SHA3_256 (0.10s)\n"} -{"Time":"2021-09-27T06:45:53.362229-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Elapsed":0.1} -{"Time":"2021-09-27T06:45:53.362234-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Output":" --- PASS: TestSha3/SHA3_384 (0.12s)\n"} -{"Time":"2021-09-27T06:45:53.362237-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Elapsed":0.12} -{"Time":"2021-09-27T06:45:53.362241-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Elapsed":0.22} -{"Time":"2021-09-27T06:45:53.362244-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256"} -{"Time":"2021-09-27T06:45:53.362278-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Output":"=== RUN TestSanitySha3_256\n"} -{"Time":"2021-09-27T06:45:53.362284-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Output":"--- PASS: TestSanitySha3_256 (0.00s)\n"} -{"Time":"2021-09-27T06:45:53.362287-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Elapsed":0} -{"Time":"2021-09-27T06:45:53.36229-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384"} -{"Time":"2021-09-27T06:45:53.362293-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Output":"=== RUN TestSanitySha3_384\n"} -{"Time":"2021-09-27T06:45:53.362298-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Output":"--- PASS: TestSanitySha3_384 (0.00s)\n"} -{"Time":"2021-09-27T06:45:53.362414-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Elapsed":0} -{"Time":"2021-09-27T06:45:53.362433-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256"} -{"Time":"2021-09-27T06:45:53.362446-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":"=== RUN TestSanitySha2_256\n"} -{"Time":"2021-09-27T06:45:53.362463-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":"--- PASS: TestSanitySha2_256 (0.00s)\n"} -{"Time":"2021-09-27T06:45:53.362483-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Elapsed":0} -{"Time":"2021-09-27T06:45:53.362487-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384"} -{"Time":"2021-09-27T06:45:53.36249-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Output":"=== RUN TestSanitySha2_384\n"} -{"Time":"2021-09-27T06:45:53.362494-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Output":"--- PASS: TestSanitySha2_384 (0.00s)\n"} -{"Time":"2021-09-27T06:45:53.362498-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Elapsed":0} -{"Time":"2021-09-27T06:45:53.362502-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128"} -{"Time":"2021-09-27T06:45:53.362505-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Output":"=== RUN TestSanityKmac128\n"} -{"Time":"2021-09-27T06:45:53.362508-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Output":"--- PASS: TestSanityKmac128 (0.00s)\n"} -{"Time":"2021-09-27T06:45:53.36252-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Elapsed":0} -{"Time":"2021-09-27T06:45:53.362523-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI"} -{"Time":"2021-09-27T06:45:53.362526-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":"=== RUN TestHashersAPI\n"} -{"Time":"2021-09-27T06:45:53.362529-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":" hash_test.go:114: math rand seed is 1632739553362249000\n"} -{"Time":"2021-09-27T06:45:53.362534-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":"--- PASS: TestHashersAPI (0.00s)\n"} -{"Time":"2021-09-27T06:45:53.36255-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Elapsed":0} -{"Time":"2021-09-27T06:45:53.362555-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3"} -{"Time":"2021-09-27T06:45:53.362558-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":"=== RUN TestSha3\n"} -{"Time":"2021-09-27T06:45:53.362561-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":" hash_test.go:158: math rand seed is 1632739553362497000\n"} -{"Time":"2021-09-27T06:45:53.362565-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256"} -{"Time":"2021-09-27T06:45:53.362568-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Output":"=== RUN TestSha3/SHA3_256\n"} -{"Time":"2021-09-27T06:45:53.484859-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384"} -{"Time":"2021-09-27T06:45:53.484885-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Output":"=== RUN TestSha3/SHA3_384\n"} -{"Time":"2021-09-27T06:45:53.605156-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":"--- PASS: TestSha3 (0.24s)\n"} -{"Time":"2021-09-27T06:45:53.605222-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Output":" --- PASS: TestSha3/SHA3_256 (0.12s)\n"} -{"Time":"2021-09-27T06:45:53.605251-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Elapsed":0.12} -{"Time":"2021-09-27T06:45:53.605259-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Output":" --- PASS: TestSha3/SHA3_384 (0.12s)\n"} -{"Time":"2021-09-27T06:45:53.605264-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Elapsed":0.12} -{"Time":"2021-09-27T06:45:53.605267-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Elapsed":0.24} -{"Time":"2021-09-27T06:45:53.605271-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256"} -{"Time":"2021-09-27T06:45:53.605274-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Output":"=== RUN TestSanitySha3_256\n"} -{"Time":"2021-09-27T06:45:53.605379-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Output":"--- PASS: TestSanitySha3_256 (0.00s)\n"} -{"Time":"2021-09-27T06:45:53.605388-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Elapsed":0} -{"Time":"2021-09-27T06:45:53.605392-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384"} -{"Time":"2021-09-27T06:45:53.605395-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Output":"=== RUN TestSanitySha3_384\n"} -{"Time":"2021-09-27T06:45:53.605399-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Output":"--- PASS: TestSanitySha3_384 (0.00s)\n"} -{"Time":"2021-09-27T06:45:53.605402-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Elapsed":0} -{"Time":"2021-09-27T06:45:53.605416-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256"} -{"Time":"2021-09-27T06:45:53.60542-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":"=== RUN TestSanitySha2_256\n"} -{"Time":"2021-09-27T06:45:53.605423-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":"--- PASS: TestSanitySha2_256 (0.00s)\n"} -{"Time":"2021-09-27T06:45:53.605426-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Elapsed":0} -{"Time":"2021-09-27T06:45:53.605429-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384"} -{"Time":"2021-09-27T06:45:53.605432-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Output":"=== RUN TestSanitySha2_384\n"} -{"Time":"2021-09-27T06:45:53.605444-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Output":"--- PASS: TestSanitySha2_384 (0.00s)\n"} -{"Time":"2021-09-27T06:45:53.605447-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Elapsed":0} -{"Time":"2021-09-27T06:45:53.60545-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128"} -{"Time":"2021-09-27T06:45:53.605458-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Output":"=== RUN TestSanityKmac128\n"} -{"Time":"2021-09-27T06:45:53.605463-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Output":"--- PASS: TestSanityKmac128 (0.00s)\n"} -{"Time":"2021-09-27T06:45:53.605473-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Elapsed":0} -{"Time":"2021-09-27T06:45:53.605475-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI"} -{"Time":"2021-09-27T06:45:53.605478-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":"=== RUN TestHashersAPI\n"} -{"Time":"2021-09-27T06:45:53.605482-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":" hash_test.go:114: math rand seed is 1632739553605325000\n"} -{"Time":"2021-09-27T06:45:53.605642-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":"--- PASS: TestHashersAPI (0.00s)\n"} -{"Time":"2021-09-27T06:45:53.605657-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Elapsed":0} -{"Time":"2021-09-27T06:45:53.605663-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3"} -{"Time":"2021-09-27T06:45:53.605668-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":"=== RUN TestSha3\n"} -{"Time":"2021-09-27T06:45:53.605673-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":" hash_test.go:158: math rand seed is 1632739553605582000\n"} -{"Time":"2021-09-27T06:45:53.605678-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256"} -{"Time":"2021-09-27T06:45:53.605682-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Output":"=== RUN TestSha3/SHA3_256\n"} -{"Time":"2021-09-27T06:45:53.704399-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384"} -{"Time":"2021-09-27T06:45:53.704425-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Output":"=== RUN TestSha3/SHA3_384\n"} -{"Time":"2021-09-27T06:45:53.826472-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":"--- PASS: TestSha3 (0.22s)\n"} -{"Time":"2021-09-27T06:45:53.826499-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Output":" --- PASS: TestSha3/SHA3_256 (0.10s)\n"} -{"Time":"2021-09-27T06:45:53.826504-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Elapsed":0.1} -{"Time":"2021-09-27T06:45:53.82652-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Output":" --- PASS: TestSha3/SHA3_384 (0.12s)\n"} -{"Time":"2021-09-27T06:45:53.826523-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Elapsed":0.12} -{"Time":"2021-09-27T06:45:53.826527-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Elapsed":0.22} -{"Time":"2021-09-27T06:45:53.826531-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256"} -{"Time":"2021-09-27T06:45:53.826534-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Output":"=== RUN TestSanitySha3_256\n"} -{"Time":"2021-09-27T06:45:53.826537-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Output":"--- PASS: TestSanitySha3_256 (0.00s)\n"} -{"Time":"2021-09-27T06:45:53.82654-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Elapsed":0} -{"Time":"2021-09-27T06:45:53.826546-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384"} -{"Time":"2021-09-27T06:45:53.826549-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Output":"=== RUN TestSanitySha3_384\n"} -{"Time":"2021-09-27T06:45:53.826677-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Output":"--- PASS: TestSanitySha3_384 (0.00s)\n"} -{"Time":"2021-09-27T06:45:53.826691-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Elapsed":0} -{"Time":"2021-09-27T06:45:53.826711-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256"} -{"Time":"2021-09-27T06:45:53.826715-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":"=== RUN TestSanitySha2_256\n"} -{"Time":"2021-09-27T06:45:53.826721-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":"--- PASS: TestSanitySha2_256 (0.00s)\n"} -{"Time":"2021-09-27T06:45:53.826724-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Elapsed":0} -{"Time":"2021-09-27T06:45:53.826727-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384"} -{"Time":"2021-09-27T06:45:53.82673-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Output":"=== RUN TestSanitySha2_384\n"} -{"Time":"2021-09-27T06:45:53.826733-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Output":"--- PASS: TestSanitySha2_384 (0.00s)\n"} -{"Time":"2021-09-27T06:45:53.826737-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Elapsed":0} -{"Time":"2021-09-27T06:45:53.82674-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128"} -{"Time":"2021-09-27T06:45:53.826752-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Output":"=== RUN TestSanityKmac128\n"} -{"Time":"2021-09-27T06:45:53.82676-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Output":"--- PASS: TestSanityKmac128 (0.00s)\n"} -{"Time":"2021-09-27T06:45:53.826763-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Elapsed":0} -{"Time":"2021-09-27T06:45:53.826766-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI"} -{"Time":"2021-09-27T06:45:53.826768-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":"=== RUN TestHashersAPI\n"} -{"Time":"2021-09-27T06:45:53.826774-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":" hash_test.go:114: math rand seed is 1632739553826502000\n"} -{"Time":"2021-09-27T06:45:53.826778-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":"--- PASS: TestHashersAPI (0.00s)\n"} -{"Time":"2021-09-27T06:45:53.826781-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Elapsed":0} -{"Time":"2021-09-27T06:45:53.826783-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3"} -{"Time":"2021-09-27T06:45:53.826786-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":"=== RUN TestSha3\n"} -{"Time":"2021-09-27T06:45:53.826789-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":" hash_test.go:158: math rand seed is 1632739553826733000\n"} -{"Time":"2021-09-27T06:45:53.826807-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256"} -{"Time":"2021-09-27T06:45:53.82681-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Output":"=== RUN TestSha3/SHA3_256\n"} -{"Time":"2021-09-27T06:45:53.92865-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384"} -{"Time":"2021-09-27T06:45:53.928683-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Output":"=== RUN TestSha3/SHA3_384\n"} -{"Time":"2021-09-27T06:45:54.054194-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":"--- PASS: TestSha3 (0.23s)\n"} -{"Time":"2021-09-27T06:45:54.054225-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Output":" --- PASS: TestSha3/SHA3_256 (0.10s)\n"} -{"Time":"2021-09-27T06:45:54.054231-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Elapsed":0.1} -{"Time":"2021-09-27T06:45:54.054238-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Output":" --- PASS: TestSha3/SHA3_384 (0.13s)\n"} -{"Time":"2021-09-27T06:45:54.054251-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Elapsed":0.13} -{"Time":"2021-09-27T06:45:54.054272-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Elapsed":0.23} -{"Time":"2021-09-27T06:45:54.054276-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256"} -{"Time":"2021-09-27T06:45:54.054279-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Output":"=== RUN TestSanitySha3_256\n"} -{"Time":"2021-09-27T06:45:54.05429-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Output":"--- PASS: TestSanitySha3_256 (0.00s)\n"} -{"Time":"2021-09-27T06:45:54.054299-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Elapsed":0} -{"Time":"2021-09-27T06:45:54.054302-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384"} -{"Time":"2021-09-27T06:45:54.054364-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Output":"=== RUN TestSanitySha3_384\n"} -{"Time":"2021-09-27T06:45:54.054376-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Output":"--- PASS: TestSanitySha3_384 (0.00s)\n"} -{"Time":"2021-09-27T06:45:54.05438-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Elapsed":0} -{"Time":"2021-09-27T06:45:54.054383-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256"} -{"Time":"2021-09-27T06:45:54.054386-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":"=== RUN TestSanitySha2_256\n"} -{"Time":"2021-09-27T06:45:54.054389-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":"--- PASS: TestSanitySha2_256 (0.00s)\n"} -{"Time":"2021-09-27T06:45:54.054421-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Elapsed":0} -{"Time":"2021-09-27T06:45:54.054426-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384"} -{"Time":"2021-09-27T06:45:54.054429-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Output":"=== RUN TestSanitySha2_384\n"} -{"Time":"2021-09-27T06:45:54.054435-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Output":"--- PASS: TestSanitySha2_384 (0.00s)\n"} -{"Time":"2021-09-27T06:45:54.054438-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Elapsed":0} -{"Time":"2021-09-27T06:45:54.054441-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128"} -{"Time":"2021-09-27T06:45:54.054444-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Output":"=== RUN TestSanityKmac128\n"} -{"Time":"2021-09-27T06:45:54.054447-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Output":"--- PASS: TestSanityKmac128 (0.00s)\n"} -{"Time":"2021-09-27T06:45:54.05445-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Elapsed":0} -{"Time":"2021-09-27T06:45:54.054453-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI"} -{"Time":"2021-09-27T06:45:54.054464-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":"=== RUN TestHashersAPI\n"} -{"Time":"2021-09-27T06:45:54.054468-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":" hash_test.go:114: math rand seed is 1632739554054239000\n"} -{"Time":"2021-09-27T06:45:54.054481-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":"--- PASS: TestHashersAPI (0.00s)\n"} -{"Time":"2021-09-27T06:45:54.054485-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Elapsed":0} -{"Time":"2021-09-27T06:45:54.054488-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3"} -{"Time":"2021-09-27T06:45:54.054491-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":"=== RUN TestSha3\n"} -{"Time":"2021-09-27T06:45:54.054495-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":" hash_test.go:158: math rand seed is 1632739554054464000\n"} -{"Time":"2021-09-27T06:45:54.054499-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256"} -{"Time":"2021-09-27T06:45:54.054501-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Output":"=== RUN TestSha3/SHA3_256\n"} -{"Time":"2021-09-27T06:45:54.15541-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384"} -{"Time":"2021-09-27T06:45:54.155492-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Output":"=== RUN TestSha3/SHA3_384\n"} -{"Time":"2021-09-27T06:45:54.27999-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":"--- PASS: TestSha3 (0.23s)\n"} -{"Time":"2021-09-27T06:45:54.280024-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Output":" --- PASS: TestSha3/SHA3_256 (0.10s)\n"} -{"Time":"2021-09-27T06:45:54.280032-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Elapsed":0.1} -{"Time":"2021-09-27T06:45:54.280037-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Output":" --- PASS: TestSha3/SHA3_384 (0.12s)\n"} -{"Time":"2021-09-27T06:45:54.280041-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Elapsed":0.12} -{"Time":"2021-09-27T06:45:54.280045-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Elapsed":0.23} -{"Time":"2021-09-27T06:45:54.280049-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256"} -{"Time":"2021-09-27T06:45:54.280086-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Output":"=== RUN TestSanitySha3_256\n"} -{"Time":"2021-09-27T06:45:54.280103-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Output":"--- PASS: TestSanitySha3_256 (0.00s)\n"} -{"Time":"2021-09-27T06:45:54.28011-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Elapsed":0} -{"Time":"2021-09-27T06:45:54.280113-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384"} -{"Time":"2021-09-27T06:45:54.280126-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Output":"=== RUN TestSanitySha3_384\n"} -{"Time":"2021-09-27T06:45:54.280131-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Output":"--- PASS: TestSanitySha3_384 (0.00s)\n"} -{"Time":"2021-09-27T06:45:54.280204-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Elapsed":0} -{"Time":"2021-09-27T06:45:54.280214-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256"} -{"Time":"2021-09-27T06:45:54.280239-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":"=== RUN TestSanitySha2_256\n"} -{"Time":"2021-09-27T06:45:54.280248-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":"--- PASS: TestSanitySha2_256 (0.00s)\n"} -{"Time":"2021-09-27T06:45:54.280252-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Elapsed":0} -{"Time":"2021-09-27T06:45:54.280256-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384"} -{"Time":"2021-09-27T06:45:54.280259-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Output":"=== RUN TestSanitySha2_384\n"} -{"Time":"2021-09-27T06:45:54.280262-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Output":"--- PASS: TestSanitySha2_384 (0.00s)\n"} -{"Time":"2021-09-27T06:45:54.280265-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Elapsed":0} -{"Time":"2021-09-27T06:45:54.280268-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128"} -{"Time":"2021-09-27T06:45:54.280271-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Output":"=== RUN TestSanityKmac128\n"} -{"Time":"2021-09-27T06:45:54.280282-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Output":"--- PASS: TestSanityKmac128 (0.00s)\n"} -{"Time":"2021-09-27T06:45:54.280301-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Elapsed":0} -{"Time":"2021-09-27T06:45:54.280306-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI"} -{"Time":"2021-09-27T06:45:54.280308-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":"=== RUN TestHashersAPI\n"} -{"Time":"2021-09-27T06:45:54.280311-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":" hash_test.go:114: math rand seed is 1632739554280043000\n"} -{"Time":"2021-09-27T06:45:54.280334-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":"--- PASS: TestHashersAPI (0.00s)\n"} -{"Time":"2021-09-27T06:45:54.280338-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Elapsed":0} -{"Time":"2021-09-27T06:45:54.280341-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3"} -{"Time":"2021-09-27T06:45:54.280344-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":"=== RUN TestSha3\n"} -{"Time":"2021-09-27T06:45:54.280348-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":" hash_test.go:158: math rand seed is 1632739554280256000\n"} -{"Time":"2021-09-27T06:45:54.280351-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256"} -{"Time":"2021-09-27T06:45:54.280354-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Output":"=== RUN TestSha3/SHA3_256\n"} -{"Time":"2021-09-27T06:45:54.380565-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384"} -{"Time":"2021-09-27T06:45:54.380592-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Output":"=== RUN TestSha3/SHA3_384\n"} -{"Time":"2021-09-27T06:45:54.500675-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":"--- PASS: TestSha3 (0.22s)\n"} -{"Time":"2021-09-27T06:45:54.500703-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Output":" --- PASS: TestSha3/SHA3_256 (0.10s)\n"} -{"Time":"2021-09-27T06:45:54.50071-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Elapsed":0.1} -{"Time":"2021-09-27T06:45:54.500717-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Output":" --- PASS: TestSha3/SHA3_384 (0.12s)\n"} -{"Time":"2021-09-27T06:45:54.500721-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Elapsed":0.12} -{"Time":"2021-09-27T06:45:54.500739-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Elapsed":0.22} -{"Time":"2021-09-27T06:45:54.500742-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256"} -{"Time":"2021-09-27T06:45:54.500745-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Output":"=== RUN TestSanitySha3_256\n"} -{"Time":"2021-09-27T06:45:54.500748-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Output":"--- PASS: TestSanitySha3_256 (0.00s)\n"} -{"Time":"2021-09-27T06:45:54.500751-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Elapsed":0} -{"Time":"2021-09-27T06:45:54.500754-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384"} -{"Time":"2021-09-27T06:45:54.500862-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Output":"=== RUN TestSanitySha3_384\n"} -{"Time":"2021-09-27T06:45:54.500873-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Output":"--- PASS: TestSanitySha3_384 (0.00s)\n"} -{"Time":"2021-09-27T06:45:54.500896-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Elapsed":0} -{"Time":"2021-09-27T06:45:54.500913-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256"} -{"Time":"2021-09-27T06:45:54.500917-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":"=== RUN TestSanitySha2_256\n"} -{"Time":"2021-09-27T06:45:54.500923-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":"--- PASS: TestSanitySha2_256 (0.00s)\n"} -{"Time":"2021-09-27T06:45:54.500926-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Elapsed":0} -{"Time":"2021-09-27T06:45:54.500929-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384"} -{"Time":"2021-09-27T06:45:54.500932-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Output":"=== RUN TestSanitySha2_384\n"} -{"Time":"2021-09-27T06:45:54.500935-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Output":"--- PASS: TestSanitySha2_384 (0.00s)\n"} -{"Time":"2021-09-27T06:45:54.500957-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Elapsed":0} -{"Time":"2021-09-27T06:45:54.500961-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128"} -{"Time":"2021-09-27T06:45:54.500964-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Output":"=== RUN TestSanityKmac128\n"} -{"Time":"2021-09-27T06:45:54.500968-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Output":"--- PASS: TestSanityKmac128 (0.00s)\n"} -{"Time":"2021-09-27T06:45:54.500971-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Elapsed":0} -{"Time":"2021-09-27T06:45:54.500974-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI"} -{"Time":"2021-09-27T06:45:54.500977-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":"=== RUN TestHashersAPI\n"} -{"Time":"2021-09-27T06:45:54.50098-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":" hash_test.go:114: math rand seed is 1632739554500707000\n"} -{"Time":"2021-09-27T06:45:54.500988-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":"--- PASS: TestHashersAPI (0.00s)\n"} -{"Time":"2021-09-27T06:45:54.501007-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Elapsed":0} -{"Time":"2021-09-27T06:45:54.501011-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3"} -{"Time":"2021-09-27T06:45:54.501014-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":"=== RUN TestSha3\n"} -{"Time":"2021-09-27T06:45:54.501017-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":" hash_test.go:158: math rand seed is 1632739554500935000\n"} -{"Time":"2021-09-27T06:45:54.501023-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256"} -{"Time":"2021-09-27T06:45:54.501026-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Output":"=== RUN TestSha3/SHA3_256\n"} -{"Time":"2021-09-27T06:45:54.59914-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384"} -{"Time":"2021-09-27T06:45:54.599167-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Output":"=== RUN TestSha3/SHA3_384\n"} -{"Time":"2021-09-27T06:45:54.719353-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":"--- PASS: TestSha3 (0.22s)\n"} -{"Time":"2021-09-27T06:45:54.719385-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Output":" --- PASS: TestSha3/SHA3_256 (0.10s)\n"} -{"Time":"2021-09-27T06:45:54.71939-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Elapsed":0.1} -{"Time":"2021-09-27T06:45:54.719396-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Output":" --- PASS: TestSha3/SHA3_384 (0.12s)\n"} -{"Time":"2021-09-27T06:45:54.719399-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Elapsed":0.12} -{"Time":"2021-09-27T06:45:54.719402-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Elapsed":0.22} -{"Time":"2021-09-27T06:45:54.719405-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Output":"PASS\n"} -{"Time":"2021-09-27T06:45:54.720685-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Output":"ok \tgithub.com/onflow/flow-go/crypto/hash\t2.541s\n"} -{"Time":"2021-09-27T06:45:54.720757-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Elapsed":2.541} \ No newline at end of file +{"Time":"2021-09-27T06:45:52.470262-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256"} +{"Time":"2021-09-27T06:45:52.470682-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Output":"=== RUN TestSanitySha3_256\n"} +{"Time":"2021-09-27T06:45:52.470718-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Output":"--- PASS: TestSanitySha3_256 (0.00s)\n"} +{"Time":"2021-09-27T06:45:52.470724-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Elapsed":0} +{"Time":"2021-09-27T06:45:52.470741-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384"} +{"Time":"2021-09-27T06:45:52.470745-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Output":"=== RUN TestSanitySha3_384\n"} +{"Time":"2021-09-27T06:45:52.470751-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Output":"--- PASS: TestSanitySha3_384 (0.00s)\n"} +{"Time":"2021-09-27T06:45:52.470756-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Elapsed":0} +{"Time":"2021-09-27T06:45:52.47076-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256"} +{"Time":"2021-09-27T06:45:52.470876-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":"=== RUN TestSanitySha2_256\n"} +{"Time":"2021-09-27T06:45:52.470895-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":"--- PASS: TestSanitySha2_256 (0.00s)\n"} +{"Time":"2021-09-27T06:45:52.470901-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Elapsed":0} +{"Time":"2021-09-27T06:45:52.470907-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384"} +{"Time":"2021-09-27T06:45:52.470911-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Output":"=== RUN TestSanitySha2_384\n"} +{"Time":"2021-09-27T06:45:52.470917-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Output":"--- PASS: TestSanitySha2_384 (0.00s)\n"} +{"Time":"2021-09-27T06:45:52.47093-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Elapsed":0} +{"Time":"2021-09-27T06:45:52.470935-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128"} +{"Time":"2021-09-27T06:45:52.470943-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Output":"=== RUN TestSanityKmac128\n"} +{"Time":"2021-09-27T06:45:52.470949-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Output":"--- PASS: TestSanityKmac128 (0.00s)\n"} +{"Time":"2021-09-27T06:45:52.470953-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Elapsed":0} +{"Time":"2021-09-27T06:45:52.470957-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI"} +{"Time":"2021-09-27T06:45:52.470981-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":"=== RUN TestHashersAPI\n"} +{"Time":"2021-09-27T06:45:52.470992-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":" hash_test.go:114: math rand seed is 1632739552470379000\n"} +{"Time":"2021-09-27T06:45:52.471024-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":"--- PASS: TestHashersAPI (0.00s)\n"} +{"Time":"2021-09-27T06:45:52.471029-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Elapsed":0} +{"Time":"2021-09-27T06:45:52.471034-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3"} +{"Time":"2021-09-27T06:45:52.471042-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":"=== RUN TestSha3\n"} +{"Time":"2021-09-27T06:45:52.471046-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":" hash_test.go:158: math rand seed is 1632739552470723000\n"} +{"Time":"2021-09-27T06:45:52.471051-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256"} +{"Time":"2021-09-27T06:45:52.471055-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Output":"=== RUN TestSha3/SHA3_256\n"} +{"Time":"2021-09-27T06:45:52.575573-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384"} +{"Time":"2021-09-27T06:45:52.575605-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Output":"=== RUN TestSha3/SHA3_384\n"} +{"Time":"2021-09-27T06:45:52.696742-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":"--- PASS: TestSha3 (0.23s)\n"} +{"Time":"2021-09-27T06:45:52.696768-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Output":" --- PASS: TestSha3/SHA3_256 (0.10s)\n"} +{"Time":"2021-09-27T06:45:52.696773-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Elapsed":0.1} +{"Time":"2021-09-27T06:45:52.696784-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Output":" --- PASS: TestSha3/SHA3_384 (0.12s)\n"} +{"Time":"2021-09-27T06:45:52.696787-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Elapsed":0.12} +{"Time":"2021-09-27T06:45:52.69679-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Elapsed":0.23} +{"Time":"2021-09-27T06:45:52.696793-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256"} +{"Time":"2021-09-27T06:45:52.696796-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Output":"=== RUN TestSanitySha3_256\n"} +{"Time":"2021-09-27T06:45:52.6968-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Output":"--- PASS: TestSanitySha3_256 (0.00s)\n"} +{"Time":"2021-09-27T06:45:52.696803-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Elapsed":0} +{"Time":"2021-09-27T06:45:52.696806-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384"} +{"Time":"2021-09-27T06:45:52.696809-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Output":"=== RUN TestSanitySha3_384\n"} +{"Time":"2021-09-27T06:45:52.697192-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Output":"--- PASS: TestSanitySha3_384 (0.00s)\n"} +{"Time":"2021-09-27T06:45:52.697219-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Elapsed":0} +{"Time":"2021-09-27T06:45:52.697226-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256"} +{"Time":"2021-09-27T06:45:52.697243-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":"=== RUN TestSanitySha2_256\n"} +{"Time":"2021-09-27T06:45:52.697259-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":"--- PASS: TestSanitySha2_256 (0.00s)\n"} +{"Time":"2021-09-27T06:45:52.697266-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Elapsed":0} +{"Time":"2021-09-27T06:45:52.697276-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384"} +{"Time":"2021-09-27T06:45:52.697281-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Output":"=== RUN TestSanitySha2_384\n"} +{"Time":"2021-09-27T06:45:52.697286-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Output":"--- PASS: TestSanitySha2_384 (0.00s)\n"} +{"Time":"2021-09-27T06:45:52.69729-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Elapsed":0} +{"Time":"2021-09-27T06:45:52.697295-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128"} +{"Time":"2021-09-27T06:45:52.6973-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Output":"=== RUN TestSanityKmac128\n"} +{"Time":"2021-09-27T06:45:52.697305-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Output":"--- PASS: TestSanityKmac128 (0.00s)\n"} +{"Time":"2021-09-27T06:45:52.697309-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Elapsed":0} +{"Time":"2021-09-27T06:45:52.697314-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI"} +{"Time":"2021-09-27T06:45:52.697318-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":"=== RUN TestHashersAPI\n"} +{"Time":"2021-09-27T06:45:52.697322-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":" hash_test.go:114: math rand seed is 1632739552696815000\n"} +{"Time":"2021-09-27T06:45:52.697325-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":"--- PASS: TestHashersAPI (0.00s)\n"} +{"Time":"2021-09-27T06:45:52.697328-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Elapsed":0} +{"Time":"2021-09-27T06:45:52.697331-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3"} +{"Time":"2021-09-27T06:45:52.697335-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":"=== RUN TestSha3\n"} +{"Time":"2021-09-27T06:45:52.697348-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":" hash_test.go:158: math rand seed is 1632739552697024000\n"} +{"Time":"2021-09-27T06:45:52.697359-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256"} +{"Time":"2021-09-27T06:45:52.697362-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Output":"=== RUN TestSha3/SHA3_256\n"} +{"Time":"2021-09-27T06:45:52.796063-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384"} +{"Time":"2021-09-27T06:45:52.79612-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Output":"=== RUN TestSha3/SHA3_384\n"} +{"Time":"2021-09-27T06:45:52.917391-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":"--- PASS: TestSha3 (0.22s)\n"} +{"Time":"2021-09-27T06:45:52.917417-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Output":" --- PASS: TestSha3/SHA3_256 (0.10s)\n"} +{"Time":"2021-09-27T06:45:52.917421-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Elapsed":0.1} +{"Time":"2021-09-27T06:45:52.917427-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Output":" --- PASS: TestSha3/SHA3_384 (0.12s)\n"} +{"Time":"2021-09-27T06:45:52.917431-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Elapsed":0.12} +{"Time":"2021-09-27T06:45:52.917436-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Elapsed":0.22} +{"Time":"2021-09-27T06:45:52.917441-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256"} +{"Time":"2021-09-27T06:45:52.917446-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Output":"=== RUN TestSanitySha3_256\n"} +{"Time":"2021-09-27T06:45:52.917449-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Output":"--- PASS: TestSanitySha3_256 (0.00s)\n"} +{"Time":"2021-09-27T06:45:52.917452-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Elapsed":0} +{"Time":"2021-09-27T06:45:52.917455-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384"} +{"Time":"2021-09-27T06:45:52.917458-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Output":"=== RUN TestSanitySha3_384\n"} +{"Time":"2021-09-27T06:45:52.91757-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Output":"--- PASS: TestSanitySha3_384 (0.00s)\n"} +{"Time":"2021-09-27T06:45:52.917587-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Elapsed":0} +{"Time":"2021-09-27T06:45:52.917591-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256"} +{"Time":"2021-09-27T06:45:52.917595-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":"=== RUN TestSanitySha2_256\n"} +{"Time":"2021-09-27T06:45:52.917599-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":"--- PASS: TestSanitySha2_256 (0.00s)\n"} +{"Time":"2021-09-27T06:45:52.917602-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Elapsed":0} +{"Time":"2021-09-27T06:45:52.917605-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384"} +{"Time":"2021-09-27T06:45:52.917608-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Output":"=== RUN TestSanitySha2_384\n"} +{"Time":"2021-09-27T06:45:52.917611-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Output":"--- PASS: TestSanitySha2_384 (0.00s)\n"} +{"Time":"2021-09-27T06:45:52.91762-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Elapsed":0} +{"Time":"2021-09-27T06:45:52.917624-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128"} +{"Time":"2021-09-27T06:45:52.917631-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Output":"=== RUN TestSanityKmac128\n"} +{"Time":"2021-09-27T06:45:52.917635-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Output":"--- PASS: TestSanityKmac128 (0.00s)\n"} +{"Time":"2021-09-27T06:45:52.917647-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Elapsed":0} +{"Time":"2021-09-27T06:45:52.91765-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI"} +{"Time":"2021-09-27T06:45:52.917653-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":"=== RUN TestHashersAPI\n"} +{"Time":"2021-09-27T06:45:52.917658-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":" hash_test.go:114: math rand seed is 1632739552917474000\n"} +{"Time":"2021-09-27T06:45:52.917717-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":"--- PASS: TestHashersAPI (0.00s)\n"} +{"Time":"2021-09-27T06:45:52.917729-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Elapsed":0} +{"Time":"2021-09-27T06:45:52.917733-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3"} +{"Time":"2021-09-27T06:45:52.917737-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":"=== RUN TestSha3\n"} +{"Time":"2021-09-27T06:45:52.917742-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":" hash_test.go:158: math rand seed is 1632739552917708000\n"} +{"Time":"2021-09-27T06:45:52.917753-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256"} +{"Time":"2021-09-27T06:45:52.917756-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Output":"=== RUN TestSha3/SHA3_256\n"} +{"Time":"2021-09-27T06:45:53.018094-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384"} +{"Time":"2021-09-27T06:45:53.018182-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Output":"=== RUN TestSha3/SHA3_384\n"} +{"Time":"2021-09-27T06:45:53.140382-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":"--- PASS: TestSha3 (0.22s)\n"} +{"Time":"2021-09-27T06:45:53.140409-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Output":" --- PASS: TestSha3/SHA3_256 (0.10s)\n"} +{"Time":"2021-09-27T06:45:53.140413-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Elapsed":0.1} +{"Time":"2021-09-27T06:45:53.140425-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Output":" --- PASS: TestSha3/SHA3_384 (0.12s)\n"} +{"Time":"2021-09-27T06:45:53.140428-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Elapsed":0.12} +{"Time":"2021-09-27T06:45:53.140431-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Elapsed":0.22} +{"Time":"2021-09-27T06:45:53.140434-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256"} +{"Time":"2021-09-27T06:45:53.140447-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Output":"=== RUN TestSanitySha3_256\n"} +{"Time":"2021-09-27T06:45:53.140452-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Output":"--- PASS: TestSanitySha3_256 (0.00s)\n"} +{"Time":"2021-09-27T06:45:53.140455-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Elapsed":0} +{"Time":"2021-09-27T06:45:53.140458-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384"} +{"Time":"2021-09-27T06:45:53.14046-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Output":"=== RUN TestSanitySha3_384\n"} +{"Time":"2021-09-27T06:45:53.140464-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Output":"--- PASS: TestSanitySha3_384 (0.00s)\n"} +{"Time":"2021-09-27T06:45:53.140578-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Elapsed":0} +{"Time":"2021-09-27T06:45:53.140591-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256"} +{"Time":"2021-09-27T06:45:53.140599-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":"=== RUN TestSanitySha2_256\n"} +{"Time":"2021-09-27T06:45:53.14062-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":"--- PASS: TestSanitySha2_256 (0.00s)\n"} +{"Time":"2021-09-27T06:45:53.140635-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Elapsed":0} +{"Time":"2021-09-27T06:45:53.140641-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384"} +{"Time":"2021-09-27T06:45:53.140646-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Output":"=== RUN TestSanitySha2_384\n"} +{"Time":"2021-09-27T06:45:53.140651-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Output":"--- PASS: TestSanitySha2_384 (0.00s)\n"} +{"Time":"2021-09-27T06:45:53.140656-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Elapsed":0} +{"Time":"2021-09-27T06:45:53.14066-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128"} +{"Time":"2021-09-27T06:45:53.140689-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Output":"=== RUN TestSanityKmac128\n"} +{"Time":"2021-09-27T06:45:53.1407-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Output":"--- PASS: TestSanityKmac128 (0.00s)\n"} +{"Time":"2021-09-27T06:45:53.140705-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Elapsed":0} +{"Time":"2021-09-27T06:45:53.14071-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI"} +{"Time":"2021-09-27T06:45:53.140718-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":"=== RUN TestHashersAPI\n"} +{"Time":"2021-09-27T06:45:53.140724-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":" hash_test.go:114: math rand seed is 1632739553140451000\n"} +{"Time":"2021-09-27T06:45:53.140754-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":"--- PASS: TestHashersAPI (0.00s)\n"} +{"Time":"2021-09-27T06:45:53.140765-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Elapsed":0} +{"Time":"2021-09-27T06:45:53.140768-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3"} +{"Time":"2021-09-27T06:45:53.140772-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":"=== RUN TestSha3\n"} +{"Time":"2021-09-27T06:45:53.140775-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":" hash_test.go:158: math rand seed is 1632739553140702000\n"} +{"Time":"2021-09-27T06:45:53.140779-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256"} +{"Time":"2021-09-27T06:45:53.141538-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Output":"=== RUN TestSha3/SHA3_256\n"} +{"Time":"2021-09-27T06:45:53.240746-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384"} +{"Time":"2021-09-27T06:45:53.24077-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Output":"=== RUN TestSha3/SHA3_384\n"} +{"Time":"2021-09-27T06:45:53.362197-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":"--- PASS: TestSha3 (0.22s)\n"} +{"Time":"2021-09-27T06:45:53.362225-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Output":" --- PASS: TestSha3/SHA3_256 (0.10s)\n"} +{"Time":"2021-09-27T06:45:53.362229-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Elapsed":0.1} +{"Time":"2021-09-27T06:45:53.362234-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Output":" --- PASS: TestSha3/SHA3_384 (0.12s)\n"} +{"Time":"2021-09-27T06:45:53.362237-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Elapsed":0.12} +{"Time":"2021-09-27T06:45:53.362241-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Elapsed":0.22} +{"Time":"2021-09-27T06:45:53.362244-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256"} +{"Time":"2021-09-27T06:45:53.362278-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Output":"=== RUN TestSanitySha3_256\n"} +{"Time":"2021-09-27T06:45:53.362284-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Output":"--- PASS: TestSanitySha3_256 (0.00s)\n"} +{"Time":"2021-09-27T06:45:53.362287-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Elapsed":0} +{"Time":"2021-09-27T06:45:53.36229-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384"} +{"Time":"2021-09-27T06:45:53.362293-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Output":"=== RUN TestSanitySha3_384\n"} +{"Time":"2021-09-27T06:45:53.362298-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Output":"--- PASS: TestSanitySha3_384 (0.00s)\n"} +{"Time":"2021-09-27T06:45:53.362414-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Elapsed":0} +{"Time":"2021-09-27T06:45:53.362433-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256"} +{"Time":"2021-09-27T06:45:53.362446-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":"=== RUN TestSanitySha2_256\n"} +{"Time":"2021-09-27T06:45:53.362463-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":"--- PASS: TestSanitySha2_256 (0.00s)\n"} +{"Time":"2021-09-27T06:45:53.362483-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Elapsed":0} +{"Time":"2021-09-27T06:45:53.362487-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384"} +{"Time":"2021-09-27T06:45:53.36249-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Output":"=== RUN TestSanitySha2_384\n"} +{"Time":"2021-09-27T06:45:53.362494-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Output":"--- PASS: TestSanitySha2_384 (0.00s)\n"} +{"Time":"2021-09-27T06:45:53.362498-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Elapsed":0} +{"Time":"2021-09-27T06:45:53.362502-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128"} +{"Time":"2021-09-27T06:45:53.362505-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Output":"=== RUN TestSanityKmac128\n"} +{"Time":"2021-09-27T06:45:53.362508-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Output":"--- PASS: TestSanityKmac128 (0.00s)\n"} +{"Time":"2021-09-27T06:45:53.36252-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Elapsed":0} +{"Time":"2021-09-27T06:45:53.362523-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI"} +{"Time":"2021-09-27T06:45:53.362526-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":"=== RUN TestHashersAPI\n"} +{"Time":"2021-09-27T06:45:53.362529-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":" hash_test.go:114: math rand seed is 1632739553362249000\n"} +{"Time":"2021-09-27T06:45:53.362534-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":"--- PASS: TestHashersAPI (0.00s)\n"} +{"Time":"2021-09-27T06:45:53.36255-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Elapsed":0} +{"Time":"2021-09-27T06:45:53.362555-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3"} +{"Time":"2021-09-27T06:45:53.362558-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":"=== RUN TestSha3\n"} +{"Time":"2021-09-27T06:45:53.362561-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":" hash_test.go:158: math rand seed is 1632739553362497000\n"} +{"Time":"2021-09-27T06:45:53.362565-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256"} +{"Time":"2021-09-27T06:45:53.362568-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Output":"=== RUN TestSha3/SHA3_256\n"} +{"Time":"2021-09-27T06:45:53.484859-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384"} +{"Time":"2021-09-27T06:45:53.484885-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Output":"=== RUN TestSha3/SHA3_384\n"} +{"Time":"2021-09-27T06:45:53.605156-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":"--- PASS: TestSha3 (0.24s)\n"} +{"Time":"2021-09-27T06:45:53.605222-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Output":" --- PASS: TestSha3/SHA3_256 (0.12s)\n"} +{"Time":"2021-09-27T06:45:53.605251-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Elapsed":0.12} +{"Time":"2021-09-27T06:45:53.605259-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Output":" --- PASS: TestSha3/SHA3_384 (0.12s)\n"} +{"Time":"2021-09-27T06:45:53.605264-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Elapsed":0.12} +{"Time":"2021-09-27T06:45:53.605267-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Elapsed":0.24} +{"Time":"2021-09-27T06:45:53.605271-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256"} +{"Time":"2021-09-27T06:45:53.605274-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Output":"=== RUN TestSanitySha3_256\n"} +{"Time":"2021-09-27T06:45:53.605379-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Output":"--- PASS: TestSanitySha3_256 (0.00s)\n"} +{"Time":"2021-09-27T06:45:53.605388-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Elapsed":0} +{"Time":"2021-09-27T06:45:53.605392-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384"} +{"Time":"2021-09-27T06:45:53.605395-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Output":"=== RUN TestSanitySha3_384\n"} +{"Time":"2021-09-27T06:45:53.605399-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Output":"--- PASS: TestSanitySha3_384 (0.00s)\n"} +{"Time":"2021-09-27T06:45:53.605402-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Elapsed":0} +{"Time":"2021-09-27T06:45:53.605416-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256"} +{"Time":"2021-09-27T06:45:53.60542-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":"=== RUN TestSanitySha2_256\n"} +{"Time":"2021-09-27T06:45:53.605423-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":"--- PASS: TestSanitySha2_256 (0.00s)\n"} +{"Time":"2021-09-27T06:45:53.605426-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Elapsed":0} +{"Time":"2021-09-27T06:45:53.605429-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384"} +{"Time":"2021-09-27T06:45:53.605432-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Output":"=== RUN TestSanitySha2_384\n"} +{"Time":"2021-09-27T06:45:53.605444-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Output":"--- PASS: TestSanitySha2_384 (0.00s)\n"} +{"Time":"2021-09-27T06:45:53.605447-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Elapsed":0} +{"Time":"2021-09-27T06:45:53.60545-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128"} +{"Time":"2021-09-27T06:45:53.605458-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Output":"=== RUN TestSanityKmac128\n"} +{"Time":"2021-09-27T06:45:53.605463-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Output":"--- PASS: TestSanityKmac128 (0.00s)\n"} +{"Time":"2021-09-27T06:45:53.605473-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Elapsed":0} +{"Time":"2021-09-27T06:45:53.605475-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI"} +{"Time":"2021-09-27T06:45:53.605478-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":"=== RUN TestHashersAPI\n"} +{"Time":"2021-09-27T06:45:53.605482-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":" hash_test.go:114: math rand seed is 1632739553605325000\n"} +{"Time":"2021-09-27T06:45:53.605642-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":"--- PASS: TestHashersAPI (0.00s)\n"} +{"Time":"2021-09-27T06:45:53.605657-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Elapsed":0} +{"Time":"2021-09-27T06:45:53.605663-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3"} +{"Time":"2021-09-27T06:45:53.605668-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":"=== RUN TestSha3\n"} +{"Time":"2021-09-27T06:45:53.605673-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":" hash_test.go:158: math rand seed is 1632739553605582000\n"} +{"Time":"2021-09-27T06:45:53.605678-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256"} +{"Time":"2021-09-27T06:45:53.605682-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Output":"=== RUN TestSha3/SHA3_256\n"} +{"Time":"2021-09-27T06:45:53.704399-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384"} +{"Time":"2021-09-27T06:45:53.704425-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Output":"=== RUN TestSha3/SHA3_384\n"} +{"Time":"2021-09-27T06:45:53.826472-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":"--- PASS: TestSha3 (0.22s)\n"} +{"Time":"2021-09-27T06:45:53.826499-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Output":" --- PASS: TestSha3/SHA3_256 (0.10s)\n"} +{"Time":"2021-09-27T06:45:53.826504-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Elapsed":0.1} +{"Time":"2021-09-27T06:45:53.82652-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Output":" --- PASS: TestSha3/SHA3_384 (0.12s)\n"} +{"Time":"2021-09-27T06:45:53.826523-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Elapsed":0.12} +{"Time":"2021-09-27T06:45:53.826527-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Elapsed":0.22} +{"Time":"2021-09-27T06:45:53.826531-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256"} +{"Time":"2021-09-27T06:45:53.826534-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Output":"=== RUN TestSanitySha3_256\n"} +{"Time":"2021-09-27T06:45:53.826537-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Output":"--- PASS: TestSanitySha3_256 (0.00s)\n"} +{"Time":"2021-09-27T06:45:53.82654-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Elapsed":0} +{"Time":"2021-09-27T06:45:53.826546-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384"} +{"Time":"2021-09-27T06:45:53.826549-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Output":"=== RUN TestSanitySha3_384\n"} +{"Time":"2021-09-27T06:45:53.826677-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Output":"--- PASS: TestSanitySha3_384 (0.00s)\n"} +{"Time":"2021-09-27T06:45:53.826691-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Elapsed":0} +{"Time":"2021-09-27T06:45:53.826711-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256"} +{"Time":"2021-09-27T06:45:53.826715-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":"=== RUN TestSanitySha2_256\n"} +{"Time":"2021-09-27T06:45:53.826721-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":"--- PASS: TestSanitySha2_256 (0.00s)\n"} +{"Time":"2021-09-27T06:45:53.826724-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Elapsed":0} +{"Time":"2021-09-27T06:45:53.826727-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384"} +{"Time":"2021-09-27T06:45:53.82673-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Output":"=== RUN TestSanitySha2_384\n"} +{"Time":"2021-09-27T06:45:53.826733-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Output":"--- PASS: TestSanitySha2_384 (0.00s)\n"} +{"Time":"2021-09-27T06:45:53.826737-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Elapsed":0} +{"Time":"2021-09-27T06:45:53.82674-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128"} +{"Time":"2021-09-27T06:45:53.826752-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Output":"=== RUN TestSanityKmac128\n"} +{"Time":"2021-09-27T06:45:53.82676-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Output":"--- PASS: TestSanityKmac128 (0.00s)\n"} +{"Time":"2021-09-27T06:45:53.826763-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Elapsed":0} +{"Time":"2021-09-27T06:45:53.826766-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI"} +{"Time":"2021-09-27T06:45:53.826768-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":"=== RUN TestHashersAPI\n"} +{"Time":"2021-09-27T06:45:53.826774-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":" hash_test.go:114: math rand seed is 1632739553826502000\n"} +{"Time":"2021-09-27T06:45:53.826778-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":"--- PASS: TestHashersAPI (0.00s)\n"} +{"Time":"2021-09-27T06:45:53.826781-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Elapsed":0} +{"Time":"2021-09-27T06:45:53.826783-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3"} +{"Time":"2021-09-27T06:45:53.826786-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":"=== RUN TestSha3\n"} +{"Time":"2021-09-27T06:45:53.826789-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":" hash_test.go:158: math rand seed is 1632739553826733000\n"} +{"Time":"2021-09-27T06:45:53.826807-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256"} +{"Time":"2021-09-27T06:45:53.82681-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Output":"=== RUN TestSha3/SHA3_256\n"} +{"Time":"2021-09-27T06:45:53.92865-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384"} +{"Time":"2021-09-27T06:45:53.928683-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Output":"=== RUN TestSha3/SHA3_384\n"} +{"Time":"2021-09-27T06:45:54.054194-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":"--- PASS: TestSha3 (0.23s)\n"} +{"Time":"2021-09-27T06:45:54.054225-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Output":" --- PASS: TestSha3/SHA3_256 (0.10s)\n"} +{"Time":"2021-09-27T06:45:54.054231-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Elapsed":0.1} +{"Time":"2021-09-27T06:45:54.054238-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Output":" --- PASS: TestSha3/SHA3_384 (0.13s)\n"} +{"Time":"2021-09-27T06:45:54.054251-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Elapsed":0.13} +{"Time":"2021-09-27T06:45:54.054272-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Elapsed":0.23} +{"Time":"2021-09-27T06:45:54.054276-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256"} +{"Time":"2021-09-27T06:45:54.054279-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Output":"=== RUN TestSanitySha3_256\n"} +{"Time":"2021-09-27T06:45:54.05429-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Output":"--- PASS: TestSanitySha3_256 (0.00s)\n"} +{"Time":"2021-09-27T06:45:54.054299-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Elapsed":0} +{"Time":"2021-09-27T06:45:54.054302-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384"} +{"Time":"2021-09-27T06:45:54.054364-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Output":"=== RUN TestSanitySha3_384\n"} +{"Time":"2021-09-27T06:45:54.054376-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Output":"--- PASS: TestSanitySha3_384 (0.00s)\n"} +{"Time":"2021-09-27T06:45:54.05438-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Elapsed":0} +{"Time":"2021-09-27T06:45:54.054383-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256"} +{"Time":"2021-09-27T06:45:54.054386-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":"=== RUN TestSanitySha2_256\n"} +{"Time":"2021-09-27T06:45:54.054389-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":"--- PASS: TestSanitySha2_256 (0.00s)\n"} +{"Time":"2021-09-27T06:45:54.054421-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Elapsed":0} +{"Time":"2021-09-27T06:45:54.054426-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384"} +{"Time":"2021-09-27T06:45:54.054429-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Output":"=== RUN TestSanitySha2_384\n"} +{"Time":"2021-09-27T06:45:54.054435-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Output":"--- PASS: TestSanitySha2_384 (0.00s)\n"} +{"Time":"2021-09-27T06:45:54.054438-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Elapsed":0} +{"Time":"2021-09-27T06:45:54.054441-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128"} +{"Time":"2021-09-27T06:45:54.054444-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Output":"=== RUN TestSanityKmac128\n"} +{"Time":"2021-09-27T06:45:54.054447-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Output":"--- PASS: TestSanityKmac128 (0.00s)\n"} +{"Time":"2021-09-27T06:45:54.05445-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Elapsed":0} +{"Time":"2021-09-27T06:45:54.054453-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI"} +{"Time":"2021-09-27T06:45:54.054464-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":"=== RUN TestHashersAPI\n"} +{"Time":"2021-09-27T06:45:54.054468-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":" hash_test.go:114: math rand seed is 1632739554054239000\n"} +{"Time":"2021-09-27T06:45:54.054481-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":"--- PASS: TestHashersAPI (0.00s)\n"} +{"Time":"2021-09-27T06:45:54.054485-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Elapsed":0} +{"Time":"2021-09-27T06:45:54.054488-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3"} +{"Time":"2021-09-27T06:45:54.054491-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":"=== RUN TestSha3\n"} +{"Time":"2021-09-27T06:45:54.054495-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":" hash_test.go:158: math rand seed is 1632739554054464000\n"} +{"Time":"2021-09-27T06:45:54.054499-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256"} +{"Time":"2021-09-27T06:45:54.054501-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Output":"=== RUN TestSha3/SHA3_256\n"} +{"Time":"2021-09-27T06:45:54.15541-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384"} +{"Time":"2021-09-27T06:45:54.155492-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Output":"=== RUN TestSha3/SHA3_384\n"} +{"Time":"2021-09-27T06:45:54.27999-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":"--- PASS: TestSha3 (0.23s)\n"} +{"Time":"2021-09-27T06:45:54.280024-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Output":" --- PASS: TestSha3/SHA3_256 (0.10s)\n"} +{"Time":"2021-09-27T06:45:54.280032-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Elapsed":0.1} +{"Time":"2021-09-27T06:45:54.280037-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Output":" --- PASS: TestSha3/SHA3_384 (0.12s)\n"} +{"Time":"2021-09-27T06:45:54.280041-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Elapsed":0.12} +{"Time":"2021-09-27T06:45:54.280045-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Elapsed":0.23} +{"Time":"2021-09-27T06:45:54.280049-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256"} +{"Time":"2021-09-27T06:45:54.280086-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Output":"=== RUN TestSanitySha3_256\n"} +{"Time":"2021-09-27T06:45:54.280103-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Output":"--- PASS: TestSanitySha3_256 (0.00s)\n"} +{"Time":"2021-09-27T06:45:54.28011-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Elapsed":0} +{"Time":"2021-09-27T06:45:54.280113-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384"} +{"Time":"2021-09-27T06:45:54.280126-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Output":"=== RUN TestSanitySha3_384\n"} +{"Time":"2021-09-27T06:45:54.280131-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Output":"--- PASS: TestSanitySha3_384 (0.00s)\n"} +{"Time":"2021-09-27T06:45:54.280204-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Elapsed":0} +{"Time":"2021-09-27T06:45:54.280214-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256"} +{"Time":"2021-09-27T06:45:54.280239-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":"=== RUN TestSanitySha2_256\n"} +{"Time":"2021-09-27T06:45:54.280248-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":"--- PASS: TestSanitySha2_256 (0.00s)\n"} +{"Time":"2021-09-27T06:45:54.280252-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Elapsed":0} +{"Time":"2021-09-27T06:45:54.280256-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384"} +{"Time":"2021-09-27T06:45:54.280259-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Output":"=== RUN TestSanitySha2_384\n"} +{"Time":"2021-09-27T06:45:54.280262-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Output":"--- PASS: TestSanitySha2_384 (0.00s)\n"} +{"Time":"2021-09-27T06:45:54.280265-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Elapsed":0} +{"Time":"2021-09-27T06:45:54.280268-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128"} +{"Time":"2021-09-27T06:45:54.280271-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Output":"=== RUN TestSanityKmac128\n"} +{"Time":"2021-09-27T06:45:54.280282-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Output":"--- PASS: TestSanityKmac128 (0.00s)\n"} +{"Time":"2021-09-27T06:45:54.280301-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Elapsed":0} +{"Time":"2021-09-27T06:45:54.280306-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI"} +{"Time":"2021-09-27T06:45:54.280308-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":"=== RUN TestHashersAPI\n"} +{"Time":"2021-09-27T06:45:54.280311-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":" hash_test.go:114: math rand seed is 1632739554280043000\n"} +{"Time":"2021-09-27T06:45:54.280334-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":"--- PASS: TestHashersAPI (0.00s)\n"} +{"Time":"2021-09-27T06:45:54.280338-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Elapsed":0} +{"Time":"2021-09-27T06:45:54.280341-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3"} +{"Time":"2021-09-27T06:45:54.280344-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":"=== RUN TestSha3\n"} +{"Time":"2021-09-27T06:45:54.280348-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":" hash_test.go:158: math rand seed is 1632739554280256000\n"} +{"Time":"2021-09-27T06:45:54.280351-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256"} +{"Time":"2021-09-27T06:45:54.280354-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Output":"=== RUN TestSha3/SHA3_256\n"} +{"Time":"2021-09-27T06:45:54.380565-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384"} +{"Time":"2021-09-27T06:45:54.380592-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Output":"=== RUN TestSha3/SHA3_384\n"} +{"Time":"2021-09-27T06:45:54.500675-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":"--- PASS: TestSha3 (0.22s)\n"} +{"Time":"2021-09-27T06:45:54.500703-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Output":" --- PASS: TestSha3/SHA3_256 (0.10s)\n"} +{"Time":"2021-09-27T06:45:54.50071-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Elapsed":0.1} +{"Time":"2021-09-27T06:45:54.500717-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Output":" --- PASS: TestSha3/SHA3_384 (0.12s)\n"} +{"Time":"2021-09-27T06:45:54.500721-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Elapsed":0.12} +{"Time":"2021-09-27T06:45:54.500739-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Elapsed":0.22} +{"Time":"2021-09-27T06:45:54.500742-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256"} +{"Time":"2021-09-27T06:45:54.500745-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Output":"=== RUN TestSanitySha3_256\n"} +{"Time":"2021-09-27T06:45:54.500748-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Output":"--- PASS: TestSanitySha3_256 (0.00s)\n"} +{"Time":"2021-09-27T06:45:54.500751-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Elapsed":0} +{"Time":"2021-09-27T06:45:54.500754-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384"} +{"Time":"2021-09-27T06:45:54.500862-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Output":"=== RUN TestSanitySha3_384\n"} +{"Time":"2021-09-27T06:45:54.500873-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Output":"--- PASS: TestSanitySha3_384 (0.00s)\n"} +{"Time":"2021-09-27T06:45:54.500896-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Elapsed":0} +{"Time":"2021-09-27T06:45:54.500913-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256"} +{"Time":"2021-09-27T06:45:54.500917-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":"=== RUN TestSanitySha2_256\n"} +{"Time":"2021-09-27T06:45:54.500923-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":"--- PASS: TestSanitySha2_256 (0.00s)\n"} +{"Time":"2021-09-27T06:45:54.500926-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Elapsed":0} +{"Time":"2021-09-27T06:45:54.500929-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384"} +{"Time":"2021-09-27T06:45:54.500932-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Output":"=== RUN TestSanitySha2_384\n"} +{"Time":"2021-09-27T06:45:54.500935-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Output":"--- PASS: TestSanitySha2_384 (0.00s)\n"} +{"Time":"2021-09-27T06:45:54.500957-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Elapsed":0} +{"Time":"2021-09-27T06:45:54.500961-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128"} +{"Time":"2021-09-27T06:45:54.500964-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Output":"=== RUN TestSanityKmac128\n"} +{"Time":"2021-09-27T06:45:54.500968-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Output":"--- PASS: TestSanityKmac128 (0.00s)\n"} +{"Time":"2021-09-27T06:45:54.500971-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Elapsed":0} +{"Time":"2021-09-27T06:45:54.500974-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI"} +{"Time":"2021-09-27T06:45:54.500977-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":"=== RUN TestHashersAPI\n"} +{"Time":"2021-09-27T06:45:54.50098-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":" hash_test.go:114: math rand seed is 1632739554500707000\n"} +{"Time":"2021-09-27T06:45:54.500988-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":"--- PASS: TestHashersAPI (0.00s)\n"} +{"Time":"2021-09-27T06:45:54.501007-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Elapsed":0} +{"Time":"2021-09-27T06:45:54.501011-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3"} +{"Time":"2021-09-27T06:45:54.501014-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":"=== RUN TestSha3\n"} +{"Time":"2021-09-27T06:45:54.501017-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":" hash_test.go:158: math rand seed is 1632739554500935000\n"} +{"Time":"2021-09-27T06:45:54.501023-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256"} +{"Time":"2021-09-27T06:45:54.501026-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Output":"=== RUN TestSha3/SHA3_256\n"} +{"Time":"2021-09-27T06:45:54.59914-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384"} +{"Time":"2021-09-27T06:45:54.599167-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Output":"=== RUN TestSha3/SHA3_384\n"} +{"Time":"2021-09-27T06:45:54.719353-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":"--- PASS: TestSha3 (0.22s)\n"} +{"Time":"2021-09-27T06:45:54.719385-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Output":" --- PASS: TestSha3/SHA3_256 (0.10s)\n"} +{"Time":"2021-09-27T06:45:54.71939-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Elapsed":0.1} +{"Time":"2021-09-27T06:45:54.719396-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Output":" --- PASS: TestSha3/SHA3_384 (0.12s)\n"} +{"Time":"2021-09-27T06:45:54.719399-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Elapsed":0.12} +{"Time":"2021-09-27T06:45:54.719402-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Elapsed":0.22} +{"Time":"2021-09-27T06:45:54.719405-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Output":"PASS\n"} +{"Time":"2021-09-27T06:45:54.720685-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Output":"ok \tgithub.com/onflow/crypto/hash\t2.541s\n"} +{"Time":"2021-09-27T06:45:54.720757-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Elapsed":2.541} \ No newline at end of file diff --git a/tools/test_monitor/testdata/summary1/raw/test-result-crypto-hash-2-count-pass.json b/tools/test_monitor/testdata/summary1/raw/test-result-crypto-hash-2-count-pass.json index e4001bb454a..50582a7c37e 100644 --- a/tools/test_monitor/testdata/summary1/raw/test-result-crypto-hash-2-count-pass.json +++ b/tools/test_monitor/testdata/summary1/raw/test-result-crypto-hash-2-count-pass.json @@ -1,79 +1,79 @@ -{"Time":"2021-10-04T10:34:10.203071-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256"} -{"Time":"2021-10-04T10:34:10.203659-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Output":"=== RUN TestSanitySha3_256\n"} -{"Time":"2021-10-04T10:34:10.203707-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Output":"--- PASS: TestSanitySha3_256 (0.00s)\n"} -{"Time":"2021-10-04T10:34:10.203717-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Elapsed":0} -{"Time":"2021-10-04T10:34:10.203809-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384"} -{"Time":"2021-10-04T10:34:10.203817-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Output":"=== RUN TestSanitySha3_384\n"} -{"Time":"2021-10-04T10:34:10.203824-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Output":"--- PASS: TestSanitySha3_384 (0.00s)\n"} -{"Time":"2021-10-04T10:34:10.20383-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Elapsed":0} -{"Time":"2021-10-04T10:34:10.203837-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256"} -{"Time":"2021-10-04T10:34:10.203843-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":"=== RUN TestSanitySha2_256\n"} -{"Time":"2021-10-04T10:34:10.20385-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":"--- PASS: TestSanitySha2_256 (0.00s)\n"} -{"Time":"2021-10-04T10:34:10.203858-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Elapsed":0} -{"Time":"2021-10-04T10:34:10.203865-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384"} -{"Time":"2021-10-04T10:34:10.203871-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Output":"=== RUN TestSanitySha2_384\n"} -{"Time":"2021-10-04T10:34:10.203878-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Output":"--- PASS: TestSanitySha2_384 (0.00s)\n"} -{"Time":"2021-10-04T10:34:10.203884-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Elapsed":0} -{"Time":"2021-10-04T10:34:10.203891-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128"} -{"Time":"2021-10-04T10:34:10.203897-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Output":"=== RUN TestSanityKmac128\n"} -{"Time":"2021-10-04T10:34:10.203904-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Output":"--- PASS: TestSanityKmac128 (0.00s)\n"} -{"Time":"2021-10-04T10:34:10.20391-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Elapsed":0} -{"Time":"2021-10-04T10:34:10.203916-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI"} -{"Time":"2021-10-04T10:34:10.203923-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":"=== RUN TestHashersAPI\n"} -{"Time":"2021-10-04T10:34:10.203933-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":" hash_test.go:114: math rand seed is 1633358050203144000\n"} -{"Time":"2021-10-04T10:34:10.203947-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":"--- PASS: TestHashersAPI (0.00s)\n"} -{"Time":"2021-10-04T10:34:10.203954-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Elapsed":0} -{"Time":"2021-10-04T10:34:10.203977-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3"} -{"Time":"2021-10-04T10:34:10.203987-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":"=== RUN TestSha3\n"} -{"Time":"2021-10-04T10:34:10.203991-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":" hash_test.go:158: math rand seed is 1633358050203374000\n"} -{"Time":"2021-10-04T10:34:10.203999-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256"} -{"Time":"2021-10-04T10:34:10.204004-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Output":"=== RUN TestSha3/SHA3_256\n"} -{"Time":"2021-10-04T10:34:10.306005-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384"} -{"Time":"2021-10-04T10:34:10.306046-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Output":"=== RUN TestSha3/SHA3_384\n"} -{"Time":"2021-10-04T10:34:10.430169-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":"--- PASS: TestSha3 (0.23s)\n"} -{"Time":"2021-10-04T10:34:10.430224-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Output":" --- PASS: TestSha3/SHA3_256 (0.10s)\n"} -{"Time":"2021-10-04T10:34:10.430234-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Elapsed":0.1} -{"Time":"2021-10-04T10:34:10.430278-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Output":" --- PASS: TestSha3/SHA3_384 (0.12s)\n"} -{"Time":"2021-10-04T10:34:10.430284-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Elapsed":0.12} -{"Time":"2021-10-04T10:34:10.430289-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Elapsed":0.23} -{"Time":"2021-10-04T10:34:10.430295-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256"} -{"Time":"2021-10-04T10:34:10.430299-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Output":"=== RUN TestSanitySha3_256\n"} -{"Time":"2021-10-04T10:34:10.430305-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Output":"--- PASS: TestSanitySha3_256 (0.00s)\n"} -{"Time":"2021-10-04T10:34:10.430309-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_256","Elapsed":0} -{"Time":"2021-10-04T10:34:10.430314-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384"} -{"Time":"2021-10-04T10:34:10.430318-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Output":"=== RUN TestSanitySha3_384\n"} -{"Time":"2021-10-04T10:34:10.430337-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Output":"--- PASS: TestSanitySha3_384 (0.00s)\n"} -{"Time":"2021-10-04T10:34:10.430343-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha3_384","Elapsed":0} -{"Time":"2021-10-04T10:34:10.430347-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256"} -{"Time":"2021-10-04T10:34:10.430351-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":"=== RUN TestSanitySha2_256\n"} -{"Time":"2021-10-04T10:34:10.430356-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Output":"--- PASS: TestSanitySha2_256 (0.00s)\n"} -{"Time":"2021-10-04T10:34:10.430362-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_256","Elapsed":0} -{"Time":"2021-10-04T10:34:10.430384-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384"} -{"Time":"2021-10-04T10:34:10.430389-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Output":"=== RUN TestSanitySha2_384\n"} -{"Time":"2021-10-04T10:34:10.430395-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Output":"--- PASS: TestSanitySha2_384 (0.00s)\n"} -{"Time":"2021-10-04T10:34:10.4304-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanitySha2_384","Elapsed":0} -{"Time":"2021-10-04T10:34:10.430404-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128"} -{"Time":"2021-10-04T10:34:10.430408-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Output":"=== RUN TestSanityKmac128\n"} -{"Time":"2021-10-04T10:34:10.430413-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Output":"--- PASS: TestSanityKmac128 (0.00s)\n"} -{"Time":"2021-10-04T10:34:10.430418-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSanityKmac128","Elapsed":0} -{"Time":"2021-10-04T10:34:10.430423-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI"} -{"Time":"2021-10-04T10:34:10.430427-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":"=== RUN TestHashersAPI\n"} -{"Time":"2021-10-04T10:34:10.430432-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":" hash_test.go:114: math rand seed is 1633358050430256000\n"} -{"Time":"2021-10-04T10:34:10.430469-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Output":"--- PASS: TestHashersAPI (0.00s)\n"} -{"Time":"2021-10-04T10:34:10.430483-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestHashersAPI","Elapsed":0} -{"Time":"2021-10-04T10:34:10.430488-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3"} -{"Time":"2021-10-04T10:34:10.430493-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":"=== RUN TestSha3\n"} -{"Time":"2021-10-04T10:34:10.430499-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":" hash_test.go:158: math rand seed is 1633358050430467000\n"} -{"Time":"2021-10-04T10:34:10.430504-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256"} -{"Time":"2021-10-04T10:34:10.430509-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Output":"=== RUN TestSha3/SHA3_256\n"} -{"Time":"2021-10-04T10:34:10.530207-04:00","Action":"run","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384"} -{"Time":"2021-10-04T10:34:10.530243-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Output":"=== RUN TestSha3/SHA3_384\n"} -{"Time":"2021-10-04T10:34:10.654891-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Output":"--- PASS: TestSha3 (0.22s)\n"} -{"Time":"2021-10-04T10:34:10.654967-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Output":" --- PASS: TestSha3/SHA3_256 (0.10s)\n"} -{"Time":"2021-10-04T10:34:10.654975-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_256","Elapsed":0.1} -{"Time":"2021-10-04T10:34:10.654986-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Output":" --- PASS: TestSha3/SHA3_384 (0.12s)\n"} -{"Time":"2021-10-04T10:34:10.654991-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3/SHA3_384","Elapsed":0.12} -{"Time":"2021-10-04T10:34:10.655024-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Test":"TestSha3","Elapsed":0.22} -{"Time":"2021-10-04T10:34:10.65503-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Output":"PASS\n"} -{"Time":"2021-10-04T10:34:10.656297-04:00","Action":"output","Package":"github.com/onflow/flow-go/crypto/hash","Output":"ok \tgithub.com/onflow/flow-go/crypto/hash\t0.642s\n"} -{"Time":"2021-10-04T10:34:10.656366-04:00","Action":"pass","Package":"github.com/onflow/flow-go/crypto/hash","Elapsed":0.643} +{"Time":"2021-10-04T10:34:10.203071-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256"} +{"Time":"2021-10-04T10:34:10.203659-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Output":"=== RUN TestSanitySha3_256\n"} +{"Time":"2021-10-04T10:34:10.203707-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Output":"--- PASS: TestSanitySha3_256 (0.00s)\n"} +{"Time":"2021-10-04T10:34:10.203717-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Elapsed":0} +{"Time":"2021-10-04T10:34:10.203809-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384"} +{"Time":"2021-10-04T10:34:10.203817-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Output":"=== RUN TestSanitySha3_384\n"} +{"Time":"2021-10-04T10:34:10.203824-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Output":"--- PASS: TestSanitySha3_384 (0.00s)\n"} +{"Time":"2021-10-04T10:34:10.20383-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Elapsed":0} +{"Time":"2021-10-04T10:34:10.203837-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256"} +{"Time":"2021-10-04T10:34:10.203843-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":"=== RUN TestSanitySha2_256\n"} +{"Time":"2021-10-04T10:34:10.20385-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":"--- PASS: TestSanitySha2_256 (0.00s)\n"} +{"Time":"2021-10-04T10:34:10.203858-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Elapsed":0} +{"Time":"2021-10-04T10:34:10.203865-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384"} +{"Time":"2021-10-04T10:34:10.203871-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Output":"=== RUN TestSanitySha2_384\n"} +{"Time":"2021-10-04T10:34:10.203878-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Output":"--- PASS: TestSanitySha2_384 (0.00s)\n"} +{"Time":"2021-10-04T10:34:10.203884-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Elapsed":0} +{"Time":"2021-10-04T10:34:10.203891-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128"} +{"Time":"2021-10-04T10:34:10.203897-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Output":"=== RUN TestSanityKmac128\n"} +{"Time":"2021-10-04T10:34:10.203904-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Output":"--- PASS: TestSanityKmac128 (0.00s)\n"} +{"Time":"2021-10-04T10:34:10.20391-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Elapsed":0} +{"Time":"2021-10-04T10:34:10.203916-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI"} +{"Time":"2021-10-04T10:34:10.203923-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":"=== RUN TestHashersAPI\n"} +{"Time":"2021-10-04T10:34:10.203933-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":" hash_test.go:114: math rand seed is 1633358050203144000\n"} +{"Time":"2021-10-04T10:34:10.203947-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":"--- PASS: TestHashersAPI (0.00s)\n"} +{"Time":"2021-10-04T10:34:10.203954-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Elapsed":0} +{"Time":"2021-10-04T10:34:10.203977-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3"} +{"Time":"2021-10-04T10:34:10.203987-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":"=== RUN TestSha3\n"} +{"Time":"2021-10-04T10:34:10.203991-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":" hash_test.go:158: math rand seed is 1633358050203374000\n"} +{"Time":"2021-10-04T10:34:10.203999-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256"} +{"Time":"2021-10-04T10:34:10.204004-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Output":"=== RUN TestSha3/SHA3_256\n"} +{"Time":"2021-10-04T10:34:10.306005-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384"} +{"Time":"2021-10-04T10:34:10.306046-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Output":"=== RUN TestSha3/SHA3_384\n"} +{"Time":"2021-10-04T10:34:10.430169-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":"--- PASS: TestSha3 (0.23s)\n"} +{"Time":"2021-10-04T10:34:10.430224-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Output":" --- PASS: TestSha3/SHA3_256 (0.10s)\n"} +{"Time":"2021-10-04T10:34:10.430234-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Elapsed":0.1} +{"Time":"2021-10-04T10:34:10.430278-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Output":" --- PASS: TestSha3/SHA3_384 (0.12s)\n"} +{"Time":"2021-10-04T10:34:10.430284-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Elapsed":0.12} +{"Time":"2021-10-04T10:34:10.430289-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Elapsed":0.23} +{"Time":"2021-10-04T10:34:10.430295-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256"} +{"Time":"2021-10-04T10:34:10.430299-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Output":"=== RUN TestSanitySha3_256\n"} +{"Time":"2021-10-04T10:34:10.430305-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Output":"--- PASS: TestSanitySha3_256 (0.00s)\n"} +{"Time":"2021-10-04T10:34:10.430309-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_256","Elapsed":0} +{"Time":"2021-10-04T10:34:10.430314-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384"} +{"Time":"2021-10-04T10:34:10.430318-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Output":"=== RUN TestSanitySha3_384\n"} +{"Time":"2021-10-04T10:34:10.430337-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Output":"--- PASS: TestSanitySha3_384 (0.00s)\n"} +{"Time":"2021-10-04T10:34:10.430343-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha3_384","Elapsed":0} +{"Time":"2021-10-04T10:34:10.430347-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256"} +{"Time":"2021-10-04T10:34:10.430351-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":"=== RUN TestSanitySha2_256\n"} +{"Time":"2021-10-04T10:34:10.430356-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Output":"--- PASS: TestSanitySha2_256 (0.00s)\n"} +{"Time":"2021-10-04T10:34:10.430362-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_256","Elapsed":0} +{"Time":"2021-10-04T10:34:10.430384-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384"} +{"Time":"2021-10-04T10:34:10.430389-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Output":"=== RUN TestSanitySha2_384\n"} +{"Time":"2021-10-04T10:34:10.430395-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Output":"--- PASS: TestSanitySha2_384 (0.00s)\n"} +{"Time":"2021-10-04T10:34:10.4304-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanitySha2_384","Elapsed":0} +{"Time":"2021-10-04T10:34:10.430404-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128"} +{"Time":"2021-10-04T10:34:10.430408-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Output":"=== RUN TestSanityKmac128\n"} +{"Time":"2021-10-04T10:34:10.430413-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Output":"--- PASS: TestSanityKmac128 (0.00s)\n"} +{"Time":"2021-10-04T10:34:10.430418-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSanityKmac128","Elapsed":0} +{"Time":"2021-10-04T10:34:10.430423-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI"} +{"Time":"2021-10-04T10:34:10.430427-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":"=== RUN TestHashersAPI\n"} +{"Time":"2021-10-04T10:34:10.430432-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":" hash_test.go:114: math rand seed is 1633358050430256000\n"} +{"Time":"2021-10-04T10:34:10.430469-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Output":"--- PASS: TestHashersAPI (0.00s)\n"} +{"Time":"2021-10-04T10:34:10.430483-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestHashersAPI","Elapsed":0} +{"Time":"2021-10-04T10:34:10.430488-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3"} +{"Time":"2021-10-04T10:34:10.430493-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":"=== RUN TestSha3\n"} +{"Time":"2021-10-04T10:34:10.430499-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":" hash_test.go:158: math rand seed is 1633358050430467000\n"} +{"Time":"2021-10-04T10:34:10.430504-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256"} +{"Time":"2021-10-04T10:34:10.430509-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Output":"=== RUN TestSha3/SHA3_256\n"} +{"Time":"2021-10-04T10:34:10.530207-04:00","Action":"run","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384"} +{"Time":"2021-10-04T10:34:10.530243-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Output":"=== RUN TestSha3/SHA3_384\n"} +{"Time":"2021-10-04T10:34:10.654891-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Output":"--- PASS: TestSha3 (0.22s)\n"} +{"Time":"2021-10-04T10:34:10.654967-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Output":" --- PASS: TestSha3/SHA3_256 (0.10s)\n"} +{"Time":"2021-10-04T10:34:10.654975-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_256","Elapsed":0.1} +{"Time":"2021-10-04T10:34:10.654986-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Output":" --- PASS: TestSha3/SHA3_384 (0.12s)\n"} +{"Time":"2021-10-04T10:34:10.654991-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3/SHA3_384","Elapsed":0.12} +{"Time":"2021-10-04T10:34:10.655024-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Test":"TestSha3","Elapsed":0.22} +{"Time":"2021-10-04T10:34:10.65503-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Output":"PASS\n"} +{"Time":"2021-10-04T10:34:10.656297-04:00","Action":"output","Package":"github.com/onflow/crypto/hash","Output":"ok \tgithub.com/onflow/crypto/hash\t0.642s\n"} +{"Time":"2021-10-04T10:34:10.656366-04:00","Action":"pass","Package":"github.com/onflow/crypto/hash","Elapsed":0.643} diff --git a/tools/test_monitor/testdata/summary2/test1-1package-1failure/expected-output/failures/TestSanitySha3_256+github.com-onflow-flow-go-crypto-hash/failure1.txt b/tools/test_monitor/testdata/summary2/test1-1package-1failure/expected-output/failures/TestSanitySha3_256+github.com-onflow-crypto-hash/failure1.txt similarity index 100% rename from tools/test_monitor/testdata/summary2/test1-1package-1failure/expected-output/failures/TestSanitySha3_256+github.com-onflow-flow-go-crypto-hash/failure1.txt rename to tools/test_monitor/testdata/summary2/test1-1package-1failure/expected-output/failures/TestSanitySha3_256+github.com-onflow-crypto-hash/failure1.txt diff --git a/tools/test_monitor/testdata/summary2/test1-1package-1failure/expected-output/test1-1package-1failure.json b/tools/test_monitor/testdata/summary2/test1-1package-1failure/expected-output/test1-1package-1failure.json index bd477393a56..00aad44afd2 100644 --- a/tools/test_monitor/testdata/summary2/test1-1package-1failure/expected-output/test1-1package-1failure.json +++ b/tools/test_monitor/testdata/summary2/test1-1package-1failure/expected-output/test1-1package-1failure.json @@ -1,8 +1,8 @@ { "tests": { - "github.com/onflow/flow-go/crypto/hash/TestSanitySha3_256": { + "github.com/onflow/crypto/hash/TestSanitySha3_256": { "test": "TestSanitySha3_256", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 1, "passed": 0, "failed": 1, @@ -14,9 +14,9 @@ 0 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSanitySha3_384": { + "github.com/onflow/crypto/hash/TestSanitySha3_384": { "test": "TestSanitySha3_384", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 1, "passed": 1, "failed": 0, @@ -28,9 +28,9 @@ 0 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSanitySha2_256": { + "github.com/onflow/crypto/hash/TestSanitySha2_256": { "test": "TestSanitySha2_256", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 1, "passed": 1, "failed": 0, @@ -42,9 +42,9 @@ 0 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSanitySha2_384": { + "github.com/onflow/crypto/hash/TestSanitySha2_384": { "test": "TestSanitySha2_384", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 1, "passed": 1, "failed": 0, @@ -56,9 +56,9 @@ 0 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSanityKmac128": { + "github.com/onflow/crypto/hash/TestSanityKmac128": { "test": "TestSanityKmac128", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 1, "passed": 1, "failed": 0, @@ -70,9 +70,9 @@ 0 ] }, - "github.com/onflow/flow-go/crypto/hash/TestHashersAPI": { + "github.com/onflow/crypto/hash/TestHashersAPI": { "test": "TestHashersAPI", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 1, "passed": 1, "failed": 0, @@ -84,9 +84,9 @@ 0 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSha3": { + "github.com/onflow/crypto/hash/TestSha3": { "test": "TestSha3", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 1, "passed": 1, "failed": 0, @@ -98,9 +98,9 @@ 0.23 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSha3/SHA3_256": { + "github.com/onflow/crypto/hash/TestSha3/SHA3_256": { "test": "TestSha3/SHA3_256", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 1, "passed": 1, "failed": 0, @@ -112,9 +112,9 @@ 0.11 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSha3/SHA3_384": { + "github.com/onflow/crypto/hash/TestSha3/SHA3_384": { "test": "TestSha3/SHA3_384", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 1, "passed": 1, "failed": 0, diff --git a/tools/test_monitor/testdata/summary2/test1-1package-1failure/input/test-result-crypto-hash-1-count-fail.json b/tools/test_monitor/testdata/summary2/test1-1package-1failure/input/test-result-crypto-hash-1-count-fail.json index 9ebb56c98b8..25746e1fe7a 100644 --- a/tools/test_monitor/testdata/summary2/test1-1package-1failure/input/test-result-crypto-hash-1-count-fail.json +++ b/tools/test_monitor/testdata/summary2/test1-1package-1failure/input/test-result-crypto-hash-1-count-fail.json @@ -3,7 +3,7 @@ { "json": { "test": "TestSanitySha3_256", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "output": [ {"item": "=== RUN TestSanitySha3_256\n"}, {"item": " hash_test.go:21: \n"}, @@ -29,7 +29,7 @@ { "json": { "test": "TestSanitySha3_384", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "output": [ {"item": "=== RUN TestSanitySha3_384\n"}, {"item": "--- PASS: TestSanitySha3_384 (0.00s)\n"} @@ -41,7 +41,7 @@ { "json": { "test": "TestSanitySha2_256", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "output": [ {"item": "=== RUN TestSanitySha2_256\n"}, {"item": "--- PASS: TestSanitySha2_256 (0.00s)\n"} @@ -53,7 +53,7 @@ { "json": { "test": "TestSanitySha2_384", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "output": [ {"item": "=== RUN TestSanitySha2_384\n"}, {"item": "--- PASS: TestSanitySha2_384 (0.00s)\n"} @@ -65,7 +65,7 @@ { "json": { "test": "TestSanityKmac128", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "output": [ {"item": "=== RUN TestSanityKmac128\n"}, {"item": "--- PASS: TestSanityKmac128 (0.00s)\n"} @@ -77,7 +77,7 @@ { "json": { "test": "TestHashersAPI", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "output": [ {"item": "=== RUN TestHashersAPI\n"}, {"item": " hash_test.go:114: math rand seed is 1632498687765218000\n"}, @@ -90,7 +90,7 @@ { "json": { "test": "TestSha3", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "output": [ {"item": "=== RUN TestSha3\n"}, {"item": " hash_test.go:158: math rand seed is 1632498687765661000\n"}, @@ -103,7 +103,7 @@ { "json": { "test": "TestSha3/SHA3_256", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "output": [ {"item": "=== RUN TestSha3/SHA3_256\n"}, {"item": " --- PASS: TestSha3/SHA3_256 (0.11s)\n"} @@ -115,7 +115,7 @@ { "json": { "test": "TestSha3/SHA3_384", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "output": [ {"item": "=== RUN TestSha3/SHA3_384\n"}, {"item": " --- PASS: TestSha3/SHA3_384 (0.12s)\n"} diff --git a/tools/test_monitor/testdata/summary2/test4-multi-failures/expected-output/failures/TestSanitySha2_256+github.com-onflow-flow-go-crypto-hash/failure1.txt b/tools/test_monitor/testdata/summary2/test4-multi-failures/expected-output/failures/TestSanitySha2_256+github.com-onflow-crypto-hash/failure1.txt similarity index 100% rename from tools/test_monitor/testdata/summary2/test4-multi-failures/expected-output/failures/TestSanitySha2_256+github.com-onflow-flow-go-crypto-hash/failure1.txt rename to tools/test_monitor/testdata/summary2/test4-multi-failures/expected-output/failures/TestSanitySha2_256+github.com-onflow-crypto-hash/failure1.txt diff --git a/tools/test_monitor/testdata/summary2/test4-multi-failures/expected-output/failures/TestSanitySha2_256+github.com-onflow-flow-go-crypto-hash/failure10.txt b/tools/test_monitor/testdata/summary2/test4-multi-failures/expected-output/failures/TestSanitySha2_256+github.com-onflow-crypto-hash/failure10.txt similarity index 100% rename from tools/test_monitor/testdata/summary2/test4-multi-failures/expected-output/failures/TestSanitySha2_256+github.com-onflow-flow-go-crypto-hash/failure10.txt rename to tools/test_monitor/testdata/summary2/test4-multi-failures/expected-output/failures/TestSanitySha2_256+github.com-onflow-crypto-hash/failure10.txt diff --git a/tools/test_monitor/testdata/summary2/test4-multi-failures/expected-output/failures/TestSanitySha2_256+github.com-onflow-flow-go-crypto-hash/failure2.txt b/tools/test_monitor/testdata/summary2/test4-multi-failures/expected-output/failures/TestSanitySha2_256+github.com-onflow-crypto-hash/failure2.txt similarity index 100% rename from tools/test_monitor/testdata/summary2/test4-multi-failures/expected-output/failures/TestSanitySha2_256+github.com-onflow-flow-go-crypto-hash/failure2.txt rename to tools/test_monitor/testdata/summary2/test4-multi-failures/expected-output/failures/TestSanitySha2_256+github.com-onflow-crypto-hash/failure2.txt diff --git a/tools/test_monitor/testdata/summary2/test4-multi-failures/expected-output/failures/TestSanitySha2_256+github.com-onflow-flow-go-crypto-hash/failure3.txt b/tools/test_monitor/testdata/summary2/test4-multi-failures/expected-output/failures/TestSanitySha2_256+github.com-onflow-crypto-hash/failure3.txt similarity index 100% rename from tools/test_monitor/testdata/summary2/test4-multi-failures/expected-output/failures/TestSanitySha2_256+github.com-onflow-flow-go-crypto-hash/failure3.txt rename to tools/test_monitor/testdata/summary2/test4-multi-failures/expected-output/failures/TestSanitySha2_256+github.com-onflow-crypto-hash/failure3.txt diff --git a/tools/test_monitor/testdata/summary2/test4-multi-failures/expected-output/failures/TestSanitySha2_256+github.com-onflow-flow-go-crypto-hash/failure4.txt b/tools/test_monitor/testdata/summary2/test4-multi-failures/expected-output/failures/TestSanitySha2_256+github.com-onflow-crypto-hash/failure4.txt similarity index 100% rename from tools/test_monitor/testdata/summary2/test4-multi-failures/expected-output/failures/TestSanitySha2_256+github.com-onflow-flow-go-crypto-hash/failure4.txt rename to tools/test_monitor/testdata/summary2/test4-multi-failures/expected-output/failures/TestSanitySha2_256+github.com-onflow-crypto-hash/failure4.txt diff --git a/tools/test_monitor/testdata/summary2/test4-multi-failures/expected-output/failures/TestSanitySha2_256+github.com-onflow-flow-go-crypto-hash/failure5.txt b/tools/test_monitor/testdata/summary2/test4-multi-failures/expected-output/failures/TestSanitySha2_256+github.com-onflow-crypto-hash/failure5.txt similarity index 100% rename from tools/test_monitor/testdata/summary2/test4-multi-failures/expected-output/failures/TestSanitySha2_256+github.com-onflow-flow-go-crypto-hash/failure5.txt rename to tools/test_monitor/testdata/summary2/test4-multi-failures/expected-output/failures/TestSanitySha2_256+github.com-onflow-crypto-hash/failure5.txt diff --git a/tools/test_monitor/testdata/summary2/test4-multi-failures/expected-output/failures/TestSanitySha2_256+github.com-onflow-flow-go-crypto-hash/failure6.txt b/tools/test_monitor/testdata/summary2/test4-multi-failures/expected-output/failures/TestSanitySha2_256+github.com-onflow-crypto-hash/failure6.txt similarity index 100% rename from tools/test_monitor/testdata/summary2/test4-multi-failures/expected-output/failures/TestSanitySha2_256+github.com-onflow-flow-go-crypto-hash/failure6.txt rename to tools/test_monitor/testdata/summary2/test4-multi-failures/expected-output/failures/TestSanitySha2_256+github.com-onflow-crypto-hash/failure6.txt diff --git a/tools/test_monitor/testdata/summary2/test4-multi-failures/expected-output/failures/TestSanitySha2_256+github.com-onflow-flow-go-crypto-hash/failure7.txt b/tools/test_monitor/testdata/summary2/test4-multi-failures/expected-output/failures/TestSanitySha2_256+github.com-onflow-crypto-hash/failure7.txt similarity index 100% rename from tools/test_monitor/testdata/summary2/test4-multi-failures/expected-output/failures/TestSanitySha2_256+github.com-onflow-flow-go-crypto-hash/failure7.txt rename to tools/test_monitor/testdata/summary2/test4-multi-failures/expected-output/failures/TestSanitySha2_256+github.com-onflow-crypto-hash/failure7.txt diff --git a/tools/test_monitor/testdata/summary2/test4-multi-failures/expected-output/failures/TestSanitySha2_256+github.com-onflow-flow-go-crypto-hash/failure8.txt b/tools/test_monitor/testdata/summary2/test4-multi-failures/expected-output/failures/TestSanitySha2_256+github.com-onflow-crypto-hash/failure8.txt similarity index 100% rename from tools/test_monitor/testdata/summary2/test4-multi-failures/expected-output/failures/TestSanitySha2_256+github.com-onflow-flow-go-crypto-hash/failure8.txt rename to tools/test_monitor/testdata/summary2/test4-multi-failures/expected-output/failures/TestSanitySha2_256+github.com-onflow-crypto-hash/failure8.txt diff --git a/tools/test_monitor/testdata/summary2/test4-multi-failures/expected-output/failures/TestSanitySha2_256+github.com-onflow-flow-go-crypto-hash/failure9.txt b/tools/test_monitor/testdata/summary2/test4-multi-failures/expected-output/failures/TestSanitySha2_256+github.com-onflow-crypto-hash/failure9.txt similarity index 100% rename from tools/test_monitor/testdata/summary2/test4-multi-failures/expected-output/failures/TestSanitySha2_256+github.com-onflow-flow-go-crypto-hash/failure9.txt rename to tools/test_monitor/testdata/summary2/test4-multi-failures/expected-output/failures/TestSanitySha2_256+github.com-onflow-crypto-hash/failure9.txt diff --git a/tools/test_monitor/testdata/summary2/test4-multi-failures/expected-output/failures/TestSanitySha3_256+github.com-onflow-flow-go-crypto-hash/failure1.txt b/tools/test_monitor/testdata/summary2/test4-multi-failures/expected-output/failures/TestSanitySha3_256+github.com-onflow-crypto-hash/failure1.txt similarity index 100% rename from tools/test_monitor/testdata/summary2/test4-multi-failures/expected-output/failures/TestSanitySha3_256+github.com-onflow-flow-go-crypto-hash/failure1.txt rename to tools/test_monitor/testdata/summary2/test4-multi-failures/expected-output/failures/TestSanitySha3_256+github.com-onflow-crypto-hash/failure1.txt diff --git a/tools/test_monitor/testdata/summary2/test4-multi-failures/expected-output/test4-multi-failures.json b/tools/test_monitor/testdata/summary2/test4-multi-failures/expected-output/test4-multi-failures.json index 29b56d45a53..0b358ac4099 100644 --- a/tools/test_monitor/testdata/summary2/test4-multi-failures/expected-output/test4-multi-failures.json +++ b/tools/test_monitor/testdata/summary2/test4-multi-failures/expected-output/test4-multi-failures.json @@ -1,8 +1,8 @@ { "tests": { - "github.com/onflow/flow-go/crypto/hash/TestSanitySha3_256": { + "github.com/onflow/crypto/hash/TestSanitySha3_256": { "test": "TestSanitySha3_256", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 24, "failed": 1, @@ -38,9 +38,9 @@ 0 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSanitySha3_384": { + "github.com/onflow/crypto/hash/TestSanitySha3_384": { "test": "TestSanitySha3_384", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 25, "failed": 0, @@ -76,9 +76,9 @@ 0 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSanitySha2_256": { + "github.com/onflow/crypto/hash/TestSanitySha2_256": { "test": "TestSanitySha2_256", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 15, "failed": 10, @@ -114,9 +114,9 @@ 0 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSanitySha2_384": { + "github.com/onflow/crypto/hash/TestSanitySha2_384": { "test": "TestSanitySha2_384", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 25, "failed": 0, @@ -152,9 +152,9 @@ 0 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSanityKmac128": { + "github.com/onflow/crypto/hash/TestSanityKmac128": { "test": "TestSanityKmac128", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 25, "failed": 0, @@ -190,9 +190,9 @@ 0 ] }, - "github.com/onflow/flow-go/crypto/hash/TestHashersAPI": { + "github.com/onflow/crypto/hash/TestHashersAPI": { "test": "TestHashersAPI", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 24, "passed": 24, "failed": 0, @@ -227,9 +227,9 @@ 0 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSha3": { + "github.com/onflow/crypto/hash/TestSha3": { "test": "TestSha3", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 25, "failed": 0, @@ -265,9 +265,9 @@ 0.22 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSha3/SHA3_256": { + "github.com/onflow/crypto/hash/TestSha3/SHA3_256": { "test": "TestSha3/SHA3_256", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 25, "failed": 0, @@ -303,9 +303,9 @@ 0.1 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSha3/SHA3_384": { + "github.com/onflow/crypto/hash/TestSha3/SHA3_384": { "test": "TestSha3/SHA3_384", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 25, "failed": 0, diff --git a/tools/test_monitor/testdata/summary2/test5-multi-failures-multi-exceptions/expected-output/failures/TestSanitySha2_256+github.com-onflow-flow-go-crypto-hash/failure1.txt b/tools/test_monitor/testdata/summary2/test5-multi-failures-multi-exceptions/expected-output/failures/TestSanitySha2_256+github.com-onflow-crypto-hash/failure1.txt similarity index 100% rename from tools/test_monitor/testdata/summary2/test5-multi-failures-multi-exceptions/expected-output/failures/TestSanitySha2_256+github.com-onflow-flow-go-crypto-hash/failure1.txt rename to tools/test_monitor/testdata/summary2/test5-multi-failures-multi-exceptions/expected-output/failures/TestSanitySha2_256+github.com-onflow-crypto-hash/failure1.txt diff --git a/tools/test_monitor/testdata/summary2/test5-multi-failures-multi-exceptions/expected-output/failures/TestSanitySha2_256+github.com-onflow-flow-go-crypto-hash/failure10.txt b/tools/test_monitor/testdata/summary2/test5-multi-failures-multi-exceptions/expected-output/failures/TestSanitySha2_256+github.com-onflow-crypto-hash/failure10.txt similarity index 100% rename from tools/test_monitor/testdata/summary2/test5-multi-failures-multi-exceptions/expected-output/failures/TestSanitySha2_256+github.com-onflow-flow-go-crypto-hash/failure10.txt rename to tools/test_monitor/testdata/summary2/test5-multi-failures-multi-exceptions/expected-output/failures/TestSanitySha2_256+github.com-onflow-crypto-hash/failure10.txt diff --git a/tools/test_monitor/testdata/summary2/test5-multi-failures-multi-exceptions/expected-output/failures/TestSanitySha2_256+github.com-onflow-flow-go-crypto-hash/failure2.txt b/tools/test_monitor/testdata/summary2/test5-multi-failures-multi-exceptions/expected-output/failures/TestSanitySha2_256+github.com-onflow-crypto-hash/failure2.txt similarity index 100% rename from tools/test_monitor/testdata/summary2/test5-multi-failures-multi-exceptions/expected-output/failures/TestSanitySha2_256+github.com-onflow-flow-go-crypto-hash/failure2.txt rename to tools/test_monitor/testdata/summary2/test5-multi-failures-multi-exceptions/expected-output/failures/TestSanitySha2_256+github.com-onflow-crypto-hash/failure2.txt diff --git a/tools/test_monitor/testdata/summary2/test5-multi-failures-multi-exceptions/expected-output/failures/TestSanitySha2_256+github.com-onflow-flow-go-crypto-hash/failure3.txt b/tools/test_monitor/testdata/summary2/test5-multi-failures-multi-exceptions/expected-output/failures/TestSanitySha2_256+github.com-onflow-crypto-hash/failure3.txt similarity index 100% rename from tools/test_monitor/testdata/summary2/test5-multi-failures-multi-exceptions/expected-output/failures/TestSanitySha2_256+github.com-onflow-flow-go-crypto-hash/failure3.txt rename to tools/test_monitor/testdata/summary2/test5-multi-failures-multi-exceptions/expected-output/failures/TestSanitySha2_256+github.com-onflow-crypto-hash/failure3.txt diff --git a/tools/test_monitor/testdata/summary2/test5-multi-failures-multi-exceptions/expected-output/failures/TestSanitySha2_256+github.com-onflow-flow-go-crypto-hash/failure4.txt b/tools/test_monitor/testdata/summary2/test5-multi-failures-multi-exceptions/expected-output/failures/TestSanitySha2_256+github.com-onflow-crypto-hash/failure4.txt similarity index 100% rename from tools/test_monitor/testdata/summary2/test5-multi-failures-multi-exceptions/expected-output/failures/TestSanitySha2_256+github.com-onflow-flow-go-crypto-hash/failure4.txt rename to tools/test_monitor/testdata/summary2/test5-multi-failures-multi-exceptions/expected-output/failures/TestSanitySha2_256+github.com-onflow-crypto-hash/failure4.txt diff --git a/tools/test_monitor/testdata/summary2/test5-multi-failures-multi-exceptions/expected-output/failures/TestSanitySha2_256+github.com-onflow-flow-go-crypto-hash/failure5.txt b/tools/test_monitor/testdata/summary2/test5-multi-failures-multi-exceptions/expected-output/failures/TestSanitySha2_256+github.com-onflow-crypto-hash/failure5.txt similarity index 100% rename from tools/test_monitor/testdata/summary2/test5-multi-failures-multi-exceptions/expected-output/failures/TestSanitySha2_256+github.com-onflow-flow-go-crypto-hash/failure5.txt rename to tools/test_monitor/testdata/summary2/test5-multi-failures-multi-exceptions/expected-output/failures/TestSanitySha2_256+github.com-onflow-crypto-hash/failure5.txt diff --git a/tools/test_monitor/testdata/summary2/test5-multi-failures-multi-exceptions/expected-output/failures/TestSanitySha2_256+github.com-onflow-flow-go-crypto-hash/failure6.txt b/tools/test_monitor/testdata/summary2/test5-multi-failures-multi-exceptions/expected-output/failures/TestSanitySha2_256+github.com-onflow-crypto-hash/failure6.txt similarity index 100% rename from tools/test_monitor/testdata/summary2/test5-multi-failures-multi-exceptions/expected-output/failures/TestSanitySha2_256+github.com-onflow-flow-go-crypto-hash/failure6.txt rename to tools/test_monitor/testdata/summary2/test5-multi-failures-multi-exceptions/expected-output/failures/TestSanitySha2_256+github.com-onflow-crypto-hash/failure6.txt diff --git a/tools/test_monitor/testdata/summary2/test5-multi-failures-multi-exceptions/expected-output/failures/TestSanitySha2_256+github.com-onflow-flow-go-crypto-hash/failure7.txt b/tools/test_monitor/testdata/summary2/test5-multi-failures-multi-exceptions/expected-output/failures/TestSanitySha2_256+github.com-onflow-crypto-hash/failure7.txt similarity index 100% rename from tools/test_monitor/testdata/summary2/test5-multi-failures-multi-exceptions/expected-output/failures/TestSanitySha2_256+github.com-onflow-flow-go-crypto-hash/failure7.txt rename to tools/test_monitor/testdata/summary2/test5-multi-failures-multi-exceptions/expected-output/failures/TestSanitySha2_256+github.com-onflow-crypto-hash/failure7.txt diff --git a/tools/test_monitor/testdata/summary2/test5-multi-failures-multi-exceptions/expected-output/failures/TestSanitySha2_256+github.com-onflow-flow-go-crypto-hash/failure8.txt b/tools/test_monitor/testdata/summary2/test5-multi-failures-multi-exceptions/expected-output/failures/TestSanitySha2_256+github.com-onflow-crypto-hash/failure8.txt similarity index 100% rename from tools/test_monitor/testdata/summary2/test5-multi-failures-multi-exceptions/expected-output/failures/TestSanitySha2_256+github.com-onflow-flow-go-crypto-hash/failure8.txt rename to tools/test_monitor/testdata/summary2/test5-multi-failures-multi-exceptions/expected-output/failures/TestSanitySha2_256+github.com-onflow-crypto-hash/failure8.txt diff --git a/tools/test_monitor/testdata/summary2/test5-multi-failures-multi-exceptions/expected-output/failures/TestSanitySha2_256+github.com-onflow-flow-go-crypto-hash/failure9.txt b/tools/test_monitor/testdata/summary2/test5-multi-failures-multi-exceptions/expected-output/failures/TestSanitySha2_256+github.com-onflow-crypto-hash/failure9.txt similarity index 100% rename from tools/test_monitor/testdata/summary2/test5-multi-failures-multi-exceptions/expected-output/failures/TestSanitySha2_256+github.com-onflow-flow-go-crypto-hash/failure9.txt rename to tools/test_monitor/testdata/summary2/test5-multi-failures-multi-exceptions/expected-output/failures/TestSanitySha2_256+github.com-onflow-crypto-hash/failure9.txt diff --git a/tools/test_monitor/testdata/summary2/test5-multi-failures-multi-exceptions/expected-output/failures/TestSanitySha3_256+github.com-onflow-flow-go-crypto-hash/failure1.txt b/tools/test_monitor/testdata/summary2/test5-multi-failures-multi-exceptions/expected-output/failures/TestSanitySha3_256+github.com-onflow-crypto-hash/failure1.txt similarity index 100% rename from tools/test_monitor/testdata/summary2/test5-multi-failures-multi-exceptions/expected-output/failures/TestSanitySha3_256+github.com-onflow-flow-go-crypto-hash/failure1.txt rename to tools/test_monitor/testdata/summary2/test5-multi-failures-multi-exceptions/expected-output/failures/TestSanitySha3_256+github.com-onflow-crypto-hash/failure1.txt diff --git a/tools/test_monitor/testdata/summary2/test5-multi-failures-multi-exceptions/expected-output/test5-multi-failures-multi-exceptions.json b/tools/test_monitor/testdata/summary2/test5-multi-failures-multi-exceptions/expected-output/test5-multi-failures-multi-exceptions.json index 040e31b8abc..b3ef9ce1823 100644 --- a/tools/test_monitor/testdata/summary2/test5-multi-failures-multi-exceptions/expected-output/test5-multi-failures-multi-exceptions.json +++ b/tools/test_monitor/testdata/summary2/test5-multi-failures-multi-exceptions/expected-output/test5-multi-failures-multi-exceptions.json @@ -1,8 +1,8 @@ { "tests": { - "github.com/onflow/flow-go/crypto/hash/TestSanitySha3_256": { + "github.com/onflow/crypto/hash/TestSanitySha3_256": { "test": "TestSanitySha3_256", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 24, "failed": 1, @@ -38,9 +38,9 @@ 0 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSanitySha3_384": { + "github.com/onflow/crypto/hash/TestSanitySha3_384": { "test": "TestSanitySha3_384", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 25, "failed": 0, @@ -76,9 +76,9 @@ 0 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSanitySha2_256": { + "github.com/onflow/crypto/hash/TestSanitySha2_256": { "test": "TestSanitySha2_256", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 24, "passed": 14, "failed": 10, @@ -113,9 +113,9 @@ 0 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSanitySha2_384": { + "github.com/onflow/crypto/hash/TestSanitySha2_384": { "test": "TestSanitySha2_384", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 25, "failed": 0, @@ -151,9 +151,9 @@ 0 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSanityKmac128": { + "github.com/onflow/crypto/hash/TestSanityKmac128": { "test": "TestSanityKmac128", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 25, "failed": 0, @@ -189,9 +189,9 @@ 0 ] }, - "github.com/onflow/flow-go/crypto/hash/TestHashersAPI": { + "github.com/onflow/crypto/hash/TestHashersAPI": { "test": "TestHashersAPI", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 24, "passed": 24, "failed": 0, @@ -226,9 +226,9 @@ 0 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSha3": { + "github.com/onflow/crypto/hash/TestSha3": { "test": "TestSha3", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 25, "failed": 0, @@ -264,9 +264,9 @@ 0.22 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSha3/SHA3_256": { + "github.com/onflow/crypto/hash/TestSha3/SHA3_256": { "test": "TestSha3/SHA3_256", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 25, "failed": 0, @@ -302,9 +302,9 @@ 0.1 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSha3/SHA3_384": { + "github.com/onflow/crypto/hash/TestSha3/SHA3_384": { "test": "TestSha3/SHA3_384", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 25, "failed": 0, diff --git a/tools/test_monitor/testdata/summary3/test1-1package-1failure/expected-output/test1-1package-1failure.json b/tools/test_monitor/testdata/summary3/test1-1package-1failure/expected-output/test1-1package-1failure.json index abb5ac0b893..51b4e4d10f1 100644 --- a/tools/test_monitor/testdata/summary3/test1-1package-1failure/expected-output/test1-1package-1failure.json +++ b/tools/test_monitor/testdata/summary3/test1-1package-1failure/expected-output/test1-1package-1failure.json @@ -3,7 +3,7 @@ "most_failures": [ { "test": "TestSanitySha3_256", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 1, "passed": 0, "failed": 1, @@ -20,7 +20,7 @@ "longest_running": [ { "test": "TestSha3", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 1, "passed": 1, "failed": 0, @@ -34,7 +34,7 @@ }, { "test": "TestSha3/SHA3_384", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 1, "passed": 1, "failed": 0, @@ -48,7 +48,7 @@ }, { "test": "TestSha3/SHA3_256", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 1, "passed": 1, "failed": 0, diff --git a/tools/test_monitor/testdata/summary3/test1-1package-1failure/input/test1-1package-1failure.json b/tools/test_monitor/testdata/summary3/test1-1package-1failure/input/test1-1package-1failure.json index bd477393a56..00aad44afd2 100644 --- a/tools/test_monitor/testdata/summary3/test1-1package-1failure/input/test1-1package-1failure.json +++ b/tools/test_monitor/testdata/summary3/test1-1package-1failure/input/test1-1package-1failure.json @@ -1,8 +1,8 @@ { "tests": { - "github.com/onflow/flow-go/crypto/hash/TestSanitySha3_256": { + "github.com/onflow/crypto/hash/TestSanitySha3_256": { "test": "TestSanitySha3_256", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 1, "passed": 0, "failed": 1, @@ -14,9 +14,9 @@ 0 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSanitySha3_384": { + "github.com/onflow/crypto/hash/TestSanitySha3_384": { "test": "TestSanitySha3_384", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 1, "passed": 1, "failed": 0, @@ -28,9 +28,9 @@ 0 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSanitySha2_256": { + "github.com/onflow/crypto/hash/TestSanitySha2_256": { "test": "TestSanitySha2_256", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 1, "passed": 1, "failed": 0, @@ -42,9 +42,9 @@ 0 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSanitySha2_384": { + "github.com/onflow/crypto/hash/TestSanitySha2_384": { "test": "TestSanitySha2_384", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 1, "passed": 1, "failed": 0, @@ -56,9 +56,9 @@ 0 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSanityKmac128": { + "github.com/onflow/crypto/hash/TestSanityKmac128": { "test": "TestSanityKmac128", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 1, "passed": 1, "failed": 0, @@ -70,9 +70,9 @@ 0 ] }, - "github.com/onflow/flow-go/crypto/hash/TestHashersAPI": { + "github.com/onflow/crypto/hash/TestHashersAPI": { "test": "TestHashersAPI", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 1, "passed": 1, "failed": 0, @@ -84,9 +84,9 @@ 0 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSha3": { + "github.com/onflow/crypto/hash/TestSha3": { "test": "TestSha3", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 1, "passed": 1, "failed": 0, @@ -98,9 +98,9 @@ 0.23 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSha3/SHA3_256": { + "github.com/onflow/crypto/hash/TestSha3/SHA3_256": { "test": "TestSha3/SHA3_256", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 1, "passed": 1, "failed": 0, @@ -112,9 +112,9 @@ 0.11 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSha3/SHA3_384": { + "github.com/onflow/crypto/hash/TestSha3/SHA3_384": { "test": "TestSha3/SHA3_384", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 1, "passed": 1, "failed": 0, diff --git a/tools/test_monitor/testdata/summary3/test4-multi-failures/expected-output/test4-multi-failures.json b/tools/test_monitor/testdata/summary3/test4-multi-failures/expected-output/test4-multi-failures.json index 49a32f08d29..fee741cc3ea 100644 --- a/tools/test_monitor/testdata/summary3/test4-multi-failures/expected-output/test4-multi-failures.json +++ b/tools/test_monitor/testdata/summary3/test4-multi-failures/expected-output/test4-multi-failures.json @@ -3,7 +3,7 @@ "most_failures": [ { "test": "TestSanitySha3_256_4", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 24, "passed": 14, "failed": 19, @@ -40,7 +40,7 @@ }, { "test": "TestSanitySha3_256_2", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 24, "passed": 14, "failed": 11, @@ -77,7 +77,7 @@ }, { "test": "TestSanitySha3_256", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 24, "passed": 14, "failed": 10, @@ -114,7 +114,7 @@ }, { "test": "TestSanitySha3_256_3", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 24, "passed": 14, "failed": 9, @@ -151,7 +151,7 @@ }, { "test": "TestSanitySha2_256", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 24, "failed": 1, @@ -192,7 +192,7 @@ "longest_running": [ { "test": "TestSha3", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 25, "failed": 0, @@ -230,7 +230,7 @@ }, { "test": "TestSha3/SHA3_384", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 25, "failed": 0, @@ -268,7 +268,7 @@ }, { "test": "TestSha3/SHA3_256", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 25, "failed": 0, diff --git a/tools/test_monitor/testdata/summary3/test4-multi-failures/input/test4-multi-failures.json b/tools/test_monitor/testdata/summary3/test4-multi-failures/input/test4-multi-failures.json index f61be9b8c2d..ce2de3a7f6a 100644 --- a/tools/test_monitor/testdata/summary3/test4-multi-failures/input/test4-multi-failures.json +++ b/tools/test_monitor/testdata/summary3/test4-multi-failures/input/test4-multi-failures.json @@ -1,8 +1,8 @@ { "tests": { - "github.com/onflow/flow-go/crypto/hash/TestSanitySha2_256": { + "github.com/onflow/crypto/hash/TestSanitySha2_256": { "test": "TestSanitySha2_256", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 24, "failed": 1, @@ -38,9 +38,9 @@ 0 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSanitySha3_384": { + "github.com/onflow/crypto/hash/TestSanitySha3_384": { "test": "TestSanitySha3_384", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 25, "failed": 0, @@ -76,9 +76,9 @@ 0 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSanitySha3_256": { + "github.com/onflow/crypto/hash/TestSanitySha3_256": { "test": "TestSanitySha3_256", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 24, "passed": 14, "failed": 10, @@ -113,9 +113,9 @@ 0 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSanitySha2_384": { + "github.com/onflow/crypto/hash/TestSanitySha2_384": { "test": "TestSanitySha2_384", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 25, "failed": 0, @@ -151,9 +151,9 @@ 0 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSanitySha3_256_2": { + "github.com/onflow/crypto/hash/TestSanitySha3_256_2": { "test": "TestSanitySha3_256_2", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 24, "passed": 14, "failed": 11, @@ -188,9 +188,9 @@ 0 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSanityKmac128": { + "github.com/onflow/crypto/hash/TestSanityKmac128": { "test": "TestSanityKmac128", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 25, "failed": 0, @@ -226,9 +226,9 @@ 0 ] }, - "github.com/onflow/flow-go/crypto/hash/TestHashersAPI": { + "github.com/onflow/crypto/hash/TestHashersAPI": { "test": "TestHashersAPI", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 24, "passed": 24, "failed": 0, @@ -263,9 +263,9 @@ 0 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSha3": { + "github.com/onflow/crypto/hash/TestSha3": { "test": "TestSha3", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 25, "failed": 0, @@ -301,9 +301,9 @@ 0.22 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSanitySha3_256_3": { + "github.com/onflow/crypto/hash/TestSanitySha3_256_3": { "test": "TestSanitySha3_256_3", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 24, "passed": 14, "failed": 9, @@ -338,9 +338,9 @@ 0 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSha3/SHA3_256": { + "github.com/onflow/crypto/hash/TestSha3/SHA3_256": { "test": "TestSha3/SHA3_256", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 25, "failed": 0, @@ -376,9 +376,9 @@ 0.1 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSanitySha3_256_4": { + "github.com/onflow/crypto/hash/TestSanitySha3_256_4": { "test": "TestSanitySha3_256_4", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 24, "passed": 14, "failed": 19, @@ -413,9 +413,9 @@ 0 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSha3/SHA3_384": { + "github.com/onflow/crypto/hash/TestSha3/SHA3_384": { "test": "TestSha3/SHA3_384", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 25, "failed": 0, diff --git a/tools/test_monitor/testdata/summary3/test5-multi-durations/expected-output/test5-multi-durations.json b/tools/test_monitor/testdata/summary3/test5-multi-durations/expected-output/test5-multi-durations.json index 7336f71d3d8..86ad9d9f88f 100644 --- a/tools/test_monitor/testdata/summary3/test5-multi-durations/expected-output/test5-multi-durations.json +++ b/tools/test_monitor/testdata/summary3/test5-multi-durations/expected-output/test5-multi-durations.json @@ -18,7 +18,7 @@ "most_failures": [ { "test": "TestSanitySha2_256", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 24, "passed": 14, "failed": 10, @@ -55,7 +55,7 @@ }, { "test": "TestSanitySha3_256", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 24, "failed": 1, @@ -96,7 +96,7 @@ "longest_running": [ { "test": "TestSha3_2", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 25, "failed": 0, @@ -134,7 +134,7 @@ }, { "test": "TestSha3", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 25, "failed": 0, @@ -172,7 +172,7 @@ }, { "test": "TestSha3_3", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 25, "failed": 0, @@ -210,7 +210,7 @@ }, { "test": "TestSha3/SHA3_384", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 25, "failed": 0, @@ -248,7 +248,7 @@ }, { "test": "TestSha3/SHA3_256", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 25, "failed": 0, @@ -286,7 +286,7 @@ }, { "test": "TestSha3_4", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 25, "failed": 0, diff --git a/tools/test_monitor/testdata/summary3/test5-multi-durations/input/test5-multi-durations.json b/tools/test_monitor/testdata/summary3/test5-multi-durations/input/test5-multi-durations.json index efc8872446e..e73536877c9 100644 --- a/tools/test_monitor/testdata/summary3/test5-multi-durations/input/test5-multi-durations.json +++ b/tools/test_monitor/testdata/summary3/test5-multi-durations/input/test5-multi-durations.json @@ -1,8 +1,8 @@ { "tests": { - "github.com/onflow/flow-go/crypto/hash/TestSanitySha3_256": { + "github.com/onflow/crypto/hash/TestSanitySha3_256": { "test": "TestSanitySha3_256", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 24, "failed": 1, @@ -38,9 +38,9 @@ 0 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSanitySha3_384": { + "github.com/onflow/crypto/hash/TestSanitySha3_384": { "test": "TestSanitySha3_384", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 25, "failed": 0, @@ -76,9 +76,9 @@ 0 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSanitySha2_256": { + "github.com/onflow/crypto/hash/TestSanitySha2_256": { "test": "TestSanitySha2_256", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 24, "passed": 14, "failed": 10, @@ -113,9 +113,9 @@ 0 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSanitySha2_384": { + "github.com/onflow/crypto/hash/TestSanitySha2_384": { "test": "TestSanitySha2_384", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 25, "failed": 0, @@ -151,9 +151,9 @@ 0 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSanityKmac128": { + "github.com/onflow/crypto/hash/TestSanityKmac128": { "test": "TestSanityKmac128", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 25, "failed": 0, @@ -189,9 +189,9 @@ 0 ] }, - "github.com/onflow/flow-go/crypto/hash/TestHashersAPI": { + "github.com/onflow/crypto/hash/TestHashersAPI": { "test": "TestHashersAPI", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 24, "passed": 24, "failed": 0, @@ -226,9 +226,9 @@ 0 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSha3": { + "github.com/onflow/crypto/hash/TestSha3": { "test": "TestSha3", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 25, "failed": 0, @@ -264,9 +264,9 @@ 0.22 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSha3/SHA3_256": { + "github.com/onflow/crypto/hash/TestSha3/SHA3_256": { "test": "TestSha3/SHA3_256", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 25, "failed": 0, @@ -302,9 +302,9 @@ 0.1 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSha3/SHA3_384": { + "github.com/onflow/crypto/hash/TestSha3/SHA3_384": { "test": "TestSha3/SHA3_384", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 25, "failed": 0, @@ -516,9 +516,9 @@ 0 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSha3_4": { + "github.com/onflow/crypto/hash/TestSha3_4": { "test": "TestSha3_4", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 25, "failed": 0, @@ -600,9 +600,9 @@ 0 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSha3_2": { + "github.com/onflow/crypto/hash/TestSha3_2": { "test": "TestSha3_2", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 25, "failed": 0, @@ -638,9 +638,9 @@ 0.22 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSha3_3": { + "github.com/onflow/crypto/hash/TestSha3_3": { "test": "TestSha3_3", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 25, "failed": 0, diff --git a/tools/test_monitor/testdata/summary3/test6-multi-failures-cap/expected-output/test6-multi-failures-cap.json b/tools/test_monitor/testdata/summary3/test6-multi-failures-cap/expected-output/test6-multi-failures-cap.json index 5db7f8f8015..2b1cf11a24e 100644 --- a/tools/test_monitor/testdata/summary3/test6-multi-failures-cap/expected-output/test6-multi-failures-cap.json +++ b/tools/test_monitor/testdata/summary3/test6-multi-failures-cap/expected-output/test6-multi-failures-cap.json @@ -3,7 +3,7 @@ "most_failures": [ { "test": "TestSanitySha3_256_6", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 24, "passed": 12, "failed": 21, @@ -40,7 +40,7 @@ }, { "test": "TestSanitySha3_256_5", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 24, "passed": 13, "failed": 20, @@ -77,7 +77,7 @@ }, { "test": "TestSanitySha3_256_4", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 24, "passed": 14, "failed": 19, @@ -114,7 +114,7 @@ }, { "test": "TestSanitySha3_256_7", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 24, "passed": 12, "failed": 17, @@ -151,7 +151,7 @@ }, { "test": "TestSanitySha3_256_2", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 24, "passed": 14, "failed": 11, @@ -191,7 +191,7 @@ "longest_running": [ { "test": "TestSha3", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 25, "failed": 0, @@ -229,7 +229,7 @@ }, { "test": "TestSha3/SHA3_384", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 25, "failed": 0, @@ -267,7 +267,7 @@ }, { "test": "TestSha3/SHA3_256", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 25, "failed": 0, diff --git a/tools/test_monitor/testdata/summary3/test6-multi-failures-cap/input/test6-multi-failures-cap.json b/tools/test_monitor/testdata/summary3/test6-multi-failures-cap/input/test6-multi-failures-cap.json index 69d0aedbe34..fddf7ef19ad 100644 --- a/tools/test_monitor/testdata/summary3/test6-multi-failures-cap/input/test6-multi-failures-cap.json +++ b/tools/test_monitor/testdata/summary3/test6-multi-failures-cap/input/test6-multi-failures-cap.json @@ -1,8 +1,8 @@ { "tests": { - "github.com/onflow/flow-go/crypto/hash/TestSanitySha2_256": { + "github.com/onflow/crypto/hash/TestSanitySha2_256": { "test": "TestSanitySha2_256", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 24, "failed": 1, @@ -38,9 +38,9 @@ 0 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSanitySha3_384": { + "github.com/onflow/crypto/hash/TestSanitySha3_384": { "test": "TestSanitySha3_384", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 25, "failed": 0, @@ -76,9 +76,9 @@ 0 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSanitySha3_256": { + "github.com/onflow/crypto/hash/TestSanitySha3_256": { "test": "TestSanitySha3_256", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 24, "passed": 14, "failed": 10, @@ -113,9 +113,9 @@ 0 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSanitySha2_384": { + "github.com/onflow/crypto/hash/TestSanitySha2_384": { "test": "TestSanitySha2_384", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 25, "failed": 0, @@ -151,9 +151,9 @@ 0 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSanitySha3_256_2": { + "github.com/onflow/crypto/hash/TestSanitySha3_256_2": { "test": "TestSanitySha3_256_2", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 24, "passed": 14, "failed": 11, @@ -188,9 +188,9 @@ 0 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSanityKmac128": { + "github.com/onflow/crypto/hash/TestSanityKmac128": { "test": "TestSanityKmac128", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 25, "failed": 0, @@ -226,9 +226,9 @@ 0 ] }, - "github.com/onflow/flow-go/crypto/hash/TestHashersAPI": { + "github.com/onflow/crypto/hash/TestHashersAPI": { "test": "TestHashersAPI", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 24, "passed": 24, "failed": 0, @@ -263,9 +263,9 @@ 0 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSha3": { + "github.com/onflow/crypto/hash/TestSha3": { "test": "TestSha3", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 25, "failed": 0, @@ -301,9 +301,9 @@ 0.22 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSanitySha3_256_3": { + "github.com/onflow/crypto/hash/TestSanitySha3_256_3": { "test": "TestSanitySha3_256_3", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 24, "passed": 14, "failed": 9, @@ -338,9 +338,9 @@ 0 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSha3/SHA3_256": { + "github.com/onflow/crypto/hash/TestSha3/SHA3_256": { "test": "TestSha3/SHA3_256", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 25, "failed": 0, @@ -376,9 +376,9 @@ 0.1 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSanitySha3_256_4": { + "github.com/onflow/crypto/hash/TestSanitySha3_256_4": { "test": "TestSanitySha3_256_4", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 24, "passed": 14, "failed": 19, @@ -413,9 +413,9 @@ 0 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSanitySha3_256_5": { + "github.com/onflow/crypto/hash/TestSanitySha3_256_5": { "test": "TestSanitySha3_256_5", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 24, "passed": 13, "failed": 20, @@ -450,9 +450,9 @@ 0 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSanitySha3_256_6": { + "github.com/onflow/crypto/hash/TestSanitySha3_256_6": { "test": "TestSanitySha3_256_6", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 24, "passed": 12, "failed": 21, @@ -487,9 +487,9 @@ 0 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSanitySha3_256_7": { + "github.com/onflow/crypto/hash/TestSanitySha3_256_7": { "test": "TestSanitySha3_256_7", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 24, "passed": 12, "failed": 17, @@ -524,9 +524,9 @@ 0 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSha3/SHA3_384": { + "github.com/onflow/crypto/hash/TestSha3/SHA3_384": { "test": "TestSha3/SHA3_384", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 25, "failed": 0, diff --git a/tools/test_monitor/testdata/summary3/test7-multi-durations-cap/expected-output/test7-multi-durations-cap.json b/tools/test_monitor/testdata/summary3/test7-multi-durations-cap/expected-output/test7-multi-durations-cap.json index 00fffd33996..78832c110ff 100644 --- a/tools/test_monitor/testdata/summary3/test7-multi-durations-cap/expected-output/test7-multi-durations-cap.json +++ b/tools/test_monitor/testdata/summary3/test7-multi-durations-cap/expected-output/test7-multi-durations-cap.json @@ -18,7 +18,7 @@ "most_failures": [ { "test": "TestSanitySha2_256", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 24, "passed": 14, "failed": 10, @@ -55,7 +55,7 @@ }, { "test": "TestSanitySha3_256", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 24, "failed": 1, @@ -96,7 +96,7 @@ "longest_running": [ { "test": "TestSha3_8", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 25, "failed": 0, @@ -134,7 +134,7 @@ }, { "test": "TestSha3_9", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 25, "failed": 0, @@ -172,7 +172,7 @@ }, { "test": "TestSha3_10", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 25, "failed": 0, @@ -210,7 +210,7 @@ }, { "test": "TestSha3_7", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 25, "failed": 0, @@ -248,7 +248,7 @@ }, { "test": "TestSha3_5", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 25, "failed": 0, diff --git a/tools/test_monitor/testdata/summary3/test7-multi-durations-cap/input/test7-multi-durations-cap.json b/tools/test_monitor/testdata/summary3/test7-multi-durations-cap/input/test7-multi-durations-cap.json index 0763288eadf..5e3ee5f7c51 100644 --- a/tools/test_monitor/testdata/summary3/test7-multi-durations-cap/input/test7-multi-durations-cap.json +++ b/tools/test_monitor/testdata/summary3/test7-multi-durations-cap/input/test7-multi-durations-cap.json @@ -1,8 +1,8 @@ { "tests": { - "github.com/onflow/flow-go/crypto/hash/TestSanitySha3_256": { + "github.com/onflow/crypto/hash/TestSanitySha3_256": { "test": "TestSanitySha3_256", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 24, "failed": 1, @@ -38,9 +38,9 @@ 0 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSanitySha3_384": { + "github.com/onflow/crypto/hash/TestSanitySha3_384": { "test": "TestSanitySha3_384", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 25, "failed": 0, @@ -76,9 +76,9 @@ 0 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSanitySha2_256": { + "github.com/onflow/crypto/hash/TestSanitySha2_256": { "test": "TestSanitySha2_256", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 24, "passed": 14, "failed": 10, @@ -113,9 +113,9 @@ 0 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSanitySha2_384": { + "github.com/onflow/crypto/hash/TestSanitySha2_384": { "test": "TestSanitySha2_384", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 25, "failed": 0, @@ -151,9 +151,9 @@ 0 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSanityKmac128": { + "github.com/onflow/crypto/hash/TestSanityKmac128": { "test": "TestSanityKmac128", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 25, "failed": 0, @@ -189,9 +189,9 @@ 0 ] }, - "github.com/onflow/flow-go/crypto/hash/TestHashersAPI": { + "github.com/onflow/crypto/hash/TestHashersAPI": { "test": "TestHashersAPI", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 24, "passed": 24, "failed": 0, @@ -226,9 +226,9 @@ 0 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSha3": { + "github.com/onflow/crypto/hash/TestSha3": { "test": "TestSha3", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 25, "failed": 0, @@ -264,9 +264,9 @@ 0.22 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSha3/SHA3_256": { + "github.com/onflow/crypto/hash/TestSha3/SHA3_256": { "test": "TestSha3/SHA3_256", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 25, "failed": 0, @@ -302,9 +302,9 @@ 0.1 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSha3/SHA3_384": { + "github.com/onflow/crypto/hash/TestSha3/SHA3_384": { "test": "TestSha3/SHA3_384", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 25, "failed": 0, @@ -516,9 +516,9 @@ 0 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSha3_4": { + "github.com/onflow/crypto/hash/TestSha3_4": { "test": "TestSha3_4", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 25, "failed": 0, @@ -600,9 +600,9 @@ 0 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSha3_2": { + "github.com/onflow/crypto/hash/TestSha3_2": { "test": "TestSha3_2", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 25, "failed": 0, @@ -638,9 +638,9 @@ 0.22 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSha3_3": { + "github.com/onflow/crypto/hash/TestSha3_3": { "test": "TestSha3_3", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 25, "failed": 0, @@ -676,9 +676,9 @@ 0.22 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSha3_5": { + "github.com/onflow/crypto/hash/TestSha3_5": { "test": "TestSha3_5", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 25, "failed": 0, @@ -714,9 +714,9 @@ 0.22 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSha3_6": { + "github.com/onflow/crypto/hash/TestSha3_6": { "test": "TestSha3_6", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 25, "failed": 0, @@ -752,9 +752,9 @@ 0.22 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSha3_7": { + "github.com/onflow/crypto/hash/TestSha3_7": { "test": "TestSha3_7", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 25, "failed": 0, @@ -790,9 +790,9 @@ 0.22 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSha3_8": { + "github.com/onflow/crypto/hash/TestSha3_8": { "test": "TestSha3_8", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 25, "failed": 0, @@ -828,9 +828,9 @@ 0.22 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSha3_9": { + "github.com/onflow/crypto/hash/TestSha3_9": { "test": "TestSha3_9", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 25, "failed": 0, @@ -866,9 +866,9 @@ 0.22 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSha3_10": { + "github.com/onflow/crypto/hash/TestSha3_10": { "test": "TestSha3_10", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 25, "failed": 0, @@ -904,9 +904,9 @@ 0.22 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSha3_11": { + "github.com/onflow/crypto/hash/TestSha3_11": { "test": "TestSha3_11", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 25, "failed": 0, @@ -942,9 +942,9 @@ 0.22 ] }, - "github.com/onflow/flow-go/crypto/hash/TestSha3_12": { + "github.com/onflow/crypto/hash/TestSha3_12": { "test": "TestSha3_12", - "package": "github.com/onflow/flow-go/crypto/hash", + "package": "github.com/onflow/crypto/hash", "runs": 25, "passed": 25, "failed": 0, diff --git a/utils/debug/README.md b/utils/debug/README.md index c7591241d10..fc5435d34a7 100644 --- a/utils/debug/README.md +++ b/utils/debug/README.md @@ -39,7 +39,7 @@ func TestDebugger_RunTransaction(t *testing.T) { script := []byte(fmt.Sprintf(scriptTemplate, chain.ServiceAddress())) txBody := flow.NewTransactionBody(). - SetGasLimit(9999). + SetComputeLimit(9999). SetScript([]byte(script)). SetPayer(chain.ServiceAddress()). SetProposalKey(chain.ServiceAddress(), 0, 0) diff --git a/utils/debug/registerCache.go b/utils/debug/registerCache.go index 086be61b250..11e0c801ca7 100644 --- a/utils/debug/registerCache.go +++ b/utils/debug/registerCache.go @@ -97,10 +97,10 @@ func (f *fileRegisterCache) Get(owner, key string) ([]byte, bool) { func (f *fileRegisterCache) Set(owner, key string, value []byte) { valueCopy := make([]byte, len(value)) copy(valueCopy, value) - fmt.Println(hex.EncodeToString([]byte(owner)), hex.EncodeToString([]byte(key)), len(value)) + ownerAddr := flow.BytesToAddress([]byte(owner)) + fmt.Println(ownerAddr.Hex(), hex.EncodeToString([]byte(key)), len(value)) f.data[owner+"~"+key] = flow.RegisterEntry{ - Key: flow.NewRegisterID(hex.EncodeToString([]byte(owner)), - hex.EncodeToString([]byte(key))), + Key: flow.NewRegisterID(ownerAddr, hex.EncodeToString([]byte(key))), Value: flow.RegisterValue(valueCopy), } } diff --git a/utils/grpcutils/grpc.go b/utils/grpcutils/grpc.go index d900da7ada6..43c834e48f7 100644 --- a/utils/grpcutils/grpc.go +++ b/utils/grpcutils/grpc.go @@ -8,8 +8,8 @@ import ( lcrypto "github.com/libp2p/go-libp2p/core/crypto" libp2ptls "github.com/libp2p/go-libp2p/p2p/security/tls" + "github.com/onflow/crypto" - "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/network/p2p/keyutils" ) diff --git a/utils/rand/rand.go b/utils/rand/rand.go index e56e2e3df60..93040db41b6 100644 --- a/utils/rand/rand.go +++ b/utils/rand/rand.go @@ -5,7 +5,7 @@ // This package should be used instead of `math/rand` for any use-case requiring // a secure randomness. It provides similar APIs to the ones provided by `math/rand`. // This package does not implement any determinstic RNG (Pseudo-RNG) based on -// user input seeds. For the deterministic use-cases please use `flow-go/crypto/random`. +// user input seeds. For the deterministic use-cases please use `github.com/onflow/crypto/random`. // // Functions in this package may return an error if the underlying system implementation fails // to read new randoms. When that happens, this package considers it an irrecoverable exception. diff --git a/utils/rand/rand_test.go b/utils/rand/rand_test.go index 73d7ca539ca..99c0f14303a 100644 --- a/utils/rand/rand_test.go +++ b/utils/rand/rand_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/onflow/flow-go/crypto/random" + "github.com/onflow/crypto/random" ) func TestRandomIntegers(t *testing.T) { diff --git a/utils/unittest/cluster.go b/utils/unittest/cluster.go index 80d8627342c..a88f4c6c13a 100644 --- a/utils/unittest/cluster.go +++ b/utils/unittest/cluster.go @@ -2,12 +2,12 @@ package unittest import ( "fmt" - "sort" + + "golang.org/x/exp/slices" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/model/flow/factory" "github.com/onflow/flow-go/model/flow/filter" - "github.com/onflow/flow-go/model/flow/order" ) // TransactionForCluster generates a transaction that will be assigned to the @@ -51,9 +51,7 @@ func ClusterAssignment(n uint, nodes flow.IdentityList) flow.AssignmentList { collectors := nodes.Filter(filter.HasRole(flow.RoleCollection)) // order, so the same list results in the same - sort.Slice(collectors, func(i, j int) bool { - return order.Canonical(collectors[i], collectors[j]) - }) + slices.SortFunc(collectors, flow.Canonical) assignments := make(flow.AssignmentList, n) for i, collector := range collectors { diff --git a/utils/unittest/execution_state.go b/utils/unittest/execution_state.go index 2bdb44ae0a8..5e0b3d3620f 100644 --- a/utils/unittest/execution_state.go +++ b/utils/unittest/execution_state.go @@ -4,10 +4,9 @@ import ( "encoding/hex" "fmt" - "github.com/onflow/flow-go/crypto" - "github.com/onflow/flow-go/crypto/hash" - "github.com/onflow/cadence" + "github.com/onflow/crypto" + "github.com/onflow/crypto/hash" "github.com/onflow/flow-go/model/flow" ) @@ -24,7 +23,7 @@ const ServiceAccountPrivateKeySignAlgo = crypto.ECDSAP256 const ServiceAccountPrivateKeyHashAlgo = hash.SHA2_256 // Pre-calculated state commitment with root account with the above private key -const GenesisStateCommitmentHex = "8476e9a47da2f993fd0d04efc10238113f217b0c037ccf903613088d51027d6a" +const GenesisStateCommitmentHex = "e4674bba14f59af783bbf70b2a43c1696a7d9888eeaca86cf74b033580fe1c23" var GenesisStateCommitment flow.StateCommitment @@ -88,10 +87,10 @@ func genesisCommitHexByChainID(chainID flow.ChainID) string { return GenesisStateCommitmentHex } if chainID == flow.Testnet { - return "7df9e8ac804d283b27052abda8278866edcb6cae298862a21634ca62892049e7" + return "bfe964655cf13711b93dbaf156aaebbc24a607beed69dd36d71b593832b5129c" } if chainID == flow.Sandboxnet { return "e1c08b17f9e5896f03fe28dd37ca396c19b26628161506924fbf785834646ea1" } - return "8476b2a11c9bf11e70b82adfa2daee598ba08e6e7d40e78958768b7aed4e6a4d" + return "a56a2750708bc981eb949a3b02a41061dc6b7e6bfa9f31a19a48f560f616bed3" } diff --git a/utils/unittest/fixtures.go b/utils/unittest/fixtures.go index 73830143a26..b6a8e624d19 100644 --- a/utils/unittest/fixtures.go +++ b/utils/unittest/fixtures.go @@ -14,12 +14,12 @@ import ( pubsub_pb "github.com/libp2p/go-libp2p-pubsub/pb" "github.com/libp2p/go-libp2p/core/peer" "github.com/onflow/cadence" + "github.com/onflow/crypto" + "github.com/onflow/crypto/hash" "github.com/stretchr/testify/require" sdk "github.com/onflow/flow-go-sdk" hotstuff "github.com/onflow/flow-go/consensus/hotstuff/model" - "github.com/onflow/flow-go/crypto" - "github.com/onflow/flow-go/crypto/hash" "github.com/onflow/flow-go/engine" "github.com/onflow/flow-go/engine/access/rest/util" "github.com/onflow/flow-go/fvm/storage/snapshot" @@ -33,7 +33,6 @@ import ( "github.com/onflow/flow-go/model/encoding" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/model/flow/filter" - "github.com/onflow/flow-go/model/flow/order" "github.com/onflow/flow-go/model/messages" "github.com/onflow/flow-go/model/verification" "github.com/onflow/flow-go/module" @@ -43,8 +42,8 @@ import ( "github.com/onflow/flow-go/module/updatable_configs" "github.com/onflow/flow-go/network/channels" "github.com/onflow/flow-go/network/message" + p2pconfig "github.com/onflow/flow-go/network/p2p/config" "github.com/onflow/flow-go/network/p2p/keyutils" - "github.com/onflow/flow-go/network/p2p/p2pconf" "github.com/onflow/flow-go/state/protocol" "github.com/onflow/flow-go/state/protocol/inmem" "github.com/onflow/flow-go/utils/dsl" @@ -73,9 +72,13 @@ func AddressFixture() flow.Address { } func RandomAddressFixture() flow.Address { + return RandomAddressFixtureForChain(flow.Testnet) +} + +func RandomAddressFixtureForChain(chainID flow.ChainID) flow.Address { // we use a 32-bit index - since the linear address generator uses 45 bits, // this won't error - addr, err := flow.Testnet.Chain().AddressAtIndex(uint64(rand.Uint32())) + addr, err := chainID.Chain().AddressAtIndex(uint64(rand.Uint32())) if err != nil { panic(err) } @@ -1429,10 +1432,7 @@ func TransactionDSLFixture(chain flow.Chain) dsl.Transaction { // RegisterIDFixture returns a RegisterID with a fixed key and owner func RegisterIDFixture() flow.RegisterID { - return flow.RegisterID{ - Owner: "owner", - Key: "key", - } + return flow.NewRegisterID(RandomAddressFixture(), "key") } // VerifiableChunkDataFixture returns a complete verifiable chunk with an @@ -1713,6 +1713,11 @@ func EventsFixture( return events } +func EventTypeFixture(chainID flow.ChainID) flow.EventType { + eventType := fmt.Sprintf("A.%s.TestContract.TestEvent1", RandomAddressFixtureForChain(chainID)) + return flow.EventType(eventType) +} + // EventFixture returns an event func EventFixture( eType flow.EventType, @@ -1733,7 +1738,8 @@ func EventFixture( func EmulatorRootKey() (*flow.AccountPrivateKey, error) { // TODO seems this key literal doesn't decode anymore - emulatorRootKey, err := crypto.DecodePrivateKey(crypto.ECDSAP256, []byte("f87db87930770201010420ae2cc975dcbdd0ebc56f268b1d8a95834c2955970aea27042d35ec9f298b9e5aa00a06082a8648ce3d030107a1440342000417f5a527137785d2d773fee84b4c7ee40266a1dd1f36ddd46ecf25db6df6a499459629174de83256f2a44ebd4325b9def67d523b755a8926218c4efb7904f8ce0203")) + emulatorRootKey, err := crypto.DecodePrivateKey(crypto.ECDSAP256, + []byte("f87db87930770201010420ae2cc975dcbdd0ebc56f268b1d8a95834c2955970aea27042d35ec9f298b9e5aa00a06082a8648ce3d030107a1440342000417f5a527137785d2d773fee84b4c7ee40266a1dd1f36ddd46ecf25db6df6a499459629174de83256f2a44ebd4325b9def67d523b755a8926218c4efb7904f8ce0203")) if err != nil { return nil, err } @@ -1969,7 +1975,7 @@ func VoteWithBeaconSig() func(*hotstuff.Vote) { func WithParticipants(participants flow.IdentityList) func(*flow.EpochSetup) { return func(setup *flow.EpochSetup) { - setup.Participants = participants.Sort(order.Canonical) + setup.Participants = participants.Sort(flow.Canonical) setup.Assignments = ClusterAssignment(1, participants) } } @@ -2001,7 +2007,7 @@ func EpochSetupFixture(opts ...func(setup *flow.EpochSetup)) *flow.EpochSetup { Counter: uint64(rand.Uint32()), FirstView: uint64(0), FinalView: uint64(rand.Uint32() + 1000), - Participants: participants.Sort(order.Canonical), + Participants: participants.Sort(flow.Canonical), RandomSource: SeedFixture(flow.EpochSetupRandomSourceLength), DKGPhase1FinalView: 100, DKGPhase2FinalView: 200, @@ -2179,7 +2185,7 @@ func RootSnapshotFixtureWithChainID( chainID flow.ChainID, opts ...func(*flow.Block), ) *inmem.Snapshot { - block, result, seal := BootstrapFixtureWithChainID(participants.Sort(order.Canonical), chainID, opts...) + block, result, seal := BootstrapFixtureWithChainID(participants.Sort(flow.Canonical), chainID, opts...) qc := QuorumCertificateFixture(QCWithRootBlockID(block.ID())) root, err := inmem.SnapshotFromBootstrapState(block, result, seal, qc) if err != nil { @@ -2750,8 +2756,8 @@ func GossipSubMessageFixtures(n int, topic string, opts ...func(*pubsub_pb.Messa // The values are not guaranteed to be valid between 0 and 1000. // Returns: // - p2pconf.ResourceManagerOverrideLimit: a random resource limit override. -func LibP2PResourceLimitOverrideFixture() p2pconf.ResourceManagerOverrideLimit { - return p2pconf.ResourceManagerOverrideLimit{ +func LibP2PResourceLimitOverrideFixture() p2pconfig.ResourceManagerOverrideLimit { + return p2pconfig.ResourceManagerOverrideLimit{ StreamsInbound: rand.Intn(1000), StreamsOutbound: rand.Intn(1000), ConnectionsInbound: rand.Intn(1000), diff --git a/utils/unittest/generator/events.go b/utils/unittest/generator/events.go index ac86cd43136..bd77da80f7b 100644 --- a/utils/unittest/generator/events.go +++ b/utils/unittest/generator/events.go @@ -10,6 +10,7 @@ import ( "github.com/onflow/flow/protobuf/go/flow/entities" "github.com/onflow/flow-go/model/flow" + "github.com/onflow/flow-go/utils/unittest" ) type EventGeneratorOption func(*Events) @@ -41,8 +42,13 @@ func EventGenerator(opts ...EventGeneratorOption) *Events { } func (g *Events) New() flow.Event { - location := common.StringLocation("test") - identifier := fmt.Sprintf("FooEvent%d", g.count) + address, err := common.BytesToAddress(unittest.RandomAddressFixture().Bytes()) + if err != nil { + panic(fmt.Sprintf("unexpected error while creating random address: %s", err)) + } + + location := common.NewAddressLocation(nil, address, "TestContract") + identifier := fmt.Sprintf("TestContract.FooEvent%d", g.count) typeID := location.TypeID(nil, identifier) testEventType := &cadence.EventType{ diff --git a/utils/unittest/keys.go b/utils/unittest/keys.go index 6dac2b3f703..adf300948e6 100644 --- a/utils/unittest/keys.go +++ b/utils/unittest/keys.go @@ -3,7 +3,7 @@ package unittest import ( "encoding/hex" - "github.com/onflow/flow-go/crypto" + "github.com/onflow/crypto" "github.com/onflow/flow-go/model/encodable" ) diff --git a/utils/unittest/logging.go b/utils/unittest/logging.go index a200a61525e..c1b993cfab7 100644 --- a/utils/unittest/logging.go +++ b/utils/unittest/logging.go @@ -30,7 +30,7 @@ func Logger() zerolog.Logger { writer = os.Stderr } - return LoggerWithWriterAndLevel(writer, zerolog.TraceLevel) + return LoggerWithWriterAndLevel(writer, zerolog.InfoLevel) } func LoggerWithWriterAndLevel(writer io.Writer, level zerolog.Level) zerolog.Logger { diff --git a/utils/unittest/math.go b/utils/unittest/math.go new file mode 100644 index 00000000000..eb44762247d --- /dev/null +++ b/utils/unittest/math.go @@ -0,0 +1,61 @@ +package unittest + +import ( + "math" + "testing" + + "github.com/stretchr/testify/require" +) + +// RequireNumericallyClose is a wrapper around require.Equal that allows for a small epsilon difference between +// two floats. This is useful when comparing floats that are the result of a computation. For example, when comparing +// the result of a computation with a constant. +// The epsilon is calculated as: +// +// epsilon = max(|a|, |b|) * epsilon +// +// Example: +// +// RequireNumericallyClose(t, 1.0, 1.1, 0.1) // passes since 1.0 * 0.1 = 0.1 < 0.1 +// RequireNumericallyClose(t, 1.0, 1.1, 0.01) // fails since 1.0 * 0.01 = 0.01 < 0.1 +// RequireNumericallyClose(t, 1.0, 1.1, 0.11) // fails since 1.1 * 0.11 = 0.121 > 0.1 +// +// Args: +// +// t: the testing.TB instance +// a: the first float +// b: the second float +func RequireNumericallyClose(t testing.TB, a, b float64, epsilon float64, msgAndArgs ...interface{}) { + require.True(t, AreNumericallyClose(a, b, epsilon), msgAndArgs...) +} + +// AreNumericallyClose returns true if the two floats are within epsilon of each other. +// The epsilon is calculated as: +// +// epsilon = max(|a|, |b|) * epsilon +// +// Example: +// +// AreNumericallyClose(1.0, 1.1, 0.1) // true since 1.0 * 0.1 = 0.1 < 0.1 +// AreNumericallyClose(1.0, 1.1, 0.01) // false since 1.0 * 0.01 = 0.01 < 0.1 +// AreNumericallyClose(1.0, 1.1, 0.11) // false since 1.1 * 0.11 = 0.121 > 0.1 +// +// Args: +// a: the first float +// b: the second float +// epsilon: the epsilon value +// Returns: +// true if the two floats are within epsilon of each other +// false otherwise +func AreNumericallyClose(a, b float64, epsilon float64) bool { + if a == float64(0) { + return math.Abs(b) <= epsilon + } + if b == float64(0) { + return math.Abs(a) <= epsilon + } + + nominator := math.Abs(a - b) + denominator := math.Max(math.Abs(a), math.Abs(b)) + return nominator/denominator <= epsilon +} diff --git a/utils/unittest/math_test.go b/utils/unittest/math_test.go new file mode 100644 index 00000000000..bfbc4547d91 --- /dev/null +++ b/utils/unittest/math_test.go @@ -0,0 +1,36 @@ +package unittest_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/onflow/flow-go/utils/unittest" +) + +func TestAreNumericallyClose(t *testing.T) { + tests := []struct { + name string + a float64 + b float64 + epsilon float64 + expected bool + }{ + {"close enough under epsilon", 1.0, 1.1, 0.1, true}, + {"not close under epsilon", 1.0, 1.1, 0.01, false}, + {"equal values", 2.0, 2.0, 0.1, true}, + {"zero epsilon with equal values", 2.0, 2.0, 0.0, true}, + {"zero epsilon with different values", 2.0, 2.1, 0.0, false}, + {"first value zero", 0, 0.1, 0.1, true}, + {"both values zero", 0, 0, 0.1, true}, + {"negative values close enough", -1.0, -1.1, 0.1, true}, + {"negative values not close enough", -1.0, -1.2, 0.1, false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual := unittest.AreNumericallyClose(tt.a, tt.b, tt.epsilon) + require.Equal(t, tt.expected, actual, "test Failed: %s", tt.name) + }) + } +} diff --git a/utils/unittest/mockEntity.go b/utils/unittest/mockEntity.go index 60dfca14ac7..4b1b66300ad 100644 --- a/utils/unittest/mockEntity.go +++ b/utils/unittest/mockEntity.go @@ -7,6 +7,7 @@ import ( // MockEntity implements a bare minimum entity for sake of test. type MockEntity struct { Identifier flow.Identifier + Nonce uint64 } func (m MockEntity) ID() flow.Identifier { diff --git a/utils/unittest/mocks/mock_getters.go b/utils/unittest/mocks/mock_getters.go new file mode 100644 index 00000000000..c36d53c75f0 --- /dev/null +++ b/utils/unittest/mocks/mock_getters.go @@ -0,0 +1,55 @@ +package mocks + +import "github.com/onflow/flow-go/storage" + +// StorageMapGetter implements a simple generic getter function for mock storage methods. +// This is useful to avoid duplicating boilerplate code for mock storage methods. +// +// Example: +// Instead of the following code: +// +// results.On("ByID", mock.AnythingOfType("flow.Identifier")).Return( +// func(resultID flow.Identifier) (*flow.ExecutionResult, error) { +// if result, ok := s.resultMap[resultID]; ok { +// return result, nil +// } +// return nil, storage.ErrNotFound +// }, +// ) +// +// Use this: +// +// results.On("ByID", mock.AnythingOfType("flow.Identifier")).Return( +// mocks.StorageMapGetter(s.resultMap), +// ) +func StorageMapGetter[K comparable, V any](m map[K]V) func(key K) (V, error) { + return func(key K) (V, error) { + if val, ok := m[key]; ok { + return val, nil + } + return *new(V), storage.ErrNotFound + } +} + +// ConvertStorageOutput maps the output type from a getter function to a different type. +// This is useful to avoid maintaining multiple maps for the same data. +// +// Example usage: +// +// blockMap := map[uint64]*flow.Block{} +// +// headers.On("BlockIDByHeight", mock.AnythingOfType("uint64")).Return( +// mocks.ConvertStorageOutput( +// mocks.StorageMapGetter(s.blockMap), +// func(block *flow.Block) flow.Identifier { return block.ID() }, +// ), +// ) +func ConvertStorageOutput[K comparable, V any, R any](fn func(key K) (V, error), mapper func(V) R) func(key K) (R, error) { + return func(key K) (R, error) { + v, err := fn(key) + if err != nil { + return *new(R), err + } + return mapper(v), err + } +} diff --git a/utils/unittest/service_events_fixtures.go b/utils/unittest/service_events_fixtures.go index d95148938ce..856b5405c87 100644 --- a/utils/unittest/service_events_fixtures.go +++ b/utils/unittest/service_events_fixtures.go @@ -1,30 +1,37 @@ package unittest import ( + "crypto/rand" + "encoding/hex" + "github.com/onflow/cadence" "github.com/onflow/cadence/encoding/ccf" "github.com/onflow/cadence/runtime/common" + "github.com/onflow/crypto" - "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/fvm/systemcontracts" "github.com/onflow/flow-go/model/flow" ) // This file contains service event fixtures for testing purposes. +func EpochSetupRandomSourceFixture() []byte { + source := make([]byte, flow.EpochSetupRandomSourceLength) + _, err := rand.Read(source) + if err != nil { + panic(err) + } + return source +} + // EpochSetupFixtureByChainID returns an EpochSetup service event as a Cadence event // representation and as a protocol model representation. func EpochSetupFixtureByChainID(chain flow.ChainID) (flow.Event, *flow.EpochSetup) { events := systemcontracts.ServiceEventsForChain(chain) event := EventFixture(events.EpochSetup.EventType(), 1, 1, IdentifierFixture(), 0) - event.Payload = EpochSetupFixtureCCF - - // randomSource is [0,0,...,1,2,3,4] - randomSource := make([]uint8, flow.EpochSetupRandomSourceLength) - for i := 0; i < 4; i++ { - randomSource[flow.EpochSetupRandomSourceLength-1-i] = uint8(4 - i) - } + randomSource := EpochSetupRandomSourceFixture() + event.Payload = EpochSetupFixtureCCF(randomSource) expected := &flow.EpochSetup{ Counter: 1, @@ -165,7 +172,8 @@ func VersionBeaconFixtureByChainID(chain flow.ChainID) (flow.Event, *flow.Versio return event, expected } -func createEpochSetupEvent() cadence.Event { +func createEpochSetupEvent(randomSource []byte) cadence.Event { + randomSourceHex := hex.EncodeToString(randomSource) return cadence.NewEvent([]cadence.Value{ // counter @@ -184,7 +192,7 @@ func createEpochSetupEvent() cadence.Event { createEpochCollectors(), // randomSource - cadence.String("01020304"), + cadence.String(randomSourceHex), // DKGPhase1FinalView cadence.UInt64(150), @@ -1019,8 +1027,8 @@ func ufix64FromString(s string) cadence.UFix64 { return f } -var EpochSetupFixtureCCF = func() []byte { - b, err := ccf.Encode(createEpochSetupEvent()) +func EpochSetupFixtureCCF(randomSource []byte) []byte { + b, err := ccf.Encode(createEpochSetupEvent(randomSource)) if err != nil { panic(err) } @@ -1029,7 +1037,54 @@ var EpochSetupFixtureCCF = func() []byte { panic(err) } return b -}() +} + +func EpochSetupCCFWithNonHexRandomSource() []byte { + // randomSource of correct length but made of non hex characters + randomSource := "ZZ" + for len(randomSource) != 2*flow.EpochSetupRandomSourceLength { + randomSource = randomSource + "aa" + } + + event := cadence.NewEvent([]cadence.Value{ + // counter + cadence.NewUInt64(1), + + // nodeInfo + createEpochNodes(), + + // firstView + cadence.NewUInt64(100), + + // finalView + cadence.NewUInt64(200), + + // collectorClusters + createEpochCollectors(), + + // randomSource + cadence.String(randomSource), + + // DKGPhase1FinalView + cadence.UInt64(150), + + // DKGPhase2FinalView + cadence.UInt64(160), + + // DKGPhase3FinalView + cadence.UInt64(170), + }).WithType(newFlowEpochEpochSetupEventType()) + + b, err := ccf.Encode(event) + if err != nil { + panic(err) + } + _, err = ccf.Decode(nil, b) + if err != nil { + panic(err) + } + return b +} var EpochCommitFixtureCCF = func() []byte { b, err := ccf.Encode(createEpochCommittedEvent()) diff --git a/utils/unittest/unittest.go b/utils/unittest/unittest.go index 9fba23ccd69..4d13b279087 100644 --- a/utils/unittest/unittest.go +++ b/utils/unittest/unittest.go @@ -17,10 +17,10 @@ import ( "github.com/cockroachdb/pebble" "github.com/dgraph-io/badger/v2" "github.com/libp2p/go-libp2p/core/peer" + "github.com/onflow/crypto" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/module" "github.com/onflow/flow-go/module/util"