diff --git a/.circleci/config.yml b/.circleci/config.yml index a4567113a6..7842cb7032 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -439,6 +439,7 @@ commands: - run: working_directory: /tmp command: | + sudo rm -rf ${HOME}/node_pkg/* sudo rm -rf << parameters.build_dir >> sudo mkdir -p << parameters.build_dir >> sudo chown -R $USER:$GROUP << parameters.build_dir >> diff --git a/.github/workflows/container.yml b/.github/workflows/container.yml new file mode 100644 index 0000000000..fa4b39c4cf --- /dev/null +++ b/.github/workflows/container.yml @@ -0,0 +1,57 @@ +name: container + +on: + workflow_dispatch: + push: + branches: + - master + - rel/* + - feature/* + tags: + - "*" + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v3 + + - name: Generate Container Metadata + id: meta + uses: docker/metadata-action@v4 + with: + images: | + docker.io/${{ github.repository_owner }}/algod + tags: | + type=sha,format=long,prefix= + type=ref,event=tag + type=ref,event=branch + type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'rel/stable') }} + + - name: Login to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Setup QEMU + uses: docker/setup-qemu-action@v2 + + - name: Setup Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Build and Push + uses: docker/build-push-action@v3 + with: + context: ./ + file: ./Dockerfile + push: true + tags: ${{ steps.meta.outputs.tags }} + platforms: linux/amd64,linux/arm64 + cache-from: type=gha + cache-to: type=gha,mode=max + build-args: | + SHA=${{ github.sha }} + URL=${{ github.server_url }}/${{ github.repository }}.git + BRANCH=${{ github.ref_name }} diff --git a/.github/workflows/reviewdog.yml b/.github/workflows/reviewdog.yml index d76ac49e80..ec1a859436 100644 --- a/.github/workflows/reviewdog.yml +++ b/.github/workflows/reviewdog.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out code into the Go module directory - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: fetch-depth: 0 # required for new-from-rev option in .golangci.yml - name: Install libraries @@ -33,7 +33,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out code into the Go module directory - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: fetch-depth: 0 # required for new-from-rev option in .golangci.yml - name: Install libraries @@ -46,14 +46,14 @@ jobs: echo "$GITHUB_WORKSPACE/bin" >> $GITHUB_PATH echo "$RUNNER_WORKSPACE/$(basename $GITHUB_REPOSITORY)/bin" >> $GITHUB_PATH - name: Install specific golang - uses: actions/setup-go@v2 + uses: actions/setup-go@v3 with: go-version: '1.17.13' - name: Create folders for golangci-lint run: mkdir -p cicdtmp/golangci-lint - name: Check if custom golangci-lint is already built id: cache-golangci-lint - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: cicdtmp/golangci-lint/golangci-lint-cgo key: cicd-golangci-lint-cgo-v0.0.1 @@ -69,7 +69,7 @@ jobs: cd ../../ - name: Install reviewdog run: | - curl -sfL https://raw.githubusercontent.com/reviewdog/reviewdog/v0.13.0/install.sh | sh -s + curl -sfL https://raw.githubusercontent.com/reviewdog/reviewdog/v0.14.1/install.sh | sh -s reviewdog --version - name: Build custom linters run: | @@ -80,21 +80,25 @@ jobs: - name: Run golangci-lint with reviewdog env: REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: > - ./cicdtmp/golangci-lint/golangci-lint-cgo run - --out-format line-number - -c .golangci-warnings.yml - --allow-parallel-runners - | reviewdog - -f=golangci-lint - -name="Lint Warnings" - -reporter=github-check - -filter-mode=added - -fail-on-error=false + run: | + set -e + + ./cicdtmp/golangci-lint/golangci-lint-cgo run \ + --out-format line-number \ + -c .golangci-warnings.yml \ + --issues-exit-code 0 \ + --allow-parallel-runners > temp_golangci-lint-cgo.txt + + cat temp_golangci-lint-cgo.txt | reviewdog \ + -f=golangci-lint \ + -name="Lint Warnings" \ + -reporter=github-check \ + -filter-mode=added \ + -fail-on-error=true \ -level=warning - name: Slack Notification env: SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} run: | curl -X POST --data-urlencode "payload={\"text\": \"Reviewdog failed. ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} \"}" $SLACK_WEBHOOK - if: ${{ failure() && (contains(github.ref_name, 'rel/nightly') || contains(github.ref_name, 'rel/beta') || contains(github.ref_name, 'rel/stable') || contains(github.ref_name, 'master')) }} \ No newline at end of file + if: ${{ failure() && (contains(github.ref_name, 'rel/nightly') || contains(github.ref_name, 'rel/beta') || contains(github.ref_name, 'rel/stable') || contains(github.ref_name, 'master')) }} diff --git a/.golangci-warnings.yml b/.golangci-warnings.yml index e3b8d22ff2..f8d2063475 100644 --- a/.golangci-warnings.yml +++ b/.golangci-warnings.yml @@ -9,10 +9,8 @@ linters: - partitiontest - structcheck - varcheck - - unconvert - unused - linters-settings: custom: partitiontest: @@ -55,7 +53,6 @@ issues: - deadcode - structcheck - varcheck - - unconvert - unused # Add all linters here -- Comment this block out for testing linters - path: test/linttest/lintissues\.go @@ -63,7 +60,6 @@ issues: - deadcode - structcheck - varcheck - - unconvert - unused - path: crypto/secp256k1/secp256_test\.go linters: diff --git a/.golangci.yml b/.golangci.yml index 0122edf822..30b4d28128 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -117,7 +117,61 @@ issues: - staticcheck - typecheck # Ignore missing parallel tests in existing packages - - path: (agreement|catchup|cmd|config|crypto|daemon|data|gen|ledger|logging|netdeploy|network|node|protocol|rpcs|shared|stateproof|test|tools|util).*_test.go + - path: agreement.*_test\.go + linters: + - paralleltest + - path: catchup.*_test\.go + linters: + - paralleltest + - path: cmd.*_test\.go + linters: + - paralleltest + - path: config.*_test\.go + linters: + - paralleltest + - path: crypto.*_test\.go + linters: + - paralleltest + - path: daemon.*_test\.go + linters: + - paralleltest + - path: data.*_test\.go + linters: + - paralleltest + - path: gen.*_test\.go + linters: + - paralleltest + - path: ledger.*_test\.go + linters: + - paralleltest + - path: logging.*_test\.go + linters: + - paralleltest + - path: netdeploy.*_test\.go + linters: + - paralleltest + - path: network.*_test\.go + linters: + - paralleltest + - path: node.*_test\.go + linters: + - paralleltest + - path: protocol.*_test\.go + linters: + - paralleltest + - path: rpcs.*_test\.go + linters: + - paralleltest + - path: stateproof.*_test\.go + linters: + - paralleltest + - path: test.*_test\.go + linters: + - paralleltest + - path: tools.*_test\.go + linters: + - paralleltest + - path: util.*_test\.go linters: - paralleltest # Add all linters here -- Comment this block out for testing linters diff --git a/Dockerfile b/Dockerfile index 56c0a8b79b..ab55a90940 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,75 +1,66 @@ -ARG GO_VERSION=1.17.5 -FROM golang:$GO_VERSION-bullseye as builder +FROM ubuntu:18.04 as builder -ARG CHANNEL=nightly -ARG URL= -ARG BRANCH= -ARG SHA= +ARG GO_VERSION="1.17.13" + +ARG CHANNEL +ARG URL +ARG BRANCH +ARG SHA +ARG TARGETARCH + +ADD https://go.dev/dl/go${GO_VERSION}.linux-${TARGETARCH}.tar.gz /go.tar.gz # Basic dependencies. -ENV HOME /node -ENV DEBIAN_FRONTEND noninteractive +ENV HOME="/node" DEBIAN_FRONTEND="noninteractive" GOPATH="/dist" + RUN apt-get update && \ - apt-get install -y \ + apt-get install -y --no-install-recommends \ + ca-certificates \ apt-utils \ bsdmainutils \ curl \ git \ - git-core \ - python3 + && rm -rf /var/lib/apt/lists/* && \ + \ + tar -C /usr/local -xzf /go.tar.gz && \ + rm -rf /go.tar.gz -COPY ./docker/files/ /node/files -COPY ./installer/genesis /node/files/run/genesis -COPY ./cmd/updater/update.sh /node/files/build/update.sh -COPY ./installer/config.json.example /node/files/build/config.json +ENV PATH="/usr/local/go/bin:${PATH}" -RUN find /node/files +COPY ./docker/files/ /dist/files +COPY ./installer/genesis /dist/files/run/genesis +COPY ./cmd/updater/update.sh /dist/files/build/update.sh +COPY ./installer/config.json.example /dist/files/run/config.json.example # Install algod binaries. -RUN /node/files/build/install.sh \ - -p "/node/bin" \ - -d "/node/data" \ +RUN /dist/files/build/install.sh \ + -p "${GOPATH}/bin" \ + -d "/algod/data" \ -c "${CHANNEL}" \ -u "${URL}" \ -b "${BRANCH}" \ -s "${SHA}" -# Copy binaries into a clean image -# TODO: We don't need most of the binaries. -# Should we delete everything except goal/algod/algocfg/tealdbg? FROM debian:bullseye-slim as final -COPY --from=builder "/node/bin/" "/node/bin" -COPY --from=builder "/node/data/" "/node/dataTemplate" -COPY --from=builder "/node/files/run" "/node/run" -ENV BIN_DIR="/node/bin" -ENV PATH="$BIN_DIR:${PATH}" -ENV ALGOD_PORT=8080 -ENV ALGORAND_DATA="/algod/data" -RUN mkdir -p "$ALGORAND_DATA" -WORKDIR /node/data +ENV PATH="/node/bin:${PATH}" ALGOD_PORT="8080" ALGORAND_DATA="/algod/data" # curl is needed to lookup the fast catchup url -RUN apt-get update && apt-get install -y \ - curl \ - && rm -rf /var/lib/apt/lists/* +RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates curl && \ + rm -rf /var/lib/apt/lists/* && \ + mkdir -p "$ALGORAND_DATA" && \ + groupadd --system algorand && \ + useradd --no-log-init --create-home --system --gid algorand algorand && \ + chown -R algorand:algorand /algod -# TODO: This works fine, but causes problems when mounting a volume -# Use algorand user instead of root -#RUN groupadd -r algorand && \ -# useradd --no-log-init -r -g algorand algorand && \ -# chown -R algorand.algorand /node && \ -# chown -R algorand.algorand /algod -#USER algorand +USER algorand -# Algod REST API -EXPOSE $ALGOD_PORT +COPY --chown=algorand:algorand --from=builder "/dist/bin/" "/node/bin/" +COPY --chown=algorand:algorand --from=builder "/dist/files/run/" "/node/run/" -# Algod Gossip Port -EXPOSE 4160 +# Expose Algod REST API, Algod Gossip, and Prometheus Metrics ports +EXPOSE $ALGOD_PORT 4160 9100 -# Prometheus Metrics -EXPOSE 9100 +WORKDIR /algod CMD ["/node/run/run.sh"] -#CMD ["/bin/bash"] diff --git a/Makefile b/Makefile index 52c955b38f..a418c7e35e 100644 --- a/Makefile +++ b/Makefile @@ -85,7 +85,7 @@ GOLDFLAGS := $(GOLDFLAGS_BASE) \ UNIT_TEST_SOURCES := $(sort $(shell GOPATH=$(GOPATH) && GO111MODULE=off && go list ./... | grep -v /go-algorand/test/ )) ALGOD_API_PACKAGES := $(sort $(shell GOPATH=$(GOPATH) && GO111MODULE=off && cd daemon/algod/api; go list ./... )) -MSGP_GENERATE := ./protocol ./protocol/test ./crypto ./crypto/merklearray ./crypto/merklesignature ./crypto/stateproof ./data/basics ./data/transactions ./data/stateproofmsg ./data/committee ./data/bookkeeping ./data/hashable ./agreement ./rpcs ./node ./ledger ./ledger/ledgercore ./ledger/store ./stateproof ./data/account ./daemon/algod/api/spec/v2 +MSGP_GENERATE := ./protocol ./protocol/test ./crypto ./crypto/merklearray ./crypto/merklesignature ./crypto/stateproof ./data/basics ./data/transactions ./data/stateproofmsg ./data/committee ./data/bookkeeping ./data/hashable ./agreement ./rpcs ./node ./ledger ./ledger/ledgercore ./ledger/store ./ledger/encoded ./stateproof ./data/account ./daemon/algod/api/spec/v2 default: build diff --git a/README.md b/README.md index a935722f71..9e787d6d4b 100644 --- a/README.md +++ b/README.md @@ -192,6 +192,6 @@ A number of packages provide utilities for the various components: Please see the [COPYING_FAQ](COPYING_FAQ) for details about how to apply our license. -Copyright (C) 2019-2022, Algorand Inc. +Copyright (C) 2019-2023, Algorand Inc. [developer site url]: https://developer.algorand.org/ diff --git a/agreement/abstractions.go b/agreement/abstractions.go index cf58aa3725..f5d09dc1ef 100644 --- a/agreement/abstractions.go +++ b/agreement/abstractions.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/actions.go b/agreement/actions.go index ca33c18db1..833dc8af36 100644 --- a/agreement/actions.go +++ b/agreement/actions.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/actor.go b/agreement/actor.go index 96106eb138..e02c5eb4c2 100644 --- a/agreement/actor.go +++ b/agreement/actor.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/agreeInstall.go b/agreement/agreeInstall.go index 1028eedb99..28b1985b22 100644 --- a/agreement/agreeInstall.go +++ b/agreement/agreeInstall.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/agreementtest/keyManager.go b/agreement/agreementtest/keyManager.go index fe86fad82a..6222cffb6e 100644 --- a/agreement/agreementtest/keyManager.go +++ b/agreement/agreementtest/keyManager.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/agreementtest/simulate.go b/agreement/agreementtest/simulate.go index cafc4e5f3e..225d0632c8 100644 --- a/agreement/agreementtest/simulate.go +++ b/agreement/agreementtest/simulate.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -170,7 +170,10 @@ func Simulate(dbname string, n basics.Round, roundDeadline time.Duration, ledger } _ = accessor - service := agreement.MakeService(parameters) + service, err := agreement.MakeService(parameters) + if err != nil { + return err + } service.Start() defer service.Shutdown() defer stopwatch.shutdown() diff --git a/agreement/agreementtest/simulate_test.go b/agreement/agreementtest/simulate_test.go index 173f91fad8..6d8b2b43b6 100644 --- a/agreement/agreementtest/simulate_test.go +++ b/agreement/agreementtest/simulate_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -33,6 +33,7 @@ import ( "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/account" "github.com/algorand/go-algorand/data/basics" + basics_testing "github.com/algorand/go-algorand/data/basics/testing" "github.com/algorand/go-algorand/data/bookkeeping" "github.com/algorand/go-algorand/data/committee" "github.com/algorand/go-algorand/logging" @@ -310,7 +311,7 @@ func TestSimulate(t *testing.T) { // generate accounts genesis := make(map[basics.Address]basics.AccountData) incentivePoolAtStart := uint64(1000 * 1000) - accData := basics.MakeAccountData(basics.NotParticipating, basics.MicroAlgos{Raw: incentivePoolAtStart}) + accData := basics_testing.MakeAccountData(basics.NotParticipating, basics.MicroAlgos{Raw: incentivePoolAtStart}) genesis[poolAddr] = accData gen := rand.New(rand.NewSource(2)) diff --git a/agreement/asyncVoteVerifier.go b/agreement/asyncVoteVerifier.go index 877c92dfb2..e30c9c5b1a 100644 --- a/agreement/asyncVoteVerifier.go +++ b/agreement/asyncVoteVerifier.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/asyncVoteVerifier_test.go b/agreement/asyncVoteVerifier_test.go index 8a4e819595..e344d10b70 100644 --- a/agreement/asyncVoteVerifier_test.go +++ b/agreement/asyncVoteVerifier_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -34,6 +34,9 @@ func (fp *expiredExecPool) EnqueueBacklog(enqueueCtx context.Context, t execpool // generate an error, to see if we correctly report that on the verifyVote() call. return context.Canceled } +func (fp *expiredExecPool) BufferSize() (length, capacity int) { + return +} // Test async vote verifier against a full execution pool. func TestVerificationAgainstFullExecutionPool(t *testing.T) { diff --git a/agreement/autopsy.go b/agreement/autopsy.go index 2b344f0a66..e940ae4bc9 100644 --- a/agreement/autopsy.go +++ b/agreement/autopsy.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/bundle.go b/agreement/bundle.go index de297110ef..a7670b53db 100644 --- a/agreement/bundle.go +++ b/agreement/bundle.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/bundle_test.go b/agreement/bundle_test.go index 615b2c5081..4542b0cd7f 100644 --- a/agreement/bundle_test.go +++ b/agreement/bundle_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/cadaver.go b/agreement/cadaver.go index d3f626adaf..3b5c8a78cd 100644 --- a/agreement/cadaver.go +++ b/agreement/cadaver.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -48,6 +48,7 @@ type cadaver struct { overrideSetup bool // if true, do not execute code in trySetup baseFilename string // no logging happens if this is "" + baseDirectory string // if empty, will be data directory fileSizeTarget int64 out *cadaverHandle @@ -60,11 +61,14 @@ type cadaver struct { } func (c *cadaver) filename() string { - // Put cadaver files in our data directory - p := config.GetCurrentVersion().DataDirectory + baseDir := c.baseDirectory + if baseDir == "" { + // Put cadaver files in our data directory + baseDir = config.GetCurrentVersion().DataDirectory + } fmtstr := "%s.cdv" - return filepath.Join(p, fmt.Sprintf(fmtstr, c.baseFilename)) + return filepath.Join(baseDir, fmt.Sprintf(fmtstr, c.baseFilename)) } func (c *cadaver) init() (err error) { @@ -115,6 +119,7 @@ func (c *cadaver) trySetup() bool { if c.out == nil { err := c.init() if err != nil { + logging.Base().Warn(err) c.failed = err return false } diff --git a/agreement/certificate.go b/agreement/certificate.go index 34c4192d8b..6ce4b9e0a4 100644 --- a/agreement/certificate.go +++ b/agreement/certificate.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/certificate_test.go b/agreement/certificate_test.go index 4881ac2443..9ae2ebfa2e 100644 --- a/agreement/certificate_test.go +++ b/agreement/certificate_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/common_test.go b/agreement/common_test.go index fa43d10912..361ab2e2ad 100644 --- a/agreement/common_test.go +++ b/agreement/common_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/coservice.go b/agreement/coservice.go index 0126cb85d3..7fe4ab1f52 100644 --- a/agreement/coservice.go +++ b/agreement/coservice.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/cryptoRequestContext.go b/agreement/cryptoRequestContext.go index 88530e3570..8807e3f57f 100644 --- a/agreement/cryptoRequestContext.go +++ b/agreement/cryptoRequestContext.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/cryptoRequestContext_test.go b/agreement/cryptoRequestContext_test.go index d41533f2a2..44b279aeed 100644 --- a/agreement/cryptoRequestContext_test.go +++ b/agreement/cryptoRequestContext_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/cryptoVerifier.go b/agreement/cryptoVerifier.go index ca4bceb666..70609a634f 100644 --- a/agreement/cryptoVerifier.go +++ b/agreement/cryptoVerifier.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/cryptoVerifier_test.go b/agreement/cryptoVerifier_test.go index 21b78c601e..880702b8da 100644 --- a/agreement/cryptoVerifier_test.go +++ b/agreement/cryptoVerifier_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/demux.go b/agreement/demux.go index ad51038b4e..8d96444d5d 100644 --- a/agreement/demux.go +++ b/agreement/demux.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/demux_test.go b/agreement/demux_test.go index 7d7bbc71bd..692d2cc5f9 100644 --- a/agreement/demux_test.go +++ b/agreement/demux_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/doc.go b/agreement/doc.go index ffbc79fa0e..0d4ae97af0 100644 --- a/agreement/doc.go +++ b/agreement/doc.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/encoding_test.go b/agreement/encoding_test.go index 9e899ca5c1..7554ec4467 100644 --- a/agreement/encoding_test.go +++ b/agreement/encoding_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/errors.go b/agreement/errors.go index eae272b9ff..e0929b63e4 100644 --- a/agreement/errors.go +++ b/agreement/errors.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/events.go b/agreement/events.go index f418cc38f4..5304b1798f 100644 --- a/agreement/events.go +++ b/agreement/events.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -947,6 +947,6 @@ func (e messageEvent) AttachValidatedAt(d time.Duration) messageEvent { } func (e messageEvent) AttachReceivedAt(d time.Duration) messageEvent { - e.Input.Proposal.receivedAt = d + e.Input.UnauthenticatedProposal.receivedAt = d return e } diff --git a/agreement/events_test.go b/agreement/events_test.go index 243dd05089..c558f0067b 100644 --- a/agreement/events_test.go +++ b/agreement/events_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -21,6 +21,7 @@ import ( "testing" "github.com/algorand/go-algorand/protocol" + "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/require" ) @@ -28,6 +29,7 @@ import ( // properly decoded from ConsensusVersionView. // This test is only needed for agreement state serialization switch from reflection to msgp. func TestSerializableErrorBackwardCompatibility(t *testing.T) { + partitiontest.PartitionTest(t) encodedEmpty, err := base64.StdEncoding.DecodeString("gqNFcnLAp1ZlcnNpb26jdjEw") require.NoError(t, err) diff --git a/agreement/fuzzer/bandwidthFilter_test.go b/agreement/fuzzer/bandwidthFilter_test.go index e5a31bf2e0..93b958f187 100644 --- a/agreement/fuzzer/bandwidthFilter_test.go +++ b/agreement/fuzzer/bandwidthFilter_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/fuzzer/catchupFilter_test.go b/agreement/fuzzer/catchupFilter_test.go index 64493d8402..2ef23d44b2 100644 --- a/agreement/fuzzer/catchupFilter_test.go +++ b/agreement/fuzzer/catchupFilter_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/fuzzer/clockedFilter_test.go b/agreement/fuzzer/clockedFilter_test.go index ceab6560a6..ee4884acd4 100644 --- a/agreement/fuzzer/clockedFilter_test.go +++ b/agreement/fuzzer/clockedFilter_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/fuzzer/dropMessageFilter_test.go b/agreement/fuzzer/dropMessageFilter_test.go index 519782c77c..976a1d4482 100644 --- a/agreement/fuzzer/dropMessageFilter_test.go +++ b/agreement/fuzzer/dropMessageFilter_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/fuzzer/duplicateMessageFilter_test.go b/agreement/fuzzer/duplicateMessageFilter_test.go index 6d0e25f909..80a4f9b45b 100644 --- a/agreement/fuzzer/duplicateMessageFilter_test.go +++ b/agreement/fuzzer/duplicateMessageFilter_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/fuzzer/filter_test.go b/agreement/fuzzer/filter_test.go index ede2d907e1..b4b0b7972d 100644 --- a/agreement/fuzzer/filter_test.go +++ b/agreement/fuzzer/filter_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/fuzzer/fuzzer.go b/agreement/fuzzer/fuzzer.go index 3c404015b9..847810f455 100644 --- a/agreement/fuzzer/fuzzer.go +++ b/agreement/fuzzer/fuzzer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/fuzzer/fuzzer_test.go b/agreement/fuzzer/fuzzer_test.go index e5437f8bc4..332b2fb87b 100644 --- a/agreement/fuzzer/fuzzer_test.go +++ b/agreement/fuzzer/fuzzer_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -150,7 +150,10 @@ func (n *Fuzzer) initAgreementNode(nodeID int, filters ...NetworkFilterFactory) cadaverFilename = "" } - n.agreements[nodeID] = agreement.MakeService(n.agreementParams[nodeID]) + n.agreements[nodeID], err = agreement.MakeService(n.agreementParams[nodeID]) + if err != nil { + return false + } n.agreements[nodeID].SetTracerFilename(cadaverFilename) @@ -578,7 +581,11 @@ func (n *Fuzzer) CrashNode(nodeID int) { n.ledgers[nodeID].ClearNotifications() n.agreementParams[nodeID].Network = gossip.WrapNetwork(n.facades[nodeID], n.log, config.GetDefaultLocal()) - n.agreements[nodeID] = agreement.MakeService(n.agreementParams[nodeID]) + var err error + n.agreements[nodeID], err = agreement.MakeService(n.agreementParams[nodeID]) + if err != nil { + panic(err) + } cadaverFilename := fmt.Sprintf("%v-%v", n.networkName, nodeID) if n.disableTraces == true { diff --git a/agreement/fuzzer/ledger_test.go b/agreement/fuzzer/ledger_test.go index 2ae73dd694..c0ffb8b531 100644 --- a/agreement/fuzzer/ledger_test.go +++ b/agreement/fuzzer/ledger_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/fuzzer/messageDecoderFilter_test.go b/agreement/fuzzer/messageDecoderFilter_test.go index cb80ed990b..03b0af15dd 100644 --- a/agreement/fuzzer/messageDecoderFilter_test.go +++ b/agreement/fuzzer/messageDecoderFilter_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/fuzzer/messageDelayFilter_test.go b/agreement/fuzzer/messageDelayFilter_test.go index 04cf55081f..02636b50a9 100644 --- a/agreement/fuzzer/messageDelayFilter_test.go +++ b/agreement/fuzzer/messageDelayFilter_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/fuzzer/messageDuplicationFilter_test.go b/agreement/fuzzer/messageDuplicationFilter_test.go index 747301a11f..4a7d4e88c9 100644 --- a/agreement/fuzzer/messageDuplicationFilter_test.go +++ b/agreement/fuzzer/messageDuplicationFilter_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/fuzzer/messagePriorityQueue_test.go b/agreement/fuzzer/messagePriorityQueue_test.go index bbb9f13647..8638cb48b4 100644 --- a/agreement/fuzzer/messagePriorityQueue_test.go +++ b/agreement/fuzzer/messagePriorityQueue_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/fuzzer/messageReflectionFilter_test.go b/agreement/fuzzer/messageReflectionFilter_test.go index c1b58ef27f..73aeae5b1c 100644 --- a/agreement/fuzzer/messageReflectionFilter_test.go +++ b/agreement/fuzzer/messageReflectionFilter_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/fuzzer/messageRegossipFilter_test.go b/agreement/fuzzer/messageRegossipFilter_test.go index 628db283a4..264de6e647 100644 --- a/agreement/fuzzer/messageRegossipFilter_test.go +++ b/agreement/fuzzer/messageRegossipFilter_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/fuzzer/messageReorderingFilter_test.go b/agreement/fuzzer/messageReorderingFilter_test.go index 79680ab885..23049deaee 100644 --- a/agreement/fuzzer/messageReorderingFilter_test.go +++ b/agreement/fuzzer/messageReorderingFilter_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/fuzzer/networkFacade_test.go b/agreement/fuzzer/networkFacade_test.go index 5c7018ef03..d00ec4e7c1 100644 --- a/agreement/fuzzer/networkFacade_test.go +++ b/agreement/fuzzer/networkFacade_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/fuzzer/nodeCrashFilter_test.go b/agreement/fuzzer/nodeCrashFilter_test.go index a15930b94d..ef31ca4d88 100644 --- a/agreement/fuzzer/nodeCrashFilter_test.go +++ b/agreement/fuzzer/nodeCrashFilter_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/fuzzer/nullFilter_test.go b/agreement/fuzzer/nullFilter_test.go index 0cc7300c34..e6ee528e17 100644 --- a/agreement/fuzzer/nullFilter_test.go +++ b/agreement/fuzzer/nullFilter_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/fuzzer/router_test.go b/agreement/fuzzer/router_test.go index f63bb389ef..74e39c1ec1 100644 --- a/agreement/fuzzer/router_test.go +++ b/agreement/fuzzer/router_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/fuzzer/schedulerFilter_test.go b/agreement/fuzzer/schedulerFilter_test.go index dd48aa5339..d6e00e7734 100644 --- a/agreement/fuzzer/schedulerFilter_test.go +++ b/agreement/fuzzer/schedulerFilter_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/fuzzer/tests_test.go b/agreement/fuzzer/tests_test.go index 2dfc837072..fa48979a3f 100644 --- a/agreement/fuzzer/tests_test.go +++ b/agreement/fuzzer/tests_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/fuzzer/topologyFilter_test.go b/agreement/fuzzer/topologyFilter_test.go index 841e9eb041..db62ff6045 100644 --- a/agreement/fuzzer/topologyFilter_test.go +++ b/agreement/fuzzer/topologyFilter_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/fuzzer/trafficStatisticsFilter_test.go b/agreement/fuzzer/trafficStatisticsFilter_test.go index 802583c4d8..72cad0fc67 100644 --- a/agreement/fuzzer/trafficStatisticsFilter_test.go +++ b/agreement/fuzzer/trafficStatisticsFilter_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/fuzzer/validator_test.go b/agreement/fuzzer/validator_test.go index 95a198e5ce..87425a0997 100644 --- a/agreement/fuzzer/validator_test.go +++ b/agreement/fuzzer/validator_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/fuzzer/voteFilter_test.go b/agreement/fuzzer/voteFilter_test.go index e99a19b319..655708758a 100644 --- a/agreement/fuzzer/voteFilter_test.go +++ b/agreement/fuzzer/voteFilter_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/gossip/network.go b/agreement/gossip/network.go index 43eefd2db5..3341882772 100644 --- a/agreement/gossip/network.go +++ b/agreement/gossip/network.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/gossip/networkFull_test.go b/agreement/gossip/networkFull_test.go index ae5088d2d1..784baa8c00 100644 --- a/agreement/gossip/networkFull_test.go +++ b/agreement/gossip/networkFull_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/gossip/network_test.go b/agreement/gossip/network_test.go index 063fe84344..bd4f4baf38 100644 --- a/agreement/gossip/network_test.go +++ b/agreement/gossip/network_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/keyManager_test.go b/agreement/keyManager_test.go index 43ab187698..106431c91f 100644 --- a/agreement/keyManager_test.go +++ b/agreement/keyManager_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/listener.go b/agreement/listener.go index b0dafaff71..22ec285854 100644 --- a/agreement/listener.go +++ b/agreement/listener.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/message.go b/agreement/message.go index a1f6a8c806..d7d52bc8ab 100644 --- a/agreement/message.go +++ b/agreement/message.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/message_test.go b/agreement/message_test.go index 76209a5f91..3c3ab77f0b 100644 --- a/agreement/message_test.go +++ b/agreement/message_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/msgp_gen.go b/agreement/msgp_gen.go index 3c396226b8..2fa1e11a0a 100644 --- a/agreement/msgp_gen.go +++ b/agreement/msgp_gen.go @@ -3791,7 +3791,7 @@ func (z *proposal) MarshalMsg(b []byte) (o []byte) { o = msgp.Require(b, z.Msgsize()) // omitempty: check for empty values zb0004Len := uint32(29) - var zb0004Mask uint64 /* 38 bits */ + var zb0004Mask uint64 /* 39 bits */ if (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsLevel == 0 { zb0004Len-- zb0004Mask |= 0x40 @@ -3854,59 +3854,59 @@ func (z *proposal) MarshalMsg(b []byte) (o []byte) { } if (*z).unauthenticatedProposal.Block.BlockHeader.Round.MsgIsZero() { zb0004Len-- - zb0004Mask |= 0x400000 + zb0004Mask |= 0x800000 } if (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsRecalculationRound.MsgIsZero() { zb0004Len-- - zb0004Mask |= 0x800000 + zb0004Mask |= 0x1000000 } if (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsPool.MsgIsZero() { zb0004Len-- - zb0004Mask |= 0x1000000 + zb0004Mask |= 0x2000000 } if (*z).unauthenticatedProposal.SeedProof.MsgIsZero() { zb0004Len-- - zb0004Mask |= 0x2000000 + zb0004Mask |= 0x4000000 } if (*z).unauthenticatedProposal.Block.BlockHeader.Seed.MsgIsZero() { zb0004Len-- - zb0004Mask |= 0x4000000 + zb0004Mask |= 0x8000000 } if len((*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking) == 0 { zb0004Len-- - zb0004Mask |= 0x8000000 + zb0004Mask |= 0x10000000 } if (*z).unauthenticatedProposal.Block.BlockHeader.TxnCounter == 0 { zb0004Len-- - zb0004Mask |= 0x10000000 + zb0004Mask |= 0x20000000 } if (*z).unauthenticatedProposal.Block.BlockHeader.TimeStamp == 0 { zb0004Len-- - zb0004Mask |= 0x20000000 + zb0004Mask |= 0x40000000 } if (*z).unauthenticatedProposal.Block.BlockHeader.TxnCommitments.NativeSha512_256Commitment.MsgIsZero() { zb0004Len-- - zb0004Mask |= 0x40000000 + zb0004Mask |= 0x80000000 } if (*z).unauthenticatedProposal.Block.BlockHeader.TxnCommitments.Sha256Commitment.MsgIsZero() { zb0004Len-- - zb0004Mask |= 0x80000000 + zb0004Mask |= 0x100000000 } if (*z).unauthenticatedProposal.Block.Payset.MsgIsZero() { zb0004Len-- - zb0004Mask |= 0x100000000 + zb0004Mask |= 0x200000000 } if (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradeDelay.MsgIsZero() { zb0004Len-- - zb0004Mask |= 0x200000000 + zb0004Mask |= 0x400000000 } if (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradePropose.MsgIsZero() { zb0004Len-- - zb0004Mask |= 0x400000000 + zb0004Mask |= 0x800000000 } if (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradeApprove == false { zb0004Len-- - zb0004Mask |= 0x800000000 + zb0004Mask |= 0x1000000000 } // variable map header, size zb0004Len o = msgp.AppendMapHeader(o, zb0004Len) @@ -3993,32 +3993,32 @@ func (z *proposal) MarshalMsg(b []byte) (o []byte) { o = append(o, 0xa4, 0x72, 0x61, 0x74, 0x65) o = msgp.AppendUint64(o, (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsRate) } - if (zb0004Mask & 0x400000) == 0 { // if not empty + if (zb0004Mask & 0x800000) == 0 { // if not empty // string "rnd" o = append(o, 0xa3, 0x72, 0x6e, 0x64) o = (*z).unauthenticatedProposal.Block.BlockHeader.Round.MarshalMsg(o) } - if (zb0004Mask & 0x800000) == 0 { // if not empty + if (zb0004Mask & 0x1000000) == 0 { // if not empty // string "rwcalr" o = append(o, 0xa6, 0x72, 0x77, 0x63, 0x61, 0x6c, 0x72) o = (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsRecalculationRound.MarshalMsg(o) } - if (zb0004Mask & 0x1000000) == 0 { // if not empty + if (zb0004Mask & 0x2000000) == 0 { // if not empty // string "rwd" o = append(o, 0xa3, 0x72, 0x77, 0x64) o = (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsPool.MarshalMsg(o) } - if (zb0004Mask & 0x2000000) == 0 { // if not empty + if (zb0004Mask & 0x4000000) == 0 { // if not empty // string "sdpf" o = append(o, 0xa4, 0x73, 0x64, 0x70, 0x66) o = (*z).unauthenticatedProposal.SeedProof.MarshalMsg(o) } - if (zb0004Mask & 0x4000000) == 0 { // if not empty + if (zb0004Mask & 0x8000000) == 0 { // if not empty // string "seed" o = append(o, 0xa4, 0x73, 0x65, 0x65, 0x64) o = (*z).unauthenticatedProposal.Block.BlockHeader.Seed.MarshalMsg(o) } - if (zb0004Mask & 0x8000000) == 0 { // if not empty + if (zb0004Mask & 0x10000000) == 0 { // if not empty // string "spt" o = append(o, 0xa3, 0x73, 0x70, 0x74) if (*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking == nil { @@ -4038,42 +4038,42 @@ func (z *proposal) MarshalMsg(b []byte) (o []byte) { o = zb0002.MarshalMsg(o) } } - if (zb0004Mask & 0x10000000) == 0 { // if not empty + if (zb0004Mask & 0x20000000) == 0 { // if not empty // string "tc" o = append(o, 0xa2, 0x74, 0x63) o = msgp.AppendUint64(o, (*z).unauthenticatedProposal.Block.BlockHeader.TxnCounter) } - if (zb0004Mask & 0x20000000) == 0 { // if not empty + if (zb0004Mask & 0x40000000) == 0 { // if not empty // string "ts" o = append(o, 0xa2, 0x74, 0x73) o = msgp.AppendInt64(o, (*z).unauthenticatedProposal.Block.BlockHeader.TimeStamp) } - if (zb0004Mask & 0x40000000) == 0 { // if not empty + if (zb0004Mask & 0x80000000) == 0 { // if not empty // string "txn" o = append(o, 0xa3, 0x74, 0x78, 0x6e) o = (*z).unauthenticatedProposal.Block.BlockHeader.TxnCommitments.NativeSha512_256Commitment.MarshalMsg(o) } - if (zb0004Mask & 0x80000000) == 0 { // if not empty + if (zb0004Mask & 0x100000000) == 0 { // if not empty // string "txn256" o = append(o, 0xa6, 0x74, 0x78, 0x6e, 0x32, 0x35, 0x36) o = (*z).unauthenticatedProposal.Block.BlockHeader.TxnCommitments.Sha256Commitment.MarshalMsg(o) } - if (zb0004Mask & 0x100000000) == 0 { // if not empty + if (zb0004Mask & 0x200000000) == 0 { // if not empty // string "txns" o = append(o, 0xa4, 0x74, 0x78, 0x6e, 0x73) o = (*z).unauthenticatedProposal.Block.Payset.MarshalMsg(o) } - if (zb0004Mask & 0x200000000) == 0 { // if not empty + if (zb0004Mask & 0x400000000) == 0 { // if not empty // string "upgradedelay" o = append(o, 0xac, 0x75, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x64, 0x65, 0x6c, 0x61, 0x79) o = (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradeDelay.MarshalMsg(o) } - if (zb0004Mask & 0x400000000) == 0 { // if not empty + if (zb0004Mask & 0x800000000) == 0 { // if not empty // string "upgradeprop" o = append(o, 0xab, 0x75, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x70, 0x72, 0x6f, 0x70) o = (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradePropose.MarshalMsg(o) } - if (zb0004Mask & 0x800000000) == 0 { // if not empty + if (zb0004Mask & 0x1000000000) == 0 { // if not empty // string "upgradeyes" o = append(o, 0xaa, 0x75, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x79, 0x65, 0x73) o = msgp.AppendBool(o, (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradeApprove) @@ -7839,7 +7839,7 @@ func (z *transmittedPayload) MarshalMsg(b []byte) (o []byte) { o = msgp.Require(b, z.Msgsize()) // omitempty: check for empty values zb0004Len := uint32(30) - var zb0004Mask uint64 /* 37 bits */ + var zb0004Mask uint64 /* 38 bits */ if (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsLevel == 0 { zb0004Len-- zb0004Mask |= 0x80 @@ -7906,59 +7906,59 @@ func (z *transmittedPayload) MarshalMsg(b []byte) (o []byte) { } if (*z).unauthenticatedProposal.Block.BlockHeader.Round.MsgIsZero() { zb0004Len-- - zb0004Mask |= 0x800000 + zb0004Mask |= 0x1000000 } if (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsRecalculationRound.MsgIsZero() { zb0004Len-- - zb0004Mask |= 0x1000000 + zb0004Mask |= 0x2000000 } if (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsPool.MsgIsZero() { zb0004Len-- - zb0004Mask |= 0x2000000 + zb0004Mask |= 0x4000000 } if (*z).unauthenticatedProposal.SeedProof.MsgIsZero() { zb0004Len-- - zb0004Mask |= 0x4000000 + zb0004Mask |= 0x8000000 } if (*z).unauthenticatedProposal.Block.BlockHeader.Seed.MsgIsZero() { zb0004Len-- - zb0004Mask |= 0x8000000 + zb0004Mask |= 0x10000000 } if len((*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking) == 0 { zb0004Len-- - zb0004Mask |= 0x10000000 + zb0004Mask |= 0x20000000 } if (*z).unauthenticatedProposal.Block.BlockHeader.TxnCounter == 0 { zb0004Len-- - zb0004Mask |= 0x20000000 + zb0004Mask |= 0x40000000 } if (*z).unauthenticatedProposal.Block.BlockHeader.TimeStamp == 0 { zb0004Len-- - zb0004Mask |= 0x40000000 + zb0004Mask |= 0x80000000 } if (*z).unauthenticatedProposal.Block.BlockHeader.TxnCommitments.NativeSha512_256Commitment.MsgIsZero() { zb0004Len-- - zb0004Mask |= 0x80000000 + zb0004Mask |= 0x100000000 } if (*z).unauthenticatedProposal.Block.BlockHeader.TxnCommitments.Sha256Commitment.MsgIsZero() { zb0004Len-- - zb0004Mask |= 0x100000000 + zb0004Mask |= 0x200000000 } if (*z).unauthenticatedProposal.Block.Payset.MsgIsZero() { zb0004Len-- - zb0004Mask |= 0x200000000 + zb0004Mask |= 0x400000000 } if (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradeDelay.MsgIsZero() { zb0004Len-- - zb0004Mask |= 0x400000000 + zb0004Mask |= 0x800000000 } if (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradePropose.MsgIsZero() { zb0004Len-- - zb0004Mask |= 0x800000000 + zb0004Mask |= 0x1000000000 } if (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradeApprove == false { zb0004Len-- - zb0004Mask |= 0x1000000000 + zb0004Mask |= 0x2000000000 } // variable map header, size zb0004Len o = msgp.AppendMapHeader(o, zb0004Len) @@ -8050,32 +8050,32 @@ func (z *transmittedPayload) MarshalMsg(b []byte) (o []byte) { o = append(o, 0xa4, 0x72, 0x61, 0x74, 0x65) o = msgp.AppendUint64(o, (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsRate) } - if (zb0004Mask & 0x800000) == 0 { // if not empty + if (zb0004Mask & 0x1000000) == 0 { // if not empty // string "rnd" o = append(o, 0xa3, 0x72, 0x6e, 0x64) o = (*z).unauthenticatedProposal.Block.BlockHeader.Round.MarshalMsg(o) } - if (zb0004Mask & 0x1000000) == 0 { // if not empty + if (zb0004Mask & 0x2000000) == 0 { // if not empty // string "rwcalr" o = append(o, 0xa6, 0x72, 0x77, 0x63, 0x61, 0x6c, 0x72) o = (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsRecalculationRound.MarshalMsg(o) } - if (zb0004Mask & 0x2000000) == 0 { // if not empty + if (zb0004Mask & 0x4000000) == 0 { // if not empty // string "rwd" o = append(o, 0xa3, 0x72, 0x77, 0x64) o = (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsPool.MarshalMsg(o) } - if (zb0004Mask & 0x4000000) == 0 { // if not empty + if (zb0004Mask & 0x8000000) == 0 { // if not empty // string "sdpf" o = append(o, 0xa4, 0x73, 0x64, 0x70, 0x66) o = (*z).unauthenticatedProposal.SeedProof.MarshalMsg(o) } - if (zb0004Mask & 0x8000000) == 0 { // if not empty + if (zb0004Mask & 0x10000000) == 0 { // if not empty // string "seed" o = append(o, 0xa4, 0x73, 0x65, 0x65, 0x64) o = (*z).unauthenticatedProposal.Block.BlockHeader.Seed.MarshalMsg(o) } - if (zb0004Mask & 0x10000000) == 0 { // if not empty + if (zb0004Mask & 0x20000000) == 0 { // if not empty // string "spt" o = append(o, 0xa3, 0x73, 0x70, 0x74) if (*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking == nil { @@ -8095,42 +8095,42 @@ func (z *transmittedPayload) MarshalMsg(b []byte) (o []byte) { o = zb0002.MarshalMsg(o) } } - if (zb0004Mask & 0x20000000) == 0 { // if not empty + if (zb0004Mask & 0x40000000) == 0 { // if not empty // string "tc" o = append(o, 0xa2, 0x74, 0x63) o = msgp.AppendUint64(o, (*z).unauthenticatedProposal.Block.BlockHeader.TxnCounter) } - if (zb0004Mask & 0x40000000) == 0 { // if not empty + if (zb0004Mask & 0x80000000) == 0 { // if not empty // string "ts" o = append(o, 0xa2, 0x74, 0x73) o = msgp.AppendInt64(o, (*z).unauthenticatedProposal.Block.BlockHeader.TimeStamp) } - if (zb0004Mask & 0x80000000) == 0 { // if not empty + if (zb0004Mask & 0x100000000) == 0 { // if not empty // string "txn" o = append(o, 0xa3, 0x74, 0x78, 0x6e) o = (*z).unauthenticatedProposal.Block.BlockHeader.TxnCommitments.NativeSha512_256Commitment.MarshalMsg(o) } - if (zb0004Mask & 0x100000000) == 0 { // if not empty + if (zb0004Mask & 0x200000000) == 0 { // if not empty // string "txn256" o = append(o, 0xa6, 0x74, 0x78, 0x6e, 0x32, 0x35, 0x36) o = (*z).unauthenticatedProposal.Block.BlockHeader.TxnCommitments.Sha256Commitment.MarshalMsg(o) } - if (zb0004Mask & 0x200000000) == 0 { // if not empty + if (zb0004Mask & 0x400000000) == 0 { // if not empty // string "txns" o = append(o, 0xa4, 0x74, 0x78, 0x6e, 0x73) o = (*z).unauthenticatedProposal.Block.Payset.MarshalMsg(o) } - if (zb0004Mask & 0x400000000) == 0 { // if not empty + if (zb0004Mask & 0x800000000) == 0 { // if not empty // string "upgradedelay" o = append(o, 0xac, 0x75, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x64, 0x65, 0x6c, 0x61, 0x79) o = (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradeDelay.MarshalMsg(o) } - if (zb0004Mask & 0x800000000) == 0 { // if not empty + if (zb0004Mask & 0x1000000000) == 0 { // if not empty // string "upgradeprop" o = append(o, 0xab, 0x75, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x70, 0x72, 0x6f, 0x70) o = (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradePropose.MarshalMsg(o) } - if (zb0004Mask & 0x1000000000) == 0 { // if not empty + if (zb0004Mask & 0x2000000000) == 0 { // if not empty // string "upgradeyes" o = append(o, 0xaa, 0x75, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x79, 0x65, 0x73) o = msgp.AppendBool(o, (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradeApprove) @@ -9415,7 +9415,7 @@ func (z *unauthenticatedProposal) MarshalMsg(b []byte) (o []byte) { o = msgp.Require(b, z.Msgsize()) // omitempty: check for empty values zb0004Len := uint32(29) - var zb0004Mask uint64 /* 35 bits */ + var zb0004Mask uint64 /* 36 bits */ if (*z).Block.BlockHeader.RewardsState.RewardsLevel == 0 { zb0004Len-- zb0004Mask |= 0x40 @@ -9478,59 +9478,59 @@ func (z *unauthenticatedProposal) MarshalMsg(b []byte) (o []byte) { } if (*z).Block.BlockHeader.Round.MsgIsZero() { zb0004Len-- - zb0004Mask |= 0x200000 + zb0004Mask |= 0x400000 } if (*z).Block.BlockHeader.RewardsState.RewardsRecalculationRound.MsgIsZero() { zb0004Len-- - zb0004Mask |= 0x400000 + zb0004Mask |= 0x800000 } if (*z).Block.BlockHeader.RewardsState.RewardsPool.MsgIsZero() { zb0004Len-- - zb0004Mask |= 0x800000 + zb0004Mask |= 0x1000000 } if (*z).SeedProof.MsgIsZero() { zb0004Len-- - zb0004Mask |= 0x1000000 + zb0004Mask |= 0x2000000 } if (*z).Block.BlockHeader.Seed.MsgIsZero() { zb0004Len-- - zb0004Mask |= 0x2000000 + zb0004Mask |= 0x4000000 } if len((*z).Block.BlockHeader.StateProofTracking) == 0 { zb0004Len-- - zb0004Mask |= 0x4000000 + zb0004Mask |= 0x8000000 } if (*z).Block.BlockHeader.TxnCounter == 0 { zb0004Len-- - zb0004Mask |= 0x8000000 + zb0004Mask |= 0x10000000 } if (*z).Block.BlockHeader.TimeStamp == 0 { zb0004Len-- - zb0004Mask |= 0x10000000 + zb0004Mask |= 0x20000000 } if (*z).Block.BlockHeader.TxnCommitments.NativeSha512_256Commitment.MsgIsZero() { zb0004Len-- - zb0004Mask |= 0x20000000 + zb0004Mask |= 0x40000000 } if (*z).Block.BlockHeader.TxnCommitments.Sha256Commitment.MsgIsZero() { zb0004Len-- - zb0004Mask |= 0x40000000 + zb0004Mask |= 0x80000000 } if (*z).Block.Payset.MsgIsZero() { zb0004Len-- - zb0004Mask |= 0x80000000 + zb0004Mask |= 0x100000000 } if (*z).Block.BlockHeader.UpgradeVote.UpgradeDelay.MsgIsZero() { zb0004Len-- - zb0004Mask |= 0x100000000 + zb0004Mask |= 0x200000000 } if (*z).Block.BlockHeader.UpgradeVote.UpgradePropose.MsgIsZero() { zb0004Len-- - zb0004Mask |= 0x200000000 + zb0004Mask |= 0x400000000 } if (*z).Block.BlockHeader.UpgradeVote.UpgradeApprove == false { zb0004Len-- - zb0004Mask |= 0x400000000 + zb0004Mask |= 0x800000000 } // variable map header, size zb0004Len o = msgp.AppendMapHeader(o, zb0004Len) @@ -9617,32 +9617,32 @@ func (z *unauthenticatedProposal) MarshalMsg(b []byte) (o []byte) { o = append(o, 0xa4, 0x72, 0x61, 0x74, 0x65) o = msgp.AppendUint64(o, (*z).Block.BlockHeader.RewardsState.RewardsRate) } - if (zb0004Mask & 0x200000) == 0 { // if not empty + if (zb0004Mask & 0x400000) == 0 { // if not empty // string "rnd" o = append(o, 0xa3, 0x72, 0x6e, 0x64) o = (*z).Block.BlockHeader.Round.MarshalMsg(o) } - if (zb0004Mask & 0x400000) == 0 { // if not empty + if (zb0004Mask & 0x800000) == 0 { // if not empty // string "rwcalr" o = append(o, 0xa6, 0x72, 0x77, 0x63, 0x61, 0x6c, 0x72) o = (*z).Block.BlockHeader.RewardsState.RewardsRecalculationRound.MarshalMsg(o) } - if (zb0004Mask & 0x800000) == 0 { // if not empty + if (zb0004Mask & 0x1000000) == 0 { // if not empty // string "rwd" o = append(o, 0xa3, 0x72, 0x77, 0x64) o = (*z).Block.BlockHeader.RewardsState.RewardsPool.MarshalMsg(o) } - if (zb0004Mask & 0x1000000) == 0 { // if not empty + if (zb0004Mask & 0x2000000) == 0 { // if not empty // string "sdpf" o = append(o, 0xa4, 0x73, 0x64, 0x70, 0x66) o = (*z).SeedProof.MarshalMsg(o) } - if (zb0004Mask & 0x2000000) == 0 { // if not empty + if (zb0004Mask & 0x4000000) == 0 { // if not empty // string "seed" o = append(o, 0xa4, 0x73, 0x65, 0x65, 0x64) o = (*z).Block.BlockHeader.Seed.MarshalMsg(o) } - if (zb0004Mask & 0x4000000) == 0 { // if not empty + if (zb0004Mask & 0x8000000) == 0 { // if not empty // string "spt" o = append(o, 0xa3, 0x73, 0x70, 0x74) if (*z).Block.BlockHeader.StateProofTracking == nil { @@ -9662,42 +9662,42 @@ func (z *unauthenticatedProposal) MarshalMsg(b []byte) (o []byte) { o = zb0002.MarshalMsg(o) } } - if (zb0004Mask & 0x8000000) == 0 { // if not empty + if (zb0004Mask & 0x10000000) == 0 { // if not empty // string "tc" o = append(o, 0xa2, 0x74, 0x63) o = msgp.AppendUint64(o, (*z).Block.BlockHeader.TxnCounter) } - if (zb0004Mask & 0x10000000) == 0 { // if not empty + if (zb0004Mask & 0x20000000) == 0 { // if not empty // string "ts" o = append(o, 0xa2, 0x74, 0x73) o = msgp.AppendInt64(o, (*z).Block.BlockHeader.TimeStamp) } - if (zb0004Mask & 0x20000000) == 0 { // if not empty + if (zb0004Mask & 0x40000000) == 0 { // if not empty // string "txn" o = append(o, 0xa3, 0x74, 0x78, 0x6e) o = (*z).Block.BlockHeader.TxnCommitments.NativeSha512_256Commitment.MarshalMsg(o) } - if (zb0004Mask & 0x40000000) == 0 { // if not empty + if (zb0004Mask & 0x80000000) == 0 { // if not empty // string "txn256" o = append(o, 0xa6, 0x74, 0x78, 0x6e, 0x32, 0x35, 0x36) o = (*z).Block.BlockHeader.TxnCommitments.Sha256Commitment.MarshalMsg(o) } - if (zb0004Mask & 0x80000000) == 0 { // if not empty + if (zb0004Mask & 0x100000000) == 0 { // if not empty // string "txns" o = append(o, 0xa4, 0x74, 0x78, 0x6e, 0x73) o = (*z).Block.Payset.MarshalMsg(o) } - if (zb0004Mask & 0x100000000) == 0 { // if not empty + if (zb0004Mask & 0x200000000) == 0 { // if not empty // string "upgradedelay" o = append(o, 0xac, 0x75, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x64, 0x65, 0x6c, 0x61, 0x79) o = (*z).Block.BlockHeader.UpgradeVote.UpgradeDelay.MarshalMsg(o) } - if (zb0004Mask & 0x200000000) == 0 { // if not empty + if (zb0004Mask & 0x400000000) == 0 { // if not empty // string "upgradeprop" o = append(o, 0xab, 0x75, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x70, 0x72, 0x6f, 0x70) o = (*z).Block.BlockHeader.UpgradeVote.UpgradePropose.MarshalMsg(o) } - if (zb0004Mask & 0x400000000) == 0 { // if not empty + if (zb0004Mask & 0x800000000) == 0 { // if not empty // string "upgradeyes" o = append(o, 0xaa, 0x75, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x79, 0x65, 0x73) o = msgp.AppendBool(o, (*z).Block.BlockHeader.UpgradeVote.UpgradeApprove) diff --git a/agreement/params.go b/agreement/params.go index 168c252608..de90d29136 100644 --- a/agreement/params.go +++ b/agreement/params.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/persistence.go b/agreement/persistence.go index 497e4b9af0..5e85955b30 100644 --- a/agreement/persistence.go +++ b/agreement/persistence.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/persistence_test.go b/agreement/persistence_test.go index 7a4ec3db4d..fbd9323b09 100644 --- a/agreement/persistence_test.go +++ b/agreement/persistence_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -240,6 +240,7 @@ func TestEmptyMapDeserialization(t *testing.T) { } func TestDecodeFailures(t *testing.T) { + partitiontest.PartitionTest(t) clock := timers.MakeMonotonicClock(time.Date(2015, 1, 2, 5, 6, 7, 8, time.UTC)) ce := clock.Encode() log := makeServiceLogger(logging.Base()) diff --git a/agreement/player.go b/agreement/player.go index cc29240aa7..1ae552b0b1 100644 --- a/agreement/player.go +++ b/agreement/player.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/playerContract.go b/agreement/playerContract.go index fa95c2278c..cfc574fef0 100644 --- a/agreement/playerContract.go +++ b/agreement/playerContract.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/player_permutation_test.go b/agreement/player_permutation_test.go index d7dcf9add8..0335093933 100644 --- a/agreement/player_permutation_test.go +++ b/agreement/player_permutation_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/player_test.go b/agreement/player_test.go index 3e3cf8167b..83368c3d0d 100644 --- a/agreement/player_test.go +++ b/agreement/player_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -19,6 +19,7 @@ package agreement import ( "fmt" "testing" + "time" "github.com/stretchr/testify/require" @@ -3232,4 +3233,89 @@ func TestPlayerAlwaysResynchsPinnedValue(t *testing.T) { require.Truef(t, trace.Contains(rePayloadEvent), "Player should relay payload even if not staged in previous period") } +// test that ReceivedAt and ValidateAt timing information are retained in proposalStore +// when the payloadPresent and payloadVerified events are processed, and that both timings +// are available when the ensureAction is called for the block. +func TestPlayerRetainsReceivedValidatedAt(t *testing.T) { + partitiontest.PartitionTest(t) + + const r = round(20239) + const p = period(1001) + pWhite, pM, helper := setupP(t, r-1, p, soft) + pP, pV := helper.MakeRandomProposalPayload(t, r-1) + + // send a payload + // store an arbitrary proposal/payload + vVote := helper.MakeVerifiedVote(t, 0, r-1, p, propose, *pV) + inMsg := messageEvent{T: voteVerified, Input: message{Vote: vVote, UnauthenticatedVote: vVote.u()}} + err, panicErr := pM.transition(inMsg) + require.NoError(t, err) + require.NoError(t, panicErr) + + // payloadPresent + m := message{UnauthenticatedProposal: pP.u()} + inMsg = messageEvent{T: payloadPresent, Input: m} + inMsg = inMsg.AttachReceivedAt(time.Second) + err, panicErr = pM.transition(inMsg) + require.NoError(t, err) + require.NoError(t, panicErr) + + // make sure payload verify request + verifyEvent := ev(cryptoAction{T: verifyPayload, M: m, TaskIndex: 0}) + require.Truef(t, pM.getTrace().Contains(verifyEvent), "Player should verify payload") + + // payloadVerified + inMsg = messageEvent{T: payloadVerified, Input: message{Proposal: *pP}, Proto: ConsensusVersionView{Version: protocol.ConsensusCurrentVersion}} + inMsg = inMsg.AttachValidatedAt(2 * time.Second) + err, panicErr = pM.transition(inMsg) + require.NoError(t, err) + require.NoError(t, panicErr) + + // gen cert to move into the next round + votes := make([]vote, int(cert.threshold(config.Consensus[protocol.ConsensusCurrentVersion]))) + for i := 0; i < int(cert.threshold(config.Consensus[protocol.ConsensusCurrentVersion])); i++ { + votes[i] = helper.MakeVerifiedVote(t, i, r-1, p, cert, *pV) + } + bun := unauthenticatedBundle{ + Round: r - 1, + Period: p, + Proposal: *pV, + } + inMsg = messageEvent{ + T: bundleVerified, + Input: message{ + Bundle: bundle{ + U: bun, + Votes: votes, + }, + UnauthenticatedBundle: bun, + }, + Proto: ConsensusVersionView{Version: protocol.ConsensusCurrentVersion}, + } + err, panicErr = pM.transition(inMsg) + require.NoError(t, err) + require.NoError(t, panicErr) + + require.Equalf(t, r, pWhite.Round, "player did not enter new round") + require.Equalf(t, period(0), pWhite.Period, "player did not enter period 0 in new round") + commitEvent := ev(ensureAction{Certificate: Certificate(bun), Payload: *pP}) + require.Truef(t, pM.getTrace().Contains(commitEvent), "Player should try to ensure block/digest on ledger") + + // find and unwrap ensureAction from trace + var ea ensureAction + var foundEA bool + for _, ev := range pM.getTrace().events { + if wae, ok := ev.(wrappedActionEvent); ok { + if wae.action.t() == ensure { + require.False(t, foundEA) + ea = wae.action.(ensureAction) + foundEA = true + } + } + } + require.True(t, foundEA) + require.Equal(t, 2*time.Second, ea.Payload.validatedAt) + require.Equal(t, time.Second, ea.Payload.receivedAt) +} + // todo: test pipelined rounds, and round interruption diff --git a/agreement/proposal.go b/agreement/proposal.go index ca0d6e6788..ec382fe947 100644 --- a/agreement/proposal.go +++ b/agreement/proposal.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -59,6 +59,11 @@ type unauthenticatedProposal struct { OriginalPeriod period `codec:"oper"` OriginalProposer basics.Address `codec:"oprop"` + + // receivedAt indicates the time at which this proposal was + // delivered to the agreement package (as a messageEvent), + // relative to the zero of that round. + receivedAt time.Duration } // TransmittedPayload exported for dumping textual versions of messages diff --git a/agreement/proposalManager.go b/agreement/proposalManager.go index 5abe5f0528..ca972b56e4 100644 --- a/agreement/proposalManager.go +++ b/agreement/proposalManager.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/proposalManagerContract.go b/agreement/proposalManagerContract.go index 8f20bd457b..df1593848d 100644 --- a/agreement/proposalManagerContract.go +++ b/agreement/proposalManagerContract.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/proposalManager_test.go b/agreement/proposalManager_test.go index a759739d0f..e76de0be5e 100644 --- a/agreement/proposalManager_test.go +++ b/agreement/proposalManager_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/proposalStore.go b/agreement/proposalStore.go index 841dc91b9b..1b50fb6f9f 100644 --- a/agreement/proposalStore.go +++ b/agreement/proposalStore.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -86,6 +86,9 @@ func (a blockAssembler) bind(p proposal) (blockAssembler, error) { a.Payload = p a.Assembled = true + // remember when the original unauthenticatedProposal was received + a.Payload.receivedAt = a.Pipeline.receivedAt + return a, nil } diff --git a/agreement/proposalStoreContract.go b/agreement/proposalStoreContract.go index 6d888bc6dc..b1d0192ace 100644 --- a/agreement/proposalStoreContract.go +++ b/agreement/proposalStoreContract.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/proposalStore_test.go b/agreement/proposalStore_test.go index 237579c842..46f9cd38da 100644 --- a/agreement/proposalStore_test.go +++ b/agreement/proposalStore_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/proposalTable.go b/agreement/proposalTable.go index b3ef71ac6a..a5a5ff2eb1 100644 --- a/agreement/proposalTable.go +++ b/agreement/proposalTable.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/proposalTable_test.go b/agreement/proposalTable_test.go index af81305fb1..2cb5e9f380 100644 --- a/agreement/proposalTable_test.go +++ b/agreement/proposalTable_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/proposalTracker.go b/agreement/proposalTracker.go index e3efcd372c..4539a1f518 100644 --- a/agreement/proposalTracker.go +++ b/agreement/proposalTracker.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/proposalTrackerContract.go b/agreement/proposalTrackerContract.go index bbe4911c2b..2b995dfcac 100644 --- a/agreement/proposalTrackerContract.go +++ b/agreement/proposalTrackerContract.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/proposalTracker_test.go b/agreement/proposalTracker_test.go index 71096b4ad6..641dee70e7 100644 --- a/agreement/proposalTracker_test.go +++ b/agreement/proposalTracker_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/proposal_test.go b/agreement/proposal_test.go index 2adfb6c6c8..3102a08a84 100644 --- a/agreement/proposal_test.go +++ b/agreement/proposal_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/pseudonode.go b/agreement/pseudonode.go index 6075e8ebfc..3ebb681ca9 100644 --- a/agreement/pseudonode.go +++ b/agreement/pseudonode.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/pseudonode_test.go b/agreement/pseudonode_test.go index dfb9f2ce1f..a60a1e5efa 100644 --- a/agreement/pseudonode_test.go +++ b/agreement/pseudonode_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/router.go b/agreement/router.go index f6f0ea0105..040fd525e1 100644 --- a/agreement/router.go +++ b/agreement/router.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/selector.go b/agreement/selector.go index d19cfef23e..505d54188a 100644 --- a/agreement/selector.go +++ b/agreement/selector.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/service.go b/agreement/service.go index 00b192b5c4..3cac126193 100644 --- a/agreement/service.go +++ b/agreement/service.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -88,7 +88,7 @@ type externalDemuxSignals struct { // MakeService creates a new Agreement Service instance given a set of Parameters. // // Call Start to start execution and Shutdown to finish execution. -func MakeService(p Parameters) *Service { +func MakeService(p Parameters) (*Service, error) { s := new(Service) s.parameters = parameters(p) @@ -97,12 +97,16 @@ func MakeService(p Parameters) *Service { // GOAL2-541: tracer is not concurrency safe. It should only ever be // accessed by main state machine loop. - s.tracer = makeTracer(s.log, defaultCadaverName, p.CadaverSizeTarget, + var err error + s.tracer, err = makeTracer(s.log, defaultCadaverName, p.CadaverSizeTarget, p.CadaverDirectory, s.Local.EnableAgreementReporting, s.Local.EnableAgreementTimeMetrics) + if err != nil { + return nil, err + } s.persistenceLoop = makeAsyncPersistenceLoop(s.log, s.Accessor, s.Ledger) - return s + return s, nil } // SetTracerFilename updates the tracer filename used. diff --git a/agreement/service_test.go b/agreement/service_test.go index b5ec2ce613..2cf6969629 100644 --- a/agreement/service_test.go +++ b/agreement/service_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -752,7 +752,8 @@ func setupAgreementWithValidator(t *testing.T, numNodes int, traceLevel traceLev os.Remove(cadaverFilename + ".cdv") os.Remove(cadaverFilename + ".cdv.archive") - services[i] = MakeService(params) + services[i], err = MakeService(params) + require.NoError(t, err) services[i].tracer.cadaver.baseFilename = cadaverFilename services[i].tracer.level = traceLevel services[i].tracer.tag = strconv.Itoa(i) diff --git a/agreement/sort.go b/agreement/sort.go index 21fa3fc277..f95060ca80 100644 --- a/agreement/sort.go +++ b/agreement/sort.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/sort_test.go b/agreement/sort_test.go index 8240e5eff3..be2b968ddc 100644 --- a/agreement/sort_test.go +++ b/agreement/sort_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/state_machine_test.go b/agreement/state_machine_test.go index 8518ff70d5..0effd9fda1 100644 --- a/agreement/state_machine_test.go +++ b/agreement/state_machine_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/trace.go b/agreement/trace.go index a3f8cea665..d5aeb3d9dd 100644 --- a/agreement/trace.go +++ b/agreement/trace.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -74,7 +74,7 @@ type tracer struct { const cadaverSizeMinimum = 100 * 1024 // 100 KB -func makeTracer(log serviceLogger, cadaverFilename string, cadaverSizeTarget uint64, verboseReportFlag bool, timingReportFlag bool) *tracer { +func makeTracer(log serviceLogger, cadaverFilename string, cadaverSizeTarget uint64, cadaverDirectory string, verboseReportFlag bool, timingReportFlag bool) (*tracer, error) { t := new(tracer) t.log = log t.verboseReports = verboseReportFlag @@ -85,15 +85,20 @@ func makeTracer(log serviceLogger, cadaverFilename string, cadaverSizeTarget uin if fileSizeTarget == 0 { // disabled } else if fileSizeTarget < 0 { - log.Errorf("agreement: cadaver filesize too large: int64(%v) < 0", cadaverSizeTarget) + return nil, fmt.Errorf("agreement: cadaver filesize too large: int64(%v) < 0", cadaverSizeTarget) } else if fileSizeTarget < cadaverSizeMinimum { - log.Errorf("agreement: cadaver filesize too small: %v < %v", fileSizeTarget, cadaverSizeMinimum) + return nil, fmt.Errorf("agreement: cadaver filesize too small: %v < %v", fileSizeTarget, cadaverSizeMinimum) } else if fileSizeTarget > 0 { t.cadaver.baseFilename = cadaverFilename + t.cadaver.baseDirectory = cadaverDirectory t.cadaver.fileSizeTarget = fileSizeTarget - log.Infof("agreement: cadaver set to %v", cadaverFilename) + log.Infof("agreement: cadaver set to %s", t.cadaver.filename()) + err := t.cadaver.init() + if err != nil { + return nil, err + } } - return t + return t, nil } // call this method to setup timing generators before entering target round, pipelining properly. diff --git a/agreement/traceTime.go b/agreement/traceTime.go index 08d5bdb254..d397a66fb4 100644 --- a/agreement/traceTime.go +++ b/agreement/traceTime.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/types.go b/agreement/types.go index 51793aa077..450570c620 100644 --- a/agreement/types.go +++ b/agreement/types.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/vote.go b/agreement/vote.go index e39edd7b76..af157ee3bb 100644 --- a/agreement/vote.go +++ b/agreement/vote.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/voteAggregator.go b/agreement/voteAggregator.go index 196e91a3bb..29d5e2b541 100644 --- a/agreement/voteAggregator.go +++ b/agreement/voteAggregator.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/voteAggregatorContract.go b/agreement/voteAggregatorContract.go index 843746fabd..5ebdb56929 100644 --- a/agreement/voteAggregatorContract.go +++ b/agreement/voteAggregatorContract.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/voteAggregator_test.go b/agreement/voteAggregator_test.go index 8795a0b364..554426ecb1 100644 --- a/agreement/voteAggregator_test.go +++ b/agreement/voteAggregator_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/voteAuxiliary.go b/agreement/voteAuxiliary.go index d99a01139e..d560c4f015 100644 --- a/agreement/voteAuxiliary.go +++ b/agreement/voteAuxiliary.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/voteAuxiliaryContract.go b/agreement/voteAuxiliaryContract.go index e8b501808b..ada331a6e4 100644 --- a/agreement/voteAuxiliaryContract.go +++ b/agreement/voteAuxiliaryContract.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/voteAuxiliary_test.go b/agreement/voteAuxiliary_test.go index 667c3a147f..c20fecc62d 100644 --- a/agreement/voteAuxiliary_test.go +++ b/agreement/voteAuxiliary_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/voteTracker.go b/agreement/voteTracker.go index 3945840158..f1250566a9 100644 --- a/agreement/voteTracker.go +++ b/agreement/voteTracker.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/voteTrackerContract.go b/agreement/voteTrackerContract.go index ad1585e6d1..0955230976 100644 --- a/agreement/voteTrackerContract.go +++ b/agreement/voteTrackerContract.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/voteTracker_test.go b/agreement/voteTracker_test.go index 7295659d81..75359fa342 100644 --- a/agreement/voteTracker_test.go +++ b/agreement/voteTracker_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/vote_test.go b/agreement/vote_test.go index d3f6f39bf0..22b1049be1 100644 --- a/agreement/vote_test.go +++ b/agreement/vote_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/buildnumber.dat b/buildnumber.dat index 00750edc07..d00491fd7e 100644 --- a/buildnumber.dat +++ b/buildnumber.dat @@ -1 +1 @@ -3 +1 diff --git a/catchup/catchpointService.go b/catchup/catchpointService.go index 41c75172e8..974b76ef94 100644 --- a/catchup/catchpointService.go +++ b/catchup/catchpointService.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/catchup/catchpointService_test.go b/catchup/catchpointService_test.go index 02f4a9b7a3..de91b456e4 100644 --- a/catchup/catchpointService_test.go +++ b/catchup/catchpointService_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/catchup/fetcher_test.go b/catchup/fetcher_test.go index 3c83baae01..df762bc434 100644 --- a/catchup/fetcher_test.go +++ b/catchup/fetcher_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/catchup/ledgerFetcher.go b/catchup/ledgerFetcher.go index 30c5ccb3b8..bf79c5d3b3 100644 --- a/catchup/ledgerFetcher.go +++ b/catchup/ledgerFetcher.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -30,6 +30,7 @@ import ( "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/ledger" + "github.com/algorand/go-algorand/ledger/encoded" "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/network" "github.com/algorand/go-algorand/rpcs" @@ -40,7 +41,7 @@ var errNoLedgerForRound = errors.New("no ledger available for given round") const ( // maxCatchpointFileChunkSize is a rough estimate for the worst-case scenario we're going to have of all the accounts data per a single catchpoint file chunk and one account with max resources. - maxCatchpointFileChunkSize = ledger.BalancesPerCatchpointFileChunk*(ledger.MaxEncodedBaseAccountDataSize+ledger.MaxEncodedKVDataSize) + ledger.ResourcesPerCatchpointFileChunk*ledger.MaxEncodedBaseResourceDataSize + maxCatchpointFileChunkSize = ledger.BalancesPerCatchpointFileChunk*(ledger.MaxEncodedBaseAccountDataSize+encoded.MaxEncodedKVDataSize) + ledger.ResourcesPerCatchpointFileChunk*ledger.MaxEncodedBaseResourceDataSize // defaultMinCatchpointFileDownloadBytesPerSecond defines the worst-case scenario download speed we expect to get while downloading a catchpoint file defaultMinCatchpointFileDownloadBytesPerSecond = 20 * 1024 // catchpointFileStreamReadSize defines the number of bytes we would attempt to read at each iteration from the incoming http data stream diff --git a/catchup/ledgerFetcher_test.go b/catchup/ledgerFetcher_test.go index 4cb57d7fd3..248192c4f7 100644 --- a/catchup/ledgerFetcher_test.go +++ b/catchup/ledgerFetcher_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/catchup/networkFetcher.go b/catchup/networkFetcher.go deleted file mode 100644 index d82395e8d3..0000000000 --- a/catchup/networkFetcher.go +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright (C) 2019-2022 Algorand, Inc. -// This file is part of go-algorand -// -// go-algorand is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as -// published by the Free Software Foundation, either version 3 of the -// License, or (at your option) any later version. -// -// go-algorand is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with go-algorand. If not, see . - -package catchup - -import ( - "context" - "errors" - "fmt" - "time" - - "github.com/algorand/go-algorand/agreement" - "github.com/algorand/go-algorand/config" - "github.com/algorand/go-algorand/data/basics" - "github.com/algorand/go-algorand/data/bookkeeping" - "github.com/algorand/go-algorand/logging" - "github.com/algorand/go-algorand/network" -) - -// NetworkFetcher is the struct used to export fetchBlock function from universalFetcher -type NetworkFetcher struct { - log logging.Logger - cfg config.Local - auth BlockAuthenticator - peerSelector *peerSelector - fetcher *universalBlockFetcher -} - -// MakeNetworkFetcher initializes a NetworkFetcher service -func MakeNetworkFetcher(log logging.Logger, net network.GossipNode, cfg config.Local, auth BlockAuthenticator, pipelineFetch bool) *NetworkFetcher { - netFetcher := &NetworkFetcher{ - log: log, - cfg: cfg, - auth: auth, - peerSelector: createPeerSelector(net, cfg, pipelineFetch), - fetcher: makeUniversalBlockFetcher(log, net, cfg), - } - return netFetcher -} - -func (netFetcher *NetworkFetcher) getHTTPPeer() (network.HTTPPeer, *peerSelectorPeer, error) { - for retryCount := 0; retryCount < netFetcher.cfg.CatchupBlockDownloadRetryAttempts; retryCount++ { - psp, err := netFetcher.peerSelector.getNextPeer() - if err != nil { - if err != errPeerSelectorNoPeerPoolsAvailable { - err = fmt.Errorf("FetchBlock: unable to obtain a list of peers to download the block from : %w", err) - return nil, nil, err - } - // this is a possible on startup, since the network package might have yet to retrieve the list of peers. - netFetcher.log.Infof("FetchBlock: unable to obtain a list of peers to download the block from; will retry shortly.") - time.Sleep(noPeersAvailableSleepInterval) - continue - } - peer := psp.Peer - httpPeer, ok := peer.(network.HTTPPeer) - if ok { - return httpPeer, psp, nil - } - netFetcher.log.Warnf("FetchBlock: non-HTTP peer was provided by the peer selector") - netFetcher.peerSelector.rankPeer(psp, peerRankInvalidDownload) - } - return nil, nil, errors.New("FetchBlock: recurring non-HTTP peer was provided by the peer selector") -} - -// FetchBlock function given a round number returns a block from a http peer -func (netFetcher *NetworkFetcher) FetchBlock(ctx context.Context, round basics.Round) (*bookkeeping.Block, - *agreement.Certificate, time.Duration, error) { - // internal retry attempt to fetch the block - for retryCount := 0; retryCount < netFetcher.cfg.CatchupBlockDownloadRetryAttempts; retryCount++ { - httpPeer, psp, err := netFetcher.getHTTPPeer() - if err != nil { - return nil, nil, time.Duration(0), err - } - - blk, cert, downloadDuration, err := netFetcher.fetcher.fetchBlock(ctx, round, httpPeer) - if err != nil { - if ctx.Err() != nil { - // caller of the function decided to cancel the download - return nil, nil, time.Duration(0), err - } - netFetcher.log.Infof("FetchBlock: failed to download block %d on attempt %d out of %d. %v", - round, retryCount+1, netFetcher.cfg.CatchupBlockDownloadRetryAttempts, err) - netFetcher.peerSelector.rankPeer(psp, peerRankDownloadFailed) - continue // retry the fetch - } - - // Check that the block's contents match the block header - if !blk.ContentsMatchHeader() && blk.Round() > 0 { - netFetcher.peerSelector.rankPeer(psp, peerRankInvalidDownload) - // Check if this mismatch is due to an unsupported protocol version - if _, ok := config.Consensus[blk.BlockHeader.CurrentProtocol]; !ok { - netFetcher.log.Errorf("FetchBlock: downloaded block(%v) unsupported protocol version detected: '%v'", - round, blk.BlockHeader.CurrentProtocol) - } - netFetcher.log.Warnf("FetchBlock: downloaded block(%v) contents do not match header", round) - netFetcher.log.Infof("FetchBlock: failed to download block %d on attempt %d out of %d. %v", - round, retryCount+1, netFetcher.cfg.CatchupBlockDownloadRetryAttempts, err) - continue // retry the fetch - } - - // Authenticate the block. for correct execution, caller should call FetchBlock only when the lookback block is available - if netFetcher.cfg.CatchupVerifyCertificate() { - err = netFetcher.auth.Authenticate(blk, cert) - if err != nil { - netFetcher.log.Warnf("FetchBlock: cert authenticatation failed for block %d on attempt %d out of %d. %v", - round, retryCount+1, netFetcher.cfg.CatchupBlockDownloadRetryAttempts, err) - netFetcher.peerSelector.rankPeer(psp, peerRankInvalidDownload) - continue // retry the fetch - } - } - - // upon successful download rank the peer according to the download speed - peerRank := netFetcher.peerSelector.peerDownloadDurationToRank(psp, downloadDuration) - netFetcher.peerSelector.rankPeer(psp, peerRank) - return blk, cert, downloadDuration, err - - } - err := fmt.Errorf("FetchBlock failed after multiple blocks download attempts: %v unsuccessful attempts", - netFetcher.cfg.CatchupBlockDownloadRetryAttempts) - return nil, nil, time.Duration(0), err -} diff --git a/catchup/networkFetcher_test.go b/catchup/networkFetcher_test.go deleted file mode 100644 index 7c6a2c885b..0000000000 --- a/catchup/networkFetcher_test.go +++ /dev/null @@ -1,190 +0,0 @@ -// Copyright (C) 2019-2022 Algorand, Inc. -// This file is part of go-algorand -// -// go-algorand is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as -// published by the Free Software Foundation, either version 3 of the -// License, or (at your option) any later version. -// -// go-algorand is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with go-algorand. If not, see . - -package catchup - -import ( - "context" - "sync" - "testing" - - "github.com/algorand/go-algorand/config" - "github.com/algorand/go-algorand/data/bookkeeping" - "github.com/algorand/go-algorand/logging" - "github.com/algorand/go-algorand/rpcs" - "github.com/algorand/go-algorand/test/partitiontest" - "github.com/stretchr/testify/require" -) - -func TestFetchBlock(t *testing.T) { - partitiontest.PartitionTest(t) - - ledger, next, b, err := buildTestLedger(t, bookkeeping.Block{}) - if err != nil { - t.Fatal(err) - return - } - - blockServiceConfig := config.GetDefaultLocal() - blockServiceConfig.EnableBlockService = true - blockServiceConfig.EnableBlockServiceFallbackToArchiver = false - - net := &httpTestPeerSource{} - ls := rpcs.MakeBlockService(logging.Base(), blockServiceConfig, ledger, net, "test genesisID") - - node := basicRPCNode{} - node.RegisterHTTPHandler(rpcs.BlockServiceBlockPath, ls) - node.start() - defer node.stop() - rootURL := node.rootURL() - - net.addPeer(rootURL) - - // Disable block authentication - cfg := config.GetDefaultLocal() - cfg.CatchupBlockValidateMode = 1 - fetcher := MakeNetworkFetcher(logging.TestingLog(t), net, cfg, nil, false) - - block, _, duration, err := fetcher.FetchBlock(context.Background(), next) - - require.NoError(t, err) - require.Equal(t, &b, block) - require.GreaterOrEqual(t, int64(duration), int64(0)) - - block, cert, duration, err := fetcher.FetchBlock(context.Background(), next+1) - - require.Error(t, errNoBlockForRound, err) - require.Contains(t, err.Error(), "FetchBlock failed after multiple blocks download attempts") - require.Nil(t, block) - require.Nil(t, cert) - require.Equal(t, int64(duration), int64(0)) -} - -func TestConcurrentAttemptsToFetchBlockSuccess(t *testing.T) { - partitiontest.PartitionTest(t) - - ledger, next, b, err := buildTestLedger(t, bookkeeping.Block{}) - if err != nil { - t.Fatal(err) - return - } - - blockServiceConfig := config.GetDefaultLocal() - blockServiceConfig.EnableBlockService = true - blockServiceConfig.EnableBlockServiceFallbackToArchiver = false - - net := &httpTestPeerSource{} - ls := rpcs.MakeBlockService(logging.Base(), blockServiceConfig, ledger, net, "test genesisID") - - node := basicRPCNode{} - node.RegisterHTTPHandler(rpcs.BlockServiceBlockPath, ls) - node.start() - defer node.stop() - rootURL := node.rootURL() - - net.addPeer(rootURL) - - // Disable block authentication - cfg := config.GetDefaultLocal() - cfg.CatchupBlockValidateMode = 1 - fetcher := MakeNetworkFetcher(logging.TestingLog(t), net, cfg, nil, false) - - // start is used to synchronize concurrent fetchBlock attempts - // parallelRequests represents number of concurrent attempts - start := make(chan struct{}) - parallelRequests := int(cfg.CatchupParallelBlocks) - var wg sync.WaitGroup - wg.Add(parallelRequests) - for i := 0; i < parallelRequests; i++ { - go func() { - <-start - block, _, duration, err := fetcher.FetchBlock(context.Background(), next) - require.NoError(t, err) - require.Equal(t, &b, block) - require.GreaterOrEqual(t, int64(duration), int64(0)) - wg.Done() - }() - } - close(start) - wg.Wait() -} - -func TestHTTPPeerNotAvailable(t *testing.T) { - partitiontest.PartitionTest(t) - - net := &httpTestPeerSource{} - - // Disable block authentication - cfg := config.GetDefaultLocal() - cfg.CatchupBlockValidateMode = 1 - cfg.CatchupBlockDownloadRetryAttempts = 1 - - fetcher := MakeNetworkFetcher(logging.TestingLog(t), net, cfg, nil, false) - - _, _, _, err := fetcher.FetchBlock(context.Background(), 1) - require.Contains(t, err.Error(), "recurring non-HTTP peer was provided by the peer selector") -} - -func TestFetchBlockFailed(t *testing.T) { - partitiontest.PartitionTest(t) - - net := &httpTestPeerSource{} - wsPeer := makeTestUnicastPeer(net, t) - net.addPeer(wsPeer.GetAddress()) - - // Disable block authentication - cfg := config.GetDefaultLocal() - cfg.CatchupBlockValidateMode = 1 - cfg.CatchupBlockDownloadRetryAttempts = 1 - - fetcher := MakeNetworkFetcher(logging.TestingLog(t), net, cfg, nil, false) - - _, _, _, err := fetcher.FetchBlock(context.Background(), 1) - require.Contains(t, err.Error(), "FetchBlock failed after multiple blocks download attempts") -} - -func TestFetchBlockAuthenticationFailed(t *testing.T) { - partitiontest.PartitionTest(t) - - ledger, next, _, err := buildTestLedger(t, bookkeeping.Block{}) - if err != nil { - t.Fatal(err) - return - } - - blockServiceConfig := config.GetDefaultLocal() - blockServiceConfig.EnableBlockService = true - blockServiceConfig.EnableBlockServiceFallbackToArchiver = false - - net := &httpTestPeerSource{} - ls := rpcs.MakeBlockService(logging.Base(), blockServiceConfig, ledger, net, "test genesisID") - - node := basicRPCNode{} - node.RegisterHTTPHandler(rpcs.BlockServiceBlockPath, ls) - node.start() - defer node.stop() - rootURL := node.rootURL() - - net.addPeer(rootURL) - - cfg := config.GetDefaultLocal() - cfg.CatchupBlockDownloadRetryAttempts = 1 - - fetcher := MakeNetworkFetcher(logging.TestingLog(t), net, cfg, &mockedAuthenticator{errorRound: int(next)}, false) - - _, _, _, err = fetcher.FetchBlock(context.Background(), next) - require.Contains(t, err.Error(), "FetchBlock failed after multiple blocks download attempts") -} diff --git a/catchup/peerSelector.go b/catchup/peerSelector.go index 822d0b709b..b78d802faa 100644 --- a/catchup/peerSelector.go +++ b/catchup/peerSelector.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/catchup/peerSelector_test.go b/catchup/peerSelector_test.go index c10298a519..8e66b9ab82 100644 --- a/catchup/peerSelector_test.go +++ b/catchup/peerSelector_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/catchup/pref_test.go b/catchup/pref_test.go index 4a56156e1a..688958ab26 100644 --- a/catchup/pref_test.go +++ b/catchup/pref_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/catchup/service.go b/catchup/service.go index 9198228256..2204212df2 100644 --- a/catchup/service.go +++ b/catchup/service.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/catchup/service_test.go b/catchup/service_test.go index efa341c347..4d1bafb285 100644 --- a/catchup/service_test.go +++ b/catchup/service_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/catchup/universalFetcher.go b/catchup/universalFetcher.go index 4527c2f5e9..789ce55c7f 100644 --- a/catchup/universalFetcher.go +++ b/catchup/universalFetcher.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/catchup/universalFetcher_test.go b/catchup/universalFetcher_test.go index 49b921dd44..6107a695f5 100644 --- a/catchup/universalFetcher_test.go +++ b/catchup/universalFetcher_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/algocfg/datadir.go b/cmd/algocfg/datadir.go index 99ca1a21ba..93aa4d7a10 100644 --- a/cmd/algocfg/datadir.go +++ b/cmd/algocfg/datadir.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/algocfg/getCommand.go b/cmd/algocfg/getCommand.go index c22ff597c1..3ad9a17943 100644 --- a/cmd/algocfg/getCommand.go +++ b/cmd/algocfg/getCommand.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -46,7 +46,7 @@ var getCmd = &cobra.Command{ onDataDirs(func(dataDir string) { cfg, err := config.LoadConfigFromDisk(dataDir) if err != nil && !os.IsNotExist(err) { - reportWarnf("Error loading config file from '%s'", dataDir) + reportWarnf("Error loading config file from '%s' - %s", dataDir, err) anyError = true return } diff --git a/cmd/algocfg/getCommand_test.go b/cmd/algocfg/getCommand_test.go index 0547c1ccc3..6c1285bb76 100644 --- a/cmd/algocfg/getCommand_test.go +++ b/cmd/algocfg/getCommand_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/algocfg/main.go b/cmd/algocfg/main.go index 0e38511f83..d3ee5c8a96 100644 --- a/cmd/algocfg/main.go +++ b/cmd/algocfg/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/algocfg/messages.go b/cmd/algocfg/messages.go index e0e27aa3a9..5225cfc860 100644 --- a/cmd/algocfg/messages.go +++ b/cmd/algocfg/messages.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/algocfg/report.go b/cmd/algocfg/report.go index 219f840703..a688a04edd 100644 --- a/cmd/algocfg/report.go +++ b/cmd/algocfg/report.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/algocfg/resetCommand.go b/cmd/algocfg/resetCommand.go index 187afe93c6..6e57bb2329 100644 --- a/cmd/algocfg/resetCommand.go +++ b/cmd/algocfg/resetCommand.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -48,7 +48,7 @@ var resetCmd = &cobra.Command{ onDataDirs(func(dataDir string) { cfg, err := config.LoadConfigFromDisk(dataDir) if err != nil && !os.IsNotExist(err) { - reportWarnf("Error loading config file from '%s'", dataDir) + reportWarnf("Error loading config file from '%s' - %s", dataDir, err) anyError = true return } diff --git a/cmd/algocfg/setCommand.go b/cmd/algocfg/setCommand.go index b5a4e6c1b5..ac1d7c6ea7 100644 --- a/cmd/algocfg/setCommand.go +++ b/cmd/algocfg/setCommand.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -52,7 +52,7 @@ var setCmd = &cobra.Command{ onDataDirs(func(dataDir string) { cfg, err := config.LoadConfigFromDisk(dataDir) if err != nil && !os.IsNotExist(err) { - reportWarnf("Error loading config file from '%s'", dataDir) + reportWarnf("Error loading config file from '%s' - %s", dataDir, err) anyError = true return } diff --git a/cmd/algod/main.go b/cmd/algod/main.go index b67747984c..cb0960e61b 100644 --- a/cmd/algod/main.go +++ b/cmd/algod/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -344,7 +344,17 @@ func run() int { // Send a heartbeat event every 10 minutes as a sign of life go func() { - ticker := time.NewTicker(10 * time.Minute) + var interval time.Duration + defaultIntervalSecs := config.GetDefaultLocal().HeartbeatUpdateInterval + switch { + case cfg.HeartbeatUpdateInterval <= 0: // use default + interval = time.Second * time.Duration(defaultIntervalSecs) + case cfg.HeartbeatUpdateInterval < 60: // min frequency 1 minute + interval = time.Minute + default: + interval = time.Second * time.Duration(cfg.HeartbeatUpdateInterval) + } + ticker := time.NewTicker(interval) defer ticker.Stop() sendHeartbeat := func() { diff --git a/cmd/algod/main_test.go b/cmd/algod/main_test.go index c25505167c..e2a37a8f87 100644 --- a/cmd/algod/main_test.go +++ b/cmd/algod/main_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/algofix/deadlock.go b/cmd/algofix/deadlock.go index b7342235ef..c6a9757647 100644 --- a/cmd/algofix/deadlock.go +++ b/cmd/algofix/deadlock.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/algofix/deadlock_test.go b/cmd/algofix/deadlock_test.go index 720bafa105..426b4297fc 100644 --- a/cmd/algofix/deadlock_test.go +++ b/cmd/algofix/deadlock_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/algoh/blockWatcher.go b/cmd/algoh/blockWatcher.go index 43da894d02..bcfb519a88 100644 --- a/cmd/algoh/blockWatcher.go +++ b/cmd/algoh/blockWatcher.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/algoh/blockWatcher_test.go b/cmd/algoh/blockWatcher_test.go index 1e4db05732..a623897295 100644 --- a/cmd/algoh/blockWatcher_test.go +++ b/cmd/algoh/blockWatcher_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/algoh/blockstats.go b/cmd/algoh/blockstats.go index 590cfb4352..fd54bdff4c 100644 --- a/cmd/algoh/blockstats.go +++ b/cmd/algoh/blockstats.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/algoh/blockstats_test.go b/cmd/algoh/blockstats_test.go index 0398404dda..2c58e8c430 100644 --- a/cmd/algoh/blockstats_test.go +++ b/cmd/algoh/blockstats_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/algoh/client.go b/cmd/algoh/client.go index 6d25759ec8..a0a12194d5 100644 --- a/cmd/algoh/client.go +++ b/cmd/algoh/client.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/algoh/deadman.go b/cmd/algoh/deadman.go index a15046f077..6b0e0e1bf0 100644 --- a/cmd/algoh/deadman.go +++ b/cmd/algoh/deadman.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/algoh/eventsender.go b/cmd/algoh/eventsender.go index 64ed53eae1..678dd203a2 100644 --- a/cmd/algoh/eventsender.go +++ b/cmd/algoh/eventsender.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/algoh/main.go b/cmd/algoh/main.go index 4e75ae837b..bcca7d5675 100644 --- a/cmd/algoh/main.go +++ b/cmd/algoh/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/algoh/mockClient.go b/cmd/algoh/mockClient.go index ce532a4f46..45e081e1e1 100644 --- a/cmd/algoh/mockClient.go +++ b/cmd/algoh/mockClient.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/algokey/common.go b/cmd/algokey/common.go index 9362fab74f..71b58498c9 100644 --- a/cmd/algokey/common.go +++ b/cmd/algokey/common.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/algokey/export.go b/cmd/algokey/export.go index 2b00275805..2966eabd10 100644 --- a/cmd/algokey/export.go +++ b/cmd/algokey/export.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/algokey/generate.go b/cmd/algokey/generate.go index a52011699d..4d42735854 100644 --- a/cmd/algokey/generate.go +++ b/cmd/algokey/generate.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/algokey/import.go b/cmd/algokey/import.go index 6f17cd06af..34f90ca2e0 100644 --- a/cmd/algokey/import.go +++ b/cmd/algokey/import.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/algokey/keyreg.go b/cmd/algokey/keyreg.go index 156697e7fc..02f18649bc 100644 --- a/cmd/algokey/keyreg.go +++ b/cmd/algokey/keyreg.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/algokey/main.go b/cmd/algokey/main.go index 450253e566..c04b73a3ca 100644 --- a/cmd/algokey/main.go +++ b/cmd/algokey/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/algokey/multisig.go b/cmd/algokey/multisig.go index b6d0bb108a..ef51d5dac1 100644 --- a/cmd/algokey/multisig.go +++ b/cmd/algokey/multisig.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/algokey/part.go b/cmd/algokey/part.go index 3fb7e49c21..034b9821a5 100644 --- a/cmd/algokey/part.go +++ b/cmd/algokey/part.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/algokey/sign.go b/cmd/algokey/sign.go index 14f14e58b5..3d8bf6dbcb 100644 --- a/cmd/algokey/sign.go +++ b/cmd/algokey/sign.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/algons/commands.go b/cmd/algons/commands.go index 522d0b8084..15469dd2b5 100644 --- a/cmd/algons/commands.go +++ b/cmd/algons/commands.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/algons/dnsCmd.go b/cmd/algons/dnsCmd.go index a1771414c1..10b4c18881 100644 --- a/cmd/algons/dnsCmd.go +++ b/cmd/algons/dnsCmd.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/algorelay/commands.go b/cmd/algorelay/commands.go index ebc0478544..8199c742c2 100644 --- a/cmd/algorelay/commands.go +++ b/cmd/algorelay/commands.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/algorelay/eb/eb.go b/cmd/algorelay/eb/eb.go index 7bd635ad75..c03ef85b1f 100644 --- a/cmd/algorelay/eb/eb.go +++ b/cmd/algorelay/eb/eb.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/algorelay/relayCmd.go b/cmd/algorelay/relayCmd.go index ff281313b6..0d97485757 100644 --- a/cmd/algorelay/relayCmd.go +++ b/cmd/algorelay/relayCmd.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/buildtools/commands.go b/cmd/buildtools/commands.go index 4f71804d30..92a73124cb 100644 --- a/cmd/buildtools/commands.go +++ b/cmd/buildtools/commands.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/buildtools/genesis.go b/cmd/buildtools/genesis.go index e1aab257ae..19712d4232 100644 --- a/cmd/buildtools/genesis.go +++ b/cmd/buildtools/genesis.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/catchpointdump/commands.go b/cmd/catchpointdump/commands.go index e06b0b79ab..82deb897f1 100644 --- a/cmd/catchpointdump/commands.go +++ b/cmd/catchpointdump/commands.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/catchpointdump/database.go b/cmd/catchpointdump/database.go index 227ff7a6ac..29c05e78d1 100644 --- a/cmd/catchpointdump/database.go +++ b/cmd/catchpointdump/database.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/catchpointdump/file.go b/cmd/catchpointdump/file.go index 97766ff971..79a426c0a5 100644 --- a/cmd/catchpointdump/file.go +++ b/cmd/catchpointdump/file.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -32,11 +32,11 @@ import ( "github.com/spf13/cobra" + "github.com/algorand/avm-abi/apps" cmdutil "github.com/algorand/go-algorand/cmd/util" "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/bookkeeping" - "github.com/algorand/go-algorand/data/transactions/logic" "github.com/algorand/go-algorand/ledger" "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/ledger/store" @@ -436,7 +436,7 @@ func printAccountsDatabase(databaseName string, stagingTables bool, fileHeader l func printKeyValue(writer *bufio.Writer, key, value []byte) { var pretty string - ai, rest, err := logic.SplitBoxKey(string(key)) + ai, rest, err := apps.SplitBoxKey(string(key)) if err == nil { pretty = fmt.Sprintf("box(%d, %s)", ai, base64.StdEncoding.EncodeToString([]byte(rest))) } else { diff --git a/cmd/catchpointdump/net.go b/cmd/catchpointdump/net.go index e63afb1871..d30cfc297d 100644 --- a/cmd/catchpointdump/net.go +++ b/cmd/catchpointdump/net.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/catchupsrv/download.go b/cmd/catchupsrv/download.go index 6a5880d42a..af4d7df6bf 100644 --- a/cmd/catchupsrv/download.go +++ b/cmd/catchupsrv/download.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/catchupsrv/download_test.go b/cmd/catchupsrv/download_test.go index b616f81f5a..f0f911ed13 100644 --- a/cmd/catchupsrv/download_test.go +++ b/cmd/catchupsrv/download_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/catchupsrv/main.go b/cmd/catchupsrv/main.go index 1f0de542aa..f89c69ad07 100644 --- a/cmd/catchupsrv/main.go +++ b/cmd/catchupsrv/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/catchupsrv/tarblocks.go b/cmd/catchupsrv/tarblocks.go index 25f686f0ca..d5957d5a21 100644 --- a/cmd/catchupsrv/tarblocks.go +++ b/cmd/catchupsrv/tarblocks.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/dbgen/main.go b/cmd/dbgen/main.go index f73809ed76..0bcc3e93c6 100644 --- a/cmd/dbgen/main.go +++ b/cmd/dbgen/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/diagcfg/main.go b/cmd/diagcfg/main.go index e2f1003d45..6953daf780 100644 --- a/cmd/diagcfg/main.go +++ b/cmd/diagcfg/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/diagcfg/messages.go b/cmd/diagcfg/messages.go index 70898a043e..45acf2e3df 100644 --- a/cmd/diagcfg/messages.go +++ b/cmd/diagcfg/messages.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/diagcfg/metric.go b/cmd/diagcfg/metric.go index 4fefd69b72..d5c44189a3 100644 --- a/cmd/diagcfg/metric.go +++ b/cmd/diagcfg/metric.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/diagcfg/telemetry.go b/cmd/diagcfg/telemetry.go index e49e0c2151..fac9bb46f4 100644 --- a/cmd/diagcfg/telemetry.go +++ b/cmd/diagcfg/telemetry.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/dispenser/server.go b/cmd/dispenser/server.go index 7890ab8f40..064d9f58e2 100644 --- a/cmd/dispenser/server.go +++ b/cmd/dispenser/server.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/genesis/newgenesis.go b/cmd/genesis/newgenesis.go index d86d67df3c..25d5f92d81 100644 --- a/cmd/genesis/newgenesis.go +++ b/cmd/genesis/newgenesis.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/goal/account.go b/cmd/goal/account.go index 6598422aa4..792d39b097 100644 --- a/cmd/goal/account.go +++ b/cmd/goal/account.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/goal/accountsList.go b/cmd/goal/accountsList.go index 366a132c69..390d3b2b07 100644 --- a/cmd/goal/accountsList.go +++ b/cmd/goal/accountsList.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/goal/application.go b/cmd/goal/application.go index e58598bcc3..e01e1fdee5 100644 --- a/cmd/goal/application.go +++ b/cmd/goal/application.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -31,6 +31,7 @@ import ( "github.com/spf13/cobra" "github.com/algorand/avm-abi/abi" + "github.com/algorand/avm-abi/apps" "github.com/algorand/go-algorand/crypto" apiclient "github.com/algorand/go-algorand/daemon/algod/api/client" "github.com/algorand/go-algorand/data/basics" @@ -198,8 +199,8 @@ func panicIfErr(err error) { } } -func newAppCallBytes(arg string) logic.AppCallBytes { - appBytes, err := logic.NewAppCallBytes(arg) +func newAppCallBytes(arg string) apps.AppCallBytes { + appBytes, err := apps.NewAppCallBytes(arg) if err != nil { reportErrorf(err.Error()) } @@ -207,16 +208,16 @@ func newAppCallBytes(arg string) logic.AppCallBytes { } type appCallInputs struct { - Accounts []string `codec:"accounts"` - ForeignApps []uint64 `codec:"foreignapps"` - ForeignAssets []uint64 `codec:"foreignassets"` - Boxes []boxRef `codec:"boxes"` - Args []logic.AppCallBytes `codec:"args"` + Accounts []string `codec:"accounts"` + ForeignApps []uint64 `codec:"foreignapps"` + ForeignAssets []uint64 `codec:"foreignassets"` + Boxes []boxRef `codec:"boxes"` + Args []apps.AppCallBytes `codec:"args"` } type boxRef struct { - appID uint64 `codec:"app"` - name logic.AppCallBytes `codec:"name"` + appID uint64 `codec:"app"` + name apps.AppCallBytes `codec:"name"` } // newBoxRef parses a command-line box ref, which is an optional appId, a comma, @@ -335,7 +336,7 @@ func processAppInputFile() (args [][]byte, accounts []string, foreignApps []uint return parseAppInputs(inputs) } -func getAppInputs() (args [][]byte, accounts []string, apps []uint64, assets []uint64, boxes []transactions.BoxRef) { +func getAppInputs() (args [][]byte, accounts []string, _ []uint64, assets []uint64, boxes []transactions.BoxRef) { if appInputFilename != "" { if appArgs != nil || appStrAccounts != nil || foreignApps != nil || foreignAssets != nil { reportErrorf("Cannot specify both command-line arguments/resources and JSON input filename") @@ -348,7 +349,7 @@ func getAppInputs() (args [][]byte, accounts []string, apps []uint64, assets []u // on it. appArgs became `StringArrayVar` in order to support abi arguments // which contain commas. - var encodedArgs []logic.AppCallBytes + var encodedArgs []apps.AppCallBytes for _, arg := range appArgs { if len(arg) > 0 { encodedArgs = append(encodedArgs, newAppCallBytes(arg)) diff --git a/cmd/goal/application_test.go b/cmd/goal/application_test.go index 7de23a5be0..687518fe0c 100644 --- a/cmd/goal/application_test.go +++ b/cmd/goal/application_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/goal/asset.go b/cmd/goal/asset.go index fdf5a61a20..97606c4cba 100644 --- a/cmd/goal/asset.go +++ b/cmd/goal/asset.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/goal/box.go b/cmd/goal/box.go index f603b043dc..4fd6495fa6 100644 --- a/cmd/goal/box.go +++ b/cmd/goal/box.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/goal/clerk.go b/cmd/goal/clerk.go index 84aef5e699..dce74a81d4 100644 --- a/cmd/goal/clerk.go +++ b/cmd/goal/clerk.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -422,7 +422,7 @@ var sendCmd = &cobra.Command{ CurrentProtocol: proto, }, } - groupCtx, err := verify.PrepareGroupContext([]transactions.SignedTxn{uncheckedTxn}, blockHeader, nil) + groupCtx, err := verify.PrepareGroupContext([]transactions.SignedTxn{uncheckedTxn}, &blockHeader, nil) if err == nil { err = verify.LogicSigSanityCheck(&uncheckedTxn, 0, groupCtx) } @@ -823,7 +823,7 @@ var signCmd = &cobra.Command{ } var groupCtx *verify.GroupContext if lsig.Logic != nil { - groupCtx, err = verify.PrepareGroupContext(txnGroup, contextHdr, nil) + groupCtx, err = verify.PrepareGroupContext(txnGroup, &contextHdr, nil) if err != nil { // this error has to be unsupported protocol reportErrorf("%s: %v", txFilename, err) @@ -962,7 +962,7 @@ func assembleFileImpl(fname string, printWarnings bool) *logic.OpStream { } ops, err := logic.AssembleString(string(text)) if err != nil { - ops.ReportProblems(fname, os.Stderr) + ops.ReportMultipleErrors(fname, os.Stderr) reportErrorf("%s: %s", fname, err) } _, params := getProto(protoVersion) diff --git a/cmd/goal/commands.go b/cmd/goal/commands.go index 5440c04ecf..ce17c9de48 100644 --- a/cmd/goal/commands.go +++ b/cmd/goal/commands.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/goal/commands_test.go b/cmd/goal/commands_test.go index 1d78642562..d83751331d 100644 --- a/cmd/goal/commands_test.go +++ b/cmd/goal/commands_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/goal/common.go b/cmd/goal/common.go index 893fcd176d..3c4189dbf1 100644 --- a/cmd/goal/common.go +++ b/cmd/goal/common.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/goal/completion.go b/cmd/goal/completion.go index 235b1a2387..418b1c8570 100644 --- a/cmd/goal/completion.go +++ b/cmd/goal/completion.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/goal/formatting.go b/cmd/goal/formatting.go index ed4b3116ee..9eebf3fbb8 100644 --- a/cmd/goal/formatting.go +++ b/cmd/goal/formatting.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/goal/formatting_test.go b/cmd/goal/formatting_test.go index 9915597d7d..f368c54c9a 100644 --- a/cmd/goal/formatting_test.go +++ b/cmd/goal/formatting_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -72,6 +72,7 @@ func TestNewBoxRef(t *testing.T) { } func TestStringsToBoxRefs(t *testing.T) { + partitiontest.PartitionTest(t) brs := stringsToBoxRefs([]string{"77,str:hello", "55,int:6", "int:88"}) require.EqualValues(t, 77, brs[0].appID) require.EqualValues(t, 55, brs[1].appID) diff --git a/cmd/goal/inspect.go b/cmd/goal/inspect.go index bdb11f434c..9f8c078d1c 100644 --- a/cmd/goal/inspect.go +++ b/cmd/goal/inspect.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/goal/inspect_test.go b/cmd/goal/inspect_test.go index 2e97444b38..7f4eb7e1c8 100644 --- a/cmd/goal/inspect_test.go +++ b/cmd/goal/inspect_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/goal/interact.go b/cmd/goal/interact.go index 8777163e7d..000b7e8459 100644 --- a/cmd/goal/interact.go +++ b/cmd/goal/interact.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -29,6 +29,7 @@ import ( "github.com/spf13/cobra" + "github.com/algorand/avm-abi/apps" "github.com/algorand/go-algorand/crypto" apiclient "github.com/algorand/go-algorand/daemon/algod/api/client" "github.com/algorand/go-algorand/data/basics" @@ -513,7 +514,7 @@ var appExecuteCmd = &cobra.Command{ var inputs appCallInputs for _, arg := range proc.Args { - var callArg logic.AppCallBytes + var callArg apps.AppCallBytes callArg.Encoding = arg.Kind if !procFlags.Changed(arg.Name) && arg.Default != "" { diff --git a/cmd/goal/kmd.go b/cmd/goal/kmd.go index fa4ef9182f..3e9901f5a3 100644 --- a/cmd/goal/kmd.go +++ b/cmd/goal/kmd.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/goal/ledger.go b/cmd/goal/ledger.go index d06cf32379..208655ad6e 100644 --- a/cmd/goal/ledger.go +++ b/cmd/goal/ledger.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/goal/logging.go b/cmd/goal/logging.go index a54b460318..eb05c73996 100644 --- a/cmd/goal/logging.go +++ b/cmd/goal/logging.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/goal/messages.go b/cmd/goal/messages.go index 3fd1ce36cd..8e4dcebf89 100644 --- a/cmd/goal/messages.go +++ b/cmd/goal/messages.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -55,39 +55,41 @@ const ( errorKMDFailedToStop = "Failed to stop kmd: %s" // Node - infoNodeStart = "Algorand node successfully started!" - infoNodeAlreadyStarted = "Algorand node was already started!" - infoNodeDidNotRestart = "Algorand node did not restart. The node is still running!" - infoTryingToStopNode = "Trying to stop the node..." - infoNodeShuttingDown = "Algorand node is shutting down..." - infoNodeSuccessfullyStopped = "The node was successfully stopped." - infoNodeStatus = "Last committed block: %d\nTime since last block: %s\nSync Time: %s\nLast consensus protocol: %s\nNext consensus protocol: %s\nRound for next consensus protocol: %d\nNext consensus protocol supported: %v" - catchupStoppedOnUnsupported = "Last supported block (%d) is committed. The next block consensus protocol is not supported. Catchup service is stopped." - infoNodeCatchpointCatchupStatus = "Last committed block: %d\nSync Time: %s\nCatchpoint: %s" - infoNodeCatchpointCatchupAccounts = "Catchpoint total accounts: %d\nCatchpoint accounts processed: %d\nCatchpoint accounts verified: %d\nCatchpoint total KVs: %d\nCatchpoint KVs processed: %d\nCatchpoint KVs verified: %d" - infoNodeCatchpointCatchupBlocks = "Catchpoint total blocks: %d\nCatchpoint downloaded blocks: %d" - nodeLastCatchpoint = "Last Catchpoint: %s" - errorNodeCreationIPFailure = "Parsing passed IP %v failed: need a valid IPv4 or IPv6 address with a specified port number" - errorNodeNotDetected = "Algorand node does not appear to be running: %s" - errorNodeStatus = "Cannot contact Algorand node: %s" - errorNodeFailedToStart = "Algorand node failed to start: %s" - errorNodeRunning = "Node must be stopped before writing APIToken" - errorNodeFailGenToken = "Cannot generate API token: %s" - errorNodeCreation = "Error during node creation: %v" - errorNodeManagedBySystemd = "This node is using systemd and should be managed with systemctl. For additional information refer to https://developer.algorand.org/docs/run-a-node/setup/install/#installing-algod-as-a-systemd-service" - errorKill = "Cannot kill node: %s" - errorCloningNode = "Error cloning the node: %s" - infoNodeCloned = "Node cloned successfully to: %s" - infoNodeWroteToken = "Successfully wrote new API token: %s" - infoNodePendingTxnsDescription = "Pending Transactions (Truncated max=%d, Total in pool=%d): " - infoNodeNoPendingTxnsDescription = "None" - infoDataDir = "[Data Directory: %s]" - errLoadingConfig = "Error loading Config file from '%s': %v" - errorNodeFailedToShutdown = "Unable to shut down node: %v" - errorCatchpointLabelParsingFailed = "The provided catchpoint is not a valid one" - errorCatchpointLabelMissing = "A catchpoint argument is needed: %s" - errorUnableToLookupCatchpointLabel = "Unable to fetch catchpoint label" - errorTooManyCatchpointLabels = "The catchup command expect a single catchpoint" + infoNodeStart = "Algorand node successfully started!" + infoNodeAlreadyStarted = "Algorand node was already started!" + infoNodeDidNotRestart = "Algorand node did not restart. The node is still running!" + infoTryingToStopNode = "Trying to stop the node..." + infoNodeShuttingDown = "Algorand node is shutting down..." + infoNodeSuccessfullyStopped = "The node was successfully stopped." + infoNodeStatus = "Last committed block: %d\nTime since last block: %s\nSync Time: %s\nLast consensus protocol: %s\nNext consensus protocol: %s\nRound for next consensus protocol: %d\nNext consensus protocol supported: %v" + infoNodeStatusConsensusUpgradeVoting = "Consensus upgrate state: Voting\nYes votes: %d\nNo votes: %d\nVotes remaining: %d\nYes votes required: %d\nVote window close round: %d" + infoNodeStatusConsensusUpgradeScheduled = "Consensus upgrade state: Scheduled" + catchupStoppedOnUnsupported = "Last supported block (%d) is committed. The next block consensus protocol is not supported. Catchup service is stopped." + infoNodeCatchpointCatchupStatus = "Last committed block: %d\nSync Time: %s\nCatchpoint: %s" + infoNodeCatchpointCatchupAccounts = "Catchpoint total accounts: %d\nCatchpoint accounts processed: %d\nCatchpoint accounts verified: %d\nCatchpoint total KVs: %d\nCatchpoint KVs processed: %d\nCatchpoint KVs verified: %d" + infoNodeCatchpointCatchupBlocks = "Catchpoint total blocks: %d\nCatchpoint downloaded blocks: %d" + nodeLastCatchpoint = "Last Catchpoint: %s" + errorNodeCreationIPFailure = "Parsing passed IP %v failed: need a valid IPv4 or IPv6 address with a specified port number" + errorNodeNotDetected = "Algorand node does not appear to be running: %s" + errorNodeStatus = "Cannot contact Algorand node: %s" + errorNodeFailedToStart = "Algorand node failed to start: %s" + errorNodeRunning = "Node must be stopped before writing APIToken" + errorNodeFailGenToken = "Cannot generate API token: %s" + errorNodeCreation = "Error during node creation: %v" + errorNodeManagedBySystemd = "This node is using systemd and should be managed with systemctl. For additional information refer to https://developer.algorand.org/docs/run-a-node/setup/install/#installing-algod-as-a-systemd-service" + errorKill = "Cannot kill node: %s" + errorCloningNode = "Error cloning the node: %s" + infoNodeCloned = "Node cloned successfully to: %s" + infoNodeWroteToken = "Successfully wrote new API token: %s" + infoNodePendingTxnsDescription = "Pending Transactions (Truncated max=%d, Total in pool=%d): " + infoNodeNoPendingTxnsDescription = "None" + infoDataDir = "[Data Directory: %s]" + errLoadingConfig = "Error loading Config file from '%s': %v" + errorNodeFailedToShutdown = "Unable to shut down node: %v" + errorCatchpointLabelParsingFailed = "The provided catchpoint is not a valid one" + errorCatchpointLabelMissing = "A catchpoint argument is needed: %s" + errorUnableToLookupCatchpointLabel = "Unable to fetch catchpoint label" + errorTooManyCatchpointLabels = "The catchup command expect a single catchpoint" // Asset malformedMetadataHash = "Cannot base64-decode metadata hash %s: %s" diff --git a/cmd/goal/multisig.go b/cmd/goal/multisig.go index 643b335d0e..5db3d09af9 100644 --- a/cmd/goal/multisig.go +++ b/cmd/goal/multisig.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -163,7 +163,7 @@ var signProgramCmd = &cobra.Command{ } ops, err := logic.AssembleString(string(text)) if err != nil { - ops.ReportProblems(programSource, os.Stderr) + ops.ReportMultipleErrors(programSource, os.Stderr) reportErrorf("%s: %s", programSource, err) } if outname == "" { diff --git a/cmd/goal/network.go b/cmd/goal/network.go index ff4b7005ba..35565fb6fa 100644 --- a/cmd/goal/network.go +++ b/cmd/goal/network.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/goal/node.go b/cmd/goal/node.go index a17551c475..fcfa27d6dc 100644 --- a/cmd/goal/node.go +++ b/cmd/goal/node.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -440,6 +440,7 @@ func makeStatusString(stat model.NodeStatusResponse) string { lastRoundTime := fmt.Sprintf("%.1fs", time.Duration(stat.TimeSinceLastRound).Seconds()) catchupTime := fmt.Sprintf("%.1fs", time.Duration(stat.CatchupTime).Seconds()) var statusString string + if stat.Catchpoint == nil || (*stat.Catchpoint) == "" { statusString = fmt.Sprintf( infoNodeStatus, @@ -458,6 +459,37 @@ func makeStatusString(stat model.NodeStatusResponse) string { if stat.StoppedAtUnsupportedRound { statusString = statusString + "\n" + fmt.Sprintf(catchupStoppedOnUnsupported, stat.LastRound) } + + upgradeNextProtocolVoteBefore := uint64(0) + if stat.UpgradeNextProtocolVoteBefore != nil { + upgradeNextProtocolVoteBefore = *stat.UpgradeNextProtocolVoteBefore + } + + if upgradeNextProtocolVoteBefore > stat.LastRound { + upgradeVotesRequired := uint64(0) + upgradeNoVotes := uint64(0) + upgradeYesVotes := uint64(0) + if stat.UpgradeVotesRequired != nil { + upgradeVotesRequired = *stat.UpgradeVotesRequired + } + if stat.UpgradeNoVotes != nil { + upgradeNoVotes = *stat.UpgradeNoVotes + } + if stat.UpgradeYesVotes != nil { + upgradeYesVotes = *stat.UpgradeYesVotes + } + statusString = statusString + "\n" + fmt.Sprintf( + infoNodeStatusConsensusUpgradeVoting, + upgradeYesVotes, + upgradeNoVotes, + upgradeNextProtocolVoteBefore-stat.LastRound, + upgradeVotesRequired, + upgradeNextProtocolVoteBefore, + ) + } else if upgradeNextProtocolVoteBefore > 0 { + statusString = statusString + "\n" + infoNodeStatusConsensusUpgradeScheduled + } + } else { statusString = fmt.Sprintf( infoNodeCatchpointCatchupStatus, diff --git a/cmd/goal/node_test.go b/cmd/goal/node_test.go index 3c35485bfc..aa3829cfbf 100644 --- a/cmd/goal/node_test.go +++ b/cmd/goal/node_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/goal/tealsign.go b/cmd/goal/tealsign.go index 5ff7617d06..33af6ca011 100644 --- a/cmd/goal/tealsign.go +++ b/cmd/goal/tealsign.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/goal/wallet.go b/cmd/goal/wallet.go index 90faa86c00..800d4b179b 100644 --- a/cmd/goal/wallet.go +++ b/cmd/goal/wallet.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/incorporate/incorporate.go b/cmd/incorporate/incorporate.go index 48a2ac8300..38631a101f 100644 --- a/cmd/incorporate/incorporate.go +++ b/cmd/incorporate/incorporate.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/kmd/codes/codes.go b/cmd/kmd/codes/codes.go index 12b0fb38f6..ecfe573529 100644 --- a/cmd/kmd/codes/codes.go +++ b/cmd/kmd/codes/codes.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/kmd/main.go b/cmd/kmd/main.go index 6d15aef482..92bfe62ac9 100644 --- a/cmd/kmd/main.go +++ b/cmd/kmd/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/kmd/mlock_darwin.go b/cmd/kmd/mlock_darwin.go index 14791e7fe4..5e6e75cccc 100644 --- a/cmd/kmd/mlock_darwin.go +++ b/cmd/kmd/mlock_darwin.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/kmd/mlock_linux.go b/cmd/kmd/mlock_linux.go index ff295866a1..c650ec15c7 100644 --- a/cmd/kmd/mlock_linux.go +++ b/cmd/kmd/mlock_linux.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/kmd/mlock_windows.go b/cmd/kmd/mlock_windows.go index 64e82811f5..20db381e2c 100644 --- a/cmd/kmd/mlock_windows.go +++ b/cmd/kmd/mlock_windows.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/loadgenerator/config.go b/cmd/loadgenerator/config.go index 8a3a13cdbf..5ffb86fd74 100644 --- a/cmd/loadgenerator/config.go +++ b/cmd/loadgenerator/config.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/loadgenerator/main.go b/cmd/loadgenerator/main.go index 54f1f5248e..6ef8cd2383 100644 --- a/cmd/loadgenerator/main.go +++ b/cmd/loadgenerator/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/msgpacktool/main.go b/cmd/msgpacktool/main.go index fac70fcec0..b6e359a0df 100644 --- a/cmd/msgpacktool/main.go +++ b/cmd/msgpacktool/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/netdummy/main.go b/cmd/netdummy/main.go index 92d96adcd5..cdc85e4167 100644 --- a/cmd/netdummy/main.go +++ b/cmd/netdummy/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/netgoal/commands.go b/cmd/netgoal/commands.go index 30522e87da..6fba6f8fa5 100644 --- a/cmd/netgoal/commands.go +++ b/cmd/netgoal/commands.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/netgoal/generate.go b/cmd/netgoal/generate.go index 0eed602bf6..88653df885 100644 --- a/cmd/netgoal/generate.go +++ b/cmd/netgoal/generate.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/netgoal/messages.go b/cmd/netgoal/messages.go index 1db5a08283..a575c47351 100644 --- a/cmd/netgoal/messages.go +++ b/cmd/netgoal/messages.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/netgoal/network.go b/cmd/netgoal/network.go index 4ebba13f1a..f0efdc2cd6 100644 --- a/cmd/netgoal/network.go +++ b/cmd/netgoal/network.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/netgoal/recipe.go b/cmd/netgoal/recipe.go index ba8af9b767..c1356b73f8 100644 --- a/cmd/netgoal/recipe.go +++ b/cmd/netgoal/recipe.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/nodecfg/apply.go b/cmd/nodecfg/apply.go index 77e302c7b6..689476537c 100644 --- a/cmd/nodecfg/apply.go +++ b/cmd/nodecfg/apply.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -34,6 +34,7 @@ var applyRootDir string var applyRootNodeDir string var applyPublicAddress string var nodeConfigBucket string +var disableDNS bool func init() { applyCmd.Flags().StringVarP(&applyChannel, "channel", "c", "", "Channel for the nodes we are configuring") @@ -49,6 +50,8 @@ func init() { applyCmd.Flags().StringVarP(&applyPublicAddress, "publicaddress", "a", "", "The public address to use if registering Relay or for Metrics") applyCmd.Flags().StringVarP(&nodeConfigBucket, "bucket", "b", "", "S3 bucket to get node configuration from.") + + applyCmd.Flags().BoolVarP(&disableDNS, "disable-dns", "N", false, "disable setting DNS entries") } var applyCmd = &cobra.Command{ @@ -122,7 +125,7 @@ func doApply(rootDir string, rootNodeDir, channel string, hostName string, dnsNa return fmt.Errorf("configuration does not include this host: %s", hostName) } - if hostNeedsDNSName(hostCfg) && dnsName == "" { + if hostNeedsDNSName(hostCfg) && dnsName == "" && !disableDNS { return fmt.Errorf("publicaddress is required - Host contains Relays or exposes Metrics") } diff --git a/cmd/nodecfg/commands.go b/cmd/nodecfg/commands.go index af3dc08a89..ef6b908185 100644 --- a/cmd/nodecfg/commands.go +++ b/cmd/nodecfg/commands.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/nodecfg/download.go b/cmd/nodecfg/download.go index 762c0ae869..fd59e0dff2 100644 --- a/cmd/nodecfg/download.go +++ b/cmd/nodecfg/download.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/nodecfg/get.go b/cmd/nodecfg/get.go index 707509f66a..6ca11c7363 100644 --- a/cmd/nodecfg/get.go +++ b/cmd/nodecfg/get.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/opdoc/opdoc.go b/cmd/opdoc/opdoc.go index 1a1e9941d1..e0b69fa111 100644 --- a/cmd/opdoc/opdoc.go +++ b/cmd/opdoc/opdoc.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -270,14 +270,20 @@ func typeString(types []logic.StackType) string { case logic.StackAny: out[i] = '.' case logic.StackNone: - if i == 0 && len(types) == 1 { - return "" - } - panic("unexpected StackNone in opdoc typeString") + out[i] = '_' default: panic("unexpected type in opdoc typeString") } } + + // Cant return None and !None from same op + if strings.Contains(string(out), "_") { + if strings.ContainsAny(string(out), "UB.") { + panic("unexpected StackNone in opdoc typeString") + } + return "" + } + return string(out) } @@ -295,6 +301,9 @@ func fieldsAndTypes(group logic.FieldGroup) ([]string, string) { } func argEnums(name string) ([]string, string) { + // reminder: this needs to be manually updated every time + // a new opcode is added with an associated FieldGroup + // it'd be nice to have this auto-update switch name { case "txn", "gtxn", "gtxns", "itxn", "gitxn": return fieldsAndTypes(logic.TxnFields) @@ -314,6 +323,16 @@ func argEnums(name string) ([]string, string) { return fieldsAndTypes(logic.AppParamsFields) case "acct_params_get": return fieldsAndTypes(logic.AcctParamsFields) + case "block": + return fieldsAndTypes(logic.BlockFields) + case "json_ref": + return fieldsAndTypes(logic.JSONRefTypes) + case "base64_decode": + return fieldsAndTypes(logic.Base64Encodings) + case "vrf_verify": + return fieldsAndTypes(logic.VrfStandards) + case "ecdsa_pk_recover", "ecdsa_verify", "ecdsa_pk_decompress": + return fieldsAndTypes(logic.EcdsaCurves) default: return nil, "" } diff --git a/cmd/opdoc/tmLanguage.go b/cmd/opdoc/tmLanguage.go index 6501babc06..a24c3dfd8f 100644 --- a/cmd/opdoc/tmLanguage.go +++ b/cmd/opdoc/tmLanguage.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/partitiontest_linter/go.mod b/cmd/partitiontest_linter/go.mod index 3e9db30e99..57f9fbb1fc 100644 --- a/cmd/partitiontest_linter/go.mod +++ b/cmd/partitiontest_linter/go.mod @@ -2,7 +2,8 @@ module github.com/algorand/go-algorand/cmd/partitiontest_linter go 1.17 -require golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 -require golang.org/x/sys v0.0.0-20210510120138-977fb7262007 -require golang.org/x/mod v0.4.2 -require golang.org/x/tools v0.1.3 +require golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect + +require golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect + +require golang.org/x/tools v0.1.12 diff --git a/cmd/partitiontest_linter/go.sum b/cmd/partitiontest_linter/go.sum index e652f3edc8..4965383eee 100644 --- a/cmd/partitiontest_linter/go.sum +++ b/cmd/partitiontest_linter/go.sum @@ -1,27 +1,6 @@ -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.3 h1:L69ShwSZEyCsLKoAxDKeMvLDZkumEe8gXUZAjab0tX8= -golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -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-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= diff --git a/cmd/partitiontest_linter/linter.go b/cmd/partitiontest_linter/linter.go index 8d5ab728de..50c1298a09 100644 --- a/cmd/partitiontest_linter/linter.go +++ b/cmd/partitiontest_linter/linter.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -124,7 +124,7 @@ func doesParameterNameMatch(call *ast.CallExpr, fn *ast.FuncDecl) bool { for _, oneArg := range call.Args { if realArg, ok := oneArg.(*ast.Ident); ok { - if realArg.Obj.Name == fn.Type.Params.List[0].Names[0].Obj.Name { + if realArg != nil && realArg.Obj != nil && realArg.Obj.Name == fn.Type.Params.List[0].Names[0].Obj.Name { return true } } diff --git a/cmd/partitiontest_linter/linter_test.go b/cmd/partitiontest_linter/linter_test.go index d62fcef73b..26a3e85be9 100644 --- a/cmd/partitiontest_linter/linter_test.go +++ b/cmd/partitiontest_linter/linter_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/partitiontest_linter/plugin/plugin.go b/cmd/partitiontest_linter/plugin/plugin.go index 587529feac..ee229371f2 100644 --- a/cmd/partitiontest_linter/plugin/plugin.go +++ b/cmd/partitiontest_linter/plugin/plugin.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/partitiontest_linter/testdata/linter_testdata_test.go b/cmd/partitiontest_linter/testdata/linter_testdata_test.go index 3658e6211f..d84cb837c2 100644 --- a/cmd/partitiontest_linter/testdata/linter_testdata_test.go +++ b/cmd/partitiontest_linter/testdata/linter_testdata_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/pingpong/commands.go b/cmd/pingpong/commands.go index 71733f22f8..7e78eedb03 100644 --- a/cmd/pingpong/commands.go +++ b/cmd/pingpong/commands.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/pingpong/runCmd.go b/cmd/pingpong/runCmd.go index 4e516423cd..7813d4f313 100644 --- a/cmd/pingpong/runCmd.go +++ b/cmd/pingpong/runCmd.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -90,6 +90,7 @@ var generatedAccountsCount uint64 var generatedAccountsOffset uint64 var generatedAccountSampleMethod string var configPath string +var latencyPath string func init() { rootCmd.AddCommand(runCmd) @@ -111,6 +112,7 @@ func init() { runCmd.Flags().StringVar(&refreshTime, "refresh", "", "Duration of time (seconds) between refilling accounts with money (0 means no refresh)") runCmd.Flags().StringVar(&logicProg, "program", "", "File containing the compiled program to include as a logic sig") runCmd.Flags().StringVar(&configPath, "config", "", "path to read config json from, or json literal") + runCmd.Flags().StringVar(&latencyPath, "latency", "", "path to write txn latency log to (.gz for compressed)") runCmd.Flags().BoolVar(&saveConfig, "save", false, "Save the effective configuration to disk") runCmd.Flags().BoolVar(&useDefault, "reset", false, "Reset to the default configuration (not read from disk)") runCmd.Flags().BoolVar(&quietish, "quiet", false, "quietish stdout logging") @@ -316,7 +318,7 @@ var runCmd = &cobra.Command{ } ops, err := logic.AssembleString(programStr) if err != nil { - ops.ReportProblems(teal, os.Stderr) + ops.ReportMultipleErrors(teal, os.Stderr) reportErrorf("Internal error, cannot assemble %v \n", programStr) } cfg.Program = ops.Program @@ -440,6 +442,10 @@ var runCmd = &cobra.Command{ reportErrorf("numAccounts is greater than number of account mnemonics provided") } + if latencyPath != "" { + cfg.TotalLatencyOut = latencyPath + } + cfg.SetDefaultWeights() err = cfg.Check() if err != nil { diff --git a/cmd/pingpong/teal_programs.go b/cmd/pingpong/teal_programs.go index e565842114..150877188b 100644 --- a/cmd/pingpong/teal_programs.go +++ b/cmd/pingpong/teal_programs.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/tealdbg/README.md b/cmd/tealdbg/README.md index b5a7bebbf5..14967677f0 100644 --- a/cmd/tealdbg/README.md +++ b/cmd/tealdbg/README.md @@ -9,6 +9,7 @@ - [Protocol](#protocol) - [Transaction and Transaction Group](#transaction-and-transaction-group) - [Balance records](#balance-records) + - [Indexer Support](#indexer-support) - [Execution mode](#execution-mode) - [Chrome DevTools Frontend Features](#chrome-devtools-frontend-features) - [Configure the Listener](#configure-the-listener) @@ -44,7 +45,7 @@ and balance records (see [Setting Debug Context](#setting-debug-context) for det Remote debugger might be useful for debugging unit tests for TEAL (currently in Golang only) or for hacking **algod** `eval` and breaking on any TEAL evaluation. The protocol consist of three REST endpoints and one data structure describing the evaluator state. -See `WebDebuggerHook` and `TestWebDebuggerManual` in [go-algorand sources](https://github.com/algorand/go-algorand/tree/master/data/transactions/logic) for more details. +See `WebDebugger` and `TestWebDebuggerManual` in [go-algorand sources](https://github.com/algorand/go-algorand/tree/master/data/transactions/logic) for more details. ### Frontends @@ -206,13 +207,18 @@ Refer to the [Chrome DevTools debugging](https://developers.google.com/web/tools The evaluator accepts a new `Debugger` parameter described as the interface: ```golang -type DebuggerHook interface { +// Debugger is an interface that supports the first version of AVM debuggers. +// It consists of a set of functions called by eval function during AVM program execution. +// +// Deprecated: This interface does not support non-app call or inner transactions. Use EvalTracer +// instead. +type Debugger interface { // Register is fired on program creation - Register(state *DebugState) error + Register(state *DebugState) // Update is fired on every step - Update(state *DebugState) error + Update(state *DebugState) // Complete is called when the program exits - Complete(state *DebugState) error + Complete(state *DebugState) } ``` If `Debugger` is set the evaluator calls `Register` on creation, `Update` on every step and `Complete` on exit. @@ -251,13 +257,13 @@ The core calls `SessionEnded` on `Complete` call. If one needs to debug TEAL in as much real environment as possible then do -1. Add `WebDebuggerHook` to `data/transactions/logic/eval.go`: +1. Add `WebDebugger` to `data/transactions/logic/eval.go`: ```golang cx.program = program // begin new code debugURL := os.Getenv("TEAL_DEBUGGER_URL") - cx.Debugger = &WebDebuggerHook{URL: debugURL} + cx.Debugger = &WebDebugger{URL: debugURL} // end new code if cx.Debugger != nil { diff --git a/cmd/tealdbg/bundle_home_html.sh b/cmd/tealdbg/bundle_home_html.sh index 0950954209..8c83a4aaab 100755 --- a/cmd/tealdbg/bundle_home_html.sh +++ b/cmd/tealdbg/bundle_home_html.sh @@ -3,7 +3,7 @@ THISDIR=$(dirname $0) cat < $THISDIR/homepage.go -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/tealdbg/cdt/proto.go b/cmd/tealdbg/cdt/proto.go index ad527fc09d..dd28fcd829 100644 --- a/cmd/tealdbg/cdt/proto.go +++ b/cmd/tealdbg/cdt/proto.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/tealdbg/cdtSession.go b/cmd/tealdbg/cdtSession.go index 3ae5675b33..f7b74eb9ec 100644 --- a/cmd/tealdbg/cdtSession.go +++ b/cmd/tealdbg/cdtSession.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/tealdbg/cdtSession_test.go b/cmd/tealdbg/cdtSession_test.go index 1668c1a03a..89a2ce1b04 100644 --- a/cmd/tealdbg/cdtSession_test.go +++ b/cmd/tealdbg/cdtSession_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/tealdbg/cdtState.go b/cmd/tealdbg/cdtState.go index 21273982fd..2ee335a21b 100644 --- a/cmd/tealdbg/cdtState.go +++ b/cmd/tealdbg/cdtState.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/tealdbg/cdtStateObjects.go b/cmd/tealdbg/cdtStateObjects.go index b6daf39f1a..377492d23c 100644 --- a/cmd/tealdbg/cdtStateObjects.go +++ b/cmd/tealdbg/cdtStateObjects.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/tealdbg/cdtdbg.go b/cmd/tealdbg/cdtdbg.go index fd88bf92e0..45de17d3eb 100644 --- a/cmd/tealdbg/cdtdbg.go +++ b/cmd/tealdbg/cdtdbg.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/tealdbg/cdtdbg_test.go b/cmd/tealdbg/cdtdbg_test.go index 41164a3af3..a4e8177359 100644 --- a/cmd/tealdbg/cdtdbg_test.go +++ b/cmd/tealdbg/cdtdbg_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/tealdbg/debugger.go b/cmd/tealdbg/debugger.go index b9b8995609..3639a41afe 100644 --- a/cmd/tealdbg/debugger.go +++ b/cmd/tealdbg/debugger.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -26,6 +26,7 @@ import ( "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/transactions/logic" + "github.com/algorand/go-algorand/logging" ) // Notification is sent to the client over their websocket connection @@ -528,7 +529,7 @@ func (d *Debugger) SaveProgram( } // Register setups new session and notifies frontends if any -func (d *Debugger) Register(state *logic.DebugState) error { +func (d *Debugger) Register(state *logic.DebugState) { sid := state.ExecID pcOffset := make(map[int]int, len(state.PCOffset)) for _, pco := range state.PCOffset { @@ -556,12 +557,17 @@ func (d *Debugger) Register(state *logic.DebugState) error { // Wait for acknowledgement <-s.acknowledged - - return nil } // Update process state update notifications: pauses or continues as needed -func (d *Debugger) Update(state *logic.DebugState) error { +func (d *Debugger) Update(state *logic.DebugState) { + err := d.update(state) + if err != nil { + logging.Base().Errorf("error in Update hook: %s", err.Error()) + } +} + +func (d *Debugger) update(state *logic.DebugState) error { sid := state.ExecID s, err := d.getSession(sid) if err != nil { @@ -596,7 +602,14 @@ func (d *Debugger) Update(state *logic.DebugState) error { } // Complete terminates session and notifies frontends if any -func (d *Debugger) Complete(state *logic.DebugState) error { +func (d *Debugger) Complete(state *logic.DebugState) { + err := d.complete(state) + if err != nil { + logging.Base().Errorf("error in Complete hook: %s", err.Error()) + } +} + +func (d *Debugger) complete(state *logic.DebugState) error { sid := state.ExecID s, err := d.getSession(sid) if err != nil { diff --git a/cmd/tealdbg/debugger_test.go b/cmd/tealdbg/debugger_test.go index 4f4b35ea20..a73fb3f4f7 100644 --- a/cmd/tealdbg/debugger_test.go +++ b/cmd/tealdbg/debugger_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -102,7 +102,7 @@ func TestDebuggerSimple(t *testing.T) { debugger.AddAdapter(da) ep := logic.NewEvalParams(make([]transactions.SignedTxnWithAD, 1), &proto, nil) - ep.Debugger = debugger + ep.Tracer = logic.MakeEvalTracerDebuggerAdaptor(debugger) ep.SigLedger = logic.NoHeaderLedger{} source := `int 0 diff --git a/cmd/tealdbg/dryrunRequest.go b/cmd/tealdbg/dryrunRequest.go index b6973cec95..5f13aa6cfe 100644 --- a/cmd/tealdbg/dryrunRequest.go +++ b/cmd/tealdbg/dryrunRequest.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -47,23 +47,6 @@ func ddrFromParams(dp *DebugParams) (ddr v2.DryrunRequest, err error) { return } -func convertAccounts(accounts []model.Account) (records []basics.BalanceRecord, err error) { - for _, a := range accounts { - var addr basics.Address - addr, err = basics.UnmarshalChecksumAddress(a.Address) - if err != nil { - return - } - var ad basics.AccountData - ad, err = v2.AccountToAccountData(&a) - if err != nil { - return - } - records = append(records, basics.BalanceRecord{Addr: addr, AccountData: ad}) - } - return -} - func balanceRecordsFromDdr(ddr *v2.DryrunRequest) (records []basics.BalanceRecord, err error) { accounts := make(map[basics.Address]basics.AccountData) for _, a := range ddr.Accounts { diff --git a/cmd/tealdbg/homepage.go b/cmd/tealdbg/homepage.go index 4af43c4c8f..7e77ed291d 100644 --- a/cmd/tealdbg/homepage.go +++ b/cmd/tealdbg/homepage.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/tealdbg/local.go b/cmd/tealdbg/local.go index fa3c5d6fc5..8e9d9b81fc 100644 --- a/cmd/tealdbg/local.go +++ b/cmd/tealdbg/local.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -536,7 +536,7 @@ func (r *LocalRunner) RunAll() error { // ep.Debugger = r.debugger // if ep.Debugger != nil // FALSE if r.debugger != nil { - ep.Debugger = r.debugger + ep.Tracer = logic.MakeEvalTracerDebuggerAdaptor(r.debugger) } } diff --git a/cmd/tealdbg/localLedger.go b/cmd/tealdbg/localLedger.go index b045466dfc..c76039c7b3 100644 --- a/cmd/tealdbg/localLedger.go +++ b/cmd/tealdbg/localLedger.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/tealdbg/localLedger_test.go b/cmd/tealdbg/localLedger_test.go index 3dad5508cd..cf53b53d84 100644 --- a/cmd/tealdbg/localLedger_test.go +++ b/cmd/tealdbg/localLedger_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/tealdbg/local_test.go b/cmd/tealdbg/local_test.go index f39c9da8a0..22fee98f60 100644 --- a/cmd/tealdbg/local_test.go +++ b/cmd/tealdbg/local_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/tealdbg/main.go b/cmd/tealdbg/main.go index 75e437c149..461ed0d03c 100644 --- a/cmd/tealdbg/main.go +++ b/cmd/tealdbg/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/tealdbg/remote.go b/cmd/tealdbg/remote.go index 05b468979c..0656e418df 100644 --- a/cmd/tealdbg/remote.go +++ b/cmd/tealdbg/remote.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -26,7 +26,7 @@ import ( "github.com/algorand/go-algorand/protocol" ) -// RemoteHookAdapter provides HTTP transport for WebDebuggerHook +// RemoteHookAdapter provides HTTP transport for WebDebugger type RemoteHookAdapter struct { debugger *Debugger } @@ -38,7 +38,7 @@ func MakeRemoteHook(debugger *Debugger) *RemoteHookAdapter { return r } -// Setup adds HTTP handlers for remote WebDebuggerHook +// Setup adds HTTP handlers for remote WebDebugger func (rha *RemoteHookAdapter) Setup(router *mux.Router) { router.HandleFunc("/exec/register", rha.registerHandler).Methods("POST") router.HandleFunc("/exec/update", rha.updateHandler).Methods("POST") @@ -59,11 +59,7 @@ func (rha *RemoteHookAdapter) registerHandler(w http.ResponseWriter, r *http.Req } // Register, and wait for user to acknowledge registration - err = rha.debugger.Register(&state) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - return - } + rha.debugger.Register(&state) // Proceed! w.WriteHeader(http.StatusOK) @@ -78,7 +74,7 @@ func (rha *RemoteHookAdapter) updateHandler(w http.ResponseWriter, r *http.Reque } // Ask debugger to process and wait to continue - err = rha.debugger.Update(&state) + err = rha.debugger.update(&state) if err != nil { w.WriteHeader(http.StatusBadRequest) return @@ -96,7 +92,7 @@ func (rha *RemoteHookAdapter) completeHandler(w http.ResponseWriter, r *http.Req } // Ask debugger to process and wait to continue - err = rha.debugger.Complete(&state) + err = rha.debugger.complete(&state) if err != nil { w.WriteHeader(http.StatusBadRequest) return diff --git a/cmd/tealdbg/remote_test.go b/cmd/tealdbg/remote_test.go index 5ac654cd75..66d077981d 100644 --- a/cmd/tealdbg/remote_test.go +++ b/cmd/tealdbg/remote_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/tealdbg/server.go b/cmd/tealdbg/server.go index cf062af627..695cb030df 100644 --- a/cmd/tealdbg/server.go +++ b/cmd/tealdbg/server.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/tealdbg/server_test.go b/cmd/tealdbg/server_test.go index a91be71819..81c6c28370 100644 --- a/cmd/tealdbg/server_test.go +++ b/cmd/tealdbg/server_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/tealdbg/util.go b/cmd/tealdbg/util.go index d611c7bcb1..d91220e71c 100644 --- a/cmd/tealdbg/util.go +++ b/cmd/tealdbg/util.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/tealdbg/webdbg.go b/cmd/tealdbg/webdbg.go index fe6058d9b3..d237d70afc 100644 --- a/cmd/tealdbg/webdbg.go +++ b/cmd/tealdbg/webdbg.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/tealdbg/webdbg_test.go b/cmd/tealdbg/webdbg_test.go index be6801d636..d45ac9f217 100644 --- a/cmd/tealdbg/webdbg_test.go +++ b/cmd/tealdbg/webdbg_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/updater/commands.go b/cmd/updater/commands.go index 03d2f2a48d..cc152ff5d6 100644 --- a/cmd/updater/commands.go +++ b/cmd/updater/commands.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/updater/sendCmd.go b/cmd/updater/sendCmd.go index d3a4a0cd9d..d194d8ca9b 100644 --- a/cmd/updater/sendCmd.go +++ b/cmd/updater/sendCmd.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/updater/toolsCmd.go b/cmd/updater/toolsCmd.go index d236442520..4ebaa8b4ae 100644 --- a/cmd/updater/toolsCmd.go +++ b/cmd/updater/toolsCmd.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/updater/update.sh b/cmd/updater/update.sh index cc4403ffd1..94a4b2ba26 100755 --- a/cmd/updater/update.sh +++ b/cmd/updater/update.sh @@ -334,7 +334,7 @@ function check_for_updater() { function check_for_update() { determine_current_version check_for_updater - LATEST="$(${SCRIPTPATH}/updater ver check -c ${CHANNEL} ${BUCKET} | sed -n '2 p')" + LATEST="$(${SCRIPTPATH}/updater ver check -c ${CHANNEL} ${BUCKET} | tail -1)" if [ $? -ne 0 ]; then echo "No remote updates found" return 1 diff --git a/cmd/updater/util.go b/cmd/updater/util.go index 0bb03baa04..14fa62cf54 100644 --- a/cmd/updater/util.go +++ b/cmd/updater/util.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/updater/versionCmd.go b/cmd/updater/versionCmd.go index aeb24a7a97..35713f9b39 100644 --- a/cmd/updater/versionCmd.go +++ b/cmd/updater/versionCmd.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/updater/version_test.go b/cmd/updater/version_test.go index 34f23499e5..a29658d537 100644 --- a/cmd/updater/version_test.go +++ b/cmd/updater/version_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/util/cmd.go b/cmd/util/cmd.go index e38972e9c2..3b8b1c8841 100644 --- a/cmd/util/cmd.go +++ b/cmd/util/cmd.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/components/mocks/mockCatchpointCatchupAccessor.go b/components/mocks/mockCatchpointCatchupAccessor.go index 00758de72d..0b45f42be6 100644 --- a/components/mocks/mockCatchpointCatchupAccessor.go +++ b/components/mocks/mockCatchpointCatchupAccessor.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/components/mocks/mockNetwork.go b/components/mocks/mockNetwork.go index 77e5f58780..8c8eb113f1 100644 --- a/components/mocks/mockNetwork.go +++ b/components/mocks/mockNetwork.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/components/mocks/mockParticipationRegistry.go b/components/mocks/mockParticipationRegistry.go index d7a53c36f0..6af12ef1dd 100644 --- a/components/mocks/mockParticipationRegistry.go +++ b/components/mocks/mockParticipationRegistry.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/config/buildvars.go b/config/buildvars.go index a9a67d584a..af0bfe523e 100644 --- a/config/buildvars.go +++ b/config/buildvars.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/config/config.go b/config/config.go index c1c9893ca6..a180a9a479 100644 --- a/config/config.go +++ b/config/config.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -77,6 +77,10 @@ const ParticipationRegistryFilename = "partregistry.sqlite" // built-in supported consensus protocols. const ConfigurableConsensusProtocolsFilename = "consensus.json" +// The default gossip fanout setting when configured as a relay (here, as we +// do not expose in normal config so it is not in code generated local_defaults.go +const defaultRelayGossipFanout = 8 + // LoadConfigFromDisk returns a Local config structure based on merging the defaults // with settings loaded from the config file from the custom dir. If the custom file // cannot be loaded, the default config is returned (with the error from loading the @@ -124,6 +128,12 @@ func mergeConfigFromFile(configpath string, source Local) (Local, error) { source.Archival = true source.EnableLedgerService = true source.EnableBlockService = true + + // If gossip fanout has not been explicitly overridden, use defaultRelayGossipFanout + // rather then the default gossip fanout setting from defaultLocal + if source.GossipFanout == defaultLocal.GossipFanout { + source.GossipFanout = defaultRelayGossipFanout + } } return source, err diff --git a/config/config_test.go b/config/config_test.go index db35269ad6..c2ee070fc4 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -582,6 +582,7 @@ func TestGetNonDefaultConfigValues(t *testing.T) { } func TestLocal_TxFiltering(t *testing.T) { + partitiontest.PartitionTest(t) cfg := GetDefaultLocal() // ensure the default diff --git a/config/consensus.go b/config/consensus.go index bd67aa3404..4f1ac86736 100644 --- a/config/consensus.go +++ b/config/consensus.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/config/consensus_test.go b/config/consensus_test.go index dd77ed499a..98b5e874c7 100644 --- a/config/consensus_test.go +++ b/config/consensus_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/config/defaultsGenerator/defaultsGenerator.go b/config/defaultsGenerator/defaultsGenerator.go index 4ee4a1671a..9c3365f895 100644 --- a/config/defaultsGenerator/defaultsGenerator.go +++ b/config/defaultsGenerator/defaultsGenerator.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/config/keyfile.go b/config/keyfile.go index 28aea1f221..0f158e1fda 100644 --- a/config/keyfile.go +++ b/config/keyfile.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/config/localTemplate.go b/config/localTemplate.go index 6be2f0e1a4..7caa5e9dee 100644 --- a/config/localTemplate.go +++ b/config/localTemplate.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -41,7 +41,7 @@ type Local struct { // Version tracks the current version of the defaults so we can migrate old -> new // This is specifically important whenever we decide to change the default value // for an existing parameter. This field tag must be updated any time we add a new version. - Version uint32 `version[0]:"0" version[1]:"1" version[2]:"2" version[3]:"3" version[4]:"4" version[5]:"5" version[6]:"6" version[7]:"7" version[8]:"8" version[9]:"9" version[10]:"10" version[11]:"11" version[12]:"12" version[13]:"13" version[14]:"14" version[15]:"15" version[16]:"16" version[17]:"17" version[18]:"18" version[19]:"19" version[20]:"20" version[21]:"21" version[22]:"22" version[23]:"23" version[24]:"24" version[25]:"25" version[26]:"26"` + Version uint32 `version[0]:"0" version[1]:"1" version[2]:"2" version[3]:"3" version[4]:"4" version[5]:"5" version[6]:"6" version[7]:"7" version[8]:"8" version[9]:"9" version[10]:"10" version[11]:"11" version[12]:"12" version[13]:"13" version[14]:"14" version[15]:"15" version[16]:"16" version[17]:"17" version[18]:"18" version[19]:"19" version[20]:"20" version[21]:"21" version[22]:"22" version[23]:"23" version[24]:"24" version[25]:"25" version[26]:"26" version[27]:"27"` // environmental (may be overridden) // When enabled, stores blocks indefinitely, otherwise, only the most recent blocks @@ -59,7 +59,7 @@ type Local struct { // what we should tell people to connect to PublicAddress string `version[0]:""` - MaxConnectionsPerIP int `version[3]:"30"` + MaxConnectionsPerIP int `version[3]:"30" version[27]:"15"` // 0 == disable PeerPingPeriodSeconds int `version[0]:"0"` @@ -72,11 +72,12 @@ type Local struct { BaseLoggerDebugLevel uint32 `version[0]:"1" version[1]:"4"` // if this is 0, do not produce agreement.cadaver CadaverSizeTarget uint64 `version[0]:"1073741824" version[24]:"0"` + CadaverDirectory string `version[27]:""` // IncomingConnectionsLimit specifies the max number of long-lived incoming // connections. 0 means no connections allowed. Must be non-negative. - // Estimating 5MB per incoming connection, 5MB*800 = 4GB - IncomingConnectionsLimit int `version[0]:"-1" version[1]:"10000" version[17]:"800"` + // Estimating 1.5MB per incoming connection, 1.5MB*2400 = 3.6GB + IncomingConnectionsLimit int `version[0]:"-1" version[1]:"10000" version[17]:"800" version[27]:"2400"` // BroadcastConnectionsLimit specifies the number of connections that // will receive broadcast (gossip) messages from this node. If the @@ -161,6 +162,20 @@ type Local struct { SuggestedFeeBlockHistory int `version[0]:"3"` + // TxBacklogServiceRateWindowSeconds is the window size used to determine the service rate of the txBacklog + TxBacklogServiceRateWindowSeconds int `version[27]:"10"` + + // TxBacklogReservedCapacityPerPeer determines how much dedicated serving capacity the TxBacklog gives each peer + TxBacklogReservedCapacityPerPeer int `version[27]:"20"` + + // EnableTxBacklogRateLimiting controls if a rate limiter and congestion manager shouild be attached to the tx backlog enqueue process + // if enabled, the over-all TXBacklog Size will be larger by MAX_PEERS*TxBacklogReservedCapacityPerPeer + EnableTxBacklogRateLimiting bool `version[27]:"false"` + + // TxBacklogSize is the queue size used for receiving transactions. default of 26000 to approximate 1 block of transactions + // if EnableTxBacklogRateLimiting enabled, the over-all size will be larger by MAX_PEERS*TxBacklogReservedCapacityPerPeer + TxBacklogSize int `version[27]:"26000"` + // TxPoolSize is the number of transactions that fit in the transaction pool TxPoolSize int `version[0]:"50000" version[5]:"15000" version[23]:"75000"` @@ -247,6 +262,10 @@ type Local struct { // telemetry ( when enabled ). Defined in seconds. PeerConnectionsUpdateInterval int `version[5]:"3600"` + // HeartbeatUpdateInterval defines the interval at which the heartbeat information is being sent to the + // telemetry ( when enabled ). Defined in seconds. Minimum value is 60. + HeartbeatUpdateInterval int `version[27]:"600"` + // EnableProfiler enables the go pprof endpoints, should be false if // the algod api will be exposed to untrusted individuals EnableProfiler bool `version[0]:"false"` @@ -441,13 +460,13 @@ type Local struct { MaxAPIResourcesPerAccount uint64 `version[21]:"100000"` // AgreementIncomingVotesQueueLength sets the size of the buffer holding incoming votes. - AgreementIncomingVotesQueueLength uint64 `version[21]:"10000"` + AgreementIncomingVotesQueueLength uint64 `version[21]:"10000" version[27]:"20000"` // AgreementIncomingProposalsQueueLength sets the size of the buffer holding incoming proposals. - AgreementIncomingProposalsQueueLength uint64 `version[21]:"25"` + AgreementIncomingProposalsQueueLength uint64 `version[21]:"25" version[27]:"50"` // AgreementIncomingBundlesQueueLength sets the size of the buffer holding incoming bundles. - AgreementIncomingBundlesQueueLength uint64 `version[21]:"7"` + AgreementIncomingBundlesQueueLength uint64 `version[21]:"7" version[27]:"15"` // MaxAcctLookback sets the maximum lookback range for account states, // i.e. the ledger can answer account states questions for the range Latest-MaxAcctLookback...Latest @@ -467,6 +486,10 @@ type Local struct { // 0x01 (txFilterRawMsg) - check for raw tx message duplicates // 0x02 (txFilterCanonical) - check for canonical tx group duplicates TxIncomingFilteringFlags uint32 `version[26]:"1"` + + // EnableExperimentalAPI enables experimental API endpoint. Note that these endpoints have no + // guarantees in terms of functionality or future support. + EnableExperimentalAPI bool `version[26]:"false"` } // DNSBootstrapArray returns an array of one or more DNS Bootstrap identifiers diff --git a/config/local_defaults.go b/config/local_defaults.go index 412c4a3dd2..18604556f6 100644 --- a/config/local_defaults.go +++ b/config/local_defaults.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -20,17 +20,18 @@ package config var defaultLocal = Local{ - Version: 26, + Version: 27, AccountUpdatesStatsInterval: 5000000000, AccountsRebuildSynchronousMode: 1, - AgreementIncomingBundlesQueueLength: 7, - AgreementIncomingProposalsQueueLength: 25, - AgreementIncomingVotesQueueLength: 10000, + AgreementIncomingBundlesQueueLength: 15, + AgreementIncomingProposalsQueueLength: 50, + AgreementIncomingVotesQueueLength: 20000, AnnounceParticipationKey: true, Archival: false, BaseLoggerDebugLevel: 4, BlockServiceCustomFallbackEndpoints: "", BroadcastConnectionsLimit: -1, + CadaverDirectory: "", CadaverSizeTarget: 0, CatchpointFileHistoryLength: 365, CatchpointInterval: 10000, @@ -59,6 +60,7 @@ var defaultLocal = Local{ EnableBlockServiceFallbackToArchiver: true, EnableCatchupFromArchiveServers: false, EnableDeveloperAPI: false, + EnableExperimentalAPI: false, EnableGossipBlockService: true, EnableIncomingMessageFilter: false, EnableLedgerService: false, @@ -70,6 +72,7 @@ var defaultLocal = Local{ EnableRequestLogger: false, EnableRuntimeMetrics: false, EnableTopAccountsReporting: false, + EnableTxBacklogRateLimiting: false, EnableUsageLog: false, EnableVerbosedTransactionSyncLogging: false, EndpointAddress: "127.0.0.1:0", @@ -77,7 +80,8 @@ var defaultLocal = Local{ ForceFetchTransactions: false, ForceRelayMessages: false, GossipFanout: 4, - IncomingConnectionsLimit: 800, + HeartbeatUpdateInterval: 600, + IncomingConnectionsLimit: 2400, IncomingMessageFilterBucketCount: 5, IncomingMessageFilterBucketSize: 512, IsIndexerActive: false, @@ -89,7 +93,7 @@ var defaultLocal = Local{ MaxAPIResourcesPerAccount: 100000, MaxAcctLookback: 4, MaxCatchpointDownloadDuration: 7200000000000, - MaxConnectionsPerIP: 30, + MaxConnectionsPerIP: 15, MinCatchpointFileDownloadBytesPerSecond: 20480, NetAddress: "", NetworkMessageTraceServer: "", @@ -119,6 +123,9 @@ var defaultLocal = Local{ TelemetryToLog: true, TransactionSyncDataExchangeRate: 0, TransactionSyncSignificantMessageThreshold: 0, + TxBacklogReservedCapacityPerPeer: 20, + TxBacklogServiceRateWindowSeconds: 10, + TxBacklogSize: 26000, TxIncomingFilteringFlags: 1, TxPoolExponentialIncreaseFactor: 2, TxPoolSize: 75000, diff --git a/config/migrate.go b/config/migrate.go index 405314c628..35b4ec82fb 100644 --- a/config/migrate.go +++ b/config/migrate.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/config/version.go b/config/version.go index 6d1a91c3f2..4c91e271fd 100644 --- a/config/version.go +++ b/config/version.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -33,7 +33,7 @@ const VersionMajor = 3 // VersionMinor is the Minor semantic version number (x.#.z) - changed when backwards-compatible features are introduced. // Not enforced until after initial public release (x > 0). -const VersionMinor = 13 +const VersionMinor = 14 // Version is the type holding our full version information. type Version struct { diff --git a/crypto/batchverifier.go b/crypto/batchverifier.go index a358d65e38..9c14771bac 100644 --- a/crypto/batchverifier.go +++ b/crypto/batchverifier.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -53,7 +53,7 @@ const minBatchVerifierAlloc = 16 // Batch verifications errors var ( - ErrBatchVerificationFailed = errors.New("At least one signature didn't pass verification") + ErrBatchHasFailedSigs = errors.New("At least one signature didn't pass verification") ) //export ed25519_randombytes_unsafe @@ -104,8 +104,8 @@ func (b *BatchVerifier) expand() { b.signatures = signatures } -// getNumberOfEnqueuedSignatures returns the number of signatures current enqueue onto the batch verifier object -func (b *BatchVerifier) getNumberOfEnqueuedSignatures() int { +// GetNumberOfEnqueuedSignatures returns the number of signatures currently enqueued into the BatchVerifier +func (b *BatchVerifier) GetNumberOfEnqueuedSignatures() int { return len(b.messages) } @@ -120,18 +120,18 @@ func (b *BatchVerifier) Verify() error { // if some signatures are invalid, true will be set in failed at the corresponding indexes, and // ErrBatchVerificationFailed for err func (b *BatchVerifier) VerifyWithFeedback() (failed []bool, err error) { - if b.getNumberOfEnqueuedSignatures() == 0 { + if b.GetNumberOfEnqueuedSignatures() == 0 { return nil, nil } - var messages = make([][]byte, b.getNumberOfEnqueuedSignatures()) - for i, m := range b.messages { - messages[i] = HashRep(m) + var messages = make([][]byte, b.GetNumberOfEnqueuedSignatures()) + for i := range b.messages { + messages[i] = HashRep(b.messages[i]) } allValid, failed := batchVerificationImpl(messages, b.publicKeys, b.signatures) if allValid { return failed, nil } - return failed, ErrBatchVerificationFailed + return failed, ErrBatchHasFailedSigs } // batchVerificationImpl invokes the ed25519 batch verification algorithm. diff --git a/crypto/batchverifier_test.go b/crypto/batchverifier_test.go index 7c5455703c..c572503ff3 100644 --- a/crypto/batchverifier_test.go +++ b/crypto/batchverifier_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -17,6 +17,7 @@ package crypto import ( + "fmt" "math/rand" "runtime" "testing" @@ -64,7 +65,7 @@ func TestBatchVerifierBulk(t *testing.T) { sig := sigSecrets.Sign(msg) bv.EnqueueSignature(sigSecrets.SignatureVerifier, msg, sig) } - require.Equal(t, n, bv.getNumberOfEnqueuedSignatures()) + require.Equal(t, n, bv.GetNumberOfEnqueuedSignatures()) require.NoError(t, bv.Verify()) } @@ -121,6 +122,67 @@ func BenchmarkBatchVerifier(b *testing.B) { require.NoError(b, bv.Verify()) } +// BenchmarkBatchVerifierBig with b.N over 1000 will report the expected performance +// gain as the batchsize increases. All sigs are valid. +func BenchmarkBatchVerifierBig(b *testing.B) { + c := makeCurve25519Secret() + for batchSize := 1; batchSize <= 96; batchSize++ { + bv := MakeBatchVerifierWithHint(batchSize) + for i := 0; i < batchSize; i++ { + str := randString() + bv.EnqueueSignature(c.SignatureVerifier, str, c.Sign(str)) + } + b.Run(fmt.Sprintf("running batchsize %d", batchSize), func(b *testing.B) { + totalTransactions := b.N + count := totalTransactions / batchSize + if count*batchSize < totalTransactions { + count++ + } + for x := 0; x < count; x++ { + require.NoError(b, bv.Verify()) + } + }) + } +} + +// BenchmarkBatchVerifierBigWithInvalid builds over BenchmarkBatchVerifierBig by introducing +// invalid sigs to even numbered batch sizes. This shows the impact of invalid sigs on the +// performance. Basically, all the gains from batching disappear. +func BenchmarkBatchVerifierBigWithInvalid(b *testing.B) { + c := makeCurve25519Secret() + badSig := Signature{} + for batchSize := 1; batchSize <= 96; batchSize++ { + bv := MakeBatchVerifierWithHint(batchSize) + for i := 0; i < batchSize; i++ { + str := randString() + if batchSize%2 == 0 && (i == 0 || rand.Float32() < 0.1) { + bv.EnqueueSignature(c.SignatureVerifier, str, badSig) + } else { + bv.EnqueueSignature(c.SignatureVerifier, str, c.Sign(str)) + } + } + b.Run(fmt.Sprintf("running batchsize %d", batchSize), func(b *testing.B) { + totalTransactions := b.N + count := totalTransactions / batchSize + if count*batchSize < totalTransactions { + count++ + } + for x := 0; x < count; x++ { + failed, err := bv.VerifyWithFeedback() + if err != nil { + for i, f := range failed { + if bv.signatures[i] == badSig { + require.True(b, f) + } else { + require.False(b, f) + } + } + } + } + }) + } +} + func TestEmpty(t *testing.T) { partitiontest.PartitionTest(t) bv := MakeBatchVerifier() @@ -155,10 +217,10 @@ func TestBatchVerifierIndividualResults(t *testing.T) { } bv.EnqueueSignature(sigSecrets.SignatureVerifier, msg, sig) } - require.Equal(t, n, bv.getNumberOfEnqueuedSignatures()) + require.Equal(t, n, bv.GetNumberOfEnqueuedSignatures()) failed, err := bv.VerifyWithFeedback() if hasBadSig { - require.ErrorIs(t, err, ErrBatchVerificationFailed) + require.ErrorIs(t, err, ErrBatchHasFailedSigs) } else { require.NoError(t, err) } @@ -185,10 +247,10 @@ func TestBatchVerifierIndividualResultsAllValid(t *testing.T) { sig := sigSecrets.Sign(msg) bv.EnqueueSignature(sigSecrets.SignatureVerifier, msg, sig) } - require.Equal(t, n, bv.getNumberOfEnqueuedSignatures()) + require.Equal(t, n, bv.GetNumberOfEnqueuedSignatures()) failed, err := bv.VerifyWithFeedback() require.NoError(t, err) - require.Equal(t, bv.getNumberOfEnqueuedSignatures(), len(failed)) + require.Equal(t, bv.GetNumberOfEnqueuedSignatures(), len(failed)) for _, f := range failed { require.False(t, f) } diff --git a/crypto/crypto_test.go b/crypto/crypto_test.go index 86c65f9097..f0e8b1d0ef 100644 --- a/crypto/crypto_test.go +++ b/crypto/crypto_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/cryptoerror.go b/crypto/cryptoerror.go index 26636dd887..3282f8d4a9 100644 --- a/crypto/cryptoerror.go +++ b/crypto/cryptoerror.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/curve25519.go b/crypto/curve25519.go index 91dc5d63e2..e409fc794b 100644 --- a/crypto/curve25519.go +++ b/crypto/curve25519.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/curve25519_test.go b/crypto/curve25519_test.go index 27d153b9df..acd152cba0 100644 --- a/crypto/curve25519_test.go +++ b/crypto/curve25519_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/digest.go b/crypto/digest.go index a65fa4d39b..27bef9a3cd 100644 --- a/crypto/digest.go +++ b/crypto/digest.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/encoding_test.go b/crypto/encoding_test.go index 515f6a3eff..527a56d2cc 100644 --- a/crypto/encoding_test.go +++ b/crypto/encoding_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/falconWrapper.go b/crypto/falconWrapper.go index f24bc94339..0a2c000b9c 100644 --- a/crypto/falconWrapper.go +++ b/crypto/falconWrapper.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/falconWrapper_test.go b/crypto/falconWrapper_test.go index 20b5441eef..45adb124ab 100644 --- a/crypto/falconWrapper_test.go +++ b/crypto/falconWrapper_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -17,10 +17,11 @@ package crypto import ( + "testing" + "github.com/algorand/falcon" "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/require" - "testing" ) func TestSignAndVerifyFalcon(t *testing.T) { diff --git a/crypto/hashes.go b/crypto/hashes.go index 67ccdafc0c..b1a541bbad 100644 --- a/crypto/hashes.go +++ b/crypto/hashes.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/hashes_test.go b/crypto/hashes_test.go index a3054d298a..dd4b8c3bd7 100644 --- a/crypto/hashes_test.go +++ b/crypto/hashes_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/memcpy_chk_windows.c b/crypto/memcpy_chk_windows.c index f14eee0fe0..d79118cdf3 100644 --- a/crypto/memcpy_chk_windows.c +++ b/crypto/memcpy_chk_windows.c @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/merklearray/array.go b/crypto/merklearray/array.go index 6b783d8601..4e04b53383 100644 --- a/crypto/merklearray/array.go +++ b/crypto/merklearray/array.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/merklearray/layer.go b/crypto/merklearray/layer.go index b1a9281fc0..45aa93ff84 100644 --- a/crypto/merklearray/layer.go +++ b/crypto/merklearray/layer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/merklearray/merkle.go b/crypto/merklearray/merkle.go index 803eb5a25d..b4591bf6b7 100644 --- a/crypto/merklearray/merkle.go +++ b/crypto/merklearray/merkle.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/merklearray/merkle_test.go b/crypto/merklearray/merkle_test.go index 0f87474e52..9a1a5c0fd7 100644 --- a/crypto/merklearray/merkle_test.go +++ b/crypto/merklearray/merkle_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/merklearray/partial.go b/crypto/merklearray/partial.go index c8e62f4dd0..a1d0861dcc 100644 --- a/crypto/merklearray/partial.go +++ b/crypto/merklearray/partial.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/merklearray/proof.go b/crypto/merklearray/proof.go index 2670f69f15..e64479a968 100644 --- a/crypto/merklearray/proof.go +++ b/crypto/merklearray/proof.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/merklearray/proof_test.go b/crypto/merklearray/proof_test.go index 4645dccb26..d3683689f6 100644 --- a/crypto/merklearray/proof_test.go +++ b/crypto/merklearray/proof_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/merklearray/vectorCommitmentArray.go b/crypto/merklearray/vectorCommitmentArray.go index a9f9b3580c..c11e295aea 100644 --- a/crypto/merklearray/vectorCommitmentArray.go +++ b/crypto/merklearray/vectorCommitmentArray.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/merklearray/vectorCommitmentArray_test.go b/crypto/merklearray/vectorCommitmentArray_test.go index d3a4ae0652..e3887880dc 100644 --- a/crypto/merklearray/vectorCommitmentArray_test.go +++ b/crypto/merklearray/vectorCommitmentArray_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/merklearray/worker.go b/crypto/merklearray/worker.go index e7a4dc2228..2a8059336f 100644 --- a/crypto/merklearray/worker.go +++ b/crypto/merklearray/worker.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/merklesignature/committablePublicKeys.go b/crypto/merklesignature/committablePublicKeys.go index 7401c67efc..fd4a9f26a1 100644 --- a/crypto/merklesignature/committablePublicKeys.go +++ b/crypto/merklesignature/committablePublicKeys.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -34,7 +34,7 @@ type ( keyLifetime uint64 } - // CommittablePublicKey is used to create a binary representation of public keys in the merkle + // CommittablePublicKey is used to create a binary representation of public keys in the merkle // signature scheme. CommittablePublicKey struct { VerifyingKey crypto.FalconVerifier diff --git a/crypto/merklesignature/committablePublicKeys_test.go b/crypto/merklesignature/committablePublicKeys_test.go index a6884cc59d..08108b302f 100644 --- a/crypto/merklesignature/committablePublicKeys_test.go +++ b/crypto/merklesignature/committablePublicKeys_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/merklesignature/const.go b/crypto/merklesignature/const.go index 767f14aaef..52d98390f7 100644 --- a/crypto/merklesignature/const.go +++ b/crypto/merklesignature/const.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/merklesignature/kats_test.go b/crypto/merklesignature/kats_test.go index bc61ec47b2..610cdbab91 100644 --- a/crypto/merklesignature/kats_test.go +++ b/crypto/merklesignature/kats_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/merklesignature/keysBuilder.go b/crypto/merklesignature/keysBuilder.go index 93553e7d5e..d284ca29b7 100644 --- a/crypto/merklesignature/keysBuilder.go +++ b/crypto/merklesignature/keysBuilder.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/merklesignature/keysBuilder_test.go b/crypto/merklesignature/keysBuilder_test.go index ec9e487fe3..24f426e703 100644 --- a/crypto/merklesignature/keysBuilder_test.go +++ b/crypto/merklesignature/keysBuilder_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/merklesignature/merkleSignatureScheme.go b/crypto/merklesignature/merkleSignatureScheme.go index b2763d496d..f11ecb9202 100644 --- a/crypto/merklesignature/merkleSignatureScheme.go +++ b/crypto/merklesignature/merkleSignatureScheme.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/merklesignature/merkleSignatureScheme_test.go b/crypto/merklesignature/merkleSignatureScheme_test.go index db4f4e6e44..9b56cb11df 100644 --- a/crypto/merklesignature/merkleSignatureScheme_test.go +++ b/crypto/merklesignature/merkleSignatureScheme_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/merklesignature/persistentMerkleSignatureScheme.go b/crypto/merklesignature/persistentMerkleSignatureScheme.go index 2ece3380da..c862dcb965 100644 --- a/crypto/merklesignature/persistentMerkleSignatureScheme.go +++ b/crypto/merklesignature/persistentMerkleSignatureScheme.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/merklesignature/persistentMerkleSignatureScheme_test.go b/crypto/merklesignature/persistentMerkleSignatureScheme_test.go index 970daf903f..5885f1cf6d 100644 --- a/crypto/merklesignature/persistentMerkleSignatureScheme_test.go +++ b/crypto/merklesignature/persistentMerkleSignatureScheme_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/merklesignature/posdivs.go b/crypto/merklesignature/posdivs.go index 9ce88d53e9..ac152234f4 100644 --- a/crypto/merklesignature/posdivs.go +++ b/crypto/merklesignature/posdivs.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/merklesignature/posdivs_test.go b/crypto/merklesignature/posdivs_test.go index 579a1c3d5e..394c318705 100644 --- a/crypto/merklesignature/posdivs_test.go +++ b/crypto/merklesignature/posdivs_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/merkletrie/bitset.go b/crypto/merkletrie/bitset.go index f7f2d78d09..3007372abd 100644 --- a/crypto/merkletrie/bitset.go +++ b/crypto/merkletrie/bitset.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/merkletrie/bitset_test.go b/crypto/merkletrie/bitset_test.go index 758c3299e3..2fa5a35f92 100644 --- a/crypto/merkletrie/bitset_test.go +++ b/crypto/merkletrie/bitset_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/merkletrie/cache.go b/crypto/merkletrie/cache.go index 4375825a28..208397edb9 100644 --- a/crypto/merkletrie/cache.go +++ b/crypto/merkletrie/cache.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/merkletrie/cache_test.go b/crypto/merkletrie/cache_test.go index 15b14e6767..d9c7a23e30 100644 --- a/crypto/merkletrie/cache_test.go +++ b/crypto/merkletrie/cache_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/merkletrie/committer.go b/crypto/merkletrie/committer.go index ac6fcd72c2..bad5fe7e0a 100644 --- a/crypto/merkletrie/committer.go +++ b/crypto/merkletrie/committer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/merkletrie/committer_test.go b/crypto/merkletrie/committer_test.go index 8152743023..6f8dfb2a78 100644 --- a/crypto/merkletrie/committer_test.go +++ b/crypto/merkletrie/committer_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/merkletrie/node.go b/crypto/merkletrie/node.go index e4d3a18f3f..33c2f673ab 100644 --- a/crypto/merkletrie/node.go +++ b/crypto/merkletrie/node.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/merkletrie/node_test.go b/crypto/merkletrie/node_test.go index 76121b5ae3..1495a8e9c0 100644 --- a/crypto/merkletrie/node_test.go +++ b/crypto/merkletrie/node_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/merkletrie/trie.go b/crypto/merkletrie/trie.go index 22c03e1cec..7a214e7097 100644 --- a/crypto/merkletrie/trie.go +++ b/crypto/merkletrie/trie.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/merkletrie/trie_test.go b/crypto/merkletrie/trie_test.go index fd9b38d989..f71545962b 100644 --- a/crypto/merkletrie/trie_test.go +++ b/crypto/merkletrie/trie_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/multisig.go b/crypto/multisig.go index 62ec187a2b..d6f19bf4ab 100644 --- a/crypto/multisig.go +++ b/crypto/multisig.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/multisig_test.go b/crypto/multisig_test.go index 5035300d21..e7f4a17dab 100644 --- a/crypto/multisig_test.go +++ b/crypto/multisig_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/onetimesig.go b/crypto/onetimesig.go index 2aaa58bc0c..344fd33f77 100644 --- a/crypto/onetimesig.go +++ b/crypto/onetimesig.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/onetimesig_test.go b/crypto/onetimesig_test.go index af60a3c736..143bb5c162 100644 --- a/crypto/onetimesig_test.go +++ b/crypto/onetimesig_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/passphrase/errors.go b/crypto/passphrase/errors.go index 7785ca256d..0a50d59c49 100644 --- a/crypto/passphrase/errors.go +++ b/crypto/passphrase/errors.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/passphrase/passphrase.go b/crypto/passphrase/passphrase.go index f90593ce53..0f0d09d34c 100644 --- a/crypto/passphrase/passphrase.go +++ b/crypto/passphrase/passphrase.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/passphrase/passphrase_test.go b/crypto/passphrase/passphrase_test.go index 3874f57e0c..543ed5fd77 100644 --- a/crypto/passphrase/passphrase_test.go +++ b/crypto/passphrase/passphrase_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/passphrase/wordlist.go b/crypto/passphrase/wordlist.go index 4da8591359..670a72b3d7 100644 --- a/crypto/passphrase/wordlist.go +++ b/crypto/passphrase/wordlist.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/rand.go b/crypto/rand.go index 729e01c17b..99f22e1457 100644 --- a/crypto/rand.go +++ b/crypto/rand.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/rand_test.go b/crypto/rand_test.go index 5f69fd55ce..129f831714 100644 --- a/crypto/rand_test.go +++ b/crypto/rand_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/stateproof/builder.go b/crypto/stateproof/builder.go index 3f85656ab4..0e86fa8942 100644 --- a/crypto/stateproof/builder.go +++ b/crypto/stateproof/builder.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/stateproof/builder_test.go b/crypto/stateproof/builder_test.go index 780262e851..6938a4b17e 100644 --- a/crypto/stateproof/builder_test.go +++ b/crypto/stateproof/builder_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/stateproof/coinGenerator.go b/crypto/stateproof/coinGenerator.go index fa88c57706..1c019c869f 100644 --- a/crypto/stateproof/coinGenerator.go +++ b/crypto/stateproof/coinGenerator.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/stateproof/coinGenerator_test.go b/crypto/stateproof/coinGenerator_test.go index 39f3d760c8..d15091f6d0 100644 --- a/crypto/stateproof/coinGenerator_test.go +++ b/crypto/stateproof/coinGenerator_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/stateproof/committableSignatureSlot.go b/crypto/stateproof/committableSignatureSlot.go index 78aef90ec0..7ae0e8ac53 100644 --- a/crypto/stateproof/committableSignatureSlot.go +++ b/crypto/stateproof/committableSignatureSlot.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/stateproof/committableSignatureSlot_test.go b/crypto/stateproof/committableSignatureSlot_test.go index 811c79c4df..b2519cea6c 100644 --- a/crypto/stateproof/committableSignatureSlot_test.go +++ b/crypto/stateproof/committableSignatureSlot_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/stateproof/const.go b/crypto/stateproof/const.go index a9dab2813d..eefe0d1a93 100644 --- a/crypto/stateproof/const.go +++ b/crypto/stateproof/const.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/stateproof/structs.go b/crypto/stateproof/structs.go index d8e0b6883d..7ee599b7ae 100644 --- a/crypto/stateproof/structs.go +++ b/crypto/stateproof/structs.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/stateproof/verifier.go b/crypto/stateproof/verifier.go index 892c9d4770..67d83068f1 100644 --- a/crypto/stateproof/verifier.go +++ b/crypto/stateproof/verifier.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/stateproof/verifier_test.go b/crypto/stateproof/verifier_test.go index 91ee123119..23a5fac3ad 100644 --- a/crypto/stateproof/verifier_test.go +++ b/crypto/stateproof/verifier_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/stateproof/weights.go b/crypto/stateproof/weights.go index 8d0bdd13dc..aa849ec6a9 100644 --- a/crypto/stateproof/weights.go +++ b/crypto/stateproof/weights.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/stateproof/weights_test.go b/crypto/stateproof/weights_test.go index d7a6948970..5800c49f54 100644 --- a/crypto/stateproof/weights_test.go +++ b/crypto/stateproof/weights_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/util.go b/crypto/util.go index 2ee4ee7941..aa8dd3cfc4 100644 --- a/crypto/util.go +++ b/crypto/util.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/util_test.go b/crypto/util_test.go index 281d78a1de..667da0bcd0 100644 --- a/crypto/util_test.go +++ b/crypto/util_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/vrf.go b/crypto/vrf.go index dfc2fbf1c6..bfdf4ec407 100644 --- a/crypto/vrf.go +++ b/crypto/vrf.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/vrf_test.go b/crypto/vrf_test.go index 72c7f68b3a..22b3e75277 100644 --- a/crypto/vrf_test.go +++ b/crypto/vrf_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/algod/api/Makefile b/daemon/algod/api/Makefile index 811b18dc6f..c4104d9e80 100644 --- a/daemon/algod/api/Makefile +++ b/daemon/algod/api/Makefile @@ -2,7 +2,7 @@ GOPATH := $(shell go env GOPATH) GOPATH1 := $(firstword $(subst :, ,$(GOPATH))) # `make all` or just `make` should be appropriate for dev work -all: server/v2/generated/model/types.go server/v2/generated/nonparticipating/public/routes.go server/v2/generated/nonparticipating/private/routes.go server/v2/generated/participating/public/routes.go server/v2/generated/participating/private/routes.go server/v2/generated/data/routes.go +all: server/v2/generated/model/types.go server/v2/generated/nonparticipating/public/routes.go server/v2/generated/nonparticipating/private/routes.go server/v2/generated/participating/public/routes.go server/v2/generated/participating/private/routes.go server/v2/generated/data/routes.go server/v2/generated/experimental/routes.go # `make generate` should be able to replace old `generate.sh` script and be appropriate for build system use generate: oapi-codegen all @@ -23,6 +23,9 @@ server/v2/generated/participating/private/routes.go: algod.oas3.yml server/v2/generated/data/routes.go: algod.oas3.yml $(GOPATH1)/bin/oapi-codegen -config ./server/v2/generated/data/data_routes.yml algod.oas3.yml +server/v2/generated/experimental/routes.go: algod.oas3.yml + $(GOPATH1)/bin/oapi-codegen -config ./server/v2/generated/experimental/experimental_routes.yml algod.oas3.yml + server/v2/generated/model/types.go: algod.oas3.yml $(GOPATH1)/bin/oapi-codegen -config ./server/v2/generated/model/model_types.yml algod.oas3.yml diff --git a/daemon/algod/api/README.md b/daemon/algod/api/README.md index b23f77128d..482ba23004 100644 --- a/daemon/algod/api/README.md +++ b/daemon/algod/api/README.md @@ -20,10 +20,10 @@ Each API in `algod.oas2.json`, except for some pre-existing `common` APIs, shoul 1. Either `public` or `private`. This controls the type of authentication used by the API--the `public` APIs use the `algod.token` token, while the `private` APIs use the admin token, found in `algod.admin.token` within the algod data directory. -2. The type, or group, of API. This is currently `participating`, `nonparticipating`, or `data`, but may expand in the -future to encompass different sets of APIs such as `experimental` APIs. Additional APIs should be added to one of the -existing sets of tags based on its use case--unless you intend to create a new group in which case you will need to -additionally ensure your new APIs are registered. +2. The type, or group, of API. This is currently `participating`, `nonparticipating`, `data`, or `experimental`, but +may expand in the future to encompass different sets of APIs. Additional APIs should be added to one of the existing +sets of tags based on its use case--unless you intend to create a new group in which case you will need to additionally +ensure your new APIs are registered. For backwards compatibility, the default set of APIs registered will always be `participating` and `nonparticipating` APIs. @@ -38,6 +38,8 @@ participation keys, the agreement service, etc. * A special set of APIs which require manipulating the node state in order to provide additional data about the node state at some predefined granularity. For example, SetSyncRound and GetLedgerStateDelta used together control and expose StateDelta objects containing per-round ledger differences that get compacted when actually written to the ledger DB. +* `experimental` + * APIs which are still in development and not ready to be generally released. ## What codegen tool is used? diff --git a/daemon/algod/api/algod.oas2.json b/daemon/algod/api/algod.oas2.json index ce1c9f5879..b2bd99e36c 100644 --- a/daemon/algod/api/algod.oas2.json +++ b/daemon/algod/api/algod.oas2.json @@ -1088,7 +1088,7 @@ "schemes": [ "http" ], - "summary": "Gets the node status after waiting for the given round.", + "summary": "Gets the node status after waiting for a round after the given round.", "operationId": "WaitForBlock", "parameters": [ { @@ -1157,7 +1157,7 @@ "schemes": [ "http" ], - "summary": "Broadcasts a raw transaction to the network.", + "summary": "Broadcasts a raw transaction or transaction group to the network.", "operationId": "RawTransaction", "parameters": [ { @@ -1205,6 +1205,69 @@ } } }, + "/v2/transactions/simulate": { + "post": { + "tags": [ + "public", + "experimental" + ], + "consumes": [ + "application/x-binary" + ], + "produces": [ + "application/msgpack" + ], + "schemes": [ + "http" + ], + "summary": "Simulates a raw transaction or transaction group as it would be evaluated on the network. WARNING: This endpoint is experimental and under active development. There are no guarantees in terms of functionality or future support.", + "operationId": "SimulateTransaction", + "parameters": [ + { + "description": "The byte encoded transaction to simulate", + "name": "rawtxn", + "in": "body", + "required": true, + "schema": { + "type": "string", + "format": "binary" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/SimulationResponse" + }, + "400": { + "description": "Bad Request - Malformed Algorand transaction", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + }, + "401": { + "description": "Invalid API Token", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + }, + "500": { + "description": "Internal Error", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + }, + "503": { + "description": "Service Temporarily Unavailable", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + }, + "default": { + "description": "Unknown Error" + } + } + } + }, "/v2/transactions/params": { "get": { "tags": [ @@ -4054,6 +4117,38 @@ "catchpoint-acquired-blocks": { "description": "The number of blocks that have already been obtained by the node as part of the catchup", "type": "integer" + }, + "upgrade-delay": { + "description": "Upgrade delay", + "type": "integer" + }, + "upgrade-node-vote": { + "description": "This node's upgrade vote", + "type": "boolean" + }, + "upgrade-votes-required": { + "description": "Yes votes required for consensus upgrade", + "type": "integer" + }, + "upgrade-votes": { + "description": "Total votes cast for consensus upgrade", + "type": "integer" + }, + "upgrade-yes-votes": { + "description": "Yes votes cast for consensus upgrade", + "type": "integer" + }, + "upgrade-no-votes": { + "description": "No votes cast for consensus upgrade", + "type": "integer" + }, + "upgrade-next-protocol-vote-before": { + "description": "Next protocol round", + "type": "integer" + }, + "upgrade-vote-rounds": { + "description": "Total voting ounds for current upgrade", + "type": "integer" } } } @@ -4128,6 +4223,29 @@ } } }, + "SimulationResponse": { + "description": "Result of a transaction group simulation.", + "tags": [ + "experimental" + ], + "schema": { + "type": "object", + "required": [ + "failure-message", + "missing-signatures" + ], + "properties": { + "failure-message": { + "description": "\\[fm\\] Failure message, if the transaction would have failed during a live broadcast.", + "type": "string" + }, + "missing-signatures": { + "description": "\\[ms\\] Whether any transactions would have failed during a live broadcast because they were missing signatures.", + "type": "boolean" + } + } + } + }, "SupplyResponse": { "description": "Supply represents the current supply of MicroAlgos in the system.", "schema": { diff --git a/daemon/algod/api/algod.oas3.yml b/daemon/algod/api/algod.oas3.yml index bbb5e85b16..fa8d86f2ba 100644 --- a/daemon/algod/api/algod.oas3.yml +++ b/daemon/algod/api/algod.oas3.yml @@ -613,6 +613,38 @@ "time-since-last-round": { "description": "TimeSinceLastRound in nanoseconds", "type": "integer" + }, + "upgrade-delay": { + "description": "Upgrade delay", + "type": "integer" + }, + "upgrade-next-protocol-vote-before": { + "description": "Next protocol round", + "type": "integer" + }, + "upgrade-no-votes": { + "description": "No votes cast for consensus upgrade", + "type": "integer" + }, + "upgrade-node-vote": { + "description": "This node's upgrade vote", + "type": "boolean" + }, + "upgrade-vote-rounds": { + "description": "Total voting ounds for current upgrade", + "type": "integer" + }, + "upgrade-votes": { + "description": "Total votes cast for consensus upgrade", + "type": "integer" + }, + "upgrade-votes-required": { + "description": "Yes votes required for consensus upgrade", + "type": "integer" + }, + "upgrade-yes-votes": { + "description": "Yes votes cast for consensus upgrade", + "type": "integer" } }, "required": [ @@ -721,6 +753,30 @@ }, "description": "Transaction ID of the submission." }, + "SimulationResponse": { + "content": { + "application/json": { + "schema": { + "properties": { + "failure-message": { + "description": "\\[fm\\] Failure message, if the transaction would have failed during a live broadcast.", + "type": "string" + }, + "missing-signatures": { + "description": "\\[ms\\] Whether any transactions would have failed during a live broadcast because they were missing signatures.", + "type": "boolean" + } + }, + "required": [ + "failure-message", + "missing-signatures" + ], + "type": "object" + } + } + }, + "description": "Result of a transaction group simulation." + }, "StateProofResponse": { "content": { "application/json": { @@ -4770,6 +4826,38 @@ "time-since-last-round": { "description": "TimeSinceLastRound in nanoseconds", "type": "integer" + }, + "upgrade-delay": { + "description": "Upgrade delay", + "type": "integer" + }, + "upgrade-next-protocol-vote-before": { + "description": "Next protocol round", + "type": "integer" + }, + "upgrade-no-votes": { + "description": "No votes cast for consensus upgrade", + "type": "integer" + }, + "upgrade-node-vote": { + "description": "This node's upgrade vote", + "type": "boolean" + }, + "upgrade-vote-rounds": { + "description": "Total voting ounds for current upgrade", + "type": "integer" + }, + "upgrade-votes": { + "description": "Total votes cast for consensus upgrade", + "type": "integer" + }, + "upgrade-votes-required": { + "description": "Yes votes required for consensus upgrade", + "type": "integer" + }, + "upgrade-yes-votes": { + "description": "Yes votes cast for consensus upgrade", + "type": "integer" } }, "required": [ @@ -4913,6 +5001,38 @@ "time-since-last-round": { "description": "TimeSinceLastRound in nanoseconds", "type": "integer" + }, + "upgrade-delay": { + "description": "Upgrade delay", + "type": "integer" + }, + "upgrade-next-protocol-vote-before": { + "description": "Next protocol round", + "type": "integer" + }, + "upgrade-no-votes": { + "description": "No votes cast for consensus upgrade", + "type": "integer" + }, + "upgrade-node-vote": { + "description": "This node's upgrade vote", + "type": "boolean" + }, + "upgrade-vote-rounds": { + "description": "Total voting ounds for current upgrade", + "type": "integer" + }, + "upgrade-votes": { + "description": "Total votes cast for consensus upgrade", + "type": "integer" + }, + "upgrade-votes-required": { + "description": "Yes votes required for consensus upgrade", + "type": "integer" + }, + "upgrade-yes-votes": { + "description": "Yes votes cast for consensus upgrade", + "type": "integer" } }, "required": [ @@ -4975,7 +5095,7 @@ "description": "Unknown Error" } }, - "summary": "Gets the node status after waiting for the given round.", + "summary": "Gets the node status after waiting for a round after the given round.", "tags": [ "public", "nonparticipating" @@ -5346,7 +5466,7 @@ "description": "Unknown Error" } }, - "summary": "Broadcasts a raw transaction to the network.", + "summary": "Broadcasts a raw transaction or transaction group to the network.", "tags": [ "public", "participating" @@ -5687,6 +5807,99 @@ ] } }, + "/v2/transactions/simulate": { + "post": { + "operationId": "SimulateTransaction", + "requestBody": { + "content": { + "application/x-binary": { + "schema": { + "format": "binary", + "type": "string" + } + } + }, + "description": "The byte encoded transaction to simulate", + "required": true + }, + "responses": { + "200": { + "content": { + "application/msgpack": { + "schema": { + "properties": { + "failure-message": { + "description": "\\[fm\\] Failure message, if the transaction would have failed during a live broadcast.", + "type": "string" + }, + "missing-signatures": { + "description": "\\[ms\\] Whether any transactions would have failed during a live broadcast because they were missing signatures.", + "type": "boolean" + } + }, + "required": [ + "failure-message", + "missing-signatures" + ], + "type": "object" + } + } + }, + "description": "Result of a transaction group simulation." + }, + "400": { + "content": { + "application/msgpack": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Bad Request - Malformed Algorand transaction" + }, + "401": { + "content": { + "application/msgpack": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Invalid API Token" + }, + "500": { + "content": { + "application/msgpack": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Internal Error" + }, + "503": { + "content": { + "application/msgpack": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Service Temporarily Unavailable" + }, + "default": { + "content": {}, + "description": "Unknown Error" + } + }, + "summary": "Simulates a raw transaction or transaction group as it would be evaluated on the network. WARNING: This endpoint is experimental and under active development. There are no guarantees in terms of functionality or future support.", + "tags": [ + "public", + "experimental" + ], + "x-codegen-request-body-name": "rawtxn" + } + }, "/versions": { "get": { "description": "Retrieves the supported API versions, binary build versions, and genesis information.", diff --git a/daemon/algod/api/client/encoding.go b/daemon/algod/api/client/encoding.go index 036948c1d0..365fb7d503 100644 --- a/daemon/algod/api/client/encoding.go +++ b/daemon/algod/api/client/encoding.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/algod/api/client/restClient.go b/daemon/algod/api/client/restClient.go index a0ac7d1f2a..3cf61e9036 100644 --- a/daemon/algod/api/client/restClient.go +++ b/daemon/algod/api/client/restClient.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/algod/api/server/common/handlers.go b/daemon/algod/api/server/common/handlers.go index f9e2c47476..0aa107f427 100644 --- a/daemon/algod/api/server/common/handlers.go +++ b/daemon/algod/api/server/common/handlers.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/algod/api/server/common/metrics.go b/daemon/algod/api/server/common/metrics.go index 4920acfb69..18e6b80274 100644 --- a/daemon/algod/api/server/common/metrics.go +++ b/daemon/algod/api/server/common/metrics.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/algod/api/server/common/responses.go b/daemon/algod/api/server/common/responses.go index 452ed5808b..b775d2040c 100644 --- a/daemon/algod/api/server/common/responses.go +++ b/daemon/algod/api/server/common/responses.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/algod/api/server/common/routes.go b/daemon/algod/api/server/common/routes.go index 153a685bf9..545d92bb27 100644 --- a/daemon/algod/api/server/common/routes.go +++ b/daemon/algod/api/server/common/routes.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/algod/api/server/lib/common.go b/daemon/algod/api/server/lib/common.go index 0bc7394847..2599774a8b 100644 --- a/daemon/algod/api/server/lib/common.go +++ b/daemon/algod/api/server/lib/common.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/algod/api/server/lib/middlewares/auth.go b/daemon/algod/api/server/lib/middlewares/auth.go index 168afdc844..363e2b2b05 100644 --- a/daemon/algod/api/server/lib/middlewares/auth.go +++ b/daemon/algod/api/server/lib/middlewares/auth.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/algod/api/server/lib/middlewares/auth_test.go b/daemon/algod/api/server/lib/middlewares/auth_test.go index 7e00b66c3d..4bdfa7b85c 100644 --- a/daemon/algod/api/server/lib/middlewares/auth_test.go +++ b/daemon/algod/api/server/lib/middlewares/auth_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/algod/api/server/lib/middlewares/connectionLimiter.go b/daemon/algod/api/server/lib/middlewares/connectionLimiter.go index bf27ef2b60..21e6c0bd10 100644 --- a/daemon/algod/api/server/lib/middlewares/connectionLimiter.go +++ b/daemon/algod/api/server/lib/middlewares/connectionLimiter.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/algod/api/server/lib/middlewares/connectionLimiter_test.go b/daemon/algod/api/server/lib/middlewares/connectionLimiter_test.go index 6de0a3c207..b3e5ea06a0 100644 --- a/daemon/algod/api/server/lib/middlewares/connectionLimiter_test.go +++ b/daemon/algod/api/server/lib/middlewares/connectionLimiter_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/algod/api/server/lib/middlewares/cors.go b/daemon/algod/api/server/lib/middlewares/cors.go index 71cacb280e..4bdf532e37 100644 --- a/daemon/algod/api/server/lib/middlewares/cors.go +++ b/daemon/algod/api/server/lib/middlewares/cors.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/algod/api/server/lib/middlewares/logger.go b/daemon/algod/api/server/lib/middlewares/logger.go index 94559abd09..5ede3aa49a 100644 --- a/daemon/algod/api/server/lib/middlewares/logger.go +++ b/daemon/algod/api/server/lib/middlewares/logger.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/algod/api/server/router.go b/daemon/algod/api/server/router.go index 9f6c4277bd..ab79ee3051 100644 --- a/daemon/algod/api/server/router.go +++ b/daemon/algod/api/server/router.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -30,6 +30,7 @@ import ( "github.com/algorand/go-algorand/daemon/algod/api/server/lib/middlewares" "github.com/algorand/go-algorand/daemon/algod/api/server/v1/routes" v2 "github.com/algorand/go-algorand/daemon/algod/api/server/v2" + "github.com/algorand/go-algorand/daemon/algod/api/server/v2/generated/experimental" npprivate "github.com/algorand/go-algorand/daemon/algod/api/server/v2/generated/nonparticipating/private" nppublic "github.com/algorand/go-algorand/daemon/algod/api/server/v2/generated/nonparticipating/public" pprivate "github.com/algorand/go-algorand/daemon/algod/api/server/v2/generated/participating/private" @@ -112,6 +113,10 @@ func NewRouter(logger logging.Logger, node *node.AlgorandFullNode, shutdown <-ch ppublic.RegisterHandlers(e, &v2Handler, apiAuthenticator) pprivate.RegisterHandlers(e, &v2Handler, adminAuthenticator) + if node.Config().EnableExperimentalAPI { + experimental.RegisterHandlers(e, &v2Handler, apiAuthenticator) + } + return e } diff --git a/daemon/algod/api/server/router_test.go b/daemon/algod/api/server/router_test.go index 0e481e5fc1..98e772f276 100644 --- a/daemon/algod/api/server/router_test.go +++ b/daemon/algod/api/server/router_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/algod/api/server/v1/handlers/errors.go b/daemon/algod/api/server/v1/handlers/errors.go index 0506eea6e8..09720c78e7 100644 --- a/daemon/algod/api/server/v1/handlers/errors.go +++ b/daemon/algod/api/server/v1/handlers/errors.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/algod/api/server/v1/handlers/handlers.go b/daemon/algod/api/server/v1/handlers/handlers.go index e1209cf1d0..f5b914687b 100644 --- a/daemon/algod/api/server/v1/handlers/handlers.go +++ b/daemon/algod/api/server/v1/handlers/handlers.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/algod/api/server/v1/routes/routes.go b/daemon/algod/api/server/v1/routes/routes.go index d5f1e3a4b9..dc163454e9 100644 --- a/daemon/algod/api/server/v1/routes/routes.go +++ b/daemon/algod/api/server/v1/routes/routes.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/algod/api/server/v2/account.go b/daemon/algod/api/server/v2/account.go index 4c2392c06f..9c25021b9c 100644 --- a/daemon/algod/api/server/v2/account.go +++ b/daemon/algod/api/server/v2/account.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/algod/api/server/v2/account_test.go b/daemon/algod/api/server/v2/account_test.go index c6562abee1..ac1abd3b9d 100644 --- a/daemon/algod/api/server/v2/account_test.go +++ b/daemon/algod/api/server/v2/account_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/algod/api/server/v2/delta.go b/daemon/algod/api/server/v2/delta.go index d97cc2d426..f48115a3f7 100644 --- a/daemon/algod/api/server/v2/delta.go +++ b/daemon/algod/api/server/v2/delta.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/algod/api/server/v2/delta_test.go b/daemon/algod/api/server/v2/delta_test.go index 921798d28c..596954f43d 100644 --- a/daemon/algod/api/server/v2/delta_test.go +++ b/daemon/algod/api/server/v2/delta_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/algod/api/server/v2/dryrun.go b/daemon/algod/api/server/v2/dryrun.go index f11ad58ea4..49a5a427da 100644 --- a/daemon/algod/api/server/v2/dryrun.go +++ b/daemon/algod/api/server/v2/dryrun.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -181,24 +181,22 @@ func (ddr *dryrunDebugReceiver) stateToState(state *logic.DebugState) model.Dryr return st } -// Register is fired on program creation (DebuggerHook interface) -func (ddr *dryrunDebugReceiver) Register(state *logic.DebugState) error { +// Register is fired on program creation (logic.Debugger interface) +func (ddr *dryrunDebugReceiver) Register(state *logic.DebugState) { ddr.disassembly = state.Disassembly ddr.lines = strings.Split(state.Disassembly, "\n") - return nil } -// Update is fired on every step (DebuggerHook interface) -func (ddr *dryrunDebugReceiver) Update(state *logic.DebugState) error { +// Update is fired on every step (logic.Debugger interface) +func (ddr *dryrunDebugReceiver) Update(state *logic.DebugState) { st := ddr.stateToState(state) ddr.history = append(ddr.history, st) ddr.updateScratch() - return nil } -// Complete is called when the program exits (DebuggerHook interface) -func (ddr *dryrunDebugReceiver) Complete(state *logic.DebugState) error { - return ddr.Update(state) +// Complete is called when the program exits (logic.Debugger interface) +func (ddr *dryrunDebugReceiver) Complete(state *logic.DebugState) { + ddr.Update(state) } type dryrunLedger struct { @@ -421,7 +419,7 @@ func doDryrunRequest(dr *DryrunRequest, response *model.DryrunResponse) { var result model.DryrunTxnResult if len(stxn.Lsig.Logic) > 0 { var debug dryrunDebugReceiver - ep.Debugger = &debug + ep.Tracer = logic.MakeEvalTracerDebuggerAdaptor(&debug) ep.SigLedger = &dl pass, err := logic.EvalSignature(ti, ep) var messages []string @@ -505,7 +503,7 @@ func doDryrunRequest(dr *DryrunRequest, response *model.DryrunResponse) { messages[0] = fmt.Sprintf("uploaded state did not include app id %d referenced in txn[%d]", appIdx, ti) } else { var debug dryrunDebugReceiver - ep.Debugger = &debug + ep.Tracer = logic.MakeEvalTracerDebuggerAdaptor(&debug) var program []byte messages = make([]string, 1) if stxn.Txn.OnCompletion == transactions.ClearStateOC { diff --git a/daemon/algod/api/server/v2/dryrun_test.go b/daemon/algod/api/server/v2/dryrun_test.go index 9ec7444e59..fd74e90108 100644 --- a/daemon/algod/api/server/v2/dryrun_test.go +++ b/daemon/algod/api/server/v2/dryrun_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/algod/api/server/v2/errors.go b/daemon/algod/api/server/v2/errors.go index 5df5a920d0..947a38fa97 100644 --- a/daemon/algod/api/server/v2/errors.go +++ b/daemon/algod/api/server/v2/errors.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -29,14 +29,12 @@ var ( errFailedRetrievingLatestBlockHeaderStatus = "failed retrieving latests block header" errFailedRetrievingSyncRound = "failed retrieving sync round from ledger" errFailedSettingSyncRound = "failed to set sync round on the ledger" - errSyncModeNotEnabled = "sync mode must be enabled" errFailedParsingFormatOption = "failed to parse the format option" errFailedToParseAddress = "failed to parse the address" errFailedToParseExclude = "failed to parse exclude" errFailedToParseTransaction = "failed to parse transaction" errFailedToParseBlock = "failed to parse block" errFailedToParseCert = "failed to parse cert" - errFailedToParseSourcemap = "failed to parse sourcemap" errFailedToEncodeResponse = "failed to encode response" errInternalFailure = "internal failure" errNoValidTxnSpecified = "no valid transaction ID was specified" diff --git a/daemon/algod/api/server/v2/generated/data/data_routes.yml b/daemon/algod/api/server/v2/generated/data/data_routes.yml index 6de9f2be58..06a6306492 100644 --- a/daemon/algod/api/server/v2/generated/data/data_routes.yml +++ b/daemon/algod/api/server/v2/generated/data/data_routes.yml @@ -11,6 +11,7 @@ output-options: - private - participating - nonparticipating + - experimental type-mappings: integer: uint64 skip-prune: true diff --git a/daemon/algod/api/server/v2/generated/data/routes.go b/daemon/algod/api/server/v2/generated/data/routes.go index 3e44f8c82f..8b842d8bbb 100644 --- a/daemon/algod/api/server/v2/generated/data/routes.go +++ b/daemon/algod/api/server/v2/generated/data/routes.go @@ -136,174 +136,178 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "jbnbH2//fwAAAP//SDOA3yjJAAA=", + "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/generated/experimental/experimental_routes.yml b/daemon/algod/api/server/v2/generated/experimental/experimental_routes.yml new file mode 100644 index 0000000000..d62923b4bf --- /dev/null +++ b/daemon/algod/api/server/v2/generated/experimental/experimental_routes.yml @@ -0,0 +1,21 @@ +package: experimental +generate: + echo-server: true + embedded-spec: true +output-options: + include-tags: + - experimental + - public + exclude-tags: + - common + - private + - participating + - nonparticipating + - data + type-mappings: + integer: uint64 + skip-prune: true +additional-imports: + - alias: "." + package: "github.com/algorand/go-algorand/daemon/algod/api/server/v2/generated/model" +output: ./server/v2/generated/experimental/routes.go diff --git a/daemon/algod/api/server/v2/generated/experimental/routes.go b/daemon/algod/api/server/v2/generated/experimental/routes.go new file mode 100644 index 0000000000..105fee6226 --- /dev/null +++ b/daemon/algod/api/server/v2/generated/experimental/routes.go @@ -0,0 +1,321 @@ +// Package experimental provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/algorand/oapi-codegen DO NOT EDIT. +package experimental + +import ( + "bytes" + "compress/gzip" + "encoding/base64" + "fmt" + "net/url" + "path" + "strings" + + . "github.com/algorand/go-algorand/daemon/algod/api/server/v2/generated/model" + "github.com/getkin/kin-openapi/openapi3" + "github.com/labstack/echo/v4" +) + +// ServerInterface represents all server handlers. +type ServerInterface interface { + // Simulates a raw transaction or transaction group as it would be evaluated on the network. WARNING: This endpoint is experimental and under active development. There are no guarantees in terms of functionality or future support. + // (POST /v2/transactions/simulate) + SimulateTransaction(ctx echo.Context) error +} + +// ServerInterfaceWrapper converts echo contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface +} + +// SimulateTransaction converts echo context to params. +func (w *ServerInterfaceWrapper) SimulateTransaction(ctx echo.Context) error { + var err error + + ctx.Set(Api_keyScopes, []string{""}) + + // Invoke the callback with all the unmarshalled arguments + err = w.Handler.SimulateTransaction(ctx) + return err +} + +// This is a simple interface which specifies echo.Route addition functions which +// are present on both echo.Echo and echo.Group, since we want to allow using +// either of them for path registration +type EchoRouter interface { + CONNECT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + DELETE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + GET(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + HEAD(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + OPTIONS(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + PATCH(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + POST(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + PUT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + TRACE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route +} + +// RegisterHandlers adds each server route to the EchoRouter. +func RegisterHandlers(router EchoRouter, si ServerInterface, m ...echo.MiddlewareFunc) { + RegisterHandlersWithBaseURL(router, si, "", m...) +} + +// Registers handlers, and prepends BaseURL to the paths, so that the paths +// can be served under a prefix. +func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL string, m ...echo.MiddlewareFunc) { + + wrapper := ServerInterfaceWrapper{ + Handler: si, + } + + router.POST(baseURL+"/v2/transactions/simulate", wrapper.SimulateTransaction, m...) + +} + +// Base64 encoded, gzipped, json marshaled Swagger object +var swaggerSpec = []string{ + + "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", +} + +// GetSwagger returns the content of the embedded swagger specification file +// or error if failed to decode +func decodeSpec() ([]byte, error) { + zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) + if err != nil { + return nil, fmt.Errorf("error base64 decoding spec: %s", err) + } + zr, err := gzip.NewReader(bytes.NewReader(zipped)) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %s", err) + } + var buf bytes.Buffer + _, err = buf.ReadFrom(zr) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %s", err) + } + + return buf.Bytes(), nil +} + +var rawSpec = decodeSpecCached() + +// a naive cached of a decoded swagger spec +func decodeSpecCached() func() ([]byte, error) { + data, err := decodeSpec() + return func() ([]byte, error) { + return data, err + } +} + +// Constructs a synthetic filesystem for resolving external references when loading openapi specifications. +func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { + var res = make(map[string]func() ([]byte, error)) + if len(pathToFile) > 0 { + res[pathToFile] = rawSpec + } + + return res +} + +// GetSwagger returns the Swagger specification corresponding to the generated code +// in this file. The external references of Swagger specification are resolved. +// The logic of resolving external references is tightly connected to "import-mapping" feature. +// Externally referenced files must be embedded in the corresponding golang packages. +// Urls can be supported but this task was out of the scope. +func GetSwagger() (swagger *openapi3.T, err error) { + var resolvePath = PathToRawSpec("") + + loader := openapi3.NewLoader() + loader.IsExternalRefsAllowed = true + loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { + var pathToFile = url.String() + pathToFile = path.Clean(pathToFile) + getSpec, ok := resolvePath[pathToFile] + if !ok { + err1 := fmt.Errorf("path not found: %s", pathToFile) + return nil, err1 + } + return getSpec() + } + var specData []byte + specData, err = rawSpec() + if err != nil { + return + } + swagger, err = loader.LoadFromData(specData) + if err != nil { + return + } + return +} diff --git a/daemon/algod/api/server/v2/generated/model/types.go b/daemon/algod/api/server/v2/generated/model/types.go index f184d1a5f2..e9e07b3592 100644 --- a/daemon/algod/api/server/v2/generated/model/types.go +++ b/daemon/algod/api/server/v2/generated/model/types.go @@ -1045,6 +1045,30 @@ type NodeStatusResponse struct { // TimeSinceLastRound TimeSinceLastRound in nanoseconds TimeSinceLastRound uint64 `json:"time-since-last-round"` + + // UpgradeDelay Upgrade delay + UpgradeDelay *uint64 `json:"upgrade-delay,omitempty"` + + // UpgradeNextProtocolVoteBefore Next protocol round + UpgradeNextProtocolVoteBefore *uint64 `json:"upgrade-next-protocol-vote-before,omitempty"` + + // UpgradeNoVotes No votes cast for consensus upgrade + UpgradeNoVotes *uint64 `json:"upgrade-no-votes,omitempty"` + + // UpgradeNodeVote This node's upgrade vote + UpgradeNodeVote *bool `json:"upgrade-node-vote,omitempty"` + + // UpgradeVoteRounds Total voting ounds for current upgrade + UpgradeVoteRounds *uint64 `json:"upgrade-vote-rounds,omitempty"` + + // UpgradeVotes Total votes cast for consensus upgrade + UpgradeVotes *uint64 `json:"upgrade-votes,omitempty"` + + // UpgradeVotesRequired Yes votes required for consensus upgrade + UpgradeVotesRequired *uint64 `json:"upgrade-votes-required,omitempty"` + + // UpgradeYesVotes Yes votes cast for consensus upgrade + UpgradeYesVotes *uint64 `json:"upgrade-yes-votes,omitempty"` } // ParticipationKeyResponse Represents a participation key used by the node. @@ -1074,6 +1098,15 @@ type PostTransactionsResponse struct { TxId string `json:"txId"` } +// SimulationResponse defines model for SimulationResponse. +type SimulationResponse struct { + // FailureMessage \[fm\] Failure message, if the transaction would have failed during a live broadcast. + FailureMessage string `json:"failure-message"` + + // MissingSignatures \[ms\] Whether any transactions would have failed during a live broadcast because they were missing signatures. + MissingSignatures bool `json:"missing-signatures"` +} + // StateProofResponse Represents a state proof and its corresponding message type StateProofResponse = StateProof diff --git a/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go b/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go index 64f2bc2841..bc7cdf5d5b 100644 --- a/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go +++ b/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go @@ -130,173 +130,177 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "H4sIAAAAAAAC/+x9+3PcNtLgv4Ka/ar8uOFIfmXXqtr6TrGSrM6PuCxt9u6zfAmG7JnBigMwBCjNxKf/", - "/QoNgARJgMORFHvz1f5ka4hHo9Fo9BufJ6lYF4IDV3Jy9HlS0JKuQUGJf9E0FRVXCcv0XxnItGSFYoJP", - "jtw3IlXJ+HIynTD9a0HVajKdcLqGpo3uP52U8GvFSsgmR6qsYDqR6QrWVA+stoVuXY+0SZYisUMcmyFO", - "TyY3Ax9olpUgZR/KH3m+JYyneZUBUSXlkqb6kyTXTK2IWjFJbGfCOBEciFgQtWo1JgsGeSZnbpG/VlBu", - "vVXayeNLumlATEqRQx/OV2I9ZxwcVFADVW8IUYJksMBGK6qInkHD6hoqQSTQMl2RhSh3gGqA8OEFXq0n", - "Rx8nEngGJe5WCuwK/7soAX6DRNFyCWryaRpa3EJBmSi2Dizt1GK/BFnlShJsi2tcsivgRPeakbeVVGQO", - "hHLy4ftX5NmzZy/1QtZUKcgskUVX1czur8l0nxxNMqrAfe7TGs2XoqQ8S+r2H75/hfOf2QWObUWlhPBh", - "OdZfyOlJbAGuY4CEGFewxH1oUb/uETgUzc9zWIgSRu6JaXyvm+LP/1V3JaUqXRWCcRXYF4Jfifkc5GFe", - "9yEeVgPQal9oTJV60I+HyctPn59Mnxze/OnjcfJf9s8Xz25GLv9VPe4ODAQbplVZAk+3ybIEiqdlRXkf", - "Hx8sPciVqPKMrOgVbj5dI6u3fYnua1jnFc0rTScsLcVxvhSSUEtGGSxolSviJiYVzzWb0qNZaidMkqIU", - "VyyDbKq57/WKpSuSUmmGwHbkmuW5psFKQhajtfDqBg7TjY8SDdet8IEL+tdFRrOuHZiADXKDJM2FhESJ", - "HdeTu3Eoz4h/oTR3ldzvsiLnKyA4uf5gLlvEHdc0nedbonBfM0IlocRdTVPCFmQrKnKNm5OzS+xvV6Ox", - "tiYaabg5rXtUH94Y+nrICCBvLkQOlCPy3Lnro4wv2LIqQZLrFaiVvfNKkIXgEoiY/xNSpbf9f539+I6I", - "krwFKekS3tP0kgBPRRbfYztp6Ab/pxR6w9dyWdD0Mnxd52zNAiC/pRu2rtaEV+s5lHq/3P2gBClBVSWP", - "AWRG3EFna7rpT3peVjzFzW2mbQlqmpSYLHK6nZHTBVnTzV8PpxYcSWiekwJ4xviSqA2PCml67t3gJaWo", - "eDZChlF6w7xbUxaQsgWDjNSjDEBip9kFD+P7wdNIVh44bpAoOPUsO8DhsAnQjD66+gsp6BI8kpmRv1vO", - "hV+VuAReMzgy3+KnooQrJipZd4rAiFMPi9dcKEiKEhYsQGNnFh2ae5g2lr2urYCTCq4o45BpzotACwWG", - "E0Vh8iYcVmb6V/ScSvjmeewCb76O3P2F6O764I6P2m1slJgjGbgX9Vd7YMNiU6v/COXPn1uyZWJ+7m0k", - "W57rq2TBcrxm/qn3z6GhksgEWohwF49kS05VVcLRBX+s/yIJOVOUZ7TM9C9r89PbKlfsjC31T7n56Y1Y", - "svSMLSPIrGENalPYbW3+0eOF2bHaBJWGN0JcVoW/oLSllc635PQktslmzH0J87hWZX2t4nzjNI19e6hN", - "vZERIKO4K6hueAnbEjS0NF3gP5sF0hNdlL/pf4oi171VsQihVtOxvW/RNmBtBsdFkbOUaiR+sJ/1V80E", - "wGgJtGlxgBfq0WcPxKIUBZSKmUFpUSS5SGmeSEUVjvQfJSwmR5M/HTTGlQPTXR54k7/Rvc6wk5ZHjYyT", - "0KLYY4z3Wq6RA8xCM2j8hGzCsD2UiBg3m6hJiWkWnMMV5WrW6CMtflAf4I92pgbfRpQx+O7oV1GEE9Nw", - "DtKIt6bhA0k81BNEK0G0orS5zMW8/uHhcVE0GMTvx0Vh8IGiITCUumDDpJKPcPm0OUn+PKcnM/KDPzbK", - "2YLnW305GFFD3w0Le2vZW6w2HNk1NCM+kAS3U5QzvTUODVqGvw+KQ51hJXIt9eykFd34b7atT2b691Gd", - "/xgk5uM2TlyoRVnMGQUGf/E0l4cdyukTjrXlzMhxt+/tyEaPEiaYW9HK4H6acQfwWKPwuqSFAdB+MXcp", - "46iBmUYG1jty05GMLgizd4Y9WkOobn3Wdp6HICRICh0Yvs1Fevk3Klf3cObnbqz+8cNpyApoBiVZUbma", - "TUJShn+8mtHGHDHdELV3MvemmtVLvK/l7VhaRhX1lmbhDYslBvXYD5kelAHd5Uf8D82J/qzPtmb9ZtgZ", - "OUcGJs1xth6ETKvyRkEwM+kGaGIQZG20d6K17r2gfNVMHt6nUXv0nTEY2B2yi8AdEpt7Pwbfik0Ihm/F", - "pncExAbkfdCHHgfFSAVrOQK+EwuZwP236KNlSbd9JOPYY5CsF6hFV4mngfs3vp6lsbwez0V5O+7TYSuc", - "NPZkQvWoHvOddpCETasisaQYsEmZBp2BGhfeMNPoDh/CWAsLZ4r+DliQetT7wEJ7oPvGglgXLId7IP1V", - "kOnPqYRnT8nZ345fPHn689MX32iSLEqxLOmazLcKJHlodTMi1TaHR/2VoXZU5So8+jfPnRWyPW5oHCmq", - "MoU1LfpDGeumEYFMM6Lb9bHWRjOuugZwzOE8B83JDdqJMdxr0E6Y1BLWen4vmxFDWNbMkhELSQY7iWnf", - "5TXTbP0lltuyug9VFspSlAH7Gh4xJVKRJ1dQSiYCrpL3tgWxLZx4W3R/N9CSayqJnhtNvxVHgSJAWWrD", - "x/N9M/T5hje4GeT8Zr2B1dl5x+xLG/nOkihJAWWiNpxkMK+WLU1oUYo1oSTDjnhH/wDqbMtTtKrdB5HG", - "1bQ142jil1ueejqb3qgcsmVrE+6um3Wx4uxzZqoHMgCORscb/Ixq/Qnkit67/NKdIAT7K7eRBliS6Yao", - "Bb9hy5XyBMz3pRCL+4cxNEsIUPxgxPNc9+kL6e9EBnqxlbyHy7gZrKF1vac+hdO5qBShhIsM0KJSyfA1", - "HXHLoz8Q3ZjKv/nVykjcc9CElNJKr7YqCDrpepyj6ZjQ1FBvgqiRES9G7X4yrcx0xuWbl0AzrdUDJ2Ju", - "XQXWiYGLpOhhVO6is0JC4Cy14CpKkYKUkCXWRLETNNfOMBE1gCcEHAGuZyFSkAUt7wzs5dVOOC9hm6A/", - "XJKHr3+Sj74CvEoomu9ALLYJobdW+Kw/qA/1uOmHCK47uU92tATieK7WLjWDyEFBDIV74SS6f12Iert4", - "d7RcQYmemd+V4t0kdyOgGtTfmd7vCm1VRKK8rKJzztZot+OUCwmp4JkMDpZTqZJdbFk3amljegUeJwxx", - "Yhw4IpS8oVIZbyLjGRpBzHWC8xgBRU8RBzgqkOqRf3KyaH/sVN+DXFayFkxlVRSiVJCF1sBhMzDXO9jU", - "c4mFN3Yt/SpBKgm7Ro5hyRvfIsusxCCIqtrobt3t/cWhaVrf89sgKltANIgYAuTMtfKw60e6RABhskG0", - "IRwmO5RTh9dMJ1KJotDcQiUVr/vF0HRmWh+rvzdt+8RFVXNvZwL07MrBZCG/Npg1MU4rqlVoHJms6aWW", - "PVAhNm7PPsz6MCaS8RSSIcrXx/JMt/KPwI5DGrFF2ChKb7bO4ejQb5DookSwYxdiC44YRt7TUrGUFSgp", - "vobtvQvO3QmC5nqSgaJMK+veByNEF35/YvzY3TFvJ0iP0mH74PeU2MBycibxwmgDfwlb1FjemwCpcy+s", - "6h40gcCo+nRTThBQF3ahBRi/CWxoqvKtvubUCrbkGkogspqvmVIm4q2tKChRJP4AQfvgwIzWGG6Ci9wO", - "jLHOn+FQ3vL6WzGdGIlqGL7zjljVQoeVpAoh8hG6dw8ZQQhG+U1JIfSuMxtg6aLwHCW1gLRCDHpCaub5", - "QLbQjCsg/0dUJKUcBdZKQX0jiBLZLF6/egZ9gdVzWg9pgyHIYQ1GDscvjx93F/74sd1zJskCrl1Usm7Y", - "Rcfjx6gFvxdStQ7XPVha9HE7DfB2NJzqi8LKcF2esttDZ0ces5PvO4PX1lZ9pqS0hKuXf2cG0DmZmzFr", - "92lknHcSxx1lE/WGDq0b9x3NPL+PjaYZOgRdf2LPqd58jPnVtXyVb++BT5uBSAlFCRJPla+XSPNVLPzA", - "dXvs5FYqWPdNN6brzxHB5oMTC3pSpuA545CsBYdtMFeLcXiLH0O9zcmOdEYeG+vbFZta8HfAas8zhgrv", - "il/cbY+U39cBJfew+d1xO1Y7P2QftVLIC0JJmjPUWQWXqqxSdcEpSsXeWQ443pysH9eTXrkmYcUsoDfZ", - "oS44RadrLSsHnQULCGjB3wM4dUlWyyVI1ZEPFgAX3LZinFScKZxrrfcrMRtWQIner5lpuaZbsqA5qnW/", - "QSnIvFLtGxMji6XSWpcxIeppiFhccKpIDloDfcv4+QaHcyZ4RzMc1LUoL2sszILnYQkcJJNJ2EH4g/mK", - "sRt2+Ssbx4FpXuazMTrp8Zvw462CVurS/334n0cfj5P/oslvh8nL/3Hw6fPzm0ePez8+vfnrX/9f+6dn", - "N3999J//EdopB3so7tVCfnpipcnTExQZGqtTD/YvZnFYM54Eicz3rXRoizzUgo8joEeNWc/u+gVXG64J", - "6YrmLKPqduTQZXG9s2hOR4dqWhvRUSDdWve8iO/AZUiAyXRY462v8b5PPRxhjmZQGzSO52VRcbOVlbSm", - "WAygdL5NsZjWWQQme/iIYIj5ijrHvP3z6YtvJtMmNLz+rvVr8/VTgJJZtgklAGSwCclX9oDgwXggSUG3", - "ElSYeyDsQTeu8Sb5w65BC+ZyxYovzymkYvMwh3NhaVZP2/BTbuLF9PlBo+rW2mrE4svDrUqADAq1CmUV", - "tiQFbNXsJkDH0VWU4gr4lLAZzLp6UrYE6RzKOdAFZrehYVCMCbOtz4EhNEcVHtb9hYxSRkL0g8Kt5dY3", - "04m9/OW9y+N24BBc3TlrC6r7Wwny4IfvzsmBZZjygclFMUN72QMB+4MNkG25QDU3M7nUJhnngl/wE1gw", - "zvT3owueUUUP5lSyVB5UEspvaU55CrOlIEcu5vaEKnrBe5JWtNyBF+1Mimqes5Rc+hJxQ54mhbU/wsXF", - "R5ovxcXFp543qC+/2qmC/MVMkFwztRKVSmyOXlLCNS2zAOiyztHCkU2G7dCsU2LHNqzY5gDa8cM8jxaF", - "7OZq9JdfFLlevkeG0mYi6C0jUonSySJaQDHQ4P6+E/ZiKOm1S/CsJEjyy5oWHxlXn0hyUR0ePgPSSl74", - "xV75mia3BbQsVbfKJelaqXDhRq+BjSppUtAlyODyFdACdx/l5TXaRPOcYLdW0oQLCsOhmgU4fMQ3wMCx", - "dwA4Lu7M9HLFFsJLwE+4hdhGixuNq+G2++WlUdx6uzqpGL1dqtQq0Wc7uCqpSdztTJ2DvdRClvP/SLbE", - "GBubrj4Hkq4gvYQMM2dhXajttNXduRitoOlYB5Mmw9wEQWMaJBr15kCqIqNWFKd8281Hk6CUC/L5AJew", - "PRdNFuU+CWjtfCgZO6hIqZ50qYnVP7Z2jO7mWz825oAUhUsrwvhyRxZHNV24PvGDbETeezjEIaJo5evE", - "EEHLACIM8UdQcIuF6vHuRPqh5WktY25uvkBCuuP9xDZplCfrcvZXg2lI5vsasFyFuJZkTrXcLmylBZPz", - "43GxStIlRCRk3646MrOmZYvFQXbde8GbTiy6F1rvvgmCbBones1BSgH9RZMKKjOdQAM3kzHd4wpmBAso", - "WYTNcxST6ogMw3Ro2bJvm4owMdDCBAwlbwQOB0YbI75ks6LSFYHAWhnuLI+SAX7HHLahzOVTz0fuFcSo", - "85Idz+2e0552afOXXdKyy1T2VcsRWcdawsewvNB2CI4CUAY5LM3CTWNHKE0+XbNBGo4fF4uccSBJyN1O", - "pRQpM1U8mmvGzgFaPn5MiDEBk9EjhMjYAxtdUjgweSf8s8mX+wDJbT4gdWOjM8v7G8KhyyYATYs8otAs", - "nPFIqKPjANTGaNT3VydSCIchjE+JZnNXNNdszmp8zSC9BFoUWzvpstYp+igmzg5Y4M3FsteazFV0m9X4", - "MpMDOizQDUA8F5vE5C4EJd75Zq7pPRiTh5kUoYNpUpUfSDIXG3S049ViYsB2wBKHw4HhafgbJpFesV/s", - "NjfADE07LE2FqFAiyVhzXk0uMXFizNQRCSZGLg+97ONbAdAxdjR1+qzyu1NJbYsn/cu8udWmTVUNF+4c", - "Ov6xIxTcpQj++laYOl/YmhA+QCrKLG6n0ITKVF34sG9esGUbNd8YnVE8UITxuK1tOBWiv3MRf3ALnmae", - "AUScmGD9HiTfbQqhpVsTzG8yuy1SjJxYgslRksZmJRlf5lYwiKEptGAXjeIwbpbcVGpxA46TnUObG1Hy", - "h2ApijAc+2gqHyx+BqCInPIGDpTD7wiJze4ehOUmTh/vu6J98KC0AyvaNQU8XSt0O2jy6Xsz+z5TCTmg", - "9py0tI3kMuTjvrj4KAFFszPXzbPyYeUCyrePvGidEpZMKmi8TVqCdZj+0nZ8igWThFjEV6eKcqHX90GI", - "Wp4zFTmwY2uZX3wFV0JBsmClVAm66oJL0I2+l2h9+l43DSsV7XggUzuQZeFLFKe9hG2SsbwK06ud9/WJ", - "nvZdLTvIao6CCeMEaLoic6x1GYwSHJjaBJIOLviNWfAbem/rHXcadFM9canJpT3HH+RcdG66IXYQIMAQ", - "cfR3LYrSgQvUy43rc0dPwTCHE6/T2ZCboneYMjf2zvgql6EXE+bMSANrwdCgaFhmICCHLEtRFYapN2Wu", - "g1lsXKikZfwIoKs28EhFL00mRnuD+bK2qYTDpoxePWpo23bHgHz8eHz3cFYITnK4gnx3+CtFjDsDDkZG", - "mBEw9IZgILmL8dgt1fd3oEFYvdIujEFq6Uk3Q47bRjWyhaca3RoJVuPOpoyO9t5pCc3RW0PffdddUSQZ", - "5BBM0PiHl4FBiwLTrF3jULKCHozxDDZhcMynaagYdd94XzGuTOHC+6qJ1hln/LL9ymFjUFCYGlf7112L", - "65jeLvloji8qQpS1c2CQEePgtWbnlfHvUl/kGqdFwbJNx+9pRo1ax+8FY3hB2cF2YMCjjVDqTwmyXTGu", - "MeaZusWtgi2zUZg5b9d182UafyomXdX9PqLq1MBduDoHmr+G7U+6LS5ncjOd3M1NGsK1HXEHrt/X2xvE", - "M4bhGbdZK+phT5TToijFFc0T60yOkWYprixpYnPne/7C0lqY651/d/zmvQX/ZjpJc6BlUms70VVhu+IP", - "sypTnC5yQFxV7xVVtX3OaMPe5tcVtXwH9PUKbAVlT6HulXpsggu8o2gd0otwNPBO97KNgzBLHIiHgKIO", - "h2hcdSYaoh0BQa8oy52PzEEbidzFxY27G4NcwR/gzpEU/l10r+ymd7rDp6Ohrh08yZ9roMbz2pQxl0Tw", - "bric1oLR9YakuqZYqNF4QPrMiVdr9BokMmdp2J/K51ITBzdxMroxwcYRfVqPWLFI2BWvmDeWbiZHGLU7", - "QHpzBJHpin7GcDcX9v2ZirNfKyAsA670pxJPZeegov3Uetb712lYqrQDG298M/xdZAy/SGn3xrMy15CA", - "4Ufl9MA9qa1+bqG190n/4IUf7BHc58/YuxIHAvMsfVhqNokKq3Z0zWgJfedbNc7+ZqulRuYIvj3DZLIo", - "xW8QNlWhhS+QF+jKsjKMaP0N+CwgrndZTO3JaZ7QaWaPbndMuvE9Tu2AxAjV4857IThYH9J5oyk3W22e", - "gmjFtYcJxs8gOTDjNwRjYe5l3eT0ek5DxTO1kKFh8twvLb+5EsR1dri3PhpmK+XOiBc3VrdlJmO+gLJJ", - "2e1X37mlwGCmHS0qNJIBUq0vE0xNrE8uRWCYil9Tbl4UQW8EHiXbWyv4ziB0LUqsdyHDLv4MUrYOGpcu", - "Lj5mad+dm7ElM+9pVBK8BxvsQOYhIkNF9tELE07XoOZ0QQ6n3pMwdjcydsUkm+eALZ6YFnMqwRhVXOSG", - "66KXB1ytJDZ/OqL5quJZCZlaSYNYKUgt1KF6UweqzEFdA3ByiO2evCQPMURHsit4pLFo7+fJ0ZOX6GA1", - "fxyGLgD7cM4QN8mQnTj9P0zHGKNkxtCM2446C1oDzGtnccY1cJpM1zFnCVtaXrf7LK0pp0sIR4Wud8Bk", - "+uJuoi+ggxeemad6pCrFljAVnh8U1fwpkmmm2Z8Bg6RivWZqbQM5pFhrempeYzCTuuHMuz+2kK6Dy33E", - "eKjChYN0lMgv6/cx91to1Ri19o6uoY3WKaGmyEnOmkhFV96bnLoaSlhZuC4obHCj59JLRzEHAxcXpCgZ", - "V6hYVGqR/IWkK1rSVLO/WQzcZP7N80A15XZVT74f4F8c7yVIKK/CqC8jZO9kCNuXPOSCJ2vNUbJHTWan", - "dyqjgVvhEJ1YnNDw0GOFMj1KEiW3qkVu1OPUdyI8PjDgHUmxXs9e9Lj3yr44ZVZlmDxopXfo7x/eWClj", - "LcpQYcTmuFuJowRVMrjCOP3wJukx77gXZT5qF+4C/dd1njqR0xPL3FmOKgL7eHw83QB9Pn5k4m28PW1P", - "T0vmCrp9UMMZ5wExjwXu8nvc5RmRVud9oHIcehx0ESNCKwG2g7H9NOC7mxg8l09rh2I4ai8tRJnfisCS", - "Xe352sdjMyYDdqvYBaI/aAY1t0NNSbvO95ePqHFukX5kh/7iYMU/usB+ZWaDSHYriGyi9wZBcDuz+rsX", - "XEbJt2IzdlM7vNtt7L8AaoIoqVie/dTUBuk88VBSnq6CwSJz3fHn5jG6enHmMAcrY64o5yYaoW+bQC3l", - "Z6fNBPStf4qx86wZH9m2++qEWW5ncQ3gbTAdUG5CjV6mcj2Bj9V22YU6rS9fiozgPE0ZxuZe779W4tWU", - "/7UCqUL3In4wqQVoUV9oKjal3YFnaMeYkR/MY9IrIK0qcWg/YOsqNxXHTIFt4+qpilzQbEr0OOffHb8h", - "ZlbTxzypZEqqL82121pFPD53n0Dbodja+8jo06uWCos2SkXXRahEiW5x7hpgHRTfu4SKtY+dGTkxNg3p", - "NGYziaaHBSvXkJF6OitVI03o/yhF0xUaC1osNU7y498CcFQpvfc363e06rKreO403PY5APMawJQILTlc", - "M2neEIYraFdFqUsEWTHAVUlpL6+sODeUEpSKh0pY3QbtDjgTBekcUEHIOojfU3qxYep7Po1whr2CdQy7", - "7yz0Ht40NTbq95Hc2/Ap5YKzFKsIhq5m+x7xGO/siIKL4cwAG28jJ4HDFXzdoU7WsFiMvvfgGKFFXN89", - "5H3Vm2qow/yp8OHbFVVkCUpazgbZ1D1SYi3UjEuwZXTxaWqPT4qy5fFGDhkMomjk5D3JCJOzIyaH7/W3", - "d9YghVmLl4yj6ulyJEyCpLEh43OpSuurTJGlwAwKeyj8NX3UfWZYrCWDzaeZe14VxzAOY71sEx3RH+rY", - "xUrY2ATd9pVuawrqNT+38uDMpMdFYSeNP2ETlAfUhkcRHPB514FeHnLr8f3RBshtMMgJ71NNaHCFIRJQ", - "EJsaE3nOpZMEo4VWQ1HYgpj46GAdrWCY6BvGoXn8N3BBpMErATcGz2ukn0xLqowIOIqnnQPNMS4ixNCk", - "sk6xuw7V2WAbT1qkEzdHfBubl2gijKNu0AhulG/rN4c1dXvCxCt87Nwisv+uDEpVVoiyyTXtl2ZCjEMz", - "bveWVfsC6B+DvkxkuquSmpOzz00UK1Uyr7IlqIRmWcie8C1+JfiVZBVKDrCBtKrrNxcFSbEyX7tUYZ/a", - "7ESp4LJaD8zlGtxxOu/ppgA1+M9HuR3GwOv5Fv8NFS+O74wND9o7xt7FAmV1+tw+cnN7pJ7Uq2k6kWyZ", - "jMcE3il3R0cz9e0Ivel/r5Sei2UbkC9coGyIy/l7FOJv3+mLw6/f1avIba6WurwWhoMK9+Amqo11YZg2", - "V3JZp705vQf9hg0Q8af5pnj5RfJaPFsvNfer8WvHslvSaDIWVbZ+gqJkkAVFc9JNXJnJPkcowjb9WCyZ", - "CSXTn3u9x0mGPTkbxx5EqAtS7AP02kVAk4IyG7TRMIs+Zm26V9xcOHTomg3uLsImUUUtdq+vYglPLg/Y", - "ZHZ0HjO7BFtUqSjhionKhUO4eDmnEppf7WPSXl5xdP39uBmc6uuaQaNG23P7cIZZptXJX/9koisJcFVu", - "/wVMuL1N7z0FF6pZ3HoIzgpXQXuTGntXntSvyV1eJWuRDSVMv/6JnDjf0qh7xxFyqNySyOzzS8Fk8Te2", - "+L9rpqXP0dO+tZ2Oi2J46kiGeH9y03Df6WOlpvT5HLK6vXfn1zyg55sQArqKl87MYaPCT+X0smGvgcCm", - "AKx16yU2x6tnjCUom+SI2mqSA5UwgGG/apttOxLJ55s3uv24ZPvwE4bxkrNNmVlknoWQrHmWJfS24ciQ", - "43N8ntDzGPbHcvF+V5AqUbbimEqAfQro6sm8d3P/XXo2YiipI7Md/Q+UmZ1OfN4STFS0x4s2JXLQq4Yu", - "10CpetMmwOxtZ6YPSQVTN4T+YUFzGX6lKhrs2ql84gWsBAo9hxd2mo2o9m2XM/ViIFg2jMhwJoAJ/v7v", - "iUwT136/6Oy91jSsVfQKL3jFQ8yjOrM9AkjqKGqUDHG/lsDtk8qLEGp2Z0UtFpAqdrWj0MU/VsC9IgpT", - "ZwlGWBZe3QtWZ9lgQdH9/RwNQEN1KAbh8Qr73xmcWI7oJWwfSNKihuArP1Mn3N+mliRiAG8tLXgUQoai", - "FI3rygaOMVlTBmLBRQWb7tBU5Y4+r+jJObecy5FkW+IZmPJKhGzfo+bSXfeqBIYJI7FaGP0HzuIWjxN8", - "T07WTx+7WpS+XZCc9iv2X9talliWpPbWuqqWIN1vrgaRmSVnl+A/AIm+cSyhYFsEjb3OjpwMyEm97G/3", - "OFcX6EU9M2tyOPr5voEa0Bj9lOZCK8FJLN2pnTZRh3k9kCY4FMUUfDkO4VpAaR/KxZshFxISJVxo3RAc", - "Q6gwEbC3QoKMvrtggItWQ/3QlHvF92dMsQxqA1/9BZIS1lRDV3pFWeNzDiH7lfnuElxdTa6dNu2aXpOd", - "VVVd9g6TPST6VL8g9rbcnTh7G/M249w8yy9DMYVco9L3vxalyKrUFoLxDkbtAhhdsGyAlQQtw2l/lT0j", - "X47VwN94ZQguYXtg7C/pivKlV17Nh96I9mYNXuWyzm7fq+U/bOTMl2YBy3uB82taz6eTQog8iThcT/uF", - "Zrtn4JKll1rMrpq498gTi+Qh+vnqiJrr1dYVVi0K4JA9mhFyzE2mkQuuab901JmcP1BD829w1qwytZ+t", - "YX92wcMpG1jUp7wjf3PDDHM1CZr53XEqM8iOMqabSJHbkl4HHhztx9ONDnfpPgLZEJWBIiSl3LJU16jz", - "3TfuB0jfewVxWPvxK/k1Ucyl8RGhtOQ8N13h5W3j+hn3HqPrsAM831jjvcjouJEF5yuHGr+tkeItJUoJ", - "reXvsv/YBTZ8ydsiiVmTepmmALEJU2vvi2fck69qm1kYz33TGpbtExxr/vZNchJ9hqYMq0c4+lyWVzT/", - "8mY1rOd4jPiwz4qHF+rrvz6SDSrl7eL93tBRc3u67v1Nzd+jGfAfoPco6Oy1Q1nnT/0SpnORYYl7mpNc", - "NC/i4pDkGsc03uEn35C5zaIrSkiZZJ0E42v3qkmt7uEjX81r88P65a51/iTUHcjYKgiiIO+aFxKUwPuh", - "gbA5ol+ZqURObpDKQ9TXI4sA/kI8yi9ns+O6uGy5jc2LM514SFHCPbuPvUCwPd3H/UI9Y5dnXKT60qkk", - "9Nc5+rZu4TZwUTdrGxv70EfuUBn9MSEL4dcxdHeMmTAIwadlCIJKfnnyCylhgW9HCvL4MU7w+PHUNv3l", - "afuzPs6PHwfFuC8WLWFwZMew8wYpxjrTeqkwsClYGSn698Eyd3tho/uOYAcIV+fMIfgaDE7t4ka/cClo", - "lLl3GvjN0mzjXfzMQ5lbcj1RCPc/xXIXTHx+JE2mcxYqlme7DmUr6al5+RbTen62Cblf5e3dn40tu88m", - "7fuH+8TIdQ8AIiaw1tbk3lReOtOITCbbLZC3hMSVViVTW6wT5kyf7OdgTM0PtbfEeoHryjJW7lDiEupK", - "c41vpZJOsvlB0BxlAa3PYISiEiKfke82dF3kYJnUXx/M/wzP/vI8O3z25M/zvxy+OEzh+YuXh4f05XP6", - "5OWzJ/D0Ly+eH8KTxTcv50+zp8+fzp8/ff7Ni5fps+dP5s+/efnnB/oO0CAbQCeuKsXkf+MD1cnx+9Pk", - "XAPb4IQW7DVszVuYmozdK5s0RS4Ia8ryyZH76X867jZLxboZ3v06sUnvk5VShTw6OLi+vp75XQ6WaExN", - "lKjS1YGbp/cM5/H70zo9zMRC4Y6azB9NCriplhSO8duH787OyfH701lDMJOjyeHscPYEaxkXwGnBJkeT", - "Z/gTnp4V7vuBKyJ89PlmOjlYAc3RJ67/WIMqWeo+yWu6XEI5s8+N6p+unh44Me7gszUk3wx9O/Bf7jn4", - "3LK3Zzt6YqDLwWdXxGq4datKlPUzeB1GQjHU7GCOGchjm4L0GseXgsqdPPiM6kn09wOblhn+iGqiOQMH", - "zikVbtnC0me10bB2eqRUpauqOPiM/0GavDFMIoeQC8pkM1LSNJ8SpgidixKrR6l0pfmCK1vDpNdygpRq", - "iPw008Ste70yELgCdaZi79HHfgAiDkTcSMgJNJk3B7U1U8OL0e/uFZGtb5pW++a++XiYvPz0+cn0yeHN", - "n/R9Yv988exmpC/5VT0uOasvi5ENP2HNF7SK4/l9eni419PAPbW0WaTZpDocORDEYHYiWccsJ3arOgOR", - "Ghk7alN0hg89pXwznTzfc8WDtrtWiHbgSeRvaUZcgi/O/eTLzX3K0ZOv+Tox99bNdPLiS67+lGuSpznB", - "ll6xsf7W/51fcnHNXUstZFTrNS237hjLFlMgdrPxKqNLiZbckl1RlO244O1y9Z/QexBKso7wG6noLfjN", - "me71b37zpfgNbtJ98Jv2QPfMb57ueeb/+Cv+N4f9o3HYM8Pu7sRhrcBn8tr6EqiJ7D/A+mLb/s9bngZ/", - "7A/UfTI49PPB5/ZLPC0ZWa4qlYlrUx4leClgrWaa28KOaICuFSoliBugCSgkP9qsq3yLVneWAaEY3S4q", - "1Wi8urNzEzfmJT1C85z4knGcAA37OIupYEq9UB0JqeDm8d3OBWQheycy6F9AeMX8WkG5be4YC+Nk2uJA", - "loQC9ULvzND7DONmPwJDB4TxnvWJo35xt/X3wTVlSl9TNrIPMdrvrIDmB7ZwQOfXJlev9wUTEL0fPZ0o", - "/OtBXQ8r+LGrbIa+WmXLNWqsSb51Bve8tst8/KS3Dis2WnJojA1HBwcYDrMSUh1MbqafO4YI/+Onerdc", - "waR6124+3fz/AAAA///m3Czx7MQAAA==", + "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/generated/nonparticipating/public/public_routes.yml b/daemon/algod/api/server/v2/generated/nonparticipating/public/public_routes.yml index 0cc18f190c..8566bf764e 100644 --- a/daemon/algod/api/server/v2/generated/nonparticipating/public/public_routes.yml +++ b/daemon/algod/api/server/v2/generated/nonparticipating/public/public_routes.yml @@ -11,6 +11,7 @@ output-options: - common - participating - data + - experimental type-mappings: integer: uint64 skip-prune: true diff --git a/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go b/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go index 3ec9dfd564..dbfb7a17ca 100644 --- a/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go +++ b/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go @@ -63,7 +63,7 @@ type ServerInterface interface { // Gets the current node status. // (GET /v2/status) GetStatus(ctx echo.Context) error - // Gets the node status after waiting for the given round. + // Gets the node status after waiting for a round after the given round. // (GET /v2/status/wait-for-block-after/{round}) WaitForBlock(ctx echo.Context, round uint64) error // Compile TEAL source code to binary, produce its hash @@ -548,227 +548,234 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", + "qt9//P8BAAD//0vKaWunRwEA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/generated/participating/private/routes.go b/daemon/algod/api/server/v2/generated/participating/private/routes.go index 6b1676e8e8..9052808c03 100644 --- a/daemon/algod/api/server/v2/generated/participating/private/routes.go +++ b/daemon/algod/api/server/v2/generated/participating/private/routes.go @@ -158,175 +158,179 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/generated/participating/public/public_routes.yml b/daemon/algod/api/server/v2/generated/participating/public/public_routes.yml index 5dac08d5b6..a568d05443 100644 --- a/daemon/algod/api/server/v2/generated/participating/public/public_routes.yml +++ b/daemon/algod/api/server/v2/generated/participating/public/public_routes.yml @@ -11,6 +11,7 @@ output-options: - common - nonparticipating - data + - experimental type-mappings: integer: uint64 skip-prune: true diff --git a/daemon/algod/api/server/v2/generated/participating/public/routes.go b/daemon/algod/api/server/v2/generated/participating/public/routes.go index 613a5d1cd0..bf14f4ef1a 100644 --- a/daemon/algod/api/server/v2/generated/participating/public/routes.go +++ b/daemon/algod/api/server/v2/generated/participating/public/routes.go @@ -24,7 +24,7 @@ type ServerInterface interface { // Get a list of unconfirmed transactions currently in the transaction pool by address. // (GET /v2/accounts/{address}/transactions/pending) GetPendingTransactionsByAddress(ctx echo.Context, address string, params GetPendingTransactionsByAddressParams) error - // Broadcasts a raw transaction to the network. + // Broadcasts a raw transaction or transaction group to the network. // (POST /v2/transactions) RawTransaction(ctx echo.Context) error // Get a list of unconfirmed transactions currently in the transaction pool. @@ -177,183 +177,188 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "H4sIAAAAAAAC/+x9/XPbOJLov4Knu6okPlF2vmY3rpq65yQzs75JMqnYO7t3cd4sRLYkrCmAC4C2NHn5", - "31+hAZAgCVKU7Ul29uWnxCI+Go1Go9GfHyepWBeCA9dqcvxxUlBJ16BB4l80TUXJdcIy81cGKpWs0Ezw", - "ybH/RpSWjC8n0wkzvxZUrybTCadrqNuY/tOJhH+UTEI2OdayhOlEpStYUzOw3hamdTXSJlmKxA1xYoc4", - "fTn5NPCBZpkEpbpQ/sTzLWE8zcsMiJaUK5qaT4pcM70iesUUcZ0J40RwIGJB9KrRmCwY5Jma+UX+owS5", - "DVbpJu9f0qcaxESKHLpwvhDrOePgoYIKqGpDiBYkgwU2WlFNzAwGVt9QC6KAynRFFkLuANUCEcILvFxP", - "jt9PFPAMJO5WCuwK/7uQAL9Coqlcgp58mMYWt9AgE83WkaWdOuxLUGWuFcG2uMYluwJOTK8ZeV0qTeZA", - "KCfvvn9BHj9+/MwsZE21hswRWe+q6tnDNdnuk+NJRjX4z11ao/lSSMqzpGr/7vsXOP+ZW+DYVlQpiB+W", - "E/OFnL7sW4DvGCEhxjUscR8a1G96RA5F/fMcFkLCyD2xje90U8L5v+iupFSnq0IwriP7QvArsZ+jPCzo", - "PsTDKgAa7QuDKWkGfX+UPPvw8eH04dGnf3t/kvyP+/Pp408jl/+iGncHBqIN01JK4Ok2WUqgeFpWlHfx", - "8c7Rg1qJMs/Iil7h5tM1snrXl5i+lnVe0bw0dMJSKU7ypVCEOjLKYEHLXBM/MSl5btiUGc1RO2GKFFJc", - "sQyyqeG+1yuWrkhKlR0C25FrlueGBksFWR+txVc3cJg+hSgxcN0IH7igf15k1OvagQnYIDdI0lwoSLTY", - "cT35G4fyjIQXSn1Xqf0uK3K+AoKTmw/2skXccUPTeb4lGvc1I1QRSvzVNCVsQbaiJNe4OTm7xP5uNQZr", - "a2KQhpvTuEfN4e1DXwcZEeTNhciBckSeP3ddlPEFW5YSFLlegV65O0+CKgRXQMT875Bqs+3/dfbTGyIk", - "eQ1K0SW8peklAZ6KrH+P3aSxG/zvSpgNX6tlQdPL+HWdszWLgPyabti6XBNerucgzX75+0ELIkGXkvcB", - "ZEfcQWdruulOei5LnuLm1tM2BDVDSkwVOd3OyOmCrOnm26OpA0cRmuekAJ4xviR6w3uFNDP3bvASKUqe", - "jZBhtNmw4NZUBaRswSAj1SgDkLhpdsHD+H7w1JJVAI4fpBecapYd4HDYRGjGHF3zhRR0CQHJzMifHefC", - "r1pcAq8YHJlv8VMh4YqJUlWdemDEqYfFay40JIWEBYvQ2JlDh+Eeto1jr2sn4KSCa8o4ZIbzItBCg+VE", - "vTAFEw4/ZrpX9Jwq+OZJ3wVefx25+wvR3vXBHR+129gosUcyci+ar+7AxsWmRv8Rj79wbsWWif25s5Fs", - "eW6ukgXL8Zr5u9k/j4ZSIRNoIMJfPIotOdWlhOMLfmD+Igk505RnVGbml7X96XWZa3bGluan3P70SixZ", - "esaWPcisYI2+prDb2v5jxouzY72JPhpeCXFZFuGC0sardL4lpy/7NtmOuS9hnlRP2fBVcb7xL419e+hN", - "tZE9QPbirqCm4SVsJRhoabrAfzYLpCe6kL+af4oiN711sYih1tCxu29RN+B0BidFkbOUGiS+c5/NV8ME", - "wL4SaN3iEC/U448BiIUUBUjN7KC0KJJcpDRPlKYaR/p3CYvJ8eTfDmvlyqHtrg6DyV+ZXmfYycijVsZJ", - "aFHsMcZbI9eoAWZhGDR+QjZh2R5KRIzbTTSkxAwLzuGKcj2r3yMNflAd4PduphrfVpSx+G69r3oRTmzD", - "OSgr3tqG9xQJUE8QrQTRitLmMhfz6of7J0VRYxC/nxSFxQeKhsBQ6oINU1o9wOXT+iSF85y+nJEfwrFR", - "zhY835rLwYoa5m5YuFvL3WKV4sitoR7xniK4nULOzNZ4NBgZ/i4oDt8MK5EbqWcnrZjGf3JtQzIzv4/q", - "/PsgsRC3/cSFryiHOfuAwV+Cl8v9FuV0CcfpcmbkpN33ZmRjRokTzI1oZXA/7bgDeKxQeC1pYQF0X+xd", - "yji+wGwjC+stuelIRheFOTjDAa0hVDc+azvPQxQSJIUWDM9zkV7+iarVHZz5uR+re/xwGrICmoEkK6pW", - "s0lMygiPVz3amCNmGuLrncyDqWbVEu9qeTuWllFNg6U5eONiiUU99kOmBzLydvkJ/0NzYj6bs21Yvx12", - "Rs6RgSl7nJ0FITNPeftAsDOZBqhiEGRtX+/EvLr3gvJFPXl8n0bt0XdWYeB2yC0Cd0hs7vwYPBebGAzP", - "xaZzBMQG1F3QhxkHxUgNazUCvpcOMoH779BHpaTbLpJx7DFINgs0oqvC08DDG9/MUmteT+ZC3oz7tNgK", - "J7U+mVAzasB8py0kYdOySBwpRnRStkFroNqEN8w02sPHMNbAwpmmvwEWlBn1LrDQHOiusSDWBcvhDkh/", - "FWX6c6rg8SNy9qeTpw8f/fLo6TeGJAsplpKuyXyrQZH77m1GlN7m8KC7MnwdlbmOj/7NE6+FbI4bG0eJ", - "UqawpkV3KKvdtCKQbUZMuy7WmmjGVVcAjjmc52A4uUU7sYp7A9pLpoyEtZ7fyWb0ISyrZ8mIgySDncS0", - "7/LqabbhEuVWlnfxlAUphYzo1/CIaZGKPLkCqZiImEreuhbEtfDibdH+3UJLrqkiZm5U/ZYcBYoIZekN", - "H8/37dDnG17jZpDz2/VGVufmHbMvTeR7TaIiBchEbzjJYF4uGy+hhRRrQkmGHfGO/gH02ZanqFW7CyLt", - "f6atGUcVv9ryNHizmY3KIVs2NuH2b7M2Vrx+zk51T0XAMeh4hZ/xWf8Sck3vXH5pTxCD/YXfSAssyUxD", - "fAW/YsuVDgTMt1KIxd3DGJslBih+sOJ5bvp0hfQ3IgOz2FLdwWVcD1bTutnTkMLpXJSaUMJFBqhRKVX8", - "mu4xy6M9EM2YOrz59cpK3HMwhJTS0qy2LAga6Tqco+6Y0NRSb4KoUT1WjMr8ZFvZ6azJN5dAM/OqB07E", - "3JkKnBEDF0nRwqj9ReeEhMhZasBVSJGCUpAlTkWxEzTfzjIRPYAnBBwBrmYhSpAFlbcG9vJqJ5yXsE3Q", - "Hq7I/R9/Vg++ALxaaJrvQCy2iaG3evA5e1AX6nHTDxFce/KQ7KgE4nmueV0aBpGDhj4U7oWT3v1rQ9TZ", - "xduj5QokWmZ+U4r3k9yOgCpQf2N6vy20ZdHj5eUeOudsjXo7TrlQkAqeqehgOVU62cWWTaPGa8ysIOCE", - "MU6MA/cIJa+o0taayHiGShB7neA8VkAxU/QD3CuQmpF/9rJod+zU3INclaoSTFVZFEJqyGJr4LAZmOsN", - "bKq5xCIYu5J+tSClgl0j92EpGN8hy67EIojqSunuzO3dxaFq2tzz2ygqG0DUiBgC5My3CrAberr0AMJU", - "jWhLOEy1KKdyr5lOlBZFYbiFTkpe9etD05ltfaL/XLftEhfV9b2dCTCzaw+Tg/zaYtb6OK2oeULjyGRN", - "L43sgQ9ia/bswmwOY6IYTyEZonxzLM9Mq/AI7DikPboI50UZzNY6HC36jRJdLxHs2IW+BfcoRt5SqVnK", - "CpQUf4TtnQvO7Qmi6nqSgabMPNaDD1aILsL+xNqx22PeTJAe9Ybtgt95xEaWkzOFF0YT+EvY4ovlrXWQ", - "Og/cqu7gJRAZ1ZxuygkC6t0ujAATNoENTXW+NdecXsGWXIMEosr5mmltPd6aDwUtiiQcIKofHJjRKcOt", - "c5HfgTHa+TMcKlhedyumEytRDcN33hKrGuhwklQhRD7i7d1BRhSCUXZTUgiz68w5WHovPE9JDSCdEIOW", - "kIp53lMNNOMKyH+LkqSUo8BaaqhuBCGRzeL1a2YwF1g1p7OQ1hiCHNZg5XD8cnDQXvjBgdtzpsgCrr1X", - "smnYRsfBAb6C3wqlG4frDjQt5ridRng7Kk7NReFkuDZP2W2hcyOP2cm3rcErbas5U0o5wjXLvzUDaJ3M", - "zZi1hzQyzjqJ447SiQZDx9aN+45qnt9GR1MPHYOuO3FgVK8/9tnVjXyVb++AT9uBiIRCgsJTFb5LlP0q", - "FqHjujt2aqs0rLuqG9v1lx7B5p0XCzpSpuA545CsBYdtNFaLcXiNH2O97cnu6Yw8tq9vW2xqwN8CqznP", - "GCq8LX5xtwNSfls5lNzB5rfHbWntQpd9fJVCXhBK0pzhm1VwpWWZ6gtOUSoOznLE8OZl/f530gvfJP4w", - "i7yb3FAXnKLRtZKVo8aCBURewd8D+OeSKpdLULolHywALrhrxTgpOdM419rsV2I3rACJ1q+ZbbmmW7Kg", - "OT7rfgUpyLzUzRsTPYuVNq8uq0I00xCxuOBUkxzMC/Q14+cbHM6r4D3NcNDXQl5WWJhFz8MSOCimkriB", - "8Af7FX033PJXzo8Dw7zsZ6t0MuPX7sdbDY3Qpf9z/z+P358k/0OTX4+SZ/9x+OHjk08PDjo/Pvr07bf/", - "t/nT40/fPvjPf4/tlIc95vfqID996aTJ05coMtRapw7sn03jsGY8iRJZaFtp0Ra5bwQfT0AParWe2/UL", - "rjfcENIVzVlG9c3Ioc3iOmfRno4W1TQ2ovWA9Gvd8yK+BZchESbTYo03vsa7NvW4hzmqQZ3TOJ6XRcnt", - "VpbKqWLRgdLbNsViWkUR2OjhY4Iu5ivqDfPuz0dPv5lMa9fw6rt5X9uvHyKUzLJNLAAgg01MvnIHBA/G", - "PUUKulWg49wDYY+aca01KRx2DUYwVytWfH5OoTSbxzmcd0tz77QNP+XWX8ycH1Sqbp2uRiw+P9xaAmRQ", - "6FUsqrAhKWCrejcBWoauQoor4FPCZjBrv5OyJShvUM6BLjC6DRWDYoybbXUOLKF5qgiwHi5k1GMkRj8o", - "3Dpu/Wk6cZe/unN53A0cg6s9Z6VB9X9rQe798N05OXQMU92zsSh26CB6IKJ/cA6yDROo4WY2ltoG41zw", - "C/4SFowz8/34gmdU08M5VSxVh6UC+ZzmlKcwWwpy7H1uX1JNL3hH0upNdxB4O5OinOcsJZehRFyTpw1h", - "7Y5wcfGe5ktxcfGhYw3qyq9uqih/sRMk10yvRKkTF6OXSLimMouArqoYLRzZRtgOzTolbmzLil0MoBs/", - "zvNoUah2rEZ3+UWRm+UHZKhcJILZMqK0kF4WMQKKhQb3941wF4Ok1z7As1SgyN/WtHjPuP5Akovy6Ogx", - "kEbwwt/clW9ocltAQ1N1o1iStpYKF27fNbDRkiYFXYKKLl8DLXD3UV5eo040zwl2awRNeKcwHKpegMdH", - "/wZYOPZ2AMfFndlePtlCfAn4CbcQ2xhxozY13HS/gjCKG29XKxSjs0ulXiXmbEdXpQyJ+52pYrCXRsjy", - "9h/Fluhj48LV50DSFaSXkGHkLKwLvZ02unsToxM0PetgykaYWydoDINEpd4cSFlk1InilG/b8WgKtPZO", - "Pu/gErbnoo6i3CcArRkPpfoOKlJqIF0aYg2PrRujvfnOjo0xIEXhw4rQv9yTxXFFF75P/0G2Iu8dHOIY", - "UTTidfoQQWUEEZb4e1Bwg4Wa8W5F+rHlmVfG3N58kYB0z/uJa1I/npzJOVwNhiHZ72vAdBXiWpE5NXK7", - "cJkWbMxPwMVKRZfQIyGHetWRkTUNXSwOsuvei950YtG+0Dr3TRRk2zgxa45SCpgvhlTwMdNyNPAzWdU9", - "rmBGMIGSQ9g8RzGp8siwTIfKhn7bZoTpAy1OwCB5LXB4MJoYCSWbFVU+CQTmyvBneZQM8BvGsA1FLp8G", - "NvIgIUYVl+x5bvucdl6XLn7ZBy37SOXwaTki6thI+OiWF9sOwVEAyiCHpV24bewJpY6nqzfIwPHTYpEz", - "DiSJmdupUiJlNotHfc24OcDIxweEWBUwGT1CjIwDsNEkhQOTNyI8m3y5D5DcxQNSPzYas4K/Ie66bB3Q", - "jMgjCsPCGe9xdfQcgDofjer+ankK4TCE8SkxbO6K5obNuRdfPUgngBbF1la4rDOKPugTZwc08PZi2WtN", - "9iq6yWpCmckDHRfoBiCei01iYxeiEu98Mzf0HvXJw0iK2MG0ocr3FJmLDRra8WqxPmA7YOmHw4MRvPA3", - "TCG9Yr++29wCMzTtsDQVo0KFJOPUeRW59IkTY6bukWD6yOV+EH18IwBayo46T597/O58pDbFk+5lXt9q", - "0zqrhnd3jh3/viMU3aUe/HW1MFW8sFMhvINUyKxfT2EIlekq8WFXveDSNhq+MTqieCAJ40nzteGfEN2d", - "67EHN+Cp5xlAxEvrrN+B5LtNIYx0a535bWS3Q4qVEyXYGCVldVaK8WXuBIM+NMUW7L1RPMbtkutMLX7A", - "cbJzbHN7HvlDsBRFHI59XirvHH4GoOg55TUcKIffEhIX3T0Iy6d++njbFu2jB6XpWNHMKRC8tWK3gyGf", - "rjWzazNVkAO+npPGayO5jNm4Ly7eK0DR7Mx3C7R8mLmA8u2DwFtHwpIpDbW1yUiwHtOfW49PMWGSEIv+", - "1elCLsz63glRyXM2Iwd2bCzzs6/gSmhIFkwqnaCpLroE0+h7hdqn703T+KOi6Q9kcweyLH6J4rSXsE0y", - "lpdxenXz/vjSTPumkh1UOUfBhHECNF2ROea6jHoJDkxtHUkHF/zKLvgVvbP1jjsNpqmZWBpyac7xOzkX", - "rZtuiB1ECDBGHN1d60XpwAUaxMZ1uWPwwLCHE6/T2ZCZonOYMj/2Tv8qH6HXJ8zZkQbWgq5BvW6ZEYcc", - "spSiLCxTr9NcR6PYuNBJQ/kRQVel4FGaXtpIjOYG82WlU4m7Tdl39aihXdsdA/Lx4/HdwzkhOMnhCvLd", - "7q8UMe4VOOgZYUdA1xuCjuTex2O3VN/dgRph1UrbMEappSPdDBlu66eRSzxVv62RYA3uXMjoaOudkdA8", - "vdX03TXdFUWSQQ7RAI2/BBEYtCgwzNo3jgUrmMEYz2ATB8d+msaSUXeV9yXj2iYuvKucaK1xxi87zBw2", - "BgWFzXG1f961/jdmsEshmvsX1UOUlXFgkBHj4NXLLkjj36a+nmucFgXLNi27px21Vzt+JxjDC8oNtgMD", - "AW3EQn8kqGbGuFqZZ/MWNxK2zEZh5ryZ1y2UacKpmPJZ97uIqkIDd+HqHGj+I2x/Nm1xOZNP08ntzKQx", - "XLsRd+D6bbW9UTyjG541mzW8HvZEOS0KKa5onjhjch9pSnHlSBObe9vzZ5bW4lzv/LuTV28d+J+mkzQH", - "KpPqtdO7KmxX/G5WZZPT9RwQn9V7RXWln7Ov4WDzq4xaoQH6egUug3LwoO6keqydC4Kj6AzSi7g38E7z", - "svODsEsc8IeAonKHqE111hui6QFBryjLvY3MQ9vjuYuLG3c3RrlCOMCtPSnCu+hO2U3ndMdPR01dO3hS", - "ONdAjue1TWOuiOBtdznzCkbTG5LqmmKiRmsB6TInXq7RapConKVxeyqfK0Mc3PrJmMYEG/e8p82IJetx", - "u+IlC8YyzdQIpXYLyGCOKDJ90s8+3M2Fqz9TcvaPEgjLgGvzSeKpbB1U1J86y3r3Oo1LlW5ga42vh7+N", - "jBEmKW3feE7mGhIwQq+cDrgvK62fX2hlfTI/BO4Hezj3hTN2rsQBxzxHH46abaDCquldM1pC31mrxuvf", - "XLbUnjmitWeYShZS/ApxVRVq+CJxgT4tK0OP1l+BzyLiepvFVJacuoROPXvvdvdJN6HFqemQ2EP1uPOB", - "Cw7mh/TWaMrtVttSEA2/9jjBhBEkh3b8mmAczJ2om5xez2kseaYRMgxMgfmlYTfXgvjOHvfORsNcptwZ", - "CfzGqrbMRswXIOuQ3W72nRsKDHba0aJCLRkg1YYywdT6+uRKRIYp+TXltqIIWiPwKLne5oHvFULXQmK+", - "CxU38WeQsnVUuXRx8T5Lu+bcjC2ZradRKggKNriBbCEiS0Wu6IV1p6tRc7ogR9OgJIzbjYxdMcXmOWCL", - "h7bFnCqwShXvueG7mOUB1yuFzR+NaL4qeSYh0ytlEasEqYQ6fN5Ujipz0NcAnBxhu4fPyH100VHsCh4Y", - "LLr7eXL88BkaWO0fR7ELwBXOGeImGbIT//6P0zH6KNkxDON2o86i2gBb7ayfcQ2cJtt1zFnClo7X7T5L", - "a8rpEuJeoesdMNm+uJtoC2jhhWe2VI/SUmwJ0/H5QVPDn3oizQz7s2CQVKzXTK+dI4cSa0NPdTUGO6kf", - "ztb9cYl0PVz+I/pDFd4dpPWI/Lx2H3u/xVaNXmtv6BqaaJ0SapOc5Kz2VPTpvcmpz6GEmYWrhMIWN2Yu", - "s3QUc9BxcUEKybjGh0WpF8kfSbqikqaG/c36wE3m3zyJZFNuZvXk+wH+2fEuQYG8iqNe9pC9lyFcX3Kf", - "C56sDUfJHtSRncGp7HXcirvo9PkJDQ89VigzoyS95FY2yI0GnPpWhMcHBrwlKVbr2Yse917ZZ6fMUsbJ", - "g5Zmh/787pWTMtZCxhIj1sfdSRwStGRwhX768U0yY95yL2Q+ahduA/2XNZ56kTMQy/xZ7n0I7GPxCd4G", - "aPMJPRNvYu1pWnoaMlfU7IMvnHEWEFsscJfd4zZlRBqd94HKc+hx0PUoERoBsC2M7fcCvr2KITD5NHao", - "D0fNpcUo87mILNnnnq9sPC5iMqK36rtAzAfDoOZuqClp5vn+/B413izS9ewwXzys+Ecb2C/MbBDJfgU9", - "mxjUIIhuZ1Z9D5zLKHkuNmM3tcW7/cb+E6AmipKS5dnPdW6QVokHSXm6ijqLzE3HX+pidNXi7GGOZsZc", - "Uc6tN0JXN4GvlF/8ayby3vq7GDvPmvGRbdtVJ+xyW4urAW+C6YHyExr0Mp2bCUKsNtMuVGF9+VJkBOep", - "0zDW93q3WkmQU/4fJSgduxfxgw0tQI36wlCxTe0OPEM9xoz8YItJr4A0ssSh/oCty9xmHLMJtq2ppyxy", - "QbMpMeOcf3fyithZbR9bUsmmVF/aa7exin7/3H0cbYd8a+8ios+sWmlM2qg0XRexFCWmxblvgHlQQusS", - "PqxD7MzIS6vTUP7FbCcx9LBgcg0ZqaZzUjXShPmP1jRdobKgwVL7SX58LQBPlSqov1nV0arSruK5M3C7", - "cgC2GsCUCCM5XDNlawjDFTSzolQpgpwY4LOkNJcnS84tpUSl4qEUVjdBuwfOekF6A1QUshbi95RenJv6", - "nqURzrBXNI9hu85Cp/CmzbFR1UfyteFTygVnKWYRjF3Nrh7xGOvsiISL8cgA52+jJpHDFa3uUAVrOCz2", - "1nvwjNAhrmseCr6aTbXUYf/UWPh2RTVZglaOs0E29UVKnIaacQUujS6Wpg74pJANizdyyKgTRS0n70lG", - "GJzdo3L43nx74xRSGLV4yTg+PX2MhA2QtDpkLJeqzXuVabIUGEHhDkW4pvemzwyTtWSw+TDz5VVxDGsw", - "Nsu23hHdoU68r4TzTTBtX5i2NqFe/XMjDs5OelIUbtL+EjZReUBveC+CIzbvytErQG41fjjaALkNOjnh", - "fWoIDa7QRQIK4kJjesq5tIJgjNBqKQpbEOsfHc2jFXUTfcU41MV/IxdEGr0ScGPwvPb0U6mk2oqAo3ja", - "OdAc/SJiDE1pZxS77VCtDXb+pEU68XP0b2NdiaaHcVQNasGN8m1Vc9hQdyBMvMBi5w6R3boyKFU5IcoF", - "1zQrzcQYh2HcvpZV8wLoHoOuTGS7a0ntydnnJupLVTIvsyXohGZZTJ/wHL8S/EqyEiUH2EBaVvmbi4Kk", - "mJmvmaqwS21uolRwVa4H5vINbjldULopQg1h+Si/w+h4Pd/iv7Hkxf0749yD9vax975AWRU+t4/c3Byp", - "I/Uamk4UWybjMYF3yu3RUU99M0Kv+98ppedi2QTkMycoG+Jy4R7F+Nt35uII83d1MnLbq6VKr4XuoMIX", - "3MRnY5UYpsmVfNRpZ86goN+wAqK/NN8UL7+euJZA10vt/Wrt2n3RLWlvMBbVLn+CpmSQBfXGpFu/Mht9", - "jlDEdfp9vmTWlcx87vQeJxl25GwcexCh3kmxC9CP3gOaFJQ5p42aWXQx68K9+tWFQ4eu3uD2IlwQVa/G", - "7servoAnHwdsIztaxcwuwSVVKiRcMVF6dwjvL+efhPZXV0w6iCvuXX/Xbwan+rJq0F6l7bkrnGGX6d7k", - "P/5svSsJcC23/wQq3M6md0rBxXIWNwrBOeEqqm/SY+/Kl1U1ucurZC2yoYDpH38mL71tadS94wk5lm5J", - "ZK78UjRY/JVL/u+bGelz9LSvXaeTohieuidCvDu5bbjv9H2ppsz5HNK6vfXn1xbQC1UIkbdKEM7MYaPj", - "pXI60bDXQGBTAOa6DQKb+7NnjCUoF+SIr9UkB6pgAMNh1jbXdiSSzzevTPtxwfbxEob9KWfrNLPIPAuh", - "WF2WJVbbcKTL8TmWJwwsht2xvL/fFaRayIYfkwTYJ4GumSyom/s19WyPoqTyzPb0P5BmdjoJeUs0UNEd", - "L1qnyEGrGppcI6nqbZsIs3edmTkkJUz9EOaHBc1VvEpVr7NrK/NJ4LASSfQcX9hpNiLbt1vONPCBYNkw", - "IuORANb5+18Tmdav/W7R2anWNPyq6CReCJKH2KI6sz0cSCovapQMcb+WwF1J5UUMNbujohYLSDW72pHo", - "4i8r4EEShanXBCMsiyDvBauibDCh6P52jhqgoTwUg/AEif1vDU5fjOglbO8p0qCGaJWfqRfub5JLEjGA", - "t5YRPAqhYl6K1nTlHMeYqigDseC9gm13qLNy95ZXDOScG87lSbIp8QxMeSViuu9Rc5mue2UCw4CRvlwY", - "3QJn/RqPl1hPTlWlj30uylAvSE67GfuvXS5LTEtSWWt9VktQ/jefg8jOkrNLCAtAom0cUyi4FlFlr9cj", - "JwNyUif62xfnagO9qGZmdQxHN943kgMavZ/SXJhHcNIX7tQMm6jcvO4p6xyKYgpWjkO4FiBdoVy8GXKh", - "INHCu9YNwTGECusBeyMkqN66Cxa43myo7+p0r1h/xibLoM7xNVwgkbCmBjoZJGXtn3MI2S/sdx/g6nNy", - "7dRpV/Sa7Myq6qN3mOogMaT6BXG35e7A2Zuotxnntiy/ivkUcoPK0P5aSJGVqUsEExyMygQwOmHZACuJ", - "aobT7io7Sr4cs4G/CtIQXML20Opf0hXlyyC9Wgi9Fe3tGoLMZa3dvlPNf1zJmS/tApZ3AueX1J5PJ4UQ", - "edJjcD3tJpptn4FLll4aMbus/d57SiyS+2jnqzxqrldbn1i1KIBD9mBGyAm3kUbeuaZZ6ag1Ob+nh+bf", - "4KxZaXM/O8X+7ILHQzYwqY+8JX/zwwxzNQWG+d1yKjvIjjSmm54kt5JeRwqOdv3pRru7tItA1kRloYhJ", - "KTdM1TXqfHeV+xHSD6ogDr9+wkx+tReztDYilJa85aYtvLyuTT/j6jH6DjvAC5U1QUVGz40cOF/Y1fh1", - "hZRgKb2U0Fj+Lv2PW2DNl4ItUhg1aZZpExBbN7XmvgTKPfWi0pnF8dxVrWHaPsEx529XJafQZmjTsAaE", - "Y86lvKL551erYT7HE8SHKyseX2j4/g2RbFGpbubv94qOmjt4697d1PwtqgH/AmaPosZeN5Qz/lSVML2J", - "DFPc05zkoq6Ii0OSaxzTWocffkPmLoqukJAyxVoBxte+qkn13MMiX3W1+eH35a51/iz0LcjYPRBEQd7U", - "FRK0wPuhhrA+ol+YqfSc3CiVx6ivQxYR/MV4VJjOZsd1cdkwG9uKMy1/SCHhjs3HgSPYnubjbqKescuz", - "JlJz6ZQKuuscfVs3cBu5qOu1jfV96CJ3KI3+GJeFeHUM0x19JixCsLQMQVDJ3x7+jUhYYO1IQQ4OcIKD", - "g6lr+rdHzc/mOB8cRMW4z+YtYXHkxnDzRinGGdM6oTCwKZjsSfr3zjF3d2Gj+Y5gB4hn58whWg0Gp/Z+", - "o585FTTK3DsV/HZprvEufhagzC+5miiG+5/7Yhesf35PmEzrLJQsz3YdykbQU135FsN6fnEBuV+k9u4v", - "VpfdZZOu/uE+PnLtA4CIiay1MXkwVRDONCKSyXWLxC0hcaWlZHqLecK86pP9EvWp+aGyljgrcJVZxskd", - "WlxClWmutq2Uyks2Pwiaoyxg3jPooaiFyGfkuw1dFzk4JvXtvfkf4PEfn2RHjx/+Yf7Ho6dHKTx5+uzo", - "iD57Qh8+e/wQHv3x6ZMjeLj45tn8UfboyaP5k0dPvnn6LH385OH8yTfP/nDP3AEGZAvoxGelmPwVC1Qn", - "J29Pk3MDbI0TWrAfYWtrYRoy9lU2aYpcENaU5ZNj/9P/9txtlop1Pbz/deKC3icrrQt1fHh4fX09C7sc", - "LlGZmmhRpqtDP0+nDOfJ29MqPMz6QuGO2sgfQwq4qY4UTvDbu+/OzsnJ29NZTTCT48nR7Gj2EHMZF8Bp", - "wSbHk8f4E56eFe77oU8ifPzx03RyuAKao03c/LEGLVnqP6lrulyCnLlyo+anq0eHXow7/OgUyZ+Gvh2G", - "lXsOPzb07dmOnujocvjRJ7Eabt3IEuXsDGa5y5hB9wdw94Rz/YjYJRSqN+3oU6KEdNq2QjJhTtLURren", - "EijSvZAYnqVlyVOr8LZTAMf/vj75K1o6Xp/8lXxLjqYuak/hMy82vdUlVSRwmlmwuypT9Xx7UpcsqVPc", - "Hr+PPEmiZVDxCBn6CCi8GrHmYGitDotHV/zY8Nij5NmHj0//+Cl2J3XL73skBcaMEPVa+ERPiLQ13Xzb", - "h7KNPR24hn+UILf1ItZ0MwkB7tq/Il5tC7YsJWoQ6xj9yl/XVcNkivzX2U9viJDE6RTe0vQydOCLgePu", - "sxAiX5zMhYOt1bJoxk5UOPyAmV8QCjzFj46O9ioQ3HIu6lKRKytPvX9dV4OnCGxoqvMtoXj/bK2pSZXz", - "OktTUxTQokjCAaKv5IEZfX2jmGP7vkrESHAf1hEahq+dpb2BDucdhfXUdptXO8iIQvAhdnuHW+tp5Ovu", - "/mvsblcYIIUwZ5ph8Gh9n+RdN0UVFO9w4PbYR2bkv0WJIputYwmxVJM4A9qS/JzOwBv4t+VYRbTCzsFB", - "e+EHB27PmSILuEYOSjk2bKPj4AALnz/Zk5UNquYbERijzs4+w3U26zXdVBn+KFaw4Fhm8QpI8Nh8cvTw", - "d7vCU47eRUbWJFaW/jSdPP0db9kpN1ILzQm2tKt5/LtdzRnIK5YCOYd1ISSVLN+SP/MqQD9IF9llf3/m", - "l1xcc48I80ws12sqt05CphXPKXmQMmGQ/3QMs7UUjVyULhXa8FD+nDTKCfPl5MMnL+CPfDUMNTucY8ag", - "sU1BBY37nx5ojFGHH9Gc0Pv7oUujEv+IZh37Zj30TmTxlo1XzUe9MbC2eqRUp6uyOPyI/8E3ZACWDVrs", - "gmvDNg4xedy2+/OWp9EfuwO160HHfj782Cyz1ECoWpU6E9dBXzRYWGtbd76qQm/j78NryrSREJwnIOaQ", - "7XbWQPNDl2ig9Wsd29f5ggGLwY8tmaIQNhdM8632jl6HEoqVFkDp5yLbDnCbTTJnHI9gyCJqVZj92H0f", - "dBjD+Qps6nVvyY0IYFqQuRQ0S6nC1KQuJUfn1ffplo+Plty4OY3Y6RBMfEh3ncrMYdpdEBPHHSNhBfsS", - "ZPRGSVdZFdpvLJV0IHpOM+KTByXkNc3NhkOGZbgkhswFIP/WEsWXFwG+8J392S7Z5/7wKULRbaZ1OIM0", - "OWMuT/NaMmd9CTxx3CaZi2zrc9FLeq031p+mzccOq4x/0Y93oE7759ah7VKdfdVYfdVYfdVpfNVYfd3d", - "rxqrr/qcr/qc/2/1OfsocWIypFNi9IuSmBuVNua1bzRaR4RVLD5sNiVMVwJXN0E70zNCzjHehppbAq5A", - "0hyL2KgggG6NnpeqTFOA7PiCJw1IrH+jmfh+/V/rWHpRHh09BnL0oN1HaZbnIW/u9kVhFj/Z/EDfkovJ", - "xaQzkoS1uILMhpeH8Qe2185h/1c17k+dUCaMAF3RK6giJogqFwuWMovyXPAloUtR+1gZvk24wC9YrNgl", - "KiBMT12aF6bItVm8y1DbDJNoiuVdCeC03sKdhu0WucRt2obw9jRo/8cYa/a/rgh+09it23LJwbE7LPMr", - "y/gcLOOLM43fu6kw0PH9S8qQT46e/G4XFGqE3whNvkdv/tvJWlVK71jQ+2gpqnY7Dd048Q6sHDjffzCc", - "Hks7ueux9ko8PjzEuNmVUPpwYi6vpsdi+PFDBZSvrDApJLvCRI0fPv2/AAAA//9Yvh6rFd0AAA==", + "H4sIAAAAAAAC/+x9/XPcNpLov4I3d1W2dUNJ/srFqkrdk+0kq4vtuCwlu3uWX4Ihe2aw4gAMAEoz8dP/", + "/goNgARJkMORFHuzzz/ZGuKj0Wg0Gv35cZKKVSE4cK0mRx8nBZV0BRok/kXTVJRcJywzf2WgUskKzQSf", + "HPlvRGnJ+GIynTDza0H1cjKdcLqCuo3pP51I+K1kErLJkZYlTCcqXcKKmoH1pjCtq5HWyUIkbohjO8TJ", + "y8n1wAeaZRKU6kL5I883hPE0LzMgWlKuaGo+KXLF9JLoJVPEdSaME8GBiDnRy0ZjMmeQZ2rfL/K3EuQm", + "WKWbvH9J1zWIiRQ5dOF8IVYzxsFDBRVQ1YYQLUgGc2y0pJqYGQysvqEWRAGV6ZLMhdwCqgUihBd4uZoc", + "vZ8o4BlI3K0U2CX+dy4BfodEU7kAPfkwjS1urkEmmq0iSztx2Jegylwrgm1xjQt2CZyYXvvkdak0mQGh", + "nLz77gV5/PjxM7OQFdUaMkdkvauqZw/XZLtPjiYZ1eA/d2mN5gshKc+Sqv27717g/KdugWNbUaUgfliO", + "zRdy8rJvAb5jhIQY17DAfWhQv+kRORT1zzOYCwkj98Q2vtNNCef/rLuSUp0uC8G4juwLwa/Efo7ysKD7", + "EA+rAGi0LwympBn0/WHy7MPHh9OHh9f/9v44+R/359PH1yOX/6IadwsGog3TUkrg6SZZSKB4WpaUd/Hx", + "ztGDWooyz8iSXuLm0xWyeteXmL6WdV7SvDR0wlIpjvOFUIQ6MspgTstcEz8xKXlu2JQZzVE7YYoUUlyy", + "DLKp4b5XS5YuSUqVHQLbkSuW54YGSwVZH63FVzdwmK5DlBi4boQPXNA/LzLqdW3BBKyRGyRpLhQkWmy5", + "nvyNQ3lGwgulvqvUbpcVOVsCwcnNB3vZIu64oek83xCN+5oRqggl/mqaEjYnG1GSK9ycnF1gf7cag7UV", + "MUjDzWnco+bw9qGvg4wI8mZC5EA5Is+fuy7K+JwtSgmKXC1BL92dJ0EVgisgYvYPSLXZ9v8+/fENEZK8", + "BqXoAt7S9IIAT0XWv8du0tgN/g8lzIav1KKg6UX8us7ZikVAfk3XbFWuCC9XM5Bmv/z9oAWRoEvJ+wCy", + "I26hsxVddyc9kyVPcXPraRuCmiElpoqcbvbJyZys6Pqbw6kDRxGa56QAnjG+IHrNe4U0M/d28BIpSp6N", + "kGG02bDg1lQFpGzOICPVKAOQuGm2wcP4bvDUklUAjh+kF5xqli3gcFhHaMYcXfOFFHQBAcnsk58c58Kv", + "WlwArxgcmW3wUyHhkolSVZ16YMSph8VrLjQkhYQ5i9DYqUOH4R62jWOvKyfgpIJryjhkhvMi0EKD5US9", + "MAUTDj9mulf0jCr46knfBV5/Hbn7c9He9cEdH7Xb2CixRzJyL5qv7sDGxaZG/xGPv3BuxRaJ/bmzkWxx", + "Zq6SOcvxmvmH2T+PhlIhE2ggwl88ii041aWEo3O+Z/4iCTnVlGdUZuaXlf3pdZlrdsoW5qfc/vRKLFh6", + "yhY9yKxgjb6msNvK/mPGi7NjvY4+Gl4JcVEW4YLSxqt0tiEnL/s22Y65K2EeV0/Z8FVxtvYvjV176HW1", + "kT1A9uKuoKbhBWwkGGhpOsd/1nOkJzqXv5t/iiI3vXUxj6HW0LG7b1E34HQGx0WRs5QaJL5zn81XwwTA", + "vhJo3eIAL9SjjwGIhRQFSM3soLQoklykNE+UphpH+ncJ88nR5N8OauXKge2uDoLJX5lep9jJyKNWxklo", + "Uewwxlsj16gBZmEYNH5CNmHZHkpEjNtNNKTEDAvO4ZJyvV+/Rxr8oDrA791MNb6tKGPx3Xpf9SKc2IYz", + "UFa8tQ3vKRKgniBaCaIVpc1FLmbVD/ePi6LGIH4/LgqLDxQNgaHUBWumtHqAy6f1SQrnOXm5T74Px0Y5", + "W/B8Yy4HK2qYu2Hubi13i1WKI7eGesR7iuB2Crlvtsajwcjwd0Fx+GZYitxIPVtpxTT+i2sbkpn5fVTn", + "PweJhbjtJy58RTnM2QcM/hK8XO63KKdLOE6Xs0+O231vRjZmlDjB3IhWBvfTjjuAxwqFV5IWFkD3xd6l", + "jOMLzDaysN6Sm45kdFGYgzMc0BpCdeOztvU8RCFBUmjB8DwX6cVfqFrewZmf+bG6xw+nIUugGUiypGq5", + "P4lJGeHxqkcbc8RMQ3y9k1kw1X61xLta3palZVTTYGkO3rhYYlGP/ZDpgYy8XX7E/9CcmM/mbBvWb4fd", + "J2fIwJQ9zs6CkJmnvH0g2JlMA1QxCLKyr3diXt07Qfminjy+T6P26FurMHA75BaBOyTWd34Mnot1DIbn", + "Yt05AmIN6i7ow4yDYqSGlRoB30sHmcD9d+ijUtJNF8k49hgkmwUa0VXhaeDhjW9mqTWvxzMhb8Z9WmyF", + "k1qfTKgZNWC+0xaSsGlZJI4UIzop26A1UG3CG2Ya7eFjGGtg4VTTPwALyox6F1hoDnTXWBCrguVwB6S/", + "jDL9GVXw+BE5/cvx04ePfnn09CtDkoUUC0lXZLbRoMh99zYjSm9yeNBdGb6OylzHR//qiddCNseNjaNE", + "KVNY0aI7lNVuWhHINiOmXRdrTTTjqisAxxzOMzCc3KKdWMW9Ae0lU0bCWs3uZDP6EJbVs2TEQZLBVmLa", + "dXn1NJtwiXIjy7t4yoKUQkb0a3jEtEhFnlyCVExETCVvXQviWnjxtmj/bqElV1QRMzeqfkuOAkWEsvSa", + "j+f7duizNa9xM8j57Xojq3PzjtmXJvK9JlGRAmSi15xkMCsXjZfQXIoVoSTDjnhHfw/6dMNT1KrdBZH2", + "P9NWjKOKX214GrzZzEblkC0am3D7t1kbK14/Z6e6pyLgGHS8ws/4rH8JuaZ3Lr+0J4jB/sJvpAWWZKYh", + "voJfscVSBwLmWynE/O5hjM0SAxQ/WPE8N326QvobkYFZbKnu4DKuB6tp3expSOF0JkpNKOEiA9SolCp+", + "TfeY5dEeiGZMHd78emkl7hkYQkppaVZbFgSNdB3OUXdMaGqpN0HUqB4rRmV+sq3sdNbkm0ugmXnVAydi", + "5kwFzoiBi6RoYdT+onNCQuQsNeAqpEhBKcgSp6LYCppvZ5mIHsATAo4AV7MQJcicylsDe3G5Fc4L2CRo", + "D1fk/g8/qwefAV4tNM23IBbbxNBbPficPagL9bjphwiuPXlIdlQC8TzXvC4Ng8hBQx8Kd8JJ7/61Iers", + "4u3RcgkSLTN/KMX7SW5HQBWofzC93xbasujx8nIPnTO2Qr0dp1woSAXPVHSwnCqdbGPLplHjNWZWEHDC", + "GCfGgXuEkldUaWtNZDxDJYi9TnAeK6CYKfoB7hVIzcg/e1m0O3Zq7kGuSlUJpqosCiE1ZLE1cFgPzPUG", + "1tVcYh6MXUm/WpBSwbaR+7AUjO+QZVdiEUR1pXR35vbu4lA1be75TRSVDSBqRAwBcupbBdgNPV16AGGq", + "RrQlHKZalFO510wnSouiMNxCJyWv+vWh6dS2PtY/1W27xEV1fW9nAszs2sPkIL+ymLU+TktqntA4MlnR", + "CyN74IPYmj27MJvDmCjGU0iGKN8cy1PTKjwCWw9pWSwkzSDJIKeb7qA/2c/Efh4aAHe8fvgIDYn1Z4lv", + "ek3J3n1gYGiB46mY8EjwC0nNETQvj5pAXO8tI2eAY8eYk6Oje9VQOFd0i/x4uGy71ZER8Ta8FNrsuCUH", + "hNgx9DHw9qChGvnmmMDOSf0sa0/xd1BugkqM2H2SDai+JdTj77SAHmWacwMOjkuLu7cYcJRr9nKxLWyk", + "78T2aPbeUqlZygp86vwAmzt/+bUniNqbSAaashwyEnywr8Ai7E+sI0Z7zJu9BEcpYbrgd7QwkeXkTKHE", + "0wT+Ajb45H5rPfzOAr/AO3jKRkY11xPlBAH1fkNGAg+bwJqmOt8YOU0vYUOuQAJR5WzFtLYum82XrhZF", + "Eg4QVXAPzOisOdY7zu/AGPPSKQ4VLK+7FdOJfRIMw3fWehc00OGeAoUQ+QjlUQcZUQhGGf5JIcyuM+ch", + "7N1IPSU1gHRMG0151e1/TzXQjCsgfxclSSnHF1epoRJphEQ5AeVHM4ORwKo5nYm/xhDksAL7kMQve3vt", + "he/tuT1niszhyrvVm4ZtdOztoRrnrVC6cbjuQFVojttJ5PpAzT/ee855ocVTtpuY3chjdvJta/DKXGDO", + "lFKOcM3yb80AWidzPWbtIY2MM6/juKOU+sHQsXXjvp+yVZnf1YbPKctLCf3WsfPz9/PV+fkH8p1t6Q3b", + "U0/kITqu6rCIubuNSomuNSRn5n0rBc2MgBDV7eMi+SKpnDNVFJyVMuD81Z1DyjetQL6xMJAZpLS0XsmO", + "azsIavdQtR+RF1u720ZhdCEj1eNlru2lHWJ1IUVZEFVtu6UCTTX8MarmeugYlN2JA9+g+mOfe5B5Juab", + "O7it7UBEQiFBIW8N1SvKfhXzMP7GMV+1URpWXQ207fpLz/vsXe87R/CccUhWgsMmGnLKOLzGj7Helr/3", + "dMabtq9vW3huwN8CqznPGGq8LX5xtwOG9rbyi7uDzW+P2zI+hJFHqFyDvCCUpDlD1ZvgSssy1eec4uM+", + "OGwR/wH/jOlX97zwTeL6pYj6xw11zin6jlRP/ihfnEOEL38H4LU+qlwsQOmWlDgHOOeuFeOk5EzjXCuz", + "X4ndsAIkGvH3bcsV3ZA5zVE79TtIQWalbjJXDJBQmuW5s4SYaYiYn3OqSQ6Gq75m/GyNw3lLoqcZDvpK", + "yIsKC/vR87AADoqpJO7n8L39ii5obvlL546G0ar2s9Wdm/HrKIoNvv3rCMz/c/+/jt4fJ/9Dk98Pk2f/", + "cfDh45PrB3udHx9df/PN/23+9Pj6mwf/9e+xnfKwx9z3HeQnL92b4uQlCo618rwD+ydTnK4YT6JEFpqI", + "W7RF7hvx1xPQg6ZaQS/hnOs1N4R0SXOWUX0zcmizuM5ZtKejRTWNjWipEfxadxTHbsFlSITJtFjjja/x", + "rmtQPFAGrTku9gXPy7zkditL5SxK6AfuXTTEfFoFQ9kkCEcEI2WW1PsXuT8fPf1qMq0jXKrvk+nEff0Q", + "oWSWrWNxTBmsY1K2OyB4MO4pUtCNAh3nHgh71BvFGsXDYVdgnmdqyYpPzymUZrM4h/Pete61vuYn3Lq9", + "mvODtqGNUzmL+aeHW0uADAq9jAVHNyQFbFXvJkDLXl9IcQl8Stg+7Ldfy9kClPeLyYHOMUgX7RtiTLRA", + "dQ4soXmqCLAeLmTUkzRGPyjcOm59PZ24y1/duTzuBo7B1Z6zMgT5v7Ug977/9owcOIap7tmQOjt0EAQV", + "0UI5P/+GJ4fhZjYlhI0pPOfn/CXMGWfm+9E5z6imBzOqWKoOSgXyOc0pT2F/IciRDx14STU95x1Jqzdr", + "SxC0QYpylrOUXIQScU2eNhI/+myk+UKYh2PbqN2VX91UUf5iJ0iumF6KUicu1DiRcEVlzGigqlBTHNkm", + "ChiadUrc2JYVu1BmN36c59GiUO2Qs+7yiyI3yw/IULmAKrNlRGkhvSxiBBQLDe7vG+EuBkmvfJx6qUCR", + "X1e0eM+4/kCS8/Lw8DGQRgzWr+7KNzS5KaChr7xRSFxbV4kLt+8aWGtJk4IuepQGGmiBu4/y8gof2XlO", + "sFsj9sv7tuJQ9QI8Pvo3wMKxcxwLLu7U9vI5Y+JLwE+4hdjGiBu1xfSm+xVEg914u1oRZZ1dKvUyMWc7", + "uiplSNzvTJVKYmGELG/GVmyBroIu68YMSLqE9AIyTAAAq0Jvpo3u3lPCCZqedTBlE2XYWA6M5kbV7gxI", + "WWTUieIthZLBsAKtva/iO7iAzZmog8F3iaNthnWqvoOKlBpIl4ZYw2PrxmhvvnPHQV1XUfjoSAyT8WRx", + "VNGF79N/kK3IeweHOEYUjbDDPkRQGUGEJf4eFNxgoWa8W5F+bHnmlTGzN18kr4bn/cQ1qR9PznMmXA1G", + "U9rvK8CsO+JKkRk1crtwCWNs6GLAxUpFF9AjIYfa9ZEBgg2NPA6y7d6L3nRi3r7QOvdNFGTbODFrjlIK", + "mC+GVPAx0/KX8jNZA45VoBLMA+cQNstRTKocyyzTobJh5bCJrfpAixMwSF4LHB6MJkZCyWZJlc9lgyl/", + "/FkeJQP8gaG4QwkYTgJXnyCvT6X49jy3fU47r0uXhsHnXvAJF8Kn5YjkCUbCR+/i2HYIjgJQBjks7MJt", + "Y08odVhwvUEGjh/n85xxIEnMa4gqJVJmkxHV14ybA4x8vEeIVQGT0SPEyDgAGw2TODB5I8KzyRe7AMld", + "WDP1Y6NJM/gb4hEY1o/WiDyiMCyc8R6Pbc8BqHM1q+6vlsMjDkMYnxLD5i5pbtice/HVg3TyAKDY2or6", + "d6bxB33i7IAG3l4sO63JXkU3WU0oM3mg4wLdAMQzsU5sCFZU4p2tZ4beo67FGBAWO5g248I9RWZije4W", + "eLVYV9YtsPTD4cEIXvhrppBesV/fbW6BGZp2WJqKUaFCknHqvIpc+sSJMVP3SDB95HI/SKJwIwBayo46", + "3ah7/G59pDbFk+5lXt9q0zo5kI/aiB3/viMU3aUe/HW1MFXaA6dCeAepkFm/nsIQKtNV/tauesFlnzV8", + "Y3RihIFcssfN14Z/QnR3rscroAFPPc8AIl7amKMOJN+uC2GkWxuTZBNUOKRYOVGCDbVUVmelGF/kUHlu", + "RtEUW7D3SfIYt0uuE075AcfJzrHN7XnkD8FSFHE4dnmpvHP4GYCi55TXcKAcfktIXJKKQViu++njbVu0", + "jx6UpntNMzVK8NaK3Q6GfLrWzK7NVEEO+HpOGq+N5CJm4z4/f68ARbNT3y3Q8mECFso3DwKfLQkLpjTU", + "1iYjwXpMf2o9PsW8b0LM+1enCzk363snRCXP2cRC2LGxzE++AvR5njOpdIKmuugSTKPvFGqfvjNN44+K", + "pleYTYHKsvglitNewCbJWF7G6dXN+8NLM+2bSnZQ5QwFE8YJ0HRJZpiyN+orOjC1dSceXPAru+BX9M7W", + "O+40mKZmYmnIpTnHn+RctG66IXYQIcAYcXR3rRelAxdoEOLb5Y7BA8MeTrxO94fMFJ3DlPmxt/pX+UDj", + "PmHOjjSwFnQN6nXOjTjkWD8yy9TrbP3RYFwudNJQfkTQVSl4lKYXNqCsucF8UelU4m5T9l09amjXdsuA", + "fPx4fPtwTghOcriEfLsTNEWMewUOekbYEdD1hmA4gffx2C7Vd3egRli10jaMUWrpSDdDhtv6aeTy59Vv", + "ayRYgzsX+T7aemckNE9vNX13TXdFkWSQQzTO7K9BIBktCswW4RvHAnrMYIxnsI6DYz9NYzn1u8r7knFt", + "86/eVWrH1jjjlx0mQByDgsKm6ts9fWT/GzPYpRDN/YvqIcrKODDIiHHw6mUXVCNpU1/PNU6LgmXrlt3T", + "jtqrHb8TjOEF5QbbgoGANmIRjBJUM/Flrcyz6dcbeaf2R2HmrJmeMpRpwqmY8sVDuoiqIpy34eoMaP4D", + "bH42bXE5k+vp5HZm0hiu3YhbcP222t4ontENz5rNGl4PO6KcFoUUlzRPnDG5jzSluHSkic297fkTS2tx", + "rnf27fGrtw786+kkzYHKpHrt9K4K2xV/mlXZHJs9B8QXJ1hSXenn7Gs42PwqMWBogL5agksEHzyoOxlr", + "a+eC4Cg6g/Q87g281bzs/CDsEgf8IaCo3CFqU531hmh6QNBLynJvI/PQ9nju4uLG3Y1RrhAOcGtPivAu", + "ulN20znd8dNRU9cWnhTONZCqfmWrMSgieNtdzryC0fSGpLqimG/WWkC6zImXK7QaJCpnadyeymcYYsOt", + "n4xpTLBxz3vajFiyHrcrXrJgLNNMjVBqt4AM5ogi0+cu7sPdTLgyWiVnv5VAWAZcm08ST2XroKL+1FnW", + "u9dpXKp0A1trfD38bWSMMNdy+8ZzMteQgBF65XTAfVlp/fxCK+uT+SFwP9jBuS+csXMlDjjmOfpw1GwD", + "FZZN75rREvrWklte/+aSPvfMES2hxVQyl+J3iKuqUMMXiQ712aUZerT+DnxESFltyakrgdWz9253n3QT", + "WpyaDok9VI87H7jgYJpbb42m3G61rWjT8GuPE0wYQXJgx68JxsHcibrJ6dWMxnIAGyHDwBSYXxp2cy2I", + "7+xx72w0zCX83ieB31jVltnEHwXIOnC7m0TshgKDnXa0qFBLBki1oUwwtb4+uRKRYUp+RbktjITWCDxK", + "rrd54HuF0JWQmLZHxU38GaRsFVUunZ+/z9KuOTdjC2bLApUKgrozbiBbT81SkavdY93patSczMnhNKhs", + "5XYjY5dMsVkO2OKhbTGjCqxSxXtu+C5mecD1UmHzRyOaL0ueScj0UlnEKkEqoQ6fN5Wjygz0FQAnh9ju", + "4TNyH110FLuEBwaL7n6eHD18hgZW+8dh7AJw9b+GuEk2D4Nc43SMPkp2DMO43aj7UW2ALdrYz7gGTpPt", + "OuYsYUvH67afpRXldAFxr9DVFphsX9xNtAW08MIzW3FMaSk2hPWEG4Omhj/1RJoZ9mfBIKlYrZheOUcO", + "JVaGnuqiMnZSP5wtX+bygXu4/Ef0hyq8O0jrEflp7T72foutGr3W3tAVNNE6JdTmaspZ7anoqxSQE58K", + "DhOkV3nRLW7MXGbpKOag4+KcFJJxjQ+LUs+Tr0m6pJKmhv3t94GbzL56EkkK30xOzHcD/JPjXYICeRlH", + "vewhey9DuL7kPhc8WRmOkj2oIzuDU9nruBV30enzExoeeqxQZkZJesmtbJAbDTj1rQiPDwx4S1Ks1rMT", + "Pe68sk9OmaWMkwctzQ799O6VkzJWQsbyu9bH3UkcErRkcIl++vFNMmPeci9kPmoXbgP95zWeepEzEMv8", + "We59COxi8QneBmjzCT0Tb2LtaVp6GjJX1OyDL5xxFhBb83Sb3eM21ZAanXeBynPocdD1KBEaAbAtjO32", + "Ar69iiEw+TR2qA9HzaXFKPO5iCzZl9CobDwuYjKit+q7QMwHw6BmbqgpaZYr+PQeNd4s0vXsMF88rPhH", + "G9jPzGwQyX4FPZsYlFKJbmdWfQ+cyyh5LtZjN7XFu/3G/hOgJoqSkuXZz3VukFalGkl5uow6i8xMx1/q", + "mprV4uxhjib4XVLOrTdCVzeBr5Rf/Gsm8t76hxg7z4rxkW3bxXPscluLqwFvgumB8hMa9DKdmwlCrDbT", + "LlRhfflCZATnqbPJ1vd6t+hSUBrjtxKUjt2L+MGGFqBGfW6o2FaoAJ6hHmOffG9r4i+BNHIFov7AZmmC", + "zNcJsKaessgFzabEjHP27fErYme1fWxlOFsZYmGv3cYq+v1zd3G0HfKtvYuIPrNqpTF1p9J0VcRSlJgW", + "Z74B5kEJrUv4sA6xs09eWp2G8i9mO4mhhzmTK8hINZ2TqpEmzH+0pukSlQUNltpP8uNLmniqVEEZ4aoc", + "YJU9Gs+dgdtVNbFFTaZEGMnhiilbCh0uoZkVpUoR5MQAnyWluTxZcm4pJSoVD6WwugnaPXDWC9IboKKQ", + "tRC/o/Ti3NR3rPByir2i2Szb5WI69YNtjo2qzNtrXwGacsFZirkkY1ezK6s+xjo7Iu1mPDLA+duoSeRw", + "RYvUVMEaDou9ZWs8I3SI65qHgq9mUy112D811u9eUk0WoJXjbJBNfa0lp6FmXIHLBo4V9gM+KWTD4o0c", + "MupEUcvJO5IRBmf3qBy+M9/eOIUURi1eMI5PTx8jYQMkrQ4Zqz5r815lmiwERlC4QxGu6b3ps4/JWjJY", + "f9j3VaJxDGswNsu23hHdoY69r4TzTTBtX5i2NqFe/XMjDs5OelwUbtL+SlxReUCveS+CIzbvytErQG41", + "fjjaALkNOjnhfWoIDS7RRQIK4kJjeqpStYJgjNBqKQpbEOsfHc2jFXUTfcU41DXMIxdEGr0ScGPwvPb0", + "U6mk2oqAo3jaGdAc/SJiDE1pZxS77VCtDXb+pEU68XP0b2NdUKuHcVQNasGN8k1VOt1QdyBMvKB55SQU", + "KY+FUpUTolxwTbNgVoxxGMbtE3I2L4DuMejKRLa7ltSenF1uor5UJbMyW4BOaJbF9AnP8SvBrz5dKawh", + "Lass3kVBUszM10xV2KU2N1EquCpXA3P5BrecLqhAF6GGsAqe32F0vJ5t8N9YCuv+nXHuQTv72HtfoKwK", + "n9tFbm6O1JF6DU0nii2S8ZjAO+X26Kinvhmh1/3vlNJzsWgC8okTlA1xuXCPYvztW3NxhPm7OnnZ7dVS", + "pddCd1Dh6wbjs7FKDNPkSj7qtDNnkHl5WAHRX2F0ipdfT1xLoOul9n61du2+6Ja0NxiLapc/QVMyyIJ6", + "Y9KtX5mNPkco4jr9Pl8y60pmPnd6j5MMO3I2jj2IUO+k2AXoB+8BTQrKnNNGzSy6mHXhXv3qwqFDV29w", + "exEuiKpXY/fDZV/Ak48DtpEdrZqMF+CSKhUSLpkovTuE95fzT0L7q6uJH8QV966/6zeDU31eNWiv0vbM", + "1f+xy3Rv8h9+tt6VBLiWm38CFW5n0zsVLWM5ixv1LJ1wFdU36bF35cuqKObFZbIS2VDA9A8/k5fetjTq", + "3vGEHEu3JDJXRS4aLP7KlYDwzYz0OXra167TcVEMT90TId6d3Dbcdfq+VFPmfA5p3d7682vrgIYqhMhb", + "JQhn5rDWPcWf2tGwV0BgXQDmug0Cm/uzZ4wlKBfkiK/VJAeqYADDYdY213Ykks/Wr0z7ccH28Uqs/Sln", + "6zSzyDwLoVhdnCdWonWky/EZVlkNLIbdsby/3yWkGisy1X5MEmCXBLpmsqD895fUsz2Kksoz29P/QJrZ", + "6STkLdFARXe8aJ0iB61qaHKNpKq3bSLM3nVm5pCUMPVDmB/mNFfxqmi9zq6tzCeBw0ok0XN8YSfZiGzf", + "bjnTwAeCZcOIjEcCWOfvf01kWr/2u0Vnp2bX8Kuik3ghSB5iSyvt7+BAUnlRo2SI+7UA7irDz2Oo2R4V", + "NZ9DqtnllkQXf10CD5IoTL0mGGGZB3kvWBVlgwlFd7dz1AAN5aEYhCdI7H9rcPpiRC9gc0+RBjVEaz1N", + "vXB/k1ySiAG8tYzgUQgV81K0pivnOMZURRmIBe8VbLtDnZW7t0psIOfccC5Pkk2JZ2DKeJnKUXOZrjtl", + "AsOAkb5cGN0yd/0aj5dYVVBVFdx9LspQL0hOIoWgXC5LTEtSWWt9VktQ/jefg8jOkrMLCOvYom0cUyi4", + "FlFlr9cjJwNyUif6O1q9CnNn+ZlZHcPRjfeN5IBG76c0F1j5qS/cqRk2Ubl53VPWORTFFKxEhXDNQbp6", + "33gz5EJBooV3rRuCYwgV1gP2RkhQvXUXLHC92VDf1elesf6MTZZBneNruEAiYUUNdDJIyto/5xCyX9jv", + "PsDV5+TaqtOu6DXZmlXVR+8w1UFiSPVz4m7L7YGzN1FvM85BJt7W3fYp5AaVof21kCIrU5cIJjgYlQlg", + "dMKyAVYS1Qyn3VV2lHw5ZgN/FaQhuIDNgdW/pEvKF0F6tRB6K9rbNQSZy1q7faea/7iSM1/YBSzuBM7P", + "qT2fTgoh8qTH4HrSTTTbPgMXLL0wYnZZ+733FNok99HOV3nUXC03PrFqUQCH7ME+IcfcRhp555pmpaPW", + "5PyeHpp/jbNmpc397BT7++c8HrKBSX3kLfmbH2aYqykwzO+WU9lBtqQxXfckuZX0KlJ2tutPN9rdpV0K", + "tCYqC0VMSrlhqq5R57ur3I+QflAFcfj1E2byq72YpbURobRUV4ZsCi+va9PPuHqMvsMW8EJlTVCR0XMj", + "B85ndjV+XSElWEovJTSWv03/4xZY86VgixRGTZpl2gTE1k2tuS+Bck+9qHRmcTx3VWuYtk9wzPnbVckp", + "tBnaNKwB4ZhzKS9p/unVapjP8RjxAdm7foEnfP+GSLaoVDfz93tFR80dvHXvbmr+FtWAfwWzR1FjrxvK", + "GX+qSpjeRIYp7mlOclHXRcYhyRWOaa3DD78iMxdFV0hImWKtAOMrX9Wkeu5hkS/nY7nWW96X29b5s9C3", + "IGP3QBAFeVNXSNAC74cawvqIfmam0nNyo1Qeo74OWUTwF+NRYTqbLdfFRcNsbCvOtPwhhYQ7Nh8HjmA7", + "mo+7iXrGLs+aSM2lUyrornP0bd3AbeSirtc21vehi9yhNPpjXBbi1TFMd/SZsAjB0jIEQSW/PvyVSJhj", + "7UhB9vZwgr29qWv666PmZ3Oc9/aiYtwn85awOHJjuHmjFOOMaZ1QGFgXTPYk/XvnmLu7sNF8R7ADxLNz", + "5hCtBoNTe7/RT5wKGmXurQp+uzTXeBs/C1Dml1xNFMP9z32xC9Y/vydMpnUWSpZn2w5lI+iprnyLYT2/", + "uIDcz1J79xery+6ySVf/cBcfufYBQMRE1tqYPJgqCGcaEcnkukXilpC40lIyvcE8YV71yX6J+tR8X1lL", + "nBW4yizj5A4tLqDKNFfbVkrlJZvvBc1RFjDvGfRQ1ELk++TbNV0VOTgm9c292X/C46+fZIePH/7n7OvD", + "p4cpPHn67PCQPntCHz57/BAeff30ySE8nH/1bPYoe/Tk0ezJoydfPX2WPn7ycPbkq2f/ec/cAQZkC+jE", + "Z6WY/A0LVCfHb0+SMwNsjRNasB9gY2thGjL2VTZpilwQVpTlkyP/0//23G0/Fat6eP/rxAW9T5ZaF+ro", + "4ODq6mo/7HKwQGVqokWZLg/8PJ0ynMdvT6rwMOsLhTtqI38MKeCmOlI4xm/vvj09I8dvT/ZrgpkcTQ73", + "D/cfYi7jAjgt2ORo8hh/wtOzxH0/8EmEjz5eTycHS6A52sTNHyvQkqX+k7qiiwXIfVdu1Px0+ejAi3EH", + "H50i+Xro20FYuefgY0Pfnm3piY4uBx99Eqvh1o0sUc7OYJa7iBl0vwd3TzjXj4hdQqF6044+JUpIp20r", + "JBPmJE1tdHsqgSLdC4nhWVqWPLUKbzsFcPzv6+O/oaXj9fHfyDfkcOqi9hQ+82LTW11SRQInmQW7qzJV", + "zzfHdcmSOsXt0fvIkyRaBhWPkKGPgMKrEWsOhtbqsHh0xY8Njz1Mnn34+PTr69id1C2/75EUGDNC1Gvh", + "Ez0h0lZ0/U0fytb2dOAafitBbupFrOh6EgLctX9FvNrmbFFK1CDWMfqVv66rhskU+e/TH98QIYnTKbyl", + "6UXowBcDx91nIUS+OJkLB1upRdGMnahw+AEzvyAUeIofHR7uVCC45VzUpSJXVp56/7quBk8RWNNU5xtC", + "8f7ZWFOTKmd1lqamKKBFkYQDRF/JAzP6+kYxx/ZdlYiR4D6sIzQMXztLewMdzjsK66ltN692kBGF4EPs", + "9g631tPIl93919jdrjBACmHONMPg0fo+ybtuiioo3uHA7bGP7JO/ixJFNlvHEmKpJnEGtCX5OZ2BN/Bv", + "y7GKaIWdvb32wvf23J4zReZwhRyUcmzYRsfeHhY+f7IjKxtUzTciMEadnV2G62zWa7quMvxRrGDBsczi", + "JZDgsfnk8OGfdoUnHL2LjKxJrCx9PZ08/RNv2Qk3UgvNCba0q3n8p13NKchLlgI5g1UhJJUs35CfeBWg", + "H6SL7LK/n/gFF1fcI8I8E8vVisqNk5BpxXNKHqRMGOQ/HcNsLUUjF6ULhTY8lD8njXLCfDH5cO0F/JGv", + "hqFmBzPMGDS2Kaigcf/TA40x6uAjmhN6fz9waVTiH9GsY9+sB96JLN6y8ar5qNcG1laPlOp0WRYHH/E/", + "+IYMwLJBi11wbdjGASaP23R/3vA0+mN3oHY96NjPBx+bZZYaCFXLUmfiKuiLBgtrbevOV1Xobfx9cEWZ", + "NhKC8wTEHLLdzhpofuASDbR+rWP7Ol8wYDH4sSVTFMLmgmm+1d7Rq1BCsdICKP1cZJsBbrNOZozjEQxZ", + "RK0Ksx+774MOYzhbgk297i25EQFMCzKTgmYpVZia1KXk6Lz6rm/5+GjJjeuTiJ0OwcSHdNepzBym7QUx", + "cdwxElawL0FGb5R0lVWh/cFSSQei5zQjPnlQQl7T3Gw4ZFiGS2LIXADyHy1RfH4R4DPf2Z/skn3uD58i", + "FN1mGq+jhtudrb7m/XPcQR1zo5onlGEAC+CJY0HJTGQbn6Be0iu9tk42beZ2UKUBjH68Ax3bP7dibZs+", + "7Ysa64sa64ui44sa68vuflFjfVHyfFHy/H+r5NlFsxOTIZ1mo1+UxISptDGvfbjROkysYvFhsylhuhK4", + "ulnbmd4n5AyDcKi5JeASJM2xso0KoupW6I6pyjQFyI7OedKAxDo9monv1/+13qbn5eHhYyCHD9p9lGZ5", + "HvLmbl8UZvGTTRr0DTmfnE86I0lYiUvIbMx5GJRge20d9n9V4/7YiW/CsNAlvYQqjIKocj5nKbMozwVf", + "ELoQteOV4duEC/yCFYxd9gLC9NTlfmGKXJnFu7S1zdiJpljelQBO6i3cau1ukUvc0G0Ib0cr93+MMXH/", + "64rgNw3oui2XHBy7wzK/sIxPwTI+O9P4s9sPA8Xfv6QM+eTwyZ92QaGa+I3Q5Dt08b+drFXl+Y5Fwt9U", + "ivJJ472irnZVDV0/8YqsnD7ffzAXAZaDcrdn7cl4dHCAsbZLofTBxNxtTS/H8OOHCmZfjWFSSHaJyR0/", + "XP+/AAAA//+u3heREOIAAA==", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/handlers.go b/daemon/algod/api/server/v2/handlers.go index 60a0a8e5de..21e1f91703 100644 --- a/daemon/algod/api/server/v2/handlers.go +++ b/daemon/algod/api/server/v2/handlers.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -30,6 +30,7 @@ import ( "github.com/labstack/echo/v4" + "github.com/algorand/avm-abi/apps" "github.com/algorand/go-codec/codec" "github.com/algorand/go-algorand/agreement" @@ -45,6 +46,7 @@ import ( "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/data/transactions/logic" "github.com/algorand/go-algorand/ledger/ledgercore" + "github.com/algorand/go-algorand/ledger/simulation" "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/node" "github.com/algorand/go-algorand/protocol" @@ -96,6 +98,7 @@ type NodeInterface interface { GenesisID() string GenesisHash() crypto.Digest BroadcastSignedTxGroup(txgroup []transactions.SignedTxn) error + Simulate(txgroup []transactions.SignedTxn) (vb *ledgercore.ValidatedBlock, missingSignatures bool, err error) GetPendingTransaction(txID transactions.Txid) (res node.TxnWithStatus, found bool) GetPendingTxnsFromPool() ([]transactions.SignedTxn, error) SuggestedFee() basics.MicroAlgos @@ -769,6 +772,12 @@ func (v2 *Handlers) GetStatus(ctx echo.Context) error { return internalError(ctx, err, errFailedRetrievingNodeStatus, v2.Log) } + ledger := v2.Node.LedgerForAPI() + latestBlkHdr, err := ledger.BlockHdr(ledger.Latest()) + if err != nil { + return internalError(ctx, err, errFailedRetrievingLatestBlockHeaderStatus, v2.Log) + } + response := model.NodeStatusResponse{ LastRound: uint64(stat.LastRound), LastVersion: string(stat.LastVersion), @@ -790,6 +799,29 @@ func (v2 *Handlers) GetStatus(ctx echo.Context) error { CatchpointAcquiredBlocks: &stat.CatchpointCatchupAcquiredBlocks, } + nextProtocolVoteBefore := uint64(stat.NextProtocolVoteBefore) + var votesToGo int64 = int64(nextProtocolVoteBefore) - int64(stat.LastRound) + if votesToGo < 0 { + votesToGo = 0 + } + if nextProtocolVoteBefore > 0 { + consensus := config.Consensus[protocol.ConsensusCurrentVersion] + upgradeVoteRounds := consensus.UpgradeVoteRounds + upgradeThreshold := consensus.UpgradeThreshold + votes := uint64(consensus.UpgradeVoteRounds) - uint64(votesToGo) + votesYes := stat.NextProtocolApprovals + votesNo := votes - votesYes + upgradeDelay := uint64(stat.UpgradeDelay) + response.UpgradeVotesRequired = &upgradeThreshold + response.UpgradeNodeVote = &latestBlkHdr.UpgradeApprove + response.UpgradeDelay = &upgradeDelay + response.UpgradeVotes = &votes + response.UpgradeYesVotes = &votesYes + response.UpgradeNoVotes = &votesNo + response.UpgradeNextProtocolVoteBefore = &nextProtocolVoteBefore + response.UpgradeVoteRounds = &upgradeVoteRounds + } + return ctx.JSON(http.StatusOK, response) } @@ -836,21 +868,10 @@ func (v2 *Handlers) WaitForBlock(ctx echo.Context, round uint64) error { return v2.GetStatus(ctx) } -// RawTransaction broadcasts a raw transaction to the network. -// (POST /v2/transactions) -func (v2 *Handlers) RawTransaction(ctx echo.Context) error { - stat, err := v2.Node.Status() - if err != nil { - return internalError(ctx, err, errFailedRetrievingNodeStatus, v2.Log) - } - if stat.Catchpoint != "" { - // node is currently catching up to the requested catchpoint. - return serviceUnavailable(ctx, fmt.Errorf("RawTransaction failed as the node was catchpoint catchuping"), errOperationNotAvailableDuringCatchup, v2.Log) - } - proto := config.Consensus[stat.LastVersion] - +// decodeTxGroup attempts to decode a request body containing a transaction group. +func decodeTxGroup(body io.Reader, maxTxGroupSize int) ([]transactions.SignedTxn, error) { var txgroup []transactions.SignedTxn - dec := protocol.NewDecoder(ctx.Request().Body) + dec := protocol.NewDecoder(body) for { var st transactions.SignedTxn err := dec.Decode(&st) @@ -858,18 +879,38 @@ func (v2 *Handlers) RawTransaction(ctx echo.Context) error { break } if err != nil { - return badRequest(ctx, err, err.Error(), v2.Log) + return nil, err } txgroup = append(txgroup, st) - if len(txgroup) > proto.MaxTxGroupSize { - err := fmt.Errorf("max group size is %d", proto.MaxTxGroupSize) - return badRequest(ctx, err, err.Error(), v2.Log) + if len(txgroup) > maxTxGroupSize { + err := fmt.Errorf("max group size is %d", maxTxGroupSize) + return nil, err } } if len(txgroup) == 0 { - err := errors.New("empty txgroup") + return nil, errors.New("empty txgroup") + } + + return txgroup, nil +} + +// RawTransaction broadcasts a raw transaction to the network. +// (POST /v2/transactions) +func (v2 *Handlers) RawTransaction(ctx echo.Context) error { + stat, err := v2.Node.Status() + if err != nil { + return internalError(ctx, err, errFailedRetrievingNodeStatus, v2.Log) + } + if stat.Catchpoint != "" { + // node is currently catching up to the requested catchpoint. + return serviceUnavailable(ctx, fmt.Errorf("RawTransaction failed as the node was catchpoint catchuping"), errOperationNotAvailableDuringCatchup, v2.Log) + } + proto := config.Consensus[stat.LastVersion] + + txgroup, err := decodeTxGroup(ctx.Request().Body, proto.MaxTxGroupSize) + if err != nil { return badRequest(ctx, err, err.Error(), v2.Log) } @@ -883,6 +924,57 @@ func (v2 *Handlers) RawTransaction(ctx echo.Context) error { return ctx.JSON(http.StatusOK, model.PostTransactionsResponse{TxId: txid.String()}) } +// SimulateTransaction simulates broadcasting a raw transaction to the network, returning relevant simulation results. +// (POST /v2/transactions/simulate) +func (v2 *Handlers) SimulateTransaction(ctx echo.Context) error { + if !v2.Node.Config().EnableExperimentalAPI { + // Right now this is a redundant/useless check at runtime, since experimental APIs are not registered when EnableExperimentalAPI=false. + // However, this endpoint won't always be experimental, so I've left this here as a reminder to have some other flag guarding its usage. + return ctx.String(http.StatusNotFound, fmt.Sprintf("%s was not enabled in the configuration file by setting EnableExperimentalAPI to true", ctx.Request().URL.Path)) + } + + stat, err := v2.Node.Status() + if err != nil { + return internalError(ctx, err, errFailedRetrievingNodeStatus, v2.Log) + } + if stat.Catchpoint != "" { + // node is currently catching up to the requested catchpoint. + return serviceUnavailable(ctx, fmt.Errorf("SimulateTransaction failed as the node was catchpoint catchuping"), errOperationNotAvailableDuringCatchup, v2.Log) + } + proto := config.Consensus[stat.LastVersion] + + txgroup, err := decodeTxGroup(ctx.Request().Body, proto.MaxTxGroupSize) + if err != nil { + return badRequest(ctx, err, err.Error(), v2.Log) + } + + var res model.SimulationResponse + + // Simulate transaction + _, missingSignatures, err := v2.Node.Simulate(txgroup) + if err != nil { + var invalidTxErr *simulation.InvalidTxGroupError + var evalErr *simulation.EvalFailureError + switch { + case errors.As(err, &invalidTxErr): + return badRequest(ctx, invalidTxErr, invalidTxErr.Error(), v2.Log) + case errors.As(err, &evalErr): + res.FailureMessage = evalErr.Error() + default: + return internalError(ctx, err, err.Error(), v2.Log) + } + } + res.MissingSignatures = missingSignatures + + // Return msgpack response + msgpack, err := encode(protocol.CodecHandle, &res) + if err != nil { + return internalError(ctx, err, errFailedToEncodeResponse, v2.Log) + } + + return ctx.Blob(http.StatusOK, "application/msgpack", msgpack) +} + // TealDryrun takes transactions and additional simulated ledger state and returns debugging information. // (POST /v2/teal/dryrun) func (v2 *Handlers) TealDryrun(ctx echo.Context) error { @@ -1291,7 +1383,7 @@ func (v2 *Handlers) GetApplicationBoxes(ctx echo.Context, applicationID uint64, appIdx := basics.AppIndex(applicationID) ledger := v2.Node.LedgerForAPI() lastRound := ledger.Latest() - keyPrefix := logic.MakeBoxKey(appIdx, "") + keyPrefix := apps.MakeBoxKey(uint64(appIdx), "") requestedMax, algodMax := nilToZero(params.Max), v2.Node.Config().MaxAPIBoxPerApplication max := applicationBoxesMaxKeys(requestedMax, algodMax) @@ -1337,7 +1429,7 @@ func (v2 *Handlers) GetApplicationBoxByName(ctx echo.Context, applicationID uint lastRound := ledger.Latest() encodedBoxName := params.Name - boxNameBytes, err := logic.NewAppCallBytes(encodedBoxName) + boxNameBytes, err := apps.NewAppCallBytes(encodedBoxName) if err != nil { return badRequest(ctx, err, err.Error(), v2.Log) } @@ -1346,7 +1438,7 @@ func (v2 *Handlers) GetApplicationBoxByName(ctx echo.Context, applicationID uint return badRequest(ctx, err, err.Error(), v2.Log) } - value, err := ledger.LookupKv(lastRound, logic.MakeBoxKey(appIdx, string(boxName))) + value, err := ledger.LookupKv(lastRound, apps.MakeBoxKey(uint64(appIdx), string(boxName))) if err != nil { return internalError(ctx, err, errFailedLookingUpLedger, v2.Log) } @@ -1437,7 +1529,7 @@ func (v2 *Handlers) TealCompile(ctx echo.Context, params model.TealCompileParams ops, err := logic.AssembleString(source) if err != nil { sb := strings.Builder{} - ops.ReportProblems("", &sb) + ops.ReportMultipleErrors("", &sb) return badRequest(ctx, err, sb.String(), v2.Log) } pd := logic.HashProgram(ops.Program) diff --git a/daemon/algod/api/server/v2/handlers_test.go b/daemon/algod/api/server/v2/handlers_test.go index 98635897d5..2a703e2553 100644 --- a/daemon/algod/api/server/v2/handlers_test.go +++ b/daemon/algod/api/server/v2/handlers_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/algod/api/server/v2/test/handlers_resources_test.go b/daemon/algod/api/server/v2/test/handlers_resources_test.go index d9ab3832ab..04e36a3e4f 100644 --- a/daemon/algod/api/server/v2/test/handlers_resources_test.go +++ b/daemon/algod/api/server/v2/test/handlers_resources_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -225,7 +225,7 @@ func setupTestForLargeResources(t *testing.T, acctSize, maxResults int, accountM acctData = accountMaker(acctSize) ml.accounts[fakeAddr] = acctData - mockNode := makeMockNode(&ml, t.Name(), nil) + mockNode := makeMockNode(&ml, t.Name(), nil, false) mockNode.config.MaxAPIResourcesPerAccount = uint64(maxResults) dummyShutdownChan := make(chan struct{}) handlers = v2.Handlers{ diff --git a/daemon/algod/api/server/v2/test/handlers_test.go b/daemon/algod/api/server/v2/test/handlers_test.go index 437dca2a34..dedd902c94 100644 --- a/daemon/algod/api/server/v2/test/handlers_test.go +++ b/daemon/algod/api/server/v2/test/handlers_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -37,6 +37,7 @@ import ( "github.com/algorand/go-algorand/agreement" "github.com/algorand/go-algorand/catchup" + "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/crypto/merklearray" "github.com/algorand/go-algorand/crypto/merklesignature" @@ -59,12 +60,12 @@ import ( const stateProofIntervalForHandlerTests = uint64(256) -func setupTestForMethodGet(t *testing.T) (v2.Handlers, echo.Context, *httptest.ResponseRecorder, []account.Root, []transactions.SignedTxn, func()) { +func setupTestForMethodGet(t *testing.T, consensusUpgrade bool) (v2.Handlers, echo.Context, *httptest.ResponseRecorder, []account.Root, []transactions.SignedTxn, func()) { numAccounts := 1 numTransactions := 1 offlineAccounts := true mockLedger, rootkeys, _, stxns, releasefunc := testingenv(t, numAccounts, numTransactions, offlineAccounts) - mockNode := makeMockNode(mockLedger, t.Name(), nil) + mockNode := makeMockNode(mockLedger, t.Name(), nil, consensusUpgrade) dummyShutdownChan := make(chan struct{}) handler := v2.Handlers{ Node: mockNode, @@ -82,13 +83,13 @@ func TestSimpleMockBuilding(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - handler, _, _, _, _, releasefunc := setupTestForMethodGet(t) + handler, _, _, _, _, releasefunc := setupTestForMethodGet(t, false) defer releasefunc() require.Equal(t, t.Name(), handler.Node.GenesisID()) } func accountInformationTest(t *testing.T, address string, expectedCode int) { - handler, c, rec, _, _, releasefunc := setupTestForMethodGet(t) + handler, c, rec, _, _, releasefunc := setupTestForMethodGet(t, false) defer releasefunc() err := handler.AccountInformation(c, address, model.AccountInformationParams{}) require.NoError(t, err) @@ -111,7 +112,7 @@ func TestAccountInformation(t *testing.T) { } func getBlockTest(t *testing.T, blockNum uint64, format string, expectedCode int) { - handler, c, rec, _, _, releasefunc := setupTestForMethodGet(t) + handler, c, rec, _, _, releasefunc := setupTestForMethodGet(t, false) defer releasefunc() err := handler.GetBlock(c, blockNum, model.GetBlockParams{Format: (*model.GetBlockParamsFormat)(&format)}) require.NoError(t, err) @@ -134,7 +135,7 @@ func TestGetLedgerStateDelta(t *testing.T) { t.Parallel() a := require.New(t) - handler, c, rec, _, _, releasefunc := setupTestForMethodGet(t) + handler, c, rec, _, _, releasefunc := setupTestForMethodGet(t, false) defer releasefunc() insertRounds(a, handler, 3) @@ -163,7 +164,7 @@ func TestSyncRound(t *testing.T) { numTransactions := 1 offlineAccounts := true mockLedger, _, _, _, releasefunc := testingenv(t, numAccounts, numTransactions, offlineAccounts) - mockNode := makeMockNode(mockLedger, t.Name(), nil) + mockNode := makeMockNode(mockLedger, t.Name(), nil, false) dummyShutdownChan := make(chan struct{}) handler := v2.Handlers{ Node: mockNode, @@ -224,7 +225,7 @@ func TestSyncRound(t *testing.T) { } func addBlockHelper(t *testing.T) (v2.Handlers, echo.Context, *httptest.ResponseRecorder, transactions.SignedTxn, func()) { - handler, c, rec, _, _, releasefunc := setupTestForMethodGet(t) + handler, c, rec, _, _, releasefunc := setupTestForMethodGet(t, false) l := handler.Node.LedgerForAPI() @@ -308,7 +309,7 @@ func TestGetBlockHash(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - handler, c, rec, _, _, releasefunc := setupTestForMethodGet(t) + handler, c, rec, _, _, releasefunc := setupTestForMethodGet(t, false) defer releasefunc() err := handler.GetBlockHash(c, 0) @@ -326,7 +327,7 @@ func TestGetBlockGetBlockHash(t *testing.T) { t.Parallel() a := require.New(t) - handler, c, rec, _, _, releasefunc := setupTestForMethodGet(t) + handler, c, rec, _, _, releasefunc := setupTestForMethodGet(t, false) defer releasefunc() insertRounds(a, handler, 2) @@ -398,7 +399,7 @@ func TestGetSupply(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - handler, c, _, _, _, releasefunc := setupTestForMethodGet(t) + handler, c, _, _, _, releasefunc := setupTestForMethodGet(t, false) defer releasefunc() err := handler.GetSupply(c) require.NoError(t, err) @@ -408,7 +409,7 @@ func TestGetStatus(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - handler, c, rec, _, _, releasefunc := setupTestForMethodGet(t) + handler, c, rec, _, _, releasefunc := setupTestForMethodGet(t, false) defer releasefunc() err := handler.GetStatus(c) require.NoError(t, err) @@ -439,11 +440,60 @@ func TestGetStatus(t *testing.T) { require.Equal(t, expectedResult, actualResult) } +func TestGetStatusConsensusUpgrade(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + handler, c, rec, _, _, releasefunc := setupTestForMethodGet(t, true) + defer releasefunc() + err := handler.GetStatus(c) + require.NoError(t, err) + stat := cannedStatusReportConsensusUpgradeGolden + consensus := config.Consensus[protocol.ConsensusCurrentVersion] + votesToGo := uint64(stat.NextProtocolVoteBefore) - uint64(stat.LastRound) + nextProtocolVoteBefore := uint64(stat.NextProtocolVoteBefore) + votes := uint64(consensus.UpgradeVoteRounds) - votesToGo + votesNo := votes - stat.NextProtocolApprovals + + expectedResult := model.NodeStatusResponse{ + LastRound: uint64(stat.LastRound), + LastVersion: string(stat.LastVersion), + NextVersion: string(stat.NextVersion), + NextVersionRound: uint64(stat.NextVersionRound), + NextVersionSupported: stat.NextVersionSupported, + TimeSinceLastRound: uint64(stat.TimeSinceLastRound().Nanoseconds()), + CatchupTime: uint64(stat.CatchupTime.Nanoseconds()), + StoppedAtUnsupportedRound: stat.StoppedAtUnsupportedRound, + LastCatchpoint: &stat.LastCatchpoint, + Catchpoint: &stat.Catchpoint, + CatchpointTotalAccounts: &stat.CatchpointCatchupTotalAccounts, + CatchpointProcessedAccounts: &stat.CatchpointCatchupProcessedAccounts, + CatchpointVerifiedAccounts: &stat.CatchpointCatchupVerifiedAccounts, + CatchpointTotalBlocks: &stat.CatchpointCatchupTotalBlocks, + CatchpointAcquiredBlocks: &stat.CatchpointCatchupAcquiredBlocks, + CatchpointTotalKvs: &stat.CatchpointCatchupTotalKVs, + CatchpointProcessedKvs: &stat.CatchpointCatchupProcessedKVs, + CatchpointVerifiedKvs: &stat.CatchpointCatchupVerifiedKVs, + UpgradeVotesRequired: &consensus.UpgradeThreshold, + UpgradeNodeVote: &stat.UpgradeApprove, + UpgradeDelay: &stat.UpgradeDelay, + UpgradeNoVotes: &votesNo, + UpgradeYesVotes: &stat.NextProtocolApprovals, + UpgradeVoteRounds: &consensus.UpgradeVoteRounds, + UpgradeNextProtocolVoteBefore: &nextProtocolVoteBefore, + UpgradeVotes: &votes, + } + actualResult := model.NodeStatusResponse{} + err = protocol.DecodeJSON(rec.Body.Bytes(), &actualResult) + require.NoError(t, err) + require.Equal(t, expectedResult, actualResult) +} + func TestGetStatusAfterBlock(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - handler, c, rec, _, _, releasefunc := setupTestForMethodGet(t) + handler, c, rec, _, _, releasefunc := setupTestForMethodGet(t, false) defer releasefunc() err := handler.WaitForBlock(c, 0) require.NoError(t, err) @@ -456,7 +506,7 @@ func TestGetTransactionParams(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - handler, c, rec, _, _, releasefunc := setupTestForMethodGet(t) + handler, c, rec, _, _, releasefunc := setupTestForMethodGet(t, false) defer releasefunc() err := handler.TransactionParams(c) require.NoError(t, err) @@ -464,7 +514,7 @@ func TestGetTransactionParams(t *testing.T) { } func pendingTransactionInformationTest(t *testing.T, txidToUse int, format string, expectedCode int) { - handler, c, rec, _, stxns, releasefunc := setupTestForMethodGet(t) + handler, c, rec, _, stxns, releasefunc := setupTestForMethodGet(t, false) defer releasefunc() txid := "bad txid" if txidToUse >= 0 { @@ -487,7 +537,7 @@ func TestPendingTransactionInformation(t *testing.T) { } func getPendingTransactionsTest(t *testing.T, format string, max uint64, expectedCode int) { - handler, c, rec, _, _, releasefunc := setupTestForMethodGet(t) + handler, c, rec, _, _, releasefunc := setupTestForMethodGet(t, false) defer releasefunc() params := model.GetPendingTransactionsParams{Format: (*model.GetPendingTransactionsParamsFormat)(&format), Max: &max} err := handler.GetPendingTransactions(c, params) @@ -566,7 +616,7 @@ func TestPendingTransactions(t *testing.T) { } func pendingTransactionsByAddressTest(t *testing.T, rootkeyToUse int, format string, expectedCode int) { - handler, c, rec, rootkeys, _, releasefunc := setupTestForMethodGet(t) + handler, c, rec, rootkeys, _, releasefunc := setupTestForMethodGet(t, false) defer releasefunc() address := "bad address" if rootkeyToUse >= 0 { @@ -588,15 +638,16 @@ func TestPendingTransactionsByAddress(t *testing.T) { pendingTransactionsByAddressTest(t, -1, "json", 400) } -func postTransactionTest(t *testing.T, txnToUse, expectedCode int) { +func prepareTransactionTest(t *testing.T, txnToUse, expectedCode int, enableTransactionSimulator bool) (handler v2.Handlers, c echo.Context, rec *httptest.ResponseRecorder, releasefunc func()) { numAccounts := 5 numTransactions := 5 offlineAccounts := true mockLedger, _, _, stxns, releasefunc := testingenv(t, numAccounts, numTransactions, offlineAccounts) - defer releasefunc() dummyShutdownChan := make(chan struct{}) - mockNode := makeMockNode(mockLedger, t.Name(), nil) - handler := v2.Handlers{ + mockNode := makeMockNode(mockLedger, t.Name(), nil, false) + mockNode.config.EnableExperimentalAPI = enableTransactionSimulator + handler = v2.Handlers{ + Node: mockNode, Log: logging.Base(), Shutdown: dummyShutdownChan, @@ -609,8 +660,14 @@ func postTransactionTest(t *testing.T, txnToUse, expectedCode int) { body = bytes.NewReader(bodyBytes) } req := httptest.NewRequest(http.MethodPost, "/", body) - rec := httptest.NewRecorder() - c := e.NewContext(req, rec) + rec = httptest.NewRecorder() + c = e.NewContext(req, rec) + return +} + +func postTransactionTest(t *testing.T, txnToUse, expectedCode int) { + handler, c, rec, releasefunc := prepareTransactionTest(t, txnToUse, expectedCode, false) + defer releasefunc() err := handler.RawTransaction(c) require.NoError(t, err) require.Equal(t, expectedCode, rec.Code) @@ -624,6 +681,23 @@ func TestPostTransaction(t *testing.T) { postTransactionTest(t, 0, 200) } +func simulateTransactionTest(t *testing.T, txnToUse, expectedCode int, enableTransactionSimulator bool) { + handler, c, rec, releasefunc := prepareTransactionTest(t, txnToUse, expectedCode, enableTransactionSimulator) + defer releasefunc() + err := handler.SimulateTransaction(c) + require.NoError(t, err) + require.Equal(t, expectedCode, rec.Code) +} + +func TestSimulateTransaction(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + simulateTransactionTest(t, -1, 400, true) + simulateTransactionTest(t, 0, 404, false) + simulateTransactionTest(t, 0, 200, true) +} + func startCatchupTest(t *testing.T, catchpoint string, nodeError error, expectedCode int) { numAccounts := 1 numTransactions := 1 @@ -631,7 +705,7 @@ func startCatchupTest(t *testing.T, catchpoint string, nodeError error, expected mockLedger, _, _, _, releasefunc := testingenv(t, numAccounts, numTransactions, offlineAccounts) defer releasefunc() dummyShutdownChan := make(chan struct{}) - mockNode := makeMockNode(mockLedger, t.Name(), nodeError) + mockNode := makeMockNode(mockLedger, t.Name(), nodeError, false) handler := v2.Handlers{ Node: mockNode, Log: logging.Base(), @@ -672,7 +746,7 @@ func abortCatchupTest(t *testing.T, catchpoint string, expectedCode int) { mockLedger, _, _, _, releasefunc := testingenv(t, numAccounts, numTransactions, offlineAccounts) defer releasefunc() dummyShutdownChan := make(chan struct{}) - mockNode := makeMockNode(mockLedger, t.Name(), nil) + mockNode := makeMockNode(mockLedger, t.Name(), nil, false) handler := v2.Handlers{ Node: mockNode, Log: logging.Base(), @@ -707,7 +781,7 @@ func tealCompileTest(t *testing.T, bytesToUse []byte, expectedCode int, mockLedger, _, _, _, releasefunc := testingenv(t, numAccounts, numTransactions, offlineAccounts) defer releasefunc() dummyShutdownChan := make(chan struct{}) - mockNode := makeMockNode(mockLedger, t.Name(), nil) + mockNode := makeMockNode(mockLedger, t.Name(), nil, false) mockNode.config.EnableDeveloperAPI = enableDeveloperAPI handler := v2.Handlers{ Node: mockNode, @@ -741,7 +815,7 @@ func TestTealCompile(t *testing.T) { t.Parallel() params := model.TealCompileParams{} - tealCompileTest(t, nil, 200, true, params, nil) // nil program should work + tealCompileTest(t, nil, 400, true, params, nil) // nil program should NOT work goodProgram := fmt.Sprintf(`#pragma version %d int 1 @@ -778,7 +852,7 @@ func tealDisassembleTest(t *testing.T, program []byte, expectedCode int, mockLedger, _, _, _, releasefunc := testingenv(t, numAccounts, numTransactions, offlineAccounts) defer releasefunc() dummyShutdownChan := make(chan struct{}) - mockNode := makeMockNode(mockLedger, t.Name(), nil) + mockNode := makeMockNode(mockLedger, t.Name(), nil, false) mockNode.config.EnableDeveloperAPI = enableDeveloperAPI handler := v2.Handlers{ Node: mockNode, @@ -841,7 +915,7 @@ func tealDryrunTest( mockLedger, _, _, _, releasefunc := testingenv(t, numAccounts, numTransactions, offlineAccounts) defer releasefunc() dummyShutdownChan := make(chan struct{}) - mockNode := makeMockNode(mockLedger, t.Name(), nil) + mockNode := makeMockNode(mockLedger, t.Name(), nil, false) mockNode.config.EnableDeveloperAPI = enableDeveloperAPI handler := v2.Handlers{ Node: mockNode, @@ -960,7 +1034,7 @@ func TestAppendParticipationKeys(t *testing.T) { mockLedger, _, _, _, releasefunc := testingenv(t, 1, 1, true) defer releasefunc() - mockNode := makeMockNode(mockLedger, t.Name(), nil) + mockNode := makeMockNode(mockLedger, t.Name(), nil, false) handler := v2.Handlers{ Node: mockNode, Log: logging.Base(), @@ -1044,7 +1118,7 @@ func TestAppendParticipationKeys(t *testing.T) { t.Run("Internal error", func(t *testing.T) { // Create mock node with an error. expectedErr := errors.New("expected error") - mockNode := makeMockNode(mockLedger, t.Name(), expectedErr) + mockNode := makeMockNode(mockLedger, t.Name(), expectedErr, false) handler := v2.Handlers{ Node: mockNode, Log: logging.Base(), @@ -1210,7 +1284,7 @@ func TestStateProofNotFound(t *testing.T) { partitiontest.PartitionTest(t) a := require.New(t) - handler, ctx, responseRecorder, _, _, releasefunc := setupTestForMethodGet(t) + handler, ctx, responseRecorder, _, _, releasefunc := setupTestForMethodGet(t, false) defer releasefunc() insertRounds(a, handler, 700) @@ -1223,7 +1297,7 @@ func TestStateProofHigherRoundThanLatest(t *testing.T) { partitiontest.PartitionTest(t) a := require.New(t) - handler, ctx, responseRecorder, _, _, releasefunc := setupTestForMethodGet(t) + handler, ctx, responseRecorder, _, _, releasefunc := setupTestForMethodGet(t, false) defer releasefunc() a.NoError(handler.GetStateProof(ctx, 2)) @@ -1234,7 +1308,7 @@ func TestStateProof200(t *testing.T) { partitiontest.PartitionTest(t) a := require.New(t) - handler, ctx, responseRecorder, _, _, releasefunc := setupTestForMethodGet(t) + handler, ctx, responseRecorder, _, _, releasefunc := setupTestForMethodGet(t, false) defer releasefunc() insertRounds(a, handler, 1000) @@ -1252,7 +1326,7 @@ func TestHeaderProofRoundTooHigh(t *testing.T) { partitiontest.PartitionTest(t) a := require.New(t) - handler, ctx, responseRecorder, _, _, releasefunc := setupTestForMethodGet(t) + handler, ctx, responseRecorder, _, _, releasefunc := setupTestForMethodGet(t, false) defer releasefunc() a.NoError(handler.GetLightBlockHeaderProof(ctx, 2)) @@ -1263,7 +1337,7 @@ func TestHeaderProofStateProofNotFound(t *testing.T) { partitiontest.PartitionTest(t) a := require.New(t) - handler, ctx, responseRecorder, _, _, releasefunc := setupTestForMethodGet(t) + handler, ctx, responseRecorder, _, _, releasefunc := setupTestForMethodGet(t, false) defer releasefunc() insertRounds(a, handler, 700) @@ -1276,7 +1350,7 @@ func TestGetBlockProof200(t *testing.T) { partitiontest.PartitionTest(t) a := require.New(t) - handler, ctx, responseRecorder, _, _, releasefunc := setupTestForMethodGet(t) + handler, ctx, responseRecorder, _, _, releasefunc := setupTestForMethodGet(t, false) defer releasefunc() insertRounds(a, handler, 1000) diff --git a/daemon/algod/api/server/v2/test/helpers.go b/daemon/algod/api/server/v2/test/helpers.go index f18ebf86b0..51467b65d9 100644 --- a/daemon/algod/api/server/v2/test/helpers.go +++ b/daemon/algod/api/server/v2/test/helpers.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -32,6 +32,7 @@ import ( "github.com/algorand/go-algorand/data" "github.com/algorand/go-algorand/data/account" "github.com/algorand/go-algorand/data/basics" + basics_testing "github.com/algorand/go-algorand/data/basics/testing" "github.com/algorand/go-algorand/data/bookkeeping" "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/data/transactions/logic" @@ -62,6 +63,30 @@ var cannedStatusReportGolden = node.StatusReport{ LastCatchpoint: "", } +var cannedStatusReportConsensusUpgradeGolden = node.StatusReport{ + LastRound: basics.Round(97000), + LastVersion: protocol.ConsensusCurrentVersion, + NextVersion: protocol.ConsensusCurrentVersion, + NextVersionRound: 200000, + NextVersionSupported: true, + StoppedAtUnsupportedRound: true, + Catchpoint: "", + CatchpointCatchupAcquiredBlocks: 0, + CatchpointCatchupProcessedAccounts: 0, + CatchpointCatchupVerifiedAccounts: 0, + CatchpointCatchupTotalAccounts: 0, + CatchpointCatchupTotalKVs: 0, + CatchpointCatchupProcessedKVs: 0, + CatchpointCatchupVerifiedKVs: 0, + CatchpointCatchupTotalBlocks: 0, + LastCatchpoint: "", + UpgradePropose: "upgradePropose", + UpgradeApprove: false, + UpgradeDelay: 0, + NextProtocolVoteBefore: 100000, + NextProtocolApprovals: 5000, +} + var poolAddrRewardBaseGolden = uint64(0) var poolAddrAssetsGolden = make([]model.AssetHolding, 0) var poolAddrCreatedAssetsGolden = make([]model.Asset, 0) @@ -116,13 +141,14 @@ var poolDeltaResponseGolden = model.LedgerStateDelta{ // package `data` and package `node`, which themselves import `mocks` type mockNode struct { mock.Mock - ledger v2.LedgerForAPI - genesisID string - config config.Local - err error - id account.ParticipationID - keys account.StateProofKeys - usertxns map[basics.Address][]node.TxnWithStatus + ledger v2.LedgerForAPI + genesisID string + config config.Local + err error + id account.ParticipationID + keys account.StateProofKeys + usertxns map[basics.Address][]node.TxnWithStatus + consensusUpgrade bool } func (m *mockNode) InstallParticipationKey(partKeyBinary []byte) (account.ParticipationID, error) { @@ -160,13 +186,14 @@ func (m *mockNode) AppendParticipationKeys(id account.ParticipationID, keys acco return m.err } -func makeMockNode(ledger v2.LedgerForAPI, genesisID string, nodeError error) *mockNode { +func makeMockNode(ledger v2.LedgerForAPI, genesisID string, nodeError error, consensusUpgrade bool) *mockNode { return &mockNode{ - ledger: ledger, - genesisID: genesisID, - config: config.GetDefaultLocal(), - err: nodeError, - usertxns: map[basics.Address][]node.TxnWithStatus{}, + ledger: ledger, + genesisID: genesisID, + config: config.GetDefaultLocal(), + err: nodeError, + usertxns: map[basics.Address][]node.TxnWithStatus{}, + consensusUpgrade: consensusUpgrade, } } @@ -174,8 +201,12 @@ func (m *mockNode) LedgerForAPI() v2.LedgerForAPI { return m.ledger } -func (m *mockNode) Status() (s node.StatusReport, err error) { - s = cannedStatusReportGolden +func (m mockNode) Status() (s node.StatusReport, err error) { + if m.consensusUpgrade { + s = cannedStatusReportConsensusUpgradeGolden + } else { + s = cannedStatusReportGolden + } return } func (m *mockNode) GenesisID() string { @@ -190,6 +221,10 @@ func (m *mockNode) BroadcastSignedTxGroup(txgroup []transactions.SignedTxn) erro return m.err } +func (m *mockNode) Simulate(txgroup []transactions.SignedTxn) (*ledgercore.ValidatedBlock, bool, error) { + return nil, false, m.err +} + func (m *mockNode) GetPendingTransaction(txID transactions.Txid) (res node.TxnWithStatus, found bool) { res = node.TxnWithStatus{} found = true @@ -229,10 +264,6 @@ func (m *mockNode) GetTransaction(addr basics.Address, txID transactions.Txid, m return node.TxnWithStatus{}, false } -func (m *mockNode) PoolStats() node.PoolStats { - return node.PoolStats{} -} - func (m *mockNode) IsArchival() bool { return false } @@ -326,9 +357,9 @@ func testingenv(t testing.TB, numAccounts, numTxs int, offlineAccounts bool) (*d short := root.Address() if offlineAccounts && i > P/2 { - genesis[short] = basics.MakeAccountData(basics.Offline, startamt) + genesis[short] = basics_testing.MakeAccountData(basics.Offline, startamt) } else { - data := basics.MakeAccountData(basics.Online, startamt) + data := basics_testing.MakeAccountData(basics.Online, startamt) data.SelectionID = parts[i].VRFSecrets().PK data.VoteID = parts[i].VotingSecrets().OneTimeSignatureVerifier genesis[short] = data @@ -336,13 +367,13 @@ func testingenv(t testing.TB, numAccounts, numTxs int, offlineAccounts bool) (*d part.Close() } - genesis[poolAddr] = basics.MakeAccountData(basics.NotParticipating, basics.MicroAlgos{Raw: 100000 * uint64(proto.RewardsRateRefreshInterval)}) + genesis[poolAddr] = basics_testing.MakeAccountData(basics.NotParticipating, basics.MicroAlgos{Raw: 100000 * uint64(proto.RewardsRateRefreshInterval)}) program := logic.Program(retOneProgram) lhash := crypto.HashObj(&program) var addr basics.Address copy(addr[:], lhash[:]) - ad := basics.MakeAccountData(basics.NotParticipating, basics.MicroAlgos{Raw: 100000 * uint64(proto.RewardsRateRefreshInterval)}) + ad := basics_testing.MakeAccountData(basics.NotParticipating, basics.MicroAlgos{Raw: 100000 * uint64(proto.RewardsRateRefreshInterval)}) ad.AppLocalStates = map[basics.AppIndex]basics.AppLocalState{1: {}} genesis[addr] = ad diff --git a/daemon/algod/api/server/v2/utils.go b/daemon/algod/api/server/v2/utils.go index 78db965b5b..52690cb8e3 100644 --- a/daemon/algod/api/server/v2/utils.go +++ b/daemon/algod/api/server/v2/utils.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/algod/api/spec/common/model.go b/daemon/algod/api/spec/common/model.go index 66741a65b6..4b4d4eae77 100644 --- a/daemon/algod/api/spec/common/model.go +++ b/daemon/algod/api/spec/common/model.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/algod/api/spec/v2/model.go b/daemon/algod/api/spec/v2/model.go index 9b997b2b74..845bc7c227 100644 --- a/daemon/algod/api/spec/v2/model.go +++ b/daemon/algod/api/spec/v2/model.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/algod/api/swagger.go b/daemon/algod/api/swagger.go index e20506e2cf..5402e802ec 100644 --- a/daemon/algod/api/swagger.go +++ b/daemon/algod/api/swagger.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/algod/deadlockLogger.go b/daemon/algod/deadlockLogger.go index 62fc1af4c5..923db611f8 100644 --- a/daemon/algod/deadlockLogger.go +++ b/daemon/algod/deadlockLogger.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/algod/deadlock_test.go b/daemon/algod/deadlock_test.go index b3a7b24de6..83cc118592 100644 --- a/daemon/algod/deadlock_test.go +++ b/daemon/algod/deadlock_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/algod/server.go b/daemon/algod/server.go index 4f1ce35158..0c65b2efab 100644 --- a/daemon/algod/server.go +++ b/daemon/algod/server.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/algod/server_test.go b/daemon/algod/server_test.go index 145a6e9b66..bb8ec350e7 100644 --- a/daemon/algod/server_test.go +++ b/daemon/algod/server_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/kmd/api/api.go b/daemon/kmd/api/api.go index 182eb19c3e..e27c974809 100644 --- a/daemon/kmd/api/api.go +++ b/daemon/kmd/api/api.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/kmd/api/cors.go b/daemon/kmd/api/cors.go index 89f9bcd89e..96c00b7ce6 100644 --- a/daemon/kmd/api/cors.go +++ b/daemon/kmd/api/cors.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/kmd/api/v1/auth.go b/daemon/kmd/api/v1/auth.go index 35e73aadba..eb28cb9909 100644 --- a/daemon/kmd/api/v1/auth.go +++ b/daemon/kmd/api/v1/auth.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/kmd/api/v1/errors.go b/daemon/kmd/api/v1/errors.go index 9193ea291c..493551f640 100644 --- a/daemon/kmd/api/v1/errors.go +++ b/daemon/kmd/api/v1/errors.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/kmd/api/v1/handlers.go b/daemon/kmd/api/v1/handlers.go index 199f1443da..f585cb274a 100644 --- a/daemon/kmd/api/v1/handlers.go +++ b/daemon/kmd/api/v1/handlers.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/kmd/client/client.go b/daemon/kmd/client/client.go index 413e3e8d70..339d970f6a 100644 --- a/daemon/kmd/client/client.go +++ b/daemon/kmd/client/client.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/kmd/client/requests.go b/daemon/kmd/client/requests.go index 78da5b4c59..dbd6cf69af 100644 --- a/daemon/kmd/client/requests.go +++ b/daemon/kmd/client/requests.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/kmd/client/wrappers.go b/daemon/kmd/client/wrappers.go index f3392850c6..cb490c791e 100644 --- a/daemon/kmd/client/wrappers.go +++ b/daemon/kmd/client/wrappers.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/kmd/config/config.go b/daemon/kmd/config/config.go index 95a03fe0d7..3192c7a9a0 100644 --- a/daemon/kmd/config/config.go +++ b/daemon/kmd/config/config.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/kmd/config/errors.go b/daemon/kmd/config/errors.go index 75800791b5..510725b099 100644 --- a/daemon/kmd/config/errors.go +++ b/daemon/kmd/config/errors.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/kmd/kmd.go b/daemon/kmd/kmd.go index b46fb6beb7..e0890c09b1 100644 --- a/daemon/kmd/kmd.go +++ b/daemon/kmd/kmd.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/kmd/lib/kmdapi/common.go b/daemon/kmd/lib/kmdapi/common.go index 58aa755c0d..d3b05a115d 100644 --- a/daemon/kmd/lib/kmdapi/common.go +++ b/daemon/kmd/lib/kmdapi/common.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/kmd/lib/kmdapi/requests.go b/daemon/kmd/lib/kmdapi/requests.go index 860d3c4f57..c2d323c78c 100644 --- a/daemon/kmd/lib/kmdapi/requests.go +++ b/daemon/kmd/lib/kmdapi/requests.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/kmd/lib/kmdapi/responses.go b/daemon/kmd/lib/kmdapi/responses.go index f404e843ef..4d233e9cbd 100644 --- a/daemon/kmd/lib/kmdapi/responses.go +++ b/daemon/kmd/lib/kmdapi/responses.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/kmd/server/errors.go b/daemon/kmd/server/errors.go index 86396e4891..9aac3feeb7 100644 --- a/daemon/kmd/server/errors.go +++ b/daemon/kmd/server/errors.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/kmd/server/server.go b/daemon/kmd/server/server.go index b36c2859c3..0210791948 100644 --- a/daemon/kmd/server/server.go +++ b/daemon/kmd/server/server.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/kmd/session/auth.go b/daemon/kmd/session/auth.go index 3ea9d990ff..ecf2db88c4 100644 --- a/daemon/kmd/session/auth.go +++ b/daemon/kmd/session/auth.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/kmd/session/session.go b/daemon/kmd/session/session.go index f1a8b0a52d..6aac936d9a 100644 --- a/daemon/kmd/session/session.go +++ b/daemon/kmd/session/session.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/kmd/wallet/driver/driver.go b/daemon/kmd/wallet/driver/driver.go index f549d128d8..9f1bb0c7fa 100644 --- a/daemon/kmd/wallet/driver/driver.go +++ b/daemon/kmd/wallet/driver/driver.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/kmd/wallet/driver/ledger.go b/daemon/kmd/wallet/driver/ledger.go index 1d64a6ab16..e26abe11d5 100644 --- a/daemon/kmd/wallet/driver/ledger.go +++ b/daemon/kmd/wallet/driver/ledger.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/kmd/wallet/driver/ledger_errors.go b/daemon/kmd/wallet/driver/ledger_errors.go index 420859c833..0fd8811083 100644 --- a/daemon/kmd/wallet/driver/ledger_errors.go +++ b/daemon/kmd/wallet/driver/ledger_errors.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/kmd/wallet/driver/ledger_hid.go b/daemon/kmd/wallet/driver/ledger_hid.go index 28ba4ce5c0..36140f9189 100644 --- a/daemon/kmd/wallet/driver/ledger_hid.go +++ b/daemon/kmd/wallet/driver/ledger_hid.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/kmd/wallet/driver/sqlite.go b/daemon/kmd/wallet/driver/sqlite.go index 8ad659c282..46992734db 100644 --- a/daemon/kmd/wallet/driver/sqlite.go +++ b/daemon/kmd/wallet/driver/sqlite.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/kmd/wallet/driver/sqlite_crypto.go b/daemon/kmd/wallet/driver/sqlite_crypto.go index 5cca480c90..2749f26838 100644 --- a/daemon/kmd/wallet/driver/sqlite_crypto.go +++ b/daemon/kmd/wallet/driver/sqlite_crypto.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/kmd/wallet/driver/sqlite_errors.go b/daemon/kmd/wallet/driver/sqlite_errors.go index bcfb00fda4..39e122a4e7 100644 --- a/daemon/kmd/wallet/driver/sqlite_errors.go +++ b/daemon/kmd/wallet/driver/sqlite_errors.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/kmd/wallet/driver/util.go b/daemon/kmd/wallet/driver/util.go index 2701242e32..7319ae444d 100644 --- a/daemon/kmd/wallet/driver/util.go +++ b/daemon/kmd/wallet/driver/util.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/kmd/wallet/wallet.go b/daemon/kmd/wallet/wallet.go index d83770f5dd..d4b6b5b1a6 100644 --- a/daemon/kmd/wallet/wallet.go +++ b/daemon/kmd/wallet/wallet.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/account/account.go b/data/account/account.go index 8e7bb53372..9c0d978c8f 100644 --- a/data/account/account.go +++ b/data/account/account.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/account/partInstall.go b/data/account/partInstall.go index 2c943935f9..0b10872785 100644 --- a/data/account/partInstall.go +++ b/data/account/partInstall.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/account/participation.go b/data/account/participation.go index 3209fd7ea0..0e1d4564a4 100644 --- a/data/account/participation.go +++ b/data/account/participation.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/account/participationRegistry.go b/data/account/participationRegistry.go index c8892513e6..1381ddbafd 100644 --- a/data/account/participationRegistry.go +++ b/data/account/participationRegistry.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/account/participationRegistryBench_test.go b/data/account/participationRegistryBench_test.go index 378950b775..4c6f1098cd 100644 --- a/data/account/participationRegistryBench_test.go +++ b/data/account/participationRegistryBench_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/account/participationRegistry_test.go b/data/account/participationRegistry_test.go index 286edf117b..ef77283ce7 100644 --- a/data/account/participationRegistry_test.go +++ b/data/account/participationRegistry_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -738,7 +738,7 @@ func TestParticipion_Blobs(t *testing.T) { registry, dbfile := getRegistry(t) defer registryCloseTest(t, registry, dbfile) - access, err := db.MakeAccessor("writetest_root", false, true) + access, err := db.MakeAccessor(t.Name()+"_writetest_root", false, true) if err != nil { panic(err) } @@ -746,7 +746,7 @@ func TestParticipion_Blobs(t *testing.T) { access.Close() a.NoError(err) - access, err = db.MakeAccessor("writetest", false, true) + access, err = db.MakeAccessor(t.Name()+"_writetest", false, true) if err != nil { panic(err) } @@ -781,7 +781,7 @@ func TestParticipion_EmptyBlobs(t *testing.T) { registry, dbfile := getRegistry(t) defer registryCloseTest(t, registry, dbfile) - access, err := db.MakeAccessor("writetest_root", false, true) + access, err := db.MakeAccessor(t.Name()+"_writetest_root", false, true) if err != nil { panic(err) } @@ -789,7 +789,7 @@ func TestParticipion_EmptyBlobs(t *testing.T) { access.Close() a.NoError(err) - access, err = db.MakeAccessor("writetest", false, true) + access, err = db.MakeAccessor(t.Name()+"_writetest", false, true) if err != nil { panic(err) } @@ -963,7 +963,7 @@ func TestGetRoundSecretsWithNilStateProofVerifier(t *testing.T) { registry, dbfile := getRegistry(t) defer registryCloseTest(t, registry, dbfile) - access, err := db.MakeAccessor("stateprooftest", false, true) + access, err := db.MakeAccessor(t.Name()+"_stateprooftest", false, true) if err != nil { panic(err) } @@ -1012,7 +1012,7 @@ func TestAddingSecretTwice(t *testing.T) { registry, dbfile := getRegistry(t) defer registryCloseTest(t, registry, dbfile) - access, err := db.MakeAccessor("stateprooftest", false, true) + access, err := db.MakeAccessor(t.Name()+"_stateprooftest", false, true) if err != nil { panic(err) } @@ -1050,7 +1050,7 @@ func TestGetRoundSecretsWithoutStateProof(t *testing.T) { registry, dbfile := getRegistry(t) defer registryCloseTest(t, registry, dbfile) - access, err := db.MakeAccessor("stateprooftest", false, true) + access, err := db.MakeAccessor(t.Name()+"_stateprooftest", false, true) if err != nil { panic(err) } @@ -1177,7 +1177,7 @@ func TestFlushResetsLastError(t *testing.T) { registry, dbfile := getRegistry(t) defer registryCloseTest(t, registry, dbfile) - access, err := db.MakeAccessor("stateprooftest", false, true) + access, err := db.MakeAccessor(t.Name()+"_stateprooftest", false, true) a.NoError(err) root, err := GenerateRoot(access) diff --git a/data/account/participation_test.go b/data/account/participation_test.go index 9a48bb7a05..b9a417a8fd 100644 --- a/data/account/participation_test.go +++ b/data/account/participation_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -531,7 +531,7 @@ func TestFillDBWithParticipationKeys(t *testing.T) { a.NoError(err) } -func TestKeyregValidityPeriod(t *testing.T) { +func TestKeyregValidityPeriod(t *testing.T) { //nolint:paralleltest // Not parallel because it modifies config.Consensus partitiontest.PartitionTest(t) a := require.New(t) diff --git a/data/account/registeryDbOps.go b/data/account/registeryDbOps.go index f83055d3de..e6722e5eb5 100644 --- a/data/account/registeryDbOps.go +++ b/data/account/registeryDbOps.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/account/rootInstall.go b/data/account/rootInstall.go index 4bf9a73e29..50f1c6e972 100644 --- a/data/account/rootInstall.go +++ b/data/account/rootInstall.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/accountManager.go b/data/accountManager.go index 39998a09d1..222a45944a 100644 --- a/data/accountManager.go +++ b/data/accountManager.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/accountManager_test.go b/data/accountManager_test.go index 1fcfe56bf7..0be2ec1399 100644 --- a/data/accountManager_test.go +++ b/data/accountManager_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -43,6 +43,11 @@ import ( func TestAccountManagerKeys(t *testing.T) { partitiontest.PartitionTest(t) + if testing.Short() { + t.Log("this is a long test and skipping for -short") + return + } + registry := &mocks.MockParticipationRegistry{} testAccountManagerKeys(t, registry, false) } @@ -85,6 +90,11 @@ func registryCloseTest(t testing.TB, registry account.ParticipationRegistry, dbf func TestAccountManagerKeysRegistry(t *testing.T) { partitiontest.PartitionTest(t) + if testing.Short() { + t.Log("this is a long test and skipping for -short") + return + } + registry, dbName := getRegistryImpl(t, false, true) defer registryCloseTest(t, registry, dbName) testAccountManagerKeys(t, registry, true) @@ -123,6 +133,7 @@ func testAccountManagerKeys(t *testing.T, registry account.ParticipationRegistry accessor, err := db.MakeErasableAccessor(partFilename) require.NoError(t, err) + defer accessor.Close() accessor.SetLogger(log) part, err := account.FillDBWithParticipationKeys(accessor, root.Address(), 0, 100, 10000) diff --git a/data/basics/address.go b/data/basics/address.go index 412b7bf75b..6e2249f4fb 100644 --- a/data/basics/address.go +++ b/data/basics/address.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/basics/address_test.go b/data/basics/address_test.go index eac7f09e5d..264b90a565 100644 --- a/data/basics/address_test.go +++ b/data/basics/address_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/basics/fields_test.go b/data/basics/fields_test.go index c3cf25284b..4baacdb167 100644 --- a/data/basics/fields_test.go +++ b/data/basics/fields_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/basics/overflow.go b/data/basics/overflow.go index 5296fd8849..c52f995723 100644 --- a/data/basics/overflow.go +++ b/data/basics/overflow.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/basics/sort.go b/data/basics/sort.go index 6841a67885..0d3c75d9d3 100644 --- a/data/basics/sort.go +++ b/data/basics/sort.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/basics/stateProofParticipant.go b/data/basics/stateProofParticipant.go index 5dcc3ad85c..a2c4fc490a 100644 --- a/data/basics/stateProofParticipant.go +++ b/data/basics/stateProofParticipant.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/basics/teal.go b/data/basics/teal.go index 1aaac047fb..9f247f2f36 100644 --- a/data/basics/teal.go +++ b/data/basics/teal.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/basics/teal_test.go b/data/basics/teal_test.go index 9090379d67..6703faeec3 100644 --- a/data/basics/teal_test.go +++ b/data/basics/teal_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/logging/telemetryspec/operation.go b/data/basics/testing/userBalance.go similarity index 60% rename from logging/telemetryspec/operation.go rename to data/basics/testing/userBalance.go index 037e8fca37..13efdf5d58 100644 --- a/logging/telemetryspec/operation.go +++ b/data/basics/testing/userBalance.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -14,10 +14,18 @@ // You should have received a copy of the GNU Affero General Public License // along with go-algorand. If not, see . -package telemetryspec +package testing -// Telemetry operations +import ( + "github.com/algorand/go-algorand/data/basics" +) -// Operation is the type used to identify strings used for telemetry operation identifiers. -// We want these to be stable and easy to find / document so we can create queries against them. -type Operation string +// MakeAccountData returns a AccountData with non-empty voting fields for online accounts +func MakeAccountData(status basics.Status, algos basics.MicroAlgos) basics.AccountData { + ad := basics.AccountData{Status: status, MicroAlgos: algos} + if status == basics.Online { + ad.VoteFirstValid = 1 + ad.VoteLastValid = 100_000 + } + return ad +} diff --git a/data/basics/units.go b/data/basics/units.go index c96b082333..de23f533aa 100644 --- a/data/basics/units.go +++ b/data/basics/units.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/basics/units_test.go b/data/basics/units_test.go index 5e97f4ca59..b5c698e46b 100644 --- a/data/basics/units_test.go +++ b/data/basics/units_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/basics/userBalance.go b/data/basics/userBalance.go index 06d7c4b6b8..f10c326354 100644 --- a/data/basics/userBalance.go +++ b/data/basics/userBalance.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -414,11 +414,6 @@ func (app AppIndex) Address() Address { return Address(crypto.HashObj(app)) } -// MakeAccountData returns a UserToken -func MakeAccountData(status Status, algos MicroAlgos) AccountData { - return AccountData{Status: status, MicroAlgos: algos} -} - // Money returns the amount of MicroAlgos associated with the user's account func (u AccountData) Money(proto config.ConsensusParams, rewardsLevel uint64) (money MicroAlgos, rewards MicroAlgos) { e := u.WithUpdatedRewards(proto, rewardsLevel) diff --git a/data/basics/userBalance_test.go b/data/basics/userBalance_test.go index 347dadfe69..912c75aed8 100644 --- a/data/basics/userBalance_test.go +++ b/data/basics/userBalance_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/bookkeeping/block.go b/data/bookkeeping/block.go index 702344235a..344148afa5 100644 --- a/data/bookkeeping/block.go +++ b/data/bookkeeping/block.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/bookkeeping/block_test.go b/data/bookkeeping/block_test.go index 5330c85cd7..2b4fa8c81f 100644 --- a/data/bookkeeping/block_test.go +++ b/data/bookkeeping/block_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -206,7 +206,7 @@ func TestMakeBlockUpgrades(t *testing.T) { require.Equal(t, bd2.NextProtocolSwitchOn-bd2.NextProtocolVoteBefore, basics.Round(5)) } -func TestBlockUnsupported(t *testing.T) { +func TestBlockUnsupported(t *testing.T) { //nolint:paralleltest // Not parallel because it modifies config.Consensus partitiontest.PartitionTest(t) var b Block diff --git a/data/bookkeeping/encoding_test.go b/data/bookkeeping/encoding_test.go index e892bb5029..95ccfbbca5 100644 --- a/data/bookkeeping/encoding_test.go +++ b/data/bookkeeping/encoding_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/bookkeeping/genesis.go b/data/bookkeeping/genesis.go index 7f01519e84..a81103f281 100644 --- a/data/bookkeeping/genesis.go +++ b/data/bookkeeping/genesis.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/bookkeeping/genesis_test.go b/data/bookkeeping/genesis_test.go index 9ca60bd5e1..5810e079dd 100644 --- a/data/bookkeeping/genesis_test.go +++ b/data/bookkeeping/genesis_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/bookkeeping/lightBlockHeader.go b/data/bookkeeping/lightBlockHeader.go index e9530faa18..ea283e0399 100644 --- a/data/bookkeeping/lightBlockHeader.go +++ b/data/bookkeeping/lightBlockHeader.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/bookkeeping/lightBlockHeader_test.go b/data/bookkeeping/lightBlockHeader_test.go index c9d39c0084..50d561c475 100644 --- a/data/bookkeeping/lightBlockHeader_test.go +++ b/data/bookkeeping/lightBlockHeader_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/bookkeeping/prettyprinting.go b/data/bookkeeping/prettyprinting.go index 7c8e28a0af..96c259a813 100644 --- a/data/bookkeeping/prettyprinting.go +++ b/data/bookkeeping/prettyprinting.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/bookkeeping/txn_merkle.go b/data/bookkeeping/txn_merkle.go index cfaf95ad8d..7ab84d0293 100644 --- a/data/bookkeeping/txn_merkle.go +++ b/data/bookkeeping/txn_merkle.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/bookkeeping/txn_merkle_test.go b/data/bookkeeping/txn_merkle_test.go index 4ead543dae..22df9025a4 100644 --- a/data/bookkeeping/txn_merkle_test.go +++ b/data/bookkeeping/txn_merkle_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/committee/committee.go b/data/committee/committee.go index d70eca646f..263aac999b 100644 --- a/data/committee/committee.go +++ b/data/committee/committee.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/committee/common_test.go b/data/committee/common_test.go index b0b77efd11..89e72fd791 100644 --- a/data/committee/common_test.go +++ b/data/committee/common_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/committee/credential.go b/data/committee/credential.go index c725cc9a8f..8debf212a9 100644 --- a/data/committee/credential.go +++ b/data/committee/credential.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/committee/credential_test.go b/data/committee/credential_test.go index fe13be8c16..0e3c3ea406 100644 --- a/data/committee/credential_test.go +++ b/data/committee/credential_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/committee/encoding_test.go b/data/committee/encoding_test.go index ae34120de9..be1a5ad9e5 100644 --- a/data/committee/encoding_test.go +++ b/data/committee/encoding_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/committee/sortition/sortition.go b/data/committee/sortition/sortition.go index 5b8300add6..05f3476f7f 100644 --- a/data/committee/sortition/sortition.go +++ b/data/committee/sortition/sortition.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/committee/sortition/sortition_test.go b/data/committee/sortition/sortition_test.go index 81bf893985..6d46857629 100644 --- a/data/committee/sortition/sortition_test.go +++ b/data/committee/sortition/sortition_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/common_test.go b/data/common_test.go index ab1004fc19..0ba58ad8d9 100644 --- a/data/common_test.go +++ b/data/common_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -26,6 +26,7 @@ import ( "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/account" "github.com/algorand/go-algorand/data/basics" + basics_testing "github.com/algorand/go-algorand/data/basics/testing" "github.com/algorand/go-algorand/data/bookkeeping" "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/logging" @@ -103,16 +104,16 @@ func testingenv(t testing.TB, numAccounts, numTxs int, offlineAccounts bool) (*L short := root.Address() if offlineAccounts && i > P/2 { - genesis[short] = basics.MakeAccountData(basics.Offline, startamt) + genesis[short] = basics_testing.MakeAccountData(basics.Offline, startamt) } else { - data := basics.MakeAccountData(basics.Online, startamt) + data := basics_testing.MakeAccountData(basics.Online, startamt) data.SelectionID = parts[i].VRFSecrets().PK data.VoteID = parts[i].VotingSecrets().OneTimeSignatureVerifier genesis[short] = data } } - genesis[poolAddr] = basics.MakeAccountData(basics.NotParticipating, basics.MicroAlgos{Raw: 100000 * uint64(proto.RewardsRateRefreshInterval)}) + genesis[poolAddr] = basics_testing.MakeAccountData(basics.NotParticipating, basics.MicroAlgos{Raw: 100000 * uint64(proto.RewardsRateRefreshInterval)}) bootstrap := bookkeeping.MakeGenesisBalances(genesis, poolAddr, sinkAddr) diff --git a/data/datatest/fabricateLedger.go b/data/datatest/fabricateLedger.go index 9c859bbeee..8bd1c3f251 100644 --- a/data/datatest/fabricateLedger.go +++ b/data/datatest/fabricateLedger.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/datatest/impls.go b/data/datatest/impls.go index 7a8f804eaf..960b12e374 100644 --- a/data/datatest/impls.go +++ b/data/datatest/impls.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/hashable/message.go b/data/hashable/message.go index ae0ae7806d..bfa032633f 100644 --- a/data/hashable/message.go +++ b/data/hashable/message.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/ledger.go b/data/ledger.go index 101da721af..04e5320614 100644 --- a/data/ledger.go +++ b/data/ledger.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -80,7 +80,7 @@ type roundSeed struct { func LoadLedger( log logging.Logger, dbFilenamePrefix string, memory bool, genesisProto protocol.ConsensusVersion, genesisBal bookkeeping.GenesisBalances, genesisID string, genesisHash crypto.Digest, - blockListeners []ledger.BlockListener, cfg config.Local, + blockListeners []ledgercore.BlockListener, cfg config.Local, ) (*Ledger, error) { if genesisBal.Balances == nil { genesisBal.Balances = make(map[basics.Address]basics.AccountData) diff --git a/data/ledger_test.go b/data/ledger_test.go index 9b74ddc854..540dfea1d3 100644 --- a/data/ledger_test.go +++ b/data/ledger_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -28,6 +28,7 @@ import ( "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/basics" + basics_testing "github.com/algorand/go-algorand/data/basics/testing" "github.com/algorand/go-algorand/data/bookkeeping" "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/ledger" @@ -78,12 +79,12 @@ func testGenerateInitState(tb testing.TB, proto protocol.ConsensusVersion) (gene if i%2 == 0 { accountStatus = basics.NotParticipating } - initAccounts[genaddrs[i]] = basics.MakeAccountData(accountStatus, basics.MicroAlgos{Raw: uint64((i + 100) * 100000)}) + initAccounts[genaddrs[i]] = basics_testing.MakeAccountData(accountStatus, basics.MicroAlgos{Raw: uint64((i + 100) * 100000)}) } initKeys[poolAddr] = poolSecret - initAccounts[poolAddr] = basics.MakeAccountData(basics.NotParticipating, basics.MicroAlgos{Raw: 1234567}) + initAccounts[poolAddr] = basics_testing.MakeAccountData(basics.NotParticipating, basics.MicroAlgos{Raw: 1234567}) initKeys[sinkAddr] = sinkSecret - initAccounts[sinkAddr] = basics.MakeAccountData(basics.NotParticipating, basics.MicroAlgos{Raw: 7654321}) + initAccounts[sinkAddr] = basics_testing.MakeAccountData(basics.NotParticipating, basics.MicroAlgos{Raw: 7654321}) incentivePoolBalanceAtGenesis := initAccounts[poolAddr].MicroAlgos initialRewardsPerRound := incentivePoolBalanceAtGenesis.Raw / uint64(params.RewardsRateRefreshInterval) @@ -326,6 +327,10 @@ func TestLedgerSeed(t *testing.T) { func TestConsensusVersion(t *testing.T) { partitiontest.PartitionTest(t) + if testing.Short() { + t.Log("this is a long test and skipping for -short") + return + } // find a consensus protocol that leads to ConsensusCurrentVersion var previousProtocol protocol.ConsensusVersion @@ -491,8 +496,8 @@ func TestLedgerErrorValidate(t *testing.T) { blk.BlockHeader.GenesisHash = crypto.Hash([]byte(t.Name())) accts := make(map[basics.Address]basics.AccountData) - accts[testPoolAddr] = basics.MakeAccountData(basics.NotParticipating, basics.MicroAlgos{Raw: 0}) - accts[testSinkAddr] = basics.MakeAccountData(basics.NotParticipating, basics.MicroAlgos{Raw: 0}) + accts[testPoolAddr] = basics_testing.MakeAccountData(basics.NotParticipating, basics.MicroAlgos{Raw: 0}) + accts[testSinkAddr] = basics_testing.MakeAccountData(basics.NotParticipating, basics.MicroAlgos{Raw: 0}) genesisInitState := ledgercore.InitState{ Accounts: accts, @@ -513,7 +518,7 @@ func TestLedgerErrorValidate(t *testing.T) { defer realLedger.Close() l := Ledger{Ledger: realLedger, log: log} - l.log.SetLevel(logging.Debug) + l.log.SetLevel(logging.Warn) require.NotNil(t, &l) totalsRound, _, err := realLedger.LatestTotals() diff --git a/data/pools/errors.go b/data/pools/errors.go index e465983fa9..819678b2ca 100644 --- a/data/pools/errors.go +++ b/data/pools/errors.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/pools/statusCache.go b/data/pools/statusCache.go index 3b25f7f679..0b8d967417 100644 --- a/data/pools/statusCache.go +++ b/data/pools/statusCache.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/pools/transactionPool.go b/data/pools/transactionPool.go index 44852c3459..bed4e17e68 100644 --- a/data/pools/transactionPool.go +++ b/data/pools/transactionPool.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/pools/transactionPool_test.go b/data/pools/transactionPool_test.go index be0546c3b1..02239d50fa 100644 --- a/data/pools/transactionPool_test.go +++ b/data/pools/transactionPool_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/stateproofmsg/message.go b/data/stateproofmsg/message.go index aea1954756..5f1c2e3433 100644 --- a/data/stateproofmsg/message.go +++ b/data/stateproofmsg/message.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/application.go b/data/transactions/application.go index 70fd774dfd..6ae70ed651 100644 --- a/data/transactions/application.go +++ b/data/transactions/application.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/application_test.go b/data/transactions/application_test.go index 64777185c7..24bff87a02 100644 --- a/data/transactions/application_test.go +++ b/data/transactions/application_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/asset.go b/data/transactions/asset.go index 9daafd5bc8..c15ec3db93 100644 --- a/data/transactions/asset.go +++ b/data/transactions/asset.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/common_test.go b/data/transactions/common_test.go index b600ce6cf5..0509241e60 100644 --- a/data/transactions/common_test.go +++ b/data/transactions/common_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/error.go b/data/transactions/error.go index 86ce011023..4ce693cf9c 100644 --- a/data/transactions/error.go +++ b/data/transactions/error.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/json_test.go b/data/transactions/json_test.go index 547224fb6c..1e5474e54d 100644 --- a/data/transactions/json_test.go +++ b/data/transactions/json_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -30,6 +30,7 @@ import ( "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/data/txntest" "github.com/algorand/go-algorand/protocol" + "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/require" ) @@ -45,6 +46,8 @@ func compact(data []byte) string { // TestJsonMarshal ensures that BoxRef names are b64 encoded, since they may not be characters. func TestJsonMarshal(t *testing.T) { + partitiontest.PartitionTest(t) + marshal := protocol.EncodeJSON(transactions.BoxRef{Index: 4, Name: []byte("joe")}) require.Equal(t, `{"i":4,"n":"am9l"}`, compact(marshal)) @@ -60,6 +63,7 @@ func TestJsonMarshal(t *testing.T) { // TestJsonUnmarshal ensures that BoxRef unmarshaling expects b64 names func TestJsonUnmarshal(t *testing.T) { + partitiontest.PartitionTest(t) var br transactions.BoxRef decode(t, `{"i":4,"n":"am9l"}`, &br) @@ -82,6 +86,8 @@ func TestJsonUnmarshal(t *testing.T) { // encoded. These things could change without breaking the protocol, should stay // the same for the sake of REST API compatibility. func TestTxnJson(t *testing.T) { + partitiontest.PartitionTest(t) + txn := txntest.Txn{ Sender: basics.Address{0x01, 0x02, 0x03}, } diff --git a/data/transactions/keyreg.go b/data/transactions/keyreg.go index 401617ef72..6e917ca115 100644 --- a/data/transactions/keyreg.go +++ b/data/transactions/keyreg.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/logic/TEAL_opcodes.md b/data/transactions/logic/TEAL_opcodes.md index cd2bd5842d..54a80db8f9 100644 --- a/data/transactions/logic/TEAL_opcodes.md +++ b/data/transactions/logic/TEAL_opcodes.md @@ -280,7 +280,7 @@ The notation J,K indicates that two uint64 values J and K are interpreted as a u ## bytecblock bytes ... -- Opcode: 0x26 {varuint count} [({varuint value length} bytes), ...] +- Opcode: 0x26 {varuint count} [({varuint length} bytes), ...] - Stack: ... → ... - prepare block of byte-array constants for use by bytec @@ -318,7 +318,7 @@ The notation J,K indicates that two uint64 values J and K are interpreted as a u ## arg n -- Opcode: 0x2c {uint8 arg index N} +- Opcode: 0x2c {uint8 arg index} - Stack: ... → ..., []byte - Nth LogicSig argument - Mode: Signature @@ -811,7 +811,7 @@ When A is a uint64, index 0 is the least significant bit. Setting bit 3 to 1 on ## json_ref r -- Opcode: 0x5f {uint8 return type} +- Opcode: 0x5f {uint8 return type index} - Stack: ..., A: []byte, B: []byte → ..., any - key B's value, of type R, from a [valid](jsonspec.md) utf-8 encoded json object A - **Cost**: 25 + 2 per 7 bytes of A @@ -1059,7 +1059,7 @@ pushint args are not added to the intcblock during assembly processes ## pushbytess bytes ... -- Opcode: 0x82 {varuint count} [({varuint value length} bytes), ...] +- Opcode: 0x82 {varuint count} [({varuint length} bytes), ...] - Stack: ... → ..., [N items] - push sequences of immediate byte arrays to stack (first byte array being deepest) - Availability: v8 @@ -1548,7 +1548,7 @@ For boxes that exceed 4,096 bytes, consider `box_create`, `box_extract`, and `bo ## block f -- Opcode: 0xd1 {uint8 block field} +- Opcode: 0xd1 {uint8 block field index} - Stack: ..., A: uint64 → ..., any - field F of block A. Fail unless A falls between txn.LastValid-1002 and txn.FirstValid (exclusive) - Availability: v7 diff --git a/data/transactions/logic/assembler.go b/data/transactions/logic/assembler.go index e25e806bef..45489918a7 100644 --- a/data/transactions/logic/assembler.go +++ b/data/transactions/logic/assembler.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -1622,7 +1622,7 @@ func isFullSpec(spec OpSpec) bool { } // mergeProtos allows us to support typetracking of pseudo-ops which are given an improper number of immediates -//by creating a new proto that is a combination of all the pseudo-op's possibilities +// by creating a new proto that is a combination of all the pseudo-op's possibilities func mergeProtos(specs map[int]OpSpec) (Proto, uint64, bool) { var args StackTypes var returns StackTypes @@ -1857,10 +1857,13 @@ func splitTokens(tokens []string) (current, rest []string) { // assemble reads text from an input and accumulates the program func (ops *OpStream) assemble(text string) error { - fin := strings.NewReader(text) if ops.Version > LogicVersion && ops.Version != assemblerNoVersion { return ops.errorf("Can not assemble version %d", ops.Version) } + if strings.TrimSpace(text) == "" { + return ops.errorf("Cannot assemble empty program text") + } + fin := strings.NewReader(text) scanner := bufio.NewScanner(fin) for scanner.Scan() { ops.sourceLine++ @@ -1904,7 +1907,7 @@ func (ops *OpStream) assemble(text string) error { if ok { ops.trace("%3d: %s\t", ops.sourceLine, opstring) ops.recordSourceLine() - if spec.Modes == modeApp { + if spec.Modes == ModeApp { ops.HasStatefulOps = true } args, returns := spec.Arg.Types, spec.Return.Types @@ -2411,8 +2414,19 @@ func (ops *OpStream) warnf(format string, a ...interface{}) error { return ops.warn(fmt.Errorf(format, a...)) } -// ReportProblems issues accumulated warnings and outputs errors to an io.Writer. -func (ops *OpStream) ReportProblems(fname string, writer io.Writer) { +// ReportMultipleErrors issues accumulated warnings and outputs errors to an io.Writer. +// In the case of exactly 1 error and no warnings, a slightly different format is provided +// to handle the cases when the original error is or isn't reported elsewhere. +// In the case of > 10 errors, only the first 10 errors will be reported. +func (ops *OpStream) ReportMultipleErrors(fname string, writer io.Writer) { + if len(ops.Errors) == 1 && len(ops.Warnings) == 0 { + prefix := "" + if fname != "" { + prefix = fmt.Sprintf("%s: ", fname) + } + fmt.Fprintf(writer, "%s1 error: %s\n", prefix, ops.Errors[0]) + return + } for i, e := range ops.Errors { if i > 9 { break @@ -2789,7 +2803,7 @@ func disassembleInstrumented(program []byte, labels map[int]string) (text string return } op := opsByOpcode[version][program[dis.pc]] - if op.Modes == modeApp { + if op.Modes == ModeApp { ds.hasStatefulOps = true } if op.Name == "" { diff --git a/data/transactions/logic/assembler_test.go b/data/transactions/logic/assembler_test.go index d53b6fc16d..cc5cd97a44 100644 --- a/data/transactions/logic/assembler_test.go +++ b/data/transactions/logic/assembler_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -19,6 +19,7 @@ package logic import ( "bytes" "encoding/hex" + "errors" "fmt" "regexp" "strings" @@ -1457,7 +1458,7 @@ done:` require.Equal(t, expectedProgBytes, ops.Program) } -func TestMultipleErrors(t *testing.T) { +func TestSeveralErrors(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() @@ -1623,10 +1624,12 @@ func TestAssembleDisassembleCycle(t *testing.T) { // catch any suprises. require.LessOrEqual(t, LogicVersion, len(nonsense)) // Allow nonsense for future versions for v, source := range nonsense { + v, source := v, source if v > LogicVersion { continue // We allow them to be set, but can't test assembly beyond LogicVersion } t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) { + t.Parallel() ops := testProg(t, source, v) t2, err := Disassemble(ops.Program) require.NoError(t, err) @@ -3051,3 +3054,108 @@ func TestAssemblePushConsts(t *testing.T) { source = `pushbytess "x" "y"; +` testProg(t, source, AssemblerMaxVersion, Expect{1, "+ arg 1 wanted type uint64 got []byte"}) } + +func TestAssembleEmpty(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + emptyExpect := Expect{0, "Cannot assemble empty program text"} + emptyPrograms := []string{ + "", + " ", + " \n\t\t\t\n\n ", + " \n \t \t \t \n \n \n\n", + } + + nonEmpty := " \n \t \t \t int 1 \n \n \t \t \n\n" + + for version := uint64(1); version <= AssemblerMaxVersion; version++ { + for _, prog := range emptyPrograms { + testProg(t, prog, version, emptyExpect) + } + testProg(t, nonEmpty, version) + } +} + +func TestReportMultipleErrors(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + assertWithMsg := func(t *testing.T, expectedOutput string, b bytes.Buffer) { + if b.String() != expectedOutput { + t.Errorf("Unexpected output: got %q, want %q", b.String(), expectedOutput) + } + } + + ops := &OpStream{ + Errors: []lineError{ + {Line: 1, Err: errors.New("error 1")}, + {Err: errors.New("error 2")}, + {Line: 3, Err: errors.New("error 3")}, + }, + Warnings: []error{ + errors.New("warning 1"), + errors.New("warning 2"), + }, + } + + // Test the case where fname is not empty + var b bytes.Buffer + ops.ReportMultipleErrors("test.txt", &b) + expected := `test.txt: 1: error 1 +test.txt: 0: error 2 +test.txt: 3: error 3 +test.txt: warning 1 +test.txt: warning 2 +` + assertWithMsg(t, expected, b) + + // Test the case where fname is empty + b.Reset() + ops.ReportMultipleErrors("", &b) + expected = `1: error 1 +0: error 2 +3: error 3 +warning 1 +warning 2 +` + assertWithMsg(t, expected, b) + + // no errors or warnings at all + ops = &OpStream{} + b.Reset() + ops.ReportMultipleErrors("blah blah", &b) + expected = "" + assertWithMsg(t, expected, b) + + // more than 10 errors: + file := "great-file.go" + les := []lineError{} + expectedStrs := []string{} + for i := 1; i <= 11; i++ { + errS := fmt.Errorf("error %d", i) + les = append(les, lineError{i, errS}) + if i <= 10 { + expectedStrs = append(expectedStrs, fmt.Sprintf("%s: %d: %s", file, i, errS)) + } + } + expected = strings.Join(expectedStrs, "\n") + "\n" + ops = &OpStream{Errors: les} + b.Reset() + ops.ReportMultipleErrors(file, &b) + assertWithMsg(t, expected, b) + + // exactly 1 error + filename + ops = &OpStream{Errors: []lineError{{42, errors.New("super annoying error")}}} + b.Reset() + ops.ReportMultipleErrors("galaxy.py", &b) + expected = "galaxy.py: 1 error: 42: super annoying error\n" + assertWithMsg(t, expected, b) + + // exactly 1 error w/o filename + ops = &OpStream{Errors: []lineError{{42, errors.New("super annoying error")}}} + b.Reset() + ops.ReportMultipleErrors("", &b) + expected = "1 error: 42: super annoying error\n" + assertWithMsg(t, expected, b) +} diff --git a/data/transactions/logic/backwardCompat_test.go b/data/transactions/logic/backwardCompat_test.go index 04fff18282..df6bd68210 100644 --- a/data/transactions/logic/backwardCompat_test.go +++ b/data/transactions/logic/backwardCompat_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -467,19 +467,24 @@ func TestBackwardCompatAssemble(t *testing.T) { source := "int 1; int 1; bnz done; done:" t.Run("v=default", func(t *testing.T) { + t.Parallel() testProg(t, source, assemblerNoVersion, Expect{1, "label \"done\" is too far away"}) }) t.Run("v=default", func(t *testing.T) { + t.Parallel() testProg(t, source, 0, Expect{1, "label \"done\" is too far away"}) }) t.Run("v=default", func(t *testing.T) { + t.Parallel() testProg(t, source, 1, Expect{1, "label \"done\" is too far away"}) }) for v := uint64(2); v <= AssemblerMaxVersion; v++ { + v := v t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) { + t.Parallel() testLogic(t, source, v, defaultEvalParams()) }) } @@ -487,6 +492,7 @@ func TestBackwardCompatAssemble(t *testing.T) { func TestExplicitConstants(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() require.Equal(t, 4096, maxStringSize, "constant changed, make it version dependent") require.Equal(t, 64, maxByteMathSize, "constant changed, move it version dependent") diff --git a/data/transactions/logic/blackbox_test.go b/data/transactions/logic/blackbox_test.go index e26f84a8f5..79029f2c38 100644 --- a/data/transactions/logic/blackbox_test.go +++ b/data/transactions/logic/blackbox_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -34,6 +34,7 @@ import ( func TestNewAppEvalParams(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() params := []config.ConsensusParams{ {Application: true, MaxAppProgramCost: 700}, @@ -78,8 +79,11 @@ func TestNewAppEvalParams(t *testing.T) { } for i, param := range params { + param := param for j, testCase := range cases { + i, j, param, testCase := i, j, param, testCase t.Run(fmt.Sprintf("i=%d,j=%d", i, j), func(t *testing.T) { + t.Parallel() ep := logic.NewEvalParams(testCase.group, ¶m, nil) require.NotNil(t, ep) require.Equal(t, ep.TxnGroup, testCase.group) diff --git a/data/transactions/logic/box.go b/data/transactions/logic/box.go index 6f2e9ccd9a..ebc1c25f2e 100644 --- a/data/transactions/logic/box.go +++ b/data/transactions/logic/box.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -17,7 +17,6 @@ package logic import ( - "encoding/binary" "fmt" "github.com/algorand/go-algorand/data/basics" @@ -283,36 +282,3 @@ func opBoxPut(cx *EvalContext) error { appAddr := cx.getApplicationAddress(cx.appID) return cx.Ledger.NewBox(cx.appID, name, value, appAddr) } - -const boxPrefix = "bx:" -const boxPrefixLength = len(boxPrefix) -const boxNameIndex = boxPrefixLength + 8 // len("bx:") + 8 (appIdx, big-endian) - -// MakeBoxKey creates the key that a box named `name` under app `appIdx` should use. -func MakeBoxKey(appIdx basics.AppIndex, name string) string { - /* This format is chosen so that a simple indexing scheme on the key would - allow for quick lookups of all the boxes of a certain app, or even all - the boxes of a certain app with a certain prefix. - - The "bx:" prefix is so that the kvstore might be usable for things - besides boxes. - */ - key := make([]byte, boxNameIndex+len(name)) - copy(key, boxPrefix) - binary.BigEndian.PutUint64(key[boxPrefixLength:], uint64(appIdx)) - copy(key[boxNameIndex:], name) - return string(key) -} - -// SplitBoxKey extracts an appid and box name from a string that was created by MakeBoxKey() -func SplitBoxKey(key string) (basics.AppIndex, string, error) { - if len(key) < boxNameIndex { - return 0, "", fmt.Errorf("SplitBoxKey() cannot extract AppIndex as key (%s) too short (length=%d)", key, len(key)) - } - if key[:boxPrefixLength] != boxPrefix { - return 0, "", fmt.Errorf("SplitBoxKey() illegal app box prefix in key (%s). Expected prefix '%s'", key, boxPrefix) - } - keyBytes := []byte(key) - app := basics.AppIndex(binary.BigEndian.Uint64(keyBytes[boxPrefixLength:boxNameIndex])) - return app, key[boxNameIndex:], nil -} diff --git a/data/transactions/logic/box_test.go b/data/transactions/logic/box_test.go index 515f0ad696..77c3adf1b2 100644 --- a/data/transactions/logic/box_test.go +++ b/data/transactions/logic/box_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -27,17 +27,19 @@ import ( "github.com/algorand/go-algorand/data/txntest" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" - "github.com/stretchr/testify/require" ) func TestBoxNewDel(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - ep, txn, ledger := logic.MakeSampleEnv() - for _, size := range []int{24, 0} { + size := size t.Run(fmt.Sprintf("box size=%d", size), func(t *testing.T) { + t.Parallel() + + ep, txn, ledger := logic.MakeSampleEnv() + createSelf := fmt.Sprintf(`byte "self"; int %d; box_create;`, size) createOther := fmt.Sprintf(`byte "other"; int %d; box_create;`, size) @@ -218,7 +220,9 @@ func TestBoxUnavailableWithClearState(t *testing.T) { } for name, program := range tests { + name, program := name, program t.Run(name, func(t *testing.T) { + t.Parallel() ep, _, l := logic.MakeSampleEnv() l.NewApp(basics.Address{}, 888, basics.AppParams{}) ep.TxnGroup[0].Txn.OnCompletion = transactions.ClearStateOC @@ -521,18 +525,23 @@ func TestEarlyPanics(t *testing.T) { "box_replace": `byte "%s"; int 0; byte "new"; box_replace`, } - ep, _, l := logic.MakeSampleEnv() - l.NewApp(basics.Address{}, 888, basics.AppParams{}) - for name, program := range tests { + name, program := name, program t.Run(name+"/zero", func(t *testing.T) { + t.Parallel() + ep, _, l := logic.MakeSampleEnv() + l.NewApp(basics.Address{}, 888, basics.AppParams{}) logic.TestApp(t, fmt.Sprintf(program, ""), ep, "zero length") }) } big := strings.Repeat("x", 65) for name, program := range tests { + name, program := name, program t.Run(name+"/long", func(t *testing.T) { + t.Parallel() + ep, _, l := logic.MakeSampleEnv() + l.NewApp(basics.Address{}, 888, basics.AppParams{}) logic.TestApp(t, fmt.Sprintf(program, big), ep, "name too long") }) } @@ -558,45 +567,3 @@ func TestBoxTotals(t *testing.T) { logic.TestApp(t, `int 888; app_params_get AppAddress; assert; acct_params_get AcctTotalBoxBytes; pop; int 35; ==`, ep) } - -func TestMakeBoxKey(t *testing.T) { - partitiontest.PartitionTest(t) - t.Parallel() - - type testCase struct { - description string - name string - app basics.AppIndex - key string - err string - } - - pp := func(tc testCase) string { - return fmt.Sprintf("<<<%s>>> (name, app) = (%#v, %d) --should--> key = %#v (err = [%s])", tc.description, tc.name, tc.app, tc.key, tc.err) - } - - var testCases = []testCase{ - // COPACETIC: - {"zero appid", "stranger", 0, "bx:\x00\x00\x00\x00\x00\x00\x00\x00stranger", ""}, - {"typical", "348-8uj", 131231, "bx:\x00\x00\x00\x00\x00\x02\x00\x9f348-8uj", ""}, - {"empty box name", "", 42, "bx:\x00\x00\x00\x00\x00\x00\x00*", ""}, - {"random byteslice", "{\xbb\x04\a\xd1\xe2\xc6I\x81{", 13475904583033571713, "bx:\xbb\x04\a\xd1\xe2\xc6I\x81{\xbb\x04\a\xd1\xe2\xc6I\x81{", ""}, - - // ERRORS: - {"too short", "", 0, "stranger", "SplitBoxKey() cannot extract AppIndex as key (stranger) too short (length=8)"}, - {"wrong prefix", "", 0, "strangersINTHEdark", "SplitBoxKey() illegal app box prefix in key (strangersINTHEdark). Expected prefix 'bx:'"}, - } - - for _, tc := range testCases { - app, name, err := logic.SplitBoxKey(tc.key) - - if tc.err == "" { - key := logic.MakeBoxKey(tc.app, tc.name) - require.Equal(t, tc.app, app, pp(tc)) - require.Equal(t, tc.name, name, pp(tc)) - require.Equal(t, tc.key, key, pp(tc)) - } else { - require.EqualError(t, err, tc.err, pp(tc)) - } - } -} diff --git a/data/transactions/logic/debugger.go b/data/transactions/logic/debugger.go index a2a0453c48..0a8e013da8 100644 --- a/data/transactions/logic/debugger.go +++ b/data/transactions/logic/debugger.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -33,19 +33,80 @@ import ( "github.com/algorand/go-algorand/protocol" ) -// DebuggerHook functions are called by eval function during TEAL program execution -// if provided -type DebuggerHook interface { +// Debugger is an interface that supports the first version of AVM debuggers. +// It consists of a set of functions called by eval function during AVM program execution. +// +// Deprecated: This interface does not support non-app call or inner transactions. Use EvalTracer +// instead. +type Debugger interface { // Register is fired on program creation - Register(state *DebugState) error + Register(state *DebugState) // Update is fired on every step - Update(state *DebugState) error + Update(state *DebugState) // Complete is called when the program exits - Complete(state *DebugState) error + Complete(state *DebugState) +} + +type debuggerEvalTracerAdaptor struct { + NullEvalTracer + + debugger Debugger + txnDepth int + debugState *DebugState +} + +// MakeEvalTracerDebuggerAdaptor creates an adaptor that externally adheres to the EvalTracer +// interface, but drives a Debugger interface +// +// Warning: The output EvalTracer is specifically designed to be invoked under the exact same +// circumstances that the previous Debugger interface was invoked. This means that it will only work +// properly if you attach it directly to a logic.EvalParams and execute a program. If you attempt to +// run this EvalTracer under a different entry point (such as by attaching it to a BlockEvaluator), +// it WILL NOT work properly. +func MakeEvalTracerDebuggerAdaptor(debugger Debugger) EvalTracer { + return &debuggerEvalTracerAdaptor{debugger: debugger} +} + +// BeforeTxnGroup updates inner txn depth +func (a *debuggerEvalTracerAdaptor) BeforeTxnGroup(ep *EvalParams) { + a.txnDepth++ +} + +// AfterTxnGroup updates inner txn depth +func (a *debuggerEvalTracerAdaptor) AfterTxnGroup(ep *EvalParams) { + a.txnDepth-- +} + +// BeforeProgram invokes the debugger's Register hook +func (a *debuggerEvalTracerAdaptor) BeforeProgram(cx *EvalContext) { + if a.txnDepth > 0 { + // only report updates for top-level transactions, for backwards compatibility + return + } + a.debugState = makeDebugState(cx) + a.debugger.Register(a.refreshDebugState(cx, nil)) +} + +// BeforeOpcode invokes the debugger's Update hook +func (a *debuggerEvalTracerAdaptor) BeforeOpcode(cx *EvalContext) { + if a.txnDepth > 0 { + // only report updates for top-level transactions, for backwards compatibility + return + } + a.debugger.Update(a.refreshDebugState(cx, nil)) +} + +// AfterProgram invokes the debugger's Complete hook +func (a *debuggerEvalTracerAdaptor) AfterProgram(cx *EvalContext, evalError error) { + if a.txnDepth > 0 { + // only report updates for top-level transactions, for backwards compatibility + return + } + a.debugger.Complete(a.refreshDebugState(cx, evalError)) } -// WebDebuggerHook represents a connection to tealdbg -type WebDebuggerHook struct { +// WebDebugger represents a connection to tealdbg +type WebDebugger struct { URL string } @@ -115,7 +176,7 @@ func makeDebugState(cx *EvalContext) *DebugState { globals := make([]basics.TealValue, len(globalFieldSpecs)) for _, fs := range globalFieldSpecs { // Don't try to grab app only fields when evaluating a signature - if (cx.runModeFlags&modeSig) != 0 && fs.mode == modeApp { + if (cx.runModeFlags&ModeSig) != 0 && fs.mode == ModeApp { continue } sv, err := cx.globalFieldToValue(fs) @@ -126,7 +187,7 @@ func makeDebugState(cx *EvalContext) *DebugState { } ds.Globals = globals - if (cx.runModeFlags & modeApp) != 0 { + if (cx.runModeFlags & ModeApp) != 0 { ds.EvalDelta = cx.txn.EvalDelta } @@ -221,8 +282,8 @@ func (d *DebugState) parseCallstack(callstack []frame) []CallFrame { return callFrames } -func (cx *EvalContext) refreshDebugState(evalError error) *DebugState { - ds := cx.debugState +func (a *debuggerEvalTracerAdaptor) refreshDebugState(cx *EvalContext, evalError error) *DebugState { + ds := a.debugState // Update pc, line, error, stack, scratch space, callstack, // and opcode budget @@ -247,14 +308,14 @@ func (cx *EvalContext) refreshDebugState(evalError error) *DebugState { ds.OpcodeBudget = cx.remainingBudget() ds.CallStack = ds.parseCallstack(cx.callstack) - if (cx.runModeFlags & modeApp) != 0 { + if (cx.runModeFlags & ModeApp) != 0 { ds.EvalDelta = cx.txn.EvalDelta } return ds } -func (dbg *WebDebuggerHook) postState(state *DebugState, endpoint string) error { +func (dbg *WebDebugger) postState(state *DebugState, endpoint string) error { var body bytes.Buffer enc := protocol.NewJSONEncoder(&body) err := enc.Encode(state) @@ -285,7 +346,7 @@ func (dbg *WebDebuggerHook) postState(state *DebugState, endpoint string) error } // Register sends state to remote debugger -func (dbg *WebDebuggerHook) Register(state *DebugState) error { +func (dbg *WebDebugger) Register(state *DebugState) { u, err := url.Parse(dbg.URL) if err != nil { logging.Base().Errorf("Failed to parse url: %s", err.Error()) @@ -295,15 +356,24 @@ func (dbg *WebDebuggerHook) Register(state *DebugState) error { if h != "localhost" && h != "127.0.0.1" && h != "::1" { logging.Base().Warnf("Unsecured communication with non-local debugger: %s", h) } - return dbg.postState(state, "exec/register") + err = dbg.postState(state, "exec/register") + if err != nil { + logging.Base().Errorf("Failed to post state to exec/register: %s", err.Error()) + } } // Update sends state to remote debugger -func (dbg *WebDebuggerHook) Update(state *DebugState) error { - return dbg.postState(state, "exec/update") +func (dbg *WebDebugger) Update(state *DebugState) { + err := dbg.postState(state, "exec/update") + if err != nil { + logging.Base().Errorf("Failed to post state to exec/update: %s", err.Error()) + } } // Complete sends state to remote debugger -func (dbg *WebDebuggerHook) Complete(state *DebugState) error { - return dbg.postState(state, "exec/complete") +func (dbg *WebDebugger) Complete(state *DebugState) { + err := dbg.postState(state, "exec/complete") + if err != nil { + logging.Base().Errorf("Failed to post state to exec/complete: %s", err.Error()) + } } diff --git a/data/transactions/logic/debugger_test.go b/data/transactions/logic/debugger_test.go index 2775e74e21..26bd940b79 100644 --- a/data/transactions/logic/debugger_test.go +++ b/data/transactions/logic/debugger_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -26,7 +26,7 @@ import ( "github.com/stretchr/testify/require" ) -var testProgram string = `intcblock 0 1 1 1 1 5 100 +const debuggerTestProgram string = `intcblock 0 1 1 1 1 5 100 bytecblock 0x414c474f 0x1337 0x2001 0xdeadbeef 0x70077007 bytec 0 sha256 @@ -63,7 +63,7 @@ bytec 4 && ` -func TestWebDebuggerManual(t *testing.T) { +func TestWebDebuggerManual(t *testing.T) { //nolint:paralleltest // Manual test partitiontest.PartitionTest(t) debugURL := os.Getenv("TEAL_DEBUGGER_URL") @@ -80,51 +80,84 @@ func TestWebDebuggerManual(t *testing.T) { tx.SelectionPK[:], tx.Note, } - ep.Debugger = &WebDebuggerHook{URL: debugURL} - testLogic(t, testProgram, AssemblerMaxVersion, ep) + ep.Tracer = MakeEvalTracerDebuggerAdaptor(&WebDebugger{URL: debugURL}) + testLogic(t, debuggerTestProgram, AssemblerMaxVersion, ep) } -type testDbgHook struct { +type testDebugger struct { register int update int complete int state *DebugState } -func (d *testDbgHook) Register(state *DebugState) error { +func (d *testDebugger) Register(state *DebugState) { d.register++ d.state = state - return nil } -func (d *testDbgHook) Update(state *DebugState) error { +func (d *testDebugger) Update(state *DebugState) { d.update++ d.state = state - return nil } -func (d *testDbgHook) Complete(state *DebugState) error { +func (d *testDebugger) Complete(state *DebugState) { d.complete++ d.state = state - return nil } -func TestDebuggerHook(t *testing.T) { +func TestDebuggerProgramEval(t *testing.T) { partitiontest.PartitionTest(t) - - testDbg := testDbgHook{} - ep := defaultEvalParams() - ep.Debugger = &testDbg - testLogic(t, testProgram, AssemblerMaxVersion, ep) - - require.Equal(t, 1, testDbg.register) - require.Equal(t, 1, testDbg.complete) - require.Greater(t, testDbg.update, 1) - require.Len(t, testDbg.state.Stack, 1) + t.Parallel() + + t.Run("logicsig", func(t *testing.T) { + t.Parallel() + testDbg := testDebugger{} + ep := defaultEvalParams() + ep.Tracer = MakeEvalTracerDebuggerAdaptor(&testDbg) + testLogic(t, debuggerTestProgram, AssemblerMaxVersion, ep) + + require.Equal(t, 1, testDbg.register) + require.Equal(t, 1, testDbg.complete) + require.Equal(t, 35, testDbg.update) + require.Len(t, testDbg.state.Stack, 1) + }) + + t.Run("simple app", func(t *testing.T) { + t.Parallel() + testDbg := testDebugger{} + ep := defaultEvalParams() + ep.Tracer = MakeEvalTracerDebuggerAdaptor(&testDbg) + testApp(t, debuggerTestProgram, ep) + + require.Equal(t, 1, testDbg.register) + require.Equal(t, 1, testDbg.complete) + require.Equal(t, 35, testDbg.update) + require.Len(t, testDbg.state.Stack, 1) + }) + + t.Run("app with inner txns", func(t *testing.T) { + t.Parallel() + testDbg := testDebugger{} + ep, tx, ledger := MakeSampleEnv() + + // Establish 888 as the app id, and fund it. + ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) + ledger.NewAccount(basics.AppIndex(888).Address(), 200000) + + ep.Tracer = MakeEvalTracerDebuggerAdaptor(&testDbg) + testApp(t, innerTxnTestProgram, ep) + + require.Equal(t, 1, testDbg.register) + require.Equal(t, 1, testDbg.complete) + require.Equal(t, 27, testDbg.update) + require.Len(t, testDbg.state.Stack, 1) + }) } func TestLineToPC(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() dState := DebugState{ Disassembly: "abc\ndef\nghi", @@ -162,6 +195,7 @@ func TestLineToPC(t *testing.T) { func TestValueDeltaToValueDelta(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() vDelta := basics.ValueDelta{ Action: basics.SetUintAction, @@ -186,6 +220,7 @@ intc_0 func TestParseCallstack(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() expectedCallFrames := []CallFrame{ { @@ -210,6 +245,7 @@ func TestParseCallstack(t *testing.T) { func TestCallStackUpdate(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() expectedCallFrames := []CallFrame{ { @@ -222,9 +258,9 @@ func TestCallStackUpdate(t *testing.T) { }, } - testDbg := testDbgHook{} + testDbg := testDebugger{} ep := defaultEvalParams() - ep.Debugger = &testDbg + ep.Tracer = MakeEvalTracerDebuggerAdaptor(&testDbg) testLogic(t, testCallStackProgram, AssemblerMaxVersion, ep) require.Equal(t, 1, testDbg.register) diff --git a/data/transactions/logic/doc.go b/data/transactions/logic/doc.go index 243c22ec2c..31a0412fa3 100644 --- a/data/transactions/logic/doc.go +++ b/data/transactions/logic/doc.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -224,12 +224,12 @@ var opcodeImmediateNotes = map[string]string{ "intc": "{uint8 int constant index}", "pushint": "{varuint int}", "pushints": "{varuint count} [{varuint value}, ...]", - "bytecblock": "{varuint count} [({varuint value length} bytes), ...]", + "bytecblock": "{varuint count} [({varuint length} bytes), ...]", "bytec": "{uint8 byte constant index}", "pushbytes": "{varuint length} {bytes}", - "pushbytess": "{varuint count} [({varuint value length} bytes), ...]", + "pushbytess": "{varuint count} [({varuint length} bytes), ...]", - "arg": "{uint8 arg index N}", + "arg": "{uint8 arg index}", "global": "{uint8 global field index}", "txn": "{uint8 transaction field index}", @@ -279,10 +279,10 @@ var opcodeImmediateNotes = map[string]string{ "ecdsa_pk_recover": "{uint8 curve index}", "base64_decode": "{uint8 encoding index}", - "json_ref": "{uint8 return type}", + "json_ref": "{uint8 return type index}", "vrf_verify": "{uint8 parameters index}", - "block": "{uint8 block field}", + "block": "{uint8 block field index}", "switch": "{uint8 branch count} [{int16 branch offset, big-endian}, ...]", "match": "{uint8 branch count} [{int16 branch offset, big-endian}, ...]", diff --git a/data/transactions/logic/doc_test.go b/data/transactions/logic/doc_test.go index 9b5f2a9505..8afca4520f 100644 --- a/data/transactions/logic/doc_test.go +++ b/data/transactions/logic/doc_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -27,6 +27,7 @@ import ( func TestOpDocs(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() opsSeen := make(map[string]bool, len(OpSpecs)) for _, op := range OpSpecs { @@ -50,6 +51,7 @@ func TestOpDocs(t *testing.T) { // around for non-existent opcodes, most likely from a rename. func TestDocStragglers(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() for op := range opDocExtras { _, ok := opDocByName[op] @@ -63,6 +65,7 @@ func TestDocStragglers(t *testing.T) { func TestOpGroupCoverage(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() opsSeen := make(map[string]bool, len(OpSpecs)) for _, op := range OpSpecs { @@ -87,6 +90,7 @@ func TestOpGroupCoverage(t *testing.T) { func TestOpDoc(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() xd := OpDoc("txn") require.NotEmpty(t, xd) @@ -96,6 +100,7 @@ func TestOpDoc(t *testing.T) { func TestOpImmediateNote(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() xd := OpImmediateNote("txn") require.NotEmpty(t, xd) @@ -105,6 +110,7 @@ func TestOpImmediateNote(t *testing.T) { func TestAllImmediatesDocumented(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() for _, op := range OpSpecs { count := len(op.Immediates) @@ -135,6 +141,7 @@ func TestAllImmediatesDocumented(t *testing.T) { func TestOpDocExtra(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() xd := OpDocExtra("bnz") require.NotEmpty(t, xd) @@ -144,6 +151,7 @@ func TestOpDocExtra(t *testing.T) { func TestOpAllCosts(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() a := OpAllCosts("+") require.Len(t, a, 1) @@ -158,6 +166,7 @@ func TestOpAllCosts(t *testing.T) { func TestOnCompletionDescription(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() desc := OnCompletionDescription(0) require.Equal(t, "Only execute the `ApprovalProgram` associated with this application ID, with no additional effects.", desc) diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index ab03e0f954..158ca6e08b 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -282,8 +282,8 @@ type EvalParams struct { SigLedger LedgerForSignature Ledger LedgerForLogic - // optional debugger - Debugger DebuggerHook + // optional tracer + Tracer EvalTracer // MinAvmVersion is the minimum allowed AVM version of this program. // The program must reject if its version is less than this version. If @@ -455,7 +455,7 @@ func NewInnerEvalParams(txg []transactions.SignedTxnWithAD, caller *EvalContext) logger: caller.logger, SigLedger: caller.SigLedger, Ledger: caller.Ledger, - Debugger: nil, // See #4438, where this becomes caller.Debugger + Tracer: caller.Tracer, MinAvmVersion: &minAvmVersion, FeeCredit: caller.FeeCredit, Specials: caller.Specials, @@ -474,28 +474,31 @@ func NewInnerEvalParams(txg []transactions.SignedTxnWithAD, caller *EvalContext) type evalFunc func(cx *EvalContext) error type checkFunc func(cx *EvalContext) error -type runMode uint64 +// RunMode is a bitset of logic evaluation modes. +// There are currently two such modes: Signature and Application. +type RunMode uint64 const ( - // modeSig is LogicSig execution - modeSig runMode = 1 << iota + // ModeSig is LogicSig execution + ModeSig RunMode = 1 << iota - // modeApp is application/contract execution - modeApp + // ModeApp is application/contract execution + ModeApp // local constant, run in any mode - modeAny = modeSig | modeApp + modeAny = ModeSig | ModeApp ) -func (r runMode) Any() bool { +// Any checks if this mode bitset represents any evaluation mode +func (r RunMode) Any() bool { return r == modeAny } -func (r runMode) String() string { +func (r RunMode) String() string { switch r { - case modeSig: + case ModeSig: return "Signature" - case modeApp: + case ModeApp: return "Application" case modeAny: return "Any" @@ -547,7 +550,7 @@ type EvalContext struct { *EvalParams // determines eval mode: runModeSignature or runModeApplication - runModeFlags runMode + runModeFlags RunMode // the index of the transaction being evaluated groupIndex int @@ -591,9 +594,11 @@ type EvalContext struct { instructionStarts []bool programHashCached crypto.Digest +} - // Stores state & disassembly for the optional debugger - debugState *DebugState +// RunMode returns the evaluation context's mode (signature or application) +func (cx *EvalContext) RunMode() RunMode { + return cx.runModeFlags } // StackType describes the type of a value on the operand stack @@ -700,7 +705,7 @@ func EvalContract(program []byte, gi int, aid basics.AppIndex, params *EvalParam } cx := EvalContext{ EvalParams: params, - runModeFlags: modeApp, + runModeFlags: ModeApp, groupIndex: gi, txn: ¶ms.TxnGroup[gi], appID: aid, @@ -787,7 +792,7 @@ func EvalSignatureFull(gi int, params *EvalParams) (pass bool, pcx *EvalContext, } cx := EvalContext{ EvalParams: params, - runModeFlags: modeSig, + runModeFlags: ModeSig, groupIndex: gi, txn: ¶ms.TxnGroup[gi], } @@ -834,17 +839,11 @@ func eval(program []byte, cx *EvalContext) (pass bool, err error) { cx.txn.EvalDelta.GlobalDelta = basics.StateDelta{} cx.txn.EvalDelta.LocalDeltas = make(map[uint64]basics.StateDelta) - if cx.Debugger != nil { - cx.debugState = makeDebugState(cx) - if derr := cx.Debugger.Register(cx.refreshDebugState(err)); derr != nil { - return false, derr - } + if cx.Tracer != nil { + cx.Tracer.BeforeProgram(cx) defer func() { - // Ensure we update the debugger before exiting - errDbg := cx.Debugger.Complete(cx.refreshDebugState(err)) - if err == nil { - err = errDbg - } + // Ensure we update the tracer before exiting + cx.Tracer.AfterProgram(cx, err) }() } @@ -859,13 +858,15 @@ func eval(program []byte, cx *EvalContext) (pass bool, err error) { } for (err == nil) && (cx.pc < len(cx.program)) { - if cx.Debugger != nil { - if derr := cx.Debugger.Update(cx.refreshDebugState(err)); derr != nil { - return false, derr - } + if cx.Tracer != nil { + cx.Tracer.BeforeOpcode(cx) } err = cx.step() + + if cx.Tracer != nil { + cx.Tracer.AfterOpcode(cx, err) + } } if err != nil { if cx.Trace != nil { @@ -895,17 +896,17 @@ func eval(program []byte, cx *EvalContext) (pass bool, err error) { // these static checks include a cost estimate that must be low enough // (controlled by params.Proto). func CheckContract(program []byte, params *EvalParams) error { - return check(program, params, modeApp) + return check(program, params, ModeApp) } // CheckSignature should be faster than EvalSignature. It can perform static // checks and reject programs that are invalid. Prior to v4, these static checks // include a cost estimate that must be low enough (controlled by params.Proto). func CheckSignature(gi int, params *EvalParams) error { - return check(params.TxnGroup[gi].Lsig.Logic, params, modeSig) + return check(params.TxnGroup[gi].Lsig.Logic, params, ModeSig) } -func check(program []byte, params *EvalParams, mode runMode) (err error) { +func check(program []byte, params *EvalParams, mode RunMode) (err error) { defer func() { if x := recover(); x != nil { buf := make([]byte, 16*1024) @@ -1012,7 +1013,7 @@ func (cx *EvalContext) Cost() int { } func (cx *EvalContext) remainingBudget() int { - if cx.runModeFlags == modeSig { + if cx.runModeFlags == ModeSig { return int(cx.Proto.LogicSigMaxCost) - cx.cost } @@ -1822,6 +1823,15 @@ func opBytesSqrt(cx *EvalContext) error { return nil } +func nonzero(b []byte) []byte { + for i := range b { + if b[i] != 0 { + return b[i:] + } + } + return nil +} + func opBytesLt(cx *EvalContext) error { last := len(cx.stack) - 1 prev := last - 1 @@ -1830,9 +1840,18 @@ func opBytesLt(cx *EvalContext) error { return errors.New("math attempted on large byte-array") } - rhs := new(big.Int).SetBytes(cx.stack[last].Bytes) - lhs := new(big.Int).SetBytes(cx.stack[prev].Bytes) - cx.stack[prev] = boolToSV(lhs.Cmp(rhs) < 0) + rhs := nonzero(cx.stack[last].Bytes) + lhs := nonzero(cx.stack[prev].Bytes) + + switch { + case len(lhs) < len(rhs): + cx.stack[prev] = boolToSV(true) + case len(lhs) > len(rhs): + cx.stack[prev] = boolToSV(false) + default: + cx.stack[prev] = boolToSV(bytes.Compare(lhs, rhs) < 0) + } + cx.stack = cx.stack[:last] return nil } @@ -1866,9 +1885,10 @@ func opBytesEq(cx *EvalContext) error { return errors.New("math attempted on large byte-array") } - rhs := new(big.Int).SetBytes(cx.stack[last].Bytes) - lhs := new(big.Int).SetBytes(cx.stack[prev].Bytes) - cx.stack[prev] = boolToSV(lhs.Cmp(rhs) == 0) + rhs := nonzero(cx.stack[last].Bytes) + lhs := nonzero(cx.stack[prev].Bytes) + + cx.stack[prev] = boolToSV(bytes.Equal(lhs, rhs)) cx.stack = cx.stack[:last] return nil } @@ -2602,7 +2622,7 @@ func (cx *EvalContext) getTxID(txn *transactions.Transaction, groupIndex int, in func (cx *EvalContext) txnFieldToStack(stxn *transactions.SignedTxnWithAD, fs *txnFieldSpec, arrayFieldIdx uint64, groupIndex int, inner bool) (sv stackValue, err error) { if fs.effects { - if cx.runModeFlags == modeSig { + if cx.runModeFlags == ModeSig { return sv, fmt.Errorf("txn[%s] not allowed in current mode", fs.field) } if cx.version < txnEffectsVersion && !inner { @@ -2870,7 +2890,7 @@ func (cx *EvalContext) opTxnImpl(gi uint64, src txnSource, field TxnField, ai ui case srcGroup: if fs.effects && gi >= uint64(cx.groupIndex) { // Test mode so that error is clearer - if cx.runModeFlags == modeSig { + if cx.runModeFlags == ModeSig { return sv, fmt.Errorf("txn[%s] not allowed in current mode", fs.field) } return sv, fmt.Errorf("txn effects can only be read from past txns %d %d", gi, cx.groupIndex) @@ -5154,7 +5174,16 @@ func opItxnSubmit(cx *EvalContext) error { } ep := NewInnerEvalParams(cx.subtxns, cx) + + if ep.Tracer != nil { + ep.Tracer.BeforeTxnGroup(ep) + } + for i := range ep.TxnGroup { + if ep.Tracer != nil { + ep.Tracer.BeforeTxn(ep, i) + } + err := cx.Ledger.Perform(i, ep) if err != nil { return err @@ -5162,11 +5191,20 @@ func opItxnSubmit(cx *EvalContext) error { // This is mostly a no-op, because Perform does its work "in-place", but // RecordAD has some further responsibilities. ep.RecordAD(i, ep.TxnGroup[i].ApplyData) + + if ep.Tracer != nil { + ep.Tracer.AfterTxn(ep, i, ep.TxnGroup[i].ApplyData) + } } cx.txn.EvalDelta.InnerTxns = append(cx.txn.EvalDelta.InnerTxns, ep.TxnGroup...) cx.subtxns = nil // must clear the inner txid cache, otherwise prior inner txids will be returned for this group cx.innerTxidCache = nil + + if ep.Tracer != nil { + ep.Tracer.AfterTxnGroup(ep) + } + return nil } diff --git a/data/transactions/logic/evalAppTxn_test.go b/data/transactions/logic/evalAppTxn_test.go index 25a073ba3c..b33a97d742 100644 --- a/data/transactions/logic/evalAppTxn_test.go +++ b/data/transactions/logic/evalAppTxn_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -35,6 +35,7 @@ import ( func TestInnerTypesV5(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() v5, _, _ := MakeSampleEnvWithVersion(5) // not alllowed in v5 @@ -47,6 +48,7 @@ func TestInnerTypesV5(t *testing.T) { func TestCurrentInnerTypes(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() ep, tx, ledger := MakeSampleEnv() TestApp(t, "itxn_submit; int 1;", ep, "itxn_submit without itxn_begin") @@ -99,6 +101,7 @@ func TestCurrentInnerTypes(t *testing.T) { func TestFieldTypes(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() ep, _, _ := MakeSampleEnv() TestApp(t, "itxn_begin; byte \"pay\"; itxn_field Sender;", ep, "not an address") @@ -129,6 +132,7 @@ func appAddr(id int) basics.Address { func TestAppPay(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() pay := ` itxn_begin @@ -176,6 +180,7 @@ func TestAppPay(t *testing.T) { func TestAppAssetOptIn(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() ep, tx, ledger := MakeSampleEnv() // Establish 888 as the app id, and fund it. @@ -237,6 +242,7 @@ int 1 func TestRekeyPay(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() pay := ` itxn_begin @@ -262,6 +268,7 @@ func TestRekeyPay(t *testing.T) { func TestRekeyBack(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() payAndUnkey := ` itxn_begin @@ -288,6 +295,7 @@ func TestRekeyBack(t *testing.T) { func TestDefaultSender(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() pay := ` itxn_begin @@ -309,6 +317,7 @@ func TestDefaultSender(t *testing.T) { func TestAppAxfer(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() axfer := ` itxn_begin @@ -372,6 +381,7 @@ func TestAppAxfer(t *testing.T) { func TestExtraFields(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() pay := ` itxn_begin @@ -394,6 +404,7 @@ func TestExtraFields(t *testing.T) { func TestBadFieldV5(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() pay := ` itxn_begin @@ -418,6 +429,7 @@ func TestBadFieldV5(t *testing.T) { func TestBadField(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() pay := ` itxn_begin @@ -443,6 +455,7 @@ func TestBadField(t *testing.T) { func TestNumInnerShallow(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() pay := ` itxn_begin @@ -485,6 +498,7 @@ func TestNumInnerShallow(t *testing.T) { // in a group. func TestNumInnerPooled(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() pay := ` itxn_begin @@ -523,6 +537,7 @@ func TestNumInnerPooled(t *testing.T) { func TestAssetCreate(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() create := ` itxn_begin @@ -551,6 +566,7 @@ func TestAssetCreate(t *testing.T) { func TestAssetFreeze(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() create := ` itxn_begin @@ -599,6 +615,7 @@ func TestAssetFreeze(t *testing.T) { func TestKeyReg(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() keyreg := ` store 6 // StateProofPK @@ -655,6 +672,7 @@ func TestKeyReg(t *testing.T) { ` t.Run("nonparticipating", func(t *testing.T) { + t.Parallel() params := ` int 0 // VoteFirst int 0 // VoteLast @@ -671,6 +689,7 @@ func TestKeyReg(t *testing.T) { }) t.Run("offline", func(t *testing.T) { + t.Parallel() params := ` int 0 // VoteFirst int 0 // VoteLast @@ -687,6 +706,7 @@ func TestKeyReg(t *testing.T) { }) t.Run("online without StateProofPK", func(t *testing.T) { + t.Parallel() params := ` int 100 // VoteFirst int 200 // VoteLast @@ -704,6 +724,7 @@ func TestKeyReg(t *testing.T) { }) t.Run("online with StateProofPK", func(t *testing.T) { + t.Parallel() params := ` int 100 // VoteFirst int 16777315 // VoteLast @@ -722,6 +743,7 @@ func TestKeyReg(t *testing.T) { }) t.Run("online with StateProofPK and too long validity period", func(t *testing.T) { + t.Parallel() params := ` int 100 // VoteFirst int 16777316 // VoteLast @@ -742,6 +764,7 @@ func TestKeyReg(t *testing.T) { func TestFieldSetting(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() ep, tx, ledger := MakeSampleEnv() ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) @@ -786,6 +809,7 @@ func TestFieldSetting(t *testing.T) { func TestInnerGroup(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() ep, tx, ledger := MakeSampleEnv() ep.FeeCredit = nil // default sample env starts at 401 @@ -808,6 +832,7 @@ txn Sender; itxn_field Receiver; func TestInnerFeePooling(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() ep, tx, ledger := MakeSampleEnv() ep.FeeCredit = nil // default sample env starts at 401 @@ -878,6 +903,7 @@ txn Sender; itxn_field Receiver; // immediate failures. func TestApplCreation(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() ep, tx, _ := MakeSampleEnv() @@ -963,8 +989,6 @@ func TestBigApplCreation(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - ep, _, _ := MakeSampleEnv() - p := "itxn_begin;" s := "; int 1" @@ -974,7 +998,12 @@ func TestBigApplCreation(t *testing.T) { // First, test normal accummulation for _, pgm := range []string{"Approval", "ClearState"} { + pgm := pgm t.Run(pgm, func(t *testing.T) { + t.Parallel() + + ep, _, _ := MakeSampleEnv() + basic := "itxn_field " + pgm + "Program" pages := "itxn_field " + pgm + "ProgramPages" TestApp(t, p+`int 1000; bzero; `+pages+` @@ -1062,6 +1091,7 @@ func TestApplSubmission(t *testing.T) { func TestInnerApplCreate(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() ep, tx, ledger := MakeSampleEnv() ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) @@ -1129,6 +1159,7 @@ int 5000; app_params_get AppGlobalNumByteSlice; !; assert; !; assert; int 1 func TestCreateOldAppFails(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() ep, tx, ledger := MakeSampleEnv() ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) @@ -1191,6 +1222,7 @@ int 1 func TestSelfReentrancy(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() ep, tx, ledger := MakeSampleEnv() ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) @@ -1207,6 +1239,7 @@ int 1 func TestIndirectReentrancy(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() ep, tx, ledger := MakeSampleEnv() call888 := TestProg(t, `itxn_begin @@ -1236,6 +1269,7 @@ int 1 // needlessly picky to test, but the appID used to be stored outside the cx. func TestInnerAppID(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() ep, tx, ledger := MakeSampleEnv() logID := TestProg(t, `global CurrentApplicationID; itob; log; int 1`, AssemblerMaxVersion) @@ -1264,6 +1298,7 @@ int 222 // about 690 (see next test). func TestInnerBudgetIncrement(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() ep, tx, ledger := MakeSampleEnv() gasup := TestProg(t, "pushint 1", AssemblerMaxVersion) @@ -1293,6 +1328,7 @@ itxn_submit; func TestIncrementCheck(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() ep, tx, ledger := MakeSampleEnv() gasup := TestProg(t, "pushint 1", AssemblerMaxVersion) @@ -1326,6 +1362,7 @@ int 1 // TestInnerTxIDs confirms that TxIDs are available and different func TestInnerTxIDs(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() ep, tx, ledger := MakeSampleEnv() txid := TestProg(t, "txn TxID; log; int 1", AssemblerMaxVersion) @@ -1421,6 +1458,7 @@ gitxn 1 TxID // but set and unique on non-singletons func TestInnerGroupIDs(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() ep, tx, ledger := MakeSampleEnv() gid := TestProg(t, "global GroupID; log; int 1", AssemblerMaxVersion) @@ -1761,7 +1799,9 @@ int 1 ` for _, unified := range []bool{true, false} { - t.Run(fmt.Sprintf("unified=%t", unified), func(t *testing.T) { //nolint:paralleltest // NO t.Parallel(). unified variable is actually shared + unified := unified + t.Run(fmt.Sprintf("unified=%t", unified), func(t *testing.T) { + t.Parallel() ep, parentTx, ledger := MakeSampleEnv() ep.Proto.UnifyInnerTxIDs = unified @@ -2087,9 +2127,9 @@ int 1 ` for _, unified := range []bool{true, false} { + unified := unified t.Run(fmt.Sprintf("unified=%t", unified), func(t *testing.T) { - // t.Parallel() NO! unified variable is actually shared - + t.Parallel() ep, parentTx, ledger := MakeSampleEnv() ep.Proto.UnifyInnerTxIDs = unified @@ -2224,8 +2264,10 @@ func TestInnerTxIDCaching(t *testing.T) { parentAppID := basics.AppIndex(888) childAppID := basics.AppIndex(222) - for _, unified := range []bool{true, false} { //nolint:paralleltest // NO t.Parallel(). unified variable is actually shared + for _, unified := range []bool{true, false} { + unified := unified t.Run(fmt.Sprintf("unified=%t", unified), func(t *testing.T) { + t.Parallel() ep, parentTx, ledger := MakeSampleEnv() ep.Proto.UnifyInnerTxIDs = unified @@ -2321,6 +2363,7 @@ btoi // TestGtixn confirms access to itxn groups func TestGtixn(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() ep, tx, ledger := MakeSampleEnv() two := TestProg(t, "byte 0x22; log; int 1", AssemblerMaxVersion) @@ -2407,6 +2450,7 @@ int 1 // TestGtxnLog confirms that gtxn can now access previous txn's Logs. func TestGtxnLog(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() ep, tx, ledger := MakeSampleEnv() two := TestProg(t, "byte 0x22; log; int 1", AssemblerMaxVersion) @@ -2436,6 +2480,7 @@ int 1 // TestGtxnApps confirms that gtxn can now access previous txn's created app id. func TestGtxnApps(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() ep, tx, ledger := MakeSampleEnv() appcheck := TestProg(t, ` @@ -2483,6 +2528,7 @@ int 5001 // TestGtxnAsa confirms that gtxn can now access previous txn's created asa id. func TestGtxnAsa(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() ep, tx, ledger := MakeSampleEnv() appcheck := TestProg(t, ` @@ -2520,6 +2566,7 @@ int 5001 // TestCallerGlobals checks that a called app can see its caller. func TestCallerGlobals(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() ep, tx, ledger := MakeSampleEnv() globals := TestProg(t, fmt.Sprintf(` @@ -2550,6 +2597,7 @@ int 1 // transactions. func TestNumInnerDeep(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() pay := ` itxn_begin @@ -2600,6 +2648,7 @@ itxn_submit // foreign-arrays rules. func TestCreateAndUse(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() axfer := ` itxn_begin @@ -2715,6 +2764,7 @@ func hexProgram(t *testing.T, source string) string { // the address for it can be looked up. func TestCreateUseApp(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() pay5back := main(` itxn_begin @@ -2749,6 +2799,7 @@ int 1 // because of the strict adherence to the foreign-accounts rules. func TestCreateAndPay(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() pay5back := main(` itxn_begin @@ -2792,6 +2843,7 @@ int 1 // across multiple inner transaction groups func TestInnerGaid(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() ep, tx, ledger := MakeSampleEnv() ep.Proto.MaxInnerTransactions = 100 @@ -2983,6 +3035,7 @@ itxn_submit func TestForeignAppAccountAccess(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() ep, tx, ledger := MakeSampleEnv() ledger.NewAccount(appAddr(888), 50_000) diff --git a/data/transactions/logic/evalBench_test.go b/data/transactions/logic/evalBench_test.go index 956e1441e1..240da71bdf 100644 --- a/data/transactions/logic/evalBench_test.go +++ b/data/transactions/logic/evalBench_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/logic/evalCrypto_test.go b/data/transactions/logic/evalCrypto_test.go index e3dfff2f71..65f0787a97 100644 --- a/data/transactions/logic/evalCrypto_test.go +++ b/data/transactions/logic/evalCrypto_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -147,18 +147,27 @@ func BenchmarkVerify(b *testing.B) { benches := [][]string{ {"pop", "", "int 1234576; int 6712; pop; pop", "int 1"}, {"add", "", "int 1234576; int 6712; +; pop", "int 1"}, - /* - {"ed25519verify_bare", "", `byte 0x - byte 0x - addr - ed25519verify_bare - assert`, "int 1"},*/ - {"ecdsa_verify", "", `byte 0x71a5910445820f57989c027bdf9391c80097874d249e0f38bf90834fdec2877f + {"ed25519verify_bare", "", ` +byte 0x62fdfc072182654f163f5f0f9a621d729566c74d0aa413bf009c9800418c19cd +byte 0xaab40a8b4f1f386504af2473804abbc03bbd94506e8e0c8db881fc2b2c3aee65b867b25caa47fa25ae2105bf1731398df336213707f2d25f9b1d31b3dc133307; +addr C7ZCK6N2AJQMVEP4FRTK2UW45UFR6DKPRJHJVWB5O4VQOZMFPK2KCMR7M4 +ed25519verify_bare; assert +`, "int 1"}, + {"ecdsa_verify k1", "", ` +byte 0x71a5910445820f57989c027bdf9391c80097874d249e0f38bf90834fdec2877f byte 0x5eb27782eb1a5df8de9a5d51613ad5ca730840ddf4af919c6feb15cde14f9978 byte 0x0cb3c0d636ed991ee030d09c295de3121eb166cb9e1552cf0ef0fb2358f35f0f byte 0x79de0699673571df1de8486718d06a3e7838f6831ec4ef3fb963788fbfb773b7 byte 0xd76446a3393af3e2eefada16df80cc6a881a56f4cf41fa2ab4769c5708ce878d ecdsa_verify Secp256k1 +assert`, "int 1"}, + {"ecdsa_verify r1", "", ` +byte 0x71a5910445820f57989c027bdf9391c80097874d249e0f38bf90834fdec2877f +byte 0xc010fc83ea196d6f5ce8a44637060bdcfb5bf1199cfc5bb893684d450c4f160c +byte 0x8e391a7b9cd75a99e8ebfe703036caebd9e91ae8339bd7e2abfb0f273eb8e972 +byte 0x13e49a19378bbfa8d55ac81a35b87d7bae456c79fcf04a78803d8eb45b253fab +byte 0xa2d237cd897ca70787abf04d2155c6dc2fbe26fd642e0472cd75c13dc919ef1a +ecdsa_verify Secp256r1 assert`, "int 1"}, {"vrf_verify", "", `byte 0x72 byte 0xae5b66bdf04b4c010bfe32b2fc126ead2107b697634f6f7337b9bff8785ee111200095ece87dde4dbe87343f6df3b107d91798c8a7eb1245d3bb9c5aafb093358c13e6ae1111a55717e895fd15f99f07 @@ -337,7 +346,9 @@ byte 0x%s {pkTampered2, false}, } for i, test := range decompressTests { + i, test, source := i, test, source t.Run(fmt.Sprintf("decompress/pass=%v", test.pass), func(t *testing.T) { + t.Parallel() t.Log("decompressTests i", i) src := fmt.Sprintf(source, hex.EncodeToString(test.key), hex.EncodeToString(x), hex.EncodeToString(y)) if test.pass { @@ -381,7 +392,9 @@ ecdsa_verify Secp256k1 {"testdata1", r, false}, } for _, test := range verifyTests { + test, source := test, source t.Run(fmt.Sprintf("verify/pass=%v", test.pass), func(t *testing.T) { + t.Parallel() src := fmt.Sprintf(source, test.data, hex.EncodeToString(test.r), hex.EncodeToString(s), hex.EncodeToString(x), hex.EncodeToString(y)) if test.pass { testAccepts(t, src, 5) @@ -428,7 +441,9 @@ load 1 pkExpanded := secp256k1.S256().Marshal(key.PublicKey.X, key.PublicKey.Y) for i, test := range recoverTests { + i, test, source := i, test, source t.Run(fmt.Sprintf("recover/%d", i), func(t *testing.T) { + t.Parallel() src := fmt.Sprintf(source, hex.EncodeToString(msg[:]), test.v, hex.EncodeToString(r), hex.EncodeToString(s), hex.EncodeToString(x), hex.EncodeToString(y), hex.EncodeToString(pkExpanded)) test.checker(t, src, 5) }) @@ -487,7 +502,9 @@ byte 0x%s {pkTampered2, false}, } for i, test := range decompressTests { + i, test, source := i, test, source t.Run(fmt.Sprintf("decompress/pass=%v", test.pass), func(t *testing.T) { + t.Parallel() t.Log("decompressTests i", i) src := fmt.Sprintf(source, hex.EncodeToString(test.key), hex.EncodeToString(x), hex.EncodeToString(y)) if test.pass { @@ -530,7 +547,9 @@ ecdsa_verify Secp256r1 {"testdata1", r, false}, } for _, test := range verifyTests { + test, source := test, source t.Run(fmt.Sprintf("verify/pass=%v", test.pass), func(t *testing.T) { + t.Parallel() src := fmt.Sprintf(source, test.data, hex.EncodeToString(test.r), hex.EncodeToString(s), hex.EncodeToString(x), hex.EncodeToString(y)) if test.pass { testAccepts(t, src, fidoVersion) diff --git a/data/transactions/logic/evalStateful_test.go b/data/transactions/logic/evalStateful_test.go index 0d0676c164..a2f33cc909 100644 --- a/data/transactions/logic/evalStateful_test.go +++ b/data/transactions/logic/evalStateful_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -197,44 +197,47 @@ pop bytec_0 log ` - tests := map[runMode]string{ - modeSig: opcodesRunModeAny + opcodesRunModeSignature, - modeApp: opcodesRunModeAny + opcodesRunModeApplication, + tests := map[RunMode]string{ + ModeSig: opcodesRunModeAny + opcodesRunModeSignature, + ModeApp: opcodesRunModeAny + opcodesRunModeApplication, } - ep, tx, ledger := makeSampleEnv() - ep.TxnGroup[0].Lsig.Args = [][]byte{ - tx.Sender[:], - tx.Receiver[:], - tx.CloseRemainderTo[:], - tx.VotePK[:], - tx.SelectionPK[:], - tx.Note, - } - params := basics.AssetParams{ - Total: 1000, - Decimals: 2, - DefaultFrozen: false, - UnitName: "ALGO", - AssetName: "", - URL: string(protocol.PaymentTx), - Manager: tx.Sender, - Reserve: tx.Receiver, - Freeze: tx.Receiver, - Clawback: tx.Receiver, - } - algoValue := basics.TealValue{Type: basics.TealUintType, Uint: 0x77} - ledger.NewAccount(tx.Sender, 1) - ledger.NewApp(tx.Sender, 100, basics.AppParams{}) - ledger.NewLocals(tx.Sender, 100) - ledger.NewLocal(tx.Sender, 100, "ALGO", algoValue) - ledger.NewAsset(tx.Sender, 5, params) - for mode, test := range tests { + mode, test := mode, test t.Run(fmt.Sprintf("opcodes_mode=%d", mode), func(t *testing.T) { + t.Parallel() + + ep, tx, ledger := makeSampleEnv() + ep.TxnGroup[0].Lsig.Args = [][]byte{ + tx.Sender[:], + tx.Receiver[:], + tx.CloseRemainderTo[:], + tx.VotePK[:], + tx.SelectionPK[:], + tx.Note, + } ep.TxnGroup[0].Txn.ApplicationID = 100 ep.TxnGroup[0].Txn.ForeignAssets = []basics.AssetIndex{5} // needed since v4 - if mode == modeSig { + params := basics.AssetParams{ + Total: 1000, + Decimals: 2, + DefaultFrozen: false, + UnitName: "ALGO", + AssetName: "", + URL: string(protocol.PaymentTx), + Manager: tx.Sender, + Reserve: tx.Receiver, + Freeze: tx.Receiver, + Clawback: tx.Receiver, + } + algoValue := basics.TealValue{Type: basics.TealUintType, Uint: 0x77} + ledger.NewAccount(tx.Sender, 1) + ledger.NewApp(tx.Sender, 100, basics.AppParams{}) + ledger.NewLocals(tx.Sender, 100) + ledger.NewLocal(tx.Sender, 100, "ALGO", algoValue) + ledger.NewAsset(tx.Sender, 5, params) + + if mode == ModeSig { testLogic(t, test, AssemblerMaxVersion, ep) } else { testApp(t, test, ep) @@ -295,13 +298,14 @@ log } for _, source := range statefulOpcodeCalls { + source := source testLogic(t, source, AssemblerMaxVersion, defaultEvalParams(), "not allowed in current mode", "not allowed in current mode") } - require.Equal(t, runMode(1), modeSig) - require.Equal(t, runMode(2), modeApp) - require.True(t, modeAny == modeSig|modeApp) + require.Equal(t, RunMode(1), ModeSig) + require.Equal(t, RunMode(2), ModeApp) + require.True(t, modeAny == ModeSig|ModeApp) require.True(t, modeAny.Any()) } @@ -1276,7 +1280,9 @@ intc_1 "delete": {sourceDelete, 12}, } for name, cmdtest := range tests { + name, cmdtest := name, cmdtest t.Run(fmt.Sprintf("test=%s", name), func(t *testing.T) { + t.Parallel() source := cmdtest.source firstCmdOffset := cmdtest.accNumOffset @@ -1628,7 +1634,9 @@ int 1 "delete": sourceDelete, } for name, source := range tests { + name, source := name, source t.Run(fmt.Sprintf("test=%s", name), func(t *testing.T) { + t.Parallel() ops, err := AssembleStringWithVersion(source, AssemblerMaxVersion) require.NoError(t, err) @@ -2255,9 +2263,8 @@ int 1 require.Equal(t, 1, len(delta.LocalDeltas[0])) } -func TestEnumFieldErrors(t *testing.T) { +func TestEnumFieldErrors(t *testing.T) { // nolint:paralleltest // manipulates globalFieldSpecs partitiontest.PartitionTest(t) - // t.Parallel() NO! manipulates globalFieldSpecs source := `txn Amount` origSpec := txnFieldSpecs[Amount] @@ -2340,48 +2347,6 @@ func TestReturnTypes(t *testing.T) { StackAny: "int 1\n", StackBytes: "byte 0x33343536\n", // Which is the string "3456" } - ep, tx, ledger := makeSampleEnv() - - tx.Type = protocol.ApplicationCallTx - tx.ApplicationID = 1 - tx.ForeignApps = []basics.AppIndex{tx.ApplicationID} - tx.ForeignAssets = []basics.AssetIndex{basics.AssetIndex(1), basics.AssetIndex(1)} - tx.Boxes = []transactions.BoxRef{{ - Name: []byte("3456"), - }} - ep.TxnGroup[0].Lsig.Args = [][]byte{ - []byte("aoeu"), - []byte("aoeu"), - []byte("aoeu2"), - []byte("aoeu3"), - } - // We are going to run with GroupIndex=1, so make tx1 interesting too (so - // txn can look at things) - ep.TxnGroup[1] = ep.TxnGroup[0] - - ep.pastScratch[0] = &scratchSpace{} // for gload - ledger.NewAccount(tx.Sender, 1) - params := basics.AssetParams{ - Total: 1000, - Decimals: 2, - DefaultFrozen: false, - UnitName: "ALGO", - AssetName: "", - URL: string(protocol.PaymentTx), - Manager: tx.Sender, - Reserve: tx.Receiver, - Freeze: tx.Receiver, - Clawback: tx.Receiver, - } - ledger.NewAsset(tx.Sender, 1, params) - ledger.NewApp(tx.Sender, 1, basics.AppParams{}) - ledger.NewAccount(tx.Receiver, 1000000) - ledger.NewLocals(tx.Receiver, 1) - key, err := hex.DecodeString("33343536") - require.NoError(t, err) - algoValue := basics.TealValue{Type: basics.TealUintType, Uint: 0x77} - ledger.NewLocal(tx.Receiver, 1, string(key), algoValue) - ledger.NewAccount(appAddr(1), 1000000) // We try to form a snippet that will test every opcode, by sandwiching it // between arguments that correspond to the opcode's input types, and then @@ -2477,7 +2442,7 @@ func TestReturnTypes(t *testing.T) { } byName := OpsByName[LogicVersion] - for _, m := range []runMode{modeSig, modeApp} { + for _, m := range []RunMode{ModeSig, ModeApp} { for name, spec := range byName { // Only try an opcode in its modes if (m & spec.Modes) == 0 { @@ -2486,7 +2451,10 @@ func TestReturnTypes(t *testing.T) { if skipCmd[name] || spec.trusted { continue } + m, name, spec := m, name, spec t.Run(fmt.Sprintf("mode=%s,opcode=%s", m, name), func(t *testing.T) { + t.Parallel() + provideStackInput := true cmd := name if special, ok := specialCmd[name]; ok { @@ -2529,6 +2497,49 @@ func TestReturnTypes(t *testing.T) { sb.WriteString(cmd + "\n") ops := testProg(t, sb.String(), AssemblerMaxVersion) + ep, tx, ledger := makeSampleEnv() + + tx.Type = protocol.ApplicationCallTx + tx.ApplicationID = 1 + tx.ForeignApps = []basics.AppIndex{tx.ApplicationID} + tx.ForeignAssets = []basics.AssetIndex{basics.AssetIndex(1), basics.AssetIndex(1)} + tx.Boxes = []transactions.BoxRef{{ + Name: []byte("3456"), + }} + ep.TxnGroup[0].Lsig.Args = [][]byte{ + []byte("aoeu"), + []byte("aoeu"), + []byte("aoeu2"), + []byte("aoeu3"), + } + // We are going to run with GroupIndex=1, so make tx1 interesting too (so + // txn can look at things) + ep.TxnGroup[1] = ep.TxnGroup[0] + + ep.pastScratch[0] = &scratchSpace{} // for gload + ledger.NewAccount(tx.Sender, 1) + params := basics.AssetParams{ + Total: 1000, + Decimals: 2, + DefaultFrozen: false, + UnitName: "ALGO", + AssetName: "", + URL: string(protocol.PaymentTx), + Manager: tx.Sender, + Reserve: tx.Receiver, + Freeze: tx.Receiver, + Clawback: tx.Receiver, + } + ledger.NewAsset(tx.Sender, 1, params) + ledger.NewApp(tx.Sender, 1, basics.AppParams{}) + ledger.NewAccount(tx.Receiver, 1000000) + ledger.NewLocals(tx.Receiver, 1) + key, err := hex.DecodeString("33343536") + require.NoError(t, err) + algoValue := basics.TealValue{Type: basics.TealUintType, Uint: 0x77} + ledger.NewLocal(tx.Receiver, 1, string(key), algoValue) + ledger.NewAccount(appAddr(1), 1000000) + ep.reset() // for Trace and budget isolation ep.pastScratch[0] = &scratchSpace{} // for gload // these allows the box_* opcodes that to work @@ -2551,7 +2562,7 @@ func TestReturnTypes(t *testing.T) { // is checked for typing, we can't get hung up on whether it is // exactly one positive int. But if it fails for any *other* // reason, we're not doing a good test. - _, err := eval(ops.Program, &cx) + _, err = eval(ops.Program, &cx) if err != nil { // Allow the kinds of errors we expect, but fail for stuff // that indicates the opcode itself failed. @@ -2710,6 +2721,7 @@ func appAddr(id int) basics.Address { func TestAppInfo(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() ep, tx, ledger := makeSampleEnv() require.Equal(t, 888, int(tx.ApplicationID)) diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index 5fcbd3f193..6731c19824 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -229,6 +229,7 @@ func TestMinAvmVersionParamEvalCheckSignature(t *testing.T) { func TestTxnFieldToTealValue(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() txn := transactions.Transaction{} groupIndex := 0 @@ -1809,7 +1810,9 @@ func TestTxn(t *testing.T) { clearOps := testProg(t, "int 1", 1) for v, source := range tests { + v, source := v, source t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) { + t.Parallel() ops := testProg(t, source, v) txn := makeSampleTxn() if v >= appsEnabledVersion { @@ -2072,7 +2075,9 @@ gtxn 0 Sender } for v, source := range tests { + v, source := v, source t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) { + t.Parallel() txn := makeSampleTxn() // RekeyTo not allowed in v1 if v < rekeyingEnabledVersion { @@ -2726,7 +2731,9 @@ func TestGload(t *testing.T) { } for i, testCase := range cases { + i, testCase := i, testCase t.Run(fmt.Sprintf("i=%d", i), func(t *testing.T) { + t.Parallel() sources := testCase.tealSources // Initialize txgroup @@ -2746,7 +2753,7 @@ func TestGload(t *testing.T) { // for more complex group transaction cases type failureCase struct { firstTxn transactions.SignedTxn - runMode runMode + runMode RunMode errContains string } @@ -2756,7 +2763,7 @@ func TestGload(t *testing.T) { Type: protocol.PaymentTx, }, }, - runMode: modeApp, + runMode: ModeApp, errContains: "can't use gload on non-app call txn with index 0", } @@ -2766,13 +2773,15 @@ func TestGload(t *testing.T) { Type: protocol.ApplicationCallTx, }, }, - runMode: modeSig, + runMode: ModeSig, errContains: "gload not allowed in current mode", } failCases := []failureCase{nonAppCall, logicSigCall} for j, failCase := range failCases { + j, failCase := j, failCase t.Run(fmt.Sprintf("j=%d", j), func(t *testing.T) { + t.Parallel() appcall := transactions.SignedTxn{ Txn: transactions.Transaction{ @@ -2785,7 +2794,7 @@ func TestGload(t *testing.T) { program := testProg(t, "gload 0 0", AssemblerMaxVersion).Program switch failCase.runMode { - case modeApp: + case ModeApp: testAppBytes(t, program, ep, failCase.errContains) default: testLogicBytes(t, program, ep, failCase.errContains, failCase.errContains) @@ -3106,7 +3115,9 @@ func TestShortBytecblock2(t *testing.T) { "0026efbfbdefbfbd30", } for _, src := range sources { + src := src t.Run(src, func(t *testing.T) { + t.Parallel() program, err := hex.DecodeString(src) require.NoError(t, err) testLogicBytes(t, program, defaultEvalParams(), "const bytes list", "const bytes list") @@ -3125,10 +3136,13 @@ func checkPanic(cx *EvalContext) error { func TestPanic(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() log := logging.TestingLog(t) for v := uint64(1); v <= AssemblerMaxVersion; v++ { + v := v t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) { + t.Parallel() ops := testProg(t, `int 1`, v) var hackedOpcode int var oldSpec OpSpec @@ -3300,7 +3314,9 @@ done: intc_1 ` for _, line := range branches { + line := line t.Run(fmt.Sprintf("branch=%s", line), func(t *testing.T) { + t.Parallel() source := fmt.Sprintf(template, line) ops, err := AssembleStringWithVersion(source, AssemblerMaxVersion) require.NoError(t, err) @@ -3712,27 +3728,37 @@ main: } func BenchmarkByteLogic(b *testing.B) { + e64 := "byte 0x8090a0b0c0d0e0f0;" + o64 := "byte 0x1020304050607080;" + hex128e := "90a0b0c0d0e0f0001020304050607080" + hex128o := "102030405060708090a0b0c0d0e0f000" + e128 := "byte 0x" + strings.Repeat(hex128e, 1) + ";" + o128 := "byte 0x" + strings.Repeat(hex128o, 1) + ";" + e256 := "byte 0x" + strings.Repeat(hex128e, 2) + ";" + o256 := "byte 0x" + strings.Repeat(hex128o, 2) + ";" + e512 := "byte 0x" + strings.Repeat(hex128e, 4) + ";" + o512 := "byte 0x" + strings.Repeat(hex128o, 4) + ";" + benches := [][]string{ - {"b&", "", "byte 0x012345678901feab; byte 0x01ffffffffffffff; b&; pop", "int 1"}, - {"b|", "", "byte 0x0ffff1234576abef; byte 0x1202120212021202; b|; pop", "int 1"}, - {"b^", "", "byte 0x0ffff1234576abef; byte 0x1202120212021202; b^; pop", "int 1"}, - {"b~", "byte 0x0123457673624736", "b~", "pop; int 1"}, - - {"b&big", - "byte 0x0123457601234576012345760123457601234576012345760123457601234576", - "byte 0x01ffffffffffffff01ffffffffffffff01234576012345760123457601234576; b&", - "pop; int 1"}, - {"b|big", - "byte 0x0123457601234576012345760123457601234576012345760123457601234576", - "byte 0xffffff01ffffffffffffff01234576012345760123457601234576; b|", - "pop; int 1"}, - {"b^big", "", // u256^u256 - `byte 0x123457601234576012345760123457601234576012345760123457601234576a - byte 0xf123457601234576012345760123457601234576012345760123457601234576; b^; pop`, - "int 1"}, - {"b~big", "byte 0xa123457601234576012345760123457601234576012345760123457601234576", - "b~", - "pop; int 1"}, + {"b& 8", "", e64 + o64 + "b&; pop", "int 1"}, + {"b| 8", "", e64 + o64 + "b|; pop", "int 1"}, + {"b^ 8", "", e64 + o64 + "b^; pop", "int 1"}, + {"b~ 8", e64, "b~", "pop; int 1"}, + + {"b& 16", "", e128 + o128 + "b&; pop", "int 1"}, + {"b| 16", "", e128 + o128 + "b|; pop", "int 1"}, + {"b^ 16", "", e128 + o128 + "b^; pop", "int 1"}, + {"b~ 16", e128, "b~", "pop; int 1"}, + + {"b& 32", "", e256 + o256 + "b&; pop", "int 1"}, + {"b| 32", "", e256 + o256 + "b|; pop", "int 1"}, + {"b^ 32", "", e256 + o256 + "b^; pop", "int 1"}, + {"b~ 32", e256, "b~", "pop; int 1"}, + + {"b& 64", "", e512 + o512 + "b&; pop", "int 1"}, + {"b| 64", "", e512 + o512 + "b|; pop", "int 1"}, + {"b^ 64", "", e512 + o512 + "b^; pop", "int 1"}, + {"b~ 64", e512, "b~", "pop; int 1"}, } for _, bench := range benches { b.Run(bench[0], func(b *testing.B) { @@ -3743,44 +3769,79 @@ func BenchmarkByteLogic(b *testing.B) { } func BenchmarkByteMath(b *testing.B) { + u64 := "byte 0x8090a0b0c0d0e0f0;" + hex128 := "102030405060708090a0b0c0d0e0f000" + u128 := "byte 0x" + strings.Repeat(hex128, 1) + ";" + u256 := "byte 0x" + strings.Repeat(hex128, 2) + ";" + u512 := "byte 0x" + strings.Repeat(hex128, 4) + ";" + benches := [][]string{ - {"bpop", "", "byte 0x01ffffffffffffff; pop", "int 1"}, - - {"b+", "byte 0x01234576", "byte 0x01ffffffffffffff; b+", "pop; int 1"}, - {"b-", "byte 0x0ffff1234576", "byte 0x1202; b-", "pop; int 1"}, - {"b*", "", "byte 0x01234576; byte 0x0223627389; b*; pop", "int 1"}, - {"b/", "", "byte 0x0123457673624736; byte 0x0223627389; b/; pop", "int 1"}, - {"b%", "", "byte 0x0123457673624736; byte 0x0223627389; b/; pop", "int 1"}, - {"bsqrt", "", "byte 0x0123457673624736; bsqrt; pop", "int 1"}, - - {"b+big", // u256 + u256 - "byte 0x0123457601234576012345760123457601234576012345760123457601234576", - "byte 0x01ffffffffffffff01ffffffffffffff01234576012345760123457601234576; b+", - "pop; int 1"}, - {"b-big", // second is a bit small, so we can subtract it over and over - "byte 0x0123457601234576012345760123457601234576012345760123457601234576", - "byte 0xffffff01ffffffffffffff01234576012345760123457601234576; b-", - "pop; int 1"}, - {"b*big", "", // u256*u256 - `byte 0xa123457601234576012345760123457601234576012345760123457601234576 - byte 0xf123457601234576012345760123457601234576012345760123457601234576; b*; pop`, - "int 1"}, - {"b/big", "", // u256 / u128 (half sized divisor seems pessimal) - `byte 0xa123457601234576012345760123457601234576012345760123457601234576 - byte 0x34576012345760123457601234576312; b/; pop`, - "int 1"}, - {"b%big", "", // u256 / u128 (half sized divisor seems pessimal) - `byte 0xa123457601234576012345760123457601234576012345760123457601234576 - byte 0x34576012345760123457601234576312; b/; pop`, - "int 1"}, - {"bsqrt-big", "", - `byte 0xa123457601234576012345760123457601234576012345760123457601234576 - bsqrt; pop`, - "int 1"}, + {"bytec", u128 + "pop"}, + + {"b+ 128", u128 + u128 + "b+; pop"}, + {"b- 128", u128 + u128 + "b-; pop"}, + {"b* 128", u128 + u128 + "b*; pop"}, + // half sized divisor seems pessimal for / and % + {"b/ 128", u128 + u64 + "b/; pop"}, + {"b% 128", u128 + u64 + "b%; pop"}, + {"bsqrt 128", u128 + "bsqrt; pop"}, + + {"b+ 256", u256 + u256 + "b+; pop"}, + {"b- 256", u256 + u256 + "b-; pop"}, + {"b* 256", u256 + u256 + "b*; pop"}, + {"b/ 256", u256 + u128 + "b/; pop"}, + {"b% 256", u256 + u128 + "b%; pop"}, + {"bsqrt 256", u256 + "bsqrt; pop"}, + + {"b+ 512", u512 + u512 + "b+; pop"}, + {"b- 512", u512 + u512 + "b-; pop"}, + {"b* 512", u512 + u512 + "b*; pop"}, + {"b/ 512", u512 + u256 + "b/; pop"}, + {"b% 512", u512 + u256 + "b%; pop"}, + {"bsqrt 512", u512 + "bsqrt; pop"}, + + {"bytec recheck", u128 + "pop"}, } for _, bench := range benches { b.Run(bench[0], func(b *testing.B) { - benchmarkOperation(b, bench[1], bench[2], bench[3]) + b.ReportAllocs() + benchmarkOperation(b, "", bench[1], "int 1") + }) + } +} + +func BenchmarkByteCompare(b *testing.B) { + u64 := "byte 0x8090a0b0c0d0e0f0;" + hex128 := "102030405060708090a0b0c0d0e0f000" + u128 := "byte 0x" + strings.Repeat(hex128, 1) + ";" + u256 := "byte 0x" + strings.Repeat(hex128, 2) + ";" + u512 := "byte 0x" + strings.Repeat(hex128, 4) + ";" + //u4k := "byte 0x" + strings.Repeat(hex128, 256) + ";" + + benches := [][]string{ + {"b== 64", u64 + u64 + "b==; pop"}, + {"b< 64", u64 + u64 + "b<; pop"}, + {"b<= 64", u64 + u64 + "b<=; pop"}, + {"b== 128", u128 + u128 + "b==; pop"}, + {"b< 128", u128 + u128 + "b<; pop"}, + {"b<= 128", u128 + u128 + "b<=; pop"}, + {"b== 256", u256 + u256 + "b==; pop"}, + {"b< 256", u256 + u256 + "b<; pop"}, + {"b<= 256", u256 + u256 + "b<=; pop"}, + {"b== 512", u512 + u512 + "b==; pop"}, + {"b< 512", u512 + u512 + "b<; pop"}, + {"b<= 512", u512 + u512 + "b<=; pop"}, + // These can only be run with the maxByteMathSize check removed. They + // show that we can remove that check in a later AVM version, as there + // is no appreciable cost to even a 4k compare. + // {"b== 4k", u4k + u4k + "b==; pop"}, + // {"b< 4k", u4k + u4k + "b<; pop"}, + // {"b<= 4k", u4k + u4k + "b<=; pop"}, + } + for _, bench := range benches { + b.Run(bench[0], func(b *testing.B) { + b.ReportAllocs() + benchmarkOperation(b, "", bench[1], "int 1") }) } } @@ -4135,7 +4196,9 @@ func TestAnyRekeyToOrApplicationRaisesMinAvmVersion(t *testing.T) { } for ci, cse := range cases { + ci, cse := ci, cse t.Run(fmt.Sprintf("ci=%d", ci), func(t *testing.T) { + t.Parallel() ep := defaultEvalParams(cse.group...) // Computed MinAvmVersion should be == validFromVersion @@ -4853,9 +4916,23 @@ func TestBytesCompare(t *testing.T) { testPanics(t, "byte 0x10; int 65; bzero; b<=", 4) testAccepts(t, "byte 0x10; int 64; bzero; b>", 4) testPanics(t, "byte 0x10; int 65; bzero; b>", 4) + testAccepts(t, "byte 0x1010; byte 0x10; b<; !", 4) + + testAccepts(t, "byte 0x2000; byte 0x70; b<; !", 4) + testAccepts(t, "byte 0x7000; byte 0x20; b<; !", 4) + + // All zero input are interesting, because they lead to bytes.Compare being + // called with nils. Show that is correct. + testAccepts(t, "byte 0x10; byte 0x00; b<; !", 4) + testAccepts(t, "byte 0x10; byte 0x0000; b<; !", 4) + testAccepts(t, "byte 0x00; byte 0x10; b<", 4) + testAccepts(t, "byte 0x0000; byte 0x10; b<", 4) + testAccepts(t, "byte 0x0000; byte 0x00; b<; !", 4) + testAccepts(t, "byte 0x; byte 0x00; b==", 4) testAccepts(t, "byte 0x11; byte 0x10; b>", 4) testAccepts(t, "byte 0x11; byte 0x0010; b>", 4) + testAccepts(t, "byte 0x1010; byte 0x11; b>", 4) testAccepts(t, "byte 0x11; byte 0x10; b>=", 4) testAccepts(t, "byte 0x11; byte 0x0011; b>=", 4) @@ -4964,7 +5041,7 @@ func TestLog(t *testing.T) { msg := strings.Repeat("a", 400) failCases := []struct { source string - runMode runMode + runMode RunMode errContains string // For cases where assembly errors, we manually put in the bytes assembledBytes []byte @@ -4972,44 +5049,44 @@ func TestLog(t *testing.T) { { source: fmt.Sprintf(`byte "%s"; log; int 1`, strings.Repeat("a", maxLogSize+1)), errContains: fmt.Sprintf("> %d bytes limit", maxLogSize), - runMode: modeApp, + runMode: ModeApp, }, { source: fmt.Sprintf(`byte "%s"; log; byte "%s"; log; byte "%s"; log; int 1`, msg, msg, msg), errContains: fmt.Sprintf("> %d bytes limit", maxLogSize), - runMode: modeApp, + runMode: ModeApp, }, { source: fmt.Sprintf(`%s; int 1`, strings.Repeat(`byte "a"; log; `, maxLogCalls+1)), errContains: "too many log calls", - runMode: modeApp, + runMode: ModeApp, }, { source: `int 1; loop: byte "a"; log; int 1; +; dup; int 35; <; bnz loop;`, errContains: "too many log calls", - runMode: modeApp, + runMode: ModeApp, }, { source: fmt.Sprintf(`int 1; loop: byte "%s"; log; int 1; +; dup; int 6; <; bnz loop;`, strings.Repeat(`a`, 400)), errContains: fmt.Sprintf("> %d bytes limit", maxLogSize), - runMode: modeApp, + runMode: ModeApp, }, { source: `load 0; log`, errContains: "log arg 0 wanted []byte but got uint64", - runMode: modeApp, + runMode: ModeApp, assembledBytes: []byte{byte(ep.Proto.LogicSigVersion), 0x34, 0x00, 0xb0}, }, { source: `byte "a logging message"; log; int 1`, errContains: "log not allowed in current mode", - runMode: modeSig, + runMode: ModeSig, }, } for _, c := range failCases { switch c.runMode { - case modeApp: + case ModeApp: if c.assembledBytes == nil { testApp(t, c.source, ep, c.errContains) } else { @@ -5036,7 +5113,9 @@ func TestPcDetails(t *testing.T) { {"b end; end:", 4, ""}, } for i, test := range tests { + i, test := i, test t.Run(fmt.Sprintf("i=%d", i), func(t *testing.T) { + t.Parallel() ops := testProg(t, test.source, LogicVersion) ep, _, _ := makeSampleEnv() ep.Trace = &strings.Builder{} diff --git a/data/transactions/logic/export_test.go b/data/transactions/logic/export_test.go index 67346482a1..de151bfd9f 100644 --- a/data/transactions/logic/export_test.go +++ b/data/transactions/logic/export_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/logic/fields.go b/data/transactions/logic/fields.go index 0b62a5239c..0af2079a35 100644 --- a/data/transactions/logic/fields.go +++ b/data/transactions/logic/fields.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -537,7 +537,7 @@ var GlobalFieldNames [invalidGlobalField]string type globalFieldSpec struct { field GlobalField ftype StackType - mode runMode + mode RunMode version uint64 doc string } @@ -556,7 +556,7 @@ func (fs globalFieldSpec) Version() uint64 { } func (fs globalFieldSpec) Note() string { note := fs.doc - if fs.mode == modeApp { + if fs.mode == ModeApp { note = addExtra(note, "Application mode only.") } // There are no Signature mode only globals @@ -572,21 +572,21 @@ var globalFieldSpecs = [...]globalFieldSpec{ {GroupSize, StackUint64, modeAny, 0, "Number of transactions in this atomic transaction group. At least 1"}, {LogicSigVersion, StackUint64, modeAny, 2, "Maximum supported version"}, - {Round, StackUint64, modeApp, 2, "Current round number"}, - {LatestTimestamp, StackUint64, modeApp, 2, + {Round, StackUint64, ModeApp, 2, "Current round number"}, + {LatestTimestamp, StackUint64, ModeApp, 2, "Last confirmed block UNIX timestamp. Fails if negative"}, - {CurrentApplicationID, StackUint64, modeApp, 2, "ID of current application executing"}, - {CreatorAddress, StackBytes, modeApp, 3, + {CurrentApplicationID, StackUint64, ModeApp, 2, "ID of current application executing"}, + {CreatorAddress, StackBytes, ModeApp, 3, "Address of the creator of the current application"}, - {CurrentApplicationAddress, StackBytes, modeApp, 5, + {CurrentApplicationAddress, StackBytes, ModeApp, 5, "Address that the current application controls"}, {GroupID, StackBytes, modeAny, 5, "ID of the transaction group. 32 zero bytes if the transaction is not part of a group."}, {OpcodeBudget, StackUint64, modeAny, 6, "The remaining cost that can be spent by opcodes in this program."}, - {CallerApplicationID, StackUint64, modeApp, 6, + {CallerApplicationID, StackUint64, ModeApp, 6, "The application ID of the application that called this application. 0 if this application is at the top-level."}, - {CallerApplicationAddress, StackBytes, modeApp, 6, + {CallerApplicationAddress, StackBytes, ModeApp, 6, "The application address of the application that called this application. ZeroAddress if this application is at the top-level."}, } diff --git a/data/transactions/logic/fields_test.go b/data/transactions/logic/fields_test.go index 5ae0422946..cbe425d5f4 100644 --- a/data/transactions/logic/fields_test.go +++ b/data/transactions/logic/fields_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/logic/frames.go b/data/transactions/logic/frames.go index 1acc0c3c2b..4eda0e9330 100644 --- a/data/transactions/logic/frames.go +++ b/data/transactions/logic/frames.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/logic/frames_test.go b/data/transactions/logic/frames_test.go index b02714a88a..c46a8dd848 100644 --- a/data/transactions/logic/frames_test.go +++ b/data/transactions/logic/frames_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/logic/jsonspec_test.go b/data/transactions/logic/jsonspec_test.go index 3ebe131e8a..a2bfcfff24 100644 --- a/data/transactions/logic/jsonspec_test.go +++ b/data/transactions/logic/jsonspec_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -32,6 +32,7 @@ import ( func TestParseScalar(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() intScalar := `{"key0": 4160}` _, err := parseJSON([]byte(intScalar)) require.NoError(t, err) @@ -42,6 +43,7 @@ func TestParseScalar(t *testing.T) { func TestParseTrailingCommas(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() for i := 1; i <= 10; i++ { commas := strings.Repeat(",", i) intScalar := `{"key0": 4160` + commas + `}` @@ -55,6 +57,7 @@ func TestParseTrailingCommas(t *testing.T) { func TestParseComments(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() text := `{"key0": /*comment*/"algo"}` _, err := parseJSON([]byte(text)) require.Error(t, err) @@ -65,6 +68,7 @@ func TestParseComments(t *testing.T) { func TestParseUnclosed(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() text := `{"key0": ["algo"}` _, err := parseJSON([]byte(text)) require.Error(t, err) @@ -84,6 +88,7 @@ func TestParseUnclosed(t *testing.T) { func TestParseNested(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() text := `{"key0": [[1,2,3],[4,5,6]], "key1":{"key10":{"key100":"algo"}}}` _, err := parseJSON([]byte(text)) require.NoError(t, err) @@ -91,6 +96,7 @@ func TestParseNested(t *testing.T) { func TestParseWhiteSpace(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() //empty text text := "" _, err := parseJSON([]byte(text)) @@ -107,6 +113,7 @@ func TestParseWhiteSpace(t *testing.T) { func TestParseSpecialValues(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() text := `{"key0": NaN}` _, err := parseJSON([]byte(text)) require.Error(t, err) @@ -129,6 +136,7 @@ func TestParseSpecialValues(t *testing.T) { func TestParseHexValue(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() text := `{"key0": 0x1}` _, err := parseJSON([]byte(text)) require.Error(t, err) @@ -139,6 +147,7 @@ func TestParseHexValue(t *testing.T) { func TestParseBigNum(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() // values in range uint64 parsed correctly // parse 0 text := `{"key0":0}` @@ -162,6 +171,7 @@ func TestParseBigNum(t *testing.T) { func TestParseArrays(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() text := `{"key0": [,1,]}` _, err := parseJSON([]byte(text)) require.Error(t, err) @@ -175,6 +185,7 @@ func TestParseArrays(t *testing.T) { func TestParseKeys(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() text := `{"": 1}` _, err := parseJSON([]byte(text)) require.NoError(t, err) @@ -211,6 +222,7 @@ func TestParseKeys(t *testing.T) { func TestParseFileEncoding(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() // create utf-8, utf-16, and utf-32 encoded text and check which is supported by json // it appears that json only supports utf-8 encoded json text @@ -250,6 +262,7 @@ func TestParseFileEncoding(t *testing.T) { func TestParseByteOrderMark(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() // byte order mark is not allowed at the beginning of a JSON text, // it is treated as an error text := "\uFEFF{\"key0\": 1}" @@ -259,6 +272,7 @@ func TestParseByteOrderMark(t *testing.T) { func TestParseControlChar(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() // control chars (u0000 - u001F) must be escaped for i := 0x0; i <= 0x1f; i++ { text := fmt.Sprintf("{\"key0\":\"\\u%04X\"}", i) @@ -269,6 +283,7 @@ func TestParseControlChar(t *testing.T) { func TestParseEscapeChar(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() // escaped control char text := "{\"key0\": \"\\u0000\"}" _, err := parseJSON([]byte(text)) @@ -287,6 +302,7 @@ func TestParseEscapeChar(t *testing.T) { func TestParseEscapedInvalidChar(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() // unicode escape sequence remains in string // accepted surrogate pair text := `{"key0": "\uD801\udc37"}` @@ -307,6 +323,7 @@ func TestParseEscapedInvalidChar(t *testing.T) { func TestParseRawNonUnicodeChar(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() text := `{"key0": "πζθ"}` _, err := parseJSON([]byte(text)) require.NoError(t, err) diff --git a/data/transactions/logic/langspec.json b/data/transactions/logic/langspec.json index 148f83e073..cbfb4b0d95 100644 --- a/data/transactions/logic/langspec.json +++ b/data/transactions/logic/langspec.json @@ -67,6 +67,10 @@ "Args": "BBBBB", "Returns": "U", "Size": 2, + "ArgEnum": [ + "Secp256k1", + "Secp256r1" + ], "Doc": "for (data A, signature B, C and pubkey D, E) verify the signature of the data against the pubkey =\u003e {0 or 1}", "DocExtra": "The 32 byte Y-component of a public key is the last element on the stack, preceded by X-component of a pubkey, preceded by S and R components of a signature, preceded by the data that is fifth element on the stack. All values are big-endian encoded. The signed data must be 32 bytes long, and signatures in lower-S form are only accepted.", "ImmediateNote": "{uint8 curve index}", @@ -81,6 +85,10 @@ "Args": "B", "Returns": "BB", "Size": 2, + "ArgEnum": [ + "Secp256k1", + "Secp256r1" + ], "Doc": "decompress pubkey A into components X, Y", "DocExtra": "The 33 byte public key in a compressed form to be decompressed into X and Y (top) components. All values are big-endian encoded.", "ImmediateNote": "{uint8 curve index}", @@ -95,6 +103,10 @@ "Args": "BUBB", "Returns": "BB", "Size": 2, + "ArgEnum": [ + "Secp256k1", + "Secp256r1" + ], "Doc": "for (data A, recovery id B, signature C, D) recover a public key", "DocExtra": "S (top) and R elements of a signature, recovery id and data (bottom) are expected on the stack and used to deriver a public key. All values are big-endian encoded. The signed data must be 32 bytes long.", "ImmediateNote": "{uint8 curve index}", @@ -470,7 +482,7 @@ "Size": 0, "Doc": "prepare block of byte-array constants for use by bytec", "DocExtra": "`bytecblock` loads the following program bytes into an array of byte-array constants in the evaluator. These constants can be referred to by `bytec` and `bytec_*` which will push the value onto the stack. Subsequent calls to `bytecblock` reset and replace the bytes constants available to the script.", - "ImmediateNote": "{varuint count} [({varuint value length} bytes), ...]", + "ImmediateNote": "{varuint count} [({varuint length} bytes), ...]", "IntroducedVersion": 1, "Groups": [ "Loading Values" @@ -538,7 +550,7 @@ "Returns": "B", "Size": 2, "Doc": "Nth LogicSig argument", - "ImmediateNote": "{uint8 arg index N}", + "ImmediateNote": "{uint8 arg index}", "IntroducedVersion": 1, "Groups": [ "Loading Values" @@ -1410,6 +1422,11 @@ "Args": "B", "Returns": "B", "Size": 2, + "ArgEnum": [ + "URLEncoding", + "StdEncoding" + ], + "ArgEnumTypes": "..", "Doc": "decode A which was base64-encoded using _encoding_ E. Fail if A is not base64 encoded with encoding E", "DocExtra": "*Warning*: Usage should be restricted to very rare use cases. In almost all cases, smart contracts should directly handle non-encoded byte-strings.\tThis opcode should only be used in cases where base64 is the only available option, e.g. interoperability with a third-party that only signs base64 strings.\n\n Decodes A using the base64 encoding E. Specify the encoding with an immediate arg either as URL and Filename Safe (`URLEncoding`) or Standard (`StdEncoding`). See [RFC 4648 sections 4 and 5](https://rfc-editor.org/rfc/rfc4648.html#section-4). It is assumed that the encoding ends with the exact number of `=` padding characters as required by the RFC. When padding occurs, any unused pad bits in the encoding must be set to zero or the decoding will fail. The special cases of `\\n` and `\\r` are allowed but completely ignored. An error will result when attempting to decode a string with a character that is not in the encoding alphabet or not one of `=`, `\\r`, or `\\n`.", "ImmediateNote": "{uint8 encoding index}", @@ -1424,9 +1441,15 @@ "Args": "BB", "Returns": ".", "Size": 2, + "ArgEnum": [ + "JSONString", + "JSONUint64", + "JSONObject" + ], + "ArgEnumTypes": "BUB", "Doc": "key B's value, of type R, from a [valid](jsonspec.md) utf-8 encoded json object A", "DocExtra": "*Warning*: Usage should be restricted to very rare use cases, as JSON decoding is expensive and quite limited. In addition, JSON objects are large and not optimized for size.\n\nAlmost all smart contracts should use simpler and smaller methods (such as the [ABI](https://arc.algorand.foundation/ARCs/arc-0004). This opcode should only be used in cases where JSON is only available option, e.g. when a third-party only signs JSON.", - "ImmediateNote": "{uint8 return type}", + "ImmediateNote": "{uint8 return type index}", "IntroducedVersion": 7, "Groups": [ "Byte Array Manipulation" @@ -1704,7 +1727,7 @@ "Size": 0, "Doc": "push sequences of immediate byte arrays to stack (first byte array being deepest)", "DocExtra": "pushbytess args are not added to the bytecblock during assembly processes", - "ImmediateNote": "{varuint count} [({varuint value length} bytes), ...]", + "ImmediateNote": "{varuint count} [({varuint length} bytes), ...]", "IntroducedVersion": 8, "Groups": [ "Loading Values" @@ -2651,6 +2674,9 @@ "Args": "BBB", "Returns": "BU", "Size": 2, + "ArgEnum": [ + "VrfAlgorand" + ], "Doc": "Verify the proof B of message A against pubkey C. Returns vrf output and verification flag.", "DocExtra": "`VrfAlgorand` is the VRF used in Algorand. It is ECVRF-ED25519-SHA512-Elligator2, specified in the IETF internet draft [draft-irtf-cfrg-vrf-03](https://datatracker.ietf.org/doc/draft-irtf-cfrg-vrf/03/).", "ImmediateNote": "{uint8 parameters index}", @@ -2665,8 +2691,13 @@ "Args": "U", "Returns": ".", "Size": 2, + "ArgEnum": [ + "BlkSeed", + "BlkTimestamp" + ], + "ArgEnumTypes": "BU", "Doc": "field F of block A. Fail unless A falls between txn.LastValid-1002 and txn.FirstValid (exclusive)", - "ImmediateNote": "{uint8 block field}", + "ImmediateNote": "{uint8 block field index}", "IntroducedVersion": 7, "Groups": [ "State Access" diff --git a/data/transactions/logic/ledger_test.go b/data/transactions/logic/ledger_test.go index 5709673059..f56f349f1f 100644 --- a/data/transactions/logic/ledger_test.go +++ b/data/transactions/logic/ledger_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/logic/mocktracer/tracer.go b/data/transactions/logic/mocktracer/tracer.go new file mode 100644 index 0000000000..967798ec91 --- /dev/null +++ b/data/transactions/logic/mocktracer/tracer.go @@ -0,0 +1,147 @@ +// Copyright (C) 2019-2023 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package mocktracer + +import ( + "github.com/algorand/go-algorand/data/transactions" + "github.com/algorand/go-algorand/data/transactions/logic" + "github.com/algorand/go-algorand/protocol" +) + +// EventType represents a type of logic.EvalTracer event +type EventType string + +const ( + // BeforeTxnGroupEvent represents the logic.EvalTracer.BeforeTxnGroup event + BeforeTxnGroupEvent EventType = "BeforeTxnGroup" + // AfterTxnGroupEvent represents the logic.EvalTracer.AfterTxnGroup event + AfterTxnGroupEvent EventType = "AfterTxnGroup" + // BeforeTxnEvent represents the logic.EvalTracer.BeforeTxn event + BeforeTxnEvent EventType = "BeforeTxn" + // AfterTxnEvent represents the logic.EvalTracer.AfterTxn event + AfterTxnEvent EventType = "AfterTxn" + // BeforeProgramEvent represents the logic.EvalTracer.BeforeProgram event + BeforeProgramEvent EventType = "BeforeProgram" + // AfterProgramEvent represents the logic.EvalTracer.AfterProgram event + AfterProgramEvent EventType = "AfterProgram" + // BeforeOpcodeEvent represents the logic.EvalTracer.BeforeOpcode event + BeforeOpcodeEvent EventType = "BeforeOpcode" + // AfterOpcodeEvent represents the logic.EvalTracer.AfterOpcode event + AfterOpcodeEvent EventType = "AfterOpcode" +) + +// Event represents a logic.EvalTracer event +type Event struct { + Type EventType + + // only for BeforeProgram and AfterProgram + LogicEvalMode logic.RunMode + + // only for BeforeTxn and AfterTxn + TxnType protocol.TxType + + // only for AfterTxn + TxnApplyData transactions.ApplyData + + // only for BeforeTxnGroup and AfterTxnGroup + GroupSize int +} + +// BeforeTxnGroup creates a new Event with the type BeforeTxnGroupEvent +func BeforeTxnGroup(groupSize int) Event { + return Event{Type: BeforeTxnGroupEvent, GroupSize: groupSize} +} + +// AfterTxnGroup creates a new Event with the type AfterTxnGroupEvent +func AfterTxnGroup(groupSize int) Event { + return Event{Type: AfterTxnGroupEvent, GroupSize: groupSize} +} + +// BeforeProgram creates a new Event with the type BeforeProgramEvent +func BeforeProgram(mode logic.RunMode) Event { + return Event{Type: BeforeProgramEvent, LogicEvalMode: mode} +} + +// BeforeTxn creates a new Event with the type BeforeTxnEvent +func BeforeTxn(txnType protocol.TxType) Event { + return Event{Type: BeforeTxnEvent, TxnType: txnType} +} + +// AfterTxn creates a new Event with the type AfterTxnEvent +func AfterTxn(txnType protocol.TxType, ad transactions.ApplyData) Event { + return Event{Type: AfterTxnEvent, TxnType: txnType, TxnApplyData: ad} +} + +// AfterProgram creates a new Event with the type AfterProgramEvent +func AfterProgram(mode logic.RunMode) Event { + return Event{Type: AfterProgramEvent, LogicEvalMode: mode} +} + +// BeforeOpcode creates a new Event with the type BeforeOpcodeEvent +func BeforeOpcode() Event { + return Event{Type: BeforeOpcodeEvent} +} + +// AfterOpcode creates a new Event with the type AfterOpcodeEvent +func AfterOpcode() Event { + return Event{Type: AfterOpcodeEvent} +} + +// Tracer is a mock tracer that implements logic.EvalTracer +type Tracer struct { + Events []Event +} + +// BeforeTxnGroup mocks the logic.EvalTracer.BeforeTxnGroup method +func (d *Tracer) BeforeTxnGroup(ep *logic.EvalParams) { + d.Events = append(d.Events, BeforeTxnGroup(len(ep.TxnGroup))) +} + +// AfterTxnGroup mocks the logic.EvalTracer.AfterTxnGroup method +func (d *Tracer) AfterTxnGroup(ep *logic.EvalParams) { + d.Events = append(d.Events, AfterTxnGroup(len(ep.TxnGroup))) +} + +// BeforeTxn mocks the logic.EvalTracer.BeforeTxn method +func (d *Tracer) BeforeTxn(ep *logic.EvalParams, groupIndex int) { + d.Events = append(d.Events, BeforeTxn(ep.TxnGroup[groupIndex].Txn.Type)) +} + +// AfterTxn mocks the logic.EvalTracer.AfterTxn method +func (d *Tracer) AfterTxn(ep *logic.EvalParams, groupIndex int, ad transactions.ApplyData) { + d.Events = append(d.Events, AfterTxn(ep.TxnGroup[groupIndex].Txn.Type, ad)) +} + +// BeforeProgram mocks the logic.EvalTracer.BeforeProgram method +func (d *Tracer) BeforeProgram(cx *logic.EvalContext) { + d.Events = append(d.Events, BeforeProgram(cx.RunMode())) +} + +// AfterProgram mocks the logic.EvalTracer.AfterProgram method +func (d *Tracer) AfterProgram(cx *logic.EvalContext, evalError error) { + d.Events = append(d.Events, AfterProgram(cx.RunMode())) +} + +// BeforeOpcode mocks the logic.EvalTracer.BeforeOpcode method +func (d *Tracer) BeforeOpcode(cx *logic.EvalContext) { + d.Events = append(d.Events, BeforeOpcode()) +} + +// AfterOpcode mocks the logic.EvalTracer.AfterOpcode method +func (d *Tracer) AfterOpcode(cx *logic.EvalContext, evalError error) { + d.Events = append(d.Events, AfterOpcode()) +} diff --git a/data/transactions/logic/opcodes.go b/data/transactions/logic/opcodes.go index 38dde2e085..8f47b57caf 100644 --- a/data/transactions/logic/opcodes.go +++ b/data/transactions/logic/opcodes.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -121,7 +121,7 @@ type OpDetails struct { check checkFunc // static check bytecode (and determine size) refine refineFunc // refine arg/return types based on ProgramKnowledge at assembly time - Modes runMode // all modes that opcode can run in. i.e (cx.mode & Modes) != 0 allows + Modes RunMode // all modes that opcode can run in. i.e (cx.mode & Modes) != 0 allows FullCost linearCost // if non-zero, the cost of the opcode, no immediates matter Size int // if non-zero, the known size of opcode. if 0, check() determines. @@ -221,13 +221,13 @@ func (d OpDetails) costs(cost int) OpDetails { return d } -func only(m runMode) OpDetails { +func only(m RunMode) OpDetails { d := detDefault() d.Modes = m return d } -func (d OpDetails) only(m runMode) OpDetails { +func (d OpDetails) only(m RunMode) OpDetails { d.Modes = m return d } @@ -419,7 +419,7 @@ var OpSpecs = []OpSpec{ {0x03, "sha512_256", opSHA512_256, proto("b:b"), 7, unlimitedStorage, costByLength(17, 5, 8)}, */ - {0x04, "ed25519verify", opEd25519Verify, proto("bbb:i"), 1, costly(1900).only(modeSig)}, + {0x04, "ed25519verify", opEd25519Verify, proto("bbb:i"), 1, costly(1900).only(ModeSig)}, {0x04, "ed25519verify", opEd25519Verify, proto("bbb:i"), 5, costly(1900)}, {0x05, "ecdsa_verify", opEcdsaVerify, proto("bbbbb:i"), 5, costByField("v", &EcdsaCurves, ecdsaVerifyCosts)}, @@ -463,11 +463,11 @@ var OpSpecs = []OpSpec{ {0x29, "bytec_1", opByteConst1, proto(":b"), 1, detDefault()}, {0x2a, "bytec_2", opByteConst2, proto(":b"), 1, detDefault()}, {0x2b, "bytec_3", opByteConst3, proto(":b"), 1, detDefault()}, - {0x2c, "arg", opArg, proto(":b"), 1, immediates("n").only(modeSig).assembler(asmArg)}, - {0x2d, "arg_0", opArg0, proto(":b"), 1, only(modeSig)}, - {0x2e, "arg_1", opArg1, proto(":b"), 1, only(modeSig)}, - {0x2f, "arg_2", opArg2, proto(":b"), 1, only(modeSig)}, - {0x30, "arg_3", opArg3, proto(":b"), 1, only(modeSig)}, + {0x2c, "arg", opArg, proto(":b"), 1, immediates("n").only(ModeSig).assembler(asmArg)}, + {0x2d, "arg_0", opArg0, proto(":b"), 1, only(ModeSig)}, + {0x2e, "arg_1", opArg1, proto(":b"), 1, only(ModeSig)}, + {0x2f, "arg_2", opArg2, proto(":b"), 1, only(ModeSig)}, + {0x30, "arg_3", opArg3, proto(":b"), 1, only(ModeSig)}, // txn, gtxn, and gtxns are also implemented as pseudoOps to choose // between scalar and array version based on number of immediates. {0x31, "txn", opTxn, proto(":a"), 1, field("f", &TxnScalarFields)}, @@ -481,11 +481,11 @@ var OpSpecs = []OpSpec{ {0x38, "gtxns", opGtxns, proto("i:a"), 3, immediates("f").field("f", &TxnScalarFields)}, {0x39, "gtxnsa", opGtxnsa, proto("i:a"), 3, immediates("f", "i").field("f", &TxnArrayFields)}, // Group scratch space access - {0x3a, "gload", opGload, proto(":a"), 4, immediates("t", "i").only(modeApp)}, - {0x3b, "gloads", opGloads, proto("i:a"), 4, immediates("i").only(modeApp)}, + {0x3a, "gload", opGload, proto(":a"), 4, immediates("t", "i").only(ModeApp)}, + {0x3b, "gloads", opGloads, proto("i:a"), 4, immediates("i").only(ModeApp)}, // Access creatable IDs (consider deprecating, as txn CreatedAssetID, CreatedApplicationID should be enough - {0x3c, "gaid", opGaid, proto(":i"), 4, immediates("t").only(modeApp)}, - {0x3d, "gaids", opGaids, proto("i:i"), 4, only(modeApp)}, + {0x3c, "gaid", opGaid, proto(":i"), 4, immediates("t").only(ModeApp)}, + {0x3d, "gaids", opGaids, proto("i:i"), 4, only(ModeApp)}, // Like load/store, but scratch slot taken from TOS instead of immediate {0x3e, "loads", opLoads, proto("i:a"), 5, typed(typeLoads)}, @@ -526,31 +526,31 @@ var OpSpecs = []OpSpec{ {0x5e, "base64_decode", opBase64Decode, proto("b:b"), fidoVersion, field("e", &Base64Encodings).costByLength(1, 1, 16, 0)}, {0x5f, "json_ref", opJSONRef, proto("bb:a"), fidoVersion, field("r", &JSONRefTypes).costByLength(25, 2, 7, 1)}, - {0x60, "balance", opBalance, proto("i:i"), 2, only(modeApp)}, - {0x60, "balance", opBalance, proto("a:i"), directRefEnabledVersion, only(modeApp)}, - {0x61, "app_opted_in", opAppOptedIn, proto("ii:i"), 2, only(modeApp)}, - {0x61, "app_opted_in", opAppOptedIn, proto("ai:i"), directRefEnabledVersion, only(modeApp)}, - {0x62, "app_local_get", opAppLocalGet, proto("ib:a"), 2, only(modeApp)}, - {0x62, "app_local_get", opAppLocalGet, proto("ab:a"), directRefEnabledVersion, only(modeApp)}, - {0x63, "app_local_get_ex", opAppLocalGetEx, proto("iib:ai"), 2, only(modeApp)}, - {0x63, "app_local_get_ex", opAppLocalGetEx, proto("aib:ai"), directRefEnabledVersion, only(modeApp)}, - {0x64, "app_global_get", opAppGlobalGet, proto("b:a"), 2, only(modeApp)}, - {0x65, "app_global_get_ex", opAppGlobalGetEx, proto("ib:ai"), 2, only(modeApp)}, - {0x66, "app_local_put", opAppLocalPut, proto("iba:"), 2, only(modeApp)}, - {0x66, "app_local_put", opAppLocalPut, proto("aba:"), directRefEnabledVersion, only(modeApp)}, - {0x67, "app_global_put", opAppGlobalPut, proto("ba:"), 2, only(modeApp)}, - {0x68, "app_local_del", opAppLocalDel, proto("ib:"), 2, only(modeApp)}, - {0x68, "app_local_del", opAppLocalDel, proto("ab:"), directRefEnabledVersion, only(modeApp)}, - {0x69, "app_global_del", opAppGlobalDel, proto("b:"), 2, only(modeApp)}, - - {0x70, "asset_holding_get", opAssetHoldingGet, proto("ii:ai"), 2, field("f", &AssetHoldingFields).only(modeApp)}, - {0x70, "asset_holding_get", opAssetHoldingGet, proto("ai:ai"), directRefEnabledVersion, field("f", &AssetHoldingFields).only(modeApp)}, - {0x71, "asset_params_get", opAssetParamsGet, proto("i:ai"), 2, field("f", &AssetParamsFields).only(modeApp)}, - {0x72, "app_params_get", opAppParamsGet, proto("i:ai"), 5, field("f", &AppParamsFields).only(modeApp)}, - {0x73, "acct_params_get", opAcctParamsGet, proto("a:ai"), 6, field("f", &AcctParamsFields).only(modeApp)}, - - {0x78, "min_balance", opMinBalance, proto("i:i"), 3, only(modeApp)}, - {0x78, "min_balance", opMinBalance, proto("a:i"), directRefEnabledVersion, only(modeApp)}, + {0x60, "balance", opBalance, proto("i:i"), 2, only(ModeApp)}, + {0x60, "balance", opBalance, proto("a:i"), directRefEnabledVersion, only(ModeApp)}, + {0x61, "app_opted_in", opAppOptedIn, proto("ii:i"), 2, only(ModeApp)}, + {0x61, "app_opted_in", opAppOptedIn, proto("ai:i"), directRefEnabledVersion, only(ModeApp)}, + {0x62, "app_local_get", opAppLocalGet, proto("ib:a"), 2, only(ModeApp)}, + {0x62, "app_local_get", opAppLocalGet, proto("ab:a"), directRefEnabledVersion, only(ModeApp)}, + {0x63, "app_local_get_ex", opAppLocalGetEx, proto("iib:ai"), 2, only(ModeApp)}, + {0x63, "app_local_get_ex", opAppLocalGetEx, proto("aib:ai"), directRefEnabledVersion, only(ModeApp)}, + {0x64, "app_global_get", opAppGlobalGet, proto("b:a"), 2, only(ModeApp)}, + {0x65, "app_global_get_ex", opAppGlobalGetEx, proto("ib:ai"), 2, only(ModeApp)}, + {0x66, "app_local_put", opAppLocalPut, proto("iba:"), 2, only(ModeApp)}, + {0x66, "app_local_put", opAppLocalPut, proto("aba:"), directRefEnabledVersion, only(ModeApp)}, + {0x67, "app_global_put", opAppGlobalPut, proto("ba:"), 2, only(ModeApp)}, + {0x68, "app_local_del", opAppLocalDel, proto("ib:"), 2, only(ModeApp)}, + {0x68, "app_local_del", opAppLocalDel, proto("ab:"), directRefEnabledVersion, only(ModeApp)}, + {0x69, "app_global_del", opAppGlobalDel, proto("b:"), 2, only(ModeApp)}, + + {0x70, "asset_holding_get", opAssetHoldingGet, proto("ii:ai"), 2, field("f", &AssetHoldingFields).only(ModeApp)}, + {0x70, "asset_holding_get", opAssetHoldingGet, proto("ai:ai"), directRefEnabledVersion, field("f", &AssetHoldingFields).only(ModeApp)}, + {0x71, "asset_params_get", opAssetParamsGet, proto("i:ai"), 2, field("f", &AssetParamsFields).only(ModeApp)}, + {0x72, "app_params_get", opAppParamsGet, proto("i:ai"), 5, field("f", &AppParamsFields).only(ModeApp)}, + {0x73, "acct_params_get", opAcctParamsGet, proto("a:ai"), 6, field("f", &AcctParamsFields).only(ModeApp)}, + + {0x78, "min_balance", opMinBalance, proto("i:i"), 3, only(ModeApp)}, + {0x78, "min_balance", opMinBalance, proto("a:i"), directRefEnabledVersion, only(ModeApp)}, // Immediate bytes and ints. Smaller code size for single use of constant. {0x80, "pushbytes", opPushBytes, proto(":b"), 3, constants(asmPushBytes, opPushBytes, "bytes", immBytes)}, @@ -607,33 +607,33 @@ var OpSpecs = []OpSpec{ {0xaf, "bzero", opBytesZero, proto("i:b"), 4, detDefault()}, // AVM "effects" - {0xb0, "log", opLog, proto("b:"), 5, only(modeApp)}, - {0xb1, "itxn_begin", opTxBegin, proto(":"), 5, only(modeApp)}, - {0xb2, "itxn_field", opItxnField, proto("a:"), 5, immediates("f").typed(typeTxField).field("f", &TxnFields).only(modeApp).assembler(asmItxnField)}, - {0xb3, "itxn_submit", opItxnSubmit, proto(":"), 5, only(modeApp)}, - {0xb4, "itxn", opItxn, proto(":a"), 5, field("f", &TxnScalarFields).only(modeApp).assembler(asmItxn)}, - {0xb5, "itxna", opItxna, proto(":a"), 5, immediates("f", "i").field("f", &TxnArrayFields).only(modeApp)}, - {0xb6, "itxn_next", opItxnNext, proto(":"), 6, only(modeApp)}, - {0xb7, "gitxn", opGitxn, proto(":a"), 6, immediates("t", "f").field("f", &TxnFields).only(modeApp).assembler(asmGitxn)}, - {0xb8, "gitxna", opGitxna, proto(":a"), 6, immediates("t", "f", "i").field("f", &TxnArrayFields).only(modeApp)}, + {0xb0, "log", opLog, proto("b:"), 5, only(ModeApp)}, + {0xb1, "itxn_begin", opTxBegin, proto(":"), 5, only(ModeApp)}, + {0xb2, "itxn_field", opItxnField, proto("a:"), 5, immediates("f").typed(typeTxField).field("f", &TxnFields).only(ModeApp).assembler(asmItxnField)}, + {0xb3, "itxn_submit", opItxnSubmit, proto(":"), 5, only(ModeApp)}, + {0xb4, "itxn", opItxn, proto(":a"), 5, field("f", &TxnScalarFields).only(ModeApp).assembler(asmItxn)}, + {0xb5, "itxna", opItxna, proto(":a"), 5, immediates("f", "i").field("f", &TxnArrayFields).only(ModeApp)}, + {0xb6, "itxn_next", opItxnNext, proto(":"), 6, only(ModeApp)}, + {0xb7, "gitxn", opGitxn, proto(":a"), 6, immediates("t", "f").field("f", &TxnFields).only(ModeApp).assembler(asmGitxn)}, + {0xb8, "gitxna", opGitxna, proto(":a"), 6, immediates("t", "f", "i").field("f", &TxnArrayFields).only(ModeApp)}, // Unlimited Global Storage - Boxes - {0xb9, "box_create", opBoxCreate, proto("bi:i"), boxVersion, only(modeApp)}, - {0xba, "box_extract", opBoxExtract, proto("bii:b"), boxVersion, only(modeApp)}, - {0xbb, "box_replace", opBoxReplace, proto("bib:"), boxVersion, only(modeApp)}, - {0xbc, "box_del", opBoxDel, proto("b:i"), boxVersion, only(modeApp)}, - {0xbd, "box_len", opBoxLen, proto("b:ii"), boxVersion, only(modeApp)}, - {0xbe, "box_get", opBoxGet, proto("b:bi"), boxVersion, only(modeApp)}, - {0xbf, "box_put", opBoxPut, proto("bb:"), boxVersion, only(modeApp)}, + {0xb9, "box_create", opBoxCreate, proto("bi:i"), boxVersion, only(ModeApp)}, + {0xba, "box_extract", opBoxExtract, proto("bii:b"), boxVersion, only(ModeApp)}, + {0xbb, "box_replace", opBoxReplace, proto("bib:"), boxVersion, only(ModeApp)}, + {0xbc, "box_del", opBoxDel, proto("b:i"), boxVersion, only(ModeApp)}, + {0xbd, "box_len", opBoxLen, proto("b:ii"), boxVersion, only(ModeApp)}, + {0xbe, "box_get", opBoxGet, proto("b:bi"), boxVersion, only(ModeApp)}, + {0xbf, "box_put", opBoxPut, proto("bb:"), boxVersion, only(ModeApp)}, // Dynamic indexing {0xc0, "txnas", opTxnas, proto("i:a"), 5, field("f", &TxnArrayFields)}, {0xc1, "gtxnas", opGtxnas, proto("i:a"), 5, immediates("t", "f").field("f", &TxnArrayFields)}, {0xc2, "gtxnsas", opGtxnsas, proto("ii:a"), 5, field("f", &TxnArrayFields)}, - {0xc3, "args", opArgs, proto("i:b"), 5, only(modeSig)}, - {0xc4, "gloadss", opGloadss, proto("ii:a"), 6, only(modeApp)}, - {0xc5, "itxnas", opItxnas, proto("i:a"), 6, field("f", &TxnArrayFields).only(modeApp)}, - {0xc6, "gitxnas", opGitxnas, proto("i:a"), 6, immediates("t", "f").field("f", &TxnArrayFields).only(modeApp)}, + {0xc3, "args", opArgs, proto("i:b"), 5, only(ModeSig)}, + {0xc4, "gloadss", opGloadss, proto("ii:a"), 6, only(ModeApp)}, + {0xc5, "itxnas", opItxnas, proto("i:a"), 6, field("f", &TxnArrayFields).only(ModeApp)}, + {0xc6, "gitxnas", opGitxnas, proto("i:a"), 6, immediates("t", "f").field("f", &TxnArrayFields).only(ModeApp)}, // randomness support {0xd0, "vrf_verify", opVrfVerify, proto("bbb:bi"), randomnessVersion, field("s", &VrfStandards).costs(5700)}, diff --git a/data/transactions/logic/opcodes_test.go b/data/transactions/logic/opcodes_test.go index 4acbc040ec..2ce3648a69 100644 --- a/data/transactions/logic/opcodes_test.go +++ b/data/transactions/logic/opcodes_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -58,7 +58,7 @@ func (os *OpSpec) equals(oso *OpSpec) bool { return true } -func TestOpcodesByVersionReordered(t *testing.T) { +func TestOpcodesByVersionReordered(t *testing.T) { // nolint:paralleltest // manipulates global OpSpecs partitiontest.PartitionTest(t) // Make a copy to restore to the original @@ -82,6 +82,7 @@ func TestOpcodesByVersionReordered(t *testing.T) { func TestOpcodesByVersion(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() testOpcodesByVersion(t) } diff --git a/data/transactions/logic/pairing.go b/data/transactions/logic/pairing.go index 25dfea40a6..315caac701 100644 --- a/data/transactions/logic/pairing.go +++ b/data/transactions/logic/pairing.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/logic/pairing_test.go b/data/transactions/logic/pairing_test.go index 75f6e2bc29..c96bd67197 100644 --- a/data/transactions/logic/pairing_test.go +++ b/data/transactions/logic/pairing_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/logic/parsing.go b/data/transactions/logic/parsing.go deleted file mode 100644 index 7a74292216..0000000000 --- a/data/transactions/logic/parsing.go +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright (C) 2019-2022 Algorand, Inc. -// This file is part of go-algorand -// -// go-algorand is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as -// published by the Free Software Foundation, either version 3 of the -// License, or (at your option) any later version. -// -// go-algorand is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with go-algorand. If not, see . - -package logic - -import ( - "encoding/base32" - "encoding/base64" - "encoding/binary" - "fmt" - "strconv" - "strings" - - "github.com/algorand/avm-abi/abi" - "github.com/algorand/go-algorand/data/basics" -) - -// AppCallBytes represents an encoding and a value of an app call argument. -type AppCallBytes struct { - Encoding string `codec:"encoding"` - Value string `codec:"value"` -} - -// NewAppCallBytes parses an argument of the form "encoding:value" to AppCallBytes. -func NewAppCallBytes(arg string) (AppCallBytes, error) { - parts := strings.SplitN(arg, ":", 2) - if len(parts) != 2 { - return AppCallBytes{}, fmt.Errorf("all arguments and box names should be of the form 'encoding:value'") - } - return AppCallBytes{ - Encoding: parts[0], - Value: parts[1], - }, nil -} - -// Raw converts an AppCallBytes arg to a byte array. -func (arg AppCallBytes) Raw() (rawValue []byte, parseErr error) { - switch arg.Encoding { - case "str", "string": - rawValue = []byte(arg.Value) - case "int", "integer": - num, err := strconv.ParseUint(arg.Value, 10, 64) - if err != nil { - parseErr = fmt.Errorf("Could not parse uint64 from string (%s): %v", arg.Value, err) - return - } - ibytes := make([]byte, 8) - binary.BigEndian.PutUint64(ibytes, num) - rawValue = ibytes - case "addr", "address": - addr, err := basics.UnmarshalChecksumAddress(arg.Value) - if err != nil { - parseErr = fmt.Errorf("Could not unmarshal checksummed address from string (%s): %v", arg.Value, err) - return - } - rawValue = addr[:] - case "b32", "base32", "byte base32": - data, err := base32.StdEncoding.DecodeString(arg.Value) - if err != nil { - parseErr = fmt.Errorf("Could not decode base32-encoded string (%s): %v", arg.Value, err) - return - } - rawValue = data - case "b64", "base64", "byte base64": - data, err := base64.StdEncoding.DecodeString(arg.Value) - if err != nil { - parseErr = fmt.Errorf("Could not decode base64-encoded string (%s): %v", arg.Value, err) - return - } - rawValue = data - case "abi": - typeAndValue := strings.SplitN(arg.Value, ":", 2) - if len(typeAndValue) != 2 { - parseErr = fmt.Errorf("Could not decode abi string (%s): should split abi-type and abi-value with colon", arg.Value) - return - } - abiType, err := abi.TypeOf(typeAndValue[0]) - if err != nil { - parseErr = fmt.Errorf("Could not decode abi type string (%s): %v", typeAndValue[0], err) - return - } - value, err := abiType.UnmarshalFromJSON([]byte(typeAndValue[1])) - if err != nil { - parseErr = fmt.Errorf("Could not decode abi value string (%s):%v ", typeAndValue[1], err) - return - } - return abiType.Encode(value) - default: - parseErr = fmt.Errorf("Unknown encoding: %s", arg.Encoding) - } - return -} diff --git a/data/transactions/logic/parsing_test.go b/data/transactions/logic/parsing_test.go deleted file mode 100644 index 5bc3113b8e..0000000000 --- a/data/transactions/logic/parsing_test.go +++ /dev/null @@ -1,139 +0,0 @@ -// Copyright (C) 2019-2022 Algorand, Inc. -// This file is part of go-algorand -// -// go-algorand is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as -// published by the Free Software Foundation, either version 3 of the -// License, or (at your option) any later version. -// -// go-algorand is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with go-algorand. If not, see . - -package logic - -import ( - "encoding/base32" - "encoding/base64" - "encoding/binary" - "fmt" - "math" - "testing" - - "github.com/algorand/avm-abi/abi" - "github.com/algorand/go-algorand/data/basics" - - "github.com/algorand/go-algorand/test/partitiontest" - "github.com/stretchr/testify/require" -) - -func TestNewAppCallBytes(t *testing.T) { - partitiontest.PartitionTest(t) - t.Parallel() - - t.Run("errors", func(t *testing.T) { - _, err := NewAppCallBytes("hello") - require.Error(t, err) - - for _, v := range []string{":x", "int:-1"} { - acb, err := NewAppCallBytes(v) - _, err = acb.Raw() - require.Error(t, err) - } - }) - - for _, v := range []string{"hello", "1:2"} { - for _, e := range []string{"str", "string"} { - t.Run(fmt.Sprintf("encoding=%v,value=%v", e, v), func(t *testing.T) { - acb, err := NewAppCallBytes(fmt.Sprintf("%v:%v", e, v)) - require.NoError(t, err) - r, err := acb.Raw() - require.NoError(t, err) - require.Equal(t, v, string(r)) - }) - } - - for _, e := range []string{"b32", "base32", "byte base32"} { - ve := base32.StdEncoding.EncodeToString([]byte(v)) - t.Run(fmt.Sprintf("encoding=%v,value=%v", e, ve), func(t *testing.T) { - acb, err := NewAppCallBytes(fmt.Sprintf("%v:%v", e, ve)) - require.NoError(t, err) - r, err := acb.Raw() - require.NoError(t, err) - require.Equal(t, ve, base32.StdEncoding.EncodeToString(r)) - }) - } - - for _, e := range []string{"b64", "base64", "byte base64"} { - ve := base64.StdEncoding.EncodeToString([]byte(v)) - t.Run(fmt.Sprintf("encoding=%v,value=%v", e, ve), func(t *testing.T) { - acb, err := NewAppCallBytes(fmt.Sprintf("%v:%v", e, ve)) - require.NoError(t, err) - r, err := acb.Raw() - require.NoError(t, err) - require.Equal(t, ve, base64.StdEncoding.EncodeToString(r)) - }) - } - } - - for _, v := range []uint64{1, 0, math.MaxUint64} { - for _, e := range []string{"int", "integer"} { - t.Run(fmt.Sprintf("encoding=%v,value=%v", e, v), func(t *testing.T) { - acb, err := NewAppCallBytes(fmt.Sprintf("%v:%v", e, v)) - require.NoError(t, err) - r, err := acb.Raw() - require.NoError(t, err) - require.Equal(t, v, binary.BigEndian.Uint64(r)) - }) - } - } - - for _, v := range []string{"737777777777777777777777777777777777777777777777777UFEJ2CI"} { - for _, e := range []string{"addr", "address"} { - t.Run(fmt.Sprintf("encoding=%v,value=%v", e, v), func(t *testing.T) { - acb, err := NewAppCallBytes(fmt.Sprintf("%v:%v", e, v)) - require.NoError(t, err) - r, err := acb.Raw() - require.NoError(t, err) - addr, err := basics.UnmarshalChecksumAddress(v) - require.NoError(t, err) - expectedBytes := []byte{} - expectedBytes = addr[:] - require.Equal(t, expectedBytes, r) - }) - } - } - - type abiCase struct { - abiType, rawValue string - } - for _, v := range []abiCase{ - { - `(uint64,string,bool[])`, - `[399,"should pass",[true,false,false,true]]`, - }} { - for _, e := range []string{"abi"} { - t.Run(fmt.Sprintf("encoding=%v,value=%v", e, v), func(t *testing.T) { - acb, err := NewAppCallBytes(fmt.Sprintf( - "%v:%v:%v", e, v.abiType, v.rawValue)) - require.NoError(t, err) - r, err := acb.Raw() - require.NoError(t, err) - require.NotEmpty(t, r) - - // Confirm round-trip works. - abiType, err := abi.TypeOf(v.abiType) - require.NoError(t, err) - d, err := abiType.Decode(r) - require.NoError(t, err) - vv, err := abiType.Encode(d) - require.NoError(t, err) - require.Equal(t, r, vv) - }) - } - } -} diff --git a/data/transactions/logic/program.go b/data/transactions/logic/program.go index 58265b7f98..4568ebe744 100644 --- a/data/transactions/logic/program.go +++ b/data/transactions/logic/program.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/logic/sourcemap.go b/data/transactions/logic/sourcemap.go index b2e6e2acc7..3580a120e7 100644 --- a/data/transactions/logic/sourcemap.go +++ b/data/transactions/logic/sourcemap.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/logic/sourcemap_test.go b/data/transactions/logic/sourcemap_test.go index 7407f39ddd..c3b1a73a80 100644 --- a/data/transactions/logic/sourcemap_test.go +++ b/data/transactions/logic/sourcemap_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -26,6 +26,7 @@ import ( func TestGetSourceMap(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() a := require.New(t) sourceNames := []string{"test.teal"} @@ -55,6 +56,7 @@ func TestGetSourceMap(t *testing.T) { func TestVLQ(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() a := require.New(t) a.Equal("AAAA", MakeSourceMapLine(0, 0, 0, 0)) diff --git a/data/transactions/logic/tracer.go b/data/transactions/logic/tracer.go new file mode 100644 index 0000000000..929d5cdb89 --- /dev/null +++ b/data/transactions/logic/tracer.go @@ -0,0 +1,160 @@ +// Copyright (C) 2019-2023 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package logic + +import "github.com/algorand/go-algorand/data/transactions" + +// EvalTracer functions are called by eval function during AVM program execution, if a tracer +// is provided. +// +// Refer to the lifecycle graph below for the sequence in which hooks are called. +// +// NOTE: Arguments given to Tracer hooks (EvalParams and EvalContext) are passed by reference, +// they are not copies. It is therefore the responsibility of the tracer implementation to NOT +// modify the state of the structs passed to them. Additionally, hooks are responsible for copying +// the information they need from the argument structs. No guarantees are made that the referenced +// state will not change between hook calls. This decision was made in an effort to reduce the +// performance impact of tracers. +// +// LOGICSIG LIFECYCLE GRAPH +// ┌─────────────────────────┐ +// │ LogicSig Evaluation │ +// ├─────────────────────────┤ +// │ > BeforeProgram │ +// │ │ +// │ ┌───────────────────┐ │ +// │ │ Teal Operation │ │ +// │ ├───────────────────┤ │ +// │ │ > BeforeOpcode │ │ +// │ │ │ │ +// │ │ > AfterOpcode │ │ +// │ └───────────────────┘ │ +// | ⁞ ⁞ ⁞ ⁞ ⁞ ⁞ ⁞ │ +// │ │ +// │ > AfterProgram │ +// └─────────────────────────┘ +// +// APP LIFECYCLE GRAPH +// ┌──────────────────────────────────────────────────────┐ +// │ Transaction Evaluation │ +// ├──────────────────────────────────────────────────────┤ +// │ > BeforeTxnGroup │ +// │ │ +// │ ┌────────────────────────────────────────────────┐ │ +// │ │ > BeforeTxn │ │ +// │ │ │ │ +// │ │ ┌──────────────────────────────────────────┐ │ │ +// │ │ │ ? App Call │ │ │ +// │ │ ├──────────────────────────────────────────┤ │ │ +// │ │ │ > BeforeProgram │ │ │ +// │ │ │ │ │ │ +// │ │ │ ┌────────────────────────────────────┐ │ │ │ +// │ │ │ │ Teal Operation │ │ │ │ +// │ │ │ ├────────────────────────────────────┤ │ │ │ +// │ │ │ │ > BeforeOpcode │ │ │ │ +// │ │ │ │ ┌──────────────────────────────┐ │ │ │ │ +// │ │ │ │ │ ? Inner Transaction Group │ │ │ │ │ +// │ │ │ │ ├──────────────────────────────┤ │ │ │ │ +// │ │ │ │ │ > BeforeTxnGroup │ │ │ │ │ +// │ │ │ │ │ ┌────────────────────────┐ │ │ │ │ │ +// │ │ │ │ │ │ Transaction Evaluation │ │ │ │ │ │ +// │ │ │ │ │ ├────────────────────────┤ │ │ │ │ │ +// │ │ │ │ │ │ ... │ │ │ │ │ │ +// │ │ │ │ │ └────────────────────────┘ │ │ │ │ │ +// │ │ │ │ │ ⁞ ⁞ ⁞ ⁞ ⁞ ⁞ ⁞ ⁞ │ │ │ │ │ +// │ │ │ │ │ │ │ │ │ │ +// │ │ │ │ │ > AfterTxnGroup │ │ │ │ │ +// │ │ │ │ └──────────────────────────────┘ │ │ │ │ +// │ │ │ │ > AfterOpcode │ │ │ │ +// │ │ │ └────────────────────────────────────┘ │ │ │ +// │ │ │ ⁞ ⁞ ⁞ ⁞ ⁞ ⁞ ⁞ ⁞ ⁞ ⁞ ⁞ ⁞ │ │ │ +// │ │ │ │ │ │ +// │ │ │ > AfterProgram │ │ │ +// │ │ └──────────────────────────────────────────┘ │ │ +// | | ⁞ ⁞ ⁞ ⁞ ⁞ ⁞ ⁞ ⁞ ⁞ ⁞ ⁞ ⁞ ⁞ ⁞ │ | +// │ │ │ │ +// │ │ > AfterTxn │ │ +// │ └────────────────────────────────────────────────┘ │ +// | ⁞ ⁞ ⁞ ⁞ ⁞ ⁞ ⁞ ⁞ ⁞ ⁞ ⁞ ⁞ ⁞ ⁞ ⁞ ⁞ | +// │ │ +// │ > AfterTxnGroup │ +// └──────────────────────────────────────────────────────┘ +type EvalTracer interface { + // BeforeTxnGroup is called before a transaction group is executed. This includes both top-level + // and inner transaction groups. The argument ep is the EvalParams object for the group; if the + // group is an inner group, this is the EvalParams object for the inner group. + // + // Each transaction within the group calls BeforeTxn and subsequent hooks, as described in the + // lifecycle diagram. + BeforeTxnGroup(ep *EvalParams) + + // AfterTxnGroup is called after a transaction group has been executed. This includes both + // top-level and inner transaction groups. The argument ep is the EvalParams object for the + // group; if the group is an inner group, this is the EvalParams object for the inner group. + AfterTxnGroup(ep *EvalParams) + + // BeforeTxn is called before a transaction is executed. + // + // groupIndex refers to the index of the transaction in the transaction group that will be executed. + BeforeTxn(ep *EvalParams, groupIndex int) + + // AfterTxn is called after a transaction has been executed. + // + // groupIndex refers to the index of the transaction in the transaction group that was just executed. + // ad is the ApplyData result of the transaction; prefer using this instead of + // ep.TxnGroup[groupIndex].ApplyData, since it may not be populated at this point. + AfterTxn(ep *EvalParams, groupIndex int, ad transactions.ApplyData) + + // BeforeProgram is called before an app or LogicSig program is evaluated. + BeforeProgram(cx *EvalContext) + + // AfterProgram is called after an app or LogicSig program is evaluated. + AfterProgram(cx *EvalContext, evalError error) + + // BeforeOpcode is called before the op is evaluated + BeforeOpcode(cx *EvalContext) + + // AfterOpcode is called after the op has been evaluated + AfterOpcode(cx *EvalContext, evalError error) +} + +// NullEvalTracer implements EvalTracer, but all of its hook methods do nothing +type NullEvalTracer struct{} + +// BeforeTxnGroup does nothing +func (n NullEvalTracer) BeforeTxnGroup(ep *EvalParams) {} + +// AfterTxnGroup does nothing +func (n NullEvalTracer) AfterTxnGroup(ep *EvalParams) {} + +// BeforeTxn does nothing +func (n NullEvalTracer) BeforeTxn(ep *EvalParams, groupIndex int) {} + +// AfterTxn does nothing +func (n NullEvalTracer) AfterTxn(ep *EvalParams, groupIndex int, ad transactions.ApplyData) {} + +// BeforeProgram does nothing +func (n NullEvalTracer) BeforeProgram(cx *EvalContext) {} + +// AfterProgram does nothing +func (n NullEvalTracer) AfterProgram(cx *EvalContext, evalError error) {} + +// BeforeOpcode does nothing +func (n NullEvalTracer) BeforeOpcode(cx *EvalContext) {} + +// AfterOpcode does nothing +func (n NullEvalTracer) AfterOpcode(cx *EvalContext, evalError error) {} diff --git a/data/transactions/logic/tracer_test.go b/data/transactions/logic/tracer_test.go new file mode 100644 index 0000000000..c47f3d1927 --- /dev/null +++ b/data/transactions/logic/tracer_test.go @@ -0,0 +1,195 @@ +// Copyright (C) 2019-2023 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package logic + +import ( + "testing" + + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/data/transactions" + "github.com/algorand/go-algorand/test/partitiontest" + "github.com/stretchr/testify/require" +) + +const innerTxnTestProgram string = `itxn_begin +int appl +itxn_field TypeEnum +int NoOp +itxn_field OnCompletion +byte 0x068101 // #pragma version 6; int 1; +dup +itxn_field ApprovalProgram +itxn_field ClearStateProgram +itxn_submit + +itxn_begin +int pay +itxn_field TypeEnum +int 1 +itxn_field Amount +global CurrentApplicationAddress +itxn_field Receiver +itxn_next +int pay +itxn_field TypeEnum +int 2 +itxn_field Amount +global CurrentApplicationAddress +itxn_field Receiver +itxn_submit + +int 1 +` + +// can't use mocktracer.Tracer because the import would be circular +type testEvalTracer struct { + beforeTxnGroupCalls int + afterTxnGroupCalls int + + beforeTxnCalls int + afterTxnCalls int + + beforeProgramCalls int + afterProgramCalls int + programModes []RunMode + + beforeOpcodeCalls int + afterOpcodeCalls int +} + +func (t *testEvalTracer) BeforeTxnGroup(ep *EvalParams) { + t.beforeTxnGroupCalls++ +} + +func (t *testEvalTracer) AfterTxnGroup(ep *EvalParams) { + t.afterTxnGroupCalls++ +} + +func (t *testEvalTracer) BeforeTxn(ep *EvalParams, groupIndex int) { + t.beforeTxnCalls++ +} + +func (t *testEvalTracer) AfterTxn(ep *EvalParams, groupIndex int, ad transactions.ApplyData) { + t.afterTxnCalls++ +} + +func (t *testEvalTracer) BeforeProgram(cx *EvalContext) { + t.beforeProgramCalls++ + t.programModes = append(t.programModes, cx.RunMode()) +} + +func (t *testEvalTracer) AfterProgram(cx *EvalContext, evalError error) { + t.afterProgramCalls++ +} + +func (t *testEvalTracer) BeforeOpcode(cx *EvalContext) { + t.beforeOpcodeCalls++ +} + +func (t *testEvalTracer) AfterOpcode(cx *EvalContext, evalError error) { + t.afterOpcodeCalls++ +} + +func TestEvalWithTracer(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + t.Run("logicsig", func(t *testing.T) { + t.Parallel() + testTracer := testEvalTracer{} + ep := defaultEvalParams() + ep.Tracer = &testTracer + testLogic(t, debuggerTestProgram, AssemblerMaxVersion, ep) + + // BeforeTxnGroup/AfterTxnGroup/BeforeTxn/AfterTxn are only called for the inner txns in + // this test, not the top-level ones + require.Zero(t, testTracer.beforeTxnGroupCalls) + require.Zero(t, testTracer.afterTxnGroupCalls) + require.Zero(t, testTracer.beforeTxnCalls) + require.Zero(t, testTracer.afterTxnCalls) + + require.Equal(t, 1, testTracer.beforeProgramCalls) + require.Equal(t, 1, testTracer.afterProgramCalls) + require.Equal(t, []RunMode{ModeSig}, testTracer.programModes) + + require.Equal(t, 35, testTracer.beforeOpcodeCalls) + require.Equal(t, testTracer.beforeOpcodeCalls, testTracer.afterOpcodeCalls) + }) + + t.Run("simple app", func(t *testing.T) { + t.Parallel() + testTracer := testEvalTracer{} + ep := defaultEvalParams() + ep.Tracer = &testTracer + testApp(t, debuggerTestProgram, ep) + + // BeforeTxnGroup/AfterTxnGroup/BeforeTxn/AfterTxn are only called for the inner txns in + // this test, not the top-level ones + require.Zero(t, testTracer.beforeTxnGroupCalls) + require.Zero(t, testTracer.afterTxnGroupCalls) + require.Zero(t, testTracer.beforeTxnCalls) + require.Zero(t, testTracer.afterTxnCalls) + + require.Equal(t, 1, testTracer.beforeProgramCalls) + require.Equal(t, 1, testTracer.afterProgramCalls) + require.Equal(t, []RunMode{ModeApp}, testTracer.programModes) + + require.Equal(t, 35, testTracer.beforeOpcodeCalls) + require.Equal(t, testTracer.beforeOpcodeCalls, testTracer.afterOpcodeCalls) + }) + + t.Run("app with inner txns", func(t *testing.T) { + t.Parallel() + testTracer := testEvalTracer{} + ep, tx, ledger := MakeSampleEnv() + + // Establish 888 as the app id, and fund it. + ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) + ledger.NewAccount(basics.AppIndex(888).Address(), 200000) + + ep.Tracer = &testTracer + testApp(t, innerTxnTestProgram, ep) + + // BeforeTxnGroup/AfterTxnGroup/BeforeTxn/AfterTxn are only called for the inner txns in + // this test, not the top-level ones + + // two groups of inner txns were issued + require.Equal(t, 2, testTracer.beforeTxnGroupCalls) + require.Equal(t, 2, testTracer.afterTxnGroupCalls) + + // three total inner txns were issued + require.Equal(t, 3, testTracer.beforeTxnCalls) + require.Equal(t, 3, testTracer.afterTxnCalls) + + require.Equal(t, 2, testTracer.beforeProgramCalls) + require.Equal(t, 2, testTracer.afterProgramCalls) + require.Equal(t, []RunMode{ModeApp, ModeApp}, testTracer.programModes) + + appCallTealOps := 27 + innerAppCallTealOps := 1 + require.Equal(t, appCallTealOps+innerAppCallTealOps, testTracer.beforeOpcodeCalls) + require.Equal(t, testTracer.beforeOpcodeCalls, testTracer.afterOpcodeCalls) + }) +} + +func TestNullEvalTracerIsEvalTracer(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + var tracer EvalTracer = NullEvalTracer{} + require.NotNil(t, tracer) +} diff --git a/data/transactions/logicsig.go b/data/transactions/logicsig.go index 03a520fc65..e2869a89b6 100644 --- a/data/transactions/logicsig.go +++ b/data/transactions/logicsig.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/payment.go b/data/transactions/payment.go index 7676315a36..fee9c395ee 100644 --- a/data/transactions/payment.go +++ b/data/transactions/payment.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/payment_test.go b/data/transactions/payment_test.go index 7f88c7e793..5d3f83cba7 100644 --- a/data/transactions/payment_test.go +++ b/data/transactions/payment_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/payset.go b/data/transactions/payset.go index c75755788b..f005c6b4d9 100644 --- a/data/transactions/payset.go +++ b/data/transactions/payset.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/payset_test.go b/data/transactions/payset_test.go index 9446f45b61..63d3afc424 100644 --- a/data/transactions/payset_test.go +++ b/data/transactions/payset_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/perf_test.go b/data/transactions/perf_test.go index 1aa0bed495..cda1ea5be8 100644 --- a/data/transactions/perf_test.go +++ b/data/transactions/perf_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/signedtxn.go b/data/transactions/signedtxn.go index 08ce6fad95..4f0823d7ff 100644 --- a/data/transactions/signedtxn.go +++ b/data/transactions/signedtxn.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/signedtxn_test.go b/data/transactions/signedtxn_test.go index 92de12182e..d292e66890 100644 --- a/data/transactions/signedtxn_test.go +++ b/data/transactions/signedtxn_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/sort.go b/data/transactions/sort.go index f0f10286fc..3ca8e4a569 100644 --- a/data/transactions/sort.go +++ b/data/transactions/sort.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/stateproof.go b/data/transactions/stateproof.go index 35ffe0ff1f..4bc7c9a246 100644 --- a/data/transactions/stateproof.go +++ b/data/transactions/stateproof.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/teal.go b/data/transactions/teal.go index 3851a03e29..9472fd3b92 100644 --- a/data/transactions/teal.go +++ b/data/transactions/teal.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/teal_test.go b/data/transactions/teal_test.go index e5920d0f18..52ec80e681 100644 --- a/data/transactions/teal_test.go +++ b/data/transactions/teal_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/testhelpers.go b/data/transactions/testhelpers.go index 8c981791c2..99baadb1a3 100644 --- a/data/transactions/testhelpers.go +++ b/data/transactions/testhelpers.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/transaction.go b/data/transactions/transaction.go index e0d908fd63..6c1b56fd29 100644 --- a/data/transactions/transaction.go +++ b/data/transactions/transaction.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/transaction_test.go b/data/transactions/transaction_test.go index 43467d3fd1..51f69ce714 100644 --- a/data/transactions/transaction_test.go +++ b/data/transactions/transaction_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/verify/artifact_test.go b/data/transactions/verify/artifact_test.go index 8444dfa353..226ac54491 100644 --- a/data/transactions/verify/artifact_test.go +++ b/data/transactions/verify/artifact_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -77,7 +77,7 @@ func BenchmarkTinyMan(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { - _, err := TxnGroup(stxnss[i], hdr, nil, &logic.NoHeaderLedger{}) + _, err := TxnGroup(stxnss[i], &hdr, nil, &logic.NoHeaderLedger{}) require.NoError(b, err) } }) @@ -93,7 +93,7 @@ func BenchmarkTinyMan(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { - _, err := TxnGroup(stxns, hdr, nil, &logic.NoHeaderLedger{}) + _, err := TxnGroup(stxns, &hdr, nil, &logic.NoHeaderLedger{}) require.NoError(b, err) } }) diff --git a/data/transactions/verify/txn.go b/data/transactions/verify/txn.go index a4fe2ebd4d..2edd69aa05 100644 --- a/data/transactions/verify/txn.go +++ b/data/transactions/verify/txn.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -21,6 +21,9 @@ import ( "encoding/binary" "errors" "fmt" + "sync" + "sync/atomic" + "time" "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" @@ -28,6 +31,8 @@ import ( "github.com/algorand/go-algorand/data/bookkeeping" "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/data/transactions/logic" + "github.com/algorand/go-algorand/ledger/ledgercore" + "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/util/execpool" "github.com/algorand/go-algorand/util/metrics" @@ -44,6 +49,8 @@ var msigLsigLessOrEqual4 = metrics.MakeCounter(metrics.MetricName{Name: "algod_v var msigLsigLessOrEqual10 = metrics.MakeCounter(metrics.MetricName{Name: "algod_verify_msig_lsig_5_10", Description: "Total transaction scripts with 5-10 msigs"}) var msigLsigMore10 = metrics.MakeCounter(metrics.MetricName{Name: "algod_verify_msig_lsig_10", Description: "Total transaction scripts with 11+ msigs"}) +var errShuttingDownError = errors.New("not verified, verifier is shutting down") + // The PaysetGroups is taking large set of transaction groups and attempt to verify their validity using multiple go-routines. // When doing so, it attempts to break these into smaller "worksets" where each workset takes about 2ms of execution time in order // to avoid context switching overhead while providing good validation cancellation responsiveness. Each one of these worksets is @@ -51,6 +58,19 @@ var msigLsigMore10 = metrics.MakeCounter(metrics.MetricName{Name: "algod_verify_ // show that these are realistic numbers ) const txnPerWorksetThreshold = 32 +// batchSizeBlockLimit is the limit when the batch exceeds, will be added to the exec pool, even if the pool is saturated +// and the batch verifier will block until the exec pool accepts the batch +const batchSizeBlockLimit = 1024 + +// waitForNextTxnDuration is the time to wait before sending the batch to the exec pool +// If the incoming txn rate is low, a txn in the batch may wait no less than +// waitForNextTxnDuration before it is set for verification. +// This can introduce a latency to the propagation of a transaction in the network, +// since every relay will go through this wait time before broadcasting the txn. +// However, when the incoming txn rate is high, the batch will fill up quickly and will send +// for signature evaluation before waitForNextTxnDuration. +const waitForNextTxnDuration = 2 * time.Millisecond + // When the PaysetGroups is generating worksets, it enqueues up to concurrentWorksets entries to the execution pool. This serves several // purposes : // - if the verification task need to be aborted, there are only concurrentWorksets entries that are currently redundant on the execution pool queue. @@ -124,7 +144,7 @@ func (e *TxGroupError) Unwrap() error { // PrepareGroupContext prepares a verification group parameter object for a given transaction // group. -func PrepareGroupContext(group []transactions.SignedTxn, contextHdr bookkeeping.BlockHeader, ledger logic.LedgerForSignature) (*GroupContext, error) { +func PrepareGroupContext(group []transactions.SignedTxn, contextHdr *bookkeeping.BlockHeader, ledger logic.LedgerForSignature) (*GroupContext, error) { if len(group) == 0 { return nil, nil } @@ -155,7 +175,7 @@ func (g *GroupContext) Equal(other *GroupContext) bool { // txnBatchPrep verifies a SignedTxn having no obviously inconsistent data. // Block-assembly time checks of LogicSig and accounting rules may still block the txn. // It is the caller responsibility to call batchVerifier.Verify(). -func txnBatchPrep(s *transactions.SignedTxn, txnIdx int, groupCtx *GroupContext, verifier *crypto.BatchVerifier) *TxGroupError { +func txnBatchPrep(s *transactions.SignedTxn, txnIdx int, groupCtx *GroupContext, verifier *crypto.BatchVerifier, evalTracer logic.EvalTracer) *TxGroupError { if !groupCtx.consensusParams.SupportRekeying && (s.AuthAddr != basics.Address{}) { return &TxGroupError{err: errRekeyingNotSupported, Reason: TxGroupErrorReasonGeneric} } @@ -164,14 +184,23 @@ func txnBatchPrep(s *transactions.SignedTxn, txnIdx int, groupCtx *GroupContext, return &TxGroupError{err: err, Reason: TxGroupErrorReasonNotWellFormed} } - return stxnCoreChecks(s, txnIdx, groupCtx, verifier) + return stxnCoreChecks(s, txnIdx, groupCtx, verifier, evalTracer) } // TxnGroup verifies a []SignedTxn as being signed and having no obviously inconsistent data. -func TxnGroup(stxs []transactions.SignedTxn, contextHdr bookkeeping.BlockHeader, cache VerifiedTransactionCache, ledger logic.LedgerForSignature) (groupCtx *GroupContext, err error) { +func TxnGroup(stxs []transactions.SignedTxn, contextHdr *bookkeeping.BlockHeader, cache VerifiedTransactionCache, ledger logic.LedgerForSignature) (groupCtx *GroupContext, err error) { + return txnGroup(stxs, contextHdr, cache, ledger, nil) +} + +// TxnGroupWithTracer verifies a []SignedTxn as being signed and having no obviously inconsistent data, while using a tracer. +func TxnGroupWithTracer(stxs []transactions.SignedTxn, contextHdr *bookkeeping.BlockHeader, cache VerifiedTransactionCache, ledger logic.LedgerForSignature, evalTracer logic.EvalTracer) (groupCtx *GroupContext, err error) { + return txnGroup(stxs, contextHdr, cache, ledger, evalTracer) +} + +func txnGroup(stxs []transactions.SignedTxn, contextHdr *bookkeeping.BlockHeader, cache VerifiedTransactionCache, ledger logic.LedgerForSignature, evalTracer logic.EvalTracer) (groupCtx *GroupContext, err error) { batchVerifier := crypto.MakeBatchVerifier() - if groupCtx, err = txnGroupBatchPrep(stxs, contextHdr, ledger, batchVerifier); err != nil { + if groupCtx, err = txnGroupBatchPrep(stxs, contextHdr, ledger, batchVerifier, evalTracer); err != nil { return nil, err } @@ -188,7 +217,7 @@ func TxnGroup(stxs []transactions.SignedTxn, contextHdr bookkeeping.BlockHeader, // txnGroupBatchPrep verifies a []SignedTxn having no obviously inconsistent data. // it is the caller responsibility to call batchVerifier.Verify() -func txnGroupBatchPrep(stxs []transactions.SignedTxn, contextHdr bookkeeping.BlockHeader, ledger logic.LedgerForSignature, verifier *crypto.BatchVerifier) (*GroupContext, error) { +func txnGroupBatchPrep(stxs []transactions.SignedTxn, contextHdr *bookkeeping.BlockHeader, ledger logic.LedgerForSignature, verifier *crypto.BatchVerifier, evalTracer logic.EvalTracer) (*GroupContext, error) { groupCtx, err := PrepareGroupContext(stxs, contextHdr, ledger) if err != nil { return nil, err @@ -197,7 +226,7 @@ func txnGroupBatchPrep(stxs []transactions.SignedTxn, contextHdr bookkeeping.Blo minFeeCount := uint64(0) feesPaid := uint64(0) for i, stxn := range stxs { - prepErr := txnBatchPrep(&stxn, i, groupCtx, verifier) + prepErr := txnBatchPrep(&stxn, i, groupCtx, verifier, evalTracer) if prepErr != nil { // re-wrap the error with more details prepErr.err = fmt.Errorf("transaction %+v invalid : %w", stxn, prepErr.err) @@ -229,43 +258,56 @@ func txnGroupBatchPrep(stxs []transactions.SignedTxn, contextHdr bookkeeping.Blo return groupCtx, nil } -// stxnCoreChecks runs signatures validity checks and enqueues signature into batchVerifier for verification. -func stxnCoreChecks(s *transactions.SignedTxn, txnIdx int, groupCtx *GroupContext, batchVerifier *crypto.BatchVerifier) *TxGroupError { - numSigs := 0 - hasSig := false - hasMsig := false - hasLogicSig := false +type sigOrTxnType int + +const regularSig sigOrTxnType = 1 +const multiSig sigOrTxnType = 2 +const logicSig sigOrTxnType = 3 +const stateProofTxn sigOrTxnType = 4 + +// checkTxnSigTypeCounts checks the number of signature types and reports an error in case of a violation +func checkTxnSigTypeCounts(s *transactions.SignedTxn) (sigType sigOrTxnType, err *TxGroupError) { + numSigCategories := 0 if s.Sig != (crypto.Signature{}) { - numSigs++ - hasSig = true + numSigCategories++ + sigType = regularSig } if !s.Msig.Blank() { - numSigs++ - hasMsig = true + numSigCategories++ + sigType = multiSig } if !s.Lsig.Blank() { - numSigs++ - hasLogicSig = true + numSigCategories++ + sigType = logicSig } - if numSigs == 0 { + if numSigCategories == 0 { // Special case: special sender address can issue special transaction // types (state proof txn) without any signature. The well-formed // check ensures that this transaction cannot pay any fee, and // cannot have any other interesting fields, except for the state proof payload. if s.Txn.Sender == transactions.StateProofSender && s.Txn.Type == protocol.StateProofTx { - return nil + return stateProofTxn, nil } - return &TxGroupError{err: errTxnSigHasNoSig, Reason: TxGroupErrorReasonHasNoSig} + return 0, &TxGroupError{err: errTxnSigHasNoSig, Reason: TxGroupErrorReasonHasNoSig} } - if numSigs > 1 { - return &TxGroupError{err: errTxnSigNotWellFormed, Reason: TxGroupErrorReasonSigNotWellFormed} + if numSigCategories > 1 { + return 0, &TxGroupError{err: errTxnSigNotWellFormed, Reason: TxGroupErrorReasonSigNotWellFormed} } + return sigType, nil +} - if hasSig { +// stxnCoreChecks runs signatures validity checks and enqueues signature into batchVerifier for verification. +func stxnCoreChecks(s *transactions.SignedTxn, txnIdx int, groupCtx *GroupContext, batchVerifier *crypto.BatchVerifier, evalTracer logic.EvalTracer) *TxGroupError { + sigType, err := checkTxnSigTypeCounts(s) + if err != nil { + return err + } + + switch sigType { + case regularSig: batchVerifier.EnqueueSignature(crypto.SignatureVerifier(s.Authorizer()), s.Txn, s.Sig) return nil - } - if hasMsig { + case multiSig: if err := crypto.MultisigBatchPrep(s.Txn, crypto.Digest(s.Authorizer()), s.Msig, batchVerifier); err != nil { return &TxGroupError{err: fmt.Errorf("multisig validation failed: %w", err), Reason: TxGroupErrorReasonMsigNotWellFormed} } @@ -283,14 +325,19 @@ func stxnCoreChecks(s *transactions.SignedTxn, txnIdx int, groupCtx *GroupContex msigMore10.Inc(nil) } return nil - } - if hasLogicSig { - if err := logicSigVerify(s, txnIdx, groupCtx); err != nil { + + case logicSig: + if err := logicSigVerify(s, txnIdx, groupCtx, evalTracer); err != nil { return &TxGroupError{err: err, Reason: TxGroupErrorReasonLogicSigFailed} } return nil + + case stateProofTxn: + return nil + + default: + return &TxGroupError{err: errUnknownSignature, Reason: TxGroupErrorReasonGeneric} } - return &TxGroupError{err: errUnknownSignature, Reason: TxGroupErrorReasonGeneric} } // LogicSigSanityCheck checks that the signature is valid and that the program is basically well formed. @@ -390,7 +437,7 @@ func logicSigSanityCheckBatchPrep(txn *transactions.SignedTxn, groupIndex int, g } // logicSigVerify checks that the signature is valid, executing the program. -func logicSigVerify(txn *transactions.SignedTxn, groupIndex int, groupCtx *GroupContext) error { +func logicSigVerify(txn *transactions.SignedTxn, groupIndex int, groupCtx *GroupContext, evalTracer logic.EvalTracer) error { err := LogicSigSanityCheck(txn, groupIndex, groupCtx) if err != nil { return err @@ -404,6 +451,7 @@ func logicSigVerify(txn *transactions.SignedTxn, groupIndex int, groupCtx *Group TxnGroup: transactions.WrapSignedTxnsWithAD(groupCtx.signedGroupTxns), MinAvmVersion: &groupCtx.minAvmVersion, SigLedger: groupCtx.ledger, + Tracer: evalTracer, } pass, cx, err := logic.EvalSignatureFull(groupIndex, &ep) if err != nil { @@ -463,7 +511,7 @@ func PaysetGroups(ctx context.Context, payset [][]transactions.SignedTxn, blkHea batchVerifier := crypto.MakeBatchVerifierWithHint(len(payset)) for i, signTxnsGrp := range txnGroups { - groupCtxs[i], grpErr = txnGroupBatchPrep(signTxnsGrp, blkHeader, ledger, batchVerifier) + groupCtxs[i], grpErr = txnGroupBatchPrep(signTxnsGrp, &blkHeader, ledger, batchVerifier, nil) // abort only if it's a non-cache error. if grpErr != nil { return grpErr @@ -536,3 +584,377 @@ func (w *worksetBuilder) next() (txnGroups [][]transactions.SignedTxn) { func (w *worksetBuilder) completed() bool { return w.idx >= len(w.payset) } + +// UnverifiedElement is the element passed to the Stream verifier +// BacklogMessage is a *txBacklogMsg from data/txHandler.go which needs to be +// passed back to that context +type UnverifiedElement struct { + TxnGroup []transactions.SignedTxn + BacklogMessage interface{} +} + +// VerificationResult is the result of the txn group verification +// BacklogMessage is the reference associated with the txn group which was +// initially passed to the stream verifier +type VerificationResult struct { + TxnGroup []transactions.SignedTxn + BacklogMessage interface{} + Err error +} + +// StreamVerifier verifies txn groups received through the stxnChan channel, and returns the +// results through the resultChan +type StreamVerifier struct { + resultChan chan<- *VerificationResult + droppedChan chan<- *UnverifiedElement + stxnChan <-chan *UnverifiedElement + verificationPool execpool.BacklogPool + ctx context.Context + cache VerifiedTransactionCache + activeLoopWg sync.WaitGroup + nbw *NewBlockWatcher + ledger logic.LedgerForSignature +} + +// NewBlockWatcher is a struct used to provide a new block header to the +// stream verifier +type NewBlockWatcher struct { + blkHeader atomic.Value +} + +// MakeNewBlockWatcher construct a new block watcher with the initial blkHdr +func MakeNewBlockWatcher(blkHdr bookkeeping.BlockHeader) (nbw *NewBlockWatcher) { + nbw = &NewBlockWatcher{} + nbw.blkHeader.Store(&blkHdr) + return nbw +} + +// OnNewBlock implements the interface to subscribe to new block notifications from the ledger +func (nbw *NewBlockWatcher) OnNewBlock(block bookkeeping.Block, delta ledgercore.StateDelta) { + bh := nbw.blkHeader.Load().(*bookkeeping.BlockHeader) + if bh.Round >= block.BlockHeader.Round { + return + } + nbw.blkHeader.Store(&block.BlockHeader) +} + +func (nbw *NewBlockWatcher) getBlockHeader() (bh *bookkeeping.BlockHeader) { + return nbw.blkHeader.Load().(*bookkeeping.BlockHeader) +} + +type batchLoad struct { + txnGroups [][]transactions.SignedTxn + groupCtxs []*GroupContext + elementBacklogMessage []interface{} + messagesForTxn []int +} + +func makeBatchLoad(l int) (bl batchLoad) { + bl.txnGroups = make([][]transactions.SignedTxn, 0, l) + bl.groupCtxs = make([]*GroupContext, 0, l) + bl.elementBacklogMessage = make([]interface{}, 0, l) + bl.messagesForTxn = make([]int, 0, l) + return bl +} + +func (bl *batchLoad) addLoad(txngrp []transactions.SignedTxn, gctx *GroupContext, backlogMsg interface{}, numBatchableSigs int) { + bl.txnGroups = append(bl.txnGroups, txngrp) + bl.groupCtxs = append(bl.groupCtxs, gctx) + bl.elementBacklogMessage = append(bl.elementBacklogMessage, backlogMsg) + bl.messagesForTxn = append(bl.messagesForTxn, numBatchableSigs) + +} + +// LedgerForStreamVerifier defines the ledger methods used by the StreamVerifier. +type LedgerForStreamVerifier interface { + logic.LedgerForSignature + RegisterBlockListeners([]ledgercore.BlockListener) + Latest() basics.Round + BlockHdr(rnd basics.Round) (blk bookkeeping.BlockHeader, err error) +} + +// MakeStreamVerifier creates a new stream verifier and returns the chans used to send txn groups +// to it and obtain the txn signature verification result from +func MakeStreamVerifier(stxnChan <-chan *UnverifiedElement, resultChan chan<- *VerificationResult, + droppedChan chan<- *UnverifiedElement, ledger LedgerForStreamVerifier, + verificationPool execpool.BacklogPool, cache VerifiedTransactionCache) (*StreamVerifier, error) { + + latest := ledger.Latest() + latestHdr, err := ledger.BlockHdr(latest) + if err != nil { + return nil, errors.New("MakeStreamVerifier: Could not get header for previous block") + } + + nbw := MakeNewBlockWatcher(latestHdr) + ledger.RegisterBlockListeners([]ledgercore.BlockListener{nbw}) + + return &StreamVerifier{ + resultChan: resultChan, + stxnChan: stxnChan, + droppedChan: droppedChan, + verificationPool: verificationPool, + cache: cache, + nbw: nbw, + ledger: ledger, + }, nil +} + +// Start is called when the verifier is created and whenever it needs to restart after +// the ctx is canceled +func (sv *StreamVerifier) Start(ctx context.Context) { + sv.ctx = ctx + sv.activeLoopWg.Add(1) + go sv.batchingLoop() +} + +// WaitForStop waits until the batching loop terminates afer the ctx is canceled +func (sv *StreamVerifier) WaitForStop() { + sv.activeLoopWg.Wait() +} + +func (sv *StreamVerifier) cleanup(pending []*UnverifiedElement) { + // report an error for the unchecked txns + // drop the messages without reporting if the receiver does not consume + for _, uel := range pending { + sv.sendResult(uel.TxnGroup, uel.BacklogMessage, errShuttingDownError) + } +} + +func (sv *StreamVerifier) batchingLoop() { + defer sv.activeLoopWg.Done() + timer := time.NewTicker(waitForNextTxnDuration) + defer timer.Stop() + var added bool + var numberOfSigsInCurrent uint64 + var numberOfBatchAttempts uint64 + ue := make([]*UnverifiedElement, 0, 8) + defer func() { sv.cleanup(ue) }() + for { + select { + case stx := <-sv.stxnChan: + numberOfBatchableSigsInGroup, err := getNumberOfBatchableSigsInGroup(stx.TxnGroup) + if err != nil { + // wrong number of signatures + sv.sendResult(stx.TxnGroup, stx.BacklogMessage, err) + continue + } + + // if no batchable signatures here, send this as a task of its own + if numberOfBatchableSigsInGroup == 0 { + err := sv.addVerificationTaskToThePoolNow([]*UnverifiedElement{stx}) + if err != nil { + return + } + continue // stx is handled, continue + } + + // add this txngrp to the list of batchable txn groups + numberOfSigsInCurrent = numberOfSigsInCurrent + numberOfBatchableSigsInGroup + ue = append(ue, stx) + if numberOfSigsInCurrent > txnPerWorksetThreshold { + // enough transaction in the batch to efficiently verify + + if numberOfSigsInCurrent > batchSizeBlockLimit { + // do not consider adding more txns to this batch. + // bypass the exec pool situation and queue anyway + // this is to prevent creation of very large batches + err := sv.addVerificationTaskToThePoolNow(ue) + if err != nil { + return + } + added = true + } else { + added, err = sv.tryAddVerificationTaskToThePool(ue) + if err != nil { + return + } + } + if added { + numberOfSigsInCurrent = 0 + ue = make([]*UnverifiedElement, 0, 8) + numberOfBatchAttempts = 0 + } else { + // was not added because of the exec pool buffer length + numberOfBatchAttempts++ + } + } + case <-timer.C: + // timer ticked. it is time to send the batch even if it is not full + if numberOfSigsInCurrent == 0 { + // nothing batched yet... wait some more + continue + } + var err error + if numberOfBatchAttempts > 1 { + // bypass the exec pool situation and queue anyway + // this is to prevent long delays in transaction propagation + // at least one transaction here has waited 3 x waitForNextTxnDuration + err = sv.addVerificationTaskToThePoolNow(ue) + added = true + } else { + added, err = sv.tryAddVerificationTaskToThePool(ue) + } + if err != nil { + return + } + if added { + numberOfSigsInCurrent = 0 + ue = make([]*UnverifiedElement, 0, 8) + numberOfBatchAttempts = 0 + } else { + // was not added because of the exec pool buffer length. wait for some more txns + numberOfBatchAttempts++ + } + case <-sv.ctx.Done(): + return + } + } +} + +func (sv *StreamVerifier) sendResult(veTxnGroup []transactions.SignedTxn, veBacklogMessage interface{}, err error) { + // send the txn result out the pipe + select { + case sv.resultChan <- &VerificationResult{ + TxnGroup: veTxnGroup, + BacklogMessage: veBacklogMessage, + Err: err, + }: + default: + // we failed to write to the output queue, since the queue was full. + sv.droppedChan <- &UnverifiedElement{veTxnGroup, veBacklogMessage} + } +} + +func (sv *StreamVerifier) tryAddVerificationTaskToThePool(ue []*UnverifiedElement) (added bool, err error) { + // if the exec pool buffer is full, can go back and collect + // more signatures instead of waiting in the exec pool buffer + // more signatures to the batch do not harm performance but introduce latency when delayed (see crypto.BenchmarkBatchVerifierBig) + + // if the buffer is full + if l, c := sv.verificationPool.BufferSize(); l == c { + return false, nil + } + err = sv.addVerificationTaskToThePoolNow(ue) + if err != nil { + // An error is returned when the context of the pool expires + return false, err + } + return true, nil +} + +func (sv *StreamVerifier) addVerificationTaskToThePoolNow(ue []*UnverifiedElement) error { + // if the context is canceled when the task is in the queue, it should be canceled + // copy the ctx here so that when the StreamVerifier is started again, and a new context + // is created, this task still gets canceled due to the ctx at the time of this task + taskCtx := sv.ctx + function := func(arg interface{}) interface{} { + if taskCtx.Err() != nil { + // ctx is canceled. the results will be returned + sv.cleanup(ue) + return nil + } + + ue := arg.([]*UnverifiedElement) + batchVerifier := crypto.MakeBatchVerifier() + + bl := makeBatchLoad(len(ue)) + // TODO: separate operations here, and get the sig verification inside the LogicSig to the batch here + blockHeader := sv.nbw.getBlockHeader() + for _, ue := range ue { + groupCtx, err := txnGroupBatchPrep(ue.TxnGroup, blockHeader, sv.ledger, batchVerifier, nil) + if err != nil { + // verification failed, no need to add the sig to the batch, report the error + sv.sendResult(ue.TxnGroup, ue.BacklogMessage, err) + continue + } + totalBatchCount := batchVerifier.GetNumberOfEnqueuedSignatures() + bl.addLoad(ue.TxnGroup, groupCtx, ue.BacklogMessage, totalBatchCount) + } + + failed, err := batchVerifier.VerifyWithFeedback() + // this error can only be crypto.ErrBatchHasFailedSigs + if err == nil { // success, all signatures verified + for i := range bl.txnGroups { + sv.sendResult(bl.txnGroups[i], bl.elementBacklogMessage[i], nil) + } + sv.cache.AddPayset(bl.txnGroups, bl.groupCtxs) + return nil + } + + verifiedTxnGroups := make([][]transactions.SignedTxn, 0, len(bl.txnGroups)) + verifiedGroupCtxs := make([]*GroupContext, 0, len(bl.groupCtxs)) + failedSigIdx := 0 + for txgIdx := range bl.txnGroups { + txGroupSigFailed := false + for failedSigIdx < bl.messagesForTxn[txgIdx] { + if failed[failedSigIdx] { + // if there is a failed sig check, then no need to check the rest of the + // sigs for this txnGroup + failedSigIdx = bl.messagesForTxn[txgIdx] + txGroupSigFailed = true + } else { + // proceed to check the next sig belonging to this txnGroup + failedSigIdx++ + } + } + var result error + if !txGroupSigFailed { + verifiedTxnGroups = append(verifiedTxnGroups, bl.txnGroups[txgIdx]) + verifiedGroupCtxs = append(verifiedGroupCtxs, bl.groupCtxs[txgIdx]) + } else { + result = err + } + sv.sendResult(bl.txnGroups[txgIdx], bl.elementBacklogMessage[txgIdx], result) + } + // loading them all at once by locking the cache once + sv.cache.AddPayset(verifiedTxnGroups, verifiedGroupCtxs) + return nil + } + + // EnqueueBacklog returns an error when the context is canceled + err := sv.verificationPool.EnqueueBacklog(sv.ctx, function, ue, nil) + if err != nil { + logging.Base().Infof("addVerificationTaskToThePoolNow: EnqueueBacklog returned an error and StreamVerifier will stop: %v", err) + } + return err +} + +func getNumberOfBatchableSigsInGroup(stxs []transactions.SignedTxn) (batchSigs uint64, err error) { + batchSigs = 0 + for i := range stxs { + count, err := getNumberOfBatchableSigsInTxn(&stxs[i]) + if err != nil { + return 0, err + } + batchSigs = batchSigs + count + } + return +} + +func getNumberOfBatchableSigsInTxn(stx *transactions.SignedTxn) (uint64, error) { + sigType, err := checkTxnSigTypeCounts(stx) + if err != nil { + return 0, err + } + switch sigType { + case regularSig: + return 1, nil + case multiSig: + sig := stx.Msig + batchSigs := uint64(0) + for _, subsigi := range sig.Subsigs { + if (subsigi.Sig != crypto.Signature{}) { + batchSigs++ + } + } + return batchSigs, nil + case logicSig: + // Currently the sigs in here are not batched. Something to consider later. + return 0, nil + case stateProofTxn: + return 0, nil + default: + // this case is impossible + return 0, nil + } +} diff --git a/data/transactions/verify/txn_test.go b/data/transactions/verify/txn_test.go index 3e23c8f352..eb03917fbd 100644 --- a/data/transactions/verify/txn_test.go +++ b/data/transactions/verify/txn_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -17,8 +17,14 @@ package verify import ( + "bytes" "context" + "encoding/binary" + "errors" + "fmt" "math/rand" + "runtime" + "sync" "testing" "time" @@ -30,14 +36,19 @@ import ( "github.com/algorand/go-algorand/data/bookkeeping" "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/data/transactions/logic" + "github.com/algorand/go-algorand/data/transactions/logic/mocktracer" + "github.com/algorand/go-algorand/data/txntest" + "github.com/algorand/go-algorand/ledger/ledgercore" + "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" "github.com/algorand/go-algorand/util/execpool" + "github.com/algorand/go-algorand/util/metrics" ) var feeSink = basics.Address{0x7, 0xda, 0xcb, 0x4b, 0x6d, 0x9e, 0xd1, 0x41, 0xb1, 0x75, 0x76, 0xbd, 0x45, 0x9a, 0xe6, 0x42, 0x1d, 0x48, 0x6d, 0xa3, 0xd4, 0xef, 0x22, 0x47, 0xc4, 0x9, 0xa3, 0x96, 0xb8, 0x2e, 0xa2, 0x21} var poolAddr = basics.Address{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff} -var blockHeader = bookkeeping.BlockHeader{ +var blockHeader = &bookkeeping.BlockHeader{ RewardsState: bookkeeping.RewardsState{ FeeSink: feeSink, RewardsPool: poolAddr, @@ -46,6 +57,8 @@ var blockHeader = bookkeeping.BlockHeader{ CurrentProtocol: protocol.ConsensusCurrentVersion, }, } +var protoMaxGroupSize = config.Consensus[protocol.ConsensusCurrentVersion].MaxTxGroupSize +var txBacklogSize = config.Consensus[protocol.ConsensusCurrentVersion].MaxTxnBytesPerBlock / 200 var spec = transactions.SpecialAddresses{ FeeSink: feeSink, @@ -55,27 +68,29 @@ var spec = transactions.SpecialAddresses{ func verifyTxn(s *transactions.SignedTxn, txnIdx int, groupCtx *GroupContext) error { batchVerifier := crypto.MakeBatchVerifier() - if err := txnBatchPrep(s, txnIdx, groupCtx, batchVerifier); err != nil { + if err := txnBatchPrep(s, txnIdx, groupCtx, batchVerifier, nil); err != nil { return err } return batchVerifier.Verify() } type DummyLedgerForSignature struct { + badHdr bool } func (d *DummyLedgerForSignature) BlockHdrCached(basics.Round) (bookkeeping.BlockHeader, error) { - return bookkeeping.BlockHeader{ - Round: 50, - GenesisHash: crypto.Hash([]byte{1, 2, 3, 4, 5}), - UpgradeState: bookkeeping.UpgradeState{ - CurrentProtocol: protocol.ConsensusCurrentVersion, - }, - RewardsState: bookkeeping.RewardsState{ - FeeSink: feeSink, - RewardsPool: poolAddr, - }, - }, nil + return createDummyBlockHeader(), nil +} +func (d *DummyLedgerForSignature) BlockHdr(rnd basics.Round) (blk bookkeeping.BlockHeader, err error) { + if d.badHdr { + return bookkeeping.BlockHeader{}, fmt.Errorf("test error block hdr") + } + return createDummyBlockHeader(), nil +} +func (d *DummyLedgerForSignature) Latest() basics.Round { + return 0 +} +func (d *DummyLedgerForSignature) RegisterBlockListeners([]ledgercore.BlockListener) { } func keypair() *crypto.SignatureSecrets { @@ -108,20 +123,7 @@ func generateMultiSigTxn(numTxs, numAccs int, blockRound basics.Round, t *testin exp = int(blockRound) + rand.Intn(30) } - txs[i] = transactions.Transaction{ - Type: protocol.PaymentTx, - Header: transactions.Header{ - Sender: multiAddress[s], - Fee: basics.MicroAlgos{Raw: f}, - FirstValid: basics.Round(iss), - LastValid: basics.Round(exp), - GenesisHash: crypto.Hash([]byte{1, 2, 3, 4, 5}), - }, - PaymentTxnFields: transactions.PaymentTxnFields{ - Receiver: multiAddress[r], - Amount: basics.MicroAlgos{Raw: uint64(a)}, - }, - } + txs[i] = createPayTransaction(f, iss, exp, a, multiAddress[s], multiAddress[r]) signed[i].Txn = txs[i] // create multi sig that 2 out of 3 has signed the txn @@ -172,7 +174,7 @@ func generateAccounts(numAccs int) ([]*crypto.SignatureSecrets, []basics.Address return secrets, addresses, pks } -func generateTestObjects(numTxs, numAccs int, blockRound basics.Round) ([]transactions.Transaction, []transactions.SignedTxn, []*crypto.SignatureSecrets, []basics.Address) { +func generateTestObjects(numTxs, numAccs, noteOffset int, blockRound basics.Round) ([]transactions.Transaction, []transactions.SignedTxn, []*crypto.SignatureSecrets, []basics.Address) { txs := make([]transactions.Transaction, numTxs) signed := make([]transactions.SignedTxn, numTxs) secrets, addresses, _ := generateAccounts(numAccs) @@ -192,20 +194,11 @@ func generateTestObjects(numTxs, numAccs int, blockRound basics.Round) ([]transa exp = int(blockRound) + rand.Intn(30) } - txs[i] = transactions.Transaction{ - Type: protocol.PaymentTx, - Header: transactions.Header{ - Sender: addresses[s], - Fee: basics.MicroAlgos{Raw: f}, - FirstValid: basics.Round(iss), - LastValid: basics.Round(exp), - GenesisHash: crypto.Hash([]byte{1, 2, 3, 4, 5}), - }, - PaymentTxnFields: transactions.PaymentTxnFields{ - Receiver: addresses[r], - Amount: basics.MicroAlgos{Raw: uint64(a)}, - }, - } + txs[i] = createPayTransaction(f, iss, exp, a, addresses[s], addresses[r]) + noteField := make([]byte, binary.MaxVarintLen64) + binary.PutUvarint(noteField, uint64(i+noteOffset)) + txs[i].Note = noteField + signed[i] = txs[i].Sign(secrets[s]) u += 100 } @@ -218,7 +211,7 @@ func TestSignedPayment(t *testing.T) { proto := config.Consensus[protocol.ConsensusCurrentVersion] - payments, stxns, secrets, addrs := generateTestObjects(1, 1, 0) + payments, stxns, secrets, addrs := generateTestObjects(1, 1, 0, 0) payment, stxn, secret, addr := payments[0], stxns[0], secrets[0], addrs[0] groupCtx, err := PrepareGroupContext(stxns, blockHeader, nil) @@ -239,7 +232,7 @@ func TestSignedPayment(t *testing.T) { func TestTxnValidationEncodeDecode(t *testing.T) { partitiontest.PartitionTest(t) - _, signed, _, _ := generateTestObjects(100, 50, 0) + _, signed, _, _ := generateTestObjects(100, 50, 0, 0) for _, txn := range signed { groupCtx, err := PrepareGroupContext([]transactions.SignedTxn{txn}, blockHeader, nil) @@ -261,7 +254,7 @@ func TestTxnValidationEncodeDecode(t *testing.T) { func TestTxnValidationEmptySig(t *testing.T) { partitiontest.PartitionTest(t) - _, signed, _, _ := generateTestObjects(100, 50, 0) + _, signed, _, _ := generateTestObjects(100, 50, 0, 0) for _, txn := range signed { groupCtx, err := PrepareGroupContext([]transactions.SignedTxn{txn}, blockHeader, nil) @@ -281,7 +274,7 @@ func TestTxnValidationEmptySig(t *testing.T) { const spProto = protocol.ConsensusVersion("test-state-proof-enabled") -func TestTxnValidationStateProof(t *testing.T) { +func TestTxnValidationStateProof(t *testing.T) { //nolint:paralleltest // Not parallel because it modifies config.Consensus partitiontest.PartitionTest(t) proto := config.Consensus[protocol.ConsensusCurrentVersion] @@ -299,7 +292,7 @@ func TestTxnValidationStateProof(t *testing.T) { }, } - var blockHeader = bookkeeping.BlockHeader{ + var blockHeader = &bookkeeping.BlockHeader{ RewardsState: bookkeeping.RewardsState{ FeeSink: feeSink, RewardsPool: poolAddr, @@ -369,17 +362,105 @@ func TestDecodeNil(t *testing.T) { } } +func TestTxnGroupWithTracer(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + proto := config.Consensus[protocol.ConsensusCurrentVersion] + + account := keypair() + accountAddr := basics.Address(account.SignatureVerifier) + + ops1, err := logic.AssembleString(`#pragma version 6 +pushint 1`) + require.NoError(t, err) + program1 := ops1.Program + program1Addr := basics.Address(logic.HashProgram(program1)) + + ops2, err := logic.AssembleString(`#pragma version 6 +pushbytes "test" +pop +pushint 1`) + require.NoError(t, err) + program2 := ops2.Program + program2Addr := basics.Address(logic.HashProgram(program2)) + + // this shouldn't be invoked during this test + appProgram := "err" + + lsigPay := txntest.Txn{ + Type: protocol.PaymentTx, + Sender: program1Addr, + Receiver: accountAddr, + Fee: proto.MinTxnFee, + } + + normalSigAppCall := txntest.Txn{ + Type: protocol.ApplicationCallTx, + Sender: accountAddr, + ApprovalProgram: appProgram, + ClearStateProgram: appProgram, + Fee: proto.MinTxnFee, + } + + lsigAppCall := txntest.Txn{ + Type: protocol.ApplicationCallTx, + Sender: program2Addr, + ApprovalProgram: appProgram, + ClearStateProgram: appProgram, + Fee: proto.MinTxnFee, + } + + txntest.Group(&lsigPay, &normalSigAppCall, &lsigAppCall) + + txgroup := []transactions.SignedTxn{ + { + Lsig: transactions.LogicSig{ + Logic: program1, + }, + Txn: lsigPay.Txn(), + }, + normalSigAppCall.Txn().Sign(account), + { + Lsig: transactions.LogicSig{ + Logic: program2, + }, + Txn: lsigAppCall.Txn(), + }, + } + + mockTracer := &mocktracer.Tracer{} + _, err = TxnGroupWithTracer(txgroup, blockHeader, nil, logic.NoHeaderLedger{}, mockTracer) + require.NoError(t, err) + + expectedEvents := []mocktracer.Event{ + mocktracer.BeforeProgram(logic.ModeSig), // first txn start + mocktracer.BeforeOpcode(), mocktracer.AfterOpcode(), // first txn LogicSig: 1 op + mocktracer.AfterProgram(logic.ModeSig), // first txn end + // nothing for second txn (not signed with a LogicSig) + mocktracer.BeforeProgram(logic.ModeSig), // third txn start + mocktracer.BeforeOpcode(), mocktracer.AfterOpcode(), mocktracer.BeforeOpcode(), mocktracer.AfterOpcode(), mocktracer.BeforeOpcode(), mocktracer.AfterOpcode(), // third txn LogicSig: 3 ops + mocktracer.AfterProgram(logic.ModeSig), // third txn end + } + require.Equal(t, expectedEvents, mockTracer.Events) +} + func TestPaysetGroups(t *testing.T) { partitiontest.PartitionTest(t) - _, signedTxn, secrets, addrs := generateTestObjects(10000, 20, 50) + if testing.Short() { + t.Log("this is a long test and skipping for -short") + return + } + + _, signedTxn, secrets, addrs := generateTestObjects(10000, 20, 0, 50) blkHdr := createDummyBlockHeader() execPool := execpool.MakePool(t) verificationPool := execpool.MakeBacklog(execPool, 64, execpool.LowPriority, t) defer verificationPool.Shutdown() - txnGroups := generateTransactionGroups(signedTxn, secrets, addrs) + txnGroups := generateTransactionGroups(protoMaxGroupSize, signedTxn, secrets, addrs) startPaysetGroupsTime := time.Now() err := PaysetGroups(context.Background(), txnGroups, blkHdr, verificationPool, MakeVerifiedTransactionCache(50000), nil) @@ -399,9 +480,9 @@ func TestPaysetGroups(t *testing.T) { // we define a test that would take 10 seconds to execute, and try to abort at 1.5 seconds. txnCount := len(signedTxn) * 10 * int(time.Second/paysetGroupDuration) - _, signedTxn, secrets, addrs = generateTestObjects(txnCount, 20, 50) + _, signedTxn, secrets, addrs = generateTestObjects(txnCount, 20, 0, 50) - txnGroups = generateTransactionGroups(signedTxn, secrets, addrs) + txnGroups = generateTransactionGroups(protoMaxGroupSize, signedTxn, secrets, addrs) ctx, ctxCancelFunc := context.WithTimeout(context.Background(), 1500*time.Millisecond) defer ctxCancelFunc() @@ -442,14 +523,14 @@ func BenchmarkPaysetGroups(b *testing.B) { if b.N < 2000 { b.N = 2000 } - _, signedTxn, secrets, addrs := generateTestObjects(b.N, 20, 50) + _, signedTxn, secrets, addrs := generateTestObjects(b.N, 20, 0, 50) blkHdr := createDummyBlockHeader() execPool := execpool.MakePool(b) verificationPool := execpool.MakeBacklog(execPool, 64, execpool.LowPriority, b) defer verificationPool.Shutdown() - txnGroups := generateTransactionGroups(signedTxn, secrets, addrs) + txnGroups := generateTransactionGroups(protoMaxGroupSize, signedTxn, secrets, addrs) cache := MakeVerifiedTransactionCache(50000) b.ResetTimer() @@ -461,7 +542,7 @@ func BenchmarkPaysetGroups(b *testing.B) { func TestTxnGroupMixedSignatures(t *testing.T) { partitiontest.PartitionTest(t) - _, signedTxn, secrets, addrs := generateTestObjects(1, 20, 50) + _, signedTxn, secrets, addrs := generateTestObjects(1, 20, 0, 50) blkHdr := createDummyBlockHeader() // add a simple logic that verifies this condition: @@ -472,16 +553,16 @@ byte base64 5rZMNsevs5sULO+54aN+OvU6lQ503z2X+SSYUABIx7E= ==`) require.NoError(t, err) - txnGroups := generateTransactionGroups(signedTxn, secrets, addrs) + txnGroups := generateTransactionGroups(protoMaxGroupSize, signedTxn, secrets, addrs) dummyLedger := DummyLedgerForSignature{} - _, err = TxnGroup(txnGroups[0], blkHdr, nil, &dummyLedger) + _, err = TxnGroup(txnGroups[0], &blkHdr, nil, &dummyLedger) require.NoError(t, err) ///// no sig tmpSig := txnGroups[0][0].Sig txnGroups[0][0].Sig = crypto.Signature{} - _, err = TxnGroup(txnGroups[0], blkHdr, nil, &dummyLedger) + _, err = TxnGroup(txnGroups[0], &blkHdr, nil, &dummyLedger) require.Error(t, err) require.Contains(t, err.Error(), "has no sig") txnGroups[0][0].Sig = tmpSig @@ -492,14 +573,14 @@ byte base64 5rZMNsevs5sULO+54aN+OvU6lQ503z2X+SSYUABIx7E= Key: crypto.PublicKey{0x1}, Sig: crypto.Signature{0x2}, } - _, err = TxnGroup(txnGroups[0], blkHdr, nil, &dummyLedger) + _, err = TxnGroup(txnGroups[0], &blkHdr, nil, &dummyLedger) require.Error(t, err) require.Contains(t, err.Error(), "should only have one of Sig or Msig or LogicSig") txnGroups[0][0].Msig.Subsigs = nil ///// Sig + logic txnGroups[0][0].Lsig.Logic = op.Program - _, err = TxnGroup(txnGroups[0], blkHdr, nil, &dummyLedger) + _, err = TxnGroup(txnGroups[0], &blkHdr, nil, &dummyLedger) require.Error(t, err) require.Contains(t, err.Error(), "should only have one of Sig or Msig or LogicSig") txnGroups[0][0].Lsig.Logic = []byte{} @@ -512,7 +593,7 @@ byte base64 5rZMNsevs5sULO+54aN+OvU6lQ503z2X+SSYUABIx7E= Key: crypto.PublicKey{0x1}, Sig: crypto.Signature{0x2}, } - _, err = TxnGroup(txnGroups[0], blkHdr, nil, &dummyLedger) + _, err = TxnGroup(txnGroups[0], &blkHdr, nil, &dummyLedger) require.Error(t, err) require.Contains(t, err.Error(), "should only have one of Sig or Msig or LogicSig") txnGroups[0][0].Lsig.Logic = []byte{} @@ -528,36 +609,45 @@ byte base64 5rZMNsevs5sULO+54aN+OvU6lQ503z2X+SSYUABIx7E= Key: crypto.PublicKey{0x1}, Sig: crypto.Signature{0x2}, } - _, err = TxnGroup(txnGroups[0], blkHdr, nil, &dummyLedger) + _, err = TxnGroup(txnGroups[0], &blkHdr, nil, &dummyLedger) require.Error(t, err) require.Contains(t, err.Error(), "should only have one of Sig or Msig") } -func generateTransactionGroups(signedTxns []transactions.SignedTxn, secrets []*crypto.SignatureSecrets, addrs []basics.Address) [][]transactions.SignedTxn { +func generateTransactionGroups(maxGroupSize int, signedTxns []transactions.SignedTxn, + secrets []*crypto.SignatureSecrets, addrs []basics.Address) [][]transactions.SignedTxn { addrToSecret := make(map[basics.Address]*crypto.SignatureSecrets) for i, addr := range addrs { addrToSecret[addr] = secrets[i] } txnGroups := make([][]transactions.SignedTxn, 0, len(signedTxns)) - for i := 0; i < len(signedTxns); i++ { - txnPerGroup := 1 + rand.Intn(15) - if i+txnPerGroup >= len(signedTxns) { - txnPerGroup = len(signedTxns) - i - 1 + for i := 0; i < len(signedTxns); { + txnsInGroup := rand.Intn(protoMaxGroupSize-1) + 1 + if txnsInGroup > maxGroupSize { + txnsInGroup = maxGroupSize + } + if i+txnsInGroup > len(signedTxns) { + txnsInGroup = len(signedTxns) - i } - newGroup := signedTxns[i : i+txnPerGroup+1] + + newGroup := signedTxns[i : i+txnsInGroup] var txGroup transactions.TxGroup - for _, txn := range newGroup { - txGroup.TxGroupHashes = append(txGroup.TxGroupHashes, crypto.HashObj(txn.Txn)) + if txnsInGroup > 1 { + for _, txn := range newGroup { + txGroup.TxGroupHashes = append(txGroup.TxGroupHashes, crypto.HashObj(txn.Txn)) + } } groupHash := crypto.HashObj(txGroup) for j := range newGroup { - newGroup[j].Txn.Group = groupHash + if txnsInGroup > 1 { + newGroup[j].Txn.Group = groupHash + } newGroup[j].Sig = addrToSecret[newGroup[j].Txn.Sender].Sign(&newGroup[j].Txn) } txnGroups = append(txnGroups, newGroup) - i += txnPerGroup + i += txnsInGroup } return txnGroups @@ -566,17 +656,17 @@ func generateTransactionGroups(signedTxns []transactions.SignedTxn, secrets []*c func TestTxnGroupCacheUpdate(t *testing.T) { partitiontest.PartitionTest(t) - _, signedTxn, secrets, addrs := generateTestObjects(100, 20, 50) + _, signedTxn, secrets, addrs := generateTestObjects(100, 20, 0, 50) blkHdr := createDummyBlockHeader() - txnGroups := generateTransactionGroups(signedTxn, secrets, addrs) + txnGroups := generateTransactionGroups(protoMaxGroupSize, signedTxn, secrets, addrs) breakSignatureFunc := func(txn *transactions.SignedTxn) { txn.Sig[0]++ } restoreSignatureFunc := func(txn *transactions.SignedTxn) { txn.Sig[0]-- } - verifyGroup(t, txnGroups, blkHdr, breakSignatureFunc, restoreSignatureFunc, crypto.ErrBatchVerificationFailed.Error()) + verifyGroup(t, txnGroups, &blkHdr, breakSignatureFunc, restoreSignatureFunc, crypto.ErrBatchHasFailedSigs.Error()) } // TestTxnGroupCacheUpdateMultiSig makes sure that a payment transaction signed with multisig @@ -598,7 +688,7 @@ func TestTxnGroupCacheUpdateMultiSig(t *testing.T) { restoreSignatureFunc := func(txn *transactions.SignedTxn) { txn.Msig.Subsigs[0].Sig[0]-- } - verifyGroup(t, txnGroups, blkHdr, breakSignatureFunc, restoreSignatureFunc, crypto.ErrBatchVerificationFailed.Error()) + verifyGroup(t, txnGroups, &blkHdr, breakSignatureFunc, restoreSignatureFunc, crypto.ErrBatchHasFailedSigs.Error()) } // TestTxnGroupCacheUpdateFailLogic test makes sure that a payment transaction contains a logic (and no signature) @@ -606,7 +696,7 @@ func TestTxnGroupCacheUpdateMultiSig(t *testing.T) { func TestTxnGroupCacheUpdateFailLogic(t *testing.T) { partitiontest.PartitionTest(t) - _, signedTxn, _, _ := generateTestObjects(100, 20, 50) + _, signedTxn, _, _ := generateTestObjects(100, 20, 0, 50) blkHdr := createDummyBlockHeader() // sign the transaction with logic @@ -638,7 +728,7 @@ byte base64 5rZMNsevs5sULO+54aN+OvU6lQ503z2X+SSYUABIx7E= txn.Lsig.Args[0][0]-- } initCounter := logicCostTotal.GetUint64Value() - verifyGroup(t, txnGroups, blkHdr, breakSignatureFunc, restoreSignatureFunc, "rejected by logic") + verifyGroup(t, txnGroups, &blkHdr, breakSignatureFunc, restoreSignatureFunc, "rejected by logic") currentCounter := logicCostTotal.GetUint64Value() require.Greater(t, currentCounter, initCounter) } @@ -649,7 +739,7 @@ byte base64 5rZMNsevs5sULO+54aN+OvU6lQ503z2X+SSYUABIx7E= func TestTxnGroupCacheUpdateLogicWithSig(t *testing.T) { partitiontest.PartitionTest(t) - _, signedTxn, secrets, addresses := generateTestObjects(100, 20, 50) + _, signedTxn, secrets, addresses := generateTestObjects(100, 20, 0, 50) blkHdr := createDummyBlockHeader() for i := 0; i < len(signedTxn); i++ { @@ -683,7 +773,7 @@ byte base64 5rZMNsevs5sULO+54aN+OvU6lQ503z2X+SSYUABIx7E= restoreSignatureFunc := func(txn *transactions.SignedTxn) { txn.Lsig.Sig[0]-- } - verifyGroup(t, txnGroups, blkHdr, breakSignatureFunc, restoreSignatureFunc, crypto.ErrBatchVerificationFailed.Error()) + verifyGroup(t, txnGroups, &blkHdr, breakSignatureFunc, restoreSignatureFunc, crypto.ErrBatchHasFailedSigs.Error()) // signature is correct and logic fails breakSignatureFunc = func(txn *transactions.SignedTxn) { @@ -692,7 +782,7 @@ byte base64 5rZMNsevs5sULO+54aN+OvU6lQ503z2X+SSYUABIx7E= restoreSignatureFunc = func(txn *transactions.SignedTxn) { txn.Lsig.Args[0][0]-- } - verifyGroup(t, txnGroups, blkHdr, breakSignatureFunc, restoreSignatureFunc, "rejected by logic") + verifyGroup(t, txnGroups, &blkHdr, breakSignatureFunc, restoreSignatureFunc, "rejected by logic") } // TestTxnGroupCacheUpdateLogicWithMultiSig makes sure that a payment transaction contains logicsig signed with multisig is valid @@ -714,20 +804,7 @@ func TestTxnGroupCacheUpdateLogicWithMultiSig(t *testing.T) { a := rand.Intn(1000) f := config.Consensus[protocol.ConsensusCurrentVersion].MinTxnFee + uint64(rand.Intn(10)) - signedTxn[i].Txn = transactions.Transaction{ - Type: protocol.PaymentTx, - Header: transactions.Header{ - Sender: multiAddress[s], - Fee: basics.MicroAlgos{Raw: f}, - FirstValid: basics.Round(1), - LastValid: basics.Round(100), - GenesisHash: crypto.Hash([]byte{1, 2, 3, 4, 5}), - }, - PaymentTxnFields: transactions.PaymentTxnFields{ - Receiver: multiAddress[r], - Amount: basics.MicroAlgos{Raw: uint64(a)}, - }, - } + signedTxn[i].Txn = createPayTransaction(f, 1, 100, a, multiAddress[s], multiAddress[r]) // add a simple logic that verifies this condition: // sha256(arg0) == base64decode(5rZMNsevs5sULO+54aN+OvU6lQ503z2X+SSYUABIx7E=) op, err := logic.AssembleString(`arg 0 @@ -767,7 +844,7 @@ byte base64 5rZMNsevs5sULO+54aN+OvU6lQ503z2X+SSYUABIx7E= txn.Lsig.Msig.Subsigs[0].Sig[0]-- } - verifyGroup(t, txnGroups, blkHdr, breakSignatureFunc, restoreSignatureFunc, crypto.ErrBatchVerificationFailed.Error()) + verifyGroup(t, txnGroups, &blkHdr, breakSignatureFunc, restoreSignatureFunc, crypto.ErrBatchHasFailedSigs.Error()) // signature is correct and logic fails breakSignatureFunc = func(txn *transactions.SignedTxn) { txn.Lsig.Args[0][0]++ @@ -775,7 +852,7 @@ byte base64 5rZMNsevs5sULO+54aN+OvU6lQ503z2X+SSYUABIx7E= restoreSignatureFunc = func(txn *transactions.SignedTxn) { txn.Lsig.Args[0][0]-- } - verifyGroup(t, txnGroups, blkHdr, breakSignatureFunc, restoreSignatureFunc, "rejected by logic") + verifyGroup(t, txnGroups, &blkHdr, breakSignatureFunc, restoreSignatureFunc, "rejected by logic") } func createDummyBlockHeader() bookkeeping.BlockHeader { @@ -792,10 +869,27 @@ func createDummyBlockHeader() bookkeeping.BlockHeader { } } +func createPayTransaction(fee uint64, fv, lv, amount int, sender, receiver basics.Address) transactions.Transaction { + return transactions.Transaction{ + Type: protocol.PaymentTx, + Header: transactions.Header{ + Sender: sender, + Fee: basics.MicroAlgos{Raw: fee}, + FirstValid: basics.Round(fv), + LastValid: basics.Round(lv), + GenesisHash: crypto.Hash([]byte{1, 2, 3, 4, 5}), + }, + PaymentTxnFields: transactions.PaymentTxnFields{ + Receiver: receiver, + Amount: basics.MicroAlgos{Raw: uint64(amount)}, + }, + } +} + // verifyGroup uses TxnGroup to verify txns and add them to the // cache. Then makes sure that only the valid txns are verified and added to // the cache. -func verifyGroup(t *testing.T, txnGroups [][]transactions.SignedTxn, blkHdr bookkeeping.BlockHeader, breakSig func(txn *transactions.SignedTxn), restoreSig func(txn *transactions.SignedTxn), errorString string) { +func verifyGroup(t *testing.T, txnGroups [][]transactions.SignedTxn, blkHdr *bookkeeping.BlockHeader, breakSig func(txn *transactions.SignedTxn), restoreSig func(txn *transactions.SignedTxn), errorString string) { cache := MakeVerifiedTransactionCache(1000) breakSig(&txnGroups[0][0]) @@ -859,25 +953,13 @@ func BenchmarkTxn(b *testing.B) { if b.N < 2000 { b.N = 2000 } - _, signedTxn, secrets, addrs := generateTestObjects(b.N, 20, 50) - blk := bookkeeping.Block{ - BlockHeader: bookkeeping.BlockHeader{ - Round: 50, - GenesisHash: crypto.Hash([]byte{1, 2, 3, 4, 5}), - UpgradeState: bookkeeping.UpgradeState{ - CurrentProtocol: protocol.ConsensusCurrentVersion, - }, - RewardsState: bookkeeping.RewardsState{ - FeeSink: feeSink, - RewardsPool: poolAddr, - }, - }, - } - txnGroups := generateTransactionGroups(signedTxn, secrets, addrs) + _, signedTxn, secrets, addrs := generateTestObjects(b.N, 20, 0, 50) + blk := bookkeeping.Block{BlockHeader: createDummyBlockHeader()} + txnGroups := generateTransactionGroups(protoMaxGroupSize, signedTxn, secrets, addrs) b.ResetTimer() for _, txnGroup := range txnGroups { - groupCtx, err := PrepareGroupContext(txnGroup, blk.BlockHeader, nil) + groupCtx, err := PrepareGroupContext(txnGroup, &blk.BlockHeader, nil) require.NoError(b, err) for i, txn := range txnGroup { err := verifyTxn(&txn, i, groupCtx) @@ -886,3 +968,801 @@ func BenchmarkTxn(b *testing.B) { } b.StopTimer() } + +var droppedFromPool = metrics.MakeCounter(metrics.MetricName{Name: "test_streamVerifierTestCore_messages_dropped_pool", Description: "Test streamVerifierTestCore messages dropped from pool"}) + +func streamVerifierTestCore(txnGroups [][]transactions.SignedTxn, badTxnGroups map[uint64]struct{}, + expectedError error, t *testing.T) (sv *StreamVerifier) { + + numOfTxnGroups := len(txnGroups) + verificationPool := execpool.MakeBacklog(nil, 0, execpool.LowPriority, t) + defer verificationPool.Shutdown() + + ctx, cancel := context.WithCancel(context.Background()) + cache := MakeVerifiedTransactionCache(50000) + + defer cancel() + + stxnChan := make(chan *UnverifiedElement) + resultChan := make(chan *VerificationResult, txBacklogSize) + droppedChan := make(chan *UnverifiedElement) + sv, err := MakeStreamVerifier(stxnChan, resultChan, droppedChan, &DummyLedgerForSignature{}, verificationPool, cache) + require.NoError(t, err) + sv.Start(ctx) + + wg := sync.WaitGroup{} + + errChan := make(chan error) + var badSigResultCounter int + var goodSigResultCounter int + + wg.Add(1) + go processResults(ctx, errChan, resultChan, numOfTxnGroups, badTxnGroups, &badSigResultCounter, &goodSigResultCounter, &wg) + + wg.Add(1) + // send txn groups to be verified + go func() { + defer wg.Done() + for _, tg := range txnGroups { + stxnChan <- &UnverifiedElement{TxnGroup: tg, BacklogMessage: nil} + } + }() + + for err := range errChan { + require.ErrorContains(t, err, expectedError.Error()) + } + + wg.Wait() + + verifyResults(txnGroups, badTxnGroups, cache, badSigResultCounter, goodSigResultCounter, t) + return sv +} + +func processResults(ctx context.Context, errChan chan<- error, resultChan <-chan *VerificationResult, + numOfTxnGroups int, badTxnGroups map[uint64]struct{}, + badSigResultCounter, goodSigResultCounter *int, wg *sync.WaitGroup) { + defer wg.Done() + defer close(errChan) + // process the results + for x := 0; x < numOfTxnGroups; x++ { + select { + case <-ctx.Done(): + case result := <-resultChan: + u, _ := binary.Uvarint(result.TxnGroup[0].Txn.Note) + if _, has := badTxnGroups[u]; has { + (*badSigResultCounter)++ + if result.Err == nil { + err := fmt.Errorf("%dth (%d)transaction varified with a bad sig", x, u) + errChan <- err + return + } + // we expected an error, but it is not the general crypto error + if result.Err != crypto.ErrBatchHasFailedSigs { + errChan <- result.Err + } + } else { + (*goodSigResultCounter)++ + if result.Err != nil { + errChan <- result.Err + } + } + } + } +} + +func verifyResults(txnGroups [][]transactions.SignedTxn, badTxnGroups map[uint64]struct{}, + cache VerifiedTransactionCache, + badSigResultCounter, goodSigResultCounter int, t *testing.T) { + // check if all txns have been checked. + require.Equal(t, len(txnGroups), badSigResultCounter+goodSigResultCounter) + require.Equal(t, len(badTxnGroups), badSigResultCounter) + + // check the cached transactions + // note that the result of each verified txn group is send before the batch is added to the cache + // the test does not know if the batch is not added to the cache yet, so some elts might be missing from the cache + unverifiedGroups := cache.GetUnverifiedTransactionGroups(txnGroups, spec, protocol.ConsensusCurrentVersion) + require.GreaterOrEqual(t, len(unverifiedGroups), badSigResultCounter) + for _, txn := range unverifiedGroups { + u, _ := binary.Uvarint(txn[0].Txn.Note) + if _, has := badTxnGroups[u]; has { + delete(badTxnGroups, u) + } + } + require.Empty(t, badTxnGroups, "unverifiedGroups should have all the transactions with invalid sigs") +} + +func getSignedTransactions(numOfTxns, maxGrpSize, noteOffset int, badTxnProb float32) (txnGroups [][]transactions.SignedTxn, badTxnGroups map[uint64]struct{}) { + + _, signedTxn, secrets, addrs := generateTestObjects(numOfTxns, 20, noteOffset, 50) + txnGroups = generateTransactionGroups(maxGrpSize, signedTxn, secrets, addrs) + + badTxnGroups = make(map[uint64]struct{}) + + for tgi := range txnGroups { + if rand.Float32() < badTxnProb { + // make a bad sig + t := rand.Intn(len(txnGroups[tgi])) + txnGroups[tgi][t].Sig[0] = txnGroups[tgi][t].Sig[0] + 1 + u, _ := binary.Uvarint(txnGroups[tgi][0].Txn.Note) + badTxnGroups[u] = struct{}{} + } + } + return + +} + +// TestStreamVerifier tests the basic functionality +func TestStreamVerifier(t *testing.T) { + partitiontest.PartitionTest(t) + + numOfTxns := 4000 + txnGroups, badTxnGroups := getSignedTransactions(numOfTxns, protoMaxGroupSize, 0, 0.5) + + sv := streamVerifierTestCore(txnGroups, badTxnGroups, nil, t) + sv.WaitForStop() +} + +// TestStreamVerifierCases tests various valid and invalid transaction signature cases +func TestStreamVerifierCases(t *testing.T) { + partitiontest.PartitionTest(t) + + numOfTxns := 10 + txnGroups, badTxnGroups := getSignedTransactions(numOfTxns, 1, 0, 0) + mod := 1 + + // txn with 0 sigs + txnGroups[mod][0].Sig = crypto.Signature{} + u, _ := binary.Uvarint(txnGroups[mod][0].Txn.Note) + badTxnGroups[u] = struct{}{} + sv := streamVerifierTestCore(txnGroups, badTxnGroups, errTxnSigHasNoSig, t) + sv.WaitForStop() + mod++ + + _, signedTxns, secrets, addrs := generateTestObjects(numOfTxns, 20, 0, 50) + txnGroups = generateTransactionGroups(1, signedTxns, secrets, addrs) + badTxnGroups = make(map[uint64]struct{}) + + // invalid stateproof txn + txnGroups[mod][0].Sig = crypto.Signature{} + txnGroups[mod][0].Txn.Type = protocol.StateProofTx + txnGroups[mod][0].Txn.Header.Sender = transactions.StateProofSender + u, _ = binary.Uvarint(txnGroups[mod][0].Txn.Note) + badTxnGroups[u] = struct{}{} + errFeeMustBeZeroInStateproofTxn := errors.New("fee must be zero in state-proof transaction") + sv = streamVerifierTestCore(txnGroups, badTxnGroups, errFeeMustBeZeroInStateproofTxn, t) + sv.WaitForStop() + mod++ + + _, signedTxns, secrets, addrs = generateTestObjects(numOfTxns, 20, 0, 50) + txnGroups = generateTransactionGroups(1, signedTxns, secrets, addrs) + badTxnGroups = make(map[uint64]struct{}) + + // acceptable stateproof txn + txnGroups[mod][0].Sig = crypto.Signature{} + txnGroups[mod][0].Txn.Note = nil + txnGroups[mod][0].Txn.Type = protocol.StateProofTx + txnGroups[mod][0].Txn.Header.Fee = basics.MicroAlgos{Raw: 0} + txnGroups[mod][0].Txn.Header.Sender = transactions.StateProofSender + txnGroups[mod][0].Txn.PaymentTxnFields = transactions.PaymentTxnFields{} + sv = streamVerifierTestCore(txnGroups, badTxnGroups, nil, t) + sv.WaitForStop() + mod++ + + // multisig + _, mSigTxn, _, _ := generateMultiSigTxn(1, 6, 50, t) + txnGroups[mod] = mSigTxn + sv = streamVerifierTestCore(txnGroups, badTxnGroups, nil, t) + sv.WaitForStop() + mod++ + + _, signedTxn, secrets, addrs := generateTestObjects(numOfTxns, 20, 0, 50) + txnGroups = generateTransactionGroups(1, signedTxn, secrets, addrs) + badTxnGroups = make(map[uint64]struct{}) + + // logicsig + // add a simple logic that verifies this condition: + // sha256(arg0) == base64decode(5rZMNsevs5sULO+54aN+OvU6lQ503z2X+SSYUABIx7E=) + op, err := logic.AssembleString(`arg 0 +sha256 +byte base64 5rZMNsevs5sULO+54aN+OvU6lQ503z2X+SSYUABIx7E= +==`) + require.NoError(t, err) + s := rand.Intn(len(secrets)) + txnGroups[mod][0].Sig = crypto.Signature{} + txnGroups[mod][0].Txn.Sender = addrs[s] + txnGroups[mod][0].Lsig.Args = [][]byte{[]byte("=0\x97S\x85H\xe9\x91B\xfd\xdb;1\xf5Z\xaec?\xae\xf2I\x93\x08\x12\x94\xaa~\x06\x08\x849b")} + txnGroups[mod][0].Lsig.Logic = op.Program + program := logic.Program(op.Program) + txnGroups[mod][0].Lsig.Sig = secrets[s].Sign(program) + sv = streamVerifierTestCore(txnGroups, badTxnGroups, nil, t) + sv.WaitForStop() + mod++ + + // bad lgicsig + s = rand.Intn(len(secrets)) + txnGroups[mod][0].Sig = crypto.Signature{} + txnGroups[mod][0].Txn.Sender = addrs[s] + txnGroups[mod][0].Lsig.Args = [][]byte{[]byte("=0\x97S\x85H\xe9\x91B\xfd\xdb;1\xf5Z\xaec?\xae\xf2I\x93\x08\x12\x94\xaa~\x06\x08\x849b")} + txnGroups[mod][0].Lsig.Args[0][0]++ + txnGroups[mod][0].Lsig.Logic = op.Program + txnGroups[mod][0].Lsig.Sig = secrets[s].Sign(program) + u, _ = binary.Uvarint(txnGroups[mod][0].Txn.Note) + badTxnGroups[u] = struct{}{} + sv = streamVerifierTestCore(txnGroups, badTxnGroups, errors.New("rejected by logic"), t) + sv.WaitForStop() + mod++ + + _, signedTxn, secrets, addrs = generateTestObjects(numOfTxns, 20, 0, 50) + txnGroups = generateTransactionGroups(1, signedTxn, secrets, addrs) + badTxnGroups = make(map[uint64]struct{}) + + // txn with sig and msig + txnGroups[mod][0].Msig = mSigTxn[0].Msig + u, _ = binary.Uvarint(txnGroups[mod][0].Txn.Note) + badTxnGroups[u] = struct{}{} + sv = streamVerifierTestCore(txnGroups, badTxnGroups, errTxnSigNotWellFormed, t) + sv.WaitForStop() +} + +// TestStreamVerifierIdel starts the verifer and sends nothing, to trigger the timer, then sends a txn +func TestStreamVerifierIdel(t *testing.T) { + partitiontest.PartitionTest(t) + + numOfTxns := 1 + txnGroups, badTxnGroups := getSignedTransactions(numOfTxns, protoMaxGroupSize, 0, 0.5) + + sv := streamVerifierTestCore(txnGroups, badTxnGroups, nil, t) + sv.WaitForStop() +} + +func TestGetNumberOfBatchableSigsInGroup(t *testing.T) { + partitiontest.PartitionTest(t) + + numOfTxns := 10 + txnGroups, _ := getSignedTransactions(numOfTxns, 1, 0, 0) + mod := 1 + + // txn with 0 sigs + txnGroups[mod][0].Sig = crypto.Signature{} + batchSigs, err := getNumberOfBatchableSigsInGroup(txnGroups[mod]) + require.ErrorIs(t, err, errTxnSigHasNoSig) + mod++ + + _, signedTxns, secrets, addrs := generateTestObjects(numOfTxns, 20, 0, 50) + txnGroups = generateTransactionGroups(1, signedTxns, secrets, addrs) + batchSigs, err = getNumberOfBatchableSigsInGroup(txnGroups[0]) + require.NoError(t, err) + require.Equal(t, uint64(1), batchSigs) + + // stateproof txn + txnGroups[mod][0].Sig = crypto.Signature{} + txnGroups[mod][0].Txn.Type = protocol.StateProofTx + txnGroups[mod][0].Txn.Header.Sender = transactions.StateProofSender + batchSigs, err = getNumberOfBatchableSigsInGroup(txnGroups[mod]) + require.NoError(t, err) + require.Equal(t, uint64(0), batchSigs) + mod++ + + // multisig + _, mSigTxn, _, _ := generateMultiSigTxn(1, 6, 50, t) + batchSigs, err = getNumberOfBatchableSigsInGroup(mSigTxn) + require.NoError(t, err) + require.Equal(t, uint64(2), batchSigs) + mod++ + + _, signedTxn, secrets, addrs := generateTestObjects(numOfTxns, 20, 0, 50) + txnGroups = generateTransactionGroups(1, signedTxn, secrets, addrs) + + // logicsig + op, err := logic.AssembleString(`arg 0 +sha256 +byte base64 5rZMNsevs5sULO+54aN+OvU6lQ503z2X+SSYUABIx7E= +==`) + require.NoError(t, err) + s := rand.Intn(len(secrets)) + txnGroups[mod][0].Sig = crypto.Signature{} + txnGroups[mod][0].Txn.Sender = addrs[s] + txnGroups[mod][0].Lsig.Args = [][]byte{[]byte("=0\x97S\x85H\xe9\x91B\xfd\xdb;1\xf5Z\xaec?\xae\xf2I\x93\x08\x12\x94\xaa~\x06\x08\x849b")} + txnGroups[mod][0].Lsig.Logic = op.Program + program := logic.Program(op.Program) + txnGroups[mod][0].Lsig.Sig = secrets[s].Sign(program) + batchSigs, err = getNumberOfBatchableSigsInGroup(txnGroups[mod]) + require.NoError(t, err) + require.Equal(t, uint64(0), batchSigs) + mod++ + + // txn with sig and msig + _, signedTxn, secrets, addrs = generateTestObjects(numOfTxns, 20, 0, 50) + txnGroups = generateTransactionGroups(1, signedTxn, secrets, addrs) + txnGroups[mod][0].Msig = mSigTxn[0].Msig + batchSigs, err = getNumberOfBatchableSigsInGroup(txnGroups[mod]) + require.ErrorIs(t, err, errTxnSigNotWellFormed) +} + +// TestStreamVerifierPoolShutdown tests what happens when the exec pool shuts down +func TestStreamVerifierPoolShutdown(t *testing.T) { //nolint:paralleltest // Not parallel because it depends on the default logger + partitiontest.PartitionTest(t) + + // only one transaction should be sufficient for the batch verifier + // to realize the pool is terminated and to shut down + numOfTxns := 1 + txnGroups, badTxnGroups := getSignedTransactions(numOfTxns, protoMaxGroupSize, 0, 0.5) + + // check the logged information + var logBuffer bytes.Buffer + log := logging.Base() + log.SetOutput(&logBuffer) + log.SetLevel(logging.Info) + + // prepare the stream verifier + numOfTxnGroups := len(txnGroups) + verificationPool := execpool.MakeBacklog(nil, 0, execpool.LowPriority, t) + _, buffLen := verificationPool.BufferSize() + + // make sure the pool is shut down and the buffer is full + holdTasks := make(chan interface{}) + for x := 0; x < buffLen+runtime.NumCPU(); x++ { + verificationPool.EnqueueBacklog(context.Background(), + func(arg interface{}) interface{} { <-holdTasks; return nil }, nil, nil) + } + wg := sync.WaitGroup{} + wg.Add(1) + go func() { + defer wg.Done() + // Shutdown will block until all tasks held by holdTasks is released + verificationPool.Shutdown() + }() + // Send more tasks to break the backlog worker after b.pool.Enqueue returns the error + for x := 0; x < 100; x++ { + verificationPool.EnqueueBacklog(context.Background(), + func(arg interface{}) interface{} { <-holdTasks; return nil }, nil, nil) + } + // release the tasks + close(holdTasks) + + // make sure the EnqueueBacklogis returning err + for x := 0; x < 10; x++ { + err := verificationPool.EnqueueBacklog(context.Background(), + func(arg interface{}) interface{} { return nil }, nil, nil) + require.Error(t, err, fmt.Sprintf("x = %d", x)) + } + + ctx, cancel := context.WithCancel(context.Background()) + cache := MakeVerifiedTransactionCache(50000) + + stxnChan := make(chan *UnverifiedElement) + resultChan := make(chan *VerificationResult, txBacklogSize) + droppedChan := make(chan *UnverifiedElement) + sv, err := MakeStreamVerifier(stxnChan, resultChan, droppedChan, &DummyLedgerForSignature{}, verificationPool, cache) + require.NoError(t, err) + sv.Start(ctx) + + errChan := make(chan error) + + var badSigResultCounter int + var goodSigResultCounter int + + wg.Add(1) + go processResults(ctx, errChan, resultChan, numOfTxnGroups, badTxnGroups, &badSigResultCounter, &goodSigResultCounter, &wg) + + // When the exec pool shuts down, the batch verifier should gracefully stop + // cancel the context so that the test can terminate + wg.Add(1) + go func() { + defer wg.Done() + sv.WaitForStop() + cancel() + }() + + wg.Add(1) + // send txn groups to be verified + go func() { + defer wg.Done() + for _, tg := range txnGroups { + select { + case <-ctx.Done(): + break + case stxnChan <- &UnverifiedElement{TxnGroup: tg, BacklogMessage: nil}: + } + } + }() + for err := range errChan { + require.ErrorIs(t, err, errShuttingDownError) + } + require.Contains(t, logBuffer.String(), "addVerificationTaskToThePoolNow: EnqueueBacklog returned an error and StreamVerifier will stop: context canceled") +} + +// TestStreamVerifierRestart tests what happens when the context is canceled +func TestStreamVerifierRestart(t *testing.T) { + partitiontest.PartitionTest(t) + + numOfTxns := 1000 + txnGroups, badTxnGroups := getSignedTransactions(numOfTxns, 1, 0, 0.5) + + // prepare the stream verifier + numOfTxnGroups := len(txnGroups) + verificationPool := execpool.MakeBacklog(nil, 0, execpool.LowPriority, t) + defer verificationPool.Shutdown() + + cache := MakeVerifiedTransactionCache(50) + + stxnChan := make(chan *UnverifiedElement) + resultChan := make(chan *VerificationResult, txBacklogSize) + droppedChan := make(chan *UnverifiedElement) + + ctx, cancel := context.WithCancel(context.Background()) + sv, err := MakeStreamVerifier(stxnChan, resultChan, droppedChan, &DummyLedgerForSignature{}, verificationPool, cache) + require.NoError(t, err) + sv.Start(ctx) + + errChan := make(chan error) + + var badSigResultCounter int + var goodSigResultCounter int + + ctx2, cancel2 := context.WithCancel(context.Background()) + + wg := sync.WaitGroup{} + wg.Add(1) + go processResults(ctx2, errChan, resultChan, numOfTxnGroups, badTxnGroups, &badSigResultCounter, &goodSigResultCounter, &wg) + + wg.Add(1) + // send txn groups to be verified + go func() { + defer wg.Done() + for i, tg := range txnGroups { + if (i+1)%10 == 0 { + cancel() + sv.WaitForStop() + ctx, cancel = context.WithCancel(context.Background()) + sv.Start(ctx) + } + select { + case <-ctx2.Done(): + break + case stxnChan <- &UnverifiedElement{TxnGroup: tg, BacklogMessage: nil}: + } + } + cancel() + }() + for err := range errChan { + require.ErrorIs(t, err, errShuttingDownError) + } + wg.Wait() + sv.WaitForStop() + cancel2() // not necessary, but the golint will want to see this +} + +// TestBlockWatcher runs multiple goroutines to check the concurency and correctness of the block watcher +func TestStreamVerifierBlockWatcher(t *testing.T) { + partitiontest.PartitionTest(t) + blkHdr := createDummyBlockHeader() + nbw := MakeNewBlockWatcher(blkHdr) + startingRound := blkHdr.Round + + wg := sync.WaitGroup{} + count := 100 + + wg.Add(1) + go func() { + defer wg.Done() + for x := 0; x < 100; x++ { + blkHdr.Round++ + nbw.OnNewBlock(bookkeeping.Block{BlockHeader: blkHdr}, ledgercore.StateDelta{}) + time.Sleep(10 * time.Millisecond) + nbw.OnNewBlock(bookkeeping.Block{BlockHeader: blkHdr}, ledgercore.StateDelta{}) + } + }() + + bhStore := make(map[basics.Round]*bookkeeping.BlockHeader) + wg.Add(1) + go func() { + defer wg.Done() + for { + bh := nbw.getBlockHeader() + bhStore[bh.Round] = bh + if bh.Round == startingRound+10 { + break + } + } + }() + wg.Wait() + bh := nbw.getBlockHeader() + require.Equal(t, uint64(startingRound)+uint64(count), uint64(bh.Round)) + // There should be no inconsistency after new blocks are added + for r, bh := range bhStore { + require.Equal(t, r, bh.Round) + } +} + +func getSaturatedExecPool(t *testing.T) (execpool.BacklogPool, chan interface{}) { + verificationPool := execpool.MakeBacklog(nil, 0, execpool.LowPriority, t) + _, buffLen := verificationPool.BufferSize() + + // make the buffer full to control when the tasks get executed + holdTasks := make(chan interface{}) + for x := 0; x < buffLen+runtime.NumCPU()+1; x++ { + verificationPool.EnqueueBacklog(context.Background(), + func(arg interface{}) interface{} { + <-holdTasks + return nil + }, nil, nil) + } + return verificationPool, holdTasks +} + +// TestStreamVerifierCtxCancel tests the termination when the ctx is canceled +// To make sure that the batchingLoop is still working on a batch when the +// ctx is cancled, this test first saturates the exec pool buffer, then +// sends a txn and immediately cancels the ctx so that the batch is not +// passed to the exec pool yet, but is in batchingLoop +func TestStreamVerifierCtxCancel(t *testing.T) { + partitiontest.PartitionTest(t) + + verificationPool, holdTasks := getSaturatedExecPool(t) + defer verificationPool.Shutdown() + ctx, cancel := context.WithCancel(context.Background()) + cache := MakeVerifiedTransactionCache(50) + stxnChan := make(chan *UnverifiedElement) + resultChan := make(chan *VerificationResult, txBacklogSize) + droppedChan := make(chan *UnverifiedElement) + sv, err := MakeStreamVerifier(stxnChan, resultChan, droppedChan, &DummyLedgerForSignature{}, verificationPool, cache) + require.NoError(t, err) + sv.Start(ctx) + + var result *VerificationResult + wg := sync.WaitGroup{} + wg.Add(1) + go func() { + defer wg.Done() + // no verification tasks should be executed + // one result should be returned + result = <-resultChan + }() + + // send batchSizeBlockLimit after the exec pool buffer is full + numOfTxns := 1 + txnGroups, _ := getSignedTransactions(numOfTxns, 1, 0, 0.5) + stxnChan <- &UnverifiedElement{TxnGroup: txnGroups[0], BacklogMessage: nil} + // cancel the ctx before the sig is sent to the exec pool + cancel() + + // the main loop should stop after cancel() + sv.WaitForStop() + + // release the tasks + close(holdTasks) + + wg.Wait() + require.ErrorIs(t, result.Err, errShuttingDownError) +} + +// TestStreamVerifierCtxCancelPoolQueue tests the termination when the ctx is canceled +// To make sure that the batchingLoop is still working on a batch when the +// ctx is cancled, this test first saturates the exec pool buffer, then +// sends a txn and cancels the ctx after multiple waitForNextTxnDuration +// so that the batch is sent to the pool. Since the pool is saturated, +// the task will be stuck waiting to be queued when the context is canceled +// everything should be gracefully terminated +func TestStreamVerifierCtxCancelPoolQueue(t *testing.T) { //nolint:paralleltest // Not parallel because it depends on the default logger + partitiontest.PartitionTest(t) + + verificationPool, holdTasks := getSaturatedExecPool(t) + + // check the logged information + var logBuffer bytes.Buffer + log := logging.Base() + log.SetOutput(&logBuffer) + log.SetLevel(logging.Info) + + ctx, cancel := context.WithCancel(context.Background()) + cache := MakeVerifiedTransactionCache(50) + stxnChan := make(chan *UnverifiedElement) + resultChan := make(chan *VerificationResult, txBacklogSize) + droppedChan := make(chan *UnverifiedElement) + sv, err := MakeStreamVerifier(stxnChan, resultChan, droppedChan, &DummyLedgerForSignature{}, verificationPool, cache) + require.NoError(t, err) + sv.Start(ctx) + + var result *VerificationResult + wg := sync.WaitGroup{} + wg.Add(1) + go func() { + defer wg.Done() + for { + result = <-resultChan + // at least one errShuttingDownError is expected + if result.Err != errShuttingDownError { + continue + } + break + } + }() + + // send batchSizeBlockLimit after the exec pool buffer is full + numOfTxns := 1 + txnGroups, _ := getSignedTransactions(numOfTxns, 1, 0, 0.5) + + wg.Add(1) + // run in separate goroutine because the exec pool is blocked here, and this will not advance + // until holdTasks are closed + go func() { + defer wg.Done() + for { + select { + // Normally, a single txn is sufficient, but the goroutines could be scheduled is such a way that + // the single transaction slips through and passes the batch verifier before the exec pool shuts down. + // this happens when close(holdTasks) runs and frees the exec pool, and lets the txns get verified, before + // verificationPool.Shutdown() executes. + case stxnChan <- &UnverifiedElement{TxnGroup: txnGroups[0], BacklogMessage: nil}: + case <-ctx.Done(): + return + } + } + }() + // cancel the ctx as the sig is not yet sent to the exec pool + // the test might sporadically fail if between sending the txn above + // and the cancelation, 2 x waitForNextTxnDuration elapses (10ms) + time.Sleep(6 * waitForNextTxnDuration) + go func() { + // wait a bit before releasing the tasks, so that the verificationPool ctx first gets canceled + time.Sleep(20 * time.Millisecond) + close(holdTasks) + }() + verificationPool.Shutdown() + + // the main loop should stop before calling cancel() when the exec pool shuts down and returns an error + sv.WaitForStop() + cancel() + + wg.Wait() + require.ErrorIs(t, result.Err, errShuttingDownError) + require.Contains(t, logBuffer.String(), "addVerificationTaskToThePoolNow: EnqueueBacklog returned an error and StreamVerifier will stop: context canceled") +} + +// TestStreamVerifierPostVBlocked tests the behavior when the return channel (result chan) of verified +// transactions is blocked, and checks droppedFromPool counter to confirm the drops +func TestStreamVerifierPostVBlocked(t *testing.T) { + partitiontest.PartitionTest(t) + + // prepare the stream verifier + verificationPool := execpool.MakeBacklog(nil, 0, execpool.LowPriority, t) + defer verificationPool.Shutdown() + errChan := make(chan error) + var badSigResultCounter int + var goodSigResultCounter int + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + cache := MakeVerifiedTransactionCache(50) + + txBacklogSizeMod := txBacklogSize / 20 + + stxnChan := make(chan *UnverifiedElement) + resultChan := make(chan *VerificationResult, txBacklogSizeMod) + droppedChan := make(chan *UnverifiedElement) + sv, err := MakeStreamVerifier(stxnChan, resultChan, droppedChan, &DummyLedgerForSignature{}, verificationPool, cache) + require.NoError(t, err) + + defer close(droppedChan) + go func() { + for range droppedChan { + droppedFromPool.Inc(nil) + } + }() + + // start the verifier + sv.Start(ctx) + overflow := 3 + // send txBacklogSizeMod + 3 transactions to overflow the result buffer + numOfTxns := txBacklogSizeMod + overflow + txnGroups, badTxnGroups := getSignedTransactions(numOfTxns, 1, 0, 0.5) + numOfTxnGroups := len(txnGroups) + for _, tg := range txnGroups { + stxnChan <- &UnverifiedElement{TxnGroup: tg, BacklogMessage: nil} + } + + var droppedPool uint64 + // wait until overflow transactions are dropped + for w := 0; w < 100; w++ { + droppedPool = droppedFromPool.GetUint64Value() + if droppedPool >= uint64(overflow) { + break + } + time.Sleep(time.Millisecond * 20) + } + + require.Equal(t, uint64(overflow), droppedPool) + + wg := sync.WaitGroup{} + wg.Add(1) + // make sure the other results are fine + go processResults(ctx, errChan, resultChan, numOfTxnGroups-overflow, badTxnGroups, &badSigResultCounter, &goodSigResultCounter, &wg) + + for err := range errChan { + require.ErrorIs(t, err, errShuttingDownError) + fmt.Println(badTxnGroups) + } + + // check if more transactions can be accepted + errChan = make(chan error) + + wg.Add(1) + // make sure the other results are fine + txnGroups, badTxnGroups2 := getSignedTransactions(numOfTxns, 1, numOfTxns, 0.5) + // need to combine these, since left overs from the previous one could still come out + for b := range badTxnGroups2 { + badTxnGroups[b] = struct{}{} + } + go processResults(ctx, errChan, resultChan, numOfTxnGroups, badTxnGroups, &badSigResultCounter, &goodSigResultCounter, &wg) + + for _, tg := range txnGroups { + stxnChan <- &UnverifiedElement{TxnGroup: tg, BacklogMessage: nil} + } + + for err := range errChan { + require.ErrorIs(t, err, errShuttingDownError) + fmt.Println(badTxnGroups) + } + + wg.Wait() +} + +func TestStreamVerifierMakeStreamVerifierErr(t *testing.T) { + partitiontest.PartitionTest(t) + _, err := MakeStreamVerifier(nil, nil, nil, &DummyLedgerForSignature{badHdr: true}, nil, nil) + require.Error(t, err) +} + +// TestStreamVerifierCancelWhenPooled tests the case where the ctx is cancled after the verification +// task is queued to the exec pool and before the task is executed in the pool +func TestStreamVerifierCancelWhenPooled(t *testing.T) { + partitiontest.PartitionTest(t) + numOfTxns := 1000 + txnGroups, badTxnGroups := getSignedTransactions(numOfTxns, 1, 0, 0.5) + + // prepare the stream verifier + numOfTxnGroups := len(txnGroups) + execPool := execpool.MakePool(t) + defer execPool.Shutdown() + verificationPool := execpool.MakeBacklog(execPool, 64, execpool.LowPriority, t) + defer verificationPool.Shutdown() + + cache := MakeVerifiedTransactionCache(50) + + stxnChan := make(chan *UnverifiedElement) + resultChan := make(chan *VerificationResult, txBacklogSize) + droppedChan := make(chan *UnverifiedElement) + ctx, cancel := context.WithCancel(context.Background()) + sv, err := MakeStreamVerifier(stxnChan, resultChan, droppedChan, &DummyLedgerForSignature{}, verificationPool, cache) + require.NoError(t, err) + sv.Start(ctx) + + errChan := make(chan error) + + var badSigResultCounter int + var goodSigResultCounter int + + ctx2, cancel2 := context.WithCancel(context.Background()) + + wg := sync.WaitGroup{} + wg.Add(1) + go processResults(ctx2, errChan, resultChan, numOfTxnGroups, badTxnGroups, &badSigResultCounter, &goodSigResultCounter, &wg) + + wg.Add(1) + // send txn groups to be verified + go func() { + defer wg.Done() + for _, tg := range txnGroups { + stxnChan <- &UnverifiedElement{TxnGroup: tg, BacklogMessage: nil} + } + // cancel the ctx, and expect at least one task queued to the pool but not yet executed + cancel() + }() + for err := range errChan { + require.ErrorIs(t, err, errShuttingDownError) + } + wg.Wait() + sv.WaitForStop() + cancel2() // not necessary, but the golint will want to see this +} diff --git a/data/transactions/verify/verifiedTxnCache.go b/data/transactions/verify/verifiedTxnCache.go index 82f0e9772e..efa4701506 100644 --- a/data/transactions/verify/verifiedTxnCache.go +++ b/data/transactions/verify/verifiedTxnCache.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -59,7 +59,7 @@ type VerifiedTransactionCache interface { // in the cache, the new entry overrides the old one. Add(txgroup []transactions.SignedTxn, groupCtx *GroupContext) // AddPayset works in a similar way to Add, but is intended for adding an array of transaction groups, along with their corresponding contexts. - AddPayset(txgroup [][]transactions.SignedTxn, groupCtxs []*GroupContext) error + AddPayset(txgroup [][]transactions.SignedTxn, groupCtxs []*GroupContext) // GetUnverifiedTransactionGroups compares the provided payset against the currently cached transactions and figure which transaction groups aren't fully cached. GetUnverifiedTransactionGroups(payset [][]transactions.SignedTxn, CurrSpecAddrs transactions.SpecialAddresses, CurrProto protocol.ConsensusVersion) [][]transactions.SignedTxn // UpdatePinned replaces the pinned entries with the one provided in the pinnedTxns map. This is typically expected to be a subset of the @@ -106,13 +106,12 @@ func (v *verifiedTransactionCache) Add(txgroup []transactions.SignedTxn, groupCt } // AddPayset works in a similar way to Add, but is intended for adding an array of transaction groups, along with their corresponding contexts. -func (v *verifiedTransactionCache) AddPayset(txgroup [][]transactions.SignedTxn, groupCtxs []*GroupContext) error { +func (v *verifiedTransactionCache) AddPayset(txgroup [][]transactions.SignedTxn, groupCtxs []*GroupContext) { v.bucketsLock.Lock() defer v.bucketsLock.Unlock() for i := range txgroup { v.add(txgroup[i], groupCtxs[i]) } - return nil } // GetUnverifiedTransactionGroups compares the provided payset against the currently cached transactions and figure which transaction groups aren't fully cached. @@ -268,8 +267,7 @@ func (v *mockedCache) Add(txgroup []transactions.SignedTxn, groupCtx *GroupConte return } -func (v *mockedCache) AddPayset(txgroup [][]transactions.SignedTxn, groupCtxs []*GroupContext) error { - return nil +func (v *mockedCache) AddPayset(txgroup [][]transactions.SignedTxn, groupCtxs []*GroupContext) { } func (v *mockedCache) GetUnverifiedTransactionGroups(txnGroups [][]transactions.SignedTxn, currSpecAddrs transactions.SpecialAddresses, currProto protocol.ConsensusVersion) (unverifiedGroups [][]transactions.SignedTxn) { diff --git a/data/transactions/verify/verifiedTxnCache_test.go b/data/transactions/verify/verifiedTxnCache_test.go index e3001db674..b90eb66efa 100644 --- a/data/transactions/verify/verifiedTxnCache_test.go +++ b/data/transactions/verify/verifiedTxnCache_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -32,8 +32,8 @@ func TestAddingToCache(t *testing.T) { icache := MakeVerifiedTransactionCache(500) impl := icache.(*verifiedTransactionCache) - _, signedTxn, secrets, addrs := generateTestObjects(10, 5, 50) - txnGroups := generateTransactionGroups(signedTxn, secrets, addrs) + _, signedTxn, secrets, addrs := generateTestObjects(10, 5, 0, 50) + txnGroups := generateTransactionGroups(protoMaxGroupSize, signedTxn, secrets, addrs) groupCtx, err := PrepareGroupContext(txnGroups[0], blockHeader, nil) require.NoError(t, err) impl.Add(txnGroups[0], groupCtx) @@ -52,7 +52,7 @@ func TestBucketCycling(t *testing.T) { entriesPerBucket := 100 icache := MakeVerifiedTransactionCache(entriesPerBucket * (bucketCount - 1)) impl := icache.(*verifiedTransactionCache) - _, signedTxn, _, _ := generateTestObjects(entriesPerBucket*bucketCount*2, bucketCount, 0) + _, signedTxn, _, _ := generateTestObjects(entriesPerBucket*bucketCount*2, bucketCount, 0, 0) require.Equal(t, entriesPerBucket*bucketCount*2, len(signedTxn)) groupCtx, err := PrepareGroupContext([]transactions.SignedTxn{signedTxn[0]}, blockHeader, nil) @@ -82,8 +82,8 @@ func TestGetUnverifiedTransactionGroups50(t *testing.T) { size := 300 icache := MakeVerifiedTransactionCache(size * 2) impl := icache.(*verifiedTransactionCache) - _, signedTxn, secrets, addrs := generateTestObjects(size*2, 10+size/1000, 0) - txnGroups := generateTransactionGroups(signedTxn, secrets, addrs) + _, signedTxn, secrets, addrs := generateTestObjects(size*2, 10+size/1000, 0, 0) + txnGroups := generateTransactionGroups(protoMaxGroupSize, signedTxn, secrets, addrs) expectedUnverifiedGroups := make([][]transactions.SignedTxn, 0, len(txnGroups)/2) // add every even transaction to the cache. @@ -107,8 +107,8 @@ func BenchmarkGetUnverifiedTransactionGroups50(b *testing.B) { } icache := MakeVerifiedTransactionCache(b.N * 2) impl := icache.(*verifiedTransactionCache) - _, signedTxn, secrets, addrs := generateTestObjects(b.N*2, 10+b.N/1000, 0) - txnGroups := generateTransactionGroups(signedTxn, secrets, addrs) + _, signedTxn, secrets, addrs := generateTestObjects(b.N*2, 10+b.N/1000, 0, 0) + txnGroups := generateTransactionGroups(protoMaxGroupSize, signedTxn, secrets, addrs) queryTxnGroups := make([][]transactions.SignedTxn, 0, b.N) // add every even transaction to the cache. @@ -140,8 +140,8 @@ func TestUpdatePinned(t *testing.T) { size := 100 icache := MakeVerifiedTransactionCache(size * 10) impl := icache.(*verifiedTransactionCache) - _, signedTxn, secrets, addrs := generateTestObjects(size*2, 10, 0) - txnGroups := generateTransactionGroups(signedTxn, secrets, addrs) + _, signedTxn, secrets, addrs := generateTestObjects(size*2, 10, 0, 0) + txnGroups := generateTransactionGroups(protoMaxGroupSize, signedTxn, secrets, addrs) // insert some entries. for i := 0; i < len(txnGroups); i++ { @@ -169,8 +169,8 @@ func TestPinningTransactions(t *testing.T) { size := 100 icache := MakeVerifiedTransactionCache(size) impl := icache.(*verifiedTransactionCache) - _, signedTxn, secrets, addrs := generateTestObjects(size*2, 10, 0) - txnGroups := generateTransactionGroups(signedTxn, secrets, addrs) + _, signedTxn, secrets, addrs := generateTestObjects(size*2, 10, 0, 0) + txnGroups := generateTransactionGroups(protoMaxGroupSize, signedTxn, secrets, addrs) // insert half of the entries. for i := 0; i < len(txnGroups)/2; i++ { diff --git a/data/txDupCache.go b/data/txDupCache.go index 026a168578..96a426dc7b 100644 --- a/data/txDupCache.go +++ b/data/txDupCache.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -109,6 +109,7 @@ type txSaltedCache struct { curSalt [4]byte prevSalt [4]byte ctx context.Context + wg sync.WaitGroup } func makeSaltedCache(size int) *txSaltedCache { @@ -117,9 +118,10 @@ func makeSaltedCache(size int) *txSaltedCache { } } -func (c *txSaltedCache) start(ctx context.Context, refreshInterval time.Duration) { +func (c *txSaltedCache) Start(ctx context.Context, refreshInterval time.Duration) { c.ctx = ctx if refreshInterval != 0 { + c.wg.Add(1) go c.salter(refreshInterval) } @@ -128,9 +130,14 @@ func (c *txSaltedCache) start(ctx context.Context, refreshInterval time.Duration c.moreSalt() } +func (c *txSaltedCache) WaitForStop() { + c.wg.Wait() +} + // salter is a goroutine refreshing the cache by schedule func (c *txSaltedCache) salter(refreshInterval time.Duration) { ticker := time.NewTicker(refreshInterval) + defer c.wg.Done() defer ticker.Stop() for { select { @@ -221,6 +228,12 @@ func (c *txSaltedCache) CheckAndPut(msg []byte) (*crypto.Digest, bool) { // already added to cache between RUnlock() and Lock(), return return d, found } + } else { + // Do another check to see if another copy of the transaction won the race to write it to the cache + // Only check current to save a lookup since swaps are rare and no need to re-hash + if _, found := c.cur[*d]; found { + return d, found + } } if len(c.cur) >= c.maxSize { diff --git a/data/txDupCache_test.go b/data/txDupCache_test.go index e0bfac25a5..13a36b19d1 100644 --- a/data/txDupCache_test.go +++ b/data/txDupCache_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -120,7 +120,7 @@ func TestTxHandlerSaltedCacheBasic(t *testing.T) { const size = 20 cache := makeSaltedCache(size) - cache.start(context.Background(), 0) + cache.Start(context.Background(), 0) require.Zero(t, cache.Len()) // add some unique random @@ -204,7 +204,7 @@ func TestTxHandlerSaltedCacheScheduled(t *testing.T) { const size = 20 updateInterval := 1000 * time.Microsecond cache := makeSaltedCache(size) - cache.start(context.Background(), updateInterval) + cache.Start(context.Background(), updateInterval) require.Zero(t, cache.Len()) // add some unique random @@ -229,7 +229,7 @@ func TestTxHandlerSaltedCacheManual(t *testing.T) { const size = 20 cache := makeSaltedCache(2 * size) - cache.start(context.Background(), 0) + cache.Start(context.Background(), 0) require.Zero(t, cache.Len()) // add some unique random @@ -294,7 +294,7 @@ func (m digestCacheMaker) make(size int) cachePusher { } func (m saltedCacheMaker) make(size int) cachePusher { scp := &saltedCachePusher{c: makeSaltedCache(size)} - scp.c.start(context.Background(), 0) + scp.c.Start(context.Background(), 0) return scp } diff --git a/data/txHandler.go b/data/txHandler.go index ade8b80187..66cac800af 100644 --- a/data/txHandler.go +++ b/data/txHandler.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -35,16 +35,11 @@ import ( "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/network" "github.com/algorand/go-algorand/protocol" + "github.com/algorand/go-algorand/util" "github.com/algorand/go-algorand/util/execpool" "github.com/algorand/go-algorand/util/metrics" ) -// The size txBacklogSize used to determine the size of the backlog that is used to store incoming transaction messages before starting dropping them. -// It should be configured to be higher then the number of CPU cores, so that the execution pool get saturated, but not too high to avoid lockout of the -// execution pool for a long duration of time. -// Set backlog at 'approximately one block' by dividing block size by a typical transaction size. -var txBacklogSize = config.Consensus[protocol.ConsensusCurrentVersion].MaxTxnBytesPerBlock / 200 - var transactionMessagesHandled = metrics.MakeCounter(metrics.TransactionMessagesHandled) var transactionMessagesDroppedFromBacklog = metrics.MakeCounter(metrics.TransactionMessagesDroppedFromBacklog) var transactionMessagesDroppedFromPool = metrics.MakeCounter(metrics.TransactionMessagesDroppedFromPool) @@ -66,6 +61,13 @@ var transactionMessagesBacklogSizeGauge = metrics.MakeGauge(metrics.TransactionM var transactionGroupTxSyncHandled = metrics.MakeCounter(metrics.TransactionGroupTxSyncHandled) var transactionGroupTxSyncRemember = metrics.MakeCounter(metrics.TransactionGroupTxSyncRemember) var transactionGroupTxSyncAlreadyCommitted = metrics.MakeCounter(metrics.TransactionGroupTxSyncAlreadyCommitted) +var txBacklogDroppedCongestionManagement = metrics.MakeCounter(metrics.TransactionMessagesTxnDroppedCongestionManagement) + +// ErrInvalidTxPool is reported when nil is passed for the tx pool +var ErrInvalidTxPool = errors.New("MakeTxHandler: txPool is nil on initialization") + +// ErrInvalidLedger is reported when nil is passed for the ledger +var ErrInvalidLedger = errors.New("MakeTxHandler: ledger is nil on initialization") var transactionMessageTxPoolRememberCounter = metrics.NewTagCounter( "algod_transaction_messages_txpool_remember_err_{TAG}", "Number of transaction messages not remembered by txpool b/c of {TAG}", @@ -104,6 +106,7 @@ type txBacklogMsg struct { rawmsgDataHash *crypto.Digest // hash (if any) of raw message data from the network unverifiedTxGroupHash *crypto.Digest // hash (if any) of the unverifiedTxGroup verificationErr error // The verification error generated by the verification function, if any. + capguard *util.ErlCapacityGuard // the structure returned from the elastic rate limiter, to be released when dequeued } // TxHandler handles transaction messages @@ -114,7 +117,7 @@ type TxHandler struct { genesisHash crypto.Digest txVerificationPool execpool.BacklogPool backlogQueue chan *txBacklogMsg - postVerificationQueue chan *txBacklogMsg + postVerificationQueue chan *verify.VerificationResult backlogWg sync.WaitGroup net network.GossipNode msgCache *txSaltedCache @@ -122,6 +125,10 @@ type TxHandler struct { cacheConfig txHandlerConfig ctx context.Context ctxCancel context.CancelFunc + streamVerifier *verify.StreamVerifier + streamVerifierChan chan *verify.UnverifiedElement + streamVerifierDropped chan *verify.UnverifiedElement + erl *util.ElasticRateLimiter } // TxHandlerOpts is TxHandler configuration options @@ -142,16 +149,20 @@ type txHandlerConfig struct { } // MakeTxHandler makes a new handler for transaction messages -func MakeTxHandler(opts TxHandlerOpts) *TxHandler { +func MakeTxHandler(opts TxHandlerOpts) (*TxHandler, error) { if opts.TxPool == nil { - logging.Base().Fatal("MakeTxHandler: txPool is nil on initialization") - return nil + return nil, ErrInvalidTxPool } if opts.Ledger == nil { - logging.Base().Fatal("MakeTxHandler: ledger is nil on initialization") - return nil + return nil, ErrInvalidLedger + } + + // backlog size is big enough for each peer to have its reserved capacity in the backlog, plus the config's backlogSize as a shared capacity + txBacklogSize := opts.Config.TxBacklogSize + if opts.Config.EnableTxBacklogRateLimiting { + txBacklogSize += (opts.Config.IncomingConnectionsLimit * opts.Config.TxBacklogReservedCapacityPerPeer) } handler := &TxHandler{ @@ -161,31 +172,75 @@ func MakeTxHandler(opts TxHandlerOpts) *TxHandler { ledger: opts.Ledger, txVerificationPool: opts.ExecutionPool, backlogQueue: make(chan *txBacklogMsg, txBacklogSize), - postVerificationQueue: make(chan *txBacklogMsg, txBacklogSize), + postVerificationQueue: make(chan *verify.VerificationResult, txBacklogSize), net: opts.Net, msgCache: makeSaltedCache(2 * txBacklogSize), txCanonicalCache: makeDigestCache(2 * txBacklogSize), cacheConfig: txHandlerConfig{opts.Config.TxFilterRawMsgEnabled(), opts.Config.TxFilterCanonicalEnabled()}, + streamVerifierChan: make(chan *verify.UnverifiedElement), + streamVerifierDropped: make(chan *verify.UnverifiedElement), + } + + if opts.Config.EnableTxBacklogRateLimiting { + rateLimiter := util.NewElasticRateLimiter( + txBacklogSize, + opts.Config.TxBacklogReservedCapacityPerPeer, + time.Duration(opts.Config.TxBacklogServiceRateWindowSeconds)*time.Second, + txBacklogDroppedCongestionManagement, + ) + handler.erl = rateLimiter + } + + // prepare the transaction stream verifer + var err error + handler.streamVerifier, err = verify.MakeStreamVerifier(handler.streamVerifierChan, + handler.postVerificationQueue, handler.streamVerifierDropped, handler.ledger, + handler.txVerificationPool, handler.ledger.VerifiedTransactionCache()) + if err != nil { + return nil, err + } + go handler.droppedTxnWatcher() + return handler, nil +} + +func (handler *TxHandler) droppedTxnWatcher() { + for unverified := range handler.streamVerifierDropped { + // we failed to write to the output queue, since the queue was full. + // adding the metric here allows us to monitor how frequently it happens. + transactionMessagesDroppedFromPool.Inc(nil) + + tx := unverified.BacklogMessage.(*txBacklogMsg) + + // delete from duplicate caches to give it a chance to be re-submitted + handler.deleteFromCaches(tx.rawmsgDataHash, tx.unverifiedTxGroupHash) } - return handler } // Start enables the processing of incoming messages at the transaction handler func (handler *TxHandler) Start() { handler.ctx, handler.ctxCancel = context.WithCancel(context.Background()) - handler.msgCache.start(handler.ctx, 60*time.Second) + handler.msgCache.Start(handler.ctx, 60*time.Second) handler.net.RegisterHandlers([]network.TaggedMessageHandler{ {Tag: protocol.TxnTag, MessageHandler: network.HandlerFunc(handler.processIncomingTxn)}, }) handler.backlogWg.Add(2) go handler.backlogWorker() go handler.backlogGaugeThread() + handler.streamVerifier.Start(handler.ctx) + if handler.erl != nil { + handler.erl.Start() + } } // Stop suspends the processing of incoming messages at the transaction handler func (handler *TxHandler) Stop() { handler.ctxCancel() + if handler.erl != nil { + handler.erl.Stop() + } handler.backlogWg.Wait() + handler.streamVerifier.WaitForStop() + handler.msgCache.WaitForStop() } func reencode(stxns []transactions.SignedTxn) []byte { @@ -203,7 +258,7 @@ func (handler *TxHandler) backlogGaugeThread() { for { select { case <-ticker.C: - transactionMessagesBacklogSizeGauge.Set(float64(len(handler.backlogQueue))) + transactionMessagesBacklogSizeGauge.Set(uint64(len(handler.backlogQueue))) case <-handler.ctx.Done(): return } @@ -223,7 +278,9 @@ func (handler *TxHandler) backlogWorker() { if !ok { return } - handler.postProcessCheckedTxn(wi) + m := wi.BacklogMessage.(*txBacklogMsg) + m.verificationErr = wi.Err + handler.postProcessCheckedTxn(m) // restart the loop so that we could empty out the post verification queue. continue @@ -234,21 +291,39 @@ func (handler *TxHandler) backlogWorker() { select { case wi, ok := <-handler.backlogQueue: if !ok { + // this is never happening since handler.backlogQueue is never closed return } + if wi.capguard != nil { + if err := wi.capguard.Release(); err != nil { + logging.Base().Warnf("Failed to release capacity to ElasticRateLimiter: %v", err) + } + } if handler.checkAlreadyCommitted(wi) { transactionMessagesAlreadyCommitted.Inc(nil) + if wi.capguard != nil { + wi.capguard.Served() + } continue } - - // enqueue the task to the verification pool. - handler.txVerificationPool.EnqueueBacklog(handler.ctx, handler.asyncVerifySignature, wi, nil) - + // handler.streamVerifierChan does not receive if ctx is cancled + select { + case handler.streamVerifierChan <- &verify.UnverifiedElement{TxnGroup: wi.unverifiedTxGroup, BacklogMessage: wi}: + case <-handler.ctx.Done(): + transactionMessagesDroppedFromBacklog.Inc(nil) + return + } + if wi.capguard != nil { + wi.capguard.Served() + } case wi, ok := <-handler.postVerificationQueue: if !ok { + // this is never happening since handler.postVerificationQueue is never closed return } - handler.postProcessCheckedTxn(wi) + m := wi.BacklogMessage.(*txBacklogMsg) + m.verificationErr = wi.Err + handler.postProcessCheckedTxn(m) case <-handler.ctx.Done(): return @@ -257,7 +332,7 @@ func (handler *TxHandler) backlogWorker() { } func (handler *TxHandler) postProcessReportErrors(err error) { - if errors.Is(err, crypto.ErrBatchVerificationFailed) { + if errors.Is(err, crypto.ErrBatchHasFailedSigs) { transactionMessagesTxnSigVerificationFailed.Inc(nil) return } @@ -421,36 +496,6 @@ func (handler *TxHandler) postProcessCheckedTxn(wi *txBacklogMsg) { handler.net.Relay(handler.ctx, protocol.TxnTag, reencode(verifiedTxGroup), false, wi.rawmsg.Sender) } -// asyncVerifySignature verifies that the given transaction group is valid, and update the txBacklogMsg data structure accordingly. -func (handler *TxHandler) asyncVerifySignature(arg interface{}) interface{} { - tx := arg.(*txBacklogMsg) - - // build the transaction verification context - latest := handler.ledger.Latest() - latestHdr, err := handler.ledger.BlockHdr(latest) - if err != nil { - tx.verificationErr = fmt.Errorf("could not get header for previous block %d: %w", latest, err) - logging.Base().Warnf("Could not get header for previous block %d: %v", latest, err) - } else { - // we can't use PaysetGroups here since it's using a execpool like this go-routine and we don't want to deadlock. - _, tx.verificationErr = verify.TxnGroup(tx.unverifiedTxGroup, latestHdr, handler.ledger.VerifiedTransactionCache(), handler.ledger) - } - - select { - case handler.postVerificationQueue <- tx: - default: - // we failed to write to the output queue, since the queue was full. - // adding the metric here allows us to monitor how frequently it happens. - transactionMessagesDroppedFromPool.Inc(nil) - - // delete from duplicate caches to give it chance to be re-submitted - // this relatively rare operation and implementation is expensive (requires re-hashing) - handler.deleteFromCaches(tx.rawmsgDataHash, tx.unverifiedTxGroupHash) - - } - return nil -} - func (handler *TxHandler) deleteFromCaches(msgKey *crypto.Digest, canonicalKey *crypto.Digest) { if handler.cacheConfig.enableFilteringCanonical && canonicalKey != nil { handler.txCanonicalCache.Delete(canonicalKey) @@ -529,6 +574,27 @@ func (handler *TxHandler) processIncomingTxn(rawmsg network.IncomingMessage) net dec := protocol.NewMsgpDecoderBytes(rawmsg.Data) ntx := 0 consumed := 0 + + var err error + var capguard *util.ErlCapacityGuard + if handler.erl != nil { + // consume a capacity unit + // if the elastic rate limiter cannot vend a capacity, the error it returns + // is sufficient to indicate that we should enable Congestion Control, because + // an issue in vending capacity indicates the underlying resource (TXBacklog) is full + capguard, err = handler.erl.ConsumeCapacity(rawmsg.Sender.(util.ErlClient)) + if err != nil { + handler.erl.EnableCongestionControl() + // if there is no capacity, it is the same as if we failed to put the item onto the backlog, so report such + transactionMessagesDroppedFromBacklog.Inc(nil) + return network.OutgoingMessage{Action: network.Ignore} + } + // if the backlog Queue has 50% of its buffer back, turn congestion control off + if float64(cap(handler.backlogQueue))*0.5 > float64(len(handler.backlogQueue)) { + handler.erl.DisableCongestionControl() + } + } + for { if len(unverifiedTxGroup) == ntx { n := make([]transactions.SignedTxn, len(unverifiedTxGroup)*2) @@ -579,6 +645,7 @@ func (handler *TxHandler) processIncomingTxn(rawmsg network.IncomingMessage) net unverifiedTxGroup: unverifiedTxGroup, rawmsgDataHash: msgKey, unverifiedTxGroupHash: canonicalKey, + capguard: capguard, }: default: // if we failed here we want to increase the corresponding metric. It might suggest that we diff --git a/data/txHandler_test.go b/data/txHandler_test.go index 4e8dcdfe1d..275a8e9e3f 100644 --- a/data/txHandler_test.go +++ b/data/txHandler_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -33,6 +33,8 @@ import ( "github.com/stretchr/testify/require" + "github.com/algorand/go-deadlock" + "github.com/algorand/go-algorand/agreement" "github.com/algorand/go-algorand/components/mocks" "github.com/algorand/go-algorand/config" @@ -42,7 +44,7 @@ import ( "github.com/algorand/go-algorand/data/pools" "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/data/transactions/verify" - realledger "github.com/algorand/go-algorand/ledger" + "github.com/algorand/go-algorand/data/txntest" "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/network" @@ -50,11 +52,17 @@ import ( "github.com/algorand/go-algorand/test/partitiontest" "github.com/algorand/go-algorand/util/execpool" "github.com/algorand/go-algorand/util/metrics" - - "github.com/algorand/go-deadlock" ) -func makeTestGenesisAccounts(tb require.TestingT, numUsers int) ([]basics.Address, []*crypto.SignatureSecrets, map[basics.Address]basics.AccountData) { +// txHandler uses config values to determine backlog size. Tests should use a static value +var txBacklogSize = 26000 + +// mock sender is used to implement OnClose, since TXHandlers expect to use Senders and ERL Clients +type mockSender struct{} + +func (m mockSender) OnClose(func()) {} + +func makeTestGenesisAccounts(tb testing.TB, numUsers int) ([]basics.Address, []*crypto.SignatureSecrets, map[basics.Address]basics.AccountData) { addresses := make([]basics.Address, numUsers) secrets := make([]*crypto.SignatureSecrets, numUsers) genesis := make(map[basics.Address]basics.AccountData) @@ -89,14 +97,20 @@ func BenchmarkTxHandlerProcessing(b *testing.B) { const inMem = true cfg := config.GetDefaultLocal() cfg.Archival = true + cfg.TxBacklogReservedCapacityPerPeer = 1 + cfg.IncomingConnectionsLimit = 10 ledger, err := LoadLedger(log, ledgerName, inMem, protocol.ConsensusCurrentVersion, genBal, genesisID, genesisHash, nil, cfg) require.NoError(b, err) + defer ledger.Close() l := ledger cfg.TxPoolSize = 75000 cfg.EnableProcessBlockStats = false - txHandler := makeTestTxHandler(l, cfg) + txHandler, err := makeTestTxHandler(l, cfg) + require.NoError(b, err) + defer txHandler.txVerificationPool.Shutdown() + defer close(txHandler.streamVerifierDropped) makeTxns := func(N int) [][]transactions.SignedTxn { ret := make([][]transactions.SignedTxn, 0, N) @@ -141,7 +155,7 @@ func BenchmarkTxHandlerProcessing(b *testing.B) { b.Logf("verifying %d signedTransactionGroups", len(signedTransactionGroups)) b.ResetTimer() for i := range signedTransactionGroups { - verify.TxnGroup(signedTransactionGroups[i], hdr, vtc, l) + verify.TxnGroup(signedTransactionGroups[i], &hdr, vtc, l) } }) } @@ -150,8 +164,8 @@ func BenchmarkTxHandlerProcessing(b *testing.B) { type vtCache struct{} func (vtCache) Add(txgroup []transactions.SignedTxn, groupCtx *verify.GroupContext) {} -func (vtCache) AddPayset(txgroup [][]transactions.SignedTxn, groupCtxs []*verify.GroupContext) error { - return nil +func (vtCache) AddPayset(txgroup [][]transactions.SignedTxn, groupCtxs []*verify.GroupContext) { + return } func (vtCache) GetUnverifiedTransactionGroups(payset [][]transactions.SignedTxn, CurrSpecAddrs transactions.SpecialAddresses, CurrProto protocol.ConsensusVersion) [][]transactions.SignedTxn { return nil @@ -214,7 +228,7 @@ func TestTxHandlerProcessIncomingTxn(t *testing.T) { const numTxns = 11 handler := makeTestTxHandlerOrphaned(1) stxns, blob := makeRandomTransactions(numTxns) - action := handler.processIncomingTxn(network.IncomingMessage{Data: blob}) + action := handler.processIncomingTxn(network.IncomingMessage{Data: blob, Sender: mockSender{}}) require.Equal(t, network.OutgoingMessage{Action: network.Ignore}, action) require.Equal(t, 1, len(handler.backlogQueue)) @@ -432,6 +446,45 @@ func BenchmarkTxHandlerProcessIncomingTxn16(b *testing.B) { finConsume() } +// BenchmarkTxHandlerProcessIncomingLogicTxn16 is similar to BenchmarkTxHandlerProcessIncomingTxn16 +// but with logicsig groups of 4 txns +func BenchmarkTxHandlerProcessIncomingLogicTxn16(b *testing.B) { + deadlockDisable := deadlock.Opts.Disable + deadlock.Opts.Disable = true + defer func() { + deadlock.Opts.Disable = deadlockDisable + }() + + const numSendThreads = 16 + handler := makeTestTxHandlerOrphaned(txBacklogSize) + + // prepare tx groups + blobs := make([][]byte, b.N) + stxns := make([][]transactions.SignedTxn, b.N) + for i := 0; i < b.N; i++ { + txns := txntest.CreateTinyManTxGroup(b, true) + stxns[i], _ = txntest.CreateTinyManSignedTxGroup(b, txns) + var blob []byte + for j := range stxns[i] { + encoded := protocol.Encode(&stxns[i][j]) + blob = append(blob, encoded...) + } + blobs[i] = blob + } + numTxnsPerGroup := len(stxns[0]) + + statsCh := make(chan [4]int, 1) + defer close(statsCh) + finConsume := benchTxHandlerProcessIncomingTxnConsume(b, handler, numTxnsPerGroup, 0, statsCh) + + // submit tx groups + b.ResetTimer() + finalizeSubmit := benchTxHandlerProcessIncomingTxnSubmit(b, handler, blobs, numSendThreads) + + finalizeSubmit() + finConsume() +} + // BenchmarkTxHandlerIncDeDup checks txn receiving with duplicates // simulating processing delay func BenchmarkTxHandlerIncDeDup(b *testing.B) { @@ -624,7 +677,7 @@ func TestTxHandlerProcessIncomingCensoring(t *testing.T) { } t.Run("single", func(t *testing.T) { - handler := makeTestTxHandlerOrphaned(txBacklogSize) + handler := makeTestTxHandlerOrphanedWithContext(context.Background(), txBacklogSize, txBacklogSize, txHandlerConfig{true, true}, 0) stxns, blob := makeRandomTransactions(1) stxn := stxns[0] action := handler.processIncomingTxn(network.IncomingMessage{Data: blob}) @@ -649,7 +702,7 @@ func TestTxHandlerProcessIncomingCensoring(t *testing.T) { }) t.Run("group", func(t *testing.T) { - handler := makeTestTxHandlerOrphaned(txBacklogSize) + handler := makeTestTxHandlerOrphanedWithContext(context.Background(), txBacklogSize, txBacklogSize, txHandlerConfig{true, true}, 0) num := rand.Intn(config.MaxTxGroupSize-1) + 2 // 2..config.MaxTxGroupSize require.LessOrEqual(t, num, config.MaxTxGroupSize) stxns, blob := makeRandomTransactions(num) @@ -719,10 +772,10 @@ func TestTxHandlerProcessIncomingCensoring(t *testing.T) { // makeTestTxHandlerOrphaned creates a tx handler without any backlog consumer. // It is caller responsibility to run a consumer thread. func makeTestTxHandlerOrphaned(backlogSize int) *TxHandler { - return makeTestTxHandlerOrphanedWithContext(context.Background(), txBacklogSize, txBacklogSize, 0) + return makeTestTxHandlerOrphanedWithContext(context.Background(), txBacklogSize, txBacklogSize, txHandlerConfig{true, false}, 0) } -func makeTestTxHandlerOrphanedWithContext(ctx context.Context, backlogSize int, cacheSize int, refreshInterval time.Duration) *TxHandler { +func makeTestTxHandlerOrphanedWithContext(ctx context.Context, backlogSize int, cacheSize int, txHandlerConfig txHandlerConfig, refreshInterval time.Duration) *TxHandler { if backlogSize <= 0 { backlogSize = txBacklogSize } @@ -733,13 +786,13 @@ func makeTestTxHandlerOrphanedWithContext(ctx context.Context, backlogSize int, backlogQueue: make(chan *txBacklogMsg, backlogSize), msgCache: makeSaltedCache(cacheSize), txCanonicalCache: makeDigestCache(cacheSize), - cacheConfig: txHandlerConfig{true, true}, + cacheConfig: txHandlerConfig, } - handler.msgCache.start(ctx, refreshInterval) + handler.msgCache.Start(ctx, refreshInterval) return handler } -func makeTestTxHandler(dl *Ledger, cfg config.Local) *TxHandler { +func makeTestTxHandler(dl *Ledger, cfg config.Local) (*TxHandler, error) { tp := pools.MakeTransactionPool(dl.Ledger, cfg, logging.Base()) backlogPool := execpool.MakeBacklog(nil, 0, execpool.LowPriority, nil) opts := TxHandlerOpts{ @@ -839,7 +892,7 @@ func TestTxHandlerProcessIncomingCacheRotation(t *testing.T) { t.Run("scheduled", func(t *testing.T) { // double enqueue a single txn message, ensure it discarded ctx, cancelFunc := context.WithCancel(context.Background()) - handler := makeTestTxHandlerOrphanedWithContext(ctx, txBacklogSize, txBacklogSize, 10*time.Millisecond) + handler := makeTestTxHandlerOrphanedWithContext(ctx, txBacklogSize, txBacklogSize, txHandlerConfig{true, true}, 10*time.Millisecond) var action network.OutgoingMessage var msg *txBacklogMsg @@ -900,9 +953,8 @@ func TestTxHandlerProcessIncomingCacheRotation(t *testing.T) { // TestTxHandlerProcessIncomingCacheBacklogDrop checks if dropped messages are also removed from caches func TestTxHandlerProcessIncomingCacheBacklogDrop(t *testing.T) { partitiontest.PartitionTest(t) - t.Parallel() - handler := makeTestTxHandlerOrphanedWithContext(context.Background(), 1, 20, 0) + handler := makeTestTxHandlerOrphanedWithContext(context.Background(), 1, 20, txHandlerConfig{true, true}, 0) stxns1, blob1 := makeRandomTransactions(1) require.Equal(t, 1, len(stxns1)) @@ -931,6 +983,7 @@ func TestTxHandlerProcessIncomingCacheTxPoolDrop(t *testing.T) { const numUsers = 100 log := logging.TestingLog(t) + log.SetLevel(logging.Panic) // prepare the accounts addresses, secrets, genesis := makeTestGenesisAccounts(t, numUsers) @@ -939,13 +992,27 @@ func TestTxHandlerProcessIncomingCacheTxPoolDrop(t *testing.T) { const inMem = true cfg := config.GetDefaultLocal() cfg.Archival = true + cfg.EnableTxBacklogRateLimiting = false cfg.TxIncomingFilteringFlags = 3 // txFilterRawMsg + txFilterCanonical ledger, err := LoadLedger(log, ledgerName, inMem, protocol.ConsensusCurrentVersion, genBal, genesisID, genesisHash, nil, cfg) require.NoError(t, err) + defer ledger.Close() l := ledger - handler := makeTestTxHandler(l, cfg) - handler.postVerificationQueue = make(chan *txBacklogMsg) + handler, err := makeTestTxHandler(l, cfg) + require.NoError(t, err) + defer handler.txVerificationPool.Shutdown() + defer close(handler.streamVerifierDropped) + + // saturate the postVerificationQueue +loop: + for { + select { + case handler.postVerificationQueue <- &verify.VerificationResult{}: + default: + break loop + } + } makeTxns := func(sendIdx, recvIdx int) ([]transactions.SignedTxn, []byte) { tx := transactions.Transaction{ @@ -980,8 +1047,22 @@ func TestTxHandlerProcessIncomingCacheTxPoolDrop(t *testing.T) { require.Equal(t, stxns, msg.unverifiedTxGroup) initialCount := transactionMessagesDroppedFromPool.GetUint64Value() - handler.asyncVerifySignature(msg) - currentCount := transactionMessagesDroppedFromPool.GetUint64Value() + + // emulate handler.Start() without the backlog + handler.ctx, handler.ctxCancel = context.WithCancel(context.Background()) + handler.streamVerifier.Start(handler.ctx) + defer handler.streamVerifier.WaitForStop() + defer handler.ctxCancel() + handler.streamVerifierChan <- &verify.UnverifiedElement{ + TxnGroup: msg.unverifiedTxGroup, BacklogMessage: msg} + var currentCount uint64 + for x := 0; x < 1000; x++ { + currentCount = transactionMessagesDroppedFromPool.GetUint64Value() + if currentCount > 0 { + break + } + time.Sleep(10 * time.Millisecond) + } require.Equal(t, initialCount+1, currentCount) require.Equal(t, 0, handler.msgCache.Len()) require.Equal(t, 0, handler.txCanonicalCache.Len()) @@ -1028,7 +1109,7 @@ func BenchmarkTxHandlerDecoderMsgp(b *testing.B) { } // TestTxHandlerIncomingTxHandle checks the correctness with single txns -func TestTxHandlerIncomingTxHandle(t *testing.T) { +func TestTxHandlerIncomingTxHandle(t *testing.T) { //nolint:paralleltest // Not parallel because incomingTxHandlerProcessing mutates global metrics partitiontest.PartitionTest(t) numberOfTransactionGroups := 1000 @@ -1036,7 +1117,7 @@ func TestTxHandlerIncomingTxHandle(t *testing.T) { } // TestTxHandlerIncomingTxGroupHandle checks the correctness with txn groups -func TestTxHandlerIncomingTxGroupHandle(t *testing.T) { +func TestTxHandlerIncomingTxGroupHandle(t *testing.T) { //nolint:paralleltest // Not parallel because incomingTxHandlerProcessing mutates global metrics partitiontest.PartitionTest(t) numberOfTransactionGroups := 1000 / proto.MaxTxGroupSize @@ -1044,7 +1125,7 @@ func TestTxHandlerIncomingTxGroupHandle(t *testing.T) { } // TestTxHandlerIncomingTxHandleDrops accounts for the dropped txns when the verifier/exec pool is saturated -func TestTxHandlerIncomingTxHandleDrops(t *testing.T) { +func TestTxHandlerIncomingTxHandleDrops(t *testing.T) { //nolint:paralleltest // Not parallel because it changes the backlog size partitiontest.PartitionTest(t) // use smaller backlog size to test the message drops @@ -1066,9 +1147,13 @@ func incomingTxHandlerProcessing(maxGroupSize, numberOfTransactionGroups int, t transactionMessagesDroppedFromBacklog = metrics.MakeCounter(metrics.TransactionMessagesDroppedFromBacklog) transactionMessagesDroppedFromPool = metrics.MakeCounter(metrics.TransactionMessagesDroppedFromPool) }() + // reset the counters + transactionMessagesDroppedFromBacklog = metrics.MakeCounter(metrics.TransactionMessagesDroppedFromBacklog) + transactionMessagesDroppedFromPool = metrics.MakeCounter(metrics.TransactionMessagesDroppedFromPool) const numUsers = 100 log := logging.TestingLog(t) + log.SetLevel(logging.Warn) // prepare the accounts addresses, secrets, genesis := makeTestGenesisAccounts(t, numUsers) @@ -1077,24 +1162,31 @@ func incomingTxHandlerProcessing(maxGroupSize, numberOfTransactionGroups int, t const inMem = true cfg := config.GetDefaultLocal() cfg.Archival = true + cfg.EnableTxBacklogRateLimiting = false ledger, err := LoadLedger(log, ledgerName, inMem, protocol.ConsensusCurrentVersion, genBal, genesisID, genesisHash, nil, cfg) require.NoError(t, err) + defer ledger.Close() - l := ledger - handler := makeTestTxHandler(l, cfg) + handler, err := makeTestTxHandler(ledger, cfg) + require.NoError(t, err) + defer handler.txVerificationPool.Shutdown() + defer close(handler.streamVerifierDropped) // since Start is not called, set the context here handler.ctx, handler.ctxCancel = context.WithCancel(context.Background()) defer handler.ctxCancel() - outChan := make(chan *txBacklogMsg, 10) + // emulate handler.Start() without the backlog + handler.ctx, handler.ctxCancel = context.WithCancel(context.Background()) + handler.streamVerifier.Start(handler.ctx) + + testResultChan := make(chan *txBacklogMsg, 10) wg := sync.WaitGroup{} wg.Add(1) // Make a test backlog worker, which is similar to backlogWorker, but sends the results - // through the outChan instead of passing it to postProcessCheckedTxn + // through the testResultChan instead of passing it to postProcessCheckedTxn go func() { defer wg.Done() - defer close(outChan) for { // prioritize the postVerificationQueue select { @@ -1102,7 +1194,10 @@ func incomingTxHandlerProcessing(maxGroupSize, numberOfTransactionGroups int, t if !ok { return } - outChan <- wi + txBLMsg := wi.BacklogMessage.(*txBacklogMsg) + txBLMsg.verificationErr = wi.Err + testResultChan <- txBLMsg + // restart the loop so that we could empty out the post verification queue. continue default: @@ -1112,29 +1207,20 @@ func incomingTxHandlerProcessing(maxGroupSize, numberOfTransactionGroups int, t select { case wi, ok := <-handler.backlogQueue: if !ok { - // shut down to end the test - handler.txVerificationPool.Shutdown() - close(handler.postVerificationQueue) - // wait until all the pending responses are obtained. - // this is not in backlogWorker, maybe should be - for wi := range handler.postVerificationQueue { - outChan <- wi - } return } if handler.checkAlreadyCommitted(wi) { // this is not expected during the test continue } - - // enqueue the task to the verification pool. - handler.txVerificationPool.EnqueueBacklog(handler.ctx, handler.asyncVerifySignature, wi, nil) - + handler.streamVerifierChan <- &verify.UnverifiedElement{TxnGroup: wi.unverifiedTxGroup, BacklogMessage: wi} case wi, ok := <-handler.postVerificationQueue: if !ok { return } - outChan <- wi + txBLMsg := wi.BacklogMessage.(*txBacklogMsg) + txBLMsg.verificationErr = wi.Err + testResultChan <- txBLMsg case <-handler.ctx.Done(): return @@ -1152,7 +1238,7 @@ func incomingTxHandlerProcessing(maxGroupSize, numberOfTransactionGroups int, t data = append(data, protocol.Encode(&stxn)...) } encodedSignedTransactionGroups = - append(encodedSignedTransactionGroups, network.IncomingMessage{Data: data}) + append(encodedSignedTransactionGroups, network.IncomingMessage{Data: data, Sender: mockSender{}}) } // Process the results and make sure they are correct @@ -1174,7 +1260,7 @@ func incomingTxHandlerProcessing(maxGroupSize, numberOfTransactionGroups int, t timer := time.NewTicker(250 * time.Millisecond) for { select { - case wi := <-outChan: + case wi := <-testResultChan: txnCounter = txnCounter + len(wi.unverifiedTxGroup) groupCounter++ u, _ := binary.Uvarint(wi.unverifiedTxGroup[0].Txn.Note) @@ -1210,15 +1296,31 @@ func getDropped() (droppedBacklog, droppedPool uint64) { return } -// makeSignedTxnGroups prepares N transaction groups of random (maxGroupSize) sizes with random -// invalid signatures of a given probability (invalidProb) -func makeSignedTxnGroups(N, numUsers, maxGroupSize int, invalidProb float32, addresses []basics.Address, - secrets []*crypto.SignatureSecrets) (ret [][]transactions.SignedTxn, - badTxnGroups map[uint64]interface{}) { - badTxnGroups = make(map[uint64]interface{}) +func getTransaction(sender, receiver basics.Address, u int) transactions.Transaction { + noteField := make([]byte, binary.MaxVarintLen64) + binary.PutUvarint(noteField, uint64(u)) + + tx := transactions.Transaction{ + Type: protocol.PaymentTx, + Header: transactions.Header{ + Sender: sender, + Fee: basics.MicroAlgos{Raw: proto.MinTxnFee * 2}, + FirstValid: 0, + LastValid: basics.Round(proto.MaxTxnLife), + GenesisHash: genesisHash, + Note: noteField, + }, + PaymentTxnFields: transactions.PaymentTxnFields{ + Receiver: receiver, + Amount: basics.MicroAlgos{Raw: mockBalancesMinBalance + (rand.Uint64() % 10000)}, + }, + } + return tx +} +func getTransactionGroups(N, numUsers, maxGroupSize int, addresses []basics.Address) [][]transactions.Transaction { + txnGrps := make([][]transactions.Transaction, N) protoMaxGrpSize := proto.MaxTxGroupSize - ret = make([][]transactions.SignedTxn, 0, N) for u := 0; u < N; u++ { grpSize := rand.Intn(protoMaxGrpSize-1) + 1 if grpSize > maxGroupSize { @@ -1228,72 +1330,189 @@ func makeSignedTxnGroups(N, numUsers, maxGroupSize int, invalidProb float32, add txns := make([]transactions.Transaction, 0, grpSize) for g := 0; g < grpSize; g++ { // generate transactions - noteField := make([]byte, binary.MaxVarintLen64) - binary.PutUvarint(noteField, uint64(u)) - tx := transactions.Transaction{ - Type: protocol.PaymentTx, - Header: transactions.Header{ - Sender: addresses[(u+g)%numUsers], - Fee: basics.MicroAlgos{Raw: proto.MinTxnFee * 2}, - FirstValid: 0, - LastValid: basics.Round(proto.MaxTxnLife), - GenesisHash: genesisHash, - Note: noteField, - }, - PaymentTxnFields: transactions.PaymentTxnFields{ - Receiver: addresses[(u+g+1)%numUsers], - Amount: basics.MicroAlgos{Raw: mockBalancesMinBalance + (rand.Uint64() % 10000)}, - }, - } + tx := getTransaction(addresses[(u+g)%numUsers], addresses[(u+g+1)%numUsers], u) if grpSize > 1 { txGroup.TxGroupHashes = append(txGroup.TxGroupHashes, crypto.Digest(tx.ID())) } txns = append(txns, tx) } - groupHash := crypto.HashObj(txGroup) - signedTxGroup := make([]transactions.SignedTxn, 0, grpSize) - for g, txn := range txns { - if grpSize > 1 { - txn.Group = groupHash + if grpSize > 1 { + groupHash := crypto.HashObj(txGroup) + for t := range txns { + txns[t].Group = groupHash } - signedTx := txn.Sign(secrets[(u+g)%numUsers]) - signedTx.Txn = txn + } + txnGrps[u] = txns + } + return txnGrps +} + +func signTransactionGroups(txnGroups [][]transactions.Transaction, secrets []*crypto.SignatureSecrets, invalidProb float32) ( + ret [][]transactions.SignedTxn, badTxnGroups map[uint64]interface{}) { + numUsers := len(secrets) + badTxnGroups = make(map[uint64]interface{}) + for tg := range txnGroups { + grpSize := len(txnGroups[tg]) + signedTxGroup := make([]transactions.SignedTxn, 0, grpSize) + for t := range txnGroups[tg] { + signedTx := txnGroups[tg][t].Sign(secrets[(tg+t)%numUsers]) + signedTx.Txn = txnGroups[tg][t] signedTxGroup = append(signedTxGroup, signedTx) } // randomly make bad signatures if rand.Float32() < invalidProb { tinGrp := rand.Intn(grpSize) signedTxGroup[tinGrp].Sig[0] = signedTxGroup[tinGrp].Sig[0] + 1 - badTxnGroups[uint64(u)] = struct{}{} + badTxnGroups[uint64(tg)] = struct{}{} } ret = append(ret, signedTxGroup) } return } +func signMSigTransactionGroups(txnGroups [][]transactions.Transaction, secrets []*crypto.SignatureSecrets, + invalidProb float32, msigSize int) (ret [][]transactions.SignedTxn, badTxnGroups map[uint64]interface{}, err error) { + ret = make([][]transactions.SignedTxn, len(txnGroups)) + numUsers := len(secrets) + badTxnGroups = make(map[uint64]interface{}) + badTxnGroupsMU := deadlock.Mutex{} + // process them using multiple threads + workers := make(chan interface{}, runtime.NumCPU()-1) + wg := sync.WaitGroup{} + errChan := make(chan error, 1) + for tg := range txnGroups { + wg.Add(1) + workers <- struct{}{} + go func(i int) { + defer func() { + wg.Done() + <-workers + }() + msigVer := uint8(1) + msigTHld := uint8(msigSize) + pks := make([]crypto.PublicKey, msigSize) + for x := 0; x < msigSize; x++ { + pks[x] = secrets[(i+x)%numUsers].SignatureVerifier + } + multiSigAddr, err := crypto.MultisigAddrGen(msigVer, msigTHld, pks) + if err != nil { + select { + case errChan <- err: + return + default: + return + } + } + grpSize := len(txnGroups[i]) + signedTxGroup := make([]transactions.SignedTxn, grpSize) + sigsForTxn := make([]crypto.MultisigSig, msigTHld) + + for t := range txnGroups[i] { + txnGroups[i][t].Sender = basics.Address(multiSigAddr) + for s := range sigsForTxn { + sig, err := crypto.MultisigSign(txnGroups[i][t], crypto.Digest(multiSigAddr), msigVer, msigTHld, pks, *secrets[(i+s)%numUsers]) + if err != nil { + select { + case errChan <- err: + return + default: + return + } + } + sigsForTxn[s] = sig + } + msig, err := crypto.MultisigAssemble(sigsForTxn) + if err != nil { + select { + case errChan <- err: + return + default: + return + } + } + signedTxGroup[t].Txn = txnGroups[i][t] + signedTxGroup[t].Msig = msig + } + // randomly make bad signatures + if rand.Float32() < invalidProb { + tinGrp := rand.Intn(grpSize) + tinMsig := rand.Intn(len(signedTxGroup[tinGrp].Msig.Subsigs)) + signedTxGroup[tinGrp].Msig.Subsigs[tinMsig].Sig[0] = signedTxGroup[tinGrp].Msig.Subsigs[tinMsig].Sig[0] + 1 + badTxnGroupsMU.Lock() + badTxnGroups[uint64(i)] = struct{}{} + badTxnGroupsMU.Unlock() + } + ret[i] = signedTxGroup + }(tg) + } + wg.Wait() + close(errChan) + err = <-errChan + return +} + +// makeSignedTxnGroups prepares N transaction groups of random (maxGroupSize) sizes with random +// invalid signatures of a given probability (invalidProb) +func makeSignedTxnGroups(N, numUsers, maxGroupSize int, invalidProb float32, addresses []basics.Address, + secrets []*crypto.SignatureSecrets) (ret [][]transactions.SignedTxn, + badTxnGroups map[uint64]interface{}) { + + txnGroups := getTransactionGroups(N, numUsers, maxGroupSize, addresses) + ret, badTxnGroups = signTransactionGroups(txnGroups, secrets, invalidProb) + return +} + +const numBenchUsers = 512 + // BenchmarkHandleTxns sends signed transactions directly to the verifier func BenchmarkHandleTxns(b *testing.B) { maxGroupSize := 1 - tpss := []int{6000000, 600000, 60000, 6000} invalidRates := []float32{0.5, 0.001} - for _, tps := range tpss { + for _, ivr := range invalidRates { + b.Run(fmt.Sprintf("inv_%.3f", ivr), func(b *testing.B) { + txGen := makeSigGenerator(b, numBenchUsers, maxGroupSize, ivr) + runHandlerBenchmarkWithBacklog(b, txGen, 0, false) + }) + } +} + +// BenchmarkHandleTxnGroups sends signed transaction groups directly to the verifier +func BenchmarkHandleTxnGroups(b *testing.B) { + maxGroupSize := proto.MaxTxGroupSize / 2 + invalidRates := []float32{0.5, 0.001} + for _, ivr := range invalidRates { + b.Run(fmt.Sprintf("inv_%.3f", ivr), func(b *testing.B) { + txGen := makeSigGenerator(b, numBenchUsers, maxGroupSize, ivr) + runHandlerBenchmarkWithBacklog(b, txGen, 0, false) + }) + } +} + +// BenchmarkHandleMsigTxns sends signed transactions directly to the verifier +func BenchmarkHandleMsigTxns(b *testing.B) { + maxGroupSize := 1 + msigSizes := []int{64, 16, 8, 4} + invalidRates := []float32{0.5, 0.001} + for _, msigSize := range msigSizes { for _, ivr := range invalidRates { - b.Run(fmt.Sprintf("tps_%d_inv_%.3f", tps, ivr), func(b *testing.B) { - runHandlerBenchmarkWithBacklog(maxGroupSize, tps, ivr, b, false) + b.Run(fmt.Sprintf("msigSize_%d_inv_%.3f", msigSize, ivr), func(b *testing.B) { + txGen := makeMsigGenerator(b, numBenchUsers, maxGroupSize, ivr, msigSize) + runHandlerBenchmarkWithBacklog(b, txGen, 0, false) }) } } } // BenchmarkHandleTxnGroups sends signed transaction groups directly to the verifier -func BenchmarkHandleTxnGroups(b *testing.B) { +func BenchmarkHandleMsigTxnGroups(b *testing.B) { maxGroupSize := proto.MaxTxGroupSize / 2 - tpss := []int{6000000, 600000, 60000, 6000} + msigSizes := []int{64, 16, 8, 4} invalidRates := []float32{0.5, 0.001} - for _, tps := range tpss { + for _, msigSize := range msigSizes { for _, ivr := range invalidRates { - b.Run(fmt.Sprintf("tps_%d_inv_%.3f", tps, ivr), func(b *testing.B) { - runHandlerBenchmarkWithBacklog(maxGroupSize, tps, ivr, b, false) + b.Run(fmt.Sprintf("msigSize_%d_inv_%.3f", msigSize, ivr), func(b *testing.B) { + txGen := makeMsigGenerator(b, numBenchUsers, maxGroupSize, ivr, msigSize) + runHandlerBenchmarkWithBacklog(b, txGen, 0, false) }) } } @@ -1308,7 +1527,8 @@ func BenchmarkHandleBLWTxns(b *testing.B) { for _, tps := range tpss { for _, ivr := range invalidRates { b.Run(fmt.Sprintf("tps_%d_inv_%.3f", tps, ivr), func(b *testing.B) { - runHandlerBenchmarkWithBacklog(maxGroupSize, tps, ivr, b, true) + txGen := makeSigGenerator(b, numBenchUsers, maxGroupSize, ivr) + runHandlerBenchmarkWithBacklog(b, txGen, tps, true) }) } } @@ -1323,47 +1543,172 @@ func BenchmarkHandleBLWTxnGroups(b *testing.B) { for _, tps := range tpss { for _, ivr := range invalidRates { b.Run(fmt.Sprintf("tps_%d_inv_%.3f", tps, ivr), func(b *testing.B) { - runHandlerBenchmarkWithBacklog(maxGroupSize, tps, ivr, b, true) + txGen := makeSigGenerator(b, numBenchUsers, maxGroupSize, ivr) + runHandlerBenchmarkWithBacklog(b, txGen, tps, true) }) } } } -// runHandlerBenchmarkWithBacklog benchmarks the number of transactions verfied or dropped -func runHandlerBenchmarkWithBacklog(maxGroupSize, tps int, invalidRate float32, b *testing.B, useBacklogWorker bool) { +// BenchmarkHandleTxnGroups sends signed transaction groups directly to the verifier +func BenchmarkHandleLsigTxnGroups(b *testing.B) { + maxGroupSize := proto.MaxTxGroupSize / 2 + invalidRates := []float32{0.5, 0.001} + for _, ivr := range invalidRates { + b.Run(fmt.Sprintf("lsig-inv_%.3f", ivr), func(b *testing.B) { + txGen := makeLsigGenerator(b, numBenchUsers, maxGroupSize, ivr) + runHandlerBenchmarkWithBacklog(b, txGen, 0, false) + }) + } +} + +type txGenIf interface { + makeLedger(tb testing.TB, cfg config.Local, log logging.Logger, namePrefix string) *Ledger + createSignedTxGroups(tb testing.TB, txgCount int) ([][]transactions.SignedTxn, map[uint64]interface{}) +} + +type txGenerator struct { + numUsers int + maxGroupSize int + invalidRate float32 + + addresses []basics.Address + secrets []*crypto.SignatureSecrets + genesis map[basics.Address]basics.AccountData +} + +type sigGenerator struct { + txGenerator +} + +type msigGenerator struct { + txGenerator + msigSize int +} + +type lsigGenerator struct { + txGenerator +} + +func makeTxGenerator(tb testing.TB, numUsers, maxGroupSize int, invalidRate float32) *txGenerator { + addresses, secrets, genesis := makeTestGenesisAccounts(tb, numUsers) + return &txGenerator{ + numUsers: numUsers, + maxGroupSize: maxGroupSize, + invalidRate: invalidRate, + addresses: addresses, + secrets: secrets, + genesis: genesis, + } +} + +func (g *txGenerator) makeLedger(tb testing.TB, cfg config.Local, log logging.Logger, namePrefix string) *Ledger { + genBal := bookkeeping.MakeGenesisBalances(g.genesis, sinkAddr, poolAddr) + ivrString := strings.IndexAny(fmt.Sprintf("%f", g.invalidRate), "1") + ledgerName := fmt.Sprintf("%s-in_mem-w_inv=%d", namePrefix, ivrString) + ledgerName = strings.Replace(ledgerName, "#", "-", 1) + const inMem = true + ledger, err := LoadLedger(log, ledgerName, inMem, protocol.ConsensusCurrentVersion, genBal, genesisID, genesisHash, nil, cfg) + require.NoError(tb, err) + return ledger +} + +func makeSigGenerator(tb testing.TB, numUsers, maxGroupSize int, invalidRate float32) *sigGenerator { + return &sigGenerator{ + txGenerator: *makeTxGenerator(tb, numUsers, maxGroupSize, invalidRate), + } +} + +func (g *sigGenerator) createSignedTxGroups(tb testing.TB, txgCount int) ([][]transactions.SignedTxn, map[uint64]interface{}) { + return makeSignedTxnGroups(txgCount, g.numUsers, g.maxGroupSize, g.invalidRate, g.addresses, g.secrets) +} + +func makeMsigGenerator(tb testing.TB, numUsers, maxGroupSize int, invalidRate float32, msigSize int) *msigGenerator { + return &msigGenerator{ + txGenerator: *makeTxGenerator(tb, numUsers, maxGroupSize, invalidRate), + msigSize: msigSize, + } +} + +func (g *msigGenerator) createSignedTxGroups(tb testing.TB, txgCount int) ([][]transactions.SignedTxn, map[uint64]interface{}) { + txnGroups := getTransactionGroups(txgCount, g.numUsers, g.maxGroupSize, g.addresses) + signedTransactionGroups, badTxnGroups, err := signMSigTransactionGroups(txnGroups, g.secrets, g.invalidRate, g.msigSize) + require.NoError(tb, err) + return signedTransactionGroups, badTxnGroups +} + +func makeLsigGenerator(tb testing.TB, numUsers, maxGroupSize int, invalidRate float32) *lsigGenerator { + return &lsigGenerator{ + txGenerator: *makeTxGenerator(tb, numUsers, maxGroupSize, invalidRate), + } +} + +func (g *lsigGenerator) createSignedTxGroups(tb testing.TB, txgCount int) ([][]transactions.SignedTxn, map[uint64]interface{}) { + stxns := make([][]transactions.SignedTxn, txgCount) + badTxnGroups := make(map[uint64]interface{}) + for i := 0; i < txgCount; i++ { + txns := txntest.CreateTinyManTxGroup(tb, true) + stxns[i], _ = txntest.CreateTinyManSignedTxGroup(tb, txns) + + // randomly make bad signatures + if rand.Float32() < g.invalidRate { + tinGrp := rand.Intn(len(txns)) + if stxns[i][tinGrp].Sig != (crypto.Signature{}) { + stxns[i][tinGrp].Sig[0] = stxns[i][tinGrp].Sig[0] + 1 + } else { + stxns[i][tinGrp].Lsig.Logic[0] = 255 + } + badTxnGroups[uint64(i)] = struct{}{} + } + } + return stxns, badTxnGroups +} + +// runHandlerBenchmarkWithBacklog benchmarks the number of transactions verified or dropped +func runHandlerBenchmarkWithBacklog(b *testing.B, txGen txGenIf, tps int, useBacklogWorker bool) { defer func() { // reset the counters transactionMessagesDroppedFromBacklog = metrics.MakeCounter(metrics.TransactionMessagesDroppedFromBacklog) transactionMessagesDroppedFromPool = metrics.MakeCounter(metrics.TransactionMessagesDroppedFromPool) }() + // reset the counters + transactionMessagesDroppedFromBacklog = metrics.MakeCounter(metrics.TransactionMessagesDroppedFromBacklog) + transactionMessagesDroppedFromPool = metrics.MakeCounter(metrics.TransactionMessagesDroppedFromPool) - const numUsers = 100 log := logging.TestingLog(b) log.SetLevel(logging.Warn) - addresses, secrets, genesis := makeTestGenesisAccounts(b, numUsers) - genBal := bookkeeping.MakeGenesisBalances(genesis, sinkAddr, poolAddr) - ivrString := strings.IndexAny(fmt.Sprintf("%f", invalidRate), "1") - ledgerName := fmt.Sprintf("%s-mem-%d-%d", b.Name(), b.N, ivrString) - ledgerName = strings.Replace(ledgerName, "#", "-", 1) - const inMem = true cfg := config.GetDefaultLocal() cfg.Archival = true - ledger, err := LoadLedger(log, ledgerName, inMem, protocol.ConsensusCurrentVersion, genBal, genesisID, genesisHash, nil, cfg) + cfg.TxBacklogReservedCapacityPerPeer = 1 + cfg.IncomingConnectionsLimit = 10 + ledger := txGen.makeLedger(b, cfg, log, fmt.Sprintf("%s-%d", b.Name(), b.N)) + defer ledger.Close() + handler, err := makeTestTxHandler(ledger, cfg) require.NoError(b, err) + defer handler.txVerificationPool.Shutdown() + defer close(handler.streamVerifierDropped) + + // The benchmark generates only 1000 txns, and reuses them. This is done for faster benchmark time and the + // ability to have long runs without being limited to the memory. The dedup will block the txns once the same + // ones are rotated again. If the purpose is to test dedup, then this can be changed by setting + // genTCount = b.N + handler.cacheConfig.enableFilteringRawMsg = false + handler.cacheConfig.enableFilteringCanonical = false - l := ledger - handler := makeTestTxHandler(l, cfg) // since Start is not called, set the context here handler.ctx, handler.ctxCancel = context.WithCancel(context.Background()) defer handler.ctxCancel() - testResultChan := handler.postVerificationQueue + // emulate handler.Start() without the backlog + handler.ctx, handler.ctxCancel = context.WithCancel(context.Background()) + handler.streamVerifier.Start(handler.ctx) + + testResultChan := make(chan *txBacklogMsg, 10) wg := sync.WaitGroup{} if useBacklogWorker { wg.Add(1) - testResultChan = make(chan *txBacklogMsg, 10) // Make a test backlog worker, which is similar to backlogWorker, but sends the results // through the testResultChan instead of passing it to postProcessCheckedTxn go func() { @@ -1375,7 +1720,9 @@ func runHandlerBenchmarkWithBacklog(maxGroupSize, tps int, invalidRate float32, if !ok { return } - testResultChan <- wi + txBLMsg := wi.BacklogMessage.(*txBacklogMsg) + txBLMsg.verificationErr = wi.Err + testResultChan <- txBLMsg // restart the loop so that we could empty out the post verification queue. continue @@ -1392,13 +1739,14 @@ func runHandlerBenchmarkWithBacklog(maxGroupSize, tps int, invalidRate float32, // this is not expected during the test continue } - handler.txVerificationPool.EnqueueBacklog(handler.ctx, handler.asyncVerifySignature, wi, nil) - + handler.streamVerifierChan <- &verify.UnverifiedElement{TxnGroup: wi.unverifiedTxGroup, BacklogMessage: wi} case wi, ok := <-handler.postVerificationQueue: if !ok { return } - testResultChan <- wi + txBLMsg := wi.BacklogMessage.(*txBacklogMsg) + txBLMsg.verificationErr = wi.Err + testResultChan <- txBLMsg case <-handler.ctx.Done(): return @@ -1407,98 +1755,172 @@ func runHandlerBenchmarkWithBacklog(maxGroupSize, tps int, invalidRate float32, }() } - // Prepare the transactions - signedTransactionGroups, badTxnGroups := makeSignedTxnGroups(b.N, numUsers, maxGroupSize, invalidRate, addresses, secrets) - var encodedSignedTransactionGroups []network.IncomingMessage + // Prepare 1000 transactions + genTCount := 1000 + if b.N < genTCount { + genTCount = b.N + } + signedTransactionGroups, badTxnGroups := txGen.createSignedTxGroups(b, genTCount) + var encStxns []network.IncomingMessage if useBacklogWorker { - encodedSignedTransactionGroups = make([]network.IncomingMessage, 0, b.N) + encStxns = make([]network.IncomingMessage, 0, genTCount) for _, stxngrp := range signedTransactionGroups { data := make([]byte, 0) for _, stxn := range stxngrp { data = append(data, protocol.Encode(&stxn)...) } - encodedSignedTransactionGroups = - append(encodedSignedTransactionGroups, network.IncomingMessage{Data: data}) + encStxns = append(encStxns, network.IncomingMessage{Data: data}) } } var tt time.Time // Process the results and make sure they are correct - rateAdjuster := time.Second / time.Duration(tps) + var rateAdjuster time.Duration + if tps > 0 { + rateAdjuster = time.Second / time.Duration(tps) + } wg.Add(1) go func() { defer wg.Done() groupCounter := uint64(0) var txnCounter uint64 invalidCounter := 0 + // report the results defer func() { if groupCounter > 1 { + timeSinceStart := time.Since(tt) droppedBacklog, droppedPool := getDropped() - b.Logf("Input T(grp)PS: %d (delay %f microsec)", tps, float64(rateAdjuster)/float64(time.Microsecond)) - b.Logf("Verified TPS: %d", uint64(txnCounter)*uint64(time.Second)/uint64(time.Since(tt))) - b.Logf("Time/txn: %d(microsec)", uint64((time.Since(tt)/time.Microsecond))/txnCounter) + if tps > 0 { + b.Logf("Input T(grp)PS: %d (delay %f microsec)", tps, float64(rateAdjuster)/float64(time.Microsecond)) + } + b.Logf("Verified TPS: %d T(grp)PS: %d", uint64(txnCounter)*uint64(time.Second)/uint64(timeSinceStart), + uint64(groupCounter)*uint64(time.Second)/uint64(timeSinceStart)) + b.Logf("Time/txn: %d(microsec)", uint64(timeSinceStart/time.Microsecond)/txnCounter) b.Logf("processed total: [%d groups (%d invalid)] [%d txns]", groupCounter, invalidCounter, txnCounter) b.Logf("dropped: [%d backlog] [%d pool]\n", droppedBacklog, droppedPool) } handler.Stop() // cancel the handler ctx }() + counterMutex := deadlock.Mutex{} stopChan := make(chan interface{}) + // monitor the counters to tell when everything is processed and the checker should stop + wg.Add(1) go func() { + defer wg.Done() for { time.Sleep(200 * time.Millisecond) droppedBacklog, droppedPool := getDropped() - if int(groupCounter+droppedBacklog+droppedPool) == len(signedTransactionGroups) { + counterMutex.Lock() + counters := groupCounter + droppedBacklog + droppedPool + counterMutex.Unlock() + if int(counters) == b.N { // all the benchmark txns processed close(stopChan) return } } }() - - for { - select { - case wi := <-testResultChan: - txnCounter = txnCounter + uint64(len(wi.unverifiedTxGroup)) - groupCounter++ - u, _ := binary.Uvarint(wi.unverifiedTxGroup[0].Txn.Note) - _, inBad := badTxnGroups[u] - if wi.verificationErr == nil { - require.False(b, inBad, "No error for invalid signature") - } else { - invalidCounter++ - require.True(b, inBad, "Error for good signature") + // pick up each output from the verifier and check it is was correctly decided + // since the data paths differ, distinguish between useBacklogWorker or not + if useBacklogWorker { + for { + select { + case wi := <-testResultChan: + txnCounter = txnCounter + uint64(len(wi.unverifiedTxGroup)) + counterMutex.Lock() + groupCounter++ + counterMutex.Unlock() + u, _ := binary.Uvarint(wi.unverifiedTxGroup[0].Txn.Note) + _, inBad := badTxnGroups[u] + if wi.verificationErr == nil { + require.False(b, inBad, "No error for invalid signature") + } else { + invalidCounter++ + require.True(b, inBad, "Error for good signature") + } + if groupCounter == uint64(b.N) { + // all the benchmark txns processed + return + } + case <-stopChan: + return } - if groupCounter == uint64(len(signedTransactionGroups)) { - // all the benchmark txns processed + } + } else { + for { + select { + case wi := <-handler.postVerificationQueue: + txnCounter = txnCounter + uint64(len(wi.TxnGroup)) + counterMutex.Lock() + groupCounter++ + counterMutex.Unlock() + u, _ := binary.Uvarint(wi.TxnGroup[0].Txn.Note) + _, inBad := badTxnGroups[u] + if wi.Err == nil { + require.False(b, inBad, "No error for invalid signature") + } else { + invalidCounter++ + require.True(b, inBad, "Error for good signature") + } + if groupCounter == uint64(b.N) { + // all the benchmark txns processed + return + } + case <-stopChan: return } - case <-stopChan: - return } } }() + completed := false + c := 0 + ticker := &time.Ticker{} + if rateAdjuster > 0 { + ticker = time.NewTicker(rateAdjuster) + } + defer ticker.Stop() b.ResetTimer() tt = time.Now() - if useBacklogWorker { - for _, tg := range encodedSignedTransactionGroups { - handler.processIncomingTxn(tg) - time.Sleep(rateAdjuster) - } - } else { - for _, stxngrp := range signedTransactionGroups { - blm := txBacklogMsg{rawmsg: nil, unverifiedTxGroup: stxngrp} - handler.txVerificationPool.EnqueueBacklog(handler.ctx, handler.asyncVerifySignature, &blm, nil) - time.Sleep(rateAdjuster) + for !completed { + for i := range signedTransactionGroups { + if useBacklogWorker { + handler.processIncomingTxn(encStxns[i]) + <-ticker.C + } else { + stxngrp := signedTransactionGroups[i] + blm := txBacklogMsg{rawmsg: nil, unverifiedTxGroup: stxngrp} + handler.streamVerifierChan <- &verify.UnverifiedElement{TxnGroup: stxngrp, BacklogMessage: &blm} + } + c++ + if c == b.N { + completed = true + break + } } } wg.Wait() handler.Stop() // cancel the handler ctx } -func TestTxHandlerPostProcessError(t *testing.T) { +func TestTxHandlerPostProcessError(t *testing.T) { //nolint:paralleltest // Not parallel because it mutates global metrics partitiontest.PartitionTest(t) - t.Parallel() + + defer func() { + transactionMessagesTxnSigVerificationFailed = metrics.MakeCounter(metrics.TransactionMessagesTxnSigVerificationFailed) + transactionMessagesAlreadyCommitted = metrics.MakeCounter(metrics.TransactionMessagesAlreadyCommitted) + transactionMessagesTxGroupInvalidFee = metrics.MakeCounter(metrics.TransactionMessagesTxGroupInvalidFee) + transactionMessagesTxnSigNotWellFormed = metrics.MakeCounter(metrics.TransactionMessagesTxnSigNotWellFormed) + transactionMessagesTxnMsigNotWellFormed = metrics.MakeCounter(metrics.TransactionMessagesTxnMsigNotWellFormed) + transactionMessagesTxnLogicSig = metrics.MakeCounter(metrics.TransactionMessagesTxnLogicSig) + }() + + transactionMessagesTxnSigVerificationFailed = metrics.MakeCounter(metrics.TransactionMessagesTxnSigVerificationFailed) + transactionMessagesAlreadyCommitted = metrics.MakeCounter(metrics.TransactionMessagesAlreadyCommitted) + transactionMessagesTxGroupInvalidFee = metrics.MakeCounter(metrics.TransactionMessagesTxGroupInvalidFee) + transactionMessagesTxnSigNotWellFormed = metrics.MakeCounter(metrics.TransactionMessagesTxnSigNotWellFormed) + transactionMessagesTxnMsigNotWellFormed = metrics.MakeCounter(metrics.TransactionMessagesTxnMsigNotWellFormed) + transactionMessagesTxnLogicSig = metrics.MakeCounter(metrics.TransactionMessagesTxnLogicSig) collect := func() map[string]float64 { // collect all specific error reason metrics except TxGroupErrorReasonNotWellFormed, @@ -1547,15 +1969,19 @@ func TestTxHandlerPostProcessError(t *testing.T) { const expected = int(verify.TxGroupErrorReasonNumValues) - 3 require.Len(t, result, expected) - errVerify := crypto.ErrBatchVerificationFailed + errVerify := crypto.ErrBatchHasFailedSigs txh.postProcessReportErrors(errVerify) result = collect() require.Len(t, result, expected+1) } -func TestTxHandlerPostProcessErrorWithVerify(t *testing.T) { +func TestTxHandlerPostProcessErrorWithVerify(t *testing.T) { //nolint:paralleltest // Not parallel because it mutates global metrics partitiontest.PartitionTest(t) - t.Parallel() + + defer func() { + transactionMessagesTxnNotWellFormed = metrics.MakeCounter(metrics.TransactionMessagesTxnNotWellFormed) + }() + transactionMessagesTxnNotWellFormed = metrics.MakeCounter(metrics.TransactionMessagesTxnNotWellFormed) txn := transactions.Transaction{} stxn := transactions.SignedTxn{Txn: txn} @@ -1565,7 +1991,7 @@ func TestTxHandlerPostProcessErrorWithVerify(t *testing.T) { CurrentProtocol: protocol.ConsensusCurrentVersion, }, } - _, err := verify.TxnGroup([]transactions.SignedTxn{stxn}, hdr, nil, nil) + _, err := verify.TxnGroup([]transactions.SignedTxn{stxn}, &hdr, nil, nil) var txGroupErr *verify.TxGroupError require.ErrorAs(t, err, &txGroupErr) @@ -1580,9 +2006,21 @@ func TestTxHandlerPostProcessErrorWithVerify(t *testing.T) { } // TestTxHandlerRememberReportErrors checks Is and As statements work as expected -func TestTxHandlerRememberReportErrors(t *testing.T) { +func TestTxHandlerRememberReportErrors(t *testing.T) { //nolint:paralleltest // Not parallel because incomingTxHandlerProcessing mutates global metrics partitiontest.PartitionTest(t) - t.Parallel() + + defer func() { + transactionMessageTxPoolRememberCounter = metrics.NewTagCounter( + "algod_transaction_messages_txpool_remember_err_{TAG}", "Number of transaction messages not remembered by txpool b/c of {TAG}", + txPoolRememberTagCap, txPoolRememberPendingEval, txPoolRememberTagNoSpace, txPoolRememberTagFee, txPoolRememberTagTxnDead, txPoolRememberTagTxnEarly, txPoolRememberTagTooLarge, txPoolRememberTagGroupID, + txPoolRememberTagTxID, txPoolRememberTagLease, txPoolRememberTagTxIDEval, txPoolRememberTagLeaseEval, txPoolRememberTagEvalGeneric, + ) + }() + transactionMessageTxPoolRememberCounter = metrics.NewTagCounter( + "algod_transaction_messages_txpool_remember_err_{TAG}", "Number of transaction messages not remembered by txpool b/c of {TAG}", + txPoolRememberTagCap, txPoolRememberPendingEval, txPoolRememberTagNoSpace, txPoolRememberTagFee, txPoolRememberTagTxnDead, txPoolRememberTagTxnEarly, txPoolRememberTagTooLarge, txPoolRememberTagGroupID, + txPoolRememberTagTxID, txPoolRememberTagLease, txPoolRememberTagTxIDEval, txPoolRememberTagLeaseEval, txPoolRememberTagEvalGeneric, + ) var txh TxHandler result := map[string]float64{} @@ -1614,25 +2052,57 @@ func TestTxHandlerRememberReportErrors(t *testing.T) { require.Equal(t, 1, getMetricCounter(txPoolRememberTagFee)) } +func makeBlockTicker() *blockTicker { + return &blockTicker{ + waiter: make(chan struct{}, 10), + } +} + type blockTicker struct { - cond sync.Cond + waiter chan struct{} } func (t *blockTicker) OnNewBlock(block bookkeeping.Block, delta ledgercore.StateDelta) { - t.cond.L.Lock() - defer t.cond.L.Unlock() - t.cond.Broadcast() + t.waiter <- struct{}{} } func (t *blockTicker) Wait() { - t.cond.L.Lock() - defer t.cond.L.Unlock() - t.cond.Wait() + timer := time.NewTimer(1 * time.Second) + defer timer.Stop() + for { + select { + case <-t.waiter: + return + case <-timer.C: + return + } + } } -func TestTxHandlerRememberReportErrorsWithTxPool(t *testing.T) { +func TestTxHandlerRememberReportErrorsWithTxPool(t *testing.T) { //nolint:paralleltest // Not parallel because it mutates global metrics partitiontest.PartitionTest(t) - t.Parallel() + defer func() { + transactionMessageTxPoolRememberCounter = metrics.NewTagCounter( + "algod_transaction_messages_txpool_remember_err_{TAG}", "Number of transaction messages not remembered by txpool b/c of {TAG}", + txPoolRememberTagCap, txPoolRememberPendingEval, txPoolRememberTagNoSpace, txPoolRememberTagFee, txPoolRememberTagTxnDead, txPoolRememberTagTxnEarly, txPoolRememberTagTooLarge, txPoolRememberTagGroupID, + txPoolRememberTagTxID, txPoolRememberTagLease, txPoolRememberTagTxIDEval, txPoolRememberTagLeaseEval, txPoolRememberTagEvalGeneric, + ) + transactionMessageTxPoolCheckCounter = metrics.NewTagCounter( + "algod_transaction_messages_txpool_check_err_{TAG}", "Number of transaction messages that didn't pass check by txpool b/c of {TAG}", + txPoolRememberTagTxnNotWellFormed, txPoolRememberTagTxnDead, txPoolRememberTagTxnEarly, txPoolRememberTagTooLarge, txPoolRememberTagGroupID, + txPoolRememberTagTxID, txPoolRememberTagLease, txPoolRememberTagTxIDEval, txPoolRememberTagLeaseEval, txPoolRememberTagEvalGeneric, + ) + }() + transactionMessageTxPoolRememberCounter = metrics.NewTagCounter( + "algod_transaction_messages_txpool_remember_err_{TAG}", "Number of transaction messages not remembered by txpool b/c of {TAG}", + txPoolRememberTagCap, txPoolRememberPendingEval, txPoolRememberTagNoSpace, txPoolRememberTagFee, txPoolRememberTagTxnDead, txPoolRememberTagTxnEarly, txPoolRememberTagTooLarge, txPoolRememberTagGroupID, + txPoolRememberTagTxID, txPoolRememberTagLease, txPoolRememberTagTxIDEval, txPoolRememberTagLeaseEval, txPoolRememberTagEvalGeneric, + ) + transactionMessageTxPoolCheckCounter = metrics.NewTagCounter( + "algod_transaction_messages_txpool_check_err_{TAG}", "Number of transaction messages that didn't pass check by txpool b/c of {TAG}", + txPoolRememberTagTxnNotWellFormed, txPoolRememberTagTxnDead, txPoolRememberTagTxnEarly, txPoolRememberTagTooLarge, txPoolRememberTagGroupID, + txPoolRememberTagTxID, txPoolRememberTagLease, txPoolRememberTagTxIDEval, txPoolRememberTagLeaseEval, txPoolRememberTagEvalGeneric, + ) result := map[string]float64{} checkResult := map[string]float64{} @@ -1683,8 +2153,12 @@ func TestTxHandlerRememberReportErrorsWithTxPool(t *testing.T) { cfg.TxPoolSize = config.MaxTxGroupSize + 1 ledger, err := LoadLedger(log, ledgerName, inMem, protocol.ConsensusCurrentVersion, genBal, genesisID, genesisHash, nil, cfg) require.NoError(t, err) + defer ledger.Close() - handler := makeTestTxHandler(ledger, cfg) + handler, err := makeTestTxHandler(ledger, cfg) + require.NoError(t, err) + defer handler.txVerificationPool.Shutdown() + defer close(handler.streamVerifierDropped) // since Start is not called, set the context here handler.ctx, handler.ctxCancel = context.WithCancel(context.Background()) defer handler.ctxCancel() @@ -1694,14 +2168,6 @@ func TestTxHandlerRememberReportErrorsWithTxPool(t *testing.T) { handler.postProcessCheckedTxn(&wi) require.Equal(t, 1, getMetricCounter(txPoolRememberTagTxnDead)) - // trigger max pool capacity metric - hdr := bookkeeping.BlockHeader{ - Round: 1, - UpgradeState: bookkeeping.UpgradeState{ - CurrentProtocol: protocol.ConsensusCurrentVersion, - }, - } - txn1 := transactions.Transaction{ Type: protocol.PaymentTx, Header: transactions.Header{ @@ -1813,30 +2279,199 @@ func TestTxHandlerRememberReportErrorsWithTxPool(t *testing.T) { // require.Equal(t, 1, getMetricCounter(txPoolRememberFee)) // make an invalid block to fail recompute pool and expose transactionMessageTxGroupRememberNoPendingEval metric - blockTicker := &blockTicker{cond: *sync.NewCond(&deadlock.Mutex{})} - blockListeners := []realledger.BlockListener{ + blockTicker := makeBlockTicker() + blockListeners := []ledgercore.BlockListener{ handler.txPool, blockTicker, } ledger.RegisterBlockListeners(blockListeners) - hdr = bookkeeping.BlockHeader{ - Round: 1, - UpgradeState: bookkeeping.UpgradeState{ - CurrentProtocol: "test", - }, - } + // add few blocks: on ci sometimes blockTicker is not fired in time in case of a single block + for i := basics.Round(1); i <= 3; i++ { + hdr := bookkeeping.BlockHeader{ + Round: i, + UpgradeState: bookkeeping.UpgradeState{ + CurrentProtocol: "test", + }, + } - blk := bookkeeping.Block{ - BlockHeader: hdr, - Payset: []transactions.SignedTxnInBlock{{}}, + blk := bookkeeping.Block{ + BlockHeader: hdr, + Payset: []transactions.SignedTxnInBlock{{}}, + } + vb := ledgercore.MakeValidatedBlock(blk, ledgercore.StateDelta{}) + err = ledger.AddValidatedBlock(vb, agreement.Certificate{}) + require.NoError(t, err) } - vb := ledgercore.MakeValidatedBlock(blk, ledgercore.StateDelta{}) - err = ledger.AddValidatedBlock(vb, agreement.Certificate{}) - require.NoError(t, err) blockTicker.Wait() wi.unverifiedTxGroup = []transactions.SignedTxn{} handler.postProcessCheckedTxn(&wi) require.Equal(t, 1, getMetricCounter(txPoolRememberPendingEval)) } + +func TestMakeTxHandlerErrors(t *testing.T) { + partitiontest.PartitionTest(t) + opts := TxHandlerOpts{ + nil, nil, nil, &mocks.MockNetwork{}, "", crypto.Digest{}, config.Local{}, + } + _, err := MakeTxHandler(opts) + require.Error(t, err, ErrInvalidTxPool) + + opts = TxHandlerOpts{ + &pools.TransactionPool{}, nil, nil, &mocks.MockNetwork{}, "", crypto.Digest{}, config.Local{}, + } + _, err = MakeTxHandler(opts) + require.Error(t, err, ErrInvalidLedger) + + // it is not possible to test MakeStreamVerifier returning an error, because it is not possible to + // get the leger return an error for returining the header of its latest round +} + +// TestTxHandlerRestartWithBacklogAndTxPool starts txHandler, sends transactions, +// stops, starts in a loop, sends more transactions, and makes sure all the transactions +// are accounted for. It uses the production backlog worker +func TestTxHandlerRestartWithBacklogAndTxPool(t *testing.T) { //nolint:paralleltest // Not parallel because it mutates global metrics + partitiontest.PartitionTest(t) + transactionMessagesDroppedFromBacklog = metrics.MakeCounter(metrics.TransactionMessagesDroppedFromBacklog) + transactionMessagesDroppedFromPool = metrics.MakeCounter(metrics.TransactionMessagesDroppedFromPool) + transactionMessagesTxnSigVerificationFailed = metrics.MakeCounter(metrics.TransactionMessagesTxnSigVerificationFailed) + transactionMessagesBacklogErr = metrics.MakeCounter(metrics.TransactionMessagesBacklogErr) + transactionMessagesAlreadyCommitted = metrics.MakeCounter(metrics.TransactionMessagesAlreadyCommitted) + transactionMessagesRemember = metrics.MakeCounter(metrics.TransactionMessagesRemember) + transactionMessagesHandled = metrics.MakeCounter(metrics.TransactionMessagesHandled) + + defer func() { + // reset the counters + transactionMessagesDroppedFromBacklog = metrics.MakeCounter(metrics.TransactionMessagesDroppedFromBacklog) + transactionMessagesDroppedFromPool = metrics.MakeCounter(metrics.TransactionMessagesDroppedFromPool) + transactionMessagesTxnSigVerificationFailed = metrics.MakeCounter(metrics.TransactionMessagesTxnSigVerificationFailed) + transactionMessagesBacklogErr = metrics.MakeCounter(metrics.TransactionMessagesBacklogErr) + transactionMessagesAlreadyCommitted = metrics.MakeCounter(metrics.TransactionMessagesAlreadyCommitted) + transactionMessagesRemember = metrics.MakeCounter(metrics.TransactionMessagesRemember) + transactionMessagesHandled = metrics.MakeCounter(metrics.TransactionMessagesHandled) + }() + + const numUsers = 100 + log := logging.TestingLog(t) + log.SetLevel(logging.Warn) + addresses := make([]basics.Address, numUsers) + secrets := make([]*crypto.SignatureSecrets, numUsers) + + // avoid printing the warning messages + origLevel := logging.Base().GetLevel() + defer func() { logging.Base().SetLevel(origLevel) }() + logging.Base().SetLevel(logging.Error) + + // prepare the accounts + genesis := make(map[basics.Address]basics.AccountData) + for i := 0; i < numUsers; i++ { + secret := keypair() + addr := basics.Address(secret.SignatureVerifier) + secrets[i] = secret + addresses[i] = addr + genesis[addr] = basics.AccountData{ + Status: basics.Online, + MicroAlgos: basics.MicroAlgos{Raw: 10000000000000}, + } + } + genesis[poolAddr] = basics.AccountData{ + Status: basics.NotParticipating, + MicroAlgos: basics.MicroAlgos{Raw: config.Consensus[protocol.ConsensusCurrentVersion].MinBalance}, + } + + // setup the ledger + require.Equal(t, len(genesis), numUsers+1) + genBal := bookkeeping.MakeGenesisBalances(genesis, sinkAddr, poolAddr) + ledgerName := fmt.Sprintf("%s-mem", t.Name()) + const inMem = true + cfg := config.GetDefaultLocal() + cfg.Archival = true + ledger, err := LoadLedger(log, ledgerName, inMem, protocol.ConsensusCurrentVersion, genBal, genesisID, genesisHash, nil, cfg) + require.NoError(t, err) + defer ledger.Ledger.Close() + + handler, err := makeTestTxHandler(ledger, cfg) + require.NoError(t, err) + defer handler.txVerificationPool.Shutdown() + defer close(handler.streamVerifierDropped) + // prepare the transactions + numTxns := 3000 + maxGroupSize := 1 + tps := 40000 + invalidRate := float32(0.5) + rateAdjuster := time.Second / time.Duration(tps) + signedTransactionGroups, badTxnGroups := makeSignedTxnGroups(numTxns, numUsers, maxGroupSize, invalidRate, addresses, secrets) + var encodedSignedTransactionGroups []network.IncomingMessage + + encodedSignedTransactionGroups = make([]network.IncomingMessage, 0, numTxns) + for _, stxngrp := range signedTransactionGroups { + data := make([]byte, 0) + for _, stxn := range stxngrp { + data = append(data, protocol.Encode(&stxn)...) + } + encodedSignedTransactionGroups = + append(encodedSignedTransactionGroups, network.IncomingMessage{Data: data}) + } + + // start the handler + handler.Start() + + // send the transactions to the backlog worker + for _, tg := range encodedSignedTransactionGroups[0 : numTxns/2] { + handler.processIncomingTxn(tg) + time.Sleep(rateAdjuster) + } + // stop in a loop to test for possible race conditions + for x := 0; x < 1000; x++ { + handler.Stop() + handler.Start() + } + handler.Stop() + + // send the second half after stopping the txHandler + for _, tg := range encodedSignedTransactionGroups[numTxns/2:] { + handler.processIncomingTxn(tg) + time.Sleep(rateAdjuster) + } + + // check that all the incomming transactions are accounted for + droppeda, droppedb := getDropped() + dropped := droppeda + droppedb + stuckInBLQueue := uint64(len(handler.backlogQueue)) + resultBadTxnCount := transactionMessagesTxnSigVerificationFailed.GetUint64Value() + resultGoodTxnCount := transactionMessagesHandled.GetUint64Value() + shutdownDropCount := transactionMessagesBacklogErr.GetUint64Value() + require.Equal(t, numTxns, int(dropped+resultGoodTxnCount+resultBadTxnCount+stuckInBLQueue+shutdownDropCount)) + + // start the handler again + handler.Start() + defer handler.Stop() + + // no dpulicates are sent at this point + require.Equal(t, 0, int(transactionMessagesAlreadyCommitted.GetUint64Value())) + + // send the same set of transactions again + for _, tg := range encodedSignedTransactionGroups { + handler.processIncomingTxn(tg) + time.Sleep(rateAdjuster) + } + + inputGoodTxnCount := len(signedTransactionGroups) - len(badTxnGroups) + tp := handler.txPool + // Wait untill all the expected transactions are in the pool + for x := 0; x < 100; x++ { + if len(tp.PendingTxGroups()) == inputGoodTxnCount { + break + } + time.Sleep(40 * time.Millisecond) + } + + // check the couters and the accepted transactions + require.Equal(t, inputGoodTxnCount, len(tp.PendingTxGroups())) + for _, txg := range tp.PendingTxGroups() { + u, _ := binary.Uvarint(txg[0].Txn.Note) + _, inBad := badTxnGroups[u] + require.False(t, inBad, "invalid transaction accepted") + } +} diff --git a/data/txntest/defi.go b/data/txntest/defi.go index 888b51c0e0..635393a675 100644 --- a/data/txntest/defi.go +++ b/data/txntest/defi.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -526,7 +526,7 @@ func CreateTinyManSignedTxGroup(tb testing.TB, txns []Txn) ([]transactions.Signe ops, err := logic.AssembleString(TmLsig) require.NoError(tb, err) - stxns := SignedTxns(&txns[0], &txns[1], &txns[2], &txns[3]) + stxns := Group(&txns[0], &txns[1], &txns[2], &txns[3]) stxns[1].Lsig.Logic = ops.Program stxns[3].Lsig.Logic = ops.Program diff --git a/data/txntest/txn.go b/data/txntest/txn.go index f1ac3768b5..8e9f699f80 100644 --- a/data/txntest/txn.go +++ b/data/txntest/txn.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -287,10 +287,10 @@ func (tx Txn) SignedTxnWithAD() transactions.SignedTxnWithAD { return transactions.SignedTxnWithAD{SignedTxn: tx.SignedTxn()} } -// SignedTxns turns a list of Txns into a slice of SignedTxns with -// GroupIDs set properly to make them a transaction group. Maybe -// another name is more approrpriate -func SignedTxns(txns ...*Txn) []transactions.SignedTxn { +// Group turns a list of Txns into a slice of SignedTxns with +// GroupIDs set properly to make them a transaction group. The input +// Txns are modified with the calculated GroupID. +func Group(txns ...*Txn) []transactions.SignedTxn { txgroup := transactions.TxGroup{ TxGroupHashes: make([]crypto.Digest, len(txns)), } diff --git a/docker/README.md b/docker/README.md index c40a21ed28..78099e6027 100644 --- a/docker/README.md +++ b/docker/README.md @@ -1,13 +1,14 @@ # Algod Container -General purpose algod docker container. +[![DockerHub](https://img.shields.io/badge/DockerHub-blue)](https://hub.docker.com/r/algorand/algod) +General purpose algod container image. -# Image Configuration +## Image Configuration There are a number of special files and environment variables used to control how a container is started. -## Default Configuration +### Default Configuration By default the following config.json overrides are applied: @@ -20,7 +21,7 @@ By default the following config.json overrides are applied: | IsIndexerActive | false | | EnableDeveloperAPI | true | -## Environment Variables +### Environment Variables The following environment variables can be supplied. Except when noted, it is possible to reconfigure deployments even after the data directory has been initialized. @@ -34,23 +35,24 @@ The following environment variables can be supplied. Except when noted, it is po | TOKEN | If set, overrides the REST API token. | | ADMIN_TOKEN | If set, overrides the REST API admin token. | +### Special Files -## Special Files - -Configuration can be modified by specifying certian files. These can be changed each time you start the container if the data directory is a mounted volume. +Configuration can be modified by specifying certain files. These can be changed each time you start the container if the data directory is a mounted volume. | File | Description | | ---- | ----------- | -| /etc/config.json | Override default configurations by providing your own file. | -| /etc/algod.token | Override default randomized REST API token. | -| /etc/algod.admin.token | Override default randomized REST API admin token. | +| /etc/algorand/config.json | Override default configurations by providing your own file. | +| /etc/algorand/algod.token | Override default randomized REST API token. | +| /etc/algorand/algod.admin.token | Override default randomized REST API admin token. | +| /etc/algorand/logging.config | Use a custom [logging.config](https://developer.algorand.org/docs/run-a-node/reference/telemetry-config/#configuration) file for configuring telemetry. | -TODO: `/etc/template.json` for overriding the private network topology. +TODO: `/etc/algorand/template.json` for overriding the private network topology. -# Example Configuration +## Example Configuration The following command launches a container configured with one of the public networks: -``` + +```bash docker run --rm -it \ -p 4190:8080 \ -e NETWORK=mainnet \ @@ -63,21 +65,51 @@ docker run --rm -it \ ``` Explanation of parts: + * `-p 4190:8080` maps the internal algod REST API to local port 4190 * `-e NETWORK=` can be set to any of the supported public networks. * `-e FAST_CATCHUP=` causes fast catchup to start shortly after launching the network. -* `-e TELEMETRY_NAME=` enables telemetry reporting to Algorand for network health analysis. +* `-e TELEMETRY_NAME=` enables telemetry reporting to Algorand for network health analysis. The value of this variable takes precedence over the `name` attribute set in `/etc/algorand/logging.config`. * `-e TOKEN=` sets the REST API token to use. -* `-v ${PWD}/data:/algod/data/` mounts a local volume to the data directory, which can be used to restart and upgrad the deployment. - +* `-v ${PWD}/data:/algod/data/` mounts a local volume to the data directory, which can be used to restart and upgrade the deployment. -# Mounting the Data Directory +## Mounting the Data Directory The data directory located at `/algod/data`. Mounting a volume at that location will allow you to shutdown and resume the node. -## Private Network +### Volume Permissions + +The container executes in the context of the `algorand` user with it's own UID and GID which is handled differently depending on your operating system. Here are a few options for how to work with this environment: + +#### Named Volume + +Using a named volume will work without any specific configuration in most cases: + +```bash +docker volume create algod-data +docker run -it --rm -d -v algod-data:/algod/data algorand/algod +``` + +#### Local Directory without SELinux + +Explicitly set the UID and GID of the container: + +```bash +docker run -it --rm -d -v /srv/data:/algod/data -u $UID:$GID algorand/algod +``` + +#### Local Directory with SELinux + +Set the UID and GID of the container while add the `Z` option to the volume definition: + +```bash +docker run -it --rm -d -v /srv/data:/algod/data:Z -u $UID:$GID algorand/algod +``` + +> See the documentation on [configuring the selinux label](https://docs.docker.com/storage/bind-mounts/#configure-the-selinux-label). + +### Private Network Private networks work a little bit differently. They are configured with, potentially, several data directories. The default topology supplied with this container is installed to `/algod/`, and has a single node named `data`. This means the private network has a data directory at `/algod/data`, matching the production configuration. Because the root directory contains some metadata, if persistence of the private network is required, you should mount the volume `/algod/` instead of `/algod/data`. This will ensure the extra metadata is included when changing images. - diff --git a/docker/files/build/install.sh b/docker/files/build/install.sh index 20d5766e47..abc57cc5ec 100755 --- a/docker/files/build/install.sh +++ b/docker/files/build/install.sh @@ -82,7 +82,11 @@ fi git log -n 5 ./scripts/configure_dev.sh -make build -./scripts/dev_install.sh -p "${BINDIR}" -d "${ALGORAND_DATA}" +# make sure the makefile uses specific values for BUILD_NUMBER and BRANCH +BUILD_NUMBER="" BRANCH="$BRANCH" make build + +shopt -s extglob + +cd "$BINDIR" && rm -vrf !(algocfg|algod|algokey|diagcfg|goal|kmd|msgpacktool|node_exporter|tealdbg|update.sh|updater|COPYING) "$BINDIR"/algod -v diff --git a/docker/files/run/run.sh b/docker/files/run/run.sh index 027c0cc7fe..627665dd6f 100755 --- a/docker/files/run/run.sh +++ b/docker/files/run/run.sh @@ -13,22 +13,25 @@ function apply_configuration() { cd "$ALGORAND_DATA" # check for config file overrides. - if [ -f "/etc/config.json" ]; then - cp /etc/config.json config.json + if [ -f "/etc/algorand/config.json" ]; then + cp /etc/algorand/config.json config.json fi - if [ -f "/etc/algod.token" ]; then - cp /etc/algod.token algod.token + if [ -f "/etc/algorand/algod.token" ]; then + cp /etc/algorand/algod.token algod.token fi - if [ -f "/etc/algod.admin.token" ]; then - cp /etc/algod.admin.token algod.admin.token + if [ -f "/etc/algorand/algod.admin.token" ]; then + cp /etc/algorand/algod.admin.token algod.admin.token + fi + if [ -f "/etc/algorand/logging.config" ]; then + cp /etc/algorand/logging.config logging.config fi # check for environment variable overrides. if [ "$TOKEN" != "" ]; then - echo "$TOKEN" > algod.token + echo "$TOKEN" >algod.token fi if [ "$ADMIN_TOKEN" != "" ]; then - echo "$ADMIN_TOKEN" > algod.admin.token + echo "$ADMIN_TOKEN" >algod.admin.token fi # configure telemetry @@ -57,8 +60,8 @@ function start_public_network() { apply_configuration - if [ $FAST_CATCHUP ]; then - catchup& + if [[ $FAST_CATCHUP ]]; then + catchup & fi # redirect output to stdout algod -o @@ -75,20 +78,19 @@ function configure_data_dir() { } function start_new_public_network() { - cd /node - if [ ! -d "run/genesis/$NETWORK" ]; then + cd /algod + if [ ! -d "/node/run/genesis/${NETWORK}" ]; then echo "No genesis file for '$NETWORK' is available." exit 1 fi mkdir -p "$ALGORAND_DATA" - mv dataTemplate/* "$ALGORAND_DATA" - rm -rf dataTemplate - cp "run/genesis/$NETWORK/genesis.json" "$ALGORAND_DATA/genesis.json" cd "$ALGORAND_DATA" - mv config.json.example config.json + cp "/node/run/genesis/${NETWORK}/genesis.json" genesis.json + cp /node/run/config.json.example config.json + configure_data_dir local ID @@ -109,18 +111,17 @@ function start_private_network() { apply_configuration # TODO: Is there a way to properly exec a private network? - goal network start -r "$ALGORAND_DATA/.." - tail -f "$ALGORAND_DATA/node.log" + goal network start -r "${ALGORAND_DATA}/.." + tail -f "${ALGORAND_DATA}/node.log" } function start_new_private_network() { - cd /node local TEMPLATE="template.json" if [ "$DEV_MODE" ]; then TEMPLATE="devmode_template.json" fi - sed -i "s/NUM_ROUNDS/${NUM_ROUNDS:-30000}/" "run/$TEMPLATE" - goal network create -n dockernet -r "$ALGORAND_DATA/.." -t "run/$TEMPLATE" + sed -i "s/NUM_ROUNDS/${NUM_ROUNDS:-30000}/" "/node/run/$TEMPLATE" + goal network create --noclean -n dockernet -r "${ALGORAND_DATA}/.." -t "/node/run/$TEMPLATE" configure_data_dir start_private_network } diff --git a/gen/generate.go b/gen/generate.go index 9274da4203..ea8792118f 100644 --- a/gen/generate.go +++ b/gen/generate.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/gen/generate_test.go b/gen/generate_test.go index 64c393b5bc..e154e7c44d 100644 --- a/gen/generate_test.go +++ b/gen/generate_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/gen/walletData.go b/gen/walletData.go index 7790e9a6be..9823c03b57 100644 --- a/gen/walletData.go +++ b/gen/walletData.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/go.mod b/go.mod index d605367586..a1f20d7dfe 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.17 require ( github.com/DataDog/zstd v1.5.2 - github.com/algorand/avm-abi v0.1.1 + github.com/algorand/avm-abi v0.2.0 github.com/algorand/falcon v0.0.0-20220727072124-02a2a64c4414 github.com/algorand/go-codec/codec v1.1.8 github.com/algorand/go-deadlock v0.2.2 diff --git a/go.sum b/go.sum index 300b5f8a1c..68a08e0848 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,8 @@ github.com/DataDog/zstd v1.5.2 h1:vUG4lAyuPCXO0TLbXvPv7EB7cNK1QV/luu55UHLrrn8= github.com/DataDog/zstd v1.5.2/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= -github.com/algorand/avm-abi v0.1.0 h1:znZFQXpSUVYz37vXbaH5OZG2VK4snTyXwnc/tV9CVr4= -github.com/algorand/avm-abi v0.1.0/go.mod h1:+CgwM46dithy850bpTeHh9MC99zpn2Snirb3QTl2O/g= -github.com/algorand/avm-abi v0.1.1 h1:dbyQKzXiyaEbzpmqXFB30yAhyqseBsyqXTyZbNbkh2Y= -github.com/algorand/avm-abi v0.1.1/go.mod h1:+CgwM46dithy850bpTeHh9MC99zpn2Snirb3QTl2O/g= +github.com/algorand/avm-abi v0.2.0 h1:bkjsG+BOEcxUcnGSALLosmltE0JZdg+ZisXKx0UDX2k= +github.com/algorand/avm-abi v0.2.0/go.mod h1:+CgwM46dithy850bpTeHh9MC99zpn2Snirb3QTl2O/g= github.com/algorand/falcon v0.0.0-20220727072124-02a2a64c4414 h1:nwYN+GQ7Z5OOfZwqBO1ma7DSlP7S1YrKWICOyjkwqrc= github.com/algorand/falcon v0.0.0-20220727072124-02a2a64c4414/go.mod h1:OkQyHlGvS0kLNcIWbC21/uQcnbfwSOQm+wiqWwBG9pQ= github.com/algorand/go-codec v1.1.8/go.mod h1:XhzVs6VVyWMLu6cApb9/192gBjGRVGm5cX5j203Heg4= diff --git a/installer/config.json.example b/installer/config.json.example index 9002388088..137578e0fa 100644 --- a/installer/config.json.example +++ b/installer/config.json.example @@ -1,15 +1,16 @@ { - "Version": 26, + "Version": 27, "AccountUpdatesStatsInterval": 5000000000, "AccountsRebuildSynchronousMode": 1, - "AgreementIncomingBundlesQueueLength": 7, - "AgreementIncomingProposalsQueueLength": 25, - "AgreementIncomingVotesQueueLength": 10000, + "AgreementIncomingBundlesQueueLength": 15, + "AgreementIncomingProposalsQueueLength": 50, + "AgreementIncomingVotesQueueLength": 20000, "AnnounceParticipationKey": true, "Archival": false, "BaseLoggerDebugLevel": 4, "BlockServiceCustomFallbackEndpoints": "", "BroadcastConnectionsLimit": -1, + "CadaverDirectory": "", "CadaverSizeTarget": 0, "CatchpointFileHistoryLength": 365, "CatchpointInterval": 10000, @@ -38,6 +39,7 @@ "EnableBlockServiceFallbackToArchiver": true, "EnableCatchupFromArchiveServers": false, "EnableDeveloperAPI": false, + "EnableExperimentalAPI": false, "EnableGossipBlockService": true, "EnableIncomingMessageFilter": false, "EnableLedgerService": false, @@ -49,6 +51,7 @@ "EnableRequestLogger": false, "EnableRuntimeMetrics": false, "EnableTopAccountsReporting": false, + "EnableTxBacklogRateLimiting": false, "EnableUsageLog": false, "EnableVerbosedTransactionSyncLogging": false, "EndpointAddress": "127.0.0.1:0", @@ -56,7 +59,8 @@ "ForceFetchTransactions": false, "ForceRelayMessages": false, "GossipFanout": 4, - "IncomingConnectionsLimit": 800, + "HeartbeatUpdateInterval": 600, + "IncomingConnectionsLimit": 2400, "IncomingMessageFilterBucketCount": 5, "IncomingMessageFilterBucketSize": 512, "IsIndexerActive": false, @@ -68,7 +72,7 @@ "MaxAPIResourcesPerAccount": 100000, "MaxAcctLookback": 4, "MaxCatchpointDownloadDuration": 7200000000000, - "MaxConnectionsPerIP": 30, + "MaxConnectionsPerIP": 15, "MinCatchpointFileDownloadBytesPerSecond": 20480, "NetAddress": "", "NetworkMessageTraceServer": "", @@ -98,6 +102,9 @@ "TelemetryToLog": true, "TransactionSyncDataExchangeRate": 0, "TransactionSyncSignificantMessageThreshold": 0, + "TxBacklogReservedCapacityPerPeer": 20, + "TxBacklogServiceRateWindowSeconds": 10, + "TxBacklogSize": 26000, "TxIncomingFilteringFlags": 1, "TxPoolExponentialIncreaseFactor": 2, "TxPoolSize": 75000, diff --git a/ledger/accountdb.go b/ledger/acctdeltas.go similarity index 67% rename from ledger/accountdb.go rename to ledger/acctdeltas.go index c82a4e5ee8..2bbb5e5e12 100644 --- a/ledger/accountdb.go +++ b/ledger/acctdeltas.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -20,14 +20,11 @@ import ( "bytes" "context" "database/sql" - "errors" "fmt" - "math" - - "github.com/algorand/msgp/msgp" "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/ledger/encoded" "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/ledger/store" "github.com/algorand/go-algorand/protocol" @@ -103,7 +100,7 @@ const MaxEncodedBaseAccountDataSize = 350 const MaxEncodedBaseResourceDataSize = 20000 // prepareNormalizedBalancesV5 converts an array of encodedBalanceRecordV5 into an equal size array of normalizedAccountBalances. -func prepareNormalizedBalancesV5(bals []encodedBalanceRecordV5, proto config.ConsensusParams) (normalizedAccountBalances []store.NormalizedAccountBalance, err error) { +func prepareNormalizedBalancesV5(bals []encoded.BalanceRecordV5, proto config.ConsensusParams) (normalizedAccountBalances []store.NormalizedAccountBalance, err error) { normalizedAccountBalances = make([]store.NormalizedAccountBalance, len(bals)) for i, balance := range bals { normalizedAccountBalances[i].Address = balance.Address @@ -141,8 +138,8 @@ func prepareNormalizedBalancesV5(bals []encodedBalanceRecordV5, proto config.Con return } -// prepareNormalizedBalancesV6 converts an array of encodedBalanceRecordV6 into an equal size array of normalizedAccountBalances. -func prepareNormalizedBalancesV6(bals []encodedBalanceRecordV6, proto config.ConsensusParams) (normalizedAccountBalances []store.NormalizedAccountBalance, err error) { +// prepareNormalizedBalancesV6 converts an array of encoded.BalanceRecordV6 into an equal size array of normalizedAccountBalances. +func prepareNormalizedBalancesV6(bals []encoded.BalanceRecordV6, proto config.ConsensusParams) (normalizedAccountBalances []store.NormalizedAccountBalance, err error) { normalizedAccountBalances = make([]store.NormalizedAccountBalance, len(bals)) for i, balance := range bals { normalizedAccountBalances[i].Address = balance.Address @@ -488,7 +485,8 @@ func (a *compactAccountDeltas) accountsLoadOld(tx *sql.Tx) (err error) { case sql.ErrNoRows: // we don't have that account, just return an empty record. a.updateOld(idx, store.PersistedAccountData{Addr: addr}) - err = nil + // Note: the err will be ignored in this case since `err` is being shadowed. + // this behaviour is equivalent to `err = nil` default: // unexpected error - let the caller know that we couldn't complete the operation. return err @@ -1062,601 +1060,3 @@ func onlineAccountsNewRoundImpl( return } - -// catchpointAccountResourceCounter keeps track of the resources processed for the current account -type catchpointAccountResourceCounter struct { - totalAppParams uint64 - totalAppLocalStates uint64 - totalAssetParams uint64 - totalAssets uint64 -} - -// encodedAccountsBatchIter allows us to iterate over the accounts data stored in the accountbase table. -type encodedAccountsBatchIter struct { - accountsRows *sql.Rows - resourcesRows *sql.Rows - nextBaseRow pendingBaseRow - nextResourceRow pendingResourceRow - acctResCnt catchpointAccountResourceCounter -} - -// Next returns an array containing the account data, in the same way it appear in the database -// returning accountCount accounts data at a time. -func (iterator *encodedAccountsBatchIter) Next(ctx context.Context, tx *sql.Tx, accountCount int, resourceCount int) (bals []encodedBalanceRecordV6, numAccountsProcessed uint64, err error) { - if iterator.accountsRows == nil { - iterator.accountsRows, err = tx.QueryContext(ctx, "SELECT rowid, address, data FROM accountbase ORDER BY rowid") - if err != nil { - return - } - } - if iterator.resourcesRows == nil { - iterator.resourcesRows, err = tx.QueryContext(ctx, "SELECT addrid, aidx, data FROM resources ORDER BY addrid, aidx") - if err != nil { - return - } - } - - // gather up to accountCount encoded accounts. - bals = make([]encodedBalanceRecordV6, 0, accountCount) - var encodedRecord encodedBalanceRecordV6 - var baseAcct store.BaseAccountData - var numAcct int - baseCb := func(addr basics.Address, rowid int64, accountData *store.BaseAccountData, encodedAccountData []byte) (err error) { - encodedRecord = encodedBalanceRecordV6{Address: addr, AccountData: encodedAccountData} - baseAcct = *accountData - numAcct++ - return nil - } - - var totalResources int - - // emptyCount := 0 - resCb := func(addr basics.Address, cidx basics.CreatableIndex, resData *store.ResourcesData, encodedResourceData []byte, lastResource bool) error { - - emptyBaseAcct := baseAcct.TotalAppParams == 0 && baseAcct.TotalAppLocalStates == 0 && baseAcct.TotalAssetParams == 0 && baseAcct.TotalAssets == 0 - if !emptyBaseAcct && resData != nil { - if encodedRecord.Resources == nil { - encodedRecord.Resources = make(map[uint64]msgp.Raw) - } - encodedRecord.Resources[uint64(cidx)] = encodedResourceData - if resData.IsApp() && resData.IsOwning() { - iterator.acctResCnt.totalAppParams++ - } - if resData.IsApp() && resData.IsHolding() { - iterator.acctResCnt.totalAppLocalStates++ - } - - if resData.IsAsset() && resData.IsOwning() { - iterator.acctResCnt.totalAssetParams++ - } - if resData.IsAsset() && resData.IsHolding() { - iterator.acctResCnt.totalAssets++ - } - totalResources++ - } - - if baseAcct.TotalAppParams == iterator.acctResCnt.totalAppParams && - baseAcct.TotalAppLocalStates == iterator.acctResCnt.totalAppLocalStates && - baseAcct.TotalAssetParams == iterator.acctResCnt.totalAssetParams && - baseAcct.TotalAssets == iterator.acctResCnt.totalAssets { - - encodedRecord.ExpectingMoreEntries = false - bals = append(bals, encodedRecord) - numAccountsProcessed++ - - iterator.acctResCnt = catchpointAccountResourceCounter{} - - return nil - } - - // max resources per chunk reached, stop iterating. - if lastResource { - encodedRecord.ExpectingMoreEntries = true - bals = append(bals, encodedRecord) - encodedRecord.Resources = nil - } - - return nil - } - - _, iterator.nextBaseRow, iterator.nextResourceRow, err = processAllBaseAccountRecords( - iterator.accountsRows, iterator.resourcesRows, - baseCb, resCb, - iterator.nextBaseRow, iterator.nextResourceRow, accountCount, resourceCount, - ) - if err != nil { - iterator.Close() - return - } - - if len(bals) == accountCount || totalResources == resourceCount { - // we're done with this iteration. - return - } - - err = iterator.accountsRows.Err() - if err != nil { - iterator.Close() - return - } - // Do not Close() the iterator here. It is the caller's responsibility to - // do so, signalled by the return of an empty chunk. If we Close() here, the - // next call to Next() will start all over! - return -} - -// Close shuts down the encodedAccountsBatchIter, releasing database resources. -func (iterator *encodedAccountsBatchIter) Close() { - if iterator.accountsRows != nil { - iterator.accountsRows.Close() - iterator.accountsRows = nil - } - if iterator.resourcesRows != nil { - iterator.resourcesRows.Close() - iterator.resourcesRows = nil - } -} - -// orderedAccountsIterStep is used by orderedAccountsIter to define the current step -// -//msgp:ignore orderedAccountsIterStep -type orderedAccountsIterStep int - -const ( - // startup step - oaiStepStartup = orderedAccountsIterStep(0) - // delete old ordering table if we have any leftover from previous invocation - oaiStepDeleteOldOrderingTable = orderedAccountsIterStep(0) - // create new ordering table - oaiStepCreateOrderingTable = orderedAccountsIterStep(1) - // query the existing accounts - oaiStepQueryAccounts = orderedAccountsIterStep(2) - // iterate over the existing accounts and insert their hash & address into the staging ordering table - oaiStepInsertAccountData = orderedAccountsIterStep(3) - // create an index on the ordering table so that we can efficiently scan it. - oaiStepCreateOrderingAccountIndex = orderedAccountsIterStep(4) - // query the ordering table - oaiStepSelectFromOrderedTable = orderedAccountsIterStep(5) - // iterate over the ordering table - oaiStepIterateOverOrderedTable = orderedAccountsIterStep(6) - // cleanup and delete ordering table - oaiStepShutdown = orderedAccountsIterStep(7) - // do nothing as we're done. - oaiStepDone = orderedAccountsIterStep(8) -) - -// orderedAccountsIter allows us to iterate over the accounts addresses in the order of the account hashes. -type orderedAccountsIter struct { - step orderedAccountsIterStep - accountBaseRows *sql.Rows - hashesRows *sql.Rows - resourcesRows *sql.Rows - tx *sql.Tx - pendingBaseRow pendingBaseRow - pendingResourceRow pendingResourceRow - accountCount int - insertStmt *sql.Stmt -} - -// makeOrderedAccountsIter creates an ordered account iterator. Note that due to implementation reasons, -// only a single iterator can be active at a time. -func makeOrderedAccountsIter(tx *sql.Tx, accountCount int) *orderedAccountsIter { - return &orderedAccountsIter{ - tx: tx, - accountCount: accountCount, - step: oaiStepStartup, - } -} - -type pendingBaseRow struct { - addr basics.Address - rowid int64 - accountData *store.BaseAccountData - encodedAccountData []byte -} - -type pendingResourceRow struct { - addrid int64 - aidx basics.CreatableIndex - buf []byte -} - -func processAllResources( - resRows *sql.Rows, - addr basics.Address, accountData *store.BaseAccountData, acctRowid int64, pr pendingResourceRow, resourceCount int, - callback func(addr basics.Address, creatableIdx basics.CreatableIndex, resData *store.ResourcesData, encodedResourceData []byte, lastResource bool) error, -) (pendingResourceRow, int, error) { - var err error - count := 0 - - // Declare variabled outside of the loop to prevent allocations per iteration. - // At least resData is resolved as "escaped" because of passing it by a pointer to protocol.Decode() - var buf []byte - var addrid int64 - var aidx basics.CreatableIndex - var resData store.ResourcesData - for { - if pr.addrid != 0 { - // some accounts may not have resources, consider the following case: - // acct 1 and 3 has resources, account 2 does not - // in this case addrid = 3 after processing resources from 1, but acctRowid = 2 - // and we need to skip accounts without resources - if pr.addrid > acctRowid { - err = callback(addr, 0, nil, nil, false) - return pr, count, err - } - if pr.addrid < acctRowid { - err = fmt.Errorf("resource table entries mismatches accountbase table entries : reached addrid %d while expecting resource for %d", pr.addrid, acctRowid) - return pendingResourceRow{}, count, err - } - addrid = pr.addrid - buf = pr.buf - aidx = pr.aidx - pr = pendingResourceRow{} - } else { - if !resRows.Next() { - err = callback(addr, 0, nil, nil, false) - if err != nil { - return pendingResourceRow{}, count, err - } - break - } - err = resRows.Scan(&addrid, &aidx, &buf) - if err != nil { - return pendingResourceRow{}, count, err - } - if addrid < acctRowid { - err = fmt.Errorf("resource table entries mismatches accountbase table entries : reached addrid %d while expecting resource for %d", addrid, acctRowid) - return pendingResourceRow{}, count, err - } else if addrid > acctRowid { - err = callback(addr, 0, nil, nil, false) - return pendingResourceRow{addrid, aidx, buf}, count, err - } - } - resData = store.ResourcesData{} - err = protocol.Decode(buf, &resData) - if err != nil { - return pendingResourceRow{}, count, err - } - count++ - if resourceCount > 0 && count == resourceCount { - // last resource to be included in chunk - err := callback(addr, aidx, &resData, buf, true) - return pendingResourceRow{}, count, err - } - err = callback(addr, aidx, &resData, buf, false) - if err != nil { - return pendingResourceRow{}, count, err - } - } - return pendingResourceRow{}, count, nil -} - -func processAllBaseAccountRecords( - baseRows *sql.Rows, - resRows *sql.Rows, - baseCb func(addr basics.Address, rowid int64, accountData *store.BaseAccountData, encodedAccountData []byte) error, - resCb func(addr basics.Address, creatableIdx basics.CreatableIndex, resData *store.ResourcesData, encodedResourceData []byte, lastResource bool) error, - pendingBase pendingBaseRow, pendingResource pendingResourceRow, accountCount int, resourceCount int, -) (int, pendingBaseRow, pendingResourceRow, error) { - var addr basics.Address - var prevAddr basics.Address - var err error - count := 0 - - var accountData store.BaseAccountData - var addrbuf []byte - var buf []byte - var rowid int64 - for { - if pendingBase.rowid != 0 { - addr = pendingBase.addr - rowid = pendingBase.rowid - accountData = *pendingBase.accountData - buf = pendingBase.encodedAccountData - pendingBase = pendingBaseRow{} - } else { - if !baseRows.Next() { - break - } - - err = baseRows.Scan(&rowid, &addrbuf, &buf) - if err != nil { - return 0, pendingBaseRow{}, pendingResourceRow{}, err - } - - if len(addrbuf) != len(addr) { - err = fmt.Errorf("account DB address length mismatch: %d != %d", len(addrbuf), len(addr)) - return 0, pendingBaseRow{}, pendingResourceRow{}, err - } - - copy(addr[:], addrbuf) - - accountData = store.BaseAccountData{} - err = protocol.Decode(buf, &accountData) - if err != nil { - return 0, pendingBaseRow{}, pendingResourceRow{}, err - } - } - - err = baseCb(addr, rowid, &accountData, buf) - if err != nil { - return 0, pendingBaseRow{}, pendingResourceRow{}, err - } - - var resourcesProcessed int - pendingResource, resourcesProcessed, err = processAllResources(resRows, addr, &accountData, rowid, pendingResource, resourceCount, resCb) - if err != nil { - err = fmt.Errorf("failed to gather resources for account %v, addrid %d, prev address %v : %w", addr, rowid, prevAddr, err) - return 0, pendingBaseRow{}, pendingResourceRow{}, err - } - - if resourcesProcessed == resourceCount { - // we're done with this iteration. - pendingBase := pendingBaseRow{ - addr: addr, - rowid: rowid, - accountData: &accountData, - encodedAccountData: buf, - } - return count, pendingBase, pendingResource, nil - } - resourceCount -= resourcesProcessed - - count++ - if accountCount > 0 && count == accountCount { - // we're done with this iteration. - return count, pendingBaseRow{}, pendingResource, nil - } - prevAddr = addr - } - - return count, pendingBaseRow{}, pendingResource, nil -} - -// accountAddressHash is used by Next to return a single account address and the associated hash. -type accountAddressHash struct { - addrid int64 - digest []byte -} - -// Next returns an array containing the account address and hash -// the Next function works in multiple processing stages, where it first processes the current accounts and order them -// followed by returning the ordered accounts. In the first phase, it would return empty accountAddressHash array -// and sets the processedRecords to the number of accounts that were processed. On the second phase, the acct -// would contain valid data ( and optionally the account data as well, if was asked in makeOrderedAccountsIter) and -// the processedRecords would be zero. If err is sql.ErrNoRows it means that the iterator have completed it's work and no further -// accounts exists. Otherwise, the caller is expected to keep calling "Next" to retrieve the next set of accounts -// ( or let the Next function make some progress toward that goal ) -func (iterator *orderedAccountsIter) Next(ctx context.Context) (acct []accountAddressHash, processedRecords int, err error) { - if iterator.step == oaiStepDeleteOldOrderingTable { - // although we're going to delete this table anyway when completing the iterator execution, we'll try to - // clean up any intermediate table. - _, err = iterator.tx.ExecContext(ctx, "DROP TABLE IF EXISTS accountsiteratorhashes") - if err != nil { - return - } - iterator.step = oaiStepCreateOrderingTable - return - } - if iterator.step == oaiStepCreateOrderingTable { - // create the temporary table - _, err = iterator.tx.ExecContext(ctx, "CREATE TABLE accountsiteratorhashes(addrid INTEGER, hash blob)") - if err != nil { - return - } - iterator.step = oaiStepQueryAccounts - return - } - if iterator.step == oaiStepQueryAccounts { - // iterate over the existing accounts - iterator.accountBaseRows, err = iterator.tx.QueryContext(ctx, "SELECT rowid, address, data FROM accountbase ORDER BY rowid") - if err != nil { - return - } - // iterate over the existing resources - iterator.resourcesRows, err = iterator.tx.QueryContext(ctx, "SELECT addrid, aidx, data FROM resources ORDER BY addrid, aidx") - if err != nil { - return - } - // prepare the insert statement into the temporary table - iterator.insertStmt, err = iterator.tx.PrepareContext(ctx, "INSERT INTO accountsiteratorhashes(addrid, hash) VALUES(?, ?)") - if err != nil { - return - } - iterator.step = oaiStepInsertAccountData - return - } - if iterator.step == oaiStepInsertAccountData { - var lastAddrID int64 - baseCb := func(addr basics.Address, rowid int64, accountData *store.BaseAccountData, encodedAccountData []byte) (err error) { - hash := store.AccountHashBuilderV6(addr, accountData, encodedAccountData) - _, err = iterator.insertStmt.ExecContext(ctx, rowid, hash) - if err != nil { - return - } - lastAddrID = rowid - return nil - } - - resCb := func(addr basics.Address, cidx basics.CreatableIndex, resData *store.ResourcesData, encodedResourceData []byte, lastResource bool) error { - if resData != nil { - hash, err := store.ResourcesHashBuilderV6(resData, addr, cidx, resData.UpdateRound, encodedResourceData) - if err != nil { - return err - } - _, err = iterator.insertStmt.ExecContext(ctx, lastAddrID, hash) - return err - } - return nil - } - - count := 0 - count, iterator.pendingBaseRow, iterator.pendingResourceRow, err = processAllBaseAccountRecords( - iterator.accountBaseRows, iterator.resourcesRows, - baseCb, resCb, - iterator.pendingBaseRow, iterator.pendingResourceRow, iterator.accountCount, math.MaxInt, - ) - if err != nil { - iterator.Close(ctx) - return - } - - if count == iterator.accountCount { - // we're done with this iteration. - processedRecords = count - return - } - - // make sure the resource iterator has no more entries. - if iterator.resourcesRows.Next() { - iterator.Close(ctx) - err = errors.New("resource table entries exceed the ones specified in the accountbase table") - return - } - - processedRecords = count - iterator.accountBaseRows.Close() - iterator.accountBaseRows = nil - iterator.resourcesRows.Close() - iterator.resourcesRows = nil - iterator.insertStmt.Close() - iterator.insertStmt = nil - iterator.step = oaiStepCreateOrderingAccountIndex - return - } - if iterator.step == oaiStepCreateOrderingAccountIndex { - // create an index. It shown that even when we're making a single select statement in step 5, it would be better to have this index vs. not having it at all. - // note that this index is using the rowid of the accountsiteratorhashes table. - _, err = iterator.tx.ExecContext(ctx, "CREATE INDEX accountsiteratorhashesidx ON accountsiteratorhashes(hash)") - if err != nil { - iterator.Close(ctx) - return - } - iterator.step = oaiStepSelectFromOrderedTable - return - } - if iterator.step == oaiStepSelectFromOrderedTable { - // select the data from the ordered table - iterator.hashesRows, err = iterator.tx.QueryContext(ctx, "SELECT addrid, hash FROM accountsiteratorhashes ORDER BY hash") - - if err != nil { - iterator.Close(ctx) - return - } - iterator.step = oaiStepIterateOverOrderedTable - return - } - - if iterator.step == oaiStepIterateOverOrderedTable { - acct = make([]accountAddressHash, iterator.accountCount) - acctIdx := 0 - for iterator.hashesRows.Next() { - err = iterator.hashesRows.Scan(&(acct[acctIdx].addrid), &(acct[acctIdx].digest)) - if err != nil { - iterator.Close(ctx) - return - } - acctIdx++ - if acctIdx == iterator.accountCount { - // we're done with this iteration. - return - } - } - acct = acct[:acctIdx] - iterator.step = oaiStepShutdown - iterator.hashesRows.Close() - iterator.hashesRows = nil - return - } - if iterator.step == oaiStepShutdown { - err = iterator.Close(ctx) - if err != nil { - return - } - iterator.step = oaiStepDone - // fallthrough - } - return nil, 0, sql.ErrNoRows -} - -// Close shuts down the orderedAccountsBuilderIter, releasing database resources. -func (iterator *orderedAccountsIter) Close(ctx context.Context) (err error) { - if iterator.accountBaseRows != nil { - iterator.accountBaseRows.Close() - iterator.accountBaseRows = nil - } - if iterator.resourcesRows != nil { - iterator.resourcesRows.Close() - iterator.resourcesRows = nil - } - if iterator.hashesRows != nil { - iterator.hashesRows.Close() - iterator.hashesRows = nil - } - if iterator.insertStmt != nil { - iterator.insertStmt.Close() - iterator.insertStmt = nil - } - _, err = iterator.tx.ExecContext(ctx, "DROP TABLE IF EXISTS accountsiteratorhashes") - return -} - -// catchpointPendingHashesIterator allows us to iterate over the hashes in the catchpointpendinghashes table in their order. -type catchpointPendingHashesIterator struct { - hashCount int - tx *sql.Tx - rows *sql.Rows -} - -// makeCatchpointPendingHashesIterator create a pending hashes iterator that retrieves the hashes in the catchpointpendinghashes table. -func makeCatchpointPendingHashesIterator(hashCount int, tx *sql.Tx) *catchpointPendingHashesIterator { - return &catchpointPendingHashesIterator{ - hashCount: hashCount, - tx: tx, - } -} - -// Next returns an array containing the hashes, returning HashCount hashes at a time. -func (iterator *catchpointPendingHashesIterator) Next(ctx context.Context) (hashes [][]byte, err error) { - if iterator.rows == nil { - iterator.rows, err = iterator.tx.QueryContext(ctx, "SELECT data FROM catchpointpendinghashes ORDER BY data") - if err != nil { - return - } - } - - // gather up to accountCount encoded accounts. - hashes = make([][]byte, iterator.hashCount) - hashIdx := 0 - for iterator.rows.Next() { - err = iterator.rows.Scan(&hashes[hashIdx]) - if err != nil { - iterator.Close() - return - } - - hashIdx++ - if hashIdx == iterator.hashCount { - // we're done with this iteration. - return - } - } - hashes = hashes[:hashIdx] - err = iterator.rows.Err() - if err != nil { - iterator.Close() - return - } - // we just finished reading the table. - iterator.Close() - return -} - -// Close shuts down the catchpointPendingHashesIterator, releasing database resources. -func (iterator *catchpointPendingHashesIterator) Close() { - if iterator.rows != nil { - iterator.rows.Close() - iterator.rows = nil - } -} diff --git a/ledger/accountdb_test.go b/ledger/acctdeltas_test.go similarity index 99% rename from ledger/accountdb_test.go rename to ledger/acctdeltas_test.go index c290ed827c..21ac1e5dcc 100644 --- a/ledger/accountdb_test.go +++ b/ledger/acctdeltas_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -32,14 +32,14 @@ import ( "testing" "time" - "github.com/algorand/go-algorand/data/transactions/logic" - "github.com/stretchr/testify/require" + "github.com/algorand/avm-abi/apps" "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/crypto/merklesignature" "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/ledger/encoded" "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/ledger/store" storetesting "github.com/algorand/go-algorand/ledger/store/testing" @@ -905,9 +905,9 @@ func benchmarkWriteCatchpointStagingBalancesSub(b *testing.B, ascendingOrder boo last64KAccountCreationTime = time.Duration(0) } var chunk catchpointFileChunkV6 - chunk.Balances = make([]encodedBalanceRecordV6, chunkSize) + chunk.Balances = make([]encoded.BalanceRecordV6, chunkSize) for i := uint64(0); i < chunkSize; i++ { - var randomAccount encodedBalanceRecordV6 + var randomAccount encoded.BalanceRecordV6 accountData := store.BaseAccountData{RewardsBase: accountsLoaded + i} accountData.MicroAlgos.Raw = crypto.RandUint63() randomAccount.AccountData = protocol.Encode(&accountData) @@ -1180,12 +1180,12 @@ func BenchmarkLookupKeyByPrefix(b *testing.B) { crypto.RandBytes(nameBuffer) crypto.RandBytes(valueBuffer) appID := basics.AppIndex(crypto.RandUint64()) - boxKey := logic.MakeBoxKey(appID, string(nameBuffer)) + boxKey := apps.MakeBoxKey(uint64(appID), string(nameBuffer)) err = writer.UpsertKvPair(boxKey, valueBuffer) require.NoError(b, err) if i == 0 { - prefix = logic.MakeBoxKey(appID, "") + prefix = apps.MakeBoxKey(uint64(appID), "") } } err = tx.Commit() diff --git a/ledger/acctonline.go b/ledger/acctonline.go index bc300cf1f8..f73b153e24 100644 --- a/ledger/acctonline.go +++ b/ledger/acctonline.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -360,8 +360,6 @@ func (ao *onlineAccounts) prepareCommit(dcc *deferredCommitContext) error { // Index that corresponds to the oldest round still in deltas startIndex := len(ao.onlineRoundParamsData) - len(ao.deltas) - 1 if ao.onlineRoundParamsData[startIndex+1].CurrentProtocol != ao.onlineRoundParamsData[startIndex+int(offset)].CurrentProtocol { - ao.accountsMu.RUnlock() - // in scheduleCommit, we expect that this function to update the catchpointWriting when // it's on a catchpoint round and the node is configured to generate catchpoints. Doing this in a deferred function // here would prevent us from "forgetting" to update this variable later on. @@ -539,7 +537,6 @@ func (ao *onlineAccounts) onlineTotalsEx(rnd basics.Round) (basics.MicroAlgos, e func (ao *onlineAccounts) onlineTotalsImpl(rnd basics.Round) (basics.MicroAlgos, error) { offset, err := ao.roundParamsOffset(rnd) if err != nil { - ao.log.Warnf("onlineAccounts failed to fetch online totals for rnd: %d", rnd) return basics.MicroAlgos{}, err } @@ -551,7 +548,6 @@ func (ao *onlineAccounts) onlineTotalsImpl(rnd basics.Round) (basics.MicroAlgos, func (ao *onlineAccounts) LookupOnlineAccountData(rnd basics.Round, addr basics.Address) (data basics.OnlineAccountData, err error) { oad, err := ao.lookupOnlineAccountData(rnd, addr) if err != nil { - ao.log.Warnf("onlineAccounts failed to fetch online account data for rnd: %d, addr: %v", rnd, addr) return } diff --git a/ledger/acctonline_test.go b/ledger/acctonline_test.go index 9debfc9e29..d892c21b67 100644 --- a/ledger/acctonline_test.go +++ b/ledger/acctonline_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/acctupdates.go b/ledger/acctupdates.go index c2cfc69fc6..dbaaefca69 100644 --- a/ledger/acctupdates.go +++ b/ledger/acctupdates.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/acctupdates_test.go b/ledger/acctupdates_test.go index 7329c61e12..723e2c1986 100644 --- a/ledger/acctupdates_test.go +++ b/ledger/acctupdates_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -31,11 +31,11 @@ import ( "github.com/stretchr/testify/require" + "github.com/algorand/avm-abi/apps" "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/bookkeeping" - "github.com/algorand/go-algorand/data/transactions/logic" "github.com/algorand/go-algorand/ledger/internal" "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/ledger/store" @@ -1290,7 +1290,7 @@ func TestBoxNamesByAppIDs(t *testing.T) { boxChange := ledgercore.KvValueDelta{Data: []byte(boxName)} auNewBlock(t, currentRound, au, accts, opts, map[string]ledgercore.KvValueDelta{ - logic.MakeBoxKey(appID, boxName): boxChange, + apps.MakeBoxKey(uint64(appID), boxName): boxChange, }) auCommitSync(t, currentRound, au, ml) @@ -1305,10 +1305,10 @@ func TestBoxNamesByAppIDs(t *testing.T) { // check input, see all present keys are all still there for _, storedBoxName := range testingBoxNames[:i+1] { - res, err := au.LookupKeysByPrefix(currentRound, logic.MakeBoxKey(boxNameToAppID[storedBoxName], ""), 10000) + res, err := au.LookupKeysByPrefix(currentRound, apps.MakeBoxKey(uint64(boxNameToAppID[storedBoxName]), ""), 10000) require.NoError(t, err) require.Len(t, res, 1) - require.Equal(t, logic.MakeBoxKey(boxNameToAppID[storedBoxName], storedBoxName), res[0]) + require.Equal(t, apps.MakeBoxKey(uint64(boxNameToAppID[storedBoxName]), storedBoxName), res[0]) } } @@ -1319,12 +1319,12 @@ func TestBoxNamesByAppIDs(t *testing.T) { // remove inserted box appID := boxNameToAppID[boxName] auNewBlock(t, currentRound, au, accts, opts, map[string]ledgercore.KvValueDelta{ - logic.MakeBoxKey(appID, boxName): {}, + apps.MakeBoxKey(uint64(appID), boxName): {}, }) auCommitSync(t, currentRound, au, ml) // ensure recently removed key is not present, and it is not part of the result - res, err := au.LookupKeysByPrefix(currentRound, logic.MakeBoxKey(boxNameToAppID[boxName], ""), 10000) + res, err := au.LookupKeysByPrefix(currentRound, apps.MakeBoxKey(uint64(boxNameToAppID[boxName]), ""), 10000) require.NoError(t, err) require.Len(t, res, 0) } diff --git a/ledger/applications_test.go b/ledger/applications_test.go index 1d27bb9ed1..1887da254e 100644 --- a/ledger/applications_test.go +++ b/ledger/applications_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/apply/application.go b/ledger/apply/application.go index e02af73c57..3d03349ebe 100644 --- a/ledger/apply/application.go +++ b/ledger/apply/application.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/apply/application_test.go b/ledger/apply/application_test.go index e9e9f699c7..bc8eb74ae9 100644 --- a/ledger/apply/application_test.go +++ b/ledger/apply/application_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/apply/apply.go b/ledger/apply/apply.go index 34017e383d..b694d9ed5d 100644 --- a/ledger/apply/apply.go +++ b/ledger/apply/apply.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/apply/asset.go b/ledger/apply/asset.go index 53b4bbfafb..a29dd7ff55 100644 --- a/ledger/apply/asset.go +++ b/ledger/apply/asset.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/apply/asset_test.go b/ledger/apply/asset_test.go index c46d6363f5..2b7908ac64 100644 --- a/ledger/apply/asset_test.go +++ b/ledger/apply/asset_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/apply/keyreg.go b/ledger/apply/keyreg.go index 4c7155d715..206ff31bf3 100644 --- a/ledger/apply/keyreg.go +++ b/ledger/apply/keyreg.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/apply/keyreg_test.go b/ledger/apply/keyreg_test.go index 09699e3f3d..7589b9fb1f 100644 --- a/ledger/apply/keyreg_test.go +++ b/ledger/apply/keyreg_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/apply/mockBalances_test.go b/ledger/apply/mockBalances_test.go index 8ac9701770..a9b8387159 100644 --- a/ledger/apply/mockBalances_test.go +++ b/ledger/apply/mockBalances_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/apply/payment.go b/ledger/apply/payment.go index c86f791e5a..ad8f128e95 100644 --- a/ledger/apply/payment.go +++ b/ledger/apply/payment.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/apply/payment_test.go b/ledger/apply/payment_test.go index 6f20dd9a51..db82869e23 100644 --- a/ledger/apply/payment_test.go +++ b/ledger/apply/payment_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/apply/stateproof.go b/ledger/apply/stateproof.go index fca56c0f3a..dba21a7241 100644 --- a/ledger/apply/stateproof.go +++ b/ledger/apply/stateproof.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/apptxn_test.go b/ledger/apptxn_test.go index 194c6e84ae..a08a3b74b2 100644 --- a/ledger/apptxn_test.go +++ b/ledger/apptxn_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/archival_test.go b/ledger/archival_test.go index 50d367d65e..c7f00b7d6a 100644 --- a/ledger/archival_test.go +++ b/ledger/archival_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -29,12 +29,11 @@ import ( "github.com/stretchr/testify/require" - "github.com/algorand/go-deadlock" - "github.com/algorand/go-algorand/agreement" "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/basics" + basics_testing "github.com/algorand/go-algorand/data/basics/testing" "github.com/algorand/go-algorand/data/bookkeeping" "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/data/transactions/logic" @@ -111,8 +110,8 @@ func getInitState() (genesisInitState ledgercore.InitState) { blk.FeeSink = testSinkAddr accts := make(map[basics.Address]basics.AccountData) - accts[testPoolAddr] = basics.MakeAccountData(basics.NotParticipating, basics.MicroAlgos{Raw: 1234567890}) - accts[testSinkAddr] = basics.MakeAccountData(basics.NotParticipating, basics.MicroAlgos{Raw: 1234567890}) + accts[testPoolAddr] = basics_testing.MakeAccountData(basics.NotParticipating, basics.MicroAlgos{Raw: 1234567890}) + accts[testSinkAddr] = basics_testing.MakeAccountData(basics.NotParticipating, basics.MicroAlgos{Raw: 1234567890}) genesisInitState.Accounts = accts genesisInitState.Block = blk @@ -191,13 +190,6 @@ func TestArchivalRestart(t *testing.T) { // Start in archival mode, add 2K blocks, restart, ensure all blocks are there - // disable deadlock checking code - deadlockDisable := deadlock.Opts.Disable - deadlock.Opts.Disable = true - defer func() { - deadlock.Opts.Disable = deadlockDisable - }() - dbName := fmt.Sprintf("%s.%d", t.Name(), crypto.RandUint64()) dbPrefix := filepath.Join(t.TempDir(), dbName) @@ -338,13 +330,6 @@ func TestArchivalCreatables(t *testing.T) { // restart, ensure all assets are there in index unless they were // deleted - // disable deadlock checking code - deadlockDisable := deadlock.Opts.Disable - deadlock.Opts.Disable = true - defer func() { - deadlock.Opts.Disable = deadlockDisable - }() - dbName := fmt.Sprintf("%s.%d", t.Name(), crypto.RandUint64()) dbPrefix := filepath.Join(t.TempDir(), dbName) @@ -377,7 +362,7 @@ func TestArchivalCreatables(t *testing.T) { _, err := rand.Read(creator[:]) require.NoError(t, err) creators = append(creators, creator) - genesisInitState.Accounts[creator] = basics.MakeAccountData(basics.Offline, basics.MicroAlgos{Raw: 1234567890}) + genesisInitState.Accounts[creator] = basics_testing.MakeAccountData(basics.Offline, basics.MicroAlgos{Raw: 1234567890}) } // open ledger @@ -690,11 +675,6 @@ func TestArchivalFromNonArchival(t *testing.T) { partitiontest.PartitionTest(t) // Start in non-archival mode, add 2K blocks, restart in archival mode ensure only genesis block is there - deadlockDisable := deadlock.Opts.Disable - deadlock.Opts.Disable = true - defer func() { - deadlock.Opts.Disable = deadlockDisable - }() dbName := fmt.Sprintf("%s.%d", t.Name(), crypto.RandUint64()) dbPrefix := filepath.Join(t.TempDir(), dbName) @@ -712,7 +692,7 @@ func TestArchivalFromNonArchival(t *testing.T) { addr := basics.Address{} _, err := rand.Read(addr[:]) require.NoError(t, err) - br := basics.BalanceRecord{AccountData: basics.MakeAccountData(basics.Offline, basics.MicroAlgos{Raw: 1234567890}), Addr: addr} + br := basics.BalanceRecord{AccountData: basics_testing.MakeAccountData(basics.Offline, basics.MicroAlgos{Raw: 1234567890}), Addr: addr} genesisInitState.Accounts[addr] = br.AccountData balanceRecords = append(balanceRecords, br) } diff --git a/ledger/blockHeaderCache.go b/ledger/blockHeaderCache.go index 5e2be47d29..b0f27f78ec 100644 --- a/ledger/blockHeaderCache.go +++ b/ledger/blockHeaderCache.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/blockHeaderCache_test.go b/ledger/blockHeaderCache_test.go index a5fac5ae61..6728e71c65 100644 --- a/ledger/blockHeaderCache_test.go +++ b/ledger/blockHeaderCache_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/blockqueue.go b/ledger/blockqueue.go index 4dfa1ae2cc..a72c1d8cba 100644 --- a/ledger/blockqueue.go +++ b/ledger/blockqueue.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -52,10 +52,29 @@ type blockQueue struct { closed chan struct{} } -func bqInit(l *Ledger) (*blockQueue, error) { +func newBlockQueue(l *Ledger) (*blockQueue, error) { bq := &blockQueue{} bq.cond = sync.NewCond(&bq.mu) bq.l = l + return bq, nil +} + +func (bq *blockQueue) start() error { + bq.mu.Lock() + defer bq.mu.Unlock() + + if bq.running { + // this should be harmless, but it should also be impossible + bq.l.log.Warn("blockQueue.start() already started") + return nil + } + if bq.closed != nil { + // a previus close() is still waiting on a previous syncer() to finish + oldsyncer := bq.closed + bq.mu.Unlock() + <-oldsyncer + bq.mu.Lock() + } bq.running = true bq.closed = make(chan struct{}) ledgerBlockqInitCount.Inc(nil) @@ -67,33 +86,32 @@ func bqInit(l *Ledger) (*blockQueue, error) { }) ledgerBlockqInitMicros.AddMicrosecondsSince(start, nil) if err != nil { - return nil, err + return err } go bq.syncer() - return bq, nil + return nil } -func (bq *blockQueue) close() { +func (bq *blockQueue) stop() { bq.mu.Lock() - defer func() { - bq.mu.Unlock() - // we want to block here until the sync go routine is done. - // it's not (just) for the sake of a complete cleanup, but rather - // to ensure that the sync goroutine isn't busy in a notifyCommit - // call which might be blocked inside one of the trackers. - <-bq.closed - }() - + closechan := bq.closed if bq.running { bq.running = false bq.cond.Broadcast() } - + bq.mu.Unlock() + + // we want to block here until the sync go routine is done. + // it's not (just) for the sake of a complete cleanup, but rather + // to ensure that the sync goroutine isn't busy in a notifyCommit + // call which might be blocked inside one of the trackers. + if closechan != nil { + <-closechan + } } func (bq *blockQueue) syncer() { - defer close(bq.closed) bq.mu.Lock() for { for bq.running && len(bq.q) == 0 { @@ -101,6 +119,8 @@ func (bq *blockQueue) syncer() { } if !bq.running { + close(bq.closed) + bq.closed = nil bq.mu.Unlock() return } diff --git a/ledger/blockqueue_test.go b/ledger/blockqueue_test.go index 90aa7b4fee..2d702cfb90 100644 --- a/ledger/blockqueue_test.go +++ b/ledger/blockqueue_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/boxtxn_test.go b/ledger/boxtxn_test.go index 92f1886329..f9099a4f91 100644 --- a/ledger/boxtxn_test.go +++ b/ledger/boxtxn_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/bulletin.go b/ledger/bulletin.go index 1e158aa806..5968c7f4f0 100644 --- a/ledger/bulletin.go +++ b/ledger/bulletin.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -81,7 +81,10 @@ func (b *bulletin) Wait(round basics.Round) chan struct{} { } func (b *bulletin) loadFromDisk(l ledgerForTracker, _ basics.Round) error { - b.pendingNotificationRequests = make(map[basics.Round]notifier) + // We want to keep existing notification requests in memory if this flow is triggered by reloadLedger. + if b.pendingNotificationRequests == nil { + b.pendingNotificationRequests = make(map[basics.Round]notifier) + } b.latestRound = l.Latest() return nil } diff --git a/ledger/bulletin_test.go b/ledger/bulletin_test.go index a3d5ae1f90..5a6f6bb833 100644 --- a/ledger/bulletin_test.go +++ b/ledger/bulletin_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -17,9 +17,10 @@ package ledger import ( - "github.com/algorand/go-algorand/test/partitiontest" "testing" "time" + + "github.com/algorand/go-algorand/test/partitiontest" ) const epsilon = 5 * time.Millisecond diff --git a/ledger/catchpointfileheader.go b/ledger/catchpointfileheader.go index 3b46002704..21cd6dbd59 100644 --- a/ledger/catchpointfileheader.go +++ b/ledger/catchpointfileheader.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/catchpointtracker.go b/ledger/catchpointtracker.go index 2867b285c7..6968f580a0 100644 --- a/ledger/catchpointtracker.go +++ b/ledger/catchpointtracker.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -1360,7 +1360,7 @@ func (ct *catchpointTracker) initializeHashes(ctx context.Context, tx *sql.Tx, r if rootHash.IsZero() { ct.log.Infof("initializeHashes rebuilding merkle trie for round %d", rnd) - accountBuilderIt := makeOrderedAccountsIter(tx, trieRebuildAccountChunkSize) + accountBuilderIt := store.MakeOrderedAccountsIter(tx, trieRebuildAccountChunkSize) defer accountBuilderIt.Close(ctx) startTrieBuildTime := time.Now() trieHashCount := 0 @@ -1380,18 +1380,18 @@ func (ct *catchpointTracker) initializeHashes(ctx context.Context, tx *sql.Tx, r trieHashCount += len(accts) pendingTrieHashes += len(accts) for _, acct := range accts { - added, err := trie.Add(acct.digest) + added, err := trie.Add(acct.Digest) if err != nil { return fmt.Errorf("initializeHashes was unable to add acct to trie: %v", err) } if !added { // we need to translate the "addrid" into actual account address so that // we can report the failure. - addr, err := arw.LookupAccountAddressFromAddressID(ctx, acct.addrid) + addr, err := arw.LookupAccountAddressFromAddressID(ctx, acct.Addrid) if err != nil { - ct.log.Warnf("initializeHashes attempted to add duplicate acct hash '%s' to merkle trie for account id %d : %v", hex.EncodeToString(acct.digest), acct.addrid, err) + ct.log.Warnf("initializeHashes attempted to add duplicate acct hash '%s' to merkle trie for account id %d : %v", hex.EncodeToString(acct.Digest), acct.Addrid, err) } else { - ct.log.Warnf("initializeHashes attempted to add duplicate acct hash '%s' to merkle trie for account %v", hex.EncodeToString(acct.digest), addr) + ct.log.Warnf("initializeHashes attempted to add duplicate acct hash '%s' to merkle trie for account %v", hex.EncodeToString(acct.Digest), addr) } } } @@ -1431,15 +1431,13 @@ func (ct *catchpointTracker) initializeHashes(ctx context.Context, tx *sql.Tx, r // Now add the kvstore hashes pendingTrieHashes = 0 - kvs, err := tx.QueryContext(ctx, "SELECT key, value FROM kvstore") + kvs, err := store.MakeKVsIter(ctx, tx) if err != nil { return err } defer kvs.Close() for kvs.Next() { - var k []byte - var v []byte - err := kvs.Scan(&k, &v) + k, v, err := kvs.KeyValue() if err != nil { return err } diff --git a/ledger/catchpointtracker_test.go b/ledger/catchpointtracker_test.go index 82a34888ec..33cfe9baed 100644 --- a/ledger/catchpointtracker_test.go +++ b/ledger/catchpointtracker_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/catchpointwriter.go b/ledger/catchpointwriter.go index e204a8ae74..839a2f24c0 100644 --- a/ledger/catchpointwriter.go +++ b/ledger/catchpointwriter.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -25,9 +25,7 @@ import ( "os" "path/filepath" - "github.com/algorand/msgp/msgp" - - "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/ledger/encoded" "github.com/algorand/go-algorand/ledger/store" "github.com/algorand/go-algorand/protocol" ) @@ -41,11 +39,6 @@ const ( // 100,000 resources * 20KB/resource => roughly max 2GB per chunk if all of them are max'ed out apps. // In reality most entries are asset holdings, and they are very small. ResourcesPerCatchpointFileChunk = 100_000 - - // resourcesPerCatchpointFileChunkBackwardCompatible is the old value for ResourcesPerCatchpointFileChunk. - // Size of a single resource entry was underestimated to 300 bytes that holds only for assets and not for apps. - // It is safe to remove after April, 2023 since we are only supporting catchpoint that are 6 months old. - resourcesPerCatchpointFileChunkBackwardCompatible = 300_000 ) // catchpointWriter is the struct managing the persistence of accounts data into the catchpoint file. @@ -65,65 +58,34 @@ type catchpointWriter struct { chunkNum uint64 writtenBytes int64 biggestChunkLen uint64 - accountsIterator encodedAccountsBatchIter + accountsIterator accountsBatchIter maxResourcesPerChunk int accountsDone bool - kvRows *sql.Rows + kvRows kvIter } -type encodedBalanceRecordV5 struct { - _struct struct{} `codec:",omitempty,omitemptyarray"` - - Address basics.Address `codec:"pk,allocbound=crypto.DigestSize"` - AccountData msgp.Raw `codec:"ad"` // encoding of basics.AccountData +type kvIter interface { + Next() bool + KeyValue() ([]byte, []byte, error) + Close() } -type catchpointFileBalancesChunkV5 struct { - _struct struct{} `codec:",omitempty,omitemptyarray"` - Balances []encodedBalanceRecordV5 `codec:"bl,allocbound=BalancesPerCatchpointFileChunk"` +type accountsBatchIter interface { + Next(ctx context.Context, tx *sql.Tx, accountCount int, resourceCount int) ([]encoded.BalanceRecordV6, uint64, error) + Close() } -// SortUint64 re-export this sort, which is implemented in basics, and being used by the msgp when -// encoding the resources map below. -type SortUint64 = basics.SortUint64 - -type encodedBalanceRecordV6 struct { - _struct struct{} `codec:",omitempty,omitemptyarray"` - - Address basics.Address `codec:"a,allocbound=crypto.DigestSize"` - AccountData msgp.Raw `codec:"b"` // encoding of baseAccountData - Resources map[uint64]msgp.Raw `codec:"c,allocbound=resourcesPerCatchpointFileChunkBackwardCompatible"` // map of resourcesData - - // flag indicating whether there are more records for the same account coming up - ExpectingMoreEntries bool `codec:"e"` -} - -// Adjust these to be big enough for boxes, but not directly tied to box values. -const ( - // For boxes: "bx:<8 bytes><64 byte name>" - encodedKVRecordV6MaxKeyLength = 128 - - // For boxes: MaxBoxSize - encodedKVRecordV6MaxValueLength = 32768 - - // MaxEncodedKVDataSize is the max size of serialized KV entry, checked with TestEncodedKVDataSize. - // Exact value is 32906 that is 10 bytes more than 32768 + 128 - MaxEncodedKVDataSize = 33000 -) - -type encodedKVRecordV6 struct { - _struct struct{} `codec:",omitempty,omitemptyarray"` - - Key []byte `codec:"k,allocbound=encodedKVRecordV6MaxKeyLength"` - Value []byte `codec:"v,allocbound=encodedKVRecordV6MaxValueLength"` +type catchpointFileBalancesChunkV5 struct { + _struct struct{} `codec:",omitempty,omitemptyarray"` + Balances []encoded.BalanceRecordV5 `codec:"bl,allocbound=BalancesPerCatchpointFileChunk"` } type catchpointFileChunkV6 struct { _struct struct{} `codec:",omitempty,omitemptyarray"` - Balances []encodedBalanceRecordV6 `codec:"bl,allocbound=BalancesPerCatchpointFileChunk"` + Balances []encoded.BalanceRecordV6 `codec:"bl,allocbound=BalancesPerCatchpointFileChunk"` numAccounts uint64 - KVs []encodedKVRecordV6 `codec:"kv,allocbound=BalancesPerCatchpointFileChunk"` + KVs []encoded.KVRecordV6 `codec:"kv,allocbound=BalancesPerCatchpointFileChunk"` } func (chunk catchpointFileChunkV6) empty() bool { @@ -166,6 +128,7 @@ func makeCatchpointWriter(ctx context.Context, filePath string, tx *sql.Tx, maxR file: file, compressor: compressor, tar: tar, + accountsIterator: store.MakeEncodedAccoutsBatchIter(), maxResourcesPerChunk: maxResourcesPerChunk, } return res, nil @@ -323,22 +286,20 @@ func (cw *catchpointWriter) readDatabaseStep(ctx context.Context, tx *sql.Tx) er // Create the *Rows iterator JIT if cw.kvRows == nil { - rows, err := tx.QueryContext(ctx, "SELECT key, value FROM kvstore") + rows, err := store.MakeKVsIter(ctx, tx) if err != nil { return err } cw.kvRows = rows } - kvrs := make([]encodedKVRecordV6, 0, BalancesPerCatchpointFileChunk) + kvrs := make([]encoded.KVRecordV6, 0, BalancesPerCatchpointFileChunk) for cw.kvRows.Next() { - var k []byte - var v []byte - err := cw.kvRows.Scan(&k, &v) + k, v, err := cw.kvRows.KeyValue() if err != nil { return err } - kvrs = append(kvrs, encodedKVRecordV6{Key: k, Value: v}) + kvrs = append(kvrs, encoded.KVRecordV6{Key: k, Value: v}) if len(kvrs) == BalancesPerCatchpointFileChunk { break } diff --git a/ledger/catchpointwriter_test.go b/ledger/catchpointwriter_test.go index 0bdac4a2a1..478df2bb25 100644 --- a/ledger/catchpointwriter_test.go +++ b/ledger/catchpointwriter_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -24,7 +24,6 @@ import ( "database/sql" "fmt" "io" - "math" "os" "path/filepath" "strconv" @@ -33,13 +32,14 @@ import ( "github.com/stretchr/testify/require" + "github.com/algorand/avm-abi/apps" "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/crypto/merkletrie" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/transactions" - "github.com/algorand/go-algorand/data/transactions/logic" "github.com/algorand/go-algorand/data/txntest" + "github.com/algorand/go-algorand/ledger/encoded" "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/ledger/store" ledgertesting "github.com/algorand/go-algorand/ledger/testing" @@ -71,19 +71,19 @@ func TestCatchpointFileBalancesChunkEncoding(t *testing.T) { for i := uint64(0); i < numResources; i++ { resources[i] = encodedResourceData } - balance := encodedBalanceRecordV6{ + balance := encoded.BalanceRecordV6{ Address: ledgertesting.RandomAddress(), AccountData: encodedBaseAD, Resources: resources, } - balances := make([]encodedBalanceRecordV6, numChunkEntries) - kv := encodedKVRecordV6{ - Key: make([]byte, encodedKVRecordV6MaxKeyLength), - Value: make([]byte, encodedKVRecordV6MaxValueLength), + balances := make([]encoded.BalanceRecordV6, numChunkEntries) + kv := encoded.KVRecordV6{ + Key: make([]byte, encoded.KVRecordV6MaxKeyLength), + Value: make([]byte, encoded.KVRecordV6MaxValueLength), } crypto.RandBytes(kv.Key[:]) crypto.RandBytes(kv.Value[:]) - kvs := make([]encodedKVRecordV6, numChunkEntries) + kvs := make([]encoded.KVRecordV6, numChunkEntries) for i := 0; i < numChunkEntries; i++ { balances[i] = balance @@ -493,7 +493,7 @@ func TestFullCatchpointWriterOverflowAccounts(t *testing.T) { require.NoError(t, err) require.Zero(t, h) - iter := makeOrderedAccountsIter(tx, trieRebuildAccountChunkSize) + iter := store.MakeOrderedAccountsIter(tx, trieRebuildAccountChunkSize) defer iter.Close(ctx) for { accts, _, err := iter.Next(ctx) @@ -507,7 +507,7 @@ func TestFullCatchpointWriterOverflowAccounts(t *testing.T) { if len(accts) > 0 { for _, acct := range accts { - added, err := trie.Add(acct.digest) + added, err := trie.Add(acct.Digest) require.NoError(t, err) require.True(t, added) } @@ -779,7 +779,7 @@ func TestCatchpointAfterTxns(t *testing.T) { values, err = l.LookupKeysByPrefix(l.Latest(), "bx:", 10) require.NoError(t, err) require.Len(t, values, 1) - v, err := l.LookupKv(l.Latest(), logic.MakeBoxKey(boxApp, "xxx")) + v, err := l.LookupKv(l.Latest(), apps.MakeBoxKey(uint64(boxApp), "xxx")) require.NoError(t, err) require.Equal(t, strings.Repeat("\x00", 24), string(v)) @@ -869,41 +869,7 @@ func TestCatchpointAfterBoxTxns(t *testing.T) { values, err := l.LookupKeysByPrefix(l.Latest(), "bx:", 10) require.NoError(t, err) require.Len(t, values, 1) - v, err := l.LookupKv(l.Latest(), logic.MakeBoxKey(boxApp, "xxx")) + v, err := l.LookupKv(l.Latest(), apps.MakeBoxKey(uint64(boxApp), "xxx")) require.NoError(t, err) require.Equal(t, strings.Repeat("f", 24), string(v)) } - -func TestEncodedKVRecordV6Allocbounds(t *testing.T) { - partitiontest.PartitionTest(t) - t.Parallel() - - for version, params := range config.Consensus { - require.GreaterOrEqualf(t, uint64(encodedKVRecordV6MaxValueLength), params.MaxBoxSize, "Allocbound constant no longer valid as of consensus version %s", version) - longestPossibleBoxName := string(make([]byte, params.MaxAppKeyLen)) - longestPossibleKey := logic.MakeBoxKey(basics.AppIndex(math.MaxUint64), longestPossibleBoxName) - require.GreaterOrEqualf(t, encodedKVRecordV6MaxValueLength, len(longestPossibleKey), "Allocbound constant no longer valid as of consensus version %s", version) - } -} - -func TestEncodedKVDataSize(t *testing.T) { - partitiontest.PartitionTest(t) - t.Parallel() - - currentConsensusParams := config.Consensus[protocol.ConsensusCurrentVersion] - - require.GreaterOrEqual(t, encodedKVRecordV6MaxKeyLength, currentConsensusParams.MaxAppKeyLen) - require.GreaterOrEqual(t, uint64(encodedKVRecordV6MaxValueLength), currentConsensusParams.MaxBoxSize) - - kvEntry := encodedKVRecordV6{ - Key: make([]byte, encodedKVRecordV6MaxKeyLength), - Value: make([]byte, encodedKVRecordV6MaxValueLength), - } - - crypto.RandBytes(kvEntry.Key[:]) - crypto.RandBytes(kvEntry.Value[:]) - - encoded := kvEntry.MarshalMsg(nil) - require.GreaterOrEqual(t, MaxEncodedKVDataSize, len(encoded)) - -} diff --git a/ledger/catchupaccessor.go b/ledger/catchupaccessor.go index cbe18ba243..41e14a167e 100644 --- a/ledger/catchupaccessor.go +++ b/ledger/catchupaccessor.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -31,6 +31,7 @@ import ( "github.com/algorand/go-algorand/crypto/merkletrie" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/bookkeeping" + "github.com/algorand/go-algorand/ledger/encoded" "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/ledger/store" "github.com/algorand/go-algorand/ledger/store/blockdb" @@ -97,7 +98,7 @@ type stagingWriter interface { writeBalances(context.Context, []store.NormalizedAccountBalance) error writeCreatables(context.Context, []store.NormalizedAccountBalance) error writeHashes(context.Context, []store.NormalizedAccountBalance) error - writeKVs(context.Context, []encodedKVRecordV6) error + writeKVs(context.Context, []encoded.KVRecordV6) error isShared() bool } @@ -112,7 +113,7 @@ func (w *stagingWriterImpl) writeBalances(ctx context.Context, balances []store. }) } -func (w *stagingWriterImpl) writeKVs(ctx context.Context, kvrs []encodedKVRecordV6) error { +func (w *stagingWriterImpl) writeKVs(ctx context.Context, kvrs []encoded.KVRecordV6) error { return w.wdb.Atomic(func(ctx context.Context, tx *sql.Tx) (err error) { crw := store.NewCatchpointSQLReaderWriter(tx) @@ -166,6 +167,14 @@ type catchpointCatchupAccessorImpl struct { nextExpectedAccount basics.Address } +// catchpointAccountResourceCounter keeps track of the resources processed for the current account +type catchpointAccountResourceCounter struct { + totalAppParams uint64 + totalAppLocalStates uint64 + totalAssetParams uint64 + totalAssets uint64 +} + // CatchpointCatchupState is the state of the current catchpoint catchup process type CatchpointCatchupState int32 @@ -388,7 +397,7 @@ func (c *catchpointCatchupAccessorImpl) processStagingBalances(ctx context.Conte var normalizedAccountBalances []store.NormalizedAccountBalance var expectingMoreEntries []bool - var chunkKVs []encodedKVRecordV6 + var chunkKVs []encoded.KVRecordV6 switch progress.Version { default: @@ -659,7 +668,7 @@ func (c *catchpointCatchupAccessorImpl) BuildMerkleTrie(ctx context.Context, pro defer close(writerQueue) err := rdb.Atomic(func(transactionCtx context.Context, tx *sql.Tx) (err error) { - it := makeCatchpointPendingHashesIterator(trieRebuildAccountChunkSize, tx) + it := store.MakeCatchpointPendingHashesIterator(trieRebuildAccountChunkSize, tx) var hashes [][]byte for { hashes, err = it.Next(transactionCtx) diff --git a/ledger/catchupaccessor_test.go b/ledger/catchupaccessor_test.go index d79d636e3c..bb4d8aa163 100644 --- a/ledger/catchupaccessor_test.go +++ b/ledger/catchupaccessor_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -32,6 +32,7 @@ import ( "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/bookkeeping" + "github.com/algorand/go-algorand/ledger/encoded" "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/ledger/store" ledgertesting "github.com/algorand/go-algorand/ledger/testing" @@ -57,9 +58,9 @@ func createTestingEncodedChunks(accountsCount uint64) (encodedAccountChunks [][] last64KIndex = len(encodedAccountChunks) } var chunk catchpointFileChunkV6 - chunk.Balances = make([]encodedBalanceRecordV6, chunkSize) + chunk.Balances = make([]encoded.BalanceRecordV6, chunkSize) for i := uint64(0); i < chunkSize; i++ { - var randomAccount encodedBalanceRecordV6 + var randomAccount encoded.BalanceRecordV6 accountData := store.BaseAccountData{} accountData.MicroAlgos.Raw = crypto.RandUint63() randomAccount.AccountData = protocol.Encode(&accountData) @@ -406,8 +407,8 @@ func TestCatchupAccessorResourceCountMismatch(t *testing.T) { require.NoError(t, err) var balances catchpointFileChunkV6 - balances.Balances = make([]encodedBalanceRecordV6, 1) - var randomAccount encodedBalanceRecordV6 + balances.Balances = make([]encoded.BalanceRecordV6, 1) + var randomAccount encoded.BalanceRecordV6 accountData := store.BaseAccountData{} accountData.MicroAlgos.Raw = crypto.RandUint63() accountData.TotalAppParams = 1 @@ -435,7 +436,7 @@ func (w *testStagingWriter) writeCreatables(ctx context.Context, balances []stor return nil } -func (w *testStagingWriter) writeKVs(ctx context.Context, kvrs []encodedKVRecordV6) error { +func (w *testStagingWriter) writeKVs(ctx context.Context, kvrs []encoded.KVRecordV6) error { return nil } @@ -485,8 +486,8 @@ func TestCatchupAccessorProcessStagingBalances(t *testing.T) { return accountData } - encodedBalanceRecordFromBase := func(addr basics.Address, base store.BaseAccountData, resources map[uint64]msgp.Raw, more bool) encodedBalanceRecordV6 { - ebr := encodedBalanceRecordV6{ + encodedBalanceRecordFromBase := func(addr basics.Address, base store.BaseAccountData, resources map[uint64]msgp.Raw, more bool) encoded.BalanceRecordV6 { + ebr := encoded.BalanceRecordV6{ Address: addr, AccountData: protocol.Encode(&base), Resources: resources, @@ -530,14 +531,14 @@ func TestCatchupAccessorProcessStagingBalances(t *testing.T) { // make chunks chunks := []catchpointFileChunkV6{ { - Balances: []encodedBalanceRecordV6{ + Balances: []encoded.BalanceRecordV6{ encodedBalanceRecordFromBase(ledgertesting.RandomAddress(), acctA, nil, false), encodedBalanceRecordFromBase(ledgertesting.RandomAddress(), acctB, nil, false), encodedBalanceRecordFromBase(addrX, acctX, acctXRes1, true), }, }, { - Balances: []encodedBalanceRecordV6{ + Balances: []encoded.BalanceRecordV6{ encodedBalanceRecordFromBase(addrX, acctX, acctXRes2, false), encodedBalanceRecordFromBase(ledgertesting.RandomAddress(), acctC, nil, false), encodedBalanceRecordFromBase(ledgertesting.RandomAddress(), acctD, nil, false), diff --git a/ledger/double_test.go b/ledger/double_test.go index 40e39b88c2..9b4ca20f11 100644 --- a/ledger/double_test.go +++ b/ledger/double_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -152,8 +152,8 @@ func (dl *DoubleLedger) fundedApp(sender basics.Address, amount uint64, source s } func (dl *DoubleLedger) reloadLedgers() { - require.NoError(dl.t, dl.generator.ReloadLedger()) - require.NoError(dl.t, dl.validator.ReloadLedger()) + require.NoError(dl.t, dl.generator.reloadLedger()) + require.NoError(dl.t, dl.validator.reloadLedger()) } func checkBlock(t *testing.T, checkLedger *Ledger, vb *ledgercore.ValidatedBlock) { diff --git a/ledger/encoded/msgp_gen.go b/ledger/encoded/msgp_gen.go new file mode 100644 index 0000000000..189a6b73bf --- /dev/null +++ b/ledger/encoded/msgp_gen.go @@ -0,0 +1,587 @@ +package encoded + +// Code generated by github.com/algorand/msgp DO NOT EDIT. + +import ( + "sort" + + "github.com/algorand/msgp/msgp" +) + +// The following msgp objects are implemented in this file: +// BalanceRecordV5 +// |-----> (*) MarshalMsg +// |-----> (*) CanMarshalMsg +// |-----> (*) UnmarshalMsg +// |-----> (*) CanUnmarshalMsg +// |-----> (*) Msgsize +// |-----> (*) MsgIsZero +// +// BalanceRecordV6 +// |-----> (*) MarshalMsg +// |-----> (*) CanMarshalMsg +// |-----> (*) UnmarshalMsg +// |-----> (*) CanUnmarshalMsg +// |-----> (*) Msgsize +// |-----> (*) MsgIsZero +// +// KVRecordV6 +// |-----> (*) MarshalMsg +// |-----> (*) CanMarshalMsg +// |-----> (*) UnmarshalMsg +// |-----> (*) CanUnmarshalMsg +// |-----> (*) Msgsize +// |-----> (*) MsgIsZero +// + +// MarshalMsg implements msgp.Marshaler +func (z *BalanceRecordV5) MarshalMsg(b []byte) (o []byte) { + o = msgp.Require(b, z.Msgsize()) + // omitempty: check for empty values + zb0001Len := uint32(2) + var zb0001Mask uint8 /* 3 bits */ + if (*z).AccountData.MsgIsZero() { + zb0001Len-- + zb0001Mask |= 0x2 + } + if (*z).Address.MsgIsZero() { + zb0001Len-- + zb0001Mask |= 0x4 + } + // variable map header, size zb0001Len + o = append(o, 0x80|uint8(zb0001Len)) + if zb0001Len != 0 { + if (zb0001Mask & 0x2) == 0 { // if not empty + // string "ad" + o = append(o, 0xa2, 0x61, 0x64) + o = (*z).AccountData.MarshalMsg(o) + } + if (zb0001Mask & 0x4) == 0 { // if not empty + // string "pk" + o = append(o, 0xa2, 0x70, 0x6b) + o = (*z).Address.MarshalMsg(o) + } + } + return +} + +func (_ *BalanceRecordV5) CanMarshalMsg(z interface{}) bool { + _, ok := (z).(*BalanceRecordV5) + return ok +} + +// UnmarshalMsg implements msgp.Unmarshaler +func (z *BalanceRecordV5) UnmarshalMsg(bts []byte) (o []byte, err error) { + var field []byte + _ = field + var zb0001 int + var zb0002 bool + zb0001, zb0002, bts, err = msgp.ReadMapHeaderBytes(bts) + if _, ok := err.(msgp.TypeError); ok { + zb0001, zb0002, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + if zb0001 > 0 { + zb0001-- + bts, err = (*z).Address.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Address") + return + } + } + if zb0001 > 0 { + zb0001-- + bts, err = (*z).AccountData.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "AccountData") + return + } + } + if zb0001 > 0 { + err = msgp.ErrTooManyArrayFields(zb0001) + if err != nil { + err = msgp.WrapError(err, "struct-from-array") + return + } + } + } else { + if err != nil { + err = msgp.WrapError(err) + return + } + if zb0002 { + (*z) = BalanceRecordV5{} + } + for zb0001 > 0 { + zb0001-- + field, bts, err = msgp.ReadMapKeyZC(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + switch string(field) { + case "pk": + bts, err = (*z).Address.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "Address") + return + } + case "ad": + bts, err = (*z).AccountData.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "AccountData") + return + } + default: + err = msgp.ErrNoField(string(field)) + if err != nil { + err = msgp.WrapError(err) + return + } + } + } + } + o = bts + return +} + +func (_ *BalanceRecordV5) CanUnmarshalMsg(z interface{}) bool { + _, ok := (z).(*BalanceRecordV5) + return ok +} + +// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message +func (z *BalanceRecordV5) Msgsize() (s int) { + s = 1 + 3 + (*z).Address.Msgsize() + 3 + (*z).AccountData.Msgsize() + return +} + +// MsgIsZero returns whether this is a zero value +func (z *BalanceRecordV5) MsgIsZero() bool { + return ((*z).Address.MsgIsZero()) && ((*z).AccountData.MsgIsZero()) +} + +// MarshalMsg implements msgp.Marshaler +func (z *BalanceRecordV6) MarshalMsg(b []byte) (o []byte) { + o = msgp.Require(b, z.Msgsize()) + // omitempty: check for empty values + zb0003Len := uint32(4) + var zb0003Mask uint8 /* 5 bits */ + if (*z).Address.MsgIsZero() { + zb0003Len-- + zb0003Mask |= 0x2 + } + if (*z).AccountData.MsgIsZero() { + zb0003Len-- + zb0003Mask |= 0x4 + } + if len((*z).Resources) == 0 { + zb0003Len-- + zb0003Mask |= 0x8 + } + if (*z).ExpectingMoreEntries == false { + zb0003Len-- + zb0003Mask |= 0x10 + } + // variable map header, size zb0003Len + o = append(o, 0x80|uint8(zb0003Len)) + if zb0003Len != 0 { + if (zb0003Mask & 0x2) == 0 { // if not empty + // string "a" + o = append(o, 0xa1, 0x61) + o = (*z).Address.MarshalMsg(o) + } + if (zb0003Mask & 0x4) == 0 { // if not empty + // string "b" + o = append(o, 0xa1, 0x62) + o = (*z).AccountData.MarshalMsg(o) + } + if (zb0003Mask & 0x8) == 0 { // if not empty + // string "c" + o = append(o, 0xa1, 0x63) + if (*z).Resources == nil { + o = msgp.AppendNil(o) + } else { + o = msgp.AppendMapHeader(o, uint32(len((*z).Resources))) + } + zb0001_keys := make([]uint64, 0, len((*z).Resources)) + for zb0001 := range (*z).Resources { + zb0001_keys = append(zb0001_keys, zb0001) + } + sort.Sort(SortUint64(zb0001_keys)) + for _, zb0001 := range zb0001_keys { + zb0002 := (*z).Resources[zb0001] + _ = zb0002 + o = msgp.AppendUint64(o, zb0001) + o = zb0002.MarshalMsg(o) + } + } + if (zb0003Mask & 0x10) == 0 { // if not empty + // string "e" + o = append(o, 0xa1, 0x65) + o = msgp.AppendBool(o, (*z).ExpectingMoreEntries) + } + } + return +} + +func (_ *BalanceRecordV6) CanMarshalMsg(z interface{}) bool { + _, ok := (z).(*BalanceRecordV6) + return ok +} + +// UnmarshalMsg implements msgp.Unmarshaler +func (z *BalanceRecordV6) UnmarshalMsg(bts []byte) (o []byte, err error) { + var field []byte + _ = field + var zb0003 int + var zb0004 bool + zb0003, zb0004, bts, err = msgp.ReadMapHeaderBytes(bts) + if _, ok := err.(msgp.TypeError); ok { + zb0003, zb0004, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + if zb0003 > 0 { + zb0003-- + bts, err = (*z).Address.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Address") + return + } + } + if zb0003 > 0 { + zb0003-- + bts, err = (*z).AccountData.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "AccountData") + return + } + } + if zb0003 > 0 { + zb0003-- + var zb0005 int + var zb0006 bool + zb0005, zb0006, bts, err = msgp.ReadMapHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Resources") + return + } + if zb0005 > resourcesPerCatchpointFileChunkBackwardCompatible { + err = msgp.ErrOverflow(uint64(zb0005), uint64(resourcesPerCatchpointFileChunkBackwardCompatible)) + err = msgp.WrapError(err, "struct-from-array", "Resources") + return + } + if zb0006 { + (*z).Resources = nil + } else if (*z).Resources == nil { + (*z).Resources = make(map[uint64]msgp.Raw, zb0005) + } + for zb0005 > 0 { + var zb0001 uint64 + var zb0002 msgp.Raw + zb0005-- + zb0001, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Resources") + return + } + bts, err = zb0002.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Resources", zb0001) + return + } + (*z).Resources[zb0001] = zb0002 + } + } + if zb0003 > 0 { + zb0003-- + (*z).ExpectingMoreEntries, bts, err = msgp.ReadBoolBytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "ExpectingMoreEntries") + return + } + } + if zb0003 > 0 { + err = msgp.ErrTooManyArrayFields(zb0003) + if err != nil { + err = msgp.WrapError(err, "struct-from-array") + return + } + } + } else { + if err != nil { + err = msgp.WrapError(err) + return + } + if zb0004 { + (*z) = BalanceRecordV6{} + } + for zb0003 > 0 { + zb0003-- + field, bts, err = msgp.ReadMapKeyZC(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + switch string(field) { + case "a": + bts, err = (*z).Address.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "Address") + return + } + case "b": + bts, err = (*z).AccountData.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "AccountData") + return + } + case "c": + var zb0007 int + var zb0008 bool + zb0007, zb0008, bts, err = msgp.ReadMapHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "Resources") + return + } + if zb0007 > resourcesPerCatchpointFileChunkBackwardCompatible { + err = msgp.ErrOverflow(uint64(zb0007), uint64(resourcesPerCatchpointFileChunkBackwardCompatible)) + err = msgp.WrapError(err, "Resources") + return + } + if zb0008 { + (*z).Resources = nil + } else if (*z).Resources == nil { + (*z).Resources = make(map[uint64]msgp.Raw, zb0007) + } + for zb0007 > 0 { + var zb0001 uint64 + var zb0002 msgp.Raw + zb0007-- + zb0001, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "Resources") + return + } + bts, err = zb0002.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "Resources", zb0001) + return + } + (*z).Resources[zb0001] = zb0002 + } + case "e": + (*z).ExpectingMoreEntries, bts, err = msgp.ReadBoolBytes(bts) + if err != nil { + err = msgp.WrapError(err, "ExpectingMoreEntries") + return + } + default: + err = msgp.ErrNoField(string(field)) + if err != nil { + err = msgp.WrapError(err) + return + } + } + } + } + o = bts + return +} + +func (_ *BalanceRecordV6) CanUnmarshalMsg(z interface{}) bool { + _, ok := (z).(*BalanceRecordV6) + return ok +} + +// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message +func (z *BalanceRecordV6) Msgsize() (s int) { + s = 1 + 2 + (*z).Address.Msgsize() + 2 + (*z).AccountData.Msgsize() + 2 + msgp.MapHeaderSize + if (*z).Resources != nil { + for zb0001, zb0002 := range (*z).Resources { + _ = zb0001 + _ = zb0002 + s += 0 + msgp.Uint64Size + zb0002.Msgsize() + } + } + s += 2 + msgp.BoolSize + return +} + +// MsgIsZero returns whether this is a zero value +func (z *BalanceRecordV6) MsgIsZero() bool { + return ((*z).Address.MsgIsZero()) && ((*z).AccountData.MsgIsZero()) && (len((*z).Resources) == 0) && ((*z).ExpectingMoreEntries == false) +} + +// MarshalMsg implements msgp.Marshaler +func (z *KVRecordV6) MarshalMsg(b []byte) (o []byte) { + o = msgp.Require(b, z.Msgsize()) + // omitempty: check for empty values + zb0001Len := uint32(2) + var zb0001Mask uint8 /* 3 bits */ + if len((*z).Key) == 0 { + zb0001Len-- + zb0001Mask |= 0x2 + } + if len((*z).Value) == 0 { + zb0001Len-- + zb0001Mask |= 0x4 + } + // variable map header, size zb0001Len + o = append(o, 0x80|uint8(zb0001Len)) + if zb0001Len != 0 { + if (zb0001Mask & 0x2) == 0 { // if not empty + // string "k" + o = append(o, 0xa1, 0x6b) + o = msgp.AppendBytes(o, (*z).Key) + } + if (zb0001Mask & 0x4) == 0 { // if not empty + // string "v" + o = append(o, 0xa1, 0x76) + o = msgp.AppendBytes(o, (*z).Value) + } + } + return +} + +func (_ *KVRecordV6) CanMarshalMsg(z interface{}) bool { + _, ok := (z).(*KVRecordV6) + return ok +} + +// UnmarshalMsg implements msgp.Unmarshaler +func (z *KVRecordV6) UnmarshalMsg(bts []byte) (o []byte, err error) { + var field []byte + _ = field + var zb0001 int + var zb0002 bool + zb0001, zb0002, bts, err = msgp.ReadMapHeaderBytes(bts) + if _, ok := err.(msgp.TypeError); ok { + zb0001, zb0002, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + if zb0001 > 0 { + zb0001-- + var zb0003 int + zb0003, err = msgp.ReadBytesBytesHeader(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Key") + return + } + if zb0003 > KVRecordV6MaxKeyLength { + err = msgp.ErrOverflow(uint64(zb0003), uint64(KVRecordV6MaxKeyLength)) + return + } + (*z).Key, bts, err = msgp.ReadBytesBytes(bts, (*z).Key) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Key") + return + } + } + if zb0001 > 0 { + zb0001-- + var zb0004 int + zb0004, err = msgp.ReadBytesBytesHeader(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Value") + return + } + if zb0004 > KVRecordV6MaxValueLength { + err = msgp.ErrOverflow(uint64(zb0004), uint64(KVRecordV6MaxValueLength)) + return + } + (*z).Value, bts, err = msgp.ReadBytesBytes(bts, (*z).Value) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Value") + return + } + } + if zb0001 > 0 { + err = msgp.ErrTooManyArrayFields(zb0001) + if err != nil { + err = msgp.WrapError(err, "struct-from-array") + return + } + } + } else { + if err != nil { + err = msgp.WrapError(err) + return + } + if zb0002 { + (*z) = KVRecordV6{} + } + for zb0001 > 0 { + zb0001-- + field, bts, err = msgp.ReadMapKeyZC(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + switch string(field) { + case "k": + var zb0005 int + zb0005, err = msgp.ReadBytesBytesHeader(bts) + if err != nil { + err = msgp.WrapError(err, "Key") + return + } + if zb0005 > KVRecordV6MaxKeyLength { + err = msgp.ErrOverflow(uint64(zb0005), uint64(KVRecordV6MaxKeyLength)) + return + } + (*z).Key, bts, err = msgp.ReadBytesBytes(bts, (*z).Key) + if err != nil { + err = msgp.WrapError(err, "Key") + return + } + case "v": + var zb0006 int + zb0006, err = msgp.ReadBytesBytesHeader(bts) + if err != nil { + err = msgp.WrapError(err, "Value") + return + } + if zb0006 > KVRecordV6MaxValueLength { + err = msgp.ErrOverflow(uint64(zb0006), uint64(KVRecordV6MaxValueLength)) + return + } + (*z).Value, bts, err = msgp.ReadBytesBytes(bts, (*z).Value) + if err != nil { + err = msgp.WrapError(err, "Value") + return + } + default: + err = msgp.ErrNoField(string(field)) + if err != nil { + err = msgp.WrapError(err) + return + } + } + } + } + o = bts + return +} + +func (_ *KVRecordV6) CanUnmarshalMsg(z interface{}) bool { + _, ok := (z).(*KVRecordV6) + return ok +} + +// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message +func (z *KVRecordV6) Msgsize() (s int) { + s = 1 + 2 + msgp.BytesPrefixSize + len((*z).Key) + 2 + msgp.BytesPrefixSize + len((*z).Value) + return +} + +// MsgIsZero returns whether this is a zero value +func (z *KVRecordV6) MsgIsZero() bool { + return (len((*z).Key) == 0) && (len((*z).Value) == 0) +} diff --git a/ledger/encoded/msgp_gen_test.go b/ledger/encoded/msgp_gen_test.go new file mode 100644 index 0000000000..415339c728 --- /dev/null +++ b/ledger/encoded/msgp_gen_test.go @@ -0,0 +1,195 @@ +//go:build !skip_msgp_testing +// +build !skip_msgp_testing + +package encoded + +// Code generated by github.com/algorand/msgp DO NOT EDIT. + +import ( + "testing" + + "github.com/algorand/msgp/msgp" + + "github.com/algorand/go-algorand/protocol" + "github.com/algorand/go-algorand/test/partitiontest" +) + +func TestMarshalUnmarshalBalanceRecordV5(t *testing.T) { + partitiontest.PartitionTest(t) + v := BalanceRecordV5{} + bts := v.MarshalMsg(nil) + left, err := v.UnmarshalMsg(bts) + if err != nil { + t.Fatal(err) + } + if len(left) > 0 { + t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left) + } + + left, err = msgp.Skip(bts) + if err != nil { + t.Fatal(err) + } + if len(left) > 0 { + t.Errorf("%d bytes left over after Skip(): %q", len(left), left) + } +} + +func TestRandomizedEncodingBalanceRecordV5(t *testing.T) { + protocol.RunEncodingTest(t, &BalanceRecordV5{}) +} + +func BenchmarkMarshalMsgBalanceRecordV5(b *testing.B) { + v := BalanceRecordV5{} + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + v.MarshalMsg(nil) + } +} + +func BenchmarkAppendMsgBalanceRecordV5(b *testing.B) { + v := BalanceRecordV5{} + bts := make([]byte, 0, v.Msgsize()) + bts = v.MarshalMsg(bts[0:0]) + b.SetBytes(int64(len(bts))) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + bts = v.MarshalMsg(bts[0:0]) + } +} + +func BenchmarkUnmarshalBalanceRecordV5(b *testing.B) { + v := BalanceRecordV5{} + bts := v.MarshalMsg(nil) + b.ReportAllocs() + b.SetBytes(int64(len(bts))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := v.UnmarshalMsg(bts) + if err != nil { + b.Fatal(err) + } + } +} + +func TestMarshalUnmarshalBalanceRecordV6(t *testing.T) { + partitiontest.PartitionTest(t) + v := BalanceRecordV6{} + bts := v.MarshalMsg(nil) + left, err := v.UnmarshalMsg(bts) + if err != nil { + t.Fatal(err) + } + if len(left) > 0 { + t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left) + } + + left, err = msgp.Skip(bts) + if err != nil { + t.Fatal(err) + } + if len(left) > 0 { + t.Errorf("%d bytes left over after Skip(): %q", len(left), left) + } +} + +func TestRandomizedEncodingBalanceRecordV6(t *testing.T) { + protocol.RunEncodingTest(t, &BalanceRecordV6{}) +} + +func BenchmarkMarshalMsgBalanceRecordV6(b *testing.B) { + v := BalanceRecordV6{} + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + v.MarshalMsg(nil) + } +} + +func BenchmarkAppendMsgBalanceRecordV6(b *testing.B) { + v := BalanceRecordV6{} + bts := make([]byte, 0, v.Msgsize()) + bts = v.MarshalMsg(bts[0:0]) + b.SetBytes(int64(len(bts))) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + bts = v.MarshalMsg(bts[0:0]) + } +} + +func BenchmarkUnmarshalBalanceRecordV6(b *testing.B) { + v := BalanceRecordV6{} + bts := v.MarshalMsg(nil) + b.ReportAllocs() + b.SetBytes(int64(len(bts))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := v.UnmarshalMsg(bts) + if err != nil { + b.Fatal(err) + } + } +} + +func TestMarshalUnmarshalKVRecordV6(t *testing.T) { + partitiontest.PartitionTest(t) + v := KVRecordV6{} + bts := v.MarshalMsg(nil) + left, err := v.UnmarshalMsg(bts) + if err != nil { + t.Fatal(err) + } + if len(left) > 0 { + t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left) + } + + left, err = msgp.Skip(bts) + if err != nil { + t.Fatal(err) + } + if len(left) > 0 { + t.Errorf("%d bytes left over after Skip(): %q", len(left), left) + } +} + +func TestRandomizedEncodingKVRecordV6(t *testing.T) { + protocol.RunEncodingTest(t, &KVRecordV6{}) +} + +func BenchmarkMarshalMsgKVRecordV6(b *testing.B) { + v := KVRecordV6{} + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + v.MarshalMsg(nil) + } +} + +func BenchmarkAppendMsgKVRecordV6(b *testing.B) { + v := KVRecordV6{} + bts := make([]byte, 0, v.Msgsize()) + bts = v.MarshalMsg(bts[0:0]) + b.SetBytes(int64(len(bts))) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + bts = v.MarshalMsg(bts[0:0]) + } +} + +func BenchmarkUnmarshalKVRecordV6(b *testing.B) { + v := KVRecordV6{} + bts := v.MarshalMsg(nil) + b.ReportAllocs() + b.SetBytes(int64(len(bts))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := v.UnmarshalMsg(bts) + if err != nil { + b.Fatal(err) + } + } +} diff --git a/node/poolStats.go b/ledger/encoded/recordsV5.go similarity index 61% rename from node/poolStats.go rename to ledger/encoded/recordsV5.go index 19b0090b4e..9af95207c6 100644 --- a/node/poolStats.go +++ b/ledger/encoded/recordsV5.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -14,11 +14,17 @@ // You should have received a copy of the GNU Affero General Public License // along with go-algorand. If not, see . -package node +package encoded -// PoolStats represents some statistics about the transaction pool -type PoolStats struct { - NumConfirmed uint64 - NumOutstanding uint64 - NumExpired uint64 +import ( + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/msgp/msgp" +) + +// BalanceRecordV5 is the encoded account balance record. +type BalanceRecordV5 struct { + _struct struct{} `codec:",omitempty,omitemptyarray"` + + Address basics.Address `codec:"pk,allocbound=crypto.DigestSize"` + AccountData msgp.Raw `codec:"ad"` // encoding of basics.AccountData } diff --git a/ledger/encoded/recordsV6.go b/ledger/encoded/recordsV6.go new file mode 100644 index 0000000000..6d0616c80b --- /dev/null +++ b/ledger/encoded/recordsV6.go @@ -0,0 +1,64 @@ +// Copyright (C) 2019-2023 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package encoded + +import ( + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/msgp/msgp" +) + +// Adjust these to be big enough for boxes, but not directly tied to box values. +const ( + // For boxes: "bx:<8 bytes><64 byte name>" + KVRecordV6MaxKeyLength = 128 + + // For boxes: MaxBoxSize + KVRecordV6MaxValueLength = 32768 + + // MaxEncodedKVDataSize is the max size of serialized KV entry, checked with TestEncodedKVDataSize. + // Exact value is 32906 that is 10 bytes more than 32768 + 128 + MaxEncodedKVDataSize = 33000 + + // resourcesPerCatchpointFileChunkBackwardCompatible is the old value for ResourcesPerCatchpointFileChunk. + // Size of a single resource entry was underestimated to 300 bytes that holds only for assets and not for apps. + // It is safe to remove after April, 2023 since we are only supporting catchpoint that are 6 months old. + resourcesPerCatchpointFileChunkBackwardCompatible = 300_000 +) + +// SortUint64 re-export this sort, which is implemented in basics, and being used by the msgp when +// encoding the resources map below. +type SortUint64 = basics.SortUint64 + +// BalanceRecordV6 is the encoded account balance record. +type BalanceRecordV6 struct { + _struct struct{} `codec:",omitempty,omitemptyarray"` + + Address basics.Address `codec:"a,allocbound=crypto.DigestSize"` + AccountData msgp.Raw `codec:"b"` // encoding of baseAccountData + Resources map[uint64]msgp.Raw `codec:"c,allocbound=resourcesPerCatchpointFileChunkBackwardCompatible"` // map of resourcesData + + // flag indicating whether there are more records for the same account coming up + ExpectingMoreEntries bool `codec:"e"` +} + +// KVRecordV6 is the encoded KV record. +type KVRecordV6 struct { + _struct struct{} `codec:",omitempty,omitemptyarray"` + + Key []byte `codec:"k,allocbound=KVRecordV6MaxKeyLength"` + Value []byte `codec:"v,allocbound=KVRecordV6MaxValueLength"` +} diff --git a/ledger/encoded/recordsV6_test.go b/ledger/encoded/recordsV6_test.go new file mode 100644 index 0000000000..9e5dd14384 --- /dev/null +++ b/ledger/encoded/recordsV6_test.go @@ -0,0 +1,63 @@ +// Copyright (C) 2019-2023 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package encoded + +import ( + "math" + "testing" + + "github.com/algorand/avm-abi/apps" + "github.com/algorand/go-algorand/config" + "github.com/algorand/go-algorand/crypto" + "github.com/algorand/go-algorand/protocol" + "github.com/algorand/go-algorand/test/partitiontest" + "github.com/stretchr/testify/require" +) + +func TestEncodedKVRecordV6Allocbounds(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + for version, params := range config.Consensus { + require.GreaterOrEqualf(t, uint64(KVRecordV6MaxValueLength), params.MaxBoxSize, "Allocbound constant no longer valid as of consensus version %s", version) + longestPossibleBoxName := string(make([]byte, params.MaxAppKeyLen)) + longestPossibleKey := apps.MakeBoxKey(math.MaxUint64, longestPossibleBoxName) + require.GreaterOrEqualf(t, KVRecordV6MaxValueLength, len(longestPossibleKey), "Allocbound constant no longer valid as of consensus version %s", version) + } +} + +func TestEncodedKVDataSize(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + currentConsensusParams := config.Consensus[protocol.ConsensusCurrentVersion] + + require.GreaterOrEqual(t, KVRecordV6MaxKeyLength, currentConsensusParams.MaxAppKeyLen) + require.GreaterOrEqual(t, uint64(KVRecordV6MaxValueLength), currentConsensusParams.MaxBoxSize) + + kvEntry := KVRecordV6{ + Key: make([]byte, KVRecordV6MaxKeyLength), + Value: make([]byte, KVRecordV6MaxValueLength), + } + + crypto.RandBytes(kvEntry.Key[:]) + crypto.RandBytes(kvEntry.Value[:]) + + encoded := kvEntry.MarshalMsg(nil) + require.GreaterOrEqual(t, MaxEncodedKVDataSize, len(encoded)) + +} diff --git a/ledger/eval_simple_test.go b/ledger/eval_simple_test.go index 802e8de9d1..4a2317dc1a 100644 --- a/ledger/eval_simple_test.go +++ b/ledger/eval_simple_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/evalbench_test.go b/ledger/evalbench_test.go index fcbfef6817..9b827d83fb 100644 --- a/ledger/evalbench_test.go +++ b/ledger/evalbench_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/evalindexer.go b/ledger/evalindexer.go index 3e2d8ca34b..a575c638bd 100644 --- a/ledger/evalindexer.go +++ b/ledger/evalindexer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/evalindexer_test.go b/ledger/evalindexer_test.go index 77b7dbde7d..d5636f27ca 100644 --- a/ledger/evalindexer_test.go +++ b/ledger/evalindexer_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/fullblock_perf_test.go b/ledger/fullblock_perf_test.go index d2d50cd8f7..9bea4a00a0 100644 --- a/ledger/fullblock_perf_test.go +++ b/ledger/fullblock_perf_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -32,6 +32,7 @@ import ( "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/basics" + basics_testing "github.com/algorand/go-algorand/data/basics/testing" "github.com/algorand/go-algorand/data/bookkeeping" "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/data/transactions/logic" @@ -81,7 +82,7 @@ func setupEnv(b *testing.B, numAccts int) (bc *benchConfig) { creator := basics.Address{} _, err := rand.Read(creator[:]) require.NoError(b, err) - genesisInitState.Accounts[creator] = basics.MakeAccountData(basics.Offline, basics.MicroAlgos{Raw: 1234567890000000000}) + genesisInitState.Accounts[creator] = basics_testing.MakeAccountData(basics.Offline, basics.MicroAlgos{Raw: 1234567890000000000}) logger := logging.TestingLog(b) logger.SetLevel(logging.Warn) diff --git a/ledger/internal/appcow.go b/ledger/internal/appcow.go index d38fbfacc6..c43eb94f16 100644 --- a/ledger/internal/appcow.go +++ b/ledger/internal/appcow.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/internal/appcow_test.go b/ledger/internal/appcow_test.go index db659636ba..d4025524c4 100644 --- a/ledger/internal/appcow_test.go +++ b/ledger/internal/appcow_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/internal/applications.go b/ledger/internal/applications.go index 1bf694eed4..f0466b7d4a 100644 --- a/ledger/internal/applications.go +++ b/ledger/internal/applications.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -19,6 +19,7 @@ package internal import ( "fmt" + "github.com/algorand/avm-abi/apps" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/bookkeeping" "github.com/algorand/go-algorand/data/transactions/logic" @@ -222,7 +223,7 @@ func (cs *roundCowState) NewBox(appIdx basics.AppIndex, key string, value []byte return fmt.Errorf("box size too large: %d, maximum is %d", size, cs.proto.MaxBoxSize) } - fullKey := logic.MakeBoxKey(appIdx, key) + fullKey := apps.MakeBoxKey(uint64(appIdx), key) _, exists, err := cs.kvGet(fullKey) if err != nil { return err @@ -246,12 +247,12 @@ func (cs *roundCowState) NewBox(appIdx basics.AppIndex, key string, value []byte } func (cs *roundCowState) GetBox(appIdx basics.AppIndex, key string) ([]byte, bool, error) { - fullKey := logic.MakeBoxKey(appIdx, key) + fullKey := apps.MakeBoxKey(uint64(appIdx), key) return cs.kvGet(fullKey) } func (cs *roundCowState) SetBox(appIdx basics.AppIndex, key string, value []byte) error { - fullKey := logic.MakeBoxKey(appIdx, key) + fullKey := apps.MakeBoxKey(uint64(appIdx), key) old, ok, err := cs.kvGet(fullKey) if err != nil { return err @@ -267,7 +268,7 @@ func (cs *roundCowState) SetBox(appIdx basics.AppIndex, key string, value []byte } func (cs *roundCowState) DelBox(appIdx basics.AppIndex, key string, appAddr basics.Address) (bool, error) { - fullKey := logic.MakeBoxKey(appIdx, key) + fullKey := apps.MakeBoxKey(uint64(appIdx), key) value, ok, err := cs.kvGet(fullKey) if err != nil { diff --git a/ledger/internal/assetcow.go b/ledger/internal/assetcow.go index f675d823cf..3813dad7ce 100644 --- a/ledger/internal/assetcow.go +++ b/ledger/internal/assetcow.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/internal/cow.go b/ledger/internal/cow.go index e27dd777f5..ade61f8204 100644 --- a/ledger/internal/cow.go +++ b/ledger/internal/cow.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/internal/cow_creatables.go b/ledger/internal/cow_creatables.go index 9db6977421..d43135cf89 100644 --- a/ledger/internal/cow_creatables.go +++ b/ledger/internal/cow_creatables.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/internal/cow_test.go b/ledger/internal/cow_test.go index 14eb748fb6..562c60f925 100644 --- a/ledger/internal/cow_test.go +++ b/ledger/internal/cow_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/internal/eval.go b/ledger/internal/eval.go index 5fc3c51fa7..1a1a1eea50 100644 --- a/ledger/internal/eval.go +++ b/ledger/internal/eval.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -594,6 +594,8 @@ type BlockEvaluator struct { l LedgerForEvaluator maxTxnBytesPerBlock int + + Tracer logic.EvalTracer } // LedgerForEvaluator defines the ledger interface needed by the evaluator. @@ -925,14 +927,7 @@ func (eval *BlockEvaluator) Transaction(txn transactions.SignedTxn, ad transacti // TransactionGroup tentatively adds a new transaction group as part of this block evaluation. // If the transaction group cannot be added to the block without violating some constraints, // an error is returned and the block evaluator state is unchanged. -func (eval *BlockEvaluator) TransactionGroup(txads []transactions.SignedTxnWithAD) error { - return eval.transactionGroup(txads) -} - -// transactionGroup tentatively executes a group of transactions as part of this block evaluation. -// If the transaction group cannot be added to the block without violating some constraints, -// an error is returned and the block evaluator state is unchanged. -func (eval *BlockEvaluator) transactionGroup(txgroup []transactions.SignedTxnWithAD) error { +func (eval *BlockEvaluator) TransactionGroup(txgroup []transactions.SignedTxnWithAD) error { // Nothing to do if there are no transactions. if len(txgroup) == 0 { return nil @@ -953,17 +948,30 @@ func (eval *BlockEvaluator) transactionGroup(txgroup []transactions.SignedTxnWit defer cow.recycle() evalParams := logic.NewEvalParams(txgroup, &eval.proto, &eval.specials) + evalParams.Tracer = eval.Tracer + + if eval.Tracer != nil { + eval.Tracer.BeforeTxnGroup(evalParams) + } // Evaluate each transaction in the group txibs = make([]transactions.SignedTxnInBlock, 0, len(txgroup)) for gi, txad := range txgroup { var txib transactions.SignedTxnInBlock + if eval.Tracer != nil { + eval.Tracer.BeforeTxn(evalParams, gi) + } + err := eval.transaction(txad.SignedTxn, evalParams, gi, txad.ApplyData, cow, &txib) if err != nil { return err } + if eval.Tracer != nil { + eval.Tracer.AfterTxn(evalParams, gi, txib.ApplyData) + } + txibs = append(txibs, txib) if eval.validate { @@ -1010,6 +1018,10 @@ func (eval *BlockEvaluator) transactionGroup(txgroup []transactions.SignedTxnWit eval.blockTxBytes += groupTxBytes cow.commitToParent() + if eval.Tracer != nil { + eval.Tracer.AfterTxnGroup(evalParams) + } + return nil } diff --git a/ledger/internal/eval_test.go b/ledger/internal/eval_test.go index d04294e4cc..5803a4d613 100644 --- a/ledger/internal/eval_test.go +++ b/ledger/internal/eval_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -33,11 +33,14 @@ import ( "github.com/algorand/go-algorand/crypto/merklesignature" "github.com/algorand/go-algorand/crypto/stateproof" "github.com/algorand/go-algorand/data/basics" + basics_testing "github.com/algorand/go-algorand/data/basics/testing" "github.com/algorand/go-algorand/data/bookkeeping" "github.com/algorand/go-algorand/data/stateproofmsg" "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/data/transactions/logic" + "github.com/algorand/go-algorand/data/transactions/logic/mocktracer" "github.com/algorand/go-algorand/data/transactions/verify" + "github.com/algorand/go-algorand/data/txntest" "github.com/algorand/go-algorand/ledger/apply" "github.com/algorand/go-algorand/ledger/ledgercore" ledgertesting "github.com/algorand/go-algorand/ledger/testing" @@ -307,8 +310,203 @@ func TestPrivateTransactionGroup(t *testing.T) { require.Error(t, err) // too many } +func tealOpLogs(count int) []mocktracer.Event { + var log []mocktracer.Event + + for i := 0; i < count; i++ { + log = append(log, mocktracer.BeforeOpcode(), mocktracer.AfterOpcode()) + } + + return log +} + +func flatten(rows [][]mocktracer.Event) []mocktracer.Event { + var out []mocktracer.Event + for _, row := range rows { + out = append(out, row...) + } + return out +} + +const innerTxnTestProgram string = `#pragma version 6 +itxn_begin +int appl +itxn_field TypeEnum +int NoOp +itxn_field OnCompletion +byte 0x068101 // #pragma version 6; int 1; +dup +itxn_field ApprovalProgram +itxn_field ClearStateProgram +itxn_submit + +itxn_begin +int pay +itxn_field TypeEnum +int 1 +itxn_field Amount +global CurrentApplicationAddress +itxn_field Receiver +itxn_next +int pay +itxn_field TypeEnum +int 2 +itxn_field Amount +global CurrentApplicationAddress +itxn_field Receiver +itxn_submit + +int 1 +` + +func TestTransactionGroupWithTracer(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + genesisInitState, addrs, keys := ledgertesting.Genesis(10) + + innerAppID := 3 + innerAppAddress := basics.AppIndex(innerAppID).Address() + balances := genesisInitState.Accounts + balances[innerAppAddress] = basics_testing.MakeAccountData(basics.Offline, basics.MicroAlgos{Raw: 1000000}) + + genesisBalances := bookkeeping.GenesisBalances{ + Balances: genesisInitState.Accounts, + FeeSink: testSinkAddr, + RewardsPool: testPoolAddr, + Timestamp: 0, + } + l := newTestLedger(t, genesisBalances) + + blkHeader, err := l.BlockHdr(basics.Round(0)) + require.NoError(t, err) + newBlock := bookkeeping.MakeBlock(blkHeader) + eval, err := l.StartEvaluator(newBlock.BlockHeader, 0, 0) + require.NoError(t, err) + eval.validate = true + eval.generate = true + + basicProgram := `#pragma version 6 +byte "hello" +log +int 1` + + genHash := l.GenesisHash() + + // a basic app call + basicAppCallTxn := txntest.Txn{ + Type: protocol.ApplicationCallTx, + Sender: addrs[0], + ApprovalProgram: basicProgram, + ClearStateProgram: basicProgram, + + FirstValid: newBlock.Round(), + LastValid: newBlock.Round() + 1000, + Fee: minFee, + GenesisHash: genHash, + } + + // a non-app call txn + payTxn := txntest.Txn{ + Type: protocol.PaymentTx, + Sender: addrs[1], + Receiver: addrs[2], + CloseRemainderTo: addrs[3], + Amount: 1_000_000, + + FirstValid: newBlock.Round(), + LastValid: newBlock.Round() + 1000, + Fee: minFee, + GenesisHash: genHash, + } + + // an app call that spawns inner txns + innerAppCallTxn := txntest.Txn{ + Type: protocol.ApplicationCallTx, + Sender: addrs[0], + ApprovalProgram: innerTxnTestProgram, + ClearStateProgram: basicProgram, + + FirstValid: newBlock.Round(), + LastValid: newBlock.Round() + 1000, + Fee: minFee, + GenesisHash: genHash, + } + + txntest.Group(&basicAppCallTxn, &payTxn, &innerAppCallTxn) + + txgroup := transactions.WrapSignedTxnsWithAD([]transactions.SignedTxn{ + basicAppCallTxn.Txn().Sign(keys[0]), + payTxn.Txn().Sign(keys[1]), + innerAppCallTxn.Txn().Sign(keys[0]), + }) + + require.Len(t, eval.block.Payset, 0) + + tracer := &mocktracer.Tracer{} + eval.Tracer = tracer + err = eval.TransactionGroup(txgroup) + require.NoError(t, err) + + require.Len(t, eval.block.Payset, len(txgroup)) + + expectedADs := make([]transactions.ApplyData, len(txgroup)) + for i, txn := range eval.block.Payset { + expectedADs[i] = txn.ApplyData + } + + expectedEvents := flatten([][]mocktracer.Event{ + { + mocktracer.BeforeTxnGroup(3), + mocktracer.BeforeTxn(protocol.ApplicationCallTx), // start basicAppCallTxn + mocktracer.BeforeProgram(logic.ModeApp), + }, + tealOpLogs(3), + { + mocktracer.AfterProgram(logic.ModeApp), + mocktracer.AfterTxn(protocol.ApplicationCallTx, expectedADs[0]), // end basicAppCallTxn + mocktracer.BeforeTxn(protocol.PaymentTx), // start payTxn + mocktracer.AfterTxn(protocol.PaymentTx, expectedADs[1]), // end payTxn + mocktracer.BeforeTxn(protocol.ApplicationCallTx), // start innerAppCallTxn + mocktracer.BeforeProgram(logic.ModeApp), + }, + tealOpLogs(10), + { + mocktracer.BeforeOpcode(), + mocktracer.BeforeTxnGroup(1), // start first itxn group + mocktracer.BeforeTxn(protocol.ApplicationCallTx), + mocktracer.BeforeProgram(logic.ModeApp), + }, + tealOpLogs(1), + { + mocktracer.AfterProgram(logic.ModeApp), + mocktracer.AfterTxn(protocol.ApplicationCallTx, expectedADs[2].EvalDelta.InnerTxns[0].ApplyData), + mocktracer.AfterTxnGroup(1), // end first itxn group + mocktracer.AfterOpcode(), + }, + tealOpLogs(14), + { + mocktracer.BeforeOpcode(), + mocktracer.BeforeTxnGroup(2), // start second itxn group + mocktracer.BeforeTxn(protocol.PaymentTx), + mocktracer.AfterTxn(protocol.PaymentTx, expectedADs[2].EvalDelta.InnerTxns[1].ApplyData), + mocktracer.BeforeTxn(protocol.PaymentTx), + mocktracer.AfterTxn(protocol.PaymentTx, expectedADs[2].EvalDelta.InnerTxns[2].ApplyData), + mocktracer.AfterTxnGroup(2), // end second itxn group + mocktracer.AfterOpcode(), + }, + tealOpLogs(1), + { + mocktracer.AfterProgram(logic.ModeApp), + mocktracer.AfterTxn(protocol.ApplicationCallTx, expectedADs[2]), // end innerAppCallTxn + mocktracer.AfterTxnGroup(3), + }, + }) + require.Equal(t, expectedEvents, tracer.Events) +} + // BlockEvaluator.workaroundOverspentRewards() fixed a couple issues on testnet. -// This is now part of history and has to be re-created when running catchup on testnet. So, test to ensure it keeps happenning. +// This is now part of history and has to be re-created when running catchup on testnet. So, test to ensure it keeps happening. func TestTestnetFixup(t *testing.T) { partitiontest.PartitionTest(t) diff --git a/ledger/internal/evalindexer.go b/ledger/internal/evalindexer.go index b765e0e1ef..babf6c87db 100644 --- a/ledger/internal/evalindexer.go +++ b/ledger/internal/evalindexer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/internal/prefetcher/error.go b/ledger/internal/prefetcher/error.go index 58b52f891b..77c1cb999c 100644 --- a/ledger/internal/prefetcher/error.go +++ b/ledger/internal/prefetcher/error.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/internal/prefetcher/prefetcher.go b/ledger/internal/prefetcher/prefetcher.go index 07b95c57d4..e00d78f701 100644 --- a/ledger/internal/prefetcher/prefetcher.go +++ b/ledger/internal/prefetcher/prefetcher.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/internal/prefetcher/prefetcher_alignment_test.go b/ledger/internal/prefetcher/prefetcher_alignment_test.go index 2b553c974a..1d5291cf17 100644 --- a/ledger/internal/prefetcher/prefetcher_alignment_test.go +++ b/ledger/internal/prefetcher/prefetcher_alignment_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/internal/prefetcher/prefetcher_test.go b/ledger/internal/prefetcher/prefetcher_test.go index 555cc8f6d2..b9c1d80eb3 100644 --- a/ledger/internal/prefetcher/prefetcher_test.go +++ b/ledger/internal/prefetcher/prefetcher_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/internal/prefetcher/prefetcher_whitebox_test.go b/ledger/internal/prefetcher/prefetcher_whitebox_test.go index c5ada063a6..6a8738b48c 100644 --- a/ledger/internal/prefetcher/prefetcher_whitebox_test.go +++ b/ledger/internal/prefetcher/prefetcher_whitebox_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/ledger.go b/ledger/ledger.go index 12c4148cf7..cd023e5023 100644 --- a/ledger/ledger.go +++ b/ledger/ledger.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -160,6 +160,11 @@ func OpenLedger( l.genesisAccounts = make(map[basics.Address]basics.AccountData) } + l.blockQ, err = newBlockQueue(l) + if err != nil { + return nil, err + } + err = l.reloadLedger() if err != nil { return nil, err @@ -168,19 +173,12 @@ func OpenLedger( return l, nil } -// ReloadLedger is exported for the benefit of tests in the internal -// package. Revisit this when we rename / restructure that thing -func (l *Ledger) ReloadLedger() error { - return l.reloadLedger() -} - func (l *Ledger) reloadLedger() error { // similar to the Close function, we want to start by closing the blockQ first. The // blockQ is having a sync goroutine which indirectly calls other trackers. We want to eliminate that go-routine first, // and follow up by taking the trackers lock. if l.blockQ != nil { - l.blockQ.close() - l.blockQ = nil + l.blockQ.stop() } // take the trackers lock. This would ensure that no other goroutine is using the trackers. @@ -192,9 +190,9 @@ func (l *Ledger) reloadLedger() error { // init block queue var err error - l.blockQ, err = bqInit(l) + err = l.blockQ.start() if err != nil { - err = fmt.Errorf("reloadLedger.bqInit %v", err) + err = fmt.Errorf("reloadLedger.blockQ.start %v", err) return err } @@ -381,8 +379,7 @@ func (l *Ledger) Close() { // we shut the the blockqueue first, since it's sync goroutine dispatches calls // back to the trackers. if l.blockQ != nil { - l.blockQ.close() - l.blockQ = nil + l.blockQ.stop() } // take the trackers lock. This would ensure that no other goroutine is using the trackers. @@ -399,7 +396,7 @@ func (l *Ledger) Close() { // RegisterBlockListeners registers listeners that will be called when a // new block is added to the ledger. -func (l *Ledger) RegisterBlockListeners(listeners []BlockListener) { +func (l *Ledger) RegisterBlockListeners(listeners []ledgercore.BlockListener) { l.notifier.register(listeners) } diff --git a/ledger/ledger_perf_test.go b/ledger/ledger_perf_test.go index a855f70dea..2c5cb8ec5f 100644 --- a/ledger/ledger_perf_test.go +++ b/ledger/ledger_perf_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -31,6 +31,7 @@ import ( "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/basics" + basics_testing "github.com/algorand/go-algorand/data/basics/testing" "github.com/algorand/go-algorand/data/bookkeeping" "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/data/transactions/logic" @@ -151,7 +152,7 @@ func benchmarkFullBlocks(params testParams, b *testing.B) { creator := basics.Address{} _, err := rand.Read(creator[:]) require.NoError(b, err) - genesisInitState.Accounts[creator] = basics.MakeAccountData(basics.Offline, basics.MicroAlgos{Raw: 1234567890}) + genesisInitState.Accounts[creator] = basics_testing.MakeAccountData(basics.Offline, basics.MicroAlgos{Raw: 1234567890}) // Make some accounts to opt into ASA var accts []basics.Address @@ -160,7 +161,7 @@ func benchmarkFullBlocks(params testParams, b *testing.B) { acct := basics.Address{} _, err = rand.Read(acct[:]) require.NoError(b, err) - genesisInitState.Accounts[acct] = basics.MakeAccountData(basics.Offline, basics.MicroAlgos{Raw: 1234567890}) + genesisInitState.Accounts[acct] = basics_testing.MakeAccountData(basics.Offline, basics.MicroAlgos{Raw: 1234567890}) accts = append(accts, acct) } } diff --git a/ledger/ledger_test.go b/ledger/ledger_test.go index 785281eade..b9a1c64037 100644 --- a/ledger/ledger_test.go +++ b/ledger/ledger_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -27,6 +27,7 @@ import ( "runtime" "sort" "testing" + "time" "github.com/stretchr/testify/require" @@ -1477,6 +1478,31 @@ func benchLedgerCache(b *testing.B, startRound basics.Round) { } } +func triggerTrackerFlush(t *testing.T, l *Ledger, genesisInitState ledgercore.InitState) { + l.trackers.mu.RLock() + initialDbRound := l.trackers.dbRound + currentDbRound := initialDbRound + l.trackers.lastFlushTime = time.Time{} + l.trackers.mu.RUnlock() + + addEmptyValidatedBlock(t, l, genesisInitState.Accounts) + + const timeout = 2 * time.Second + started := time.Now() + + // We can't truly wait for scheduleCommit to take place, which means without waiting using sleeps + // we might beat scheduleCommit's addition to accountsWriting, making our wait on it continue immediately. + // The solution is to wait for the advancement of l.trackers.dbRound, which is a side effect of postCommit's success. + for currentDbRound == initialDbRound { + time.Sleep(50 * time.Microsecond) + require.True(t, time.Now().Sub(started) < timeout) + l.trackers.mu.RLock() + currentDbRound = l.trackers.dbRound + l.trackers.mu.RUnlock() + } + l.trackers.waitAccountsWriting() +} + func TestLedgerReload(t *testing.T) { partitiontest.PartitionTest(t) @@ -1513,6 +1539,36 @@ func TestLedgerReload(t *testing.T) { } } +func TestWaitLedgerReload(t *testing.T) { + partitiontest.PartitionTest(t) + a := require.New(t) + + dbName := fmt.Sprintf("%s.%d", t.Name(), crypto.RandUint64()) + genesisInitState, _ := ledgertesting.GenerateInitState(t, protocol.ConsensusCurrentVersion, 100) + const inMem = true + cfg := config.GetDefaultLocal() + cfg.MaxAcctLookback = 0 + log := logging.TestingLog(t) + log.SetLevel(logging.Info) + l, err := OpenLedger(log, dbName, inMem, genesisInitState, cfg) + require.NoError(t, err) + defer l.Close() + + waitRound := l.Latest() + 1 + waitChannel := l.Wait(waitRound) + + err = l.reloadLedger() + a.NoError(err) + triggerTrackerFlush(t, l, genesisInitState) + + select { + case <-waitChannel: + return + default: + a.Failf("", "Wait channel did not receive an expected signal for round %d", waitRound) + } +} + // TestGetLastCatchpointLabel tests ledger.GetLastCatchpointLabel is returning the correct value. func TestGetLastCatchpointLabel(t *testing.T) { partitiontest.PartitionTest(t) diff --git a/ledger/ledgercore/accountdata.go b/ledger/ledgercore/accountdata.go index bafd9f32ad..69e2c543eb 100644 --- a/ledger/ledgercore/accountdata.go +++ b/ledger/ledgercore/accountdata.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/ledgercore/accountdata_test.go b/ledger/ledgercore/accountdata_test.go index a22a6b5b04..76eb82d0d3 100644 --- a/ledger/ledgercore/accountdata_test.go +++ b/ledger/ledgercore/accountdata_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/ledgercore/accountresource.go b/ledger/ledgercore/accountresource.go index 5ccb1e53d4..4d22ec0dc1 100644 --- a/ledger/ledgercore/accountresource.go +++ b/ledger/ledgercore/accountresource.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/ledgercore/catchpointlabel.go b/ledger/ledgercore/catchpointlabel.go index 2b2e085fab..6f0b90f9d9 100644 --- a/ledger/ledgercore/catchpointlabel.go +++ b/ledger/ledgercore/catchpointlabel.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/ledgercore/catchpointlabel_test.go b/ledger/ledgercore/catchpointlabel_test.go index 5e7a220262..1bfbe31158 100644 --- a/ledger/ledgercore/catchpointlabel_test.go +++ b/ledger/ledgercore/catchpointlabel_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/ledgercore/error.go b/ledger/ledgercore/error.go index 28311973c0..b1dff71a4a 100644 --- a/ledger/ledgercore/error.go +++ b/ledger/ledgercore/error.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/ledgercore/misc.go b/ledger/ledgercore/misc.go index 5bc39cd1cc..86fc2c3ff9 100644 --- a/ledger/ledgercore/misc.go +++ b/ledger/ledgercore/misc.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -28,3 +28,8 @@ type InitState struct { Accounts map[basics.Address]basics.AccountData GenesisHash crypto.Digest } + +// BlockListener represents an object that needs to get notified on new blocks. +type BlockListener interface { + OnNewBlock(block bookkeeping.Block, delta StateDelta) +} diff --git a/ledger/ledgercore/onlineacct.go b/ledger/ledgercore/onlineacct.go index 765067bb2a..690ebcfe52 100644 --- a/ledger/ledgercore/onlineacct.go +++ b/ledger/ledgercore/onlineacct.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/ledgercore/statedelta.go b/ledger/ledgercore/statedelta.go index 688c8d093b..7672f0a864 100644 --- a/ledger/ledgercore/statedelta.go +++ b/ledger/ledgercore/statedelta.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/ledgercore/statedelta_test.go b/ledger/ledgercore/statedelta_test.go index aeae35b36c..dd0f3e201d 100644 --- a/ledger/ledgercore/statedelta_test.go +++ b/ledger/ledgercore/statedelta_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/ledgercore/totals.go b/ledger/ledgercore/totals.go index 291db77a27..4e75ddbf44 100644 --- a/ledger/ledgercore/totals.go +++ b/ledger/ledgercore/totals.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/ledgercore/totals_test.go b/ledger/ledgercore/totals_test.go index 12d3b9a2a2..66f4e09ac3 100644 --- a/ledger/ledgercore/totals_test.go +++ b/ledger/ledgercore/totals_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/ledgercore/validatedBlock.go b/ledger/ledgercore/validatedBlock.go index 9b4aa30f10..84d09e2a9a 100644 --- a/ledger/ledgercore/validatedBlock.go +++ b/ledger/ledgercore/validatedBlock.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/ledgercore/votersForRound.go b/ledger/ledgercore/votersForRound.go index 1212778b31..c1afd9eb5f 100644 --- a/ledger/ledgercore/votersForRound.go +++ b/ledger/ledgercore/votersForRound.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/lruaccts.go b/ledger/lruaccts.go index 13e639a95a..22928012af 100644 --- a/ledger/lruaccts.go +++ b/ledger/lruaccts.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/lruaccts_test.go b/ledger/lruaccts_test.go index 30f1f082c6..625d40f3ea 100644 --- a/ledger/lruaccts_test.go +++ b/ledger/lruaccts_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/lrukv.go b/ledger/lrukv.go index 30530fe6bd..77986eb1b6 100644 --- a/ledger/lrukv.go +++ b/ledger/lrukv.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/lrukv_test.go b/ledger/lrukv_test.go index 01ed002065..18d0a47072 100644 --- a/ledger/lrukv_test.go +++ b/ledger/lrukv_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/lruonlineaccts.go b/ledger/lruonlineaccts.go index bc8ccedfd4..35c8224d41 100644 --- a/ledger/lruonlineaccts.go +++ b/ledger/lruonlineaccts.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/lruonlineaccts_test.go b/ledger/lruonlineaccts_test.go index 2b9994480e..0e0b314385 100644 --- a/ledger/lruonlineaccts_test.go +++ b/ledger/lruonlineaccts_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/lruresources.go b/ledger/lruresources.go index b2f9a63b04..222c1d6c07 100644 --- a/ledger/lruresources.go +++ b/ledger/lruresources.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/lruresources_test.go b/ledger/lruresources_test.go index 695d1f026b..6c97309031 100644 --- a/ledger/lruresources_test.go +++ b/ledger/lruresources_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/metrics.go b/ledger/metrics.go index 66a3466784..4700dfcd55 100644 --- a/ledger/metrics.go +++ b/ledger/metrics.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -56,10 +56,10 @@ func (mt *metricsTracker) close() { func (mt *metricsTracker) newBlock(blk bookkeeping.Block, delta ledgercore.StateDelta) { rnd := blk.Round() - mt.ledgerRound.Set(float64(rnd)) - mt.ledgerTransactionsTotal.Add(float64(len(blk.Payset)), map[string]string{}) + mt.ledgerRound.Set(uint64(rnd)) + mt.ledgerTransactionsTotal.AddUint64(uint64(len(blk.Payset)), nil) // TODO rewards: need to provide meaningful metric here. - mt.ledgerRewardClaimsTotal.Add(float64(1), map[string]string{}) + mt.ledgerRewardClaimsTotal.Inc(nil) } func (mt *metricsTracker) committedUpTo(committedRnd basics.Round) (retRound, lookback basics.Round) { diff --git a/ledger/metrics_test.go b/ledger/metrics_test.go index ca24e0f0f2..bca0b6bcc0 100644 --- a/ledger/metrics_test.go +++ b/ledger/metrics_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/msgp_gen.go b/ledger/msgp_gen.go index ddd01a87f5..39c489e281 100644 --- a/ledger/msgp_gen.go +++ b/ledger/msgp_gen.go @@ -3,9 +3,9 @@ package ledger // Code generated by github.com/algorand/msgp DO NOT EDIT. import ( - "sort" - "github.com/algorand/msgp/msgp" + + "github.com/algorand/go-algorand/ledger/encoded" ) // The following msgp objects are implemented in this file: @@ -41,30 +41,6 @@ import ( // |-----> (*) Msgsize // |-----> (*) MsgIsZero // -// encodedBalanceRecordV5 -// |-----> (*) MarshalMsg -// |-----> (*) CanMarshalMsg -// |-----> (*) UnmarshalMsg -// |-----> (*) CanUnmarshalMsg -// |-----> (*) Msgsize -// |-----> (*) MsgIsZero -// -// encodedBalanceRecordV6 -// |-----> (*) MarshalMsg -// |-----> (*) CanMarshalMsg -// |-----> (*) UnmarshalMsg -// |-----> (*) CanUnmarshalMsg -// |-----> (*) Msgsize -// |-----> (*) MsgIsZero -// -// encodedKVRecordV6 -// |-----> (*) MarshalMsg -// |-----> (*) CanMarshalMsg -// |-----> (*) UnmarshalMsg -// |-----> (*) CanUnmarshalMsg -// |-----> (*) Msgsize -// |-----> (*) MsgIsZero -// // MarshalMsg implements msgp.Marshaler func (z CatchpointCatchupState) MarshalMsg(b []byte) (o []byte) { @@ -424,29 +400,7 @@ func (z *catchpointFileBalancesChunkV5) MarshalMsg(b []byte) (o []byte) { o = msgp.AppendArrayHeader(o, uint32(len((*z).Balances))) } for zb0001 := range (*z).Balances { - // omitempty: check for empty values - zb0003Len := uint32(2) - var zb0003Mask uint8 /* 3 bits */ - if (*z).Balances[zb0001].AccountData.MsgIsZero() { - zb0003Len-- - zb0003Mask |= 0x2 - } - if (*z).Balances[zb0001].Address.MsgIsZero() { - zb0003Len-- - zb0003Mask |= 0x4 - } - // variable map header, size zb0003Len - o = append(o, 0x80|uint8(zb0003Len)) - if (zb0003Mask & 0x2) == 0 { // if not empty - // string "ad" - o = append(o, 0xa2, 0x61, 0x64) - o = (*z).Balances[zb0001].AccountData.MarshalMsg(o) - } - if (zb0003Mask & 0x4) == 0 { // if not empty - // string "pk" - o = append(o, 0xa2, 0x70, 0x6b) - o = (*z).Balances[zb0001].Address.MarshalMsg(o) - } + o = (*z).Balances[zb0001].MarshalMsg(o) } } } @@ -490,77 +444,13 @@ func (z *catchpointFileBalancesChunkV5) UnmarshalMsg(bts []byte) (o []byte, err } else if (*z).Balances != nil && cap((*z).Balances) >= zb0004 { (*z).Balances = ((*z).Balances)[:zb0004] } else { - (*z).Balances = make([]encodedBalanceRecordV5, zb0004) + (*z).Balances = make([]encoded.BalanceRecordV5, zb0004) } for zb0001 := range (*z).Balances { - var zb0006 int - var zb0007 bool - zb0006, zb0007, bts, err = msgp.ReadMapHeaderBytes(bts) - if _, ok := err.(msgp.TypeError); ok { - zb0006, zb0007, bts, err = msgp.ReadArrayHeaderBytes(bts) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "Balances", zb0001) - return - } - if zb0006 > 0 { - zb0006-- - bts, err = (*z).Balances[zb0001].Address.UnmarshalMsg(bts) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "Balances", zb0001, "struct-from-array", "Address") - return - } - } - if zb0006 > 0 { - zb0006-- - bts, err = (*z).Balances[zb0001].AccountData.UnmarshalMsg(bts) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "Balances", zb0001, "struct-from-array", "AccountData") - return - } - } - if zb0006 > 0 { - err = msgp.ErrTooManyArrayFields(zb0006) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "Balances", zb0001, "struct-from-array") - return - } - } - } else { - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "Balances", zb0001) - return - } - if zb0007 { - (*z).Balances[zb0001] = encodedBalanceRecordV5{} - } - for zb0006 > 0 { - zb0006-- - field, bts, err = msgp.ReadMapKeyZC(bts) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "Balances", zb0001) - return - } - switch string(field) { - case "pk": - bts, err = (*z).Balances[zb0001].Address.UnmarshalMsg(bts) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "Balances", zb0001, "Address") - return - } - case "ad": - bts, err = (*z).Balances[zb0001].AccountData.UnmarshalMsg(bts) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "Balances", zb0001, "AccountData") - return - } - default: - err = msgp.ErrNoField(string(field)) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "Balances", zb0001) - return - } - } - } + bts, err = (*z).Balances[zb0001].UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Balances", zb0001) + return } } } @@ -588,94 +478,30 @@ func (z *catchpointFileBalancesChunkV5) UnmarshalMsg(bts []byte) (o []byte, err } switch string(field) { case "bl": - var zb0008 int - var zb0009 bool - zb0008, zb0009, bts, err = msgp.ReadArrayHeaderBytes(bts) + var zb0006 int + var zb0007 bool + zb0006, zb0007, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "Balances") return } - if zb0008 > BalancesPerCatchpointFileChunk { - err = msgp.ErrOverflow(uint64(zb0008), uint64(BalancesPerCatchpointFileChunk)) + if zb0006 > BalancesPerCatchpointFileChunk { + err = msgp.ErrOverflow(uint64(zb0006), uint64(BalancesPerCatchpointFileChunk)) err = msgp.WrapError(err, "Balances") return } - if zb0009 { + if zb0007 { (*z).Balances = nil - } else if (*z).Balances != nil && cap((*z).Balances) >= zb0008 { - (*z).Balances = ((*z).Balances)[:zb0008] + } else if (*z).Balances != nil && cap((*z).Balances) >= zb0006 { + (*z).Balances = ((*z).Balances)[:zb0006] } else { - (*z).Balances = make([]encodedBalanceRecordV5, zb0008) + (*z).Balances = make([]encoded.BalanceRecordV5, zb0006) } for zb0001 := range (*z).Balances { - var zb0010 int - var zb0011 bool - zb0010, zb0011, bts, err = msgp.ReadMapHeaderBytes(bts) - if _, ok := err.(msgp.TypeError); ok { - zb0010, zb0011, bts, err = msgp.ReadArrayHeaderBytes(bts) - if err != nil { - err = msgp.WrapError(err, "Balances", zb0001) - return - } - if zb0010 > 0 { - zb0010-- - bts, err = (*z).Balances[zb0001].Address.UnmarshalMsg(bts) - if err != nil { - err = msgp.WrapError(err, "Balances", zb0001, "struct-from-array", "Address") - return - } - } - if zb0010 > 0 { - zb0010-- - bts, err = (*z).Balances[zb0001].AccountData.UnmarshalMsg(bts) - if err != nil { - err = msgp.WrapError(err, "Balances", zb0001, "struct-from-array", "AccountData") - return - } - } - if zb0010 > 0 { - err = msgp.ErrTooManyArrayFields(zb0010) - if err != nil { - err = msgp.WrapError(err, "Balances", zb0001, "struct-from-array") - return - } - } - } else { - if err != nil { - err = msgp.WrapError(err, "Balances", zb0001) - return - } - if zb0011 { - (*z).Balances[zb0001] = encodedBalanceRecordV5{} - } - for zb0010 > 0 { - zb0010-- - field, bts, err = msgp.ReadMapKeyZC(bts) - if err != nil { - err = msgp.WrapError(err, "Balances", zb0001) - return - } - switch string(field) { - case "pk": - bts, err = (*z).Balances[zb0001].Address.UnmarshalMsg(bts) - if err != nil { - err = msgp.WrapError(err, "Balances", zb0001, "Address") - return - } - case "ad": - bts, err = (*z).Balances[zb0001].AccountData.UnmarshalMsg(bts) - if err != nil { - err = msgp.WrapError(err, "Balances", zb0001, "AccountData") - return - } - default: - err = msgp.ErrNoField(string(field)) - if err != nil { - err = msgp.WrapError(err, "Balances", zb0001) - return - } - } - } + bts, err = (*z).Balances[zb0001].UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "Balances", zb0001) + return } } default: @@ -700,7 +526,7 @@ func (_ *catchpointFileBalancesChunkV5) CanUnmarshalMsg(z interface{}) bool { func (z *catchpointFileBalancesChunkV5) Msgsize() (s int) { s = 1 + 3 + msgp.ArrayHeaderSize for zb0001 := range (*z).Balances { - s += 1 + 3 + (*z).Balances[zb0001].Address.Msgsize() + 3 + (*z).Balances[zb0001].AccountData.Msgsize() + s += (*z).Balances[zb0001].Msgsize() } return } @@ -748,29 +574,7 @@ func (z *catchpointFileChunkV6) MarshalMsg(b []byte) (o []byte) { o = msgp.AppendArrayHeader(o, uint32(len((*z).KVs))) } for zb0002 := range (*z).KVs { - // omitempty: check for empty values - zb0004Len := uint32(2) - var zb0004Mask uint8 /* 3 bits */ - if len((*z).KVs[zb0002].Key) == 0 { - zb0004Len-- - zb0004Mask |= 0x2 - } - if len((*z).KVs[zb0002].Value) == 0 { - zb0004Len-- - zb0004Mask |= 0x4 - } - // variable map header, size zb0004Len - o = append(o, 0x80|uint8(zb0004Len)) - if (zb0004Mask & 0x2) == 0 { // if not empty - // string "k" - o = append(o, 0xa1, 0x6b) - o = msgp.AppendBytes(o, (*z).KVs[zb0002].Key) - } - if (zb0004Mask & 0x4) == 0 { // if not empty - // string "v" - o = append(o, 0xa1, 0x76) - o = msgp.AppendBytes(o, (*z).KVs[zb0002].Value) - } + o = (*z).KVs[zb0002].MarshalMsg(o) } } } @@ -814,7 +618,7 @@ func (z *catchpointFileChunkV6) UnmarshalMsg(bts []byte) (o []byte, err error) { } else if (*z).Balances != nil && cap((*z).Balances) >= zb0005 { (*z).Balances = ((*z).Balances)[:zb0005] } else { - (*z).Balances = make([]encodedBalanceRecordV6, zb0005) + (*z).Balances = make([]encoded.BalanceRecordV6, zb0005) } for zb0001 := range (*z).Balances { bts, err = (*z).Balances[zb0001].UnmarshalMsg(bts) @@ -843,117 +647,13 @@ func (z *catchpointFileChunkV6) UnmarshalMsg(bts []byte) (o []byte, err error) { } else if (*z).KVs != nil && cap((*z).KVs) >= zb0007 { (*z).KVs = ((*z).KVs)[:zb0007] } else { - (*z).KVs = make([]encodedKVRecordV6, zb0007) + (*z).KVs = make([]encoded.KVRecordV6, zb0007) } for zb0002 := range (*z).KVs { - var zb0009 int - var zb0010 bool - zb0009, zb0010, bts, err = msgp.ReadMapHeaderBytes(bts) - if _, ok := err.(msgp.TypeError); ok { - zb0009, zb0010, bts, err = msgp.ReadArrayHeaderBytes(bts) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "KVs", zb0002) - return - } - if zb0009 > 0 { - zb0009-- - var zb0011 int - zb0011, err = msgp.ReadBytesBytesHeader(bts) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "KVs", zb0002, "struct-from-array", "Key") - return - } - if zb0011 > encodedKVRecordV6MaxKeyLength { - err = msgp.ErrOverflow(uint64(zb0011), uint64(encodedKVRecordV6MaxKeyLength)) - return - } - (*z).KVs[zb0002].Key, bts, err = msgp.ReadBytesBytes(bts, (*z).KVs[zb0002].Key) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "KVs", zb0002, "struct-from-array", "Key") - return - } - } - if zb0009 > 0 { - zb0009-- - var zb0012 int - zb0012, err = msgp.ReadBytesBytesHeader(bts) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "KVs", zb0002, "struct-from-array", "Value") - return - } - if zb0012 > encodedKVRecordV6MaxValueLength { - err = msgp.ErrOverflow(uint64(zb0012), uint64(encodedKVRecordV6MaxValueLength)) - return - } - (*z).KVs[zb0002].Value, bts, err = msgp.ReadBytesBytes(bts, (*z).KVs[zb0002].Value) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "KVs", zb0002, "struct-from-array", "Value") - return - } - } - if zb0009 > 0 { - err = msgp.ErrTooManyArrayFields(zb0009) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "KVs", zb0002, "struct-from-array") - return - } - } - } else { - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "KVs", zb0002) - return - } - if zb0010 { - (*z).KVs[zb0002] = encodedKVRecordV6{} - } - for zb0009 > 0 { - zb0009-- - field, bts, err = msgp.ReadMapKeyZC(bts) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "KVs", zb0002) - return - } - switch string(field) { - case "k": - var zb0013 int - zb0013, err = msgp.ReadBytesBytesHeader(bts) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "KVs", zb0002, "Key") - return - } - if zb0013 > encodedKVRecordV6MaxKeyLength { - err = msgp.ErrOverflow(uint64(zb0013), uint64(encodedKVRecordV6MaxKeyLength)) - return - } - (*z).KVs[zb0002].Key, bts, err = msgp.ReadBytesBytes(bts, (*z).KVs[zb0002].Key) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "KVs", zb0002, "Key") - return - } - case "v": - var zb0014 int - zb0014, err = msgp.ReadBytesBytesHeader(bts) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "KVs", zb0002, "Value") - return - } - if zb0014 > encodedKVRecordV6MaxValueLength { - err = msgp.ErrOverflow(uint64(zb0014), uint64(encodedKVRecordV6MaxValueLength)) - return - } - (*z).KVs[zb0002].Value, bts, err = msgp.ReadBytesBytes(bts, (*z).KVs[zb0002].Value) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "KVs", zb0002, "Value") - return - } - default: - err = msgp.ErrNoField(string(field)) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "KVs", zb0002) - return - } - } - } + bts, err = (*z).KVs[zb0002].UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "KVs", zb0002) + return } } } @@ -981,24 +681,24 @@ func (z *catchpointFileChunkV6) UnmarshalMsg(bts []byte) (o []byte, err error) { } switch string(field) { case "bl": - var zb0015 int - var zb0016 bool - zb0015, zb0016, bts, err = msgp.ReadArrayHeaderBytes(bts) + var zb0009 int + var zb0010 bool + zb0009, zb0010, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "Balances") return } - if zb0015 > BalancesPerCatchpointFileChunk { - err = msgp.ErrOverflow(uint64(zb0015), uint64(BalancesPerCatchpointFileChunk)) + if zb0009 > BalancesPerCatchpointFileChunk { + err = msgp.ErrOverflow(uint64(zb0009), uint64(BalancesPerCatchpointFileChunk)) err = msgp.WrapError(err, "Balances") return } - if zb0016 { + if zb0010 { (*z).Balances = nil - } else if (*z).Balances != nil && cap((*z).Balances) >= zb0015 { - (*z).Balances = ((*z).Balances)[:zb0015] + } else if (*z).Balances != nil && cap((*z).Balances) >= zb0009 { + (*z).Balances = ((*z).Balances)[:zb0009] } else { - (*z).Balances = make([]encodedBalanceRecordV6, zb0015) + (*z).Balances = make([]encoded.BalanceRecordV6, zb0009) } for zb0001 := range (*z).Balances { bts, err = (*z).Balances[zb0001].UnmarshalMsg(bts) @@ -1008,134 +708,30 @@ func (z *catchpointFileChunkV6) UnmarshalMsg(bts []byte) (o []byte, err error) { } } case "kv": - var zb0017 int - var zb0018 bool - zb0017, zb0018, bts, err = msgp.ReadArrayHeaderBytes(bts) + var zb0011 int + var zb0012 bool + zb0011, zb0012, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "KVs") return } - if zb0017 > BalancesPerCatchpointFileChunk { - err = msgp.ErrOverflow(uint64(zb0017), uint64(BalancesPerCatchpointFileChunk)) + if zb0011 > BalancesPerCatchpointFileChunk { + err = msgp.ErrOverflow(uint64(zb0011), uint64(BalancesPerCatchpointFileChunk)) err = msgp.WrapError(err, "KVs") return } - if zb0018 { + if zb0012 { (*z).KVs = nil - } else if (*z).KVs != nil && cap((*z).KVs) >= zb0017 { - (*z).KVs = ((*z).KVs)[:zb0017] + } else if (*z).KVs != nil && cap((*z).KVs) >= zb0011 { + (*z).KVs = ((*z).KVs)[:zb0011] } else { - (*z).KVs = make([]encodedKVRecordV6, zb0017) + (*z).KVs = make([]encoded.KVRecordV6, zb0011) } for zb0002 := range (*z).KVs { - var zb0019 int - var zb0020 bool - zb0019, zb0020, bts, err = msgp.ReadMapHeaderBytes(bts) - if _, ok := err.(msgp.TypeError); ok { - zb0019, zb0020, bts, err = msgp.ReadArrayHeaderBytes(bts) - if err != nil { - err = msgp.WrapError(err, "KVs", zb0002) - return - } - if zb0019 > 0 { - zb0019-- - var zb0021 int - zb0021, err = msgp.ReadBytesBytesHeader(bts) - if err != nil { - err = msgp.WrapError(err, "KVs", zb0002, "struct-from-array", "Key") - return - } - if zb0021 > encodedKVRecordV6MaxKeyLength { - err = msgp.ErrOverflow(uint64(zb0021), uint64(encodedKVRecordV6MaxKeyLength)) - return - } - (*z).KVs[zb0002].Key, bts, err = msgp.ReadBytesBytes(bts, (*z).KVs[zb0002].Key) - if err != nil { - err = msgp.WrapError(err, "KVs", zb0002, "struct-from-array", "Key") - return - } - } - if zb0019 > 0 { - zb0019-- - var zb0022 int - zb0022, err = msgp.ReadBytesBytesHeader(bts) - if err != nil { - err = msgp.WrapError(err, "KVs", zb0002, "struct-from-array", "Value") - return - } - if zb0022 > encodedKVRecordV6MaxValueLength { - err = msgp.ErrOverflow(uint64(zb0022), uint64(encodedKVRecordV6MaxValueLength)) - return - } - (*z).KVs[zb0002].Value, bts, err = msgp.ReadBytesBytes(bts, (*z).KVs[zb0002].Value) - if err != nil { - err = msgp.WrapError(err, "KVs", zb0002, "struct-from-array", "Value") - return - } - } - if zb0019 > 0 { - err = msgp.ErrTooManyArrayFields(zb0019) - if err != nil { - err = msgp.WrapError(err, "KVs", zb0002, "struct-from-array") - return - } - } - } else { - if err != nil { - err = msgp.WrapError(err, "KVs", zb0002) - return - } - if zb0020 { - (*z).KVs[zb0002] = encodedKVRecordV6{} - } - for zb0019 > 0 { - zb0019-- - field, bts, err = msgp.ReadMapKeyZC(bts) - if err != nil { - err = msgp.WrapError(err, "KVs", zb0002) - return - } - switch string(field) { - case "k": - var zb0023 int - zb0023, err = msgp.ReadBytesBytesHeader(bts) - if err != nil { - err = msgp.WrapError(err, "KVs", zb0002, "Key") - return - } - if zb0023 > encodedKVRecordV6MaxKeyLength { - err = msgp.ErrOverflow(uint64(zb0023), uint64(encodedKVRecordV6MaxKeyLength)) - return - } - (*z).KVs[zb0002].Key, bts, err = msgp.ReadBytesBytes(bts, (*z).KVs[zb0002].Key) - if err != nil { - err = msgp.WrapError(err, "KVs", zb0002, "Key") - return - } - case "v": - var zb0024 int - zb0024, err = msgp.ReadBytesBytesHeader(bts) - if err != nil { - err = msgp.WrapError(err, "KVs", zb0002, "Value") - return - } - if zb0024 > encodedKVRecordV6MaxValueLength { - err = msgp.ErrOverflow(uint64(zb0024), uint64(encodedKVRecordV6MaxValueLength)) - return - } - (*z).KVs[zb0002].Value, bts, err = msgp.ReadBytesBytes(bts, (*z).KVs[zb0002].Value) - if err != nil { - err = msgp.WrapError(err, "KVs", zb0002, "Value") - return - } - default: - err = msgp.ErrNoField(string(field)) - if err != nil { - err = msgp.WrapError(err, "KVs", zb0002) - return - } - } - } + bts, err = (*z).KVs[zb0002].UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "KVs", zb0002) + return } } default: @@ -1164,7 +760,7 @@ func (z *catchpointFileChunkV6) Msgsize() (s int) { } s += 3 + msgp.ArrayHeaderSize for zb0002 := range (*z).KVs { - s += 1 + 2 + msgp.BytesPrefixSize + len((*z).KVs[zb0002].Key) + 2 + msgp.BytesPrefixSize + len((*z).KVs[zb0002].Value) + s += (*z).KVs[zb0002].Msgsize() } return } @@ -1173,555 +769,3 @@ func (z *catchpointFileChunkV6) Msgsize() (s int) { func (z *catchpointFileChunkV6) MsgIsZero() bool { return (len((*z).Balances) == 0) && (len((*z).KVs) == 0) } - -// MarshalMsg implements msgp.Marshaler -func (z *encodedBalanceRecordV5) MarshalMsg(b []byte) (o []byte) { - o = msgp.Require(b, z.Msgsize()) - // omitempty: check for empty values - zb0001Len := uint32(2) - var zb0001Mask uint8 /* 3 bits */ - if (*z).AccountData.MsgIsZero() { - zb0001Len-- - zb0001Mask |= 0x2 - } - if (*z).Address.MsgIsZero() { - zb0001Len-- - zb0001Mask |= 0x4 - } - // variable map header, size zb0001Len - o = append(o, 0x80|uint8(zb0001Len)) - if zb0001Len != 0 { - if (zb0001Mask & 0x2) == 0 { // if not empty - // string "ad" - o = append(o, 0xa2, 0x61, 0x64) - o = (*z).AccountData.MarshalMsg(o) - } - if (zb0001Mask & 0x4) == 0 { // if not empty - // string "pk" - o = append(o, 0xa2, 0x70, 0x6b) - o = (*z).Address.MarshalMsg(o) - } - } - return -} - -func (_ *encodedBalanceRecordV5) CanMarshalMsg(z interface{}) bool { - _, ok := (z).(*encodedBalanceRecordV5) - return ok -} - -// UnmarshalMsg implements msgp.Unmarshaler -func (z *encodedBalanceRecordV5) UnmarshalMsg(bts []byte) (o []byte, err error) { - var field []byte - _ = field - var zb0001 int - var zb0002 bool - zb0001, zb0002, bts, err = msgp.ReadMapHeaderBytes(bts) - if _, ok := err.(msgp.TypeError); ok { - zb0001, zb0002, bts, err = msgp.ReadArrayHeaderBytes(bts) - if err != nil { - err = msgp.WrapError(err) - return - } - if zb0001 > 0 { - zb0001-- - bts, err = (*z).Address.UnmarshalMsg(bts) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "Address") - return - } - } - if zb0001 > 0 { - zb0001-- - bts, err = (*z).AccountData.UnmarshalMsg(bts) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "AccountData") - return - } - } - if zb0001 > 0 { - err = msgp.ErrTooManyArrayFields(zb0001) - if err != nil { - err = msgp.WrapError(err, "struct-from-array") - return - } - } - } else { - if err != nil { - err = msgp.WrapError(err) - return - } - if zb0002 { - (*z) = encodedBalanceRecordV5{} - } - for zb0001 > 0 { - zb0001-- - field, bts, err = msgp.ReadMapKeyZC(bts) - if err != nil { - err = msgp.WrapError(err) - return - } - switch string(field) { - case "pk": - bts, err = (*z).Address.UnmarshalMsg(bts) - if err != nil { - err = msgp.WrapError(err, "Address") - return - } - case "ad": - bts, err = (*z).AccountData.UnmarshalMsg(bts) - if err != nil { - err = msgp.WrapError(err, "AccountData") - return - } - default: - err = msgp.ErrNoField(string(field)) - if err != nil { - err = msgp.WrapError(err) - return - } - } - } - } - o = bts - return -} - -func (_ *encodedBalanceRecordV5) CanUnmarshalMsg(z interface{}) bool { - _, ok := (z).(*encodedBalanceRecordV5) - return ok -} - -// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message -func (z *encodedBalanceRecordV5) Msgsize() (s int) { - s = 1 + 3 + (*z).Address.Msgsize() + 3 + (*z).AccountData.Msgsize() - return -} - -// MsgIsZero returns whether this is a zero value -func (z *encodedBalanceRecordV5) MsgIsZero() bool { - return ((*z).Address.MsgIsZero()) && ((*z).AccountData.MsgIsZero()) -} - -// MarshalMsg implements msgp.Marshaler -func (z *encodedBalanceRecordV6) MarshalMsg(b []byte) (o []byte) { - o = msgp.Require(b, z.Msgsize()) - // omitempty: check for empty values - zb0003Len := uint32(4) - var zb0003Mask uint8 /* 5 bits */ - if (*z).Address.MsgIsZero() { - zb0003Len-- - zb0003Mask |= 0x2 - } - if (*z).AccountData.MsgIsZero() { - zb0003Len-- - zb0003Mask |= 0x4 - } - if len((*z).Resources) == 0 { - zb0003Len-- - zb0003Mask |= 0x8 - } - if (*z).ExpectingMoreEntries == false { - zb0003Len-- - zb0003Mask |= 0x10 - } - // variable map header, size zb0003Len - o = append(o, 0x80|uint8(zb0003Len)) - if zb0003Len != 0 { - if (zb0003Mask & 0x2) == 0 { // if not empty - // string "a" - o = append(o, 0xa1, 0x61) - o = (*z).Address.MarshalMsg(o) - } - if (zb0003Mask & 0x4) == 0 { // if not empty - // string "b" - o = append(o, 0xa1, 0x62) - o = (*z).AccountData.MarshalMsg(o) - } - if (zb0003Mask & 0x8) == 0 { // if not empty - // string "c" - o = append(o, 0xa1, 0x63) - if (*z).Resources == nil { - o = msgp.AppendNil(o) - } else { - o = msgp.AppendMapHeader(o, uint32(len((*z).Resources))) - } - zb0001_keys := make([]uint64, 0, len((*z).Resources)) - for zb0001 := range (*z).Resources { - zb0001_keys = append(zb0001_keys, zb0001) - } - sort.Sort(SortUint64(zb0001_keys)) - for _, zb0001 := range zb0001_keys { - zb0002 := (*z).Resources[zb0001] - _ = zb0002 - o = msgp.AppendUint64(o, zb0001) - o = zb0002.MarshalMsg(o) - } - } - if (zb0003Mask & 0x10) == 0 { // if not empty - // string "e" - o = append(o, 0xa1, 0x65) - o = msgp.AppendBool(o, (*z).ExpectingMoreEntries) - } - } - return -} - -func (_ *encodedBalanceRecordV6) CanMarshalMsg(z interface{}) bool { - _, ok := (z).(*encodedBalanceRecordV6) - return ok -} - -// UnmarshalMsg implements msgp.Unmarshaler -func (z *encodedBalanceRecordV6) UnmarshalMsg(bts []byte) (o []byte, err error) { - var field []byte - _ = field - var zb0003 int - var zb0004 bool - zb0003, zb0004, bts, err = msgp.ReadMapHeaderBytes(bts) - if _, ok := err.(msgp.TypeError); ok { - zb0003, zb0004, bts, err = msgp.ReadArrayHeaderBytes(bts) - if err != nil { - err = msgp.WrapError(err) - return - } - if zb0003 > 0 { - zb0003-- - bts, err = (*z).Address.UnmarshalMsg(bts) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "Address") - return - } - } - if zb0003 > 0 { - zb0003-- - bts, err = (*z).AccountData.UnmarshalMsg(bts) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "AccountData") - return - } - } - if zb0003 > 0 { - zb0003-- - var zb0005 int - var zb0006 bool - zb0005, zb0006, bts, err = msgp.ReadMapHeaderBytes(bts) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "Resources") - return - } - if zb0005 > resourcesPerCatchpointFileChunkBackwardCompatible { - err = msgp.ErrOverflow(uint64(zb0005), uint64(resourcesPerCatchpointFileChunkBackwardCompatible)) - err = msgp.WrapError(err, "struct-from-array", "Resources") - return - } - if zb0006 { - (*z).Resources = nil - } else if (*z).Resources == nil { - (*z).Resources = make(map[uint64]msgp.Raw, zb0005) - } - for zb0005 > 0 { - var zb0001 uint64 - var zb0002 msgp.Raw - zb0005-- - zb0001, bts, err = msgp.ReadUint64Bytes(bts) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "Resources") - return - } - bts, err = zb0002.UnmarshalMsg(bts) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "Resources", zb0001) - return - } - (*z).Resources[zb0001] = zb0002 - } - } - if zb0003 > 0 { - zb0003-- - (*z).ExpectingMoreEntries, bts, err = msgp.ReadBoolBytes(bts) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "ExpectingMoreEntries") - return - } - } - if zb0003 > 0 { - err = msgp.ErrTooManyArrayFields(zb0003) - if err != nil { - err = msgp.WrapError(err, "struct-from-array") - return - } - } - } else { - if err != nil { - err = msgp.WrapError(err) - return - } - if zb0004 { - (*z) = encodedBalanceRecordV6{} - } - for zb0003 > 0 { - zb0003-- - field, bts, err = msgp.ReadMapKeyZC(bts) - if err != nil { - err = msgp.WrapError(err) - return - } - switch string(field) { - case "a": - bts, err = (*z).Address.UnmarshalMsg(bts) - if err != nil { - err = msgp.WrapError(err, "Address") - return - } - case "b": - bts, err = (*z).AccountData.UnmarshalMsg(bts) - if err != nil { - err = msgp.WrapError(err, "AccountData") - return - } - case "c": - var zb0007 int - var zb0008 bool - zb0007, zb0008, bts, err = msgp.ReadMapHeaderBytes(bts) - if err != nil { - err = msgp.WrapError(err, "Resources") - return - } - if zb0007 > resourcesPerCatchpointFileChunkBackwardCompatible { - err = msgp.ErrOverflow(uint64(zb0007), uint64(resourcesPerCatchpointFileChunkBackwardCompatible)) - err = msgp.WrapError(err, "Resources") - return - } - if zb0008 { - (*z).Resources = nil - } else if (*z).Resources == nil { - (*z).Resources = make(map[uint64]msgp.Raw, zb0007) - } - for zb0007 > 0 { - var zb0001 uint64 - var zb0002 msgp.Raw - zb0007-- - zb0001, bts, err = msgp.ReadUint64Bytes(bts) - if err != nil { - err = msgp.WrapError(err, "Resources") - return - } - bts, err = zb0002.UnmarshalMsg(bts) - if err != nil { - err = msgp.WrapError(err, "Resources", zb0001) - return - } - (*z).Resources[zb0001] = zb0002 - } - case "e": - (*z).ExpectingMoreEntries, bts, err = msgp.ReadBoolBytes(bts) - if err != nil { - err = msgp.WrapError(err, "ExpectingMoreEntries") - return - } - default: - err = msgp.ErrNoField(string(field)) - if err != nil { - err = msgp.WrapError(err) - return - } - } - } - } - o = bts - return -} - -func (_ *encodedBalanceRecordV6) CanUnmarshalMsg(z interface{}) bool { - _, ok := (z).(*encodedBalanceRecordV6) - return ok -} - -// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message -func (z *encodedBalanceRecordV6) Msgsize() (s int) { - s = 1 + 2 + (*z).Address.Msgsize() + 2 + (*z).AccountData.Msgsize() + 2 + msgp.MapHeaderSize - if (*z).Resources != nil { - for zb0001, zb0002 := range (*z).Resources { - _ = zb0001 - _ = zb0002 - s += 0 + msgp.Uint64Size + zb0002.Msgsize() - } - } - s += 2 + msgp.BoolSize - return -} - -// MsgIsZero returns whether this is a zero value -func (z *encodedBalanceRecordV6) MsgIsZero() bool { - return ((*z).Address.MsgIsZero()) && ((*z).AccountData.MsgIsZero()) && (len((*z).Resources) == 0) && ((*z).ExpectingMoreEntries == false) -} - -// MarshalMsg implements msgp.Marshaler -func (z *encodedKVRecordV6) MarshalMsg(b []byte) (o []byte) { - o = msgp.Require(b, z.Msgsize()) - // omitempty: check for empty values - zb0001Len := uint32(2) - var zb0001Mask uint8 /* 3 bits */ - if len((*z).Key) == 0 { - zb0001Len-- - zb0001Mask |= 0x2 - } - if len((*z).Value) == 0 { - zb0001Len-- - zb0001Mask |= 0x4 - } - // variable map header, size zb0001Len - o = append(o, 0x80|uint8(zb0001Len)) - if zb0001Len != 0 { - if (zb0001Mask & 0x2) == 0 { // if not empty - // string "k" - o = append(o, 0xa1, 0x6b) - o = msgp.AppendBytes(o, (*z).Key) - } - if (zb0001Mask & 0x4) == 0 { // if not empty - // string "v" - o = append(o, 0xa1, 0x76) - o = msgp.AppendBytes(o, (*z).Value) - } - } - return -} - -func (_ *encodedKVRecordV6) CanMarshalMsg(z interface{}) bool { - _, ok := (z).(*encodedKVRecordV6) - return ok -} - -// UnmarshalMsg implements msgp.Unmarshaler -func (z *encodedKVRecordV6) UnmarshalMsg(bts []byte) (o []byte, err error) { - var field []byte - _ = field - var zb0001 int - var zb0002 bool - zb0001, zb0002, bts, err = msgp.ReadMapHeaderBytes(bts) - if _, ok := err.(msgp.TypeError); ok { - zb0001, zb0002, bts, err = msgp.ReadArrayHeaderBytes(bts) - if err != nil { - err = msgp.WrapError(err) - return - } - if zb0001 > 0 { - zb0001-- - var zb0003 int - zb0003, err = msgp.ReadBytesBytesHeader(bts) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "Key") - return - } - if zb0003 > encodedKVRecordV6MaxKeyLength { - err = msgp.ErrOverflow(uint64(zb0003), uint64(encodedKVRecordV6MaxKeyLength)) - return - } - (*z).Key, bts, err = msgp.ReadBytesBytes(bts, (*z).Key) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "Key") - return - } - } - if zb0001 > 0 { - zb0001-- - var zb0004 int - zb0004, err = msgp.ReadBytesBytesHeader(bts) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "Value") - return - } - if zb0004 > encodedKVRecordV6MaxValueLength { - err = msgp.ErrOverflow(uint64(zb0004), uint64(encodedKVRecordV6MaxValueLength)) - return - } - (*z).Value, bts, err = msgp.ReadBytesBytes(bts, (*z).Value) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "Value") - return - } - } - if zb0001 > 0 { - err = msgp.ErrTooManyArrayFields(zb0001) - if err != nil { - err = msgp.WrapError(err, "struct-from-array") - return - } - } - } else { - if err != nil { - err = msgp.WrapError(err) - return - } - if zb0002 { - (*z) = encodedKVRecordV6{} - } - for zb0001 > 0 { - zb0001-- - field, bts, err = msgp.ReadMapKeyZC(bts) - if err != nil { - err = msgp.WrapError(err) - return - } - switch string(field) { - case "k": - var zb0005 int - zb0005, err = msgp.ReadBytesBytesHeader(bts) - if err != nil { - err = msgp.WrapError(err, "Key") - return - } - if zb0005 > encodedKVRecordV6MaxKeyLength { - err = msgp.ErrOverflow(uint64(zb0005), uint64(encodedKVRecordV6MaxKeyLength)) - return - } - (*z).Key, bts, err = msgp.ReadBytesBytes(bts, (*z).Key) - if err != nil { - err = msgp.WrapError(err, "Key") - return - } - case "v": - var zb0006 int - zb0006, err = msgp.ReadBytesBytesHeader(bts) - if err != nil { - err = msgp.WrapError(err, "Value") - return - } - if zb0006 > encodedKVRecordV6MaxValueLength { - err = msgp.ErrOverflow(uint64(zb0006), uint64(encodedKVRecordV6MaxValueLength)) - return - } - (*z).Value, bts, err = msgp.ReadBytesBytes(bts, (*z).Value) - if err != nil { - err = msgp.WrapError(err, "Value") - return - } - default: - err = msgp.ErrNoField(string(field)) - if err != nil { - err = msgp.WrapError(err) - return - } - } - } - } - o = bts - return -} - -func (_ *encodedKVRecordV6) CanUnmarshalMsg(z interface{}) bool { - _, ok := (z).(*encodedKVRecordV6) - return ok -} - -// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message -func (z *encodedKVRecordV6) Msgsize() (s int) { - s = 1 + 2 + msgp.BytesPrefixSize + len((*z).Key) + 2 + msgp.BytesPrefixSize + len((*z).Value) - return -} - -// MsgIsZero returns whether this is a zero value -func (z *encodedKVRecordV6) MsgIsZero() bool { - return (len((*z).Key) == 0) && (len((*z).Value) == 0) -} diff --git a/ledger/msgp_gen_test.go b/ledger/msgp_gen_test.go index e7739ad972..de29b5f11d 100644 --- a/ledger/msgp_gen_test.go +++ b/ledger/msgp_gen_test.go @@ -193,183 +193,3 @@ func BenchmarkUnmarshalcatchpointFileChunkV6(b *testing.B) { } } } - -func TestMarshalUnmarshalencodedBalanceRecordV5(t *testing.T) { - partitiontest.PartitionTest(t) - v := encodedBalanceRecordV5{} - bts := v.MarshalMsg(nil) - left, err := v.UnmarshalMsg(bts) - if err != nil { - t.Fatal(err) - } - if len(left) > 0 { - t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left) - } - - left, err = msgp.Skip(bts) - if err != nil { - t.Fatal(err) - } - if len(left) > 0 { - t.Errorf("%d bytes left over after Skip(): %q", len(left), left) - } -} - -func TestRandomizedEncodingencodedBalanceRecordV5(t *testing.T) { - protocol.RunEncodingTest(t, &encodedBalanceRecordV5{}) -} - -func BenchmarkMarshalMsgencodedBalanceRecordV5(b *testing.B) { - v := encodedBalanceRecordV5{} - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - v.MarshalMsg(nil) - } -} - -func BenchmarkAppendMsgencodedBalanceRecordV5(b *testing.B) { - v := encodedBalanceRecordV5{} - bts := make([]byte, 0, v.Msgsize()) - bts = v.MarshalMsg(bts[0:0]) - b.SetBytes(int64(len(bts))) - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - bts = v.MarshalMsg(bts[0:0]) - } -} - -func BenchmarkUnmarshalencodedBalanceRecordV5(b *testing.B) { - v := encodedBalanceRecordV5{} - bts := v.MarshalMsg(nil) - b.ReportAllocs() - b.SetBytes(int64(len(bts))) - b.ResetTimer() - for i := 0; i < b.N; i++ { - _, err := v.UnmarshalMsg(bts) - if err != nil { - b.Fatal(err) - } - } -} - -func TestMarshalUnmarshalencodedBalanceRecordV6(t *testing.T) { - partitiontest.PartitionTest(t) - v := encodedBalanceRecordV6{} - bts := v.MarshalMsg(nil) - left, err := v.UnmarshalMsg(bts) - if err != nil { - t.Fatal(err) - } - if len(left) > 0 { - t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left) - } - - left, err = msgp.Skip(bts) - if err != nil { - t.Fatal(err) - } - if len(left) > 0 { - t.Errorf("%d bytes left over after Skip(): %q", len(left), left) - } -} - -func TestRandomizedEncodingencodedBalanceRecordV6(t *testing.T) { - protocol.RunEncodingTest(t, &encodedBalanceRecordV6{}) -} - -func BenchmarkMarshalMsgencodedBalanceRecordV6(b *testing.B) { - v := encodedBalanceRecordV6{} - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - v.MarshalMsg(nil) - } -} - -func BenchmarkAppendMsgencodedBalanceRecordV6(b *testing.B) { - v := encodedBalanceRecordV6{} - bts := make([]byte, 0, v.Msgsize()) - bts = v.MarshalMsg(bts[0:0]) - b.SetBytes(int64(len(bts))) - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - bts = v.MarshalMsg(bts[0:0]) - } -} - -func BenchmarkUnmarshalencodedBalanceRecordV6(b *testing.B) { - v := encodedBalanceRecordV6{} - bts := v.MarshalMsg(nil) - b.ReportAllocs() - b.SetBytes(int64(len(bts))) - b.ResetTimer() - for i := 0; i < b.N; i++ { - _, err := v.UnmarshalMsg(bts) - if err != nil { - b.Fatal(err) - } - } -} - -func TestMarshalUnmarshalencodedKVRecordV6(t *testing.T) { - partitiontest.PartitionTest(t) - v := encodedKVRecordV6{} - bts := v.MarshalMsg(nil) - left, err := v.UnmarshalMsg(bts) - if err != nil { - t.Fatal(err) - } - if len(left) > 0 { - t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left) - } - - left, err = msgp.Skip(bts) - if err != nil { - t.Fatal(err) - } - if len(left) > 0 { - t.Errorf("%d bytes left over after Skip(): %q", len(left), left) - } -} - -func TestRandomizedEncodingencodedKVRecordV6(t *testing.T) { - protocol.RunEncodingTest(t, &encodedKVRecordV6{}) -} - -func BenchmarkMarshalMsgencodedKVRecordV6(b *testing.B) { - v := encodedKVRecordV6{} - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - v.MarshalMsg(nil) - } -} - -func BenchmarkAppendMsgencodedKVRecordV6(b *testing.B) { - v := encodedKVRecordV6{} - bts := make([]byte, 0, v.Msgsize()) - bts = v.MarshalMsg(bts[0:0]) - b.SetBytes(int64(len(bts))) - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - bts = v.MarshalMsg(bts[0:0]) - } -} - -func BenchmarkUnmarshalencodedKVRecordV6(b *testing.B) { - v := encodedKVRecordV6{} - bts := v.MarshalMsg(nil) - b.ReportAllocs() - b.SetBytes(int64(len(bts))) - b.ResetTimer() - for i := 0; i < b.N; i++ { - _, err := v.UnmarshalMsg(bts) - if err != nil { - b.Fatal(err) - } - } -} diff --git a/ledger/notifier.go b/ledger/notifier.go index b7d220ac32..b9660abf9d 100644 --- a/ledger/notifier.go +++ b/ledger/notifier.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -28,11 +28,6 @@ import ( "github.com/algorand/go-algorand/ledger/ledgercore" ) -// BlockListener represents an object that needs to get notified on new blocks. -type BlockListener interface { - OnNewBlock(block bookkeeping.Block, delta ledgercore.StateDelta) -} - type blockDeltaPair struct { block bookkeeping.Block delta ledgercore.StateDelta @@ -41,7 +36,7 @@ type blockDeltaPair struct { type blockNotifier struct { mu deadlock.Mutex cond *sync.Cond - listeners []BlockListener + listeners []ledgercore.BlockListener pendingBlocks []blockDeltaPair running bool // closing is the waitgroup used to synchronize closing the worker goroutine. It's being increased during loadFromDisk, and the worker is responsible to call Done on it once it's aborting it's goroutine. The close function waits on this to complete. @@ -96,7 +91,7 @@ func (bn *blockNotifier) loadFromDisk(l ledgerForTracker, _ basics.Round) error return nil } -func (bn *blockNotifier) register(listeners []BlockListener) { +func (bn *blockNotifier) register(listeners []ledgercore.BlockListener) { bn.mu.Lock() defer bn.mu.Unlock() diff --git a/ledger/onlineaccountscache.go b/ledger/onlineaccountscache.go index 4d6c976027..e983ff024f 100644 --- a/ledger/onlineaccountscache.go +++ b/ledger/onlineaccountscache.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/onlineaccountscache_test.go b/ledger/onlineaccountscache_test.go index 23010b35d8..6cb97f6101 100644 --- a/ledger/onlineaccountscache_test.go +++ b/ledger/onlineaccountscache_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/onlinetopheap.go b/ledger/onlinetopheap.go index c7182b9cd9..6d3ba155d9 100644 --- a/ledger/onlinetopheap.go +++ b/ledger/onlinetopheap.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/onlinetopheap_test.go b/ledger/onlinetopheap_test.go index 7d72d0d410..10a068aa97 100644 --- a/ledger/onlinetopheap_test.go +++ b/ledger/onlinetopheap_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/perf_test.go b/ledger/perf_test.go index 50c407c676..68e9ab76c9 100644 --- a/ledger/perf_test.go +++ b/ledger/perf_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/persistedaccts_list.go b/ledger/persistedaccts_list.go index c7c884a860..f29f2857d0 100644 --- a/ledger/persistedaccts_list.go +++ b/ledger/persistedaccts_list.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/persistedaccts_list_test.go b/ledger/persistedaccts_list_test.go index 9c7b0d69ce..41881520cc 100644 --- a/ledger/persistedaccts_list_test.go +++ b/ledger/persistedaccts_list_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/persistedkvs.go b/ledger/persistedkvs.go index 34f3c36ecb..b97234f343 100644 --- a/ledger/persistedkvs.go +++ b/ledger/persistedkvs.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/persistedkvs_test.go b/ledger/persistedkvs_test.go index eb5ed9dff7..5d9620d27b 100644 --- a/ledger/persistedkvs_test.go +++ b/ledger/persistedkvs_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/persistedonlineaccts_list.go b/ledger/persistedonlineaccts_list.go index e5775498d1..9a00695abe 100644 --- a/ledger/persistedonlineaccts_list.go +++ b/ledger/persistedonlineaccts_list.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/persistedonlineaccts_list_test.go b/ledger/persistedonlineaccts_list_test.go index 7e4a10721b..56184fd2e7 100644 --- a/ledger/persistedonlineaccts_list_test.go +++ b/ledger/persistedonlineaccts_list_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/persistedresources_list.go b/ledger/persistedresources_list.go index baa7ac351a..2bf9cb6a2e 100644 --- a/ledger/persistedresources_list.go +++ b/ledger/persistedresources_list.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/persistedresources_list_test.go b/ledger/persistedresources_list_test.go index 5261eb42c7..484af13956 100644 --- a/ledger/persistedresources_list_test.go +++ b/ledger/persistedresources_list_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/roundlru.go b/ledger/roundlru.go index cfc4675ee2..43d8639872 100644 --- a/ledger/roundlru.go +++ b/ledger/roundlru.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/roundlru_test.go b/ledger/roundlru_test.go index ef8ac03afb..8033f34399 100644 --- a/ledger/roundlru_test.go +++ b/ledger/roundlru_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/simple_test.go b/ledger/simple_test.go index 22781c70f3..e934e3f013 100644 --- a/ledger/simple_test.go +++ b/ledger/simple_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -116,7 +116,7 @@ func txgroup(t testing.TB, ledger *Ledger, eval *internal.BlockEvaluator, txns . for _, txn := range txns { fillDefaults(t, ledger, eval, txn) } - txgroup := txntest.SignedTxns(txns...) + txgroup := txntest.Group(txns...) return eval.TransactionGroup(transactions.WrapSignedTxnsWithAD(txgroup)) } diff --git a/ledger/simulation/simulator.go b/ledger/simulation/simulator.go new file mode 100644 index 0000000000..b559e989f4 --- /dev/null +++ b/ledger/simulation/simulator.go @@ -0,0 +1,217 @@ +// Copyright (C) 2019-2023 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package simulation + +import ( + "errors" + + "github.com/algorand/go-algorand/crypto" + "github.com/algorand/go-algorand/data" + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/data/bookkeeping" + "github.com/algorand/go-algorand/data/transactions" + "github.com/algorand/go-algorand/data/transactions/logic" + "github.com/algorand/go-algorand/data/transactions/verify" + "github.com/algorand/go-algorand/ledger/ledgercore" + "github.com/algorand/go-algorand/protocol" +) + +// ============================== +// > Simulator Ledger +// ============================== + +// simulatorLedger patches the ledger interface to use a constant latest round. +type simulatorLedger struct { + *data.Ledger + start basics.Round +} + +// Latest is part of the LedgerForSimulator interface. +// We override this to use the set latest to prevent racing with the network +func (l simulatorLedger) Latest() basics.Round { + return l.start +} + +// LookupLatest would implicitly use the latest round in the _underlying_ +// Ledger, it would give wrong results if that ledger has moved forward. But it +// should never be called, as the REST API is the only code using this function, +// and the REST API should never have access to a simulatorLedger. +func (l simulatorLedger) LookupLatest(addr basics.Address) (basics.AccountData, basics.Round, basics.MicroAlgos, error) { + err := errors.New("unexpected call to LookupLatest") + return basics.AccountData{}, 0, basics.MicroAlgos{}, err +} + +// ============================== +// > Simulator Tracer +// ============================== + +type evalTracer struct { + logic.NullEvalTracer +} + +func makeTracer() logic.EvalTracer { + return &evalTracer{} +} + +// ============================== +// > Simulator Errors +// ============================== + +// SimulatorError is the base error type for all simulator errors. +type SimulatorError struct { + err error +} + +func (s SimulatorError) Error() string { + return s.err.Error() +} + +func (s SimulatorError) Unwrap() error { + return s.err +} + +// InvalidTxGroupError occurs when an invalid transaction group was submitted to the simulator. +type InvalidTxGroupError struct { + SimulatorError +} + +// EvalFailureError represents an error that occurred during evaluation. +type EvalFailureError struct { + SimulatorError +} + +// ============================== +// > Simulator +// ============================== + +// Simulator is a transaction group simulator for the block evaluator. +type Simulator struct { + ledger simulatorLedger +} + +// MakeSimulator creates a new simulator from a ledger. +func MakeSimulator(ledger *data.Ledger) *Simulator { + return &Simulator{ + ledger: simulatorLedger{ledger, ledger.Latest()}, + } +} + +func txnHasNoSignature(txn transactions.SignedTxn) bool { + return txn.Sig.Blank() && txn.Msig.Blank() && txn.Lsig.Blank() +} + +// A randomly generated private key. The actual value does not matter, as long as this is a valid +// private key. +var proxySigner = crypto.PrivateKey{ + 128, 128, 92, 23, 212, 119, 175, 51, 157, 2, 165, + 215, 137, 37, 82, 42, 52, 227, 54, 41, 243, 67, + 141, 76, 208, 17, 199, 17, 140, 46, 113, 0, 159, + 50, 105, 52, 77, 104, 118, 200, 104, 220, 105, 21, + 147, 162, 191, 236, 115, 201, 197, 128, 8, 91, 224, + 78, 104, 209, 2, 185, 110, 28, 42, 97, +} + +// check verifies that the transaction is well-formed and has valid or missing signatures. +// An invalid transaction group error is returned if the transaction is not well-formed or there are invalid signatures. +// To make things easier, we support submitting unsigned transactions and will respond whether signatures are missing. +func (s Simulator) check(hdr bookkeeping.BlockHeader, txgroup []transactions.SignedTxn, debugger logic.EvalTracer) (bool, error) { + proxySignerSecrets, err := crypto.SecretKeyToSignatureSecrets(proxySigner) + if err != nil { + return false, err + } + + // Find and prep any transactions that are missing signatures. We will modify a copy of these + // transactions to pass signature verification. The modifications will not affect the input + // txgroup slice. + // + // Note: currently we only support missing transaction signatures, but it should be possible to + // support unsigned delegated LogicSigs as well. A single-signature unsigned delegated LogicSig + // is indistinguishable from an escrow LogicSig, so we would need to decide on another way of + // denoting that a LogicSig's delegation signature is omitted, e.g. by setting all the bits of + // the signature. + missingSigs := make([]int, 0, len(txgroup)) + txnsToVerify := make([]transactions.SignedTxn, len(txgroup)) + for i, stxn := range txgroup { + if stxn.Txn.Type == protocol.StateProofTx { + return false, errors.New("cannot simulate StateProof transactions") + } + if txnHasNoSignature(stxn) { + missingSigs = append(missingSigs, i) + + // Replace the signed txn with one signed by the proxySigner. At evaluation this would + // raise an error, since the proxySigner's public key likely does not have authority + // over the sender's account. However, this will pass validation, since the signature + // itself is valid. + txnsToVerify[i] = stxn.Txn.Sign(proxySignerSecrets) + } else { + txnsToVerify[i] = stxn + } + } + + // Verify the signed transactions are well-formed and have valid signatures + _, err = verify.TxnGroupWithTracer(txnsToVerify, &hdr, nil, s.ledger, debugger) + if err != nil { + return false, InvalidTxGroupError{SimulatorError{err}} + } + + return len(missingSigs) != 0, nil +} + +func (s Simulator) evaluate(hdr bookkeeping.BlockHeader, stxns []transactions.SignedTxn, tracer logic.EvalTracer) (*ledgercore.ValidatedBlock, error) { + // s.ledger has 'StartEvaluator' because *data.Ledger is embedded in the simulatorLedger + // and data.Ledger embeds *ledger.Ledger + eval, err := s.ledger.StartEvaluator(hdr, len(stxns), 0) + if err != nil { + return nil, err + } + eval.Tracer = tracer + + group := transactions.WrapSignedTxnsWithAD(stxns) + + err = eval.TransactionGroup(group) + if err != nil { + return nil, EvalFailureError{SimulatorError{err}} + } + + // Finally, process any pending end-of-block state changes. + vb, err := eval.GenerateBlock() + if err != nil { + return nil, err + } + + return vb, nil +} + +// Simulate simulates a transaction group using the simulator. Will error if the transaction group is not well-formed. +func (s Simulator) Simulate(txgroup []transactions.SignedTxn) (*ledgercore.ValidatedBlock, bool, error) { + prevBlockHdr, err := s.ledger.BlockHdr(s.ledger.start) + if err != nil { + return nil, false, err + } + nextBlock := bookkeeping.MakeBlock(prevBlockHdr) + hdr := nextBlock.BlockHeader + simulatorTracer := makeTracer() + + // check that the transaction is well-formed and mark whether signatures are missing + missingSignatures, err := s.check(hdr, txgroup, simulatorTracer) + if err != nil { + return nil, false, err + } + + vb, err := s.evaluate(hdr, txgroup, simulatorTracer) + return vb, missingSignatures, err +} diff --git a/ledger/simulation/simulator_test.go b/ledger/simulation/simulator_test.go new file mode 100644 index 0000000000..d029109197 --- /dev/null +++ b/ledger/simulation/simulator_test.go @@ -0,0 +1,746 @@ +// Copyright (C) 2019-2023 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package simulation_test + +import ( + "encoding/binary" + "fmt" + "reflect" + "testing" + + "github.com/algorand/go-algorand/config" + "github.com/algorand/go-algorand/crypto" + "github.com/algorand/go-algorand/data" + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/data/transactions" + "github.com/algorand/go-algorand/data/transactions/logic" + + "github.com/algorand/go-algorand/ledger" + "github.com/algorand/go-algorand/ledger/internal" + "github.com/algorand/go-algorand/ledger/simulation" + ledgertesting "github.com/algorand/go-algorand/ledger/testing" + "github.com/algorand/go-algorand/libgoal" + "github.com/algorand/go-algorand/logging" + "github.com/algorand/go-algorand/protocol" + "github.com/algorand/go-algorand/test/partitiontest" + "github.com/stretchr/testify/require" +) + +// ============================== +// > Simulation Test Helpers +// ============================== + +type account struct { + addr basics.Address + sk *crypto.SignatureSecrets + acctData basics.AccountData +} + +func prepareSimulatorTest(t *testing.T) (l *data.Ledger, accounts []account, makeTxnHeader func(sender basics.Address) transactions.Header) { + genesisInitState, keys := ledgertesting.GenerateInitState(t, protocol.ConsensusCurrentVersion, 100) + + // Prepare ledger + const inMem = true + cfg := config.GetDefaultLocal() + cfg.Archival = true + log := logging.TestingLog(t) + log.SetLevel(logging.Warn) + realLedger, err := ledger.OpenLedger(log, t.Name(), inMem, genesisInitState, cfg) + require.NoError(t, err, "could not open ledger") + + l = &data.Ledger{Ledger: realLedger} + require.NotNil(t, l) + + // Reformat accounts + accounts = make([]account, len(keys)-2) // -2 for pool and sink accounts + i := 0 + for addr, key := range keys { + if addr == ledgertesting.PoolAddr() || addr == ledgertesting.SinkAddr() { + continue + } + + acctData := genesisInitState.Accounts[addr] + accounts[i] = account{addr, key, acctData} + i++ + } + + // txn header generator + hdr, err := l.BlockHdr(l.Latest()) + require.NoError(t, err) + makeTxnHeader = func(sender basics.Address) transactions.Header { + return transactions.Header{ + Fee: basics.MicroAlgos{Raw: 1000}, + FirstValid: hdr.Round, + GenesisID: hdr.GenesisID, + GenesisHash: hdr.GenesisHash, + LastValid: hdr.Round + basics.Round(1000), + Note: []byte{240, 134, 38, 55, 197, 14, 142, 132}, + Sender: sender, + } + } + + return +} + +func makeTestClient() libgoal.Client { + c, err := libgoal.MakeClientFromConfig(libgoal.ClientConfig{ + AlgodDataDir: "NO_DIR", + }, libgoal.DynamicClient) + if err != nil { + panic(err) + } + + return c +} + +// Attach group ID to a transaction group. Mutates the group directly. +func attachGroupID(txgroup []transactions.SignedTxn) error { + txnArray := make([]transactions.Transaction, len(txgroup)) + for i, txn := range txgroup { + txnArray[i] = txn.Txn + } + + client := makeTestClient() + groupID, err := client.GroupID(txnArray) + if err != nil { + return err + } + + for i := range txgroup { + txgroup[i].Txn.Header.Group = groupID + } + + return nil +} + +func uint64ToBytes(num uint64) []byte { + ibytes := make([]byte, 8) + binary.BigEndian.PutUint64(ibytes, num) + return ibytes +} + +// ============================== +// > Sanity Tests +// ============================== + +// We want to be careful that the Algod ledger does not move on to another round +// so we confirm here that all ledger methods which implicitly access the current round +// are overriden within the `simulatorLedger`. +func TestNonOverridenDataLedgerMethodsUseRoundParameter(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + l, _, _ := prepareSimulatorTest(t) + + // methods overriden by `simulatorLedger`` + overridenMethods := []string{ + "Latest", + "LookupLatest", + } + + // methods that don't use a round number + excludedMethods := []string{ + "GenesisHash", + "GenesisProto", + "LatestTotals", + "FlushCaches", + } + + methodIsSkipped := func(methodName string) bool { + for _, overridenMethod := range overridenMethods { + if overridenMethod == methodName { + return true + } + } + for _, excludedMethod := range excludedMethods { + if excludedMethod == methodName { + return true + } + } + return false + } + + methodExistsInEvalLedger := func(methodName string) bool { + evalLedgerType := reflect.TypeOf((*internal.LedgerForEvaluator)(nil)).Elem() + for i := 0; i < evalLedgerType.NumMethod(); i++ { + if evalLedgerType.Method(i).Name == methodName { + return true + } + } + return false + } + + methodHasRoundParameter := func(methodType reflect.Type) bool { + for i := 0; i < methodType.NumIn(); i++ { + if methodType.In(i) == reflect.TypeOf(basics.Round(0)) { + return true + } + } + return false + } + + ledgerType := reflect.TypeOf(l) + for i := 0; i < ledgerType.NumMethod(); i++ { + method := ledgerType.Method(i) + if methodExistsInEvalLedger(method.Name) && !methodIsSkipped(method.Name) { + require.True(t, methodHasRoundParameter(method.Type), "method %s has no round parameter", method.Name) + } + } +} + +// ============================== +// > Simulation Tests +// ============================== + +func TestPayTxn(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + for _, signed := range []bool{true, false} { + signed := signed + t.Run(fmt.Sprintf("signed=%t", signed), func(t *testing.T) { + t.Parallel() + l, accounts, makeTxnHeader := prepareSimulatorTest(t) + defer l.Close() + s := simulation.MakeSimulator(l) + sender := accounts[0] + + txn := transactions.SignedTxn{ + Txn: transactions.Transaction{ + Type: protocol.PaymentTx, + Header: makeTxnHeader(sender.addr), + PaymentTxnFields: transactions.PaymentTxnFields{ + Receiver: sender.addr, + Amount: basics.MicroAlgos{Raw: 0}, + }, + }, + } + + if signed { + txn = txn.Txn.Sign(sender.sk) + } + + txgroup := []transactions.SignedTxn{txn} + + _, _, err := s.Simulate(txgroup) + require.NoError(t, err) + }) + } +} + +func TestOverspendPayTxn(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + for _, signed := range []bool{true, false} { + signed := signed + t.Run(fmt.Sprintf("signed=%t", signed), func(t *testing.T) { + t.Parallel() + l, accounts, makeTxnHeader := prepareSimulatorTest(t) + defer l.Close() + s := simulation.MakeSimulator(l) + sender := accounts[0] + senderBalance := sender.acctData.MicroAlgos + amount := senderBalance.Raw + 100 + + txn := transactions.SignedTxn{ + Txn: transactions.Transaction{ + Type: protocol.PaymentTx, + Header: makeTxnHeader(sender.addr), + PaymentTxnFields: transactions.PaymentTxnFields{ + Receiver: sender.addr, + Amount: basics.MicroAlgos{Raw: amount}, // overspend + }, + }, + } + + if signed { + txn = txn.Txn.Sign(sender.sk) + } + + txgroup := []transactions.SignedTxn{txn} + + _, _, err := s.Simulate(txgroup) + require.ErrorAs(t, err, &simulation.EvalFailureError{}) + require.ErrorContains(t, err, fmt.Sprintf("tried to spend {%d}", amount)) + }) + } +} + +func TestAuthAddrTxn(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + for _, signed := range []bool{true, false} { + signed := signed + t.Run(fmt.Sprintf("signed=%t", signed), func(t *testing.T) { + t.Parallel() + l, accounts, makeTxnHeader := prepareSimulatorTest(t) + defer l.Close() + s := simulation.MakeSimulator(l) + sender := accounts[0] + authority := accounts[1] + + txn := transactions.SignedTxn{ + Txn: transactions.Transaction{ + Type: protocol.PaymentTx, + Header: makeTxnHeader(sender.addr), + PaymentTxnFields: transactions.PaymentTxnFields{ + Receiver: sender.addr, + Amount: basics.MicroAlgos{Raw: 0}, + }, + }, + AuthAddr: authority.addr, + } + + if signed { + txn = txn.Txn.Sign(authority.sk) + } + + txgroup := []transactions.SignedTxn{txn} + + _, _, err := s.Simulate(txgroup) + require.ErrorContains(t, err, fmt.Sprintf("should have been authorized by %s but was actually authorized by %s", sender.addr, authority.addr)) + }) + } +} + +func TestStateProofTxn(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + l, _, makeTxnHeader := prepareSimulatorTest(t) + defer l.Close() + s := simulation.MakeSimulator(l) + + txgroup := []transactions.SignedTxn{ + { + Txn: transactions.Transaction{ + Type: protocol.StateProofTx, + Header: makeTxnHeader(transactions.StateProofSender), + // No need to fill out StateProofTxnFields, this should fail at signature verification + }, + }, + } + + _, _, err := s.Simulate(txgroup) + require.ErrorContains(t, err, "cannot simulate StateProof transactions") +} + +func TestSimpleGroupTxn(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + l, accounts, makeTxnHeader := prepareSimulatorTest(t) + defer l.Close() + s := simulation.MakeSimulator(l) + sender1 := accounts[0].addr + sender1Balance := accounts[0].acctData.MicroAlgos + sender2 := accounts[1].addr + sender2Balance := accounts[1].acctData.MicroAlgos + + // Send money back and forth + txgroup := []transactions.SignedTxn{ + { + Txn: transactions.Transaction{ + Type: protocol.PaymentTx, + Header: makeTxnHeader(sender1), + PaymentTxnFields: transactions.PaymentTxnFields{ + Receiver: sender2, + Amount: basics.MicroAlgos{Raw: 1000000}, + }, + }, + }, + { + Txn: transactions.Transaction{ + Type: protocol.PaymentTx, + Header: makeTxnHeader(sender2), + PaymentTxnFields: transactions.PaymentTxnFields{ + Receiver: sender1, + Amount: basics.MicroAlgos{Raw: 0}, + }, + }, + }, + } + + // Should fail if there is no group parameter + _, _, err := s.Simulate(txgroup) + require.ErrorAs(t, err, &simulation.EvalFailureError{}) + require.ErrorContains(t, err, "had zero Group but was submitted in a group of 2") + + // Add group parameter + err = attachGroupID(txgroup) + require.NoError(t, err) + + // Check balances before transaction + sender1Data, _, err := l.LookupWithoutRewards(l.Latest(), sender1) + require.NoError(t, err) + require.Equal(t, sender1Balance, sender1Data.MicroAlgos) + + sender2Data, _, err := l.LookupWithoutRewards(l.Latest(), sender2) + require.NoError(t, err) + require.Equal(t, sender2Balance, sender2Data.MicroAlgos) + + // Should now pass + _, _, err = s.Simulate(txgroup) + require.NoError(t, err) + + // Confirm balances have not changed + sender1Data, _, err = l.LookupWithoutRewards(l.Latest(), sender1) + require.NoError(t, err) + require.Equal(t, sender1Balance, sender1Data.MicroAlgos) + + sender2Data, _, err = l.LookupWithoutRewards(l.Latest(), sender2) + require.NoError(t, err) + require.Equal(t, sender2Balance, sender2Data.MicroAlgos) +} + +const trivialAVMProgram = `#pragma version 2 +int 1` +const rejectAVMProgram = `#pragma version 2 +int 0` + +func TestSimpleAppCall(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + l, accounts, makeTxnHeader := prepareSimulatorTest(t) + defer l.Close() + s := simulation.MakeSimulator(l) + sender := accounts[0].addr + + // Compile AVM program + ops, err := logic.AssembleString(trivialAVMProgram) + require.NoError(t, err, ops.Errors) + prog := ops.Program + + // Create program and call it + futureAppID := 1 + txgroup := []transactions.SignedTxn{ + { + Txn: transactions.Transaction{ + Type: protocol.ApplicationCallTx, + Header: makeTxnHeader(sender), + ApplicationCallTxnFields: transactions.ApplicationCallTxnFields{ + ApplicationID: 0, + ApprovalProgram: prog, + ClearStateProgram: prog, + LocalStateSchema: basics.StateSchema{ + NumUint: 0, + NumByteSlice: 0, + }, + GlobalStateSchema: basics.StateSchema{ + NumUint: 0, + NumByteSlice: 0, + }, + }, + }, + }, + { + Txn: transactions.Transaction{ + Type: protocol.ApplicationCallTx, + Header: makeTxnHeader(sender), + ApplicationCallTxnFields: transactions.ApplicationCallTxnFields{ + ApplicationID: basics.AppIndex(futureAppID), + }, + }, + }, + } + + err = attachGroupID(txgroup) + require.NoError(t, err) + + _, _, err = s.Simulate(txgroup) + require.NoError(t, err) +} + +func TestRejectAppCall(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + l, accounts, makeTxnHeader := prepareSimulatorTest(t) + defer l.Close() + s := simulation.MakeSimulator(l) + sender := accounts[0].addr + + // Compile AVM program + ops, err := logic.AssembleString(rejectAVMProgram) + require.NoError(t, err, ops.Errors) + prog := ops.Program + + // Create program and call it + txgroup := []transactions.SignedTxn{ + { + Txn: transactions.Transaction{ + Type: protocol.ApplicationCallTx, + Header: makeTxnHeader(sender), + ApplicationCallTxnFields: transactions.ApplicationCallTxnFields{ + ApplicationID: 0, + ApprovalProgram: prog, + ClearStateProgram: prog, + LocalStateSchema: basics.StateSchema{ + NumUint: 0, + NumByteSlice: 0, + }, + GlobalStateSchema: basics.StateSchema{ + NumUint: 0, + NumByteSlice: 0, + }, + }, + }, + }, + } + + err = attachGroupID(txgroup) + require.NoError(t, err) + + _, _, err = s.Simulate(txgroup) + require.ErrorAs(t, err, &simulation.EvalFailureError{}) + require.ErrorContains(t, err, "transaction rejected by ApprovalProgram") +} + +func TestSignatureCheck(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + l, accounts, makeTxnHeader := prepareSimulatorTest(t) + defer l.Close() + s := simulation.MakeSimulator(l) + sender := accounts[0].addr + + txgroup := []transactions.SignedTxn{ + { + Txn: transactions.Transaction{ + Type: protocol.PaymentTx, + Header: makeTxnHeader(sender), + PaymentTxnFields: transactions.PaymentTxnFields{ + Receiver: sender, + Amount: basics.MicroAlgos{Raw: 0}, + }, + }, + }, + } + + // should catch missing signature + _, missingSignatures, err := s.Simulate(txgroup) + require.NoError(t, err) + require.True(t, missingSignatures) + + // add signature + signatureSecrets := accounts[0].sk + txgroup[0] = txgroup[0].Txn.Sign(signatureSecrets) + + // should not error now that we have a signature + _, missingSignatures, err = s.Simulate(txgroup) + require.NoError(t, err) + require.False(t, missingSignatures) + + // should error with invalid signature + txgroup[0].Sig[0] += byte(1) // will wrap if > 255 + _, _, err = s.Simulate(txgroup) + require.ErrorAs(t, err, &simulation.InvalidTxGroupError{}) + require.ErrorContains(t, err, "one signature didn't pass") +} + +// TestInvalidTxGroup tests that a transaction group with invalid transactions +// is rejected by the simulator as an InvalidTxGroupError instead of a EvalFailureError. +func TestInvalidTxGroup(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + l, accounts, makeTxnHeader := prepareSimulatorTest(t) + defer l.Close() + s := simulation.MakeSimulator(l) + receiver := accounts[0].addr + + txgroup := []transactions.SignedTxn{ + { + Txn: transactions.Transaction{ + Type: protocol.PaymentTx, + // invalid sender + Header: makeTxnHeader(ledgertesting.PoolAddr()), + PaymentTxnFields: transactions.PaymentTxnFields{ + Receiver: receiver, + Amount: basics.MicroAlgos{Raw: 0}, + }, + }, + }, + } + + // should error with invalid transaction group error + _, _, err := s.Simulate(txgroup) + require.ErrorAs(t, err, &simulation.InvalidTxGroupError{}) + require.ErrorContains(t, err, "transaction from incentive pool is invalid") +} + +const accountBalanceCheckProgram = `#pragma version 4 + txn ApplicationID // [appId] + bz end // [] + int 1 // [1] + balance // [bal[1]] + itob // [itob(bal[1])] + txn ApplicationArgs 0 // [itob(bal[1]), args[0]] + == // [itob(bal[1])=?=args[0]] + assert + b end +end: + int 1 // [1] +` + +func TestBalanceChangesWithApp(t *testing.T) { + // Send a payment transaction to a new account and confirm its balance within an app call + partitiontest.PartitionTest(t) + t.Parallel() + + l, accounts, makeTxnHeader := prepareSimulatorTest(t) + defer l.Close() + s := simulation.MakeSimulator(l) + sender := accounts[0].addr + senderBalance := accounts[0].acctData.MicroAlgos.Raw + sendAmount := senderBalance - 500000 + receiver := accounts[1].addr + receiverBalance := accounts[1].acctData.MicroAlgos.Raw + + // Compile approval program + ops, err := logic.AssembleString(accountBalanceCheckProgram) + require.NoError(t, err, ops.Errors) + approvalProg := ops.Program + + // Compile clear program + ops, err = logic.AssembleString(trivialAVMProgram) + require.NoError(t, err, ops.Errors) + clearStateProg := ops.Program + + futureAppID := 1 + txgroup := []transactions.SignedTxn{ + // create app + { + Txn: transactions.Transaction{ + Type: protocol.ApplicationCallTx, + Header: makeTxnHeader(sender), + ApplicationCallTxnFields: transactions.ApplicationCallTxnFields{ + ApplicationID: 0, + ApprovalProgram: approvalProg, + ClearStateProgram: clearStateProg, + LocalStateSchema: basics.StateSchema{ + NumUint: 0, + NumByteSlice: 0, + }, + GlobalStateSchema: basics.StateSchema{ + NumUint: 0, + NumByteSlice: 0, + }, + }, + }, + }, + // check balance + { + Txn: transactions.Transaction{ + Type: protocol.ApplicationCallTx, + Header: makeTxnHeader(sender), + ApplicationCallTxnFields: transactions.ApplicationCallTxnFields{ + ApplicationID: basics.AppIndex(futureAppID), + Accounts: []basics.Address{receiver}, + ApplicationArgs: [][]byte{uint64ToBytes(receiverBalance)}, + }, + }, + }, + // send payment + { + Txn: transactions.Transaction{ + Type: protocol.PaymentTx, + Header: makeTxnHeader(sender), + PaymentTxnFields: transactions.PaymentTxnFields{ + Receiver: receiver, + Amount: basics.MicroAlgos{Raw: sendAmount}, + }, + }, + }, + // check balance changed + { + Txn: transactions.Transaction{ + Type: protocol.ApplicationCallTx, + Header: makeTxnHeader(sender), + ApplicationCallTxnFields: transactions.ApplicationCallTxnFields{ + ApplicationID: basics.AppIndex(futureAppID), + Accounts: []basics.Address{receiver}, + ApplicationArgs: [][]byte{uint64ToBytes(receiverBalance + sendAmount)}, + }, + }, + }, + } + + err = attachGroupID(txgroup) + require.NoError(t, err) + + _, _, err = s.Simulate(txgroup) + require.NoError(t, err) +} + +// TestBalanceChangesWithApp tests that the simulator's transaction group checks +// allow for pooled fees across a mix of signed and unsigned transactions. +// Transaction 1 is a signed transaction with not enough fees paid on its own. +// Transaction 2 is an unsigned transaction with enough fees paid to cover transaction 1. +func TestPooledFeesAcrossSignedAndUnsigned(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + l, accounts, makeTxnHeader := prepareSimulatorTest(t) + defer l.Close() + s := simulation.MakeSimulator(l) + sender1 := accounts[0].addr + sender2 := accounts[1].addr + + txnHeader1 := makeTxnHeader(sender1) + txnHeader2 := makeTxnHeader(sender2) + txnHeader1.Fee = basics.MicroAlgos{Raw: txnHeader1.Fee.Raw - 100} + txnHeader2.Fee = basics.MicroAlgos{Raw: txnHeader2.Fee.Raw + 100} + + // Send money back and forth + txgroup := []transactions.SignedTxn{ + { + Txn: transactions.Transaction{ + Type: protocol.PaymentTx, + Header: txnHeader1, + PaymentTxnFields: transactions.PaymentTxnFields{ + Receiver: sender2, + Amount: basics.MicroAlgos{Raw: 1000000}, + }, + }, + }, + { + Txn: transactions.Transaction{ + Type: protocol.PaymentTx, + Header: txnHeader2, + PaymentTxnFields: transactions.PaymentTxnFields{ + Receiver: sender1, + Amount: basics.MicroAlgos{Raw: 0}, + }, + }, + }, + } + + err := attachGroupID(txgroup) + require.NoError(t, err) + + // add signature to txn 1 + signatureSecrets := accounts[0].sk + txgroup[0] = txgroup[0].Txn.Sign(signatureSecrets) + + _, _, err = s.Simulate(txgroup) + require.NoError(t, err) +} diff --git a/ledger/store/accountsV2.go b/ledger/store/accountsV2.go index 649e4111bb..cb2c1d34eb 100644 --- a/ledger/store/accountsV2.go +++ b/ledger/store/accountsV2.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/accountsV2_test.go b/ledger/store/accountsV2_test.go index a9a5bf9864..c06d982007 100644 --- a/ledger/store/accountsV2_test.go +++ b/ledger/store/accountsV2_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/blockdb/blockdb.go b/ledger/store/blockdb/blockdb.go index 6417d84529..64b6f02fcc 100644 --- a/ledger/store/blockdb/blockdb.go +++ b/ledger/store/blockdb/blockdb.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/blockdb/blockdb_test.go b/ledger/store/blockdb/blockdb_test.go index f659888c2b..858e561e48 100644 --- a/ledger/store/blockdb/blockdb_test.go +++ b/ledger/store/blockdb/blockdb_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/catchpoint.go b/ledger/store/catchpoint.go index 0b4f2da66d..7b8915de02 100644 --- a/ledger/store/catchpoint.go +++ b/ledger/store/catchpoint.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/catchpointPendingHashesIter.go b/ledger/store/catchpointPendingHashesIter.go new file mode 100644 index 0000000000..69b616789c --- /dev/null +++ b/ledger/store/catchpointPendingHashesIter.go @@ -0,0 +1,81 @@ +// Copyright (C) 2019-2023 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package store + +import ( + "context" + "database/sql" +) + +// catchpointPendingHashesIterator allows us to iterate over the hashes in the catchpointpendinghashes table in their order. +type catchpointPendingHashesIterator struct { + hashCount int + tx *sql.Tx + rows *sql.Rows +} + +// MakeCatchpointPendingHashesIterator create a pending hashes iterator that retrieves the hashes in the catchpointpendinghashes table. +func MakeCatchpointPendingHashesIterator(hashCount int, tx *sql.Tx) *catchpointPendingHashesIterator { + return &catchpointPendingHashesIterator{ + hashCount: hashCount, + tx: tx, + } +} + +// Next returns an array containing the hashes, returning HashCount hashes at a time. +func (iterator *catchpointPendingHashesIterator) Next(ctx context.Context) (hashes [][]byte, err error) { + if iterator.rows == nil { + iterator.rows, err = iterator.tx.QueryContext(ctx, "SELECT data FROM catchpointpendinghashes ORDER BY data") + if err != nil { + return + } + } + + // gather up to accountCount encoded accounts. + hashes = make([][]byte, iterator.hashCount) + hashIdx := 0 + for iterator.rows.Next() { + err = iterator.rows.Scan(&hashes[hashIdx]) + if err != nil { + iterator.Close() + return + } + + hashIdx++ + if hashIdx == iterator.hashCount { + // we're done with this iteration. + return + } + } + hashes = hashes[:hashIdx] + err = iterator.rows.Err() + if err != nil { + iterator.Close() + return + } + // we just finished reading the table. + iterator.Close() + return +} + +// Close shuts down the catchpointPendingHashesIterator, releasing database resources. +func (iterator *catchpointPendingHashesIterator) Close() { + if iterator.rows != nil { + iterator.rows.Close() + iterator.rows = nil + } +} diff --git a/ledger/store/catchpoint_test.go b/ledger/store/catchpoint_test.go index d4da07ae16..ad69ecbaf6 100644 --- a/ledger/store/catchpoint_test.go +++ b/ledger/store/catchpoint_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/data.go b/ledger/store/data.go index d09771bd34..58edc7d456 100644 --- a/ledger/store/data.go +++ b/ledger/store/data.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/data_test.go b/ledger/store/data_test.go index 8e360b4bdd..b6afe29a07 100644 --- a/ledger/store/data_test.go +++ b/ledger/store/data_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/encodedAccountsIter.go b/ledger/store/encodedAccountsIter.go new file mode 100644 index 0000000000..1dd5310489 --- /dev/null +++ b/ledger/store/encodedAccountsIter.go @@ -0,0 +1,165 @@ +// Copyright (C) 2019-2023 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package store + +import ( + "context" + "database/sql" + + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/ledger/encoded" + "github.com/algorand/msgp/msgp" +) + +// encodedAccountsBatchIter allows us to iterate over the accounts data stored in the accountbase table. +type encodedAccountsBatchIter struct { + accountsRows *sql.Rows + resourcesRows *sql.Rows + nextBaseRow pendingBaseRow + nextResourceRow pendingResourceRow + acctResCnt catchpointAccountResourceCounter +} + +// catchpointAccountResourceCounter keeps track of the resources processed for the current account +type catchpointAccountResourceCounter struct { + totalAppParams uint64 + totalAppLocalStates uint64 + totalAssetParams uint64 + totalAssets uint64 +} + +// MakeEncodedAccoutsBatchIter creates an empty accounts batch iterator. +func MakeEncodedAccoutsBatchIter() *encodedAccountsBatchIter { + return &encodedAccountsBatchIter{} +} + +// Next returns an array containing the account data, in the same way it appear in the database +// returning accountCount accounts data at a time. +func (iterator *encodedAccountsBatchIter) Next(ctx context.Context, tx *sql.Tx, accountCount int, resourceCount int) (bals []encoded.BalanceRecordV6, numAccountsProcessed uint64, err error) { + if iterator.accountsRows == nil { + iterator.accountsRows, err = tx.QueryContext(ctx, "SELECT rowid, address, data FROM accountbase ORDER BY rowid") + if err != nil { + return + } + } + if iterator.resourcesRows == nil { + iterator.resourcesRows, err = tx.QueryContext(ctx, "SELECT addrid, aidx, data FROM resources ORDER BY addrid, aidx") + if err != nil { + return + } + } + + // gather up to accountCount encoded accounts. + bals = make([]encoded.BalanceRecordV6, 0, accountCount) + var encodedRecord encoded.BalanceRecordV6 + var baseAcct BaseAccountData + var numAcct int + baseCb := func(addr basics.Address, rowid int64, accountData *BaseAccountData, encodedAccountData []byte) (err error) { + encodedRecord = encoded.BalanceRecordV6{Address: addr, AccountData: encodedAccountData} + baseAcct = *accountData + numAcct++ + return nil + } + + var totalResources int + + // emptyCount := 0 + resCb := func(addr basics.Address, cidx basics.CreatableIndex, resData *ResourcesData, encodedResourceData []byte, lastResource bool) error { + + emptyBaseAcct := baseAcct.TotalAppParams == 0 && baseAcct.TotalAppLocalStates == 0 && baseAcct.TotalAssetParams == 0 && baseAcct.TotalAssets == 0 + if !emptyBaseAcct && resData != nil { + if encodedRecord.Resources == nil { + encodedRecord.Resources = make(map[uint64]msgp.Raw) + } + encodedRecord.Resources[uint64(cidx)] = encodedResourceData + if resData.IsApp() && resData.IsOwning() { + iterator.acctResCnt.totalAppParams++ + } + if resData.IsApp() && resData.IsHolding() { + iterator.acctResCnt.totalAppLocalStates++ + } + + if resData.IsAsset() && resData.IsOwning() { + iterator.acctResCnt.totalAssetParams++ + } + if resData.IsAsset() && resData.IsHolding() { + iterator.acctResCnt.totalAssets++ + } + totalResources++ + } + + if baseAcct.TotalAppParams == iterator.acctResCnt.totalAppParams && + baseAcct.TotalAppLocalStates == iterator.acctResCnt.totalAppLocalStates && + baseAcct.TotalAssetParams == iterator.acctResCnt.totalAssetParams && + baseAcct.TotalAssets == iterator.acctResCnt.totalAssets { + + encodedRecord.ExpectingMoreEntries = false + bals = append(bals, encodedRecord) + numAccountsProcessed++ + + iterator.acctResCnt = catchpointAccountResourceCounter{} + + return nil + } + + // max resources per chunk reached, stop iterating. + if lastResource { + encodedRecord.ExpectingMoreEntries = true + bals = append(bals, encodedRecord) + encodedRecord.Resources = nil + } + + return nil + } + + _, iterator.nextBaseRow, iterator.nextResourceRow, err = processAllBaseAccountRecords( + iterator.accountsRows, iterator.resourcesRows, + baseCb, resCb, + iterator.nextBaseRow, iterator.nextResourceRow, accountCount, resourceCount, + ) + if err != nil { + iterator.Close() + return + } + + if len(bals) == accountCount || totalResources == resourceCount { + // we're done with this iteration. + return + } + + err = iterator.accountsRows.Err() + if err != nil { + iterator.Close() + return + } + // Do not Close() the iterator here. It is the caller's responsibility to + // do so, signalled by the return of an empty chunk. If we Close() here, the + // next call to Next() will start all over! + return +} + +// Close shuts down the encodedAccountsBatchIter, releasing database resources. +func (iterator *encodedAccountsBatchIter) Close() { + if iterator.accountsRows != nil { + iterator.accountsRows.Close() + iterator.accountsRows = nil + } + if iterator.resourcesRows != nil { + iterator.resourcesRows.Close() + iterator.resourcesRows = nil + } +} diff --git a/ledger/store/hashing.go b/ledger/store/hashing.go index 40a5b6c311..fa409a2c60 100644 --- a/ledger/store/hashing.go +++ b/ledger/store/hashing.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/interface.go b/ledger/store/interface.go index c9f06ee53d..d2f8fc4e00 100644 --- a/ledger/store/interface.go +++ b/ledger/store/interface.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/kvsIter.go b/ledger/store/kvsIter.go new file mode 100644 index 0000000000..f831fa203a --- /dev/null +++ b/ledger/store/kvsIter.go @@ -0,0 +1,53 @@ +// Copyright (C) 2019-2023 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package store + +import ( + "context" + "database/sql" +) + +type kvsIter struct { + tx *sql.Tx + rows *sql.Rows +} + +// MakeKVsIter creates a KV iterator. +func MakeKVsIter(ctx context.Context, tx *sql.Tx) (*kvsIter, error) { + rows, err := tx.QueryContext(ctx, "SELECT key, value FROM kvstore") + if err != nil { + return nil, err + } + + return &kvsIter{ + tx: tx, + rows: rows, + }, nil +} + +func (iter *kvsIter) Next() bool { + return iter.rows.Next() +} + +func (iter *kvsIter) KeyValue() (k []byte, v []byte, err error) { + err = iter.rows.Scan(&k, &v) + return k, v, err +} + +func (iter *kvsIter) Close() { + iter.rows.Close() +} diff --git a/ledger/store/merkle_commiter.go b/ledger/store/merkle_commiter.go index 3d837c495a..bc7502dac6 100644 --- a/ledger/store/merkle_commiter.go +++ b/ledger/store/merkle_commiter.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/orderedAccountsIter.go b/ledger/store/orderedAccountsIter.go new file mode 100644 index 0000000000..017de0cafc --- /dev/null +++ b/ledger/store/orderedAccountsIter.go @@ -0,0 +1,433 @@ +// Copyright (C) 2019-2023 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package store + +import ( + "context" + "database/sql" + "errors" + "fmt" + "math" + + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/protocol" +) + +// orderedAccountsIter allows us to iterate over the accounts addresses in the order of the account hashes. +type orderedAccountsIter struct { + step orderedAccountsIterStep + accountBaseRows *sql.Rows + hashesRows *sql.Rows + resourcesRows *sql.Rows + tx *sql.Tx + pendingBaseRow pendingBaseRow + pendingResourceRow pendingResourceRow + accountCount int + insertStmt *sql.Stmt +} + +// orderedAccountsIterStep is used by orderedAccountsIter to define the current step +// +//msgp:ignore orderedAccountsIterStep +type orderedAccountsIterStep int + +const ( + // startup step + oaiStepStartup = orderedAccountsIterStep(0) + // delete old ordering table if we have any leftover from previous invocation + oaiStepDeleteOldOrderingTable = orderedAccountsIterStep(0) + // create new ordering table + oaiStepCreateOrderingTable = orderedAccountsIterStep(1) + // query the existing accounts + oaiStepQueryAccounts = orderedAccountsIterStep(2) + // iterate over the existing accounts and insert their hash & address into the staging ordering table + oaiStepInsertAccountData = orderedAccountsIterStep(3) + // create an index on the ordering table so that we can efficiently scan it. + oaiStepCreateOrderingAccountIndex = orderedAccountsIterStep(4) + // query the ordering table + oaiStepSelectFromOrderedTable = orderedAccountsIterStep(5) + // iterate over the ordering table + oaiStepIterateOverOrderedTable = orderedAccountsIterStep(6) + // cleanup and delete ordering table + oaiStepShutdown = orderedAccountsIterStep(7) + // do nothing as we're done. + oaiStepDone = orderedAccountsIterStep(8) +) + +type pendingBaseRow struct { + addr basics.Address + rowid int64 + accountData *BaseAccountData + encodedAccountData []byte +} + +type pendingResourceRow struct { + addrid int64 + aidx basics.CreatableIndex + buf []byte +} + +// MakeOrderedAccountsIter creates an ordered account iterator. Note that due to implementation reasons, +// only a single iterator can be active at a time. +func MakeOrderedAccountsIter(tx *sql.Tx, accountCount int) *orderedAccountsIter { + return &orderedAccountsIter{ + tx: tx, + accountCount: accountCount, + step: oaiStepStartup, + } +} + +// accountAddressHash is used by Next to return a single account address and the associated hash. +type accountAddressHash struct { + Addrid int64 + Digest []byte +} + +// Next returns an array containing the account address and hash +// the Next function works in multiple processing stages, where it first processes the current accounts and order them +// followed by returning the ordered accounts. In the first phase, it would return empty accountAddressHash array +// and sets the processedRecords to the number of accounts that were processed. On the second phase, the acct +// would contain valid data ( and optionally the account data as well, if was asked in makeOrderedAccountsIter) and +// the processedRecords would be zero. If err is sql.ErrNoRows it means that the iterator have completed it's work and no further +// accounts exists. Otherwise, the caller is expected to keep calling "Next" to retrieve the next set of accounts +// ( or let the Next function make some progress toward that goal ) +func (iterator *orderedAccountsIter) Next(ctx context.Context) (acct []accountAddressHash, processedRecords int, err error) { + if iterator.step == oaiStepDeleteOldOrderingTable { + // although we're going to delete this table anyway when completing the iterator execution, we'll try to + // clean up any intermediate table. + _, err = iterator.tx.ExecContext(ctx, "DROP TABLE IF EXISTS accountsiteratorhashes") + if err != nil { + return + } + iterator.step = oaiStepCreateOrderingTable + return + } + if iterator.step == oaiStepCreateOrderingTable { + // create the temporary table + _, err = iterator.tx.ExecContext(ctx, "CREATE TABLE accountsiteratorhashes(addrid INTEGER, hash blob)") + if err != nil { + return + } + iterator.step = oaiStepQueryAccounts + return + } + if iterator.step == oaiStepQueryAccounts { + // iterate over the existing accounts + iterator.accountBaseRows, err = iterator.tx.QueryContext(ctx, "SELECT rowid, address, data FROM accountbase ORDER BY rowid") + if err != nil { + return + } + // iterate over the existing resources + iterator.resourcesRows, err = iterator.tx.QueryContext(ctx, "SELECT addrid, aidx, data FROM resources ORDER BY addrid, aidx") + if err != nil { + return + } + // prepare the insert statement into the temporary table + iterator.insertStmt, err = iterator.tx.PrepareContext(ctx, "INSERT INTO accountsiteratorhashes(addrid, hash) VALUES(?, ?)") + if err != nil { + return + } + iterator.step = oaiStepInsertAccountData + return + } + if iterator.step == oaiStepInsertAccountData { + var lastAddrID int64 + baseCb := func(addr basics.Address, rowid int64, accountData *BaseAccountData, encodedAccountData []byte) (err error) { + hash := AccountHashBuilderV6(addr, accountData, encodedAccountData) + _, err = iterator.insertStmt.ExecContext(ctx, rowid, hash) + if err != nil { + return + } + lastAddrID = rowid + return nil + } + + resCb := func(addr basics.Address, cidx basics.CreatableIndex, resData *ResourcesData, encodedResourceData []byte, lastResource bool) error { + if resData != nil { + hash, err := ResourcesHashBuilderV6(resData, addr, cidx, resData.UpdateRound, encodedResourceData) + if err != nil { + return err + } + _, err = iterator.insertStmt.ExecContext(ctx, lastAddrID, hash) + return err + } + return nil + } + + count := 0 + count, iterator.pendingBaseRow, iterator.pendingResourceRow, err = processAllBaseAccountRecords( + iterator.accountBaseRows, iterator.resourcesRows, + baseCb, resCb, + iterator.pendingBaseRow, iterator.pendingResourceRow, iterator.accountCount, math.MaxInt, + ) + if err != nil { + iterator.Close(ctx) + return + } + + if count == iterator.accountCount { + // we're done with this iteration. + processedRecords = count + return + } + + // make sure the resource iterator has no more entries. + if iterator.resourcesRows.Next() { + iterator.Close(ctx) + err = errors.New("resource table entries exceed the ones specified in the accountbase table") + return + } + + processedRecords = count + iterator.accountBaseRows.Close() + iterator.accountBaseRows = nil + iterator.resourcesRows.Close() + iterator.resourcesRows = nil + iterator.insertStmt.Close() + iterator.insertStmt = nil + iterator.step = oaiStepCreateOrderingAccountIndex + return + } + if iterator.step == oaiStepCreateOrderingAccountIndex { + // create an index. It shown that even when we're making a single select statement in step 5, it would be better to have this index vs. not having it at all. + // note that this index is using the rowid of the accountsiteratorhashes table. + _, err = iterator.tx.ExecContext(ctx, "CREATE INDEX accountsiteratorhashesidx ON accountsiteratorhashes(hash)") + if err != nil { + iterator.Close(ctx) + return + } + iterator.step = oaiStepSelectFromOrderedTable + return + } + if iterator.step == oaiStepSelectFromOrderedTable { + // select the data from the ordered table + iterator.hashesRows, err = iterator.tx.QueryContext(ctx, "SELECT addrid, hash FROM accountsiteratorhashes ORDER BY hash") + + if err != nil { + iterator.Close(ctx) + return + } + iterator.step = oaiStepIterateOverOrderedTable + return + } + + if iterator.step == oaiStepIterateOverOrderedTable { + acct = make([]accountAddressHash, iterator.accountCount) + acctIdx := 0 + for iterator.hashesRows.Next() { + err = iterator.hashesRows.Scan(&(acct[acctIdx].Addrid), &(acct[acctIdx].Digest)) + if err != nil { + iterator.Close(ctx) + return + } + acctIdx++ + if acctIdx == iterator.accountCount { + // we're done with this iteration. + return + } + } + acct = acct[:acctIdx] + iterator.step = oaiStepShutdown + iterator.hashesRows.Close() + iterator.hashesRows = nil + return + } + if iterator.step == oaiStepShutdown { + err = iterator.Close(ctx) + if err != nil { + return + } + iterator.step = oaiStepDone + // fallthrough + } + return nil, 0, sql.ErrNoRows +} + +// Close shuts down the orderedAccountsBuilderIter, releasing database resources. +func (iterator *orderedAccountsIter) Close(ctx context.Context) (err error) { + if iterator.accountBaseRows != nil { + iterator.accountBaseRows.Close() + iterator.accountBaseRows = nil + } + if iterator.resourcesRows != nil { + iterator.resourcesRows.Close() + iterator.resourcesRows = nil + } + if iterator.hashesRows != nil { + iterator.hashesRows.Close() + iterator.hashesRows = nil + } + if iterator.insertStmt != nil { + iterator.insertStmt.Close() + iterator.insertStmt = nil + } + _, err = iterator.tx.ExecContext(ctx, "DROP TABLE IF EXISTS accountsiteratorhashes") + return +} + +func processAllBaseAccountRecords( + baseRows *sql.Rows, + resRows *sql.Rows, + baseCb func(addr basics.Address, rowid int64, accountData *BaseAccountData, encodedAccountData []byte) error, + resCb func(addr basics.Address, creatableIdx basics.CreatableIndex, resData *ResourcesData, encodedResourceData []byte, lastResource bool) error, + pendingBase pendingBaseRow, pendingResource pendingResourceRow, accountCount int, resourceCount int, +) (int, pendingBaseRow, pendingResourceRow, error) { + var addr basics.Address + var prevAddr basics.Address + var err error + count := 0 + + var accountData BaseAccountData + var addrbuf []byte + var buf []byte + var rowid int64 + for { + if pendingBase.rowid != 0 { + addr = pendingBase.addr + rowid = pendingBase.rowid + accountData = *pendingBase.accountData + buf = pendingBase.encodedAccountData + pendingBase = pendingBaseRow{} + } else { + if !baseRows.Next() { + break + } + + err = baseRows.Scan(&rowid, &addrbuf, &buf) + if err != nil { + return 0, pendingBaseRow{}, pendingResourceRow{}, err + } + + if len(addrbuf) != len(addr) { + err = fmt.Errorf("account DB address length mismatch: %d != %d", len(addrbuf), len(addr)) + return 0, pendingBaseRow{}, pendingResourceRow{}, err + } + + copy(addr[:], addrbuf) + + accountData = BaseAccountData{} + err = protocol.Decode(buf, &accountData) + if err != nil { + return 0, pendingBaseRow{}, pendingResourceRow{}, err + } + } + + err = baseCb(addr, rowid, &accountData, buf) + if err != nil { + return 0, pendingBaseRow{}, pendingResourceRow{}, err + } + + var resourcesProcessed int + pendingResource, resourcesProcessed, err = processAllResources(resRows, addr, &accountData, rowid, pendingResource, resourceCount, resCb) + if err != nil { + err = fmt.Errorf("failed to gather resources for account %v, addrid %d, prev address %v : %w", addr, rowid, prevAddr, err) + return 0, pendingBaseRow{}, pendingResourceRow{}, err + } + + if resourcesProcessed == resourceCount { + // we're done with this iteration. + pendingBase := pendingBaseRow{ + addr: addr, + rowid: rowid, + accountData: &accountData, + encodedAccountData: buf, + } + return count, pendingBase, pendingResource, nil + } + resourceCount -= resourcesProcessed + + count++ + if accountCount > 0 && count == accountCount { + // we're done with this iteration. + return count, pendingBaseRow{}, pendingResource, nil + } + prevAddr = addr + } + + return count, pendingBaseRow{}, pendingResource, nil +} + +func processAllResources( + resRows *sql.Rows, + addr basics.Address, accountData *BaseAccountData, acctRowid int64, pr pendingResourceRow, resourceCount int, + callback func(addr basics.Address, creatableIdx basics.CreatableIndex, resData *ResourcesData, encodedResourceData []byte, lastResource bool) error, +) (pendingResourceRow, int, error) { + var err error + count := 0 + + // Declare variabled outside of the loop to prevent allocations per iteration. + // At least resData is resolved as "escaped" because of passing it by a pointer to protocol.Decode() + var buf []byte + var addrid int64 + var aidx basics.CreatableIndex + var resData ResourcesData + for { + if pr.addrid != 0 { + // some accounts may not have resources, consider the following case: + // acct 1 and 3 has resources, account 2 does not + // in this case addrid = 3 after processing resources from 1, but acctRowid = 2 + // and we need to skip accounts without resources + if pr.addrid > acctRowid { + err = callback(addr, 0, nil, nil, false) + return pr, count, err + } + if pr.addrid < acctRowid { + err = fmt.Errorf("resource table entries mismatches accountbase table entries : reached addrid %d while expecting resource for %d", pr.addrid, acctRowid) + return pendingResourceRow{}, count, err + } + addrid = pr.addrid + buf = pr.buf + aidx = pr.aidx + pr = pendingResourceRow{} + } else { + if !resRows.Next() { + err = callback(addr, 0, nil, nil, false) + if err != nil { + return pendingResourceRow{}, count, err + } + break + } + err = resRows.Scan(&addrid, &aidx, &buf) + if err != nil { + return pendingResourceRow{}, count, err + } + if addrid < acctRowid { + err = fmt.Errorf("resource table entries mismatches accountbase table entries : reached addrid %d while expecting resource for %d", addrid, acctRowid) + return pendingResourceRow{}, count, err + } else if addrid > acctRowid { + err = callback(addr, 0, nil, nil, false) + return pendingResourceRow{addrid, aidx, buf}, count, err + } + } + resData = ResourcesData{} + err = protocol.Decode(buf, &resData) + if err != nil { + return pendingResourceRow{}, count, err + } + count++ + if resourceCount > 0 && count == resourceCount { + // last resource to be included in chunk + err := callback(addr, aidx, &resData, buf, true) + return pendingResourceRow{}, count, err + } + err = callback(addr, aidx, &resData, buf, false) + if err != nil { + return pendingResourceRow{}, count, err + } + } + return pendingResourceRow{}, count, nil +} diff --git a/ledger/store/schema.go b/ledger/store/schema.go index 84bab3f1c0..acbd102b15 100644 --- a/ledger/store/schema.go +++ b/ledger/store/schema.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/schema_test.go b/ledger/store/schema_test.go index 6074a3f5e7..cab8c9b71e 100644 --- a/ledger/store/schema_test.go +++ b/ledger/store/schema_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/sql.go b/ledger/store/sql.go index c7f8bb3095..42c1ebf8fe 100644 --- a/ledger/store/sql.go +++ b/ledger/store/sql.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/sql_test.go b/ledger/store/sql_test.go index 2eb96bfe65..3bacf343ac 100644 --- a/ledger/store/sql_test.go +++ b/ledger/store/sql_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/testing.go b/ledger/store/testing.go index c637076c1d..0e426a28c3 100644 --- a/ledger/store/testing.go +++ b/ledger/store/testing.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/testing/helpers.go b/ledger/store/testing/helpers.go index 0fb5ec8246..34ba3e3ffb 100644 --- a/ledger/store/testing/helpers.go +++ b/ledger/store/testing/helpers.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/trackerdbV2.go b/ledger/store/trackerdbV2.go index feb7604ca7..19a8fbcaa9 100644 --- a/ledger/store/trackerdbV2.go +++ b/ledger/store/trackerdbV2.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/testing/accountsTotals.go b/ledger/testing/accountsTotals.go index bad08294e8..48109cc84c 100644 --- a/ledger/testing/accountsTotals.go +++ b/ledger/testing/accountsTotals.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/testing/consensusRange.go b/ledger/testing/consensusRange.go index 877e03fae7..e96bcc7280 100644 --- a/ledger/testing/consensusRange.go +++ b/ledger/testing/consensusRange.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/testing/consensusRange_test.go b/ledger/testing/consensusRange_test.go index df51ec7201..325373a396 100644 --- a/ledger/testing/consensusRange_test.go +++ b/ledger/testing/consensusRange_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/testing/initState.go b/ledger/testing/initState.go index 559d03b8a5..00cbe0d1ba 100644 --- a/ledger/testing/initState.go +++ b/ledger/testing/initState.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -24,6 +24,7 @@ import ( "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/basics" + basics_testing "github.com/algorand/go-algorand/data/basics/testing" "github.com/algorand/go-algorand/data/bookkeeping" "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/protocol" @@ -65,15 +66,15 @@ func GenerateInitState(tb testing.TB, proto protocol.ConsensusVersion, baseAlgoP for i := range genaddrs { initKeys[genaddrs[i]] = gensecrets[i] // Give each account quite a bit more balance than MinFee or MinBalance - ad := basics.MakeAccountData(basics.Online, basics.MicroAlgos{Raw: uint64((i + baseAlgoPerAccount) * 100000)}) + ad := basics_testing.MakeAccountData(basics.Online, basics.MicroAlgos{Raw: uint64((i + baseAlgoPerAccount) * 100000)}) ad.VoteFirstValid = 1 ad.VoteLastValid = 100_000 initAccounts[genaddrs[i]] = ad } initKeys[poolAddr] = poolSecret - initAccounts[poolAddr] = basics.MakeAccountData(basics.NotParticipating, basics.MicroAlgos{Raw: 1234567}) + initAccounts[poolAddr] = basics_testing.MakeAccountData(basics.NotParticipating, basics.MicroAlgos{Raw: 1234567}) initKeys[sinkAddr] = sinkSecret - initAccounts[sinkAddr] = basics.MakeAccountData(basics.NotParticipating, basics.MicroAlgos{Raw: 7654321}) + initAccounts[sinkAddr] = basics_testing.MakeAccountData(basics.NotParticipating, basics.MicroAlgos{Raw: 7654321}) incentivePoolBalanceAtGenesis := initAccounts[poolAddr].MicroAlgos var initialRewardsPerRound uint64 diff --git a/ledger/testing/randomAccounts.go b/ledger/testing/randomAccounts.go index c3c559911a..9fb9953496 100644 --- a/ledger/testing/randomAccounts.go +++ b/ledger/testing/randomAccounts.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -34,6 +34,16 @@ import ( var testPoolAddr = basics.Address{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff} var testSinkAddr = basics.Address{0x2c, 0x2a, 0x6c, 0xe9, 0xa9, 0xa7, 0xc2, 0x8c, 0x22, 0x95, 0xfd, 0x32, 0x4f, 0x77, 0xa5, 0x4, 0x8b, 0x42, 0xc2, 0xb7, 0xa8, 0x54, 0x84, 0xb6, 0x80, 0xb1, 0xe1, 0x3d, 0x59, 0x9b, 0xeb, 0x36} +// PoolAddr returns a copy of the test pool address +func PoolAddr() basics.Address { + return testPoolAddr +} + +// SinkAddr returns a copy of the test sink address +func SinkAddr() basics.Address { + return testSinkAddr +} + // RandomAddress generates a random address func RandomAddress() basics.Address { var addr basics.Address diff --git a/ledger/testing/randomAccounts_test.go b/ledger/testing/randomAccounts_test.go index 97744f497c..0829c4340b 100644 --- a/ledger/testing/randomAccounts_test.go +++ b/ledger/testing/randomAccounts_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/testing/testGenesis.go b/ledger/testing/testGenesis.go index 609118f248..80d752c759 100644 --- a/ledger/testing/testGenesis.go +++ b/ledger/testing/testGenesis.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/tracker.go b/ledger/tracker.go index 1612856444..b87c6fbe3f 100644 --- a/ledger/tracker.go +++ b/ledger/tracker.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/tracker_test.go b/ledger/tracker_test.go index 0f319ff555..8be223d6f5 100644 --- a/ledger/tracker_test.go +++ b/ledger/tracker_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/trackerdb.go b/ledger/trackerdb.go index 8a6dda4bfe..17c6872d95 100644 --- a/ledger/trackerdb.go +++ b/ledger/trackerdb.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/txnbench_test.go b/ledger/txnbench_test.go index 788ecbe1fc..ddc7aeba99 100644 --- a/ledger/txnbench_test.go +++ b/ledger/txnbench_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/txtail.go b/ledger/txtail.go index b262b3ce3e..879f0ee7d3 100644 --- a/ledger/txtail.go +++ b/ledger/txtail.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/txtail_test.go b/ledger/txtail_test.go index 1f0d06a293..fce531aa9f 100644 --- a/ledger/txtail_test.go +++ b/ledger/txtail_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/voters.go b/ledger/voters.go index d0a76a6cd7..1648e707f4 100644 --- a/ledger/voters.go +++ b/ledger/voters.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -18,9 +18,10 @@ package ledger import ( "fmt" - "github.com/algorand/go-algorand/stateproof" "sync" + "github.com/algorand/go-algorand/stateproof" + "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/bookkeeping" diff --git a/ledger/voters_test.go b/ledger/voters_test.go index 78e9eb7b87..892d3679c8 100644 --- a/ledger/voters_test.go +++ b/ledger/voters_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/libgoal/accounts.go b/libgoal/accounts.go index 3fc02fc883..f9e223e69a 100644 --- a/libgoal/accounts.go +++ b/libgoal/accounts.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/libgoal/error.go b/libgoal/error.go index 63ce176aa1..ca18016921 100644 --- a/libgoal/error.go +++ b/libgoal/error.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/libgoal/libgoal.go b/libgoal/libgoal.go index c764890794..4a27b70587 100644 --- a/libgoal/libgoal.go +++ b/libgoal/libgoal.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -765,7 +765,7 @@ func (c *Client) ApplicationBoxes(appID uint64, maxBoxNum uint64) (resp model.Bo } // GetApplicationBoxByName takes an app's index and box name and returns its value. -// The box name should be of the form `encoding:value`. See logic.AppCallBytes for more information. +// The box name should be of the form `encoding:value`. See apps.AppCallBytes for more information. func (c *Client) GetApplicationBoxByName(index uint64, name string) (resp model.BoxResponse, err error) { algod, err := c.ensureAlgodClient() if err == nil { diff --git a/libgoal/libgoal_test.go b/libgoal/libgoal_test.go index 96c524ef45..87df50ad92 100644 --- a/libgoal/libgoal_test.go +++ b/libgoal/libgoal_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/libgoal/lockedFile.go b/libgoal/lockedFile.go index 26b235d243..6b09db8e3a 100644 --- a/libgoal/lockedFile.go +++ b/libgoal/lockedFile.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/libgoal/lockedFileLinux.go b/libgoal/lockedFileLinux.go index 9c60ca028d..eb90377f10 100644 --- a/libgoal/lockedFileLinux.go +++ b/libgoal/lockedFileLinux.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/libgoal/lockedFileUnix.go b/libgoal/lockedFileUnix.go index b32af5def4..0d2b50e622 100644 --- a/libgoal/lockedFileUnix.go +++ b/libgoal/lockedFileUnix.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/libgoal/lockedFileWindows.go b/libgoal/lockedFileWindows.go index c89785d80a..337943a5cf 100644 --- a/libgoal/lockedFileWindows.go +++ b/libgoal/lockedFileWindows.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/libgoal/participation.go b/libgoal/participation.go index 7bdb8981b6..f57629a362 100644 --- a/libgoal/participation.go +++ b/libgoal/participation.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/libgoal/system.go b/libgoal/system.go index 6cf7166710..a72214dfb0 100644 --- a/libgoal/system.go +++ b/libgoal/system.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/libgoal/teal.go b/libgoal/teal.go index f0b18ede68..90295799c2 100644 --- a/libgoal/teal.go +++ b/libgoal/teal.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/libgoal/transactions.go b/libgoal/transactions.go index ac2eaf26aa..5ff9e3bbaf 100644 --- a/libgoal/transactions.go +++ b/libgoal/transactions.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/libgoal/unencryptedWallet.go b/libgoal/unencryptedWallet.go index 8953fe0eac..35b74d7545 100644 --- a/libgoal/unencryptedWallet.go +++ b/libgoal/unencryptedWallet.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/libgoal/walletHandles.go b/libgoal/walletHandles.go index c587c9af5d..302d465956 100644 --- a/libgoal/walletHandles.go +++ b/libgoal/walletHandles.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/libgoal/wallets.go b/libgoal/wallets.go index 5ba91a5a23..3e0f12d545 100644 --- a/libgoal/wallets.go +++ b/libgoal/wallets.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/logging/collector.go b/logging/collector.go index 7e3bedf2d7..39203964c9 100644 --- a/logging/collector.go +++ b/logging/collector.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/logging/cyclicWriter.go b/logging/cyclicWriter.go index 0c88955df3..d6166a82aa 100644 --- a/logging/cyclicWriter.go +++ b/logging/cyclicWriter.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/logging/cyclicWriter_test.go b/logging/cyclicWriter_test.go index 5719be9303..d52b826bd2 100644 --- a/logging/cyclicWriter_test.go +++ b/logging/cyclicWriter_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/logging/log.go b/logging/log.go index 1849774edd..ebe600f7f7 100644 --- a/logging/log.go +++ b/logging/log.go @@ -134,6 +134,9 @@ type Logger interface { // Set the logging version (Info by default) SetLevel(Level) + // Get the logging version + GetLevel() Level + // Sets the output target SetOutput(io.Writer) @@ -155,7 +158,6 @@ type Logger interface { Metrics(category telemetryspec.Category, metrics telemetryspec.MetricDetails, details interface{}) Event(category telemetryspec.Category, identifier telemetryspec.Event) EventWithDetails(category telemetryspec.Category, identifier telemetryspec.Event, details interface{}) - StartOperation(category telemetryspec.Category, identifier telemetryspec.Operation) TelemetryOperation GetTelemetrySession() string GetTelemetryGUID() string GetInstanceName() string @@ -285,6 +287,10 @@ func (l logger) WithFields(fields Fields) Logger { } } +func (l logger) GetLevel() (lvl Level) { + return Level(l.entry.Logger.Level) +} + func (l logger) SetLevel(lvl Level) { l.entry.Logger.Level = logrus.Level(lvl) } @@ -451,13 +457,6 @@ func (l logger) EventWithDetails(category telemetryspec.Category, identifier tel } } -func (l logger) StartOperation(category telemetryspec.Category, identifier telemetryspec.Operation) TelemetryOperation { - if l.loggerState.telemetry != nil { - return l.loggerState.telemetry.logStartOperation(l, category, identifier) - } - return TelemetryOperation{} -} - func (l logger) CloseTelemetry() { if l.loggerState.telemetry != nil { l.loggerState.telemetry.Close() diff --git a/logging/logBuffer.go b/logging/logBuffer.go index 8064e0d17e..26b1045f26 100644 --- a/logging/logBuffer.go +++ b/logging/logBuffer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/logging/logBuffer_test.go b/logging/logBuffer_test.go index b2f8dc1e18..04a87f3f60 100644 --- a/logging/logBuffer_test.go +++ b/logging/logBuffer_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/logging/log_test.go b/logging/log_test.go index 2f4365ccb6..af62bae912 100644 --- a/logging/log_test.go +++ b/logging/log_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -60,6 +60,15 @@ func TestFileOutputNewLogger(t *testing.T) { } +func TestSetGetLevel(t *testing.T) { + partitiontest.PartitionTest(t) + + nl := NewLogger() + require.Equal(t, Info, nl.GetLevel()) + nl.SetLevel(Error) + require.Equal(t, Error, nl.GetLevel()) +} + func TestSetLevelNewLogger(t *testing.T) { partitiontest.PartitionTest(t) a := require.New(t) diff --git a/logging/logspec/agreement.go b/logging/logspec/agreement.go index 6091891fbc..45fe5e1b6d 100644 --- a/logging/logspec/agreement.go +++ b/logging/logspec/agreement.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/logging/logspec/ledger.go b/logging/logspec/ledger.go index e7538b562f..0ed363340f 100644 --- a/logging/logspec/ledger.go +++ b/logging/logspec/ledger.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/logging/logspec/root.go b/logging/logspec/root.go index 1bec1eb96a..6dd08d1d5d 100644 --- a/logging/logspec/root.go +++ b/logging/logspec/root.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/logging/telemetry.go b/logging/telemetry.go index 6ed62bf400..7daa471920 100644 --- a/logging/telemetry.go +++ b/logging/telemetry.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -244,12 +244,6 @@ func (t *telemetryState) logEvent(l logger, category telemetryspec.Category, ide t.logTelemetry(l, buildMessage(string(category), string(identifier)), details) } -func (t *telemetryState) logStartOperation(l logger, category telemetryspec.Category, identifier telemetryspec.Operation) TelemetryOperation { - op := makeTelemetryOperation(t, category, identifier) - t.logTelemetry(l, buildMessage(string(category), string(identifier), "Start"), nil) - return op -} - func buildMessage(args ...string) string { message := telemetryPrefix + strings.Join(args, telemetrySeparator) return message diff --git a/logging/telemetryCommon.go b/logging/telemetryCommon.go index d4d57a817d..41a13d9497 100644 --- a/logging/telemetryCommon.go +++ b/logging/telemetryCommon.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -17,24 +17,11 @@ package logging import ( - "sync" - "time" - "github.com/algorand/go-deadlock" "github.com/sirupsen/logrus" - - "github.com/algorand/go-algorand/logging/telemetryspec" + "sync" ) -// TelemetryOperation wraps the context for an ongoing telemetry.StartOperation call -type TelemetryOperation struct { - startTime time.Time - category telemetryspec.Category - identifier telemetryspec.Operation - telemetryState *telemetryState - pending int32 -} - type telemetryHook interface { Fire(entry *logrus.Entry) error Levels() []logrus.Level diff --git a/logging/telemetryConfig.go b/logging/telemetryConfig.go index 452202f919..eefd84e4a0 100644 --- a/logging/telemetryConfig.go +++ b/logging/telemetryConfig.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/logging/telemetryConfig_test.go b/logging/telemetryConfig_test.go index dd7cce322f..8a61ba41c9 100644 --- a/logging/telemetryConfig_test.go +++ b/logging/telemetryConfig_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/logging/telemetryFilteredHook.go b/logging/telemetryFilteredHook.go index 86c402b878..c925577830 100644 --- a/logging/telemetryFilteredHook.go +++ b/logging/telemetryFilteredHook.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/logging/telemetryOperation.go b/logging/telemetryOperation.go deleted file mode 100644 index 077902dd2f..0000000000 --- a/logging/telemetryOperation.go +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (C) 2019-2022 Algorand, Inc. -// This file is part of go-algorand -// -// go-algorand is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as -// published by the Free Software Foundation, either version 3 of the -// License, or (at your option) any later version. -// -// go-algorand is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with go-algorand. If not, see . - -package logging - -import ( - "sync/atomic" - "time" - - "github.com/sirupsen/logrus" - - "github.com/algorand/go-algorand/logging/telemetryspec" -) - -func makeTelemetryOperation(telemetryState *telemetryState, category telemetryspec.Category, identifier telemetryspec.Operation) TelemetryOperation { - return TelemetryOperation{ - startTime: time.Now(), - category: category, - identifier: identifier, - telemetryState: telemetryState, - pending: 1, // Indicates we should process Stop() when called - } -} - -// Stop is called to report the completion of an operation started by logger.StartOperation -func (op *TelemetryOperation) Stop(l logger, details interface{}) { - // If we have already called Stop, or if we're a nil operation, don't do anything - if !atomic.CompareAndSwapInt32(&op.pending, 1, 0) { - return - } - - elapsed := time.Since(op.startTime).Nanoseconds() - entry := l.WithFields(logrus.Fields{ - "duration": elapsed, - }).(logger) - - op.telemetryState.logTelemetry(entry, buildMessage(string(op.category), string(op.identifier), "Stop"), details) -} diff --git a/logging/telemetry_test.go b/logging/telemetry_test.go index a8a913831f..1d1b9f8d44 100644 --- a/logging/telemetry_test.go +++ b/logging/telemetry_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -19,12 +19,10 @@ package logging import ( "encoding/json" "fmt" - "os" - "testing" - "time" - "github.com/sirupsen/logrus" "github.com/stretchr/testify/require" + "os" + "testing" "github.com/algorand/go-deadlock" @@ -159,17 +157,11 @@ func TestTelemetryHook(t *testing.T) { f.telem.logMetrics(f.l, testString1, testMetrics{}, nil) f.telem.logEvent(f.l, testString1, testString2, nil) - op := f.telem.logStartOperation(f.l, testString1, testString2) - time.Sleep(1 * time.Millisecond) - op.Stop(f.l, nil) entries := f.hookEntries() - a.Equal(4, len(entries)) + a.Equal(2, len(entries)) a.Equal(buildMessage(testString1, testString2), entries[0]) a.Equal(buildMessage(testString1, testString2), entries[1]) - a.Equal(buildMessage(testString1, testString2, "Start"), entries[2]) - a.Equal(buildMessage(testString1, testString2, "Stop"), entries[3]) - a.NotZero(f.hookData()[3]["duration"]) } func TestNilMetrics(t *testing.T) { @@ -182,23 +174,6 @@ func TestNilMetrics(t *testing.T) { a.Zero(len(f.hookEntries())) } -func TestMultipleOperationStop(t *testing.T) { - partitiontest.PartitionTest(t) - a := require.New(t) - f := makeTelemetryTestFixture(logrus.InfoLevel) - - op := f.telem.logStartOperation(f.l, testString1, testString2) - op.Stop(f.l, nil) - - // Start and stop should result in 2 entries - a.Equal(2, len(f.hookEntries())) - - op.Stop(f.l, nil) - - // Calling stop again should not result in another entry - a.Equal(2, len(f.hookEntries())) -} - func TestDetails(t *testing.T) { partitiontest.PartitionTest(t) a := require.New(t) diff --git a/logging/telemetryhook.go b/logging/telemetryhook.go index 88ef932332..8e036eacda 100644 --- a/logging/telemetryhook.go +++ b/logging/telemetryhook.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -222,9 +222,9 @@ func (el elasticClientLogger) Printf(format string, v ...interface{}) { case logrus.InfoLevel: el.logger.Infof(format, v...) case logrus.WarnLevel: - el.logger.Warnf(format, v...) + el.logger.WithFields(Fields{"TelemetryError": true}).Warnf(format, v...) default: - el.logger.Errorf(format, v...) + el.logger.WithFields(Fields{"TelemetryError": true}).Errorf(format, v...) } } diff --git a/logging/telemetryhook_test.go b/logging/telemetryhook_test.go index 40c25832f9..43bc1acf41 100644 --- a/logging/telemetryhook_test.go +++ b/logging/telemetryhook_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/logging/telemetryspec/category.go b/logging/telemetryspec/category.go index c7a8acf7a5..44077c722f 100644 --- a/logging/telemetryspec/category.go +++ b/logging/telemetryspec/category.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/logging/telemetryspec/event.go b/logging/telemetryspec/event.go index 0cc633429c..34d161bce5 100644 --- a/logging/telemetryspec/event.go +++ b/logging/telemetryspec/event.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -99,17 +99,6 @@ type BlockAcceptedEventDetails struct { VoteBufLen uint64 } -// TopAccountsEvent event -const TopAccountsEvent Event = "TopAccounts" - -// TopAccountEventDetails contains details for the BlockAcceptedEvent -type TopAccountEventDetails struct { - Round uint64 - OnlineAccounts []map[string]interface{} - OnlineCirculation uint64 - OfflineCirculation uint64 -} - // AccountRegisteredEvent event const AccountRegisteredEvent Event = "AccountRegistered" diff --git a/logging/telemetryspec/eventTiming.go b/logging/telemetryspec/eventTiming.go index a43177a89f..be6f803286 100644 --- a/logging/telemetryspec/eventTiming.go +++ b/logging/telemetryspec/eventTiming.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/logging/telemetryspec/metric.go b/logging/telemetryspec/metric.go index ec538769c5..fcb6c1d45d 100644 --- a/logging/telemetryspec/metric.go +++ b/logging/telemetryspec/metric.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -18,6 +18,7 @@ package telemetryspec import ( "bytes" + "encoding/json" "fmt" "strconv" "strings" @@ -60,7 +61,7 @@ type AssembleBlockStats struct { TotalLength uint64 EarlyCommittedCount uint64 // number of transaction groups that were pending on the transaction pool but have been included in previous block Nanoseconds int64 - ProcessingTime transactionProcessingTimeDistibution + ProcessingTime transactionProcessingTimeDistribution BlockGenerationDuration uint64 TransactionsLoopStartTime int64 StateProofNextRound uint64 // next round for which state proof if expected @@ -215,7 +216,7 @@ func (m AccountsUpdateMetrics) Identifier() Metric { return accountsUpdateMetricsIdentifier } -type transactionProcessingTimeDistibution struct { +type transactionProcessingTimeDistribution struct { // 10 buckets: 0-100Kns, 100Kns-200Kns .. 900Kns-1ms // 9 buckets: 1ms-2ms .. 9ms-10ms // 9 buckets: 10ms-20ms .. 90ms-100ms @@ -226,7 +227,7 @@ type transactionProcessingTimeDistibution struct { // MarshalJSON supports json.Marshaler interface // generate comma delimited text representing the transaction processing timing -func (t transactionProcessingTimeDistibution) MarshalJSON() ([]byte, error) { +func (t transactionProcessingTimeDistribution) MarshalJSON() ([]byte, error) { var outStr strings.Builder outStr.WriteString("[") for i, bucket := range t.transactionBuckets { @@ -239,7 +240,7 @@ func (t transactionProcessingTimeDistibution) MarshalJSON() ([]byte, error) { return []byte(outStr.String()), nil } -func (t *transactionProcessingTimeDistibution) AddTransaction(duration time.Duration) { +func (t *transactionProcessingTimeDistribution) AddTransaction(duration time.Duration) { var idx int64 if duration < 10*time.Millisecond { if duration < time.Millisecond { @@ -260,3 +261,53 @@ func (t *transactionProcessingTimeDistibution) AddTransaction(duration time.Dura t.transactionBuckets[idx]++ } } + +func (t *transactionProcessingTimeDistribution) UnmarshalJSON(data []byte) error { + var arr []json.Number + if err := json.Unmarshal(data, &arr); err != nil { + return err + } + if len(arr) != len(t.transactionBuckets) { + return fmt.Errorf("array has %d buckets, should have %d", len(arr), len(t.transactionBuckets)) + } + for i := range t.transactionBuckets { + val, err := arr[i].Int64() + if err != nil { + return fmt.Errorf("bucket has invalid value %s", arr[i]) + } + t.transactionBuckets[i] = int(val) + } + return nil +} + +func (t *transactionProcessingTimeDistribution) MarshalString() string { + var out strings.Builder + var offset int + var base, mul time.Duration +bucketloop: + for i, val := range t.transactionBuckets { + switch { + case i < 10: + mul = 100000 * time.Nanosecond + case i < 19: + mul = time.Millisecond + base = mul + offset = 10 + case i < 28: + mul = 10 * time.Millisecond + base = mul + offset = 19 + case i < 37: + mul = 100 * time.Millisecond + base = mul + offset = 28 + case i == 37: + break bucketloop + } + start := base + time.Duration(i-offset)*mul + end := base + time.Duration(i+1-offset)*mul + out.WriteString(fmt.Sprintf("%s - %s: %d\n", start, end, val)) + } + out.WriteString(fmt.Sprintf(">1s: %d\n", t.transactionBuckets[37])) + return out.String() +} diff --git a/logging/telemetryspec/metric_test.go b/logging/telemetryspec/metric_test.go index c6d6489da5..85693bdbab 100644 --- a/logging/telemetryspec/metric_test.go +++ b/logging/telemetryspec/metric_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -27,9 +27,9 @@ import ( "github.com/algorand/go-algorand/test/partitiontest" ) -func TestTransactionProcessingTimeDistibutionFormatting(t *testing.T) { +func TestTransactionProcessingTimeDistributionFormatting(t *testing.T) { partitiontest.PartitionTest(t) - var processingTime transactionProcessingTimeDistibution + var processingTime transactionProcessingTimeDistribution processingTime.AddTransaction(50000 * time.Nanosecond) processingTime.AddTransaction(80000 * time.Nanosecond) processingTime.AddTransaction(120000 * time.Nanosecond) @@ -39,10 +39,15 @@ func TestTransactionProcessingTimeDistibutionFormatting(t *testing.T) { processingTime.AddTransaction(2 * time.Millisecond) bytes, err := processingTime.MarshalJSON() require.NoError(t, err) - require.Equal(t, []byte("[2,3,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]"), bytes) + expected := "[2,3,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]" + require.Equal(t, []byte(expected), bytes) + + var decPT transactionProcessingTimeDistribution + require.NoError(t, json.Unmarshal([]byte(expected), &decPT)) + require.Equal(t, processingTime, decPT) container := struct { - ProcessingTime transactionProcessingTimeDistibution + ProcessingTime transactionProcessingTimeDistribution }{ProcessingTime: processingTime} bytes, err = json.Marshal(container) @@ -50,6 +55,15 @@ func TestTransactionProcessingTimeDistibutionFormatting(t *testing.T) { require.Equal(t, []byte("{\"ProcessingTime\":[2,3,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}"), bytes) } +func TestTransactionProcessingTimeDistributionPrint(t *testing.T) { + partitiontest.PartitionTest(t) + + var decPT transactionProcessingTimeDistribution + expected := "[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]" + require.NoError(t, json.Unmarshal([]byte(expected), &decPT)) + t.Log("\n" + decPT.MarshalString()) +} + func TestAssembleBlockStatsString(t *testing.T) { partitiontest.PartitionTest(t) diff --git a/logging/testingLogger.go b/logging/testingLogger.go index 60696b9635..6492c7ddd8 100644 --- a/logging/testingLogger.go +++ b/logging/testingLogger.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/logging/usage.go b/logging/usage.go index da668a72a7..2a668ceb72 100644 --- a/logging/usage.go +++ b/logging/usage.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -54,7 +54,7 @@ func UsageLogThread(ctx context.Context, log Logger, period time.Duration, wg *s Utime, Stime, _ = util.GetCurrentProcessTimes() runtime.ReadMemStats(&mst) - ramUsageGauge.Set(float64(mst.HeapInuse)) + ramUsageGauge.Set(uint64(mst.HeapInuse)) if hasPrev { userNanos := Utime - prevUtime diff --git a/netdeploy/network.go b/netdeploy/network.go index 6d14819c4c..1d5e503de3 100644 --- a/netdeploy/network.go +++ b/netdeploy/network.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/netdeploy/networkTemplate.go b/netdeploy/networkTemplate.go index 5456179287..b0a913a445 100644 --- a/netdeploy/networkTemplate.go +++ b/netdeploy/networkTemplate.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/netdeploy/networkTemplates_test.go b/netdeploy/networkTemplates_test.go index c3e6445bdd..f8e3ee6857 100644 --- a/netdeploy/networkTemplates_test.go +++ b/netdeploy/networkTemplates_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/netdeploy/network_test.go b/netdeploy/network_test.go index 383d7f9529..dce8a10e4d 100644 --- a/netdeploy/network_test.go +++ b/netdeploy/network_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/netdeploy/remote/bootstrappedNetwork.go b/netdeploy/remote/bootstrappedNetwork.go index 3738ba4662..855a2ea3b3 100644 --- a/netdeploy/remote/bootstrappedNetwork.go +++ b/netdeploy/remote/bootstrappedNetwork.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/netdeploy/remote/bootstrappedNetwork_test.go b/netdeploy/remote/bootstrappedNetwork_test.go index 1c9f14e6e3..ac0afa6428 100644 --- a/netdeploy/remote/bootstrappedNetwork_test.go +++ b/netdeploy/remote/bootstrappedNetwork_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/netdeploy/remote/buildConfig.go b/netdeploy/remote/buildConfig.go index 22e35e21e5..bffb85f0af 100644 --- a/netdeploy/remote/buildConfig.go +++ b/netdeploy/remote/buildConfig.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/netdeploy/remote/deployedNetwork.go b/netdeploy/remote/deployedNetwork.go index 353389c991..a6b7be3f77 100644 --- a/netdeploy/remote/deployedNetwork.go +++ b/netdeploy/remote/deployedNetwork.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/netdeploy/remote/deployedNetwork_test.go b/netdeploy/remote/deployedNetwork_test.go index f33e7cea21..98149ac32e 100644 --- a/netdeploy/remote/deployedNetwork_test.go +++ b/netdeploy/remote/deployedNetwork_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/netdeploy/remote/hostConfig.go b/netdeploy/remote/hostConfig.go index d9710b80b1..34af36ef41 100644 --- a/netdeploy/remote/hostConfig.go +++ b/netdeploy/remote/hostConfig.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/netdeploy/remote/hostTemplate.go b/netdeploy/remote/hostTemplate.go index faeccb3d02..530c80b325 100644 --- a/netdeploy/remote/hostTemplate.go +++ b/netdeploy/remote/hostTemplate.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/netdeploy/remote/nodeConfig.go b/netdeploy/remote/nodeConfig.go index 143271d5df..2a6eee0bfb 100644 --- a/netdeploy/remote/nodeConfig.go +++ b/netdeploy/remote/nodeConfig.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/netdeploy/remote/nodeWalletData.go b/netdeploy/remote/nodeWalletData.go index 1f08ea2520..87ca8d41cc 100644 --- a/netdeploy/remote/nodeWalletData.go +++ b/netdeploy/remote/nodeWalletData.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/netdeploy/remote/nodecfg/nodeConfigurator.go b/netdeploy/remote/nodecfg/nodeConfigurator.go index 2d286f7dea..4621373b25 100644 --- a/netdeploy/remote/nodecfg/nodeConfigurator.go +++ b/netdeploy/remote/nodecfg/nodeConfigurator.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -87,7 +87,7 @@ func (nc *nodeConfigurator) apply(rootConfigDir, rootNodeDir string) (err error) for _, nodeDir := range nodeDirs { nodeDir.delaySave = true - err = nodeDir.configure(nc.dnsName) + err = nodeDir.configure() if err != nil { break } @@ -96,7 +96,7 @@ func (nc *nodeConfigurator) apply(rootConfigDir, rootNodeDir string) (err error) nodeDir.saveConfig() } - if err == nil { + if err == nil && nc.dnsName != "" { fmt.Fprint(os.Stdout, "... registering DNS / SRV records\n") err = nc.registerDNSRecords() } diff --git a/netdeploy/remote/nodecfg/nodeDir.go b/netdeploy/remote/nodecfg/nodeDir.go index 1136875457..59f2eba509 100644 --- a/netdeploy/remote/nodecfg/nodeDir.go +++ b/netdeploy/remote/nodecfg/nodeDir.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -52,7 +52,7 @@ type nodeDir struct { // * EnableBlockStats // * DashboardEndpoint // * DeadlockOverride -func (nd *nodeDir) configure(dnsName string) (err error) { +func (nd *nodeDir) configure() (err error) { fmt.Fprintf(os.Stdout, "Configuring Node %s\n", nd.Name) if err = nd.configureRelay(nd.IsRelay()); err != nil { fmt.Fprintf(os.Stdout, "Error during configureRelay: %s\n", err) diff --git a/netdeploy/remote/topology.go b/netdeploy/remote/topology.go index b909abda57..1f0b6d7364 100644 --- a/netdeploy/remote/topology.go +++ b/netdeploy/remote/topology.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/connPerfMon.go b/network/connPerfMon.go index d1b2e72194..e74614ae10 100644 --- a/network/connPerfMon.go +++ b/network/connPerfMon.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/connPerfMon_test.go b/network/connPerfMon_test.go index b323e1ec2b..a8398b0f36 100644 --- a/network/connPerfMon_test.go +++ b/network/connPerfMon_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/dialer.go b/network/dialer.go index 8d7c18aaac..0dc3619d41 100644 --- a/network/dialer.go +++ b/network/dialer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/limited_reader_slurper.go b/network/limited_reader_slurper.go index 8795579f4c..2bdbb756a0 100644 --- a/network/limited_reader_slurper.go +++ b/network/limited_reader_slurper.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/limited_reader_slurper_test.go b/network/limited_reader_slurper_test.go index 5170693485..92509d95f6 100644 --- a/network/limited_reader_slurper_test.go +++ b/network/limited_reader_slurper_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/messageFilter.go b/network/messageFilter.go index c497b27700..b42651f46e 100644 --- a/network/messageFilter.go +++ b/network/messageFilter.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/messageFilter_test.go b/network/messageFilter_test.go index 15ce99b9aa..1df0e2c00f 100644 --- a/network/messageFilter_test.go +++ b/network/messageFilter_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/messagetracer/graphtrace.go b/network/messagetracer/graphtrace.go index 68c465914b..5438c78345 100644 --- a/network/messagetracer/graphtrace.go +++ b/network/messagetracer/graphtrace.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/messagetracer/interface.go b/network/messagetracer/interface.go index 567a7860b7..e684cc1797 100644 --- a/network/messagetracer/interface.go +++ b/network/messagetracer/interface.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/msgCompressor.go b/network/msgCompressor.go index 463e38f559..a46f37f2c2 100644 --- a/network/msgCompressor.go +++ b/network/msgCompressor.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/msgCompressor_test.go b/network/msgCompressor_test.go index 0a8713c870..5e3b927f90 100644 --- a/network/msgCompressor_test.go +++ b/network/msgCompressor_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/msgOfInterest.go b/network/msgOfInterest.go index 2345ab20fd..c7a3faa126 100644 --- a/network/msgOfInterest.go +++ b/network/msgOfInterest.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/msgOfInterest_test.go b/network/msgOfInterest_test.go index 971886b54e..c8c8dfda35 100644 --- a/network/msgOfInterest_test.go +++ b/network/msgOfInterest_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/multiplexer.go b/network/multiplexer.go index cc25efd3ed..fe5b3dcf42 100644 --- a/network/multiplexer.go +++ b/network/multiplexer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/multiplexer_test.go b/network/multiplexer_test.go index 58ce88becc..1d0215b909 100644 --- a/network/multiplexer_test.go +++ b/network/multiplexer_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/netprio.go b/network/netprio.go index bc512afa05..378bea4c05 100644 --- a/network/netprio.go +++ b/network/netprio.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/netprio_test.go b/network/netprio_test.go index ff02d4abbe..4fc920e8c2 100644 --- a/network/netprio_test.go +++ b/network/netprio_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/peersheap.go b/network/peersheap.go index dfc572aaed..03224b0ea5 100644 --- a/network/peersheap.go +++ b/network/peersheap.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/phonebook.go b/network/phonebook.go index 656bf9c6d3..1ff3ed542d 100644 --- a/network/phonebook.go +++ b/network/phonebook.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/phonebook_test.go b/network/phonebook_test.go index b1d6c35f4b..64f0d7c03a 100644 --- a/network/phonebook_test.go +++ b/network/phonebook_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/rateLimitingTransport.go b/network/rateLimitingTransport.go index 88c6fec6b5..a3fd332cb5 100644 --- a/network/rateLimitingTransport.go +++ b/network/rateLimitingTransport.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/requestLogger.go b/network/requestLogger.go index b455de85c3..562abd9f56 100644 --- a/network/requestLogger.go +++ b/network/requestLogger.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/requestLogger_test.go b/network/requestLogger_test.go index b45d985dff..a17d054e43 100644 --- a/network/requestLogger_test.go +++ b/network/requestLogger_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/requestTracker.go b/network/requestTracker.go index 2ae34c81f1..d2f05d8bf6 100644 --- a/network/requestTracker.go +++ b/network/requestTracker.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/requestTracker_test.go b/network/requestTracker_test.go index 8e9a03eb39..8998b41f77 100644 --- a/network/requestTracker_test.go +++ b/network/requestTracker_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/topics.go b/network/topics.go index d9d05dab93..762312585d 100644 --- a/network/topics.go +++ b/network/topics.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/topics_test.go b/network/topics_test.go index 07b29d09e7..b01ede7ff3 100644 --- a/network/topics_test.go +++ b/network/topics_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/wsNetwork.go b/network/wsNetwork.go index d6778a132f..b617cf63b1 100644 --- a/network/wsNetwork.go +++ b/network/wsNetwork.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -1182,8 +1182,8 @@ func (wn *WebsocketNetwork) ServeHTTP(response http.ResponseWriter, request *htt wn.maybeSendMessagesOfInterest(peer, nil) - peers.Set(float64(wn.NumPeers())) - incomingPeers.Set(float64(wn.numIncomingPeers())) + peers.Set(uint64(wn.NumPeers())) + incomingPeers.Set(uint64(wn.numIncomingPeers())) } func (wn *WebsocketNetwork) maybeSendMessagesOfInterest(peer *wsPeer, messagesOfInterestEnc []byte) { @@ -2214,8 +2214,8 @@ func (wn *WebsocketNetwork) tryConnect(addr, gossipAddr string) { wn.maybeSendMessagesOfInterest(peer, nil) - peers.Set(float64(wn.NumPeers())) - outgoingPeers.Set(float64(wn.numOutgoingPeers())) + peers.Set(uint64(wn.NumPeers())) + outgoingPeers.Set(uint64(wn.numOutgoingPeers())) if wn.prioScheme != nil { challenge := response.Header.Get(PriorityChallengeHeader) @@ -2332,9 +2332,9 @@ func (wn *WebsocketNetwork) removePeer(peer *wsPeer, reason disconnectReason) { PPCount: atomic.LoadUint64(&peer.ppMessageCount), }) - peers.Set(float64(wn.NumPeers())) - incomingPeers.Set(float64(wn.numIncomingPeers())) - outgoingPeers.Set(float64(wn.numOutgoingPeers())) + peers.Set(uint64(wn.NumPeers())) + incomingPeers.Set(uint64(wn.numIncomingPeers())) + outgoingPeers.Set(uint64(wn.numOutgoingPeers())) wn.peersLock.Lock() defer wn.peersLock.Unlock() @@ -2399,8 +2399,8 @@ func (wn *WebsocketNetwork) countPeersSetGauges() { numIn++ } } - networkIncomingConnections.Set(float64(numIn)) - networkOutgoingConnections.Set(float64(numOut)) + networkIncomingConnections.Set(uint64(numIn)) + networkOutgoingConnections.Set(uint64(numOut)) } func justHost(hostPort string) string { diff --git a/network/wsNetwork_test.go b/network/wsNetwork_test.go index 61e0678287..726db954c6 100644 --- a/network/wsNetwork_test.go +++ b/network/wsNetwork_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/wsPeer.go b/network/wsPeer.go index 94a1bd2b79..b608d5dd1b 100644 --- a/network/wsPeer.go +++ b/network/wsPeer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -275,6 +275,9 @@ type wsPeer struct { // clientDataStoreMu synchronizes access to clientDataStore clientDataStoreMu deadlock.Mutex + + // closers is a slice of functions to run when the peer is closed + closers []func() } // HTTPPeer is what the opaque Peer might be. @@ -323,7 +326,8 @@ func (wp *wsPeer) Version() string { return wp.version } -// Unicast sends the given bytes to this specific peer. Does not wait for message to be sent. +// Unicast sends the given bytes to this specific peer. Does not wait for message to be sent. +// // (Implements UnicastPeer) func (wp *wsPeer) Unicast(ctx context.Context, msg []byte, tag protocol.Tag) error { var err error @@ -843,6 +847,10 @@ func (wp *wsPeer) Close(deadline time.Time) { wp.net.log.Infof("failed to CloseWithoutFlush to connection for %s", wp.conn.RemoteAddr().String()) } } + // now call all registered closers + for _, f := range wp.closers { + f() + } } // CloseAndWait internally calls Close() then waits for all peer activity to stop @@ -963,6 +971,13 @@ func (wp *wsPeer) pfProposalCompressionSupported() bool { return wp.features&pfCompressedProposal != 0 } +func (wp *wsPeer) OnClose(f func()) { + if wp.closers == nil { + wp.closers = []func(){} + } + wp.closers = append(wp.closers, f) +} + type peerFeatureFlag int const pfCompressedProposal peerFeatureFlag = 1 diff --git a/network/wsPeer_test.go b/network/wsPeer_test.go index 2798a52566..7ed49b17c2 100644 --- a/network/wsPeer_test.go +++ b/network/wsPeer_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/node/assemble_test.go b/node/assemble_test.go index 3a3d3979a4..8b0a8afaeb 100644 --- a/node/assemble_test.go +++ b/node/assemble_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/node/error.go b/node/error.go index 02bf72505e..d177f0c870 100644 --- a/node/error.go +++ b/node/error.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/node/impls.go b/node/impls.go index 2b60cf04ba..a33bc6e737 100644 --- a/node/impls.go +++ b/node/impls.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/node/indexer/db.go b/node/indexer/db.go index 9c74d4ae17..a46106af07 100644 --- a/node/indexer/db.go +++ b/node/indexer/db.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/node/indexer/indexer.go b/node/indexer/indexer.go index c022d48efc..064685fe7c 100644 --- a/node/indexer/indexer.go +++ b/node/indexer/indexer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/node/indexer/indexer_test.go b/node/indexer/indexer_test.go index d8fbea077d..ff1e398a45 100644 --- a/node/indexer/indexer_test.go +++ b/node/indexer/indexer_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/node/netprio.go b/node/netprio.go index 9057fb3282..1bf6c17a1e 100644 --- a/node/netprio.go +++ b/node/netprio.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/node/node.go b/node/node.go index e87aad9338..94bb876d88 100644 --- a/node/node.go +++ b/node/node.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -42,6 +42,7 @@ import ( "github.com/algorand/go-algorand/data/transactions/verify" "github.com/algorand/go-algorand/ledger" "github.com/algorand/go-algorand/ledger/ledgercore" + "github.com/algorand/go-algorand/ledger/simulation" "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/network" "github.com/algorand/go-algorand/network/messagetracer" @@ -89,6 +90,11 @@ type StatusReport struct { CatchpointCatchupVerifiedKVs uint64 CatchpointCatchupTotalBlocks uint64 CatchpointCatchupAcquiredBlocks uint64 + UpgradePropose protocol.ConsensusVersion + UpgradeApprove bool + UpgradeDelay uint64 + NextProtocolVoteBefore basics.Round + NextProtocolApprovals uint64 } // TimeSinceLastRound returns the time since the last block was approved (locally), or 0 if no blocks seen @@ -192,8 +198,6 @@ func MakeFull(log logging.Logger, rootDir string, cfg config.Local, phonebookAdd p2pNode.SetPrioScheme(node) node.net = p2pNode - accountListener := makeTopAccountListener(log) - // load stored data genesisDir := filepath.Join(rootDir, genesis.ID()) ledgerPathnamePrefix := filepath.Join(genesisDir, config.LedgerFilenamePrefix) @@ -213,7 +217,7 @@ func MakeFull(log logging.Logger, rootDir string, cfg config.Local, phonebookAdd node.cryptoPool = execpool.MakePool(node) node.lowPriorityCryptoVerificationPool = execpool.MakeBacklog(node.cryptoPool, 2*node.cryptoPool.GetParallelism(), execpool.LowPriority, node) node.highPriorityCryptoVerificationPool = execpool.MakeBacklog(node.cryptoPool, 2*node.cryptoPool.GetParallelism(), execpool.HighPriority, node) - node.ledger, err = data.LoadLedger(node.log, ledgerPathnamePrefix, false, genesis.Proto, genalloc, node.genesisID, node.genesisHash, []ledger.BlockListener{}, cfg) + node.ledger, err = data.LoadLedger(node.log, ledgerPathnamePrefix, false, genesis.Proto, genalloc, node.genesisID, node.genesisHash, []ledgercore.BlockListener{}, cfg) if err != nil { log.Errorf("Cannot initialize ledger (%s): %v", ledgerPathnamePrefix, err) return nil, err @@ -221,14 +225,11 @@ func MakeFull(log logging.Logger, rootDir string, cfg config.Local, phonebookAdd node.transactionPool = pools.MakeTransactionPool(node.ledger.Ledger, cfg, node.log) - blockListeners := []ledger.BlockListener{ + blockListeners := []ledgercore.BlockListener{ node.transactionPool, node, } - if node.config.EnableTopAccountsReporting { - blockListeners = append(blockListeners, &accountListener) - } node.ledger.RegisterBlockListeners(blockListeners) txHandlerOpts := data.TxHandlerOpts{ TxPool: node.transactionPool, @@ -239,7 +240,11 @@ func MakeFull(log logging.Logger, rootDir string, cfg config.Local, phonebookAdd GenesisHash: node.genesisHash, Config: cfg, } - node.txHandler = data.MakeTxHandler(txHandlerOpts) + node.txHandler, err = data.MakeTxHandler(txHandlerOpts) + if err != nil { + log.Errorf("Cannot initialize TxHandler: %v", err) + return nil, err + } // Indexer setup if cfg.IsIndexerActive && cfg.Archival { @@ -282,7 +287,11 @@ func MakeFull(log logging.Logger, rootDir string, cfg config.Local, phonebookAdd RandomSource: node, BacklogPool: node.highPriorityCryptoVerificationPool, } - node.agreementService = agreement.MakeService(agreementParameters) + node.agreementService, err = agreement.MakeService(agreementParameters) + if err != nil { + log.Errorf("unable to initialize agreement: %v", err) + return nil, err + } node.catchupBlockAuth = blockAuthenticatorImpl{Ledger: node.ledger, AsyncVoteVerifier: agreement.MakeAsyncVoteVerifier(node.lowPriorityCryptoVerificationPool)} node.catchupService = catchup.MakeService(node.log, node.config, p2pNode, node.ledger, node.catchupBlockAuth, agreementLedger.UnmatchedPendingCertificates, node.lowPriorityCryptoVerificationPool) @@ -398,7 +407,8 @@ func (node *AlgorandFullNode) startMonitoringRoutines() { go node.oldKeyDeletionThread(node.ctx.Done()) if node.config.EnableUsageLog { - go logging.UsageLogThread(node.ctx, node.log, 100*time.Millisecond, nil) + node.monitoringRoutinesWaitGroup.Add(1) + go logging.UsageLogThread(node.ctx, node.log, 100*time.Millisecond, &node.monitoringRoutinesWaitGroup) } } @@ -513,7 +523,7 @@ func (node *AlgorandFullNode) broadcastSignedTxGroup(txgroup []transactions.Sign return err } - _, err = verify.TxnGroup(txgroup, b, node.ledger.VerifiedTransactionCache(), node.ledger) + _, err = verify.TxnGroup(txgroup, &b, node.ledger.VerifiedTransactionCache(), node.ledger) if err != nil { node.log.Warnf("malformed transaction: %v", err) return err @@ -545,6 +555,13 @@ func (node *AlgorandFullNode) broadcastSignedTxGroup(txgroup []transactions.Sign return nil } +// Simulate speculatively runs a transaction group against the current +// blockchain state and returns the effects and/or errors that would result. +func (node *AlgorandFullNode) Simulate(txgroup []transactions.SignedTxn) (vb *ledgercore.ValidatedBlock, missingSignatures bool, err error) { + simulator := simulation.MakeSimulator(node.ledger) + return simulator.Simulate(txgroup) +} + // ListTxns returns SignedTxns associated with a specific account in a range of Rounds (inclusive). // TxnWithStatus returns the round in which a particular transaction appeared, // since that information is not part of the SignedTxn itself. @@ -708,6 +725,13 @@ func (node *AlgorandFullNode) Status() (s StatusReport, err error) { s.LastCatchpoint = node.ledger.GetLastCatchpointLabel() s.SynchronizingTime = node.catchupService.SynchronizingTime() s.CatchupTime = node.catchupService.SynchronizingTime() + + s.UpgradePropose = b.UpgradeVote.UpgradePropose + s.UpgradeApprove = b.UpgradeApprove + s.UpgradeDelay = uint64(b.UpgradeVote.UpgradeDelay) + s.NextProtocolVoteBefore = b.NextProtocolVoteBefore + s.NextProtocolApprovals = b.UpgradeState.NextProtocolApprovals + } return @@ -729,22 +753,6 @@ func (node *AlgorandFullNode) GenesisHash() crypto.Digest { return node.genesisHash } -// PoolStats returns a PoolStatus structure reporting stats about the transaction pool -func (node *AlgorandFullNode) PoolStats() PoolStats { - r := node.ledger.Latest() - last, err := node.ledger.Block(r) - if err != nil { - node.log.Warnf("AlgorandFullNode: could not read ledger's last round: %v", err) - return PoolStats{} - } - - return PoolStats{ - NumConfirmed: uint64(len(last.Payset)), - NumOutstanding: uint64(node.transactionPool.PendingCount()), - NumExpired: uint64(node.transactionPool.NumExpired(r)), - } -} - // SuggestedFee returns the suggested fee per byte recommended to ensure a new transaction is processed in a timely fashion. // Caller should set fee to max(MinTxnFee, SuggestedFee() * len(encoded SignedTxn)) func (node *AlgorandFullNode) SuggestedFee() basics.MicroAlgos { @@ -1009,7 +1017,7 @@ func (node *AlgorandFullNode) txPoolGaugeThread(done <-chan struct{}) { for true { select { case <-ticker.C: - txPoolGauge.Set(float64(node.transactionPool.PendingCount())) + txPoolGauge.Set(uint64(node.transactionPool.PendingCount())) case <-done: return } diff --git a/node/node_test.go b/node/node_test.go index 647e77c94a..f584710902 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -21,7 +21,9 @@ import ( "math/rand" "os" "path/filepath" + "runtime" "strconv" + "strings" "sync" "testing" "time" @@ -245,6 +247,11 @@ func TestInitialSync(t *testing.T) { t.Skip("Test takes ~25 seconds.") } + if (runtime.GOARCH == "arm" || runtime.GOARCH == "arm64") && + strings.ToUpper(os.Getenv("CIRCLECI")) == "TRUE" { + t.Skip("Test is too heavy for amd64 builder running in parallel with other packages") + } + backlogPool := execpool.MakeBacklog(nil, 0, execpool.LowPriority, nil) defer backlogPool.Shutdown() diff --git a/node/topAccountListener.go b/node/topAccountListener.go deleted file mode 100644 index a0b90e0c5c..0000000000 --- a/node/topAccountListener.go +++ /dev/null @@ -1,212 +0,0 @@ -// Copyright (C) 2019-2022 Algorand, Inc. -// This file is part of go-algorand -// -// go-algorand is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as -// published by the Free Software Foundation, either version 3 of the -// License, or (at your option) any later version. -// -// go-algorand is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with go-algorand. If not, see . - -package node - -import ( - "sort" - - "github.com/algorand/go-algorand/data/basics" - "github.com/algorand/go-algorand/data/bookkeeping" - "github.com/algorand/go-algorand/ledger/ledgercore" - "github.com/algorand/go-algorand/logging" - "github.com/algorand/go-algorand/logging/telemetryspec" - "github.com/algorand/go-algorand/protocol" -) - -const numTopAccounts = 20 - -type topAccountListener struct { - log logging.Logger - - round basics.Round - - onlineCirculation basics.MicroAlgos - - totalCirculation basics.MicroAlgos - - // Cached between rounds to optimize ledger lookups. - accounts []basics.AccountDetail -} - -func makeTopAccountListener(log logging.Logger) topAccountListener { - return topAccountListener{ - log: log, - // TODO: If needed, increase size of this slice to buffer some accounts beyond the TopN. - accounts: make([]basics.AccountDetail, 0, numTopAccounts), - } -} - -func (t *topAccountListener) init(balances basics.BalanceDetail) { - t.round = balances.Round - t.onlineCirculation = balances.OnlineMoney - t.totalCirculation = balances.TotalMoney - t.accounts = t.accounts[:0] - - // TODO: After ledger refactor this might be replaced with a loop processing pages of results from a SQL command. - t.accounts = updateTopAccounts(t.accounts, balances.Accounts) -} - -// BlockListener event, triggered when the ledger writes a new block. -func (t *topAccountListener) OnNewBlock(block bookkeeping.Block, delta ledgercore.StateDelta) { - // XXX revise for new ledger API - // t.update(block, balances) - - // If number of accounts after update is insufficient, do a full re-init - if len(t.accounts) < numTopAccounts { - // XXX revise for new ledger API - // t.init(balances) - } - - t.sendEvent() -} - -// Account cache update logic here. -func (t *topAccountListener) update(b bookkeeping.Block, balances basics.BalanceDetail) { - lastRound := t.round - - // Update metadata. - t.round = balances.Round - t.onlineCirculation = balances.OnlineMoney - t.totalCirculation = balances.TotalMoney - - // Invalidate accounts if a round is missed (this also causes the accounts to be lazily initialized). - if lastRound+1 != balances.Round { - t.accounts = t.accounts[:0] - return - } - - // No transactions to update. - if len(balances.Accounts) == 0 { - return - } - - // Lookup map for updated accounts. - accountSet := make(map[basics.Address]bool) - - payset, err := b.DecodePaysetFlat() - if err != nil { - return - } - - for _, txad := range payset { - tx := txad.SignedTxn - if tx.Txn.Type == protocol.PaymentTx { - accountSet[tx.Txn.Receiver] = true - if tx.Txn.CloseRemainderTo != (basics.Address{}) { - accountSet[tx.Txn.CloseRemainderTo] = true - } - } - accountSet[tx.Txn.Src()] = true - } - - // TODO: This loop may not be needed with the ledger refactor. - // Since the balance list currently is unrelated to the transaction list, must iterate balances. - for _, tx := range balances.Accounts { - accountSet[tx.Address] = true - } - - // Remove any accounts in the updated accountSet (they'll be merged back if necessary) - t.accounts = removeSome(t.accounts, func(addr basics.AccountDetail) bool { return accountSet[addr.Address] }) - - // Grab the smallest record after removing modified accounts - smallestAccountSize := basics.MicroAlgos{Raw: 0} - if len(t.accounts) != 0 { - smallestAccountSize = t.accounts[len(t.accounts)-1].Algos - } - - t.accounts = updateTopAccounts(t.accounts, balances.Accounts) - - // Truncate any accounts after the smallest balance. - // This triggers a full re-init if the length falls below 'numTopAccounts' - for i, acct := range t.accounts { - if acct.Algos.LessThan(smallestAccountSize) { - t.accounts = t.accounts[:i] - return - } - } -} - -// Helper method to defragment a slice using a predicate to identify stale entries. -func removeSome(slice []basics.AccountDetail, predicate func(basics.AccountDetail) bool) []basics.AccountDetail { - // Remove updated accounts (they'll be merged back in as necessary) - next, end := 0, 0 - for (next + end) < len(slice) { - if predicate(slice[next+end]) { - end++ - } else { - slice[next] = slice[next+end] - next++ - } - } - - return slice[:next] -} - -// Merge largest accounts from balances into topN, removing values from topN as necessary. -// The underlying capacity will not be modified, but the length may increase. -// Note: Doesn't check for duplicates. -func updateTopAccounts(topN []basics.AccountDetail, balances []basics.AccountDetail) []basics.AccountDetail { - for _, account := range balances { - balance := account.Algos - - // Quick check for topN if capacity is reached. - if account.Status != basics.Online || len(topN) != 0 && len(topN) == cap(topN) && balance.Raw <= topN[len(topN)-1].Algos.Raw { - continue - } - - // Find insertion point. - pos := sort.Search(len(topN), func(i int) bool { - return topN[i].Algos.LessThan(balance) - }) - - // Increase capacity if more space is available. - if len(topN) < cap(topN) { - topN = topN[:len(topN)+1] - } - - // Shift upper elements and insert - if pos < len(topN) { - copy(topN[pos+1:], topN[pos:]) - topN[pos] = account - } - } - - return topN -} - -// Compile current top account state into a telemetry event, and send it. -func (t *topAccountListener) sendEvent() { - // Build accounts object. - payload := make([]map[string]interface{}, 0) - fCirculation := float64(t.onlineCirculation.ToUint64()) - for _, account := range t.accounts[:] { - entry := make(map[string]interface{}) - entry["address"] = account.Address.String() - entry["balance"] = account.Algos.ToUint64() - entry["stake"] = float64(account.Algos.ToUint64()) / fCirculation - payload = append(payload, entry) - } - - // Send it out - t.log.EventWithDetails(telemetryspec.Accounts, telemetryspec.TopAccountsEvent, - telemetryspec.TopAccountEventDetails{ - Round: uint64(t.round), - OnlineAccounts: payload, - OnlineCirculation: t.onlineCirculation.ToUint64(), - OfflineCirculation: t.totalCirculation.ToUint64() - t.onlineCirculation.ToUint64(), - }) -} diff --git a/node/topAccountListener_test.go b/node/topAccountListener_test.go deleted file mode 100644 index 2c25f85f70..0000000000 --- a/node/topAccountListener_test.go +++ /dev/null @@ -1,368 +0,0 @@ -// Copyright (C) 2019-2022 Algorand, Inc. -// This file is part of go-algorand -// -// go-algorand is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as -// published by the Free Software Foundation, either version 3 of the -// License, or (at your option) any later version. -// -// go-algorand is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with go-algorand. If not, see . - -package node - -import ( - "fmt" - "testing" - - "github.com/algorand/go-algorand/crypto" - "github.com/algorand/go-algorand/data/basics" - "github.com/algorand/go-algorand/data/bookkeeping" - "github.com/algorand/go-algorand/data/transactions" - "github.com/algorand/go-algorand/logging" - "github.com/algorand/go-algorand/protocol" - "github.com/algorand/go-algorand/test/partitiontest" -) - -// errorString is a trivial implementation of error. -type errorString struct { - s string -} - -func (e *errorString) Error() string { - return e.s -} - -func TestUpdateTopAccounts(t *testing.T) { - partitiontest.PartitionTest(t) - - var topN []basics.AccountDetail - var input []basics.AccountDetail - - // Empty target array. - topN = []basics.AccountDetail{} - input = []basics.AccountDetail{onlineDetail(byte(0), 1), onlineDetail(byte(1), 10)} - topN = updateTopAccounts(topN, input) - - if len(topN) != 0 { - t.Errorf("Target slice not 0: len(topN) == %d", len(topN)) - } - - // Extra space available - topN = make([]basics.AccountDetail, 0, 20) - input = []basics.AccountDetail{onlineDetail(byte(0), 1), onlineDetail(byte(1), 10)} - topN = updateTopAccounts(topN, input) - - if err := verifyAccountBalances([]uint64{10, 1}, topN); err != nil { - t.Error(err) - } - - // Overflow, unmodified - topN = make([]basics.AccountDetail, 0, 4) - input = []basics.AccountDetail{ - onlineDetail(byte(0), 11), - onlineDetail(byte(1), 12), - onlineDetail(byte(2), 13), - onlineDetail(byte(3), 14), - onlineDetail(byte(4), 1), - } - topN = updateTopAccounts(topN, input) - - if err := verifyAccountBalances([]uint64{14, 13, 12, 11}, topN); err != nil { - t.Error(err) - } - - // Overflow, insert front - topN = make([]basics.AccountDetail, 0, 4) - input = []basics.AccountDetail{ - onlineDetail(byte(1), 11), - onlineDetail(byte(2), 12), - onlineDetail(byte(3), 13), - onlineDetail(byte(4), 14), - onlineDetail(byte(5), 15), - } - topN = updateTopAccounts(topN, input) - - if err := verifyAccountBalances([]uint64{15, 14, 13, 12}, topN); err != nil { - t.Error(err) - } - - // Overflow, insert middle - topN = make([]basics.AccountDetail, 0, 4) - input = []basics.AccountDetail{ - onlineDetail(byte(1), 11), - onlineDetail(byte(2), 12), - onlineDetail(byte(3), 13), - onlineDetail(byte(4), 15), - onlineDetail(byte(5), 14), - } - topN = updateTopAccounts(topN, input) - - if err := verifyAccountBalances([]uint64{15, 14, 13, 12}, topN); err != nil { - t.Error(err) - } - - // Overflow, insert end - topN = make([]basics.AccountDetail, 0, 4) - input = []basics.AccountDetail{ - onlineDetail(byte(1), 11), - onlineDetail(byte(2), 13), - onlineDetail(byte(3), 14), - onlineDetail(byte(4), 15), - onlineDetail(byte(5), 12), - } - topN = updateTopAccounts(topN, input) - - if err := verifyAccountBalances([]uint64{15, 14, 13, 12}, topN); err != nil { - t.Error(err) - } - - // Ignore offline account, shouldn't change topN - topN = updateTopAccounts(topN, []basics.AccountDetail{detail(byte(6), 200, false)}) - topN = make([]basics.AccountDetail, 0, 4) - input = []basics.AccountDetail{ - onlineDetail(byte(1), 12), - onlineDetail(byte(2), 13), - onlineDetail(byte(3), 14), - onlineDetail(byte(4), 15), - detail(byte(5), 200, false), - } - topN = updateTopAccounts(topN, input) - - if err := verifyAccountBalances([]uint64{15, 14, 13, 12}, topN); err != nil { - t.Error(err) - } -} - -func TestRemoveSome(t *testing.T) { - partitiontest.PartitionTest(t) - - // Initialize slice with 100 accounts - var accountsSlice []basics.AccountDetail - for i := 0; i <= 100; i++ { - accountsSlice = append(accountsSlice, onlineDetail(byte(i), 10)) - } - - // Remove accounts where the first byte is divisible by 10 (which includes the first and last index - remove10s := func(details basics.AccountDetail) bool { - return getInt(details)%10 == 0 - } - - accountsSlice = removeSome(accountsSlice, remove10s) - - if len(accountsSlice) != 90 { - t.Errorf("Unexpected size found after removeSome/remove10s: 90 != %d", len(accountsSlice)) - } - for _, d := range accountsSlice { - if getInt(d)%10 == 0 { - t.Errorf("Unexpected value found after removeSome/remove10s: %d", getInt(d)) - } - } - - // Remove remaining accounts where the first byte is even - removeEven := func(details basics.AccountDetail) bool { - return getInt(details)%2 == 0 - } - - accountsSlice = removeSome(accountsSlice, removeEven) - - if len(accountsSlice) != 50 { - t.Errorf("Unexpected size found after removeSome/removeEven: 50 != %d", len(accountsSlice)) - } - for _, d := range accountsSlice { - if getInt(d)%2 == 0 { - t.Errorf("Unexpected value found after removeSome/removeEven: %d", getInt(d)) - } - } -} - -func TestUpdate(t *testing.T) { - partitiontest.PartitionTest(t) - - listener := topAccountListener{ - accounts: []basics.AccountDetail{}, - round: 1, - totalCirculation: basics.MicroAlgos{Raw: 100}, - onlineCirculation: basics.MicroAlgos{Raw: 100}, - } - - balanceUpdate := basics.BalanceDetail{ - Accounts: []basics.AccountDetail{}, - Round: 2, - OnlineMoney: basics.MicroAlgos{Raw: 100000}, - TotalMoney: basics.MicroAlgos{Raw: 1000000}, - } - - // Update when accounts is empty. - listener.update(bookkeeping.Block{}, balanceUpdate) - if err := verifyListener(listener, []uint64{}, 100000, 1000000, 2); err != nil { - t.Error(err) - } - - // Transactions causing acct 1 to increase reorders the TopN. - listener.accounts = []basics.AccountDetail{ - onlineDetail(byte(0), 15), - onlineDetail(byte(1), 10), - onlineDetail(byte(2), 5), - } - balanceUpdate.Accounts = []basics.AccountDetail{onlineDetail(byte(1), 100)} - balanceUpdate.Round++ - block := makeBlockWithTxnFor([]byte{3}, []byte{1}) - - listener.update(block, balanceUpdate) - - // 10 -> 100. - if err := verifyListener(listener, []uint64{100, 15, 5}, 100000, 1000000, 3); err != nil { - t.Error(err) - } - - // Transactions causing acct 1 to decrease and falls off topN truncates result. - listener.accounts = []basics.AccountDetail{ - onlineDetail(byte(0), 15), - onlineDetail(byte(1), 10), - onlineDetail(byte(2), 5), - } - balanceUpdate.Round++ - balanceUpdate.TotalMoney = basics.MicroAlgos{Raw: 99999999} - balanceUpdate.Accounts = []basics.AccountDetail{onlineDetail(byte(1), 1)} - block = makeBlockWithTxnFor([]byte{3}, []byte{1}) - listener.update(block, balanceUpdate) - - if err := verifyListener(listener, []uint64{15, 5}, 100000, 99999999, 4); err != nil { - t.Error(err) - } - - // Transactions causing adding a balance to a new account are not reflected in TopN, because they are smaller than - // the smallest value in TopN (even though there is capacity for it). - listener.accounts = make([]basics.AccountDetail, 3, 10) - listener.accounts[0] = onlineDetail(byte(0), 15) - listener.accounts[1] = onlineDetail(byte(1), 10) - listener.accounts[2] = onlineDetail(byte(2), 5) - - balanceUpdate.Round++ - balanceUpdate.Accounts = []basics.AccountDetail{onlineDetail(byte(3), 1)} - block = makeBlockWithTxnFor([]byte{5}, []byte{3}) - listener.update(block, balanceUpdate) - - if err := verifyListener(listener, []uint64{15, 10, 5}, 100000, 99999999, 5); err != nil { - t.Error(err) - } - - // Invalid round truncates accounts slice - listener.update(block, balanceUpdate) - if len(listener.accounts) != 0 { - t.Errorf("Accounts should be truncated to zero after unexpected round: len(topN) = %d", len(listener.accounts)) - } -} - -func TestInit(t *testing.T) { - partitiontest.PartitionTest(t) - - listener := makeTopAccountListener(logging.Base()) - - // "init" should remove existing values before adding new ones. - balanceUpdate := basics.BalanceDetail{ - Accounts: make([]basics.AccountDetail, 0, 10), - Round: 2, - OnlineMoney: basics.MicroAlgos{Raw: 100}, - TotalMoney: basics.MicroAlgos{Raw: 100}, - } - - listener.accounts = append(listener.accounts, onlineDetail(byte(10), 100)) - balanceUpdate.Accounts = []basics.AccountDetail{onlineDetail(byte(1), 1)} - - listener.init(balanceUpdate) - - if err := verifyListener(listener, []uint64{1}, 100, 100, 2); err != nil { - t.Error(err) - } -} - -func makeBlockWithTxnFor(senders []byte, receivers []byte) bookkeeping.Block { - var blk bookkeeping.Block - blk.BlockHeader.GenesisID = "foo" - crypto.RandBytes(blk.BlockHeader.GenesisHash[:]) - blk.CurrentProtocol = protocol.ConsensusFuture - - paysets := make([]transactions.SignedTxnInBlock, 0, len(receivers)) - for i, b := range receivers { - txib, err := blk.EncodeSignedTxn(transactions.SignedTxn{ - Txn: transactions.Transaction{ - Type: protocol.PaymentTx, - Header: transactions.Header{ - Sender: basics.Address{senders[i]}, - GenesisID: blk.BlockHeader.GenesisID, - GenesisHash: blk.BlockHeader.GenesisHash, - }, - PaymentTxnFields: transactions.PaymentTxnFields{ - Receiver: basics.Address{b}, - // If this ends up being used by topAccountListener, add it here. - // Amount: basics.MicroAlgos{123}, - }, - }}, transactions.ApplyData{}) - if err != nil { - panic(err) - } - - paysets = append(paysets, txib) - } - - blk.Payset = paysets - return blk -} - -// Helpers for working with data objects. -func onlineDetail(b byte, bal uint64) basics.AccountDetail { - return detail(b, bal, true) -} - -func detail(b byte, bal uint64, isOnline bool) basics.AccountDetail { - state := basics.Offline - if isOnline { - state = basics.Online - } - return basics.AccountDetail{ - Address: basics.Address{b}, - Algos: basics.MicroAlgos{Raw: bal}, - Status: state, - } -} - -func getInt(detail basics.AccountDetail) uint64 { - return uint64([32]byte(detail.Address)[0]) -} - -func verifyAccountBalances(expected []uint64, actual []basics.AccountDetail) error { - if len(expected) != len(actual) { - return &errorString{fmt.Sprintf("Lengths do not equal: expected(%d) != actual(%d)", len(expected), len(actual))} - } - - for i, a := range actual { - if expected[i] != a.Algos.Raw { - return &errorString{fmt.Sprintf("Unexpected result at actual[%d]: expected(%d) != actual(%d)", i, expected[i], a.Algos.Raw)} - } - } - - return nil -} - -func verifyListener(listener topAccountListener, expected []uint64, online uint64, total uint64, round uint64) error { - if listener.round != basics.Round(round) { - return &errorString{fmt.Sprintf("Unexpected round: actual(%d) != expected(%d)", uint64(listener.round), round)} - } - - if listener.onlineCirculation.Raw != online { - return &errorString{fmt.Sprintf("Unexpected online circulation: actual(%d) != expected(%d)", listener.onlineCirculation.Raw, online)} - } - - if listener.totalCirculation.Raw != total { - return &errorString{fmt.Sprintf("Unexpected total circulation: actual(%d) != expected(%d)", listener.totalCirculation.Raw, total)} - } - - return verifyAccountBalances(expected, listener.accounts) -} diff --git a/nodecontrol/LaggedStdIo.go b/nodecontrol/LaggedStdIo.go index 4738452f31..1638dd118e 100644 --- a/nodecontrol/LaggedStdIo.go +++ b/nodecontrol/LaggedStdIo.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/nodecontrol/NodeController.go b/nodecontrol/NodeController.go index b1044b43d2..e9ce178b8e 100644 --- a/nodecontrol/NodeController.go +++ b/nodecontrol/NodeController.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/nodecontrol/algodControl.go b/nodecontrol/algodControl.go index e614cf63fb..85c9df30b4 100644 --- a/nodecontrol/algodControl.go +++ b/nodecontrol/algodControl.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/nodecontrol/algodControl_test.go b/nodecontrol/algodControl_test.go index 80c231ba79..d81d24d9e6 100644 --- a/nodecontrol/algodControl_test.go +++ b/nodecontrol/algodControl_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/nodecontrol/kmdControl.go b/nodecontrol/kmdControl.go index f55b168767..3c8936783a 100644 --- a/nodecontrol/kmdControl.go +++ b/nodecontrol/kmdControl.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/nodecontrol/kmdControl_common.go b/nodecontrol/kmdControl_common.go index 151ea79633..9e4d96feaf 100644 --- a/nodecontrol/kmdControl_common.go +++ b/nodecontrol/kmdControl_common.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/nodecontrol/kmdControl_windows.go b/nodecontrol/kmdControl_windows.go index fbb34a296d..2273295e06 100644 --- a/nodecontrol/kmdControl_windows.go +++ b/nodecontrol/kmdControl_windows.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/nodecontrol/nodeControlErrors.go b/nodecontrol/nodeControlErrors.go index 6ed43d56ac..8502b252d2 100644 --- a/nodecontrol/nodeControlErrors.go +++ b/nodecontrol/nodeControlErrors.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/protocol/codec.go b/protocol/codec.go index 3bd72eb698..e0386eb9b2 100644 --- a/protocol/codec.go +++ b/protocol/codec.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/protocol/codec_test.go b/protocol/codec_test.go index e623f9024b..d11a47a909 100644 --- a/protocol/codec_test.go +++ b/protocol/codec_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/protocol/codec_tester.go b/protocol/codec_tester.go index 8d784a0696..59a712e554 100644 --- a/protocol/codec_tester.go +++ b/protocol/codec_tester.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/protocol/consensus.go b/protocol/consensus.go index f9ee2ec0cb..00857875d6 100644 --- a/protocol/consensus.go +++ b/protocol/consensus.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/protocol/encodebench_test.go b/protocol/encodebench_test.go index 3c015bf6b6..32267a5d43 100644 --- a/protocol/encodebench_test.go +++ b/protocol/encodebench_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/protocol/hash.go b/protocol/hash.go index 2d7d48acdf..079333a438 100644 --- a/protocol/hash.go +++ b/protocol/hash.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/protocol/networks.go b/protocol/networks.go index ff1c8ee0f9..a6e3b13d1f 100644 --- a/protocol/networks.go +++ b/protocol/networks.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/protocol/stateproof.go b/protocol/stateproof.go index 6031b97d41..4e634fc37d 100644 --- a/protocol/stateproof.go +++ b/protocol/stateproof.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/protocol/tags.go b/protocol/tags.go index 01aee9126b..abe947b65b 100644 --- a/protocol/tags.go +++ b/protocol/tags.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/protocol/test/allocbound_slice.go b/protocol/test/allocbound_slice.go index c825a7d25f..1d792d5688 100644 --- a/protocol/test/allocbound_slice.go +++ b/protocol/test/allocbound_slice.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/protocol/transcode/core.go b/protocol/transcode/core.go index 863fbabf92..eaf6d00e00 100644 --- a/protocol/transcode/core.go +++ b/protocol/transcode/core.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/protocol/transcode/core_test.go b/protocol/transcode/core_test.go index e9cfc42f87..34afe8f54d 100644 --- a/protocol/transcode/core_test.go +++ b/protocol/transcode/core_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/protocol/txntype.go b/protocol/txntype.go index 4342558962..d0cfe058b5 100644 --- a/protocol/txntype.go +++ b/protocol/txntype.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/rpcs/blockService.go b/rpcs/blockService.go index 25fd161434..cd4a33ad20 100644 --- a/rpcs/blockService.go +++ b/rpcs/blockService.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/rpcs/blockService_test.go b/rpcs/blockService_test.go index 2adb2f1b29..fa934feeb0 100644 --- a/rpcs/blockService_test.go +++ b/rpcs/blockService_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/rpcs/httpTxSync.go b/rpcs/httpTxSync.go index 63b99c8a60..6f42eeaacf 100644 --- a/rpcs/httpTxSync.go +++ b/rpcs/httpTxSync.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/rpcs/ledgerService.go b/rpcs/ledgerService.go index 582dd89b46..b7af358a39 100644 --- a/rpcs/ledgerService.go +++ b/rpcs/ledgerService.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/rpcs/registrar.go b/rpcs/registrar.go index 1e782d8029..f119193298 100644 --- a/rpcs/registrar.go +++ b/rpcs/registrar.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/rpcs/txService.go b/rpcs/txService.go index 654a8e69fd..3083e2d33d 100644 --- a/rpcs/txService.go +++ b/rpcs/txService.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/rpcs/txService_test.go b/rpcs/txService_test.go index 70e13fc227..dd999d6e65 100644 --- a/rpcs/txService_test.go +++ b/rpcs/txService_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/rpcs/txSyncer.go b/rpcs/txSyncer.go index 3996371690..c724417ea3 100644 --- a/rpcs/txSyncer.go +++ b/rpcs/txSyncer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/rpcs/txSyncer_test.go b/rpcs/txSyncer_test.go index 377080ae8d..b05e050ee2 100644 --- a/rpcs/txSyncer_test.go +++ b/rpcs/txSyncer_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/scripts/compute_branch.sh b/scripts/compute_branch.sh index f0ff025d90..211be0521a 100755 --- a/scripts/compute_branch.sh +++ b/scripts/compute_branch.sh @@ -1,18 +1,21 @@ #!/usr/bin/env bash -if [[ -n $(git status --porcelain) ]]; then - # If the branch isn't clean, default to HEAD to match old behavior. - BRANCH="HEAD" -elif [ -z "${TRAVIS_BRANCH}" ]; then - # if there is no travis branch, set based on tag or branch - case "$(git describe --tags)" in - *"beta") BRANCH="rel/beta" ;; - *"stable") BRANCH="rel/stable" ;; - *"nightly") BRANCH="rel/nightly" ;; - *) BRANCH=$(git rev-parse --abbrev-ref HEAD) - esac -else - BRANCH="${TRAVIS_BRANCH}" +BRANCH="${BRANCH:-}" +if [ -z "$BRANCH" ]; then + if [[ -n $(git status --porcelain) ]]; then + # If the branch isn't clean, default to HEAD to match old behavior. + BRANCH="HEAD" + elif [ -z "${TRAVIS_BRANCH}" ]; then + # if there is no travis branch, set based on tag or branch + case "$(git describe --tags)" in + *"beta") BRANCH="rel/beta" ;; + *"stable") BRANCH="rel/stable" ;; + *"nightly") BRANCH="rel/nightly" ;; + *) BRANCH=$(git rev-parse --abbrev-ref HEAD) ;; + esac + else + BRANCH="${TRAVIS_BRANCH}" + fi fi echo "${BRANCH}" diff --git a/shared/algoh/config.go b/shared/algoh/config.go index 687fd1be28..a4cec444d1 100644 --- a/shared/algoh/config.go +++ b/shared/algoh/config.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/shared/pingpong/accounts.go b/shared/pingpong/accounts.go index 982c768aa1..ea9d74cc07 100644 --- a/shared/pingpong/accounts.go +++ b/shared/pingpong/accounts.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/shared/pingpong/accounts_test.go b/shared/pingpong/accounts_test.go index 7f2f0a737b..08def89944 100644 --- a/shared/pingpong/accounts_test.go +++ b/shared/pingpong/accounts_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -22,6 +22,8 @@ import ( "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/basics" + + "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/assert" ) @@ -33,6 +35,9 @@ func makeKeyFromSeed(i uint64) *crypto.SignatureSecrets { } func TestDeterministicAccounts(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + initCfg := PpConfig{ NumPartAccounts: 20, DeterministicKeys: true, diff --git a/shared/pingpong/config.go b/shared/pingpong/config.go index b5dd17bc5f..32f5fab7e9 100644 --- a/shared/pingpong/config.go +++ b/shared/pingpong/config.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -48,6 +48,7 @@ type PpConfig struct { Quiet bool RandomNote bool RandomLease bool + TotalLatencyOut string Program []byte LogicArgs [][]byte diff --git a/shared/pingpong/pingpong.go b/shared/pingpong/pingpong.go index c6a24f4935..32352bcfcb 100644 --- a/shared/pingpong/pingpong.go +++ b/shared/pingpong/pingpong.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -14,13 +14,19 @@ // You should have received a copy of the GNU Affero General Public License // along with go-algorand. If not, see . +// Package pingpong provides a transaction generating utility for performance testing. +// +//nolint:unused,structcheck,deadcode,varcheck // ignore unused pingpong code package pingpong import ( + "bufio" + "compress/gzip" "context" "encoding/binary" "errors" "fmt" + "io" "math" "math/rand" "os" @@ -34,6 +40,7 @@ import ( "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/daemon/algod/api/server/v2/generated/model" "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/data/bookkeeping" "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/data/transactions/logic" "github.com/algorand/go-algorand/libgoal" @@ -128,6 +135,11 @@ func (ppa *pingPongAccount) String() string { return ow.String() } +type txidSendTime struct { + txid string + when time.Time +} + // WorkerState object holds a running pingpong worker type WorkerState struct { cfg PpConfig @@ -149,6 +161,11 @@ type WorkerState struct { refreshPos int client *libgoal.Client + + // TotalLatencyOut stuff + sentTxid chan txidSendTime + latencyBlocks chan bookkeeping.Block + latencyOuts []io.Writer // latencyOuts is a chain of *os.File, gzip, etc. Write to last element. .Close() last to first. } // returns the number of boxes per app @@ -345,6 +362,25 @@ func (pps *WorkerState) schedule(n int) { //fmt.Printf("schedule now=%s next=%s\n", now, pps.nextSendTime) } +func (pps *WorkerState) recordTxidSent(txid string, err error) { + if err != nil { + return + } + if pps.sentTxid == nil { + return + } + rec := txidSendTime{ + txid: txid, + when: time.Now(), + } + select { + case pps.sentTxid <- rec: + // ok! + default: + // drop, oh well + } +} + func (pps *WorkerState) fundAccounts(client *libgoal.Client) error { var srcFunds, minFund uint64 var err error @@ -545,6 +581,9 @@ func (pps *WorkerState) RunPingPong(ctx context.Context, ac *libgoal.Client) { // error = fundAccounts() // } + if pps.cfg.TotalLatencyOut != "" { + pps.startTxLatency(ctx, ac) + } pps.nextSendTime = time.Now() ac.SetSuggestedParamsCacheAge(200 * time.Millisecond) pps.client = ac @@ -773,7 +812,9 @@ func (pps *WorkerState) sendFromTo( sentCount++ pps.schedule(1) - _, sendErr = client.BroadcastTransaction(stxn) + var txid string + txid, sendErr = client.BroadcastTransaction(stxn) + pps.recordTxidSent(txid, sendErr) } else { // Generate txn group @@ -844,6 +885,8 @@ func (pps *WorkerState) sendFromTo( sentCount += uint64(len(txGroup)) pps.schedule(len(txGroup)) sendErr = client.BroadcastTransactionGroup(stxGroup) + txid := txGroup[0].ID().String() + pps.recordTxidSent(txid, sendErr) } if sendErr != nil { @@ -1298,3 +1341,152 @@ func signTxn(signer *pingPongAccount, txn transactions.Transaction, cfg PpConfig } return } + +func (pps *WorkerState) startTxLatency(ctx context.Context, ac *libgoal.Client) { + fout, err := os.Create(pps.cfg.TotalLatencyOut) + if err != nil { + fmt.Fprintf(os.Stderr, "%s: %v", pps.cfg.TotalLatencyOut, err) + return + } + pps.latencyOuts = append(pps.latencyOuts, fout) + if strings.HasSuffix(pps.cfg.TotalLatencyOut, ".gz") { + gzout := gzip.NewWriter(fout) + pps.latencyOuts = append(pps.latencyOuts, gzout) + } else { + bw := bufio.NewWriter(fout) + pps.latencyOuts = append(pps.latencyOuts, bw) + } + pps.sentTxid = make(chan txidSendTime, 1000) + pps.latencyBlocks = make(chan bookkeeping.Block, 1) + go pps.txidLatency(ctx) + go pps.txidLatencyBlockWaiter(ctx, ac) +} + +type txidSendTimeIndexed struct { + txidSendTime + index int +} + +const txidLatencySampleSize = 10000 + +// thread which handles measuring total send-to-commit latency +func (pps *WorkerState) txidLatency(ctx context.Context) { + byTxid := make(map[string]txidSendTimeIndexed, txidLatencySampleSize) + txidList := make([]string, 0, txidLatencySampleSize) + out := pps.latencyOuts[len(pps.latencyOuts)-1] + for { + select { + case st := <-pps.sentTxid: + if len(txidList) < txidLatencySampleSize { + index := len(txidList) + txidList = append(txidList, st.txid) + byTxid[st.txid] = txidSendTimeIndexed{ + st, + index, + } + } else { + // random replacement + evict := rand.Intn(len(txidList)) + delete(byTxid, txidList[evict]) + txidList[evict] = st.txid + byTxid[st.txid] = txidSendTimeIndexed{ + st, + evict, + } + } + case bl := <-pps.latencyBlocks: + now := time.Now() + txns, err := bl.DecodePaysetFlat() + if err != nil { + fmt.Fprintf(os.Stderr, "block[%d] payset err %v", bl.Round(), err) + return + } + for _, stxn := range txns { + txid := stxn.ID().String() + st, ok := byTxid[txid] + if ok { + dt := now.Sub(st.when) + fmt.Fprintf(out, "%d\n", dt.Nanoseconds()) + } + } + case <-ctx.Done(): + return + } + } +} + +type flusher interface { + Flush() error +} + +func (pps *WorkerState) txidLatencyDone() { + for i := len(pps.latencyOuts); i >= 0; i-- { + xo := pps.latencyOuts[i] + if fl, ok := xo.(flusher); ok { + err := fl.Flush() + if err != nil { + fmt.Fprintf(os.Stderr, "%s: %v", pps.cfg.TotalLatencyOut, err) + } + } + if cl, ok := xo.(io.Closer); ok { + err := cl.Close() + if err != nil { + fmt.Fprintf(os.Stderr, "%s: %v", pps.cfg.TotalLatencyOut, err) + } + } + } +} + +const errRestartTime = time.Second + +func (pps *WorkerState) txidLatencyBlockWaiter(ctx context.Context, ac *libgoal.Client) { + defer close(pps.latencyBlocks) + done := ctx.Done() + isDone := func(err error) bool { + select { + case <-done: + return true + default: + } + fmt.Fprintf(os.Stderr, "block waiter st : %v", err) + time.Sleep(errRestartTime) + return false + } +restart: + select { + case <-done: + return + default: + } + st, err := ac.Status() + if err != nil { + if isDone(err) { + return + } + goto restart + } + nextRound := st.LastRound + for { + select { + case <-done: + return + default: + } + st, err = ac.WaitForRound(nextRound) + if err != nil { + if isDone(err) { + return + } + goto restart + } + bb, err := ac.BookkeepingBlock(st.LastRound) + if err != nil { + if isDone(err) { + return + } + goto restart + } + pps.latencyBlocks <- bb + nextRound = st.LastRound + } +} diff --git a/stateproof/abstractions.go b/stateproof/abstractions.go index 825b5090ea..8527060ae9 100644 --- a/stateproof/abstractions.go +++ b/stateproof/abstractions.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/stateproof/builder.go b/stateproof/builder.go index 3d14c1f747..fbb6813a94 100644 --- a/stateproof/builder.go +++ b/stateproof/builder.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/stateproof/db.go b/stateproof/db.go index fa10c8c5ee..b1883f5916 100644 --- a/stateproof/db.go +++ b/stateproof/db.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/stateproof/db_test.go b/stateproof/db_test.go index 8dca09af47..0de5178956 100644 --- a/stateproof/db_test.go +++ b/stateproof/db_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/stateproof/recovery.go b/stateproof/recovery.go index 5902a603eb..6bd20337f1 100644 --- a/stateproof/recovery.go +++ b/stateproof/recovery.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/stateproof/signer.go b/stateproof/signer.go index 697a56e9ee..5c762720a2 100644 --- a/stateproof/signer.go +++ b/stateproof/signer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/stateproof/stateproofMessageGenerator.go b/stateproof/stateproofMessageGenerator.go index 8befeb72f0..92befc631f 100644 --- a/stateproof/stateproofMessageGenerator.go +++ b/stateproof/stateproofMessageGenerator.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/stateproof/stateproofMessageGenerator_test.go b/stateproof/stateproofMessageGenerator_test.go index 1b909d775f..26c47ec8a2 100644 --- a/stateproof/stateproofMessageGenerator_test.go +++ b/stateproof/stateproofMessageGenerator_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/stateproof/verify/stateproof.go b/stateproof/verify/stateproof.go index 66c6f09d46..fa3641c08c 100644 --- a/stateproof/verify/stateproof.go +++ b/stateproof/verify/stateproof.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/stateproof/verify/stateproof_test.go b/stateproof/verify/stateproof_test.go index 38a76c2b80..0495729dd7 100644 --- a/stateproof/verify/stateproof_test.go +++ b/stateproof/verify/stateproof_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/stateproof/worker.go b/stateproof/worker.go index ca277dac8b..857431f04b 100644 --- a/stateproof/worker.go +++ b/stateproof/worker.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/stateproof/worker_test.go b/stateproof/worker_test.go index 9145141179..bf1ec2636b 100644 --- a/stateproof/worker_test.go +++ b/stateproof/worker_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/commandandcontrol/cc_agent/component/agent.go b/test/commandandcontrol/cc_agent/component/agent.go index fb1bc504a6..4f01065b65 100644 --- a/test/commandandcontrol/cc_agent/component/agent.go +++ b/test/commandandcontrol/cc_agent/component/agent.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/commandandcontrol/cc_agent/component/agent_test.go b/test/commandandcontrol/cc_agent/component/agent_test.go index 7ca805eb7f..b819ab83c9 100644 --- a/test/commandandcontrol/cc_agent/component/agent_test.go +++ b/test/commandandcontrol/cc_agent/component/agent_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/commandandcontrol/cc_agent/component/pingPongComponent.go b/test/commandandcontrol/cc_agent/component/pingPongComponent.go index 5bb4be8907..ffc61fa5de 100644 --- a/test/commandandcontrol/cc_agent/component/pingPongComponent.go +++ b/test/commandandcontrol/cc_agent/component/pingPongComponent.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/commandandcontrol/cc_agent/main.go b/test/commandandcontrol/cc_agent/main.go index 136c38f0d6..5adcfa5622 100644 --- a/test/commandandcontrol/cc_agent/main.go +++ b/test/commandandcontrol/cc_agent/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/commandandcontrol/cc_client/main.go b/test/commandandcontrol/cc_client/main.go index 7125ecd31b..6b64117b0b 100644 --- a/test/commandandcontrol/cc_client/main.go +++ b/test/commandandcontrol/cc_client/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/commandandcontrol/cc_service/main.go b/test/commandandcontrol/cc_service/main.go index 26e592e664..fa39f9229c 100644 --- a/test/commandandcontrol/cc_service/main.go +++ b/test/commandandcontrol/cc_service/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/commandandcontrol/lib/ccCommon.go b/test/commandandcontrol/lib/ccCommon.go index f3db52a51a..36cff09ec5 100644 --- a/test/commandandcontrol/lib/ccCommon.go +++ b/test/commandandcontrol/lib/ccCommon.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/cli/algod/cleanup_test.go b/test/e2e-go/cli/algod/cleanup_test.go index 24b77e7d9f..9e690b45a3 100644 --- a/test/e2e-go/cli/algod/cleanup_test.go +++ b/test/e2e-go/cli/algod/cleanup_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/cli/algod/expect/algod_expect_test.go b/test/e2e-go/cli/algod/expect/algod_expect_test.go index 06d54b9f32..25850d059f 100644 --- a/test/e2e-go/cli/algod/expect/algod_expect_test.go +++ b/test/e2e-go/cli/algod/expect/algod_expect_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/cli/algod/stdstreams_test.go b/test/e2e-go/cli/algod/stdstreams_test.go index cb335cad61..f2a412752a 100644 --- a/test/e2e-go/cli/algod/stdstreams_test.go +++ b/test/e2e-go/cli/algod/stdstreams_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/cli/algoh/expect/algoh_expect_test.go b/test/e2e-go/cli/algoh/expect/algoh_expect_test.go index f260a1bd6e..8a94200a6b 100644 --- a/test/e2e-go/cli/algoh/expect/algoh_expect_test.go +++ b/test/e2e-go/cli/algoh/expect/algoh_expect_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/cli/goal/account_test.go b/test/e2e-go/cli/goal/account_test.go index b01eb6fdae..fe05ccddb3 100644 --- a/test/e2e-go/cli/goal/account_test.go +++ b/test/e2e-go/cli/goal/account_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/cli/goal/clerk_test.go b/test/e2e-go/cli/goal/clerk_test.go index 9f112fbeba..165c65e9fa 100644 --- a/test/e2e-go/cli/goal/clerk_test.go +++ b/test/e2e-go/cli/goal/clerk_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/cli/goal/common_test.go b/test/e2e-go/cli/goal/common_test.go index b9d0a4b0c9..a233d87370 100644 --- a/test/e2e-go/cli/goal/common_test.go +++ b/test/e2e-go/cli/goal/common_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/cli/goal/expect/catchpointCatchupWebProxy/webproxy.go b/test/e2e-go/cli/goal/expect/catchpointCatchupWebProxy/webproxy.go index 3ba4764383..6b4ac0f311 100644 --- a/test/e2e-go/cli/goal/expect/catchpointCatchupWebProxy/webproxy.go +++ b/test/e2e-go/cli/goal/expect/catchpointCatchupWebProxy/webproxy.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/cli/goal/expect/goal_expect_test.go b/test/e2e-go/cli/goal/expect/goal_expect_test.go index 55bdf7ce6b..b90dd17d4b 100644 --- a/test/e2e-go/cli/goal/expect/goal_expect_test.go +++ b/test/e2e-go/cli/goal/expect/goal_expect_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/cli/goal/node_cleanup_test.go b/test/e2e-go/cli/goal/node_cleanup_test.go index 7ad3eaab06..ae70c89603 100644 --- a/test/e2e-go/cli/goal/node_cleanup_test.go +++ b/test/e2e-go/cli/goal/node_cleanup_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/cli/perf/libgoal_test.go b/test/e2e-go/cli/perf/libgoal_test.go index a98164003b..a3e7581c67 100644 --- a/test/e2e-go/cli/perf/libgoal_test.go +++ b/test/e2e-go/cli/perf/libgoal_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/cli/perf/payment_test.go b/test/e2e-go/cli/perf/payment_test.go index 1989ac3cb4..fc60e16b84 100644 --- a/test/e2e-go/cli/perf/payment_test.go +++ b/test/e2e-go/cli/perf/payment_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/cli/tealdbg/cdtmock/main.go b/test/e2e-go/cli/tealdbg/cdtmock/main.go index 9cb3cf0724..6d1292e4b5 100644 --- a/test/e2e-go/cli/tealdbg/cdtmock/main.go +++ b/test/e2e-go/cli/tealdbg/cdtmock/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/cli/tealdbg/expect/tealdbg_expect_test.go b/test/e2e-go/cli/tealdbg/expect/tealdbg_expect_test.go index 72248cfff5..aedf8a2d9a 100644 --- a/test/e2e-go/cli/tealdbg/expect/tealdbg_expect_test.go +++ b/test/e2e-go/cli/tealdbg/expect/tealdbg_expect_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/features/accountPerf/sixMillion_test.go b/test/e2e-go/features/accountPerf/sixMillion_test.go index b3d6c107ab..cc2095dfbd 100644 --- a/test/e2e-go/features/accountPerf/sixMillion_test.go +++ b/test/e2e-go/features/accountPerf/sixMillion_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/features/catchup/basicCatchup_test.go b/test/e2e-go/features/catchup/basicCatchup_test.go index ce908b9e64..435393f70d 100644 --- a/test/e2e-go/features/catchup/basicCatchup_test.go +++ b/test/e2e-go/features/catchup/basicCatchup_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/features/catchup/catchpointCatchup_test.go b/test/e2e-go/features/catchup/catchpointCatchup_test.go index f745c9e759..e8de54e66c 100644 --- a/test/e2e-go/features/catchup/catchpointCatchup_test.go +++ b/test/e2e-go/features/catchup/catchpointCatchup_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/features/devmode/devmode_test.go b/test/e2e-go/features/devmode/devmode_test.go index c356615e9b..ec1fa692bc 100644 --- a/test/e2e-go/features/devmode/devmode_test.go +++ b/test/e2e-go/features/devmode/devmode_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/features/multisig/multisig_test.go b/test/e2e-go/features/multisig/multisig_test.go index 07c8f96618..3452b1b505 100644 --- a/test/e2e-go/features/multisig/multisig_test.go +++ b/test/e2e-go/features/multisig/multisig_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/features/participation/accountParticipationTransitions_test.go b/test/e2e-go/features/participation/accountParticipationTransitions_test.go index 52f25ec832..e9a4e5735f 100644 --- a/test/e2e-go/features/participation/accountParticipationTransitions_test.go +++ b/test/e2e-go/features/participation/accountParticipationTransitions_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/features/participation/deletePartKeys_test.go b/test/e2e-go/features/participation/deletePartKeys_test.go index 5c7755ca74..c72925dba7 100644 --- a/test/e2e-go/features/participation/deletePartKeys_test.go +++ b/test/e2e-go/features/participation/deletePartKeys_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/features/participation/onlineOfflineParticipation_test.go b/test/e2e-go/features/participation/onlineOfflineParticipation_test.go index 0a6f233114..ed593b765b 100644 --- a/test/e2e-go/features/participation/onlineOfflineParticipation_test.go +++ b/test/e2e-go/features/participation/onlineOfflineParticipation_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/features/participation/overlappingParticipationKeys_test.go b/test/e2e-go/features/participation/overlappingParticipationKeys_test.go index 45ac6c97c6..8e4df5fb3e 100644 --- a/test/e2e-go/features/participation/overlappingParticipationKeys_test.go +++ b/test/e2e-go/features/participation/overlappingParticipationKeys_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/features/participation/participationExpiration_test.go b/test/e2e-go/features/participation/participationExpiration_test.go index 6f8c0b1a95..3bb915ab0c 100644 --- a/test/e2e-go/features/participation/participationExpiration_test.go +++ b/test/e2e-go/features/participation/participationExpiration_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/features/participation/participationRewards_test.go b/test/e2e-go/features/participation/participationRewards_test.go index 5581de1955..a9c81f6a48 100644 --- a/test/e2e-go/features/participation/participationRewards_test.go +++ b/test/e2e-go/features/participation/participationRewards_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/features/partitionRecovery/partitionRecovery_test.go b/test/e2e-go/features/partitionRecovery/partitionRecovery_test.go index dc549d72e8..3f16be7f68 100644 --- a/test/e2e-go/features/partitionRecovery/partitionRecovery_test.go +++ b/test/e2e-go/features/partitionRecovery/partitionRecovery_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/features/stateproofs/stateproofs_test.go b/test/e2e-go/features/stateproofs/stateproofs_test.go index ac2270cc4c..e293006f40 100644 --- a/test/e2e-go/features/stateproofs/stateproofs_test.go +++ b/test/e2e-go/features/stateproofs/stateproofs_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/features/teal/compile_test.go b/test/e2e-go/features/teal/compile_test.go index 866e1c54db..760551f285 100644 --- a/test/e2e-go/features/teal/compile_test.go +++ b/test/e2e-go/features/teal/compile_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/features/transactions/accountv2_test.go b/test/e2e-go/features/transactions/accountv2_test.go index 2972229346..fc6a828889 100644 --- a/test/e2e-go/features/transactions/accountv2_test.go +++ b/test/e2e-go/features/transactions/accountv2_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/features/transactions/app_pages_test.go b/test/e2e-go/features/transactions/app_pages_test.go index f9a56c2124..f9ce0878a0 100644 --- a/test/e2e-go/features/transactions/app_pages_test.go +++ b/test/e2e-go/features/transactions/app_pages_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/features/transactions/application_test.go b/test/e2e-go/features/transactions/application_test.go index 380264b298..a93794bb27 100644 --- a/test/e2e-go/features/transactions/application_test.go +++ b/test/e2e-go/features/transactions/application_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/features/transactions/asset_test.go b/test/e2e-go/features/transactions/asset_test.go index 0802308b38..3c6987b42f 100644 --- a/test/e2e-go/features/transactions/asset_test.go +++ b/test/e2e-go/features/transactions/asset_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/features/transactions/close_account_test.go b/test/e2e-go/features/transactions/close_account_test.go index 8787568dc6..3426ea957b 100644 --- a/test/e2e-go/features/transactions/close_account_test.go +++ b/test/e2e-go/features/transactions/close_account_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/features/transactions/group_test.go b/test/e2e-go/features/transactions/group_test.go index 4852da25d1..1d8341f42c 100644 --- a/test/e2e-go/features/transactions/group_test.go +++ b/test/e2e-go/features/transactions/group_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/features/transactions/lease_test.go b/test/e2e-go/features/transactions/lease_test.go index 10a238e742..7dbb061900 100644 --- a/test/e2e-go/features/transactions/lease_test.go +++ b/test/e2e-go/features/transactions/lease_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/features/transactions/onlineStatusChange_test.go b/test/e2e-go/features/transactions/onlineStatusChange_test.go index 2cbfa793a9..a8a9ceb013 100644 --- a/test/e2e-go/features/transactions/onlineStatusChange_test.go +++ b/test/e2e-go/features/transactions/onlineStatusChange_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/features/transactions/proof_test.go b/test/e2e-go/features/transactions/proof_test.go index bb5175c92b..f7d8141d7b 100644 --- a/test/e2e-go/features/transactions/proof_test.go +++ b/test/e2e-go/features/transactions/proof_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/features/transactions/sendReceive_test.go b/test/e2e-go/features/transactions/sendReceive_test.go index e09c67a6bb..1a261a8442 100644 --- a/test/e2e-go/features/transactions/sendReceive_test.go +++ b/test/e2e-go/features/transactions/sendReceive_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/globals/constants.go b/test/e2e-go/globals/constants.go index b738b6d653..b289379a10 100644 --- a/test/e2e-go/globals/constants.go +++ b/test/e2e-go/globals/constants.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/kmd/e2e_kmd_server_client_test.go b/test/e2e-go/kmd/e2e_kmd_server_client_test.go index eb61861bbc..7d7977a0b0 100644 --- a/test/e2e-go/kmd/e2e_kmd_server_client_test.go +++ b/test/e2e-go/kmd/e2e_kmd_server_client_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/kmd/e2e_kmd_sqlite_test.go b/test/e2e-go/kmd/e2e_kmd_sqlite_test.go index 496070ccb1..d8290ae5e3 100644 --- a/test/e2e-go/kmd/e2e_kmd_sqlite_test.go +++ b/test/e2e-go/kmd/e2e_kmd_sqlite_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/kmd/e2e_kmd_wallet_keyops_test.go b/test/e2e-go/kmd/e2e_kmd_wallet_keyops_test.go index 364dcfb03a..c5dd733e0d 100644 --- a/test/e2e-go/kmd/e2e_kmd_wallet_keyops_test.go +++ b/test/e2e-go/kmd/e2e_kmd_wallet_keyops_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/kmd/e2e_kmd_wallet_multisig_test.go b/test/e2e-go/kmd/e2e_kmd_wallet_multisig_test.go index be8f27e766..8995b70c7c 100644 --- a/test/e2e-go/kmd/e2e_kmd_wallet_multisig_test.go +++ b/test/e2e-go/kmd/e2e_kmd_wallet_multisig_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/kmd/e2e_kmd_wallet_test.go b/test/e2e-go/kmd/e2e_kmd_wallet_test.go index 4fb8f3a159..558c4ff30d 100644 --- a/test/e2e-go/kmd/e2e_kmd_wallet_test.go +++ b/test/e2e-go/kmd/e2e_kmd_wallet_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/perf/basic_test.go b/test/e2e-go/perf/basic_test.go index b4044303a4..be8a4c8197 100644 --- a/test/e2e-go/perf/basic_test.go +++ b/test/e2e-go/perf/basic_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/restAPI/restClient_test.go b/test/e2e-go/restAPI/restClient_test.go index 4592fd4262..146d1585c0 100644 --- a/test/e2e-go/restAPI/restClient_test.go +++ b/test/e2e-go/restAPI/restClient_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/stress/transactions/createManyAndGoOnline_test.go b/test/e2e-go/stress/transactions/createManyAndGoOnline_test.go index fc05837baf..a7e5c97555 100644 --- a/test/e2e-go/stress/transactions/createManyAndGoOnline_test.go +++ b/test/e2e-go/stress/transactions/createManyAndGoOnline_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/upgrades/application_support_test.go b/test/e2e-go/upgrades/application_support_test.go index 00ee3024a9..8746545d08 100644 --- a/test/e2e-go/upgrades/application_support_test.go +++ b/test/e2e-go/upgrades/application_support_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/upgrades/rekey_support_test.go b/test/e2e-go/upgrades/rekey_support_test.go index 56f5b6408b..4c7878bfaa 100644 --- a/test/e2e-go/upgrades/rekey_support_test.go +++ b/test/e2e-go/upgrades/rekey_support_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/upgrades/send_receive_upgrade_test.go b/test/e2e-go/upgrades/send_receive_upgrade_test.go index a3992872a9..b6b4134236 100644 --- a/test/e2e-go/upgrades/send_receive_upgrade_test.go +++ b/test/e2e-go/upgrades/send_receive_upgrade_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/upgrades/stateproof_participation_test.go b/test/e2e-go/upgrades/stateproof_participation_test.go index 75d3dad875..d8153dea5a 100644 --- a/test/e2e-go/upgrades/stateproof_participation_test.go +++ b/test/e2e-go/upgrades/stateproof_participation_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/framework/fixtures/baseFixture.go b/test/framework/fixtures/baseFixture.go index a2205d3c63..918fa6d7d5 100644 --- a/test/framework/fixtures/baseFixture.go +++ b/test/framework/fixtures/baseFixture.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/framework/fixtures/expectFixture.go b/test/framework/fixtures/expectFixture.go index 35b7489689..50206bda12 100644 --- a/test/framework/fixtures/expectFixture.go +++ b/test/framework/fixtures/expectFixture.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/framework/fixtures/fixture.go b/test/framework/fixtures/fixture.go index 914cc092e6..83bb7d7120 100644 --- a/test/framework/fixtures/fixture.go +++ b/test/framework/fixtures/fixture.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/framework/fixtures/goalFixture.go b/test/framework/fixtures/goalFixture.go index 576db08ca6..ef52b51b63 100644 --- a/test/framework/fixtures/goalFixture.go +++ b/test/framework/fixtures/goalFixture.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/framework/fixtures/kmdFixture.go b/test/framework/fixtures/kmdFixture.go index db4794d3d3..21c70dd316 100644 --- a/test/framework/fixtures/kmdFixture.go +++ b/test/framework/fixtures/kmdFixture.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/framework/fixtures/libgoalFixture.go b/test/framework/fixtures/libgoalFixture.go index 02548c1f00..7294bafc40 100644 --- a/test/framework/fixtures/libgoalFixture.go +++ b/test/framework/fixtures/libgoalFixture.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/framework/fixtures/restClientFixture.go b/test/framework/fixtures/restClientFixture.go index 5150e5c28e..1599843544 100644 --- a/test/framework/fixtures/restClientFixture.go +++ b/test/framework/fixtures/restClientFixture.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/framework/fixtures/webProxyFixture.go b/test/framework/fixtures/webProxyFixture.go index 0b9e12d936..c048875809 100644 --- a/test/framework/fixtures/webProxyFixture.go +++ b/test/framework/fixtures/webProxyFixture.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/heapwatch/block_history.py b/test/heapwatch/block_history.py index ac5c631c4f..c7fa3ce6f5 100644 --- a/test/heapwatch/block_history.py +++ b/test/heapwatch/block_history.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# Copyright (C) 2019-2022 Algorand, Inc. +# Copyright (C) 2019-2023 Algorand, Inc. # This file is part of go-algorand # # go-algorand is free software: you can redistribute it and/or modify @@ -22,6 +22,7 @@ # pip install py-algorand-sdk import argparse +import atexit import base64 import logging import os @@ -231,6 +232,7 @@ def main(): ap.add_argument('-t', '--token', default=None, help='algod API access token') ap.add_argument('--header', dest='headers', nargs='*', help='"Name: value" HTTP header (repeatable)') ap.add_argument('--all', default=False, action='store_true', help='fetch all blocks from 0') + ap.add_argument('--pid') ap.add_argument('--verbose', default=False, action='store_true') ap.add_argument('-o', '--out', default=None, help='file to append json lines to') args = ap.parse_args() @@ -240,6 +242,11 @@ def main(): else: logging.basicConfig(level=logging.INFO) + if args.pid: + with open(args.pid, 'w') as fout: + fout.write('{}'.format(os.getpid())) + atexit.register(os.remove, args.pid) + algorand_data = args.algod or os.getenv('ALGORAND_DATA') if not algorand_data and not ((args.token or args.headers) and args.addr): sys.stderr.write('must specify algod data dir by $ALGORAND_DATA or -d/--algod; OR --a/--addr and -t/--token\n') diff --git a/test/heapwatch/block_history_plot.py b/test/heapwatch/block_history_plot.py index 48bd22ebdd..03f5a45221 100644 --- a/test/heapwatch/block_history_plot.py +++ b/test/heapwatch/block_history_plot.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# Copyright (C) 2019-2022 Algorand, Inc. +# Copyright (C) 2019-2023 Algorand, Inc. # This file is part of go-algorand # # go-algorand is free software: you can redistribute it and/or modify @@ -25,6 +25,7 @@ import base64 import os import statistics +import sys from algosdk.encoding import msgpack from matplotlib import pyplot as plt @@ -65,7 +66,10 @@ def process(path, args): block = row['block'] rnd = block.get('rnd',0) if (rnd < minrnd) or ((maxrnd is not None) and (rnd > maxrnd)): + sys.stderr.write(f'skip rnd {rnd}\n') continue + if (prevrnd is not None) and (rnd <= prevrnd): + sys.stderr.write(f'wat rnd {rnd}, prevrnd {prevrnd}, line {count}\n') tc = block.get('tc', 0) ts = block.get('ts', 0) # timestamp recorded at algod, 1s resolution int _time = row['_time'] # timestamp recorded at client, 0.000001s resolution float @@ -82,6 +86,8 @@ def process(path, args): tsv.append(_time) if dt > 0.5: dtxn = tc - prevtc + if dtxn < 0: + sys.stderr.write(f'{path}:{count} tc {tc}, prevtc {prevtc}, rnd {rnd}, prevrnd {prevrnd}\n') tps = dtxn / dt mintxn = min(dtxn,mintxn) maxtxn = max(dtxn,maxtxn) @@ -93,7 +99,7 @@ def process(path, args): dtv.append(dt) txnv.append(dtxn) else: - print('b[{}] - b[{}], dt={}'.format(rnd-1,rnd,dt)) + sys.stderr.write('b[{}] - b[{}], dt={}\n'.format(rnd-1,rnd,dt)) else: tsv.append(ts) prevrnd = rnd diff --git a/test/heapwatch/block_history_relays.py b/test/heapwatch/block_history_relays.py index 5d3c7b0c75..ebf310587c 100644 --- a/test/heapwatch/block_history_relays.py +++ b/test/heapwatch/block_history_relays.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# Copyright (C) 2019-2022 Algorand, Inc. +# Copyright (C) 2019-2023 Algorand, Inc. # This file is part of go-algorand # # go-algorand is free software: you can redistribute it and/or modify diff --git a/test/heapwatch/client_ram_report.py b/test/heapwatch/client_ram_report.py index 04f212f18a..51f7bcb319 100644 --- a/test/heapwatch/client_ram_report.py +++ b/test/heapwatch/client_ram_report.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# Copyright (C) 2019-2022 Algorand, Inc. +# Copyright (C) 2019-2023 Algorand, Inc. # This file is part of go-algorand # # go-algorand is free software: you can redistribute it and/or modify diff --git a/test/heapwatch/heapWatch.py b/test/heapwatch/heapWatch.py index 883223e05c..22a5a0e3a3 100644 --- a/test/heapwatch/heapWatch.py +++ b/test/heapwatch/heapWatch.py @@ -1,6 +1,6 @@ #!/usr/bin/python3 # -# Copyright (C) 2019-2022 Algorand, Inc. +# Copyright (C) 2019-2023 Algorand, Inc. # This file is part of go-algorand # # go-algorand is free software: you can redistribute it and/or modify diff --git a/test/heapwatch/metrics_delta.py b/test/heapwatch/metrics_delta.py index 3ec4939882..d50346ec04 100644 --- a/test/heapwatch/metrics_delta.py +++ b/test/heapwatch/metrics_delta.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# Copyright (C) 2019-2022 Algorand, Inc. +# Copyright (C) 2019-2023 Algorand, Inc. # This file is part of go-algorand # # go-algorand is free software: you can redistribute it and/or modify @@ -304,6 +304,28 @@ def plot_pool(self, outpath): plt.savefig(outpath + '.svg', format='svg') plt.savefig(outpath + '.png', format='png') + def heap_xy(self): + # data from algod_go_memory_classes_heap_objects_bytes + x = [] + y = [] + for nick, ns in self.nodes.items(): + if not ns.objectBytes: + continue + for curtime, nbytes in ns.objectBytes: + x.append(curtime) + y.append(nbytes) + return x, y + + def plot_heaps(self, outpath): + # data from algod_go_memory_classes_heap_objects_bytes + from matplotlib import pyplot as plt + x, y = self.heap_xy() + if (not x) or (not y): + return + plt.scatter(x, y) + plt.savefig(outpath + '.svg', format='svg') + plt.savefig(outpath + '.png', format='png') + def anynickre(nick_re, nicks): if not nick_re: return True @@ -343,6 +365,12 @@ def process_nick_re(nre, filesByNick, nick_to_tfname, rsum, args, grsum): if anynickre(nretup, (rnick,nick)): rsum(process_files(args, nick, paths, grsum), nick) +label_colors = { + 'relay': (1.0,0,0), + 'pn': (0,0,1.0), + 'npn': (.7,.7,0), +} + def main(): os.environ['TZ'] = 'UTC' time.tzset() @@ -357,6 +385,7 @@ def main(): ap.add_argument('--nick-re', action='append', default=[], help='regexp to filter node names, may be repeated') ap.add_argument('--nick-lre', action='append', default=[], help='label:regexp to filter node names, may be repeated') ap.add_argument('--pool-plot-root', help='write to foo.svg and .png') + ap.add_argument('--heap-plot-root', help='write to foo.svg and .png') ap.add_argument('--verbose', default=False, action='store_true') args = ap.parse_args() @@ -435,6 +464,7 @@ def main(): htmlout.write(rsum.html()) return 0 if args.nick_lre: + heaps_xy_color = [] for lnre in args.nick_lre: label, nre = lnre.split(':', maxsplit=1) rsum = summary(label) @@ -443,7 +473,18 @@ def main(): print('\n') if htmlout: htmlout.write(rsum.html()) + x, y = rsum.heap_xy() + c = label_colors.get(label) + heaps_xy_color.append((x, y, c)) + if args.heap_plot_root: + from matplotlib import pyplot as plt + for x,y,c in heaps_xy_color: + plt.scatter(x, y, color=c) + plt.savefig(args.heap_plot_root + '.svg', format='svg') + plt.savefig(args.heap_plot_root + '.png', format='png') return 0 + elif args.heap_plot_root: + grsum.plot_heaps(args.heap_plot_root) # no filters, print global result print(grsum) @@ -497,6 +538,8 @@ def __init__(self): self.times = [] # algod_tx_pool_count{} self.txPool = [] + # objectBytes = [(curtime, algod_go_memory_classes_heap_objects_bytes), ...] + self.objectBytes = [] # total across all measurements self.tps = 0 self.blockTime = 0 @@ -506,6 +549,8 @@ def __init__(self): def process_files(self, args, nick=None, metrics_files=None, bisource=None): "returns self, a nodestats object" + if bisource is not None: + logger.debug('process_files %r external bisource', nick) if bisource is None: bisource = {} self.args = args @@ -546,11 +591,15 @@ def process_files(self, args, nick=None, metrics_files=None, bisource=None): with open(bijsonpath, 'rt', encoding="utf-8") as fin: bi = json.load(fin) self.biByTime[curtime] = bi + logger.debug('bi r=%d %s', bi.get('block',{}).get('rnd', 0), bijsonpath) if bi is None: bi = bisource.get(curtime) if bi is None: logger.warning('%s no blockinfo', path) self.txPool.append(cur.get('algod_tx_pool_count{}')) + objectBytes = cur.get('algod_go_memory_classes_heap_objects_bytes') + if objectBytes: + self.objectBytes.append((curtime, objectBytes)) #logger.debug('%s: %r', path, cur) verifyGood = cur.get('algod_agreement_proposal_verify_good{}') verifyMs = cur.get('algod_agreement_proposal_verify_ms{}') @@ -562,7 +611,7 @@ def process_files(self, args, nick=None, metrics_files=None, bisource=None): dt = curtime - prevtime #print("{} ->\n{}".format(prevPath, path)) #print(json.dumps(d, indent=2, sort_keys=True)) - self.deltas.append((curtime, d)) + self.deltas.append((curtime, dt, d)) tps = None blocktime = None txnCount = 0 @@ -607,8 +656,14 @@ def process_files(self, args, nick=None, metrics_files=None, bisource=None): prevbi = bi if prevbi is None or firstBi is None: return self + if prevbi is firstBi: + logger.warning('only one blockinfo for %s', nick) + return self txnCount = prevbi.get('block',{}).get('tc',0) - firstBi.get('block',{}).get('tc',0) rounds = prevbi.get('block',{}).get('rnd',0) - firstBi.get('block',{}).get('rnd',0) + if rounds == 0: + logger.warning('no rounds for %s', nick) + return self totalDt = prevtime - firstTime self.tps = txnCount / totalDt self.blockTime = totalDt / rounds @@ -625,7 +680,7 @@ def process_files(self, args, nick=None, metrics_files=None, bisource=None): reportf.close() if self.deltas and args.deltas: keys = set() - for ct, d in self.deltas: + for ct, dt, d in self.deltas: keys.update(set(d.keys())) keys = sorted(keys) deltapath = args.deltas @@ -633,9 +688,9 @@ def process_files(self, args, nick=None, metrics_files=None, bisource=None): deltapath = deltapath.replace('.csv', '.{}.csv'.format(nick)) with sopen(deltapath, 'wt') as fout: writer = csv.writer(fout) - writer.writerow(['when'] + keys) + writer.writerow(['when', 'dt'] + keys) for ct, d in self.deltas: - row = [time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(ct))] + row = [time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(ct)), dt] for k in keys: row.append(d.get(k, None)) writer.writerow(row) diff --git a/test/heapwatch/nodeHostTarget.py b/test/heapwatch/nodeHostTarget.py index 2a29f4f8f5..ccab46ba1c 100644 --- a/test/heapwatch/nodeHostTarget.py +++ b/test/heapwatch/nodeHostTarget.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# Copyright (C) 2019-2022 Algorand, Inc. +# Copyright (C) 2019-2023 Algorand, Inc. # This file is part of go-algorand # # go-algorand is free software: you can redistribute it and/or modify diff --git a/test/heapwatch/plot_crr_csv.py b/test/heapwatch/plot_crr_csv.py index 14f23b857b..f4a46cf851 100755 --- a/test/heapwatch/plot_crr_csv.py +++ b/test/heapwatch/plot_crr_csv.py @@ -47,6 +47,9 @@ def main(): klist.append((xround, v)) minv = smin(minv, v) maxv = smax(maxv, v) + if not fvals: + print(f"{fname} empty") + continue print("{} found series {}".format(fname, sorted(fvals.keys()))) fig, ax = plt.subplots() ax.set_ylabel('bytes') diff --git a/test/heapwatch/runNodeHost.py b/test/heapwatch/runNodeHost.py index 1d14881fd1..be2ce61bea 100644 --- a/test/heapwatch/runNodeHost.py +++ b/test/heapwatch/runNodeHost.py @@ -1,5 +1,5 @@ #!/usr/bin/python3 -# Copyright (C) 2019-2022 Algorand, Inc. +# Copyright (C) 2019-2023 Algorand, Inc. # This file is part of go-algorand # # go-algorand is free software: you can redistribute it and/or modify diff --git a/test/linttest/lintissues.go b/test/linttest/lintissues.go index 77b0c33c2a..cdfdbf422b 100644 --- a/test/linttest/lintissues.go +++ b/test/linttest/lintissues.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/netperf-go/puppeteer/main.go b/test/netperf-go/puppeteer/main.go index 87dbb47cf7..fbebeb705c 100644 --- a/test/netperf-go/puppeteer/main.go +++ b/test/netperf-go/puppeteer/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/netperf-go/puppeteer/promMetricFetcher.go b/test/netperf-go/puppeteer/promMetricFetcher.go index 06e0853c84..ea24008fa4 100644 --- a/test/netperf-go/puppeteer/promMetricFetcher.go +++ b/test/netperf-go/puppeteer/promMetricFetcher.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/netperf-go/puppeteer/promMetricFetcher_test.go b/test/netperf-go/puppeteer/promMetricFetcher_test.go index 86579f86f3..200a9af258 100644 --- a/test/netperf-go/puppeteer/promMetricFetcher_test.go +++ b/test/netperf-go/puppeteer/promMetricFetcher_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/netperf-go/puppeteer/puppeteer.go b/test/netperf-go/puppeteer/puppeteer.go index 5a5fab11ce..260c659677 100644 --- a/test/netperf-go/puppeteer/puppeteer.go +++ b/test/netperf-go/puppeteer/puppeteer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/netperf-go/puppeteer/puppeteer_test.go b/test/netperf-go/puppeteer/puppeteer_test.go index 7bf89cddca..e202751439 100644 --- a/test/netperf-go/puppeteer/puppeteer_test.go +++ b/test/netperf-go/puppeteer/puppeteer_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/netperf-go/puppeteer/roundpoller.go b/test/netperf-go/puppeteer/roundpoller.go index f4d689f6f1..2ce501a2a4 100644 --- a/test/netperf-go/puppeteer/roundpoller.go +++ b/test/netperf-go/puppeteer/roundpoller.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/partitiontest/filtering.go b/test/partitiontest/filtering.go index 0af5e2e141..19820f6014 100644 --- a/test/partitiontest/filtering.go +++ b/test/partitiontest/filtering.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/reflectionhelpers/helpers.go b/test/reflectionhelpers/helpers.go index ad551aa0f4..79c24d5e2e 100644 --- a/test/reflectionhelpers/helpers.go +++ b/test/reflectionhelpers/helpers.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/scripts/e2e.sh b/test/scripts/e2e.sh index b7d04ff4aa..760c3170ad 100755 --- a/test/scripts/e2e.sh +++ b/test/scripts/e2e.sh @@ -78,12 +78,15 @@ echo "RUN_KMD_WITH_UNSAFE_SCRYPT = ${RUN_KMD_WITH_UNSAFE_SCRYPT}" export BINDIR=${TEMPDIR}/bin export DATADIR=${TEMPDIR}/data +export NETWORKDIR=${TEMPDIR}/net function reset_dirs() { rm -rf ${BINDIR} rm -rf ${DATADIR} + rm -rf ${NETWORKDIR} mkdir -p ${BINDIR} mkdir -p ${DATADIR} + mkdir -p ${NETWORKDIR} } # $1 - Message @@ -121,7 +124,6 @@ if [ -z "$E2E_TEST_FILTER" ] || [ "$E2E_TEST_FILTER" == "SCRIPTS" ]; then python3 -m venv "${TEMPDIR}/ve" . "${TEMPDIR}/ve/bin/activate" "${TEMPDIR}/ve/bin/pip3" install --upgrade pip - "${TEMPDIR}/ve/bin/pip3" install --upgrade cryptograpy # Pin a version of our python SDK's so that breaking changes don't spuriously break our tests. # Please update as necessary. @@ -155,9 +157,16 @@ if [ -z "$E2E_TEST_FILTER" ] || [ "$E2E_TEST_FILTER" == "SCRIPTS" ]; then exit fi - ./timeout 200 ./e2e_basic_start_stop.sh + goal network create \ + -r $NETWORKDIR \ + -n tbd \ + -t ../testdata/nettemplates/TwoNodes50EachFuture.json + + ./timeout 200 ./e2e_basic_start_stop.sh $NETWORKDIR duration "e2e_basic_start_stop.sh" + goal network delete -r $NETWORKDIR + KEEP_TEMPS_CMD_STR="" # If the platform is arm64, we want to pass "--keep-temps" into e2e_client_runner.py diff --git a/test/scripts/e2e_basic_start_stop.sh b/test/scripts/e2e_basic_start_stop.sh index 1bb9b0bf13..03475acef9 100755 --- a/test/scripts/e2e_basic_start_stop.sh +++ b/test/scripts/e2e_basic_start_stop.sh @@ -1,24 +1,31 @@ #!/usr/bin/env bash + +set -euf -o pipefail + echo "######################################################################" echo " e2e_basic_start_stop" echo "######################################################################" -set -e -# Suppress telemetry reporting for tests -export ALGOTEST=1 +if [ "$#" -eq 0 ]; then + echo "Usage: e2e_basic_start_stop.sh " + exit 1 +fi +DATA_DIR="$1" RUNNING_COUNT=0 +# Suppress telemetry reporting for tests +export ALGOTEST=1 function update_running_count() { - PIDS=($(pgrep -u $(whoami) -x algod)) || true + PIDS=($(pgrep -u "$(whoami)" -x algod)) || true RUNNING_COUNT=${#PIDS[@]} } function verify_at_least_one_running() { # Starting up can take some time, so wait at least 2 seconds - for TRIES in 1 2 3 4 5; do + for _ in 1 2 3 4 5; do update_running_count - if [ ${RUNNING_COUNT} -ge 1 ]; then + if [ "$RUNNING_COUNT" -ge 1 ]; then return 0 fi sleep .4 @@ -28,34 +35,32 @@ function verify_at_least_one_running() { } function verify_none_running() { - local datadir=$1 - # Shutting down can take some time, so wait at least 5 seconds - for TRIES in 1 2 3 4 5; do + for _ in 1 2 3 4 5; do update_running_count - if [ ${RUNNING_COUNT} -eq 0 ]; then + if [ "$RUNNING_COUNT" -eq 0 ]; then return 0 fi sleep 1.4 done echo "algod not expected to be running but it is" - if [ -n "$datadir" ]; then + if [ -n "$DATA_DIR" ]; then echo "last 20 lines of node.log:" - tail -20 "$datadir/node.log" + tail -20 "$DATA_DIR/node.log" echo "================================" echo "stdout and stdin:" - cat "$datadir/algod-out.log" + cat "$DATA_DIR/algod-out.log" echo "================================" - cat "$datadir/algod-err.log" + cat "$DATA_DIR/algod-err.log" fi exit 1 } function verify_one_running() { # Starting up can take some time, so retry up to 2 seconds - for TRIES in 1 2 3 4 5; do + for _ in 1 2 3 4 5; do update_running_count - if [ ${RUNNING_COUNT} -eq 1 ]; then + if [ "$RUNNING_COUNT" -eq 1 ]; then return 0 fi sleep .4 @@ -70,33 +75,33 @@ verify_none_running #---------------------- # Test that we can start & stop a generic node with no overrides echo Verifying a generic node will start using goal -goal node start -d ${DATADIR} +goal node start -d "$DATA_DIR" verify_at_least_one_running echo Verifying we can stop it using goal -goal node stop -d ${DATADIR} -verify_none_running ${DATADIR} +goal node stop -d "$DATA_DIR" +verify_none_running #---------------------- # Test that we can start a generic node straight with no overrides echo Verifying a generic node will start directly -algod -d ${DATADIR} & +algod -d "$DATA_DIR" & verify_at_least_one_running -pkill -u $(whoami) -x algod || true -verify_none_running ${DATADIR} +pkill -u "$(whoami)" -x algod || true +verify_none_running #---------------------- -# Test that we can start a generic node against the datadir -# but that we cannot start a second one against same datadir -echo Verifying that the datadir algod lock works correctly -algod -d ${DATADIR} & +# Test that we can start a generic node against the data dir +# but that we cannot start a second one against same data dir +echo Verifying that the data dir algod lock works correctly +algod -d "$DATA_DIR" & verify_at_least_one_running -algod -d ${DATADIR} & +algod -d "$DATA_DIR" & verify_at_least_one_running # one should still be running verify_one_running # in fact, exactly one should still be running # clean up -pkill -u $(whoami) -x algod || true -verify_none_running ${DATADIR} +pkill -u "$(whoami)" -x algod || true +verify_none_running echo "----------------------------------------------------------------------" echo " DONE: e2e_basic_start_stop" diff --git a/test/scripts/e2e_subs/e2e-app-simple.sh b/test/scripts/e2e_subs/e2e-app-simple.sh index e1f1458ce4..660487621d 100755 --- a/test/scripts/e2e_subs/e2e-app-simple.sh +++ b/test/scripts/e2e_subs/e2e-app-simple.sh @@ -122,3 +122,27 @@ ${gcmd} app optin --app-id $APPID --from $ACCOUNT # Succeed in clearing state for the app ${gcmd} app clear --app-id $APPID --from $ACCOUNT + + +# Empty program: +printf ' ' > "${TEMPDIR}/empty_clear.teal" + +# Fail to compile an empty program +RES=$(${gcmd} clerk compile "${TEMPDIR}/empty_clear.teal" 2>&1 | tr -d '\n' || true) +EXPERROR='Cannot assemble empty program text' +if [[ $RES != *"${EXPERROR}"* ]]; then + echo RES="$RES" + echo EXPERROR="$EXPERROR" + date '+clerk-compile-test FAIL wrong error for compiling empty program %Y%m%d_%H%M%S' + false +fi + +# Fail to create an app because the clear program is empty +RES=$(${gcmd} app create --creator "${ACCOUNT}" --approval-prog "${TEMPDIR}/simple.teal" --clear-prog "${TEMPDIR}/empty_clear.teal" --global-byteslices 0 --global-ints 0 --local-byteslices 0 --local-ints 0 2>&1 | tr -d '\n' || true) +EXPERROR='Cannot assemble empty program text' +if [[ $RES != *"${EXPERROR}"* ]]; then + echo RES="$RES" + echo EXPERROR="$EXPERROR" + date '+app-create-test FAIL wrong error for creating app with empty clear program %Y%m%d_%H%M%S' + false +fi diff --git a/test/scripts/e2e_tx_latency.py b/test/scripts/e2e_tx_latency.py new file mode 100644 index 0000000000..b181002d90 --- /dev/null +++ b/test/scripts/e2e_tx_latency.py @@ -0,0 +1,400 @@ +#!/usr/bin/env python3 +# +# Measure total percieved tx latency. +# Submit transactions to algod, watch blocks for committed transaction. + +import argparse +import atexit +import base64 +import datetime +import glob +import json +import logging +import os +import queue +import re +import shutil +import statistics +import subprocess +import sys +import tempfile +import time +import threading + +# pip install py-algorand-sdk +import algosdk +from algosdk.encoding import msgpack +import algosdk.v2client +import algosdk.v2client.algod + +logger = logging.getLogger(__name__) + +def openkmd(algodata): + kmdnetpath = sorted(glob.glob(os.path.join(algodata,'kmd-*','kmd.net')))[-1] + kmdnet = open(kmdnetpath, 'rt').read().strip() + kmdtokenpath = sorted(glob.glob(os.path.join(algodata,'kmd-*','kmd.token')))[-1] + kmdtoken = open(kmdtokenpath, 'rt').read().strip() + kmd = algosdk.kmd.KMDClient(kmdtoken, 'http://' + kmdnet) + return kmd + +def openalgod(algodata): + algodnetpath = os.path.join(algodata,'algod.net') + algodnet = open(algodnetpath, 'rt').read().strip() + algodtokenpath = os.path.join(algodata,'algod.token') + algodtoken = open(algodtokenpath, 'rt').read().strip() + algod = algosdk.v2client.algod.AlgodClient(algodtoken, 'http://' + algodnet) + return algod + +def addr_token_from_algod(algorand_data): + with open(os.path.join(algorand_data, 'algod.net')) as fin: + addr = fin.read().strip() + with open(os.path.join(algorand_data, 'algod.token')) as fin: + token = fin.read().strip() + if not addr.startswith('http'): + addr = 'http://' + addr + return addr, token + +def bstr(x): + if isinstance(x, bytes): + try: + return x.decode() + except: + pass + return x + +def obnice(ob): + if isinstance(ob, dict): + return {bstr(k):obnice(v) for k,v in ob.items()} + if isinstance(ob, list): + return [obnice(x) for x in ob] + return ob + +class TxLatencyTest: + def __init__(self, args, algorand_data=None, prev_round=None, out=None): + self.algorand_data = algorand_data + self.token = args.token + self.addr = args.addr + self.headers = header_list_to_dict(args.headers) + self.prev_round = prev_round + self.out = out or sys.stdout + self.lock = threading.Lock() + self.terminated = None + self._kmd = None + self._algod = None + self.privatekey = None + self.pubw = None + self.maxpubaddr = None + self.errors = [] + self.statuses = [] + self.jsonfile = None + self.sentq = queue.Queue() + self.txidq = queue.Queue() + self.period = 1/args.tps + self.sendcount = args.sendcount + self.go = True + self.roundTimes = {} + self.txTimes = [] + return + + def connect(self): + with self.lock: + self._connect() + return self._algod, self._kmd + + def _connect(self): + if self._algod and self._kmd: + return + + # should run from inside self.lock + algodata = self.algorand_data + + logger.debug('pre kmd') + subprocess.run(['goal', 'kmd', 'start', '-t', '3600', '-d', algodata], timeout=5, check=True) + logger.debug('post kmd') + self._kmd = openkmd(algodata) + self._algod = self._algod_connect() #openalgod(algodata) + + def algod(self): + with self.lock: + if self._algod is None: + self._algod = self._algod_connect() + return self._algod + + def _algod_connect(self): + if self.algorand_data: + addr, token = addr_token_from_algod(self.algorand_data) + logger.debug('algod from %r, (%s %s)', self.algorand_data, addr, token) + else: + token = self.token + addr = self.addr + logger.debug('algod from args (%s %s)', self.addr, self.token) + self._algod = algosdk.v2client.algod.AlgodClient(token, addr, headers=self.headers) + return self._algod + + def get_pub_wallet(self): + with self.lock: + self._connect() + if not (self.pubw and self.maxpubaddr): + # find private test node public wallet and its richest account + wallets = self._kmd.list_wallets() + pubwid = None + for xw in wallets: + if xw['name'] == 'unencrypted-default-wallet': + pubwid = xw['id'] + pubw = self._kmd.init_wallet_handle(pubwid, '') + pubaddrs = self._kmd.list_keys(pubw) + maxamount = 0 + maxpubaddr = None + for pa in pubaddrs: + pai = self._algod.account_info(pa) + if pai['amount'] > maxamount: + maxamount = pai['amount'] + maxpubaddr = pai['address'] + self.pubw = pubw + self.maxpubaddr = maxpubaddr + return self.pubw, self.maxpubaddr + + def send_thread(self): + #opriv, opub = algosdk.account.generate_account() + algod = self.algod() + nextsend = time.time() + params = algod.suggested_params() + paramsMtime = nextsend + count = 0 + + while True: + txn = algosdk.transaction.PaymentTxn(self.maxpubaddr, 1000, params.first, params.last, params.gh, self.maxpubaddr, 1, gen=params.gen, flat_fee=True, note='{}_'.format(count).encode() + os.getrandom(8)) + ptxid = txn.get_txid() + if self.privatekey: + stxn = txn.sign(self.privatekey) + else: + stxn = self._kmd.sign_transaction(self.pubw, '', txn) + txid = algod.send_transaction(stxn) + if ptxid != txid: + logger.error('python txid %s, API txid %s', ptxid, txid) + logger.debug('%r', txn.dictify()) + sendt = time.time() + logger.debug('sent %s %f', txid, sendt) + self.sentq.put((txid, sendt)) + + if self.sendcount is not None: + self.sendcount -= 1 + if self.sendcount <= 0: + # signal to consumer end of sending + self.sentq.put((None, None)) + return + + if sendt - paramsMtime > 5: + params = algod.suggested_params() + paramsMtime = sendt + + while nextsend < sendt: + nextsend += self.period + time.sleep(nextsend - sendt) + + def measure_thread(self): + lastround = self.prev_round + algod = self.algod() + while self.go: + b = self.nextblock(lastround) + if b is None: + print("got None nextblock. exiting") + return + b = msgpack.loads(b, strict_map_key=False, raw=True) + b = obnice(b) + nowround = b['block'].get('rnd', 0) + logger.debug('r%d', nowround) + if (lastround is not None) and (nowround != lastround + 1): + logger.info('round jump %d to %d', lastround, nowround) + self._block_handler(b) + lastround = nowround + + def nextblock(self, lastround=None, retries=30): + trycount = 0 + while (trycount < retries) and self.go: + trycount += 1 + try: + return self._nextblock_inner(lastround) + except Exception as e: + if trycount >= retries: + logger.error('too many errors in nextblock retries') + raise + else: + logger.warning('error in nextblock(%r) (retrying): %s', lastround, e) + self._algod = None # retry with a new connection + time.sleep(1.2) + return None + + def _nextblock_inner(self, lastround): + self.block_time = None + algod = self.algod() + if lastround is None: + status = algod.status() + lastround = status['last-round'] + logger.debug('nextblock status last-round %s', lastround) + else: + try: + blk = algod.block_info(lastround + 1, response_format='msgpack') + if blk: + return blk + logger.warning('null block %d, lastround=%r', lastround+1, lastround) + except Exception as e: + pass + #logger.debug('could not get block %d: %s', lastround + 1, e, exc_info=True) + status = algod.status_after_block(lastround) + block_time = time.time() # the block has happened, don't count block data transit time + nbr = status['last-round'] + retries = 30 + while (nbr > lastround + 1) and self.go: + # if more than one block elapsed, we don't have a good time for either block + block_time = None + # try lastround+1 one last time + try: + blk = algod.block_info(lastround + 1, response_format='msgpack') + if blk: + return blk + logger.warning('null block %d, lastround=%r, status.last-round=%d', lastround+1, lastround, nbr) + time.sleep(1.1) + retries -= 1 + if retries <= 0: + raise Exception("too many null block for %d", lastround+1) + except: + break + blk = algod.block_info(nbr, response_format='msgpack') + if blk: + self.block_time = block_time + return blk + raise Exception('got None for blk {}'.format(nbr)) + + def _block_handler(self, b): + block_time = self.block_time or time.time() + nowround = b['block'].get('rnd', 0) + self.roundTimes[nowround] = block_time + # throw away txns, count is kept in round differential ['block']['tc'] + stxibs = b['block'].get('txns', []) + txids = [] + for stxib in stxibs: + txn = stxib['txn'] + #logger.debug('stxib.txn %r', txn) + hgi = stxib.pop('hgi', False) + if hgi: + txn['gh'] = b['block']['gh'] + txn['gen'] = bstr(b['block']['gen']) + txnd = txn + txn = algosdk.transaction.Transaction.undictify(txn) + txid = txn.get_txid() + logger.debug('rx txn %r, txid %s', txnd, txid) + txids.append(txid) + self.txidq.put((txids, block_time)) + + def join_thread(self): + lastSendt = None + sentq = self.sentq + sentByTxid = {} + while self.go: + if sentq is not None: + try: + txid, sendt = self.sentq.get(block=True, timeout=0.2) + if sendt is None: + sentq = None + else: + lastSendt = sendt + sentByTxid[txid] = sendt + except queue.Empty: + pass + elif (lastSendt is None) or (time.time() > (lastSendt + 8)): + self.go = False + return + try: + txids, rxt = self.txidq.get(block=False) + for txid in txids: + sendt = sentByTxid.get(txid) + if sendt is not None: + dt = rxt - sendt + self.txTimes.append(dt) + logger.debug('rx %s %f', txid, dt) + self.out.write('{}\n'.format(dt)) + else: + logger.debug('unk blk txid %r', txid) + except queue.Empty: + pass + +def header_list_to_dict(hlist): + if not hlist: + return None + p = re.compile(r':\s+') + out = {} + for x in hlist: + a, b = p.split(x, 1) + out[a] = b + return out + +def main(): + ap = argparse.ArgumentParser() + ap.add_argument('-d', '--algod', default=None, help='algod data dir') + ap.add_argument('-a', '--addr', default=None, help='algod host:port address') + ap.add_argument('-t', '--token', default=None, help='algod API access token') + ap.add_argument('--header', dest='headers', nargs='*', help='"Name: value" HTTP header (repeatable)') + ap.add_argument('--tps', type=float, default=5, help='TPS to send at') + ap.add_argument('--sendcount', type=int, default=50, help='number of test txns to send at 5 TPS') + ap.add_argument('--verbose', default=False, action='store_true') + args = ap.parse_args() + + if args.verbose: + logging.basicConfig(level=logging.DEBUG) + else: + logging.basicConfig(level=logging.INFO) + + algorand_data = args.algod or os.getenv('ALGORAND_DATA') + if not algorand_data and not ((args.token or args.headers) and args.addr): + sys.stderr.write('must specify algod data dir by $ALGORAND_DATA or -d/--algod; OR --a/--addr and -t/--token\n') + sys.exit(1) + + out = sys.stdout + + bot = TxLatencyTest( + args, + algorand_data, + prev_round=None, + out=out, + ) + + pubw, maxpubaddr = bot.get_pub_wallet() + logger.debug('get pub wallet -> %s %s', pubw, maxpubaddr) + + sender = threading.Thread(target=bot.send_thread) + sender.start() + measure = threading.Thread(target=bot.measure_thread) + measure.start() + merge = threading.Thread(target=bot.join_thread) + merge.start() + sender.join() + measure.join() + merge.join() + + txTimes = bot.txTimes + rounds = sorted(bot.roundTimes.keys()) + prevRound = None + prevRoundTime = None + roundDts = [] + for rnd in rounds: + rndTime = bot.roundTimes[rnd] + if (prevRound is not None) and (prevRound + 1 == rnd): + dt = rndTime - prevRoundTime + roundDts.append(dt) + prevRound = rnd + prevRoundTime = rndTime + roundTimeMean = statistics.mean(roundDts) + roundTimeMin = min(roundDts) + roundTimeMax = max(roundDts) + txMean = statistics.mean(txTimes) + tmin = min(txTimes) + tmax = max(txTimes) + out.write('# {} txns measured, {} rounds seen\n'.format(len(txTimes), len(rounds))) + out.write('# rnd (min={}, mean={}, max={})\n'.format(roundTimeMin, roundTimeMean, roundTimeMax)) + out.write('# tx (min={}, mean={}, max={})\n'.format(tmin, txMean, tmax)) + out.write('# tx (min={}, mean={}, max={}) (/(mean rnd))\n'.format(tmin/roundTimeMean, txMean/roundTimeMean, tmax/roundTimeMean)) + return 0 + +if __name__ == '__main__': + main() diff --git a/test/scripts/latency_plot.py b/test/scripts/latency_plot.py new file mode 100644 index 0000000000..aab94d4391 --- /dev/null +++ b/test/scripts/latency_plot.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python3 +# +# process pingpong TotalLatencyOut output file into a graph +# +# requires: +# pip install matplotlib + +import argparse +import gzip +import math +import os +import statistics +import sys +import tarfile + +def mmstdm(data): + dmin = min(data) + dmax = max(data) + dmean = statistics.mean(data) + dstd = statistics.pstdev(data) + return f'[{dmin:.3f}/{dmean:.3f} ({dstd:.3f})/{dmax:.3f}]' + +class LatencyAnalyzer: + def __init__(self): + self.data = [] + def read(self, path): + if path.endswith('.gz'): + with gzip.open(path, 'rt') as fin: + self.rlines(fin) + else: + with open(path, 'rt') as fin: + self.rlines(fin) + def rlines(self, linesource): + for line in linesource: + rec = int(line.strip())/1000000000.0 + self.data.append(rec) + def plot(self, outname): + from matplotlib import pyplot as plt + plt.plot(self.data) + plt.savefig(outname + '.png', format='png') + plt.savefig(outname + '.svg', format='svg') + def report_data(self): + self.data.sort() + some = int(math.log(len(self.data))*4) + lowest = self.data[:some] + highest = self.data[-some:] + return some, lowest, highest + def report(self): + lines = [] + some, lowest, highest = self.report_data() + lines.append(f'{len(self.data)} points: {mmstdm(self.data)}') + lines.append('min {:.3f}s'.format(min(self.data))) + lines.append('max {:.3f}s'.format(max(self.data))) + lines.append(f'lowest-{some}: {mmstdm(lowest)}') + lines.append(f'highest-{some}: {mmstdm(highest)}') + return '\n'.join(lines) + def report_html(self): + lines = [] + some, lowest, highest = self.report_data() + lines.append('

Latency

') + lines.append('
[min/mean (std)/max]
') + lines.append(f'
{len(self.data)} points: {mmstdm(self.data)}
') + lines.append(f'
min {min(self.data):.3f}s
') + lines.append(f'
max {max(self.data):.3f}s
') + lines.append(f'
lowest-{some}: {mmstdm(lowest)}
') + lines.append(f'
highest-{some}: {mmstdm(highest)}
') + return ''.join(lines) + +def main(): + ap = argparse.ArgumentParser() + ap.add_argument('latency_log', nargs='*') + ap.add_argument('-p', '--plot', help='plot base name for .png .svg') + ap.add_argument('-a', '--aout', help='report text output path (append)') + ap.add_argument('-H', '--ahtml', help='report html output path (append)') + ap.add_argument('--tardir') + args = ap.parse_args() + + la = LatencyAnalyzer() + for path in args.latency_log: + la.read(path) + if args.tardir: + for dirpath, dirnames, filenames in os.walk(args.tardir): + for fname in filenames: + if fname.endswith('.tar.bz2'): + tarname = os.path.join(dirpath, fname) + tf = tarfile.open(tarname, 'r:bz2') + for tinfo in tf: + if not tinfo.isfile(): + continue + if 'latency' in tinfo.name: + rawf = tf.extractfile(tinfo) + if tinfo.name.endswith('.gz'): + fin = gzip.open(rawf, 'rt') + else: + fin = rawf + rawf = None + try: + la.rlines(fin) + except Exception as e: + sys.stderr.write(f'{tarname}/{tinfo.name}: {e}\n') + fin.close() + if rawf is not None: + rawf.close() + + if args.plot: + la.plot(args.plot) + if args.ahtml: + with open(args.ahtml, 'at') as fout: + fout.write(la.report_html()) + if args.aout: + with open(args.aout, 'at') as fout: + fout.write(la.report()) + if (not args.aout) and (not args.ahtml): + print(la.report()) + +if __name__ == '__main__': + main() diff --git a/test/testdata/configs/config-v26.json b/test/testdata/configs/config-v26.json index 31d693f90a..6589d03266 100644 --- a/test/testdata/configs/config-v26.json +++ b/test/testdata/configs/config-v26.json @@ -38,6 +38,7 @@ "EnableBlockServiceFallbackToArchiver": true, "EnableCatchupFromArchiveServers": false, "EnableDeveloperAPI": false, + "EnableExperimentalAPI": false, "EnableGossipBlockService": true, "EnableIncomingMessageFilter": false, "EnableLedgerService": false, @@ -93,9 +94,9 @@ "RunHosted": false, "SuggestedFeeBlockHistory": 3, "SuggestedFeeSlidingWindowSize": 50, + "TelemetryToLog": true, "TLSCertFile": "", "TLSKeyFile": "", - "TelemetryToLog": true, "TransactionSyncDataExchangeRate": 0, "TransactionSyncSignificantMessageThreshold": 0, "TxIncomingFilteringFlags": 1, diff --git a/test/testdata/configs/config-v27.json b/test/testdata/configs/config-v27.json new file mode 100644 index 0000000000..137578e0fa --- /dev/null +++ b/test/testdata/configs/config-v27.json @@ -0,0 +1,116 @@ +{ + "Version": 27, + "AccountUpdatesStatsInterval": 5000000000, + "AccountsRebuildSynchronousMode": 1, + "AgreementIncomingBundlesQueueLength": 15, + "AgreementIncomingProposalsQueueLength": 50, + "AgreementIncomingVotesQueueLength": 20000, + "AnnounceParticipationKey": true, + "Archival": false, + "BaseLoggerDebugLevel": 4, + "BlockServiceCustomFallbackEndpoints": "", + "BroadcastConnectionsLimit": -1, + "CadaverDirectory": "", + "CadaverSizeTarget": 0, + "CatchpointFileHistoryLength": 365, + "CatchpointInterval": 10000, + "CatchpointTracking": 0, + "CatchupBlockDownloadRetryAttempts": 1000, + "CatchupBlockValidateMode": 0, + "CatchupFailurePeerRefreshRate": 10, + "CatchupGossipBlockFetchTimeoutSec": 4, + "CatchupHTTPBlockFetchTimeoutSec": 4, + "CatchupLedgerDownloadRetryAttempts": 50, + "CatchupParallelBlocks": 16, + "ConnectionsRateLimitingCount": 60, + "ConnectionsRateLimitingWindowSeconds": 1, + "DNSBootstrapID": ".algorand.network", + "DNSSecurityFlags": 1, + "DeadlockDetection": 0, + "DeadlockDetectionThreshold": 30, + "DisableLocalhostConnectionRateLimit": true, + "DisableNetworking": false, + "DisableOutgoingConnectionThrottling": false, + "EnableAccountUpdatesStats": false, + "EnableAgreementReporting": false, + "EnableAgreementTimeMetrics": false, + "EnableAssembleStats": false, + "EnableBlockService": false, + "EnableBlockServiceFallbackToArchiver": true, + "EnableCatchupFromArchiveServers": false, + "EnableDeveloperAPI": false, + "EnableExperimentalAPI": false, + "EnableGossipBlockService": true, + "EnableIncomingMessageFilter": false, + "EnableLedgerService": false, + "EnableMetricReporting": false, + "EnableOutgoingNetworkMessageFiltering": true, + "EnablePingHandler": true, + "EnableProcessBlockStats": false, + "EnableProfiler": false, + "EnableRequestLogger": false, + "EnableRuntimeMetrics": false, + "EnableTopAccountsReporting": false, + "EnableTxBacklogRateLimiting": false, + "EnableUsageLog": false, + "EnableVerbosedTransactionSyncLogging": false, + "EndpointAddress": "127.0.0.1:0", + "FallbackDNSResolverAddress": "", + "ForceFetchTransactions": false, + "ForceRelayMessages": false, + "GossipFanout": 4, + "HeartbeatUpdateInterval": 600, + "IncomingConnectionsLimit": 2400, + "IncomingMessageFilterBucketCount": 5, + "IncomingMessageFilterBucketSize": 512, + "IsIndexerActive": false, + "LedgerSynchronousMode": 2, + "LogArchiveMaxAge": "", + "LogArchiveName": "node.archive.log", + "LogSizeLimit": 1073741824, + "MaxAPIBoxPerApplication": 100000, + "MaxAPIResourcesPerAccount": 100000, + "MaxAcctLookback": 4, + "MaxCatchpointDownloadDuration": 7200000000000, + "MaxConnectionsPerIP": 15, + "MinCatchpointFileDownloadBytesPerSecond": 20480, + "NetAddress": "", + "NetworkMessageTraceServer": "", + "NetworkProtocolVersion": "", + "NodeExporterListenAddress": ":9100", + "NodeExporterPath": "./node_exporter", + "OptimizeAccountsDatabaseOnStartup": false, + "OutgoingMessageFilterBucketCount": 3, + "OutgoingMessageFilterBucketSize": 128, + "ParticipationKeysRefreshInterval": 60000000000, + "PeerConnectionsUpdateInterval": 3600, + "PeerPingPeriodSeconds": 0, + "PriorityPeers": {}, + "ProposalAssemblyTime": 500000000, + "PublicAddress": "", + "ReconnectTime": 60000000000, + "ReservedFDs": 256, + "RestConnectionsHardLimit": 2048, + "RestConnectionsSoftLimit": 1024, + "RestReadTimeoutSeconds": 15, + "RestWriteTimeoutSeconds": 120, + "RunHosted": false, + "SuggestedFeeBlockHistory": 3, + "SuggestedFeeSlidingWindowSize": 50, + "TLSCertFile": "", + "TLSKeyFile": "", + "TelemetryToLog": true, + "TransactionSyncDataExchangeRate": 0, + "TransactionSyncSignificantMessageThreshold": 0, + "TxBacklogReservedCapacityPerPeer": 20, + "TxBacklogServiceRateWindowSeconds": 10, + "TxBacklogSize": 26000, + "TxIncomingFilteringFlags": 1, + "TxPoolExponentialIncreaseFactor": 2, + "TxPoolSize": 75000, + "TxSyncIntervalSeconds": 60, + "TxSyncServeResponseSize": 1000000, + "TxSyncTimeoutSeconds": 30, + "UseXForwardedForAddressField": "", + "VerifiedTranscationsCacheSize": 150000 +} diff --git a/tools/boxkey/convertBoxKey.go b/tools/boxkey/convertBoxKey.go new file mode 100644 index 0000000000..0d77d2f84b --- /dev/null +++ b/tools/boxkey/convertBoxKey.go @@ -0,0 +1,48 @@ +// Copyright (C) 2019-2023 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package main + +import ( + "encoding/base64" + "encoding/hex" + "flag" + "fmt" + + "github.com/algorand/avm-abi/apps" +) + +func main() { + var name string + var appIdx uint64 + flag.Uint64Var(&appIdx, "a", 0, "base64/algorand address to convert to the other") + flag.StringVar(&name, "n", "", "base64 box name") + flag.Parse() + + if appIdx == 0 && name == "" { + fmt.Println("provide input with '-a' and '-k' flags.") + return + } + + nameBytes, err := base64.StdEncoding.DecodeString(name) + if err != nil { + fmt.Println("invalid key value") + return + } + key := apps.MakeBoxKey(appIdx, string(nameBytes)) + fmt.Println(base64.StdEncoding.EncodeToString([]byte(key))) + fmt.Println(hex.EncodeToString([]byte(key))) +} diff --git a/tools/debug/algodump/main.go b/tools/debug/algodump/main.go index f61944ff1a..be80768fc5 100644 --- a/tools/debug/algodump/main.go +++ b/tools/debug/algodump/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/debug/carpenter/main.go b/tools/debug/carpenter/main.go index d43f2267b0..625211694a 100644 --- a/tools/debug/carpenter/main.go +++ b/tools/debug/carpenter/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/debug/coroner/main.go b/tools/debug/coroner/main.go index 669e560de7..1ea04537d4 100644 --- a/tools/debug/coroner/main.go +++ b/tools/debug/coroner/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/debug/determaccount/main.go b/tools/debug/determaccount/main.go index 84dfbf73fd..2c3c16d65e 100644 --- a/tools/debug/determaccount/main.go +++ b/tools/debug/determaccount/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/debug/dumpblocks/main.go b/tools/debug/dumpblocks/main.go index 09698bdb05..461e7949cf 100644 --- a/tools/debug/dumpblocks/main.go +++ b/tools/debug/dumpblocks/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/debug/genconsensusconfig/main.go b/tools/debug/genconsensusconfig/main.go index 74f6eda2f6..edef949d3d 100644 --- a/tools/debug/genconsensusconfig/main.go +++ b/tools/debug/genconsensusconfig/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/debug/logfilter/main.go b/tools/debug/logfilter/main.go index 1c7545a48f..11878ea470 100644 --- a/tools/debug/logfilter/main.go +++ b/tools/debug/logfilter/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/debug/logfilter/main_test.go b/tools/debug/logfilter/main_test.go index 45ab8605fc..6b6902d62d 100644 --- a/tools/debug/logfilter/main_test.go +++ b/tools/debug/logfilter/main_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/misc/convertAddress.go b/tools/misc/convertAddress.go index c8c4e99970..801c26106b 100644 --- a/tools/misc/convertAddress.go +++ b/tools/misc/convertAddress.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/bootstrap.go b/tools/network/bootstrap.go index cd15ef2ab8..b3bd79b23c 100644 --- a/tools/network/bootstrap.go +++ b/tools/network/bootstrap.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/cloudflare/cloudflare.go b/tools/network/cloudflare/cloudflare.go index 714fb9635b..84f62f6533 100644 --- a/tools/network/cloudflare/cloudflare.go +++ b/tools/network/cloudflare/cloudflare.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/cloudflare/createRecord.go b/tools/network/cloudflare/createRecord.go index c68747f5bd..a41dcbccc3 100644 --- a/tools/network/cloudflare/createRecord.go +++ b/tools/network/cloudflare/createRecord.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/cloudflare/deleteRecord.go b/tools/network/cloudflare/deleteRecord.go index f0bf90ce5c..864a223dfc 100644 --- a/tools/network/cloudflare/deleteRecord.go +++ b/tools/network/cloudflare/deleteRecord.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/cloudflare/helpers.go b/tools/network/cloudflare/helpers.go index e4b995f67e..e330d93309 100644 --- a/tools/network/cloudflare/helpers.go +++ b/tools/network/cloudflare/helpers.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/cloudflare/listRecords.go b/tools/network/cloudflare/listRecords.go index 1617b61188..34efc71d53 100644 --- a/tools/network/cloudflare/listRecords.go +++ b/tools/network/cloudflare/listRecords.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/cloudflare/updateRecord.go b/tools/network/cloudflare/updateRecord.go index c4be7c362e..7add2d05be 100644 --- a/tools/network/cloudflare/updateRecord.go +++ b/tools/network/cloudflare/updateRecord.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/cloudflare/zones.go b/tools/network/cloudflare/zones.go index f5aa4b9ac2..c9a54a6dfd 100644 --- a/tools/network/cloudflare/zones.go +++ b/tools/network/cloudflare/zones.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/dnssec/anchor.go b/tools/network/dnssec/anchor.go index 8f7066904a..18d259aca3 100644 --- a/tools/network/dnssec/anchor.go +++ b/tools/network/dnssec/anchor.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/dnssec/anchor_test.go b/tools/network/dnssec/anchor_test.go index cea0d2adbc..dbd7396e0f 100644 --- a/tools/network/dnssec/anchor_test.go +++ b/tools/network/dnssec/anchor_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/dnssec/client.go b/tools/network/dnssec/client.go index 5ebfc84782..79cd79a9cf 100644 --- a/tools/network/dnssec/client.go +++ b/tools/network/dnssec/client.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/dnssec/client_test.go b/tools/network/dnssec/client_test.go index 3bcc8f79d5..7c2eede7db 100644 --- a/tools/network/dnssec/client_test.go +++ b/tools/network/dnssec/client_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/dnssec/config.go b/tools/network/dnssec/config.go index a9c38bff9a..a5c9bbbfe4 100644 --- a/tools/network/dnssec/config.go +++ b/tools/network/dnssec/config.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/dnssec/config_test.go b/tools/network/dnssec/config_test.go index 6115c7eef8..8ad1ae511b 100644 --- a/tools/network/dnssec/config_test.go +++ b/tools/network/dnssec/config_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/dnssec/config_unix.go b/tools/network/dnssec/config_unix.go index 4a8c574de1..74b3cb0f9d 100644 --- a/tools/network/dnssec/config_unix.go +++ b/tools/network/dnssec/config_unix.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/dnssec/config_unix_test.go b/tools/network/dnssec/config_unix_test.go index c757b5369f..07a6a584bf 100644 --- a/tools/network/dnssec/config_unix_test.go +++ b/tools/network/dnssec/config_unix_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/dnssec/config_windows.go b/tools/network/dnssec/config_windows.go index 41d6950070..8d291be673 100644 --- a/tools/network/dnssec/config_windows.go +++ b/tools/network/dnssec/config_windows.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/dnssec/dialer.go b/tools/network/dnssec/dialer.go index 8bb9f869b4..08144ae5bc 100644 --- a/tools/network/dnssec/dialer.go +++ b/tools/network/dnssec/dialer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/dnssec/dnssec_test.go b/tools/network/dnssec/dnssec_test.go index 4964990487..86c7239182 100644 --- a/tools/network/dnssec/dnssec_test.go +++ b/tools/network/dnssec/dnssec_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/dnssec/relay-check/main.go b/tools/network/dnssec/relay-check/main.go index 4b2dcad295..f82211e096 100644 --- a/tools/network/dnssec/relay-check/main.go +++ b/tools/network/dnssec/relay-check/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/dnssec/resolver.go b/tools/network/dnssec/resolver.go index 5aabcfd285..2b0b873c31 100644 --- a/tools/network/dnssec/resolver.go +++ b/tools/network/dnssec/resolver.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/dnssec/sort.go b/tools/network/dnssec/sort.go index 0bdf17f88f..2c8de2653a 100644 --- a/tools/network/dnssec/sort.go +++ b/tools/network/dnssec/sort.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/dnssec/sort_test.go b/tools/network/dnssec/sort_test.go index 3440e23030..ab1be21fbc 100644 --- a/tools/network/dnssec/sort_test.go +++ b/tools/network/dnssec/sort_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/dnssec/testHarness.go b/tools/network/dnssec/testHarness.go index 5f5310a8e3..0f4c7f4161 100644 --- a/tools/network/dnssec/testHarness.go +++ b/tools/network/dnssec/testHarness.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/dnssec/trustchain.go b/tools/network/dnssec/trustchain.go index 3cde9bea70..15f69aea1d 100644 --- a/tools/network/dnssec/trustchain.go +++ b/tools/network/dnssec/trustchain.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/dnssec/trustedchain_test.go b/tools/network/dnssec/trustedchain_test.go index 2f671c007f..6310e22ab4 100644 --- a/tools/network/dnssec/trustedchain_test.go +++ b/tools/network/dnssec/trustedchain_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/dnssec/trustedzone.go b/tools/network/dnssec/trustedzone.go index b3f20dc447..a71385503e 100644 --- a/tools/network/dnssec/trustedzone.go +++ b/tools/network/dnssec/trustedzone.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/dnssec/trustedzone_test.go b/tools/network/dnssec/trustedzone_test.go index 6d3bbaa043..877a687415 100644 --- a/tools/network/dnssec/trustedzone_test.go +++ b/tools/network/dnssec/trustedzone_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/dnssec/util.go b/tools/network/dnssec/util.go index 1622f490f1..48f5c9448c 100644 --- a/tools/network/dnssec/util.go +++ b/tools/network/dnssec/util.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/dnssec/util_test.go b/tools/network/dnssec/util_test.go index d467d52c80..2a77ad1c09 100644 --- a/tools/network/dnssec/util_test.go +++ b/tools/network/dnssec/util_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/externalIP.go b/tools/network/externalIP.go index a726d03fc0..121c43df32 100644 --- a/tools/network/externalIP.go +++ b/tools/network/externalIP.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/resolveController.go b/tools/network/resolveController.go index fd12d0dbf6..a36914f21b 100644 --- a/tools/network/resolveController.go +++ b/tools/network/resolveController.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/resolveController_test.go b/tools/network/resolveController_test.go index 8a843c9021..db8a80573a 100644 --- a/tools/network/resolveController_test.go +++ b/tools/network/resolveController_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/resolver.go b/tools/network/resolver.go index 467bb1e12f..b823b17b20 100644 --- a/tools/network/resolver.go +++ b/tools/network/resolver.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/resolver_test.go b/tools/network/resolver_test.go index a00f07ea34..6efe1bea45 100644 --- a/tools/network/resolver_test.go +++ b/tools/network/resolver_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -22,14 +22,16 @@ import ( "testing" "time" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) -func TestResolver(t *testing.T) { +func TestResolverWithDefaultDNSResolution(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() - // start with a resolver that has no specific DNS address defined. + // configure a resolver that has no specific DNS address defined. // we want to make sure that it will go to the default DNS server ( 8.8.8.8 ) resolver := Resolver{} cname, addrs, err := resolver.LookupSRV(context.Background(), "telemetry", "tls", "devnet.algodev.network") @@ -37,18 +39,34 @@ func TestResolver(t *testing.T) { require.Equal(t, "_telemetry._tls.devnet.algodev.network.", cname) require.True(t, len(addrs) == 1) require.Equal(t, defaultDNSAddress, resolver.EffectiveResolverDNS()) +} - // specify a specific resolver to work with ( cloudflare DNS server is 1.1.1.1 ) - cloudFlareIPAddr, _ := net.ResolveIPAddr("ip", "1.1.1.1") +func TestResolverWithCloudflareDNSResolution(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + resolver := Resolver{} + + // The test previously specified Cloudflare's primary DNS server (1.1.1.1). + // However, CircleCI began blocking requests to 1.1.1.1. In order to + // preserve the test's spirit, it now uses Cloudflare's secondary DNS + // server (1.0.0.1). + cloudflareIPAddr, _ := net.ResolveIPAddr("ip", "1.0.0.1") resolver = Resolver{ - dnsAddress: *cloudFlareIPAddr, + dnsAddress: *cloudflareIPAddr, } - cname, addrs, err = resolver.LookupSRV(context.Background(), "telemetry", "tls", "devnet.algodev.network") + cname, addrs, err := resolver.LookupSRV(context.Background(), "telemetry", "tls", "devnet.algodev.network") require.NoError(t, err) require.Equal(t, "_telemetry._tls.devnet.algodev.network.", cname) require.True(t, len(addrs) == 1) - require.Equal(t, "1.1.1.1", resolver.EffectiveResolverDNS()) + require.Equal(t, "1.0.0.1", resolver.EffectiveResolverDNS()) +} +func TestResolverWithInvalidDNSResolution(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + resolver := Resolver{} // specify an invalid dns resolver ip address and examine the fail case. dummyIPAddr, _ := net.ResolveIPAddr("ip", "255.255.128.1") resolver = Resolver{ @@ -56,7 +74,7 @@ func TestResolver(t *testing.T) { } timingOutContext, timingOutContextFunc := context.WithTimeout(context.Background(), time.Duration(100)*time.Millisecond) defer timingOutContextFunc() - cname, addrs, err = resolver.LookupSRV(timingOutContext, "telemetry", "tls", "devnet.algodev.network") + cname, addrs, err := resolver.LookupSRV(timingOutContext, "telemetry", "tls", "devnet.algodev.network") require.Error(t, err) require.Equal(t, "", cname) require.True(t, len(addrs) == 0) diff --git a/tools/network/telemetryURIUpdateService.go b/tools/network/telemetryURIUpdateService.go index ab0cc8110b..becae7c743 100644 --- a/tools/network/telemetryURIUpdateService.go +++ b/tools/network/telemetryURIUpdateService.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/telemetryURIUpdateService_test.go b/tools/network/telemetryURIUpdateService_test.go index afe8bf242d..1014d28df3 100644 --- a/tools/network/telemetryURIUpdateService_test.go +++ b/tools/network/telemetryURIUpdateService_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/teal/algotmpl/extract.go b/tools/teal/algotmpl/extract.go index b8c045366c..f542d6bce8 100644 --- a/tools/teal/algotmpl/extract.go +++ b/tools/teal/algotmpl/extract.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/teal/algotmpl/main.go b/tools/teal/algotmpl/main.go index 7568dda92f..824ab7ee9b 100644 --- a/tools/teal/algotmpl/main.go +++ b/tools/teal/algotmpl/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/teal/dkey/dsign/main.go b/tools/teal/dkey/dsign/main.go index 6f380d8a0b..5a4e2adfcd 100644 --- a/tools/teal/dkey/dsign/main.go +++ b/tools/teal/dkey/dsign/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/teal/tealcut/main.go b/tools/teal/tealcut/main.go index 7615cdc979..0740929db5 100644 --- a/tools/teal/tealcut/main.go +++ b/tools/teal/tealcut/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/cmdUtils.go b/util/cmdUtils.go index 29641e6284..1382be178e 100644 --- a/util/cmdUtils.go +++ b/util/cmdUtils.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/codecs/json.go b/util/codecs/json.go index 2d2c21134e..e283ef0624 100644 --- a/util/codecs/json.go +++ b/util/codecs/json.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/codecs/json_test.go b/util/codecs/json_test.go index de35acf3cf..1f56531971 100644 --- a/util/codecs/json_test.go +++ b/util/codecs/json_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/condvar/timedwait.go b/util/condvar/timedwait.go index a4605d0998..e14f2b33b7 100644 --- a/util/condvar/timedwait.go +++ b/util/condvar/timedwait.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/condvar/timedwait_test.go b/util/condvar/timedwait_test.go index 20b8f70a4d..fa7deef219 100644 --- a/util/condvar/timedwait_test.go +++ b/util/condvar/timedwait_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/db/dbpair.go b/util/db/dbpair.go index 95be092633..0fa382063e 100644 --- a/util/db/dbpair.go +++ b/util/db/dbpair.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/db/dbutil.go b/util/db/dbutil.go index 34a32320a1..7e62dfee61 100644 --- a/util/db/dbutil.go +++ b/util/db/dbutil.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/db/dbutil_test.go b/util/db/dbutil_test.go index 412f04a6c2..c35beccb23 100644 --- a/util/db/dbutil_test.go +++ b/util/db/dbutil_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/db/fullfsync_darwin.go b/util/db/fullfsync_darwin.go index 8a529305ae..3261e7864a 100644 --- a/util/db/fullfsync_darwin.go +++ b/util/db/fullfsync_darwin.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/db/initialize.go b/util/db/initialize.go index 36bf83770d..fc11bc1aa4 100644 --- a/util/db/initialize.go +++ b/util/db/initialize.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/db/initialize_test.go b/util/db/initialize_test.go index d7b879d6ae..c0c8774b11 100644 --- a/util/db/initialize_test.go +++ b/util/db/initialize_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/db/interfaces.go b/util/db/interfaces.go index 5d4abed852..938edc1031 100644 --- a/util/db/interfaces.go +++ b/util/db/interfaces.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/db/perf_test.go b/util/db/perf_test.go index 1e0a8bccd6..5740ce2d47 100644 --- a/util/db/perf_test.go +++ b/util/db/perf_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/db/versioning.go b/util/db/versioning.go index 28f6b1dbd1..6104fe6b9c 100644 --- a/util/db/versioning.go +++ b/util/db/versioning.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/db/versioning_test.go b/util/db/versioning_test.go index fd2fecc23a..74f048e30c 100644 --- a/util/db/versioning_test.go +++ b/util/db/versioning_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/execpool/backlog.go b/util/execpool/backlog.go index 966aafe97f..9e95ebe653 100644 --- a/util/execpool/backlog.go +++ b/util/execpool/backlog.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -43,6 +43,7 @@ type backlogItemTask struct { type BacklogPool interface { ExecutionPool EnqueueBacklog(enqueueCtx context.Context, t ExecFunc, arg interface{}, out chan interface{}) error + BufferSize() (length, capacity int) } // MakeBacklog creates a backlog @@ -94,6 +95,11 @@ func (b *backlog) Enqueue(enqueueCtx context.Context, t ExecFunc, arg interface{ } } +// BufferSize returns the length and the capacity of the buffer +func (b *backlog) BufferSize() (length, capacity int) { + return len(b.buffer), cap(b.buffer) +} + // Enqueue enqueues a single task into the backlog func (b *backlog) EnqueueBacklog(enqueueCtx context.Context, t ExecFunc, arg interface{}, out chan interface{}) error { select { diff --git a/util/execpool/pool.go b/util/execpool/pool.go index 1b37dd95fc..50acbf9a5a 100644 --- a/util/execpool/pool.go +++ b/util/execpool/pool.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/io.go b/util/io.go index 43068f39c9..1ab09b3b68 100644 --- a/util/io.go +++ b/util/io.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/io_test.go b/util/io_test.go index 353616c64e..5919051135 100644 --- a/util/io_test.go +++ b/util/io_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/metrics/counter.go b/util/metrics/counter.go index 5c195b5f8a..bb8355cacc 100644 --- a/util/metrics/counter.go +++ b/util/metrics/counter.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -66,13 +66,12 @@ func (counter *Counter) Inc(labels map[string]string) { if len(labels) == 0 { counter.fastAddUint64(1) } else { - counter.Add(1.0, labels) + counter.addLabels(1.0, labels) } } -// Add increases counter by x -// For adding an integer, see AddUint64(x) -func (counter *Counter) Add(x float64, labels map[string]string) { +// addLabels increases counter by x +func (counter *Counter) addLabels(x uint64, labels map[string]string) { counter.Lock() defer counter.Unlock() @@ -95,13 +94,12 @@ func (counter *Counter) Add(x float64, labels map[string]string) { } // AddUint64 increases counter by x -// If labels is nil this is much faster than Add() -// Calls through to Add() if labels is not nil. +// If labels is nil this is much faster than if labels is not nil. func (counter *Counter) AddUint64(x uint64, labels map[string]string) { if len(labels) == 0 { counter.fastAddUint64(x) } else { - counter.Add(float64(x), labels) + counter.addLabels(x, labels) } } @@ -122,7 +120,7 @@ func (counter *Counter) fastAddUint64(x uint64) { // is the first Add. Create a dummy // counterValue for the no-labels value. // Dummy counterValue simplifies display in WriteMetric. - counter.Add(0, nil) + counter.addLabels(0, nil) } } @@ -191,9 +189,9 @@ func (counter *Counter) WriteMetric(buf *strings.Builder, parentLabels string) { buf.WriteString("} ") value := l.counter if len(l.labels) == 0 { - value += float64(atomic.LoadUint64(&counter.intValue)) + value += atomic.LoadUint64(&counter.intValue) } - buf.WriteString(strconv.FormatFloat(value, 'f', -1, 32)) + buf.WriteString(strconv.FormatUint(value, 10)) buf.WriteString("\n") } } @@ -210,12 +208,12 @@ func (counter *Counter) AddMetric(values map[string]float64) { for _, l := range counter.values { sum := l.counter if len(l.labels) == 0 { - sum += float64(atomic.LoadUint64(&counter.intValue)) + sum += atomic.LoadUint64(&counter.intValue) } var suffix string if len(l.formattedLabels) > 0 { suffix = ":" + l.formattedLabels } - values[sanitizeTelemetryName(counter.name+suffix)] = sum + values[sanitizeTelemetryName(counter.name+suffix)] = float64(sum) } } diff --git a/util/metrics/counterCommon.go b/util/metrics/counterCommon.go index 26ef7f9245..2a810ace69 100644 --- a/util/metrics/counterCommon.go +++ b/util/metrics/counterCommon.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -35,7 +35,7 @@ type Counter struct { } type counterValues struct { - counter float64 + counter uint64 labels map[string]string formattedLabels string } diff --git a/util/metrics/counter_test.go b/util/metrics/counter_test.go index 72e1d3b1ad..fe7d553e4c 100644 --- a/util/metrics/counter_test.go +++ b/util/metrics/counter_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -144,8 +144,8 @@ func TestMetricCounterMixed(t *testing.T) { counter := MakeCounter(MetricName{Name: "metric_test_name1", Description: "this is the metric test for counter object"}) - counter.Add(5.25, nil) - counter.Add(8.25, map[string]string{}) + counter.AddUint64(5, nil) + counter.AddUint64(8, map[string]string{}) for i := 0; i < 20; i++ { counter.Inc(nil) // wait half-a cycle @@ -169,7 +169,7 @@ func TestMetricCounterMixed(t *testing.T) { for k, v := range test.metrics { // we have increased each one of the labels exactly 4 times. See that the counter was counting correctly. // ( counters starts at zero ) - require.Equal(t, "35.5", v, fmt.Sprintf("The metric '%s' reached value '%s'", k, v)) + require.Equal(t, "35", v, fmt.Sprintf("The metric '%s' reached value '%s'", k, v)) } } @@ -188,13 +188,13 @@ testname{host="myhost"} 0 ` require.Equal(t, expected, sbOut.String()) - c.Add(2.3, nil) + c.AddUint64(2, nil) // ensure non-zero counters are logged sbOut = strings.Builder{} c.WriteMetric(&sbOut, `host="myhost"`) expected = `# HELP testname testhelp # TYPE testname counter -testname{host="myhost"} 2.3 +testname{host="myhost"} 2 ` require.Equal(t, expected, sbOut.String()) } diff --git a/util/metrics/gauge.go b/util/metrics/gauge.go index f37896775a..ce203d47c0 100644 --- a/util/metrics/gauge.go +++ b/util/metrics/gauge.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -19,16 +19,14 @@ package metrics import ( "strconv" "strings" - - "github.com/algorand/go-deadlock" + "sync/atomic" ) // Gauge represent a single gauge variable. type Gauge struct { - deadlock.Mutex + value uint64 name string description string - value float64 } // MakeGauge create a new gauge with the provided name and description. @@ -60,24 +58,17 @@ func (gauge *Gauge) Deregister(reg *Registry) { } // Add increases gauge by x -func (gauge *Gauge) Add(x float64) { - gauge.Lock() - defer gauge.Unlock() - gauge.value += x +func (gauge *Gauge) Add(x uint64) { + atomic.AddUint64(&gauge.value, x) } // Set sets gauge to x -func (gauge *Gauge) Set(x float64) { - gauge.Lock() - defer gauge.Unlock() - gauge.value = x +func (gauge *Gauge) Set(x uint64) { + atomic.StoreUint64(&gauge.value, x) } // WriteMetric writes the metric into the output stream func (gauge *Gauge) WriteMetric(buf *strings.Builder, parentLabels string) { - gauge.Lock() - defer gauge.Unlock() - buf.WriteString("# HELP ") buf.WriteString(gauge.name) buf.WriteString(" ") @@ -91,14 +82,14 @@ func (gauge *Gauge) WriteMetric(buf *strings.Builder, parentLabels string) { buf.WriteString(parentLabels) } buf.WriteString("} ") - buf.WriteString(strconv.FormatFloat(gauge.value, 'f', -1, 32)) + value := atomic.LoadUint64(&gauge.value) + buf.WriteString(strconv.FormatUint(value, 10)) buf.WriteString("\n") } // AddMetric adds the metric into the map func (gauge *Gauge) AddMetric(values map[string]float64) { - gauge.Lock() - defer gauge.Unlock() + value := atomic.LoadUint64(&gauge.value) - values[sanitizeTelemetryName(gauge.name)] = gauge.value + values[sanitizeTelemetryName(gauge.name)] = float64(value) } diff --git a/util/metrics/gauge_test.go b/util/metrics/gauge_test.go index 41a9edb927..afa0bbd593 100644 --- a/util/metrics/gauge_test.go +++ b/util/metrics/gauge_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -52,8 +52,8 @@ func TestMetricGauge(t *testing.T) { gauges[i] = MakeGauge(MetricName{Name: fmt.Sprintf("gauge_%d", i), Description: "this is the metric test for gauge object"}) } for i := 0; i < 9; i++ { - gauges[i%3].Set(float64(i * 100)) - gauges[i%3].Add(float64(i)) + gauges[i%3].Set(uint64(i * 100)) + gauges[i%3].Add(uint64(i)) // wait half-a cycle time.Sleep(test.sampleRate / 2) } diff --git a/util/metrics/metrics.go b/util/metrics/metrics.go index 06a0431ecb..11973e85b7 100644 --- a/util/metrics/metrics.go +++ b/util/metrics/metrics.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -95,6 +95,8 @@ var ( TransactionMessagesAlreadyCommitted = MetricName{Name: "algod_transaction_messages_err_or_committed", Description: "Number of duplicate or error transaction messages after TX handler backlog"} // TransactionMessagesTxGroupInvalidFee "Number of transaction messages with invalid txgroup fee" TransactionMessagesTxGroupInvalidFee = MetricName{Name: "algod_transaction_messages_txgroup_invalid_fee", Description: "Number of transaction messages with invalid txgroup fee"} + // TransactionMessagesTxnDroppedCongestionManagement "Number of transaction messages dropped because the tx backlog is under congestion management" + TransactionMessagesTxnDroppedCongestionManagement = MetricName{Name: "algod_transaction_messages_txn_dropped_congestion_ctrl", Description: "Number of transaction messages dropped because the tx backlog is under congestion management"} // TransactionMessagesTxnNotWellFormed "Number of transaction messages not well formed" TransactionMessagesTxnNotWellFormed = MetricName{Name: "algod_transaction_messages_txn_notwell_formed", Description: "Number of transaction messages not well formed"} // TransactionMessagesTxnSigNotWellFormed "Number of transaction messages with bad formed signature" diff --git a/util/metrics/metrics_test.go b/util/metrics/metrics_test.go index 2e2828b1fb..84cd4292fb 100644 --- a/util/metrics/metrics_test.go +++ b/util/metrics/metrics_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/metrics/registry.go b/util/metrics/registry.go index 53ada420a3..2f727aaabd 100644 --- a/util/metrics/registry.go +++ b/util/metrics/registry.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/metrics/registryCommon.go b/util/metrics/registryCommon.go index 8a0f53464f..848ddc8369 100644 --- a/util/metrics/registryCommon.go +++ b/util/metrics/registryCommon.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/metrics/registry_test.go b/util/metrics/registry_test.go index 2256993f20..17328f1cba 100644 --- a/util/metrics/registry_test.go +++ b/util/metrics/registry_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -29,17 +29,17 @@ func TestWriteAdd(t *testing.T) { // Test AddMetrics and WriteMetrics with a counter counter := MakeCounter(MetricName{Name: "gauge-name", Description: "gauge description"}) - counter.Add(12.34, nil) + counter.AddUint64(12, nil) labelCounter := MakeCounter(MetricName{Name: "label-counter", Description: "counter with labels"}) - labelCounter.Add(5, map[string]string{"label": "a label value"}) + labelCounter.AddUint64(5, map[string]string{"label": "a label value"}) results := make(map[string]float64) DefaultRegistry().AddMetrics(results) require.Equal(t, 2, len(results), "results", results) require.Contains(t, results, "gauge-name") - require.InDelta(t, 12.34, results["gauge-name"], 0.01) + require.InDelta(t, 12, results["gauge-name"], 0.01) require.Contains(t, results, "label-counter_label__a_label_value_") require.InDelta(t, 5, results["label-counter_label__a_label_value_"], 0.01) @@ -50,7 +50,7 @@ func TestWriteAdd(t *testing.T) { DefaultRegistry().AddMetrics(results) require.Contains(t, results, "gauge-name") - require.InDelta(t, 12.34, results["gauge-name"], 0.01) + require.InDelta(t, 12, results["gauge-name"], 0.01) // not included in string builder bufAfter := strings.Builder{} diff --git a/util/metrics/reporter.go b/util/metrics/reporter.go index efecf6f659..19aef0c369 100644 --- a/util/metrics/reporter.go +++ b/util/metrics/reporter.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/metrics/reporter_test.go b/util/metrics/reporter_test.go index 7339c708db..4c522a9b22 100755 --- a/util/metrics/reporter_test.go +++ b/util/metrics/reporter_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/metrics/runtime.go b/util/metrics/runtime.go index 3f89ea761e..29d33e7814 100644 --- a/util/metrics/runtime.go +++ b/util/metrics/runtime.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/metrics/runtime_test.go b/util/metrics/runtime_test.go index 1032484466..32908670d6 100644 --- a/util/metrics/runtime_test.go +++ b/util/metrics/runtime_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/metrics/service.go b/util/metrics/service.go index e37f5c7341..5b1e395271 100644 --- a/util/metrics/service.go +++ b/util/metrics/service.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/metrics/serviceCommon.go b/util/metrics/serviceCommon.go index e0b26c8207..947e9c4404 100644 --- a/util/metrics/serviceCommon.go +++ b/util/metrics/serviceCommon.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/metrics/tagcounter.go b/util/metrics/tagcounter.go index 80689e7a8b..1daf30ba13 100644 --- a/util/metrics/tagcounter.go +++ b/util/metrics/tagcounter.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/metrics/tagcounter_test.go b/util/metrics/tagcounter_test.go index 9e8a507176..8c5991e195 100644 --- a/util/metrics/tagcounter_test.go +++ b/util/metrics/tagcounter_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/process.go b/util/process.go index 4ea34796a5..6a6d17d4ad 100644 --- a/util/process.go +++ b/util/process.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/process_common.go b/util/process_common.go index b2c1bc7081..03af826b0c 100644 --- a/util/process_common.go +++ b/util/process_common.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/process_windows.go b/util/process_windows.go index 4cad60df7e..7fe14d9742 100644 --- a/util/process_windows.go +++ b/util/process_windows.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/rateLimit.go b/util/rateLimit.go new file mode 100644 index 0000000000..c4e85c71e7 --- /dev/null +++ b/util/rateLimit.go @@ -0,0 +1,556 @@ +// Copyright (C) 2019-2023 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package util + +import ( + "context" + "errors" + "fmt" + "math" + "math/rand" + "sort" + "sync" + "time" + + "github.com/algorand/go-algorand/util/metrics" + "github.com/algorand/go-deadlock" +) + +var errConManDropped = errors.New("congestionManager prevented client from consuming capacity") +var errFailedConsume = errors.New("could not consume capacity from capacityQueue") +var errERLReservationExists = errors.New("client already has a reservation") +var errCapacityReturn = errors.New("could not replace capacity to channel") + +// ElasticRateLimiter holds and distributes capacity through capacityQueues +// Capacity consumers are given an error if there is no capacity available for them, +// and a "capacityGuard" structure they can use to return the capacity when finished +type ElasticRateLimiter struct { + MaxCapacity int + CapacityPerReservation int + sharedCapacity capacityQueue + capacityByClient map[ErlClient]capacityQueue + clientLock deadlock.RWMutex + // CongestionManager and enable flag + cm CongestionManager + enableCM bool + congestionControlCounter *metrics.Counter +} + +// ErlClient clients must support OnClose for reservation closing +type ErlClient interface { + OnClose(func()) +} + +// capacity is an empty structure used for loading and draining queues +type capacity struct { +} + +// Capacity Queue wraps and maintains a channel of opaque capacity structs +type capacityQueue chan capacity + +// ErlCapacityGuard is the structure returned to clients so they can release the capacity when needed +// they also inform the congestion manager of events +type ErlCapacityGuard struct { + cq capacityQueue + cm CongestionManager +} + +// Release will put capacity back into the queue attached to this capacity guard +func (cg *ErlCapacityGuard) Release() error { + if cg.cq == nil { + return nil + } + select { + case cg.cq <- capacity{}: + return nil + default: + return errCapacityReturn + } +} + +// Served will notify the CongestionManager that this resource has been served, informing the Service Rate +func (cg *ErlCapacityGuard) Served() { + if cg.cm != nil { + cg.cm.Served(time.Now()) + } +} + +func (q capacityQueue) blockingRelease() { + q <- capacity{} +} + +func (q capacityQueue) blockingConsume() { + <-q +} + +func (q capacityQueue) consume(cm CongestionManager) (ErlCapacityGuard, error) { + select { + case <-q: + return ErlCapacityGuard{ + cq: q, + cm: cm, + }, nil + default: + return ErlCapacityGuard{}, errFailedConsume + } +} + +// NewElasticRateLimiter creates an ElasticRateLimiter and initializes maps +// maxCapacity: the total (absolute maximum) number of capacity units vended by this ERL at a given time +// reservedCapacity: the number of capacity units to be reserved per client +// cmWindow: the window duration of data collection for congestion management, passed to the congestion manager +// conmanCount: the metric to increment when the congestion manager proposes dropping a request +func NewElasticRateLimiter( + maxCapacity int, + reservedCapacity int, + cmWindow time.Duration, + conmanCount *metrics.Counter) *ElasticRateLimiter { + ret := ElasticRateLimiter{ + MaxCapacity: maxCapacity, + CapacityPerReservation: reservedCapacity, + capacityByClient: map[ErlClient]capacityQueue{}, + sharedCapacity: capacityQueue(make(chan capacity, maxCapacity)), + congestionControlCounter: conmanCount, + } + congestionManager := NewREDCongestionManager( + cmWindow, + maxCapacity) + ret.cm = congestionManager + // fill the sharedCapacity + for i := 0; i < maxCapacity; i++ { + ret.sharedCapacity.blockingRelease() + } + return &ret +} + +// Start will start any underlying component of the ElasticRateLimiter +func (erl *ElasticRateLimiter) Start() { + if erl.cm != nil { + erl.cm.Start() + } +} + +// Stop will stop any underlying component of the ElasticRateLimiter +func (erl *ElasticRateLimiter) Stop() { + if erl.cm != nil { + erl.cm.Stop() + } +} + +// EnableCongestionControl turns on the flag that the ERL uses to check with its CongestionManager +func (erl *ElasticRateLimiter) EnableCongestionControl() { + erl.clientLock.Lock() + defer erl.clientLock.Unlock() + erl.enableCM = true +} + +// DisableCongestionControl turns off the flag that the ERL uses to check with its CongestionManager +func (erl *ElasticRateLimiter) DisableCongestionControl() { + erl.clientLock.Lock() + defer erl.clientLock.Unlock() + erl.enableCM = false +} + +// ConsumeCapacity will dispense one capacity from either the resource's reservedCapacity, +// and will return a guard who can return capacity when the client is ready +// Returns an error if the capacity could not be vended, which could be: +// - there is not sufficient free capacity to assign a reserved capacity block +// - there is no reserved or shared capacity available for the client +func (erl *ElasticRateLimiter) ConsumeCapacity(c ErlClient) (*ErlCapacityGuard, error) { + var q capacityQueue + var err error + var exists bool + var enableCM bool + // get the client's queue + erl.clientLock.RLock() + q, exists = erl.capacityByClient[c] + enableCM = erl.enableCM + erl.clientLock.RUnlock() + + // Step 0: Check for, and create a capacity reservation if needed + if !exists { + q, err = erl.openReservation(c) + if err != nil { + return nil, err + } + // if the client has been given a new reservation, make sure it cleans up OnClose + c.OnClose(func() { erl.closeReservation(c) }) + + // if this reservation is newly created, directly (blocking) take a capacity + q.blockingConsume() + return &ErlCapacityGuard{cq: q, cm: erl.cm}, nil + } + + // Step 1: Attempt consumption from the reserved queue + cg, err := q.consume(erl.cm) + if err == nil { + if erl.cm != nil { + erl.cm.Consumed(c, time.Now()) // notify the congestion manager that this client consumed from this queue + } + return &cg, nil + } + // Step 2: Potentially gate shared queue access if the congestion manager disallows it + if erl.cm != nil && + enableCM && + erl.cm.ShouldDrop(c) { + if erl.congestionControlCounter != nil { + erl.congestionControlCounter.Inc(nil) + } + return nil, errConManDropped + } + // Step 3: Attempt consumption from the shared queue + cg, err = erl.sharedCapacity.consume(erl.cm) + if err != nil { + return nil, err + } + if erl.cm != nil { + erl.cm.Consumed(c, time.Now()) // notify the congestion manager that this client consumed from this queue + } + return &cg, nil +} + +// openReservation creates an entry in the ElasticRateLimiter's reservedCapacity map, +// and optimistically transfers capacity from the sharedCapacity to the reservedCapacity +func (erl *ElasticRateLimiter) openReservation(c ErlClient) (capacityQueue, error) { + erl.clientLock.Lock() + defer erl.clientLock.Unlock() + if _, exists := erl.capacityByClient[c]; exists { + return capacityQueue(nil), errERLReservationExists + } + // guard against overprovisioning, if there is less than a reservedCapacity amount left + remaining := erl.MaxCapacity - (erl.CapacityPerReservation * len(erl.capacityByClient)) + if erl.CapacityPerReservation > remaining { + return capacityQueue(nil), fmt.Errorf("not enough capacity to reserve for client: %d remaining, %d requested", remaining, erl.CapacityPerReservation) + } + // make capacity for the provided client + q := capacityQueue(make(chan capacity, erl.CapacityPerReservation)) + erl.capacityByClient[c] = q + // create a thread to drain the capacity from sharedCapacity in a blocking way + // and move it to the reservation, also in a blocking way + go func() { + for i := 0; i < erl.CapacityPerReservation; i++ { + erl.sharedCapacity.blockingConsume() + q.blockingRelease() + } + }() + return q, nil +} + +// closeReservation will remove the client mapping to capacity channel, +// and will kick off a routine to drain the capacity and replace it to the shared capacity +func (erl *ElasticRateLimiter) closeReservation(c ErlClient) { + erl.clientLock.Lock() + defer erl.clientLock.Unlock() + q, exists := erl.capacityByClient[c] + // guard clauses, and preventing the ElasticRateLimiter from draining its own sharedCapacity + if !exists || q == erl.sharedCapacity { + return + } + delete(erl.capacityByClient, c) + // start a routine to consume capacity from the closed reservation, and return it to the sharedCapacity + go func() { + for i := 0; i < erl.CapacityPerReservation; i++ { + q.blockingConsume() + erl.sharedCapacity.blockingRelease() + } + }() +} + +// CongestionManager is an interface for tracking events which happen to capacityQueues +type CongestionManager interface { + Start() + Stop() + Consumed(c ErlClient, t time.Time) + Served(t time.Time) + ShouldDrop(c ErlClient) bool +} + +type event struct { + c ErlClient + t time.Time +} + +type shouldDropQuery struct { + c ErlClient + ret chan bool +} + +// "Random Early Detection" congestion manager, +// will propose to drop messages proportional to the caller's request rate vs Average Service Rate +type redCongestionManager struct { + runLock *deadlock.Mutex + running bool + window time.Duration + consumed chan event + served chan event + shouldDropQueries chan shouldDropQuery + targetRate float64 + targetRateRefreshTicks int + // exp is applied as an exponential factor in shouldDrop. 1 would be linearly proportional, higher values punish noisy neighbors more + exp float64 + // consumed is the only value tracked by-queue. The others are calculated in-total + // TODO: If we desire later, we can add mappings onto release/done for more insight + consumedByClient map[ErlClient]*[]time.Time + serves []time.Time + // synchronization for unit tests + ctx context.Context + ctxCancel context.CancelFunc + wg sync.WaitGroup +} + +// NewREDCongestionManager creates a Congestion Manager which will watches capacityGuard activity, +// and regularly calculates a Target Service Rate, and can give "Should Drop" suggestions +func NewREDCongestionManager(d time.Duration, bsize int) *redCongestionManager { + ret := redCongestionManager{ + runLock: &deadlock.Mutex{}, + window: d, + consumed: make(chan event, bsize), + served: make(chan event, bsize), + shouldDropQueries: make(chan shouldDropQuery, bsize), + targetRateRefreshTicks: bsize / 10, // have the Congestion Manager refresh its target rates every 10% through the queue + consumedByClient: map[ErlClient]*[]time.Time{}, + exp: 4, + wg: sync.WaitGroup{}, + } + return &ret +} + +// Consumed implements CongestionManager by putting an event on the consumed channel, +// to be processed by the Start() loop +func (cm *redCongestionManager) Consumed(c ErlClient, t time.Time) { + select { + case cm.consumed <- event{ + c: c, + t: t, + }: + default: + } +} + +// Served implements CongestionManager by putting an event on the done channel, +// to be processed by the Start() loop +func (cm *redCongestionManager) Served(t time.Time) { + select { + case cm.served <- event{ + t: t, + }: + default: + } +} + +// ShouldDrop implements CongestionManager by putting a query shouldDropQueries channel, +// and blocks on the response to return synchronously to the caller +// if an error should prevent the query from running, the result is defaulted to false +func (cm *redCongestionManager) ShouldDrop(c ErlClient) bool { + ret := make(chan bool) + select { + case cm.shouldDropQueries <- shouldDropQuery{ + c: c, + ret: ret, + }: + return <-ret + default: + return false + } +} + +// Start will kick off a goroutine to consume activity from the different activity channels, +// as well as service queries about if a given capacityQueue should drop +func (cm *redCongestionManager) Start() { + // check if the maintainer is already running to ensure there is only one routine + cm.runLock.Lock() + defer cm.runLock.Unlock() + if cm.running { + return + } + cm.ctx, cm.ctxCancel = context.WithCancel(context.Background()) + cm.running = true + cm.wg.Add(1) + go cm.run() +} + +func (cm *redCongestionManager) run() { + tick := 0 + targetRate := float64(0) + consumedByClient := map[ErlClient]*[]time.Time{} + serves := []time.Time{} + lastServiceRateUpdate := time.Now() + exit := false + for { + select { + // prioritize shouldDropQueries + case query := <-cm.shouldDropQueries: + cutoff := time.Now().Add(-1 * cm.window) + prune(consumedByClient[query.c], cutoff) + query.ret <- cm.shouldDrop(targetRate, query.c, consumedByClient[query.c]) + default: + select { + // "should drop" queries + case query := <-cm.shouldDropQueries: + cutoff := time.Now().Add(-1 * cm.window) + prune(consumedByClient[query.c], cutoff) + query.ret <- cm.shouldDrop(targetRate, query.c, consumedByClient[query.c]) + // consumed events -- a client has consumed capacity from a queue + case e := <-cm.consumed: + if consumedByClient[e.c] == nil { + ts := []time.Time{} + consumedByClient[e.c] = &ts + } + *(consumedByClient[e.c]) = append(*(consumedByClient[e.c]), e.t) + // served events -- the capacity has been totally served + case e := <-cm.served: + serves = append(serves, e.t) + // check for context Done, and flag the thread for shutdown + case <-cm.ctx.Done(): + exit = true + } + + } + // recalculate the service rate every N ticks, or every 100ms + // also calculate if the routine is going to exit + tick = (tick + 1) % cm.targetRateRefreshTicks + if tick == 0 || time.Now().After(lastServiceRateUpdate.Add(100*time.Millisecond)) || exit { + lastServiceRateUpdate = time.Now() + cutoff := time.Now().Add(-1 * cm.window) + prune(&serves, cutoff) + for c := range consumedByClient { + if prune(consumedByClient[c], cutoff) == 0 { + delete(consumedByClient, c) + } + } + targetRate = 0 + // targetRate is the average service rate per client per second + if len(consumedByClient) > 0 { + serviceRate := float64(len(serves)) / float64(cm.window/time.Second) + targetRate = serviceRate / float64(len(consumedByClient)) + } + } + if exit { + cm.setTargetRate(targetRate) + cm.setConsumedByClient(consumedByClient) + cm.setServes(serves) + cm.runLock.Lock() + defer cm.runLock.Unlock() + cm.running = false + cm.wg.Done() + return + } + } +} + +func (cm *redCongestionManager) Stop() { + cm.ctxCancel() + cm.wg.Wait() +} + +func (cm *redCongestionManager) setTargetRate(tr float64) { + cm.targetRate = tr +} + +func (cm *redCongestionManager) setConsumedByClient(cbc map[ErlClient]*[]time.Time) { + cm.consumedByClient = cbc +} + +func (cm *redCongestionManager) setServes(ts []time.Time) { + cm.serves = ts +} + +func (cm *redCongestionManager) arrivalRateFor(arrivals *[]time.Time) float64 { + clientArrivalRate := float64(0) + if arrivals != nil { + clientArrivalRate = float64(len(*arrivals)) / float64(cm.window/time.Second) + } + return clientArrivalRate +} + +// shouldDrop ultimately makes the recommendation to drop a given request through some fairness probability. +// Comparing this behavior with the behavior of a basic Random Early Detection system: +// A standard RED model will drop any message with chance proportional to its queue's fullness. The more full, the more random dropping is applied to all clients. +// In this RED model, there is an application of fairness, in which the chance a client's request is dropped is proportional to their individual arrival rate, vs a per-client service rate. +// A behavior example is as follows: +// client1 makes 100 requests over a given sliding window (10s for this example) +// client2 makes 200 requests over the window +// all 300 requests were served over the window +// +// This means: +// - client1's arrival rate is 100/10 = 10/s +// - client2's arrival rate is 200/10 = 20/s +// - the service rate is 300/10 = 30/s +// - the *target rate* is the service rate per client: 30/2 = 15/s +// +// When a shouldDrop request is made: +// - client1 shouldDrop: 10 / 15 > random float ? +// - client2 shouldDrop: 20 / 15 > random float ? +// - Additionally, the arrival and service rates are raised to an exponential power, to increase contrast. +// +// client2 will be throttled because it is making requests in excess of its target rate. +// client1 will be throttled proportional to its usage of the service rate. +// over time, client2 will fall in line with the appropriate service rate, while other clients will be able to use the newly freed capacity +// The net effect is that clients who are disproportionately noisy are dropped more often, +// while quieter ones are are dropped less often. +// The reason this works is that the serviceRate represents the ability for the given resource to be serviced (ie, the rate at which work is dequeued). +// When congestion management is required, the service should attempt a fair distribution of servicing to all clients. +// clients who are making requests in excess of our known ability to fairly service requests should be reduced. +func (cm *redCongestionManager) shouldDrop(targetRate float64, c ErlClient, arrivals *[]time.Time) bool { + // clients who have "never" been seen do not get dropped + clientArrivalRate := cm.arrivalRateFor(arrivals) + if clientArrivalRate == 0 { + return false + } + // if targetRate is 0, it means we haven't had any activity to calculate (or there is not enough data) + // it should not drop in this case + if targetRate == 0 { + return false + } + // A random float is selected, and the arrival rate of the given client is + // turned to a ratio against targetRate. the congestion manager recommends to drop activity + // proportional to its overuse above the targetRate + r := rand.Float64() + return (math.Pow(clientArrivalRate, cm.exp) / math.Pow(targetRate, cm.exp)) > r +} + +func prune(ts *[]time.Time, cutoff time.Time) int { + // guard against nil lists + if ts == nil { + return 0 + } + // guard against empty lists + if len(*ts) == 0 { + return 0 + } + // optimization: if the last element falls before the cutoff, prune the whole list without iteration + if (*ts)[len(*ts)-1].Before(cutoff) { + *ts = (*ts)[:0] + return 0 + } + // optimization: if the list is longer than 50 elements, use a binary search to find the cutoff line + if len(*ts) > 50 { + i := sort.Search(len(*ts), func(i int) bool { return (*ts)[i].After(cutoff) }) + *ts = (*ts)[i:] + return len(*ts) + } + // find the first inserted timestamp *after* the cutoff, and cut everything behind it off + for i, t := range *ts { + if t.After(cutoff) { + *ts = (*ts)[i:] + return len(*ts) + } + } + // if no values are after the cutoff, clear the array and give back a 0 + *ts = (*ts)[:0] + return 0 +} diff --git a/util/rateLimit_test.go b/util/rateLimit_test.go new file mode 100644 index 0000000000..669960ecfc --- /dev/null +++ b/util/rateLimit_test.go @@ -0,0 +1,291 @@ +// Copyright (C) 2019-2023 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package util + +import ( + "testing" + "time" + + "github.com/algorand/go-algorand/test/partitiontest" + "github.com/stretchr/testify/assert" +) + +type mockClient string + +type mockCongestionControl struct{} + +func (cg mockCongestionControl) Start() {} +func (cg mockCongestionControl) Stop() {} +func (cg mockCongestionControl) Consumed(c ErlClient, t time.Time) {} +func (cg mockCongestionControl) Served(t time.Time) {} +func (cg mockCongestionControl) ShouldDrop(c ErlClient) bool { return true } + +func (c mockClient) OnClose(func()) { + return +} + +func TestNewElasticRateLimiter(t *testing.T) { + partitiontest.PartitionTest(t) + erl := NewElasticRateLimiter(100, 10, time.Second, nil) + + assert.Equal(t, len(erl.sharedCapacity), 100) + assert.Equal(t, len(erl.capacityByClient), 0) +} + +func TestElasticRateLimiterCongestionControlled(t *testing.T) { + partitiontest.PartitionTest(t) + client := mockClient("client") + erl := NewElasticRateLimiter(3, 2, time.Second, nil) + // give the ERL a congestion controler with well defined behavior for testing + erl.cm = mockCongestionControl{} + + _, err := erl.ConsumeCapacity(client) + // because the ERL gives capacity to a reservation, and then asynchronously drains capacity from the share, + // wait a moment before testing the size of the sharedCapacity + time.Sleep(100 * time.Millisecond) + assert.Equal(t, 1, len(erl.capacityByClient[client])) + assert.Equal(t, 1, len(erl.sharedCapacity)) + assert.NoError(t, err) + + erl.EnableCongestionControl() + _, err = erl.ConsumeCapacity(client) + assert.Equal(t, 0, len(erl.capacityByClient[client])) + assert.Equal(t, 1, len(erl.sharedCapacity)) + assert.NoError(t, err) + + _, err = erl.ConsumeCapacity(client) + assert.Equal(t, 0, len(erl.capacityByClient[client])) + assert.Equal(t, 1, len(erl.sharedCapacity)) + assert.Error(t, err) + + erl.DisableCongestionControl() + _, err = erl.ConsumeCapacity(client) + assert.Equal(t, 0, len(erl.capacityByClient[client])) + assert.Equal(t, 0, len(erl.sharedCapacity)) + assert.NoError(t, err) +} + +func TestReservations(t *testing.T) { + partitiontest.PartitionTest(t) + client1 := mockClient("client1") + client2 := mockClient("client2") + erl := NewElasticRateLimiter(4, 1, time.Second, nil) + + _, err := erl.ConsumeCapacity(client1) + // because the ERL gives capacity to a reservation, and then asynchronously drains capacity from the share, + // wait a moment before testing the size of the sharedCapacity + time.Sleep(100 * time.Millisecond) + assert.Equal(t, 1, len(erl.capacityByClient)) + assert.NoError(t, err) + + _, err = erl.ConsumeCapacity(client2) + // because the ERL gives capacity to a reservation, and then asynchronously drains capacity from the share, + // wait a moment before testing the size of the sharedCapacity + time.Sleep(100 * time.Millisecond) + assert.Equal(t, 2, len(erl.capacityByClient)) + assert.NoError(t, err) + + erl.closeReservation(client1) + assert.Equal(t, 1, len(erl.capacityByClient)) + erl.closeReservation(client2) + assert.Equal(t, 0, len(erl.capacityByClient)) +} + +func TestConsumeReleaseCapacity(t *testing.T) { + partitiontest.PartitionTest(t) + client := mockClient("client") + erl := NewElasticRateLimiter(4, 3, time.Second, nil) + + c1, err := erl.ConsumeCapacity(client) + // because the ERL gives capacity to a reservation, and then asynchronously drains capacity from the share, + // wait a moment before testing the size of the sharedCapacity + time.Sleep(100 * time.Millisecond) + assert.Equal(t, 2, len(erl.capacityByClient[client])) + assert.Equal(t, 1, len(erl.sharedCapacity)) + assert.NoError(t, err) + + _, err = erl.ConsumeCapacity(client) + assert.Equal(t, 1, len(erl.capacityByClient[client])) + assert.Equal(t, 1, len(erl.sharedCapacity)) + assert.NoError(t, err) + + _, err = erl.ConsumeCapacity(client) + assert.Equal(t, 0, len(erl.capacityByClient[client])) + assert.Equal(t, 1, len(erl.sharedCapacity)) + assert.NoError(t, err) + + // remember this capacity, as it is a shared capacity + c4, err := erl.ConsumeCapacity(client) + assert.Equal(t, 0, len(erl.capacityByClient[client])) + assert.Equal(t, 0, len(erl.sharedCapacity)) + assert.NoError(t, err) + + _, err = erl.ConsumeCapacity(client) + assert.Equal(t, 0, len(erl.capacityByClient[client])) + assert.Equal(t, 0, len(erl.sharedCapacity)) + assert.Error(t, err) + + // now release the capacity and observe the items return to the correct places + err = c1.Release() + assert.Equal(t, 1, len(erl.capacityByClient[client])) + assert.Equal(t, 0, len(erl.sharedCapacity)) + assert.NoError(t, err) + + // now release the capacity and observe the items return to the correct places + err = c4.Release() + assert.Equal(t, 1, len(erl.capacityByClient[client])) + assert.Equal(t, 1, len(erl.sharedCapacity)) + assert.NoError(t, err) + +} + +func TestREDCongestionManagerShouldDrop(t *testing.T) { + partitiontest.PartitionTest(t) + client := mockClient("client") + other := mockClient("other") + red := NewREDCongestionManager(time.Second*10, 10000) + // calculate the target rate every request for most accurate results + red.targetRateRefreshTicks = 1 + red.Start() + // indicate that the arrival rate is essentially 1/s + for i := 0; i < 10; i++ { + red.Consumed(client, time.Now()) + } + // indicate that the service rate is essentially 0.9/s + for i := 0; i < 9; i++ { + red.Served(time.Now()) + } + // allow the statistics to catch up before asserting + time.Sleep(100 * time.Millisecond) + // the service rate should be 0.9/s, and the arrival rate for this client should be 1/s + // for this reason, it should always drop the message + for i := 0; i < 100; i++ { + assert.True(t, red.ShouldDrop(client)) + } + // this caller hasn't consumed any capacity before, so it won't need to drop + for i := 0; i < 10; i++ { + assert.False(t, red.ShouldDrop(other)) + } + // allow the congestion manager to consume and process the given messages + time.Sleep(100 * time.Millisecond) + red.Stop() + assert.Equal(t, 10, len(*red.consumedByClient[client])) + assert.Equal(t, float64(1), red.arrivalRateFor(red.consumedByClient[client])) + assert.Equal(t, 0.0, red.arrivalRateFor(red.consumedByClient[other])) + assert.Equal(t, 0.9, red.targetRate) +} + +func TestREDCongestionManagerShouldntDrop(t *testing.T) { + partitiontest.PartitionTest(t) + client := mockClient("client") + red := NewREDCongestionManager(time.Second*10, 10000) + // calculate the target rate every request for most accurate results + red.targetRateRefreshTicks = 1 + red.Start() + + // indicate that the arrival rate is essentially 0.1/s! + red.Consumed(client, time.Now()) + + // drive 10k messages, in batches of 500, with 100ms sleeps + for i := 0; i < 20; i++ { + for j := 0; j < 500; j++ { + red.Served(time.Now()) + } + time.Sleep(100 * time.Millisecond) + } + // the service rate should be 1000/s, and the arrival rate for this client should be 0.1/s + // for this reason, shouldDrop should almost certainly return false (true only 1/100k times) + for i := 0; i < 10; i++ { + assert.False(t, red.ShouldDrop(client)) + } + // allow the congestion manager to consume and process the given messages + time.Sleep(1000 * time.Millisecond) + red.Stop() + assert.Equal(t, 1, len(*red.consumedByClient[client])) + assert.Equal(t, 10000, len(red.serves)) + assert.Equal(t, 0.1, red.arrivalRateFor(red.consumedByClient[client])) + assert.Equal(t, float64(1000), red.targetRate) +} + +func TestREDCongestionManagerTargetRate(t *testing.T) { + partitiontest.PartitionTest(t) + client := mockClient("client") + red := NewREDCongestionManager(time.Second*10, 10000) + red.Start() + red.Consumed(client, time.Now()) + red.Consumed(client, time.Now()) + red.Consumed(client, time.Now()) + red.Served(time.Now()) + red.Served(time.Now()) + red.Served(time.Now()) + // allow the congestion manager to consume and process the given messages + time.Sleep(100 * time.Millisecond) + red.Stop() + assert.Equal(t, 0.3, red.arrivalRateFor(red.consumedByClient[client])) + assert.Equal(t, 0.3, red.targetRate) +} + +func TestREDCongestionManagerPrune(t *testing.T) { + partitiontest.PartitionTest(t) + client := mockClient("client") + red := NewREDCongestionManager(time.Second*10, 10000) + red.Start() + red.Consumed(client, time.Now().Add(-11*time.Second)) + red.Consumed(client, time.Now().Add(-11*time.Second)) + red.Consumed(client, time.Now().Add(-11*time.Second)) + red.Consumed(client, time.Now()) + red.Served(time.Now().Add(-11 * time.Second)) + red.Served(time.Now().Add(-11 * time.Second)) + red.Served(time.Now().Add(-11 * time.Second)) + red.Served(time.Now()) + // allow the congestion manager to consume and process the given messages + time.Sleep(100 * time.Millisecond) + red.Stop() + assert.Equal(t, 0.1, red.arrivalRateFor(red.consumedByClient[client])) + assert.Equal(t, 0.1, red.targetRate) +} + +func TestREDCongestionManagerStopStart(t *testing.T) { + partitiontest.PartitionTest(t) + client := mockClient("client") + red := NewREDCongestionManager(time.Second*10, 10000) + red.Start() + red.Consumed(client, time.Now()) + red.Consumed(client, time.Now()) + red.Consumed(client, time.Now()) + red.Served(time.Now()) + red.Served(time.Now()) + red.Served(time.Now()) + // allow the congestion manager to consume and process the given messages + time.Sleep(100 * time.Millisecond) + red.Stop() + assert.Equal(t, 0.3, red.arrivalRateFor(red.consumedByClient[client])) + assert.Equal(t, 0.3, red.targetRate) + // Do it all again, but with 2 calls instead of 3 and 4 serves instead of 3 + red.Start() + red.Consumed(client, time.Now()) + red.Consumed(client, time.Now()) + red.Served(time.Now()) + red.Served(time.Now()) + red.Served(time.Now()) + red.Served(time.Now()) + // allow the congestion manager to consume and process the given messages + time.Sleep(100 * time.Millisecond) + red.Stop() + assert.Equal(t, 0.2, red.arrivalRateFor(red.consumedByClient[client])) + assert.Equal(t, 0.4, red.targetRate) +} diff --git a/util/s3/fileIterator.go b/util/s3/fileIterator.go index d5adbd0961..b4f4ea82da 100644 --- a/util/s3/fileIterator.go +++ b/util/s3/fileIterator.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/s3/s3Helper.go b/util/s3/s3Helper.go index 43f017443a..8c70f14eda 100644 --- a/util/s3/s3Helper.go +++ b/util/s3/s3Helper.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -22,6 +22,7 @@ import ( "io" "os" "path/filepath" + "reflect" "regexp" "runtime" "strconv" @@ -32,9 +33,6 @@ import ( "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/s3" "github.com/aws/aws-sdk-go/service/s3/s3manager" - - "github.com/algorand/go-algorand/util" - "github.com/algorand/go-algorand/util/codecs" ) const ( @@ -45,9 +43,6 @@ const ( s3DefaultReleaseBucket = "algorand-releases" s3DefaultUploadBucket = "algorand-uploads" s3DefaultRegion = "us-east-1" - - downloadAction = "download" - uploadAction = "upload" ) // Helper encapsulates the s3 session state for interactive with our default S3 bucket with appropriate credentials @@ -84,20 +79,12 @@ func getS3Region() (region string) { // MakeS3SessionForUploadWithBucket upload to bucket func MakeS3SessionForUploadWithBucket(awsBucket string) (helper Helper, err error) { - creds, err := getCredentials(uploadAction, awsBucket) - if err != nil { - return - } - return makeS3Session(creds, awsBucket) + return makeS3Session(awsBucket) } // MakeS3SessionForDownloadWithBucket download from bucket func MakeS3SessionForDownloadWithBucket(awsBucket string) (helper Helper, err error) { - creds, err := getCredentials(downloadAction, awsBucket) - if err != nil { - return - } - return makeS3Session(creds, awsBucket) + return makeS3Session(awsBucket) } // UploadFileStream sends file as stream to s3 @@ -114,84 +101,43 @@ func (helper *Helper) UploadFileStream(targetFile string, reader io.Reader) erro return nil } -type s3Keys struct { - ID string - Secret string -} - -func getCredentials(action string, awsBucket string) (creds *credentials.Credentials, err error) { - awsID, awsKey := getAWSCredentials() - credentailsRequired := checkCredentialsRequired(action, awsBucket) - if !credentailsRequired && (awsID == "" || awsKey == "") { - return credentials.AnonymousCredentials, nil - } - err = validateS3Credentials(awsID, awsKey) - if err != nil { +func validateS3Bucket(awsBucket string) (err error) { + if awsBucket == "" { + err = fmt.Errorf("bucket name is empty") return } - creds = credentials.NewStaticCredentials(awsID, awsKey, "") return - } -func loadS3KeysFromFile(keyFile string) (keys s3Keys, err error) { - err = codecs.LoadObjectFromFile(keyFile, &keys) - return -} - -func getAWSCredentials() (awsID string, awsKey string) { - awsID, _ = os.LookupEnv("AWS_ACCESS_KEY_ID") - awsKey, _ = os.LookupEnv("AWS_SECRET_ACCESS_KEY") - - // If not in environment, try to load from s3.json file in bin dir - if awsID == "" || awsKey == "" { - baseDir, err := util.ExeDir() - if err == nil { - keyFile := filepath.Join(baseDir, "s3.json") - keys, err := loadS3KeysFromFile(keyFile) - if err == nil { - awsID = keys.ID - awsKey = keys.Secret - } - } - } - return -} - -func validateS3Credentials(awsID string, awsKey string) (err error) { - if awsID == "" || awsKey == "" { - err = fmt.Errorf("AWS credentials must be specified in environment variables AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY") +func makeS3Session(bucket string) (helper Helper, err error) { + err = validateS3Bucket(bucket) + if err != nil { return } - return -} -func validateS3Bucket(awsBucket string) (err error) { - if awsBucket == "" { - err = fmt.Errorf("bucket name is empty") - return + awsConfig := &aws.Config{ + CredentialsChainVerboseErrors: aws.Bool(true), + Region: aws.String(getS3Region()), } - return -} -func checkCredentialsRequired(action string, bucketName string) (required bool) { - required = true - if action == downloadAction && bucketName == s3DefaultReleaseBucket { - required = false + // s3DefaultReleaseBucket should be public, use AnonymousCredentials + if bucket == s3DefaultReleaseBucket { + awsConfig.Credentials = credentials.AnonymousCredentials } - return -} -func makeS3Session(credentials *credentials.Credentials, bucket string) (helper Helper, err error) { - err = validateS3Bucket(bucket) + sess, err := session.NewSessionWithOptions(session.Options{ + SharedConfigState: session.SharedConfigEnable, + Config: *awsConfig, + }) if err != nil { return } - sess, err := session.NewSession(&aws.Config{Region: aws.String(getS3Region()), - Credentials: credentials}) - if err != nil { - return + + // use AnonymousCredentials if none are found + if creds, err := sess.Config.Credentials.Get(); err != nil && !reflect.DeepEqual(creds, credentials.AnonymousCredentials) { + sess.Config.Credentials = credentials.AnonymousCredentials } + helper = Helper{ session: sess, bucket: bucket, @@ -294,7 +240,7 @@ func (helper *Helper) GetPackageFilesVersion(channel string, pkgFiles string, sp func GetVersionFromName(name string) (version uint64, err error) { re := regexp.MustCompile(`_(\d*)\.(\d*)\.(\d*)`) submatchAll := re.FindAllStringSubmatch(name, -1) - if submatchAll == nil || len(submatchAll) == 0 || len(submatchAll[0]) != 4 { + if len(submatchAll) == 0 || len(submatchAll[0]) != 4 { err = errors.New("unable to parse version from filename " + name) return } diff --git a/util/s3/s3Helper_test.go b/util/s3/s3Helper_test.go index 6c46095015..c0502a3b01 100644 --- a/util/s3/s3Helper_test.go +++ b/util/s3/s3Helper_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -114,8 +114,6 @@ func TestMakeS3SessionForUploadWithBucket(t *testing.T) { const emptyBucket = "" type args struct { awsBucket string - awsID string - awsSecret string } tests := []struct { name string @@ -123,21 +121,12 @@ func TestMakeS3SessionForUploadWithBucket(t *testing.T) { wantHelper Helper wantErr bool }{ - {name: "test1", args: args{awsBucket: bucket1, awsID: "AWS_ID", awsSecret: "AWS_SECRET"}, wantHelper: Helper{bucket: bucket1}, wantErr: false}, - {name: "test2", args: args{awsBucket: emptyBucket, awsID: "AWS_ID", awsSecret: "AWS_SECRET"}, wantHelper: Helper{bucket: emptyBucket}, wantErr: true}, - {name: "test3", args: args{awsBucket: bucket1, awsID: "", awsSecret: "AWS_SECRET"}, wantHelper: Helper{bucket: bucket1}, wantErr: true}, - {name: "test4", args: args{awsBucket: bucket1, awsID: "AWS_ID", awsSecret: ""}, wantHelper: Helper{bucket: bucket1}, wantErr: true}, - {name: "test5", args: args{awsBucket: bucket1, awsID: "", awsSecret: ""}, wantHelper: Helper{bucket: bucket1}, wantErr: true}, - // public upload bucket requires AWS credentials for uploads - {name: "test6", args: args{awsBucket: publicUploadBucket, awsID: "AWS_ID", awsSecret: "AWS_SECRET"}, wantHelper: Helper{bucket: publicUploadBucket}, wantErr: false}, - {name: "test7", args: args{awsBucket: publicUploadBucket, awsID: "", awsSecret: "AWS_SECRET"}, wantHelper: Helper{bucket: publicUploadBucket}, wantErr: true}, - {name: "test8", args: args{awsBucket: publicUploadBucket, awsID: "AWS_ID", awsSecret: ""}, wantHelper: Helper{bucket: publicUploadBucket}, wantErr: true}, - {name: "test9", args: args{awsBucket: publicUploadBucket, awsID: "", awsSecret: ""}, wantHelper: Helper{bucket: publicUploadBucket}, wantErr: true}, + {name: "test1", args: args{awsBucket: bucket1}, wantHelper: Helper{bucket: bucket1}, wantErr: false}, + {name: "test2", args: args{awsBucket: emptyBucket}, wantHelper: Helper{bucket: emptyBucket}, wantErr: true}, + {name: "test6", args: args{awsBucket: publicUploadBucket}, wantHelper: Helper{bucket: publicUploadBucket}, wantErr: false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - os.Setenv("AWS_ACCESS_KEY_ID", tt.args.awsID) - os.Setenv("AWS_SECRET_ACCESS_KEY", tt.args.awsSecret) gotHelper, err := MakeS3SessionForUploadWithBucket(tt.args.awsBucket) if (err != nil) != tt.wantErr { t.Errorf("MakeS3SessionForUploadWithBucket() error = %v, wantErr %v", err, tt.wantErr) @@ -158,8 +147,6 @@ func TestMakeS3SessionForDownloadWithBucket(t *testing.T) { const emptyBucket = "" type args struct { awsBucket string - awsID string - awsSecret string } tests := []struct { name string @@ -167,21 +154,12 @@ func TestMakeS3SessionForDownloadWithBucket(t *testing.T) { wantHelper Helper wantErr bool }{ - {name: "test1", args: args{awsBucket: bucket1, awsID: "AWS_ID", awsSecret: "AWS_SECRET"}, wantHelper: Helper{bucket: bucket1}, wantErr: false}, - {name: "test2", args: args{awsBucket: emptyBucket, awsID: "AWS_ID", awsSecret: "AWS_SECRET"}, wantHelper: Helper{bucket: emptyBucket}, wantErr: true}, - {name: "test3", args: args{awsBucket: bucket1, awsID: "", awsSecret: "AWS_SECRET"}, wantHelper: Helper{bucket: bucket1}, wantErr: true}, - {name: "test4", args: args{awsBucket: bucket1, awsID: "AWS_ID", awsSecret: ""}, wantHelper: Helper{bucket: bucket1}, wantErr: true}, - {name: "test5", args: args{awsBucket: bucket1, awsID: "", awsSecret: ""}, wantHelper: Helper{bucket: bucket1}, wantErr: true}, - // public release bucket does not require AWS credentials for downloads - {name: "test6", args: args{awsBucket: publicReleaseBucket, awsID: "AWS_ID", awsSecret: "AWS_SECRET"}, wantHelper: Helper{bucket: publicReleaseBucket}, wantErr: false}, - {name: "test7", args: args{awsBucket: publicReleaseBucket, awsID: "", awsSecret: "AWS_SECRET"}, wantHelper: Helper{bucket: publicReleaseBucket}, wantErr: false}, - {name: "test8", args: args{awsBucket: publicReleaseBucket, awsID: "AWS_ID", awsSecret: ""}, wantHelper: Helper{bucket: publicReleaseBucket}, wantErr: false}, - {name: "test9", args: args{awsBucket: publicReleaseBucket, awsID: "", awsSecret: ""}, wantHelper: Helper{bucket: publicReleaseBucket}, wantErr: false}, + {name: "test1", args: args{awsBucket: bucket1}, wantHelper: Helper{bucket: bucket1}, wantErr: false}, + {name: "test2", args: args{awsBucket: emptyBucket}, wantHelper: Helper{bucket: emptyBucket}, wantErr: true}, + {name: "test6", args: args{awsBucket: publicReleaseBucket}, wantHelper: Helper{bucket: publicReleaseBucket}, wantErr: false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - os.Setenv("AWS_ACCESS_KEY_ID", tt.args.awsID) - os.Setenv("AWS_SECRET_ACCESS_KEY", tt.args.awsSecret) gotHelper, err := MakeS3SessionForDownloadWithBucket(tt.args.awsBucket) if (err != nil) != tt.wantErr { t.Errorf("MakeS3SessionForDownloadWithBucket() error = %v, wantErr %v", err, tt.wantErr) diff --git a/util/sleep.go b/util/sleep.go index 0d3a60acf0..3ad397bf0c 100644 --- a/util/sleep.go +++ b/util/sleep.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/sleep_linux.go b/util/sleep_linux.go index 02d4503036..1540fce736 100644 --- a/util/sleep_linux.go +++ b/util/sleep_linux.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/sleep_linux_32.go b/util/sleep_linux_32.go index 50a0e696c2..8dd445e2ac 100644 --- a/util/sleep_linux_32.go +++ b/util/sleep_linux_32.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/sleep_linux_64.go b/util/sleep_linux_64.go index b2f7a69dbe..0fff615e76 100644 --- a/util/sleep_linux_64.go +++ b/util/sleep_linux_64.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/tar/tar.go b/util/tar/tar.go index 68d03b51f1..2ffc78c1bb 100644 --- a/util/tar/tar.go +++ b/util/tar/tar.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/tar/untar.go b/util/tar/untar.go index e6de065a66..fcbccc755f 100644 --- a/util/tar/untar.go +++ b/util/tar/untar.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/tcpinfo.go b/util/tcpinfo.go index 2b4c69d294..c387bba33f 100644 --- a/util/tcpinfo.go +++ b/util/tcpinfo.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/tcpinfo_darwin.go b/util/tcpinfo_darwin.go index cae19d06d4..ecb06ab668 100644 --- a/util/tcpinfo_darwin.go +++ b/util/tcpinfo_darwin.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/tcpinfo_linux.go b/util/tcpinfo_linux.go index 3da707e1cd..8cf1687aed 100644 --- a/util/tcpinfo_linux.go +++ b/util/tcpinfo_linux.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -56,6 +56,7 @@ func getConnTCPInfo(raw syscall.RawConn) (*TCPInfo, error) { // linuxTCPInfo is based on linux include/uapi/linux/tcp.h struct tcp_info //revive:disable:var-naming +//nolint:structcheck // complains about unused fields that are rqeuired to match C tcp_info struct type linuxTCPInfo struct { state uint8 ca_state uint8 diff --git a/util/tcpinfo_noop.go b/util/tcpinfo_noop.go index a155ed9298..7eecba7584 100644 --- a/util/tcpinfo_noop.go +++ b/util/tcpinfo_noop.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/timers/frozen.go b/util/timers/frozen.go index a9aa6c1b71..e6487b1a8a 100644 --- a/util/timers/frozen.go +++ b/util/timers/frozen.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/timers/interface.go b/util/timers/interface.go index 6c8c493e12..e96aced757 100644 --- a/util/timers/interface.go +++ b/util/timers/interface.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/timers/monotonic.go b/util/timers/monotonic.go index 80b1a7de45..70db87da3e 100644 --- a/util/timers/monotonic.go +++ b/util/timers/monotonic.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/timers/monotonic_test.go b/util/timers/monotonic_test.go index b9fecdbdbb..f8821b300b 100644 --- a/util/timers/monotonic_test.go +++ b/util/timers/monotonic_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/tokens/tokens.go b/util/tokens/tokens.go index 930f030bbe..1064a5863c 100644 --- a/util/tokens/tokens.go +++ b/util/tokens/tokens.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/util.go b/util/util.go index a19a5b0690..0ecf357344 100644 --- a/util/util.go +++ b/util/util.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/util_windows.go b/util/util_windows.go index 5a533b655d..fa91a4f8a8 100644 --- a/util/util_windows.go +++ b/util/util_windows.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/uuid/uuid.go b/util/uuid/uuid.go index ab99a5a3d5..e159ecf46e 100644 --- a/util/uuid/uuid.go +++ b/util/uuid/uuid.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/uuid/uuid_test.go b/util/uuid/uuid_test.go index 6656672256..17914af3cc 100644 --- a/util/uuid/uuid_test.go +++ b/util/uuid/uuid_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/watchdogStreamReader.go b/util/watchdogStreamReader.go index 8842483bd3..a54ca376bb 100644 --- a/util/watchdogStreamReader.go +++ b/util/watchdogStreamReader.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify