summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYossi Gilad <yossig2@gmail.com>2023-08-07 14:10:44 -0400
committerYossi Gilad <yossig2@gmail.com>2023-08-07 14:10:44 -0400
commit79550278b00a0b7d980d7d4edf790c3e30965eff (patch)
treee6de14775f62df5196db6487bc75e9131a8135c9
parent20765633081ae505d0c771503782764603ce4f04 (diff)
parent259eb32cdd5d1686ca76e1ae7c92627bf47e2927 (diff)
spec asm paramsfeature/specblockasm
-rw-r--r--.circleci/config.yml11
-rw-r--r--.github/workflows/pr-type-category.yml2
-rw-r--r--.github/workflows/tools.yml3
-rw-r--r--Dockerfile2
-rw-r--r--Makefile12
-rw-r--r--README.md2
-rw-r--r--agreement/fuzzer/networkFacade_test.go2
-rw-r--r--agreement/gossip/network_test.go2
-rw-r--r--agreement/message.go2
-rw-r--r--agreement/message_test.go2
-rw-r--r--agreement/msgp_gen.go80
-rw-r--r--catchup/catchpointService_test.go6
-rw-r--r--catchup/service.go25
-rw-r--r--cmd/algons/commands.go1
-rw-r--r--cmd/algons/dnsaddrCmd.go65
-rw-r--r--cmd/catchpointdump/file.go15
-rw-r--r--cmd/goal/clerk.go39
-rw-r--r--cmd/tealdbg/debugger_test.go16
-rw-r--r--cmd/tealdbg/local.go30
-rw-r--r--cmd/tealdbg/localLedger.go4
-rw-r--r--cmd/tealdbg/localLedger_test.go2
-rw-r--r--components/mocks/mockCatchpointCatchupAccessor.go7
-rw-r--r--config/consensus.go5
-rw-r--r--config/localTemplate.go17
-rw-r--r--config/local_defaults.go5
-rw-r--r--daemon/algod/api/algod.oas2.json33
-rw-r--r--daemon/algod/api/algod.oas3.yml32
-rw-r--r--daemon/algod/api/server/v2/dryrun.go14
-rw-r--r--daemon/algod/api/server/v2/dryrun_test.go1
-rw-r--r--daemon/algod/api/server/v2/generated/data/routes.go362
-rw-r--r--daemon/algod/api/server/v2/generated/experimental/routes.go354
-rw-r--r--daemon/algod/api/server/v2/generated/model/types.go15
-rw-r--r--daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go365
-rw-r--r--daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go513
-rw-r--r--daemon/algod/api/server/v2/generated/participating/private/routes.go369
-rw-r--r--daemon/algod/api/server/v2/generated/participating/public/routes.go388
-rw-r--r--daemon/algod/api/server/v2/handlers.go5
-rw-r--r--daemon/algod/api/server/v2/test/handlers_test.go75
-rw-r--r--daemon/algod/api/server/v2/utils.go19
-rw-r--r--data/ledger_test.go27
-rw-r--r--data/transactions/logic/README.md18
-rw-r--r--data/transactions/logic/README_in.md18
-rw-r--r--data/transactions/logic/assembler.go2
-rw-r--r--data/transactions/logic/backwardCompat_test.go84
-rw-r--r--data/transactions/logic/blackbox_test.go13
-rw-r--r--data/transactions/logic/debugger.go4
-rw-r--r--data/transactions/logic/debugger_eval_test.go6
-rw-r--r--data/transactions/logic/eval.go257
-rw-r--r--data/transactions/logic/evalAppTxn_test.go24
-rw-r--r--data/transactions/logic/evalBench_test.go9
-rw-r--r--data/transactions/logic/evalCrypto_test.go26
-rw-r--r--data/transactions/logic/evalStateful_test.go338
-rw-r--r--data/transactions/logic/eval_test.go720
-rw-r--r--data/transactions/logic/export_test.go25
-rw-r--r--data/transactions/logic/fields_test.go37
-rw-r--r--data/transactions/logic/ledger_test.go4
-rw-r--r--data/transactions/logic/opcodes.go7
-rw-r--r--data/transactions/logic/resources_test.go384
-rw-r--r--data/transactions/logic/tracer_test.go12
-rw-r--r--data/transactions/verify/artifact_test.go11
-rw-r--r--data/transactions/verify/txn.go73
-rw-r--r--data/transactions/verify/txn_test.go17
-rw-r--r--data/transactions/verify/verifiedTxnCache.go4
-rw-r--r--data/transactions/verify/verifiedTxnCache_test.go12
-rw-r--r--docker/Dockerfile2
-rw-r--r--docker/build/Dockerfile4
-rw-r--r--docker/build/Dockerfile-deploy4
-rw-r--r--docker/build/aptly.Dockerfile2
-rw-r--r--docker/build/cicd.alpine.Dockerfile3
-rw-r--r--docker/build/cicd.centos.Dockerfile2
-rw-r--r--docker/build/cicd.centos8.Dockerfile1
-rw-r--r--docker/build/cicd.ubuntu.Dockerfile4
-rw-r--r--docker/build/docker.ubuntu.Dockerfile2
-rw-r--r--docker/build/releases-page.Dockerfile2
-rw-r--r--docs/follower_node.md1
-rw-r--r--go.mod69
-rw-r--r--go.sum338
-rw-r--r--installer/config.json.example5
-rw-r--r--installer/debian/algorand-devtools/conffiles1
-rw-r--r--ledger/acctupdates.go14
-rw-r--r--ledger/acctupdates_test.go13
-rw-r--r--ledger/apply/application_test.go154
-rw-r--r--ledger/blockHeaderCache.go86
-rw-r--r--ledger/blockHeaderCache_test.go94
-rw-r--r--ledger/catchpointfilewriter.go (renamed from ledger/catchpointwriter.go)62
-rw-r--r--ledger/catchpointfilewriter_test.go (renamed from ledger/catchpointwriter_test.go)38
-rw-r--r--ledger/catchpointtracker.go47
-rw-r--r--ledger/catchpointtracker_test.go20
-rw-r--r--ledger/catchupaccessor.go69
-rw-r--r--ledger/eval/applications.go5
-rw-r--r--ledger/eval/cow.go5
-rw-r--r--ledger/eval/eval.go7
-rw-r--r--ledger/eval/eval_test.go8
-rw-r--r--ledger/eval/prefetcher/prefetcher_alignment_test.go4
-rw-r--r--ledger/evalindexer.go7
-rw-r--r--ledger/evalindexer_test.go4
-rw-r--r--ledger/ledger.go50
-rw-r--r--ledger/ledger_test.go48
-rw-r--r--ledger/lrukv.go2
-rw-r--r--ledger/lrukv_test.go7
-rw-r--r--ledger/lruresources.go2
-rw-r--r--ledger/lruresources_test.go7
-rw-r--r--ledger/roundlru.go142
-rw-r--r--ledger/roundlru_test.go95
-rw-r--r--ledger/simulation/simulation_eval_test.go78
-rw-r--r--ledger/simulation/trace.go39
-rw-r--r--ledger/simulation/tracer.go56
-rw-r--r--ledger/store/trackerdb/generickv/accounts_ext_reader.go521
-rw-r--r--ledger/store/trackerdb/generickv/accounts_ext_writer.go265
-rw-r--r--ledger/store/trackerdb/generickv/accounts_reader.go405
-rw-r--r--ledger/store/trackerdb/generickv/accounts_writer.go188
-rw-r--r--ledger/store/trackerdb/generickv/catchpoint.go48
-rw-r--r--ledger/store/trackerdb/generickv/init_accounts.go60
-rw-r--r--ledger/store/trackerdb/generickv/migrations.go225
-rw-r--r--ledger/store/trackerdb/generickv/msgp_gen.go156
-rw-r--r--ledger/store/trackerdb/generickv/msgp_gen_test.go75
-rw-r--r--ledger/store/trackerdb/generickv/onlineaccounts_reader.go186
-rw-r--r--ledger/store/trackerdb/generickv/onlineaccounts_writer.go64
-rw-r--r--ledger/store/trackerdb/generickv/reader.go78
-rw-r--r--ledger/store/trackerdb/generickv/schema.go171
-rw-r--r--ledger/store/trackerdb/generickv/stateproof_reader.go104
-rw-r--r--ledger/store/trackerdb/generickv/stateproof_writer.go76
-rw-r--r--ledger/store/trackerdb/generickv/writer.go91
-rw-r--r--ledger/store/trackerdb/sqlitedriver/catchpoint.go4
-rw-r--r--ledger/store/trackerdb/sqlitedriver/sqlitedriver.go38
-rw-r--r--ledger/store/trackerdb/store.go8
-rw-r--r--ledger/store/trackerdb/testsuite/README.md29
-rw-r--r--ledger/store/trackerdb/testsuite/accounts_ext_kv_test.go300
-rw-r--r--ledger/store/trackerdb/testsuite/accounts_kv_test.go381
-rw-r--r--ledger/store/trackerdb/testsuite/dbsemantics_test.go82
-rw-r--r--ledger/store/trackerdb/testsuite/migration_test.go140
-rw-r--r--ledger/store/trackerdb/testsuite/mockdb_test.go34
-rw-r--r--ledger/store/trackerdb/testsuite/onlineaccounts_kv_test.go521
-rw-r--r--ledger/store/trackerdb/testsuite/sqlitedb_test.go43
-rw-r--r--ledger/store/trackerdb/testsuite/stateproofs_kv_test.go130
-rw-r--r--ledger/store/trackerdb/testsuite/utils_test.go445
-rw-r--r--ledger/tracker.go2
-rw-r--r--ledger/txtail.go36
-rw-r--r--libgoal/libgoal.go12
-rw-r--r--libgoal/libgoal_test.go22
-rw-r--r--netdeploy/network.go53
-rw-r--r--netdeploy/networkTemplate.go4
-rw-r--r--netdeploy/networkTemplates_test.go63
-rw-r--r--netdeploy/network_test.go18
-rw-r--r--netdeploy/remote/nodeConfig.go1
-rw-r--r--network/msgCompressor.go2
-rw-r--r--network/multiplexer.go13
-rw-r--r--network/multiplexer_test.go7
-rw-r--r--network/netidentity.go16
-rw-r--r--network/netidentity_test.go3
-rw-r--r--network/p2p/dnsaddr/resolve.go68
-rw-r--r--network/p2p/dnsaddr/resolveController.go64
-rw-r--r--network/p2p/dnsaddr/resolveController_test.go45
-rw-r--r--network/p2p/dnsaddr/resolve_test.go111
-rw-r--r--network/p2p/peerID.go90
-rw-r--r--network/p2p/peerID_test.go104
-rw-r--r--network/p2p/peerstore/peerstore.go58
-rw-r--r--network/p2p/peerstore/peerstore_test.go90
-rw-r--r--network/p2p/peerstore/utils.go51
-rw-r--r--network/p2p/peerstore/utils_test.go74
-rw-r--r--network/wsNetwork.go168
-rw-r--r--network/wsNetwork_test.go38
-rw-r--r--network/wsPeer.go97
-rw-r--r--node/msgp_gen.go28
-rw-r--r--node/netprio.go4
-rw-r--r--node/netprio_test.go47
-rw-r--r--node/node.go11
-rw-r--r--protocol/tags.go2
-rw-r--r--rpcs/ledgerService.go25
-rwxr-xr-xscripts/check_deps.sh6
-rwxr-xr-xscripts/configure_dev.sh16
-rw-r--r--scripts/release/README.md2
-rw-r--r--scripts/release/common/docker/centos.Dockerfile2
-rw-r--r--scripts/release/common/docker/centos8.Dockerfile2
-rw-r--r--scripts/release/common/docker/setup.Dockerfile2
-rwxr-xr-xscripts/release/common/setup.sh2
-rw-r--r--stateproof/abstractions.go1
-rw-r--r--test/e2e-go/features/transactions/asset_test.go4
-rw-r--r--test/e2e-go/features/transactions/lease_test.go4
-rw-r--r--test/e2e-go/restAPI/restClient_test.go171
-rw-r--r--test/heapwatch/heapWatch.py20
-rwxr-xr-xtest/scripts/e2e_client_runner.py22
-rwxr-xr-xtest/scripts/e2e_subs/app-group.py1
-rwxr-xr-xtest/scripts/e2e_subs/app-inner-calls-csp.py1
-rwxr-xr-xtest/scripts/e2e_subs/app-inner-calls.py1
-rwxr-xr-xtest/scripts/e2e_subs/app-rekey.py1
-rwxr-xr-xtest/scripts/e2e_subs/e2e-app-simulate.sh47
-rwxr-xr-xtest/scripts/e2e_subs/e2e-teal.sh3
-rwxr-xr-xtest/scripts/e2e_subs/example.py2
-rwxr-xr-xtest/scripts/e2e_subs/goal/goal.py4
-rwxr-xr-xtest/scripts/e2e_subs/hdr-access.py1
-rwxr-xr-xtest/scripts/e2e_subs/limit-swap-test.sh2
-rwxr-xr-xtest/scripts/e2e_subs/lsig-budget.py87
-rwxr-xr-xtest/scripts/e2e_subs/shared-resources.py1
-rw-r--r--test/scripts/e2e_subs/tealprogs/stack-scratch.teal45
-rw-r--r--test/testdata/configs/config-v29.json126
-rw-r--r--test/testdata/configs/config-v30.json126
-rw-r--r--test/testdata/nettemplates/FiveNodesTwoRelays.json56
-rw-r--r--tools/block-generator/Makefile4
-rw-r--r--tools/block-generator/README.md30
-rw-r--r--tools/block-generator/generator/config_test.go2
-rw-r--r--tools/block-generator/generator/generate.go25
-rw-r--r--tools/block-generator/generator/generate_test.go35
-rw-r--r--tools/block-generator/generator/generator_ledger.go8
-rw-r--r--tools/block-generator/generator/generator_types.go4
-rw-r--r--tools/block-generator/generator/server.go7
-rw-r--r--tools/block-generator/generator/test_scenario.yml (renamed from tools/block-generator/test_config.yml)0
-rw-r--r--tools/block-generator/go.mod31
-rw-r--r--tools/block-generator/go.sum73
-rwxr-xr-xtools/block-generator/run_tests.sh97
-rw-r--r--tools/block-generator/runner/run.go124
-rw-r--r--tools/block-generator/runner/runner.go3
-rw-r--r--tools/block-generator/runner/template/conduit.yml.tmpl2
-rw-r--r--tools/block-generator/scripts/print_tps.py (renamed from tools/block-generator/print_tps.py)0
-rwxr-xr-xtools/block-generator/scripts/run_postgres.sh (renamed from tools/block-generator/run_postgres.sh)2
-rw-r--r--tools/block-generator/scripts/run_runner.py (renamed from tools/block-generator/run_runner.py)2
-rwxr-xr-xtools/block-generator/scripts/run_runner.sh (renamed from tools/block-generator/run_runner.sh)14
-rw-r--r--tools/network/resolveController.go25
-rw-r--r--tools/x-repo-types/Makefile25
-rw-r--r--tools/x-repo-types/go.mod5
-rw-r--r--tools/x-repo-types/go.sum13
-rw-r--r--util/metrics/metrics.go5
222 files changed, 10559 insertions, 3667 deletions
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 192921564..d2898da25 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -47,19 +47,20 @@ executors:
resource_class: arm.large
mac_amd64_medium:
macos:
- xcode: 13.2.1
- resource_class: medium
+ xcode: 14.2.0
+ resource_class: macos.x86.medium.gen2
environment:
HOMEBREW_NO_AUTO_UPDATE: "true"
mac_amd64_large:
macos:
- xcode: 13.2.1
- resource_class: large
+ xcode: 14.2.0
+ # Since they removed the large class for amd64, we will use medium here too.
+ resource_class: macos.x86.medium.gen2
environment:
HOMEBREW_NO_AUTO_UPDATE: "true"
mac_arm64: &executor-mac-arm64
machine: true
- resource_class: algorand/macstadium-m1-macos11
+ resource_class: algorand/macstadium-m1
environment:
HOMEBREW_NO_AUTO_UPDATE: "true"
# these are required b/c jobs explicitly assign sizes to the executors
diff --git a/.github/workflows/pr-type-category.yml b/.github/workflows/pr-type-category.yml
index 478b0b90e..b496e862a 100644
--- a/.github/workflows/pr-type-category.yml
+++ b/.github/workflows/pr-type-category.yml
@@ -10,7 +10,7 @@ jobs:
name: Check PR Category and Type
steps:
- name: Checking for correct number of required github pr labels
- uses: mheap/github-action-required-labels@v2
+ uses: mheap/github-action-required-labels@v5
with:
mode: exactly
count: 1
diff --git a/.github/workflows/tools.yml b/.github/workflows/tools.yml
index 90b90cec8..064f100f4 100644
--- a/.github/workflows/tools.yml
+++ b/.github/workflows/tools.yml
@@ -9,9 +9,6 @@ on:
- 'tools/block-generator/**'
- 'tools/x-repo-types/**'
pull_request:
- paths:
- - 'tools/block-generator/**'
- - 'tools/x-repo-types/**'
jobs:
tools_test:
diff --git a/Dockerfile b/Dockerfile
index c17d5a9e6..0c6c370c9 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,4 +1,4 @@
-FROM ubuntu:22.04 as builder
+FROM ubuntu:20.04 as builder
ARG GO_VERSION="1.20.5"
diff --git a/Makefile b/Makefile
index fdafc740f..9afd53a35 100644
--- a/Makefile
+++ b/Makefile
@@ -49,9 +49,9 @@ export CPATH=/opt/homebrew/include
export LIBRARY_PATH=/opt/homebrew/lib
endif
endif
+
ifeq ($(UNAME), Linux)
EXTLDFLAGS := -static-libstdc++ -static-libgcc
-ifeq ($(ARCH), amd64)
# the following predicate is abit misleading; it tests if we're not in centos.
ifeq (,$(wildcard /etc/centos-release))
EXTLDFLAGS += -static
@@ -59,14 +59,6 @@ endif
GOTAGSLIST += osusergo netgo static_build
GOBUILDMODE := -buildmode pie
endif
-ifeq ($(ARCH), arm)
-ifneq ("$(wildcard /etc/alpine-release)","")
-EXTLDFLAGS += -static
-GOTAGSLIST += osusergo netgo static_build
-GOBUILDMODE := -buildmode pie
-endif
-endif
-endif
ifneq (, $(findstring MINGW,$(UNAME)))
EXTLDFLAGS := -static -static-libstdc++ -static-libgcc
@@ -99,7 +91,7 @@ ALGOD_API_PACKAGES := $(sort $(shell GOPATH=$(GOPATH) && GO111MODULE=off && cd d
GOMOD_DIRS := ./tools/block-generator ./tools/x-repo-types
-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 ./network ./node ./ledger ./ledger/ledgercore ./ledger/store/trackerdb ./ledger/encoded ./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 ./network ./node ./ledger ./ledger/ledgercore ./ledger/store/trackerdb ./ledger/store/trackerdb/generickv ./ledger/encoded ./stateproof ./data/account ./daemon/algod/api/spec/v2
default: build
diff --git a/README.md b/README.md
index c8de385b2..682d88436 100644
--- a/README.md
+++ b/README.md
@@ -22,7 +22,7 @@ the [official Go documentation website](https://golang.org/doc/).
### Linux / OSX
-We currently strive to support Debian-based distributions with Ubuntu 22.04
+We currently strive to support Debian-based distributions with Ubuntu 20.04
being our official release target.
Building on Arch Linux works as well.
Our core engineering team uses Linux and OSX, so both environments are well
diff --git a/agreement/fuzzer/networkFacade_test.go b/agreement/fuzzer/networkFacade_test.go
index d00ec4e7c..6db2d88c2 100644
--- a/agreement/fuzzer/networkFacade_test.go
+++ b/agreement/fuzzer/networkFacade_test.go
@@ -77,7 +77,7 @@ func MakeNetworkFacade(fuzzer *Fuzzer, nodeID int) *NetworkFacade {
n := &NetworkFacade{
fuzzer: fuzzer,
nodeID: nodeID,
- mux: network.MakeMultiplexer(fuzzer.log),
+ mux: network.MakeMultiplexer(),
clocks: make(map[int]chan time.Time),
eventsQueues: make(map[string]int),
eventsQueuesCh: make(chan int, 1000),
diff --git a/agreement/gossip/network_test.go b/agreement/gossip/network_test.go
index bd4f4baf3..8584dc8d2 100644
--- a/agreement/gossip/network_test.go
+++ b/agreement/gossip/network_test.go
@@ -323,7 +323,7 @@ func makewhiteholeNetwork(domain *whiteholeDomain) *whiteholeNetwork {
w := &whiteholeNetwork{
peer: atomic.AddUint32(&domain.peerIdx, 1),
lastMsgRead: uint32(len(domain.messages)),
- mux: network.MakeMultiplexer(domain.log),
+ mux: network.MakeMultiplexer(),
domain: domain,
disconnected: make(map[uint32]bool),
}
diff --git a/agreement/message.go b/agreement/message.go
index d7d52bc8a..1486dd98e 100644
--- a/agreement/message.go
+++ b/agreement/message.go
@@ -28,7 +28,7 @@ type message struct {
// this field is for backwards compatibility with crash state serialized using go-codec prior to explicit unexport.
// should be removed after the next consensus update.
- MessageHandle msgp.Raw
+ MessageHandle msgp.Raw `codec:"MessageHandle,omitempty"`
// explicitly unexport this field since we can't define serializers for interface{} type
// the only implementation of this is gossip.messageMetadata which doesn't have exported fields to serialize.
messageHandle MessageHandle
diff --git a/agreement/message_test.go b/agreement/message_test.go
index 3c3ab77f0..fb0558fc1 100644
--- a/agreement/message_test.go
+++ b/agreement/message_test.go
@@ -111,6 +111,7 @@ func TestMessageBackwardCompatibility(t *testing.T) {
Tag: protocol.ProposalPayloadTag,
}
+ require.Containsf(t, string(encoded), "MessageHandle", "encoded message does not contain MessageHandle field")
var m1, m2, m3, m4 message
// Both msgp and reflection should decode the message containing old MessageHandle successfully
err = protocol.Decode(encoded, &m1)
@@ -123,6 +124,7 @@ func TestMessageBackwardCompatibility(t *testing.T) {
e1 := protocol.Encode(&m1)
e2 := protocol.EncodeReflect(&m2)
require.Equal(t, e1, e2)
+ require.NotContainsf(t, string(e1), "MessageHandle", "encoded message still contains MessageHandle field")
err = protocol.DecodeReflect(e1, &m3)
require.NoError(t, err)
err = protocol.Decode(e2, &m4)
diff --git a/agreement/msgp_gen.go b/agreement/msgp_gen.go
index 7c8b08d86..f7bf8e64a 100644
--- a/agreement/msgp_gen.go
+++ b/agreement/msgp_gen.go
@@ -2662,40 +2662,52 @@ func FreshnessDataMaxSize() (s int) {
// MarshalMsg implements msgp.Marshaler
func (z *message) MarshalMsg(b []byte) (o []byte) {
o = msgp.Require(b, z.Msgsize())
- // map header, size 9
- // string "Bundle"
- o = append(o, 0x89, 0xa6, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65)
- o = (*z).Bundle.MarshalMsg(o)
- // string "CompoundMessage"
- o = append(o, 0xaf, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x75, 0x6e, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65)
- // map header, size 2
- // string "Proposal"
- o = append(o, 0x82, 0xa8, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c)
- o = (*z).CompoundMessage.Proposal.MarshalMsg(o)
- // string "Vote"
- o = append(o, 0xa4, 0x56, 0x6f, 0x74, 0x65)
- o = (*z).CompoundMessage.Vote.MarshalMsg(o)
- // string "MessageHandle"
- o = append(o, 0xad, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65)
- o = (*z).MessageHandle.MarshalMsg(o)
- // string "Proposal"
- o = append(o, 0xa8, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c)
- o = (*z).Proposal.MarshalMsg(o)
- // string "Tag"
- o = append(o, 0xa3, 0x54, 0x61, 0x67)
- o = (*z).Tag.MarshalMsg(o)
- // string "UnauthenticatedBundle"
- o = append(o, 0xb5, 0x55, 0x6e, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65)
- o = (*z).UnauthenticatedBundle.MarshalMsg(o)
- // string "UnauthenticatedProposal"
- o = append(o, 0xb7, 0x55, 0x6e, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c)
- o = (*z).UnauthenticatedProposal.MarshalMsg(o)
- // string "UnauthenticatedVote"
- o = append(o, 0xb3, 0x55, 0x6e, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x56, 0x6f, 0x74, 0x65)
- o = (*z).UnauthenticatedVote.MarshalMsg(o)
- // string "Vote"
- o = append(o, 0xa4, 0x56, 0x6f, 0x74, 0x65)
- o = (*z).Vote.MarshalMsg(o)
+ // omitempty: check for empty values
+ zb0001Len := uint32(9)
+ var zb0001Mask uint16 /* 11 bits */
+ if (*z).MessageHandle.MsgIsZero() {
+ zb0001Len--
+ zb0001Mask |= 0x4
+ }
+ // variable map header, size zb0001Len
+ o = append(o, 0x80|uint8(zb0001Len))
+ if zb0001Len != 0 {
+ // string "Bundle"
+ o = append(o, 0xa6, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65)
+ o = (*z).Bundle.MarshalMsg(o)
+ // string "CompoundMessage"
+ o = append(o, 0xaf, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x75, 0x6e, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65)
+ // map header, size 2
+ // string "Proposal"
+ o = append(o, 0x82, 0xa8, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c)
+ o = (*z).CompoundMessage.Proposal.MarshalMsg(o)
+ // string "Vote"
+ o = append(o, 0xa4, 0x56, 0x6f, 0x74, 0x65)
+ o = (*z).CompoundMessage.Vote.MarshalMsg(o)
+ if (zb0001Mask & 0x4) == 0 { // if not empty
+ // string "MessageHandle"
+ o = append(o, 0xad, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65)
+ o = (*z).MessageHandle.MarshalMsg(o)
+ }
+ // string "Proposal"
+ o = append(o, 0xa8, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c)
+ o = (*z).Proposal.MarshalMsg(o)
+ // string "Tag"
+ o = append(o, 0xa3, 0x54, 0x61, 0x67)
+ o = (*z).Tag.MarshalMsg(o)
+ // string "UnauthenticatedBundle"
+ o = append(o, 0xb5, 0x55, 0x6e, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65)
+ o = (*z).UnauthenticatedBundle.MarshalMsg(o)
+ // string "UnauthenticatedProposal"
+ o = append(o, 0xb7, 0x55, 0x6e, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c)
+ o = (*z).UnauthenticatedProposal.MarshalMsg(o)
+ // string "UnauthenticatedVote"
+ o = append(o, 0xb3, 0x55, 0x6e, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x56, 0x6f, 0x74, 0x65)
+ o = (*z).UnauthenticatedVote.MarshalMsg(o)
+ // string "Vote"
+ o = append(o, 0xa4, 0x56, 0x6f, 0x74, 0x65)
+ o = (*z).Vote.MarshalMsg(o)
+ }
return
}
diff --git a/catchup/catchpointService_test.go b/catchup/catchpointService_test.go
index de91b456e..48cea110d 100644
--- a/catchup/catchpointService_test.go
+++ b/catchup/catchpointService_test.go
@@ -27,6 +27,7 @@ import (
"github.com/algorand/go-algorand/data/basics"
"github.com/algorand/go-algorand/data/bookkeeping"
"github.com/algorand/go-algorand/ledger"
+ "github.com/algorand/go-algorand/ledger/ledgercore"
"github.com/algorand/go-algorand/protocol"
"github.com/algorand/go-algorand/test/partitiontest"
)
@@ -76,6 +77,11 @@ func (m *catchpointCatchupAccessorMock) Ledger() (l ledger.CatchupAccessorClient
return m.l
}
+// GetVerifyData returns the balances hash, spver hash and totals used by VerifyCatchpoint
+func (m *catchpointCatchupAccessorMock) GetVerifyData(ctx context.Context) (balancesHash crypto.Digest, spverHash crypto.Digest, totals ledgercore.AccountTotals, err error) {
+ return crypto.Digest{}, crypto.Digest{}, ledgercore.AccountTotals{}, nil
+}
+
// TestCatchpointServicePeerRank ensures CatchpointService does not crash when a block fetched
// from the local ledger and not from network when ranking a peer
func TestCatchpointServicePeerRank(t *testing.T) {
diff --git a/catchup/service.go b/catchup/service.go
index 5a25a8c9b..5c08e60e3 100644
--- a/catchup/service.go
+++ b/catchup/service.go
@@ -20,6 +20,7 @@ import (
"context"
"errors"
"fmt"
+ "strings"
"sync"
"sync/atomic"
"time"
@@ -744,17 +745,19 @@ func (s *Service) fetchRound(cert agreement.Certificate, verifier *agreement.Asy
if cert.Round == fetchedCert.Round &&
cert.Proposal.BlockDigest != fetchedCert.Proposal.BlockDigest &&
fetchedCert.Authenticate(*block, s.ledger, verifier) == nil {
- s := "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n"
- s += "!!!!!!!!!! FORK DETECTED !!!!!!!!!!!\n"
- s += "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n"
- s += "fetchRound called with a cert authenticating block with hash %v.\n"
- s += "We fetched a valid cert authenticating a different block, %v. This indicates a fork.\n\n"
- s += "Cert from our agreement service:\n%#v\n\n"
- s += "Cert from the fetcher:\n%#v\n\n"
- s += "Block from the fetcher:\n%#v\n\n"
- s += "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n"
- s += "!!!!!!!!!! FORK DETECTED !!!!!!!!!!!\n"
- s += "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n"
+ var builder strings.Builder
+ builder.WriteString("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n")
+ builder.WriteString("!!!!!!!!!! FORK DETECTED !!!!!!!!!!!\n")
+ builder.WriteString("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n")
+ builder.WriteString("fetchRound called with a cert authenticating block with hash %v.\n")
+ builder.WriteString("We fetched a valid cert authenticating a different block, %v. This indicates a fork.\n\n")
+ builder.WriteString("Cert from our agreement service:\n%#v\n\n")
+ builder.WriteString("Cert from the fetcher:\n%#v\n\n")
+ builder.WriteString("Block from the fetcher:\n%#v\n\n")
+ builder.WriteString("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n")
+ builder.WriteString("!!!!!!!!!! FORK DETECTED !!!!!!!!!!!\n")
+ builder.WriteString("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n")
+ s := builder.String()
s = fmt.Sprintf(s, cert.Proposal.BlockDigest, fetchedCert.Proposal.BlockDigest, cert, fetchedCert, block)
fmt.Println(s)
logging.Base().Error(s)
diff --git a/cmd/algons/commands.go b/cmd/algons/commands.go
index 15469dd2b..3e010951d 100644
--- a/cmd/algons/commands.go
+++ b/cmd/algons/commands.go
@@ -25,6 +25,7 @@ import (
func init() {
rootCmd.AddCommand(dnsCmd)
+ rootCmd.AddCommand(dnsaddrCmd)
}
var rootCmd = &cobra.Command{
diff --git a/cmd/algons/dnsaddrCmd.go b/cmd/algons/dnsaddrCmd.go
new file mode 100644
index 000000000..1df13cecf
--- /dev/null
+++ b/cmd/algons/dnsaddrCmd.go
@@ -0,0 +1,65 @@
+// Copyright (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 <https://www.gnu.org/licenses/>.
+
+package main
+
+import (
+ "fmt"
+
+ "github.com/spf13/cobra"
+
+ "github.com/algorand/go-algorand/network/p2p/dnsaddr"
+)
+
+var (
+ dnsaddrDomain string
+ secure bool
+)
+
+func init() {
+ dnsaddrCmd.AddCommand(dnsaddrTreeCmd)
+
+ dnsaddrTreeCmd.Flags().StringVarP(&dnsaddrDomain, "domain", "d", "", "Top level domain")
+ dnsaddrTreeCmd.MarkFlagRequired("domain")
+ dnsaddrTreeCmd.Flags().BoolVarP(&secure, "secure", "s", true, "Enable dnssec")
+}
+
+var dnsaddrCmd = &cobra.Command{
+ Use: "dnsaddr",
+ Short: "Get, Set, and List Dnsaddr entries",
+ Long: "Get, Set, and List Dnsaddr entries",
+ Run: func(cmd *cobra.Command, args []string) {
+ // Fall back
+ cmd.HelpFunc()(cmd, args)
+ },
+}
+
+var dnsaddrTreeCmd = &cobra.Command{
+ Use: "tree",
+ Short: "Recursively resolves and lists the dnsaddr entries of the given domain",
+ Long: "Recursively resolves and lists the dnsaddr entries of the given domain",
+ Run: func(cmd *cobra.Command, args []string) {
+ controller := dnsaddr.NewMultiaddrDNSResolveController(secure, "")
+ addrs, err := dnsaddr.MultiaddrsFromResolver(dnsaddrDomain, controller)
+ if err != nil {
+ fmt.Printf("%s\n", err.Error())
+ return
+ }
+ for _, addr := range addrs {
+ fmt.Printf("%s\n", addr.String())
+ }
+ },
+}
diff --git a/cmd/catchpointdump/file.go b/cmd/catchpointdump/file.go
index e99a0b406..2e3580b76 100644
--- a/cmd/catchpointdump/file.go
+++ b/cmd/catchpointdump/file.go
@@ -35,6 +35,7 @@ import (
"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/crypto"
"github.com/algorand/go-algorand/data/basics"
"github.com/algorand/go-algorand/data/bookkeeping"
"github.com/algorand/go-algorand/ledger"
@@ -48,11 +49,13 @@ import (
var catchpointFile string
var outFileName string
var excludedFields = cmdutil.MakeCobraStringSliceValue(nil, []string{"version", "catchpoint"})
+var printDigests bool
func init() {
fileCmd.Flags().StringVarP(&catchpointFile, "tar", "t", "", "Specify the catchpoint file (either .tar or .tar.gz) to process")
fileCmd.Flags().StringVarP(&outFileName, "output", "o", "", "Specify an outfile for the dump ( i.e. tracker.dump.txt )")
fileCmd.Flags().BoolVarP(&loadOnly, "load", "l", false, "Load only, do not dump")
+ fileCmd.Flags().BoolVarP(&printDigests, "digest", "d", false, "Print balances and spver digests")
fileCmd.Flags().VarP(excludedFields, "exclude-fields", "e", "List of fields to exclude from the dump: ["+excludedFields.AllowedString()+"]")
}
@@ -206,6 +209,18 @@ func loadCatchpointIntoDatabase(ctx context.Context, catchupAccessor ledger.Catc
header, err := tarReader.Next()
if err != nil {
if err == io.EOF {
+ if printDigests {
+ err = catchupAccessor.BuildMerkleTrie(ctx, func(uint64, uint64) {})
+ if err != nil {
+ return fileHeader, err
+ }
+ var balanceHash, spverHash crypto.Digest
+ balanceHash, spverHash, _, err = catchupAccessor.GetVerifyData(ctx)
+ if err != nil {
+ return fileHeader, err
+ }
+ fmt.Printf("accounts digest=%s, spver digest=%s\n\n", balanceHash, spverHash)
+ }
return fileHeader, nil
}
return fileHeader, err
diff --git a/cmd/goal/clerk.go b/cmd/goal/clerk.go
index ebdae1fa0..80689635b 100644
--- a/cmd/goal/clerk.go
+++ b/cmd/goal/clerk.go
@@ -73,8 +73,11 @@ var (
simulateAllowMoreLogging bool
simulateAllowMoreOpcodeBudget bool
simulateExtraOpcodeBudget uint64
- simulateEnableRequestTrace bool
- simulateStackChange bool
+
+ simulateFullTrace bool
+ simulateEnableRequestTrace bool
+ simulateStackChange bool
+ simulateScratchChange bool
)
func init() {
@@ -98,7 +101,7 @@ func init() {
sendCmd.Flags().Uint64VarP(&amount, "amount", "a", 0, "The amount to be transferred (required), in microAlgos")
sendCmd.Flags().StringVarP(&closeToAddress, "close-to", "c", "", "Close account and send remainder to this address")
sendCmd.Flags().StringVar(&rekeyToAddress, "rekey-to", "", "Rekey account to the given spending key/address. (Future transactions from this account will need to be signed with the new key.)")
- sendCmd.Flags().StringVarP(&programSource, "from-program", "F", "", "Program source to use as account logic")
+ sendCmd.Flags().StringVarP(&programSource, "from-program", "F", "", "Program source file to use as account logic")
sendCmd.Flags().StringVarP(&progByteFile, "from-program-bytes", "P", "", "Program binary to use as account logic")
sendCmd.Flags().StringSliceVar(&argB64Strings, "argb64", nil, "Base64 encoded args to pass to transaction logic")
sendCmd.Flags().StringVarP(&logicSigFile, "logic-sig", "L", "", "LogicSig to apply to transaction")
@@ -118,7 +121,7 @@ func init() {
signCmd.Flags().StringVarP(&txFilename, "infile", "i", "", "Partially-signed transaction file to add signature to")
signCmd.Flags().StringVarP(&outFilename, "outfile", "o", "", "Filename for writing the signed transaction")
signCmd.Flags().StringVarP(&signerAddress, "signer", "S", "", "Address of key to sign with, if different from transaction \"from\" address due to rekeying")
- signCmd.Flags().StringVarP(&programSource, "program", "p", "", "Program source to use as account logic")
+ signCmd.Flags().StringVarP(&programSource, "program", "p", "", "Program source file to use as account logic")
signCmd.Flags().StringVarP(&logicSigFile, "logic-sig", "L", "", "LogicSig to apply to transaction")
signCmd.Flags().StringSliceVar(&argB64Strings, "argb64", nil, "Base64 encoded args to pass to transaction logic")
signCmd.Flags().StringVarP(&protoVersion, "proto", "P", "", "Consensus protocol version id string")
@@ -163,8 +166,11 @@ func init() {
simulateCmd.Flags().BoolVar(&simulateAllowMoreLogging, "allow-more-logging", false, "Lift the limits on log opcode during simulation")
simulateCmd.Flags().BoolVar(&simulateAllowMoreOpcodeBudget, "allow-more-opcode-budget", false, "Apply max extra opcode budget for apps per transaction group (default 320000) during simulation")
simulateCmd.Flags().Uint64Var(&simulateExtraOpcodeBudget, "extra-opcode-budget", 0, "Apply extra opcode budget for apps per transaction group during simulation")
+
+ simulateCmd.Flags().BoolVar(&simulateFullTrace, "full-trace", false, "Enable all options for simulation execution trace")
simulateCmd.Flags().BoolVar(&simulateEnableRequestTrace, "trace", false, "Enable simulation time execution trace of app calls")
simulateCmd.Flags().BoolVar(&simulateStackChange, "stack", false, "Report stack change during simulation time")
+ simulateCmd.Flags().BoolVar(&simulateScratchChange, "scratch", false, "Report scratch change during simulation time")
}
var clerkCmd = &cobra.Command{
@@ -445,7 +451,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, nil)
if err == nil {
err = verify.LogicSigSanityCheck(0, groupCtx)
}
@@ -846,7 +852,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, nil)
if err != nil {
// this error has to be unsupported protocol
reportErrorf("%s: %v", txFilename, err)
@@ -1118,7 +1124,6 @@ var dryrunCmd = &cobra.Command{
Long: "Test a TEAL program offline under various conditions and verbosity.",
Run: func(cmd *cobra.Command, args []string) {
stxns := decodeTxnsFromFile(txFilename)
- txgroup := transactions.WrapSignedTxnsWithAD(stxns)
proto, params := getProto(protoVersion)
if dumpForDryrun {
// Write dryrun data to file
@@ -1139,15 +1144,14 @@ var dryrunCmd = &cobra.Command{
if timeStamp <= 0 {
timeStamp = time.Now().Unix()
}
- for i, txn := range txgroup {
+ for i, txn := range stxns {
if txn.Lsig.Blank() {
continue
}
if uint64(txn.Lsig.Len()) > params.LogicSigMaxSize {
reportErrorf("program size too large: %d > %d", len(txn.Lsig.Logic), params.LogicSigMaxSize)
}
- ep := logic.NewEvalParams(txgroup, &params, nil)
- ep.SigLedger = logic.NoHeaderLedger{}
+ ep := logic.NewSigEvalParams(stxns, &params, logic.NoHeaderLedger{})
err := logic.CheckSignature(i, ep)
if err != nil {
reportErrorf("program failed Check: %s", err)
@@ -1366,8 +1370,17 @@ func decodeTxnsFromFile(file string) []transactions.SignedTxn {
}
func traceCmdOptionToSimulateTraceConfigModel() simulation.ExecTraceConfig {
- return simulation.ExecTraceConfig{
- Enable: simulateEnableRequestTrace,
- Stack: simulateStackChange,
+ var traceConfig simulation.ExecTraceConfig
+ if simulateFullTrace {
+ traceConfig = simulation.ExecTraceConfig{
+ Enable: true,
+ Stack: true,
+ Scratch: true,
+ }
}
+ traceConfig.Enable = traceConfig.Enable || simulateEnableRequestTrace
+ traceConfig.Stack = traceConfig.Stack || simulateStackChange
+ traceConfig.Scratch = traceConfig.Scratch || simulateScratchChange
+
+ return traceConfig
}
diff --git a/cmd/tealdbg/debugger_test.go b/cmd/tealdbg/debugger_test.go
index 295a30b00..8bcbd4bbc 100644
--- a/cmd/tealdbg/debugger_test.go
+++ b/cmd/tealdbg/debugger_test.go
@@ -103,17 +103,13 @@ func TestDebuggerSimple(t *testing.T) {
da := makeTestDbgAdapter(t)
debugger.AddAdapter(da)
- ep := logic.NewEvalParams(make([]transactions.SignedTxnWithAD, 1), &proto, nil)
- ep.Tracer = logic.MakeEvalTracerDebuggerAdaptor(debugger)
- ep.SigLedger = logic.NoHeaderLedger{}
-
- source := `int 0
-int 1
-+
-`
- ops, err := logic.AssembleStringWithVersion(source, 1)
+ ops, err := logic.AssembleStringWithVersion("int 0; int 1; +", 1)
require.NoError(t, err)
- ep.TxnGroup[0].Lsig.Logic = ops.Program
+ txn := transactions.SignedTxn{}
+ txn.Lsig.Logic = ops.Program
+
+ ep := logic.NewSigEvalParams([]transactions.SignedTxn{txn}, &proto, logic.NoHeaderLedger{})
+ ep.Tracer = logic.MakeEvalTracerDebuggerAdaptor(debugger)
_, err = logic.EvalSignature(0, ep)
require.NoError(t, err)
diff --git a/cmd/tealdbg/local.go b/cmd/tealdbg/local.go
index d3a9e57fc..cd319cf6d 100644
--- a/cmd/tealdbg/local.go
+++ b/cmd/tealdbg/local.go
@@ -237,13 +237,13 @@ type evaluation struct {
states AppState
}
-func (e *evaluation) eval(gi int, ep *logic.EvalParams) (pass bool, err error) {
+func (e *evaluation) eval(gi int, sep *logic.EvalParams, aep *logic.EvalParams) (pass bool, err error) {
if e.mode == modeStateful {
- pass, _, err = e.ba.StatefulEval(gi, ep, e.aidx, e.program)
+ pass, _, err = e.ba.StatefulEval(gi, aep, e.aidx, e.program)
return
}
- ep.TxnGroup[gi].Lsig.Logic = e.program
- return logic.EvalSignature(gi, ep)
+ sep.TxnGroup[gi].Lsig.Logic = e.program
+ return logic.EvalSignature(gi, sep)
}
// LocalRunner runs local eval
@@ -531,23 +531,17 @@ func (r *LocalRunner) RunAll() error {
return fmt.Errorf("no program to debug")
}
- configureDebugger := func(ep *logic.EvalParams) {
- // Workaround for Go's nil/empty interfaces nil check after nil assignment, i.e.
- // r.debugger = nil
- // ep.Debugger = r.debugger
- // if ep.Debugger != nil // FALSE
- if r.debugger != nil {
- ep.Tracer = logic.MakeEvalTracerDebuggerAdaptor(r.debugger)
- }
- }
-
txngroup := transactions.WrapSignedTxnsWithAD(r.txnGroup)
failed := 0
start := time.Now()
- ep := logic.NewEvalParams(txngroup, &r.proto, &transactions.SpecialAddresses{})
- ep.SigLedger = logic.NoHeaderLedger{}
- configureDebugger(ep)
+ sep := logic.NewSigEvalParams(r.txnGroup, &r.proto, &logic.NoHeaderLedger{})
+ aep := logic.NewAppEvalParams(txngroup, &r.proto, &transactions.SpecialAddresses{})
+ if r.debugger != nil {
+ t := logic.MakeEvalTracerDebuggerAdaptor(r.debugger)
+ sep.Tracer = t
+ aep.Tracer = t
+ }
var last error
for i := range r.runs {
@@ -556,7 +550,7 @@ func (r *LocalRunner) RunAll() error {
r.debugger.SaveProgram(run.name, run.program, run.source, run.offsetToLine, run.states)
}
- run.result.pass, run.result.err = run.eval(int(run.groupIndex), ep)
+ run.result.pass, run.result.err = run.eval(int(run.groupIndex), sep, aep)
if run.result.err != nil {
failed++
last = run.result.err
diff --git a/cmd/tealdbg/localLedger.go b/cmd/tealdbg/localLedger.go
index 03cf088f4..64dfe7329 100644
--- a/cmd/tealdbg/localLedger.go
+++ b/cmd/tealdbg/localLedger.go
@@ -281,10 +281,6 @@ func (l *localLedger) BlockHdr(basics.Round) (bookkeeping.BlockHeader, error) {
return bookkeeping.BlockHeader{}, nil
}
-func (l *localLedger) BlockHdrCached(basics.Round) (bookkeeping.BlockHeader, error) {
- return bookkeeping.BlockHeader{}, nil
-}
-
func (l *localLedger) GetStateProofVerificationContext(_ basics.Round) (*ledgercore.StateProofVerificationContext, error) {
return nil, fmt.Errorf("localLedger: GetStateProofVerificationContext, needed for state proof verification, is not implemented in debugger")
}
diff --git a/cmd/tealdbg/localLedger_test.go b/cmd/tealdbg/localLedger_test.go
index 623215b46..38258c54d 100644
--- a/cmd/tealdbg/localLedger_test.go
+++ b/cmd/tealdbg/localLedger_test.go
@@ -112,7 +112,7 @@ int 2
a.NoError(err)
proto := config.Consensus[protocol.ConsensusCurrentVersion]
- ep := logic.NewEvalParams([]transactions.SignedTxnWithAD{{SignedTxn: txn}}, &proto, &transactions.SpecialAddresses{})
+ ep := logic.NewAppEvalParams([]transactions.SignedTxnWithAD{{SignedTxn: txn}}, &proto, &transactions.SpecialAddresses{})
pass, delta, err := ba.StatefulEval(0, ep, appIdx, program)
a.NoError(err)
a.True(pass)
diff --git a/components/mocks/mockCatchpointCatchupAccessor.go b/components/mocks/mockCatchpointCatchupAccessor.go
index 0b45f42be..f488879e7 100644
--- a/components/mocks/mockCatchpointCatchupAccessor.go
+++ b/components/mocks/mockCatchpointCatchupAccessor.go
@@ -19,9 +19,11 @@ package mocks
import (
"context"
+ "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"
+ "github.com/algorand/go-algorand/ledger/ledgercore"
)
// MockCatchpointCatchupAccessor is a dummy CatchpointCatchupAccessor implementation which doesn't do anything.
@@ -67,6 +69,11 @@ func (m *MockCatchpointCatchupAccessor) GetCatchupBlockRound(ctx context.Context
return basics.Round(0), nil
}
+// GetVerifyData returns the balances hash, spver hash and totals used by VerifyCatchpoint
+func (m *MockCatchpointCatchupAccessor) GetVerifyData(ctx context.Context) (balancesHash crypto.Digest, spverHash crypto.Digest, totals ledgercore.AccountTotals, err error) {
+ return crypto.Digest{}, crypto.Digest{}, ledgercore.AccountTotals{}, nil
+}
+
// VerifyCatchpoint verifies that the catchpoint is valid by reconstructing the label.
func (m *MockCatchpointCatchupAccessor) VerifyCatchpoint(ctx context.Context, blk *bookkeeping.Block) (err error) {
return nil
diff --git a/config/consensus.go b/config/consensus.go
index 01d884863..8c217c18c 100644
--- a/config/consensus.go
+++ b/config/consensus.go
@@ -112,6 +112,10 @@ type ConsensusParams struct {
// rather than check each individual app call is within the budget.
EnableAppCostPooling bool
+ // EnableLogicSigCostPooling specifies LogicSig budgets are pooled across a
+ // group. The total available is len(group) * LogicSigMaxCost)
+ EnableLogicSigCostPooling bool
+
// RewardUnit specifies the number of MicroAlgos corresponding to one reward
// unit.
//
@@ -1356,6 +1360,7 @@ func initConsensusProtocols() {
vFuture.ApprovedUpgrades = map[protocol.ConsensusVersion]uint64{}
vFuture.LogicSigVersion = 10 // When moving this to a release, put a new higher LogicSigVersion here
+ vFuture.EnableLogicSigCostPooling = true
Consensus[protocol.ConsensusFuture] = vFuture
diff --git a/config/localTemplate.go b/config/localTemplate.go
index 52abd0610..672a55abd 100644
--- a/config/localTemplate.go
+++ b/config/localTemplate.go
@@ -517,15 +517,24 @@ type Local struct {
// only relevant if TxIncomingFilteringFlags is non-zero
TxIncomingFilterMaxSize uint64 `version[28]:"500000"`
+ SpeculativeAsmTimeOffset time.Duration `version[30]:"400000000"`
+ SpeculativeAssemblyDisable bool `version[30]:"false"`
+
// BlockServiceMemCap is the memory capacity in bytes which is allowed for the block service to use for HTTP block requests.
// When it exceeds this capacity, it redirects the block requests to a different node
BlockServiceMemCap uint64 `version[28]:"500000000"`
- // SpeculativeAsmTimeOffset defines when speculative block assembly first starts, nanoseconds before consensus AgreementFilterTimeoutPeriod0 or AgreementFilterTimeout
- // A huge value (greater than either AgreementFilterTimeout) disables this event.
- SpeculativeAsmTimeOffset time.Duration `version[29]:"400000000"`
+ // P2PEnable turns on the peer to peer network
+ P2PEnable bool `version[29]:"false"`
+
+ // P2PPersistPeerID will write the private key used for the node's PeerID to the P2PPrivateKeyLocation.
+ // This is only used when P2PEnable is true. If P2PPrivateKey is not specified, it uses the default location.
+ P2PPersistPeerID bool `version[29]:"true"`
- SpeculativeAssemblyDisable bool `version[29]:"false"`
+ // P2PPrivateKeyLocation allows the user to specify a custom path to the private key used for the node's PeerID.
+ // The private key provided must be an ed25519 private key.
+ // This is only used when P2PEnable is true. If the parameter is not set, it uses the default location.
+ P2PPrivateKeyLocation string `version[29]:""`
}
// DNSBootstrapArray returns an array of one or more DNS Bootstrap identifiers
diff --git a/config/local_defaults.go b/config/local_defaults.go
index b9dcc4bb9..b92833daf 100644
--- a/config/local_defaults.go
+++ b/config/local_defaults.go
@@ -106,6 +106,9 @@ var defaultLocal = Local{
OptimizeAccountsDatabaseOnStartup: false,
OutgoingMessageFilterBucketCount: 3,
OutgoingMessageFilterBucketSize: 128,
+ P2PEnable: false,
+ P2PPersistPeerID: true,
+ P2PPrivateKeyLocation: "",
ParticipationKeysRefreshInterval: 60000000000,
PeerConnectionsUpdateInterval: 3600,
PeerPingPeriodSeconds: 0,
@@ -119,7 +122,7 @@ var defaultLocal = Local{
RestReadTimeoutSeconds: 15,
RestWriteTimeoutSeconds: 120,
RunHosted: false,
- SpeculativeAsmTimeOffset: 400000000,
+ SpeculativeAsmTimeOffset: 0,
SpeculativeAssemblyDisable: false,
StorageEngine: "sqlite",
SuggestedFeeBlockHistory: 3,
diff --git a/daemon/algod/api/algod.oas2.json b/daemon/algod/api/algod.oas2.json
index 3573a7c17..02e2349de 100644
--- a/daemon/algod/api/algod.oas2.json
+++ b/daemon/algod/api/algod.oas2.json
@@ -1111,7 +1111,7 @@
"public",
"nonparticipating"
],
- "description": "Waits for a block to appear after round {round} and returns the node's status at the time.",
+ "description": "Waits for a block to appear after round {round} and returns the node's status at the time. There is a 1 minute timeout, when reached the current status is returned regardless of whether or not it is the round after the given round.",
"produces": [
"application/json"
],
@@ -1132,10 +1132,11 @@
],
"responses": {
"200": {
+ "description": "The round after the given round, or the current round if a timeout occurs.",
"$ref": "#/responses/NodeStatusResponse"
},
"400": {
- "description": "Bad Request -- number must be non-negative integer ",
+ "description": "Bad Request -- number must be non-negative integer",
"schema": {
"$ref": "#/definitions/ErrorResponse"
}
@@ -3518,6 +3519,10 @@
"stack-change": {
"description": "A boolean option enabling returning stack changes together with execution trace during simulation.",
"type": "boolean"
+ },
+ "scratch-change": {
+ "description": "A boolean option enabling returning scratch slot changes together with execution trace during simulation.",
+ "type": "boolean"
}
}
},
@@ -3880,6 +3885,23 @@
}
}
},
+ "ScratchChange": {
+ "description": "A write operation into a scratch slot.",
+ "type": "object",
+ "required": [
+ "slot",
+ "new-value"
+ ],
+ "properties": {
+ "slot": {
+ "description": "The scratch slot written.",
+ "type": "integer"
+ },
+ "new-value": {
+ "$ref": "#/definitions/AvmValue"
+ }
+ }
+ },
"SimulationOpcodeTraceUnit": {
"description": "The set of trace information and effect from evaluating a single opcode.",
"type": "object",
@@ -3891,6 +3913,13 @@
"description": "The program counter of the current opcode being evaluated.",
"type": "integer"
},
+ "scratch-changes": {
+ "description": "The writes into scratch slots.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/ScratchChange"
+ }
+ },
"spawned-inners": {
"description": "The indexes of the traces for inner transactions spawned by this opcode, if any.",
"type": "array",
diff --git a/daemon/algod/api/algod.oas3.yml b/daemon/algod/api/algod.oas3.yml
index 13017f6d7..5b784af1a 100644
--- a/daemon/algod/api/algod.oas3.yml
+++ b/daemon/algod/api/algod.oas3.yml
@@ -1934,6 +1934,23 @@
],
"type": "object"
},
+ "ScratchChange": {
+ "description": "A write operation into a scratch slot.",
+ "properties": {
+ "new-value": {
+ "$ref": "#/components/schemas/AvmValue"
+ },
+ "slot": {
+ "description": "The scratch slot written.",
+ "type": "integer"
+ }
+ },
+ "required": [
+ "new-value",
+ "slot"
+ ],
+ "type": "object"
+ },
"SimulateRequest": {
"description": "Request type for simulation endpoint.",
"properties": {
@@ -1991,6 +2008,10 @@
"description": "A boolean option for opting in execution trace features simulation endpoint.",
"type": "boolean"
},
+ "scratch-change": {
+ "description": "A boolean option enabling returning scratch slot changes together with execution trace during simulation.",
+ "type": "boolean"
+ },
"stack-change": {
"description": "A boolean option enabling returning stack changes together with execution trace during simulation.",
"type": "boolean"
@@ -2085,6 +2106,13 @@
"description": "The program counter of the current opcode being evaluated.",
"type": "integer"
},
+ "scratch-changes": {
+ "description": "The writes into scratch slots.",
+ "items": {
+ "$ref": "#/components/schemas/ScratchChange"
+ },
+ "type": "array"
+ },
"spawned-inners": {
"description": "The indexes of the traces for inner transactions spawned by this opcode, if any.",
"items": {
@@ -5471,7 +5499,7 @@
},
"/v2/status/wait-for-block-after/{round}": {
"get": {
- "description": "Waits for a block to appear after round {round} and returns the node's status at the time.",
+ "description": "Waits for a block to appear after round {round} and returns the node's status at the time. There is a 1 minute timeout, when reached the current status is returned regardless of whether or not it is the round after the given round.",
"operationId": "WaitForBlock",
"parameters": [
{
@@ -5620,7 +5648,7 @@
}
}
},
- "description": "Bad Request -- number must be non-negative integer "
+ "description": "Bad Request -- number must be non-negative integer"
},
"401": {
"content": {
diff --git a/daemon/algod/api/server/v2/dryrun.go b/daemon/algod/api/server/v2/dryrun.go
index 685ec1e68..ef0de8085 100644
--- a/daemon/algod/api/server/v2/dryrun.go
+++ b/daemon/algod/api/server/v2/dryrun.go
@@ -241,10 +241,6 @@ func (dl *dryrunLedger) BlockHdr(basics.Round) (bookkeeping.BlockHeader, error)
return bookkeeping.BlockHeader{}, nil
}
-func (dl *dryrunLedger) BlockHdrCached(basics.Round) (bookkeeping.BlockHeader, error) {
- return bookkeeping.BlockHeader{}, nil
-}
-
func (dl *dryrunLedger) CheckDup(config.ConsensusParams, basics.Round, basics.Round, basics.Round, transactions.Txid, ledgercore.Txlease) error {
return nil
}
@@ -399,7 +395,8 @@ func doDryrunRequest(dr *DryrunRequest, response *model.DryrunResponse) {
proto := config.Consensus[protocol.ConsensusVersion(dr.ProtocolVersion)]
txgroup := transactions.WrapSignedTxnsWithAD(dr.Txns)
specials := transactions.SpecialAddresses{}
- ep := logic.NewEvalParams(txgroup, &proto, &specials)
+ ep := logic.NewAppEvalParams(txgroup, &proto, &specials)
+ sep := logic.NewSigEvalParams(dr.Txns, &proto, &dl)
origEnableAppCostPooling := proto.EnableAppCostPooling
// Enable EnableAppCostPooling so that dryrun
@@ -421,11 +418,10 @@ func doDryrunRequest(dr *DryrunRequest, response *model.DryrunResponse) {
response.Txns = make([]model.DryrunTxnResult, len(dr.Txns))
for ti, stxn := range dr.Txns {
var result model.DryrunTxnResult
- if len(stxn.Lsig.Logic) > 0 {
+ if !stxn.Lsig.Blank() {
var debug dryrunDebugReceiver
- ep.Tracer = logic.MakeEvalTracerDebuggerAdaptor(&debug)
- ep.SigLedger = &dl
- pass, err := logic.EvalSignature(ti, ep)
+ sep.Tracer = logic.MakeEvalTracerDebuggerAdaptor(&debug)
+ pass, err := logic.EvalSignature(ti, sep)
var messages []string
result.Disassembly = debug.lines // Keep backwards compat
result.LogicSigDisassembly = &debug.lines // Also add to Lsig specific
diff --git a/daemon/algod/api/server/v2/dryrun_test.go b/daemon/algod/api/server/v2/dryrun_test.go
index 2f43b12a6..99e060118 100644
--- a/daemon/algod/api/server/v2/dryrun_test.go
+++ b/daemon/algod/api/server/v2/dryrun_test.go
@@ -363,6 +363,7 @@ func init() {
}
func checkLogicSigPass(t *testing.T, response *model.DryrunResponse) {
+ t.Helper()
if len(response.Txns) < 1 {
t.Error("no response txns")
} else if len(response.Txns) == 0 {
diff --git a/daemon/algod/api/server/v2/generated/data/routes.go b/daemon/algod/api/server/v2/generated/data/routes.go
index e471dfa46..af7c75e2d 100644
--- a/daemon/algod/api/server/v2/generated/data/routes.go
+++ b/daemon/algod/api/server/v2/generated/data/routes.go
@@ -115,186 +115,188 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL
var swaggerSpec = []string{
"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",
- "v18gEyBBEqxidbelmYv5JHURj0QikcgXMt/PcrWplARpzez5+1nFNd+ABY1/8TxXtbSZKNxfBZhci8oK",
- "JWfPwzdmrBZyNZvPhPu14nY9m88k30DbxvWfzzT8rRYaitlzq2uYz0y+hg13A9td5Vo3I22zlcr8EGc0",
- "xPnL2Yc9H3hRaDBmCOVPstwxIfOyLoBZzaXhuftk2I2wa2bXwjDfmQnJlASmlsyuO43ZUkBZmJOwyL/V",
- "oHfRKv3k40v60IKYaVXCEM4XarMQEgJU0ADVbAizihWwxEZrbpmbwcEaGlrFDHCdr9lS6QOgEhAxvCDr",
- "zez5m5kBWYDG3cpBXON/lxrgd8gs1yuws3fz1OKWFnRmxSaxtHOPfQ2mLq1h2BbXuBLXIJnrdcJ+qI1l",
- "C2Bcsp+/fcGePn36lVvIhlsLhSey0VW1s8drou6z57OCWwifh7TGy5XSXBZZ0/7nb1/g/Bd+gVNbcWMg",
- "fVjO3Bd2/nJsAaFjgoSEtLDCfehQv+uROBTtzwtYKg0T94Qa3+umxPN/0l3Juc3XlRLSJvaF4VdGn5M8",
- "LOq+j4c1AHTaVw5T2g365lH21bv3j+ePH334lzdn2X/7P794+mHi8l804x7AQLJhXmsNMt9lKw0cT8ua",
- "yyE+fvb0YNaqLgu25te4+XyDrN73Za4vsc5rXtaOTkSu1Vm5UoZxT0YFLHldWhYmZrUsHZtyo3lqZ8Kw",
- "SqtrUUAxd9z3Zi3yNcu5oSGwHbsRZelosDZQjNFaenV7DtOHGCUOrlvhAxf094uMdl0HMAFb5AZZXioD",
- "mVUHrqdw43BZsPhCae8qc9xlxS7XwHBy94EuW8SddDRdljtmcV8Lxg3jLFxNcyaWbKdqdoObU4or7O9X",
- "47C2YQ5puDmde9Qd3jH0DZCRQN5CqRK4ROSFczdEmVyKVa3BsJs12LW/8zSYSkkDTC3+Crl12/6/Ln76",
- "kSnNfgBj+Ape8/yKgcxVAcUJO18yqWxEGp6WEIeu59g6PFypS/6vRjma2JhVxfOr9I1eio1IrOoHvhWb",
- "esNkvVmAdlsarhCrmAZbazkGEI14gBQ3fDuc9FLXMsf9b6ftyHKO2oSpSr5DhG349s+P5h4cw3hZsgpk",
- "IeSK2a0clePc3IfBy7SqZTFBzLFuT6OL1VSQi6WAgjWj7IHET3MIHiGPg6cVviJwwiCj4DSzHABHwjZB",
- "M+50uy+s4iuISOaE/eKZG3616gpkQ+hsscNPlYZroWrTdBqBEafeL4FLZSGrNCxFgsYuPDocg6E2ngNv",
- "vAyUK2m5kFA45oxAKwvErEZhiibcr+8Mb/EFN/Dls7E7vv06cfeXqr/re3d80m5jo4yOZOLqdF/9gU1L",
- "Vp3+E/TDeG4jVhn9PNhIsbp0t81SlHgT/dXtX0BDbZAJdBAR7iYjVpLbWsPzt/Kh+4tl7MJyWXBduF82",
- "9NMPdWnFhVi5n0r66ZVaifxCrEaQ2cCaVLiw24b+ceOl2bHdJvWKV0pd1VW8oLyjuC527Pzl2CbTmMcS",
- "5lmj7caKx+U2KCPH9rDbZiNHgBzFXcVdwyvYaXDQ8nyJ/2yXSE98qX93/1RV6XrbaplCraNjfyWj+cCb",
- "Fc6qqhQ5d0j82X92Xx0TAFIkeNviFC/U5+8jECutKtBW0KC8qrJS5bzMjOUWR/pXDcvZ89m/nLb2l1Pq",
- "bk6jyV+5XhfYyYmsJAZlvKqOGOO1E33MHmbhGDR+QjZBbA+FJiFpEx0pCceCS7jm0p60KkuHHzQH+I2f",
- "qcU3STuE754KNopwRg0XYEgCpoYPDItQzxCtDNGKAumqVIvmh8/OqqrFIH4/qyrCB0qPIFAwg60w1nyO",
- "y+ftSYrnOX95wr6Lx0ZRXMly5y4HEjXc3bD0t5a/xRrbkl9DO+IDw3A7lT5xWxPQ4MT8+6A4VCvWqnRS",
- "z0FacY3/w7eNycz9PqnzPwaJxbgdJy5UtDzmSMfBXyLl5rMe5QwJx5t7TthZv+/tyMaNkiaYW9HK3v2k",
- "cffgsUHhjeYVAei/0F0qJCpp1IhgvSM3ncjokjBHZziiNYTq1mft4HlIQoKk0IPh61LlV//Bzfoezvwi",
- "jDU8fjgNWwMvQLM1N+uTWUrKiI9XO9qUI+YaooLPFtFUJ80S72t5B5ZWcMujpXl402IJoR77IdMDndBd",
- "fsL/8JK5z+5sO9ZPw56wS2Rgho6zdzIUTtsnBYFmcg3QCqHYhhR85rTuo6B80U6e3qdJe/QN2RT8DvlF",
- "4A6p7b0fg6/VNgXD12o7OAJqC+Y+6MONg2KkhY2ZAN9LD5nC/ffo41rz3RDJOPYUJLsFOtHV4GmQ8Y3v",
- "ZmmNs2cLpW/HfXpsRbLW5My4GzVivvMekrBpXWWeFBNmK2rQG6j18u1nGv3hUxjrYOHC8j8AC8aNeh9Y",
- "6A5031hQm0qUcA+kv04y/QU38PQJu/iPsy8eP/ntyRdfOpKstFppvmGLnQXDPvO6GTN2V8Lnw5WhdlSX",
- "Nj36l8+CobI7bmoco2qdw4ZXw6HIAEoiEDVjrt0Qa10046obAKcczktwnJzQzsi270B7KYyTsDaLe9mM",
- "MYQV7SwF85AUcJCYjl1eO80uXqLe6fo+VFnQWumEfQ2PmFW5KrNr0EaohDfltW/BfIsg3lb93wladsMN",
- "c3Oj6beWKFAkKMtu5XS+T0NfbmWLm72cn9abWJ2fd8q+dJEfLImGVaAzu5WsgEW96mhCS602jLMCO+Id",
- "/R1YFAUuxQYuLN9UPy2X96MqKhwoobKJDRg3E6MWTq43kCtJkRAHtDM/6hT09BETTHR2HACPkYudzNHO",
- "eB/Hdlxx3QiJTg+zk3mkxToYSyhWHbK8u7Y6hg6a6oFJgOPQ8Qo/o6HjJZSWf6v0ZWsJ/E6rurp3Ia8/",
- "59TlcL8Yb0opXN+gQwu5KrvRNysH+0lqjZ9kQS/C8fVrQOiRIl+J1dpGasVrrdTy/mFMzZICFD+QUla6",
- "PkPV7EdVOGZia3MPIlg7WMvhHN3GfI0vVG0ZZ1IVgJtfm7RwNhKvgY5i9G/bWN6za9KzFuCoK+e1W21d",
- "MfTeDu6LtmPGczqhGaLGjPiuGqcjtaLpKBag1MCLHVsASKYW3kHkXVe4SI6uZxvEGy8aJvhFB65KqxyM",
- "gSLzhqmDoIV2dHXYPXhCwBHgZhZmFFtyfWdgr64PwnkFuwwDJQz77PtfzeefAF6rLC8PIBbbpNDbqPne",
- "CziEetr0+wiuP3lMdlwDC/cKswql2RIsjKHwKJyM7l8fosEu3h0t16DRH/eHUnyY5G4E1ID6B9P7XaGt",
- "q5HwP6/eOgnPbZjkUgXBKjVYyY3NDrFl16ijg7sVRJwwxYlx4BHB6xU3lnzIQhZo+qLrBOchIcxNMQ7w",
- "qBriRv41aCDDsXN3D0pTm0YdMXVVKW2hSK1BwnbPXD/CtplLLaOxG53HKlYbODTyGJai8T2yaCWEIG4b",
- "V4sPshguDh0S7p7fJVHZAaJFxD5ALkKrCLtxCNQIIMK0iCbCEaZHOU3c1XxmrKoqxy1sVsum3xiaLqj1",
- "mf2lbTskLm7be7tQYDDyyrf3kN8QZin4bc0N83CwDb9ysgeaQcjZPYTZHcbMCJlDto/yUcVzreIjcPCQ",
- "1tVK8wKyAkq+Gw76C31m9HnfALjjrbqrLGQUxZTe9JaSQ9DInqEVjmdSwiPDLyx3R9CpAi2B+N4HRi4A",
- "x04xJ09HD5qhcK7kFoXxcNm01YkR8Ta8VtbtuKcHBNlz9CkAj+ChGfr2qMDOWat79qf4LzB+gkaOOH6S",
- "HZixJbTjH7WAERuqDxCPzkuPvfc4cJJtjrKxA3xk7MiOGHRfc21FLirUdb6H3b2rfv0Jkm5GVoDlooSC",
- "RR9IDazi/ozib/pj3k4VnGR7G4I/ML4lllMKgyJPF/gr2KHO/ZoCOyNTx33osolR3f3EJUNAQ7iYE8Hj",
- "JrDluS13TlCza9ixG9DATL3YCGspYLur6lpVZfEASb/Gnhm9E4+CIsMOTPEqXuBQ0fKGWzGfkU6wH77L",
- "nmLQQYfXBSqlygkWsgEykhBMivdglXK7LnzseIgeDpTUAdIzbfTgNtf/A9NBM66A/ZeqWc4lqly1hUam",
- "URoFBRQg3QxOBGvm9JEdLYaghA2QJolfHj7sL/zhQ7/nwrAl3IQHF65hHx0PH6Id57UytnO47sEe6o7b",
- "eeL6QIePu/i8FtLnKYcjC/zIU3bydW/wxkvkzpQxnnDd8u/MAHoncztl7TGNTIuqwHEn+XKioVPrxn2/",
- "EJu65PY+vFZwzctMXYPWooCDnNxPLJT85pqXPzXd8DEJ5I5Gc8hyfAIxcSy4dH3o1cQh3bCNJhObDRSC",
- "Wyh3rNKQA0X5O5HPNDCeMIr/y9dcrlDS16pe+QA0Ggc5dW3IpqJrORgiKQ3ZrczQOp3i3D7oODz0cHIQ",
- "cKeL9U3bpHnc8GY+/7ZnypUaIa9v6k96t+azUVXVIfW6VVUJOd3XKhO4eEdQi/DTTjzRB4Koc0LLEF/x",
- "trhT4Db3j7G1t0OnoBxOHIXEtR/HouKcnlzu7kFaoYGYhkqDwbslti8Z+qqW8cs0f/mYnbGwGZrgqetv",
- "I8fv51FFT8lSSMg2SsIu+RhbSPgBPyaPE95vI51R0hjr21ceOvD3wOrOM4Ua74pf3O3+Ce27msy3St+X",
- "L5MGnCyXT3AdHvST+ylv6+DkZZnwCfp3K30GYObNO3mhGTdG5QKFrfPCzOmgeTeif+TSRf/rJhr3Hs5e",
- "f9ye8yt+EonGXSgrxlleCjT9KmmsrnP7VnI0LkVLTUQtBS163Nz4IjRJ2zcT5kc/1FvJMWKtMTklIy2W",
- "kLCvfAsQrI6mXq3A2J6SsgR4K30rIVkthcW5Nu64ZHReKtAYOnRCLTd8x5aOJqxiv4NWbFHbrtiOz7KM",
- "FWXpPXFuGqaWbyW3rARuLPtByMstDhe89eHISrA3Sl81WEjf7iuQYITJ0tFV39FXDHz1y1/7IFh8Rk+f",
- "yXfjxm/fbu3Q9tQ+Df8/n/378zdn2X/z7PdH2Vf/4/Td+2cfPn84+PHJhz//+f92f3r64c+f//u/pnYq",
- "wJ56NOQhP3/pVdrzl6i3tM6bAewfzXC/ETJLElkchtGjLfYZPpD1BPR516pl1/BW2q10hHTNS1E43nIb",
- "cujfMIOzSKejRzWdjehZscJaj9QG7sBlWILJ9FjjraWoYUBi+nkeehP9izs8L8ta0lYG6Zten4TAMLWc",
- "N08wKTvLc4bv89Y8RDX6P5988eVs3r6ra77P5jP/9V2CkkWxTb2eLGCbUvL8AcGD8cCwiu8M2DT3QNiT",
- "MXAUlBEPu4HNArRZi+rjcwpjxSLN4UJMvzcWbeW5pGB7d37QN7nzLg+1/PhwWw1QQGXXqawNHUENW7W7",
- "CdCLF6m0ugY5Z+IETvrGmsLpiz4arwS+xOwBqH2qKdpQcw6I0AJVRFiPFzLJIpKiHxR5PLf+MJ/5y9/c",
- "uzrkB07B1Z+zcUSGv61iD7775pKdeoZpHtBDXho6enqZUKX966JOJJHjZpSrhoS8t/KtfAlLIYX7/vyt",
- "LLjlpwtuRG5OawP6a15ymcPJSrHn4cHSS275WzmQtEbTSUVPxVhVL0qRs6tYIWnJk1KEDEd4+/YNL1fq",
- "7dt3g6CKofrgp0ryF5ogc4Kwqm3mExxkGm64TjmtTPPAHUemDCb7ZiUhW9Vk2QwJFPz4aZ7Hq8r0H7oO",
- "l19VpVt+RIbGP+N0W8aMVTrIIk5AIWhwf39U/mLQ/CbYVWoDhv1lw6s3Qtp3LHtbP3r0FFjn5edf/JXv",
- "aHJXwWTryuhD3L5RBRdOaiVsreZZxVcp39jbt28s8Ap3H+XlDdo4ypJht86L0xBRj0O1Cwj4GN8AguPo",
- "13O4uAvqFZJZpZeAn3ALsY0TN1qP/W33K3qDeuvt6r1jHexSbdeZO9vJVRlH4mFnmhw3KydkhTAKI1ao",
- "rfp0QAtg+RryK5+nBTaV3c073UOkjhc0A+sQhjL40AsyzCGBnoUFsLoquBfFudz1H/MbsDbEA/8MV7C7",
- "VG0KimNe73cfk5uxg4qUGkmXjljjY+vH6G++DwdDxb6qwptsfJwXyOJ5Qxehz/hBJpH3Hg5xiig6j53H",
- "EMF1AhFE/CMouMVC3Xh3Iv3U8pyWsaCbL5HNJ/B+5pu0ypOP3IpXg1Z3+r4BTAembgxbcCe3K5/Jih5M",
- "R1ysNnwFIxJy7NyZ+Cy54xDCQQ7de8mbTi37F9rgvkmCTI0zt+YkpYD74kgFlZlevF6YifyH3jOBCSo9",
- "whYliklNYCMxHa47TjbKuDcGWpqAQctW4AhgdDESSzZrbkKSLcxFFs7yJBngD0wAsC/ty3kUahYlHGuS",
- "ugSe2z+nA+3SJ38JGV9CmpdYtZyQssVJ+BjdntoOJVEAKqCEFS2cGgdCaZMRtBvk4PhpuSyFBJalotYi",
- "M2h0zfg5wMnHDxkjCzybPEKKjCOw0S+OA7MfVXw25eoYIKVPpsDD2OhRj/6G9LsviuN2Io+qHAsXI16t",
- "PHAA7kMdm/urF3CLwzAh58yxuWteOjbnNb52kEH2ERRbe7lGfGTG52Pi7B4HCF0sR62JrqLbrCaWmQLQ",
- "aYFuD8QLtc3o4WdS4l1sF47ek6Ht+Aw1dTApz8sDwxZqi9E+eLVQKPUBWMbhCGBEGv5WGKRX7Dd2mxMw",
- "+6bdL02lqNAgyXhzXkMuY+LElKlHJJgxcvksSt1yKwB6xo42D7JXfg8qqV3xZHiZt7favE1JFl4NpY7/",
- "2BFK7tII/oZWmCbZyuu+xJK0U3SDVrp5ZiIRMkX0jk0MnTRDV5CBElApyDpCVHaV8pw63QbwxrkI3SLj",
- "BWaz4XL3eRQJpWEljIXWiB7iJD6FeZJjEj2lluOrs5VeuvX9rFRzTZEbETt2lvnRV4ChxEuhjc3QA5Fc",
- "gmv0rUGl+lvXNC0rdWOtKOWsKNK8Aae9gl1WiLJO06uf9/uXbtofG5Zo6gXyWyEpYGWBKZKTEZh7pqYg",
- "3b0LfkULfsXvbb3TToNr6ibWjly6c/yDnIse593HDhIEmCKO4a6NonQPg4xezg65YyQ3RT7+k33W18Fh",
- "KsLYB6N2wvvdsTuKRkquJTIY7F2FQDeRE0uEjTIMD5+0jpwBXlWi2PZsoTTqqMbMjzJ4hLxsPSzg7vrB",
- "DmAgsnumXtVoMN0UfK2AT7miOxlwTiZh5rKbKC9mCPFUwoRKB0NENa/uDuHqEnj5Pex+dW1xObMP89nd",
- "TKcpXPsRD+D6dbO9STyja55MaR1PyJEo51Wl1TUvM29gHiNNra49aWLzYI/+yKwubca8/Obs1WsP/of5",
- "LC+B66wRFUZXhe2qf5hVUba/kQMSMqk7nS/I7CRKRpvfpCiLjdI3a/ApqSNpdJA7s3U4REfRG6mX6Qih",
- "gyZn7xuhJe7xkUDVuEha8x15SLpeEX7NRRnsZgHakWgeXNy0BKxJrhAPcGfvSuQky+6V3QxOd/p0tNR1",
- "gCfFc+1Jmr2hvPCGKdl3oWPM867yXvcNx8yXZBUZMidZb9CSkJlS5Gkbq1wYRxySfGeuMcPGI8KoG7EW",
- "I65YWYtoLNdsSm6bHpDRHElkmmR6nRZ3C+Vr/tRS/K0GJgqQ1n3SeCp7BxXTpHhr+/A6dbLDcC4/MFno",
- "2+HvImPEWV/7Nx4CsV/AiD11A3BfNipzWGhjkXI/RC6JIxz+8YyDK3GPs97Th6dmCl5cdz1ucYmeIf9z",
- "hEG52g/XBwrKq08/OzJHst6PMNlSq98hreehepx4sBTy3AqMcvkd4ocOcZWLDotprDtt2aJ29tHtHpNu",
- "YitUN0hhhOpx5yO3HCbcDBZqLmmr6SFJJ9YtTTBxVOkpjd8SjId5EIlb8psFT2UjdUKGg+msdQB3bOlW",
- "sdA54N40ry1odhb5kpu2gh6jV6Dbt4TDxDa3FBho2smiQisZINXGMsGc/H+lUYlhannDJVVxcf3oKPne",
- "Bsj45XrdKI2pJEza7F9ALja8TEsORT408RZiJahASW0gqoDhB6LiT0RFvopI84bIo+Z8yR7NozI8fjcK",
- "cS2MWJSALR5TiwU3yMkbQ1TTxS0PpF0bbP5kQvN1LQsNhV0bQqxRrBHqUL1pnFcLsDcAkj3Cdo+/Yp+h",
- "286Ia/jcYdHfz7Pnj79Coyv98Sh1AfgCM/u4SYHs5D89O0nTMfotaQzHuP2oJ8lX91Rhbpxx7TlN1HXK",
- "WcKWntcdPksbLvkK0pEimwMwUV/cTTSk9fAiCyqPZKxWOyZsen6w3PGnkehzx/4IDJarzUbYjXfuGLVx",
- "9NSWt6BJw3BUa8lnJg5whY/oI62Ci6inRH5coyndb6lVoyf7R76BLlrnjFP+kFK00QshXzo7D+mJMFVz",
- "k6GZcOPmcktHMQeDGZas0kJaVCxqu8z+xPI11zx37O9kDNxs8eWzRHrqbppUeRzgHx3vGgzo6zTq9QjZ",
- "BxnC92WfSSWzjeMoxefta4/oVI46c9NuuzHf4f6hpwplbpRslNzqDrnxiFPfifDkngHvSIrNeo6ix6NX",
- "9tEps9Zp8uC126Fffn7lpYyN0qmcg+1x9xKHBqsFXGPsXnqT3Jh33AtdTtqFu0D/aT0PQeSMxLJwlpOK",
- "wPXm12CWHY3ZdyL8rz/4cooD2XskzoACCZo+H/ktQjIkiSQ0DONjuGr2l8d/YRqWvkDiw4cI9MOHcy/M",
- "/eVJ9zMxqYcP05l4kjYN92uLhaNYYT9Tgeub2sOvVcLCENLeN94Q/94gYeEZY7XugzvKCz/UnHVTjH/8",
- "u/B+ItnS3sr0KXj79g1+CXjAP/qI+MRHHjewjceglYwQSlRiIUkyRfM9ipPg7Gu1nUo4PU4aiOfvAEVJ",
- "lNSiLH5tX+/2WJvmMl8n/Z4L1/G3ttZeszg6vMkUkGsuJZTJ4Uhn+C3oFgnt569q6jwbISe27RfVoOX2",
- "FtcC3gUzABUmdOgVtnQTxFjtPoxsAu/LlSoYztPmG2yP67AYS5Qy/281GJu6sPADBf+hfduxA8rYzkAW",
- "aFU4Yd9ROe01sE4yKdTmQ7aP7sv3uioVL+aYheTym7NXjGalPlQxijLGr1CZ7a6iZ9eMUqlOCyMPxZ/S",
- "T1ymj7M/5t6t2tisSfCeekTsWrQp6EXP14NqboydE/YyKoxL743dEAyT0OiN08yb0UjGRZpw/7GW52tU",
- "3TusdZzkp5c6CFRpovKiTZmwJr8onjsHt692QMUO5kzZNegbYaiKMlxD991y84jfm47CO+bu8nQtJVHK",
- "yRG3XJNN9Fi0B+DoigzuoCRkPcQfqbhRpZBjKz9cYK9kurN+GYlBXVF6BduUfwrV8XMulRQ5JhtLXdG+",
- "3PIUX+mEvGx9Y3w44v6EJg5XsnhFE07psThaziIwQo+4obMm+uo2laiD/rRY13fNLVuBNZ6zQTEPNVi8",
- "vVhIAz5fLBbnjvik0h3/M3LIZEhD1ri+jiQjfD41YgD41n370ZuH8F3BlZCoCHq0ecGPLLpYDdY67VFY",
- "tlJg/Hq6b8jNG9fnBJ9TF7B9dxKqx+IY5L51y6ZYheFQZyFywUcKuLYvXFuf5Kr5uROpTpOeVZWfdLxC",
- "T1IesFs5iuCEBzoLLsAIuc348Wh7yG1vyBHep47Q4BoDFqDCe3hAGE21ml4lNCe0EkVhC0ahfslMF0Im",
- "wHglJLS1jRMXRJ68EnBj8LyO9DO55pZEwEk87RJ4SQp1gqEZ611Udx2qn+LLoQTXGOYY38a20M4I42ga",
- "tIIbl7umpLKj7kiYeIG13D0ih2VzUKryQlSBL096hXRSjMMx7lCqq3sBjOj5HZmIumO+u2NvorHHxIu6",
- "WIHNeFGk0vd+jV8ZfmVFjZIDbCGvmzSvVcVyzJ3TTSY0pDY/Ua6kqTd75goN7jhdVJkqQQ1xdayww/hY",
- "abHDf1M5Tsd3xgfrHB0uGiJziuMyaA3DX1NSr6PpzIhVNh0TeKfcHR3t1Lcj9Lb/vVJ6qVZdQD6F2W6E",
- "y8V7lOJv37iLI86wMUjcS1dLkwADgzNVqCeKamPzdLvLlfAqG2TyRadgU69wvwFivPLgHC+/kRDt2AhL",
- "9ysZJscCtfPRdwXc+heOlrO9LGj01RhFefXMukML+1hkFwV23Z851K91L0JDyOAQoO9DPDKruPAhFC2z",
- "GGLWv1wYviWZEtPcbnB/Ef49wKjF7vvrsdj9kFAPv/crk12BT3tQabgWqg7BCSF6LaiE9GunzlfzeiK5",
- "/qHhFaf6tObQUePtpa8QQcv0Ovn3v1KsIwNp9e7vwJQ72PRBzbOhtEvmqbYJa5KLT0o23rkVpySbTOU1",
- "9LJhp+ragZpxA7J6OUUcGNaAm8/Oi6MuzFRuzBmNkjp26Ypu46nD2nRheMQqZUSb4z9V6m1imOglVmuL",
- "Up8NxwoxWteQWyzs0MaeaIBjEqG5yaLisf9MITaiTjfRtD5z2L50YcNqDgfu+MGLvuhVKmXCP5meHOus",
- "iTBEPo0ZrVcgff3W7ludyS8GlkvIrbg+8ILyP9cgo9d582CXoTrs0YNK0USgYwKe462OLUD7HjjuhSdK",
- "hHlncMbeT13B7oFhHWpIpuafh6v2NrlXEAPIHTJHIsqkInjIkOyDKoRpKAOxECLmqDu0WexGq3pF74Fv",
- "OVcgSXdxtG+E90yZLis0aS7X9aiX8xhMPfbIcliVZFz/eIlFYExTcTPkbom1dHY+zHB543O/4HvXxncS",
- "ssCACb+Fx+00SymuIK47hp6qG66L0CJpeglWnWzPfTR4GRkqavSBXjYziza+efgWLpEzDaPY81I5MSIb",
- "ewrQDSlu4nEeGAqcohT+GCzt4FqC9vUZUf4tlYHMqhAPvQ+Ofaig6LBbIcGM5ikl4EazB/3cpkfCfM0c",
- "swVxHxQWL5Bp2HAHnY6SGI3PuQ/ZL+h7ePwV8vUetDA19Hq4cESIbBdmgMSY6pfM35aHH5XdxtgkpKQa",
- "4CaV0UiC7npDKq2KOqcLOj4YjUFucr6wPawkaafJh6vs6QjRy9wr2J2SEhQqboQdjIEmyYlAjzJh9Db5",
- "Xs1vJgX36l7A+5SWq/msUqrMRpwd58M0TH2KvxL5FRTM3RQhAnSkChL7DG3sjTf7Zr0LaYeqCiQUn58w",
- "diYp5j44trt5wHuTywd23/xbnLWoKTOaN6qdvJXp4GXMWabvyM3CMPt5mAHH6u44FQ1yIMnPdiQFlOY3",
- "iZpgJ1O18qGruV+nqSUqgiIlk7QliA7EyTQhMm31ljZMZigdlKW6yZCKsiaHW0rncO26TDJkrW27OWwv",
- "IIq34cZfoDu25gXLldaQxz3Sz1QIqI3SkJUKw29SnsGldfLQBmPTJSvViqnKqbmUCjH4UJKlhaK57quM",
- "Ej25JggycviMJLUA459Ye3Cp8RDePZWMjq+SdLlO2G1ww8JuHV0KyRPc0RVMIjAnEPphm9VZqtJTd139",
- "mmNjFQCt2og8je5/rGiV0RiTFPWmUOGTCNMjRmyGBzzmKY1zEk/PEM0g+aJMeWyYP37eSYN07v6LN1h/",
- "XLYEz1xG+FmqZDHPrzISiSZMj3DSuxpba8o77EZoy5ipFT3AQ89SH75JfObD/q1IlRRLkFqzfl/xLDzW",
- "HSHbpBd+v9Obykwuprq+m1TmEzlUBMC4M7wDwySX+LFgLLFsa8YTSD5vFJF5p6q26LHhkGaS2E3OyRCx",
- "BubGrjX4x6NUX7JX0Kridh0EE9d8aC5wqicYfNlJVXm4IeNWMLL54pZ9iU9VWQnX0IkR8C9a6zwHY8Q1",
- "xIUxqTMrACo0OfcVoZTzO75wetKxX3sWuU+nYDcpLhNiaafYAVk4KblvZUbHxEw9Sg6ia1HUvIM/c4cS",
- "gWPVARM3YoD13TROcTSTSC9uH4s4GK6CNJ88lzIdrRI/qG7sXDhb0djDiQjbk20qfiPH9cIhUbYC3fTi",
- "mhFiv9lCjpdjNxzj7jhhOBgzvWQJo5Kcbnb4tvaFUSrbR2SDUqNJUdJAKBUd5zUK0rjvm7gayRIqTGIA",
- "YVregMGd0AYPRs02fMcKsVyCJl+PsVwWXBdxcyFZDtpy4RTfnbm91uOg1TXMDyo+jlPjoIFZpVQgNFsS",
- "IOXOa5RjSskEZQIdewlFgq5tq8aqoA52Jf3ahG+d8oVhdyNE4HMdoOpFh1VJlHvZhl/BkfMY8TvsnwYz",
- "EHnTsFU465QpPuyl9Z8QdXjgf5HC7qV2kvf6cZDkqCJiDDQoV623nDZnSIOp0NVLqsUVh6/2S1uEvSar",
- "Gc0HI6k6Pe/MkKeaPX5oMFERrtzbEYfiwIAZEzBzH9Z7pLRAYjovCjFW83wNPg+1p+nutI1tx40z3YQZ",
- "XsaOQlSpKsunOCcKKMEdcdIWPKRdGCf4pKr8ADtOXk4j3KCrmqglnkskR7qSMYyjuYjm/QCj7uXbEDwW",
- "1M1rjeLjDd8dznXYXsDp2GwaOWiXIeSkgdpvMB0tQzVakqkEjxHMEqc9VaZkmMTt/hdDjw5at+gftxzv",
- "+Egv4Ex6BQWLz+2jt1aFCaSSoDUudymmEUz7t1jgmFw2IWz23raqOS1/xAYlL6fb5fadBNowhDKBzagY",
- "9/6oljj1d/seXVMkLnrBgybY5xc/tBritLLgocMB8OJgp6gwePA7eXA+8cPuHxqkREt5N0YJneUfip/y",
- "C2xV6miLvJRqLVAhBnoM2N2XKDjOvGhizsZq2PdD0zDPtxOLyjIR0kaCM1WNjgjH3Yv6mpcfPywNE8Cf",
- "IT6g+HnckR3HNcVIJlSa272qfMUnzR3FMN3f1PI1htH9J7g9Sl4Lfiivqw+YP6o9vCSnyzKUkL0GyW5w",
- "TLKUPv6SLXzmoEpDLkzfBnATqrs1YTxY7NS/ZN3aA3FDh9b5q7J3IONlMKmxH9tKUehXWMkWwvaIfmKm",
- "MnJyk1Seor4BWSTwl+JRcQrfA9fFVSc4v5XqohtNabjnIP3oud2RQfrD5MRTl0eB6O7SqQ0M1zn5tu7g",
- "NnFRt2ub+sJkcpofLOMz5WFIOiWP644vU+4lN89RmXn+gDcphCM/hp83RTG/jmUpoJf4IwkxevtRi7I4",
- "RBid9CZtFXpM4PGbT4T1Serg/0ZxssOj6msR3yG4nxCTWGtn8miqKHHJhJwlvlsiQwnGoOS1FnaH+bmD",
- "xit+S76e+a6JxPaR/I3x0t99Vl1Bk+G9jduuTbhdv1O8xPuIbKrS3UKqPGHfbPmmKr1NhP35weLf4Omf",
- "nhWPnj7+t8WfHn3xKIdnX3z16BH/6hl//NXTx/DkT188ewSPl19+tXhSPHn2ZPHsybMvv/gqf/rs8eLZ",
- "l1/92wPHhxzIBOgsZIOc/e/srFyp7Oz1eXbpgG1xwivxPeyoLrUj41Dxmud4EmHDRTl7Hn76n+GEneRq",
- "0w4ffp35ZHOztbWVeX56enNzcxJ3OV1hoGZmVZ2vT8M8g5LYZ6/PG48wuTtwRynHR3BjBVI4w28/f3Nx",
- "yc5en5+0BDN7Pnt08ujksRtfVSB5JWbPZ0/xJzw9a9z3U09ss+fvP8xnp2vgJb5rcH9swGqRh08aeLHz",
- "/zc3fLUCfeLLgLufrp+cBrHi9L0PWP2w79tpXFHv9H0nrrc40BMrbp2+D4mk97fuZGr28cxRh4lQ7Gt2",
- "usDcZlObgokajy8FlQ1z+h7F5dHfT30ypvRHVFvoPJyG4Pd0yw6W3tutg7XXI+c2X9fV6Xv8D9JnBBY9",
- "fT61W3mKhvnT953V+M+D1XR/b7vHLa43qoAAsFouKTH+vs+n7+nfaCLYVqCFE/zwuYH/lZ6FnWK6yt3w",
- "5530Zu0SUsH8v0gDpJiGVEw7mbePE5sje16Exhc7mQcJNTzxxYP45NEjmv4Z/ud+Cu93Hxsnyu9fNPBi",
- "bmOM9kYYHn88GM4lvoZx/IsRf/4wn33xMbFw7nR2yUuGLWn6px9xE0BfixzYJWwqpbkW5Y79IpsESlFy",
- "7RQFXkl1IwPk7nKvNxuudyg0b9Q1GObzdkfEyTQ4MYXcaOjqaWkYbxe+MmjMx7Jmszk9LX+HgpFNyQjB",
- "XjOcKdiq2sG7p+K7g2di+i50Rc89sfyT4Dzg6KDhh3LzcH/D3vfdEzTVg9QGzf7JCP7JCO6REdhay9Ej",
- "Gt1f+CANKh+7mPN8Dfv4wfC2jC74WaVSgd0Xe5iFT/s2xisuurwiqpz3/M20dKvewUC24wKM8NWEUG9w",
- "QnEr1uuGI4Uzj7ET0V7vq4fw4d3fxf3+gstwnjs7Tm8iuC4F6IYKuBxm4vsnF/j/hgtQSlFO+zpnFsrS",
- "xGffKjz75Gzx74wlOcEm8oGqV+c49fPp+25l0I6SYNa1LdRN1BdN5uTvGeoOTa38zt+nN1zYbKm0f2OM",
- "lVuGnS3w8tQnFOz92ubwGXzBxETRj3FgZfLX06YwVvJjXx1NffXq2EijEJsVPremqdjUgxyyMfK8eef4",
- "E5Zd8MyztVw8Pz3Fd3trZezp7MP8fc+qEX9815BEyLM8q7S4xrRN7z78vwAAAP//7JaTGCXWAAA=",
+ "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",
}
// GetSwagger returns the content of the embedded swagger specification file
diff --git a/daemon/algod/api/server/v2/generated/experimental/routes.go b/daemon/algod/api/server/v2/generated/experimental/routes.go
index 043c1cbe1..47db46103 100644
--- a/daemon/algod/api/server/v2/generated/experimental/routes.go
+++ b/daemon/algod/api/server/v2/generated/experimental/routes.go
@@ -76,182 +76,184 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL
var swaggerSpec = []string{
"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",
- "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",
+ "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",
+ "1ZtnzgAA",
}
// GetSwagger returns the content of the embedded swagger specification file
diff --git a/daemon/algod/api/server/v2/generated/model/types.go b/daemon/algod/api/server/v2/generated/model/types.go
index 36eb92e6a..ae20c2896 100644
--- a/daemon/algod/api/server/v2/generated/model/types.go
+++ b/daemon/algod/api/server/v2/generated/model/types.go
@@ -642,6 +642,15 @@ type PendingTransactionResponse struct {
Txn map[string]interface{} `json:"txn"`
}
+// ScratchChange A write operation into a scratch slot.
+type ScratchChange struct {
+ // NewValue Represents an AVM value.
+ NewValue AvmValue `json:"new-value"`
+
+ // Slot The scratch slot written.
+ Slot uint64 `json:"slot"`
+}
+
// SimulateRequest Request type for simulation endpoint.
type SimulateRequest struct {
// AllowEmptySignatures Allow transactions without signatures to be simulated as if they had correct signatures.
@@ -671,6 +680,9 @@ type SimulateTraceConfig struct {
// Enable A boolean option for opting in execution trace features simulation endpoint.
Enable *bool `json:"enable,omitempty"`
+ // ScratchChange A boolean option enabling returning scratch slot changes together with execution trace during simulation.
+ ScratchChange *bool `json:"scratch-change,omitempty"`
+
// StackChange A boolean option enabling returning stack changes together with execution trace during simulation.
StackChange *bool `json:"stack-change,omitempty"`
}
@@ -728,6 +740,9 @@ type SimulationOpcodeTraceUnit struct {
// Pc The program counter of the current opcode being evaluated.
Pc uint64 `json:"pc"`
+ // ScratchChanges The writes into scratch slots.
+ ScratchChanges *[]ScratchChange `json:"scratch-changes,omitempty"`
+
// SpawnedInners The indexes of the traces for inner transactions spawned by this opcode, if any.
SpawnedInners *[]uint64 `json:"spawned-inners,omitempty"`
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 96a33bca0..08f34c9b0 100644
--- a/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go
+++ b/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go
@@ -131,188 +131,189 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL
var swaggerSpec = []string{
"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",
- "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",
- "+SHXAAA=",
+ "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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/routes.go b/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go
index 388d0789c..5ad094a2a 100644
--- a/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go
+++ b/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go
@@ -702,261 +702,264 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL
// Base64 encoded, gzipped, json marshaled Swagger object
var swaggerSpec = []string{
- "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",
- "/B+89F9ItjIazcf3H///AAAA///s8G0fPoABAA==",
+ "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",
+ "RfPh3Yf/PwAA///0dvnw8YIBAA==",
}
// 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 b5335f0f4..77ebb098c 100644
--- a/daemon/algod/api/server/v2/generated/participating/private/routes.go
+++ b/daemon/algod/api/server/v2/generated/participating/private/routes.go
@@ -159,190 +159,191 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL
var swaggerSpec = []string{
"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",
- "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",
- "jZBHueAAAA==",
+ "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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/routes.go b/daemon/algod/api/server/v2/generated/participating/public/routes.go
index cb5304c48..1b23d9839 100644
--- a/daemon/algod/api/server/v2/generated/participating/public/routes.go
+++ b/daemon/algod/api/server/v2/generated/participating/public/routes.go
@@ -177,199 +177,201 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL
// Base64 encoded, gzipped, json marshaled Swagger object
var swaggerSpec = []string{
- "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",
+ "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",
+ "s69YOiklW2EBlPcf/18AAAD//6Cbm1hv8QAA",
}
// 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 fb904f890..cd9fbbbd4 100644
--- a/daemon/algod/api/server/v2/handlers.go
+++ b/daemon/algod/api/server/v2/handlers.go
@@ -66,6 +66,9 @@ const MaxTealSourceBytes = 200_000
// become quite large, so we allow up to 1MB
const MaxTealDryrunBytes = 1_000_000
+// WaitForBlockTimeout is the timeout for the WaitForBlock endpoint.
+var WaitForBlockTimeout = 1 * time.Minute
+
// Handlers is an implementation to the V2 route handler interface defined by the generated code.
type Handlers struct {
Node NodeInterface
@@ -863,7 +866,7 @@ func (v2 *Handlers) WaitForBlock(ctx echo.Context, round uint64) error {
select {
case <-v2.Shutdown:
return internalError(ctx, err, errServiceShuttingDown, v2.Log)
- case <-time.After(1 * time.Minute):
+ case <-time.After(WaitForBlockTimeout):
case <-ledger.Wait(basics.Round(round + 1)):
}
diff --git a/daemon/algod/api/server/v2/test/handlers_test.go b/daemon/algod/api/server/v2/test/handlers_test.go
index a56577d54..de8a01145 100644
--- a/daemon/algod/api/server/v2/test/handlers_test.go
+++ b/daemon/algod/api/server/v2/test/handlers_test.go
@@ -72,16 +72,19 @@ import (
const stateProofInterval = uint64(256)
func setupMockNodeForMethodGet(t *testing.T, status node.StatusReport, devmode bool) (v2.Handlers, echo.Context, *httptest.ResponseRecorder, []account.Root, []transactions.SignedTxn, func()) {
+ return setupMockNodeForMethodGetWithShutdown(t, status, devmode, make(chan struct{}))
+}
+
+func setupMockNodeForMethodGetWithShutdown(t *testing.T, status node.StatusReport, devmode bool, shutdown chan struct{}) (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, status, devmode)
- dummyShutdownChan := make(chan struct{})
handler := v2.Handlers{
Node: mockNode,
Log: logging.Base(),
- Shutdown: dummyShutdownChan,
+ Shutdown: shutdown,
}
e := echo.New()
req := httptest.NewRequest(http.MethodGet, "/", nil)
@@ -585,9 +588,73 @@ func TestGetStatusAfterBlock(t *testing.T) {
defer releasefunc()
err := handler.WaitForBlock(c, 0)
require.NoError(t, err)
- // Expect 400 - the test ledger will always cause "errRequestedRoundInUnsupportedRound",
- // as it has not participated in agreement to build blockheaders
+
require.Equal(t, 400, rec.Code)
+ msg, err := io.ReadAll(rec.Body)
+ require.NoError(t, err)
+ require.Contains(t, string(msg), "requested round would reach only after the protocol upgrade which isn't supported")
+}
+
+func TestGetStatusAfterBlockShutdown(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+
+ catchup := cannedStatusReportGolden
+ catchup.StoppedAtUnsupportedRound = false
+ shutdownChan := make(chan struct{})
+ handler, c, rec, _, _, releasefunc := setupMockNodeForMethodGetWithShutdown(t, catchup, false, shutdownChan)
+ defer releasefunc()
+
+ close(shutdownChan)
+ err := handler.WaitForBlock(c, 0)
+ require.NoError(t, err)
+
+ require.Equal(t, 500, rec.Code)
+ msg, err := io.ReadAll(rec.Body)
+ require.NoError(t, err)
+ require.Contains(t, string(msg), "operation aborted as server is shutting down")
+}
+
+func TestGetStatusAfterBlockDuringCatchup(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+
+ catchup := cannedStatusReportGolden
+ catchup.StoppedAtUnsupportedRound = false
+ catchup.Catchpoint = "catchpoint"
+ handler, c, rec, _, _, releasefunc := setupTestForMethodGet(t, catchup)
+ defer releasefunc()
+
+ err := handler.WaitForBlock(c, 0)
+ require.NoError(t, err)
+
+ require.Equal(t, 503, rec.Code)
+ msg, err := io.ReadAll(rec.Body)
+ require.NoError(t, err)
+ require.Contains(t, string(msg), "operation not available during catchup")
+}
+
+func TestGetStatusAfterBlockTimeout(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+
+ supported := cannedStatusReportGolden
+ supported.StoppedAtUnsupportedRound = false
+ handler, c, rec, _, _, releasefunc := setupTestForMethodGet(t, supported)
+ defer releasefunc()
+
+ before := v2.WaitForBlockTimeout
+ defer func() { v2.WaitForBlockTimeout = before }()
+ v2.WaitForBlockTimeout = 1 * time.Millisecond
+ err := handler.WaitForBlock(c, 1000)
+ require.NoError(t, err)
+
+ require.Equal(t, 200, rec.Code)
+ dec := json.NewDecoder(rec.Body)
+ var resp model.NodeStatusResponse
+ err = dec.Decode(&resp)
+ require.NoError(t, err)
+ require.Equal(t, uint64(1), resp.LastRound)
}
func TestGetTransactionParams(t *testing.T) {
diff --git a/daemon/algod/api/server/v2/utils.go b/daemon/algod/api/server/v2/utils.go
index 8191b66c0..f476afa21 100644
--- a/daemon/algod/api/server/v2/utils.go
+++ b/daemon/algod/api/server/v2/utils.go
@@ -346,6 +346,24 @@ func ConvertInnerTxn(txn *transactions.SignedTxnWithAD) PreEncodedTxInfo {
return response
}
+func convertScratchChanges(scratchChanges []simulation.ScratchChange) *[]model.ScratchChange {
+ if len(scratchChanges) == 0 {
+ return nil
+ }
+ modelSC := make([]model.ScratchChange, len(scratchChanges))
+ for i, scratchChange := range scratchChanges {
+ modelSC[i] = model.ScratchChange{
+ Slot: scratchChange.Slot,
+ NewValue: model.AvmValue{
+ Type: uint64(scratchChange.NewValue.Type),
+ Uint: omitEmpty(scratchChange.NewValue.Uint),
+ Bytes: byteOrNil([]byte(scratchChange.NewValue.Bytes)),
+ },
+ }
+ }
+ return &modelSC
+}
+
func convertTealValueSliceToModel(tvs []basics.TealValue) *[]model.AvmValue {
if len(tvs) == 0 {
return nil
@@ -380,6 +398,7 @@ func convertProgramTrace(programTrace []simulation.OpcodeTraceUnit) *[]model.Sim
SpawnedInners: spawnedInnersPtr,
StackAdditions: convertTealValueSliceToModel(programTrace[i].StackAdded),
StackPopCount: omitEmpty(programTrace[i].StackPopCount),
+ ScratchChanges: convertScratchChanges(programTrace[i].ScratchSlotChanges),
}
}
return &modelProgramTrace
diff --git a/data/ledger_test.go b/data/ledger_test.go
index cd7d6297a..452bdba15 100644
--- a/data/ledger_test.go
+++ b/data/ledger_test.go
@@ -366,9 +366,11 @@ func TestConsensusVersion(t *testing.T) {
require.NotNil(t, &l)
blk := genesisInitState.Block
+ flushOffset := uint64(129) // pendingDeltasFlushThreshold = 128 will flush every 128 rounds (RewardsPool acct)
+ // txTailRetainSize = MaxTxnLife + DeeperBlockHeaderHistory = 1000 + 1
- // add 5 blocks.
- for rnd := basics.Round(1); rnd < basics.Round(consensusParams.MaxTxnLife+5); rnd++ {
+ // add some blocks.
+ for rnd := basics.Round(1); rnd < basics.Round(consensusParams.MaxTxnLife+flushOffset); rnd++ {
blk.BlockHeader.Round++
blk.BlockHeader.Seed[0] = byte(uint64(rnd))
blk.BlockHeader.Seed[1] = byte(uint64(rnd) / 256)
@@ -378,31 +380,38 @@ func TestConsensusVersion(t *testing.T) {
require.NoError(t, l.AddBlock(blk, agreement.Certificate{}))
l.WaitForCommit(rnd)
}
- // ensure that all the first 5 has the expected version.
- for rnd := basics.Round(consensusParams.MaxTxnLife); rnd < basics.Round(consensusParams.MaxTxnLife+5); rnd++ {
+ // ensure that all the first flushOffset have the expected version.
+ for rnd := basics.Round(consensusParams.MaxTxnLife); rnd < basics.Round(consensusParams.MaxTxnLife+flushOffset); rnd++ {
ver, err := l.ConsensusVersion(rnd)
require.NoError(t, err)
require.Equal(t, previousProtocol, ver)
}
// the next UpgradeVoteRounds can also be known to have the previous version.
- for rnd := basics.Round(consensusParams.MaxTxnLife + 5); rnd < basics.Round(consensusParams.MaxTxnLife+5+consensusParams.UpgradeVoteRounds); rnd++ {
+ for rnd := basics.Round(consensusParams.MaxTxnLife + flushOffset); rnd < basics.Round(consensusParams.MaxTxnLife+
+ flushOffset+consensusParams.UpgradeVoteRounds); rnd++ {
ver, err := l.ConsensusVersion(rnd)
require.NoError(t, err)
require.Equal(t, previousProtocol, ver)
}
// but two rounds ahead is not known.
- ver, err := l.ConsensusVersion(basics.Round(consensusParams.MaxTxnLife + 6 + consensusParams.UpgradeVoteRounds))
+ ver, err := l.ConsensusVersion(basics.Round(consensusParams.MaxTxnLife + flushOffset + 1 + consensusParams.UpgradeVoteRounds))
require.Equal(t, protocol.ConsensusVersion(""), ver)
- require.Equal(t, ledgercore.ErrNoEntry{Round: basics.Round(consensusParams.MaxTxnLife + 6 + consensusParams.UpgradeVoteRounds), Latest: basics.Round(consensusParams.MaxTxnLife + 4), Committed: basics.Round(consensusParams.MaxTxnLife + 4)}, err)
+ require.Equal(t, ledgercore.ErrNoEntry{
+ Round: basics.Round(consensusParams.MaxTxnLife + flushOffset + 1 + consensusParams.UpgradeVoteRounds),
+ Latest: basics.Round(consensusParams.MaxTxnLife + flushOffset - 1),
+ Committed: basics.Round(consensusParams.MaxTxnLife + flushOffset - 1)}, err)
// check round #1 which was already dropped.
ver, err = l.ConsensusVersion(basics.Round(1))
require.Equal(t, protocol.ConsensusVersion(""), ver)
- require.Equal(t, ledgercore.ErrNoEntry{Round: basics.Round(1), Latest: basics.Round(consensusParams.MaxTxnLife + 4), Committed: basics.Round(consensusParams.MaxTxnLife + 4)}, err)
+ require.Equal(t, ledgercore.ErrNoEntry{
+ Round: basics.Round(1),
+ Latest: basics.Round(consensusParams.MaxTxnLife + flushOffset - 1),
+ Committed: basics.Round(consensusParams.MaxTxnLife + flushOffset - 1)}, err)
// add another round, with upgrade
- rnd := basics.Round(consensusParams.MaxTxnLife + 5)
+ rnd := basics.Round(consensusParams.MaxTxnLife + flushOffset)
blk.BlockHeader.Round++
blk.BlockHeader.Seed[0] = byte(uint64(rnd))
blk.BlockHeader.Seed[1] = byte(uint64(rnd) / 256)
diff --git a/data/transactions/logic/README.md b/data/transactions/logic/README.md
index 7c51a9c23..6f4124f04 100644
--- a/data/transactions/logic/README.md
+++ b/data/transactions/logic/README.md
@@ -140,13 +140,17 @@ of a contract account.
The bytecode plus the length of all Args must add up to no more than
1000 bytes (consensus parameter LogicSigMaxSize). Each opcode has an
-associated cost and the program cost must total no more than 20,000
-(consensus parameter LogicSigMaxCost). Most opcodes have a cost of 1,
-but a few slow cryptographic operations have a much higher cost. Prior
-to v4, the program's cost was estimated as the static sum of all the
-opcode costs in the program (whether they were actually executed or
-not). Beginning with v4, the program's cost is tracked dynamically,
-while being evaluated. If the program exceeds its budget, it fails.
+associated cost, usually 1, but a few slow operations have higher
+costs. Prior to v4, the program's cost was estimated as the static sum
+of all the opcode costs in the program (whether they were actually
+executed or not). Beginning with v4, the program's cost is tracked
+dynamically, while being evaluated. If the program exceeds its budget,
+it fails.
+
+The total program cost of all Smart Signatures in a group must not
+exceed 20,000 (consensus parameter LogicSigMaxCost) times the number
+of transactions in the group.
+
## Execution Environment for Smart Contracts (Applications)
diff --git a/data/transactions/logic/README_in.md b/data/transactions/logic/README_in.md
index e0cc32c33..04c67758c 100644
--- a/data/transactions/logic/README_in.md
+++ b/data/transactions/logic/README_in.md
@@ -125,13 +125,17 @@ of a contract account.
The bytecode plus the length of all Args must add up to no more than
1000 bytes (consensus parameter LogicSigMaxSize). Each opcode has an
-associated cost and the program cost must total no more than 20,000
-(consensus parameter LogicSigMaxCost). Most opcodes have a cost of 1,
-but a few slow cryptographic operations have a much higher cost. Prior
-to v4, the program's cost was estimated as the static sum of all the
-opcode costs in the program (whether they were actually executed or
-not). Beginning with v4, the program's cost is tracked dynamically,
-while being evaluated. If the program exceeds its budget, it fails.
+associated cost, usually 1, but a few slow operations have higher
+costs. Prior to v4, the program's cost was estimated as the static sum
+of all the opcode costs in the program (whether they were actually
+executed or not). Beginning with v4, the program's cost is tracked
+dynamically, while being evaluated. If the program exceeds its budget,
+it fails.
+
+The total program cost of all Smart Signatures in a group must not
+exceed 20,000 (consensus parameter LogicSigMaxCost) times the number
+of transactions in the group.
+
## Execution Environment for Smart Contracts (Applications)
diff --git a/data/transactions/logic/assembler.go b/data/transactions/logic/assembler.go
index 20ba9563a..4939d9ded 100644
--- a/data/transactions/logic/assembler.go
+++ b/data/transactions/logic/assembler.go
@@ -2370,7 +2370,7 @@ func (ops *OpStream) resolveLabels() {
}
// AssemblerDefaultVersion what version of code do we emit by default
-// AssemblerDefaultVersion is set to 1 on puprose
+// AssemblerDefaultVersion is set to 1 on purpose
// to prevent accidental building of v1 official templates with version 2
// because these templates are not aware of rekeying.
const AssemblerDefaultVersion = 1
diff --git a/data/transactions/logic/backwardCompat_test.go b/data/transactions/logic/backwardCompat_test.go
index 0e0292ea9..0ae1ba319 100644
--- a/data/transactions/logic/backwardCompat_test.go
+++ b/data/transactions/logic/backwardCompat_test.go
@@ -22,6 +22,7 @@ import (
"strings"
"testing"
+ "github.com/algorand/go-algorand/config"
"github.com/algorand/go-algorand/crypto"
"github.com/algorand/go-algorand/data/basics"
"github.com/algorand/go-algorand/test/partitiontest"
@@ -277,23 +278,25 @@ func TestBackwardCompatTEALv1(t *testing.T) {
Data: data[:],
})
- ep, tx, _ := makeSampleEnvWithVersion(1)
+ stxn := makeSampleTxn()
// RekeyTo disallowed on AVM v0/v1
- tx.RekeyTo = basics.Address{}
-
- ep.TxnGroup[0].Lsig.Logic = program
- ep.TxnGroup[0].Lsig.Args = [][]byte{data[:], sig[:], pk[:], tx.Sender[:], tx.Note}
+ stxn.Txn.RekeyTo = basics.Address{}
+ stxn.Lsig.Logic = program
+ stxn.Lsig.Args = [][]byte{data[:], sig[:], pk[:], stxn.Txn.Sender[:], stxn.Txn.Note}
// ensure v1 program runs well on latest evaluator
require.Equal(t, uint8(1), program[0])
- // Cost should stay exactly 2140
- ep.Proto.LogicSigMaxCost = 2139
- err = CheckSignature(0, ep)
- require.Error(t, err)
- require.Contains(t, err.Error(), "static cost")
+ maxCost := func(cost uint64) protoOpt {
+ return func(p *config.ConsensusParams) {
+ p.LogicSigMaxCost = cost
+ }
+ }
- ep.Proto.LogicSigMaxCost = 2140
+ // Cost should stay exactly 2140 for v1, even as future changes are made
+ err = CheckSignature(0, optSigParams(maxCost(2139), stxn))
+ require.ErrorContains(t, err, "static cost")
+ ep := optSigParams(maxCost(2140), stxn)
err = CheckSignature(0, ep)
require.NoError(t, err)
@@ -305,42 +308,29 @@ func TestBackwardCompatTEALv1(t *testing.T) {
require.NoError(t, err)
require.True(t, pass)
- // Costs for v2 should be higher because of hash opcode cost changes
- ep2, tx, _ := makeSampleEnvWithVersion(2)
- ep2.Proto.LogicSigMaxCost = 2307
- ep2.TxnGroup[0].Lsig.Args = [][]byte{data[:], sig[:], pk[:], tx.Sender[:], tx.Note}
+ // Costs for v2 programs should be higher because of hash opcode cost changes
// Eval doesn't fail, but it would be ok (better?) if it did
- testLogicBytes(t, opsV2.Program, ep2, "static cost", "")
-
- ep2.Proto.LogicSigMaxCost = 2308
- testLogicBytes(t, opsV2.Program, ep2)
+ testLogicBytes(t, opsV2.Program, optSigParams(maxCost(2307), stxn), "static cost", "")
+ testLogicBytes(t, opsV2.Program, optSigParams(maxCost(2308), stxn))
// ensure v0 program runs well on latest evaluator
- ep, tx, _ = makeSampleEnv()
program[0] = 0
sig = c.Sign(Msg{
ProgramHash: crypto.HashObj(Program(program)),
Data: data[:],
})
- ep.TxnGroup[0].Lsig.Logic = program
- ep.TxnGroup[0].Lsig.Args = [][]byte{data[:], sig[:], pk[:], tx.Sender[:], tx.Note}
+ stxn.Lsig.Logic = program
+ stxn.Lsig.Args = [][]byte{data[:], sig[:], pk[:], stxn.Txn.Sender[:], stxn.Txn.Note}
// Cost remains the same, because v0 does not get dynamic treatment
- ep.Proto.LogicSigMaxCost = 2139
- ep.MinAvmVersion = new(uint64) // Was higher because sample txn has a rekey
- testLogicBytes(t, program, ep, "static cost", "")
-
- ep.Proto.LogicSigMaxCost = 2140
- testLogicBytes(t, program, ep)
+ testLogicBytes(t, program, optSigParams(maxCost(2139), stxn), "static cost", "")
+ testLogicBytes(t, program, optSigParams(maxCost(2140), stxn))
// But in v4, cost is now dynamic and exactly 1 less than v2/v3,
// because bnz skips "err". It's caught during Eval
program[0] = 4
- ep.Proto.LogicSigMaxCost = 2306
- testLogicBytes(t, program, ep, "dynamic cost")
-
- ep.Proto.LogicSigMaxCost = 2307
- testLogicBytes(t, program, ep)
+ testLogicBytes(t, program, optSigParams(maxCost(2306), stxn), "dynamic cost")
+ testLogicBytes(t, program, optSigParams(maxCost(2307), stxn))
}
// ensure v2 fields error on pre v2 logicsig version
@@ -367,23 +357,22 @@ func TestBackwardCompatGlobalFields(t *testing.T) {
ops := testProg(t, text, AssemblerMaxVersion)
- ep, _, _ := makeSampleEnvWithVersion(1)
- ep.TxnGroup[0].Lsig.Logic = ops.Program
+ stxn := makeSampleTxn()
+ stxn.Txn.RekeyTo = basics.Address{} // would mess up minavmversion
+ stxn.Lsig.Logic = ops.Program
+ ep := defaultSigParamsWithVersion(1, stxn)
_, err := EvalSignature(0, ep)
- require.Error(t, err)
- require.Contains(t, err.Error(), "greater than protocol supported version")
+ require.ErrorContains(t, err, "greater than protocol supported version")
// check opcodes failures
ep.TxnGroup[0].Lsig.Logic[0] = 1 // set version to 1
_, err = EvalSignature(0, ep)
- require.Error(t, err)
- require.Contains(t, err.Error(), "invalid global field")
+ require.ErrorContains(t, err, "invalid global field")
// check opcodes failures
ep.TxnGroup[0].Lsig.Logic[0] = 0 // set version to 0
_, err = EvalSignature(0, ep)
- require.Error(t, err)
- require.Contains(t, err.Error(), "invalid global field")
+ require.ErrorContains(t, err, "invalid global field")
}
}
@@ -430,25 +419,22 @@ func TestBackwardCompatTxnFields(t *testing.T) {
require.NoError(t, err)
}
- ep, _, _ := makeSampleEnvWithVersion(1)
+ ep := defaultSigParamsWithVersion(1)
ep.TxnGroup[0].Lsig.Logic = ops.Program
// check failure with version check
_, err = EvalSignature(0, ep)
- require.Error(t, err)
- require.Contains(t, err.Error(), "greater than protocol supported version")
+ require.ErrorContains(t, err, "greater than protocol supported version")
// check opcodes failures
ops.Program[0] = 1 // set version to 1
_, err = EvalSignature(0, ep)
- require.Error(t, err)
- require.Contains(t, err.Error(), "invalid txn field")
+ require.ErrorContains(t, err, "invalid txn field")
// check opcodes failures
ops.Program[0] = 0 // set version to 0
_, err = EvalSignature(0, ep)
- require.Error(t, err)
- require.Contains(t, err.Error(), "invalid txn field")
+ require.ErrorContains(t, err, "invalid txn field")
}
}
}
@@ -480,7 +466,7 @@ func TestBackwardCompatAssemble(t *testing.T) {
v := v
t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) {
t.Parallel()
- testLogic(t, source, v, defaultEvalParams())
+ testLogic(t, source, v, nil)
})
}
}
diff --git a/data/transactions/logic/blackbox_test.go b/data/transactions/logic/blackbox_test.go
index 79029f2c3..55e7bde4f 100644
--- a/data/transactions/logic/blackbox_test.go
+++ b/data/transactions/logic/blackbox_test.go
@@ -36,12 +36,6 @@ func TestNewAppEvalParams(t *testing.T) {
partitiontest.PartitionTest(t)
t.Parallel()
- params := []config.ConsensusParams{
- {Application: true, MaxAppProgramCost: 700},
- config.Consensus[protocol.ConsensusV29],
- config.Consensus[protocol.ConsensusFuture],
- }
-
// Create some sample transactions. The main reason this a blackbox test
// (_test package) is to have access to txntest.
payment := txntest.Txn{
@@ -78,13 +72,18 @@ func TestNewAppEvalParams(t *testing.T) {
{[]transactions.SignedTxnWithAD{appcall1, payment, appcall2}, 2},
}
+ params := []config.ConsensusParams{
+ {Application: true, MaxAppProgramCost: 700},
+ config.Consensus[protocol.ConsensusV29],
+ config.Consensus[protocol.ConsensusFuture],
+ }
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, &param, nil)
+ ep := logic.NewAppEvalParams(testCase.group, &param, nil)
require.NotNil(t, ep)
require.Equal(t, ep.TxnGroup, testCase.group)
require.Equal(t, *ep.Proto, param)
diff --git a/data/transactions/logic/debugger.go b/data/transactions/logic/debugger.go
index 2349b2463..eda022e72 100644
--- a/data/transactions/logic/debugger.go
+++ b/data/transactions/logic/debugger.go
@@ -290,8 +290,8 @@ func (a *debuggerEvalTracerAdaptor) refreshDebugState(cx *EvalContext, evalError
stack[i] = sv.toEncodedTealValue()
}
- scratch := make([]basics.TealValue, len(cx.scratch))
- for i, sv := range cx.scratch {
+ scratch := make([]basics.TealValue, len(cx.Scratch))
+ for i, sv := range cx.Scratch {
scratch[i] = sv.toEncodedTealValue()
}
diff --git a/data/transactions/logic/debugger_eval_test.go b/data/transactions/logic/debugger_eval_test.go
index 4e6b37b85..d99452f10 100644
--- a/data/transactions/logic/debugger_eval_test.go
+++ b/data/transactions/logic/debugger_eval_test.go
@@ -171,7 +171,7 @@ func TestDebuggerLogicSigEval(t *testing.T) {
t.Run(testCase.name, func(t *testing.T) {
t.Parallel()
testDbg := testDebugger{}
- ep := DefaultEvalParams()
+ ep := DefaultSigParams()
ep.Tracer = MakeEvalTracerDebuggerAdaptor(&testDbg)
TestLogic(t, testCase.program, AssemblerMaxVersion, ep, testCase.evalProblems...)
@@ -191,7 +191,7 @@ func TestDebuggerTopLeveLAppEval(t *testing.T) {
t.Run(testCase.name, func(t *testing.T) {
t.Parallel()
testDbg := testDebugger{}
- ep := DefaultEvalParams()
+ ep := DefaultAppParams()
ep.Tracer = MakeEvalTracerDebuggerAdaptor(&testDbg)
TestApp(t, testCase.program, ep, testCase.evalProblems...)
@@ -291,7 +291,7 @@ func TestCallStackUpdate(t *testing.T) {
}
testDbg := testDebugger{}
- ep := DefaultEvalParams()
+ ep := DefaultSigParams()
ep.Tracer = MakeEvalTracerDebuggerAdaptor(&testDbg)
TestLogic(t, TestCallStackProgram, AssemblerMaxVersion, ep)
diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go
index 8d764e442..f1d698383 100644
--- a/data/transactions/logic/eval.go
+++ b/data/transactions/logic/eval.go
@@ -48,9 +48,6 @@ import (
"github.com/algorand/go-algorand/protocol"
)
-// evalMaxVersion is the max version we can interpret and run
-const evalMaxVersion = LogicVersion
-
// The constants below control opcode evaluation and MAY NOT be changed without
// gating them by version. Old programs need to retain their old behavior.
@@ -181,13 +178,13 @@ func stackValueFromTealValue(tv basics.TealValue) (sv stackValue, err error) {
return
}
-// ComputeMinAvmVersion calculates the minimum safe AVM version that may be
+// computeMinAvmVersion calculates the minimum safe AVM version that may be
// used by a transaction in this group. It is important to prevent
// newly-introduced transaction fields from breaking assumptions made by older
// versions of the AVM. If one of the transactions in a group will execute a TEAL
// program whose version predates a given field, that field must not be set
// anywhere in the transaction group, or the group will be rejected.
-func ComputeMinAvmVersion(group []transactions.SignedTxnWithAD) uint64 {
+func computeMinAvmVersion(group []transactions.SignedTxnWithAD) uint64 {
var minVersion uint64
for _, txn := range group {
if !txn.Txn.RekeyTo.IsZero() {
@@ -208,7 +205,7 @@ func ComputeMinAvmVersion(group []transactions.SignedTxnWithAD) uint64 {
// only exposes things that consensus has already agreed upon, so it is
// "stateless" for signature purposes.
type LedgerForSignature interface {
- BlockHdrCached(basics.Round) (bookkeeping.BlockHeader, error)
+ BlockHdr(basics.Round) (bookkeeping.BlockHeader, error)
}
// NoHeaderLedger is intended for debugging situations in which it is reasonable
@@ -216,8 +213,8 @@ type LedgerForSignature interface {
type NoHeaderLedger struct {
}
-// BlockHdrCached always errors
-func (NoHeaderLedger) BlockHdrCached(basics.Round) (bookkeeping.BlockHeader, error) {
+// BlockHdr always errors
+func (NoHeaderLedger) BlockHdr(basics.Round) (bookkeeping.BlockHeader, error) {
return bookkeeping.BlockHeader{}, fmt.Errorf("no block header access")
}
@@ -227,7 +224,6 @@ type LedgerForLogic interface {
Authorizer(addr basics.Address) (basics.Address, error)
Round() basics.Round
PrevTimestamp() int64
- BlockHdrCached(basics.Round) (bookkeeping.BlockHeader, error)
AssetHolding(addr basics.Address, assetIdx basics.AssetIndex) (basics.AssetHolding, error)
AssetParams(aidx basics.AssetIndex) (basics.AssetParams, basics.Address, error)
@@ -276,6 +272,8 @@ func RuntimeEvalConstants() EvalConstants {
// EvalParams contains data that comes into condition evaluation.
type EvalParams struct {
+ runMode RunMode
+
Proto *config.ConsensusParams
Trace *strings.Builder
@@ -292,10 +290,9 @@ type EvalParams struct {
// 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
- // MinAvmVersion is nil, we will compute it ourselves
- MinAvmVersion *uint64
+ // minAvmVersion is the minimum allowed AVM version of a program to be
+ // evaluated in TxnGroup.
+ minAvmVersion uint64
// Amount "overpaid" by the transactions of the group. Often 0. When
// positive, it can be spent by inner transactions. Shared across a group's
@@ -308,6 +305,9 @@ type EvalParams struct {
// Total pool of app call budget in a group transaction (nil before budget pooling enabled)
PooledApplicationBudget *int
+ // Total pool of logicsig budget in a group transaction (nil before lsig pooling enabled)
+ PooledLogicSigBudget *int
+
// Total allowable inner txns in a group transaction (nil before inner pooling enabled)
pooledAllowedInners *int
@@ -355,58 +355,75 @@ func copyWithClearAD(txgroup []transactions.SignedTxnWithAD) []transactions.Sign
return copy
}
-// NewEvalParams creates an EvalParams to use while evaluating a top-level txgroup
-func NewEvalParams(txgroup []transactions.SignedTxnWithAD, proto *config.ConsensusParams, specials *transactions.SpecialAddresses) *EvalParams {
- apps := 0
+// NewSigEvalParams creates an EvalParams to be used while evaluating a group's logicsigs
+func NewSigEvalParams(txgroup []transactions.SignedTxn, proto *config.ConsensusParams, ls LedgerForSignature) *EvalParams {
+ lsigs := 0
for _, tx := range txgroup {
- if tx.Txn.Type == protocol.ApplicationCallTx {
- apps++
+ if !tx.Lsig.Blank() {
+ lsigs++
}
}
- // Make a simpler EvalParams that is good enough to evaluate LogicSigs.
- if apps == 0 {
- return &EvalParams{
- TxnGroup: txgroup,
- Proto: proto,
- Specials: specials,
+ var pooledLogicBudget *int
+ if lsigs > 0 { // don't allocate if no lsigs
+ if proto.EnableLogicSigCostPooling {
+ pooledLogicBudget = new(int)
+ *pooledLogicBudget = len(txgroup) * int(proto.LogicSigMaxCost)
}
}
- minAvmVersion := ComputeMinAvmVersion(txgroup)
+ withADs := transactions.WrapSignedTxnsWithAD(txgroup)
+ return &EvalParams{
+ runMode: ModeSig,
+ TxnGroup: withADs,
+ Proto: proto,
+ minAvmVersion: computeMinAvmVersion(withADs),
+ SigLedger: ls,
+ PooledLogicSigBudget: pooledLogicBudget,
+ }
+}
+
+// NewAppEvalParams creates an EvalParams to use while evaluating a top-level txgroup.
+func NewAppEvalParams(txgroup []transactions.SignedTxnWithAD, proto *config.ConsensusParams, specials *transactions.SpecialAddresses) *EvalParams {
+ apps := 0
+ for _, tx := range txgroup {
+ if tx.Txn.Type == protocol.ApplicationCallTx {
+ apps++
+ }
+ }
var pooledApplicationBudget *int
var pooledAllowedInners *int
+ var credit *uint64
- credit := feeCredit(txgroup, proto.MinTxnFee)
+ if apps > 0 { // none of these allocations needed if no apps
+ credit = new(uint64)
+ *credit = feeCredit(txgroup, proto.MinTxnFee)
- if proto.EnableAppCostPooling {
- pooledApplicationBudget = new(int)
- *pooledApplicationBudget = apps * proto.MaxAppProgramCost
- }
+ if proto.EnableAppCostPooling {
+ pooledApplicationBudget = new(int)
+ *pooledApplicationBudget = apps * proto.MaxAppProgramCost
+ }
- if proto.EnableInnerTransactionPooling {
- pooledAllowedInners = new(int)
- *pooledAllowedInners = proto.MaxTxGroupSize * proto.MaxInnerTransactions
+ if proto.EnableInnerTransactionPooling {
+ pooledAllowedInners = new(int)
+ *pooledAllowedInners = proto.MaxTxGroupSize * proto.MaxInnerTransactions
+ }
}
ep := &EvalParams{
+ runMode: ModeApp,
TxnGroup: copyWithClearAD(txgroup),
Proto: proto,
Specials: specials,
pastScratch: make([]*scratchSpace, len(txgroup)),
- MinAvmVersion: &minAvmVersion,
- FeeCredit: &credit,
+ minAvmVersion: computeMinAvmVersion(txgroup),
+ FeeCredit: credit,
PooledApplicationBudget: pooledApplicationBudget,
pooledAllowedInners: pooledAllowedInners,
appAddrCache: make(map[basics.AppIndex]basics.Address),
EvalConstants: RuntimeEvalConstants(),
}
- // resources are computed after ep is constructed because app addresses are
- // calculated there, and we'd like to use the caching mechanism built into
- // the EvalParams. Perhaps we can make the computation even lazier, so it is
- // only computed if needed.
- ep.available = ep.computeAvailability()
return ep
}
@@ -444,12 +461,12 @@ func feeCredit(txgroup []transactions.SignedTxnWithAD, minFee uint64) uint64 {
// NewInnerEvalParams creates an EvalParams to be used while evaluating an inner group txgroup
func NewInnerEvalParams(txg []transactions.SignedTxnWithAD, caller *EvalContext) *EvalParams {
- minAvmVersion := ComputeMinAvmVersion(txg)
+ minAvmVersion := computeMinAvmVersion(txg)
// Can't happen currently, since earliest inner callable version is higher
// than any minimum imposed otherwise. But is correct to inherit a stronger
// restriction from above, in case of future restriction.
- if minAvmVersion < *caller.MinAvmVersion {
- minAvmVersion = *caller.MinAvmVersion
+ if minAvmVersion < caller.minAvmVersion {
+ minAvmVersion = caller.minAvmVersion
}
// Unlike NewEvalParams, do not add fee credit here. opTxSubmit has already done so.
@@ -463,6 +480,7 @@ func NewInnerEvalParams(txg []transactions.SignedTxnWithAD, caller *EvalContext)
}
ep := &EvalParams{
+ runMode: ModeApp,
Proto: caller.Proto,
Trace: caller.Trace,
TxnGroup: txg,
@@ -471,7 +489,7 @@ func NewInnerEvalParams(txg []transactions.SignedTxnWithAD, caller *EvalContext)
SigLedger: caller.SigLedger,
Ledger: caller.Ledger,
Tracer: caller.Tracer,
- MinAvmVersion: &minAvmVersion,
+ minAvmVersion: minAvmVersion,
FeeCredit: caller.FeeCredit,
Specials: caller.Specials,
PooledApplicationBudget: caller.PooledApplicationBudget,
@@ -534,19 +552,25 @@ func (ep *EvalParams) log() logging.Logger {
// package. For example, after a acfg transaction is processed, the AD created
// by the acfg is added to the EvalParams this way.
func (ep *EvalParams) RecordAD(gi int, ad transactions.ApplyData) {
- if ep.available == nil {
- // This is a simplified ep. It won't be used for app evaluation, and
- // shares the TxnGroup memory with the caller. Don't touch anything!
- return
+ if ep.runMode == ModeSig {
+ // We should not be touching a signature mode EvalParams as it shares
+ // memory with its caller. LogicSigs are supposed to be stateless!
+ panic("RecordAD called in signature mode")
}
ep.TxnGroup[gi].ApplyData = ad
if aid := ad.ConfigAsset; aid != 0 {
+ if ep.available == nil { // here, and below, we may need to make `ep.available`
+ ep.available = ep.computeAvailability()
+ }
if ep.available.createdAsas == nil {
ep.available.createdAsas = make(map[basics.AssetIndex]struct{})
}
ep.available.createdAsas[aid] = struct{}{}
}
if aid := ad.ApplicationID; aid != 0 {
+ if ep.available == nil {
+ ep.available = ep.computeAvailability()
+ }
if ep.available.createdApps == nil {
ep.available.createdApps = make(map[basics.AppIndex]struct{})
}
@@ -599,7 +623,7 @@ type EvalContext struct {
intc []uint64
bytec [][]byte
version uint64
- scratch scratchSpace
+ Scratch scratchSpace
subtxns []transactions.SignedTxnWithAD // place to build for itxn_submit
cost int // cost incurred so far
@@ -631,14 +655,11 @@ func (cx *EvalContext) RunMode() RunMode {
// PC returns the program counter of the current application being evaluated
func (cx *EvalContext) PC() int { return cx.pc }
-// DisassembleLine disassembles the line pointing to current PC
-func (cx *EvalContext) DisassembleLine() (string, error) {
- currentDisassembleState := &disassembleState{
- program: cx.program, pc: cx.pc, numericTargets: true, intc: cx.intc, bytec: cx.bytec,
- }
- currentOpSpec := &opsByOpcode[cx.version][cx.program[cx.pc]]
- return disassemble(currentDisassembleState, currentOpSpec)
-}
+// GetOpSpec queries for the OpSpec w.r.t. current program byte.
+func (cx *EvalContext) GetOpSpec() OpSpec { return opsByOpcode[cx.version][cx.program[cx.pc]] }
+
+// GetProgram queries for the current program
+func (cx *EvalContext) GetProgram() []byte { return cx.program }
// avmType describes the type of a value on the operand stack
// avmTypes are a subset of StackTypes
@@ -930,6 +951,9 @@ func EvalContract(program []byte, gi int, aid basics.AppIndex, params *EvalParam
if aid == 0 {
return false, nil, errors.New("0 appId in contract eval")
}
+ if params.runMode != ModeApp {
+ return false, nil, fmt.Errorf("attempt to evaluate a contract with %s mode EvalParams", params.runMode)
+ }
cx := EvalContext{
EvalParams: params,
runMode: ModeApp,
@@ -944,6 +968,10 @@ func EvalContract(program []byte, gi int, aid basics.AppIndex, params *EvalParam
}
}
+ if cx.EvalParams.available == nil {
+ cx.EvalParams.available = cx.EvalParams.computeAvailability()
+ }
+
// If this is a creation...
if cx.txn.Txn.ApplicationID == 0 {
// make any "0 index" box refs available now that we have an appID.
@@ -1015,7 +1043,7 @@ func EvalContract(program []byte, gi int, aid basics.AppIndex, params *EvalParam
// Save scratch for `gload`. We used to copy, but cx.scratch is quite large,
// about 8k, and caused measurable CPU and memory demands. Of course, these
// should never be changed by later transactions.
- cx.pastScratch[cx.groupIndex] = &cx.scratch
+ cx.pastScratch[cx.groupIndex] = &cx.Scratch
return pass, &cx, err
}
@@ -1033,6 +1061,9 @@ func EvalSignatureFull(gi int, params *EvalParams) (bool, *EvalContext, error) {
if params.SigLedger == nil {
return false, nil, errors.New("no sig ledger in signature eval")
}
+ if params.runMode != ModeSig {
+ return false, nil, fmt.Errorf("attempt to evaluate a signature with %s mode EvalParams", params.runMode)
+ }
cx := EvalContext{
EvalParams: params,
runMode: ModeSig,
@@ -1074,21 +1105,16 @@ func eval(program []byte, cx *EvalContext) (pass bool, err error) {
}
}()
- // Avoid returning for any reason until after cx.debugState is setup. That
- // require cx to be minimally setup, too.
-
- version, vlen, verr := versionCheck(program, cx.EvalParams)
- // defer verr check until after cx and debugState is setup
-
- cx.version = version
- cx.pc = vlen
// 16 is chosen to avoid growth for small programs, and so that repeated
// doublings lead to a number just a bit above 1000, the max stack height.
cx.Stack = make([]stackValue, 0, 16)
- cx.program = program
cx.txn.EvalDelta.GlobalDelta = basics.StateDelta{}
cx.txn.EvalDelta.LocalDeltas = make(map[uint64]basics.StateDelta)
+ // We get the error here, but defer reporting so that the Tracer can be
+ // called with an basically initialized cx.
+ verr := cx.begin(program)
+
if cx.Tracer != nil {
cx.Tracer.BeforeProgram(cx)
@@ -1187,20 +1213,16 @@ func check(program []byte, params *EvalParams, mode RunMode) (err error) {
return errLogicSigNotSupported
}
- version, vlen, err := versionCheck(program, params)
- if err != nil {
- return err
- }
-
var cx EvalContext
- cx.version = version
- cx.pc = vlen
cx.EvalParams = params
cx.runMode = mode
- cx.program = program
cx.branchTargets = make([]bool, len(program)+1) // teal v2 allowed jumping to the end of the prog
cx.instructionStarts = make([]bool, len(program)+1)
+ if err := cx.begin(program); err != nil {
+ return err
+ }
+
maxCost := cx.remainingBudget()
staticCost := 0
for cx.pc < len(cx.program) {
@@ -1210,7 +1232,7 @@ func check(program []byte, params *EvalParams, mode RunMode) (err error) {
return fmt.Errorf("pc=%3d %w", cx.pc, err)
}
staticCost += stepCost
- if version < backBranchEnabledVersion && staticCost > maxCost {
+ if cx.version < backBranchEnabledVersion && staticCost > maxCost {
return fmt.Errorf("pc=%3d static cost budget of %d exceeded", cx.pc, maxCost)
}
if cx.pc <= prevpc {
@@ -1224,26 +1246,31 @@ func check(program []byte, params *EvalParams, mode RunMode) (err error) {
return nil
}
-func versionCheck(program []byte, params *EvalParams) (uint64, int, error) {
+func (cx *EvalContext) begin(program []byte) error {
+ cx.program = program
+
version, vlen, err := transactions.ProgramVersion(program)
if err != nil {
- return 0, 0, err
+ return err
}
- if version > evalMaxVersion {
- return 0, 0, fmt.Errorf("program version %d greater than max supported version %d", version, evalMaxVersion)
+ if version > LogicVersion {
+ return fmt.Errorf("program version %d greater than max supported version %d", version, LogicVersion)
}
- if version > params.Proto.LogicSigVersion {
- return 0, 0, fmt.Errorf("program version %d greater than protocol supported version %d", version, params.Proto.LogicSigVersion)
+ if version > cx.Proto.LogicSigVersion {
+ return fmt.Errorf("program version %d greater than protocol supported version %d", version, cx.Proto.LogicSigVersion)
}
-
- if params.MinAvmVersion == nil {
- minVersion := ComputeMinAvmVersion(params.TxnGroup)
- params.MinAvmVersion = &minVersion
+ if err != nil {
+ return err
}
- if version < *params.MinAvmVersion {
- return 0, 0, fmt.Errorf("program version must be >= %d for this transaction group, but have version %d", *params.MinAvmVersion, version)
+
+ cx.version = version
+ cx.pc = vlen
+
+ if cx.version < cx.EvalParams.minAvmVersion {
+ return fmt.Errorf("program version must be >= %d for this transaction group, but have version %d",
+ cx.minAvmVersion, cx.version)
}
- return version, vlen, nil
+ return nil
}
func opCompat(expected, got avmType) bool {
@@ -1283,6 +1310,9 @@ func (cx *EvalContext) AppID() basics.AppIndex {
func (cx *EvalContext) remainingBudget() int {
if cx.runMode == ModeSig {
+ if cx.PooledLogicSigBudget != nil {
+ return *cx.PooledLogicSigBudget
+ }
return int(cx.Proto.LogicSigMaxCost) - cx.cost
}
@@ -1350,23 +1380,22 @@ func (cx *EvalContext) step() error {
return fmt.Errorf("%3d %s returned 0 cost", cx.pc, spec.Name)
}
}
- cx.cost += opcost
- if cx.PooledApplicationBudget != nil {
- *cx.PooledApplicationBudget -= opcost
- }
- if cx.remainingBudget() < 0 {
- // We're not going to execute the instruction, so give the cost back.
- // This only matters if this is an inner ClearState - the caller should
- // not be over debited. (Normally, failure causes total txtree failure.)
- cx.cost -= opcost
- if cx.PooledApplicationBudget != nil {
- *cx.PooledApplicationBudget += opcost
- }
+ if opcost > cx.remainingBudget() {
return fmt.Errorf("pc=%3d dynamic cost budget exceeded, executing %s: local program cost was %d",
cx.pc, spec.Name, cx.cost)
}
+ cx.cost += opcost
+ // At most one of these pooled budgets will be non-nil, perhaps we could
+ // collapse to one variable, but there are some complex callers trying to
+ // set up big budgets for debugging runs that would have to be looked at.
+ switch {
+ case cx.PooledApplicationBudget != nil:
+ *cx.PooledApplicationBudget -= opcost
+ case cx.PooledLogicSigBudget != nil:
+ *cx.PooledLogicSigBudget -= opcost
+ }
preheight := len(cx.Stack)
err := spec.op(cx)
@@ -1892,11 +1921,11 @@ func opShiftRight(cx *EvalContext) error {
func opSqrt(cx *EvalContext) error {
/*
- It would not be safe to use math.Sqrt, because we would have to
- convert our u64 to an f64, but f64 cannot represent all u64s exactly.
+ It would not be safe to use math.Sqrt, because we would have to convert our
+ u64 to an f64, but f64 cannot represent all u64s exactly.
- This algorithm comes from Jack W. Crenshaw's 1998 article in Embedded:
- http://www.embedded.com/electronics-blogs/programmer-s-toolbox/4219659/Integer-Square-Roots
+ This algorithm comes from Jack W. Crenshaw's 1998 article in Embedded:
+ http://www.embedded.com/electronics-blogs/programmer-s-toolbox/4219659/Integer-Square-Roots
*/
last := len(cx.Stack) - 1
@@ -2919,7 +2948,7 @@ func (cx *EvalContext) txnFieldToStack(stxn *transactions.SignedTxnWithAD, fs *t
if err != nil {
return sv, err
}
- hdr, err := cx.SigLedger.BlockHdrCached(rnd)
+ hdr, err := cx.SigLedger.BlockHdr(rnd)
if err != nil {
return sv, err
}
@@ -3833,24 +3862,24 @@ func opEcdsaPkRecover(cx *EvalContext) error {
func opLoad(cx *EvalContext) error {
n := cx.program[cx.pc+1]
- cx.Stack = append(cx.Stack, cx.scratch[n])
+ cx.Stack = append(cx.Stack, cx.Scratch[n])
return nil
}
func opLoads(cx *EvalContext) error {
last := len(cx.Stack) - 1
n := cx.Stack[last].Uint
- if n >= uint64(len(cx.scratch)) {
+ if n >= uint64(len(cx.Scratch)) {
return fmt.Errorf("invalid Scratch index %d", n)
}
- cx.Stack[last] = cx.scratch[n]
+ cx.Stack[last] = cx.Scratch[n]
return nil
}
func opStore(cx *EvalContext) error {
n := cx.program[cx.pc+1]
last := len(cx.Stack) - 1
- cx.scratch[n] = cx.Stack[last]
+ cx.Scratch[n] = cx.Stack[last]
cx.Stack = cx.Stack[:last]
return nil
}
@@ -3859,10 +3888,10 @@ func opStores(cx *EvalContext) error {
last := len(cx.Stack) - 1
prev := last - 1
n := cx.Stack[prev].Uint
- if n >= uint64(len(cx.scratch)) {
+ if n >= uint64(len(cx.Scratch)) {
return fmt.Errorf("invalid Scratch index %d", n)
}
- cx.scratch[n] = cx.Stack[last]
+ cx.Scratch[n] = cx.Stack[last]
cx.Stack = cx.Stack[:prev]
return nil
}
@@ -3872,7 +3901,7 @@ func opGloadImpl(cx *EvalContext, gi int, scratchIdx byte, opName string) (stack
if gi >= len(cx.TxnGroup) {
return none, fmt.Errorf("%s lookup TxnGroup[%d] but it only has %d", opName, gi, len(cx.TxnGroup))
}
- if int(scratchIdx) >= len(cx.scratch) {
+ if int(scratchIdx) >= len(cx.Scratch) {
return none, fmt.Errorf("invalid Scratch index %d", scratchIdx)
}
if cx.TxnGroup[gi].Txn.Type != protocol.ApplicationCallTx {
@@ -5797,7 +5826,7 @@ func opBlock(cx *EvalContext) error {
return fmt.Errorf("invalid block field %s", f)
}
- hdr, err := cx.SigLedger.BlockHdrCached(round)
+ hdr, err := cx.SigLedger.BlockHdr(round)
if err != nil {
return err
}
diff --git a/data/transactions/logic/evalAppTxn_test.go b/data/transactions/logic/evalAppTxn_test.go
index f33fce350..06fee09c1 100644
--- a/data/transactions/logic/evalAppTxn_test.go
+++ b/data/transactions/logic/evalAppTxn_test.go
@@ -592,7 +592,6 @@ func TestNumInnerShallow(t *testing.T) {
ep, tx, ledger := MakeSampleEnv()
ep.Proto.EnableInnerTransactionPooling = false
- ep.Reset()
ledger.NewApp(tx.Receiver, 888, basics.AppParams{})
ledger.NewAccount(appAddr(888), 1000000)
TestApp(t, pay+";int 1", ep)
@@ -2754,20 +2753,17 @@ func TestNumInnerDeep(t *testing.T) {
itxn_submit
`
- tx := txntest.Txn{
- Type: protocol.ApplicationCallTx,
- ApplicationID: 888,
- ForeignApps: []basics.AppIndex{basics.AppIndex(222)},
- }.SignedTxnWithAD()
- require.Equal(t, 888, int(tx.Txn.ApplicationID))
- ledger := NewLedger(nil)
+ ep, tx, ledger := MakeSampleEnv()
+
+ tx.Type = protocol.ApplicationCallTx
+ tx.ApplicationID = 888
+ tx.ForeignApps = []basics.AppIndex{basics.AppIndex(222)}
- pay3 := TestProg(t, pay+pay+pay+"int 1;", AssemblerMaxVersion).Program
- ledger.NewApp(tx.Txn.Receiver, 222, basics.AppParams{
- ApprovalProgram: pay3,
+ ledger.NewApp(tx.Receiver, 222, basics.AppParams{
+ ApprovalProgram: TestProg(t, pay+pay+pay+"int 1;", AssemblerMaxVersion).Program,
})
- ledger.NewApp(tx.Txn.Receiver, 888, basics.AppParams{})
+ ledger.NewApp(tx.Receiver, 888, basics.AppParams{})
ledger.NewAccount(appAddr(888), 1_000_000)
callpay3 := `itxn_begin
@@ -2775,10 +2771,6 @@ int appl; itxn_field TypeEnum
int 222; itxn_field ApplicationID
itxn_submit
`
- txg := []transactions.SignedTxnWithAD{tx}
- ep := NewEvalParams(txg, MakeTestProto(), &transactions.SpecialAddresses{})
- ep.Ledger = ledger
- ep.SigLedger = ledger
TestApp(t, callpay3+"int 1", ep, "insufficient balance") // inner contract needs money
ledger.NewAccount(appAddr(222), 1_000_000)
diff --git a/data/transactions/logic/evalBench_test.go b/data/transactions/logic/evalBench_test.go
index 240da71bd..b9f61f0fa 100644
--- a/data/transactions/logic/evalBench_test.go
+++ b/data/transactions/logic/evalBench_test.go
@@ -33,16 +33,11 @@ func BenchmarkCheckSignature(b *testing.B) {
ops, err := logic.AssembleString(txntest.TmLsig)
require.NoError(b, err)
stxns := []transactions.SignedTxn{{Txn: txns[3].Txn(), Lsig: transactions.LogicSig{Logic: ops.Program}}}
- txgroup := transactions.WrapSignedTxnsWithAD(stxns)
- ep := logic.EvalParams{
- Proto: &proto,
- TxnGroup: txgroup,
- SigLedger: &logic.NoHeaderLedger{},
- }
+ ep := logic.NewSigEvalParams(stxns, &proto, &logic.NoHeaderLedger{})
b.ResetTimer()
for i := 0; i < b.N; i++ {
- err = logic.CheckSignature(0, &ep)
+ err = logic.CheckSignature(0, ep)
require.NoError(b, err)
}
}
diff --git a/data/transactions/logic/evalCrypto_test.go b/data/transactions/logic/evalCrypto_test.go
index 766fe66ba..e4b22e497 100644
--- a/data/transactions/logic/evalCrypto_test.go
+++ b/data/transactions/logic/evalCrypto_test.go
@@ -113,10 +113,12 @@ func TestVrfVerify(t *testing.T) {
partitiontest.PartitionTest(t)
t.Parallel()
- ep, _, _ := makeSampleEnv()
+ ep := defaultAppParams()
testApp(t, notrack("int 1; int 2; int 3; vrf_verify VrfAlgorand"), ep, "arg 0 wanted")
testApp(t, notrack("byte 0x1122; int 2; int 3; vrf_verify VrfAlgorand"), ep, "arg 1 wanted")
testApp(t, notrack("byte 0x1122; byte 0x2233; int 3; vrf_verify VrfAlgorand"), ep, "arg 2 wanted")
+
+ ep = defaultSigParams()
testLogic(t, "byte 0x1122; byte 0x2233; byte 0x3344; vrf_verify VrfAlgorand", LogicVersion, ep, "vrf proof wrong size")
// 80 byte proof
testLogic(t, "byte 0x1122; int 80; bzero; byte 0x3344; vrf_verify VrfAlgorand", LogicVersion, ep, "vrf pubkey wrong size")
@@ -210,18 +212,18 @@ ed25519verify`, pkStr), v)
var txn transactions.SignedTxn
txn.Lsig.Logic = ops.Program
txn.Lsig.Args = [][]byte{data[:], sig[:]}
- testLogicBytes(t, ops.Program, defaultEvalParams(txn))
+ testLogicBytes(t, ops.Program, defaultSigParams(txn))
// short sig will fail
txn.Lsig.Args[1] = sig[1:]
- testLogicBytes(t, ops.Program, defaultEvalParams(txn), "invalid signature")
+ testLogicBytes(t, ops.Program, defaultSigParams(txn), "invalid signature")
// flip a bit and it should not pass
msg1 := "52fdfc072182654f163f5f0f9a621d729566c74d0aa413bf009c9800418c19cd"
data1, err := hex.DecodeString(msg1)
require.NoError(t, err)
txn.Lsig.Args = [][]byte{data1, sig[:]}
- testLogicBytes(t, ops.Program, defaultEvalParams(txn), "REJECT")
+ testLogicBytes(t, ops.Program, defaultSigParams(txn), "REJECT")
})
}
}
@@ -250,18 +252,18 @@ ed25519verify_bare`, pkStr), v)
var txn transactions.SignedTxn
txn.Lsig.Logic = ops.Program
txn.Lsig.Args = [][]byte{data[:], sig[:]}
- testLogicBytes(t, ops.Program, defaultEvalParams(txn))
+ testLogicBytes(t, ops.Program, defaultSigParams(txn))
// short sig will fail
txn.Lsig.Args[1] = sig[1:]
- testLogicBytes(t, ops.Program, defaultEvalParams(txn), "invalid signature")
+ testLogicBytes(t, ops.Program, defaultSigParams(txn), "invalid signature")
// flip a bit and it should not pass
msg1 := "52fdfc072182654f163f5f0f9a621d729566c74d0aa413bf009c9800418c19cd"
data1, err := hex.DecodeString(msg1)
require.NoError(t, err)
txn.Lsig.Args = [][]byte{data1, sig[:]}
- testLogicBytes(t, ops.Program, defaultEvalParams(txn), "REJECT")
+ testLogicBytes(t, ops.Program, defaultSigParams(txn), "REJECT")
})
}
}
@@ -460,7 +462,7 @@ ecdsa_verify Secp256k1`, hex.EncodeToString(r), hex.EncodeToString(s), hex.Encod
ops := testProg(t, source, 5)
var txn transactions.SignedTxn
txn.Lsig.Logic = ops.Program
- pass, err := EvalSignature(0, defaultEvalParamsWithVersion(5, txn))
+ pass, err := EvalSignature(0, defaultSigParamsWithVersion(5, txn))
require.NoError(t, err)
require.True(t, pass)
}
@@ -568,7 +570,7 @@ ecdsa_verify Secp256r1`, hex.EncodeToString(r), hex.EncodeToString(s), hex.Encod
ops := testProg(t, source, fidoVersion)
var txn transactions.SignedTxn
txn.Lsig.Logic = ops.Program
- pass, err := EvalSignature(0, defaultEvalParamsWithVersion(fidoVersion, txn))
+ pass, err := EvalSignature(0, defaultSigParamsWithVersion(fidoVersion, txn))
require.NoError(t, err)
require.True(t, pass)
}
@@ -707,7 +709,7 @@ ed25519verify`, pkStr), AssemblerMaxVersion)
var txn transactions.SignedTxn
txn.Lsig.Logic = programs[i]
txn.Lsig.Args = [][]byte{data[i][:], signatures[i][:]}
- ep := defaultEvalParams(txn)
+ ep := defaultSigParams(txn)
pass, err := EvalSignature(0, ep)
if !pass {
b.Log(hex.EncodeToString(programs[i]))
@@ -792,7 +794,7 @@ func benchmarkEcdsa(b *testing.B, source string, curve EcdsaCurve) {
var txn transactions.SignedTxn
txn.Lsig.Logic = data[i].programs
txn.Lsig.Args = [][]byte{data[i].msg[:], data[i].r, data[i].s, data[i].x, data[i].y, data[i].pk, {uint8(data[i].v)}}
- ep := defaultEvalParams(txn)
+ ep := defaultSigParams(txn)
pass, err := EvalSignature(0, ep)
if !pass {
b.Log(hex.EncodeToString(data[i].programs))
@@ -915,7 +917,7 @@ func benchmarkBn256(b *testing.B, source string) {
var txn transactions.SignedTxn
txn.Lsig.Logic = data[i].programs
txn.Lsig.Args = [][]byte{data[i].a, data[i].k, data[i].g1, data[i].g2}
- ep := defaultEvalParams(txn)
+ ep := defaultSigParams(txn)
pass, err := EvalSignature(0, ep)
if !pass {
b.Log(hex.EncodeToString(data[i].programs))
diff --git a/data/transactions/logic/evalStateful_test.go b/data/transactions/logic/evalStateful_test.go
index 17759ef67..0bebf9d26 100644
--- a/data/transactions/logic/evalStateful_test.go
+++ b/data/transactions/logic/evalStateful_test.go
@@ -26,6 +26,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
+ "github.com/algorand/go-algorand/config"
"github.com/algorand/go-algorand/data/basics"
"github.com/algorand/go-algorand/data/transactions"
"github.com/algorand/go-algorand/protocol"
@@ -50,18 +51,12 @@ func makeSampleEnv() (*EvalParams, *transactions.Transaction, *Ledger) {
}
func makeSampleEnvWithVersion(version uint64) (*EvalParams, *transactions.Transaction, *Ledger) {
- // We'd usually like an app in the group, so that the ep created is
- // "complete". But to keep as many old tests working as possible, if
- // version < appsEnabledVersion, don't put an appl txn in it.
- firstTxn := makeSampleTxn()
- if version >= appsEnabledVersion {
- firstTxn.Txn.Type = protocol.ApplicationCallTx
- }
- // avoid putting in a RekeyTo field if version < rekeyingEnabledVersion
- if version < rekeyingEnabledVersion {
- firstTxn.Txn.RekeyTo = basics.Address{}
+ if version < appsEnabledVersion {
+ panic("makeSampleEnv is for apps, but you've asked for a version before apps work")
}
- ep := defaultEvalParamsWithVersion(version, makeSampleTxnGroup(firstTxn)...)
+ firstTxn := makeSampleTxn()
+ firstTxn.Txn.Type = protocol.ApplicationCallTx
+ ep := defaultAppParamsWithVersion(version, makeSampleTxnGroup(firstTxn)...)
ledger := NewLedger(nil)
ep.SigLedger = ledger
ep.Ledger = ledger
@@ -265,8 +260,8 @@ log
tx.SelectionPK[:],
tx.Note,
}
- ep.TxnGroup[0].Txn.ApplicationID = 100
- ep.TxnGroup[0].Txn.ForeignAssets = []basics.AssetIndex{500} // needed since v4
+ tx.ApplicationID = 100
+ tx.ForeignAssets = []basics.AssetIndex{500} // needed since v4
params := basics.AssetParams{
Total: 1000,
Decimals: 2,
@@ -287,7 +282,9 @@ log
ledger.NewAsset(tx.Sender, 5, params)
if mode == ModeSig {
+ ep.runMode = ModeSig
testLogic(t, test, AssemblerMaxVersion, ep)
+ ep.runMode = ModeApp
} else {
testApp(t, test, ep)
}
@@ -296,8 +293,8 @@ log
// check err opcode work in both modes
source := "err"
- testLogic(t, source, AssemblerMaxVersion, defaultEvalParams(), "err opcode executed")
- testApp(t, source, defaultEvalParams(), "err opcode executed")
+ testLogic(t, source, AssemblerMaxVersion, nil, "err opcode executed")
+ testApp(t, source, nil, "err opcode executed")
// check that ed25519verify and arg is not allowed in stateful mode between v2-v4
disallowedV4 := []string{
@@ -310,7 +307,7 @@ log
}
for _, source := range disallowedV4 {
ops := testProg(t, source, 4)
- testAppBytes(t, ops.Program, defaultEvalParams(),
+ testAppBytes(t, ops.Program, nil,
"not allowed in current mode", "not allowed in current mode")
}
@@ -324,7 +321,7 @@ log
}
for _, source := range disallowed {
ops := testProg(t, source, AssemblerMaxVersion)
- testAppBytes(t, ops.Program, defaultEvalParams(),
+ testAppBytes(t, ops.Program, nil,
"not allowed in current mode", "not allowed in current mode")
}
@@ -363,7 +360,7 @@ log
if v < introduced {
continue
}
- testLogic(t, source, v, defaultEvalParamsWithVersion(v),
+ testLogic(t, source, v, defaultSigParamsWithVersion(v),
"not allowed in current mode", "not allowed in current mode")
}
}
@@ -374,6 +371,26 @@ log
require.True(t, modeAny.Any())
}
+func TestApplicationsDisallowOldTeal(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+
+ const source = "int 1"
+
+ txn := makeSampleTxn()
+ txn.Txn.Type = protocol.ApplicationCallTx
+ txn.Txn.RekeyTo = basics.Address{}
+ ep := defaultAppParams(txn)
+
+ for v := uint64(0); v < appsEnabledVersion; v++ {
+ ops := testProg(t, source, v)
+ e := fmt.Sprintf("program version must be >= %d", appsEnabledVersion)
+ testAppBytes(t, ops.Program, ep, e, e)
+ }
+
+ testApp(t, source, ep)
+}
+
func TestBalance(t *testing.T) {
partitiontest.PartitionTest(t)
t.Parallel()
@@ -403,13 +420,14 @@ func TestBalance(t *testing.T) {
})
}
-func testApps(t *testing.T, programs []string, txgroup []transactions.SignedTxn, version uint64, ledger *Ledger,
+func testApps(t *testing.T, programs []string, txgroup []transactions.SignedTxn, opt protoOpt, ledger *Ledger,
expected ...expect) *EvalParams {
t.Helper()
+ proto := makeTestProto(opt)
codes := make([][]byte, len(programs))
for i, program := range programs {
if program != "" {
- codes[i] = testProg(t, program, version).Program
+ codes[i] = testProg(t, program, proto.LogicSigVersion).Program
}
}
if txgroup == nil {
@@ -421,7 +439,7 @@ func testApps(t *testing.T, programs []string, txgroup []transactions.SignedTxn,
txgroup = append(txgroup, sample)
}
}
- ep := NewEvalParams(transactions.WrapSignedTxnsWithAD(txgroup), makeTestProtoV(version), &transactions.SpecialAddresses{})
+ ep := NewAppEvalParams(transactions.WrapSignedTxnsWithAD(txgroup), proto, &transactions.SpecialAddresses{})
if ledger == nil {
ledger = NewLedger(nil)
}
@@ -461,13 +479,20 @@ func testAppsBytes(t *testing.T, programs [][]byte, ep *EvalParams, expected ...
func testApp(t *testing.T, program string, ep *EvalParams, problems ...string) transactions.EvalDelta {
t.Helper()
+ if ep == nil {
+ ep = defaultAppParamsWithVersion(LogicVersion)
+ }
ops := testProg(t, program, ep.Proto.LogicSigVersion)
return testAppBytes(t, ops.Program, ep, problems...)
}
func testAppBytes(t *testing.T, program []byte, ep *EvalParams, problems ...string) transactions.EvalDelta {
t.Helper()
- ep.reset()
+ if ep == nil {
+ ep = defaultAppParamsWithVersion(LogicVersion)
+ } else {
+ ep.reset()
+ }
aid := ep.TxnGroup[0].Txn.ApplicationID
if aid == 0 {
aid = basics.AppIndex(888)
@@ -496,15 +521,13 @@ func testAppFull(t *testing.T, program []byte, gi int, aid basics.AppIndex, ep *
require.Fail(t, "Misused testApp: %d problems", len(problems))
}
- sb := &strings.Builder{}
- ep.Trace = sb
+ ep.Trace = &strings.Builder{}
err := CheckContract(program, ep)
if checkProblem == "" {
- require.NoError(t, err, sb.String())
+ require.NoError(t, err, "Error in CheckContract %v", ep.Trace)
} else {
- require.Error(t, err, "Check\n%s\nExpected: %v", sb, checkProblem)
- require.Contains(t, err.Error(), checkProblem, sb.String())
+ require.ErrorContains(t, err, checkProblem, "Wrong error in CheckContract %v", ep.Trace)
}
// We continue on to check Eval() of things that failed Check() because it's
@@ -519,18 +542,17 @@ func testAppFull(t *testing.T, program []byte, gi int, aid basics.AppIndex, ep *
pass, err := EvalApp(program, gi, aid, ep)
delta := ep.TxnGroup[gi].EvalDelta
if evalProblem == "" {
- require.NoError(t, err, "Eval%s\nExpected: PASS", sb)
- require.True(t, pass, "Eval%s\nExpected: PASS", sb)
+ require.NoError(t, err, "Eval\n%sExpected: PASS", ep.Trace)
+ require.True(t, pass, "Eval\n%sExpected: PASS", ep.Trace)
return delta
}
// There is an evalProblem to check. REJECT is special and only means that
// the app didn't accept. Maybe it's an error, maybe it's just !pass.
if evalProblem == "REJECT" {
- require.True(t, err != nil || !pass, "Eval%s\nExpected: REJECT", sb)
+ require.True(t, err != nil || !pass, "Eval%s\nExpected: REJECT", ep.Trace)
} else {
- require.Error(t, err, "Eval\n%s\nExpected: %v", sb, evalProblem)
- require.Contains(t, err.Error(), evalProblem)
+ require.ErrorContains(t, err, evalProblem, "Wrong error in EvalContract %v", ep.Trace)
}
return delta
}
@@ -1022,9 +1044,9 @@ func testAssetsByVersion(t *testing.T, assetsTestProgram string, version uint64)
}
txn := makeSampleAppl(888)
- pre := defaultEvalParamsWithVersion(directRefEnabledVersion-1, txn)
+ pre := defaultAppParamsWithVersion(directRefEnabledVersion-1, txn)
require.GreaterOrEqual(t, version, uint64(directRefEnabledVersion))
- now := defaultEvalParamsWithVersion(version, txn)
+ now := defaultAppParamsWithVersion(version, txn)
ledger := NewLedger(
map[basics.Address]uint64{
txn.Txn.Sender: 1,
@@ -1529,7 +1551,7 @@ intc_1
var txn transactions.SignedTxn
txn.Txn.Type = protocol.ApplicationCallTx
txn.Txn.ApplicationID = 100
- ep := defaultEvalParams(txn)
+ ep := defaultAppParams(txn)
err := CheckContract(ops.Program, ep)
require.NoError(t, err)
@@ -2565,8 +2587,8 @@ func TestEnumFieldErrors(t *testing.T) { // nolint:paralleltest // manipulates t
txnFieldSpecs[Amount] = origSpec
}()
- testLogic(t, source, AssemblerMaxVersion, defaultEvalParams(), "Amount expected field type is []byte but got uint64")
- testApp(t, source, defaultEvalParams(), "Amount expected field type is []byte but got uint64")
+ testLogic(t, source, AssemblerMaxVersion, nil, "Amount expected field type is []byte but got uint64")
+ testApp(t, source, nil, "Amount expected field type is []byte but got uint64")
source = `global MinTxnFee`
@@ -2578,8 +2600,8 @@ func TestEnumFieldErrors(t *testing.T) { // nolint:paralleltest // manipulates t
globalFieldSpecs[MinTxnFee] = origMinTxnFs
}()
- testLogic(t, source, AssemblerMaxVersion, defaultEvalParams(), "MinTxnFee expected field type is []byte but got uint64")
- testApp(t, source, defaultEvalParams(), "MinTxnFee expected field type is []byte but got uint64")
+ testLogic(t, source, AssemblerMaxVersion, nil, "MinTxnFee expected field type is []byte but got uint64")
+ testApp(t, source, nil, "MinTxnFee expected field type is []byte but got uint64")
ep, tx, ledger := makeSampleEnv()
ledger.NewAccount(tx.Sender, 1)
@@ -2799,26 +2821,27 @@ 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 = 300
- tx.ForeignApps = []basics.AppIndex{tx.ApplicationID}
- tx.ForeignAssets = []basics.AssetIndex{400}
- tx.Boxes = []transactions.BoxRef{{
- Name: []byte("3456"),
- }}
- ep.TxnGroup[0].Lsig.Args = [][]byte{
+ tx0 := makeSampleTxn()
+ tx0.Txn.Type = protocol.ApplicationCallTx
+ tx0.Txn.ApplicationID = 300
+ tx0.Txn.ForeignApps = []basics.AppIndex{300}
+ tx0.Txn.ForeignAssets = []basics.AssetIndex{400}
+ tx0.Txn.Boxes = []transactions.BoxRef{{Name: []byte("3456")}}
+ tx0.Lsig.Args = [][]byte{
[]byte("aoeu"),
[]byte("aoeu"),
[]byte("aoeu2"),
[]byte("aoeu3"),
}
+ tx0.Lsig.Logic = ops.Program
// 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]
+ // `txn` opcode can look at things)
+ tx1 := tx0
+
+ sep, aep := defaultEvalParams(tx0, tx1)
+ ledger := aep.Ledger.(*Ledger)
- ep.pastScratch[0] = &scratchSpace{} // for gload
+ tx := tx0.Txn
ledger.NewAccount(tx.Sender, 1)
params := basics.AssetParams{
Total: 1000,
@@ -2842,39 +2865,34 @@ func TestReturnTypes(t *testing.T) {
ledger.NewLocal(tx.Receiver, 300, string(key), algoValue)
ledger.NewAccount(appAddr(300), 1000000)
- ep.reset() // for Trace and budget isolation
- ep.pastScratch[0] = &scratchSpace{} // for gload
// these allows the box_* opcodes that to work
ledger.CreateBox(300, "3456", 10)
- ep.ioBudget = 50
-
- cx := EvalContext{
- EvalParams: ep,
- runMode: m,
- groupIndex: 1,
- txn: &ep.TxnGroup[1],
- appID: 300,
- }
- // These set conditions for some ops that examine the group.
- // This convinces them all to work. Revisit.
- cx.TxnGroup[0].ConfigAsset = 100
+ // We are running gi=1, but we never ran gi=0. Set things up as
+ // if we did, so they can be accessed with gtxn, gload, gaid
+ aep.pastScratch[0] = &scratchSpace{}
+ aep.TxnGroup[0].ConfigAsset = 100
- // These little programs need not pass. Since the returned stack
- // 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)
+ var cx *EvalContext
+ if m == ModeApp {
+ _, cx, err = EvalContract(ops.Program, 1, 300, aep)
+ } else {
+ _, cx, err = EvalSignatureFull(1, sep)
+ }
+ // These little programs need not pass. We are just trying to
+ // examine cx.Stack for proper types/size after executing the
+ // opcode. But if it fails for any *other* reason, we're not
+ // doing a good test.
if err != nil {
// Allow the kinds of errors we expect, but fail for stuff
// that indicates the opcode itself failed.
reason := err.Error()
- if reason != "stack finished with bytes not int" &&
- !strings.HasPrefix(reason, "stack len is") {
- require.NoError(t, err, "%s: %s\n%s", name, err, ep.Trace)
+ if !strings.Contains(reason, "stack finished with bytes not int") &&
+ !strings.Contains(reason, "stack len is") {
+ require.NoError(t, err, "%s: %s\n%s", name, err, cx.Trace)
}
}
- require.Len(t, cx.Stack, len(spec.Return.Types), "%s", ep.Trace)
+ require.Len(t, cx.Stack, len(spec.Return.Types), "%s", cx.Trace)
for i := 0; i < len(spec.Return.Types); i++ {
stackType := cx.Stack[i].stackType()
retType := spec.Return.Types[i]
@@ -2901,18 +2919,147 @@ func TestTxnEffects(t *testing.T) {
testApp(t, "byte 0x32; log; gtxn 0 LastLog; byte 0x32; ==", ep, "txn effects can only be read from past txns")
// Look at the logs of tx 0
- testApps(t, []string{"", "byte 0x32; log; gtxn 0 LastLog; byte 0x; =="}, nil, AssemblerMaxVersion, nil)
- testApps(t, []string{"byte 0x33; log; int 1", "gtxn 0 LastLog; byte 0x33; =="}, nil, AssemblerMaxVersion, nil)
- testApps(t, []string{"byte 0x33; dup; log; log; int 1", "gtxn 0 NumLogs; int 2; =="}, nil, AssemblerMaxVersion, nil)
- testApps(t, []string{"byte 0x37; log; int 1", "gtxn 0 Logs 0; byte 0x37; =="}, nil, AssemblerMaxVersion, nil)
- testApps(t, []string{"byte 0x37; log; int 1", "int 0; gtxnas 0 Logs; byte 0x37; =="}, nil, AssemblerMaxVersion, nil)
+ testApps(t, []string{"", "byte 0x32; log; gtxn 0 LastLog; byte 0x; =="}, nil, nil, nil)
+ testApps(t, []string{"byte 0x33; log; int 1", "gtxn 0 LastLog; byte 0x33; =="}, nil, nil, nil)
+ testApps(t, []string{"byte 0x33; dup; log; log; int 1", "gtxn 0 NumLogs; int 2; =="}, nil, nil, nil)
+ testApps(t, []string{"byte 0x37; log; int 1", "gtxn 0 Logs 0; byte 0x37; =="}, nil, nil, nil)
+ testApps(t, []string{"byte 0x37; log; int 1", "int 0; gtxnas 0 Logs; byte 0x37; =="}, nil, nil, nil)
// Look past the logs of tx 0
- testApps(t, []string{"byte 0x37; log; int 1", "gtxna 0 Logs 1; byte 0x37; =="}, nil, AssemblerMaxVersion, nil,
+ testApps(t, []string{"byte 0x37; log; int 1", "gtxna 0 Logs 1; byte 0x37; =="}, nil, nil, nil,
exp(1, "invalid Logs index 1"))
- testApps(t, []string{"byte 0x37; log; int 1", "int 6; gtxnas 0 Logs; byte 0x37; =="}, nil, AssemblerMaxVersion, nil,
+ testApps(t, []string{"byte 0x37; log; int 1", "int 6; gtxnas 0 Logs; byte 0x37; =="}, nil, nil, nil,
exp(1, "invalid Logs index 6"))
}
+func TestLog(t *testing.T) {
+ partitiontest.PartitionTest(t)
+
+ t.Parallel()
+ var txn transactions.SignedTxn
+ txn.Txn.Type = protocol.ApplicationCallTx
+ ledger := NewLedger(nil)
+ ledger.NewApp(txn.Txn.Receiver, 0, basics.AppParams{})
+ ep := defaultAppParams(txn)
+ testCases := []struct {
+ source string
+ loglen int
+ }{
+ {
+ source: `byte "a logging message"; log; int 1`,
+ loglen: 1,
+ },
+ {
+ source: `byte "a logging message"; log; byte "a logging message"; log; int 1`,
+ loglen: 2,
+ },
+ {
+ source: fmt.Sprintf(`%s int 1`, strings.Repeat(`byte "a logging message"; log; `, maxLogCalls)),
+ loglen: maxLogCalls,
+ },
+ {
+ source: `int 1; loop: byte "a logging message"; log; int 1; +; dup; int 30; <=; bnz loop;`,
+ loglen: 30,
+ },
+ {
+ source: fmt.Sprintf(`byte "%s"; log; int 1`, strings.Repeat("a", maxLogSize)),
+ loglen: 1,
+ },
+ }
+
+ //track expected number of logs in cx.EvalDelta.Logs
+ for i, s := range testCases {
+ delta := testApp(t, s.source, ep)
+ require.Len(t, delta.Logs, s.loglen)
+ if i == len(testCases)-1 {
+ require.Equal(t, strings.Repeat("a", maxLogSize), delta.Logs[0])
+ } else {
+ for _, l := range delta.Logs {
+ require.Equal(t, "a logging message", l)
+ }
+ }
+ }
+
+ msg := strings.Repeat("a", 400)
+ failCases := []struct {
+ source string
+ errContains string
+ // For cases where assembly errors, we manually put in the bytes
+ assembledBytes []byte
+ }{
+ {
+ source: fmt.Sprintf(`byte "%s"; log; int 1`, strings.Repeat("a", maxLogSize+1)),
+ errContains: fmt.Sprintf("> %d bytes limit", maxLogSize),
+ },
+ {
+ source: fmt.Sprintf(`byte "%s"; log; byte "%s"; log; byte "%s"; log; int 1`, msg, msg, msg),
+ errContains: fmt.Sprintf("> %d bytes limit", maxLogSize),
+ },
+ {
+ source: fmt.Sprintf(`%s; int 1`, strings.Repeat(`byte "a"; log; `, maxLogCalls+1)),
+ errContains: "too many log calls",
+ },
+ {
+ source: `int 1; loop: byte "a"; log; int 1; +; dup; int 35; <; bnz loop;`,
+ errContains: "too many log calls",
+ },
+ {
+ 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),
+ },
+ {
+ source: `load 0; log`,
+ errContains: "log arg 0 wanted []byte but got uint64",
+ assembledBytes: []byte{byte(ep.Proto.LogicSigVersion), 0x34, 0x00, 0xb0},
+ },
+ }
+
+ for _, c := range failCases {
+ if c.assembledBytes == nil {
+ testApp(t, c.source, ep, c.errContains)
+ } else {
+ testAppBytes(t, c.assembledBytes, ep, c.errContains)
+ }
+ }
+}
+
+func TestGaid(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+
+ check0 := testProg(t, "gaid 0; int 100; ==", 4)
+ appTxn := makeSampleTxn()
+ appTxn.Txn.Type = protocol.ApplicationCallTx
+ targetTxn := makeSampleTxn()
+ targetTxn.Txn.Type = protocol.AssetConfigTx
+ ep := defaultAppParams(targetTxn, appTxn, makeSampleTxn())
+
+ // should fail when no creatable was created
+ _, err := EvalApp(check0.Program, 1, 888, ep)
+ require.ErrorContains(t, err, "did not create anything")
+
+ ep.TxnGroup[0].ApplyData.ConfigAsset = 100
+ pass, err := EvalApp(check0.Program, 1, 888, ep)
+ if !pass || err != nil {
+ t.Log(ep.Trace.String())
+ }
+ require.NoError(t, err)
+ require.True(t, pass)
+
+ // should fail when accessing future transaction in group
+ check2 := testProg(t, "gaid 2; int 0; >", 4)
+ _, err = EvalApp(check2.Program, 1, 888, ep)
+ require.ErrorContains(t, err, "gaid can't get creatable ID of txn ahead of the current one")
+
+ // should fail when accessing self
+ _, err = EvalApp(check0.Program, 0, 888, ep)
+ require.ErrorContains(t, err, "gaid is only for accessing creatable IDs of previous txns")
+
+ // should fail on non-creatable
+ ep.TxnGroup[0].Txn.Type = protocol.PaymentTx
+ _, err = EvalApp(check0.Program, 1, 888, ep)
+ require.ErrorContains(t, err, "can't use gaid on txn that is not an app call nor an asset config txn")
+ ep.TxnGroup[0].Txn.Type = protocol.AssetConfigTx
+}
func TestRound(t *testing.T) {
partitiontest.PartitionTest(t)
@@ -2966,6 +3113,7 @@ func TestBlockSeed(t *testing.T) {
// `block` should also work in LogicSigs, to drive home the point, blot out
// the normal Ledger
+ ep.runMode = ModeSig
ep.Ledger = nil
testLogic(t, "int 0xfffffff0; block BlkTimestamp", randomnessVersion, ep)
}
@@ -3010,11 +3158,11 @@ func TestPooledAppCallsVerifyOp(t *testing.T) {
ledger := NewLedger(nil)
call := transactions.SignedTxn{Txn: transactions.Transaction{Type: protocol.ApplicationCallTx}}
// Simulate test with 2 grouped txn
- testApps(t, []string{source, ""}, []transactions.SignedTxn{call, call}, LogicVersion, ledger,
+ testApps(t, []string{source, ""}, []transactions.SignedTxn{call, call}, nil, ledger,
exp(0, "pc=107 dynamic cost budget exceeded, executing ed25519verify: local program cost was 5"))
// Simulate test with 3 grouped txn
- testApps(t, []string{source, "", ""}, []transactions.SignedTxn{call, call, call}, LogicVersion, ledger)
+ testApps(t, []string{source, "", ""}, []transactions.SignedTxn{call, call, call}, nil, ledger)
}
func appAddr(id int) basics.Address {
@@ -3042,20 +3190,30 @@ func TestAppInfo(t *testing.T) {
testApp(t, source, ep)
}
-func TestBudget(t *testing.T) {
+func TestAppBudget(t *testing.T) {
partitiontest.PartitionTest(t)
t.Parallel()
- source := `
+ source := func(budget int) string {
+ return fmt.Sprintf(`
global OpcodeBudget
-int 699
+int %d
==
assert
global OpcodeBudget
-int 695
+int %d
==
-`
- testApp(t, source, defaultEvalParams())
+`, budget-1, budget-5)
+ }
+ testApp(t, source(700), nil)
+
+ // with pooling a two app call starts with 1400
+ testApps(t, []string{source(1400), source(1393)}, nil, nil, nil)
+
+ // without, they get base 700
+ testApps(t, []string{source(700), source(700)}, nil,
+ func(p *config.ConsensusParams) { p.EnableAppCostPooling = false }, nil)
+
}
func TestSelfMutateV8(t *testing.T) {
diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go
index b10c022ef..ee049d9d9 100644
--- a/data/transactions/logic/eval_test.go
+++ b/data/transactions/logic/eval_test.go
@@ -22,6 +22,7 @@ import (
"encoding/hex"
"encoding/json"
"fmt"
+ "io"
"math"
"strconv"
"strings"
@@ -35,23 +36,31 @@ import (
"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"
"pgregory.net/rapid"
)
-// Note that most of the tests use makeTestProto/defaultEvalParams as evaluator version so that
-// we check that v1 and v2 programs are compatible with the latest evaluator
-func makeTestProto() *config.ConsensusParams {
- return makeTestProtoV(LogicVersion)
+type protoOpt func(*config.ConsensusParams)
+
+func protoVer(version uint64) protoOpt {
+ return func(p *config.ConsensusParams) {
+ p.LogicSigVersion = version
+ p.Application = version >= appsEnabledVersion
+ }
}
func makeTestProtoV(version uint64) *config.ConsensusParams {
- return &config.ConsensusParams{
- LogicSigVersion: version,
+ return makeTestProto(protoVer(version))
+}
+
+func makeTestProto(opts ...protoOpt) *config.ConsensusParams {
+ p := config.ConsensusParams{
+ LogicSigVersion: LogicVersion,
LogicSigMaxCost: 20000,
- Application: version >= appsEnabledVersion,
+ Application: true,
MaxAppProgramCost: 700,
MaxAppKeyLen: 64,
@@ -101,10 +110,11 @@ func makeTestProtoV(version uint64) *config.ConsensusParams {
MaxGlobalSchemaEntries: 30,
MaxLocalSchemaEntries: 13,
- EnableAppCostPooling: true,
- EnableInnerTransactionPooling: true,
+ EnableAppCostPooling: true,
+ EnableLogicSigCostPooling: true,
- MinInnerApplVersion: 4,
+ EnableInnerTransactionPooling: true,
+ MinInnerApplVersion: 4,
SupportBecomeNonParticipatingTransactions: true,
@@ -113,76 +123,110 @@ func makeTestProtoV(version uint64) *config.ConsensusParams {
MaxBoxSize: 1000,
BytesPerBoxReference: 100,
}
+ for _, opt := range opts {
+ if opt != nil { // so some callsites can take one arg and pass it in
+ opt(&p)
+ }
+ }
+ return &p
}
-func defaultEvalParams(txns ...transactions.SignedTxn) *EvalParams {
- return defaultEvalParamsWithVersion(LogicVersion, txns...)
-}
-
-func benchmarkEvalParams(txn transactions.SignedTxn) *EvalParams {
- ep := defaultEvalParams(txn)
+func benchmarkSigParams(txns ...transactions.SignedTxn) *EvalParams {
+ ep := optSigParams(func(p *config.ConsensusParams) {
+ p.LogicSigMaxCost = 1_000_000_000
+ }, txns...)
ep.Trace = nil // Tracing would slow down benchmarks
- clone := *ep.Proto
- bigBudget := 1000 * 1000 * 1000 // Allow long run times
- clone.LogicSigMaxCost = uint64(bigBudget)
- clone.MaxAppProgramCost = bigBudget
- ep.Proto = &clone
- ep.PooledApplicationBudget = &bigBudget
return ep
}
-func defaultEvalParamsWithVersion(version uint64, txns ...transactions.SignedTxn) *EvalParams {
- empty := false
+func defaultSigParams(txns ...transactions.SignedTxn) *EvalParams {
+ return optSigParams(nil, txns...)
+}
+func defaultSigParamsWithVersion(version uint64, txns ...transactions.SignedTxn) *EvalParams {
+ return optSigParams(protoVer(version), txns...)
+}
+func optSigParams(opt protoOpt, txns ...transactions.SignedTxn) *EvalParams {
if len(txns) == 0 {
- empty = true
- txns = []transactions.SignedTxn{{Txn: transactions.Transaction{Type: protocol.ApplicationCallTx}}}
+ // We need a transaction to exist, because we'll be stuffing the
+ // logicsig into it in order to test them.
+ txns = make([]transactions.SignedTxn, 1)
+ }
+ // Make it non-Blank so NewSigEval does not short-circuit (but try to avoid
+ // manipulating txns if they were actually supplied with other sigs.)
+ if txns[0].Sig.Blank() && txns[0].Msig.Blank() && txns[0].Lsig.Blank() {
+ txns[0].Lsig.Logic = []byte{LogicVersion + 1} // make sure it fails if used
}
- ep := NewEvalParams(transactions.WrapSignedTxnsWithAD(txns), makeTestProtoV(version), &transactions.SpecialAddresses{})
+
+ ep := NewSigEvalParams(txns, makeTestProto(opt), &NoHeaderLedger{})
ep.Trace = &strings.Builder{}
- ep.SigLedger = NewLedger(nil)
- if empty {
- // We made an app type in order to get a full ep, but that sets MinTealVersion=2
- ep.TxnGroup[0].Txn.Type = "" // set it back
- ep.MinAvmVersion = nil // will recalculate in eval()
+ return ep
+}
+
+func defaultAppParams(txns ...transactions.SignedTxn) *EvalParams {
+ return defaultAppParamsWithVersion(LogicVersion, txns...)
+}
+func defaultAppParamsWithVersion(version uint64, txns ...transactions.SignedTxn) *EvalParams {
+ if len(txns) == 0 {
+ // Convince NewAppEvalParams not to return nil
+ txns = []transactions.SignedTxn{{
+ Txn: transactions.Transaction{Type: protocol.ApplicationCallTx},
+ }}
+ }
+ ep := NewAppEvalParams(transactions.WrapSignedTxnsWithAD(txns), makeTestProtoV(version), &transactions.SpecialAddresses{})
+ if ep != nil { // If supplied no apps, ep is nil.
+ ep.Trace = &strings.Builder{}
+ ledger := NewLedger(nil)
+ ep.Ledger = ledger
+ ep.SigLedger = ledger
}
return ep
}
-// `supportsAppEval` is test helper method for disambiguating whe `EvalParams` is suitable for logicsig vs app evaluations.
-func (ep *EvalParams) supportsAppEval() bool {
- return ep.available != nil
+func defaultEvalParams(txns ...transactions.SignedTxn) (sig *EvalParams, app *EvalParams) {
+ return defaultEvalParamsWithVersion(LogicVersion, txns...)
+}
+func defaultEvalParamsWithVersion(version uint64, txns ...transactions.SignedTxn) (sig *EvalParams, app *EvalParams) {
+ sig = defaultSigParamsWithVersion(version, txns...)
+ app = defaultAppParamsWithVersion(version, txns...)
+ // Let's share ledgers for easier testing and let sigs use it for block access
+ if app != nil {
+ sig.SigLedger = app.SigLedger
+ }
+ return sig, app
}
// reset puts an ep back into its original state. This is in *_test.go because
// no real code should ever need this. EvalParams should be created to evaluate
// a group, and then thrown away.
func (ep *EvalParams) reset() {
- if ep.Proto.EnableAppCostPooling {
- budget := ep.Proto.MaxAppProgramCost
- ep.PooledApplicationBudget = &budget
- }
- if ep.Proto.EnableInnerTransactionPooling {
- inners := ep.Proto.MaxTxGroupSize * ep.Proto.MaxInnerTransactions
- ep.pooledAllowedInners = &inners
- }
- ep.pastScratch = make([]*scratchSpace, ep.Proto.MaxTxGroupSize)
- for i := range ep.TxnGroup {
- ep.TxnGroup[i].ApplyData = transactions.ApplyData{}
- }
- if ep.available != nil {
- available := NewEvalParams(ep.TxnGroup, ep.Proto, ep.Specials).available
- if available != nil {
- ep.available = available
+ switch ep.runMode {
+ case ModeSig:
+ if ep.Proto.EnableLogicSigCostPooling {
+ budget := int(ep.Proto.LogicSigMaxCost) * len(ep.TxnGroup)
+ ep.PooledLogicSigBudget = &budget
}
- ep.available.dirtyBytes = 0
- }
- ep.readBudgetChecked = false
- ep.appAddrCache = make(map[basics.AppIndex]basics.Address)
- if ep.Trace != nil {
- ep.Trace = &strings.Builder{}
+ case ModeApp:
+ if ep.Proto.EnableAppCostPooling {
+ budget := ep.Proto.MaxAppProgramCost
+ ep.PooledApplicationBudget = &budget
+ }
+ if ep.Proto.EnableInnerTransactionPooling {
+ inners := ep.Proto.MaxTxGroupSize * ep.Proto.MaxInnerTransactions
+ ep.pooledAllowedInners = &inners
+ }
+ ep.pastScratch = make([]*scratchSpace, len(ep.TxnGroup))
+ for i := range ep.TxnGroup {
+ ep.TxnGroup[i].ApplyData = transactions.ApplyData{}
+ }
+ ep.available = nil
+ ep.readBudgetChecked = false
+ ep.appAddrCache = make(map[basics.AppIndex]basics.Address)
+ if ep.Trace != nil {
+ ep.Trace = &strings.Builder{}
+ }
+ ep.txidCache = nil
+ ep.innerTxidCache = nil
}
- ep.txidCache = nil
- ep.innerTxidCache = nil
}
func TestTooManyArgs(t *testing.T) {
@@ -196,7 +240,7 @@ func TestTooManyArgs(t *testing.T) {
txn.Lsig.Logic = ops.Program
args := [transactions.EvalMaxArgs + 1][]byte{}
txn.Lsig.Args = args[:]
- pass, err := EvalSignature(0, defaultEvalParams(txn))
+ pass, err := EvalSignature(0, defaultSigParams(txn))
require.Error(t, err)
require.False(t, pass)
})
@@ -207,23 +251,22 @@ func TestEmptyProgram(t *testing.T) {
partitiontest.PartitionTest(t)
t.Parallel()
- testLogicBytes(t, nil, defaultEvalParams(), "invalid", "invalid program (empty)")
+ testLogicBytes(t, nil, nil, "invalid", "invalid program (empty)")
}
-// TestMinAvmVersionParamEval tests eval/check reading the MinAvmVersion from the param
+// TestMinAvmVersionParamEval tests eval/check reading the minAvmVersion from the param
func TestMinAvmVersionParamEvalCheckSignature(t *testing.T) {
partitiontest.PartitionTest(t)
t.Parallel()
- params := defaultEvalParams()
- version2 := uint64(rekeyingEnabledVersion)
- params.MinAvmVersion = &version2
+ params := defaultSigParams()
+ params.minAvmVersion = uint64(rekeyingEnabledVersion)
program := make([]byte, binary.MaxVarintLen64)
// set the program version to 1
binary.PutUvarint(program, 1)
verErr := fmt.Sprintf("program version must be >= %d", appsEnabledVersion)
- testAppBytes(t, program, params, verErr, verErr)
+ testLogicBytes(t, program, params, verErr, verErr)
}
func TestTxnFieldToTealValue(t *testing.T) {
@@ -279,6 +322,10 @@ func TestTxnFirstValidTime(t *testing.T) {
t.Parallel()
ep, tx, ledger := makeSampleEnv()
+ // This is an unusual test that needs a ledger
+ // even though it's testing signatures. So it's convenient to use
+ // makeSampleEnv and then change the mode on the ep.
+ ep.runMode = ModeSig
// By default, test ledger uses an oddball round, ask it what round it's
// going to use and prep fv, lv accordingly.
@@ -339,8 +386,8 @@ func TestWrongProtoVersion(t *testing.T) {
for v := uint64(1); v <= AssemblerMaxVersion; v++ {
t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) {
ops := testProg(t, "int 1", v)
- ep := defaultEvalParamsWithVersion(0)
- testAppBytes(t, ops.Program, ep, "LogicSig not supported", "LogicSig not supported")
+ ep := defaultSigParamsWithVersion(0)
+ testLogicBytes(t, ops.Program, ep, "LogicSig not supported", "LogicSig not supported")
})
}
}
@@ -413,7 +460,7 @@ byte base64 5rZMNsevs5sULO+54aN+OvU6lQ503z2X+SSYUABIx7E=
var txn transactions.SignedTxn
txn.Lsig.Logic = ops.Program
txn.Lsig.Args = [][]byte{[]byte("=0\x97S\x85H\xe9\x91B\xfd\xdb;1\xf5Z\xaec?\xae\xf2I\x93\x08\x12\x94\xaa~\x06\x08\x849b")}
- ep := defaultEvalParams(txn)
+ ep := defaultSigParams(txn)
err := CheckSignature(0, ep)
require.NoError(t, err)
pass, cx, err := EvalSignatureFull(0, ep)
@@ -436,8 +483,7 @@ end:
`, v)
var txn transactions.SignedTxn
txn.Lsig.Logic = ops.Program
- ep := defaultEvalParams(txn)
- err := CheckSignature(0, ep)
+ err := CheckSignature(0, defaultSigParams(txn))
require.NoError(t, err)
})
}
@@ -448,7 +494,7 @@ return
`, v)
var txn transactions.SignedTxn
txn.Lsig.Logic = ops.Program
- ep := defaultEvalParams(txn)
+ ep := defaultSigParams(txn)
err := CheckSignature(0, ep)
require.NoError(t, err)
})
@@ -459,8 +505,7 @@ return
pushint := OpsByName[LogicVersion]["pushint"]
var txn transactions.SignedTxn
txn.Lsig.Logic = []byte{LogicVersion, pushint.Opcode, 0x01}
- ep := defaultEvalParams(txn)
- err := CheckSignature(0, ep)
+ err := CheckSignature(0, defaultSigParams(txn))
require.NoError(t, err)
}
@@ -515,7 +560,7 @@ func TestTLHC(t *testing.T) {
txn.Lsig.Args = [][]byte{secret}
txn.Txn.FirstValid = 999999
block := bookkeeping.Block{}
- ep := defaultEvalParams(txn)
+ ep := defaultSigParams(txn)
err := CheckSignature(0, ep)
if err != nil {
t.Log(hex.EncodeToString(ops.Program))
@@ -534,7 +579,7 @@ func TestTLHC(t *testing.T) {
txn.Txn.Receiver = a2
txn.Txn.CloseRemainderTo = a2
- ep = defaultEvalParams(txn)
+ ep = defaultSigParams(txn)
pass, err = EvalSignature(0, ep)
if !pass {
t.Log(hex.EncodeToString(ops.Program))
@@ -546,7 +591,7 @@ func TestTLHC(t *testing.T) {
txn.Txn.Receiver = a2
txn.Txn.CloseRemainderTo = a2
txn.Txn.FirstValid = 1
- ep = defaultEvalParams(txn)
+ ep = defaultSigParams(txn)
pass, err = EvalSignature(0, ep)
if pass {
t.Log(hex.EncodeToString(ops.Program))
@@ -558,7 +603,7 @@ func TestTLHC(t *testing.T) {
txn.Txn.Receiver = a1
txn.Txn.CloseRemainderTo = a1
txn.Txn.FirstValid = 999999
- ep = defaultEvalParams(txn)
+ ep = defaultSigParams(txn)
pass, err = EvalSignature(0, ep)
if !pass {
t.Log(hex.EncodeToString(ops.Program))
@@ -570,7 +615,7 @@ func TestTLHC(t *testing.T) {
// wrong answer
txn.Lsig.Args = [][]byte{[]byte("=0\x97S\x85H\xe9\x91B\xfd\xdb;1\xf5Z\xaec?\xae\xf2I\x93\x08\x12\x94\xaa~\x06\x08\x849a")}
block.BlockHeader.Round = 1
- ep = defaultEvalParams(txn)
+ ep = defaultSigParams(txn)
pass, err = EvalSignature(0, ep)
if pass {
t.Log(hex.EncodeToString(ops.Program))
@@ -1008,7 +1053,7 @@ func TestTxnBadField(t *testing.T) {
t.Parallel()
program := []byte{0x01, 0x31, 0x7f}
- testLogicBytes(t, program, defaultEvalParams(), "invalid txn field")
+ testLogicBytes(t, program, nil, "invalid txn field")
// TODO: Check should know the type stack was wrong
// test txn does not accept ApplicationArgs and Accounts
@@ -1021,7 +1066,7 @@ func TestTxnBadField(t *testing.T) {
ops := testProg(t, source, AssemblerMaxVersion)
require.Equal(t, txnaOpcode, ops.Program[1])
ops.Program[1] = txnOpcode
- testLogicBytes(t, ops.Program, defaultEvalParams(), fmt.Sprintf("invalid txn field %s", field))
+ testLogicBytes(t, ops.Program, nil, fmt.Sprintf("invalid txn field %s", field))
}
}
@@ -1030,7 +1075,7 @@ func TestGtxnBadIndex(t *testing.T) {
t.Parallel()
program := []byte{0x01, 0x33, 0x1, 0x01}
- testLogicBytes(t, program, defaultEvalParams(), "txn index 1")
+ testLogicBytes(t, program, nil, "txn index 1")
}
func TestGtxnBadField(t *testing.T) {
@@ -1039,7 +1084,7 @@ func TestGtxnBadField(t *testing.T) {
t.Parallel()
program := []byte{0x01, 0x33, 0x0, 127}
// TODO: Check should know the type stack was wrong
- testLogicBytes(t, program, defaultEvalParams(), "invalid txn field TxnField(127)")
+ testLogicBytes(t, program, nil, "invalid txn field TxnField(127)")
// test gtxn does not accept ApplicationArgs and Accounts
txnOpcode := OpsByName[LogicVersion]["txn"].Opcode
@@ -1051,7 +1096,7 @@ func TestGtxnBadField(t *testing.T) {
ops := testProg(t, source, AssemblerMaxVersion)
require.Equal(t, txnaOpcode, ops.Program[1])
ops.Program[1] = txnOpcode
- testLogicBytes(t, ops.Program, defaultEvalParams(), fmt.Sprintf("invalid txn field %s", field))
+ testLogicBytes(t, ops.Program, nil, fmt.Sprintf("invalid txn field %s", field))
}
}
@@ -1060,7 +1105,7 @@ func TestGlobalBadField(t *testing.T) {
t.Parallel()
program := []byte{0x01, 0x32, 127}
- testLogicBytes(t, program, defaultEvalParams(), "invalid global field")
+ testLogicBytes(t, program, nil, "invalid global field")
}
func TestArg(t *testing.T) {
@@ -1083,7 +1128,7 @@ func TestArg(t *testing.T) {
[]byte("aoeu4"),
}
ops := testProg(t, source, v)
- testLogicBytes(t, ops.Program, defaultEvalParams(txn))
+ testLogicBytes(t, ops.Program, defaultSigParams(txn))
})
}
}
@@ -1232,7 +1277,7 @@ func TestGlobal(t *testing.T) {
}
appcall.Txn.Group = crypto.Digest{0x07, 0x06}
- ep := defaultEvalParams(appcall)
+ ep := defaultAppParams(appcall)
ep.Ledger = ledger
testApp(t, tests[v].program, ep)
})
@@ -1277,11 +1322,11 @@ int %s
txn := transactions.SignedTxn{}
txn.Txn.Type = tt
if v < appsEnabledVersion && tt == protocol.ApplicationCallTx {
- testLogicBytes(t, ops.Program, defaultEvalParams(txn),
+ testLogicBytes(t, ops.Program, defaultSigParams(txn),
"program version must be", "program version must be")
return
}
- testLogicBytes(t, ops.Program, defaultEvalParams(txn))
+ testLogicBytes(t, ops.Program, defaultSigParams(txn))
})
}
})
@@ -1857,7 +1902,6 @@ func TestTxn(t *testing.T) {
}
txn.Txn.ApprovalProgram = ops.Program
txn.Txn.ClearStateProgram = clearOps.Program
- txn.Lsig.Logic = ops.Program
txn.Txn.ExtraProgramPages = 2
// RekeyTo not allowed in v1
if v < rekeyingEnabledVersion {
@@ -1880,25 +1924,26 @@ func TestTxn(t *testing.T) {
clearProgramHash[:],
}
// Since we test GroupIndex ==3, we need a larger group
- ep := defaultEvalParams(txn, txn, txn, txn)
- ep.TxnGroup[2].EvalDelta.Logs = []string{"x", "prefilled"}
+ sep, aep := defaultEvalParams(txn, txn, txn, txn)
if v < txnEffectsVersion {
- testLogicFull(t, ops.Program, 3, ep)
+ testLogicFull(t, ops.Program, 3, sep)
} else {
// Starting in txnEffectsVersion, there are fields we can't access in Logic mode
- testLogicFull(t, ops.Program, 3, ep, "not allowed in current mode")
+ testLogicFull(t, ops.Program, 3, sep, "not allowed in current mode")
// And the early tests use "arg" a lot - not allowed in stateful. So remove those tests.
lastArg := strings.Index(source, "arg 10\n==\n&&")
require.NotEqual(t, -1, lastArg)
+ source = source[lastArg+12:]
- appSafe := "int 1" + strings.Replace(source[lastArg+12:], `txn Sender
+ aep.TxnGroup[2].EvalDelta.Logs = []string{"x", "prefilled"} // allows gtxn 2 NumLogs
+ appSafe := "int 1" + strings.Replace(source, `txn Sender
int 0
args
==
assert`, "", 1)
ops := testProg(t, appSafe, v)
- testAppFull(t, ops.Program, 3, basics.AppIndex(888), ep)
+ testAppFull(t, ops.Program, 3, basics.AppIndex(888), aep)
}
})
}
@@ -1949,7 +1994,7 @@ return
`
ops := testProg(t, cachedTxnProg, 2)
- ep, _, _ := makeSampleEnv()
+ ep := defaultSigParams(makeSampleTxnGroup()...)
txid0 := ep.TxnGroup[0].ID()
txid1 := ep.TxnGroup[1].ID()
ep.TxnGroup[0].Lsig.Args = [][]byte{
@@ -1959,50 +2004,6 @@ return
testLogicBytes(t, ops.Program, ep)
}
-func TestGaid(t *testing.T) {
- partitiontest.PartitionTest(t)
-
- t.Parallel()
- check0 := testProg(t, "gaid 0; int 100; ==", 4)
- appTxn := makeSampleTxn()
- appTxn.Txn.Type = protocol.ApplicationCallTx
- targetTxn := makeSampleTxn()
- targetTxn.Txn.Type = protocol.AssetConfigTx
- ep := defaultEvalParams(targetTxn, appTxn, makeSampleTxn())
- ep.Ledger = NewLedger(nil)
-
- // should fail when no creatable was created
- _, err := EvalApp(check0.Program, 1, 888, ep)
- require.Error(t, err)
- require.Contains(t, err.Error(), "did not create anything")
-
- ep.TxnGroup[0].ApplyData.ConfigAsset = 100
- pass, err := EvalApp(check0.Program, 1, 888, ep)
- if !pass || err != nil {
- t.Log(ep.Trace.String())
- }
- require.NoError(t, err)
- require.True(t, pass)
-
- // should fail when accessing future transaction in group
- check2 := testProg(t, "gaid 2; int 0; >", 4)
- _, err = EvalApp(check2.Program, 1, 888, ep)
- require.Error(t, err)
- require.Contains(t, err.Error(), "gaid can't get creatable ID of txn ahead of the current one")
-
- // should fail when accessing self
- _, err = EvalApp(check0.Program, 0, 888, ep)
- require.Error(t, err)
- require.Contains(t, err.Error(), "gaid is only for accessing creatable IDs of previous txns")
-
- // should fail on non-creatable
- ep.TxnGroup[0].Txn.Type = protocol.PaymentTx
- _, err = EvalApp(check0.Program, 1, 888, ep)
- require.Error(t, err)
- require.Contains(t, err.Error(), "can't use gaid on txn that is not an app call nor an asset config txn")
- ep.TxnGroup[0].Txn.Type = protocol.AssetConfigTx
-}
-
func TestGtxn(t *testing.T) {
partitiontest.PartitionTest(t)
@@ -2128,7 +2129,7 @@ gtxn 0 Sender
txn.Txn.SelectionPK[:],
txn.Txn.Note,
}
- ep := defaultEvalParams(makeSampleTxnGroup(txn)...)
+ ep := defaultSigParams(makeSampleTxnGroup(txn)...)
testLogic(t, source, v, ep)
if v >= 3 {
gtxnsProg := strings.ReplaceAll(source, "gtxn 0", "int 0; gtxns")
@@ -2151,9 +2152,15 @@ func testLogic(t *testing.T, program string, v uint64, ep *EvalParams, problems
func testLogicBytes(t *testing.T, program []byte, ep *EvalParams, problems ...string) {
t.Helper()
+ if ep == nil {
+ ep = defaultSigParams()
+ } else {
+ ep.reset()
+ }
testLogicFull(t, program, 0, ep, problems...)
}
+// testLogicFull is the lowest-level so it does not create an ep or reset it.
func testLogicFull(t *testing.T, program []byte, gi int, ep *EvalParams, problems ...string) {
t.Helper()
@@ -2170,16 +2177,14 @@ func testLogicFull(t *testing.T, program []byte, gi int, ep *EvalParams, problem
require.Fail(t, "Misused testLogic: %d problems", len(problems))
}
- sb := &strings.Builder{}
- ep.Trace = sb
+ ep.Trace = &strings.Builder{}
- ep.TxnGroup[0].Lsig.Logic = program
+ ep.TxnGroup[gi].Lsig.Logic = program
err := CheckSignature(gi, ep)
if checkProblem == "" {
- require.NoError(t, err, sb.String())
+ require.NoError(t, err, "Error in CheckSignature %v", ep.Trace)
} else {
- require.Error(t, err, "Check\n%s\nExpected: %v", sb, checkProblem)
- require.Contains(t, err.Error(), checkProblem)
+ require.ErrorContains(t, err, checkProblem, "Wrong error in CheckSignature %v", ep.Trace)
}
// We continue on to check Eval() of things that failed Check() because it's
@@ -2189,21 +2194,45 @@ func testLogicFull(t *testing.T, program []byte, gi int, ep *EvalParams, problem
pass, err := EvalSignature(gi, ep)
if evalProblem == "" {
- require.NoError(t, err, "Eval%s\nExpected: PASS", sb)
- assert.True(t, pass, "Eval%s\nExpected: PASS", sb)
+ require.NoError(t, err, "Eval\n%sExpected: PASS", ep.Trace)
+ assert.True(t, pass, "Eval\n%sExpected: PASS", ep.Trace)
return
}
// There is an evalProblem to check. REJECT is special and only means that
// the app didn't accept. Maybe it's an error, maybe it's just !pass.
if evalProblem == "REJECT" {
- require.True(t, err != nil || !pass, "Eval%s\nExpected: REJECT", sb)
+ require.True(t, err != nil || !pass, "Eval\n%sExpected: REJECT", ep.Trace)
} else {
- require.Error(t, err, "Eval%s\nExpected: %v", sb, evalProblem)
- require.Contains(t, err.Error(), evalProblem)
+ require.ErrorContains(t, err, evalProblem, "Wrong error in EvalSignature %v", ep.Trace)
}
}
+func testLogics(t *testing.T, programs []string, txgroup []transactions.SignedTxn, opt protoOpt, expected ...expect) *EvalParams {
+ t.Helper()
+ proto := makeTestProto(opt)
+
+ if txgroup == nil {
+ for range programs {
+ txgroup = append(txgroup, makeSampleTxn())
+ }
+ }
+ // Place the logicsig code first, so NewSigEvalParams calcs budget
+ for i, program := range programs {
+ if program != "" {
+ code := testProg(t, program, proto.LogicSigVersion).Program
+ txgroup[i].Lsig.Logic = code
+ }
+ }
+ ep := NewSigEvalParams(txgroup, proto, &NoHeaderLedger{})
+ for i, program := range programs {
+ if program != "" {
+ testLogicFull(t, txgroup[i].Lsig.Logic, i, ep)
+ }
+ }
+ return ep
+}
+
func TestTxna(t *testing.T) {
partitiontest.PartitionTest(t)
@@ -2217,7 +2246,7 @@ txna ApplicationArgs 0
txn.Txn.Accounts = make([]basics.Address, 1)
txn.Txn.Accounts[0] = txn.Txn.Sender
txn.Txn.ApplicationArgs = [][]byte{txn.Txn.Sender[:]}
- ep := defaultEvalParams(txn)
+ ep := defaultSigParams(txn)
testLogicBytes(t, ops.Program, ep)
// modify txn field
@@ -2251,8 +2280,7 @@ txn Sender
ops2 := testProg(t, source, AssemblerMaxVersion)
var txn2 transactions.SignedTxn
copy(txn2.Txn.Sender[:], []byte("aoeuiaoeuiaoeuiaoeuiaoeuiaoeui00"))
- ep2 := defaultEvalParams(txn2)
- testLogicBytes(t, ops2.Program, ep2)
+ testLogicBytes(t, ops2.Program, defaultSigParams(txn2))
// check gtxna
source = `gtxna 0 Accounts 1
@@ -2292,8 +2320,7 @@ txn Sender
ops3 := testProg(t, source, AssemblerMaxVersion)
var txn3 transactions.SignedTxn
copy(txn2.Txn.Sender[:], []byte("aoeuiaoeuiaoeuiaoeuiaoeuiaoeui00"))
- ep3 := defaultEvalParams(txn3)
- testLogicBytes(t, ops3.Program, ep3)
+ testLogicBytes(t, ops3.Program, defaultSigParams(txn3))
}
// check empty values in ApplicationArgs and Account
@@ -2311,10 +2338,10 @@ int 0
var txn transactions.SignedTxn
txn.Txn.ApplicationArgs = make([][]byte, 1)
txn.Txn.ApplicationArgs[0] = []byte("")
- testLogicBytes(t, ops.Program, defaultEvalParams(txn))
+ testLogicBytes(t, ops.Program, defaultSigParams(txn))
txn.Txn.ApplicationArgs[0] = nil
- testLogicBytes(t, ops.Program, defaultEvalParams(txn))
+ testLogicBytes(t, ops.Program, defaultSigParams(txn))
source2 := `txna Accounts 1
global ZeroAddress
@@ -2325,10 +2352,10 @@ global ZeroAddress
var txn2 transactions.SignedTxn
txn2.Txn.Accounts = make([]basics.Address, 1)
txn2.Txn.Accounts[0] = basics.Address{}
- testLogicBytes(t, ops.Program, defaultEvalParams(txn2))
+ testLogicBytes(t, ops.Program, defaultSigParams(txn2))
txn2.Txn.Accounts = make([]basics.Address, 1)
- testLogicBytes(t, ops.Program, defaultEvalParams(txn2))
+ testLogicBytes(t, ops.Program, defaultSigParams(txn2))
}
func TestTxnBigPrograms(t *testing.T) {
@@ -2354,14 +2381,14 @@ int 1
for i := range txn.Txn.ApprovalProgram {
txn.Txn.ApprovalProgram[i] = byte(i % 7)
}
- testLogic(t, source, AssemblerMaxVersion, defaultEvalParams(txn))
+ testLogic(t, source, AssemblerMaxVersion, defaultSigParams(txn))
- testLogic(t, `txna ApprovalProgramPages 2`, AssemblerMaxVersion, defaultEvalParams(txn),
+ testLogic(t, `txna ApprovalProgramPages 2`, AssemblerMaxVersion, defaultSigParams(txn),
"invalid ApprovalProgramPages index")
// ClearStateProgram is not in the txn at all
- testLogic(t, `txn NumClearStateProgramPages; !`, AssemblerMaxVersion, defaultEvalParams(txn))
- testLogic(t, `txna ClearStateProgramPages 0`, AssemblerMaxVersion, defaultEvalParams(txn),
+ testLogic(t, `txn NumClearStateProgramPages; !`, AssemblerMaxVersion, defaultSigParams(txn))
+ testLogic(t, `txna ClearStateProgramPages 0`, AssemblerMaxVersion, defaultSigParams(txn),
"invalid ClearStateProgramPages index")
}
@@ -2381,7 +2408,7 @@ txnas ApplicationArgs
txn.Txn.Accounts = make([]basics.Address, 1)
txn.Txn.Accounts[0] = txn.Txn.Sender
txn.Txn.ApplicationArgs = [][]byte{txn.Txn.Sender[:]}
- ep := defaultEvalParams(txn)
+ ep := defaultSigParams(txn)
testLogicBytes(t, ops.Program, ep)
// check special case: Account 0 == Sender
@@ -2394,7 +2421,7 @@ txn Sender
ops = testProg(t, source, AssemblerMaxVersion)
var txn2 transactions.SignedTxn
copy(txn2.Txn.Sender[:], []byte("aoeuiaoeuiaoeuiaoeuiaoeuiaoeui00"))
- testLogicBytes(t, ops.Program, defaultEvalParams(txn2))
+ testLogicBytes(t, ops.Program, defaultSigParams(txn2))
// check gtxnas
source = `int 1
@@ -2414,7 +2441,7 @@ txn Sender
ops = testProg(t, source, AssemblerMaxVersion)
var txn3 transactions.SignedTxn
copy(txn3.Txn.Sender[:], []byte("aoeuiaoeuiaoeuiaoeuiaoeuiaoeui00"))
- testLogicBytes(t, ops.Program, defaultEvalParams(txn3))
+ testLogicBytes(t, ops.Program, defaultSigParams(txn3))
// check gtxnsas
source = `int 0
@@ -2780,9 +2807,9 @@ func TestGload(t *testing.T) {
}
if testCase.errContains != "" {
- testApps(t, sources, txgroup, LogicVersion, nil, exp(testCase.errTxn, testCase.errContains))
+ testApps(t, sources, txgroup, nil, nil, exp(testCase.errTxn, testCase.errContains))
} else {
- testApps(t, sources, txgroup, LogicVersion, nil)
+ testApps(t, sources, txgroup, nil, nil)
}
})
}
@@ -2826,15 +2853,12 @@ func TestGload(t *testing.T) {
},
}
- ep := defaultEvalParams(failCase.firstTxn, appcall)
- ep.SigLedger = NewLedger(nil)
-
program := testProg(t, "gload 0 0", AssemblerMaxVersion).Program
switch failCase.runMode {
case ModeApp:
- testAppBytes(t, program, ep, failCase.errContains)
+ testAppBytes(t, program, defaultAppParams(failCase.firstTxn, appcall), failCase.errContains)
default:
- testLogicBytes(t, program, ep, failCase.errContains, failCase.errContains)
+ testLogicBytes(t, program, defaultSigParams(failCase.firstTxn, appcall), failCase.errContains, failCase.errContains)
}
})
}
@@ -2888,7 +2912,7 @@ int 1
txgroup[j].Txn.Type = protocol.ApplicationCallTx
}
- testApps(t, sources, txgroup, LogicVersion, nil)
+ testApps(t, sources, txgroup, nil, nil)
}
const testCompareProgramText = `int 35
@@ -2977,19 +3001,19 @@ func TestSlowLogic(t *testing.T) {
// v1overspend fails (on v1)
ops := testProg(t, v1overspend, 1)
// We should never Eval this after it fails Check(), but nice to see it also fails.
- testLogicBytes(t, ops.Program, defaultEvalParamsWithVersion(1),
+ testLogicBytes(t, ops.Program, defaultSigParamsWithVersion(1),
"static cost", "dynamic cost")
// v2overspend passes Check, even on v2 proto, because the old low cost is "grandfathered"
ops = testProg(t, v2overspend, 1)
- testLogicBytes(t, ops.Program, defaultEvalParamsWithVersion(2))
+ testLogicBytes(t, ops.Program, defaultSigParamsWithVersion(2))
// even the shorter, v2overspend, fails when compiled as v2 code
ops = testProg(t, v2overspend, 2)
- testLogicBytes(t, ops.Program, defaultEvalParamsWithVersion(2),
+ testLogicBytes(t, ops.Program, defaultSigParamsWithVersion(2),
"static cost", "dynamic cost")
// in v4 cost is still 134, but only matters in Eval, not Check, so both fail there
- ep4 := defaultEvalParamsWithVersion(4)
+ ep4 := defaultSigParamsWithVersion(4)
ops = testProg(t, v1overspend, 4)
testLogicBytes(t, ops.Program, ep4, "dynamic cost")
@@ -2997,6 +3021,31 @@ func TestSlowLogic(t *testing.T) {
testLogicBytes(t, ops.Program, ep4, "dynamic cost")
}
+func TestSigBudget(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+
+ source := func(budget int) string {
+ return fmt.Sprintf(`
+global OpcodeBudget
+int %d
+==
+assert
+global OpcodeBudget
+int %d
+==
+`, budget-1, budget-5)
+ }
+ testLogic(t, source(20000), LogicVersion, nil)
+
+ testLogics(t, []string{source(40000), source(39993)}, nil, nil)
+
+ testLogics(t, []string{source(60000), source(59993), ""}, nil, nil)
+
+ testLogics(t, []string{source(20000), source(20000)}, nil,
+ func(p *config.ConsensusParams) { p.EnableLogicSigCostPooling = false })
+}
+
func isNotPanic(t *testing.T, err error) {
if err == nil {
return
@@ -3014,7 +3063,7 @@ func TestStackUnderflow(t *testing.T) {
t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) {
ops := testProg(t, `int 1`, v)
ops.Program = append(ops.Program, 0x08) // +
- testLogicBytes(t, ops.Program, defaultEvalParams(), "stack underflow")
+ testLogicBytes(t, ops.Program, nil, "stack underflow")
})
}
}
@@ -3027,7 +3076,7 @@ func TestWrongStackTypeRuntime(t *testing.T) {
t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) {
ops := testProg(t, `int 1`, v)
ops.Program = append(ops.Program, 0x01, 0x15) // sha256, len
- testLogicBytes(t, ops.Program, defaultEvalParams(), "sha256 arg 0 wanted")
+ testLogicBytes(t, ops.Program, nil, "sha256 arg 0 wanted")
})
}
}
@@ -3040,7 +3089,7 @@ func TestEqMismatch(t *testing.T) {
t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) {
ops := testProg(t, `byte 0x1234; int 1`, v)
ops.Program = append(ops.Program, 0x12) // ==
- testLogicBytes(t, ops.Program, defaultEvalParams(), "cannot compare")
+ testLogicBytes(t, ops.Program, nil, "cannot compare")
// TODO: Check should know the type stack was wrong
})
}
@@ -3054,7 +3103,7 @@ func TestNeqMismatch(t *testing.T) {
t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) {
ops := testProg(t, `byte 0x1234; int 1`, v)
ops.Program = append(ops.Program, 0x13) // !=
- testLogicBytes(t, ops.Program, defaultEvalParams(), "cannot compare")
+ testLogicBytes(t, ops.Program, nil, "cannot compare")
})
}
}
@@ -3067,7 +3116,7 @@ func TestWrongStackTypeRuntime2(t *testing.T) {
t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) {
ops := testProg(t, `byte 0x1234; int 1`, v)
ops.Program = append(ops.Program, 0x08) // +
- testLogicBytes(t, ops.Program, defaultEvalParams(), "+ arg 0 wanted")
+ testLogicBytes(t, ops.Program, nil, "+ arg 0 wanted")
})
}
}
@@ -3085,7 +3134,7 @@ func TestIllegalOp(t *testing.T) {
break
}
}
- testLogicBytes(t, ops.Program, defaultEvalParams(), "illegal opcode", "illegal opcode")
+ testLogicBytes(t, ops.Program, nil, "illegal opcode", "illegal opcode")
})
}
}
@@ -3103,7 +3152,7 @@ int 1
`, v)
// cut two last bytes - intc_1 and last byte of bnz
ops.Program = ops.Program[:len(ops.Program)-2]
- testLogicBytes(t, ops.Program, defaultEvalParams(),
+ testLogicBytes(t, ops.Program, nil,
"bnz program ends short", "bnz program ends short")
})
}
@@ -3118,7 +3167,7 @@ intc 0
intc 0
bnz done
done:`, 2)
- testLogicBytes(t, ops.Program, defaultEvalParams())
+ testLogicBytes(t, ops.Program, nil)
}
func TestShortBytecblock(t *testing.T) {
@@ -3133,8 +3182,7 @@ func TestShortBytecblock(t *testing.T) {
for i := 2; i < len(fullops.Program); i++ {
program := fullops.Program[:i]
t.Run(hex.EncodeToString(program), func(t *testing.T) {
- testLogicBytes(t, program, defaultEvalParams(),
- "bytes list", "bytes list")
+ testLogicBytes(t, program, nil, "bytes list", "bytes list")
})
}
})
@@ -3157,7 +3205,7 @@ func TestShortBytecblock2(t *testing.T) {
t.Parallel()
program, err := hex.DecodeString(src)
require.NoError(t, err)
- testLogicBytes(t, program, defaultEvalParams(), "const bytes list", "const bytes list")
+ testLogicBytes(t, program, nil, "const bytes list", "const bytes list")
})
}
}
@@ -3218,6 +3266,11 @@ func withPanicOpcode(t *testing.T, version uint64, panicDuringCheck bool, f func
func TestPanic(t *testing.T) { //nolint:paralleltest // Uses withPanicOpcode
partitiontest.PartitionTest(t)
+ // These tests would generate a lot of log noise which shows up if *other*
+ // tests fail. So it's pretty annoying to run `go test` on the whole
+ // package. `logSink` swallows log messages.
+ logSink := logging.NewLogger()
+ logSink.SetOutput(io.Discard)
for v := uint64(1); v <= AssemblerMaxVersion; v++ {
v := v
t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) { //nolint:paralleltest // Uses withPanicOpcode
@@ -3225,9 +3278,10 @@ func TestPanic(t *testing.T) { //nolint:paralleltest // Uses withPanicOpcode
ops := testProg(t, `int 1`, v)
ops.Program = append(ops.Program, opcode)
- params := defaultEvalParams()
- params.TxnGroup[0].Lsig.Logic = ops.Program
- err := CheckSignature(0, params)
+ ep := defaultSigParams()
+ ep.logger = logSink
+ ep.TxnGroup[0].Lsig.Logic = ops.Program
+ err := CheckSignature(0, ep)
var pe panicError
require.ErrorAs(t, err, &pe)
require.Equal(t, panicString, pe.PanicValue)
@@ -3235,11 +3289,12 @@ func TestPanic(t *testing.T) { //nolint:paralleltest // Uses withPanicOpcode
var txn transactions.SignedTxn
txn.Lsig.Logic = ops.Program
- params = defaultEvalParams(txn)
- pass, err := EvalSignature(0, params)
+ ep = defaultSigParams(txn)
+ ep.logger = logSink
+ pass, err := EvalSignature(0, ep)
if pass {
t.Log(hex.EncodeToString(ops.Program))
- t.Log(params.Trace.String())
+ t.Log(ep.Trace.String())
}
require.False(t, pass)
require.ErrorAs(t, err, &pe)
@@ -3252,9 +3307,9 @@ func TestPanic(t *testing.T) { //nolint:paralleltest // Uses withPanicOpcode
Type: protocol.ApplicationCallTx,
},
}
- params = defaultEvalParams(txn)
- params.Ledger = NewLedger(nil)
- pass, err = EvalApp(ops.Program, 0, 1, params)
+ ep := defaultAppParams(txn)
+ ep.logger = logSink
+ pass, err = EvalApp(ops.Program, 0, 1, ep)
require.False(t, pass)
require.ErrorAs(t, err, &pe)
require.Equal(t, panicString, pe.PanicValue)
@@ -3270,8 +3325,8 @@ func TestProgramTooNew(t *testing.T) {
t.Parallel()
var program [12]byte
- vlen := binary.PutUvarint(program[:], evalMaxVersion+1)
- testLogicBytes(t, program[:vlen], defaultEvalParams(),
+ vlen := binary.PutUvarint(program[:], LogicVersion+1)
+ testLogicBytes(t, program[:vlen], nil,
"greater than max supported", "greater than max supported")
}
@@ -3281,7 +3336,7 @@ func TestInvalidVersion(t *testing.T) {
t.Parallel()
program, err := hex.DecodeString("ffffffffffffffffffffffff")
require.NoError(t, err)
- testLogicBytes(t, program, defaultEvalParams(), "invalid version", "invalid version")
+ testLogicBytes(t, program, nil, "invalid version", "invalid version")
}
func TestProgramProtoForbidden(t *testing.T) {
@@ -3289,11 +3344,8 @@ func TestProgramProtoForbidden(t *testing.T) {
t.Parallel()
var program [12]byte
- vlen := binary.PutUvarint(program[:], evalMaxVersion)
- ep := defaultEvalParams()
- ep.Proto = &config.ConsensusParams{
- LogicSigVersion: evalMaxVersion - 1,
- }
+ vlen := binary.PutUvarint(program[:], LogicVersion)
+ ep := defaultSigParamsWithVersion(LogicVersion - 1)
testLogicBytes(t, program[:vlen], ep, "greater than protocol", "greater than protocol")
}
@@ -3315,16 +3367,16 @@ int 1`, v)
require.Equal(t, ops.Program, canonicalProgramBytes)
ops.Program[7] = 3 // clobber the branch offset to be in the middle of the bytecblock
// Since Eval() doesn't know the jump is bad, we reject "by luck"
- testLogicBytes(t, ops.Program, defaultEvalParams(), "aligned", "REJECT")
+ testLogicBytes(t, ops.Program, nil, "aligned", "REJECT")
// back branches are checked differently, so test misaligned back branch
ops.Program[6] = 0xff // Clobber the two bytes of offset with 0xff 0xff = -1
ops.Program[7] = 0xff // That jumps into the offset itself (pc + 3 -1)
if v < backBranchEnabledVersion {
- testLogicBytes(t, ops.Program, defaultEvalParams(), "negative branch", "negative branch")
+ testLogicBytes(t, ops.Program, nil, "negative branch", "negative branch")
} else {
// Again, if we were ever to Eval(), we would not know it's wrong. But we reject here "by luck"
- testLogicBytes(t, ops.Program, defaultEvalParams(), "back branch target", "REJECT")
+ testLogicBytes(t, ops.Program, nil, "back branch target", "REJECT")
}
})
}
@@ -3347,8 +3399,7 @@ int 1`, v)
require.NoError(t, err)
require.Equal(t, ops.Program, canonicalProgramBytes)
ops.Program[7] = 200 // clobber the branch offset to be beyond the end of the program
- testLogicBytes(t, ops.Program, defaultEvalParams(),
- "outside of program", "outside of program")
+ testLogicBytes(t, ops.Program, nil, "outside of program", "outside of program")
})
}
}
@@ -3371,7 +3422,7 @@ int 1`, v)
require.NoError(t, err)
require.Equal(t, ops.Program, canonicalProgramBytes)
ops.Program[6] = 0x70 // clobber hi byte of branch offset
- testLogicBytes(t, ops.Program, defaultEvalParams(), "outside", "outside")
+ testLogicBytes(t, ops.Program, nil, "outside", "outside")
})
}
branches := []string{
@@ -3393,8 +3444,7 @@ intc_1
require.NoError(t, err)
ops.Program[7] = 0xf0 // clobber the branch offset - highly negative
ops.Program[8] = 0xff // clobber the branch offset
- testLogicBytes(t, ops.Program, defaultEvalParams(),
- "outside of program", "outside of program")
+ testLogicBytes(t, ops.Program, nil, "outside of program", "outside of program")
})
}
}
@@ -3683,10 +3733,10 @@ func evalLoop(b *testing.B, runs int, program []byte) {
for i := 0; i < runs; i++ {
var txn transactions.SignedTxn
txn.Lsig.Logic = program
- pass, err := EvalSignature(0, benchmarkEvalParams(txn))
+ pass, err := EvalSignature(0, benchmarkSigParams(txn))
if !pass {
// rerun to trace it. tracing messes up timing too much
- ep := benchmarkEvalParams(txn)
+ ep := benchmarkSigParams(txn)
ep.Trace = &strings.Builder{}
pass, err = EvalSignature(0, ep)
b.Log(ep.Trace.String())
@@ -3998,7 +4048,7 @@ func BenchmarkCheckx5(b *testing.B) {
for _, program := range programs {
var txn transactions.SignedTxn
txn.Lsig.Logic = program
- err := CheckSignature(0, defaultEvalParams(txn))
+ err := CheckSignature(0, defaultSigParams(txn))
if err != nil {
require.NoError(b, err)
}
@@ -4102,17 +4152,15 @@ pop
txn.Lsig.Logic = ops.Program
txn.Txn.ApplicationArgs = [][]byte{[]byte("test")}
- ep := defaultEvalParams(txn)
- testLogicBytes(t, ops.Program, ep)
+ testLogicBytes(t, ops.Program, defaultSigParams(txn))
- ep = defaultEvalParamsWithVersion(1, txn)
- testLogicBytes(t, ops.Program, ep,
+ testLogicBytes(t, ops.Program, defaultSigParamsWithVersion(1, txn),
"greater than protocol supported version 1", "greater than protocol supported version 1")
// hack the version and fail on illegal opcode
ops.Program[0] = 0x1
- ep = defaultEvalParamsWithVersion(1, txn)
- testLogicBytes(t, ops.Program, ep, "illegal opcode 0x36", "illegal opcode 0x36") // txna
+ testLogicBytes(t, ops.Program, defaultSigParamsWithVersion(1, txn),
+ "illegal opcode 0x36", "illegal opcode 0x36") // txna
}
func TestStackOverflow(t *testing.T) {
@@ -4208,26 +4256,6 @@ func TestArgType(t *testing.T) {
require.Equal(t, avmUint64, sv.avmType())
}
-func TestApplicationsDisallowOldTeal(t *testing.T) {
- partitiontest.PartitionTest(t)
- t.Parallel()
-
- const source = "int 1"
-
- txn := makeSampleTxn()
- txn.Txn.Type = protocol.ApplicationCallTx
- txn.Txn.RekeyTo = basics.Address{}
- ep := defaultEvalParams(txn)
-
- for v := uint64(0); v < appsEnabledVersion; v++ {
- ops := testProg(t, source, v)
- e := fmt.Sprintf("program version must be >= %d", appsEnabledVersion)
- testAppBytes(t, ops.Program, ep, e, e)
- }
-
- testApp(t, source, ep)
-}
-
func TestAnyRekeyToOrApplicationRaisesMinAvmVersion(t *testing.T) {
partitiontest.PartitionTest(t)
t.Parallel()
@@ -4272,29 +4300,28 @@ func TestAnyRekeyToOrApplicationRaisesMinAvmVersion(t *testing.T) {
ci, cse := ci, cse
t.Run(fmt.Sprintf("ci=%d", ci), func(t *testing.T) {
t.Parallel()
- ep := defaultEvalParams(cse.group...)
+ sep, aep := defaultEvalParams(cse.group...)
// Computed MinAvmVersion should be == validFromVersion
- calc := ComputeMinAvmVersion(ep.TxnGroup)
+ calc := computeMinAvmVersion(sep.TxnGroup)
+ require.Equal(t, calc, cse.validFromVersion)
+
+ calc = computeMinAvmVersion(aep.TxnGroup)
require.Equal(t, calc, cse.validFromVersion)
// Should fail for all versions < validFromVersion
expected := fmt.Sprintf("program version must be >= %d", cse.validFromVersion)
for v := uint64(0); v < cse.validFromVersion; v++ {
ops := testProg(t, source, v)
- if ep.supportsAppEval() {
- testAppBytes(t, ops.Program, ep, expected, expected)
- }
- testLogicBytes(t, ops.Program, ep, expected, expected)
+ testAppBytes(t, ops.Program, aep, expected, expected)
+ testLogicBytes(t, ops.Program, sep, expected, expected)
}
// Should succeed for all versions >= validFromVersion
for v := cse.validFromVersion; v <= AssemblerMaxVersion; v++ {
ops := testProg(t, source, v)
- if ep.supportsAppEval() {
- testAppBytes(t, ops.Program, ep)
- }
- testLogicBytes(t, ops.Program, ep)
+ testAppBytes(t, ops.Program, aep)
+ testLogicBytes(t, ops.Program, sep)
}
})
}
@@ -4339,7 +4366,7 @@ func TestAllowedOpcodesV2(t *testing.T) {
"gtxn": true,
}
- ep := defaultEvalParamsWithVersion(2)
+ sep, aep := defaultEvalParamsWithVersion(2)
cnt := 0
for _, spec := range OpSpecs {
@@ -4349,9 +4376,9 @@ func TestAllowedOpcodesV2(t *testing.T) {
require.Contains(t, source, spec.Name)
ops := testProg(t, source, 2)
// all opcodes allowed in stateful mode so use CheckStateful/EvalContract
- err := CheckContract(ops.Program, ep)
+ err := CheckContract(ops.Program, aep)
require.NoError(t, err, source)
- _, err = EvalApp(ops.Program, 0, 0, ep)
+ _, err = EvalApp(ops.Program, 0, 0, aep)
if spec.Name != "return" {
// "return" opcode always succeeds so ignore it
require.Error(t, err, source)
@@ -4360,8 +4387,11 @@ func TestAllowedOpcodesV2(t *testing.T) {
for v := byte(0); v <= 1; v++ {
ops.Program[0] = v
- testLogicBytes(t, ops.Program, ep, "illegal opcode", "illegal opcode")
- testAppBytes(t, ops.Program, ep, "illegal opcode", "illegal opcode")
+ testLogicBytes(t, ops.Program, sep, "illegal opcode", "illegal opcode")
+ // let the program run even though minAvmVersion would ban it,
+ // so we can have this sanity check
+ aep.minAvmVersion = uint64(v)
+ testAppBytes(t, ops.Program, aep, "illegal opcode", "illegal opcode")
}
cnt++
}
@@ -4372,7 +4402,6 @@ func TestAllowedOpcodesV2(t *testing.T) {
// check all v3 opcodes: allowed in v3 and not allowed before
func TestAllowedOpcodesV3(t *testing.T) {
partitiontest.PartitionTest(t)
-
t.Parallel()
// all tests are expected to fail in evaluation
@@ -4392,7 +4421,7 @@ func TestAllowedOpcodesV3(t *testing.T) {
"pushbytes": `pushbytes "stringsfail?"`,
}
- ep := defaultEvalParamsWithVersion(3)
+ sep, aep := defaultEvalParamsWithVersion(3)
cnt := 0
for _, spec := range OpSpecs {
@@ -4402,12 +4431,15 @@ func TestAllowedOpcodesV3(t *testing.T) {
require.Contains(t, source, spec.Name)
ops := testProg(t, source, 3)
// all opcodes allowed in stateful mode so use CheckStateful/EvalContract
- testAppBytes(t, ops.Program, ep, "REJECT")
+ testAppBytes(t, ops.Program, aep, "REJECT")
- for v := byte(0); v <= 1; v++ {
+ for v := byte(0); v <= 2; v++ {
ops.Program[0] = v
- testLogicBytes(t, ops.Program, ep, "illegal opcode", "illegal opcode")
- testAppBytes(t, ops.Program, ep, "illegal opcode", "illegal opcode")
+ testLogicBytes(t, ops.Program, sep, "illegal opcode", "illegal opcode")
+ // let the program run even though minAvmVersion would ban it,
+ // so we can have this sanity check
+ aep.minAvmVersion = uint64(v)
+ testAppBytes(t, ops.Program, aep, "illegal opcode", "illegal opcode")
}
cnt++
}
@@ -4437,9 +4469,8 @@ func TestRekeyFailsOnOldVersion(t *testing.T) {
ops := testProg(t, "int 1", v)
var txn transactions.SignedTxn
txn.Txn.RekeyTo = basics.Address{1, 2, 3, 4}
- ep := defaultEvalParams(txn)
e := fmt.Sprintf("program version must be >= %d", rekeyingEnabledVersion)
- testLogicBytes(t, ops.Program, ep, e, e)
+ testLogicBytes(t, ops.Program, defaultSigParams(txn), e, e)
})
}
}
@@ -4478,13 +4509,13 @@ func testEvaluation(t *testing.T, program string, introduced uint64, tester eval
t.Helper()
var txn transactions.SignedTxn
txn.Lsig.Logic = ops.Program
- ep := defaultEvalParamsWithVersion(lv, txn)
+ ep := defaultSigParamsWithVersion(lv, txn)
err := CheckSignature(0, ep)
if err != nil {
t.Log(ep.Trace.String())
}
require.NoError(t, err)
- ep = defaultEvalParamsWithVersion(lv, txn)
+ ep = defaultSigParamsWithVersion(lv, txn)
pass, err := EvalSignature(0, ep)
ok := tester(t, pass, err)
if !ok {
@@ -5061,116 +5092,6 @@ func TestBytesConversions(t *testing.T) {
testAccepts(t, "byte 0x0011; byte 0x10; b+; btoi; int 0x21; ==", 4)
}
-func TestLog(t *testing.T) {
- partitiontest.PartitionTest(t)
-
- t.Parallel()
- var txn transactions.SignedTxn
- txn.Txn.Type = protocol.ApplicationCallTx
- ledger := NewLedger(nil)
- ledger.NewApp(txn.Txn.Receiver, 0, basics.AppParams{})
- ep := defaultEvalParams(txn)
- ep.Proto = makeTestProtoV(LogicVersion)
- ep.Ledger = ledger
- testCases := []struct {
- source string
- loglen int
- }{
- {
- source: `byte "a logging message"; log; int 1`,
- loglen: 1,
- },
- {
- source: `byte "a logging message"; log; byte "a logging message"; log; int 1`,
- loglen: 2,
- },
- {
- source: fmt.Sprintf(`%s int 1`, strings.Repeat(`byte "a logging message"; log; `, maxLogCalls)),
- loglen: maxLogCalls,
- },
- {
- source: `int 1; loop: byte "a logging message"; log; int 1; +; dup; int 30; <=; bnz loop;`,
- loglen: 30,
- },
- {
- source: fmt.Sprintf(`byte "%s"; log; int 1`, strings.Repeat("a", maxLogSize)),
- loglen: 1,
- },
- }
-
- //track expected number of logs in cx.EvalDelta.Logs
- for i, s := range testCases {
- delta := testApp(t, s.source, ep)
- require.Len(t, delta.Logs, s.loglen)
- if i == len(testCases)-1 {
- require.Equal(t, strings.Repeat("a", maxLogSize), delta.Logs[0])
- } else {
- for _, l := range delta.Logs {
- require.Equal(t, "a logging message", l)
- }
- }
- }
-
- msg := strings.Repeat("a", 400)
- failCases := []struct {
- source string
- runMode RunMode
- errContains string
- // For cases where assembly errors, we manually put in the bytes
- assembledBytes []byte
- }{
- {
- source: fmt.Sprintf(`byte "%s"; log; int 1`, strings.Repeat("a", maxLogSize+1)),
- errContains: fmt.Sprintf("> %d bytes limit", maxLogSize),
- 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,
- },
- {
- source: fmt.Sprintf(`%s; int 1`, strings.Repeat(`byte "a"; log; `, maxLogCalls+1)),
- errContains: "too many log calls",
- runMode: ModeApp,
- },
- {
- source: `int 1; loop: byte "a"; log; int 1; +; dup; int 35; <; bnz loop;`,
- errContains: "too many log calls",
- 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,
- },
- {
- source: `load 0; log`,
- errContains: "log arg 0 wanted []byte but got uint64",
- 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,
- },
- }
-
- for _, c := range failCases {
- switch c.runMode {
- case ModeApp:
- if c.assembledBytes == nil {
- testApp(t, c.source, ep, c.errContains)
- } else {
- testAppBytes(t, c.assembledBytes, ep, c.errContains)
- }
- default:
- testLogic(t, c.source, AssemblerMaxVersion, ep, c.errContains, c.errContains)
- }
- }
-}
-
func TestPcDetails(t *testing.T) {
partitiontest.PartitionTest(t)
t.Parallel()
@@ -5425,13 +5346,6 @@ func TestOpJSONRef(t *testing.T) {
partitiontest.PartitionTest(t)
t.Parallel()
- var txn transactions.SignedTxn
- txn.Txn.Type = protocol.ApplicationCallTx
- ledger := NewLedger(nil)
- ledger.NewApp(txn.Txn.Receiver, 0, basics.AppParams{})
- ep := defaultEvalParams(txn)
- ep.Ledger = ledger
- ep.SigLedger = ledger
testCases := []struct {
source string
previousVersErrors []expect
@@ -5601,15 +5515,7 @@ func TestOpJSONRef(t *testing.T) {
}
ops := testProg(t, s.source, AssemblerMaxVersion)
- err := CheckContract(ops.Program, ep)
- require.NoError(t, err, s)
-
- pass, _, err := EvalContract(ops.Program, 0, 888, ep)
- require.NoError(t, err)
- require.True(t, pass)
-
- // reset pooled budget for new "app call"
- *ep.PooledApplicationBudget = ep.Proto.MaxAppProgramCost
+ testLogicBytes(t, ops.Program, defaultSigParams())
}
failedCases := []struct {
@@ -5813,16 +5719,7 @@ func TestOpJSONRef(t *testing.T) {
}
ops := testProg(t, s.source, AssemblerMaxVersion)
-
- err := CheckContract(ops.Program, ep)
- require.NoError(t, err, s)
-
- pass, _, err := EvalContract(ops.Program, 0, 888, ep)
- require.False(t, pass)
- require.ErrorContains(t, err, s.error)
-
- // reset pooled budget for new "app call"
- *ep.PooledApplicationBudget = ep.Proto.MaxAppProgramCost
+ testLogicBytes(t, ops.Program, defaultSigParams(), s.error)
}
}
@@ -6115,3 +6012,12 @@ pop
int 1
`, 8)
}
+
+func TestNoHeaderLedger(t *testing.T) {
+ partitiontest.PartitionTest(t)
+
+ nhl := NoHeaderLedger{}
+ _, err := nhl.BlockHdr(1)
+ require.Error(t, err)
+ require.Equal(t, err, fmt.Errorf("no block header access"))
+}
diff --git a/data/transactions/logic/export_test.go b/data/transactions/logic/export_test.go
index 1a21d9ff1..34ae9d945 100644
--- a/data/transactions/logic/export_test.go
+++ b/data/transactions/logic/export_test.go
@@ -16,7 +16,12 @@
package logic
-import "github.com/algorand/go-algorand/data/basics"
+import (
+ "testing"
+
+ "github.com/algorand/go-algorand/data/basics"
+ "github.com/algorand/go-algorand/data/transactions"
+)
// Export for testing only. See
// https://medium.com/@robiplus/golang-trick-export-for-test-aa16cbd7b8cd for a
@@ -24,10 +29,6 @@ import "github.com/algorand/go-algorand/data/basics"
// we export some extra things to make testing easier there. But we do it in a
// _test.go file, so they are only exported during testing.
-func (ep *EvalParams) Reset() {
- ep.reset()
-}
-
// Inefficient (hashing), just a testing convenience
func (l *Ledger) CreateBox(app basics.AppIndex, name string, size uint64) {
l.NewBox(app, name, make([]byte, size), app.Address())
@@ -40,23 +41,31 @@ func (l *Ledger) DelBoxes(app basics.AppIndex, names ...string) {
}
}
-var DefaultEvalParams = defaultEvalParams
+var DefaultSigParams = defaultSigParams
+var DefaultAppParams = defaultAppParams
var Exp = exp
var MakeSampleEnv = makeSampleEnv
var MakeSampleEnvWithVersion = makeSampleEnvWithVersion
var MakeSampleTxn = makeSampleTxn
var MakeSampleTxnGroup = makeSampleTxnGroup
var MakeTestProto = makeTestProto
-var MakeTestProtoV = makeTestProtoV
var NoTrack = notrack
var TestLogic = testLogic
var TestApp = testApp
var TestAppBytes = testAppBytes
-var TestApps = testApps
var TestLogicRange = testLogicRange
var TestProg = testProg
var WithPanicOpcode = withPanicOpcode
+// TryApps exports "testApps" while accepting a simple uint64. Annoying, we
+// can't export call this "TestApps" because it looks like a Test function with
+// the wrong signature. But we can get that effect with the alias below.
+func TryApps(t *testing.T, programs []string, txgroup []transactions.SignedTxn, ver uint64, ledger *Ledger, expected ...expect) *EvalParams {
+ return testApps(t, programs, txgroup, protoVer(ver), ledger, expected...)
+}
+
+var TestApps = TryApps
+
const CreatedResourcesVersion = createdResourcesVersion
const AssemblerNoVersion = assemblerNoVersion
const FirstTestID = firstTestID
diff --git a/data/transactions/logic/fields_test.go b/data/transactions/logic/fields_test.go
index bedb07907..f4736f621 100644
--- a/data/transactions/logic/fields_test.go
+++ b/data/transactions/logic/fields_test.go
@@ -41,7 +41,6 @@ func TestGlobalFieldsVersions(t *testing.T) {
}
require.Greater(t, len(fields), 1)
- ledger := NewLedger(nil)
for _, field := range fields {
text := fmt.Sprintf("global %s", field.field.String())
// check assembler fails if version before introduction
@@ -54,22 +53,16 @@ func TestGlobalFieldsVersions(t *testing.T) {
ops := testProg(t, text, AssemblerMaxVersion)
// check on a version before the field version
- preLogicVersion := field.version - 1
- proto := makeTestProtoV(preLogicVersion)
- if preLogicVersion < appsEnabledVersion {
- require.False(t, proto.Application)
- }
- ep := defaultEvalParams()
- ep.Proto = proto
- ep.Ledger = ledger
+ preVersion := field.version - 1
+ ep := defaultSigParamsWithVersion(preVersion)
- // check failure with version check
- _, err := EvalApp(ops.Program, 0, 888, ep)
- require.Error(t, err)
- require.Contains(t, err.Error(), "greater than protocol supported version")
+ // check failure from whole program version check
+ testLogicBytes(t, ops.Program, ep,
+ "greater than protocol supported version",
+ "greater than protocol supported version")
// check opcodes failures
- ops.Program[0] = byte(preLogicVersion) // set version
+ ops.Program[0] = byte(preVersion) // set version
testLogicBytes(t, ops.Program, ep, "invalid global field")
// check opcodes failures on 0 version
@@ -101,7 +94,6 @@ func TestTxnFieldVersions(t *testing.T) {
}
txnaVersion := uint64(appsEnabledVersion)
- ledger := NewLedger(nil)
txn := makeSampleTxn()
// We'll reject too early if we have a nonzero RekeyTo, because that
// field must be zero for every txn in the group if this is an old
@@ -132,25 +124,18 @@ func TestTxnFieldVersions(t *testing.T) {
ops := testProg(t, text, AssemblerMaxVersion)
- preLogicVersion := fs.version - 1
- proto := makeTestProtoV(preLogicVersion)
- if preLogicVersion < appsEnabledVersion {
- require.False(t, proto.Application)
- }
- ep := defaultEvalParams()
- ep.Proto = proto
- ep.Ledger = ledger
- ep.TxnGroup = transactions.WrapSignedTxnsWithAD(txgroup)
+ preVersion := fs.version - 1
+ ep := defaultSigParamsWithVersion(preVersion, txgroup...)
// check failure with version check
testLogicBytes(t, ops.Program, ep,
"greater than protocol supported version", "greater than protocol supported version")
// check opcodes failures
- ops.Program[0] = byte(preLogicVersion) // set version
+ ops.Program[0] = byte(preVersion) // set version
checkErr := ""
evalErr := "invalid txn field"
- if txnaMode && preLogicVersion < txnaVersion {
+ if txnaMode && preVersion < txnaVersion {
checkErr = "illegal opcode"
evalErr = "illegal opcode"
}
diff --git a/data/transactions/logic/ledger_test.go b/data/transactions/logic/ledger_test.go
index 3016ae529..a111eec13 100644
--- a/data/transactions/logic/ledger_test.go
+++ b/data/transactions/logic/ledger_test.go
@@ -215,8 +215,8 @@ func (l *Ledger) PrevTimestamp() int64 {
return int64(rand.Uint32() + 1)
}
-// BlockHdrCached returns the block header for the given round, if it is available
-func (l *Ledger) BlockHdrCached(round basics.Round) (bookkeeping.BlockHeader, error) {
+// BlockHdr returns the block header for the given round, if it is available
+func (l *Ledger) BlockHdr(round basics.Round) (bookkeeping.BlockHeader, error) {
hdr := bookkeeping.BlockHeader{}
// Return a fake seed that is different for each round
seed := committee.Seed{}
diff --git a/data/transactions/logic/opcodes.go b/data/transactions/logic/opcodes.go
index 199152037..1c97be80a 100644
--- a/data/transactions/logic/opcodes.go
+++ b/data/transactions/logic/opcodes.go
@@ -391,11 +391,6 @@ func defaultDebugExplain(argCount, retCount int) debugStackExplain {
}
}
-// NextStackChange is a helper function that queries EvalContext for the coming stack change of the current PC.
-func (cx *EvalContext) NextStackChange() (deletions, additions int) {
- return (opsByOpcode[cx.version][cx.program[cx.pc]].Explain)(cx)
-}
-
func opPushIntsStackChange(cx *EvalContext) (deletions, additions int) {
// NOTE: WE ARE SWALLOWING THE ERROR HERE!
// FOR EVENTUALLY IT WOULD ERROR IN ASSEMBLY
@@ -890,7 +885,7 @@ func init() {
}
// Start from v2 and higher,
// copy lower version opcodes and overwrite matching version
- for v := uint64(2); v <= evalMaxVersion; v++ {
+ for v := uint64(2); v <= LogicVersion; v++ {
// Copy opcodes from lower version
OpsByName[v] = maps.Clone(OpsByName[v-1])
for op, oi := range opsByOpcode[v-1] {
diff --git a/data/transactions/logic/resources_test.go b/data/transactions/logic/resources_test.go
index bd5e5e17c..b796a8c4c 100644
--- a/data/transactions/logic/resources_test.go
+++ b/data/transactions/logic/resources_test.go
@@ -24,7 +24,7 @@ import (
"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/transactions/logic"
"github.com/algorand/go-algorand/data/txntest"
"github.com/algorand/go-algorand/protocol"
"github.com/algorand/go-algorand/test/partitiontest"
@@ -67,10 +67,10 @@ func TestAppSharing(t *testing.T) {
getSchema := "int 500; app_params_get AppGlobalNumByteSlice; !; assert; pop; int 1"
// In v8, the first tx can read app params of 500, because it's in its
// foreign array, but the second can't
- logic.TestApps(t, []string{getSchema, getSchema}, txntest.Group(&appl0, &appl1), 8, nil,
- logic.Exp(1, "unavailable App 500"))
+ TestApps(t, []string{getSchema, getSchema}, txntest.Group(&appl0, &appl1), 8, nil,
+ Exp(1, "unavailable App 500"))
// In v9, the second can, because the first can.
- logic.TestApps(t, []string{getSchema, getSchema}, txntest.Group(&appl0, &appl1), 9, nil)
+ TestApps(t, []string{getSchema, getSchema}, txntest.Group(&appl0, &appl1), 9, nil)
getLocalEx := `txn Sender; int 500; byte "some-key"; app_local_get_ex; pop; pop; int 1`
@@ -78,52 +78,52 @@ func TestAppSharing(t *testing.T) {
// reading the locals for a different account.
// app_local_get* requires the address and the app exist, else the program fails
- logic.TestApps(t, []string{getLocalEx, getLocalEx}, txntest.Group(&appl0, &appl1), 8, nil,
- logic.Exp(0, "no account"))
+ TestApps(t, []string{getLocalEx, getLocalEx}, txntest.Group(&appl0, &appl1), 8, nil,
+ Exp(0, "no account"))
- _, _, ledger := logic.MakeSampleEnv()
+ _, _, ledger := MakeSampleEnv()
ledger.NewAccount(appl0.Sender, 100_000)
ledger.NewAccount(appl1.Sender, 100_000)
ledger.NewApp(appl0.Sender, 500, basics.AppParams{})
ledger.NewLocals(appl0.Sender, 500) // opt in
// Now txn0 passes, but txn1 has an error because it can't see app 500
- logic.TestApps(t, []string{getLocalEx, getLocalEx}, txntest.Group(&appl0, &appl1), 9, ledger,
- logic.Exp(1, "unavailable Local State"))
+ TestApps(t, []string{getLocalEx, getLocalEx}, txntest.Group(&appl0, &appl1), 9, ledger,
+ Exp(1, "unavailable Local State"))
// But it's ok in appl2, because appl2 uses the same Sender, even though the
// foreign-app is not repeated in appl2 because the holding being accessed
// is the one from tx0.
- logic.TestApps(t, []string{getLocalEx, getLocalEx}, txntest.Group(&appl0, &appl2), 9, ledger)
- logic.TestApps(t, []string{getLocalEx, getLocalEx}, txntest.Group(&appl0, &appl2), 8, ledger, // version 8 does not get sharing
- logic.Exp(1, "unavailable App 500"))
+ TestApps(t, []string{getLocalEx, getLocalEx}, txntest.Group(&appl0, &appl2), 9, ledger)
+ TestApps(t, []string{getLocalEx, getLocalEx}, txntest.Group(&appl0, &appl2), 8, ledger, // version 8 does not get sharing
+ Exp(1, "unavailable App 500"))
// Checking if an account is opted in has pretty much the same rules
optInCheck500 := "txn Sender; int 500; app_opted_in"
// app_opted_in requires the address and the app exist, else the program fails
- logic.TestApps(t, []string{optInCheck500, optInCheck500}, txntest.Group(&appl0, &appl1), 9, nil, // nil ledger, no account
- logic.Exp(0, "no account: "+appl0.Sender.String()))
+ TestApps(t, []string{optInCheck500, optInCheck500}, txntest.Group(&appl0, &appl1), 9, nil, // nil ledger, no account
+ Exp(0, "no account: "+appl0.Sender.String()))
// Now txn0 passes, but txn1 has an error because it can't see app 500 locals for appl1.Sender
- logic.TestApps(t, []string{optInCheck500, optInCheck500}, txntest.Group(&appl0, &appl1), 9, ledger,
- logic.Exp(1, "unavailable Local State "+appl1.Sender.String()))
+ TestApps(t, []string{optInCheck500, optInCheck500}, txntest.Group(&appl0, &appl1), 9, ledger,
+ Exp(1, "unavailable Local State "+appl1.Sender.String()))
// But it's ok in appl2, because appl2 uses the same Sender, even though the
// foreign-app is not repeated in appl2 because the holding being accessed
// is the one from tx0.
- logic.TestApps(t, []string{optInCheck500, optInCheck500}, txntest.Group(&appl0, &appl2), 9, ledger)
- logic.TestApps(t, []string{optInCheck500, optInCheck500}, txntest.Group(&appl0, &appl2), 8, ledger, // version 8 does not get sharing
- logic.Exp(1, "unavailable App 500"))
+ TestApps(t, []string{optInCheck500, optInCheck500}, txntest.Group(&appl0, &appl2), 9, ledger)
+ TestApps(t, []string{optInCheck500, optInCheck500}, txntest.Group(&appl0, &appl2), 8, ledger, // version 8 does not get sharing
+ Exp(1, "unavailable App 500"))
// Confirm sharing applies to the app id called in tx0, not just foreign app array
optInCheck900 := "txn Sender; int 900; app_opted_in; !" // we did not opt any senders into 900
// as above, appl1 can't see the local state, but appl2 can b/c sender is same as appl0
- logic.TestApps(t, []string{optInCheck900, optInCheck900}, txntest.Group(&appl0, &appl1), 9, ledger,
- logic.Exp(1, "unavailable Local State "+appl1.Sender.String()))
- logic.TestApps(t, []string{optInCheck900, optInCheck900}, txntest.Group(&appl0, &appl2), 9, ledger)
- logic.TestApps(t, []string{optInCheck900, optInCheck900}, txntest.Group(&appl0, &appl2), 8, ledger, // v8=no sharing
- logic.Exp(1, "unavailable App 900"))
+ TestApps(t, []string{optInCheck900, optInCheck900}, txntest.Group(&appl0, &appl1), 9, ledger,
+ Exp(1, "unavailable Local State "+appl1.Sender.String()))
+ TestApps(t, []string{optInCheck900, optInCheck900}, txntest.Group(&appl0, &appl2), 9, ledger)
+ TestApps(t, []string{optInCheck900, optInCheck900}, txntest.Group(&appl0, &appl2), 8, ledger, // v8=no sharing
+ Exp(1, "unavailable App 900"))
// Now, confirm that *setting* a local state in tx1 that was made available
// in tx0 works. The extra check here is that the change is recorded
@@ -134,14 +134,14 @@ func TestAppSharing(t *testing.T) {
sources := []string{noop, putLocal}
appl1.ApplicationArgs = [][]byte{appl0.Sender[:]} // tx1 will try to modify local state exposed in tx0
// appl0.Sender is available, but 901's local state for it isn't (only 900 is, since 900 was called in tx0)
- logic.TestApps(t, sources, txntest.Group(&appl0, &appl1), 9, ledger,
- logic.Exp(1, "unavailable Local State "+appl0.Sender.String()))
+ TestApps(t, sources, txntest.Group(&appl0, &appl1), 9, ledger,
+ Exp(1, "unavailable Local State "+appl0.Sender.String()))
// Add 901 to tx0's ForeignApps, and it works
appl0.ForeignApps = append(appl0.ForeignApps, 901) // well, it will after we opt in
- logic.TestApps(t, sources, txntest.Group(&appl0, &appl1), 9, ledger,
- logic.Exp(1, "account "+appl0.Sender.String()+" is not opted into 901"))
+ TestApps(t, sources, txntest.Group(&appl0, &appl1), 9, ledger,
+ Exp(1, "account "+appl0.Sender.String()+" is not opted into 901"))
ledger.NewLocals(appl0.Sender, 901) // opt in
- ep := logic.TestApps(t, sources, txntest.Group(&appl0, &appl1), 9, ledger)
+ ep := TestApps(t, sources, txntest.Group(&appl0, &appl1), 9, ledger)
require.Len(t, ep.TxnGroup, 2)
ed := ep.TxnGroup[1].ApplyData.EvalDelta
require.Equal(t, map[uint64]basics.StateDelta{
@@ -157,38 +157,38 @@ func TestAppSharing(t *testing.T) {
// when running all three, appl2 can't read the locals of app in tx0 and addr in tx1
sources = []string{"", "", "gtxn 1 Sender; gtxn 0 Applications 0; byte 0xAA; app_local_get_ex"}
- logic.TestApps(t, sources, txntest.Group(&appl0, &appl1, &appl2), 9, nil,
- logic.Exp(2, "unavailable Local State")) // note that the error message is for Locals, not specialized
+ TestApps(t, sources, txntest.Group(&appl0, &appl1, &appl2), 9, nil,
+ Exp(2, "unavailable Local State")) // note that the error message is for Locals, not specialized
// same test of account in array of tx1 rather than Sender
appl1.Accounts = []basics.Address{{7, 7}}
sources = []string{"", "", "gtxn 1 Accounts 1; gtxn 0 Applications 0; byte 0xAA; app_local_get_ex"}
- logic.TestApps(t, sources, txntest.Group(&appl0, &appl1, &appl2), 9, nil,
- logic.Exp(2, "unavailable Local State")) // note that the error message is for Locals, not specialized
+ TestApps(t, sources, txntest.Group(&appl0, &appl1, &appl2), 9, nil,
+ Exp(2, "unavailable Local State")) // note that the error message is for Locals, not specialized
// try to do a put on local state of the account in tx1, but tx0 ought not have access to that local state
ledger.NewAccount(pay1.Receiver, 200_000)
ledger.NewLocals(pay1.Receiver, 900) // opt in
sources = []string{`gtxn 1 Receiver; byte "key"; byte "val"; app_local_put; int 1`}
- logic.TestApps(t, sources, txntest.Group(&appl0, &pay1), 9, ledger,
- logic.Exp(0, "unavailable Local State "+pay1.Receiver.String()))
+ TestApps(t, sources, txntest.Group(&appl0, &pay1), 9, ledger,
+ Exp(0, "unavailable Local State "+pay1.Receiver.String()))
// same for app_local_del
sources = []string{`gtxn 1 Receiver; byte "key"; app_local_del; int 1`}
- logic.TestApps(t, sources, txntest.Group(&appl0, &pay1), 9, ledger,
- logic.Exp(0, "unavailable Local State "+pay1.Receiver.String()))
+ TestApps(t, sources, txntest.Group(&appl0, &pay1), 9, ledger,
+ Exp(0, "unavailable Local State "+pay1.Receiver.String()))
// now, use an app call in tx1, with 900 in the foreign apps, so the local state is available
appl1.ForeignApps = append(appl1.ForeignApps, 900)
ledger.NewLocals(appl1.Sender, 900) // opt in
sources = []string{`gtxn 1 Sender; byte "key"; byte "val"; app_local_put; int 1`}
- logic.TestApps(t, sources, txntest.Group(&appl0, &appl1), 9, ledger)
- logic.TestApps(t, sources, txntest.Group(&appl0, &appl1), 8, ledger, // 8 doesn't share the account
- logic.Exp(0, "invalid Account reference "+appl1.Sender.String()))
+ TestApps(t, sources, txntest.Group(&appl0, &appl1), 9, ledger)
+ TestApps(t, sources, txntest.Group(&appl0, &appl1), 8, ledger, // 8 doesn't share the account
+ Exp(0, "invalid Account reference "+appl1.Sender.String()))
// same for app_local_del
sources = []string{`gtxn 1 Sender; byte "key"; app_local_del; int 1`}
- logic.TestApps(t, sources, txntest.Group(&appl0, &appl1), 9, ledger)
- logic.TestApps(t, sources, txntest.Group(&appl0, &appl1), 8, ledger, // 8 doesn't share the account
- logic.Exp(0, "invalid Account reference "+appl1.Sender.String()))
+ TestApps(t, sources, txntest.Group(&appl0, &appl1), 9, ledger)
+ TestApps(t, sources, txntest.Group(&appl0, &appl1), 8, ledger, // 8 doesn't share the account
+ Exp(0, "invalid Account reference "+appl1.Sender.String()))
}
// TestBetterLocalErrors confirms that we get specific errors about the missing
@@ -199,7 +199,7 @@ func TestBetterLocalErrors(t *testing.T) {
joe := basics.Address{9, 9, 9}
- ep, tx, ledger := logic.MakeSampleEnv()
+ ep, tx, ledger := MakeSampleEnv()
ledger.NewAccount(joe, 5000000)
ledger.NewApp(joe, 500, basics.AppParams{})
ledger.NewLocals(joe, 500)
@@ -215,19 +215,19 @@ pop; pop; int 1
binary.BigEndian.PutUint64(app, 500)
tx.ApplicationArgs = [][]byte{joe[:], app}
- logic.TestApp(t, getLocalEx, ep, "unavailable Account "+joe.String()+", unavailable App 500")
+ TestApp(t, getLocalEx, ep, "unavailable Account "+joe.String()+", unavailable App 500")
tx.Accounts = []basics.Address{joe}
- logic.TestApp(t, getLocalEx, ep, "unavailable App 500")
+ TestApp(t, getLocalEx, ep, "unavailable App 500")
tx.ForeignApps = []basics.AppIndex{500}
- logic.TestApp(t, getLocalEx, ep)
+ TestApp(t, getLocalEx, ep)
binary.BigEndian.PutUint64(tx.ApplicationArgs[1], 500)
- logic.TestApp(t, getLocalEx, ep)
+ TestApp(t, getLocalEx, ep)
binary.BigEndian.PutUint64(tx.ApplicationArgs[1], 501)
- logic.TestApp(t, getLocalEx, ep, "unavailable App 501")
+ TestApp(t, getLocalEx, ep, "unavailable App 501")
binary.BigEndian.PutUint64(tx.ApplicationArgs[1], 500)
tx.Accounts = []basics.Address{}
- logic.TestApp(t, getLocalEx, ep, "unavailable Account "+joe.String())
+ TestApp(t, getLocalEx, ep, "unavailable Account "+joe.String())
}
// TestAssetSharing confirms that as of v9, assets can be accessed across
@@ -258,32 +258,32 @@ func TestAssetSharing(t *testing.T) {
// In v8, the first tx can read asset 400, because it's in its foreign array,
// but the second can't
- logic.TestApps(t, []string{getTotal, getTotal}, txntest.Group(&appl0, &appl1), 8, nil,
- logic.Exp(1, "unavailable Asset 400"))
+ TestApps(t, []string{getTotal, getTotal}, txntest.Group(&appl0, &appl1), 8, nil,
+ Exp(1, "unavailable Asset 400"))
// In v9, the second can, because the first can.
- logic.TestApps(t, []string{getTotal, getTotal}, txntest.Group(&appl0, &appl1), 9, nil)
+ TestApps(t, []string{getTotal, getTotal}, txntest.Group(&appl0, &appl1), 9, nil)
getBalance := "txn Sender; int 400; asset_holding_get AssetBalance; pop; pop; int 1"
// In contrast, here there's no help from v9, because the second tx is
// reading a holding for a different account.
- logic.TestApps(t, []string{getBalance, getBalance}, txntest.Group(&appl0, &appl1), 8, nil,
- logic.Exp(1, "unavailable Asset 400"))
- logic.TestApps(t, []string{getBalance, getBalance}, txntest.Group(&appl0, &appl1), 9, nil,
- logic.Exp(1, "unavailable Holding"))
+ TestApps(t, []string{getBalance, getBalance}, txntest.Group(&appl0, &appl1), 8, nil,
+ Exp(1, "unavailable Asset 400"))
+ TestApps(t, []string{getBalance, getBalance}, txntest.Group(&appl0, &appl1), 9, nil,
+ Exp(1, "unavailable Holding"))
// But it's ok in appl2, because the same account is used, even though the
// foreign-asset is not repeated in appl2.
- logic.TestApps(t, []string{getBalance, getBalance}, txntest.Group(&appl0, &appl2), 9, nil)
+ TestApps(t, []string{getBalance, getBalance}, txntest.Group(&appl0, &appl2), 9, nil)
// when running all three, appl2 can't read the holding of asset in tx0 and addr in tx1
sources := []string{"", "", "gtxn 1 Sender; gtxn 0 Assets 0; asset_holding_get AssetBalance"}
- logic.TestApps(t, sources, txntest.Group(&appl0, &appl1, &appl2), 9, nil,
- logic.Exp(2, "unavailable Holding")) // note that the error message is for Holding, not specialized
+ TestApps(t, sources, txntest.Group(&appl0, &appl1, &appl2), 9, nil,
+ Exp(2, "unavailable Holding")) // note that the error message is for Holding, not specialized
// same test of account in array of tx1 rather than Sender
appl1.Accounts = []basics.Address{{7, 7}}
sources = []string{"", "", "gtxn 1 Accounts 1; gtxn 0 Assets 0; asset_holding_get AssetBalance"}
- logic.TestApps(t, sources, txntest.Group(&appl0, &appl1, &appl2), 9, nil,
- logic.Exp(2, "unavailable Holding")) // note that the error message is for Holding, not specialized
+ TestApps(t, sources, txntest.Group(&appl0, &appl1, &appl2), 9, nil,
+ Exp(2, "unavailable Holding")) // note that the error message is for Holding, not specialized
}
// TestBetterHoldingErrors confirms that we get specific errors about the missing
@@ -294,7 +294,7 @@ func TestBetterHoldingErrors(t *testing.T) {
joe := basics.Address{9, 9, 9}
- ep, tx, ledger := logic.MakeSampleEnv()
+ ep, tx, ledger := MakeSampleEnv()
ledger.NewAccount(joe, 5000000)
ledger.NewAsset(joe, 200, basics.AssetParams{})
// as creator, joe will also be opted in
@@ -309,17 +309,17 @@ pop; pop; int 1
binary.BigEndian.PutUint64(asa, 200)
tx.ApplicationArgs = [][]byte{joe[:], asa}
- logic.TestApp(t, getHoldingBalance, ep, "unavailable Account "+joe.String()+", unavailable Asset 200")
+ TestApp(t, getHoldingBalance, ep, "unavailable Account "+joe.String()+", unavailable Asset 200")
tx.Accounts = []basics.Address{joe}
- logic.TestApp(t, getHoldingBalance, ep, "unavailable Asset 200")
+ TestApp(t, getHoldingBalance, ep, "unavailable Asset 200")
tx.ForeignAssets = []basics.AssetIndex{200}
- logic.TestApp(t, getHoldingBalance, ep)
+ TestApp(t, getHoldingBalance, ep)
binary.BigEndian.PutUint64(tx.ApplicationArgs[1], 0) // slot=0 is same (200)
- logic.TestApp(t, getHoldingBalance, ep)
+ TestApp(t, getHoldingBalance, ep)
binary.BigEndian.PutUint64(tx.ApplicationArgs[1], 200)
tx.Accounts = []basics.Address{}
- logic.TestApp(t, getHoldingBalance, ep, "unavailable Account "+joe.String())
+ TestApp(t, getHoldingBalance, ep, "unavailable Account "+joe.String())
}
// TestAccountPassing checks that the current app account and foreign app's
@@ -329,9 +329,9 @@ func TestAccountPassing(t *testing.T) {
t.Parallel()
// appAddressVersion=7
- logic.TestLogicRange(t, 7, 0, func(t *testing.T, ep *logic.EvalParams, tx *transactions.Transaction, ledger *logic.Ledger) {
+ TestLogicRange(t, 7, 0, func(t *testing.T, ep *EvalParams, tx *transactions.Transaction, ledger *Ledger) {
t.Parallel()
- accept := logic.TestProg(t, "int 1", 6)
+ accept := TestProg(t, "int 1", 6)
alice := basics.Address{1, 1, 1, 1, 1}
ledger.NewApp(alice, 4, basics.AppParams{
ApprovalProgram: accept.Program,
@@ -346,12 +346,12 @@ int 1`
tx.ForeignApps = []basics.AppIndex{4}
ledger.NewAccount(appAddr(888), 50_000)
// First show that we're not just letting anything get passed in
- logic.TestApp(t, fmt.Sprintf(callWithAccount, "int 32; bzero; byte 0x07; b|"), ep,
+ TestApp(t, fmt.Sprintf(callWithAccount, "int 32; bzero; byte 0x07; b|"), ep,
"unavailable Account AAAAA")
// Now show we can pass our own address
- logic.TestApp(t, fmt.Sprintf(callWithAccount, "global CurrentApplicationAddress"), ep)
+ TestApp(t, fmt.Sprintf(callWithAccount, "global CurrentApplicationAddress"), ep)
// Or the address of one of our ForeignApps
- logic.TestApp(t, fmt.Sprintf(callWithAccount, "addr "+basics.AppIndex(4).Address().String()), ep)
+ TestApp(t, fmt.Sprintf(callWithAccount, "addr "+basics.AppIndex(4).Address().String()), ep)
})
}
@@ -360,7 +360,7 @@ func TestOtherTxSharing(t *testing.T) {
partitiontest.PartitionTest(t)
t.Parallel()
- _, _, ledger := logic.MakeSampleEnv()
+ _, _, ledger := MakeSampleEnv()
senderAcct := basics.Address{1, 2, 3, 4, 5, 6, 1}
ledger.NewAccount(senderAcct, 2001)
@@ -418,13 +418,13 @@ func TestOtherTxSharing(t *testing.T) {
}
for _, send := range []txntest.Txn{keyreg, pay, acfg, axfer, afrz} {
- logic.TestApps(t, []string{"", senderBalance}, txntest.Group(&send, &appl), 9, ledger)
- logic.TestApps(t, []string{senderBalance, ""}, txntest.Group(&appl, &send), 9, ledger)
+ TestApps(t, []string{"", senderBalance}, txntest.Group(&send, &appl), 9, ledger)
+ TestApps(t, []string{senderBalance, ""}, txntest.Group(&appl, &send), 9, ledger)
- logic.TestApps(t, []string{"", senderBalance}, txntest.Group(&send, &appl), 8, ledger,
- logic.Exp(1, "invalid Account reference"))
- logic.TestApps(t, []string{senderBalance, ""}, txntest.Group(&appl, &send), 8, ledger,
- logic.Exp(0, "invalid Account reference"))
+ TestApps(t, []string{"", senderBalance}, txntest.Group(&send, &appl), 8, ledger,
+ Exp(1, "invalid Account reference"))
+ TestApps(t, []string{senderBalance, ""}, txntest.Group(&appl, &send), 8, ledger,
+ Exp(0, "invalid Account reference"))
}
holdingAccess := `
@@ -436,82 +436,82 @@ func TestOtherTxSharing(t *testing.T) {
t.Run("keyreg", func(t *testing.T) { // nolint:paralleltest // shares `ledger`
appl.ApplicationArgs = [][]byte{senderAcct[:], {200}}
- logic.TestApps(t, []string{"", holdingAccess}, txntest.Group(&keyreg, &appl), 9, ledger,
- logic.Exp(1, "unavailable Asset 200"))
+ TestApps(t, []string{"", holdingAccess}, txntest.Group(&keyreg, &appl), 9, ledger,
+ Exp(1, "unavailable Asset 200"))
withRef := appl
withRef.ForeignAssets = []basics.AssetIndex{200}
- logic.TestApps(t, []string{"", holdingAccess}, txntest.Group(&keyreg, &withRef), 9, ledger,
- logic.Exp(1, "unavailable Holding "+senderAcct.String()))
+ TestApps(t, []string{"", holdingAccess}, txntest.Group(&keyreg, &withRef), 9, ledger,
+ Exp(1, "unavailable Holding "+senderAcct.String()))
})
t.Run("pay", func(t *testing.T) { // nolint:paralleltest // shares `ledger`
// The receiver is available for algo balance reading
appl.ApplicationArgs = [][]byte{receiverAcct[:]}
- logic.TestApps(t, []string{"", receiverBalance}, txntest.Group(&pay, &appl), 9, ledger)
+ TestApps(t, []string{"", receiverBalance}, txntest.Group(&pay, &appl), 9, ledger)
// The other account is not (it's not even in the pay txn)
appl.ApplicationArgs = [][]byte{otherAcct[:]}
- logic.TestApps(t, []string{"", otherBalance}, txntest.Group(&pay, &appl), 9, ledger,
- logic.Exp(1, "invalid Account reference "+otherAcct.String()))
+ TestApps(t, []string{"", otherBalance}, txntest.Group(&pay, &appl), 9, ledger,
+ Exp(1, "invalid Account reference "+otherAcct.String()))
// The other account becomes accessible because used in CloseRemainderTo
withClose := pay
withClose.CloseRemainderTo = otherAcct
- logic.TestApps(t, []string{"", otherBalance}, txntest.Group(&withClose, &appl), 9, ledger)
+ TestApps(t, []string{"", otherBalance}, txntest.Group(&withClose, &appl), 9, ledger)
})
t.Run("acfg", func(t *testing.T) { // nolint:paralleltest // shares `ledger`
// The other account is not available even though it's all the extra addresses
appl.ApplicationArgs = [][]byte{otherAcct[:]}
- logic.TestApps(t, []string{"", otherBalance}, txntest.Group(&acfg, &appl), 9, ledger,
- logic.Exp(1, "invalid Account reference "+otherAcct.String()))
+ TestApps(t, []string{"", otherBalance}, txntest.Group(&acfg, &appl), 9, ledger,
+ Exp(1, "invalid Account reference "+otherAcct.String()))
})
t.Run("axfer", func(t *testing.T) { // nolint:paralleltest // shares `ledger`
// The receiver is also available for algo balance reading
appl.ApplicationArgs = [][]byte{receiverAcct[:]}
- logic.TestApps(t, []string{"", receiverBalance}, txntest.Group(&axfer, &appl), 9, ledger)
+ TestApps(t, []string{"", receiverBalance}, txntest.Group(&axfer, &appl), 9, ledger)
// as is the "other" (AssetSender)
appl.ApplicationArgs = [][]byte{otherAcct[:]}
- logic.TestApps(t, []string{"", otherBalance}, txntest.Group(&axfer, &appl), 9, ledger)
+ TestApps(t, []string{"", otherBalance}, txntest.Group(&axfer, &appl), 9, ledger)
// sender holding is available
appl.ApplicationArgs = [][]byte{senderAcct[:], {byte(axfer.XferAsset)}}
- logic.TestApps(t, []string{"", holdingAccess}, txntest.Group(&axfer, &appl), 9, ledger)
+ TestApps(t, []string{"", holdingAccess}, txntest.Group(&axfer, &appl), 9, ledger)
// receiver holding is available
appl.ApplicationArgs = [][]byte{receiverAcct[:], {byte(axfer.XferAsset)}}
- logic.TestApps(t, []string{"", holdingAccess}, txntest.Group(&axfer, &appl), 9, ledger)
+ TestApps(t, []string{"", holdingAccess}, txntest.Group(&axfer, &appl), 9, ledger)
// asset sender (other) account is available
appl.ApplicationArgs = [][]byte{otherAcct[:], {byte(axfer.XferAsset)}}
- logic.TestApps(t, []string{"", holdingAccess}, txntest.Group(&axfer, &appl), 9, ledger)
+ TestApps(t, []string{"", holdingAccess}, txntest.Group(&axfer, &appl), 9, ledger)
// AssetCloseTo holding becomes available when set
appl.ApplicationArgs = [][]byte{other2Acct[:], {byte(axfer.XferAsset)}}
- logic.TestApps(t, []string{"", other2Balance}, txntest.Group(&axfer, &appl), 9, ledger,
- logic.Exp(1, "invalid Account reference "+other2Acct.String()))
- logic.TestApps(t, []string{"", holdingAccess}, txntest.Group(&axfer, &appl), 9, ledger,
- logic.Exp(1, "unavailable Account "+other2Acct.String()))
+ TestApps(t, []string{"", other2Balance}, txntest.Group(&axfer, &appl), 9, ledger,
+ Exp(1, "invalid Account reference "+other2Acct.String()))
+ TestApps(t, []string{"", holdingAccess}, txntest.Group(&axfer, &appl), 9, ledger,
+ Exp(1, "unavailable Account "+other2Acct.String()))
withClose := axfer
withClose.AssetCloseTo = other2Acct
appl.ApplicationArgs = [][]byte{other2Acct[:], {byte(axfer.XferAsset)}}
- logic.TestApps(t, []string{"", other2Balance}, txntest.Group(&withClose, &appl), 9, ledger)
- logic.TestApps(t, []string{"", holdingAccess}, txntest.Group(&withClose, &appl), 9, ledger)
+ TestApps(t, []string{"", other2Balance}, txntest.Group(&withClose, &appl), 9, ledger)
+ TestApps(t, []string{"", holdingAccess}, txntest.Group(&withClose, &appl), 9, ledger)
})
t.Run("afrz", func(t *testing.T) { // nolint:paralleltest // shares `ledger`
// The other account is available (for algo and asset)
appl.ApplicationArgs = [][]byte{otherAcct[:], {byte(afrz.FreezeAsset)}}
- logic.TestApps(t, []string{"", otherBalance}, txntest.Group(&afrz, &appl), 9, ledger)
- logic.TestApps(t, []string{"", holdingAccess}, txntest.Group(&afrz, &appl), 9, ledger)
+ TestApps(t, []string{"", otherBalance}, txntest.Group(&afrz, &appl), 9, ledger)
+ TestApps(t, []string{"", holdingAccess}, txntest.Group(&afrz, &appl), 9, ledger)
// The sender holding is _not_ (because the freezeaccount's holding is irrelevant to afrz)
appl.ApplicationArgs = [][]byte{senderAcct[:], {byte(afrz.FreezeAsset)}}
- logic.TestApps(t, []string{"", senderBalance}, txntest.Group(&afrz, &appl), 9, ledger)
- logic.TestApps(t, []string{"", holdingAccess}, txntest.Group(&afrz, &appl), 9, ledger,
- logic.Exp(1, "unavailable Holding "+senderAcct.String()))
+ TestApps(t, []string{"", senderBalance}, txntest.Group(&afrz, &appl), 9, ledger)
+ TestApps(t, []string{"", holdingAccess}, txntest.Group(&afrz, &appl), 9, ledger,
+ Exp(1, "unavailable Holding "+senderAcct.String()))
})
}
@@ -520,7 +520,7 @@ func TestSharedInnerTxns(t *testing.T) {
partitiontest.PartitionTest(t)
t.Parallel()
- _, _, ledger := logic.MakeSampleEnv()
+ _, _, ledger := MakeSampleEnv()
const asa1 = 201
const asa2 = 202
@@ -584,14 +584,14 @@ int 1
// appl has no foreign ref to senderAcct, but can still inner pay it
appl.ApplicationArgs = [][]byte{senderAcct[:]}
- logic.TestApps(t, []string{"", payToArg}, txntest.Group(&keyreg, &appl), 9, ledger)
- logic.TestApps(t, []string{"", payToArg}, txntest.Group(&keyreg, &appl), 8, ledger,
- logic.Exp(1, "unavailable Account "+senderAcct.String()))
+ TestApps(t, []string{"", payToArg}, txntest.Group(&keyreg, &appl), 9, ledger)
+ TestApps(t, []string{"", payToArg}, txntest.Group(&keyreg, &appl), 8, ledger,
+ Exp(1, "unavailable Account "+senderAcct.String()))
// confirm you can't just pay _anybody_. receiverAcct is not in use at all.
appl.ApplicationArgs = [][]byte{receiverAcct[:]}
- logic.TestApps(t, []string{"", payToArg}, txntest.Group(&keyreg, &appl), 9, ledger,
- logic.Exp(1, "unavailable Account "+receiverAcct.String()))
+ TestApps(t, []string{"", payToArg}, txntest.Group(&keyreg, &appl), 9, ledger,
+ Exp(1, "unavailable Account "+receiverAcct.String()))
})
t.Run("pay", func(t *testing.T) { // nolint:paralleltest // shares `ledger`
@@ -603,19 +603,19 @@ int 1
// appl has no foreign ref to senderAcct or receiverAcct, but can still inner pay them
appl.ApplicationArgs = [][]byte{senderAcct[:]}
- logic.TestApps(t, []string{"", payToArg}, txntest.Group(&pay, &appl), 9, ledger)
- logic.TestApps(t, []string{"", payToArg}, txntest.Group(&pay, &appl), 8, ledger,
- logic.Exp(1, "unavailable Account "+senderAcct.String()))
+ TestApps(t, []string{"", payToArg}, txntest.Group(&pay, &appl), 9, ledger)
+ TestApps(t, []string{"", payToArg}, txntest.Group(&pay, &appl), 8, ledger,
+ Exp(1, "unavailable Account "+senderAcct.String()))
appl.ApplicationArgs = [][]byte{receiverAcct[:]}
- logic.TestApps(t, []string{"", payToArg}, txntest.Group(&pay, &appl), 9, ledger)
- logic.TestApps(t, []string{"", payToArg}, txntest.Group(&pay, &appl), 8, ledger,
- logic.Exp(1, "unavailable Account "+receiverAcct.String()))
+ TestApps(t, []string{"", payToArg}, txntest.Group(&pay, &appl), 9, ledger)
+ TestApps(t, []string{"", payToArg}, txntest.Group(&pay, &appl), 8, ledger,
+ Exp(1, "unavailable Account "+receiverAcct.String()))
// confirm you can't just pay _anybody_. otherAcct is not in use at all.
appl.ApplicationArgs = [][]byte{otherAcct[:]}
- logic.TestApps(t, []string{"", payToArg}, txntest.Group(&pay, &appl), 9, ledger,
- logic.Exp(1, "unavailable Account "+otherAcct.String()))
+ TestApps(t, []string{"", payToArg}, txntest.Group(&pay, &appl), 9, ledger,
+ Exp(1, "unavailable Account "+otherAcct.String()))
})
t.Run("axfer", func(t *testing.T) { // nolint:paralleltest // shares `ledger`
@@ -629,47 +629,47 @@ int 1
// appl can pay the axfer sender
appl.ApplicationArgs = [][]byte{senderAcct[:], {asa1}}
- logic.TestApps(t, []string{"", payToArg}, txntest.Group(&axfer, &appl), 9, ledger)
- logic.TestApps(t, []string{"", payToArg}, txntest.Group(&axfer, &appl), 8, ledger,
- logic.Exp(1, "unavailable Account "+senderAcct.String()))
+ TestApps(t, []string{"", payToArg}, txntest.Group(&axfer, &appl), 9, ledger)
+ TestApps(t, []string{"", payToArg}, txntest.Group(&axfer, &appl), 8, ledger,
+ Exp(1, "unavailable Account "+senderAcct.String()))
// but can't axfer to sender, because appAcct doesn't have holding access for the asa
- logic.TestApps(t, []string{"", axferToArgs}, txntest.Group(&axfer, &appl), 9, ledger,
- logic.Exp(1, "unavailable Holding"))
+ TestApps(t, []string{"", axferToArgs}, txntest.Group(&axfer, &appl), 9, ledger,
+ Exp(1, "unavailable Holding"))
// and to the receiver
appl.ApplicationArgs = [][]byte{receiverAcct[:], {asa1}}
- logic.TestApps(t, []string{payToArg}, txntest.Group(&appl, &axfer), 9, ledger)
- logic.TestApps(t, []string{axferToArgs}, txntest.Group(&appl, &axfer), 9, ledger,
- logic.Exp(0, "unavailable Holding"))
+ TestApps(t, []string{payToArg}, txntest.Group(&appl, &axfer), 9, ledger)
+ TestApps(t, []string{axferToArgs}, txntest.Group(&appl, &axfer), 9, ledger,
+ Exp(0, "unavailable Holding"))
// and to the clawback
appl.ApplicationArgs = [][]byte{otherAcct[:], {asa1}}
- logic.TestApps(t, []string{"", payToArg}, txntest.Group(&axfer, &appl), 9, ledger)
- logic.TestApps(t, []string{"", axferToArgs}, txntest.Group(&axfer, &appl), 9, ledger,
- logic.Exp(1, "unavailable Holding"))
+ TestApps(t, []string{"", payToArg}, txntest.Group(&axfer, &appl), 9, ledger)
+ TestApps(t, []string{"", axferToArgs}, txntest.Group(&axfer, &appl), 9, ledger,
+ Exp(1, "unavailable Holding"))
// Those axfers become possible by adding the asa to the appl's ForeignAssets
appl.ForeignAssets = []basics.AssetIndex{asa1}
appl.ApplicationArgs = [][]byte{senderAcct[:], {asa1}}
- logic.TestApps(t, []string{"", axferToArgs}, txntest.Group(&axfer, &appl), 9, ledger)
+ TestApps(t, []string{"", axferToArgs}, txntest.Group(&axfer, &appl), 9, ledger)
appl.ApplicationArgs = [][]byte{receiverAcct[:], {asa1}}
- logic.TestApps(t, []string{axferToArgs}, txntest.Group(&appl, &axfer), 9, ledger)
+ TestApps(t, []string{axferToArgs}, txntest.Group(&appl, &axfer), 9, ledger)
appl.ApplicationArgs = [][]byte{otherAcct[:], {asa1}}
- logic.TestApps(t, []string{"", axferToArgs}, txntest.Group(&axfer, &appl), 9, ledger)
+ TestApps(t, []string{"", axferToArgs}, txntest.Group(&axfer, &appl), 9, ledger)
// but can't axfer a different asset
appl.ApplicationArgs = [][]byte{senderAcct[:], {asa2}}
- logic.TestApps(t, []string{"", axferToArgs}, txntest.Group(&axfer, &appl), 9, ledger,
- logic.Exp(1, fmt.Sprintf("unavailable Asset %d", asa2)))
+ TestApps(t, []string{"", axferToArgs}, txntest.Group(&axfer, &appl), 9, ledger,
+ Exp(1, fmt.Sprintf("unavailable Asset %d", asa2)))
// or correct asset to an unknown address
appl.ApplicationArgs = [][]byte{unusedAcct[:], {asa1}}
- logic.TestApps(t, []string{"", axferToArgs}, txntest.Group(&axfer, &appl), 9, ledger,
- logic.Exp(1, "unavailable Account"))
+ TestApps(t, []string{"", axferToArgs}, txntest.Group(&axfer, &appl), 9, ledger,
+ Exp(1, "unavailable Account"))
// appl can acfg the asset from tx0 (which requires asset available, not holding)
appl.ApplicationArgs = [][]byte{{asa1}}
- logic.TestApps(t, []string{"", acfgArg}, txntest.Group(&axfer, &appl), 9, ledger)
+ TestApps(t, []string{"", acfgArg}, txntest.Group(&axfer, &appl), 9, ledger)
appl.ApplicationArgs = [][]byte{{asa2}} // but not asa2
- logic.TestApps(t, []string{"", acfgArg}, txntest.Group(&axfer, &appl), 9, ledger,
- logic.Exp(1, fmt.Sprintf("unavailable Asset %d", asa2)))
+ TestApps(t, []string{"", acfgArg}, txntest.Group(&axfer, &appl), 9, ledger,
+ Exp(1, fmt.Sprintf("unavailable Asset %d", asa2)))
// Now, confirm that access to account from a pay in one tx, and asa
// from another don't allow inner axfer in the third (because there's no
@@ -682,16 +682,16 @@ int 1
}
// the asset is acfg-able
appl.ApplicationArgs = [][]byte{{asa1}}
- logic.TestApps(t, []string{"", "", acfgArg}, txntest.Group(&pay, &axfer, &appl), 9, ledger)
- logic.TestApps(t, []string{"", "", acfgArg}, txntest.Group(&axfer, &pay, &appl), 9, ledger)
+ TestApps(t, []string{"", "", acfgArg}, txntest.Group(&pay, &axfer, &appl), 9, ledger)
+ TestApps(t, []string{"", "", acfgArg}, txntest.Group(&axfer, &pay, &appl), 9, ledger)
// payAcct (the pay sender) is payable
appl.ApplicationArgs = [][]byte{payAcct[:]}
- logic.TestApps(t, []string{"", "", payToArg}, txntest.Group(&axfer, &pay, &appl), 9, ledger)
+ TestApps(t, []string{"", "", payToArg}, txntest.Group(&axfer, &pay, &appl), 9, ledger)
// but the cross-product is not available, so no axfer (opting in first, to prevent that error)
ledger.NewHolding(payAcct, asa1, 1, false)
appl.ApplicationArgs = [][]byte{payAcct[:], {asa1}}
- logic.TestApps(t, []string{"", "", axferToArgs}, txntest.Group(&axfer, &pay, &appl), 9, ledger,
- logic.Exp(2, "unavailable Holding "+payAcct.String()))
+ TestApps(t, []string{"", "", axferToArgs}, txntest.Group(&axfer, &pay, &appl), 9, ledger,
+ Exp(2, "unavailable Holding "+payAcct.String()))
})
t.Run("afrz", func(t *testing.T) { // nolint:paralleltest // shares `ledger`
@@ -705,48 +705,48 @@ int 1
// appl can pay to the sender & freeze account
appl.ApplicationArgs = [][]byte{senderAcct[:], {asa1}}
- logic.TestApps(t, []string{"", payToArg}, txntest.Group(&afrz, &appl), 9, ledger)
+ TestApps(t, []string{"", payToArg}, txntest.Group(&afrz, &appl), 9, ledger)
appl.ApplicationArgs = [][]byte{otherAcct[:], {asa1}}
- logic.TestApps(t, []string{"", payToArg}, txntest.Group(&afrz, &appl), 9, ledger)
+ TestApps(t, []string{"", payToArg}, txntest.Group(&afrz, &appl), 9, ledger)
// can't axfer to the afrz sender because appAcct holding is not available from afrz
appl.ApplicationArgs = [][]byte{senderAcct[:], {asa1}}
- logic.TestApps(t, []string{"", axferToArgs}, txntest.Group(&afrz, &appl), 9, ledger,
- logic.Exp(1, "unavailable Holding "+appAcct.String()))
+ TestApps(t, []string{"", axferToArgs}, txntest.Group(&afrz, &appl), 9, ledger,
+ Exp(1, "unavailable Holding "+appAcct.String()))
appl.ForeignAssets = []basics.AssetIndex{asa1}
// _still_ can't axfer to sender because afrz sender's holding does NOT
// become available (not note that complaint is now about that account)
- logic.TestApps(t, []string{"", axferToArgs}, txntest.Group(&afrz, &appl), 9, ledger,
- logic.Exp(1, "unavailable Holding "+senderAcct.String()))
+ TestApps(t, []string{"", axferToArgs}, txntest.Group(&afrz, &appl), 9, ledger,
+ Exp(1, "unavailable Holding "+senderAcct.String()))
// and not to the receiver which isn't in afrz
appl.ApplicationArgs = [][]byte{receiverAcct[:], {asa1}}
- logic.TestApps(t, []string{payToArg}, txntest.Group(&appl, &afrz), 9, ledger,
- logic.Exp(0, "unavailable Account "+receiverAcct.String()))
- logic.TestApps(t, []string{axferToArgs}, txntest.Group(&appl, &afrz), 9, ledger,
- logic.Exp(0, "unavailable Account "+receiverAcct.String()))
+ TestApps(t, []string{payToArg}, txntest.Group(&appl, &afrz), 9, ledger,
+ Exp(0, "unavailable Account "+receiverAcct.String()))
+ TestApps(t, []string{axferToArgs}, txntest.Group(&appl, &afrz), 9, ledger,
+ Exp(0, "unavailable Account "+receiverAcct.String()))
// otherAcct is the afrz target, it's holding and account are available
appl.ApplicationArgs = [][]byte{otherAcct[:], {asa1}}
- logic.TestApps(t, []string{"", payToArg}, txntest.Group(&afrz, &appl), 9, ledger)
- logic.TestApps(t, []string{"", axferToArgs}, txntest.Group(&afrz, &appl), 9, ledger)
+ TestApps(t, []string{"", payToArg}, txntest.Group(&afrz, &appl), 9, ledger)
+ TestApps(t, []string{"", axferToArgs}, txntest.Group(&afrz, &appl), 9, ledger)
// but still can't axfer a different asset
appl.ApplicationArgs = [][]byte{otherAcct[:], {asa2}}
- logic.TestApps(t, []string{"", axferToArgs}, txntest.Group(&afrz, &appl), 9, ledger,
- logic.Exp(1, fmt.Sprintf("unavailable Asset %d", asa2)))
+ TestApps(t, []string{"", axferToArgs}, txntest.Group(&afrz, &appl), 9, ledger,
+ Exp(1, fmt.Sprintf("unavailable Asset %d", asa2)))
appl.ForeignAssets = []basics.AssetIndex{asa2}
// once added to appl's foreign array, the appl still lacks access to other's holding
- logic.TestApps(t, []string{"", axferToArgs}, txntest.Group(&afrz, &appl), 9, ledger,
- logic.Exp(1, "unavailable Holding "+otherAcct.String()))
+ TestApps(t, []string{"", axferToArgs}, txntest.Group(&afrz, &appl), 9, ledger,
+ Exp(1, "unavailable Holding "+otherAcct.String()))
// appl can acfg the asset from tx0 (which requires asset available, not holding)
appl.ForeignAssets = []basics.AssetIndex{}
appl.ApplicationArgs = [][]byte{{asa1}}
- logic.TestApps(t, []string{"", acfgArg}, txntest.Group(&afrz, &appl), 9, ledger)
+ TestApps(t, []string{"", acfgArg}, txntest.Group(&afrz, &appl), 9, ledger)
appl.ApplicationArgs = [][]byte{{asa2}} // but not asa2
- logic.TestApps(t, []string{"", acfgArg}, txntest.Group(&afrz, &appl), 9, ledger,
- logic.Exp(1, fmt.Sprintf("unavailable Asset %d", asa2)))
+ TestApps(t, []string{"", acfgArg}, txntest.Group(&afrz, &appl), 9, ledger,
+ Exp(1, fmt.Sprintf("unavailable Asset %d", asa2)))
})
@@ -762,40 +762,40 @@ int 1
// appl can pay to the otherAcct because it was in tx0
appl.ApplicationArgs = [][]byte{otherAcct[:], {asa1}}
- logic.TestApps(t, []string{"", payToArg}, txntest.Group(&appl0, &appl), 9, ledger)
- logic.TestApps(t, []string{"", payToArg}, txntest.Group(&appl0, &appl), 8, ledger, // version 8 does not get sharing
- logic.Exp(1, "unavailable Account "+otherAcct.String()))
+ TestApps(t, []string{"", payToArg}, txntest.Group(&appl0, &appl), 9, ledger)
+ TestApps(t, []string{"", payToArg}, txntest.Group(&appl0, &appl), 8, ledger, // version 8 does not get sharing
+ Exp(1, "unavailable Account "+otherAcct.String()))
// appl can (almost) axfer asa1 to the otherAcct because both are in tx0
- logic.TestApps(t, []string{"", axferToArgs}, txntest.Group(&appl0, &appl), 9, ledger,
- logic.Exp(1, "axfer Sender: unavailable Holding"))
+ TestApps(t, []string{"", axferToArgs}, txntest.Group(&appl0, &appl), 9, ledger,
+ Exp(1, "axfer Sender: unavailable Holding"))
// but it can't take access it's OWN asa1, unless added to ForeignAssets
appl.ForeignAssets = []basics.AssetIndex{asa1}
- logic.TestApps(t, []string{"", axferToArgs}, txntest.Group(&appl0, &appl), 9, ledger)
+ TestApps(t, []string{"", axferToArgs}, txntest.Group(&appl0, &appl), 9, ledger)
// but it can't use 202 at all. notice the error is more direct that
// above, as the problem is not the axfer Sender, only, it's that 202
// can't be used at all.
appl.ApplicationArgs = [][]byte{otherAcct[:], {asa2}}
- logic.TestApps(t, []string{"", axferToArgs}, txntest.Group(&appl0, &appl), 9, ledger,
- logic.Exp(1, "unavailable Asset 202"))
+ TestApps(t, []string{"", axferToArgs}, txntest.Group(&appl0, &appl), 9, ledger,
+ Exp(1, "unavailable Asset 202"))
// And adding asa2 does not fix this problem, because the other x 202 holding is unavailable
appl.ForeignAssets = []basics.AssetIndex{asa2}
- logic.TestApps(t, []string{"", axferToArgs}, txntest.Group(&appl0, &appl), 9, ledger,
- logic.Exp(1, "axfer AssetReceiver: unavailable Holding "+otherAcct.String()+" x 202"))
+ TestApps(t, []string{"", axferToArgs}, txntest.Group(&appl0, &appl), 9, ledger,
+ Exp(1, "axfer AssetReceiver: unavailable Holding "+otherAcct.String()+" x 202"))
// Now, conduct similar tests, but with the apps performing the
// pays/axfers invoked from an outer app. Use various versions to check
// cross version sharing.
// add v8 and v9 versions of the pay app to the ledger for inner calling
- payToArgV8 := logic.TestProg(t, payToArg, 8)
+ payToArgV8 := TestProg(t, payToArg, 8)
ledger.NewApp(senderAcct, 88, basics.AppParams{ApprovalProgram: payToArgV8.Program})
ledger.NewAccount(appAddr(88), 1_000_000)
- payToArgV9 := logic.TestProg(t, payToArg, 9)
+ payToArgV9 := TestProg(t, payToArg, 9)
ledger.NewApp(senderAcct, 99, basics.AppParams{ApprovalProgram: payToArgV9.Program})
ledger.NewAccount(appAddr(99), 1_000_000)
- approvalV8 := logic.TestProg(t, "int 1", 8)
+ approvalV8 := TestProg(t, "int 1", 8)
ledger.NewApp(senderAcct, 11, basics.AppParams{ApprovalProgram: approvalV8.Program})
innerCallTemplate := `
@@ -813,28 +813,28 @@ int 1
appl.ForeignApps = []basics.AppIndex{11, 88, 99}
appl.ApplicationArgs = [][]byte{{99}, otherAcct[:], {asa1}}
- logic.TestApps(t, []string{"", innerCall}, txntest.Group(&appl0, &appl), 9, ledger)
+ TestApps(t, []string{"", innerCall}, txntest.Group(&appl0, &appl), 9, ledger)
// when the inner program is v8, it can't perform the pay
appl.ApplicationArgs = [][]byte{{88}, otherAcct[:], {asa1}}
- logic.TestApps(t, []string{"", innerCall}, txntest.Group(&appl0, &appl), 9, ledger,
- logic.Exp(1, "unavailable Account "+otherAcct.String()))
+ TestApps(t, []string{"", innerCall}, txntest.Group(&appl0, &appl), 9, ledger,
+ Exp(1, "unavailable Account "+otherAcct.String()))
// unless the caller passes in the account, but it can't pass the
// account because that also would give the called app access to the
// passed account's local state (which isn't available to the caller)
innerCallWithAccount := fmt.Sprintf(innerCallTemplate, "addr "+otherAcct.String()+"; itxn_field Accounts")
- logic.TestApps(t, []string{"", innerCallWithAccount}, txntest.Group(&appl0, &appl), 9, ledger,
- logic.Exp(1, "appl ApplicationID: unavailable Local State "+otherAcct.String()))
+ TestApps(t, []string{"", innerCallWithAccount}, txntest.Group(&appl0, &appl), 9, ledger,
+ Exp(1, "appl ApplicationID: unavailable Local State "+otherAcct.String()))
// the caller can't fix by passing 88 as a foreign app, because doing so
// is not much different than the current situation: 88 is being called,
// it's already available.
innerCallWithBoth := fmt.Sprintf(innerCallTemplate,
"addr "+otherAcct.String()+"; itxn_field Accounts; int 88; itxn_field Applications")
- logic.TestApps(t, []string{"", innerCallWithBoth}, txntest.Group(&appl0, &appl), 9, ledger,
- logic.Exp(1, "appl ApplicationID: unavailable Local State "+otherAcct.String()))
+ TestApps(t, []string{"", innerCallWithBoth}, txntest.Group(&appl0, &appl), 9, ledger,
+ Exp(1, "appl ApplicationID: unavailable Local State "+otherAcct.String()))
// the caller *can* do it if it originally had access to that 88 holding.
appl0.ForeignApps = []basics.AppIndex{88}
- logic.TestApps(t, []string{"", innerCallWithAccount}, txntest.Group(&appl0, &appl), 9, ledger)
+ TestApps(t, []string{"", innerCallWithAccount}, txntest.Group(&appl0, &appl), 9, ledger)
// here we confirm that even if we try calling another app, we still
// can't pass in `other` and 88, because that would give the app access
@@ -842,8 +842,8 @@ int 1
// of the foreign arrays, not just the accounts against called app id)
appl.ApplicationArgs = [][]byte{{11}, otherAcct[:], {asa1}}
appl0.ForeignApps = []basics.AppIndex{11}
- logic.TestApps(t, []string{"", innerCallWithBoth}, txntest.Group(&appl0, &appl), 9, ledger,
- logic.Exp(1, "appl ForeignApps: unavailable Local State "+otherAcct.String()))
+ TestApps(t, []string{"", innerCallWithBoth}, txntest.Group(&appl0, &appl), 9, ledger,
+ Exp(1, "appl ForeignApps: unavailable Local State "+otherAcct.String()))
})
@@ -856,7 +856,7 @@ func TestAccessMyLocals(t *testing.T) {
t.Parallel()
// start at 3, needs assert
- logic.TestLogicRange(t, 3, 0, func(t *testing.T, ep *logic.EvalParams, tx *transactions.Transaction, ledger *logic.Ledger) {
+ TestLogicRange(t, 3, 0, func(t *testing.T, ep *EvalParams, tx *transactions.Transaction, ledger *Ledger) {
sender := basics.Address{1, 2, 3, 4}
ledger.NewAccount(sender, 1_000_000)
// we don't really process transactions in these tests, so despite the
@@ -894,13 +894,13 @@ func TestAccessMyLocals(t *testing.T) {
app_local_del
int 1
`
- logic.TestApp(t, source, ep)
+ TestApp(t, source, ep)
if ep.Proto.LogicSigVersion >= 4 {
// confirm "txn Sender" also works
source = strings.ReplaceAll(source, "int 0\n", "txn Sender\n")
- logic.TestApp(t, source, ep)
+ TestApp(t, source, ep)
}
- logic.TestApp(t, "int 0; int 0; app_opted_in", ep)
+ TestApp(t, "int 0; int 0; app_opted_in", ep)
})
}
diff --git a/data/transactions/logic/tracer_test.go b/data/transactions/logic/tracer_test.go
index 9e5f9564b..5d44cbe2f 100644
--- a/data/transactions/logic/tracer_test.go
+++ b/data/transactions/logic/tracer_test.go
@@ -105,7 +105,7 @@ func TestLogicSigEvalWithTracer(t *testing.T) {
t.Run(testCase.name, func(t *testing.T) {
t.Parallel()
mock := mocktracer.Tracer{}
- ep := DefaultEvalParams()
+ ep := DefaultSigParams()
ep.Tracer = &mock
TestLogic(t, testCase.program, AssemblerMaxVersion, ep, testCase.evalProblems...)
@@ -123,7 +123,7 @@ func TestTopLevelAppEvalWithTracer(t *testing.T) {
t.Run(testCase.name, func(t *testing.T) {
t.Parallel()
mock := mocktracer.Tracer{}
- ep := DefaultEvalParams()
+ ep := DefaultAppParams()
ep.Tracer = &mock
TestApp(t, testCase.program, ep, testCase.evalProblems...)
@@ -190,12 +190,14 @@ func TestEvalPanicWithTracer(t *testing.T) { //nolint:paralleltest // Uses WithP
t.Run(mode.String(), func(t *testing.T) { //nolint:paralleltest // Uses WithPanicOpcode
testCase := getPanicTracerTestCase(mode)
mock := mocktracer.Tracer{}
- ep := DefaultEvalParams()
- ep.Tracer = &mock
switch mode {
case ModeSig:
+ ep := DefaultSigParams()
+ ep.Tracer = &mock
TestLogic(t, testCase.program, AssemblerMaxVersion, ep, testCase.evalProblems...)
case ModeApp:
+ ep := DefaultAppParams()
+ ep.Tracer = &mock
TestApp(t, testCase.program, ep, testCase.evalProblems...)
default:
require.Fail(t, "unknown mode")
@@ -225,7 +227,7 @@ func TestEvalWithTracerPanic(t *testing.T) {
t.Run(mode.String(), func(t *testing.T) {
t.Parallel()
tracer := panicTracer{}
- ep := DefaultEvalParams()
+ ep := DefaultSigParams()
ep.Tracer = &tracer
TestLogic(t, debuggerTestProgramApprove, AssemblerMaxVersion, ep, "panicTracer panics")
})
diff --git a/data/transactions/verify/artifact_test.go b/data/transactions/verify/artifact_test.go
index 226ac5449..1fa3d6b2d 100644
--- a/data/transactions/verify/artifact_test.go
+++ b/data/transactions/verify/artifact_test.go
@@ -43,20 +43,15 @@ func BenchmarkTinyMan(b *testing.B) {
require.NotEmpty(b, stxns[1].Lsig.Logic)
require.NotEmpty(b, stxns[2].Sig)
require.NotEmpty(b, stxns[3].Lsig.Logic)
- txgroup := transactions.WrapSignedTxnsWithAD(stxns)
b.ResetTimer()
for i := 0; i < b.N; i++ {
proto := config.Consensus[protocol.ConsensusCurrentVersion]
- ep := logic.EvalParams{
- Proto: &proto,
- TxnGroup: txgroup,
- SigLedger: &logic.NoHeaderLedger{},
- }
- pass, err := logic.EvalSignature(1, &ep)
+ ep := logic.NewSigEvalParams(stxns, &proto, &logic.NoHeaderLedger{})
+ pass, err := logic.EvalSignature(1, ep)
require.NoError(b, err)
require.True(b, pass)
- pass, err = logic.EvalSignature(3, &ep)
+ pass, err = logic.EvalSignature(3, ep)
require.NoError(b, err)
require.True(b, pass)
}
diff --git a/data/transactions/verify/txn.go b/data/transactions/verify/txn.go
index 8667d81e8..3d42694bb 100644
--- a/data/transactions/verify/txn.go
+++ b/data/transactions/verify/txn.go
@@ -59,24 +59,29 @@ const txnPerWorksetThreshold = 32
// - it allows us to linearly scan the input, and process elements only once we're going to queue them into the pool.
const concurrentWorksets = 16
-// GroupContext is the set of parameters external to a transaction which
-// stateless checks are performed against.
+// GroupContext holds values used to evaluate the LogicSigs in a group. The
+// first set are the parameters external to a transaction which could
+// potentially change the result of LogicSig evaluation. Example: If the
+// consensusVersion changes, a rule might change that matters. Certainly this is
+// _very_ rare, but we don't want to use the result of a LogicSig evaluation
+// across a protocol upgrade boundary.
//
-// For efficient caching, these parameters should either be constant
-// or change slowly over time.
-//
-// Group data are omitted because they are committed to in the
-// transaction and its ID.
+// The second set are derived from the first set and from the transaction
+// data. They are stored here only for efficiency, not for correctness, so they
+// are not checked in Equal()
type GroupContext struct {
+ // These fields determine whether a logicsig must be re-evaluated.
specAddrs transactions.SpecialAddresses
consensusVersion protocol.ConsensusVersion
- consensusParams config.ConsensusParams
- minAvmVersion uint64
- signedGroupTxns []transactions.SignedTxn
- ledger logic.LedgerForSignature
+
+ // These fields just hold useful data that ought not be recomputed (unless the above changes)
+ consensusParams config.ConsensusParams
+ signedGroupTxns []transactions.SignedTxn
+ evalParams *logic.EvalParams
}
var errTxGroupInvalidFee = errors.New("txgroup fee requirement overflow")
+var errTxGroupInsuffientLsigBudget = errors.New("txgroup lsig expectations exceed available budget")
var errTxnSigHasNoSig = errors.New("signedtxn has no sig")
var errTxnSigNotWellFormed = errors.New("signedtxn should only have one of Sig or Msig or LogicSig")
var errRekeyingNotSupported = errors.New("nonempty AuthAddr but rekeying is not supported")
@@ -125,9 +130,8 @@ func (e *TxGroupError) Unwrap() error {
return e.err
}
-// 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) {
+// PrepareGroupContext prepares a GroupCtx for a given transaction group.
+func PrepareGroupContext(group []transactions.SignedTxn, contextHdr *bookkeeping.BlockHeader, ledger logic.LedgerForSignature, evalTracer logic.EvalTracer) (*GroupContext, error) {
if len(group) == 0 {
return nil, nil
}
@@ -135,6 +139,9 @@ func PrepareGroupContext(group []transactions.SignedTxn, contextHdr *bookkeeping
if !ok {
return nil, protocol.Error(contextHdr.CurrentProtocol)
}
+
+ ep := logic.NewSigEvalParams(group, &consensusParams, ledger)
+ ep.Tracer = evalTracer
return &GroupContext{
specAddrs: transactions.SpecialAddresses{
FeeSink: contextHdr.FeeSink,
@@ -142,23 +149,21 @@ func PrepareGroupContext(group []transactions.SignedTxn, contextHdr *bookkeeping
},
consensusVersion: contextHdr.CurrentProtocol,
consensusParams: consensusParams,
- minAvmVersion: logic.ComputeMinAvmVersion(transactions.WrapSignedTxnsWithAD(group)),
signedGroupTxns: group,
- ledger: ledger,
+ evalParams: ep,
}, nil
}
// Equal compares two group contexts to see if they would represent the same verification context for a given transaction.
func (g *GroupContext) Equal(other *GroupContext) bool {
return g.specAddrs == other.specAddrs &&
- g.consensusVersion == other.consensusVersion &&
- g.minAvmVersion == other.minAvmVersion
+ g.consensusVersion == other.consensusVersion
}
// 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(gi int, groupCtx *GroupContext, verifier *crypto.BatchVerifier, evalTracer logic.EvalTracer) *TxGroupError {
+func txnBatchPrep(gi int, groupCtx *GroupContext, verifier *crypto.BatchVerifier) *TxGroupError {
s := &groupCtx.signedGroupTxns[gi]
if !groupCtx.consensusParams.SupportRekeying && (s.AuthAddr != basics.Address{}) {
return &TxGroupError{err: errRekeyingNotSupported, GroupIndex: gi, Reason: TxGroupErrorReasonGeneric}
@@ -168,7 +173,7 @@ func txnBatchPrep(gi int, groupCtx *GroupContext, verifier *crypto.BatchVerifier
return &TxGroupError{err: err, GroupIndex: gi, Reason: TxGroupErrorReasonNotWellFormed}
}
- return stxnCoreChecks(gi, groupCtx, verifier, evalTracer)
+ return stxnCoreChecks(gi, groupCtx, verifier)
}
// TxnGroup verifies a []SignedTxn as being signed and having no obviously inconsistent data.
@@ -202,7 +207,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, evalTracer logic.EvalTracer) (*GroupContext, error) {
- groupCtx, err := PrepareGroupContext(stxs, contextHdr, ledger)
+ groupCtx, err := PrepareGroupContext(stxs, contextHdr, ledger, evalTracer)
if err != nil {
return nil, err
}
@@ -210,7 +215,7 @@ func txnGroupBatchPrep(stxs []transactions.SignedTxn, contextHdr *bookkeeping.Bl
minFeeCount := uint64(0)
feesPaid := uint64(0)
for i, stxn := range stxs {
- prepErr := txnBatchPrep(i, groupCtx, verifier, evalTracer)
+ prepErr := txnBatchPrep(i, groupCtx, verifier)
if prepErr != nil {
// re-wrap the error with more details
prepErr.err = fmt.Errorf("transaction %+v invalid : %w", stxn, prepErr.err)
@@ -282,7 +287,7 @@ func checkTxnSigTypeCounts(s *transactions.SignedTxn, groupIndex int) (sigType s
}
// stxnCoreChecks runs signatures validity checks and enqueues signature into batchVerifier for verification.
-func stxnCoreChecks(gi int, groupCtx *GroupContext, batchVerifier *crypto.BatchVerifier, evalTracer logic.EvalTracer) *TxGroupError {
+func stxnCoreChecks(gi int, groupCtx *GroupContext, batchVerifier *crypto.BatchVerifier) *TxGroupError {
s := &groupCtx.signedGroupTxns[gi]
sigType, err := checkTxnSigTypeCounts(s, gi)
if err != nil {
@@ -308,7 +313,7 @@ func stxnCoreChecks(gi int, groupCtx *GroupContext, batchVerifier *crypto.BatchV
return nil
case logicSig:
- if err := logicSigVerify(gi, groupCtx, evalTracer); err != nil {
+ if err := logicSigVerify(gi, groupCtx); err != nil {
return &TxGroupError{err: err, GroupIndex: gi, Reason: TxGroupErrorReasonLogicSigFailed}
}
return nil
@@ -360,14 +365,7 @@ func logicSigSanityCheckBatchPrep(gi int, groupCtx *GroupContext, batchVerifier
return errors.New("LogicSig.Logic too long")
}
- txngroup := transactions.WrapSignedTxnsWithAD(groupCtx.signedGroupTxns)
- ep := logic.EvalParams{
- Proto: &groupCtx.consensusParams,
- TxnGroup: txngroup,
- MinAvmVersion: &groupCtx.minAvmVersion,
- SigLedger: groupCtx.ledger, // won't be needed for CheckSignature
- }
- err := logic.CheckSignature(gi, &ep)
+ err := logic.CheckSignature(gi, groupCtx.evalParams)
if err != nil {
return err
}
@@ -415,20 +413,13 @@ func logicSigSanityCheckBatchPrep(gi int, groupCtx *GroupContext, batchVerifier
}
// logicSigVerify checks that the signature is valid, executing the program.
-func logicSigVerify(gi int, groupCtx *GroupContext, evalTracer logic.EvalTracer) error {
+func logicSigVerify(gi int, groupCtx *GroupContext) error {
err := LogicSigSanityCheck(gi, groupCtx)
if err != nil {
return err
}
- ep := logic.EvalParams{
- Proto: &groupCtx.consensusParams,
- TxnGroup: transactions.WrapSignedTxnsWithAD(groupCtx.signedGroupTxns),
- MinAvmVersion: &groupCtx.minAvmVersion,
- SigLedger: groupCtx.ledger,
- Tracer: evalTracer,
- }
- pass, cx, err := logic.EvalSignatureFull(gi, &ep)
+ pass, cx, err := logic.EvalSignatureFull(gi, groupCtx.evalParams)
if err != nil {
logicErrTotal.Inc(nil)
return fmt.Errorf("transaction %v: %w", groupCtx.signedGroupTxns[gi].ID(), err)
diff --git a/data/transactions/verify/txn_test.go b/data/transactions/verify/txn_test.go
index 744371113..416c0e4c0 100644
--- a/data/transactions/verify/txn_test.go
+++ b/data/transactions/verify/txn_test.go
@@ -62,7 +62,7 @@ var spec = transactions.SpecialAddresses{
func verifyTxn(gi int, groupCtx *GroupContext) error {
batchVerifier := crypto.MakeBatchVerifier()
- if err := txnBatchPrep(gi, groupCtx, batchVerifier, nil); err != nil {
+ if err := txnBatchPrep(gi, groupCtx, batchVerifier); err != nil {
return err
}
return batchVerifier.Verify()
@@ -72,9 +72,6 @@ type DummyLedgerForSignature struct {
badHdr bool
}
-func (d *DummyLedgerForSignature) BlockHdrCached(basics.Round) (bookkeeping.BlockHeader, error) {
- 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")
@@ -208,7 +205,7 @@ func TestSignedPayment(t *testing.T) {
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)
+ groupCtx, err := PrepareGroupContext(stxns, blockHeader, nil, nil)
require.NoError(t, err)
require.NoError(t, payment.WellFormed(spec, proto), "generateTestObjects generated an invalid payment")
require.NoError(t, verifyTxn(0, groupCtx), "generateTestObjects generated a bad signedtxn")
@@ -230,7 +227,7 @@ func TestTxnValidationEncodeDecode(t *testing.T) {
_, signed, _, _ := generateTestObjects(100, 50, 0, 0)
for _, txn := range signed {
- groupCtx, err := PrepareGroupContext([]transactions.SignedTxn{txn}, blockHeader, nil)
+ groupCtx, err := PrepareGroupContext([]transactions.SignedTxn{txn}, blockHeader, nil, nil)
require.NoError(t, err)
if verifyTxn(0, groupCtx) != nil {
t.Errorf("signed transaction %#v did not verify", txn)
@@ -250,7 +247,7 @@ func TestTxnValidationEmptySig(t *testing.T) {
_, signed, _, _ := generateTestObjects(100, 50, 0, 0)
for _, txn := range signed {
- groupCtx, err := PrepareGroupContext([]transactions.SignedTxn{txn}, blockHeader, nil)
+ groupCtx, err := PrepareGroupContext([]transactions.SignedTxn{txn}, blockHeader, nil, nil)
require.NoError(t, err)
if verifyTxn(0, groupCtx) != nil {
t.Errorf("signed transaction %#v did not verify", txn)
@@ -292,7 +289,7 @@ func TestTxnValidationStateProof(t *testing.T) {
},
}
- groupCtx, err := PrepareGroupContext([]transactions.SignedTxn{stxn}, blockHeader, nil)
+ groupCtx, err := PrepareGroupContext([]transactions.SignedTxn{stxn}, blockHeader, nil, nil)
require.NoError(t, err)
err = verifyTxn(0, groupCtx)
@@ -352,7 +349,7 @@ func TestDecodeNil(t *testing.T) {
err := protocol.Decode(nilEncoding, &st)
if err == nil {
// This used to panic when run on a zero value of SignedTxn.
- groupCtx, err := PrepareGroupContext([]transactions.SignedTxn{st}, blockHeader, nil)
+ groupCtx, err := PrepareGroupContext([]transactions.SignedTxn{st}, blockHeader, nil, nil)
require.NoError(t, err)
verifyTxn(0, groupCtx)
}
@@ -1069,7 +1066,7 @@ func BenchmarkTxn(b *testing.B) {
b.ResetTimer()
for _, txnGroup := range txnGroups {
- groupCtx, err := PrepareGroupContext(txnGroup, &blk.BlockHeader, nil)
+ groupCtx, err := PrepareGroupContext(txnGroup, &blk.BlockHeader, nil, nil)
require.NoError(b, err)
for i := range txnGroup {
err := verifyTxn(i, groupCtx)
diff --git a/data/transactions/verify/verifiedTxnCache.go b/data/transactions/verify/verifiedTxnCache.go
index efa470150..184ed273e 100644
--- a/data/transactions/verify/verifiedTxnCache.go
+++ b/data/transactions/verify/verifiedTxnCache.go
@@ -22,7 +22,6 @@ import (
"github.com/algorand/go-deadlock"
"github.com/algorand/go-algorand/data/transactions"
- "github.com/algorand/go-algorand/data/transactions/logic"
"github.com/algorand/go-algorand/protocol"
)
@@ -73,7 +72,7 @@ type VerifiedTransactionCache interface {
type verifiedTransactionCache struct {
// Number of entries in each map (bucket).
entriesPerBucket int
- // bucketsLock is the lock for syncornizing the access to the cache
+ // bucketsLock is the lock for synchronizing access to the cache
bucketsLock deadlock.Mutex
// buckets is the circular cache buckets buffer
buckets []map[transactions.Txid]*GroupContext
@@ -127,7 +126,6 @@ func (v *verifiedTransactionCache) GetUnverifiedTransactionGroups(txnGroups [][]
for txnGroupIndex := 0; txnGroupIndex < len(txnGroups); txnGroupIndex++ {
signedTxnGroup := txnGroups[txnGroupIndex]
verifiedTxn := 0
- groupCtx.minAvmVersion = logic.ComputeMinAvmVersion(transactions.WrapSignedTxnsWithAD(signedTxnGroup))
baseBucket := v.base
for txnIdx := 0; txnIdx < len(signedTxnGroup); txnIdx++ {
diff --git a/data/transactions/verify/verifiedTxnCache_test.go b/data/transactions/verify/verifiedTxnCache_test.go
index b90eb66ef..880780f94 100644
--- a/data/transactions/verify/verifiedTxnCache_test.go
+++ b/data/transactions/verify/verifiedTxnCache_test.go
@@ -34,7 +34,7 @@ func TestAddingToCache(t *testing.T) {
impl := icache.(*verifiedTransactionCache)
_, signedTxn, secrets, addrs := generateTestObjects(10, 5, 0, 50)
txnGroups := generateTransactionGroups(protoMaxGroupSize, signedTxn, secrets, addrs)
- groupCtx, err := PrepareGroupContext(txnGroups[0], blockHeader, nil)
+ groupCtx, err := PrepareGroupContext(txnGroups[0], blockHeader, nil, nil)
require.NoError(t, err)
impl.Add(txnGroups[0], groupCtx)
// make it was added.
@@ -55,7 +55,7 @@ func TestBucketCycling(t *testing.T) {
_, 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)
+ groupCtx, err := PrepareGroupContext([]transactions.SignedTxn{signedTxn[0]}, blockHeader, nil, nil)
require.NoError(t, err)
// fill up the cache with entries.
@@ -92,7 +92,7 @@ func TestGetUnverifiedTransactionGroups50(t *testing.T) {
if i%2 == 0 {
expectedUnverifiedGroups = append(expectedUnverifiedGroups, txnGroups[i])
} else {
- groupCtx, _ := PrepareGroupContext(txnGroups[i], blockHeader, nil)
+ groupCtx, _ := PrepareGroupContext(txnGroups[i], blockHeader, nil, nil)
impl.Add(txnGroups[i], groupCtx)
}
}
@@ -116,7 +116,7 @@ func BenchmarkGetUnverifiedTransactionGroups50(b *testing.B) {
if i%2 == 1 {
queryTxnGroups = append(queryTxnGroups, txnGroups[i])
} else {
- groupCtx, _ := PrepareGroupContext(txnGroups[i], blockHeader, nil)
+ groupCtx, _ := PrepareGroupContext(txnGroups[i], blockHeader, nil, nil)
impl.Add(txnGroups[i], groupCtx)
}
}
@@ -145,7 +145,7 @@ func TestUpdatePinned(t *testing.T) {
// insert some entries.
for i := 0; i < len(txnGroups); i++ {
- groupCtx, _ := PrepareGroupContext(txnGroups[i], blockHeader, nil)
+ groupCtx, _ := PrepareGroupContext(txnGroups[i], blockHeader, nil, nil)
impl.Add(txnGroups[i], groupCtx)
}
@@ -174,7 +174,7 @@ func TestPinningTransactions(t *testing.T) {
// insert half of the entries.
for i := 0; i < len(txnGroups)/2; i++ {
- groupCtx, _ := PrepareGroupContext(txnGroups[i], blockHeader, nil)
+ groupCtx, _ := PrepareGroupContext(txnGroups[i], blockHeader, nil, nil)
impl.Add(txnGroups[i], groupCtx)
}
diff --git a/docker/Dockerfile b/docker/Dockerfile
index c4395ecd2..db01e6fad 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -2,7 +2,7 @@ FROM ubuntu:22.04
ARG GOLANG_VERSION
ENV DEBIAN_FRONTEND noninteractive
-RUN apt update && apt-get install -y git wget sqlite3 autoconf sudo tzdata bsdmainutils
+RUN apt update && apt-get install -y git wget autoconf sudo tzdata bsdmainutils
WORKDIR /root
RUN wget --quiet https://dl.google.com/go/go${GOLANG_VERSION}.linux-amd64.tar.gz && tar -xvf go${GOLANG_VERSION}.linux-amd64.tar.gz && mv go /usr/local
diff --git a/docker/build/Dockerfile b/docker/build/Dockerfile
index fec3fbdcc..5d096cbab 100644
--- a/docker/build/Dockerfile
+++ b/docker/build/Dockerfile
@@ -1,7 +1,7 @@
-FROM ubuntu:22.04
+FROM ubuntu:20.04
ARG GOLANG_VERSION
-RUN apt-get update && apt-get install -y git wget sqlite3 autoconf build-essential shellcheck
+RUN apt-get update && apt-get install -y git wget autoconf build-essential shellcheck
WORKDIR /root
RUN wget --quiet https://dl.google.com/go/go${GOLANG_VERSION}.linux-amd64.tar.gz && tar -xvf go${GOLANG_VERSION}.linux-amd64.tar.gz && mv go /usr/local
ENV GOROOT=/usr/local/go \
diff --git a/docker/build/Dockerfile-deploy b/docker/build/Dockerfile-deploy
index f4eb1709e..d01b505f4 100644
--- a/docker/build/Dockerfile-deploy
+++ b/docker/build/Dockerfile-deploy
@@ -1,7 +1,7 @@
-FROM --platform=linux/amd64 ubuntu:22.04
+FROM --platform=linux/amd64 ubuntu:20.04
ARG GOLANG_VERSION
-RUN apt-get update && apt-get install -y git wget sqlite3 autoconf jq bsdmainutils shellcheck
+RUN apt-get update && apt-get install -y git wget autoconf jq bsdmainutils shellcheck
WORKDIR /root
RUN wget --quiet https://dl.google.com/go/go${GOLANG_VERSION}.linux-amd64.tar.gz && tar -xvf go${GOLANG_VERSION}.linux-amd64.tar.gz && mv go /usr/local
ENV GOROOT=/usr/local/go \
diff --git a/docker/build/aptly.Dockerfile b/docker/build/aptly.Dockerfile
index 38e86e92a..1849d8e5e 100644
--- a/docker/build/aptly.Dockerfile
+++ b/docker/build/aptly.Dockerfile
@@ -1,4 +1,4 @@
-FROM ubuntu:22.04
+FROM ubuntu:20.04
ARG ARCH=amd64
ARG GOLANG_VERSION
diff --git a/docker/build/cicd.alpine.Dockerfile b/docker/build/cicd.alpine.Dockerfile
index c166ca6ec..c3ef9698a 100644
--- a/docker/build/cicd.alpine.Dockerfile
+++ b/docker/build/cicd.alpine.Dockerfile
@@ -13,8 +13,7 @@ RUN apk update && \
apk add automake && \
apk add fmt && \
apk add build-base && \
- apk add musl-dev && \
- apk add sqlite
+ apk add musl-dev
RUN apk add dpkg && \
wget http://deb.debian.org/debian/pool/main/s/shellcheck/shellcheck_0.5.0-3_armhf.deb && \
diff --git a/docker/build/cicd.centos.Dockerfile b/docker/build/cicd.centos.Dockerfile
index 0445e6b4b..f292e3d22 100644
--- a/docker/build/cicd.centos.Dockerfile
+++ b/docker/build/cicd.centos.Dockerfile
@@ -5,7 +5,7 @@ ARG GOLANG_VERSION
ARG ARCH="amd64"
RUN yum install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm && \
yum update -y && \
- yum install -y autoconf wget awscli git gnupg2 nfs-utils python3-devel sqlite3 expect jq \
+ yum install -y autoconf wget awscli git gnupg2 nfs-utils python3-devel expect jq \
libtool gcc-c++ libstdc++-devel libstdc++-static rpmdevtools createrepo rpm-sign bzip2 which ShellCheck \
libffi-devel openssl-devel
WORKDIR /root
diff --git a/docker/build/cicd.centos8.Dockerfile b/docker/build/cicd.centos8.Dockerfile
index 651fa0243..28ec63484 100644
--- a/docker/build/cicd.centos8.Dockerfile
+++ b/docker/build/cicd.centos8.Dockerfile
@@ -10,7 +10,6 @@ RUN dnf install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.n
libffi-devel openssl-devel
RUN dnf install -y epel-release && \
dnf update && \
- dnf -y install sqlite && \
dnf -y --enablerepo=powertools install libstdc++-static && \
dnf -y install make
RUN echo "${BOLD}Downloading and installing binaries...${RESET}" && \
diff --git a/docker/build/cicd.ubuntu.Dockerfile b/docker/build/cicd.ubuntu.Dockerfile
index c6c42ea9b..0b9fce77d 100644
--- a/docker/build/cicd.ubuntu.Dockerfile
+++ b/docker/build/cicd.ubuntu.Dockerfile
@@ -1,11 +1,11 @@
ARG ARCH="amd64"
-FROM ${ARCH}/ubuntu:22.04
+FROM ${ARCH}/ubuntu:20.04
ARG GOLANG_VERSION
ARG ARCH="amd64"
ARG GOARCH="amd64"
ENV DEBIAN_FRONTEND noninteractive
-RUN apt-get update && apt-get install -y build-essential git wget sqlite3 autoconf jq bsdmainutils shellcheck awscli libtool
+RUN apt-get update && apt-get install -y build-essential git wget autoconf jq bsdmainutils shellcheck awscli libtool
WORKDIR /root
RUN wget https://dl.google.com/go/go${GOLANG_VERSION}.linux-${GOARCH}.tar.gz \
&& tar -xvf go${GOLANG_VERSION}.linux-${GOARCH}.tar.gz && \
diff --git a/docker/build/docker.ubuntu.Dockerfile b/docker/build/docker.ubuntu.Dockerfile
index b94f885bf..5091afefa 100644
--- a/docker/build/docker.ubuntu.Dockerfile
+++ b/docker/build/docker.ubuntu.Dockerfile
@@ -1,6 +1,6 @@
ARG ARCH="amd64"
-FROM ${ARCH}/ubuntu:22.04
+FROM ${ARCH}/ubuntu:20.04
ARG GOLANG_VERSION
ARG ARCH="amd64"
RUN apt-get update && apt-get install curl python python3.7 python3-pip build-essential apt-transport-https ca-certificates software-properties-common -y && \
diff --git a/docker/build/releases-page.Dockerfile b/docker/build/releases-page.Dockerfile
index 7c3dceda0..c96f332d5 100644
--- a/docker/build/releases-page.Dockerfile
+++ b/docker/build/releases-page.Dockerfile
@@ -1,4 +1,4 @@
-FROM ubuntu:22.04
+FROM ubuntu:20.04
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install git python3 python3-pip -y && \
diff --git a/docs/follower_node.md b/docs/follower_node.md
index 9c58a7a82..8df230641 100644
--- a/docs/follower_node.md
+++ b/docs/follower_node.md
@@ -25,6 +25,7 @@ Follower mode was initially created to be a data source for [Conduit](https://gi
Behavior is controlled with the `config.json` file:
| property | description |
+| -------- | ----------- |
| EnableFollowMode | When set to `true` the node starts as a network follower. |
| MaxAcctLookback | The number of additional `Ledger State Delta` objects available. The default can be used, increasing to 64 or higher could help performance. |
| CatchupParallelBlocks | The number of blocks that are fetched concurrently. The default can be used, increasing to 64 or higher could help performance. |
diff --git a/go.mod b/go.mod
index a69db49ce..b63246f90 100644
--- a/go.mod
+++ b/go.mod
@@ -16,7 +16,7 @@ require (
github.com/algorand/websocket v1.4.6
github.com/aws/aws-sdk-go v1.33.0
github.com/consensys/gnark-crypto v0.7.0
- github.com/davidlazar/go-crypto v0.0.0-20170701192655-dcfb0a7ac018
+ github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c
github.com/dchest/siphash v1.2.1
github.com/fatih/color v1.13.0
github.com/getkin/kin-openapi v0.107.0
@@ -24,56 +24,103 @@ require (
github.com/golang/snappy v0.0.4
github.com/google/go-querystring v1.0.0
github.com/gorilla/mux v1.8.0
+ github.com/ipfs/go-datastore v0.6.0
+ github.com/ipfs/go-ds-pebble v0.2.4
github.com/jmoiron/sqlx v1.2.0
github.com/karalabe/usb v0.0.2
github.com/labstack/echo/v4 v4.9.1
+ github.com/libp2p/go-libp2p v0.29.0
github.com/mattn/go-sqlite3 v1.10.0
- github.com/miekg/dns v1.1.41
+ github.com/miekg/dns v1.1.55
+ github.com/multiformats/go-multiaddr v0.10.1
+ github.com/multiformats/go-multiaddr-dns v0.3.1
github.com/olivere/elastic v6.2.14+incompatible
github.com/sirupsen/logrus v1.8.1
github.com/spf13/cobra v1.3.0
- github.com/stretchr/testify v1.8.1
- golang.org/x/crypto v0.1.0
- golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1
- golang.org/x/sys v0.7.0
- golang.org/x/text v0.9.0
+ github.com/stretchr/testify v1.8.4
+ golang.org/x/crypto v0.11.0
+ golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1
+ golang.org/x/sys v0.10.0
+ golang.org/x/text v0.11.0
gopkg.in/sohlich/elogrus.v3 v3.0.0-20180410122755-1fa29e2f2009
pgregory.net/rapid v0.6.2
)
require (
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
+ github.com/beorn7/perks v1.0.1 // indirect
+ github.com/cespare/xxhash/v2 v2.2.0 // indirect
+ github.com/cockroachdb/errors v1.8.1 // indirect
+ github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f // indirect
+ github.com/cockroachdb/pebble v0.0.0-20230227185959-8285e8dd5c08 // indirect
+ github.com/cockroachdb/redact v1.0.8 // indirect
+ github.com/cockroachdb/sentry-go v0.6.1-cockroachdb.2 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
+ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
github.com/fortytw2/leaktest v1.3.0 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/swag v0.19.5 // indirect
+ github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
+ github.com/golang/protobuf v1.5.3 // indirect
+ github.com/google/go-cmp v0.5.9 // indirect
github.com/google/uuid v1.3.0 // indirect
+ github.com/hashicorp/golang-lru/v2 v2.0.2 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/invopop/yaml v0.1.0 // indirect
+ github.com/ipfs/go-cid v0.4.1 // indirect
+ github.com/ipfs/go-log/v2 v2.5.1 // indirect
+ github.com/jbenet/goprocess v0.1.4 // indirect
github.com/jmespath/go-jmespath v0.3.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
+ github.com/klauspost/compress v1.16.7 // indirect
+ github.com/klauspost/cpuid/v2 v2.2.5 // indirect
+ github.com/kr/pretty v0.2.1 // indirect
+ github.com/kr/text v0.2.0 // indirect
github.com/labstack/gommon v0.4.0 // indirect
+ github.com/libp2p/go-buffer-pool v0.1.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.12 // indirect
- github.com/mattn/go-isatty v0.0.14 // indirect
+ github.com/mattn/go-isatty v0.0.19 // indirect
+ github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
+ github.com/minio/sha256-simd v1.0.1 // indirect
github.com/mmcloughlin/addchain v0.4.0 // indirect
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
+ github.com/mr-tron/base58 v1.2.0 // indirect
+ github.com/multiformats/go-base32 v0.1.0 // indirect
+ github.com/multiformats/go-base36 v0.2.0 // indirect
+ github.com/multiformats/go-multiaddr-fmt v0.1.0 // indirect
+ github.com/multiformats/go-multibase v0.2.0 // indirect
+ github.com/multiformats/go-multicodec v0.9.0 // indirect
+ github.com/multiformats/go-multihash v0.2.3 // indirect
+ github.com/multiformats/go-multistream v0.4.1 // indirect
+ github.com/multiformats/go-varint v0.0.7 // indirect
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
+ github.com/prometheus/client_golang v1.14.0 // indirect
+ github.com/prometheus/client_model v0.4.0 // indirect
+ github.com/prometheus/common v0.37.0 // indirect
+ github.com/prometheus/procfs v0.8.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
+ github.com/spaolacci/murmur3 v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/stretchr/objx v0.5.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.1 // indirect
- golang.org/x/net v0.9.0 // indirect
- golang.org/x/sync v0.1.0 // indirect
- golang.org/x/term v0.7.0 // indirect
+ go.uber.org/atomic v1.11.0 // indirect
+ go.uber.org/multierr v1.11.0 // indirect
+ go.uber.org/zap v1.24.0 // indirect
+ golang.org/x/mod v0.12.0 // indirect
+ golang.org/x/net v0.12.0 // indirect
+ golang.org/x/term v0.10.0 // indirect
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 // indirect
+ golang.org/x/tools v0.11.0 // indirect
+ google.golang.org/protobuf v1.30.0 // indirect
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
+ lukechampine.com/blake3 v1.2.1 // indirect
)
diff --git a/go.sum b/go.sum
index 4e2d9d22c..8cb249f33 100644
--- a/go.sum
+++ b/go.sum
@@ -46,17 +46,26 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
+github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
+github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
+github.com/CloudyKit/fastprinter v0.0.0-20170127035650-74b38d55f37a/go.mod h1:EFZQ978U7x8IRnstaskI3IysnWY5Ao3QgZUKOXlsAdw=
+github.com/CloudyKit/jet v2.1.3-0.20180809161101-62edd43e4f88+incompatible/go.mod h1:HPYO+50pSWkPoj9Q/eq0aRGByCL6ScRlUmiEX5Zgm+w=
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
github.com/DataDog/zstd v1.5.2 h1:vUG4lAyuPCXO0TLbXvPv7EB7cNK1QV/luu55UHLrrn8=
github.com/DataDog/zstd v1.5.2/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
+github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY=
+github.com/Joker/jade v1.0.1-0.20190614124447-d475f43051e7/go.mod h1:6E6s8o2AE4KhCrqr6GRJjdC/gNfTdxkIXvuGZZda2VM=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
+github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0=
+github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
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.1.0 h1:xl832kfZ7hHG6B4p90DQynjfKFGbIUgUOnsRiMZXfAo=
@@ -81,22 +90,30 @@ github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kd
github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ=
github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
+github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/aws/aws-sdk-go v1.33.0 h1:Bq5Y6VTLbfnJp1IV8EL/qUU5qO1DYHda/zis/sqevkY=
github.com/aws/aws-sdk-go v1.33.0/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
+github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
+github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
+github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
+github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chrismcguire/gobberish v0.0.0-20150821175641-1d8adb509a0e h1:CHPYEbz71w8DqJ7DRIq+MXyCQsdibK08vdcQTY4ufas=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
@@ -114,19 +131,49 @@ github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWH
github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cockroachdb/datadriven v1.0.0/go.mod h1:5Ib8Meh+jk1RlHIXej6Pzevx/NLlNvQB9pmSBZErGA4=
+github.com/cockroachdb/datadriven v1.0.2 h1:H9MtNqVoVhvd9nCBwOyDjUEdZCREqbIdCJD93PBm/jA=
+github.com/cockroachdb/errors v1.6.1/go.mod h1:tm6FTP5G81vwJ5lC0SizQo374JNCOPrHyXGitRJoDqM=
+github.com/cockroachdb/errors v1.8.1 h1:A5+txlVZfOqFBDa4mGz2bUWSp0aHElvHX2bKkdbQu+Y=
+github.com/cockroachdb/errors v1.8.1/go.mod h1:qGwQn6JmZ+oMjuLwjWzUNqblqk0xl4CVV3SQbGwK7Ac=
+github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f h1:o/kfcElHqOiXqcou5a3rIlMc7oJbMQkeLk0VQJ7zgqY=
+github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI=
+github.com/cockroachdb/pebble v0.0.0-20230227185959-8285e8dd5c08 h1:acwj2RLsc4NK34fzBUQp5oy4oN+XTbRsEZVCX4puE+U=
+github.com/cockroachdb/pebble v0.0.0-20230227185959-8285e8dd5c08/go.mod h1:9lRMC4XN3/BLPtIp6kAKwIaHu369NOf2rMucPzipz50=
+github.com/cockroachdb/redact v1.0.8 h1:8QG/764wK+vmEYoOlfobpe12EQcS81ukx/a4hdVMxNw=
+github.com/cockroachdb/redact v1.0.8/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg=
+github.com/cockroachdb/sentry-go v0.6.1-cockroachdb.2 h1:IKgmqgMQlVJIZj19CdocBeSfSaiCbEBZGKODaixqtHM=
+github.com/cockroachdb/sentry-go v0.6.1-cockroachdb.2/go.mod h1:8BT+cPK6xvFOcRlk0R8eg+OTkcqI6baNH4xAkpiYVvQ=
+github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM=
github.com/consensys/gnark-crypto v0.7.0 h1:rwdy8+ssmLYRqKp+ryRRgQJl/rCq2uv+n83cOydm5UE=
github.com/consensys/gnark-crypto v0.7.0/go.mod h1:KPSuJzyxkJA8xZ/+CV47tyqkr9MmpZA3PXivK4VPrVg=
+github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
+github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
+github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
+github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU=
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/davidlazar/go-crypto v0.0.0-20170701192655-dcfb0a7ac018 h1:6xT9KW8zLC5IlbaIF5Q7JNieBoACT7iW0YTxQHR0in0=
-github.com/davidlazar/go-crypto v0.0.0-20170701192655-dcfb0a7ac018/go.mod h1:rQYf4tfk5sSwFsnDg3qYaBxSjsD9S8+59vW0dKUgme4=
+github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c h1:pFUpOrbxDR6AkioZ1ySsx5yxlDQZ8stG2b88gTPxgJU=
+github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c/go.mod h1:6UhI8N9EjYm1c2odKpFpAYeR8dsBeM7PtzQhRgxRr9U=
github.com/dchest/siphash v1.2.1 h1:4cLinnzVJDKxTCl9B01807Yiy+W7ZzVHj/KIroQRvT4=
github.com/dchest/siphash v1.2.1/go.mod h1:q+IRvb2gOSrUnYoPqHiyHXS0FOBBOdl6tONBlVnOnt4=
+github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y=
+github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs=
+github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
+github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4=
+github.com/dgraph-io/badger v1.6.2 h1:mNw0qs90GVgGGWylh0umH5iag1j6n/PeJtNvL6KY/x8=
+github.com/dgraph-io/ristretto v0.0.2 h1:a5WaUrDa0qm0YrAAS1tUykT5El3kt62KNZZeMxQn3po=
+github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
+github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
+github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
+github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
+github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
@@ -138,23 +185,40 @@ github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.
github.com/envoyproxy/go-control-plane v0.10.1/go.mod h1:AY7fTTXNdv/aJ2O5jwpxAPOWUZ7hQAEvzN5Pf27BkQQ=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws=
+github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw=
+github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
+github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
+github.com/flosch/pongo2 v0.0.0-20190707114632-bbf5a6c351f4/go.mod h1:T9YF2M40nIgbVgp3rreNmTged+9HrbNTIQf1PsaIiTA=
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
+github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
+github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc=
github.com/getkin/kin-openapi v0.107.0 h1:bxhL6QArW7BXQj8NjXfIJQy680NsMKd25nwhvpCXchg=
github.com/getkin/kin-openapi v0.107.0/go.mod h1:9Dhr+FasATJZjS4iOLvB0hkaxgYdulrNYm2e9epLWOo=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
+github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=
+github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98=
+github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
+github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
+github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
+github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
+github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
+github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8=
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY=
@@ -163,11 +227,19 @@ github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
+github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
+github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gofrs/flock v0.7.0 h1:pGFUjl501gafK9HBt1VGL1KCOd/YhIooID+xgyJCf3g=
github.com/gofrs/flock v0.7.0/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
+github.com/gogo/googleapis v0.0.0-20180223154316-0cd9801be74a/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
+github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
+github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
+github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
+github.com/gogo/status v1.1.0/go.mod h1:BFv9nrluPLmrS0EmGVvLaPNmRosr9KapBYd5/hpY1WM=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
@@ -201,9 +273,12 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
@@ -218,6 +293,8 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
@@ -248,8 +325,10 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
+github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
+github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/hashicorp/consul/api v1.11.0/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M=
github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms=
@@ -270,9 +349,12 @@ github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerX
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
+github.com/hashicorp/golang-lru/v2 v2.0.2 h1:Dwmkdr5Nc/oBiXgJS3CDHNhJtIHkuZ3DZF5twqnfBdU=
+github.com/hashicorp/golang-lru/v2 v2.0.2/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY=
@@ -281,53 +363,106 @@ github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOn
github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk=
github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4=
+github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/hydrogen18/memlistener v0.0.0-20141126152155-54553eb933fb/go.mod h1:qEIFzExnS6016fRpRfxrExeVn2gbClQA99gQhnIcdhE=
github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
+github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/invopop/yaml v0.1.0 h1:YW3WGUoJEXYfzWBjn00zIlrw7brGVD0fUKRYDPAPhrc=
github.com/invopop/yaml v0.1.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q=
+github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s=
+github.com/ipfs/go-cid v0.4.1/go.mod h1:uQHwDeX4c6CtyrFwdqyhpNcxVewur1M7l7fNU7LKwZk=
+github.com/ipfs/go-datastore v0.6.0 h1:JKyz+Gvz1QEZw0LsX1IBn+JFCJQH4SJVFtM4uWU0Myk=
+github.com/ipfs/go-datastore v0.6.0/go.mod h1:rt5M3nNbSO/8q1t4LNkLyUwRs8HupMeN/8O4Vn9YAT8=
+github.com/ipfs/go-detect-race v0.0.1 h1:qX/xay2W3E4Q1U7d9lNs1sU9nvguX0a7319XbyQ6cOk=
+github.com/ipfs/go-ds-badger v0.3.0 h1:xREL3V0EH9S219kFFueOYJJTcjgNSZ2HY1iSvN7U1Ro=
+github.com/ipfs/go-ds-leveldb v0.5.0 h1:s++MEBbD3ZKc9/8/njrn4flZLnCuY9I79v94gBUNumo=
+github.com/ipfs/go-ds-pebble v0.2.4 h1:Y/Pma4GIIxvenyuFU2oPv6eS3Yh7UebaZ6/xqpBJuds=
+github.com/ipfs/go-ds-pebble v0.2.4/go.mod h1:gcsz9feIlROgsWzO81ZlrMpWtK2mQ+b/a8F+advE+Hw=
+github.com/ipfs/go-log/v2 v2.5.1 h1:1XdUzF7048prq4aBjDQQ4SL5RxftpRGdXhNRwKSAlcY=
+github.com/ipfs/go-log/v2 v2.5.1/go.mod h1:prSpmC1Gpllc9UYWxDiZDreBYw7zp4Iqp1kOLU9U5UI=
+github.com/iris-contrib/blackfriday v2.0.0+incompatible/go.mod h1:UzZ2bDEoaSGPbkg6SAB4att1aAwTmVIx/5gCVqeyUdI=
+github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0=
+github.com/iris-contrib/i18n v0.0.0-20171121225848-987a633949d0/go.mod h1:pMCz62A0xJL6I+umB2YTlFRwWXaDFA0jy+5HzGiJjqI=
+github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw=
+github.com/jbenet/go-cienv v0.1.0/go.mod h1:TqNnHUmJgXau0nCzC7kXWeotg3J9W34CUv5Djy1+FlA=
+github.com/jbenet/goprocess v0.1.4 h1:DRGOFReOMqqDNXwW70QkacFW0YN9QnwLV0Vqk+3oU0o=
+github.com/jbenet/goprocess v0.1.4/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZlqdZVfqY4=
github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc=
github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik=
github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA=
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
+github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
+github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
+github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q=
github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=
+github.com/juju/loggo v0.0.0-20180524022052-584905176618/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U=
+github.com/juju/testing v0.0.0-20180920084828-472a3e8b2073/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
+github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
+github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
github.com/karalabe/usb v0.0.2 h1:M6QQBNxF+CQ8OFvxrT90BA0qBOXymndZnk5q235mFc4=
github.com/karalabe/usb v0.0.2/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU=
+github.com/kataras/golog v0.0.9/go.mod h1:12HJgwBIZFNGL0EJnMRhmvGA0PQGx8VFwrZtM4CqbAk=
+github.com/kataras/iris/v12 v12.0.1/go.mod h1:udK4vLQKkdDqMGJJVd/msuMtN6hpYJhg/lSzuxjhO+U=
+github.com/kataras/neffos v0.0.10/go.mod h1:ZYmJC07hQPW67eKuzlfY7SO3bC0mw83A3j6im82hfqw=
+github.com/kataras/pio v0.0.0-20190103105442-ea782b38602d/go.mod h1:NV88laa9UiiDuX9AhMbDPkGYSPugBOV6yTZB1l2K9Z0=
+github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
+github.com/klauspost/compress v1.9.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
+github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
+github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
+github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
+github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
+github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
+github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g=
github.com/labstack/echo/v4 v4.9.1 h1:GliPYSpzGKlyOhqIbG8nmHBo3i1saKWFOgh41AN3b+Y=
github.com/labstack/echo/v4 v4.9.1/go.mod h1:Pop5HLc+xoc4qhTZ1ip6C0RtP7Z+4VzRLWZZFKqbbjo=
+github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8=
github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c=
github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8=
+github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg=
+github.com/libp2p/go-libp2p v0.29.0 h1:QduJ2XQr/Crg4EnloueWDL0Jj86N3Ezhyyj7XH+XwHI=
+github.com/libp2p/go-libp2p v0.29.0/go.mod h1:iNKL7mEnZ9wAss+03IjAwM9ZAQXfVUAPUUmOACQfQ/g=
github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w=
+github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
+github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
@@ -335,20 +470,34 @@ github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
+github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
+github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
-github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
+github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
+github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o=
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
+github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
+github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
+github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
+github.com/mediocregopher/mediocre-go-lib v0.0.0-20181029021733-cb65787f37ed/go.mod h1:dSsfyI2zABAdhcbvkXqgxOxrCsbYeHCPgrZkku60dSg=
+github.com/mediocregopher/radix/v3 v3.3.0/go.mod h1:EmfVyvspXz1uZEyPBMyGK+kjWiKQGvsUt6O3Pj+LDCQ=
+github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
-github.com/miekg/dns v1.1.41 h1:WMszZWJG0XmzbK9FEmzH2TVcqYzFesusSIB41b8KHxY=
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
+github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo=
+github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
+github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ=
+github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
+github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=
+github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8=
github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
@@ -365,16 +514,57 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
+github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
+github.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
+github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
+github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
+github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE=
+github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI=
+github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0=
+github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4=
+github.com/multiformats/go-multiaddr v0.1.1/go.mod h1:aMKBKNEYmzmDmxfX88/vz+J5IU55txyt0p4aiWVohjo=
+github.com/multiformats/go-multiaddr v0.2.0/go.mod h1:0nO36NvPpyV4QzvTLi/lafl2y95ncPj0vFwVF6k6wJ4=
+github.com/multiformats/go-multiaddr v0.10.1 h1:HghtFrWyZEPrpTvgAMFJi6gFdgHfs2cb0pyfDsk+lqU=
+github.com/multiformats/go-multiaddr v0.10.1/go.mod h1:jLEZsA61rwWNZQTHHnqq2HNa+4os/Hz54eqiRnsRqYQ=
+github.com/multiformats/go-multiaddr-dns v0.3.1 h1:QgQgR+LQVt3NPTjbrLLpsaT2ufAA2y0Mkk+QRVJbW3A=
+github.com/multiformats/go-multiaddr-dns v0.3.1/go.mod h1:G/245BRQ6FJGmryJCrOuTdB37AMA5AMOVuO6NY3JwTk=
+github.com/multiformats/go-multiaddr-fmt v0.1.0 h1:WLEFClPycPkp4fnIzoFoV9FVd49/eQsuaL3/CWe167E=
+github.com/multiformats/go-multiaddr-fmt v0.1.0/go.mod h1:hGtDIW4PU4BqJ50gW2quDuPVjyWNZxToGUh/HwTZYJo=
+github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g=
+github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk=
+github.com/multiformats/go-multicodec v0.9.0 h1:pb/dlPnzee/Sxv/j4PmkDRxCOi3hXTz3IbPKOXWJkmg=
+github.com/multiformats/go-multicodec v0.9.0/go.mod h1:L3QTQvMIaVBkXOXXtVmYE+LI16i14xuaojr/H7Ai54k=
+github.com/multiformats/go-multihash v0.0.8/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew=
+github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U=
+github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM=
+github.com/multiformats/go-multistream v0.4.1 h1:rFy0Iiyn3YT0asivDUIR05leAdwZq3de4741sbiSdfo=
+github.com/multiformats/go-multistream v0.4.1/go.mod h1:Mz5eykRVAjJWckE2U78c6xqdtyNUEhKSM0Lwar2p77Q=
+github.com/multiformats/go-varint v0.0.1/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE=
+github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8=
+github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
+github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
+github.com/nats-io/nats.go v1.8.1/go.mod h1:BrFz9vVn0fU3AcH9Vn4Kd7W0NpJ651tD5omQ3M8LwxM=
+github.com/nats-io/nkeys v0.0.2/go.mod h1:dab7URMsZm6Z/jp9Z5UGa87Uutgc2mVpXLC4B7TDb/4=
+github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
+github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/olivere/elastic v6.2.14+incompatible h1:k+KadwNP/dkXE0/eu+T6otk1+5fe0tEpPyQJ4XVm5i8=
github.com/olivere/elastic v6.2.14+incompatible/go.mod h1:J+q1zQJTgAz9woqsbVRqGeB5G1iqDKVBWLNSYW8yfJ8=
+github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
+github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0=
+github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
+github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
+github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 h1:q2e307iGHPdTGp0hoxKjt1H5pDo6utceo3dQVK3I5XQ=
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o=
+github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
+github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
@@ -387,35 +577,68 @@ github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSg
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
+github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
+github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
+github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
+github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw=
+github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
+github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
+github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
+github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
+github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
+github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE=
+github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
+github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
+github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
+github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
+github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo=
+github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
+github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
+github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig=
+github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
+github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
+github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
+github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
+github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
+github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
+github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
+github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
+github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
+github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
+github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/cobra v1.3.0 h1:R7cSvGu+Vv+qX0gW5R/85dx2kmmJT5z5NM8ifdYjdn0=
github.com/spf13/cobra v1.3.0/go.mod h1:BrRVncBjOJa/eUcVVm9CE+oC6as8k+VYr4NY7WCi9V4=
+github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
+github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM=
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -431,14 +654,29 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
-github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
-github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
+github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
+github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
+github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
+github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
+github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w=
+github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4=
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
+github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
+github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
+github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
+github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
+github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
+github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI=
+github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg=
+github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM=
+github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@@ -457,21 +695,34 @@ go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
+go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
+go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
+go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
+go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
+go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
+go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
+go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI=
+go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
+go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200602180216-279210d13fed/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
-golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
+golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
+golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -482,8 +733,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
-golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
-golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
+golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 h1:MGwJjxBy0HJshjDNfLsYO8xppfqWlA5ZT9OhtUUhTNw=
+golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -511,13 +762,18 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
+golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
+golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@@ -526,6 +782,7 @@ golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -537,6 +794,7 @@ golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
@@ -551,10 +809,13 @@ golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLd
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
-golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
+golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
+golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -572,6 +833,7 @@ golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -583,13 +845,14 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
-golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -599,14 +862,20 @@ golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -620,7 +889,11 @@ golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -628,6 +901,7 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -638,6 +912,7 @@ golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -652,11 +927,16 @@ golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
-golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
+golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ=
-golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c=
+golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -666,19 +946,23 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
-golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
-golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
+golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
+golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 h1:Hir2P/De0WpUhtrKGGjvSb2YxUgyZ7EFOSLIcSSpiwE=
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190327201419-c70d86f8b7cf/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
@@ -728,6 +1012,8 @@ golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
+golang.org/x/tools v0.11.0 h1:EMCa6U9S2LtZXLAMoWiR/R8dAQFRqbAitmbJ2UKhoi8=
+golang.org/x/tools v0.11.0/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -771,6 +1057,7 @@ google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/genproto v0.0.0-20180518175338-11a468237815/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
@@ -834,6 +1121,7 @@ google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12/go.mod h1:5CzLGKJ6
google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@@ -875,6 +1163,8 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
+google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -882,15 +1172,21 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
+gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
+gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
+gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
gopkg.in/sohlich/elogrus.v3 v3.0.0-20180410122755-1fa29e2f2009 h1:q/fZgS8MMadqFFGa8WL4Oyz+TmjiZfi8UrzWhTl8d5w=
gopkg.in/sohlich/elogrus.v3 v3.0.0-20180410122755-1fa29e2f2009/go.mod h1:O0bY1e/dSoxMYZYTHP0SWKxG5EWLEvKR9/cOjWPPMKU=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
@@ -905,6 +1201,8 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
+lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI=
+lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
pgregory.net/rapid v0.6.2 h1:ErW5sL+UKtfBfUTsWHDCoeB+eZKLKMxrSd1VJY6W4bw=
pgregory.net/rapid v0.6.2/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
diff --git a/installer/config.json.example b/installer/config.json.example
index 21cfeee0e..70bcc758b 100644
--- a/installer/config.json.example
+++ b/installer/config.json.example
@@ -85,6 +85,9 @@
"OptimizeAccountsDatabaseOnStartup": false,
"OutgoingMessageFilterBucketCount": 3,
"OutgoingMessageFilterBucketSize": 128,
+ "P2PEnable": false,
+ "P2PPersistPeerID": true,
+ "P2PPrivateKeyLocation": "",
"ParticipationKeysRefreshInterval": 60000000000,
"PeerConnectionsUpdateInterval": 3600,
"PeerPingPeriodSeconds": 0,
@@ -98,7 +101,7 @@
"RestReadTimeoutSeconds": 15,
"RestWriteTimeoutSeconds": 120,
"RunHosted": false,
- "SpeculativeAsmTimeOffset": 400000000,
+ "SpeculativeAsmTimeOffset": 0,
"SpeculativeAssemblyDisable": false,
"StorageEngine": "sqlite",
"SuggestedFeeBlockHistory": 3,
diff --git a/installer/debian/algorand-devtools/conffiles b/installer/debian/algorand-devtools/conffiles
index 382fb0fdf..09b275f2f 100644
--- a/installer/debian/algorand-devtools/conffiles
+++ b/installer/debian/algorand-devtools/conffiles
@@ -1,2 +1 @@
/etc/apt/apt.conf.d/53algorand-devtools-upgrades
-
diff --git a/ledger/acctupdates.go b/ledger/acctupdates.go
index bb495f3f8..0408034ae 100644
--- a/ledger/acctupdates.go
+++ b/ledger/acctupdates.go
@@ -723,7 +723,7 @@ type accountUpdatesLedgerEvaluator struct {
au *accountUpdates
// ao is onlineAccounts for voters access
ao *onlineAccounts
- // txtail allows implementation of BlockHdrCached
+ // txtail allows BlockHdr to serve blockHdr without going to disk
tail *txTail
// prevHeader is the previous header to the current one. The usage of this is only in the context of initializeCaches where we iteratively
// building the ledgercore.StateDelta, which requires a peek on the "previous" header information.
@@ -758,17 +758,11 @@ func (aul *accountUpdatesLedgerEvaluator) BlockHdr(r basics.Round) (bookkeeping.
if r == aul.prevHeader.Round {
return aul.prevHeader, nil
}
- return bookkeeping.BlockHeader{}, ledgercore.ErrNoEntry{}
-}
-
-// BlockHdrCached returns the header of the given round. We use the txTail
-// tracker directly to avoid the tracker registry lock.
-func (aul *accountUpdatesLedgerEvaluator) BlockHdrCached(r basics.Round) (bookkeeping.BlockHeader, error) {
hdr, ok := aul.tail.blockHeader(r)
- if !ok {
- return bookkeeping.BlockHeader{}, fmt.Errorf("no cached header data for round %d", r)
+ if ok {
+ return hdr, nil
}
- return hdr, nil
+ return bookkeeping.BlockHeader{}, ledgercore.ErrNoEntry{}
}
// LatestTotals returns the totals of all accounts for the most recent round, as well as the round number
diff --git a/ledger/acctupdates_test.go b/ledger/acctupdates_test.go
index d938b0d53..658cb7133 100644
--- a/ledger/acctupdates_test.go
+++ b/ledger/acctupdates_test.go
@@ -2711,3 +2711,16 @@ func TestAcctUpdatesLookupStateDelta(t *testing.T) {
require.Contains(t, data.Assets, aidx3)
require.NotContains(t, data.Assets, aidx2)
}
+
+func TestAccountUpdatesLedgerEvaluatorNoBlockHdr(t *testing.T) {
+ partitiontest.PartitionTest(t)
+
+ aul := &accountUpdatesLedgerEvaluator{
+ prevHeader: bookkeeping.BlockHeader{},
+ tail: &txTail{},
+ }
+ hdr, err := aul.BlockHdr(99)
+ require.Error(t, err)
+ require.Equal(t, ledgercore.ErrNoEntry{}, err)
+ require.Equal(t, bookkeeping.BlockHeader{}, hdr)
+}
diff --git a/ledger/apply/application_test.go b/ledger/apply/application_test.go
index 0e6a1ff9a..5d829a386 100644
--- a/ledger/apply/application_test.go
+++ b/ledger/apply/application_test.go
@@ -380,8 +380,7 @@ func TestAppCallAddressByIndex(t *testing.T) {
a.Equal(sender, addr)
addr, err = ac.AddressByIndex(1, sender)
- a.Error(err)
- a.Contains(err.Error(), "invalid Account reference 1")
+ a.ErrorContains(err, "invalid Account reference 1")
a.Zero(len(ac.Accounts))
acc0 := getRandomAddress(a)
@@ -391,8 +390,7 @@ func TestAppCallAddressByIndex(t *testing.T) {
a.Equal(acc0, addr)
addr, err = ac.AddressByIndex(2, sender)
- a.Error(err)
- a.Contains(err.Error(), "invalid Account reference 2")
+ a.ErrorContains(err, "invalid Account reference 2")
}
func TestAppCallCheckPrograms(t *testing.T) {
@@ -401,37 +399,33 @@ func TestAppCallCheckPrograms(t *testing.T) {
a := require.New(t)
var ac transactions.ApplicationCallTxnFields
- var ep logic.EvalParams
// This check is for static costs. v26 is last with static cost checking
proto := config.Consensus[protocol.ConsensusV26]
- ep.Proto = &proto
+ ep := logic.NewAppEvalParams(nil, &proto, nil)
proto.MaxAppProgramCost = 1
- err := checkPrograms(&ac, &ep)
- a.Error(err)
- a.Contains(err.Error(), "check failed on ApprovalProgram")
+ err := checkPrograms(&ac, ep)
+ a.ErrorContains(err, "check failed on ApprovalProgram")
program := []byte{2, 0x20, 1, 1, 0x22} // version, intcb, int 1
ac.ApprovalProgram = program
ac.ClearStateProgram = program
- err = checkPrograms(&ac, &ep)
- a.Error(err)
- a.Contains(err.Error(), "check failed on ApprovalProgram")
+ err = checkPrograms(&ac, ep)
+ a.ErrorContains(err, "check failed on ApprovalProgram")
proto.MaxAppProgramCost = 10
- err = checkPrograms(&ac, &ep)
+ err = checkPrograms(&ac, ep)
a.NoError(err)
ac.ClearStateProgram = append(ac.ClearStateProgram, program...)
ac.ClearStateProgram = append(ac.ClearStateProgram, program...)
ac.ClearStateProgram = append(ac.ClearStateProgram, program...)
- err = checkPrograms(&ac, &ep)
- a.Error(err)
- a.Contains(err.Error(), "check failed on ClearStateProgram")
+ err = checkPrograms(&ac, ep)
+ a.ErrorContains(err, "check failed on ClearStateProgram")
ac.ClearStateProgram = program
- err = checkPrograms(&ac, &ep)
+ err = checkPrograms(&ac, ep)
a.NoError(err)
}
@@ -488,13 +482,15 @@ func TestAppCallApplyCreate(t *testing.T) {
h := transactions.Header{
Sender: sender,
}
- var ep logic.EvalParams
- var txnCounter uint64 = 1
b := newTestBalances()
+ b.SetProto(protocol.ConsensusFuture)
+ proto := b.ConsensusParams()
+ ep := logic.NewAppEvalParams(nil, &proto, nil)
- err := ApplicationCall(ac, h, b, nil, 0, &ep, txnCounter)
- a.Error(err)
- a.Contains(err.Error(), "ApplicationCall cannot have nil ApplyData")
+ var txnCounter uint64 = 1
+
+ err := ApplicationCall(ac, h, b, nil, 0, ep, txnCounter)
+ a.ErrorContains(err, "ApplicationCall cannot have nil ApplyData")
a.Zero(b.put)
a.Zero(b.putAppParams)
@@ -502,16 +498,11 @@ func TestAppCallApplyCreate(t *testing.T) {
b.balances[creator] = basics.AccountData{}
var ad *transactions.ApplyData = &transactions.ApplyData{}
- b.SetProto(protocol.ConsensusFuture)
- proto := b.ConsensusParams()
- ep.Proto = &proto
-
// this test will succeed in creating the app, but then fail
// because the mock balances doesn't update the creators table
// so it will think the app doesn't exist
- err = ApplicationCall(ac, h, b, ad, 0, &ep, txnCounter)
- a.Error(err)
- a.Contains(err.Error(), "only ClearState is supported")
+ err = ApplicationCall(ac, h, b, ad, 0, ep, txnCounter)
+ a.ErrorContains(err, "only ClearState is supported")
a.Equal(1, b.put)
a.Equal(1, b.putAppParams)
@@ -528,9 +519,8 @@ func TestAppCallApplyCreate(t *testing.T) {
cp.AppParams = maps.Clone(saved.AppParams)
cp.AppLocalStates = maps.Clone(saved.AppLocalStates)
b.balances[creator] = cp
- err = ApplicationCall(ac, h, b, ad, 0, &ep, txnCounter)
- a.Error(err)
- a.Contains(err.Error(), "already found app with index")
+ err = ApplicationCall(ac, h, b, ad, 0, ep, txnCounter)
+ a.ErrorContains(err, "already found app with index")
a.Equal(uint64(0), uint64(b.allocatedAppIdx))
a.Zero(b.put)
a.Zero(b.putAppParams)
@@ -547,7 +537,7 @@ func TestAppCallApplyCreate(t *testing.T) {
b.balances[creator] = cp
ac.GlobalStateSchema = basics.StateSchema{NumUint: 1}
- err = ApplicationCall(ac, h, b, ad, 0, &ep, txnCounter)
+ err = ApplicationCall(ac, h, b, ad, 0, ep, txnCounter)
a.NoError(err)
a.Equal(appIdx, b.allocatedAppIdx)
a.Equal(1, b.put)
@@ -566,7 +556,7 @@ func TestAppCallApplyCreate(t *testing.T) {
txnCounter++
appIdx = basics.AppIndex(txnCounter + 1)
b.appCreators[appIdx] = creator
- err = ApplicationCall(ac, h, b, ad, 0, &ep, txnCounter)
+ err = ApplicationCall(ac, h, b, ad, 0, ep, txnCounter)
a.NoError(err)
br = b.putBalances[creator]
a.Equal(uint32(1), br.AppParams[appIdx].ExtraProgramPages)
@@ -592,23 +582,22 @@ func TestAppCallApplyCreateOptIn(t *testing.T) {
h := transactions.Header{
Sender: sender,
}
- var ep logic.EvalParams
+ b := newTestBalancesPass()
+ b.SetProto(protocol.ConsensusFuture)
+ proto := b.ConsensusParams()
+ ep := logic.NewAppEvalParams(nil, &proto, nil)
var txnCounter uint64 = 1
appIdx := basics.AppIndex(txnCounter + 1)
var ad *transactions.ApplyData = &transactions.ApplyData{}
- b := newTestBalancesPass()
b.balances = make(map[basics.Address]basics.AccountData)
b.balances[creator] = basics.AccountData{}
- b.SetProto(protocol.ConsensusFuture)
- proto := b.ConsensusParams()
- ep.Proto = &proto
b.appCreators = map[basics.AppIndex]basics.Address{appIdx: creator}
gd := map[string]basics.ValueDelta{"uint": {Action: basics.SetUintAction, Uint: 1}}
b.delta = transactions.EvalDelta{GlobalDelta: gd}
- err := ApplicationCall(ac, h, b, ad, 0, &ep, txnCounter)
+ err := ApplicationCall(ac, h, b, ad, 0, ep, txnCounter)
a.NoError(err)
a.Equal(appIdx, b.allocatedAppIdx)
br := b.balances[creator]
@@ -650,8 +639,7 @@ func TestAppCallOptIn(t *testing.T) {
ad.AppLocalStates[appIdx] = basics.AppLocalState{}
b.balances = map[basics.Address]basics.AccountData{sender: ad}
err = optInApplication(b, sender, appIdx, params)
- a.Error(err)
- a.Contains(err.Error(), "has already opted in to app")
+ a.ErrorContains(err, "has already opted in to app")
a.Zero(b.put)
a.Zero(b.putAppLocalState)
@@ -749,14 +737,13 @@ func TestAppCallClearState(t *testing.T) {
var txnCounter uint64 = 1
appIdx := basics.AppIndex(txnCounter + 1)
b := newTestBalances()
- var ep logic.EvalParams
+ b.SetProto(protocol.ConsensusFuture)
+ proto := b.ConsensusParams()
+ ep := logic.NewAppEvalParams(nil, &proto, nil)
ad := &transactions.ApplyData{}
b.appCreators = make(map[basics.AppIndex]basics.Address)
b.balances = make(map[basics.Address]basics.AccountData, 2)
- b.SetProto(protocol.ConsensusFuture)
- proto := b.ConsensusParams()
- ep.Proto = &proto
ac := transactions.ApplicationCallTxnFields{
ApplicationID: appIdx,
@@ -782,9 +769,8 @@ func TestAppCallClearState(t *testing.T) {
b.pass = true
// check app not exist and not opted in
b.balances[sender] = basics.AccountData{}
- err := ApplicationCall(ac, h, b, ad, 0, &ep, txnCounter)
- a.Error(err)
- a.Contains(err.Error(), "is not currently opted in to app")
+ err := ApplicationCall(ac, h, b, ad, 0, ep, txnCounter)
+ a.ErrorContains(err, "is not currently opted in to app")
a.Zero(b.put)
a.Zero(b.putAppLocalState)
a.Zero(b.deleteAppLocalState)
@@ -793,7 +779,7 @@ func TestAppCallClearState(t *testing.T) {
b.balances[sender] = basics.AccountData{
AppLocalStates: map[basics.AppIndex]basics.AppLocalState{appIdx: {}},
}
- err = ApplicationCall(ac, h, b, ad, 0, &ep, txnCounter)
+ err = ApplicationCall(ac, h, b, ad, 0, ep, txnCounter)
a.NoError(err)
a.Equal(1, b.put)
a.Zero(b.putAppLocalState)
@@ -813,7 +799,7 @@ func TestAppCallClearState(t *testing.T) {
appIdx: {Schema: basics.StateSchema{NumUint: 10}},
},
}
- err = ApplicationCall(ac, h, b, ad, 0, &ep, txnCounter)
+ err = ApplicationCall(ac, h, b, ad, 0, ep, txnCounter)
a.NoError(err)
a.Equal(1, b.put)
a.Zero(b.putAppLocalState)
@@ -841,7 +827,7 @@ func TestAppCallClearState(t *testing.T) {
// one put: to opt out
b.pass = false
b.delta = transactions.EvalDelta{GlobalDelta: nil}
- err = ApplicationCall(ac, h, b, ad, 0, &ep, txnCounter)
+ err = ApplicationCall(ac, h, b, ad, 0, ep, txnCounter)
a.NoError(err)
a.Equal(1, b.put)
a.Zero(b.putAppLocalState)
@@ -858,7 +844,7 @@ func TestAppCallClearState(t *testing.T) {
b.pass = true
b.delta = transactions.EvalDelta{GlobalDelta: nil}
b.err = logic.EvalError{Err: fmt.Errorf("test error")}
- err = ApplicationCall(ac, h, b, ad, 0, &ep, txnCounter)
+ err = ApplicationCall(ac, h, b, ad, 0, ep, txnCounter)
a.NoError(err)
a.Equal(1, b.put)
a.Zero(b.putAppLocalState)
@@ -875,7 +861,7 @@ func TestAppCallClearState(t *testing.T) {
b.pass = true
b.delta = transactions.EvalDelta{GlobalDelta: nil}
b.err = fmt.Errorf("test error")
- err = ApplicationCall(ac, h, b, ad, 0, &ep, txnCounter)
+ err = ApplicationCall(ac, h, b, ad, 0, ep, txnCounter)
a.Error(err)
br = b.putBalances[sender]
a.Zero(len(br.AppLocalStates))
@@ -890,7 +876,7 @@ func TestAppCallClearState(t *testing.T) {
b.err = nil
gd := basics.StateDelta{"uint": {Action: basics.SetUintAction, Uint: 1}}
b.delta = transactions.EvalDelta{GlobalDelta: gd}
- err = ApplicationCall(ac, h, b, ad, 0, &ep, txnCounter)
+ err = ApplicationCall(ac, h, b, ad, 0, ep, txnCounter)
a.NoError(err)
a.Equal(1, b.put)
a.Zero(b.putAppLocalState)
@@ -905,7 +891,7 @@ func TestAppCallClearState(t *testing.T) {
b.err = nil
logs := []string{"a"}
b.delta = transactions.EvalDelta{Logs: []string{"a"}}
- err = ApplicationCall(ac, h, b, ad, 0, &ep, txnCounter)
+ err = ApplicationCall(ac, h, b, ad, 0, ep, txnCounter)
a.NoError(err)
a.Equal(transactions.EvalDelta{Logs: logs}, ad.EvalDelta)
}
@@ -953,8 +939,7 @@ func TestAppCallApplyCloseOut(t *testing.T) {
b.pass = false
err := ApplicationCall(ac, h, b, ad, 0, &ep, txnCounter)
- a.Error(err)
- a.Contains(err.Error(), "transaction rejected by ApprovalProgram")
+ a.ErrorContains(err, "transaction rejected by ApprovalProgram")
a.Zero(b.put)
a.Zero(b.putAppLocalState)
a.Zero(b.deleteAppLocalState)
@@ -966,8 +951,7 @@ func TestAppCallApplyCloseOut(t *testing.T) {
b.pass = true
b.balances[sender] = basics.AccountData{}
err = ApplicationCall(ac, h, b, ad, 0, &ep, txnCounter)
- a.Error(err)
- a.Contains(err.Error(), "is not opted in to any app")
+ a.ErrorContains(err, "is not opted in to any app")
a.Zero(b.put)
a.Zero(b.putAppLocalState)
a.Zero(b.deleteAppLocalState)
@@ -1032,9 +1016,11 @@ func TestAppCallApplyUpdate(t *testing.T) {
h := transactions.Header{
Sender: sender,
}
- var ep logic.EvalParams
var ad *transactions.ApplyData = &transactions.ApplyData{}
b := newTestBalances()
+ b.SetProto(protocol.ConsensusV28)
+ proto := b.ConsensusParams()
+ ep := logic.NewAppEvalParams(nil, &proto, nil)
b.balances = make(map[basics.Address]basics.AccountData)
cbr := basics.AccountData{
@@ -1046,14 +1032,9 @@ func TestAppCallApplyUpdate(t *testing.T) {
b.balances[creator] = cp
b.appCreators = map[basics.AppIndex]basics.Address{appIdx: creator}
- b.SetProto(protocol.ConsensusV28)
- proto := b.ConsensusParams()
- ep.Proto = &proto
-
b.pass = false
- err := ApplicationCall(ac, h, b, ad, 0, &ep, txnCounter)
- a.Error(err)
- a.Contains(err.Error(), "transaction rejected by ApprovalProgram")
+ err := ApplicationCall(ac, h, b, ad, 0, ep, txnCounter)
+ a.ErrorContains(err, "transaction rejected by ApprovalProgram")
a.Zero(b.put)
a.Zero(b.putAppParams)
br := b.balances[creator]
@@ -1063,7 +1044,7 @@ func TestAppCallApplyUpdate(t *testing.T) {
// check updating on empty sender's balance record - happy case
b.pass = true
b.balances[sender] = basics.AccountData{}
- err = ApplicationCall(ac, h, b, ad, 0, &ep, txnCounter)
+ err = ApplicationCall(ac, h, b, ad, 0, ep, txnCounter)
a.NoError(err)
a.Zero(b.put)
a.Equal(1, b.putAppParams)
@@ -1103,7 +1084,7 @@ func TestAppCallApplyUpdate(t *testing.T) {
logs := []string{"a"}
b.delta = transactions.EvalDelta{Logs: []string{"a"}}
- err = ApplicationCall(ac, h, b, ad, 0, &ep, txnCounter)
+ err = ApplicationCall(ac, h, b, ad, 0, ep, txnCounter)
a.NoError(err)
a.Equal(transactions.EvalDelta{Logs: logs}, ad.EvalDelta)
@@ -1128,7 +1109,7 @@ func TestAppCallApplyUpdate(t *testing.T) {
}
b.pass = true
- err = ApplicationCall(ac, h, b, ad, 0, &ep, txnCounter)
+ err = ApplicationCall(ac, h, b, ad, 0, ep, txnCounter)
a.Error(err)
a.Contains(err.Error(), fmt.Sprintf("updateApplication %s program too long", test.name))
}
@@ -1144,7 +1125,7 @@ func TestAppCallApplyUpdate(t *testing.T) {
ClearStateProgram: []byte{2},
}
b.pass = true
- err = ApplicationCall(ac, h, b, ad, 0, &ep, txnCounter)
+ err = ApplicationCall(ac, h, b, ad, 0, ep, txnCounter)
a.NoError(err)
// check extraProgramPages is used and long sum rejected
@@ -1155,9 +1136,8 @@ func TestAppCallApplyUpdate(t *testing.T) {
ClearStateProgram: appr,
}
b.pass = true
- err = ApplicationCall(ac, h, b, ad, 0, &ep, txnCounter)
- a.Error(err)
- a.Contains(err.Error(), "updateApplication app programs too long")
+ err = ApplicationCall(ac, h, b, ad, 0, ep, txnCounter)
+ a.ErrorContains(err, "updateApplication app programs too long")
}
@@ -1209,8 +1189,7 @@ func TestAppCallApplyDelete(t *testing.T) {
b.pass = false
err := ApplicationCall(ac, h, b, ad, 0, &ep, txnCounter)
- a.Error(err)
- a.Contains(err.Error(), "transaction rejected by ApprovalProgram")
+ a.ErrorContains(err, "transaction rejected by ApprovalProgram")
a.Zero(b.put)
a.Zero(b.putAppParams)
a.Zero(b.deleteAppParams)
@@ -1300,17 +1279,17 @@ func TestAppCallApplyCreateClearState(t *testing.T) {
h := transactions.Header{
Sender: sender,
}
- var ep logic.EvalParams
var txnCounter uint64 = 1
appIdx := basics.AppIndex(txnCounter + 1)
var ad *transactions.ApplyData = &transactions.ApplyData{}
b := newTestBalancesPass()
+ b.SetProto(protocol.ConsensusFuture)
+ proto := b.ConsensusParams()
+ ep := logic.NewAppEvalParams(nil, &proto, nil)
b.balances = make(map[basics.Address]basics.AccountData)
b.balances[creator] = basics.AccountData{}
b.SetProto(protocol.ConsensusFuture)
- proto := b.ConsensusParams()
- ep.Proto = &proto
b.appCreators = map[basics.AppIndex]basics.Address{appIdx: creator}
@@ -1319,9 +1298,8 @@ func TestAppCallApplyCreateClearState(t *testing.T) {
b.delta = transactions.EvalDelta{GlobalDelta: gd}
// check creation on empty balance record
- err := ApplicationCall(ac, h, b, ad, 0, &ep, txnCounter)
- a.Error(err)
- a.Contains(err.Error(), "not currently opted in")
+ err := ApplicationCall(ac, h, b, ad, 0, ep, txnCounter)
+ a.ErrorContains(err, "not currently opted in")
a.Equal(appIdx, b.allocatedAppIdx)
a.Equal(transactions.EvalDelta{}, ad.EvalDelta)
br := b.balances[creator]
@@ -1350,17 +1328,17 @@ func TestAppCallApplyCreateDelete(t *testing.T) {
h := transactions.Header{
Sender: sender,
}
- var ep logic.EvalParams
var txnCounter uint64 = 1
appIdx := basics.AppIndex(txnCounter + 1)
var ad *transactions.ApplyData = &transactions.ApplyData{}
b := newTestBalancesPass()
+ b.SetProto(protocol.ConsensusFuture)
+ proto := b.ConsensusParams()
+ ep := logic.NewAppEvalParams(nil, &proto, nil)
b.balances = make(map[basics.Address]basics.AccountData)
b.balances[creator] = basics.AccountData{}
b.SetProto(protocol.ConsensusFuture)
- proto := b.ConsensusParams()
- ep.Proto = &proto
b.appCreators = map[basics.AppIndex]basics.Address{appIdx: creator}
@@ -1369,7 +1347,7 @@ func TestAppCallApplyCreateDelete(t *testing.T) {
b.delta = transactions.EvalDelta{GlobalDelta: gd}
// check creation on empty balance record
- err := ApplicationCall(ac, h, b, ad, 0, &ep, txnCounter)
+ err := ApplicationCall(ac, h, b, ad, 0, ep, txnCounter)
a.NoError(err)
a.Equal(appIdx, b.allocatedAppIdx)
a.Equal(transactions.EvalDelta{GlobalDelta: gd}, ad.EvalDelta)
@@ -1378,7 +1356,7 @@ func TestAppCallApplyCreateDelete(t *testing.T) {
logs := []string{"a"}
b.delta = transactions.EvalDelta{Logs: []string{"a"}}
- err = ApplicationCall(ac, h, b, ad, 0, &ep, txnCounter)
+ err = ApplicationCall(ac, h, b, ad, 0, ep, txnCounter)
a.NoError(err)
a.Equal(transactions.EvalDelta{Logs: logs}, ad.EvalDelta)
diff --git a/ledger/blockHeaderCache.go b/ledger/blockHeaderCache.go
deleted file mode 100644
index b0f27f78e..000000000
--- a/ledger/blockHeaderCache.go
+++ /dev/null
@@ -1,86 +0,0 @@
-// Copyright (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 <https://www.gnu.org/licenses/>.
-
-package ledger
-
-import (
- "github.com/algorand/go-algorand/data/basics"
- "github.com/algorand/go-algorand/data/bookkeeping"
-
- "github.com/algorand/go-deadlock"
-)
-
-const latestHeaderCacheSize = 512
-const blockHeadersLRUCacheSize = 10
-
-// blockHeaderCache is a wrapper for all block header cache mechanisms used within the Ledger.
-type blockHeaderCache struct {
- lruCache heapLRUCache
- latestHeaderCache latestBlockHeaderCache
-}
-
-type latestBlockHeaderCache struct {
- blockHeaders [latestHeaderCacheSize]bookkeeping.BlockHeader
- mutex deadlock.RWMutex
-}
-
-func (c *blockHeaderCache) initialize() {
- c.lruCache.maxEntries = blockHeadersLRUCacheSize
-}
-
-func (c *blockHeaderCache) get(round basics.Round) (blockHeader bookkeeping.BlockHeader, exists bool) {
- // check latestHeaderCache first
- blockHeader, exists = c.latestHeaderCache.get(round)
- if exists {
- return
- }
-
- // if not found in latestHeaderCache, check LRUCache
- value, exists := c.lruCache.Get(round)
- if exists {
- blockHeader = value.(bookkeeping.BlockHeader)
- }
-
- return
-}
-
-func (c *blockHeaderCache) put(blockHeader bookkeeping.BlockHeader) {
- c.latestHeaderCache.put(blockHeader)
- c.lruCache.Put(blockHeader.Round, blockHeader)
-}
-
-func (c *latestBlockHeaderCache) get(round basics.Round) (blockHeader bookkeeping.BlockHeader, exists bool) {
- c.mutex.RLock()
- defer c.mutex.RUnlock()
-
- idx := round % latestHeaderCacheSize
- if round == 0 || c.blockHeaders[idx].Round != round { // blockHeader is empty or not requested round
- return bookkeeping.BlockHeader{}, false
- }
- blockHeader = c.blockHeaders[idx]
-
- return blockHeader, true
-}
-
-func (c *latestBlockHeaderCache) put(blockHeader bookkeeping.BlockHeader) {
- c.mutex.Lock()
- defer c.mutex.Unlock()
-
- idx := blockHeader.Round % latestHeaderCacheSize
- if blockHeader.Round > c.blockHeaders[idx].Round { // provided blockHeader is more recent than cached one
- c.blockHeaders[idx] = blockHeader
- }
-}
diff --git a/ledger/blockHeaderCache_test.go b/ledger/blockHeaderCache_test.go
deleted file mode 100644
index 6728e71c6..000000000
--- a/ledger/blockHeaderCache_test.go
+++ /dev/null
@@ -1,94 +0,0 @@
-// Copyright (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 <https://www.gnu.org/licenses/>.
-
-package ledger
-
-import (
- "testing"
-
- "github.com/stretchr/testify/require"
-
- "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/protocol"
- "github.com/algorand/go-algorand/test/partitiontest"
-)
-
-func TestBlockHeaderCache(t *testing.T) {
- partitiontest.PartitionTest(t)
- a := require.New(t)
-
- var cache blockHeaderCache
- cache.initialize()
- for i := basics.Round(1024); i < 1024+latestHeaderCacheSize; i++ {
- hdr := bookkeeping.BlockHeader{Round: i}
- cache.put(hdr)
- }
-
- rnd := basics.Round(120)
- hdr := bookkeeping.BlockHeader{Round: rnd}
- cache.put(hdr)
-
- _, exists := cache.get(rnd)
- a.True(exists)
-
- _, exists = cache.lruCache.Get(rnd)
- a.True(exists)
-
- _, exists = cache.latestHeaderCache.get(rnd)
- a.False(exists)
-
- rnd = basics.Round(2048)
- hdr = bookkeeping.BlockHeader{Round: rnd}
- cache.put(hdr)
-
- _, exists = cache.latestHeaderCache.get(rnd)
- a.True(exists)
-
- _, exists = cache.lruCache.Get(rnd)
- a.True(exists)
-
-}
-
-func TestLatestBlockHeaderCache(t *testing.T) {
- partitiontest.PartitionTest(t)
- a := require.New(t)
-
- var cache latestBlockHeaderCache
- for i := basics.Round(123); i < latestHeaderCacheSize; i++ {
- hdr := bookkeeping.BlockHeader{Round: i}
- cache.put(hdr)
- }
-
- for i := basics.Round(0); i < 123; i++ {
- _, exists := cache.get(i)
- a.False(exists)
- }
-
- for i := basics.Round(123); i < latestHeaderCacheSize; i++ {
- hdr, exists := cache.get(i)
- a.True(exists)
- a.Equal(i, hdr.Round)
- }
-}
-
-func TestCacheSizeConsensus(t *testing.T) {
- partitiontest.PartitionTest(t)
- a := require.New(t)
-
- a.Equal(uint64(latestHeaderCacheSize), config.Consensus[protocol.ConsensusCurrentVersion].StateProofInterval*2)
-}
diff --git a/ledger/catchpointwriter.go b/ledger/catchpointfilewriter.go
index 4c22e035b..140223c3f 100644
--- a/ledger/catchpointwriter.go
+++ b/ledger/catchpointfilewriter.go
@@ -24,7 +24,6 @@ import (
"os"
"path/filepath"
- "github.com/algorand/go-algorand/crypto"
"github.com/algorand/go-algorand/ledger/encoded"
"github.com/algorand/go-algorand/ledger/ledgercore"
"github.com/algorand/go-algorand/ledger/store/trackerdb"
@@ -47,13 +46,13 @@ const (
SPContextPerCatchpointFile = 70000
)
-// catchpointWriter is the struct managing the persistence of accounts data into the catchpoint file.
-// it's designed to work in a step fashion : a caller will call the WriteStep method in a loop until
+// catchpointFileWriter is the struct managing the persistence of accounts data into the catchpoint file.
+// it's designed to work in a step fashion : a caller will call the FileWriteStep method in a loop until
// the writing is complete. It might take multiple steps until the operation is over, and the caller
// has the option of throttling the CPU utilization in between the calls.
-type catchpointWriter struct {
+type catchpointFileWriter struct {
ctx context.Context
- tx trackerdb.TransactionScope
+ tx trackerdb.SnapshotScope
filePath string
totalAccounts uint64
totalKVs uint64
@@ -107,7 +106,7 @@ func (data catchpointStateProofVerificationContext) ToBeHashed() (protocol.HashI
return protocol.StateProofVerCtx, protocol.Encode(&data)
}
-func makeCatchpointWriter(ctx context.Context, filePath string, tx trackerdb.TransactionScope, maxResourcesPerChunk int) (*catchpointWriter, error) {
+func makeCatchpointFileWriter(ctx context.Context, filePath string, tx trackerdb.SnapshotScope, maxResourcesPerChunk int) (*catchpointFileWriter, error) {
aw, err := tx.MakeAccountsReader()
if err != nil {
return nil, err
@@ -137,7 +136,7 @@ func makeCatchpointWriter(ctx context.Context, filePath string, tx trackerdb.Tra
}
tar := tar.NewWriter(compressor)
- res := &catchpointWriter{
+ res := &catchpointFileWriter{
ctx: ctx,
tx: tx,
filePath: filePath,
@@ -152,7 +151,7 @@ func makeCatchpointWriter(ctx context.Context, filePath string, tx trackerdb.Tra
return res, nil
}
-func (cw *catchpointWriter) Abort() error {
+func (cw *catchpointFileWriter) Abort() error {
cw.accountsIterator.Close()
cw.tar.Close()
cw.compressor.Close()
@@ -160,47 +159,39 @@ func (cw *catchpointWriter) Abort() error {
return os.Remove(cw.filePath)
}
-func (cw *catchpointWriter) WriteStateProofVerificationContext() (crypto.Digest, error) {
- rawData, err := cw.tx.MakeSpVerificationCtxReader().GetAllSPContexts(cw.ctx)
- if err != nil {
- return crypto.Digest{}, err
- }
-
- wrappedData := catchpointStateProofVerificationContext{Data: rawData}
- dataHash, encodedData := crypto.EncodeAndHash(wrappedData)
-
- err = cw.tar.WriteHeader(&tar.Header{
+func (cw *catchpointFileWriter) FileWriteSPVerificationContext(encodedData []byte) error {
+ err := cw.tar.WriteHeader(&tar.Header{
Name: catchpointSPVerificationFileName,
Mode: 0600,
Size: int64(len(encodedData)),
})
if err != nil {
- return crypto.Digest{}, err
+ return err
}
_, err = cw.tar.Write(encodedData)
if err != nil {
- return crypto.Digest{}, err
+ return err
}
if chunkLen := uint64(len(encodedData)); cw.biggestChunkLen < chunkLen {
cw.biggestChunkLen = chunkLen
}
- return dataHash, nil
+ return nil
}
-// WriteStep works for a short period of time (determined by stepCtx) to get
+// FileWriteStep works for a short period of time (determined by stepCtx) to get
// some more data (accounts/resources/kvpairs) by using readDatabaseStep, and
// write that data to the open tar file in cw.tar. The writing is done in
// asyncWriter, so that it can proceed concurrently with reading the data from
// the db. asyncWriter only runs long enough to process the data read during a
-// single call to WriteStep, and WriteStep ensures that asyncWriter has finished
+// single call to FileWriteStep, and FileWriteStep ensures that asyncWriter has finished
// writing by waiting for it in a defer block, collecting any errors that may
-// have occurred during writing. Therefore, WriteStep looks like a simple
+// have occurred during writing. Therefore, FileWriteStep looks like a simple
// synchronous function to its callers.
-func (cw *catchpointWriter) WriteStep(stepCtx context.Context) (more bool, err error) {
+func (cw *catchpointFileWriter) FileWriteStep(stepCtx context.Context) (more bool, err error) {
// have we timed-out / canceled by that point ?
if more, err = hasContextDeadlineExceeded(stepCtx); more || err != nil {
return
@@ -215,15 +206,14 @@ func (cw *catchpointWriter) WriteStep(stepCtx context.Context) (more bool, err e
// writerResponse is drained, ensuring any problems from asyncWriter are
// noted (and that the writing is done).
close(writerRequest)
- drain:
+
+ // drain the writerResponse queue
for {
- select {
- case writerError, open := <-writerResponse:
- if open {
- err = writerError
- } else {
- break drain
- }
+ writerError, open := <-writerResponse
+ if open {
+ err = writerError
+ } else {
+ break
}
}
if !more {
@@ -237,7 +227,7 @@ func (cw *catchpointWriter) WriteStep(stepCtx context.Context) (more bool, err e
}
cw.writtenBytes = fileInfo.Size()
- // These don't HAVE to be closed, since the "owning" tx will be cmmmitted/rolledback
+ // These don't HAVE to be closed, since the "owning" tx will be committed/rolledback
cw.accountsIterator.Close()
if cw.kvRows != nil {
cw.kvRows.Close()
@@ -285,7 +275,7 @@ func (cw *catchpointWriter) WriteStep(stepCtx context.Context) (more bool, err e
}
}
-func (cw *catchpointWriter) asyncWriter(chunks chan catchpointFileChunkV6, response chan error, chunkNum uint64) {
+func (cw *catchpointFileWriter) asyncWriter(chunks chan catchpointFileChunkV6, response chan error, chunkNum uint64) {
defer close(response)
for chk := range chunks {
chunkNum++
@@ -317,7 +307,7 @@ func (cw *catchpointWriter) asyncWriter(chunks chan catchpointFileChunkV6, respo
// all of the account chunks first, and then the kv chunks. Even if the accounts
// are evenly divisible by BalancesPerCatchpointFileChunk, it must not return an
// empty chunk between accounts and kvs.
-func (cw *catchpointWriter) readDatabaseStep(ctx context.Context) error {
+func (cw *catchpointFileWriter) readDatabaseStep(ctx context.Context) error {
if !cw.accountsDone {
balances, numAccounts, err := cw.accountsIterator.Next(ctx, BalancesPerCatchpointFileChunk, cw.maxResourcesPerChunk)
if err != nil {
diff --git a/ledger/catchpointwriter_test.go b/ledger/catchpointfilewriter_test.go
index 1f3c41e7c..da5bc3c55 100644
--- a/ledger/catchpointwriter_test.go
+++ b/ledger/catchpointfilewriter_test.go
@@ -147,16 +147,22 @@ func verifyStateProofVerificationContextWrite(t *testing.T, data []ledgercore.St
require.NoError(t, err)
err = ml.trackerDB().Transaction(func(ctx context.Context, tx trackerdb.TransactionScope) (err error) {
- writer, err := makeCatchpointWriter(context.Background(), fileName, tx, ResourcesPerCatchpointFileChunk)
+ writer, err := makeCatchpointFileWriter(context.Background(), fileName, tx, ResourcesPerCatchpointFileChunk)
if err != nil {
return err
}
- _, err = writer.WriteStateProofVerificationContext()
+ rawData, err := tx.MakeSpVerificationCtxReader().GetAllSPContexts(ctx)
+ if err != nil {
+ return err
+ }
+ _, encodedData := crypto.EncodeAndHash(catchpointStateProofVerificationContext{Data: rawData})
+
+ err = writer.FileWriteSPVerificationContext(encodedData)
if err != nil {
return err
}
for {
- more, err := writer.WriteStep(context.Background())
+ more, err := writer.FileWriteStep(context.Background())
require.NoError(t, err)
if !more {
break
@@ -256,16 +262,21 @@ func TestBasicCatchpointWriter(t *testing.T) {
fileName := filepath.Join(temporaryDirectory, "15.data")
err = ml.trackerDB().Transaction(func(ctx context.Context, tx trackerdb.TransactionScope) (err error) {
- writer, err := makeCatchpointWriter(context.Background(), fileName, tx, ResourcesPerCatchpointFileChunk)
+ writer, err := makeCatchpointFileWriter(context.Background(), fileName, tx, ResourcesPerCatchpointFileChunk)
+ if err != nil {
+ return err
+ }
+ rawData, err := tx.MakeSpVerificationCtxReader().GetAllSPContexts(ctx)
if err != nil {
return err
}
- _, err = writer.WriteStateProofVerificationContext()
+ _, encodedData := crypto.EncodeAndHash(catchpointStateProofVerificationContext{Data: rawData})
+ err = writer.FileWriteSPVerificationContext(encodedData)
if err != nil {
return err
}
for {
- more, err := writer.WriteStep(context.Background())
+ more, err := writer.FileWriteStep(context.Background())
require.NoError(t, err)
if !more {
break
@@ -295,7 +306,7 @@ func testWriteCatchpoint(t *testing.T, rdb trackerdb.Store, datapath string, fil
}
err := rdb.Transaction(func(ctx context.Context, tx trackerdb.TransactionScope) (err error) {
- writer, err := makeCatchpointWriter(context.Background(), datapath, tx, maxResourcesPerChunk)
+ writer, err := makeCatchpointFileWriter(context.Background(), datapath, tx, maxResourcesPerChunk)
if err != nil {
return err
}
@@ -304,12 +315,17 @@ func testWriteCatchpoint(t *testing.T, rdb trackerdb.Store, datapath string, fil
if err != nil {
return err
}
- _, err = writer.WriteStateProofVerificationContext()
+ rawData, err := tx.MakeSpVerificationCtxReader().GetAllSPContexts(ctx)
+ if err != nil {
+ return err
+ }
+ _, encodedData := crypto.EncodeAndHash(catchpointStateProofVerificationContext{Data: rawData})
+ err = writer.FileWriteSPVerificationContext(encodedData)
if err != nil {
return err
}
for {
- more, err := writer.WriteStep(context.Background())
+ more, err := writer.FileWriteStep(context.Background())
require.NoError(t, err)
if !more {
break
@@ -417,7 +433,7 @@ func TestCatchpointReadDatabaseOverflowSingleAccount(t *testing.T) {
totalAccountsWritten := uint64(0)
totalResources := 0
totalChunks := 0
- cw, err := makeCatchpointWriter(context.Background(), catchpointDataFilePath, tx, maxResourcesPerChunk)
+ cw, err := makeCatchpointFileWriter(context.Background(), catchpointDataFilePath, tx, maxResourcesPerChunk)
require.NoError(t, err)
ar, err := tx.MakeAccountsReader()
@@ -523,7 +539,7 @@ func TestCatchpointReadDatabaseOverflowAccounts(t *testing.T) {
totalAccountsWritten := uint64(0)
totalResources := 0
- cw, err := makeCatchpointWriter(context.Background(), catchpointDataFilePath, tx, maxResourcesPerChunk)
+ cw, err := makeCatchpointFileWriter(context.Background(), catchpointDataFilePath, tx, maxResourcesPerChunk)
require.NoError(t, err)
// repeat this until read all accts
diff --git a/ledger/catchpointtracker.go b/ledger/catchpointtracker.go
index 97051656f..427494dec 100644
--- a/ledger/catchpointtracker.go
+++ b/ledger/catchpointtracker.go
@@ -201,6 +201,23 @@ func (ct *catchpointTracker) GetLastCatchpointLabel() string {
return ct.lastCatchpointLabel
}
+func (ct *catchpointTracker) getSPVerificationData() (encodedData []byte, spVerificationHash crypto.Digest, err error) {
+ err = ct.dbs.Snapshot(func(ctx context.Context, tx trackerdb.SnapshotScope) error {
+ rawData, dbErr := tx.MakeSpVerificationCtxReader().GetAllSPContexts(ctx)
+ if dbErr != nil {
+ return dbErr
+ }
+
+ wrappedData := catchpointStateProofVerificationContext{Data: rawData}
+ spVerificationHash, encodedData = crypto.EncodeAndHash(wrappedData)
+ return nil
+ })
+ if err != nil {
+ return nil, crypto.Digest{}, err
+ }
+ return encodedData, spVerificationHash, nil
+}
+
func (ct *catchpointTracker) finishFirstStage(ctx context.Context, dbRound basics.Round, updatingBalancesDuration time.Duration) error {
ct.log.Infof("finishing catchpoint's first stage dbRound: %d", dbRound)
@@ -209,8 +226,16 @@ func (ct *catchpointTracker) finishFirstStage(ctx context.Context, dbRound basic
var totalChunks uint64
var biggestChunkLen uint64
var spVerificationHash crypto.Digest
+ var spVerificationEncodedData []byte
var catchpointGenerationStats telemetryspec.CatchpointGenerationEventDetails
+ // Generate the SP Verification hash and encoded data. The hash is used in the label when tracking catchpoints,
+ // and the encoded data for that hash will be added to the catchpoint file if catchpoint generation is enabled.
+ spVerificationEncodedData, spVerificationHash, err := ct.getSPVerificationData()
+ if err != nil {
+ return err
+ }
+
if ct.enableGeneratingCatchpointFiles {
// Generate the catchpoint file. This is done inline so that it will
// block any new accounts from being written. generateCatchpointData()
@@ -219,8 +244,8 @@ func (ct *catchpointTracker) finishFirstStage(ctx context.Context, dbRound basic
var err error
catchpointGenerationStats.BalancesWriteTime = uint64(updatingBalancesDuration.Nanoseconds())
- totalKVs, totalAccounts, totalChunks, biggestChunkLen, spVerificationHash, err = ct.generateCatchpointData(
- ctx, dbRound, &catchpointGenerationStats)
+ totalKVs, totalAccounts, totalChunks, biggestChunkLen, err = ct.generateCatchpointData(
+ ctx, dbRound, &catchpointGenerationStats, spVerificationEncodedData)
atomic.StoreInt32(&ct.catchpointDataWriting, 0)
if err != nil {
return err
@@ -1102,7 +1127,7 @@ func (ct *catchpointTracker) isWritingCatchpointDataFile() bool {
// - Balance and KV chunk (named balances.x.msgpack).
// ...
// - Balance and KV chunk (named balances.x.msgpack).
-func (ct *catchpointTracker) generateCatchpointData(ctx context.Context, accountsRound basics.Round, catchpointGenerationStats *telemetryspec.CatchpointGenerationEventDetails) (totalKVs, totalAccounts, totalChunks, biggestChunkLen uint64, spVerificationHash crypto.Digest, err error) {
+func (ct *catchpointTracker) generateCatchpointData(ctx context.Context, accountsRound basics.Round, catchpointGenerationStats *telemetryspec.CatchpointGenerationEventDetails, encodedSPData []byte) (totalKVs, totalAccounts, totalChunks, biggestChunkLen uint64, err error) {
ct.log.Debugf("catchpointTracker.generateCatchpointData() writing catchpoint accounts for round %d", accountsRound)
startTime := time.Now()
@@ -1122,17 +1147,17 @@ func (ct *catchpointTracker) generateCatchpointData(ctx context.Context, account
chunkExecutionDuration = shortChunkExecutionDuration
}
- var catchpointWriter *catchpointWriter
+ var catchpointWriter *catchpointFileWriter
start := time.Now()
ledgerGeneratecatchpointCount.Inc(nil)
- err = ct.dbs.TransactionContext(ctx, func(dbCtx context.Context, tx trackerdb.TransactionScope) (err error) {
- catchpointWriter, err = makeCatchpointWriter(dbCtx, catchpointDataFilePath, tx, ResourcesPerCatchpointFileChunk)
+ err = ct.dbs.SnapshotContext(ctx, func(dbCtx context.Context, tx trackerdb.SnapshotScope) (err error) {
+ catchpointWriter, err = makeCatchpointFileWriter(dbCtx, catchpointDataFilePath, tx, ResourcesPerCatchpointFileChunk)
if err != nil {
return
}
- spVerificationHash, err = catchpointWriter.WriteStateProofVerificationContext()
+ err = catchpointWriter.FileWriteSPVerificationContext(encodedSPData)
if err != nil {
return
}
@@ -1140,7 +1165,7 @@ func (ct *catchpointTracker) generateCatchpointData(ctx context.Context, account
for more {
stepCtx, stepCancelFunction := context.WithTimeout(dbCtx, chunkExecutionDuration)
writeStepStartTime := time.Now()
- more, err = catchpointWriter.WriteStep(stepCtx)
+ more, err = catchpointWriter.FileWriteStep(stepCtx)
// accumulate the actual time we've spent writing in this step.
catchpointGenerationStats.CPUTime += uint64(time.Since(writeStepStartTime).Nanoseconds())
stepCancelFunction()
@@ -1187,7 +1212,7 @@ func (ct *catchpointTracker) generateCatchpointData(ctx context.Context, account
ledgerGeneratecatchpointMicros.AddMicrosecondsSince(start, nil)
if err != nil {
ct.log.Warnf("catchpointTracker.generateCatchpointData() %v", err)
- return 0, 0, 0, 0, crypto.Digest{}, err
+ return 0, 0, 0, 0, err
}
catchpointGenerationStats.FileSize = uint64(catchpointWriter.writtenBytes)
@@ -1196,7 +1221,7 @@ func (ct *catchpointTracker) generateCatchpointData(ctx context.Context, account
catchpointGenerationStats.KVsCount = catchpointWriter.totalKVs
catchpointGenerationStats.AccountsRound = uint64(accountsRound)
- return catchpointWriter.totalKVs, catchpointWriter.totalAccounts, catchpointWriter.chunkNum, catchpointWriter.biggestChunkLen, spVerificationHash, nil
+ return catchpointWriter.totalKVs, catchpointWriter.totalAccounts, catchpointWriter.chunkNum, catchpointWriter.biggestChunkLen, nil
}
func (ct *catchpointTracker) recordFirstStageInfo(ctx context.Context, tx trackerdb.TransactionScope, catchpointGenerationStats *telemetryspec.CatchpointGenerationEventDetails, accountsRound basics.Round, totalKVs uint64, totalAccounts uint64, totalChunks uint64, biggestChunkLen uint64, stateProofVerificationHash crypto.Digest) error {
@@ -1316,7 +1341,7 @@ func (ct *catchpointTracker) GetCatchpointStream(round basics.Round) (ReadCloseS
ledgerGetcatchpointCount.Inc(nil)
// TODO: we need to generalize this, check @cce PoC PR, he has something
// somewhat broken for some KVs..
- err := ct.dbs.Transaction(func(ctx context.Context, tx trackerdb.TransactionScope) (err error) {
+ err := ct.dbs.Snapshot(func(ctx context.Context, tx trackerdb.SnapshotScope) (err error) {
cr, err := tx.MakeCatchpointReader()
if err != nil {
return err
diff --git a/ledger/catchpointtracker_test.go b/ledger/catchpointtracker_test.go
index f007217b0..6669eab4a 100644
--- a/ledger/catchpointtracker_test.go
+++ b/ledger/catchpointtracker_test.go
@@ -328,9 +328,12 @@ func TestRecordCatchpointFile(t *testing.T) {
}
func createCatchpoint(t *testing.T, ct *catchpointTracker, accountsRound basics.Round, ml *mockLedgerForTracker, round basics.Round) {
+ spVerificationEncodedData, stateProofVerificationHash, err := ct.getSPVerificationData()
+ require.NoError(t, err)
+
var catchpointGenerationStats telemetryspec.CatchpointGenerationEventDetails
- _, _, _, biggestChunkLen, stateProofVerificationHash, err := ct.generateCatchpointData(
- context.Background(), accountsRound, &catchpointGenerationStats)
+ _, _, _, biggestChunkLen, err := ct.generateCatchpointData(
+ context.Background(), accountsRound, &catchpointGenerationStats, spVerificationEncodedData)
require.NoError(t, err)
require.Equal(t, calculateStateProofVerificationHash(t, ml), stateProofVerificationHash)
@@ -460,8 +463,10 @@ func BenchmarkLargeCatchpointDataWriting(b *testing.B) {
require.NoError(b, err)
var catchpointGenerationStats telemetryspec.CatchpointGenerationEventDetails
+ encodedSPData, _, err := ct.getSPVerificationData()
+ require.NoError(b, err)
b.ResetTimer()
- ct.generateCatchpointData(context.Background(), basics.Round(0), &catchpointGenerationStats)
+ ct.generateCatchpointData(context.Background(), basics.Round(0), &catchpointGenerationStats, encodedSPData)
b.StopTimer()
b.ReportMetric(float64(accountsNumber), "accounts")
}
@@ -469,7 +474,7 @@ func BenchmarkLargeCatchpointDataWriting(b *testing.B) {
func TestCatchpointReproducibleLabels(t *testing.T) {
partitiontest.PartitionTest(t)
- if runtime.GOARCH == "arm" || runtime.GOARCH == "arm64" {
+ if runtime.GOARCH == "arm" {
t.Skip("This test is too slow on ARM and causes CI builds to time out")
}
@@ -592,7 +597,12 @@ func TestCatchpointReproducibleLabels(t *testing.T) {
ml2 := ledgerHistory[rnd]
require.NotNil(t, ml2)
- ct2 := newCatchpointTracker(t, ml2, cfg, ".")
+ cfg2 := cfg
+ // every other iteration modify CatchpointTracking to ensure labels generation does not depends on catchpoint file creation
+ if rnd%2 == 0 {
+ cfg2.CatchpointTracking = int64(crypto.RandUint63())%2 + 1 //values 1 or 2
+ }
+ ct2 := newCatchpointTracker(t, ml2, cfg2, ".")
defer ct2.close()
for i := rnd + 1; i <= lastRound; i++ {
blk := bookkeeping.Block{
diff --git a/ledger/catchupaccessor.go b/ledger/catchupaccessor.go
index 50755833d..b16d3a8fb 100644
--- a/ledger/catchupaccessor.go
+++ b/ledger/catchupaccessor.go
@@ -67,6 +67,9 @@ type CatchpointCatchupAccessor interface {
// GetCatchupBlockRound returns the latest block round matching the current catchpoint
GetCatchupBlockRound(ctx context.Context) (round basics.Round, err error)
+ // GetVerifyData returns the balances hash, spver hash and totals used by VerifyCatchpoint
+ GetVerifyData(ctx context.Context) (balancesHash crypto.Digest, spverHash crypto.Digest, totals ledgercore.AccountTotals, err error)
+
// VerifyCatchpoint verifies that the catchpoint is valid by reconstructing the label.
VerifyCatchpoint(ctx context.Context, blk *bookkeeping.Block) (err error)
@@ -927,34 +930,9 @@ func (c *catchpointCatchupAccessorImpl) GetCatchupBlockRound(ctx context.Context
return basics.Round(iRound), nil
}
-// VerifyCatchpoint verifies that the catchpoint is valid by reconstructing the label.
-func (c *catchpointCatchupAccessorImpl) VerifyCatchpoint(ctx context.Context, blk *bookkeeping.Block) (err error) {
- var balancesHash crypto.Digest
+func (c *catchpointCatchupAccessorImpl) GetVerifyData(ctx context.Context) (balancesHash crypto.Digest, spverHash crypto.Digest, totals ledgercore.AccountTotals, err error) {
var rawStateProofVerificationContext []ledgercore.StateProofVerificationContext
- var blockRound basics.Round
- var totals ledgercore.AccountTotals
- var catchpointLabel string
- var version uint64
-
- catchpointLabel, err = c.catchpointStore.ReadCatchpointStateString(ctx, trackerdb.CatchpointStateCatchupLabel)
- if err != nil {
- return fmt.Errorf("unable to read catchpoint catchup state '%s': %v", trackerdb.CatchpointStateCatchupLabel, err)
- }
-
- version, err = c.catchpointStore.ReadCatchpointStateUint64(ctx, trackerdb.CatchpointStateCatchupVersion)
- if err != nil {
- return fmt.Errorf("unable to retrieve catchpoint version: %v", err)
- }
-
- var iRound uint64
- iRound, err = c.catchpointStore.ReadCatchpointStateUint64(ctx, trackerdb.CatchpointStateCatchupBlockRound)
- if err != nil {
- return fmt.Errorf("unable to read catchpoint catchup state '%s': %v", trackerdb.CatchpointStateCatchupBlockRound, err)
- }
- blockRound = basics.Round(iRound)
- start := time.Now()
- ledgerVerifycatchpointCount.Inc(nil)
err = c.ledger.trackerDB().Transaction(func(ctx context.Context, tx trackerdb.TransactionScope) (err error) {
ar, err := tx.MakeAccountsReader()
if err != nil {
@@ -989,6 +967,42 @@ func (c *catchpointCatchupAccessorImpl) VerifyCatchpoint(ctx context.Context, bl
return
})
+ if err != nil {
+ return crypto.Digest{}, crypto.Digest{}, ledgercore.AccountTotals{}, err
+ }
+
+ wrappedContext := catchpointStateProofVerificationContext{Data: rawStateProofVerificationContext}
+ spverHash = crypto.HashObj(wrappedContext)
+
+ return balancesHash, spverHash, totals, err
+}
+
+// VerifyCatchpoint verifies that the catchpoint is valid by reconstructing the label.
+func (c *catchpointCatchupAccessorImpl) VerifyCatchpoint(ctx context.Context, blk *bookkeeping.Block) (err error) {
+ var blockRound basics.Round
+ var catchpointLabel string
+ var version uint64
+
+ catchpointLabel, err = c.catchpointStore.ReadCatchpointStateString(ctx, trackerdb.CatchpointStateCatchupLabel)
+ if err != nil {
+ return fmt.Errorf("unable to read catchpoint catchup state '%s': %v", trackerdb.CatchpointStateCatchupLabel, err)
+ }
+
+ version, err = c.catchpointStore.ReadCatchpointStateUint64(ctx, trackerdb.CatchpointStateCatchupVersion)
+ if err != nil {
+ return fmt.Errorf("unable to retrieve catchpoint version: %v", err)
+ }
+
+ var iRound uint64
+ iRound, err = c.catchpointStore.ReadCatchpointStateUint64(ctx, trackerdb.CatchpointStateCatchupBlockRound)
+ if err != nil {
+ return fmt.Errorf("unable to read catchpoint catchup state '%s': %v", trackerdb.CatchpointStateCatchupBlockRound, err)
+ }
+ blockRound = basics.Round(iRound)
+
+ start := time.Now()
+ ledgerVerifycatchpointCount.Inc(nil)
+ balancesHash, spVerificationHash, totals, err := c.GetVerifyData(ctx)
ledgerVerifycatchpointMicros.AddMicrosecondsSince(start, nil)
if err != nil {
return err
@@ -997,9 +1011,6 @@ func (c *catchpointCatchupAccessorImpl) VerifyCatchpoint(ctx context.Context, bl
return fmt.Errorf("block round in block header doesn't match block round in catchpoint: %d != %d", blockRound, blk.Round())
}
- wrappedContext := catchpointStateProofVerificationContext{Data: rawStateProofVerificationContext}
- spVerificationHash := crypto.HashObj(wrappedContext)
-
var catchpointLabelMaker ledgercore.CatchpointLabelMaker
blockDigest := blk.Digest()
if version <= CatchpointFileVersionV6 {
diff --git a/ledger/eval/applications.go b/ledger/eval/applications.go
index e126cfd30..5419bc952 100644
--- a/ledger/eval/applications.go
+++ b/ledger/eval/applications.go
@@ -21,7 +21,6 @@ import (
"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"
"github.com/algorand/go-algorand/ledger/apply"
"github.com/algorand/go-algorand/ledger/ledgercore"
@@ -129,10 +128,6 @@ func (cs *roundCowState) SetLocal(addr basics.Address, appIdx basics.AppIndex, k
return cs.setKey(addr, appIdx, false, key, value, accountIdx)
}
-func (cs *roundCowState) BlockHdrCached(round basics.Round) (bookkeeping.BlockHeader, error) {
- return cs.blockHdrCached(round)
-}
-
func (cs *roundCowState) DelLocal(addr basics.Address, appIdx basics.AppIndex, key string, accountIdx uint64) error {
return cs.delKey(addr, appIdx, false, key, accountIdx)
}
diff --git a/ledger/eval/cow.go b/ledger/eval/cow.go
index 8019dc69f..d797a4a56 100644
--- a/ledger/eval/cow.go
+++ b/ledger/eval/cow.go
@@ -57,7 +57,6 @@ type roundCowParent interface {
getCreator(cidx basics.CreatableIndex, ctype basics.CreatableType) (basics.Address, bool, error)
GetStateProofNextRound() basics.Round
BlockHdr(rnd basics.Round) (bookkeeping.BlockHeader, error)
- blockHdrCached(rnd basics.Round) (bookkeeping.BlockHeader, error)
getStorageCounts(addr basics.Address, aidx basics.AppIndex, global bool) (basics.StateSchema, error)
// note: getStorageLimits is redundant with the other methods
// and is provided to optimize state schema lookups
@@ -250,10 +249,6 @@ func (cb *roundCowState) GetStateProofVerificationContext(stateProofLastAttested
return cb.lookupParent.GetStateProofVerificationContext(stateProofLastAttestedRound)
}
-func (cb *roundCowState) blockHdrCached(r basics.Round) (bookkeeping.BlockHeader, error) {
- return cb.lookupParent.blockHdrCached(r)
-}
-
func (cb *roundCowState) incTxnCount() {
cb.txnCount++
}
diff --git a/ledger/eval/eval.go b/ledger/eval/eval.go
index fcebc8ffe..5fc72ff54 100644
--- a/ledger/eval/eval.go
+++ b/ledger/eval/eval.go
@@ -40,7 +40,6 @@ import (
// LedgerForCowBase represents subset of Ledger functionality needed for cow business
type LedgerForCowBase interface {
BlockHdr(basics.Round) (bookkeeping.BlockHeader, error)
- BlockHdrCached(basics.Round) (bookkeeping.BlockHeader, error)
CheckDup(config.ConsensusParams, basics.Round, basics.Round, basics.Round, transactions.Txid, ledgercore.Txlease) error
LookupWithoutRewards(basics.Round, basics.Address) (ledgercore.AccountData, basics.Round, error)
LookupAsset(basics.Round, basics.Address, basics.AssetIndex) (ledgercore.AssetResource, error)
@@ -342,10 +341,6 @@ func (x *roundCowBase) GetStateProofVerificationContext(stateProofLastAttestedRo
return x.l.GetStateProofVerificationContext(stateProofLastAttestedRound)
}
-func (x *roundCowBase) blockHdrCached(r basics.Round) (bookkeeping.BlockHeader, error) {
- return x.l.BlockHdrCached(r)
-}
-
func (x *roundCowBase) allocated(addr basics.Address, aidx basics.AppIndex, global bool) (bool, error) {
// For global, check if app params exist
if global {
@@ -958,7 +953,7 @@ func (eval *BlockEvaluator) TransactionGroup(txgroup []transactions.SignedTxnWit
cow := eval.state.child(len(txgroup))
defer cow.recycle()
- evalParams := logic.NewEvalParams(txgroup, &eval.proto, &eval.specials)
+ evalParams := logic.NewAppEvalParams(txgroup, &eval.proto, &eval.specials)
evalParams.Tracer = eval.Tracer
if eval.Tracer != nil {
diff --git a/ledger/eval/eval_test.go b/ledger/eval/eval_test.go
index 783f975bb..b91655831 100644
--- a/ledger/eval/eval_test.go
+++ b/ledger/eval/eval_test.go
@@ -940,10 +940,6 @@ func (ledger *evalTestLedger) BlockHdr(rnd basics.Round) (bookkeeping.BlockHeade
return block.BlockHeader, nil
}
-func (ledger *evalTestLedger) BlockHdrCached(rnd basics.Round) (bookkeeping.BlockHeader, error) {
- return ledger.BlockHdr(rnd)
-}
-
func (ledger *evalTestLedger) VotersForStateProof(rnd basics.Round) (*ledgercore.VotersForRound, error) {
return nil, errors.New("untested code path")
}
@@ -1041,10 +1037,6 @@ func (l *testCowBaseLedger) BlockHdr(basics.Round) (bookkeeping.BlockHeader, err
return bookkeeping.BlockHeader{}, errors.New("not implemented")
}
-func (l *testCowBaseLedger) BlockHdrCached(rnd basics.Round) (bookkeeping.BlockHeader, error) {
- return l.BlockHdr(rnd)
-}
-
func (l *testCowBaseLedger) CheckDup(config.ConsensusParams, basics.Round, basics.Round, basics.Round, transactions.Txid, ledgercore.Txlease) error {
return errors.New("not implemented")
}
diff --git a/ledger/eval/prefetcher/prefetcher_alignment_test.go b/ledger/eval/prefetcher/prefetcher_alignment_test.go
index efb9e683b..81ce168d4 100644
--- a/ledger/eval/prefetcher/prefetcher_alignment_test.go
+++ b/ledger/eval/prefetcher/prefetcher_alignment_test.go
@@ -97,10 +97,6 @@ func (l *prefetcherAlignmentTestLedger) BlockHdr(round basics.Round) (bookkeepin
fmt.Errorf("BlockHdr() round %d not supported", round)
}
-func (l *prefetcherAlignmentTestLedger) BlockHdrCached(round basics.Round) (bookkeeping.BlockHeader, error) {
- return l.BlockHdr(round)
-}
-
func (l *prefetcherAlignmentTestLedger) CheckDup(config.ConsensusParams, basics.Round, basics.Round, basics.Round, transactions.Txid, ledgercore.Txlease) error {
return nil
}
diff --git a/ledger/evalindexer.go b/ledger/evalindexer.go
index 5ac28ce81..1caa7d8ef 100644
--- a/ledger/evalindexer.go
+++ b/ledger/evalindexer.go
@@ -45,7 +45,7 @@ type indexerLedgerForEval interface {
LatestTotals() (ledgercore.AccountTotals, error)
LookupKv(basics.Round, string) ([]byte, error)
- BlockHdrCached(basics.Round) (bookkeeping.BlockHeader, error)
+ BlockHdr(basics.Round) (bookkeeping.BlockHeader, error)
}
// FoundAddress is a wrapper for an address and a boolean.
@@ -92,11 +92,6 @@ func (l indexerLedgerConnector) BlockHdr(round basics.Round) (bookkeeping.BlockH
return l.il.LatestBlockHdr()
}
-// BlockHdrCached is part of LedgerForEvaluator interface.
-func (l indexerLedgerConnector) BlockHdrCached(round basics.Round) (bookkeeping.BlockHeader, error) {
- return l.il.BlockHdrCached(round)
-}
-
// CheckDup is part of LedgerForEvaluator interface.
func (l indexerLedgerConnector) CheckDup(config.ConsensusParams, basics.Round, basics.Round, basics.Round, transactions.Txid, ledgercore.Txlease) error {
// This function is not used by evaluator.
diff --git a/ledger/evalindexer_test.go b/ledger/evalindexer_test.go
index d5636f27c..8209bb928 100644
--- a/ledger/evalindexer_test.go
+++ b/ledger/evalindexer_test.go
@@ -47,8 +47,8 @@ func (il indexerLedgerForEvalImpl) LatestBlockHdr() (bookkeeping.BlockHeader, er
return il.l.BlockHdr(il.latestRound)
}
-func (il indexerLedgerForEvalImpl) BlockHdrCached(round basics.Round) (bookkeeping.BlockHeader, error) {
- return il.l.BlockHdrCached(round)
+func (il indexerLedgerForEvalImpl) BlockHdr(round basics.Round) (bookkeeping.BlockHeader, error) {
+ return il.l.BlockHdr(round)
}
// The value of the returned map is nil iff the account was not found.
diff --git a/ledger/ledger.go b/ledger/ledger.go
index dd54529ee..6c02951a9 100644
--- a/ledger/ledger.go
+++ b/ledger/ledger.go
@@ -91,8 +91,6 @@ type Ledger struct {
trackers trackerRegistry
trackerMu deadlock.RWMutex
- headerCache blockHeaderCache
-
// verifiedTxnCache holds all the verified transactions state
verifiedTxnCache verify.VerifiedTransactionCache
@@ -136,8 +134,6 @@ func OpenLedger(
tracer: tracer,
}
- l.headerCache.initialize()
-
defer func() {
if err != nil {
l.Close()
@@ -628,8 +624,6 @@ func (l *Ledger) OnlineCirculation(rnd basics.Round, voteRnd basics.Round) (basi
// CheckDup return whether a transaction is a duplicate one.
func (l *Ledger) CheckDup(currentProto config.ConsensusParams, current basics.Round, firstValid basics.Round, lastValid basics.Round, txid transactions.Txid, txl ledgercore.Txlease) error {
- l.trackerMu.RLock()
- defer l.trackerMu.RUnlock()
return l.txTail.checkDup(currentProto, current, firstValid, lastValid, txid, txl)
}
@@ -654,16 +648,22 @@ func (l *Ledger) Block(rnd basics.Round) (blk bookkeeping.Block, err error) {
// BlockHdr returns the BlockHeader of the block for round rnd.
func (l *Ledger) BlockHdr(rnd basics.Round) (blk bookkeeping.BlockHeader, err error) {
- blk, exists := l.headerCache.get(rnd)
- if exists {
- return
- }
- blk, err = l.blockQ.getBlockHdr(rnd)
- if err == nil {
- l.headerCache.put(blk)
+ // Expected availability range in txTail.blockHeader is [Latest - MaxTxnLife, Latest]
+ // allowing (MaxTxnLife + 1) = 1001 rounds back loopback.
+ // The depth besides the MaxTxnLife is controlled by DeeperBlockHeaderHistory parameter
+ // and currently set to 1.
+ // Explanation:
+ // Clients are expected to query blocks at rounds (txn.LastValid - (MaxTxnLife + 1)),
+ // and because a txn is alive when the current round <= txn.LastValid
+ // and valid if txn.LastValid - txn.FirstValid <= MaxTxnLife
+ // the deepest lookup happens when txn.LastValid == current => txn.LastValid == Latest + 1
+ // that gives Latest + 1 - (MaxTxnLife + 1) = Latest - MaxTxnLife as the first round to be accessible.
+ hdr, ok := l.txTail.blockHeader(rnd)
+ if !ok {
+ hdr, err = l.blockQ.getBlockHdr(rnd)
}
- return
+ return hdr, err
}
// EncodedBlockCert returns the encoded block and the corresponding encoded certificate of the block for round rnd.
@@ -712,7 +712,6 @@ func (l *Ledger) AddValidatedBlock(vb ledgercore.ValidatedBlock, cert agreement.
if err != nil {
return err
}
- l.headerCache.put(blk.BlockHeader)
l.trackers.newBlock(blk, vb.Delta())
l.log.Debugf("ledger.AddValidatedBlock: added blk %d", blk.Round())
return nil
@@ -755,27 +754,6 @@ func (l *Ledger) GenesisAccounts() map[basics.Address]basics.AccountData {
return l.genesisAccounts
}
-// BlockHdrCached returns the block header if available.
-// Expected availability range is [Latest - MaxTxnLife, Latest]
-// allowing (MaxTxnLife + 1) = 1001 rounds back loopback.
-// The depth besides the MaxTxnLife is controlled by DeeperBlockHeaderHistory parameter
-// and currently set to 1.
-// Explanation:
-// Clients are expected to query blocks at rounds (txn.LastValid - (MaxTxnLife + 1)),
-// and because a txn is alive when the current round <= txn.LastValid
-// and valid if txn.LastValid - txn.FirstValid <= MaxTxnLife
-// the deepest lookup happens when txn.LastValid == current => txn.LastValid == Latest + 1
-// that gives Latest + 1 - (MaxTxnLife + 1) = Latest - MaxTxnLife as the first round to be accessible.
-func (l *Ledger) BlockHdrCached(rnd basics.Round) (hdr bookkeeping.BlockHeader, err error) {
- l.trackerMu.RLock()
- defer l.trackerMu.RUnlock()
- hdr, ok := l.txTail.blockHeader(rnd)
- if !ok {
- err = fmt.Errorf("no cached header data for round %d", rnd)
- }
- return hdr, err
-}
-
// GetCatchpointCatchupState returns the current state of the catchpoint catchup.
func (l *Ledger) GetCatchpointCatchupState(ctx context.Context) (state CatchpointCatchupState, err error) {
return MakeCatchpointCatchupAccessor(l, l.log).GetState(ctx)
diff --git a/ledger/ledger_test.go b/ledger/ledger_test.go
index 6e5f6091c..e99574487 100644
--- a/ledger/ledger_test.go
+++ b/ledger/ledger_test.go
@@ -1385,46 +1385,6 @@ func testLedgerRegressionFaultyLeaseFirstValidCheck2f3880f7(t *testing.T, versio
}
}
-func TestLedgerBlockHdrCaching(t *testing.T) {
- partitiontest.PartitionTest(t)
- a := require.New(t)
-
- dbName := fmt.Sprintf("%s.%d", t.Name(), crypto.RandUint64())
- genesisInitState := getInitState()
- const inMem = true
- cfg := config.GetDefaultLocal()
- cfg.Archival = true
- log := logging.TestingLog(t)
- log.SetLevel(logging.Info)
- l, err := OpenLedger(log, dbName, inMem, genesisInitState, cfg)
- a.NoError(err)
- defer l.Close()
-
- blk := genesisInitState.Block
-
- for i := 0; i < 1024; i++ {
- blk.BlockHeader.Round++
- blk.BlockHeader.TimeStamp += int64(crypto.RandUint64() % 100 * 1000)
- err := l.AddBlock(blk, agreement.Certificate{})
- a.NoError(err)
-
- hdr, err := l.BlockHdr(blk.BlockHeader.Round)
- a.NoError(err)
- a.Equal(blk.BlockHeader, hdr)
- }
-
- rnd := basics.Round(128)
- hdr, err := l.BlockHdr(rnd) // should update LRU cache but not latestBlockHeaderCache
- a.NoError(err)
- a.Equal(rnd, hdr.Round)
-
- _, exists := l.headerCache.lruCache.Get(rnd)
- a.True(exists)
-
- _, exists = l.headerCache.latestHeaderCache.get(rnd)
- a.False(exists)
-}
-
func BenchmarkLedgerBlockHdrCaching(b *testing.B) {
benchLedgerCache(b, 1024-256+1)
}
@@ -2629,7 +2589,7 @@ func TestLedgerTxTailCachedBlockHeaders(t *testing.T) {
latest := l.Latest()
for i := latest - basics.Round(proto.MaxTxnLife); i <= latest; i++ {
- blk, err := l.BlockHdrCached(i)
+ blk, err := l.BlockHdr(i)
require.NoError(t, err)
require.Equal(t, blk.Round, i)
}
@@ -2643,13 +2603,13 @@ func TestLedgerTxTailCachedBlockHeaders(t *testing.T) {
start := dbRound - basics.Round(proto.MaxTxnLife)
end := latest - basics.Round(proto.MaxTxnLife)
for i := start; i < end; i++ {
- blk, err := l.BlockHdrCached(i)
+ blk, err := l.BlockHdr(i)
require.NoError(t, err)
require.Equal(t, blk.Round, i)
}
- _, err = l.BlockHdrCached(start - 1)
- require.Error(t, err)
+ _, ok := l.txTail.blockHeader(start - 1)
+ require.False(t, ok)
}
// TestLedgerKeyregFlip generates keyreg transactions for flipping genesis accounts state.
diff --git a/ledger/lrukv.go b/ledger/lrukv.go
index 8955d3d2b..8c407a9fc 100644
--- a/ledger/lrukv.go
+++ b/ledger/lrukv.go
@@ -79,7 +79,7 @@ func (m *lruKV) read(key string) (data trackerdb.PersistedKVData, has bool) {
func (m *lruKV) flushPendingWrites() {
pendingEntriesCount := len(m.pendingKVs)
if pendingEntriesCount >= m.pendingWritesWarnThreshold {
- m.log.Warnf("lruKV: number of entries in pendingKVs(%d) exceed the warning threshold of %d", pendingEntriesCount, m.pendingWritesWarnThreshold)
+ m.log.Infof("lruKV: number of entries in pendingKVs(%d) exceed the warning threshold of %d", pendingEntriesCount, m.pendingWritesWarnThreshold)
}
for ; pendingEntriesCount > 0; pendingEntriesCount-- {
select {
diff --git a/ledger/lrukv_test.go b/ledger/lrukv_test.go
index 0b0347e24..35345091b 100644
--- a/ledger/lrukv_test.go
+++ b/ledger/lrukv_test.go
@@ -18,6 +18,7 @@ package ledger
import (
"fmt"
+ "strings"
"testing"
"time"
@@ -162,8 +163,10 @@ type lruKVTestLogger struct {
warnMsgCount int
}
-func (cl *lruKVTestLogger) Warnf(s string, args ...interface{}) {
- cl.warnMsgCount++
+func (cl *lruKVTestLogger) Infof(s string, args ...interface{}) {
+ if strings.Contains(s, "exceed the warning threshold of") {
+ cl.warnMsgCount++
+ }
}
func TestLRUKVPendingWritesWarning(t *testing.T) {
diff --git a/ledger/lruresources.go b/ledger/lruresources.go
index 9886b29b1..f0a536350 100644
--- a/ledger/lruresources.go
+++ b/ledger/lruresources.go
@@ -103,7 +103,7 @@ func (m *lruResources) readAll(addr basics.Address) (ret []trackerdb.PersistedRe
func (m *lruResources) flushPendingWrites() {
pendingEntriesCount := len(m.pendingResources)
if pendingEntriesCount >= m.pendingWritesWarnThreshold {
- m.log.Warnf("lruResources: number of entries in pendingResources(%d) exceed the warning threshold of %d", pendingEntriesCount, m.pendingWritesWarnThreshold)
+ m.log.Infof("lruResources: number of entries in pendingResources(%d) exceed the warning threshold of %d", pendingEntriesCount, m.pendingWritesWarnThreshold)
}
outer:
diff --git a/ledger/lruresources_test.go b/ledger/lruresources_test.go
index 9268889a5..b678ea742 100644
--- a/ledger/lruresources_test.go
+++ b/ledger/lruresources_test.go
@@ -18,6 +18,7 @@ package ledger
import (
"encoding/binary"
+ "strings"
"testing"
"time"
@@ -180,8 +181,10 @@ type lruResourcesTestLogger struct {
warnMsgCount int
}
-func (cl *lruResourcesTestLogger) Warnf(s string, args ...interface{}) {
- cl.warnMsgCount++
+func (cl *lruResourcesTestLogger) Infof(s string, args ...interface{}) {
+ if strings.Contains(s, "exceed the warning threshold of") {
+ cl.warnMsgCount++
+ }
}
func TestLRUResourcesPendingWritesWarning(t *testing.T) {
diff --git a/ledger/roundlru.go b/ledger/roundlru.go
deleted file mode 100644
index 43d863987..000000000
--- a/ledger/roundlru.go
+++ /dev/null
@@ -1,142 +0,0 @@
-// Copyright (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 <https://www.gnu.org/licenses/>.
-
-package ledger
-
-import (
- "container/heap"
-
- "github.com/algorand/go-deadlock"
-
- "github.com/algorand/go-algorand/data/basics"
-)
-
-type lruEntry struct {
- useIndex int
- r basics.Round
- x interface{}
-}
-
-type lruHeap struct {
- heap []lruEntry
- lookup map[basics.Round]int
-}
-
-// Len is part of heap.Interface
-func (h *lruHeap) Len() int {
- return len(h.heap)
-}
-
-// Less reports whether the element with
-// index i should sort before the element with index j.
-func (h *lruHeap) Less(i, j int) bool {
- return h.heap[i].useIndex < h.heap[j].useIndex
-}
-
-// Swap swaps the elements with indexes i and j.
-func (h *lruHeap) Swap(i, j int) {
- t := h.heap[i]
- h.heap[i] = h.heap[j]
- h.heap[j] = t
- h.lookup[h.heap[i].r] = i
- h.lookup[h.heap[j].r] = j
-}
-func (h *lruHeap) Push(x interface{}) {
- // add x as element Len()
- xv := x.(lruEntry)
- h.heap = append(h.heap, xv)
- h.lookup[xv.r] = len(h.heap) - 1
-}
-func (h *lruHeap) Pop() interface{} {
- // remove and return element Len() - 1.
- oldlen := len(h.heap)
- out := h.heap[oldlen-1]
- h.heap = h.heap[:oldlen-1]
- delete(h.lookup, out.r)
- return out
-}
-
-type heapLRUCache struct {
- entries lruHeap
- lock deadlock.Mutex
- nextUseIndex int
- maxEntries int
-}
-
-func (hlc *heapLRUCache) Get(r basics.Round) (ob interface{}, exists bool) {
- hlc.lock.Lock()
- defer hlc.lock.Unlock()
- if i, present := hlc.entries.lookup[r]; present {
- out := hlc.entries.heap[i].x
- hlc.entries.heap[i].useIndex = hlc.nextUseIndex
- hlc.inc()
- heap.Fix(&hlc.entries, i)
- return out, true
- }
- return nil, false
-}
-func (hlc *heapLRUCache) Put(r basics.Round, data interface{}) {
- hlc.lock.Lock()
- defer hlc.lock.Unlock()
- if hlc.entries.heap == nil {
- hlc.entries.heap = make([]lruEntry, 1)
- hlc.entries.heap[0] = lruEntry{hlc.nextUseIndex, r, data}
- hlc.inc()
- hlc.entries.lookup = make(map[basics.Round]int)
- hlc.entries.lookup[r] = 0
- return
- }
- if i, present := hlc.entries.lookup[r]; present {
- // update data, but don't adjust LRU order
- hlc.entries.heap[i].x = data
- return
- }
- heap.Push(&hlc.entries, lruEntry{hlc.nextUseIndex, r, data})
- for len(hlc.entries.heap) > hlc.maxEntries {
- heap.Remove(&hlc.entries, 0)
- }
- hlc.inc()
-}
-
-// MaxInt is the maximum int which might be int32 or int64
-const MaxInt = int((^uint(0)) >> 1)
-
-func (hlc *heapLRUCache) inc() {
- hlc.nextUseIndex++
- if hlc.nextUseIndex == MaxInt {
- hlc.reIndex()
- }
-}
-func (hlc *heapLRUCache) reIndex() {
- if len(hlc.entries.heap) == 0 {
- return
- }
- minprio := hlc.entries.heap[0].useIndex
- maxprio := hlc.entries.heap[0].useIndex
- for i := 1; i < len(hlc.entries.heap); i++ {
- xp := hlc.entries.heap[i].useIndex
- if xp < minprio {
- minprio = xp
- }
- if xp > maxprio {
- maxprio = xp
- }
- }
- for i := range hlc.entries.heap {
- hlc.entries.heap[i].useIndex -= minprio
- }
- hlc.nextUseIndex = maxprio + 1 - minprio
-}
diff --git a/ledger/roundlru_test.go b/ledger/roundlru_test.go
deleted file mode 100644
index 8033f3439..000000000
--- a/ledger/roundlru_test.go
+++ /dev/null
@@ -1,95 +0,0 @@
-// Copyright (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 <https://www.gnu.org/licenses/>.
-
-package ledger
-
-import (
- "testing"
-
- "github.com/stretchr/testify/require"
-
- "github.com/algorand/go-algorand/data/basics"
- "github.com/algorand/go-algorand/test/partitiontest"
-)
-
-func getEq(t *testing.T, cache *heapLRUCache, r basics.Round, expected string) {
- got, exists := cache.Get(r)
- if !exists {
- t.Fatalf("expected value for cache[%v] but not present", r)
- return
- }
- actual := got.(string)
- if actual != expected {
- t.Fatalf("expected %v but got %v for %v", expected, actual, r)
- }
-}
-
-func getNone(t *testing.T, cache *heapLRUCache, r basics.Round) {
- got, exists := cache.Get(r)
- if exists {
- t.Fatalf("expected none for cache[%v] but got %v", r, got)
- return
- }
-}
-
-func TestRoundLRUBasic(t *testing.T) {
- partitiontest.PartitionTest(t)
-
- cache := heapLRUCache{maxEntries: 3}
- cache.Put(1, "one")
- cache.Put(2, "two")
- cache.Put(3, "three")
- getEq(t, &cache, 1, "one")
- getEq(t, &cache, 2, "two")
- getEq(t, &cache, 3, "three")
- cache.Put(4, "four")
- getNone(t, &cache, 1)
- getEq(t, &cache, 3, "three")
- cache.Put(5, "five")
- cache.Put(6, "six")
- getEq(t, &cache, 3, "three")
- getNone(t, &cache, 2)
- getNone(t, &cache, 4)
-}
-
-func TestRoundLRUReIndex(t *testing.T) {
- partitiontest.PartitionTest(t)
-
- cache := heapLRUCache{
- entries: lruHeap{
- heap: []lruEntry{
- {
- useIndex: MaxInt - 2,
- },
- {
- useIndex: MaxInt - 1,
- },
- {
- useIndex: MaxInt - 3,
- },
- },
- },
- maxEntries: 3,
- nextUseIndex: MaxInt - 1,
- }
-
- cache.inc()
-
- require.Equal(t, 3, cache.nextUseIndex)
- require.Equal(t, 1, cache.entries.heap[0].useIndex)
- require.Equal(t, 2, cache.entries.heap[1].useIndex)
- require.Equal(t, 0, cache.entries.heap[2].useIndex)
-}
diff --git a/ledger/simulation/simulation_eval_test.go b/ledger/simulation/simulation_eval_test.go
index de6f4047e..0228357b5 100644
--- a/ledger/simulation/simulation_eval_test.go
+++ b/ledger/simulation/simulation_eval_test.go
@@ -1205,7 +1205,7 @@ func TestLogicSigOverBudget(t *testing.T) {
` + strings.Repeat(`byte "a"
keccak256
pop
-`, 200) + `int 1`)
+`, 310) + `int 1`)
require.NoError(t, err)
program := logic.Program(op.Program)
lsigAddr := basics.Address(crypto.HashObj(&program))
@@ -1261,7 +1261,7 @@ int 1`,
ApplyData: expectedAppCallAD,
},
AppBudgetConsumed: 0,
- LogicSigBudgetConsumed: 19934,
+ LogicSigBudgetConsumed: 39998,
},
},
FailedAt: expectedFailedAt,
@@ -2514,6 +2514,11 @@ subroutine_manipulating_stack:
+ // [arg_0 * 3]
pushbytess "1!" "5!" // [arg_0 * 3, "1!", "5!"]
pushints 0 2 1 1 5 18446744073709551615 // [arg_0 * 3, "1!", "5!", 0, 2, 1, 1, 5, 18446744073709551615]
+ store 1 // [arg_0 * 3, "1!", "5!", 0, 2, 1, 1, 5]
+ load 1 // [arg_0 * 3, "1!", "5!", 0, 2, 1, 1, 5, 18446744073709551615]
+ stores // [arg_0 * 3, "1!", "5!", 0, 2, 1, 1]
+ load 1 // [arg_0 * 3, "1!", "5!", 0, 2, 1, 1, 18446744073709551615]
+ store 1 // [arg_0 * 3, "1!", "5!", 0, 2, 1, 1]
retsub
end:
@@ -2564,8 +2569,9 @@ int 1`,
{signedCreate, signedPay, signedAppCall},
},
TraceConfig: simulation.ExecTraceConfig{
- Enable: true,
- Stack: true,
+ Enable: true,
+ Stack: true,
+ Scratch: true,
},
},
developerAPI: true,
@@ -2573,8 +2579,9 @@ int 1`,
Version: simulation.ResultLatestVersion,
LastRound: env.TxnInfo.LatestRound(),
TraceConfig: simulation.ExecTraceConfig{
- Enable: true,
- Stack: true,
+ Enable: true,
+ Stack: true,
+ Scratch: true,
},
TxnGroups: []simulation.TxnGroupResult{
{
@@ -2600,11 +2607,11 @@ int 1`,
StackPopCount: 1,
},
{
- PC: 81,
+ PC: 90,
StackAdded: goValuesToTealValues(1),
},
{
- PC: 82,
+ PC: 91,
StackAdded: goValuesToTealValues(1),
StackPopCount: 1,
},
@@ -2624,7 +2631,7 @@ int 1`,
},
},
},
- AppBudgetConsumed: 34,
+ AppBudgetConsumed: 39,
Trace: &simulation.TransactionTrace{
ApprovalProgramTrace: []simulation.OpcodeTraceUnit{
{
@@ -2769,11 +2776,54 @@ int 1`,
PC: 63,
StackAdded: goValuesToTealValues(0, 2, 1, 1, 5, uint64(math.MaxUint64)),
},
- // retsub
+ // store 1
{
PC: 80,
+ StackPopCount: 1,
+ ScratchSlotChanges: []simulation.ScratchChange{
+ {
+ Slot: 1,
+ NewValue: goValuesToTealValues(uint64(math.MaxUint64))[0],
+ },
+ },
+ },
+ // load 1
+ {
+ PC: 82,
+ StackAdded: goValuesToTealValues(uint64(math.MaxUint64)),
+ },
+ // stores
+ {
+ PC: 84,
+ StackPopCount: 2,
+ ScratchSlotChanges: []simulation.ScratchChange{
+ {
+ Slot: 5,
+ NewValue: goValuesToTealValues(uint64(math.MaxUint64))[0],
+ },
+ },
+ },
+ // load 1
+ {
+ PC: 85,
+ StackAdded: goValuesToTealValues(uint64(math.MaxUint64)),
+ },
+ // store 1
+ {
+ PC: 87,
+ StackPopCount: 1,
+ ScratchSlotChanges: []simulation.ScratchChange{
+ {
+ Slot: 1,
+ NewValue: goValuesToTealValues(uint64(math.MaxUint64))[0],
+ },
+ },
+ },
+ // retsub
+ {
+ PC: 89,
StackAdded: goValuesToTealValues(applicationArg * 3),
- StackPopCount: 10,
+ StackPopCount: 8,
},
// itob
{
@@ -2792,12 +2842,12 @@ int 1`,
},
// int 1
{
- PC: 81,
+ PC: 90,
StackAdded: goValuesToTealValues(1),
},
// return
{
- PC: 82,
+ PC: 91,
StackAdded: goValuesToTealValues(1),
StackPopCount: 1,
},
@@ -2806,7 +2856,7 @@ int 1`,
},
},
AppBudgetAdded: 1400,
- AppBudgetConsumed: 39,
+ AppBudgetConsumed: 44,
},
},
},
diff --git a/ledger/simulation/trace.go b/ledger/simulation/trace.go
index 30a3219a1..574836097 100644
--- a/ledger/simulation/trace.go
+++ b/ledger/simulation/trace.go
@@ -108,8 +108,9 @@ func (eo ResultEvalOverrides) LogicEvalConstants() logic.EvalConstants {
type ExecTraceConfig struct {
_struct struct{} `codec:",omitempty"`
- Enable bool `codec:"enable"`
- Stack bool `codec:"stack-change"`
+ Enable bool `codec:"enable"`
+ Stack bool `codec:"stack-change"`
+ Scratch bool `codec:"scratch-change"`
}
// Result contains the result from a call to Simulator.Simulate
@@ -130,6 +131,9 @@ func (r Result) ReturnTrace() bool { return r.TraceConfig.Enable }
// ReturnStackChange reads from Result object and decides if simulation return stack changes.
func (r Result) ReturnStackChange() bool { return r.TraceConfig.Stack }
+// ReturnScratchChange tells if the simulation runs with scratch-change enabled.
+func (r Result) ReturnScratchChange() bool { return r.TraceConfig.Scratch }
+
// validateSimulateRequest first checks relation between request and config variables, including developerAPI:
// if `developerAPI` provided is turned off, this method would:
// - error on asking for exec trace
@@ -141,11 +145,20 @@ func validateSimulateRequest(request Request, developerAPI bool) error {
},
}
}
- if !request.TraceConfig.Enable && request.TraceConfig.Stack {
- return InvalidRequestError{
- SimulatorError{
- err: fmt.Errorf("basic trace must be enabled when enabling stack tracing"),
- },
+ if !request.TraceConfig.Enable {
+ if request.TraceConfig.Stack {
+ return InvalidRequestError{
+ SimulatorError{
+ err: fmt.Errorf("basic trace must be enabled when enabling stack tracing"),
+ },
+ }
+ }
+ if request.TraceConfig.Scratch {
+ return InvalidRequestError{
+ SimulatorError{
+ err: fmt.Errorf("basic trace must be enabled when enabling scratch slot change tracing"),
+ },
+ }
}
}
return nil
@@ -176,6 +189,15 @@ func makeSimulationResult(lastRound basics.Round, request Request, developerAPI
}, nil
}
+// ScratchChange represents a write operation into a scratch slot
+type ScratchChange struct {
+ // Slot stands for the scratch slot id get written to
+ Slot uint64
+
+ // NewValue is the stack value written to scratch slot
+ NewValue basics.TealValue
+}
+
// OpcodeTraceUnit contains the trace effects of a single opcode evaluation.
type OpcodeTraceUnit struct {
// The PC of the opcode being evaluated
@@ -191,6 +213,9 @@ type OpcodeTraceUnit struct {
// deleted element number from stack
StackPopCount uint64
+
+ // ScratchSlotChanges stands for write operations into scratch slots
+ ScratchSlotChanges []ScratchChange
}
// TransactionTrace contains the trace effects of a single transaction evaluation (including its inners)
diff --git a/ledger/simulation/tracer.go b/ledger/simulation/tracer.go
index 849bd6a5c..b56e13e42 100644
--- a/ledger/simulation/tracer.go
+++ b/ledger/simulation/tracer.go
@@ -96,6 +96,10 @@ type evalTracer struct {
// stackHeightAfterDeletion is calculated by stack height before opcode - stack element deletion number.
// NOTE: both stackChangeExplanation and stackHeightAfterDeletion are used only for Stack exposure.
stackHeightAfterDeletion int
+
+ // scratchSlots are the scratch slots changed on current opcode (currently either `store` or `stores`).
+ // NOTE: this field scratchSlots is used only for scratch change exposure.
+ scratchSlots []uint64
}
func makeEvalTracer(lastRound basics.Round, request Request, developerAPI bool) (*evalTracer, error) {
@@ -254,7 +258,7 @@ func (tracer *evalTracer) makeOpcodeTraceUnit(cx *logic.EvalContext) OpcodeTrace
}
func (o *OpcodeTraceUnit) computeStackValueDeletions(cx *logic.EvalContext, tracer *evalTracer) {
- tracer.popCount, tracer.addCount = cx.NextStackChange()
+ tracer.popCount, tracer.addCount = cx.GetOpSpec().Explain(cx)
o.StackPopCount = uint64(tracer.popCount)
stackHeight := len(cx.Stack)
@@ -284,10 +288,13 @@ func (tracer *evalTracer) BeforeOpcode(cx *logic.EvalContext) {
}
*txnTrace.programTraceRef = append(*txnTrace.programTraceRef, tracer.makeOpcodeTraceUnit(cx))
+ latestOpcodeTraceUnit := &(*txnTrace.programTraceRef)[len(*txnTrace.programTraceRef)-1]
if tracer.result.ReturnStackChange() {
- latestOpcodeTraceUnit := &(*txnTrace.programTraceRef)[len(*txnTrace.programTraceRef)-1]
latestOpcodeTraceUnit.computeStackValueDeletions(cx, tracer)
}
+ if tracer.result.ReturnScratchChange() {
+ tracer.recordChangedScratchSlots(cx)
+ }
}
}
@@ -302,12 +309,48 @@ func (o *OpcodeTraceUnit) appendAddedStackValue(cx *logic.EvalContext, tracer *e
}
}
+func (tracer *evalTracer) recordChangedScratchSlots(cx *logic.EvalContext) {
+ currentOpcodeName := cx.GetOpSpec().Name
+ last := len(cx.Stack) - 1
+ tracer.scratchSlots = nil
+
+ switch currentOpcodeName {
+ case "store":
+ slot := uint64(cx.GetProgram()[cx.PC()+1])
+ tracer.scratchSlots = append(tracer.scratchSlots, slot)
+ case "stores":
+ prev := last - 1
+ slot := cx.Stack[prev].Uint
+
+ // If something goes wrong for `stores`, we don't have to error here
+ // for in runtime already has evalError
+ if slot >= uint64(len(cx.Scratch)) {
+ return
+ }
+ tracer.scratchSlots = append(tracer.scratchSlots, slot)
+ }
+}
+
+func (tracer *evalTracer) recordUpdatedScratchVars(cx *logic.EvalContext) []ScratchChange {
+ if len(tracer.scratchSlots) == 0 {
+ return nil
+ }
+ changes := make([]ScratchChange, len(tracer.scratchSlots))
+ for i, slot := range tracer.scratchSlots {
+ changes[i] = ScratchChange{
+ Slot: slot,
+ NewValue: cx.Scratch[slot].ToTealValue(),
+ }
+ }
+ return changes
+}
+
func (tracer *evalTracer) AfterOpcode(cx *logic.EvalContext, evalError error) {
groupIndex := cx.GroupIndex()
// NOTE: only when we have no evalError on current opcode,
// we can proceed for recording stack chaange
- if evalError == nil && tracer.result.ReturnStackChange() {
+ if evalError == nil && tracer.result.ReturnTrace() {
var txnTrace *TransactionTrace
if cx.RunMode() == logic.ModeSig {
txnTrace = tracer.result.TxnGroups[0].Txns[groupIndex].Trace
@@ -316,7 +359,12 @@ func (tracer *evalTracer) AfterOpcode(cx *logic.EvalContext, evalError error) {
}
latestOpcodeTraceUnit := &(*txnTrace.programTraceRef)[len(*txnTrace.programTraceRef)-1]
- latestOpcodeTraceUnit.appendAddedStackValue(cx, tracer)
+ if tracer.result.ReturnStackChange() {
+ latestOpcodeTraceUnit.appendAddedStackValue(cx, tracer)
+ }
+ if tracer.result.ReturnScratchChange() {
+ latestOpcodeTraceUnit.ScratchSlotChanges = tracer.recordUpdatedScratchVars(cx)
+ }
}
if cx.RunMode() != logic.ModeApp {
diff --git a/ledger/store/trackerdb/generickv/accounts_ext_reader.go b/ledger/store/trackerdb/generickv/accounts_ext_reader.go
new file mode 100644
index 000000000..553d143a4
--- /dev/null
+++ b/ledger/store/trackerdb/generickv/accounts_ext_reader.go
@@ -0,0 +1,521 @@
+// Copyright (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 <https://www.gnu.org/licenses/>.
+
+package generickv
+
+import (
+ "bytes"
+ "context"
+ "encoding/binary"
+ "fmt"
+
+ "github.com/algorand/go-algorand/config"
+ "github.com/algorand/go-algorand/crypto"
+ "github.com/algorand/go-algorand/data/basics"
+ "github.com/algorand/go-algorand/ledger/ledgercore"
+ "github.com/algorand/go-algorand/ledger/store/trackerdb"
+ "github.com/algorand/go-algorand/protocol"
+)
+
+func (r *accountsReader) AccountsRound() (rnd basics.Round, err error) {
+ // SQL at time of impl:
+ //
+ // "SELECT rnd FROM acctrounds WHERE id='acctbase'"
+
+ // read round entry
+ value, closer, err := r.kvr.Get(roundKey())
+ if err != nil {
+ return
+ }
+ defer closer.Close()
+
+ // parse the bytes into a u64
+ rnd = basics.Round(binary.BigEndian.Uint64(value))
+
+ return
+}
+
+func (r *accountsReader) AccountsTotals(ctx context.Context, catchpointStaging bool) (totals ledgercore.AccountTotals, err error) {
+ // read round entry
+ value, closer, err := r.kvr.Get(totalsKey(catchpointStaging))
+ if err != nil {
+ return
+ }
+ defer closer.Close()
+
+ err = protocol.Decode(value, &totals)
+ if err != nil {
+ return
+ }
+
+ return
+}
+
+func (r *accountsReader) AccountsHashRound(ctx context.Context) (hashrnd basics.Round, err error) {
+ // TODO: catchpoint
+ return
+}
+
+func (r *accountsReader) LookupAccountAddressFromAddressID(ctx context.Context, ref trackerdb.AccountRef) (address basics.Address, err error) {
+ // TODO: catchpoint
+ return
+}
+
+func (r *accountsReader) LookupAccountRowID(addr basics.Address) (ref trackerdb.AccountRef, err error) {
+ // Note: [perf] this is not a very cheap operation since we have to pull up the entire record
+ acc, err := r.LookupAccount(addr)
+ if err != nil {
+ return
+ }
+
+ if acc.Ref == nil {
+ return nil, trackerdb.ErrNotFound
+ }
+
+ return acc.Ref, nil
+}
+
+func (r *accountsReader) LookupResourceDataByAddrID(accRef trackerdb.AccountRef, aidx basics.CreatableIndex) (data []byte, err error) {
+ // TODO: this can probably get removed in favor of LookupResources
+ // the only issue here is that the only caller of this is not doing anything with the ctype
+ // so we might have to change the signature of LookupResources to skip the ctype, which might be reasonable
+ if accRef == nil {
+ return data, trackerdb.ErrNotFound
+ }
+ xref := accRef.(accountRef)
+
+ value, closer, err := r.kvr.Get(resourceKey(xref.addr, aidx))
+ if err != nil {
+ return
+ }
+ defer closer.Close()
+
+ return value, nil
+}
+
+func (r *accountsReader) TotalResources(ctx context.Context) (total uint64, err error) {
+ // TODO: catchpoint
+ return
+}
+
+func (r *accountsReader) TotalAccounts(ctx context.Context) (total uint64, err error) {
+ // TODO: catchpoint
+ return
+}
+
+func (r *accountsReader) TotalKVs(ctx context.Context) (total uint64, err error) {
+ // TODO: catchpoint
+ return
+}
+
+// TODO: this replicates some functionality from LookupOnlineHistory, implemented for onlineAccountsReader
+func (r *accountsReader) LookupOnlineAccountDataByAddress(addr basics.Address) (ref trackerdb.OnlineAccountRef, data []byte, err error) {
+ low := onlineAccountOnlyPartialKey(addr)
+ high := onlineAccountOnlyPartialKey(addr)
+ high[len(high)-1]++
+ iter := r.kvr.NewIter(low, high, true)
+ defer iter.Close()
+
+ if iter.Next() {
+ // key is <prefix>-<addr>-<rnd> = <content>
+ key := iter.Key()
+
+ rndOffset := len(kvPrefixOnlineAccount) + 1 + 32 + 1
+ u64Rnd := binary.BigEndian.Uint64(key[rndOffset : rndOffset+8])
+
+ addrOffset := len(kvPrefixOnlineAccount) + 1
+ var addr basics.Address
+ copy(addr[:], key[addrOffset:addrOffset+32])
+
+ data, err = iter.Value()
+ if err != nil {
+ return
+ }
+
+ var oa trackerdb.BaseOnlineAccountData
+ err = protocol.Decode(data, &oa)
+ if err != nil {
+ return
+ }
+
+ ref = onlineAccountRef{
+ addr: addr,
+ round: basics.Round(u64Rnd),
+ normBalance: oa.NormalizedOnlineBalance(r.proto),
+ }
+ } else {
+ err = trackerdb.ErrNotFound
+ return
+ }
+
+ return
+}
+
+// AccountsOnlineTop returns the top n online accounts starting at position offset
+// (that is, the top offset'th account through the top offset+n-1'th account).
+//
+// The accounts are sorted by their normalized balance and address. The normalized
+// balance has to do with the reward parts of online account balances. See the
+// normalization procedure in AccountData.NormalizedOnlineBalance().
+//
+// Note that this does not check if the accounts have a vote key valid for any
+// particular round (past, present, or future).
+func (r *accountsReader) AccountsOnlineTop(rnd basics.Round, offset uint64, n uint64, proto config.ConsensusParams) (data map[basics.Address]*ledgercore.OnlineAccount, err error) {
+ // The SQL before the impl
+ // SELECT
+ // address, normalizedonlinebalance, data, max(updround) FROM onlineaccounts
+ // WHERE updround <= ?
+ // GROUP BY address HAVING normalizedonlinebalance > 0
+ // ORDER BY normalizedonlinebalance DESC, address
+ // DESC LIMIT ?
+ // OFFSET ?
+
+ // initialize return map
+ data = make(map[basics.Address]*ledgercore.OnlineAccount)
+
+ // prepare iter over online accounts (by balance)
+ low := []byte(kvPrefixOnlineAccountBalance)
+ low = append(low, "-"...)
+ high := onlineAccountBalanceOnlyPartialKey(rnd)
+ high[len(high)-1]++
+ // reverse order iterator to get high-to-low
+ iter := r.kvr.NewIter(low, high, true)
+ defer iter.Close()
+
+ var value []byte
+
+ // first, drop the results from 0 to the offset
+ for i := uint64(0); i < offset; i++ {
+ iter.Next()
+ }
+
+ // add the other results to the map
+ for i := uint64(0); i < n; i++ {
+ // if no more results, return early
+ if !iter.Next() {
+ return
+ }
+
+ // key is <prefix>-<rnd>-<balance>-<addr> = <content>
+ key := iter.Key()
+
+ // TODO: make this cleaner
+ // get the offset where the address starts
+ offset := len(kvPrefixOnlineAccountBalance) + 1 + 8 + 1 + 8 + 1
+
+ // extract address
+ var addr basics.Address
+ copy(addr[:], key[offset:])
+
+ // skip if already in map
+ if _, ok := data[addr]; ok {
+ continue
+ }
+
+ value, err = iter.Value()
+ if err != nil {
+ return
+ }
+
+ oa := trackerdb.BaseOnlineAccountData{}
+ err = protocol.Decode(value, &oa)
+ if err != nil {
+ return
+ }
+ // load the data as a ledgercore OnlineAccount
+ data[addr] = &ledgercore.OnlineAccount{
+ Address: addr,
+ MicroAlgos: oa.MicroAlgos,
+ RewardsBase: oa.RewardsBase,
+ NormalizedOnlineBalance: oa.NormalizedOnlineBalance(proto),
+ VoteFirstValid: oa.VoteFirstValid,
+ VoteLastValid: oa.VoteLastValid,
+ StateProofID: oa.StateProofID,
+ }
+ }
+ return
+}
+
+func (r *accountsReader) AccountsOnlineRoundParams() (onlineRoundParamsData []ledgercore.OnlineRoundParamsData, endRound basics.Round, err error) {
+ // The SQL at the time of writing:
+ //
+ // SELECT rnd, data FROM onlineroundparamstail ORDER BY rnd ASC
+
+ start := []byte(kvOnlineAccountRoundParams + "-")
+ end := []byte(kvOnlineAccountRoundParams + ".")
+ iter := r.kvr.NewIter(start, end, false)
+ defer iter.Close()
+
+ var value []byte
+
+ for iter.Next() {
+ // read the key
+ // schema: <prefix>-<rnd>
+ key := iter.Key()
+
+ // extract the round from the key
+ rndOffset := len(kvOnlineAccountRoundParams) + 1
+ u64Rnd := binary.BigEndian.Uint64(key[rndOffset : rndOffset+8])
+ // assign current item round as endRound
+ endRound = basics.Round(u64Rnd)
+
+ // get value for current item in the iterator
+ value, err = iter.Value()
+ if err != nil {
+ return nil, endRound, err
+ }
+
+ // decode the param
+ roundParams := ledgercore.OnlineRoundParamsData{}
+ err = protocol.Decode(value, &roundParams)
+ if err != nil {
+ return nil, endRound, err
+ }
+
+ // add the params to the return list
+ onlineRoundParamsData = append(onlineRoundParamsData, roundParams)
+ }
+
+ return
+}
+
+// OnlineAccountsAll returns all online accounts up to a provided maximum
+// the returned list of PersistedOnlineAccountData includes all of the available
+// data for each included account in ascending order of account and round
+// (example [account-1-round-1, account1-round-2, ..., account2-round-1, ...])
+func (r *accountsReader) OnlineAccountsAll(maxAccounts uint64) ([]trackerdb.PersistedOnlineAccountData, error) {
+ // The SQL at the time of impl:
+ //
+ // SELECT rowid, address, updround, data
+ // FROM onlineaccounts
+ // ORDER BY address, updround ASC
+ //
+ // Note: the SQL implementation does not seem to load the current db round to the resulting objects
+
+ // read the current db round
+ var round basics.Round
+ round, err := r.AccountsRound()
+ if err != nil {
+ return nil, err
+ }
+
+ low := []byte(kvPrefixOnlineAccount + "-")
+ high := []byte(kvPrefixOnlineAccount + ".")
+ iter := r.kvr.NewIter(low, high, false)
+ defer iter.Close()
+
+ result := make([]trackerdb.PersistedOnlineAccountData, 0, maxAccounts)
+
+ var value []byte
+ var updround uint64
+
+ // keep track of the most recently seen account so we can tally up the total number seen
+ lastAddr := basics.Address{}
+ seen := uint64(0)
+
+ for iter.Next() {
+ pitem := trackerdb.PersistedOnlineAccountData{Round: round}
+
+ // schema: <prefix>-<addr>-<rnd>
+ key := iter.Key()
+
+ addrOffset := len(kvPrefixOnlineAccount) + 1
+ var addr basics.Address
+ copy(addr[:], key[addrOffset:addrOffset+32])
+ // extract updround, it's the last section after the "-"
+
+ rndOffset := addrOffset + 32 + 1
+ updround = binary.BigEndian.Uint64(key[rndOffset : rndOffset+8])
+
+ // load addr, round and data into the persisted item
+ pitem.Addr = addr
+ pitem.UpdRound = basics.Round(updround)
+ // get value for current item in the iterator
+ value, err = iter.Value()
+ if err != nil {
+ return nil, err
+ }
+ // decode raw value
+ err = protocol.Decode(value, &pitem.AccountData)
+ if err != nil {
+ return nil, err
+ }
+ // set ref
+ normBalance := pitem.AccountData.NormalizedOnlineBalance(r.proto)
+ pitem.Ref = onlineAccountRef{addr, normBalance, pitem.UpdRound}
+ // if maxAccounts is supplied, potentially stop reading data if we've collected enough
+ if maxAccounts > 0 {
+ // we have encountered a new address
+ if !bytes.Equal(addr[:], lastAddr[:]) {
+ copy(lastAddr[:], addr[:])
+ seen++
+ }
+ // this newest account seen is beyond the maxAccounts requested, meaning we've seen all the data we need
+ if seen > maxAccounts {
+ break
+ }
+ }
+
+ // append entry to accum
+ result = append(result, pitem)
+ }
+
+ return result, nil
+}
+
+// ExpiredOnlineAccountsForRound implements trackerdb.AccountsReaderExt
+func (r *accountsReader) ExpiredOnlineAccountsForRound(rnd basics.Round, voteRnd basics.Round, proto config.ConsensusParams, rewardsLevel uint64) (data map[basics.Address]*ledgercore.OnlineAccountData, err error) {
+ // The SQL at the time of writing:
+ //
+ // SELECT address, data, max(updround)
+ // FROM onlineaccounts
+ // WHERE updround <= ? <---- ? = rnd
+ // GROUP BY address
+ // HAVING votelastvalid < ? and votelastvalid > 0 <---- ? = voteRnd
+ // ORDER BY address
+
+ // initialize return map
+ data = make(map[basics.Address]*ledgercore.OnlineAccountData)
+ expired := make(map[basics.Address]struct{})
+
+ // prepare iter over online accounts (by balance)
+ low := []byte(kvPrefixOnlineAccountBalance)
+ low = append(low, "-"...)
+ high := onlineAccountBalanceOnlyPartialKey(rnd)
+ high[len(high)-1]++
+ // reverse order iterator to get high-to-low
+ iter := r.kvr.NewIter(low, high, true)
+ defer iter.Close()
+
+ var value []byte
+
+ // add the other results to the map
+ for iter.Next() {
+ // key is <prefix>-<rnd>-<balance>-<addr> = <content>
+ key := iter.Key()
+
+ // get the addrOffset where the address starts
+ addrOffset := len(kvPrefixOnlineAccountBalance) + 1 + 8 + 1 + 8 + 1
+
+ // extract address
+ var addr basics.Address
+ copy(addr[:], key[addrOffset:])
+
+ // skip if already in map
+ // we keep only the one with `max(updround)`
+ // the reverse iter makes us hit the max first
+ if _, ok := data[addr]; ok {
+ continue
+ }
+ // when the a ccount is expired we do not add it to the data
+ // but we might have an older version that is not expired show up
+ // this would be wrong, so we skip those accounts if the latest version is expired
+ if _, ok := expired[addr]; ok {
+ continue
+ }
+
+ value, err = iter.Value()
+ if err != nil {
+ return
+ }
+
+ oa := trackerdb.BaseOnlineAccountData{}
+ err = protocol.Decode(value, &oa)
+ if err != nil {
+ return
+ }
+
+ // filter by vote expiration
+ // sql: HAVING votelastvalid < ? and votelastvalid > 0
+ // Note: we might have to add an extra index during insert if this doing this in memory becomes a perf issue
+ if !(oa.VoteLastValid < voteRnd && oa.VoteLastValid > 0) {
+ expired[addr] = struct{}{}
+ continue
+ }
+
+ // load the data as a ledgercore OnlineAccount
+ oadata := oa.GetOnlineAccountData(proto, rewardsLevel)
+ data[addr] = &oadata
+ }
+
+ return
+}
+
+func (r *accountsReader) LoadTxTail(ctx context.Context, dbRound basics.Round) (roundData []*trackerdb.TxTailRound, roundHash []crypto.Digest, baseRound basics.Round, err error) {
+ // The SQL at the time of writing:
+ //
+ // "SELECT rnd, data FROM txtail ORDER BY rnd DESC"
+
+ start := []byte(kvTxTail + "-")
+ end := []byte(kvTxTail + ".")
+ iter := r.kvr.NewIter(start, end, true)
+ defer iter.Close()
+
+ var value []byte
+
+ expectedRound := dbRound
+ for iter.Next() {
+ // read the key
+ key := iter.Key()
+
+ // extract the txTail round from the key
+ rndOffset := len(kvTxTail) + 1
+ u64Rnd := binary.BigEndian.Uint64(key[rndOffset : rndOffset+8])
+ round := basics.Round(u64Rnd)
+ // check that we are on the right round
+ if round != expectedRound {
+ return nil, nil, 0, fmt.Errorf("txtail table contain unexpected round %d; round %d was expected", round, expectedRound)
+ }
+
+ // get value for current item in the iterator
+ value, err = iter.Value()
+ if err != nil {
+ return nil, nil, 0, err
+ }
+
+ // decode the TxTail
+ tail := &trackerdb.TxTailRound{}
+ err = protocol.Decode(value, tail)
+ if err != nil {
+ return nil, nil, 0, err
+ }
+
+ // add the tail
+ roundData = append(roundData, tail)
+ // add the hash
+ roundHash = append(roundHash, crypto.Hash(value))
+
+ // step the round down (we expect the "previous" round next..)
+ expectedRound--
+ }
+
+ // reverse the array ordering in-place so that it would be incremental order.
+ for i := 0; i < len(roundData)/2; i++ {
+ roundData[i], roundData[len(roundData)-i-1] = roundData[len(roundData)-i-1], roundData[i]
+ roundHash[i], roundHash[len(roundHash)-i-1] = roundHash[len(roundHash)-i-1], roundHash[i]
+ }
+ return roundData, roundHash, expectedRound + 1, nil
+}
+
+func (r *accountsReader) LoadAllFullAccounts(ctx context.Context, balancesTable string, resourcesTable string, acctCb func(basics.Address, basics.AccountData)) (count int, err error) {
+ // TODO: catchpoint CLI
+ return
+}
+
+func (r *accountsReader) Testing() trackerdb.AccountsReaderTestExt {
+ // TODO: this can wait
+ return nil
+}
diff --git a/ledger/store/trackerdb/generickv/accounts_ext_writer.go b/ledger/store/trackerdb/generickv/accounts_ext_writer.go
new file mode 100644
index 000000000..bdb05b913
--- /dev/null
+++ b/ledger/store/trackerdb/generickv/accounts_ext_writer.go
@@ -0,0 +1,265 @@
+// Copyright (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 <https://www.gnu.org/licenses/>.
+
+package generickv
+
+import (
+ "context"
+ "encoding/binary"
+
+ "github.com/algorand/go-algorand/data/basics"
+ "github.com/algorand/go-algorand/ledger/ledgercore"
+ "github.com/algorand/go-algorand/ledger/store/trackerdb"
+ "github.com/algorand/go-algorand/protocol"
+)
+
+func (w *accountsWriter) AccountsReset(ctx context.Context) error {
+ // TODO: catchpoint
+ return nil
+}
+
+func (w *accountsWriter) ResetAccountHashes(ctx context.Context) (err error) {
+ // TODO: catchpoint
+ return
+}
+
+func (w *accountsWriter) TxtailNewRound(ctx context.Context, baseRound basics.Round, roundData [][]byte, forgetBeforeRound basics.Round) error {
+ // The SQL at the time fo writing:
+ //
+ // for i, data := range roundData:
+ // the inserted rnd value is baseRound + i
+ //
+ // INSERT INTO txtail(rnd, data) VALUES(?, ?)
+ //
+ // then it also cleans up everything before `forgetBeforeRound`:
+ //
+ // DELETE FROM txtail WHERE rnd < ?
+
+ // insert the new txTail's
+ for i, data := range roundData {
+ rnd := basics.Round(int(baseRound) + i)
+ err := w.kvw.Set(txTailKey(rnd), data)
+ if err != nil {
+ return err
+ }
+ }
+
+ // delete old ones
+ start := []byte(kvTxTail + "-")
+ end := txTailKey(forgetBeforeRound)
+ err := w.kvw.DeleteRange(start, end)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (w *accountsWriter) UpdateAccountsRound(rnd basics.Round) (err error) {
+ // The SQL at the time of writing:
+ //
+ // UPDATE acctrounds SET rnd=? WHERE id='acctbase' AND rnd<?",
+
+ // TODO: read the row for sanity? wont help the kv with race conditions, but we will need it for test parity
+ // inside a batch we wont have a read ptr..
+
+ // write round entry
+ raw := bigEndianUint64(uint64(rnd))
+ err = w.kvw.Set(roundKey(), raw)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (w *accountsWriter) UpdateAccountsHashRound(ctx context.Context, hashRound basics.Round) (err error) {
+ // TODO: catchpoint
+ return nil
+}
+
+func (w *accountsWriter) AccountsPutTotals(totals ledgercore.AccountTotals, catchpointStaging bool) (err error) {
+ // The SQL at the time of impl:
+ //
+ // id := ""
+ // if catchpointStaging {
+ // id = "catchpointStaging"
+ // }
+ // "REPLACE INTO accounttotals
+ // (id, online, onlinerewardunits, offline, offlinerewardunits, notparticipating, notparticipatingrewardunits, rewardslevel)
+ // VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
+
+ // write totals entry
+ raw := protocol.Encode(&totals)
+ err = w.kvw.Set(totalsKey(catchpointStaging), raw)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (w *accountsWriter) OnlineAccountsDelete(forgetBefore basics.Round) (err error) {
+ // The SQL at the time of impl:
+ //
+ // SELECT
+ // rowid, address, updRound, data
+ // FROM onlineaccounts
+ // WHERE updRound < ?
+ // ORDER BY address, updRound DESC
+ //
+ // The it would delete by rowid in chunks with:
+ //
+ // DELETE FROM onlineaccounts WHERE rowid IN (..)
+
+ // On the KV implmentation:
+ //
+ // We have two ranges of keys associated with online accounts:
+ // - the `onlineAccountKey(address, round)` -> "-".join(kvPrefixOnlineAccount, addr, round)
+ // - and the `onlineAccountBalanceKey(round, normBalance, addr) -> "-".join(kvPrefixOnlineAccountBalance, round, normBalance, addr)
+
+ // 1. read from the `onlineAccountBalanceKey` range since we can the addr's that will need to be deleted
+ start := []byte(kvPrefixOnlineAccountBalance + "-")
+ end := []byte(kvPrefixOnlineAccountBalance + "-")
+ end = append(end, bigEndianUint64(uint64(forgetBefore))...)
+ iter := w.kvr.NewIter(start, end, true)
+ defer iter.Close()
+
+ toDeletePrimaryIndex := make([]struct {
+ basics.Address
+ basics.Round
+ }, 0)
+
+ toDeleteSecondaryIndex := make([][]byte, 0)
+
+ var prevAddr basics.Address
+
+ for iter.Next() {
+ // read the key
+ // schema: <prefix>-<rnd>-<balance>-<addr>
+ key := iter.Key()
+
+ // extract the round from the key (offset: 1)
+ rndOffset := len(kvPrefixOnlineAccountBalance) + 1
+ u64Rnd := binary.BigEndian.Uint64(key[rndOffset : rndOffset+8])
+ round := basics.Round(u64Rnd)
+
+ // get the offset where the address starts
+ addrOffset := len(kvPrefixOnlineAccountBalance) + 1 + 8 + 1 + 8 + 1
+ var addr basics.Address
+ copy(addr[:], key[addrOffset:addrOffset+32])
+
+ if addr != prevAddr {
+ // new address
+ // if the first (latest) entry is
+ // - offline then delete all
+ // - online then safe to delete all previous except this first (latest)
+
+ // reset the state
+ prevAddr = addr
+
+ // delete on voting empty
+ var oad trackerdb.BaseOnlineAccountData
+ var data []byte
+ data, err = iter.Value()
+ if err != nil {
+ return err
+ }
+ err = protocol.Decode(data, &oad)
+ if err != nil {
+ return err
+ }
+ if oad.IsVotingEmpty() {
+ // delete this and all subsequent
+ toDeletePrimaryIndex = append(toDeletePrimaryIndex, struct {
+ basics.Address
+ basics.Round
+ }{addr, round})
+ toDeleteSecondaryIndex = append(toDeleteSecondaryIndex, key)
+ }
+
+ // restart the loop
+ // if there are some subsequent entries, they will deleted on the next iteration
+ // if no subsequent entries, the loop will reset the state and the latest entry does not get deleted
+ continue
+ }
+
+ // mark the item for deletion
+ toDeletePrimaryIndex = append(toDeletePrimaryIndex, struct {
+ basics.Address
+ basics.Round
+ }{addr, round})
+ toDeleteSecondaryIndex = append(toDeleteSecondaryIndex, key)
+ }
+
+ // 2. delete the individual addr+round entries
+ for _, item := range toDeletePrimaryIndex {
+ // TODO: [perf] we might be able to optimize this with a SingleDelete call
+ err = w.kvw.Delete(onlineAccountKey(item.Address, item.Round))
+ if err != nil {
+ return
+ }
+ }
+
+ // 3. delete the range from `onlineAccountBalanceKey`
+ for _, key := range toDeleteSecondaryIndex {
+ // TODO: [perf] we might be able to optimize this with a SingleDelete call
+ err = w.kvw.Delete(key)
+ if err != nil {
+ return
+ }
+ }
+
+ return
+}
+
+func (w *accountsWriter) AccountsPutOnlineRoundParams(onlineRoundParamsData []ledgercore.OnlineRoundParamsData, startRound basics.Round) error {
+ // The SQL at the time of impl:
+ //
+ // for i, data := range onlineRoundParamsData {
+ // the inserted rnd value is startRound + i
+ //
+ // INSERT INTO onlineroundparamstail (rnd, data) VALUES (?, ?)
+ //
+
+ // insert the round params
+ for i := range onlineRoundParamsData {
+ rnd := basics.Round(int(startRound) + i)
+ raw := protocol.Encode(&onlineRoundParamsData[i])
+ err := w.kvw.Set(onlineAccountRoundParamsKey(rnd), raw)
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func (w *accountsWriter) AccountsPruneOnlineRoundParams(deleteBeforeRound basics.Round) error {
+ // The SQL at the time of impl:
+ //
+ // DELETE FROM onlineroundparamstail WHERE rnd<?
+
+ // delete old ones
+ start := []byte(kvOnlineAccountRoundParams + "-")
+ end := onlineAccountRoundParamsKey(deleteBeforeRound)
+ err := w.kvw.DeleteRange(start, end)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
diff --git a/ledger/store/trackerdb/generickv/accounts_reader.go b/ledger/store/trackerdb/generickv/accounts_reader.go
new file mode 100644
index 000000000..b6806a109
--- /dev/null
+++ b/ledger/store/trackerdb/generickv/accounts_reader.go
@@ -0,0 +1,405 @@
+// Copyright (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 <https://www.gnu.org/licenses/>.
+
+package generickv
+
+import (
+ "encoding/binary"
+ "fmt"
+ "io"
+
+ "github.com/algorand/go-algorand/config"
+ "github.com/algorand/go-algorand/data/basics"
+ "github.com/algorand/go-algorand/ledger/store/trackerdb"
+ "github.com/algorand/go-algorand/protocol"
+)
+
+// KvRead is a low level KV db interface for reading.
+type KvRead interface {
+ Get(key []byte) ([]byte, io.Closer, error)
+ NewIter(low, high []byte, reverse bool) KvIter
+}
+
+// KvIter is a low level KV iterator.
+type KvIter interface {
+ Next() bool
+ Key() []byte
+ KeySlice() Slice
+ Value() ([]byte, error)
+ ValueSlice() (Slice, error)
+ Valid() bool
+ Close()
+}
+
+// Slice is a low level slice used during the KV iterator.
+type Slice interface {
+ Data() []byte
+ Free()
+ Size() int
+ Exists() bool
+}
+
+type accountsReader struct {
+ kvr KvRead
+ proto config.ConsensusParams
+}
+
+// MakeAccountsReader returns a kv db agnostic AccountsReader.
+func MakeAccountsReader(kvr KvRead, proto config.ConsensusParams) *accountsReader {
+ return &accountsReader{kvr, proto}
+}
+
+func (r *accountsReader) LookupAccount(addr basics.Address) (data trackerdb.PersistedAccountData, err error) {
+ // SQL impl at time of writing:
+ //
+ // SELECT
+ // accountbase.rowid,
+ // acctrounds.rnd,
+ // accountbase.data
+ // FROM acctrounds
+ // LEFT JOIN accountbase ON address=?
+ // WHERE id='acctbase'
+
+ data.Addr = addr
+
+ // read the current db round
+ data.Round, err = r.AccountsRound()
+ if err != nil {
+ return
+ }
+
+ value, closer, err := r.kvr.Get(accountKey(addr))
+ if err == trackerdb.ErrNotFound {
+ // Note: the SQL implementation returns a data value and no error even when the account does not exist.
+ return data, nil
+ } else if err != nil {
+ return
+ }
+ defer closer.Close()
+
+ err = protocol.Decode(value, &data.AccountData)
+ if err != nil {
+ return
+ }
+
+ data.Ref = accountRef{addr}
+
+ return
+}
+
+func (r *accountsReader) LookupResources(addr basics.Address, aidx basics.CreatableIndex, ctype basics.CreatableType) (data trackerdb.PersistedResourcesData, err error) {
+ data.Aidx = aidx
+
+ // read the current db round
+ data.Round, err = r.AccountsRound()
+ if err != nil {
+ return
+ }
+
+ value, closer, err := r.kvr.Get(resourceKey(addr, aidx))
+ if err == trackerdb.ErrNotFound {
+ // Note: the SQL implementation returns a data value and no error even when the account does not exist.
+ data.Data = trackerdb.MakeResourcesData(0)
+ return data, nil
+ } else if err != nil {
+ err = fmt.Errorf("unable to query resource data for address %v aidx %v ctype %v : %w", addr, aidx, ctype, err)
+ return
+ }
+ defer closer.Close()
+
+ err = protocol.Decode(value, &data.Data)
+ if err != nil {
+ return
+ }
+
+ // Note: the ctype is not filtered during the query, but rather asserted to be what the caller expected
+ if ctype == basics.AssetCreatable && !data.Data.IsAsset() {
+ err = fmt.Errorf("lookupResources asked for an asset but got %v", data.Data)
+ }
+ if ctype == basics.AppCreatable && !data.Data.IsApp() {
+ err = fmt.Errorf("lookupResources asked for an app but got %v", data.Data)
+ }
+
+ data.AcctRef = accountRef{addr}
+
+ return
+}
+
+func (r *accountsReader) LookupAllResources(addr basics.Address) (data []trackerdb.PersistedResourcesData, rnd basics.Round, err error) {
+ low := resourceAddrOnlyPartialKey(addr)
+ high := resourceAddrOnlyPartialKey(addr)
+ high[len(high)-1]++
+
+ iter := r.kvr.NewIter(low, high, false)
+ defer iter.Close()
+
+ var value []byte
+ var aidx uint64
+
+ // read the current db round
+ rnd, err = r.AccountsRound()
+ if err != nil {
+ return
+ }
+
+ for iter.Next() {
+ pitem := trackerdb.PersistedResourcesData{AcctRef: accountRef{addr}, Round: rnd}
+
+ // read the key to parse the aidx
+ // key is <prefix>-<addr>-<aidx> = <content>
+ key := iter.Key()
+
+ aidxOffset := len(kvPrefixResource) + 1 + 32 + 1
+ aidx = binary.BigEndian.Uint64(key[aidxOffset : aidxOffset+8])
+ pitem.Aidx = basics.CreatableIndex(aidx)
+
+ // get value for current item in the iterator
+ value, err = iter.Value()
+ if err != nil {
+ return
+ }
+ // decode raw value
+ err = protocol.Decode(value, &pitem.Data)
+ if err != nil {
+ return
+ }
+ // append entry to accum
+ data = append(data, pitem)
+ }
+
+ return
+}
+
+func (r *accountsReader) LookupKeyValue(key string) (pv trackerdb.PersistedKVData, err error) {
+ // read the current db round
+ pv.Round, err = r.AccountsRound()
+ if err != nil {
+ return
+ }
+
+ value, closer, err := r.kvr.Get(appKvKey(key))
+ if err == trackerdb.ErrNotFound {
+ // Note: the SQL implementation returns a data value and no error even when the account does not exist.
+ return pv, nil
+ } else if err != nil {
+ return
+ }
+ defer closer.Close()
+
+ pv.Value = value
+
+ return
+}
+
+// TODO: lifted from sql.go, we might want to refactor it
+func keyPrefixIntervalPreprocessing(prefix []byte) ([]byte, []byte) {
+ if prefix == nil {
+ prefix = []byte{}
+ }
+ prefixIncr := make([]byte, len(prefix))
+ copy(prefixIncr, prefix)
+ for i := len(prefix) - 1; i >= 0; i-- {
+ currentByteIncr := int(prefix[i]) + 1
+ if currentByteIncr > 0xFF {
+ prefixIncr = prefixIncr[:len(prefixIncr)-1]
+ continue
+ }
+ prefixIncr[i] = byte(currentByteIncr)
+ return prefix, prefixIncr
+ }
+ return prefix, nil
+}
+
+func (r *accountsReader) LookupKeysByPrefix(prefix string, maxKeyNum uint64, results map[string]bool, resultCount uint64) (round basics.Round, err error) {
+ // SQL at time of writing:
+ //
+ // SELECT acctrounds.rnd, kvstore.key
+ // FROM acctrounds LEFT JOIN kvstore ON kvstore.key >= ? AND kvstore.key < ?
+ // WHERE id='acctbase'
+
+ // read the current db round
+ round, err = r.AccountsRound()
+ if err != nil {
+ return
+ }
+
+ start, end := keyPrefixIntervalPreprocessing([]byte(prefix))
+
+ iter := r.kvr.NewIter(start, end, false)
+ defer iter.Close()
+
+ var value []byte
+
+ for iter.Next() {
+ // end iteration if we reached max results
+ if resultCount == maxKeyNum {
+ return
+ }
+
+ // read the key
+ key := string(iter.Key())
+
+ // get value for current item in the iterator
+ value, err = iter.Value()
+ if err != nil {
+ return
+ }
+
+ // mark if the key has data on the result map
+ results[key] = len(value) > 0
+
+ // inc results in range
+ resultCount++
+ }
+
+ return
+}
+
+func (r *accountsReader) ListCreatables(maxIdx basics.CreatableIndex, maxResults uint64, ctype basics.CreatableType) (results []basics.CreatableLocator, dbRound basics.Round, err error) {
+ // The old SQL impl:
+ //
+ // SELECT
+ // acctrounds.rnd,
+ // assetcreators.asset,
+ // assetcreators.creator
+ // FROM acctrounds
+ // LEFT JOIN assetcreators ON assetcreators.asset <= ? AND assetcreators.ctype = ?
+ // WHERE acctrounds.id='acctbase'
+ // ORDER BY assetcreators.asset desc
+ // LIMIT ?
+
+ // read the current db round
+ dbRound, err = r.AccountsRound()
+ if err != nil {
+ return
+ }
+
+ start := []byte(fmt.Sprintf("%s-", kvPrefixCreatorIndex))
+ end := creatableKey(basics.CreatableIndex(uint64(maxIdx) + 1))
+
+ // assets are returned in descending order of cidx
+ iter := r.kvr.NewIter(start, end, true)
+ defer iter.Close()
+
+ var value []byte
+ var resultCount uint64
+ var cidx uint64
+
+ for iter.Next() {
+ // end iteration if we reached max results
+ if resultCount == maxResults {
+ return
+ }
+
+ // read the key
+ // schema: <prefix>-<cidx>
+ key := iter.Key()
+
+ // extract cidx
+ cidxOffset := len(kvPrefixCreatorIndex) + 1
+ cidx = binary.BigEndian.Uint64(key[cidxOffset : cidxOffset+8])
+
+ // get value for current item in the iterator
+ value, err = iter.Value()
+ if err != nil {
+ return
+ }
+
+ // decode the raw value
+ var entry creatableEntry
+ err = protocol.Decode(value, &entry)
+ if err != nil {
+ return
+ }
+
+ // TODO: the ctype as part of key makes this filterable during the iter
+ if entry.Ctype != ctype {
+ continue
+ }
+
+ // create the "creatable" struct
+ cl := basics.CreatableLocator{Type: ctype, Index: basics.CreatableIndex(cidx)}
+ copy(cl.Creator[:], entry.CreatorAddr)
+
+ // add it to the the results
+ results = append(results, cl)
+
+ // inc results in range
+ resultCount++
+ }
+
+ // read the current db round
+ dbRound, err = r.AccountsRound()
+ if err != nil {
+ return
+ }
+
+ return
+}
+
+func (r *accountsReader) LookupCreator(cidx basics.CreatableIndex, ctype basics.CreatableType) (addr basics.Address, ok bool, dbRound basics.Round, err error) {
+ // The old SQL impl:
+ //
+ // SELECT
+ // acctrounds.rnd,
+ // assetcreators.creator
+ // FROM acctrounds
+ // LEFT JOIN assetcreators ON asset = ? AND ctype = ?
+ // WHERE id='acctbase'
+
+ // read the current db round
+ dbRound, err = r.AccountsRound()
+ if err != nil {
+ return
+ }
+
+ value, closer, err := r.kvr.Get(creatableKey(cidx))
+ if err == trackerdb.ErrNotFound {
+ // the record does not exist
+ // clean up the error and just return ok=false
+ err = nil
+ ok = false
+ return
+ } else if err != nil {
+ return
+ }
+ defer closer.Close()
+
+ // decode the raw value
+ var entry creatableEntry
+ err = protocol.Decode(value, &entry)
+ if err != nil {
+ return
+ }
+
+ // assert that the ctype is the one expected
+ if entry.Ctype != ctype {
+ ok = false
+ return
+ }
+
+ // copy the addr to the return
+ copy(addr[:], entry.CreatorAddr)
+
+ // mark result as ok
+ ok = true
+
+ return
+}
+
+func (r *accountsReader) Close() {
+
+}
diff --git a/ledger/store/trackerdb/generickv/accounts_writer.go b/ledger/store/trackerdb/generickv/accounts_writer.go
new file mode 100644
index 000000000..b2f808a43
--- /dev/null
+++ b/ledger/store/trackerdb/generickv/accounts_writer.go
@@ -0,0 +1,188 @@
+// Copyright (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 <https://www.gnu.org/licenses/>.
+
+package generickv
+
+import (
+ "github.com/algorand/go-algorand/data/basics"
+ "github.com/algorand/go-algorand/ledger/store/trackerdb"
+ "github.com/algorand/go-algorand/protocol"
+)
+
+// KvWrite is a low level KV db interface for writing.
+type KvWrite interface {
+ Set(key, value []byte) error
+ Delete(key []byte) error
+ DeleteRange(start, end []byte) error
+}
+
+type accountsWriter struct {
+ kvw KvWrite
+ kvr KvRead
+}
+
+type accountRef struct {
+ addr basics.Address
+}
+
+func (ref accountRef) AccountRefMarker() {}
+
+type resourceRef struct {
+ addr basics.Address
+ aidx basics.CreatableIndex
+}
+
+func (ref resourceRef) ResourceRefMarker() {}
+
+type creatableRef struct {
+ cidx basics.CreatableIndex
+}
+
+func (ref creatableRef) CreatableRefMarker() {}
+
+// MakeAccountsWriter returns a kv db agnostic AccountsWriter.
+// TODO: we should discuss what is the best approach for this `kvr KvRead`.
+// the problem is that `OnlineAccountsDelete` requires reading and writing.
+// the cleanest approach is to move that method to a separate interface, and have it only be available on 'transactions'.
+// although a snapshot+batch should be able to support it too since its all reads, then writes.
+func MakeAccountsWriter(kvw KvWrite, kvr KvRead) *accountsWriter {
+ return &accountsWriter{kvw, kvr}
+}
+
+func (w *accountsWriter) InsertAccount(addr basics.Address, normBalance uint64, data trackerdb.BaseAccountData) (ref trackerdb.AccountRef, err error) {
+ // write account entry
+ raw := protocol.Encode(&data)
+ err = w.kvw.Set(accountKey(addr), raw)
+ if err != nil {
+ return nil, err
+ }
+
+ return accountRef{addr}, nil
+}
+
+func (w *accountsWriter) DeleteAccount(ref trackerdb.AccountRef) (rowsAffected int64, err error) {
+ xref := ref.(accountRef)
+
+ // delete account entry
+ err = w.kvw.Delete(accountKey(xref.addr))
+ if err != nil {
+ return 0, err
+ }
+
+ return 1, nil
+}
+
+func (w *accountsWriter) UpdateAccount(ref trackerdb.AccountRef, normBalance uint64, data trackerdb.BaseAccountData) (rowsAffected int64, err error) {
+ xref := ref.(accountRef)
+
+ // overwrite account entry
+ raw := protocol.Encode(&data)
+ err = w.kvw.Set(accountKey(xref.addr), raw)
+ if err != nil {
+ return 0, err
+ }
+
+ return 1, nil
+}
+
+func (w *accountsWriter) InsertResource(acctRef trackerdb.AccountRef, aidx basics.CreatableIndex, data trackerdb.ResourcesData) (ref trackerdb.ResourceRef, err error) {
+ xref := acctRef.(accountRef)
+
+ // write resource entry
+ raw := protocol.Encode(&data)
+ err = w.kvw.Set(resourceKey(xref.addr, aidx), raw)
+ if err != nil {
+ return nil, err
+ }
+
+ return resourceRef{xref.addr, aidx}, nil
+}
+
+func (w *accountsWriter) DeleteResource(acctRef trackerdb.AccountRef, aidx basics.CreatableIndex) (rowsAffected int64, err error) {
+ xref := acctRef.(accountRef)
+
+ // delete resource entry
+ err = w.kvw.Delete(resourceKey(xref.addr, aidx))
+ if err != nil {
+ return 0, err
+ }
+
+ return 1, nil
+}
+
+func (w *accountsWriter) UpdateResource(acctRef trackerdb.AccountRef, aidx basics.CreatableIndex, data trackerdb.ResourcesData) (rowsAffected int64, err error) {
+ xref := acctRef.(accountRef)
+
+ // update resource entry
+ raw := protocol.Encode(&data)
+ err = w.kvw.Set(resourceKey(xref.addr, aidx), raw)
+ if err != nil {
+ return 0, err
+ }
+
+ return 1, nil
+}
+
+func (w *accountsWriter) UpsertKvPair(key string, value []byte) error {
+ // upsert kv entry
+ err := w.kvw.Set(appKvKey(key), value)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (w *accountsWriter) DeleteKvPair(key string) error {
+ // delete kv entry
+ err := w.kvw.Delete(appKvKey(key))
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+type creatableEntry struct {
+ _struct struct{} `codec:",omitempty,omitemptyarray"`
+ Ctype basics.CreatableType
+ CreatorAddr []byte
+}
+
+func (w *accountsWriter) InsertCreatable(cidx basics.CreatableIndex, ctype basics.CreatableType, creator []byte) (ref trackerdb.CreatableRef, err error) {
+ // insert creatable entry
+ raw := protocol.Encode(&creatableEntry{Ctype: ctype, CreatorAddr: creator})
+ err = w.kvw.Set(creatableKey(cidx), raw)
+ if err != nil {
+ return
+ }
+
+ return creatableRef{cidx}, nil
+}
+
+func (w *accountsWriter) DeleteCreatable(cidx basics.CreatableIndex, ctype basics.CreatableType) (rowsAffected int64, err error) {
+ // delete creatable entry
+ err = w.kvw.Delete(creatableKey(cidx))
+ if err != nil {
+ return 0, err
+ }
+
+ return 1, nil
+}
+
+func (w *accountsWriter) Close() {
+
+}
diff --git a/ledger/store/trackerdb/generickv/catchpoint.go b/ledger/store/trackerdb/generickv/catchpoint.go
new file mode 100644
index 000000000..99c8a7d95
--- /dev/null
+++ b/ledger/store/trackerdb/generickv/catchpoint.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 <https://www.gnu.org/licenses/>.
+
+package generickv
+
+import (
+ "github.com/algorand/go-algorand/ledger/store/trackerdb"
+)
+
+type catchpoint struct{}
+
+// MakeCatchpoint returns a trackerdb.Catchpoint for a KV
+func MakeCatchpoint() trackerdb.Catchpoint {
+ return &catchpoint{}
+}
+
+// MakeCatchpointReaderWriter implements trackerdb.Catchpoint
+func (*catchpoint) MakeCatchpointReaderWriter() (trackerdb.CatchpointReaderWriter, error) {
+ panic("unimplemented")
+}
+
+// MakeCatchpointWriter implements trackerdb.Catchpoint
+func (*catchpoint) MakeCatchpointWriter() (trackerdb.CatchpointWriter, error) {
+ panic("unimplemented")
+}
+
+// MakeMerkleCommitter implements trackerdb.Catchpoint
+func (*catchpoint) MakeMerkleCommitter(staging bool) (trackerdb.MerkleCommitter, error) {
+ panic("unimplemented")
+}
+
+// MakeOrderedAccountsIter implements trackerdb.Catchpoint
+func (*catchpoint) MakeOrderedAccountsIter(accountCount int) trackerdb.OrderedAccountsIter {
+ panic("unimplemented")
+}
diff --git a/ledger/store/trackerdb/generickv/init_accounts.go b/ledger/store/trackerdb/generickv/init_accounts.go
new file mode 100644
index 000000000..af587af10
--- /dev/null
+++ b/ledger/store/trackerdb/generickv/init_accounts.go
@@ -0,0 +1,60 @@
+// Copyright (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 <https://www.gnu.org/licenses/>.
+
+package generickv
+
+import (
+ "context"
+ "testing"
+
+ "github.com/algorand/go-algorand/config"
+ "github.com/algorand/go-algorand/data/basics"
+ "github.com/algorand/go-algorand/ledger/store/trackerdb"
+ "github.com/algorand/go-algorand/protocol"
+ "github.com/stretchr/testify/require"
+)
+
+type dbForInit interface {
+ trackerdb.Store
+ KvRead
+ KvWrite
+}
+
+// AccountsInitTest initializes the database for testing with the given accounts.
+func AccountsInitTest(tb testing.TB, db dbForInit, initAccounts map[basics.Address]basics.AccountData, proto protocol.ConsensusVersion) (newDatabase bool) {
+ params := trackerdb.Params{
+ InitAccounts: initAccounts,
+ InitProto: proto,
+ }
+ _, err := RunMigrations(context.Background(), db, params, trackerdb.AccountDBVersion)
+ require.NoError(tb, err)
+ return true
+}
+
+// AccountsInitLightTest initializes the database for testing with the given accounts.
+//
+// This is duplicate due to a specific legacy test in accdeltas_test.go.
+// TODO: remove the need for this.
+func AccountsInitLightTest(tb testing.TB, db dbForInit, initAccounts map[basics.Address]basics.AccountData, proto config.ConsensusParams) (newDatabase bool, err error) {
+ params := trackerdb.Params{
+ InitAccounts: initAccounts,
+ // TODO: how do we get the correct version from the proto arg?
+ InitProto: protocol.ConsensusCurrentVersion,
+ }
+ _, err = RunMigrations(context.Background(), db, params, trackerdb.AccountDBVersion)
+ require.NoError(tb, err)
+ return true, nil
+}
diff --git a/ledger/store/trackerdb/generickv/migrations.go b/ledger/store/trackerdb/generickv/migrations.go
new file mode 100644
index 000000000..9c8e942e3
--- /dev/null
+++ b/ledger/store/trackerdb/generickv/migrations.go
@@ -0,0 +1,225 @@
+// Copyright (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 <https://www.gnu.org/licenses/>.
+
+package generickv
+
+import (
+ "context"
+ "encoding/binary"
+ "fmt"
+
+ "github.com/algorand/go-algorand/config"
+ "github.com/algorand/go-algorand/data/basics"
+ "github.com/algorand/go-algorand/ledger/ledgercore"
+ "github.com/algorand/go-algorand/ledger/store/trackerdb"
+)
+
+func getSchemaVersion(ctx context.Context, kvr KvRead) (int32, error) {
+ // read version entry
+ value, closer, err := kvr.Get(schemaVersionKey())
+ if err == trackerdb.ErrNotFound {
+ // ignore the error, return version 0
+ return 0, nil
+ } else if err != nil {
+ return 0, err
+ }
+ defer closer.Close()
+
+ // parse the bytes into a i32
+ version := int32(binary.BigEndian.Uint32(value))
+
+ return version, nil
+}
+
+func setSchemaVersion(ctx context.Context, kvw KvWrite, version int32) error {
+ // write version entry
+ raw := bigEndianUint32(uint32(version))
+ err := kvw.Set(schemaVersionKey(), raw)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+type dbForMigrations interface {
+ trackerdb.Store
+ KvRead
+ KvWrite
+}
+
+// RunMigrations runs the migrations on the store up to the target version.
+func RunMigrations(ctx context.Context, db dbForMigrations, params trackerdb.Params, targetVersion int32) (mgr trackerdb.InitParams, err error) {
+
+ dbVersion, err := getSchemaVersion(ctx, db)
+ if err != nil {
+ return
+ }
+
+ mgr.SchemaVersion = dbVersion
+ mgr.VacuumOnStartup = false
+
+ migrator := &migrator{
+ currentVersion: dbVersion,
+ targetVersion: targetVersion,
+ params: params,
+ db: db,
+ }
+
+ err = migrator.Migrate(ctx)
+ if err != nil {
+ return
+ }
+
+ mgr.SchemaVersion = migrator.currentVersion
+
+ return mgr, nil
+}
+
+type migrator struct {
+ currentVersion int32
+ targetVersion int32
+ params trackerdb.Params
+ db dbForMigrations
+}
+
+func (m *migrator) Migrate(ctx context.Context) error {
+ // we cannot rollback
+ if m.currentVersion > m.targetVersion {
+ return nil
+ }
+ // upgrade the db one version at at time
+ for m.currentVersion < m.targetVersion {
+ // run next version upgrade
+ switch m.currentVersion {
+ case 0: // initial version
+ err := m.initialVersion(ctx)
+ if err != nil {
+ return err
+ }
+ default:
+ // any other version we do nothing
+ return nil
+ }
+ }
+ return nil
+}
+
+func (m *migrator) setVersion(ctx context.Context, version int32) error {
+ // update crrent version in the db
+ err := setSchemaVersion(ctx, m.db, version)
+ if err != nil {
+ return err
+ }
+ // update current version in the migrator
+ m.currentVersion = version
+ return nil
+}
+
+func (m *migrator) initialVersion(ctx context.Context) error {
+ proto := config.Consensus[m.params.InitProto]
+
+ // TODO: make this a batch scope
+ err := m.db.TransactionContext(ctx, func(ctx context.Context, tx trackerdb.TransactionScope) (err error) {
+ aow, err := tx.MakeAccountsOptimizedWriter(true, false, false, false)
+ if err != nil {
+ return err
+ }
+
+ oaow, err := tx.MakeOnlineAccountsOptimizedWriter(true)
+ if err != nil {
+ return err
+ }
+
+ aw, err := tx.MakeAccountsWriter()
+ if err != nil {
+ return err
+ }
+
+ updRound := basics.Round(0)
+
+ // mark the db as round 0
+ err = aw.UpdateAccountsRound(updRound)
+ if err != nil {
+ return err
+ }
+
+ var ot basics.OverflowTracker
+ var totals ledgercore.AccountTotals
+
+ // insert initial accounts
+ for addr, account := range m.params.InitAccounts {
+ // build a trackerdb.BaseAccountData to pass to the DB
+ var bad trackerdb.BaseAccountData
+ bad.SetAccountData(&account)
+ // insert the account
+ _, err = aow.InsertAccount(addr, account.NormalizedOnlineBalance(proto), bad)
+ if err != nil {
+ return err
+ }
+
+ // build a ledgercore.AccountData to track the totals
+ ad := ledgercore.ToAccountData(account)
+ // track the totals
+ totals.AddAccount(proto, ad, &ot)
+
+ // insert online account (if online)
+ if bad.Status == basics.Online {
+ var baseOnlineAD trackerdb.BaseOnlineAccountData
+ baseOnlineAD.BaseVotingData = bad.BaseVotingData
+ baseOnlineAD.MicroAlgos = bad.MicroAlgos
+ baseOnlineAD.RewardsBase = bad.RewardsBase
+
+ _, err = oaow.InsertOnlineAccount(addr, account.NormalizedOnlineBalance(proto), baseOnlineAD, uint64(updRound), uint64(baseOnlineAD.VoteLastValid))
+ if err != nil {
+ return err
+ }
+ }
+ }
+
+ // make sure we didn't overflow
+ if ot.Overflowed {
+ return fmt.Errorf("overflow computing totals")
+ }
+
+ // insert the totals
+ err = aw.AccountsPutTotals(totals, false)
+ if err != nil {
+ return err
+ }
+
+ // insert online params
+ params := []ledgercore.OnlineRoundParamsData{
+ {
+ OnlineSupply: totals.Online.Money.Raw,
+ RewardsLevel: totals.RewardsLevel,
+ CurrentProtocol: m.params.InitProto,
+ },
+ }
+ err = aw.AccountsPutOnlineRoundParams(params, basics.Round(0))
+ if err != nil {
+ return err
+ }
+
+ return nil
+ })
+ if err != nil {
+ return err
+ }
+
+ // KV store starts at version 10
+ return m.setVersion(ctx, 10)
+}
diff --git a/ledger/store/trackerdb/generickv/msgp_gen.go b/ledger/store/trackerdb/generickv/msgp_gen.go
new file mode 100644
index 000000000..4f5cba66b
--- /dev/null
+++ b/ledger/store/trackerdb/generickv/msgp_gen.go
@@ -0,0 +1,156 @@
+package generickv
+
+// Code generated by github.com/algorand/msgp DO NOT EDIT.
+
+import (
+ "github.com/algorand/msgp/msgp"
+
+ "github.com/algorand/go-algorand/data/basics"
+)
+
+// The following msgp objects are implemented in this file:
+// creatableEntry
+// |-----> (*) MarshalMsg
+// |-----> (*) CanMarshalMsg
+// |-----> (*) UnmarshalMsg
+// |-----> (*) CanUnmarshalMsg
+// |-----> (*) Msgsize
+// |-----> (*) MsgIsZero
+// |-----> CreatableEntryMaxSize()
+//
+
+// MarshalMsg implements msgp.Marshaler
+func (z *creatableEntry) 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).CreatorAddr) == 0 {
+ zb0001Len--
+ zb0001Mask |= 0x1
+ }
+ if (*z).Ctype.MsgIsZero() {
+ zb0001Len--
+ zb0001Mask |= 0x2
+ }
+ // variable map header, size zb0001Len
+ o = append(o, 0x80|uint8(zb0001Len))
+ if zb0001Len != 0 {
+ if (zb0001Mask & 0x1) == 0 { // if not empty
+ // string "CreatorAddr"
+ o = append(o, 0xab, 0x43, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x41, 0x64, 0x64, 0x72)
+ o = msgp.AppendBytes(o, (*z).CreatorAddr)
+ }
+ if (zb0001Mask & 0x2) == 0 { // if not empty
+ // string "Ctype"
+ o = append(o, 0xa5, 0x43, 0x74, 0x79, 0x70, 0x65)
+ o = (*z).Ctype.MarshalMsg(o)
+ }
+ }
+ return
+}
+
+func (_ *creatableEntry) CanMarshalMsg(z interface{}) bool {
+ _, ok := (z).(*creatableEntry)
+ return ok
+}
+
+// UnmarshalMsg implements msgp.Unmarshaler
+func (z *creatableEntry) 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).Ctype.UnmarshalMsg(bts)
+ if err != nil {
+ err = msgp.WrapError(err, "struct-from-array", "Ctype")
+ return
+ }
+ }
+ if zb0001 > 0 {
+ zb0001--
+ (*z).CreatorAddr, bts, err = msgp.ReadBytesBytes(bts, (*z).CreatorAddr)
+ if err != nil {
+ err = msgp.WrapError(err, "struct-from-array", "CreatorAddr")
+ 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) = creatableEntry{}
+ }
+ for zb0001 > 0 {
+ zb0001--
+ field, bts, err = msgp.ReadMapKeyZC(bts)
+ if err != nil {
+ err = msgp.WrapError(err)
+ return
+ }
+ switch string(field) {
+ case "Ctype":
+ bts, err = (*z).Ctype.UnmarshalMsg(bts)
+ if err != nil {
+ err = msgp.WrapError(err, "Ctype")
+ return
+ }
+ case "CreatorAddr":
+ (*z).CreatorAddr, bts, err = msgp.ReadBytesBytes(bts, (*z).CreatorAddr)
+ if err != nil {
+ err = msgp.WrapError(err, "CreatorAddr")
+ return
+ }
+ default:
+ err = msgp.ErrNoField(string(field))
+ if err != nil {
+ err = msgp.WrapError(err)
+ return
+ }
+ }
+ }
+ }
+ o = bts
+ return
+}
+
+func (_ *creatableEntry) CanUnmarshalMsg(z interface{}) bool {
+ _, ok := (z).(*creatableEntry)
+ return ok
+}
+
+// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
+func (z *creatableEntry) Msgsize() (s int) {
+ s = 1 + 6 + (*z).Ctype.Msgsize() + 12 + msgp.BytesPrefixSize + len((*z).CreatorAddr)
+ return
+}
+
+// MsgIsZero returns whether this is a zero value
+func (z *creatableEntry) MsgIsZero() bool {
+ return ((*z).Ctype.MsgIsZero()) && (len((*z).CreatorAddr) == 0)
+}
+
+// MaxSize returns a maximum valid message size for this message type
+func CreatableEntryMaxSize() (s int) {
+ s = 1 + 6 + basics.CreatableTypeMaxSize() + 12
+ panic("Unable to determine max size: Byteslice type z.CreatorAddr is unbounded")
+ return
+}
diff --git a/ledger/store/trackerdb/generickv/msgp_gen_test.go b/ledger/store/trackerdb/generickv/msgp_gen_test.go
new file mode 100644
index 000000000..88d79eec2
--- /dev/null
+++ b/ledger/store/trackerdb/generickv/msgp_gen_test.go
@@ -0,0 +1,75 @@
+//go:build !skip_msgp_testing
+// +build !skip_msgp_testing
+
+package generickv
+
+// 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 TestMarshalUnmarshalcreatableEntry(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ v := creatableEntry{}
+ 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 TestRandomizedEncodingcreatableEntry(t *testing.T) {
+ protocol.RunEncodingTest(t, &creatableEntry{})
+}
+
+func BenchmarkMarshalMsgcreatableEntry(b *testing.B) {
+ v := creatableEntry{}
+ b.ReportAllocs()
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ v.MarshalMsg(nil)
+ }
+}
+
+func BenchmarkAppendMsgcreatableEntry(b *testing.B) {
+ v := creatableEntry{}
+ 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 BenchmarkUnmarshalcreatableEntry(b *testing.B) {
+ v := creatableEntry{}
+ 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/store/trackerdb/generickv/onlineaccounts_reader.go b/ledger/store/trackerdb/generickv/onlineaccounts_reader.go
new file mode 100644
index 000000000..a2ca83160
--- /dev/null
+++ b/ledger/store/trackerdb/generickv/onlineaccounts_reader.go
@@ -0,0 +1,186 @@
+// Copyright (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 <https://www.gnu.org/licenses/>.
+
+package generickv
+
+import (
+ "encoding/binary"
+
+ "github.com/algorand/go-algorand/data/basics"
+ "github.com/algorand/go-algorand/ledger/ledgercore"
+ "github.com/algorand/go-algorand/ledger/store/trackerdb"
+ "github.com/algorand/go-algorand/protocol"
+)
+
+// LookupOnline pulls the Online Account data for a given account+round
+func (r *accountsReader) LookupOnline(addr basics.Address, rnd basics.Round) (data trackerdb.PersistedOnlineAccountData, err error) {
+ // SQL at the time of writing this:
+ //
+ // SELECT
+ // onlineaccounts.rowid, onlineaccounts.updround,
+ // acctrounds.rnd,
+ // onlineaccounts.data
+ // FROM acctrounds
+ // LEFT JOIN onlineaccounts ON address=? AND updround <= ?
+ // WHERE id='acctbase'
+ // ORDER BY updround DESC LIMIT 1
+
+ // read the current db round
+ data.Round, err = r.AccountsRound()
+ if err != nil {
+ return
+ }
+
+ // read latest account up to `rnd``
+ low := onlineAccountOnlyPartialKey(addr)
+ high := onlineAccountKey(addr, rnd)
+ // inc the last byte to make it inclusive
+ high[len(high)-1]++
+ iter := r.kvr.NewIter(low, high, true)
+ defer iter.Close()
+
+ var value []byte
+ var updRound uint64
+
+ if iter.Next() {
+ // schema: <prefix>-<addr>-<rnd>
+ key := iter.Key()
+
+ // extract updround, its the last section after the "-"
+ rndOffset := len(kvPrefixOnlineAccount) + 1 + 32 + 1
+ updRound = binary.BigEndian.Uint64(key[rndOffset : rndOffset+8])
+ if err != nil {
+ return
+ }
+ data.Addr = addr
+ data.UpdRound = basics.Round(updRound)
+
+ // get value for current item in the iterator
+ value, err = iter.Value()
+ if err != nil {
+ return
+ }
+
+ // parse the value
+ err = protocol.Decode(value, &data.AccountData)
+ if err != nil {
+ return
+ }
+
+ normBalance := data.AccountData.NormalizedOnlineBalance(r.proto)
+ data.Ref = onlineAccountRef{addr, normBalance, rnd}
+
+ // we have the record, we can leave
+ return
+ }
+
+ // nothing was found
+ // Note: the SQL implementation returns a data value and no error even when the account does not exist.
+ return data, nil
+}
+
+// LookupOnlineTotalsHistory pulls the total Online Algos on a given round
+func (r *accountsReader) LookupOnlineTotalsHistory(round basics.Round) (basics.MicroAlgos, error) {
+ // SQL at the time of writing this:
+ //
+ // SELECT data FROM onlineroundparamstail WHERE rnd=?
+
+ value, closer, err := r.kvr.Get(onlineAccountRoundParamsKey(round))
+ if err != nil {
+ return basics.MicroAlgos{}, err
+ }
+ defer closer.Close()
+ data := ledgercore.OnlineRoundParamsData{}
+ err = protocol.Decode(value, &data)
+ if err != nil {
+ return basics.MicroAlgos{}, err
+ }
+ return basics.MicroAlgos{Raw: data.OnlineSupply}, nil
+}
+
+func (r *accountsReader) LookupOnlineHistory(addr basics.Address) (result []trackerdb.PersistedOnlineAccountData, rnd basics.Round, err error) {
+ low := onlineAccountOnlyPartialKey(addr)
+ high := onlineAccountOnlyPartialKey(addr)
+ high[len(high)-1]++
+ iter := r.kvr.NewIter(low, high, false)
+ defer iter.Close()
+
+ var value []byte
+ var updround uint64
+
+ // read the current db round
+ rnd, err = r.AccountsRound()
+ if err != nil {
+ return
+ }
+
+ for iter.Next() {
+ pitem := trackerdb.PersistedOnlineAccountData{}
+
+ // schema: <prefix>-<addr>-<rnd>
+ key := iter.Key()
+ // extract updround, its the last section after the "-"
+ rndOffset := len(kvPrefixOnlineAccount) + 1 + 32 + 1
+ updround = binary.BigEndian.Uint64(key[rndOffset : rndOffset+8])
+ if err != nil {
+ return
+ }
+ pitem.Addr = addr
+ pitem.UpdRound = basics.Round(updround)
+ // Note: for compatibility with the SQL impl, this is not included on each item
+ // pitem.Round = rnd
+
+ // get value for current item in the iterator
+ value, err = iter.Value()
+ if err != nil {
+ return
+ }
+ // decode raw value
+ err = protocol.Decode(value, &pitem.AccountData)
+ if err != nil {
+ return
+ }
+
+ // set the ref
+ pitem.Ref = onlineAccountRef{addr, pitem.AccountData.NormalizedOnlineBalance(r.proto), pitem.UpdRound}
+
+ // append entry to accum
+ result = append(result, pitem)
+ }
+
+ return
+}
+
+func (r *accountsReader) LookupOnlineRoundParams(rnd basics.Round) (onlineRoundParamsData ledgercore.OnlineRoundParamsData, err error) {
+ // SQL impl at time of writing:
+ //
+ // SELECT data
+ // FROM onlineroundparamstail
+ // WHERE rnd=?
+
+ value, closer, err := r.kvr.Get(onlineAccountRoundParamsKey(rnd))
+ if err != nil {
+ return
+ }
+ defer closer.Close()
+
+ err = protocol.Decode(value, &onlineRoundParamsData)
+ if err != nil {
+ return
+ }
+
+ return
+}
diff --git a/ledger/store/trackerdb/generickv/onlineaccounts_writer.go b/ledger/store/trackerdb/generickv/onlineaccounts_writer.go
new file mode 100644
index 000000000..cca847c60
--- /dev/null
+++ b/ledger/store/trackerdb/generickv/onlineaccounts_writer.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 <https://www.gnu.org/licenses/>.
+
+package generickv
+
+import (
+ "github.com/algorand/go-algorand/data/basics"
+ "github.com/algorand/go-algorand/ledger/store/trackerdb"
+ "github.com/algorand/go-algorand/protocol"
+)
+
+type onlineAccountsWriter struct {
+ kvw KvWrite
+}
+
+type onlineAccountRef struct {
+ addr basics.Address
+ normBalance uint64
+ round basics.Round
+}
+
+func (ref onlineAccountRef) OnlineAccountRefMarker() {}
+
+// MakeOnlineAccountsWriter constructs an kv agnostic OnlineAccountsWriter
+func MakeOnlineAccountsWriter(kvw KvWrite) trackerdb.OnlineAccountsWriter {
+ return &onlineAccountsWriter{kvw}
+}
+
+func (w *onlineAccountsWriter) InsertOnlineAccount(addr basics.Address, normBalance uint64, data trackerdb.BaseOnlineAccountData, updRound uint64, voteLastValid uint64) (ref trackerdb.OnlineAccountRef, err error) {
+ raw := protocol.Encode(&data)
+ rnd := basics.Round(updRound)
+
+ // write to the online account key
+ err = w.kvw.Set(onlineAccountKey(addr, rnd), raw)
+ if err != nil {
+ return nil, err
+ }
+
+ // write to the secondary account balance key
+ // TODO: this is not the most efficient use of space, but its a tradeoff with a second lookup per object.
+ // this impacts `AccountsOnlineTop`, and some experiments will be needed to determine if we do extra lookups or duplicate the values.
+ err = w.kvw.Set(onlineAccountBalanceKey(updRound, normBalance, addr), raw)
+ if err != nil {
+ return nil, err
+ }
+
+ return onlineAccountRef{addr, normBalance, rnd}, nil
+}
+
+func (w *onlineAccountsWriter) Close() {
+}
diff --git a/ledger/store/trackerdb/generickv/reader.go b/ledger/store/trackerdb/generickv/reader.go
new file mode 100644
index 000000000..1532f2fca
--- /dev/null
+++ b/ledger/store/trackerdb/generickv/reader.go
@@ -0,0 +1,78 @@
+// Copyright (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 <https://www.gnu.org/licenses/>.
+
+package generickv
+
+import (
+ "context"
+
+ "github.com/algorand/go-algorand/config"
+ "github.com/algorand/go-algorand/ledger/store/trackerdb"
+)
+
+type reader struct {
+ proto config.ConsensusParams
+ KvRead
+}
+
+// MakeReader returns a trackerdb.Reader for a KV
+func MakeReader(kvr KvRead, proto config.ConsensusParams) trackerdb.Reader {
+ return &reader{proto, kvr}
+}
+
+// MakeAccountsOptimizedReader implements trackerdb.Reader
+func (r *reader) MakeAccountsOptimizedReader() (trackerdb.AccountsReader, error) {
+ return MakeAccountsReader(r, r.proto), nil
+}
+
+// MakeAccountsReader implements trackerdb.Reader
+func (r *reader) MakeAccountsReader() (trackerdb.AccountsReaderExt, error) {
+ return MakeAccountsReader(r, r.proto), nil
+}
+
+// MakeOnlineAccountsOptimizedReader implements trackerdb.Reader
+func (r *reader) MakeOnlineAccountsOptimizedReader() (trackerdb.OnlineAccountsReader, error) {
+ return MakeAccountsReader(r, r.proto), nil
+}
+
+// MakeSpVerificationCtxReader implements trackerdb.Reader
+func (r *reader) MakeSpVerificationCtxReader() trackerdb.SpVerificationCtxReader {
+ return MakeStateproofReader(r)
+}
+
+// MakeCatchpointPendingHashesIterator implements trackerdb.Reader
+func (r *reader) MakeCatchpointPendingHashesIterator(hashCount int) trackerdb.CatchpointPendingHashesIter {
+ // TODO: catchpoint
+ panic("unimplemented")
+}
+
+// MakeCatchpointReader implements trackerdb.Reader
+func (r *reader) MakeCatchpointReader() (trackerdb.CatchpointReader, error) {
+ // TODO: catchpoint
+ panic("unimplemented")
+}
+
+// MakeEncodedAccoutsBatchIter implements trackerdb.Reader
+func (r *reader) MakeEncodedAccoutsBatchIter() trackerdb.EncodedAccountsBatchIter {
+ // TODO: catchpoint
+ panic("unimplemented")
+}
+
+// MakeKVsIter implements trackerdb.Reader
+func (r *reader) MakeKVsIter(ctx context.Context) (trackerdb.KVsIter, error) {
+ // TODO: catchpoint
+ panic("unimplemented")
+}
diff --git a/ledger/store/trackerdb/generickv/schema.go b/ledger/store/trackerdb/generickv/schema.go
new file mode 100644
index 000000000..8afb41e9e
--- /dev/null
+++ b/ledger/store/trackerdb/generickv/schema.go
@@ -0,0 +1,171 @@
+// Copyright (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 <https://www.gnu.org/licenses/>.
+
+package generickv
+
+import (
+ "encoding/binary"
+
+ "github.com/algorand/go-algorand/data/basics"
+)
+
+const (
+ kvPrefixAccount = "account"
+ kvPrefixResource = "resource"
+ kvPrefixAppKv = "appkv"
+ kvPrefixCreatorIndex = "creator"
+ kvPrefixOnlineAccount = "online_account_base"
+ kvPrefixOnlineAccountBalance = "online_account_balance"
+ kvRoundKey = "global_round"
+ kvSchemaVersionKey = "global_schema_version"
+ kvTotalsKey = "global_total"
+ kvTxTail = "txtail"
+ kvOnlineAccountRoundParams = "online_account_round_params"
+ kvPrefixStateproof = "stateproofs"
+)
+
+// return the big-endian binary encoding of a uint64
+func bigEndianUint64(v uint64) []byte {
+ ret := make([]byte, 8)
+ binary.BigEndian.PutUint64(ret, v)
+ return ret
+}
+
+// return the big-endian binary encoding of a uint32
+func bigEndianUint32(v uint32) []byte {
+ ret := make([]byte, 4)
+ binary.BigEndian.PutUint32(ret, v)
+ return ret
+}
+
+// accountKey: 4-byte prefix + 32-byte address
+func accountKey(address basics.Address) []byte {
+ ret := []byte(kvPrefixAccount)
+ ret = append(ret, "-"...)
+ ret = append(ret, address[:]...)
+ return ret
+}
+
+// resourceKey: 4-byte prefix + 32-byte address + 8-byte big-endian uint64
+func resourceKey(address basics.Address, aidx basics.CreatableIndex) []byte {
+ ret := []byte(kvPrefixResource)
+ ret = append(ret, "-"...)
+ ret = append(ret, address[:]...)
+ ret = append(ret, "-"...)
+ ret = append(ret, bigEndianUint64(uint64(aidx))...)
+ return ret
+}
+
+func resourceAddrOnlyPartialKey(address basics.Address) []byte {
+ ret := []byte(kvPrefixResource)
+ ret = append(ret, "-"...)
+ ret = append(ret, address[:]...)
+ ret = append(ret, "-"...)
+ return ret
+}
+
+func appKvKey(key string) []byte {
+ ret := []byte(kvPrefixAppKv)
+ ret = append(ret, "-"...)
+ ret = append(ret, key...)
+ return ret
+}
+
+func creatableKey(cidx basics.CreatableIndex) []byte {
+ ret := []byte(kvPrefixCreatorIndex)
+ ret = append(ret, "-"...)
+ ret = append(ret, bigEndianUint64(uint64(cidx))...)
+ return ret
+}
+
+func onlineAccountKey(address basics.Address, round basics.Round) []byte {
+ ret := []byte(kvPrefixOnlineAccount)
+ ret = append(ret, "-"...)
+ ret = append(ret, address[:]...)
+ ret = append(ret, "-"...)
+ ret = append(ret, bigEndianUint64(uint64(round))...)
+ return ret
+}
+
+func onlineAccountOnlyPartialKey(address basics.Address) []byte {
+ ret := []byte(kvPrefixOnlineAccount)
+ ret = append(ret, "-"...)
+ ret = append(ret, address[:]...)
+ ret = append(ret, "-"...)
+ return ret
+}
+
+// TODO: use basics.Round
+func onlineAccountBalanceKey(round uint64, normBalance uint64, address basics.Address) []byte {
+ ret := []byte(kvPrefixOnlineAccountBalance)
+ ret = append(ret, "-"...)
+ ret = append(ret, bigEndianUint64(round)...)
+ ret = append(ret, "-"...)
+ ret = append(ret, bigEndianUint64(normBalance)...)
+ ret = append(ret, "-"...)
+ ret = append(ret, address[:]...)
+ return ret
+}
+
+func onlineAccountBalanceOnlyPartialKey(round basics.Round) []byte {
+ ret := []byte(kvPrefixOnlineAccountBalance)
+ ret = append(ret, "-"...)
+ ret = append(ret, bigEndianUint64(uint64(round))...)
+ ret = append(ret, "-"...)
+ return ret
+}
+
+func roundKey() []byte {
+ ret := []byte(kvRoundKey)
+ return ret
+}
+
+func schemaVersionKey() []byte {
+ ret := []byte(kvSchemaVersionKey)
+ return ret
+}
+
+func totalsKey(catchpointStaging bool) []byte {
+ ret := []byte(kvTotalsKey)
+ ret = append(ret, "-"...)
+ if catchpointStaging {
+ ret = append(ret, "staging"...)
+ } else {
+ ret = append(ret, "live"...)
+ }
+ return ret
+}
+
+func txTailKey(rnd basics.Round) []byte {
+ ret := []byte(kvTxTail)
+ ret = append(ret, "-"...)
+ ret = append(ret, bigEndianUint64(uint64(rnd))...)
+ return ret
+}
+
+func onlineAccountRoundParamsKey(rnd basics.Round) []byte {
+ ret := []byte(kvOnlineAccountRoundParams)
+ ret = append(ret, "-"...)
+ ret = append(ret, bigEndianUint64(uint64(rnd))...)
+ return ret
+}
+
+func stateproofKey(rnd basics.Round) []byte {
+ ret := []byte(kvPrefixStateproof)
+ ret = append(ret, "-"...)
+ ret = append(ret, bigEndianUint64(uint64(rnd))...)
+ return ret
+}
diff --git a/ledger/store/trackerdb/generickv/stateproof_reader.go b/ledger/store/trackerdb/generickv/stateproof_reader.go
new file mode 100644
index 000000000..0c3ed62b5
--- /dev/null
+++ b/ledger/store/trackerdb/generickv/stateproof_reader.go
@@ -0,0 +1,104 @@
+// Copyright (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 <https://www.gnu.org/licenses/>.
+
+package generickv
+
+import (
+ "context"
+
+ "github.com/algorand/go-algorand/data/basics"
+ "github.com/algorand/go-algorand/ledger/ledgercore"
+ "github.com/algorand/go-algorand/ledger/store/trackerdb"
+ "github.com/algorand/go-algorand/protocol"
+)
+
+type stateproofReader struct {
+ kvr KvRead
+}
+
+// MakeStateproofReader returns a trackerdb.SpVerificationCtxReader for a KV
+func MakeStateproofReader(kvr KvRead) trackerdb.SpVerificationCtxReader {
+ return &stateproofReader{kvr}
+}
+
+// LookupSPContext implements trackerdb.SpVerificationCtxReader
+func (r *stateproofReader) LookupSPContext(stateProofLastAttestedRound basics.Round) (*ledgercore.StateProofVerificationContext, error) {
+ // SQL at the time of writing:
+ //
+ // SELECT
+ // verificationcontext
+ // FROM stateproofverification
+ // WHERE lastattestedround=?
+
+ value, closer, err := r.kvr.Get(stateproofKey(stateProofLastAttestedRound))
+ if err != nil {
+ return nil, err
+ }
+ defer closer.Close()
+
+ var vc ledgercore.StateProofVerificationContext
+ err = protocol.Decode(value, &vc)
+ if err != nil {
+ return nil, err
+ }
+
+ return &vc, nil
+}
+
+// GetAllSPContexts implements trackerdb.SpVerificationCtxReader
+func (r *stateproofReader) GetAllSPContexts(ctx context.Context) ([]ledgercore.StateProofVerificationContext, error) {
+ // SQL at the time of writing:
+ //
+ // SELECT
+ // verificationContext
+ // FROM stateProofVerification
+ // ORDER BY lastattestedround
+
+ low := []byte(kvPrefixStateproof + "-")
+ high := []byte(kvPrefixStateproof + ".")
+ iter := r.kvr.NewIter(low, high, false)
+ defer iter.Close()
+
+ results := make([]ledgercore.StateProofVerificationContext, 0)
+
+ var value []byte
+ var err error
+ for iter.Next() {
+ // get value for current item in the iterator
+ value, err = iter.Value()
+ if err != nil {
+ return nil, err
+ }
+
+ // decode the value
+ vc := ledgercore.StateProofVerificationContext{}
+ err = protocol.Decode(value, &vc)
+ if err != nil {
+ return nil, err
+ }
+
+ // add the item to the results
+ results = append(results, vc)
+ }
+
+ return results, nil
+}
+
+// GetAllSPContextsFromCatchpointTbl implements trackerdb.SpVerificationCtxReader
+func (r *stateproofReader) GetAllSPContextsFromCatchpointTbl(ctx context.Context) ([]ledgercore.StateProofVerificationContext, error) {
+ // TODO: catchpoint
+ return nil, nil
+}
diff --git a/ledger/store/trackerdb/generickv/stateproof_writer.go b/ledger/store/trackerdb/generickv/stateproof_writer.go
new file mode 100644
index 000000000..42565d9ed
--- /dev/null
+++ b/ledger/store/trackerdb/generickv/stateproof_writer.go
@@ -0,0 +1,76 @@
+// Copyright (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 <https://www.gnu.org/licenses/>.
+
+package generickv
+
+import (
+ "context"
+
+ "github.com/algorand/go-algorand/data/basics"
+ "github.com/algorand/go-algorand/ledger/ledgercore"
+ "github.com/algorand/go-algorand/ledger/store/trackerdb"
+ "github.com/algorand/go-algorand/protocol"
+)
+
+type stateproofWriter struct {
+ kvw KvWrite
+}
+
+// MakeStateproofWriter returns a trackerdb.SpVerificationCtxWriter for a KV
+func MakeStateproofWriter(kvw KvWrite) trackerdb.SpVerificationCtxWriter {
+ return &stateproofWriter{kvw}
+}
+
+// StoreSPContexts implements trackerdb.SpVerificationCtxWriter
+func (w *stateproofWriter) StoreSPContexts(ctx context.Context, verificationContext []*ledgercore.StateProofVerificationContext) error {
+ // SQL at the time of writing:
+ //
+ // INSERT INTO stateProofVerification
+ // (lastattestedround, verificationContext)
+ // VALUES
+ // (?, ?)
+
+ for i := range verificationContext {
+ // write stateproof entry
+ vc := verificationContext[i]
+ raw := protocol.Encode(vc)
+ err := w.kvw.Set(stateproofKey(vc.LastAttestedRound), raw)
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+// DeleteOldSPContexts implements trackerdb.SpVerificationCtxWriter
+func (w *stateproofWriter) DeleteOldSPContexts(ctx context.Context, earliestLastAttestedRound basics.Round) error {
+ // SQL at the time of writing:
+ //
+ // DELETE FROM stateproofverification
+ // WHERE lastattestedround < ?
+
+ start := []byte(kvPrefixStateproof + "-")
+ end := stateproofKey(earliestLastAttestedRound)
+
+ return w.kvw.DeleteRange(start, end)
+}
+
+// StoreSPContextsToCatchpointTbl implements trackerdb.SpVerificationCtxWriter
+func (w *stateproofWriter) StoreSPContextsToCatchpointTbl(ctx context.Context, verificationContexts []ledgercore.StateProofVerificationContext) error {
+ // TODO: catchpoint
+ return nil
+}
diff --git a/ledger/store/trackerdb/generickv/writer.go b/ledger/store/trackerdb/generickv/writer.go
new file mode 100644
index 000000000..c5d8dcfc0
--- /dev/null
+++ b/ledger/store/trackerdb/generickv/writer.go
@@ -0,0 +1,91 @@
+// Copyright (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 <https://www.gnu.org/licenses/>.
+
+package generickv
+
+import (
+ "context"
+ "github.com/algorand/go-algorand/config"
+ "github.com/algorand/go-algorand/data/basics"
+ "github.com/algorand/go-algorand/ledger/store/trackerdb"
+ "github.com/algorand/go-algorand/protocol"
+ "testing"
+)
+
+type writer struct {
+ // TODO: the need for the store here is quite broken
+ // this is due to exposing RunMigrations and AccountsInit on the writers
+ // the internals of this methods completly ignore the "writer" and recreate a transaction
+ store trackerdb.Store
+ KvWrite
+ KvRead
+}
+
+// MakeWriter returns a trackerdb.Writer for a KV
+func MakeWriter(store trackerdb.Store, kvw KvWrite, kvr KvRead) trackerdb.Writer {
+ return &writer{store, kvw, kvr}
+}
+
+// MakeAccountsOptimizedWriter implements trackerdb.Writer
+func (w *writer) MakeAccountsOptimizedWriter(hasAccounts bool, hasResources bool, hasKvPairs bool, hasCreatables bool) (trackerdb.AccountsWriter, error) {
+ return MakeAccountsWriter(w, w), nil
+}
+
+// MakeAccountsWriter implements trackerdb.Writer
+func (w *writer) MakeAccountsWriter() (trackerdb.AccountsWriterExt, error) {
+ return MakeAccountsWriter(w, w), nil
+}
+
+// MakeOnlineAccountsOptimizedWriter implements trackerdb.Writer
+func (w *writer) MakeOnlineAccountsOptimizedWriter(hasAccounts bool) (trackerdb.OnlineAccountsWriter, error) {
+ return MakeOnlineAccountsWriter(w), nil
+}
+
+// MakeSpVerificationCtxWriter implements trackerdb.Writer
+func (w *writer) MakeSpVerificationCtxWriter() trackerdb.SpVerificationCtxWriter {
+ return MakeStateproofWriter(w)
+}
+
+// Testing implements trackerdb.Writer
+func (w *writer) Testing() trackerdb.WriterTestExt {
+ return &writerForTesting{w.store, w, w}
+}
+
+type writerForTesting struct {
+ trackerdb.Store
+ KvWrite
+ KvRead
+}
+
+// AccountsInitLightTest implements trackerdb.WriterTestExt
+func (w *writerForTesting) AccountsInitLightTest(tb testing.TB, initAccounts map[basics.Address]basics.AccountData, proto config.ConsensusParams) (newDatabase bool, err error) {
+ panic("unimplemented")
+}
+
+// AccountsInitTest implements trackerdb.WriterTestExt
+func (w *writerForTesting) AccountsInitTest(tb testing.TB, initAccounts map[basics.Address]basics.AccountData, proto protocol.ConsensusVersion) (newDatabase bool) {
+ return AccountsInitTest(tb, w, initAccounts, proto)
+}
+
+// AccountsUpdateSchemaTest implements trackerdb.WriterTestExt
+func (w *writerForTesting) AccountsUpdateSchemaTest(ctx context.Context) (err error) {
+ panic("unimplemented")
+}
+
+// ModifyAcctBaseTest implements trackerdb.WriterTestExt
+func (w *writerForTesting) ModifyAcctBaseTest() error {
+ panic("unimplemented")
+}
diff --git a/ledger/store/trackerdb/sqlitedriver/catchpoint.go b/ledger/store/trackerdb/sqlitedriver/catchpoint.go
index 388749858..ef63b7a7f 100644
--- a/ledger/store/trackerdb/sqlitedriver/catchpoint.go
+++ b/ledger/store/trackerdb/sqlitedriver/catchpoint.go
@@ -52,6 +52,10 @@ func NewCatchpointSQLReaderWriter(e db.Executable) *catchpointReaderWriter {
}
}
+func makeCatchpointReader(e db.Queryable) trackerdb.CatchpointReader {
+ return &catchpointReader{q: e}
+}
+
func (cr *catchpointReader) GetCatchpoint(ctx context.Context, round basics.Round) (fileName string, catchpoint string, fileSize int64, err error) {
err = cr.q.QueryRowContext(ctx, "SELECT filename, catchpoint, filesize FROM storedcatchpoints WHERE round=?", int64(round)).Scan(&fileName, &catchpoint, &fileSize)
return
diff --git a/ledger/store/trackerdb/sqlitedriver/sqlitedriver.go b/ledger/store/trackerdb/sqlitedriver/sqlitedriver.go
index a5fe393b4..34f4d363c 100644
--- a/ledger/store/trackerdb/sqlitedriver/sqlitedriver.go
+++ b/ledger/store/trackerdb/sqlitedriver/sqlitedriver.go
@@ -85,7 +85,7 @@ func (s *trackerSQLStore) Snapshot(fn trackerdb.SnapshotFn) (err error) {
func (s *trackerSQLStore) SnapshotContext(ctx context.Context, fn trackerdb.SnapshotFn) (err error) {
return s.pair.Rdb.AtomicContext(ctx, func(ctx context.Context, tx *sql.Tx) error {
- return fn(ctx, sqlSnapshotScope{tx, &sqlReader{tx}})
+ return fn(ctx, &sqlSnapshotScope{tx, &sqlReader{tx}})
})
}
@@ -183,6 +183,21 @@ func (r *sqlReader) MakeCatchpointPendingHashesIterator(hashCount int) trackerdb
return MakeCatchpointPendingHashesIterator(hashCount, r.q)
}
+// MakeCatchpointReader implements trackerdb.Reader
+func (r *sqlReader) MakeCatchpointReader() (trackerdb.CatchpointReader, error) {
+ return makeCatchpointReader(r.q), nil
+}
+
+// MakeEncodedAccoutsBatchIter implements trackerdb.Reader
+func (r *sqlReader) MakeEncodedAccoutsBatchIter() trackerdb.EncodedAccountsBatchIter {
+ return MakeEncodedAccoutsBatchIter(r.q)
+}
+
+// MakeKVsIter implements trackerdb.Reader
+func (r *sqlReader) MakeKVsIter(ctx context.Context) (trackerdb.KVsIter, error) {
+ return MakeKVsIter(ctx, r.q)
+}
+
type sqlWriter struct {
e db.Executable
}
@@ -236,11 +251,6 @@ type sqlCatchpoint struct {
e db.Executable
}
-// MakeCatchpointReader implements trackerdb.Catchpoint
-func (c *sqlCatchpoint) MakeCatchpointReader() (trackerdb.CatchpointReader, error) {
- return NewCatchpointSQLReaderWriter(c.e), nil
-}
-
// MakeCatchpointReaderWriter implements trackerdb.Catchpoint
func (c *sqlCatchpoint) MakeCatchpointReaderWriter() (trackerdb.CatchpointReaderWriter, error) {
return NewCatchpointSQLReaderWriter(c.e), nil
@@ -251,16 +261,6 @@ func (c *sqlCatchpoint) MakeCatchpointWriter() (trackerdb.CatchpointWriter, erro
return NewCatchpointSQLReaderWriter(c.e), nil
}
-// MakeEncodedAccoutsBatchIter implements trackerdb.Catchpoint
-func (c *sqlCatchpoint) MakeEncodedAccoutsBatchIter() trackerdb.EncodedAccountsBatchIter {
- return MakeEncodedAccoutsBatchIter(c.e)
-}
-
-// MakeKVsIter implements trackerdb.Catchpoint
-func (c *sqlCatchpoint) MakeKVsIter(ctx context.Context) (trackerdb.KVsIter, error) {
- return MakeKVsIter(ctx, c.e)
-}
-
// MakeMerkleCommitter implements trackerdb.Catchpoint
func (c *sqlCatchpoint) MakeMerkleCommitter(staging bool) (trackerdb.MerkleCommitter, error) {
return MakeMerkleCommitter(c.e, staging)
@@ -302,7 +302,11 @@ type sqlSnapshotScope struct {
trackerdb.Reader
}
-func (ss sqlSnapshotScope) Close() error {
+func (ss *sqlSnapshotScope) ResetTransactionWarnDeadline(ctx context.Context, deadline time.Time) (prevDeadline time.Time, err error) {
+ return db.ResetTransactionWarnDeadline(ctx, ss.tx, deadline)
+}
+
+func (ss *sqlSnapshotScope) Close() error {
return ss.tx.Rollback()
}
diff --git a/ledger/store/trackerdb/store.go b/ledger/store/trackerdb/store.go
index 5514c12a8..4140f5be1 100644
--- a/ledger/store/trackerdb/store.go
+++ b/ledger/store/trackerdb/store.go
@@ -59,6 +59,10 @@ type Reader interface {
// catchpoint
// Note: BuildMerkleTrie() needs this on the reader handle in sqlite to not get locked by write txns
MakeCatchpointPendingHashesIterator(hashCount int) CatchpointPendingHashesIter
+ // Note: Catchpoint tracker needs this on the reader handle in sqlite to not get locked by write txns
+ MakeCatchpointReader() (CatchpointReader, error)
+ MakeEncodedAccoutsBatchIter() EncodedAccountsBatchIter
+ MakeKVsIter(ctx context.Context) (KVsIter, error)
}
// Writer is the interface for the trackerdb write operations.
@@ -82,10 +86,7 @@ type Writer interface {
// we should split these two sets of methods into two separate interfaces
type Catchpoint interface {
// reader
- MakeCatchpointReader() (CatchpointReader, error)
MakeOrderedAccountsIter(accountCount int) OrderedAccountsIter
- MakeKVsIter(ctx context.Context) (KVsIter, error)
- MakeEncodedAccoutsBatchIter() EncodedAccountsBatchIter
// writer
MakeCatchpointWriter() (CatchpointWriter, error)
// reader/writer
@@ -122,6 +123,7 @@ type Batch interface {
// SnapshotScope is an atomic read-only scope to the store.
type SnapshotScope interface {
Reader
+ ResetTransactionWarnDeadline(ctx context.Context, deadline time.Time) (prevDeadline time.Time, err error)
}
// Snapshot is an atomic read-only accecssor to the store.
diff --git a/ledger/store/trackerdb/testsuite/README.md b/ledger/store/trackerdb/testsuite/README.md
new file mode 100644
index 000000000..15270cfde
--- /dev/null
+++ b/ledger/store/trackerdb/testsuite/README.md
@@ -0,0 +1,29 @@
+!! **DO NOT IMPORT THIS MODULE** !!
+
+---
+
+This is the `generickv` tests implemented _outside_ the package.
+This is done to avoid cirucular dependencies on some of the testing wizardry taking place.
+Namely, making the tests polymorphic on each database implementation,
+so we can reuse the test suite against multiple backends.
+
+# Adding tests to the suite
+
+1. Use the following signature on your tests:
+
+```go
+func CustomTestDoingSomething(t *customT) {
+ // your test..
+}
+```
+
+The `customT` type behaves just like `testing.T` but has some extras.
+
+2. Register your test with the suite in the `init()` function in your test file.
+
+```go
+func init() {
+ // register tests that will run on each KV implementation
+ registerTest("something", CustomTestDoingSomething)
+}
+``` \ No newline at end of file
diff --git a/ledger/store/trackerdb/testsuite/accounts_ext_kv_test.go b/ledger/store/trackerdb/testsuite/accounts_ext_kv_test.go
new file mode 100644
index 000000000..3c17c96b8
--- /dev/null
+++ b/ledger/store/trackerdb/testsuite/accounts_ext_kv_test.go
@@ -0,0 +1,300 @@
+// Copyright (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 <https://www.gnu.org/licenses/>.
+
+package testsuite
+
+import (
+ "context"
+
+ "github.com/algorand/go-algorand/crypto"
+ "github.com/algorand/go-algorand/data/basics"
+ "github.com/algorand/go-algorand/data/transactions"
+ "github.com/algorand/go-algorand/ledger/ledgercore"
+ "github.com/algorand/go-algorand/ledger/store/trackerdb"
+ "github.com/algorand/go-algorand/protocol"
+ "github.com/stretchr/testify/require"
+)
+
+func init() {
+ // register tests that will run on each KV implementation
+ registerTest("global-round-update", CustomTestRoundUpdate)
+ registerTest("global-totals", CustomTestTotals)
+ registerTest("txtail-update", CustomTestTxTail)
+ registerTest("online_accounts-round_params-update", CustomTestOnlineAccountParams)
+ registerTest("accounts-lookup_by_rowid", CustomTestAccountLookupByRowID)
+ registerTest("resources-lookup_by_rowid", CustomTestResourceLookupByRowID)
+}
+
+func CustomTestRoundUpdate(t *customT) {
+ aw, err := t.db.MakeAccountsWriter()
+ require.NoError(t, err)
+
+ ar, err := t.db.MakeAccountsReader()
+ require.NoError(t, err)
+
+ // update the round
+ err = aw.UpdateAccountsRound(basics.Round(1))
+ require.NoError(t, err)
+
+ // read the round
+ rnd, err := ar.AccountsRound()
+ require.NoError(t, err)
+ require.Equal(t, basics.Round(1), rnd)
+}
+
+func CustomTestTotals(t *customT) {
+ aw, err := t.db.MakeAccountsWriter()
+ require.NoError(t, err)
+
+ ar, err := t.db.MakeAccountsReader()
+ require.NoError(t, err)
+
+ // generate some test data
+ totals := ledgercore.AccountTotals{
+ Online: ledgercore.AlgoCount{Money: basics.MicroAlgos{Raw: 42}},
+ Offline: ledgercore.AlgoCount{Money: basics.MicroAlgos{Raw: 1000}},
+ NotParticipating: ledgercore.AlgoCount{Money: basics.MicroAlgos{Raw: 8}},
+ RewardsLevel: 9000,
+ }
+
+ // update the totals
+ err = aw.AccountsPutTotals(totals, false)
+ require.NoError(t, err)
+
+ // read the totals
+ readTotals, err := ar.AccountsTotals(context.Background(), false)
+ require.NoError(t, err)
+ require.Equal(t, totals, readTotals)
+
+ // generate some staging values
+ stagingTotals := ledgercore.AccountTotals{
+ Online: ledgercore.AlgoCount{Money: basics.MicroAlgos{Raw: 1}},
+ Offline: ledgercore.AlgoCount{Money: basics.MicroAlgos{Raw: 2}},
+ NotParticipating: ledgercore.AlgoCount{Money: basics.MicroAlgos{Raw: 3}},
+ RewardsLevel: 4,
+ }
+
+ // update the (staging) totals
+ err = aw.AccountsPutTotals(stagingTotals, true)
+ require.NoError(t, err)
+
+ // read the totals
+ readTotals, err = ar.AccountsTotals(context.Background(), true)
+ require.NoError(t, err)
+ require.Equal(t, stagingTotals, readTotals)
+
+ // double check the live data is still there
+ readTotals, err = ar.AccountsTotals(context.Background(), false)
+ require.NoError(t, err)
+ require.Equal(t, totals, readTotals)
+}
+
+func CustomTestTxTail(t *customT) {
+ aw, err := t.db.MakeAccountsWriter()
+ require.NoError(t, err)
+
+ ar, err := t.db.MakeAccountsReader()
+ require.NoError(t, err)
+
+ // generate some test data
+ baseRound := basics.Round(0)
+ roundData := []*trackerdb.TxTailRound{
+ {
+ TxnIDs: []transactions.Txid{transactions.Txid(crypto.Hash([]byte("tx-0")))},
+ },
+ {
+ TxnIDs: []transactions.Txid{transactions.Txid(crypto.Hash([]byte("tx-1")))},
+ },
+ {
+ TxnIDs: []transactions.Txid{transactions.Txid(crypto.Hash([]byte("tx-2")))},
+ },
+ }
+ // TODO: remove this conversion once we change the API to take the actual types
+ var rawRoundData [][]byte
+ for _, tail := range roundData {
+ raw := protocol.Encode(tail)
+ rawRoundData = append(rawRoundData, raw)
+ }
+
+ // write TxTail's
+ err = aw.TxtailNewRound(context.Background(), baseRound, rawRoundData, baseRound)
+ require.NoError(t, err)
+
+ // load TxTail's (error, must be the latest round)
+ _, _, _, err = ar.LoadTxTail(context.Background(), basics.Round(1))
+ require.Error(t, err)
+
+ // load TxTail's
+ txtails, hashes, readBaseRound, err := ar.LoadTxTail(context.Background(), basics.Round(2))
+ require.NoError(t, err)
+ require.Len(t, txtails, 3) // assert boundries
+ require.Equal(t, roundData[0], txtails[0]) // assert ordering
+ require.Len(t, hashes, 3)
+ require.Equal(t, basics.Round(0), readBaseRound)
+
+ // generate some more test data
+ roundData = []*trackerdb.TxTailRound{
+ {
+ TxnIDs: []transactions.Txid{transactions.Txid(crypto.Hash([]byte("tx-3")))},
+ },
+ }
+ // reset data
+ rawRoundData = make([][]byte, 0)
+ // TODO: remove this conversion once we change the API to take the actual types
+ for _, tail := range roundData {
+ raw := protocol.Encode(tail)
+ rawRoundData = append(rawRoundData, raw)
+ }
+ // write TxTail's (delete everything before round 2)
+ err = aw.TxtailNewRound(context.Background(), basics.Round(3), rawRoundData, basics.Round(2))
+ require.NoError(t, err)
+
+ // load TxTail's
+ txtails, hashes, readBaseRound, err = ar.LoadTxTail(context.Background(), basics.Round(3))
+ require.NoError(t, err)
+ require.Len(t, txtails, 2)
+ require.Len(t, hashes, 2)
+ require.Equal(t, basics.Round(2), readBaseRound)
+}
+
+func CustomTestOnlineAccountParams(t *customT) {
+ aw, err := t.db.MakeAccountsWriter()
+ require.NoError(t, err)
+
+ ar, err := t.db.MakeAccountsReader()
+ require.NoError(t, err)
+
+ // generate some test data
+ startRound := basics.Round(0)
+ roundParams := []ledgercore.OnlineRoundParamsData{
+ {OnlineSupply: 100},
+ {OnlineSupply: 42},
+ {OnlineSupply: 9000},
+ }
+
+ // clean up the db before starting with the test
+ // Note: some engines might start with some data built-in data for round 0
+ err = aw.AccountsPruneOnlineRoundParams(basics.Round(42))
+ require.NoError(t, err)
+
+ // write round params
+ err = aw.AccountsPutOnlineRoundParams(roundParams, startRound)
+ require.NoError(t, err)
+
+ // read round params
+ readParams, endRound, err := ar.AccountsOnlineRoundParams()
+ require.NoError(t, err)
+ require.Len(t, readParams, 3) // assert boundries
+ require.Equal(t, roundParams[0], readParams[0]) // assert ordering
+ require.Equal(t, basics.Round(2), endRound) // check round
+
+ // prune params
+ err = aw.AccountsPruneOnlineRoundParams(basics.Round(1))
+ require.NoError(t, err)
+
+ // read round params (again, after prunning)
+ readParams, endRound, err = ar.AccountsOnlineRoundParams()
+ require.NoError(t, err)
+ require.Len(t, readParams, 2) // assert boundries
+ require.Equal(t, roundParams[1], readParams[0]) // assert ordering, and first item
+ require.Equal(t, basics.Round(2), endRound) // check round
+}
+
+func CustomTestAccountLookupByRowID(t *customT) {
+ aow, err := t.db.MakeAccountsOptimizedWriter(true, false, false, false)
+ require.NoError(t, err)
+
+ ar, err := t.db.MakeAccountsReader()
+ require.NoError(t, err)
+
+ // generate some test data
+ addrA := RandomAddress()
+ dataA := trackerdb.BaseAccountData{
+ RewardsBase: 1000,
+ }
+ normBalanceA := dataA.NormalizedOnlineBalance(t.proto)
+ refA, err := aow.InsertAccount(addrA, normBalanceA, dataA)
+ require.NoError(t, err)
+
+ //
+ // test
+ //
+
+ // non-existing account
+ _, err = ar.LookupAccountRowID(RandomAddress())
+ require.Error(t, err)
+ require.Equal(t, err, trackerdb.ErrNotFound)
+
+ // read account
+ ref, err := ar.LookupAccountRowID(addrA)
+ require.NoError(t, err)
+ require.Equal(t, refA, ref)
+}
+
+func CustomTestResourceLookupByRowID(t *customT) {
+ aow, err := t.db.MakeAccountsOptimizedWriter(true, true, false, false)
+ require.NoError(t, err)
+
+ ar, err := t.db.MakeAccountsReader()
+ require.NoError(t, err)
+
+ // generate some test data
+ addrA := RandomAddress()
+ accDataA := trackerdb.BaseAccountData{RewardsBase: 1000}
+ refAccA, err := aow.InsertAccount(addrA, accDataA.NormalizedOnlineBalance(t.proto), accDataA)
+ require.NoError(t, err)
+
+ // generate some test data
+ resDataA0 := trackerdb.MakeResourcesData(0)
+ resDataA0.SetAssetParams(basics.AssetParams{
+ Total: 100,
+ UnitName: "t",
+ AssetName: "test-asset",
+ Manager: addrA,
+ Reserve: addrA,
+ Freeze: addrA,
+ Clawback: addrA,
+ URL: "http://127.0.0.1/8000",
+ }, true)
+ resDataA0.SetAssetHolding(basics.AssetHolding{Amount: 10})
+ aidxResA0 := basics.CreatableIndex(0)
+ _, err = aow.InsertResource(refAccA, aidxResA0, resDataA0)
+ require.NoError(t, err)
+
+ //
+ // test
+ //
+
+ // non-existing resource
+ _, err = ar.LookupResourceDataByAddrID(refAccA, basics.CreatableIndex(100))
+ require.Error(t, err)
+ require.Equal(t, err, trackerdb.ErrNotFound)
+
+ // read resource
+ data, err := ar.LookupResourceDataByAddrID(refAccA, aidxResA0)
+ require.NoError(t, err)
+ // parse the raw data
+ var res trackerdb.ResourcesData
+ err = protocol.Decode(data, &res)
+ require.NoError(t, err)
+ // assert that we got the resource
+ require.Equal(t, resDataA0, res)
+
+ // read resource on nil account
+ _, err = ar.LookupResourceDataByAddrID(nil, basics.CreatableIndex(100))
+ require.Error(t, err)
+ require.Equal(t, err, trackerdb.ErrNotFound)
+}
diff --git a/ledger/store/trackerdb/testsuite/accounts_kv_test.go b/ledger/store/trackerdb/testsuite/accounts_kv_test.go
new file mode 100644
index 000000000..1ca7b742a
--- /dev/null
+++ b/ledger/store/trackerdb/testsuite/accounts_kv_test.go
@@ -0,0 +1,381 @@
+// Copyright (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 <https://www.gnu.org/licenses/>.
+
+package testsuite
+
+import (
+ "github.com/algorand/go-algorand/data/basics"
+ "github.com/algorand/go-algorand/ledger/store/trackerdb"
+ "github.com/stretchr/testify/require"
+)
+
+func init() {
+ // register tests that will run on each KV implementation
+ registerTest("accounts-crud", CustomTestAccountsCrud)
+ registerTest("resources-crud", CustomTestResourcesCrud)
+ registerTest("resources-query-all", CustomTestResourcesQueryAll)
+ registerTest("kv-crud", CustomTestAppKVCrud)
+ registerTest("creatables-crud", CustomTestCreatablesCrud)
+}
+
+func CustomTestAccountsCrud(t *customT) {
+ aow, err := t.db.MakeAccountsOptimizedWriter(true, false, false, false)
+ require.NoError(t, err)
+
+ aor, err := t.db.MakeAccountsOptimizedReader()
+ require.NoError(t, err)
+
+ aw, err := t.db.MakeAccountsWriter()
+ require.NoError(t, err)
+
+ ar, err := t.db.MakeAccountsReader()
+ require.NoError(t, err)
+
+ // set round to 3
+ // Note: this will be used to check that we read the round
+ expectedRound := basics.Round(3)
+ err = aw.UpdateAccountsRound(expectedRound)
+ require.NoError(t, err)
+
+ // generate some test data
+ addrA := RandomAddress()
+ dataA := trackerdb.BaseAccountData{
+ RewardsBase: 1000,
+ }
+
+ // insert the account
+ normBalanceA := dataA.NormalizedOnlineBalance(t.proto)
+ refA, err := aow.InsertAccount(addrA, normBalanceA, dataA)
+ require.NoError(t, err)
+
+ // read the account
+ padA, err := aor.LookupAccount(addrA)
+ require.NoError(t, err)
+ require.Equal(t, addrA, padA.Addr) // addr is present and correct
+ require.Equal(t, refA, padA.Ref) // same ref as when we inserted it
+ require.Equal(t, dataA, padA.AccountData) // same data
+ require.Equal(t, expectedRound, padA.Round) // db round
+
+ // read the accounts "ref"
+ readRefA, err := ar.LookupAccountRowID(addrA)
+ require.NoError(t, err)
+ require.Equal(t, refA, readRefA) // same ref as when we inserted it
+
+ // update the account
+ dataA.RewardsBase = 98287
+ normBalanceA = dataA.NormalizedOnlineBalance(t.proto)
+ _, err = aow.UpdateAccount(refA, normBalanceA, dataA)
+ require.NoError(t, err)
+
+ // read updated account
+ padA, err = aor.LookupAccount(addrA)
+ require.NoError(t, err)
+ require.Equal(t, dataA, padA.AccountData) // same updated data
+
+ // delete account
+ _, err = aow.DeleteAccount(refA)
+ require.NoError(t, err)
+
+ // read deleted account
+ // Note: this is a bit counter-intuitive but lookup returns a value
+ // even when the account doesnt exist.
+ padA, err = aor.LookupAccount(addrA)
+ require.NoError(t, err)
+ require.Equal(t, addrA, padA.Addr) // the addr is there
+ require.Empty(t, padA.AccountData) // no data
+ require.Equal(t, expectedRound, padA.Round) // db round (this is present even if record does not exist)
+}
+
+func CustomTestResourcesCrud(t *customT) {
+ aow, err := t.db.MakeAccountsOptimizedWriter(true, true, false, false)
+ require.NoError(t, err)
+
+ aor, err := t.db.MakeAccountsOptimizedReader()
+ require.NoError(t, err)
+
+ aw, err := t.db.MakeAccountsWriter()
+ require.NoError(t, err)
+
+ // set round to 3
+ // Note: this will be used to check that we read the round
+ expectedRound := basics.Round(3)
+ err = aw.UpdateAccountsRound(expectedRound)
+ require.NoError(t, err)
+
+ //
+ // pre-fill the db with an account for testing
+ //
+
+ // account
+ addrA := RandomAddress()
+ accDataA := trackerdb.BaseAccountData{RewardsBase: 1000}
+ refAccA, err := aow.InsertAccount(addrA, accDataA.NormalizedOnlineBalance(t.proto), accDataA)
+ require.NoError(t, err)
+
+ //
+ // test
+ //
+
+ // generate some test data
+ resDataA0 := trackerdb.MakeResourcesData(0)
+ resDataA0.SetAssetParams(basics.AssetParams{
+ Total: 100,
+ UnitName: "t",
+ AssetName: "test-asset",
+ Manager: addrA,
+ Reserve: addrA,
+ Freeze: addrA,
+ Clawback: addrA,
+ URL: "http://127.0.0.1/8000",
+ }, true)
+ resDataA0.SetAssetHolding(basics.AssetHolding{Amount: 10})
+ aidxResA0 := basics.CreatableIndex(0)
+
+ // insert the resource
+ refResA0, err := aow.InsertResource(refAccA, aidxResA0, resDataA0)
+ require.NoError(t, err)
+ require.NotNil(t, refResA0)
+
+ // read the resource
+ prdA0, err := aor.LookupResources(addrA, aidxResA0, basics.AssetCreatable)
+ require.NoError(t, err)
+ require.Equal(t, aidxResA0, prdA0.Aidx) // aidx is present and correct
+ require.Equal(t, refAccA, prdA0.AcctRef) // acctRef is present and correct
+ require.Equal(t, resDataA0, prdA0.Data) // same data
+ require.Equal(t, expectedRound, prdA0.Round) // db round
+
+ // update the resource
+ resDataA0.Amount = 900
+ _, err = aow.UpdateResource(refAccA, aidxResA0, resDataA0)
+ require.NoError(t, err)
+
+ // read updated resource
+ prdA0, err = aor.LookupResources(addrA, aidxResA0, basics.AssetCreatable)
+ require.NoError(t, err)
+ require.Equal(t, resDataA0, prdA0.Data) // same updated data
+
+ // delete resource
+ _, err = aow.DeleteResource(refAccA, aidxResA0)
+ require.NoError(t, err)
+
+ // read deleted resource
+ // Note: this is a bit counter-intuitive but lookup returns a value
+ // even when the account doesnt exist.
+ prdA0, err = aor.LookupResources(addrA, aidxResA0, basics.AssetCreatable)
+ require.NoError(t, err)
+ require.Equal(t, aidxResA0, prdA0.Aidx) // the aidx is there
+ require.Nil(t, prdA0.AcctRef) // the account ref is not present
+ require.Equal(t, trackerdb.MakeResourcesData(0), prdA0.Data) // rnd 0, clean data
+ require.Equal(t, expectedRound, prdA0.Round) // db round (this is present even if record does not exist)
+}
+
+func CustomTestResourcesQueryAll(t *customT) {
+ aow, err := t.db.MakeAccountsOptimizedWriter(true, true, false, false)
+ require.NoError(t, err)
+
+ aor, err := t.db.MakeAccountsOptimizedReader()
+ require.NoError(t, err)
+
+ aw, err := t.db.MakeAccountsWriter()
+ require.NoError(t, err)
+
+ // set round to 3
+ // Note: this will be used to check that we read the round
+ expectedRound := basics.Round(3)
+ err = aw.UpdateAccountsRound(expectedRound)
+ require.NoError(t, err)
+
+ //
+ // pre-fill the db with an account for testing
+ //
+
+ // account A
+ addrA := RandomAddress()
+ accDataA := trackerdb.BaseAccountData{RewardsBase: 1000}
+ refAccA, err := aow.InsertAccount(addrA, accDataA.NormalizedOnlineBalance(t.proto), accDataA)
+ require.NoError(t, err)
+
+ // resource A-0
+ resDataA0 := trackerdb.ResourcesData{}
+ aidxResA0 := basics.CreatableIndex(0)
+ _, err = aow.InsertResource(refAccA, aidxResA0, resDataA0)
+ require.NoError(t, err)
+
+ // resource A-1
+ resDataA1 := trackerdb.ResourcesData{}
+ aidxResA1 := basics.CreatableIndex(1)
+ _, err = aow.InsertResource(refAccA, aidxResA1, resDataA1)
+ require.NoError(t, err)
+
+ //
+ // test
+ //
+
+ prs, rnd, err := aor.LookupAllResources(addrA)
+ require.NoError(t, err)
+ require.Equal(t, aidxResA0, prs[0].Aidx)
+ require.Equal(t, aidxResA1, prs[1].Aidx)
+ require.Equal(t, expectedRound, prs[0].Round) // db round (inside resources)
+ require.Equal(t, expectedRound, rnd) // db round (from the return)
+}
+
+func CustomTestAppKVCrud(t *customT) {
+ aow, err := t.db.MakeAccountsOptimizedWriter(true, true, true, false)
+ require.NoError(t, err)
+
+ aor, err := t.db.MakeAccountsOptimizedReader()
+ require.NoError(t, err)
+
+ aw, err := t.db.MakeAccountsWriter()
+ require.NoError(t, err)
+
+ // set round to 3
+ // Note: this will be used to check that we read the round
+ expectedRound := basics.Round(3)
+ err = aw.UpdateAccountsRound(expectedRound)
+ require.NoError(t, err)
+
+ //
+ // pre-fill the db with an account for testing
+ //
+
+ // account
+ addrA := RandomAddress()
+ accDataA := trackerdb.BaseAccountData{RewardsBase: 1000}
+ refAccA, err := aow.InsertAccount(addrA, accDataA.NormalizedOnlineBalance(t.proto), accDataA)
+ require.NoError(t, err)
+ // resource
+ resDataA0 := trackerdb.ResourcesData{}
+ aidxResA0 := basics.CreatableIndex(0)
+ refResA0, err := aow.InsertResource(refAccA, aidxResA0, resDataA0)
+ require.NoError(t, err)
+ require.NotNil(t, refResA0)
+
+ //
+ // test
+ //
+
+ // insert the kv
+ kvKey := "foobar-mykey"
+ kvValue := []byte("1234")
+ err = aow.UpsertKvPair(kvKey, kvValue)
+ require.NoError(t, err)
+
+ // read the kv
+ pv1, err := aor.LookupKeyValue(kvKey)
+ require.NoError(t, err)
+ require.Equal(t, kvValue, pv1.Value) // same data
+ require.Equal(t, expectedRound, pv1.Round) // db round
+
+ // update the kv
+ kvValue = []byte("777")
+ err = aow.UpsertKvPair(kvKey, kvValue)
+ require.NoError(t, err)
+
+ // read updated kv
+ pv1, err = aor.LookupKeyValue(kvKey)
+ require.NoError(t, err)
+ require.Equal(t, kvValue, pv1.Value) // same data
+
+ // delete the kv
+ err = aow.DeleteKvPair(kvKey)
+ require.NoError(t, err)
+
+ // read deleted kv
+ require.NoError(t, err)
+
+ // read deleted kv
+ // Note: this is a bit counter-intuitive but lookup returns a value
+ // even when the record doesn't exist.
+ pv1, err = aor.LookupKeyValue(kvKey)
+ require.NoError(t, err)
+ require.Equal(t, expectedRound, pv1.Round) // db round (this is present even if record does not exist)
+}
+
+func CustomTestCreatablesCrud(t *customT) {
+ aow, err := t.db.MakeAccountsOptimizedWriter(true, true, false, true)
+ require.NoError(t, err)
+
+ aor, err := t.db.MakeAccountsOptimizedReader()
+ require.NoError(t, err)
+
+ aw, err := t.db.MakeAccountsWriter()
+ require.NoError(t, err)
+
+ // set round to 3
+ // Note: this will be used to check that we read the round
+ expectedRound := basics.Round(3)
+ err = aw.UpdateAccountsRound(expectedRound)
+ require.NoError(t, err)
+
+ //
+ // pre-fill the db with an account for testing
+ //
+
+ // account A
+ addrA := RandomAddress()
+ accDataA := trackerdb.BaseAccountData{RewardsBase: 1000}
+ refAccA, err := aow.InsertAccount(addrA, accDataA.NormalizedOnlineBalance(t.proto), accDataA)
+ require.NoError(t, err)
+
+ // resource A-0
+ resDataA0 := trackerdb.ResourcesData{}
+ aidxResA0 := basics.CreatableIndex(0)
+ _, err = aow.InsertResource(refAccA, aidxResA0, resDataA0)
+ require.NoError(t, err)
+
+ // resource A-1
+ resDataA1 := trackerdb.ResourcesData{}
+ aidxResA1 := basics.CreatableIndex(1)
+ _, err = aow.InsertResource(refAccA, aidxResA1, resDataA1)
+ require.NoError(t, err)
+
+ //
+ // test
+ //
+
+ // insert creator for A0
+ resA0ctype := basics.AssetCreatable
+ cRefA0, err := aow.InsertCreatable(aidxResA0, resA0ctype, addrA[:])
+ require.NoError(t, err)
+ require.NotNil(t, cRefA0)
+
+ // insert creator for A1
+ resA1ctype := basics.AppCreatable
+ cRefA1, err := aow.InsertCreatable(aidxResA1, resA1ctype, addrA[:])
+ require.NoError(t, err)
+ require.NotNil(t, cRefA1)
+
+ // lookup creator (correct ctype)
+ addr, ok, rnd, err := aor.LookupCreator(aidxResA0, basics.AssetCreatable)
+ require.NoError(t, err)
+ require.True(t, ok) // ok=true when it works
+ require.Equal(t, addrA, addr) // correct owner
+ require.Equal(t, expectedRound, rnd) // db round
+
+ // lookup creator (invalid ctype)
+ _, ok, rnd, err = aor.LookupCreator(aidxResA0, basics.AppCreatable)
+ require.NoError(t, err)
+ require.False(t, ok) // ok=false when its doesnt match
+ require.Equal(t, expectedRound, rnd) // db round (this is present even if record does not exist)
+
+ // lookup creator (unknown index)
+ _, ok, rnd, err = aor.LookupCreator(basics.CreatableIndex(999), basics.AppCreatable)
+ require.NoError(t, err)
+ require.False(t, ok) // ok=false when it doesn't exist
+ require.Equal(t, expectedRound, rnd) // db round (this is present even if record does not exist)
+
+}
diff --git a/ledger/store/trackerdb/testsuite/dbsemantics_test.go b/ledger/store/trackerdb/testsuite/dbsemantics_test.go
new file mode 100644
index 000000000..b155b918a
--- /dev/null
+++ b/ledger/store/trackerdb/testsuite/dbsemantics_test.go
@@ -0,0 +1,82 @@
+// Copyright (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 <https://www.gnu.org/licenses/>.
+
+package testsuite
+
+import (
+ "context"
+
+ "github.com/algorand/go-algorand/ledger/store/trackerdb"
+ "github.com/stretchr/testify/require"
+)
+
+func init() {
+ // register tests that will run on each KV implementation
+ registerTest("db-semantics-transaction", CustomTestTransaction)
+}
+
+// This test will ensure that transaction semantics carry the same meaning across all engine implementations.
+func CustomTestTransaction(t *customT) {
+ aow, err := t.db.MakeAccountsOptimizedWriter(true, false, false, false)
+ require.NoError(t, err)
+
+ aor, err := t.db.MakeAccountsOptimizedReader()
+ require.NoError(t, err)
+
+ // generate some test data
+ addrA := RandomAddress()
+ dataA := trackerdb.BaseAccountData{
+ RewardsBase: 1000,
+ }
+
+ // insert the account
+ normBalanceA := dataA.NormalizedOnlineBalance(t.proto)
+ refA, err := aow.InsertAccount(addrA, normBalanceA, dataA)
+ require.NoError(t, err)
+
+ //
+ // test
+ //
+
+ err = t.db.Transaction(func(ctx context.Context, tx trackerdb.TransactionScope) (err error) {
+ // create a scoped writer
+ aow, err := tx.MakeAccountsOptimizedWriter(true, false, false, false)
+ require.NoError(t, err)
+
+ // create a scoped reader
+ aor, err := tx.MakeAccountsOptimizedReader()
+ require.NoError(t, err)
+
+ // read an account
+ padA, err := aor.LookupAccount(addrA)
+ require.NoError(t, err)
+ require.Equal(t, refA, padA.Ref) // same ref as when we inserted it
+
+ // update the account
+ dataA.RewardsBase = 98287
+ normBalanceA = dataA.NormalizedOnlineBalance(t.proto)
+ _, err = aow.UpdateAccount(refA, normBalanceA, dataA)
+ require.NoError(t, err)
+
+ return nil
+ })
+ require.NoError(t, err)
+
+ // read the updated record outside the transaction to make sure it was commited
+ padA, err := aor.LookupAccount(addrA)
+ require.NoError(t, err)
+ require.Equal(t, uint64(98287), padA.AccountData.RewardsBase) // same updated data
+}
diff --git a/ledger/store/trackerdb/testsuite/migration_test.go b/ledger/store/trackerdb/testsuite/migration_test.go
new file mode 100644
index 000000000..10d9c7473
--- /dev/null
+++ b/ledger/store/trackerdb/testsuite/migration_test.go
@@ -0,0 +1,140 @@
+// Copyright (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 <https://www.gnu.org/licenses/>.
+
+package testsuite
+
+import (
+ "context"
+
+ "github.com/algorand/go-algorand/crypto"
+ "github.com/algorand/go-algorand/data/basics"
+ "github.com/algorand/go-algorand/ledger/ledgercore"
+ "github.com/algorand/go-algorand/ledger/store/trackerdb"
+ "github.com/algorand/go-algorand/logging"
+ "github.com/algorand/go-algorand/protocol"
+ "github.com/stretchr/testify/require"
+)
+
+func init() {
+ // register tests that will run on each KV implementation
+ registerTest("db-migration-check-basic", CustomTestChecBasicMigration)
+ // Disabled since it's technically broken the way its written.
+ // registerTest("db-migration-check-with-accounts", CustomTestCheckMigrationWithAccounts)
+}
+
+func CustomTestChecBasicMigration(t *customT) {
+ ar, err := t.db.MakeAccountsReader()
+ require.NoError(t, err)
+
+ // check round
+ round, err := ar.AccountsRound()
+ require.NoError(t, err)
+ require.Equal(t, basics.Round(0), round) // initialized to round 0
+
+ // check account totals
+ totals, err := ar.AccountsTotals(context.Background(), false)
+ require.NoError(t, err)
+ require.Equal(t, uint64(0), totals.RewardsLevel)
+ require.Equal(t, ledgercore.AlgoCount{}, totals.Online)
+ require.Equal(t, ledgercore.AlgoCount{}, totals.Offline)
+ require.Equal(t, ledgercore.AlgoCount{}, totals.NotParticipating)
+
+ // check tx-tails
+ txTailData, hashes, baseRound, err := ar.LoadTxTail(context.Background(), basics.Round(0))
+ require.NoError(t, err)
+ require.Len(t, txTailData, 0) // no data
+ require.Len(t, hashes, 0) // no data
+ require.Equal(t, basics.Round(1), baseRound) // (the impls return +1 at the end)
+
+ // check online accounts
+ oas, err := ar.OnlineAccountsAll(99)
+ require.NoError(t, err)
+ require.Len(t, oas, 0)
+
+ // check online round params
+ oparams, endRound, err := ar.AccountsOnlineRoundParams()
+ require.NoError(t, err)
+ require.Len(t, oparams, 1)
+ require.Equal(t, basics.Round(0), endRound)
+ require.Equal(t, uint64(0), oparams[0].OnlineSupply)
+ require.Equal(t, uint64(0), oparams[0].RewardsLevel)
+ require.Equal(t, protocol.ConsensusCurrentVersion, oparams[0].CurrentProtocol)
+}
+
+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
+}
+
+func CustomTestCheckMigrationWithAccounts(t *customT) {
+ aw, err := t.db.MakeAccountsWriter()
+ require.NoError(t, err)
+
+ ar, err := t.db.MakeAccountsReader()
+ require.NoError(t, err)
+
+ // reset
+ aw.AccountsReset(context.Background())
+
+ initAccounts := make(map[basics.Address]basics.AccountData)
+
+ addrA := basics.Address(crypto.Hash([]byte("a")))
+ initAccounts[addrA] = makeAccountData(basics.Online, basics.MicroAlgos{Raw: 100})
+
+ addrB := basics.Address(crypto.Hash([]byte("b")))
+ initAccounts[addrB] = makeAccountData(basics.Online, basics.MicroAlgos{Raw: 42})
+
+ addrC := basics.Address(crypto.Hash([]byte("c")))
+ initAccounts[addrC] = makeAccountData(basics.Offline, basics.MicroAlgos{Raw: 30})
+
+ addrD := basics.Address(crypto.Hash([]byte("d")))
+ initAccounts[addrD] = makeAccountData(basics.NotParticipating, basics.MicroAlgos{Raw: 7})
+
+ params := trackerdb.Params{
+ InitProto: protocol.ConsensusCurrentVersion,
+ InitAccounts: initAccounts,
+ }
+
+ // re-run migrations
+ _, err = t.db.RunMigrations(context.Background(), params, logging.TestingLog(t), trackerdb.AccountDBVersion)
+ require.NoError(t, err)
+
+ // check account totals
+ totals, err := ar.AccountsTotals(context.Background(), false)
+ require.NoError(t, err)
+ require.Equal(t, uint64(0), totals.RewardsLevel)
+ require.Equal(t, ledgercore.AlgoCount{Money: basics.MicroAlgos{Raw: 142}}, totals.Online)
+ require.Equal(t, ledgercore.AlgoCount{Money: basics.MicroAlgos{Raw: 30}}, totals.Offline)
+ require.Equal(t, ledgercore.AlgoCount{Money: basics.MicroAlgos{Raw: 7}}, totals.NotParticipating)
+
+ // check online accounts
+ oas, err := ar.OnlineAccountsAll(99)
+ require.NoError(t, err)
+ require.Len(t, oas, 2)
+
+ // check online round params
+ oparams, endRound, err := ar.AccountsOnlineRoundParams()
+ require.NoError(t, err)
+ require.Len(t, oparams, 1)
+ require.Equal(t, basics.Round(0), endRound)
+ require.Equal(t, uint64(142), oparams[0].OnlineSupply)
+ require.Equal(t, uint64(0), oparams[0].RewardsLevel)
+ require.Equal(t, protocol.ConsensusCurrentVersion, oparams[0].CurrentProtocol)
+}
diff --git a/ledger/store/trackerdb/testsuite/mockdb_test.go b/ledger/store/trackerdb/testsuite/mockdb_test.go
new file mode 100644
index 000000000..cc9397f2b
--- /dev/null
+++ b/ledger/store/trackerdb/testsuite/mockdb_test.go
@@ -0,0 +1,34 @@
+// Copyright (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 <https://www.gnu.org/licenses/>.
+
+package testsuite
+
+import (
+ "testing"
+
+ "github.com/algorand/go-algorand/config"
+)
+
+func TestMockDB(t *testing.T) {
+ dbFactory := func(proto config.ConsensusParams) dbForTests {
+ db := makeMockDB(proto)
+
+ seedDb(t, db)
+
+ return db
+ }
+ runGenericTestsWithDB(t, dbFactory)
+}
diff --git a/ledger/store/trackerdb/testsuite/onlineaccounts_kv_test.go b/ledger/store/trackerdb/testsuite/onlineaccounts_kv_test.go
new file mode 100644
index 000000000..9062587b9
--- /dev/null
+++ b/ledger/store/trackerdb/testsuite/onlineaccounts_kv_test.go
@@ -0,0 +1,521 @@
+// Copyright (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 <https://www.gnu.org/licenses/>.
+
+package testsuite
+
+import (
+ "github.com/algorand/go-algorand/crypto"
+ "github.com/algorand/go-algorand/data/basics"
+ "github.com/algorand/go-algorand/ledger/store/trackerdb"
+ "github.com/algorand/go-algorand/protocol"
+ "github.com/stretchr/testify/require"
+)
+
+func init() {
+ // register tests that will run on each KV implementation
+ registerTest("online-accounts-write-read", CustomTestOnlineAccountsWriteRead)
+ registerTest("online-accounts-all", CustomTestOnlineAccountsAll)
+ registerTest("online-accounts-top", CustomTestAccountsOnlineTop)
+ registerTest("online-accounts-get-by-addr", CustomTestLookupOnlineAccountDataByAddress)
+ registerTest("online-accounts-history", CustomTestOnlineAccountHistory)
+ registerTest("online-accounts-delete", CustomTestOnlineAccountsDelete)
+ registerTest("online-accounts-expired", CustomTestAccountsOnlineExpired)
+}
+
+func CustomTestOnlineAccountsWriteRead(t *customT) {
+ oaw, err := t.db.MakeOnlineAccountsOptimizedWriter(true)
+ require.NoError(t, err)
+
+ oar, err := t.db.MakeOnlineAccountsOptimizedReader()
+ require.NoError(t, err)
+
+ aw, err := t.db.MakeAccountsWriter()
+ require.NoError(t, err)
+
+ // set round to 3
+ // Note: this will be used to check that we read the round
+ expectedRound := basics.Round(3)
+ err = aw.UpdateAccountsRound(expectedRound)
+ require.NoError(t, err)
+
+ // generate some test data
+ addrA := RandomAddress()
+ updRoundA := uint64(400)
+ lastValidA := uint64(500)
+ dataA := trackerdb.BaseOnlineAccountData{
+ BaseVotingData: trackerdb.BaseVotingData{},
+ MicroAlgos: basics.MicroAlgos{Raw: uint64(100)},
+ RewardsBase: uint64(200),
+ }
+ normalizedBalA := dataA.NormalizedOnlineBalance(t.proto)
+
+ // write
+ refA, err := oaw.InsertOnlineAccount(addrA, normalizedBalA, dataA, updRoundA, lastValidA)
+ require.NoError(t, err)
+
+ // read
+ poA, err := oar.LookupOnline(addrA, basics.Round(updRoundA))
+ require.NoError(t, err)
+ require.Equal(t, addrA, poA.Addr)
+ require.Equal(t, refA, poA.Ref)
+ require.Equal(t, dataA, poA.AccountData)
+ require.Equal(t, basics.Round(updRoundA), poA.UpdRound) // check the "update round" was read
+ require.Equal(t, expectedRound, poA.Round)
+
+ // write a new version
+ dataA.MicroAlgos = basics.MicroAlgos{Raw: uint64(321)}
+ normalizedBalA = dataA.NormalizedOnlineBalance(t.proto)
+ updRoundA = uint64(450)
+ _, err = oaw.InsertOnlineAccount(addrA, normalizedBalA, dataA, updRoundA, lastValidA)
+ require.NoError(t, err)
+
+ // read (latest)
+ poA, err = oar.LookupOnline(addrA, basics.Round(500))
+ require.NoError(t, err)
+ require.Equal(t, dataA, poA.AccountData) // check the data is from the new version
+ require.Equal(t, basics.Round(updRoundA), poA.UpdRound) // check the "update round"
+
+ // read (original)
+ poA, err = oar.LookupOnline(addrA, basics.Round(405))
+ require.NoError(t, err)
+ require.Equal(t, basics.MicroAlgos{Raw: uint64(100)}, poA.AccountData.MicroAlgos) // check the data is from the new version
+ require.Equal(t, basics.Round(400), poA.UpdRound) // check the "update round"
+
+ // read (at upd round)
+ poA, err = oar.LookupOnline(addrA, basics.Round(450))
+ require.NoError(t, err)
+ require.Equal(t, basics.MicroAlgos{Raw: uint64(321)}, poA.AccountData.MicroAlgos) // check the data is from the new version
+ require.Equal(t, basics.Round(450), poA.UpdRound) // check the "update round"
+}
+
+func CustomTestOnlineAccountHistory(t *customT) {
+ oaw, err := t.db.MakeOnlineAccountsOptimizedWriter(true)
+ require.NoError(t, err)
+
+ oar, err := t.db.MakeOnlineAccountsOptimizedReader()
+ require.NoError(t, err)
+
+ aw, err := t.db.MakeAccountsWriter()
+ require.NoError(t, err)
+
+ // set round to 3
+ // Note: this will be used to check that we read the round
+ expectedRound := basics.Round(3)
+ err = aw.UpdateAccountsRound(expectedRound)
+ require.NoError(t, err)
+
+ // generate some test data
+ addrA := RandomAddress()
+ dataA1 := trackerdb.BaseOnlineAccountData{
+ BaseVotingData: trackerdb.BaseVotingData{},
+ MicroAlgos: basics.MicroAlgos{Raw: uint64(20)},
+ RewardsBase: uint64(200),
+ }
+ normalizedBalA1 := dataA1.NormalizedOnlineBalance(t.proto)
+
+ refA1, err := oaw.InsertOnlineAccount(addrA, normalizedBalA1, dataA1, uint64(2), uint64(2))
+ require.NoError(t, err)
+
+ // generate some test data
+ dataA2 := trackerdb.BaseOnlineAccountData{
+ BaseVotingData: trackerdb.BaseVotingData{},
+ MicroAlgos: basics.MicroAlgos{Raw: uint64(100)},
+ RewardsBase: uint64(200),
+ }
+ normalizedBalA2 := dataA2.NormalizedOnlineBalance(t.proto)
+
+ refA2, err := oaw.InsertOnlineAccount(addrA, normalizedBalA2, dataA2, uint64(3), uint64(3))
+ require.NoError(t, err)
+
+ // generate some test data
+ addrB := RandomAddress()
+ dataB1 := trackerdb.BaseOnlineAccountData{
+ BaseVotingData: trackerdb.BaseVotingData{},
+ MicroAlgos: basics.MicroAlgos{Raw: uint64(75)},
+ RewardsBase: uint64(200),
+ }
+ normalizedBalB1 := dataB1.NormalizedOnlineBalance(t.proto)
+
+ refB1, err := oaw.InsertOnlineAccount(addrB, normalizedBalB1, dataB1, uint64(3), uint64(3))
+ require.NoError(t, err)
+
+ //
+ // the test
+ //
+
+ resultsA, rnd, err := oar.LookupOnlineHistory(addrA)
+ require.NoError(t, err)
+ require.Equal(t, expectedRound, rnd) // check the db round
+ require.Len(t, resultsA, 2)
+ require.Equal(t, basics.Round(2), resultsA[0].UpdRound) // check ordering
+ require.Equal(t, basics.Round(3), resultsA[1].UpdRound) // check ordering
+ // check item fields
+ require.Empty(t, resultsA[0].Round) // check the db round is not set
+ require.Equal(t, addrA, resultsA[0].Addr) // check addr
+ require.Equal(t, refA1, resultsA[0].Ref) // check ref
+ require.Equal(t, dataA1, resultsA[0].AccountData) // check data
+ // check ref is valid on all
+ require.Equal(t, refA2, resultsA[1].Ref) // check ref
+
+ // check for B
+ resultsB, _, err := oar.LookupOnlineHistory(addrB)
+ require.NoError(t, err)
+ require.Len(t, resultsB, 1)
+ require.Equal(t, addrB, resultsB[0].Addr) // check addr
+ require.Equal(t, refB1, resultsB[0].Ref) // check ref
+}
+
+func CustomTestOnlineAccountsAll(t *customT) {
+ oaw, err := t.db.MakeOnlineAccountsOptimizedWriter(true)
+ require.NoError(t, err)
+
+ ar, err := t.db.MakeAccountsReader()
+ require.NoError(t, err)
+
+ voteLastValid := uint64(0)
+
+ // generate some test data
+ addrA := basics.Address(crypto.Hash([]byte("a")))
+ dataA0 := trackerdb.BaseOnlineAccountData{
+ MicroAlgos: basics.MicroAlgos{Raw: uint64(200)},
+ }
+ _, err = oaw.InsertOnlineAccount(addrA, dataA0.NormalizedOnlineBalance(t.proto), dataA0, 0, voteLastValid)
+ require.NoError(t, err)
+
+ dataA1 := trackerdb.BaseOnlineAccountData{
+ MicroAlgos: basics.MicroAlgos{Raw: uint64(250)},
+ }
+ _, err = oaw.InsertOnlineAccount(addrA, dataA1.NormalizedOnlineBalance(t.proto), dataA1, 1, voteLastValid)
+ require.NoError(t, err)
+
+ addrB := basics.Address(crypto.Hash([]byte("b")))
+ dataB := trackerdb.BaseOnlineAccountData{
+ MicroAlgos: basics.MicroAlgos{Raw: uint64(100)},
+ }
+ _, err = oaw.InsertOnlineAccount(addrB, dataB.NormalizedOnlineBalance(t.proto), dataB, 0, voteLastValid)
+ require.NoError(t, err)
+
+ addrC := basics.Address(crypto.Hash([]byte("c")))
+ dataC := trackerdb.BaseOnlineAccountData{
+ MicroAlgos: basics.MicroAlgos{Raw: uint64(30)},
+ }
+ _, err = oaw.InsertOnlineAccount(addrC, dataC.NormalizedOnlineBalance(t.proto), dataC, 0, voteLastValid)
+ require.NoError(t, err)
+
+ //
+ // test
+ //
+
+ // read all accounts (with max accounts)
+ poA, err := ar.OnlineAccountsAll(2)
+ require.NoError(t, err)
+ require.Len(t, poA, 3) // account A has 2 records + 1 record from account B
+
+ require.Equal(t, addrA, poA[0].Addr)
+ require.Equal(t, basics.MicroAlgos{Raw: uint64(200)}, poA[0].AccountData.MicroAlgos)
+ require.Equal(t, basics.Round(0), poA[0].UpdRound)
+
+ require.Equal(t, addrA, poA[1].Addr)
+ require.Equal(t, basics.MicroAlgos{Raw: uint64(250)}, poA[1].AccountData.MicroAlgos)
+ require.Equal(t, basics.Round(1), poA[1].UpdRound)
+
+ require.Equal(t, addrB, poA[2].Addr)
+ require.Equal(t, basics.MicroAlgos{Raw: uint64(100)}, poA[2].AccountData.MicroAlgos)
+ require.Equal(t, basics.Round(0), poA[2].UpdRound)
+}
+
+func CustomTestAccountsOnlineTop(t *customT) {
+ oaw, err := t.db.MakeOnlineAccountsOptimizedWriter(true)
+ require.NoError(t, err)
+
+ ar, err := t.db.MakeAccountsReader()
+ require.NoError(t, err)
+
+ // generate some test data
+ var testData []basics.Address
+ updRound := uint64(0)
+ for i := 0; i < 10; i++ {
+ addr := RandomAddress()
+ microAlgos := basics.MicroAlgos{Raw: uint64(10 + i*100)}
+ rewardBase := uint64(200 + i)
+ lastValid := uint64(500 + i)
+ data := trackerdb.BaseOnlineAccountData{
+ BaseVotingData: trackerdb.BaseVotingData{},
+ MicroAlgos: microAlgos,
+ RewardsBase: rewardBase,
+ }
+ normalizedBal := data.NormalizedOnlineBalance(t.proto)
+
+ // write
+ _, err := oaw.InsertOnlineAccount(addr, normalizedBal, data, updRound, lastValid)
+ require.NoError(t, err)
+
+ testData = append(testData, addr)
+ }
+
+ // read (all)
+ poA, err := ar.AccountsOnlineTop(basics.Round(0), 0, 10, t.proto)
+ require.NoError(t, err)
+ require.Contains(t, poA, testData[9]) // most money
+ require.Contains(t, poA, testData[0]) // least money
+
+ // read (just a few)
+ poA, err = ar.AccountsOnlineTop(basics.Round(0), 1, 2, t.proto)
+ require.NoError(t, err)
+ require.Len(t, poA, 2)
+ require.Contains(t, poA, testData[8]) // (second most money, we skipped 1)
+ require.Contains(t, poA, testData[7]) // (third, we only have 2 items)
+}
+
+func CustomTestLookupOnlineAccountDataByAddress(t *customT) {
+ oaw, err := t.db.MakeOnlineAccountsOptimizedWriter(true)
+ require.NoError(t, err)
+
+ ar, err := t.db.MakeAccountsReader()
+ require.NoError(t, err)
+
+ // generate some test data
+ addrA := RandomAddress()
+ updRoundA := uint64(400)
+ lastValidA := uint64(500)
+ dataA := trackerdb.BaseOnlineAccountData{
+ BaseVotingData: trackerdb.BaseVotingData{},
+ MicroAlgos: basics.MicroAlgos{Raw: uint64(100)},
+ RewardsBase: uint64(200),
+ }
+ normalizedBalA := dataA.NormalizedOnlineBalance(t.proto)
+
+ refA, err := oaw.InsertOnlineAccount(addrA, normalizedBalA, dataA, updRoundA, lastValidA)
+ require.NoError(t, err)
+
+ //
+ // test
+ //
+
+ // check non-existing account
+ nonExistingAddr := RandomAddress()
+ _, _, err = ar.LookupOnlineAccountDataByAddress(nonExistingAddr)
+ require.Error(t, err)
+ require.Equal(t, trackerdb.ErrNotFound, err) // check the error type
+
+ // read existing addr
+ readRef, readData, err := ar.LookupOnlineAccountDataByAddress(addrA)
+ require.NoError(t, err)
+ require.Equal(t, refA, readRef) // check ref is the same
+ // the method returns raw bytes, parse them
+ var badA trackerdb.BaseOnlineAccountData
+ err = protocol.Decode(readData, &badA)
+ require.NoError(t, err)
+ require.Equal(t, dataA, badA)
+}
+
+func CustomTestOnlineAccountsDelete(t *customT) {
+ oaw, err := t.db.MakeOnlineAccountsOptimizedWriter(true)
+ require.NoError(t, err)
+
+ oar, err := t.db.MakeOnlineAccountsOptimizedReader()
+ require.NoError(t, err)
+
+ ar, err := t.db.MakeAccountsReader()
+ require.NoError(t, err)
+
+ aw, err := t.db.MakeAccountsWriter()
+ require.NoError(t, err)
+
+ // set round to 3
+ // Note: this will be used to check that we read the round
+ expectedRound := basics.Round(3)
+ err = aw.UpdateAccountsRound(expectedRound)
+ require.NoError(t, err)
+
+ // generate some test data
+ addrA := RandomAddress()
+ dataA1 := trackerdb.BaseOnlineAccountData{
+ // some value so its NOT empty
+ BaseVotingData: trackerdb.BaseVotingData{VoteKeyDilution: 1},
+ MicroAlgos: basics.MicroAlgos{Raw: uint64(20)},
+ RewardsBase: uint64(200),
+ }
+ normalizedBalA1 := dataA1.NormalizedOnlineBalance(t.proto)
+
+ _, err = oaw.InsertOnlineAccount(addrA, normalizedBalA1, dataA1, uint64(0), uint64(2))
+ require.NoError(t, err)
+
+ // generate some test data
+ dataA2 := trackerdb.BaseOnlineAccountData{
+ // some value so its NOT empty
+ BaseVotingData: trackerdb.BaseVotingData{VoteKeyDilution: 1},
+ MicroAlgos: basics.MicroAlgos{Raw: uint64(100)},
+ RewardsBase: uint64(200),
+ }
+ normalizedBalA2 := dataA2.NormalizedOnlineBalance(t.proto)
+
+ _, err = oaw.InsertOnlineAccount(addrA, normalizedBalA2, dataA2, uint64(1), uint64(3))
+ require.NoError(t, err)
+
+ // generate some test data
+ addrB := RandomAddress()
+ dataB1 := trackerdb.BaseOnlineAccountData{
+ // some value so its NOT empty
+ BaseVotingData: trackerdb.BaseVotingData{VoteKeyDilution: 1},
+ MicroAlgos: basics.MicroAlgos{Raw: uint64(75)},
+ RewardsBase: uint64(200),
+ }
+ normalizedBalB1 := dataB1.NormalizedOnlineBalance(t.proto)
+
+ _, err = oaw.InsertOnlineAccount(addrB, normalizedBalB1, dataB1, uint64(2), uint64(3))
+ require.NoError(t, err)
+
+ // timeline
+ // round 0: A touched [0]
+ // round 1: A touched [1,0]
+ // round 2: B touched [2] + A remains [1,0]
+
+ //
+ // the test
+ //
+
+ // delete before 0 (no changes)
+ err = aw.OnlineAccountsDelete(basics.Round(0))
+ require.NoError(t, err)
+
+ // check they are all there
+ oas, err := ar.AccountsOnlineTop(basics.Round(0), 0, 10, t.proto)
+ require.NoError(t, err)
+ require.Len(t, oas, 1)
+ require.Equal(t, oas[addrA].MicroAlgos, basics.MicroAlgos{Raw: uint64(20)}) // check item
+ // read the accounts directly
+ poaA, err := oar.LookupOnline(addrA, basics.Round(0))
+ require.NoError(t, err)
+ require.NotNil(t, poaA.Ref) // A was found
+
+ // delete before round 1
+ err = aw.OnlineAccountsDelete(basics.Round(1))
+ require.NoError(t, err)
+
+ // check they are all there
+ oas, err = ar.AccountsOnlineTop(basics.Round(1), 0, 10, t.proto)
+ require.NoError(t, err)
+ require.Len(t, oas, 1)
+ require.Equal(t, oas[addrA].MicroAlgos, basics.MicroAlgos{Raw: uint64(100)}) // check item
+ // read the accounts directly
+ poaA, err = oar.LookupOnline(addrA, basics.Round(1))
+ require.NoError(t, err)
+ require.NotNil(t, poaA.Ref) // A was found
+
+ // delete before round 2
+ err = aw.OnlineAccountsDelete(basics.Round(2))
+ require.NoError(t, err)
+
+ // check they are all there
+ oas, err = ar.AccountsOnlineTop(basics.Round(2), 0, 10, t.proto)
+ require.NoError(t, err)
+ require.Len(t, oas, 2)
+ require.Equal(t, oas[addrB].MicroAlgos, basics.MicroAlgos{Raw: uint64(75)}) // check item
+ // read the accounts directly
+ poaA, err = oar.LookupOnline(addrA, basics.Round(2))
+ require.NoError(t, err)
+ require.NotNil(t, poaA.Ref) // A is still found, the latest record is kept
+ require.Equal(t, basics.Round(1), poaA.UpdRound) // the latest we find is at 1
+ poaB, err := oar.LookupOnline(addrB, basics.Round(2))
+ require.NoError(t, err)
+ require.NotNil(t, poaB.Ref) // B was found
+}
+
+func CustomTestAccountsOnlineExpired(t *customT) {
+ oaw, err := t.db.MakeOnlineAccountsOptimizedWriter(true)
+ require.NoError(t, err)
+
+ ar, err := t.db.MakeAccountsReader()
+ require.NoError(t, err)
+
+ // generate some test data
+ addrA := RandomAddress()
+ dataA1 := trackerdb.BaseOnlineAccountData{
+ BaseVotingData: trackerdb.BaseVotingData{VoteLastValid: basics.Round(2)},
+ MicroAlgos: basics.MicroAlgos{Raw: uint64(20)},
+ RewardsBase: uint64(0),
+ }
+ normalizedBalA1 := dataA1.NormalizedOnlineBalance(t.proto)
+
+ _, err = oaw.InsertOnlineAccount(addrA, normalizedBalA1, dataA1, uint64(0), uint64(2))
+ require.NoError(t, err)
+
+ // generate some test data
+ dataA2 := trackerdb.BaseOnlineAccountData{
+ BaseVotingData: trackerdb.BaseVotingData{VoteLastValid: basics.Round(5)},
+ MicroAlgos: basics.MicroAlgos{Raw: uint64(100)},
+ RewardsBase: uint64(0),
+ }
+ normalizedBalA2 := dataA2.NormalizedOnlineBalance(t.proto)
+
+ _, err = oaw.InsertOnlineAccount(addrA, normalizedBalA2, dataA2, uint64(1), uint64(5))
+ require.NoError(t, err)
+
+ // generate some test data
+ addrB := RandomAddress()
+ dataB1 := trackerdb.BaseOnlineAccountData{
+ // some value so its NOT empty
+ BaseVotingData: trackerdb.BaseVotingData{VoteLastValid: basics.Round(7)},
+ MicroAlgos: basics.MicroAlgos{Raw: uint64(75)},
+ RewardsBase: uint64(0),
+ }
+ normalizedBalB1 := dataB1.NormalizedOnlineBalance(t.proto)
+
+ _, err = oaw.InsertOnlineAccount(addrB, normalizedBalB1, dataB1, uint64(2), uint64(7))
+ require.NoError(t, err)
+
+ // timeline
+ // round 0: A touched [0] // A expires at 2
+ // round 1: A touched [1,0] // A expires at 5
+ // round 2: B touched [2] + A remains [1,0] // A expires at 5, B expires at 7
+
+ //
+ // the test
+ //
+
+ // read (none)
+ expAccts, err := ar.ExpiredOnlineAccountsForRound(basics.Round(0), basics.Round(0), t.proto, 0)
+ require.NoError(t, err)
+ require.Empty(t, expAccts)
+
+ // read (at acct round, voteRnd > lastValid)
+ expAccts, err = ar.ExpiredOnlineAccountsForRound(basics.Round(0), basics.Round(4), t.proto, 0)
+ require.NoError(t, err)
+ require.Len(t, expAccts, 1)
+ require.Equal(t, expAccts[addrA].MicroAlgosWithRewards, basics.MicroAlgos{Raw: uint64(20)}) // check item
+
+ // read (at acct round, voteRnd = lastValid)
+ expAccts, err = ar.ExpiredOnlineAccountsForRound(basics.Round(0), basics.Round(2), t.proto, 0)
+ require.NoError(t, err)
+ require.Empty(t, expAccts)
+
+ // read (at acct round, voteRnd < lastValid)
+ expAccts, err = ar.ExpiredOnlineAccountsForRound(basics.Round(0), basics.Round(1), t.proto, 0)
+ require.NoError(t, err)
+ require.Empty(t, expAccts)
+
+ // read (take latest exp value)
+ expAccts, err = ar.ExpiredOnlineAccountsForRound(basics.Round(1), basics.Round(4), t.proto, 0)
+ require.NoError(t, err)
+ require.Len(t, expAccts, 0)
+
+ // read (all)
+ expAccts, err = ar.ExpiredOnlineAccountsForRound(basics.Round(3), basics.Round(20), t.proto, 0)
+ require.Len(t, expAccts, 2)
+ require.Equal(t, expAccts[addrA].MicroAlgosWithRewards, basics.MicroAlgos{Raw: uint64(100)}) // check item
+ require.Equal(t, expAccts[addrB].MicroAlgosWithRewards, basics.MicroAlgos{Raw: uint64(75)}) // check item
+}
diff --git a/ledger/store/trackerdb/testsuite/sqlitedb_test.go b/ledger/store/trackerdb/testsuite/sqlitedb_test.go
new file mode 100644
index 000000000..c4a3d1a7b
--- /dev/null
+++ b/ledger/store/trackerdb/testsuite/sqlitedb_test.go
@@ -0,0 +1,43 @@
+// Copyright (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 <https://www.gnu.org/licenses/>.
+
+package testsuite
+
+import (
+ "fmt"
+ "testing"
+
+ "github.com/algorand/go-algorand/config"
+ "github.com/algorand/go-algorand/ledger/store/trackerdb/sqlitedriver"
+ "github.com/algorand/go-algorand/logging"
+ "github.com/stretchr/testify/require"
+)
+
+func TestSqliteDB(t *testing.T) {
+ dbFactory := func(config.ConsensusParams) dbForTests {
+ // create a tmp dir for the db, the testing runtime will clean it up automatically
+ fn := fmt.Sprintf("%s/tracker-db.sqlite", t.TempDir())
+ db, err := sqlitedriver.Open(fn, false, logging.TestingLog(t))
+ require.NoError(t, err)
+
+ seedDb(t, db)
+
+ return db
+ }
+
+ // run the suite
+ runGenericTestsWithDB(t, dbFactory)
+}
diff --git a/ledger/store/trackerdb/testsuite/stateproofs_kv_test.go b/ledger/store/trackerdb/testsuite/stateproofs_kv_test.go
new file mode 100644
index 000000000..bc4214173
--- /dev/null
+++ b/ledger/store/trackerdb/testsuite/stateproofs_kv_test.go
@@ -0,0 +1,130 @@
+// Copyright (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 <https://www.gnu.org/licenses/>.
+
+package testsuite
+
+import (
+ "context"
+
+ "github.com/algorand/go-algorand/data/basics"
+ "github.com/algorand/go-algorand/ledger/ledgercore"
+ "github.com/algorand/go-algorand/ledger/store/trackerdb"
+ "github.com/stretchr/testify/require"
+)
+
+func init() {
+ // register tests that will run on each KV implementation
+ registerTest("stateproofs-crud", CustomTestStateproofsReadWrite)
+ registerTest("stateproofs-query-all", CustomTestStateproofsQueryAll)
+}
+
+func CustomTestStateproofsReadWrite(t *customT) {
+ spw := t.db.MakeSpVerificationCtxWriter()
+ spr := t.db.MakeSpVerificationCtxReader()
+
+ //
+ // test
+ //
+
+ // store no items
+ err := spw.StoreSPContexts(context.Background(), []*ledgercore.StateProofVerificationContext{})
+ require.NoError(t, err)
+
+ // store some items
+ vcs := []*ledgercore.StateProofVerificationContext{
+ {
+ LastAttestedRound: basics.Round(0),
+ OnlineTotalWeight: basics.MicroAlgos{Raw: 42},
+ },
+ {
+ LastAttestedRound: basics.Round(1),
+ OnlineTotalWeight: basics.MicroAlgos{Raw: 100},
+ },
+ {
+ LastAttestedRound: basics.Round(2),
+ OnlineTotalWeight: basics.MicroAlgos{Raw: 200},
+ },
+ }
+ err = spw.StoreSPContexts(context.Background(), vcs)
+ require.NoError(t, err)
+
+ // read non-existing item
+ vc, err := spr.LookupSPContext(basics.Round(9000))
+ require.Error(t, err)
+ require.Equal(t, trackerdb.ErrNotFound, err)
+
+ // read back a single item
+ vc, err = spr.LookupSPContext(basics.Round(0))
+ require.NoError(t, err)
+ require.Equal(t, basics.Round(0), vc.LastAttestedRound) // check round is set
+ require.Equal(t, basics.MicroAlgos{Raw: 42}, vc.OnlineTotalWeight) // check payload is read
+
+ // delete some items
+ err = spw.DeleteOldSPContexts(context.Background(), basics.Round(1))
+ require.NoError(t, err)
+
+ // read delete items
+ vc, err = spr.LookupSPContext(basics.Round(0))
+ require.Error(t, err)
+ require.Equal(t, trackerdb.ErrNotFound, err)
+
+ // read back remaining items
+ vc, err = spr.LookupSPContext(basics.Round(1))
+ require.NoError(t, err)
+ require.Equal(t, basics.Round(1), vc.LastAttestedRound) // check round is set
+ require.Equal(t, basics.MicroAlgos{Raw: 100}, vc.OnlineTotalWeight) // check payload is read
+
+ // read back remaining items
+ vc, err = spr.LookupSPContext(basics.Round(2))
+ require.NoError(t, err)
+ require.Equal(t, basics.Round(2), vc.LastAttestedRound) // check round is set
+ require.Equal(t, basics.MicroAlgos{Raw: 200}, vc.OnlineTotalWeight) // check payload is read
+}
+
+func CustomTestStateproofsQueryAll(t *customT) {
+ spw := t.db.MakeSpVerificationCtxWriter()
+ spr := t.db.MakeSpVerificationCtxReader()
+
+ // prepare the test with some data
+ // store some items
+ vcs := []*ledgercore.StateProofVerificationContext{
+ {
+ LastAttestedRound: basics.Round(0),
+ OnlineTotalWeight: basics.MicroAlgos{Raw: 42},
+ },
+ {
+ LastAttestedRound: basics.Round(1),
+ OnlineTotalWeight: basics.MicroAlgos{Raw: 100},
+ },
+ {
+ LastAttestedRound: basics.Round(2),
+ OnlineTotalWeight: basics.MicroAlgos{Raw: 200},
+ },
+ }
+ err := spw.StoreSPContexts(context.Background(), vcs)
+ require.NoError(t, err)
+
+ //
+ // test
+ //
+
+ // read all data
+ result, err := spr.GetAllSPContexts(context.Background())
+ require.NoError(t, err)
+ require.Len(t, result, 3) // check all items are present
+ require.Equal(t, basics.Round(0), result[0].LastAttestedRound) // check first item
+ require.Equal(t, basics.Round(2), result[2].LastAttestedRound) // check last item
+}
diff --git a/ledger/store/trackerdb/testsuite/utils_test.go b/ledger/store/trackerdb/testsuite/utils_test.go
new file mode 100644
index 000000000..7862155d0
--- /dev/null
+++ b/ledger/store/trackerdb/testsuite/utils_test.go
@@ -0,0 +1,445 @@
+// Copyright (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 <https://www.gnu.org/licenses/>.
+
+package testsuite
+
+// A collection of utility functions and types to write the tests in this module.
+
+import (
+ "bytes"
+ "context"
+ "io"
+ "sort"
+ "testing"
+ "time"
+
+ "github.com/algorand/go-algorand/config"
+ "github.com/algorand/go-algorand/crypto"
+ "github.com/algorand/go-algorand/data/basics"
+ "github.com/algorand/go-algorand/ledger/store/trackerdb"
+ "github.com/algorand/go-algorand/ledger/store/trackerdb/generickv"
+ "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/db"
+ "github.com/stretchr/testify/require"
+)
+
+type customT struct {
+ db dbForTests
+ proto config.ConsensusParams
+ *testing.T
+}
+
+type dbForTests interface {
+ // generickv.KvWrite
+ // generickv.KvRead
+ trackerdb.Store
+}
+
+type genericTestEntry struct {
+ name string
+ f func(*customT)
+}
+
+// list of tests to be run on each KV DB implementation
+var genericTests []genericTestEntry
+
+// registerTest registers the given test with the suite
+func registerTest(name string, f func(*customT)) {
+ genericTests = append(genericTests, genericTestEntry{name, f})
+}
+
+// runGenericTestsWithDB runs a generic set of tests on the given database
+func runGenericTestsWithDB(t *testing.T, dbFactory func(config.ConsensusParams) (db dbForTests)) {
+ proto := config.Consensus[protocol.ConsensusCurrentVersion]
+ for _, entry := range genericTests {
+ // run each test defined in the suite using the Golang subtest
+ t.Run(entry.name, func(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ // instantiate a new db for each test
+ entry.f(&customT{dbFactory(proto), proto, t})
+ })
+ }
+}
+
+func seedDb(t *testing.T, db dbForTests) {
+ params := trackerdb.Params{InitProto: protocol.ConsensusCurrentVersion}
+ _, err := db.RunMigrations(context.Background(), params, logging.TestingLog(t), trackerdb.AccountDBVersion)
+ require.NoError(t, err)
+}
+
+// RandomAddress generates a random address
+//
+// TODO: this method is defined in ledgertesting, should be moved up to basics so it can be used in more places
+func RandomAddress() basics.Address {
+ var addr basics.Address
+ crypto.RandBytes(addr[:])
+ return addr
+}
+
+type mockDB struct {
+ kvs kvstore
+ proto config.ConsensusParams
+ // use the generickv implementations
+ trackerdb.Reader
+ trackerdb.Writer
+ trackerdb.Catchpoint
+}
+
+func makeMockDB(proto config.ConsensusParams) trackerdb.Store {
+ kvs := kvstore{data: make(map[string][]byte)}
+ var db trackerdb.Store
+ db = &mockDB{
+ kvs,
+ proto,
+ generickv.MakeReader(&kvs, proto),
+ generickv.MakeWriter(db, &kvs, &kvs),
+ generickv.MakeCatchpoint(),
+ }
+ return db
+}
+
+func (db *mockDB) SetSynchronousMode(ctx context.Context, mode db.SynchronousMode, fullfsync bool) (err error) {
+ // TODO
+ return nil
+}
+
+func (db *mockDB) IsSharedCacheConnection() bool {
+ return false
+}
+
+// RunMigrations implements trackerdb.Store
+func (db *mockDB) RunMigrations(ctx context.Context, params trackerdb.Params, log logging.Logger, targetVersion int32) (mgr trackerdb.InitParams, err error) {
+ // create a anonym struct that impls the interface for the migration runner
+ aux := struct {
+ *mockDB
+ *kvstore
+ }{db, &db.kvs}
+ return generickv.RunMigrations(ctx, aux, params, targetVersion)
+}
+
+// Batch implements trackerdb.Store
+func (db *mockDB) Batch(fn trackerdb.BatchFn) (err error) {
+ return db.BatchContext(context.Background(), fn)
+}
+
+// BatchContext implements trackerdb.Store
+func (db *mockDB) BatchContext(ctx context.Context, fn trackerdb.BatchFn) (err error) {
+ handle, err := db.BeginBatch(ctx)
+ if err != nil {
+ return
+ }
+ defer handle.Close()
+
+ // run the batch
+ err = fn(ctx, handle)
+ if err != nil {
+ return
+ }
+
+ // commit the batch
+ err = handle.Commit()
+ if err != nil {
+ return
+ }
+
+ return err
+}
+
+// BeginBatch implements trackerdb.Store
+func (db *mockDB) BeginBatch(ctx context.Context) (trackerdb.Batch, error) {
+ scope := mockBatch{db}
+ return &struct {
+ mockBatch
+ trackerdb.Writer
+ }{scope, generickv.MakeWriter(db, &scope, &db.kvs)}, nil
+}
+
+// Snapshot implements trackerdb.Store
+func (db *mockDB) Snapshot(fn trackerdb.SnapshotFn) (err error) {
+ return db.SnapshotContext(context.Background(), fn)
+}
+
+// SnapshotContext implements trackerdb.Store
+func (db *mockDB) SnapshotContext(ctx context.Context, fn trackerdb.SnapshotFn) (err error) {
+ handle, err := db.BeginSnapshot(ctx)
+ if err != nil {
+ return
+ }
+ defer handle.Close()
+
+ // run the snapshot
+ err = fn(ctx, handle)
+ if err != nil {
+ return
+ }
+
+ return err
+}
+
+// BeginSnapshot implements trackerdb.Store
+func (db *mockDB) BeginSnapshot(ctx context.Context) (trackerdb.Snapshot, error) {
+ scope := mockSnapshot{db}
+ return &struct {
+ mockSnapshot
+ trackerdb.Reader
+ }{scope, generickv.MakeReader(&scope, db.proto)}, nil
+}
+
+// Transaction implements trackerdb.Store
+func (db *mockDB) Transaction(fn trackerdb.TransactionFn) (err error) {
+ return db.TransactionContext(context.Background(), fn)
+}
+
+// TransactionContext implements trackerdb.Store
+func (db *mockDB) TransactionContext(ctx context.Context, fn trackerdb.TransactionFn) (err error) {
+ handle, err := db.BeginTransaction(ctx)
+ if err != nil {
+ return
+ }
+ defer handle.Close()
+
+ // run the transaction
+ err = fn(ctx, handle)
+ if err != nil {
+ return
+ }
+
+ // commit the transaction
+ err = handle.Commit()
+ if err != nil {
+ return
+ }
+
+ return err
+}
+
+// BeginTransaction implements trackerdb.Store
+func (db *mockDB) BeginTransaction(ctx context.Context) (trackerdb.Transaction, error) {
+ scope := mockTransaction{db, db.proto}
+
+ return &struct {
+ mockTransaction
+ trackerdb.Reader
+ trackerdb.Writer
+ trackerdb.Catchpoint
+ }{scope, generickv.MakeReader(&scope, db.proto), generickv.MakeWriter(db, &scope, &scope), generickv.MakeCatchpoint()}, nil
+}
+
+func (db *mockDB) Vacuum(ctx context.Context) (stats db.VacuumStats, err error) {
+ // TODO
+ return stats, nil
+}
+
+func (db *mockDB) ResetToV6Test(ctx context.Context) error {
+ // TODO
+ return nil
+}
+
+func (db *mockDB) Close() {
+ // TODO
+}
+
+type kvstore struct {
+ data map[string][]byte
+}
+
+func (kvs *kvstore) Set(key, value []byte) error {
+ kvs.data[string(key)] = value
+ return nil
+}
+
+func (kvs *kvstore) Get(key []byte) (data []byte, closer io.Closer, err error) {
+ data, ok := kvs.data[string(key)]
+ if !ok {
+ err = trackerdb.ErrNotFound
+ return
+ }
+ return data, io.NopCloser(bytes.NewReader(data)), nil
+}
+
+func (kvs *kvstore) NewIter(low, high []byte, reverse bool) generickv.KvIter {
+ //
+ var keys []string
+
+ slow := string(low)
+ shigh := string(high)
+
+ for k := range kvs.data {
+ if k > slow && k < shigh {
+ keys = append(keys, k)
+ }
+ }
+
+ sort.Strings(keys)
+ if reverse {
+ for i, j := 0, len(keys)-1; i < j; i, j = i+1, j-1 {
+ keys[i], keys[j] = keys[j], keys[i]
+ }
+ }
+
+ return &mockIter{kvs, keys, -1}
+}
+
+func (kvs *kvstore) Delete(key []byte) error {
+ delete(kvs.data, string(key))
+ return nil
+}
+
+func (kvs *kvstore) DeleteRange(start, end []byte) error {
+ var toDelete []string
+ for k := range kvs.data {
+ if k > string(start) && k < string(end) {
+ toDelete = append(toDelete, k)
+ }
+ }
+ for i := range toDelete {
+ delete(kvs.data, toDelete[i])
+ }
+ return nil
+}
+
+type mockSnapshot struct {
+ db *mockDB
+}
+
+func (ss mockSnapshot) Get(key []byte) (value []byte, closer io.Closer, err error) {
+ return ss.db.kvs.Get(key)
+}
+
+func (ss mockSnapshot) NewIter(low, high []byte, reverse bool) generickv.KvIter {
+ return ss.db.kvs.NewIter(low, high, reverse)
+}
+
+func (ss mockSnapshot) ResetTransactionWarnDeadline(ctx context.Context, deadline time.Time) (prevDeadline time.Time, err error) {
+ return time.Now(), nil
+}
+
+func (ss mockSnapshot) Close() error {
+ return nil
+}
+
+type mockTransaction struct {
+ db *mockDB
+ proto config.ConsensusParams
+}
+
+func (txs mockTransaction) RunMigrations(ctx context.Context, params trackerdb.Params, log logging.Logger, targetVersion int32) (mgr trackerdb.InitParams, err error) {
+ // create a anonym struct that impls the interface for the migration runner
+ aux := struct {
+ *mockDB
+ *kvstore
+ }{txs.db, &txs.db.kvs}
+ return generickv.RunMigrations(ctx, aux, params, targetVersion)
+}
+
+func (txs mockTransaction) ResetTransactionWarnDeadline(ctx context.Context, deadline time.Time) (prevDeadline time.Time, err error) {
+ return time.Now(), nil
+}
+
+func (txs mockTransaction) Close() error {
+ return nil
+}
+
+func (txs mockTransaction) Commit() error {
+ return nil
+}
+
+func (txs mockTransaction) Set(key, value []byte) error {
+ return txs.db.kvs.Set(key, value)
+}
+
+func (txs mockTransaction) Get(key []byte) (value []byte, closer io.Closer, err error) {
+ return txs.db.kvs.Get(key)
+}
+
+func (txs mockTransaction) NewIter(low, high []byte, reverse bool) generickv.KvIter {
+ return txs.db.kvs.NewIter(low, high, reverse)
+}
+
+func (txs mockTransaction) Delete(key []byte) error {
+ return txs.db.kvs.Delete(key)
+}
+
+func (txs mockTransaction) DeleteRange(start, end []byte) error {
+ return txs.db.kvs.DeleteRange(start, end)
+}
+
+type mockBatch struct {
+ db *mockDB
+}
+
+func (bs mockBatch) ResetTransactionWarnDeadline(ctx context.Context, deadline time.Time) (prevDeadline time.Time, err error) {
+ return time.Now(), nil
+}
+
+func (bs mockBatch) Close() error {
+ return nil
+}
+
+func (bs mockBatch) Commit() error {
+ return nil
+}
+
+func (bs mockBatch) Set(key, value []byte) error {
+ return bs.db.kvs.Set(key, value)
+}
+
+func (bs mockBatch) Delete(key []byte) error {
+ return bs.db.kvs.Delete(key)
+}
+
+func (bs mockBatch) DeleteRange(start, end []byte) error {
+ return bs.db.kvs.DeleteRange(start, end)
+}
+
+type mockIter struct {
+ kvs *kvstore
+ keys []string
+ curr int
+}
+
+func (iter *mockIter) Next() bool {
+ if iter.curr < len(iter.keys)-1 {
+ iter.curr++
+ return true
+ }
+ iter.curr = -1
+ return false
+}
+
+func (iter *mockIter) Key() []byte {
+ return []byte(iter.keys[iter.curr])
+}
+
+func (iter *mockIter) KeySlice() generickv.Slice {
+ return nil
+}
+
+func (iter *mockIter) Value() ([]byte, error) {
+ return iter.kvs.data[iter.keys[iter.curr]], nil
+}
+
+func (iter *mockIter) ValueSlice() (generickv.Slice, error) {
+ return nil, nil
+}
+
+func (iter *mockIter) Valid() bool {
+ return iter.curr != -1
+}
+
+func (iter *mockIter) Close() {}
diff --git a/ledger/tracker.go b/ledger/tracker.go
index ebed56d78..8cfe71aff 100644
--- a/ledger/tracker.go
+++ b/ledger/tracker.go
@@ -282,6 +282,8 @@ func (dcc deferredCommitContext) newBase() basics.Round {
var errMissingAccountUpdateTracker = errors.New("initializeTrackerCaches : called without a valid accounts update tracker")
func (tr *trackerRegistry) initialize(l ledgerForTracker, trackers []ledgerTracker, cfg config.Local) (err error) {
+ tr.mu.Lock()
+ defer tr.mu.Unlock()
tr.dbs = l.trackerDB()
tr.log = l.trackerLog()
diff --git a/ledger/txtail.go b/ledger/txtail.go
index 7d71ea27e..31e44be77 100644
--- a/ledger/txtail.go
+++ b/ledger/txtail.go
@@ -76,7 +76,8 @@ type txTail struct {
// lowestBlockHeaderRound is the lowest round in blockHeaderData, used as a starting point for old entries removal
lowestBlockHeaderRound basics.Round
- // tailMu is the synchronization mutex for accessing roundTailHashes, roundTailSerializedDeltas and blockHeaderData.
+ // tailMu is the synchronization mutex for accessing internal data including
+ // lastValid, recent, lowWaterMark, roundTailHashes, roundTailSerializedDeltas and blockHeaderData.
tailMu deadlock.RWMutex
lastValid map[basics.Round]map[transactions.Txid]struct{} // map tx.LastValid -> tx confirmed set
@@ -90,6 +91,9 @@ type txTail struct {
}
func (t *txTail) loadFromDisk(l ledgerForTracker, dbRound basics.Round) error {
+ t.tailMu.Lock()
+ defer t.tailMu.Unlock()
+
t.log = l.trackerLog()
var roundData []*trackerdb.TxTailRound
@@ -191,6 +195,9 @@ func (t *txTail) close() {
func (t *txTail) newBlock(blk bookkeeping.Block, delta ledgercore.StateDelta) {
rnd := blk.Round()
+ t.tailMu.Lock()
+ defer t.tailMu.Unlock()
+
if _, has := t.recent[rnd]; has {
// Repeat, ignore
return
@@ -202,7 +209,11 @@ func (t *txTail) newBlock(blk bookkeeping.Block, delta ledgercore.StateDelta) {
tail.Hdr = blk.BlockHeader
for txid, txnInc := range delta.Txids {
- t.putLV(txnInc.LastValid, txid)
+ if _, ok := t.lastValid[txnInc.LastValid]; !ok {
+ t.lastValid[txnInc.LastValid] = make(map[transactions.Txid]struct{})
+ }
+ t.lastValid[txnInc.LastValid][txid] = struct{}{}
+
tail.TxnIDs[txnInc.Intra] = txid
tail.LastValid[txnInc.Intra] = txnInc.LastValid
if blk.Payset[txnInc.Intra].Txn.Lease != [32]byte{} {
@@ -215,8 +226,6 @@ func (t *txTail) newBlock(blk bookkeeping.Block, delta ledgercore.StateDelta) {
}
encodedTail, tailHash := tail.Encode()
- t.tailMu.Lock()
- defer t.tailMu.Unlock()
t.recent[rnd] = roundLeases{
txleases: delta.Txleases,
proto: config.Consensus[blk.CurrentProtocol],
@@ -229,6 +238,9 @@ func (t *txTail) newBlock(blk bookkeeping.Block, delta ledgercore.StateDelta) {
}
func (t *txTail) committedUpTo(rnd basics.Round) (retRound, lookback basics.Round) {
+ t.tailMu.Lock()
+ defer t.tailMu.Unlock()
+
proto := t.recent[rnd].proto
maxlife := basics.Round(proto.MaxTxnLife)
@@ -333,6 +345,12 @@ func (t errTxTailMissingRound) Error() string {
// checkDup test to see if the given transaction id/lease already exists. It returns nil if neither exists, or
// TransactionInLedgerError / LeaseInLedgerError respectively.
func (t *txTail) checkDup(proto config.ConsensusParams, current basics.Round, firstValid basics.Round, lastValid basics.Round, txid transactions.Txid, txl ledgercore.Txlease) error {
+ // txTail does not use l.trackerMu, instead uses t.tailMu to make it thread-safe
+ // t.tailMu is sufficient because the state of txTail does not depend on any outside data field
+
+ t.tailMu.RLock()
+ defer t.tailMu.RUnlock()
+
if lastValid < t.lowWaterMark {
return &errTxTailMissingRound{round: lastValid}
}
@@ -359,13 +377,6 @@ func (t *txTail) checkDup(proto config.ConsensusParams, current basics.Round, fi
return nil
}
-func (t *txTail) putLV(lastValid basics.Round, id transactions.Txid) {
- if _, ok := t.lastValid[lastValid]; !ok {
- t.lastValid[lastValid] = make(map[transactions.Txid]struct{})
- }
- t.lastValid[lastValid][id] = struct{}{}
-}
-
func (t *txTail) recentTailHash(offset uint64, retainSize uint64) (crypto.Digest, error) {
// prepare a buffer to hash.
buffer := make([]byte, (retainSize)*crypto.DigestSize)
@@ -387,8 +398,5 @@ func (t *txTail) blockHeader(rnd basics.Round) (bookkeeping.BlockHeader, bool) {
t.tailMu.RLock()
defer t.tailMu.RUnlock()
hdr, ok := t.blockHeaderData[rnd]
- if !ok {
- t.log.Warnf("txtail failed to fetch blockHeader from rnd: %d", rnd)
- }
return hdr, ok
}
diff --git a/libgoal/libgoal.go b/libgoal/libgoal.go
index 65621e610..2bc924bac 100644
--- a/libgoal/libgoal.go
+++ b/libgoal/libgoal.go
@@ -528,7 +528,17 @@ func computeValidityRounds(firstValid, lastValid, validRounds, lastRound, maxTxn
}
if firstValid == 0 {
- firstValid = lastRound + 1
+ // current node might be a bit ahead of the network, and to prevent sibling nodes from rejecting the transaction
+ // because it's FirstValid is greater than their pending block evaluator.
+ // For example, a node just added block 100 and immediately sending a new transaction.
+ // The other side is lagging behind by 100ms and its LastRound is 99 so its transaction pools accepts txns for rounds 100+.
+ // This means the node client have to set FirstValid to 100 or below.
+ if lastRound > 0 {
+ firstValid = lastRound
+ } else {
+ // there is no practical sense to set FirstValid to 0, so we set it to 1
+ firstValid = 1
+ }
}
if validRounds != 0 {
diff --git a/libgoal/libgoal_test.go b/libgoal/libgoal_test.go
index 87df50ad9..b2e5d2205 100644
--- a/libgoal/libgoal_test.go
+++ b/libgoal/libgoal_test.go
@@ -38,7 +38,7 @@ func TestValidRounds(t *testing.T) {
validRounds = 0
fv, lv, err := computeValidityRounds(firstValid, lastValid, validRounds, lastRound, maxTxnLife)
a.NoError(err)
- a.Equal(lastRound+1, fv)
+ a.Equal(lastRound, fv)
a.Equal(fv+maxTxnLife, lv)
firstValid = 0
@@ -46,34 +46,34 @@ func TestValidRounds(t *testing.T) {
validRounds = maxTxnLife + 1
fv, lv, err = computeValidityRounds(firstValid, lastValid, validRounds, lastRound, maxTxnLife)
a.NoError(err)
- a.Equal(lastRound+1, fv)
+ a.Equal(lastRound, fv)
a.Equal(fv+maxTxnLife, lv)
firstValid = 0
lastValid = 0
validRounds = maxTxnLife + 2
- fv, lv, err = computeValidityRounds(firstValid, lastValid, validRounds, lastRound, maxTxnLife)
+ _, _, err = computeValidityRounds(firstValid, lastValid, validRounds, lastRound, maxTxnLife)
a.Error(err)
a.Equal("cannot construct transaction: txn validity period 1001 is greater than protocol max txn lifetime 1000", err.Error())
firstValid = 0
lastValid = 1
validRounds = 2
- fv, lv, err = computeValidityRounds(firstValid, lastValid, validRounds, lastRound, maxTxnLife)
+ _, _, err = computeValidityRounds(firstValid, lastValid, validRounds, lastRound, maxTxnLife)
a.Error(err)
a.Equal("cannot construct transaction: ambiguous input: lastValid = 1, validRounds = 2", err.Error())
firstValid = 2
lastValid = 1
validRounds = 0
- fv, lv, err = computeValidityRounds(firstValid, lastValid, validRounds, lastRound, maxTxnLife)
+ _, _, err = computeValidityRounds(firstValid, lastValid, validRounds, lastRound, maxTxnLife)
a.Error(err)
a.Equal("cannot construct transaction: txn would first be valid on round 2 which is after last valid round 1", err.Error())
firstValid = 1
lastValid = maxTxnLife + 2
validRounds = 0
- fv, lv, err = computeValidityRounds(firstValid, lastValid, validRounds, lastRound, maxTxnLife)
+ _, _, err = computeValidityRounds(firstValid, lastValid, validRounds, lastRound, maxTxnLife)
a.Error(err)
a.Equal("cannot construct transaction: txn validity period ( 1 to 1002 ) is greater than protocol max txn lifetime 1000", err.Error())
@@ -90,7 +90,7 @@ func TestValidRounds(t *testing.T) {
validRounds = 0
fv, lv, err = computeValidityRounds(firstValid, lastValid, validRounds, lastRound, maxTxnLife)
a.NoError(err)
- a.Equal(lastRound+1, fv)
+ a.Equal(lastRound, fv)
a.Equal(lastRound+1, lv)
firstValid = 0
@@ -98,16 +98,16 @@ func TestValidRounds(t *testing.T) {
validRounds = 1
fv, lv, err = computeValidityRounds(firstValid, lastValid, validRounds, lastRound, maxTxnLife)
a.NoError(err)
- a.Equal(lastRound+1, fv)
- a.Equal(lastRound+1, lv)
+ a.Equal(lastRound, fv)
+ a.Equal(lastRound, lv)
firstValid = 0
lastValid = 0
validRounds = maxTxnLife
fv, lv, err = computeValidityRounds(firstValid, lastValid, validRounds, lastRound, maxTxnLife)
a.NoError(err)
- a.Equal(lastRound+1, fv)
- a.Equal(lastRound+maxTxnLife, lv)
+ a.Equal(lastRound, fv)
+ a.Equal(lastRound+maxTxnLife-1, lv)
firstValid = 1
lastValid = 0
diff --git a/netdeploy/network.go b/netdeploy/network.go
index 520f185a3..02202d555 100644
--- a/netdeploy/network.go
+++ b/netdeploy/network.go
@@ -30,8 +30,10 @@ import (
"github.com/algorand/go-algorand/daemon/algod/api/server/v2/generated/model"
"github.com/algorand/go-algorand/gen"
"github.com/algorand/go-algorand/libgoal"
+ "github.com/algorand/go-algorand/netdeploy/remote"
"github.com/algorand/go-algorand/nodecontrol"
"github.com/algorand/go-algorand/util"
+ "golang.org/x/exp/maps"
)
const configFileName = "network.json"
@@ -43,8 +45,8 @@ type NetworkCfg struct {
Name string `json:"Name,omitempty"`
// RelayDirs are directories where relays live (where we check for connection IP:Port)
// They are stored relative to root dir (e.g. "Primary")
- RelayDirs []string `json:"RelayDirs,omitempty"`
- TemplateFile string `json:"TemplateFile,omitempty"` // Template file used to create the network
+ RelayDirs []string `json:"RelayDirs,omitempty"`
+ Template NetworkTemplate `json:"Template,omitempty"` // Template file used to create the network
}
// Network represents an instance of a deployed network
@@ -108,6 +110,7 @@ func CreateNetworkFromTemplate(name, rootDir string, templateReader io.Reader, b
return n, err
}
n.gen = template.Genesis
+ n.cfg.Template = template
err = n.Save(rootDir)
n.SetConsensus(binDir, consensus)
@@ -278,9 +281,9 @@ func (n Network) Start(binDir string, redirectOutput bool) error {
// Start Prime Relay and get its listening address
- var peerAddressListBuilder strings.Builder
var relayAddress string
var err error
+ relayNameToAddress := map[string]string{}
for _, relayDir := range n.cfg.RelayDirs {
nodeFullPath := n.getNodeFullPath(relayDir)
nc := nodecontrol.MakeNodeController(binDir, nodeFullPath)
@@ -299,15 +302,10 @@ func (n Network) Start(binDir string, redirectOutput bool) error {
if err != nil {
return err
}
-
- if peerAddressListBuilder.Len() != 0 {
- peerAddressListBuilder.WriteString(";")
- }
- peerAddressListBuilder.WriteString(relayAddress)
+ relayNameToAddress[relayDir] = relayAddress
}
- peerAddressList := peerAddressListBuilder.String()
- err = n.startNodes(binDir, peerAddressList, redirectOutput)
+ err = n.startNodes(binDir, relayNameToAddress, redirectOutput)
return err
}
@@ -337,21 +335,38 @@ func (n Network) GetPeerAddresses(binDir string) []string {
if err != nil {
continue
}
- if strings.HasPrefix(relayAddress, "http://") {
- relayAddress = relayAddress[7:]
- }
- peerAddresses = append(peerAddresses, relayAddress)
+ peerAddresses = append(peerAddresses, strings.TrimPrefix(relayAddress, "http://"))
}
return peerAddresses
}
-func (n Network) startNodes(binDir, relayAddress string, redirectOutput bool) error {
- args := nodecontrol.AlgodStartArgs{
- PeerAddress: relayAddress,
- RedirectOutput: redirectOutput,
- ExitErrorCallback: n.nodeExitCallback,
+func (n Network) startNodes(binDir string, relayNameToAddress map[string]string, redirectOutput bool) error {
+ allRelaysAddresses := strings.Join(maps.Values(relayNameToAddress), ";")
+
+ nodeConfigToEntry := make(map[string]remote.NodeConfigGoal, len(n.cfg.Template.Nodes))
+ for _, n := range n.cfg.Template.Nodes {
+ nodeConfigToEntry[n.Name] = n
}
+
for _, nodeDir := range n.nodeDirs {
+ args := nodecontrol.AlgodStartArgs{
+ PeerAddress: allRelaysAddresses,
+ RedirectOutput: redirectOutput,
+ ExitErrorCallback: n.nodeExitCallback,
+ }
+ if n, ok := nodeConfigToEntry[nodeDir]; ok && len(n.PeerList) > 0 {
+ relayNames := strings.Split(n.PeerList, ";")
+ var peerAddresses []string
+ for _, relayName := range relayNames {
+ relayAddress, ok := relayNameToAddress[relayName]
+ if !ok {
+ return fmt.Errorf("relay %s is not defined in the network", relayName)
+ }
+ peerAddresses = append(peerAddresses, relayAddress)
+ }
+ args.PeerAddress = strings.Join(peerAddresses, ";")
+ }
+
nc := nodecontrol.MakeNodeController(binDir, n.getNodeFullPath(nodeDir))
_, err := nc.StartAlgod(args)
if err != nil {
diff --git a/netdeploy/networkTemplate.go b/netdeploy/networkTemplate.go
index 1a140e74d..2af5d7930 100644
--- a/netdeploy/networkTemplate.go
+++ b/netdeploy/networkTemplate.go
@@ -235,10 +235,14 @@ func (t NetworkTemplate) Validate() error {
}
// Follow nodes cannot be relays
+ // Relays cannot have peer list
for _, cfg := range t.Nodes {
if cfg.IsRelay && isEnableFollowMode(cfg.ConfigJSONOverride) {
return fmt.Errorf("invalid template: follower nodes may not be relays")
}
+ if cfg.IsRelay && len(cfg.PeerList) > 0 {
+ return fmt.Errorf("invalid template: relays may not have a peer list")
+ }
}
if t.Genesis.DevMode && len(t.Nodes) != 1 {
diff --git a/netdeploy/networkTemplates_test.go b/netdeploy/networkTemplates_test.go
index 63bff9f2d..f21c37ace 100644
--- a/netdeploy/networkTemplates_test.go
+++ b/netdeploy/networkTemplates_test.go
@@ -52,6 +52,7 @@ func TestLoadMissingConfig(t *testing.T) {
a := require.New(t)
templateDir, err := filepath.Abs("../test/testdata/nettemplates")
+ a.NoError(err)
template, err := loadTemplate(filepath.Join(templateDir, "<invalidname>.json"))
a.Error(err)
a.Equal(template.Genesis.NetworkName, "")
@@ -102,6 +103,68 @@ func TestValidate(t *testing.T) {
template, _ = loadTemplate(filepath.Join(templateDir, "TwoNodesOneRelay1000Accounts.json"))
err = template.Validate()
a.NoError(err)
+
+ templateDir, _ = filepath.Abs("../test/testdata/nettemplates")
+ template, _ = loadTemplate(filepath.Join(templateDir, "FiveNodesTwoRelays.json"))
+ err = template.Validate()
+ a.NoError(err)
+}
+
+func TestPeerListValidate(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+
+ devmodeGenesis := gen.GenesisData{
+ Wallets: []gen.WalletData{
+ {
+ Stake: 100,
+ },
+ },
+ }
+
+ t.Run("PeerList is optional", func(t *testing.T) {
+ t.Parallel()
+ tmpl := NetworkTemplate{
+ Genesis: devmodeGenesis,
+ Nodes: []remote.NodeConfigGoal{
+ {
+ IsRelay: true,
+ },
+ {
+ IsRelay: false,
+ },
+ },
+ }
+ require.NoError(t, tmpl.Validate())
+ })
+
+ t.Run("Relays cannot have PeerList", func(t *testing.T) {
+ t.Parallel()
+ tmpl := NetworkTemplate{
+ Genesis: devmodeGenesis,
+ Nodes: []remote.NodeConfigGoal{
+ {
+ IsRelay: true,
+ PeerList: "R2",
+ },
+ },
+ }
+ require.ErrorContains(t, tmpl.Validate(), "relays may not have a peer list")
+ })
+
+ t.Run("Non-relays might have PeerList", func(t *testing.T) {
+ t.Parallel()
+ tmpl := NetworkTemplate{
+ Genesis: devmodeGenesis,
+ Nodes: []remote.NodeConfigGoal{
+ {
+ IsRelay: false,
+ PeerList: "R2",
+ },
+ },
+ }
+ require.NoError(t, tmpl.Validate())
+ })
}
func TestDevModeValidate(t *testing.T) {
diff --git a/netdeploy/network_test.go b/netdeploy/network_test.go
index 6b035547c..cf3ea427c 100644
--- a/netdeploy/network_test.go
+++ b/netdeploy/network_test.go
@@ -24,6 +24,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/algorand/go-algorand/config"
+ "github.com/algorand/go-algorand/gen"
"github.com/algorand/go-algorand/test/partitiontest"
)
@@ -34,9 +35,11 @@ func TestSaveNetworkCfg(t *testing.T) {
a := require.New(t)
cfg := NetworkCfg{
- Name: "testName",
- RelayDirs: []string{"testPND"},
- TemplateFile: "testTemplate",
+ Name: "testName",
+ RelayDirs: []string{"testPND"},
+ Template: NetworkTemplate{
+ Genesis: gen.DefaultGenesis,
+ },
}
tmpFolder := t.TempDir()
@@ -44,6 +47,7 @@ func TestSaveNetworkCfg(t *testing.T) {
err := saveNetworkCfg(cfg, cfgFile)
a.Nil(err)
cfg1, err := loadNetworkCfg(cfgFile)
+ a.NoError(err)
a.Equal(cfg, cfg1)
}
@@ -63,9 +67,11 @@ func TestSaveConsensus(t *testing.T) {
net := Network{
cfg: NetworkCfg{
- Name: "testName",
- RelayDirs: []string{relayDir},
- TemplateFile: "testTemplate",
+ Name: "testName",
+ RelayDirs: []string{relayDir},
+ Template: NetworkTemplate{
+ Genesis: gen.DefaultGenesis,
+ },
},
nodeDirs: map[string]string{
"node1": nodeDir,
diff --git a/netdeploy/remote/nodeConfig.go b/netdeploy/remote/nodeConfig.go
index 5a8b3ac43..c3daed671 100644
--- a/netdeploy/remote/nodeConfig.go
+++ b/netdeploy/remote/nodeConfig.go
@@ -59,4 +59,5 @@ type NodeConfigGoal struct {
Wallets []NodeWalletData
DeadlockDetection int `json:"-"`
ConfigJSONOverride string `json:",omitempty"` // Raw json to merge into config.json after other modifications are complete
+ PeerList string `json:",omitempty"` // Semicolon separated list of peers to connect to. Only applicable for non-relays
}
diff --git a/network/msgCompressor.go b/network/msgCompressor.go
index a46f37f2c..28f835832 100644
--- a/network/msgCompressor.go
+++ b/network/msgCompressor.go
@@ -144,7 +144,7 @@ func (c *wsPeerMsgDataConverter) convert(tag protocol.Tag, data []byte) ([]byte,
func makeWsPeerMsgDataConverter(wp *wsPeer) *wsPeerMsgDataConverter {
c := wsPeerMsgDataConverter{
- log: wp.net.log,
+ log: wp.log,
origin: wp.originAddress,
}
diff --git a/network/multiplexer.go b/network/multiplexer.go
index fe5b3dcf4..ddcf1845d 100644
--- a/network/multiplexer.go
+++ b/network/multiplexer.go
@@ -17,24 +17,19 @@
package network
import (
+ "fmt"
"sync/atomic"
-
- "github.com/algorand/go-algorand/logging"
)
// Multiplexer is a message handler that sorts incoming messages by Tag and passes
// them along to the relevant message handler for that type of message.
type Multiplexer struct {
msgHandlers atomic.Value // stores map[Tag]MessageHandler, an immutable map.
-
- log logging.Logger
}
// MakeMultiplexer creates an empty Multiplexer
-func MakeMultiplexer(log logging.Logger) *Multiplexer {
- m := &Multiplexer{
- log: log,
- }
+func MakeMultiplexer() *Multiplexer {
+ m := &Multiplexer{}
m.ClearHandlers([]Tag{}) // allocate the map
return m
}
@@ -78,7 +73,7 @@ func (m *Multiplexer) RegisterHandlers(dispatch []TaggedMessageHandler) {
}
for _, v := range dispatch {
if _, has := mp[v.Tag]; has {
- m.log.Panicf("Already registered a handler for tag %v", v.Tag)
+ panic(fmt.Sprintf("Already registered a handler for tag %v", v.Tag))
}
mp[v.Tag] = v.MessageHandler
}
diff --git a/network/multiplexer_test.go b/network/multiplexer_test.go
index 1d0215b90..c9d17dacb 100644
--- a/network/multiplexer_test.go
+++ b/network/multiplexer_test.go
@@ -22,7 +22,6 @@ import (
"github.com/stretchr/testify/require"
- "github.com/algorand/go-algorand/logging"
"github.com/algorand/go-algorand/protocol"
"github.com/algorand/go-algorand/test/partitiontest"
)
@@ -57,7 +56,7 @@ func (th *testHandler) SawMsg(msg IncomingMessage) bool {
func TestMultiplexer(t *testing.T) {
partitiontest.PartitionTest(t)
- m := MakeMultiplexer(logging.TestingLog(t))
+ m := MakeMultiplexer()
handler := &testHandler{}
// Handler shouldn't be called before it is registered
@@ -78,7 +77,7 @@ func TestMultiplexer(t *testing.T) {
panicked = true
}
}()
- m := MakeMultiplexer(logging.TestingLog(t))
+ m := MakeMultiplexer()
m.RegisterHandlers([]TaggedMessageHandler{{protocol.TxnTag, handler}, {protocol.TxnTag, handler}})
}()
@@ -90,7 +89,7 @@ func TestMultiplexer(t *testing.T) {
panicked = true
}
}()
- m := MakeMultiplexer(logging.TestingLog(t))
+ m := MakeMultiplexer()
m.RegisterHandlers([]TaggedMessageHandler{{protocol.TxnTag, handler}})
m.RegisterHandlers([]TaggedMessageHandler{{protocol.TxnTag, handler}})
diff --git a/network/netidentity.go b/network/netidentity.go
index 9fce21fcb..6414c5e89 100644
--- a/network/netidentity.go
+++ b/network/netidentity.go
@@ -325,6 +325,8 @@ func (i identityVerificationMessageSigned) Verify(key crypto.PublicKey) bool {
// sender's claimed identity and the challenge that was assigned to it. If the identity is available,
// the peer is loaded into the identity tracker. Otherwise, we ask the network to disconnect the peer.
func identityVerificationHandler(message IncomingMessage) OutgoingMessage {
+ wn := message.Net.(*WebsocketNetwork)
+
peer := message.Sender.(*wsPeer)
// avoid doing work (crypto and potentially taking a lock) if the peer is already verified
if atomic.LoadUint32(&peer.identityVerified) == 1 {
@@ -335,27 +337,27 @@ func identityVerificationHandler(message IncomingMessage) OutgoingMessage {
err := protocol.Decode(message.Data, &msg)
if err != nil {
networkPeerIdentityError.Inc(nil)
- peer.net.log.With("err", err).With("remote", peer.OriginAddress()).With("local", localAddr).Warn("peer identity verification could not be decoded, disconnecting")
+ peer.log.With("err", err).With("remote", peer.OriginAddress()).With("local", localAddr).Warn("peer identity verification could not be decoded, disconnecting")
return OutgoingMessage{Action: Disconnect, reason: disconnectBadIdentityData}
}
if peer.identityChallenge != msg.Msg.ResponseChallenge {
networkPeerIdentityError.Inc(nil)
- peer.net.log.With("remote", peer.OriginAddress()).With("local", localAddr).Warn("peer identity verification challenge does not match, disconnecting")
+ peer.log.With("remote", peer.OriginAddress()).With("local", localAddr).Warn("peer identity verification challenge does not match, disconnecting")
return OutgoingMessage{Action: Disconnect, reason: disconnectBadIdentityData}
}
if !msg.Verify(peer.identity) {
networkPeerIdentityError.Inc(nil)
- peer.net.log.With("remote", peer.OriginAddress()).With("local", localAddr).Warn("peer identity verification is incorrectly signed, disconnecting")
+ peer.log.With("remote", peer.OriginAddress()).With("local", localAddr).Warn("peer identity verification is incorrectly signed, disconnecting")
return OutgoingMessage{Action: Disconnect, reason: disconnectBadIdentityData}
}
atomic.StoreUint32(&peer.identityVerified, 1)
// if the identity could not be claimed by this peer, it means the identity is in use
- peer.net.peersLock.Lock()
- ok := peer.net.identityTracker.setIdentity(peer)
- peer.net.peersLock.Unlock()
+ wn.peersLock.Lock()
+ ok := wn.identityTracker.setIdentity(peer)
+ wn.peersLock.Unlock()
if !ok {
networkPeerIdentityDisconnect.Inc(nil)
- peer.net.log.With("remote", peer.OriginAddress()).With("local", localAddr).Warn("peer identity already in use, disconnecting")
+ peer.log.With("remote", peer.OriginAddress()).With("local", localAddr).Warn("peer identity already in use, disconnecting")
return OutgoingMessage{Action: Disconnect, reason: disconnectDuplicateConnection}
}
return OutgoingMessage{}
diff --git a/network/netidentity_test.go b/network/netidentity_test.go
index f3c72e3e8..13731aaae 100644
--- a/network/netidentity_test.go
+++ b/network/netidentity_test.go
@@ -356,11 +356,12 @@ func TestIdentityTrackerSetIdentity(t *testing.T) {
}
// Just tests that if a peer is already verified, it just returns OutgoingMessage{}
-func TestHandlerGuard(t *testing.T) {
+func TestIdentityTrackerHandlerGuard(t *testing.T) {
partitiontest.PartitionTest(t)
p := wsPeer{identityVerified: uint32(1)}
msg := IncomingMessage{
Sender: &p,
+ Net: &WebsocketNetwork{},
}
require.Equal(t, OutgoingMessage{}, identityVerificationHandler(msg))
}
diff --git a/network/p2p/dnsaddr/resolve.go b/network/p2p/dnsaddr/resolve.go
new file mode 100644
index 000000000..ad9f4e8b4
--- /dev/null
+++ b/network/p2p/dnsaddr/resolve.go
@@ -0,0 +1,68 @@
+// Copyright (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 <https://www.gnu.org/licenses/>.
+
+package dnsaddr
+
+import (
+ "context"
+ "errors"
+ "fmt"
+
+ "github.com/multiformats/go-multiaddr"
+)
+
+func isDnsaddr(maddr multiaddr.Multiaddr) bool {
+ first, _ := multiaddr.SplitFirst(maddr)
+ return first.Protocol().Code == multiaddr.P_DNSADDR
+}
+
+// MultiaddrsFromResolver attempts to recurse through dnsaddrs starting at domain.
+// Any further dnsaddrs will be looked up until all TXT records have been fetched,
+// and the full list of resulting Multiaddrs is returned.
+// It uses the MultiaddrDNSResolveController to cycle through DNS resolvers on failure.
+func MultiaddrsFromResolver(domain string, controller *MultiaddrDNSResolveController) ([]multiaddr.Multiaddr, error) {
+ resolver := controller.Resolver()
+ if resolver == nil {
+ return nil, errors.New("passed controller has no resolvers MultiaddrsFromResolver")
+ }
+ dnsaddr, err := multiaddr.NewMultiaddr(fmt.Sprintf("/dnsaddr/%s", domain))
+ if err != nil {
+ return nil, fmt.Errorf("unable to construct multiaddr for %s : %v", domain, err)
+ }
+ var resolved []multiaddr.Multiaddr
+ var toResolve = []multiaddr.Multiaddr{dnsaddr}
+ for resolver != nil && len(toResolve) > 0 {
+ curr := toResolve[0]
+ maddrs, resolveErr := resolver.Resolve(context.Background(), curr)
+ if resolveErr != nil {
+ resolver = controller.NextResolver()
+ // If we errored, and have exhausted all resolvers, just return
+ if resolver == nil {
+ return resolved, resolveErr
+ }
+ continue
+ }
+ for _, maddr := range maddrs {
+ if isDnsaddr(maddr) {
+ toResolve = append(toResolve, maddr)
+ } else {
+ resolved = append(resolved, maddr)
+ }
+ }
+ toResolve = toResolve[1:]
+ }
+ return resolved, nil
+}
diff --git a/network/p2p/dnsaddr/resolveController.go b/network/p2p/dnsaddr/resolveController.go
new file mode 100644
index 000000000..7fb920ecb
--- /dev/null
+++ b/network/p2p/dnsaddr/resolveController.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 <https://www.gnu.org/licenses/>.
+
+package dnsaddr
+
+import (
+ madns "github.com/multiformats/go-multiaddr-dns"
+
+ log "github.com/algorand/go-algorand/logging"
+ "github.com/algorand/go-algorand/tools/network"
+)
+
+// MultiaddrDNSResolveController returns a madns.Resolver, cycling through underlying net.Resolvers
+type MultiaddrDNSResolveController struct {
+ resolver *madns.Resolver
+ nextResolvers []func() *madns.Resolver
+ controller network.ResolveController
+}
+
+// NewMultiaddrDNSResolveController constructs a MultiaddrDNSResolveController
+func NewMultiaddrDNSResolveController(secure bool, fallbackDNSResolverAddress string) *MultiaddrDNSResolveController {
+ controller := network.NewResolveController(secure, fallbackDNSResolverAddress, log.Base())
+ nextResolvers := []func() *madns.Resolver{controller.SystemDnsaddrResolver}
+ if fallbackDNSResolverAddress != "" {
+ nextResolvers = append(nextResolvers, controller.FallbackDnsaddrResolver)
+ }
+ return &MultiaddrDNSResolveController{
+ resolver: nil,
+ nextResolvers: append(nextResolvers, controller.DefaultDnsaddrResolver),
+ controller: controller,
+ }
+}
+
+// NextResolver applies the nextResolvers functions in order and returns the most recent result
+func (c *MultiaddrDNSResolveController) NextResolver() *madns.Resolver {
+ if len(c.nextResolvers) == 0 {
+ c.resolver = nil
+ } else {
+ c.resolver = c.nextResolvers[0]()
+ c.nextResolvers = c.nextResolvers[1:]
+ }
+ return c.resolver
+}
+
+// Resolver returns the current resolver, invokes NextResolver if the resolver is nil
+func (c *MultiaddrDNSResolveController) Resolver() *madns.Resolver {
+ if c.resolver == nil {
+ c.resolver = c.NextResolver()
+ }
+ return c.resolver
+}
diff --git a/network/p2p/dnsaddr/resolveController_test.go b/network/p2p/dnsaddr/resolveController_test.go
new file mode 100644
index 000000000..c097240ac
--- /dev/null
+++ b/network/p2p/dnsaddr/resolveController_test.go
@@ -0,0 +1,45 @@
+// Copyright (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 <https://www.gnu.org/licenses/>.
+
+package dnsaddr
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+
+ "github.com/algorand/go-algorand/test/partitiontest"
+)
+
+func TestDnsAddrResolveController(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+
+ dnsaddrCont := NewMultiaddrDNSResolveController(true, "127.0.0.1")
+
+ // Assert that the dnsaddr resolver cycles through the dns resolvers properly
+ assert.Equal(t, dnsaddrCont.controller.SystemDnsaddrResolver(), dnsaddrCont.Resolver())
+ assert.Equal(t, dnsaddrCont.controller.FallbackDnsaddrResolver(), dnsaddrCont.NextResolver())
+ assert.Equal(t, dnsaddrCont.controller.DefaultDnsaddrResolver(), dnsaddrCont.NextResolver())
+ // It should return nil once all the resolvers have been tried
+ assert.Nil(t, dnsaddrCont.NextResolver())
+ assert.Nil(t, dnsaddrCont.NextResolver())
+
+ // It should not include fallback if none was specified
+ dnsaddrCont = NewMultiaddrDNSResolveController(true, "")
+ assert.Equal(t, 2, len(dnsaddrCont.nextResolvers))
+
+}
diff --git a/network/p2p/dnsaddr/resolve_test.go b/network/p2p/dnsaddr/resolve_test.go
new file mode 100644
index 000000000..df564d5e9
--- /dev/null
+++ b/network/p2p/dnsaddr/resolve_test.go
@@ -0,0 +1,111 @@
+// Copyright (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 <https://www.gnu.org/licenses/>.
+
+package dnsaddr
+
+import (
+ "context"
+ "fmt"
+ "net"
+ "testing"
+
+ "github.com/multiformats/go-multiaddr"
+ madns "github.com/multiformats/go-multiaddr-dns"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+
+ "github.com/algorand/go-algorand/test/partitiontest"
+ "github.com/algorand/go-algorand/tools/network"
+)
+
+func TestIsDnsaddr(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+
+ testcases := []struct {
+ name string
+ addr string
+ expected bool
+ }{
+ {name: "DnsAddr", addr: "/dnsaddr/foobar.com", expected: true},
+ {name: "DnsAddrWithPeerId", addr: "/dnsaddr/foobar.com/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", expected: true},
+ {name: "DnsAddrWithIPPeerId", addr: "/dnsaddr/foobar.com/ip4/127.0.0.1/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", expected: true},
+ {name: "Dns4Addr", addr: "/dns4/foobar.com/", expected: false},
+ {name: "Dns6Addr", addr: "/dns6/foobar.com/", expected: false},
+ {name: "Dns4AddrWithPeerId", addr: "/dns4/foobar.com/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", expected: false},
+ }
+ for _, testcase := range testcases {
+ t.Run(testcase.name, func(t *testing.T) {
+ maddr, err := multiaddr.NewMultiaddr(testcase.addr)
+ require.NoError(t, err)
+ require.Equal(t, testcase.expected, isDnsaddr(maddr))
+ })
+ }
+}
+
+func TestMultiaddrsFromResolver(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+
+ dnsaddrCont := NewMultiaddrDNSResolveController(false, "")
+
+ // Fail on bad dnsaddr domain
+ maddrs, err := MultiaddrsFromResolver("/bogus/foobar", dnsaddrCont)
+ assert.Empty(t, maddrs)
+ assert.ErrorContains(t, err, fmt.Sprintf("unable to construct multiaddr for %s", "/bogus/foobar"))
+
+ // Success on a dnsaddr that needs to resolve recursively
+ maddrs, err = MultiaddrsFromResolver("bootstrap.libp2p.io", dnsaddrCont)
+ assert.NoError(t, err)
+ assert.NotEmpty(t, maddrs)
+ // bootstrap.libp2p.io's dnsaddr record contains 4 more dnsaddrs to resolve
+ assert.Greater(t, len(maddrs), 4)
+}
+
+type failureResolver struct{}
+
+func (f *failureResolver) LookupIPAddr(context.Context, string) ([]net.IPAddr, error) {
+ return nil, fmt.Errorf("always errors")
+}
+func (f *failureResolver) LookupTXT(context.Context, string) ([]string, error) {
+ return nil, fmt.Errorf("always errors")
+}
+
+func TestMultiaddrsFromResolverDnsFailure(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+
+ dnsaddrCont := &MultiaddrDNSResolveController{
+ resolver: nil,
+ nextResolvers: nil,
+ }
+
+ // Fail on no resolver
+ maddrs, err := MultiaddrsFromResolver("", dnsaddrCont)
+ assert.Empty(t, maddrs)
+ assert.ErrorContains(t, err, fmt.Sprintf("passed controller has no resolvers MultiaddrsFromResolver"))
+
+ resolver, _ := madns.NewResolver(madns.WithDefaultResolver(&failureResolver{}))
+ dnsaddrCont = &MultiaddrDNSResolveController{
+ resolver: resolver,
+ nextResolvers: nil,
+ controller: network.ResolveController{},
+ }
+ // Fail on resolver error
+ maddrs, err = MultiaddrsFromResolver("bootstrap.libp2p.io", dnsaddrCont)
+ assert.Empty(t, maddrs)
+ assert.ErrorContains(t, err, "always errors")
+}
diff --git a/network/p2p/peerID.go b/network/p2p/peerID.go
new file mode 100644
index 000000000..fce32a3af
--- /dev/null
+++ b/network/p2p/peerID.go
@@ -0,0 +1,90 @@
+// Copyright (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 <https://www.gnu.org/licenses/>.
+
+// Package node is the Algorand node itself, with functions exposed to the frontend
+
+package p2p
+
+import (
+ "crypto/rand"
+ "fmt"
+ "github.com/algorand/go-algorand/util"
+ "os"
+ "path"
+
+ "github.com/libp2p/go-libp2p/core/crypto"
+
+ "github.com/algorand/go-algorand/config"
+)
+
+// DefaultPrivKeyPath is the default path inside the node's root directory at which the private key
+// for p2p identity is found and persisted to when a new one is generated.
+const DefaultPrivKeyPath = "peerIDPrivKey.pem"
+
+// GetPrivKey manages loading and creation of private keys for network PeerIDs
+// It prioritizes, in this order:
+// 1. user supplied path to privKey
+// 2. default path to privKey,
+// 3. generating a new privKey.
+//
+// If a new privKey is generated it will be saved to default path if cfg.P2PPersistPeerID.
+func GetPrivKey(cfg config.Local, dataDir string) (crypto.PrivKey, error) {
+ // if user-supplied, try to load it from there
+ if cfg.P2PPrivateKeyLocation != "" {
+ return loadPrivateKeyFromFile(cfg.P2PPrivateKeyLocation)
+ }
+ // if a default path key exists load it
+ defaultPrivKeyPath := path.Join(dataDir, DefaultPrivKeyPath)
+ if util.FileExists(defaultPrivKeyPath) {
+ return loadPrivateKeyFromFile(defaultPrivKeyPath)
+ }
+ // generate a new key
+ privKey, err := generatePrivKey()
+ if err != nil {
+ return privKey, fmt.Errorf("failed to generate private key %w", err)
+ }
+ // if we want persistent PeerID, save the generated PrivKey
+ if cfg.P2PPersistPeerID {
+ return privKey, writePrivateKeyToFile(defaultPrivKeyPath, privKey)
+ }
+ return privKey, nil
+}
+
+// loadPrivateKeyFromFile attempts to read raw privKey bytes from path
+// It only supports Ed25519 keys.
+func loadPrivateKeyFromFile(path string) (crypto.PrivKey, error) {
+ bytes, err := os.ReadFile(path)
+ if err != nil {
+ return nil, err
+ }
+ // We only support Ed25519 keys
+ return crypto.UnmarshalEd25519PrivateKey(bytes)
+}
+
+// writePrivateKeyToFile attempts to write raw privKey bytes to path
+func writePrivateKeyToFile(path string, privKey crypto.PrivKey) error {
+ bytes, err := privKey.Raw()
+ if err != nil {
+ return err
+ }
+ return os.WriteFile(path, bytes, 0600)
+}
+
+// generatePrivKey creates a new Ed25519 key
+func generatePrivKey() (crypto.PrivKey, error) {
+ priv, _, err := crypto.GenerateEd25519Key(rand.Reader)
+ return priv, err
+}
diff --git a/network/p2p/peerID_test.go b/network/p2p/peerID_test.go
new file mode 100644
index 000000000..1f15b3214
--- /dev/null
+++ b/network/p2p/peerID_test.go
@@ -0,0 +1,104 @@
+// Copyright (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 <https://www.gnu.org/licenses/>.
+
+package p2p
+
+import (
+ "os"
+ "path"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+
+ "github.com/algorand/go-algorand/config"
+ "github.com/algorand/go-algorand/test/partitiontest"
+)
+
+func TestGetPrivKeyUserSupplied(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+ tempdir := t.TempDir()
+ cfg := config.GetDefaultLocal()
+ customPath := path.Join(tempdir, "foobar.pem")
+ // generate a new private key
+ privKey, err := generatePrivKey()
+ require.NoError(t, err)
+ // write it to our custom path
+ err = writePrivateKeyToFile(customPath, privKey)
+ require.NoError(t, err)
+ cfg.P2PPrivateKeyLocation = customPath
+ // make sure GetPrivKey loads our custom key
+ loadedPrivKey, err := GetPrivKey(cfg, tempdir)
+ assert.NoError(t, err)
+ assert.Equal(t, privKey, loadedPrivKey)
+}
+
+func TestGetPrivKeyUserSuppliedDoesNotExistErrors(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+ tempdir := t.TempDir()
+ cfg := config.GetDefaultLocal()
+ cfg.P2PPrivateKeyLocation = path.Join(tempdir, "foobar.pem")
+ _, err := GetPrivKey(cfg, tempdir)
+ assert.True(t, os.IsNotExist(err))
+}
+
+func TestGetPrivKeyDefault(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+ tempdir := t.TempDir()
+ cfg := config.GetDefaultLocal()
+
+ // generate a new private key
+ privKey, err := generatePrivKey()
+ require.NoError(t, err)
+ // write it to the default path
+ err = writePrivateKeyToFile(path.Join(tempdir, DefaultPrivKeyPath), privKey)
+ require.NoError(t, err)
+ // fetch the default private key
+ loadedPrivKey, err := GetPrivKey(cfg, tempdir)
+ assert.NoError(t, err)
+ assert.Equal(t, privKey, loadedPrivKey)
+}
+
+func TestGetPrivKeyUserGeneratedPersisted(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+ tempdir := t.TempDir()
+ cfg := config.GetDefaultLocal()
+ // get a generated private key
+ privKey, err := GetPrivKey(cfg, tempdir)
+ require.NoError(t, err)
+ // make sure it was persisted
+ loadedPrivKey, err := loadPrivateKeyFromFile(path.Join(tempdir, DefaultPrivKeyPath))
+ assert.NoError(t, err)
+ assert.Equal(t, privKey, loadedPrivKey)
+}
+
+func TestGetPrivKeyUserGeneratedEphemeral(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+ tempdir := t.TempDir()
+ cfg := config.GetDefaultLocal()
+ cfg.P2PPersistPeerID = false
+ // get a generated private key
+ _, err := GetPrivKey(cfg, tempdir)
+ require.NoError(t, err)
+ // make sure it was not persisted
+ _, err = loadPrivateKeyFromFile(path.Join(tempdir, DefaultPrivKeyPath))
+ assert.True(t, os.IsNotExist(err))
+}
diff --git a/network/p2p/peerstore/peerstore.go b/network/p2p/peerstore/peerstore.go
new file mode 100644
index 000000000..03cf49d1f
--- /dev/null
+++ b/network/p2p/peerstore/peerstore.go
@@ -0,0 +1,58 @@
+// Copyright (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 <https://www.gnu.org/licenses/>.
+
+package peerstore
+
+import (
+ "context"
+ "fmt"
+
+ ds "github.com/ipfs/go-datastore"
+ pebbledb "github.com/ipfs/go-ds-pebble"
+ "github.com/libp2p/go-libp2p/core/peer"
+ libp2p "github.com/libp2p/go-libp2p/core/peerstore"
+ "github.com/libp2p/go-libp2p/p2p/host/peerstore/pstoreds"
+)
+
+// PeerStore implements libp2p.Peerstore
+type PeerStore struct {
+ libp2p.Peerstore
+}
+
+func initDBStore(path string) (ds.Batching, error) {
+ store, err := pebbledb.NewDatastore(path, nil)
+ return store, err
+}
+
+// NewPeerStore creates a new peerstore backed by a datastore.
+func NewPeerStore(ctx context.Context, path string, addrInfo []*peer.AddrInfo) (*PeerStore, error) {
+ datastore, err := initDBStore(path)
+ if err != nil {
+ return nil, fmt.Errorf("cannot initialize a peerstore, invalid path for datastore: %w", err)
+ }
+ ps, err := pstoreds.NewPeerstore(ctx, datastore, pstoreds.DefaultOpts())
+ if err != nil {
+ return nil, fmt.Errorf("cannot initialize a peerstore: %w", err)
+ }
+
+ // initialize peerstore with addresses
+ for i := 0; i < len(addrInfo); i++ {
+ info := addrInfo[i]
+ ps.AddAddrs(info.ID, info.Addrs, libp2p.AddressTTL)
+ }
+ pstore := &PeerStore{ps}
+ return pstore, nil
+}
diff --git a/network/p2p/peerstore/peerstore_test.go b/network/p2p/peerstore/peerstore_test.go
new file mode 100644
index 000000000..721979922
--- /dev/null
+++ b/network/p2p/peerstore/peerstore_test.go
@@ -0,0 +1,90 @@
+// Copyright (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 <https://www.gnu.org/licenses/>.
+
+package peerstore
+
+import (
+ "context"
+ "crypto/rand"
+ "fmt"
+ "testing"
+
+ "github.com/algorand/go-algorand/test/partitiontest"
+ libp2p_crypto "github.com/libp2p/go-libp2p/core/crypto"
+ "github.com/libp2p/go-libp2p/core/peer"
+ libp2p "github.com/libp2p/go-libp2p/core/peerstore"
+ "github.com/stretchr/testify/require"
+)
+
+func TestPeerstore(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+
+ peerAddrs := []string{
+ "/dns4/ams-2.bootstrap.libp2p.io/tcp/443/wss/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb",
+ "/ip4/147.75.83.83/tcp/4001/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Na",
+ "/ip6/2604:1380:2000:7a00::1/udp/4001/quic/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj73Nb",
+ "/ip4/198.51.100.0/tcp/4242/p2p/QmYyQSo1c1Ym7orWxLYvCrM2EmxFTANf8wXmmE7DWjhx5N",
+ }
+
+ addrInfo, _ := PeerInfoFromAddrs(peerAddrs)
+ dir := t.TempDir()
+ ps, err := NewPeerStore(context.Background(), dir, addrInfo)
+ require.NoError(t, err)
+ defer ps.Close()
+
+ // peerstore is initialized with addresses
+ peers := ps.PeersWithAddrs()
+ require.Equal(t, 4, len(peers))
+
+ // add peer addresses
+ var addrs []string
+ var peerIDS []peer.ID
+ for i := 0; i < 4; i++ {
+ privKey, _, err := libp2p_crypto.GenerateEd25519Key(rand.Reader)
+ require.NoError(t, err)
+ peerID, err := peer.IDFromPrivateKey(privKey)
+ require.NoError(t, err)
+ peerIDS = append(peerIDS, peerID)
+ maddrStr := fmt.Sprintf("/ip4/1.2.3.4/tcp/%d/p2p/%s", 4000+i, peerID.String())
+ addrs = append(addrs, maddrStr)
+ }
+ addrInfo, _ = PeerInfoFromAddrs(addrs)
+ require.NoError(t, err)
+ for i := 0; i < len(addrInfo); i++ {
+ info := addrInfo[i]
+ ps.AddAddrs(info.ID, info.Addrs, libp2p.PermanentAddrTTL)
+ }
+
+ // peerstore should have 6 peers now
+ peers = ps.PeersWithAddrs()
+ require.Equal(t, 8, len(peers))
+
+ // remove a peer addr
+ ps.Peerstore.ClearAddrs(peerIDS[0])
+ peers = ps.PeersWithAddrs()
+ require.Equal(t, 7, len(peers))
+
+}
+
+func TestPeerStoreInitErrors(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+ // bad datastore path
+ _, err := NewPeerStore(context.Background(), "//", []*peer.AddrInfo{})
+ require.Contains(t, err.Error(), "invalid path for datastore")
+
+}
diff --git a/network/p2p/peerstore/utils.go b/network/p2p/peerstore/utils.go
new file mode 100644
index 000000000..eabcccbda
--- /dev/null
+++ b/network/p2p/peerstore/utils.go
@@ -0,0 +1,51 @@
+// Copyright (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 <https://www.gnu.org/licenses/>.
+
+package peerstore
+
+import (
+ "github.com/libp2p/go-libp2p/core/peer"
+ "github.com/multiformats/go-multiaddr"
+)
+
+// PeerInfoFromAddrs extracts the AddrInfo from a multiaddr string slice.
+func PeerInfoFromAddrs(addrs []string) ([]*peer.AddrInfo, map[string]string) {
+ var addrInfo []*peer.AddrInfo
+ malformedAddrs := make(map[string]string)
+ for _, addr := range addrs {
+ info, err := PeerInfoFromAddr(addr)
+ // track malformed addresses
+ if err != nil {
+ malformedAddrs[addr] = err.Error()
+ continue
+ }
+ addrInfo = append(addrInfo, info)
+ }
+ return addrInfo, malformedAddrs
+}
+
+// PeerInfoFromAddr extracts the AddrInfo from a multiaddr string.
+func PeerInfoFromAddr(addr string) (*peer.AddrInfo, error) {
+ maddr, err := multiaddr.NewMultiaddr(addr)
+ if err != nil {
+ return nil, err
+ }
+ info, err := peer.AddrInfoFromP2pAddr(maddr)
+ if err != nil {
+ return nil, err
+ }
+ return info, nil
+}
diff --git a/network/p2p/peerstore/utils_test.go b/network/p2p/peerstore/utils_test.go
new file mode 100644
index 000000000..c8927d27e
--- /dev/null
+++ b/network/p2p/peerstore/utils_test.go
@@ -0,0 +1,74 @@
+// Copyright (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 <https://www.gnu.org/licenses/>.
+
+package peerstore
+
+import (
+ "fmt"
+ "testing"
+
+ "github.com/algorand/go-algorand/test/partitiontest"
+ "github.com/stretchr/testify/require"
+)
+
+func TestPeerInfoFromAddr(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+
+ testcases := []struct {
+ name string
+ addr string
+ errMsg string
+ }{
+ {"invalid multiaddr string", "/ip4/", "failed to parse multiaddr"},
+ {"invalid tcp port", "/ip4/1.2.3.4/tcp/AAAAAAA", "failed to parse port addr"},
+ {"unknown protocol", "/ip4/1.2.3.4/tcp/443/AAAAAAA", "unknown protocol"},
+ {"badprotocol", "/badprotocol/1.2.3.4/tcp/443/wss/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb", "failed to parse multiaddr"},
+ {"invalid peerID", "/ip4/1.2.3.4/tcp/4041/p2p/AAAAAAA", "failed to parse p2p addr"},
+ {"invalid value for protocol", "/ip4/ams-2.bootstrap.libp2p.io/tcp/443/wss/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb", "invalid value \"ams-2.bootstrap.libp2p.io\" for protocol ip4"},
+ {"dns4", "/dns4/ams-2.bootstrap.libp2p.io/tcp/443/wss/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb", ""},
+ {"ipv4", "/ip4/147.75.83.83/tcp/4001/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Na", ""},
+ }
+ for _, tc := range testcases {
+ tc := tc
+ t.Run(fmt.Sprintf("test %s", tc.name), func(t *testing.T) {
+ t.Parallel()
+ _, err := PeerInfoFromAddr(tc.addr)
+ if tc.errMsg != "" {
+ require.Contains(t, err.Error(), tc.errMsg)
+ } else {
+ require.NoError(t, err)
+ }
+ })
+ }
+}
+
+func TestPeerInfoFromAddrs(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+
+ addrs := []string{
+ "/ip4/1.2.3.4/tcp/4041/p2p/AAAAAAA",
+ "/ip4/1.2.3.4/tcp/AAAAAAA",
+ "/dns4/ams-2.bootstrap.libp2p.io/tcp/443/wss/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb",
+ "/ip4/147.75.83.83/tcp/4001/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Na",
+ }
+ peerInfos, malformedAddrs := PeerInfoFromAddrs(addrs)
+ require.Len(t, peerInfos, 2)
+ require.Len(t, malformedAddrs, 2)
+ require.Contains(t, malformedAddrs["/ip4/1.2.3.4/tcp/4041/p2p/AAAAAAA"], "failed to parse multiaddr")
+ require.Contains(t, malformedAddrs["/ip4/1.2.3.4/tcp/AAAAAAA"], "failed to parse port addr")
+}
diff --git a/network/wsNetwork.go b/network/wsNetwork.go
index 56314f14b..07902bde7 100644
--- a/network/wsNetwork.go
+++ b/network/wsNetwork.go
@@ -173,12 +173,9 @@ const (
type GossipNode interface {
Address() (string, bool)
Broadcast(ctx context.Context, tag protocol.Tag, data []byte, wait bool, except Peer) error
- BroadcastArray(ctx context.Context, tag []protocol.Tag, data [][]byte, wait bool, except Peer) error
Relay(ctx context.Context, tag protocol.Tag, data []byte, wait bool, except Peer) error
- RelayArray(ctx context.Context, tag []protocol.Tag, data [][]byte, wait bool, except Peer) error
Disconnect(badnode Peer)
- DisconnectPeers()
- Ready() chan struct{}
+ DisconnectPeers() // only used by testing
// RegisterHTTPHandler path accepts gorilla/mux path annotations
RegisterHTTPHandler(path string, handler http.Handler)
@@ -226,12 +223,8 @@ type GossipNode interface {
// SubstituteGenesisID substitutes the "{genesisID}" with their network-specific genesisID.
SubstituteGenesisID(rawURL string) string
- // GetPeerData returns a value stored by SetPeerData
- GetPeerData(peer Peer, key string) interface{}
-
- // SetPeerData attaches a piece of data to a peer.
- // Other services inside go-algorand may attach data to a peer that gets garbage collected when the peer is closed.
- SetPeerData(peer Peer, key string, value interface{})
+ // called from wsPeer to report that it has closed
+ peerRemoteClose(peer *wsPeer, reason disconnectReason)
}
// IncomingMessage represents a message arriving from some peer in our p2p network
@@ -354,12 +347,8 @@ type WebsocketNetwork struct {
log logging.Logger
- readBuffer chan IncomingMessage
-
wg sync.WaitGroup
- handlers Multiplexer
-
ctx context.Context
ctxCancel context.CancelFunc
@@ -367,8 +356,8 @@ type WebsocketNetwork struct {
peers []*wsPeer
peersChangeCounter int32 // peersChangeCounter is an atomic variable that increases on each change to the peers. It helps avoiding taking the peersLock when checking if the peers list was modified.
- broadcastQueueHighPrio chan broadcastRequest
- broadcastQueueBulk chan broadcastRequest
+ broadcaster msgBroadcaster
+ handler msgHandler
phonebook Phonebook
@@ -408,9 +397,6 @@ type WebsocketNetwork struct {
// wsMaxHeaderBytes is the maximum accepted size of the header prior to upgrading to websocket connection.
wsMaxHeaderBytes int64
- // slowWritingPeerMonitorInterval defines the interval between two consecutive tests for slow peer writing
- slowWritingPeerMonitorInterval time.Duration
-
requestsTracker *RequestTracker
requestsLogger *RequestLogger
@@ -491,6 +477,43 @@ type broadcastRequest struct {
ctx context.Context
}
+// msgBroadcaster contains the logic for preparing data for broadcast, managing broadcast priorities
+// and queues. It provides a goroutine (broadcastThread) for reading from those queues and scheduling
+// broadcasts to peers managed by networkPeerManager.
+type msgBroadcaster struct {
+ ctx context.Context
+ log logging.Logger
+ config config.Local
+ broadcastQueueHighPrio chan broadcastRequest
+ broadcastQueueBulk chan broadcastRequest
+ // slowWritingPeerMonitorInterval defines the interval between two consecutive tests for slow peer writing
+ slowWritingPeerMonitorInterval time.Duration
+}
+
+// msgHandler contains the logic for handling incoming messages and managing a readBuffer. It provides
+// a goroutine (messageHandlerThread) for reading incoming messages and calling handlers.
+type msgHandler struct {
+ ctx context.Context
+ log logging.Logger
+ config config.Local
+ readBuffer chan IncomingMessage
+ Multiplexer
+}
+
+// networkPeerManager provides the network functionality needed by msgBroadcaster and msgHandler for managing
+// peer connectivity, and also sending messages.
+type networkPeerManager interface {
+ // used by msgBroadcaster
+ peerSnapshot(dest []*wsPeer) ([]*wsPeer, int32)
+ checkSlowWritingPeers()
+ getPeersChangeCounter() int32
+
+ // used by msgHandler
+ Broadcast(ctx context.Context, tag protocol.Tag, data []byte, wait bool, except Peer) error
+ disconnectThread(badnode Peer, reason disconnectReason)
+ checkPeersConnectivity()
+}
+
// Address returns a string and whether that is a 'final' address or guessed.
// Part of GossipNode interface
func (wn *WebsocketNetwork) Address() (string, bool) {
@@ -528,18 +551,17 @@ func (wn *WebsocketNetwork) Broadcast(ctx context.Context, tag protocol.Tag, dat
dataArray[0] = data
tagArray := make([]protocol.Tag, 1, 1)
tagArray[0] = tag
- return wn.BroadcastArray(ctx, tagArray, dataArray, wait, except)
+ return wn.broadcaster.BroadcastArray(ctx, tagArray, dataArray, wait, except)
}
// BroadcastArray sends an array of messages.
// If except is not nil then we will not send it to that neighboring Peer.
// if wait is true then the call blocks until the packet has actually been sent to all neighbors.
// TODO: add `priority` argument so that we don't have to guess it based on tag
-func (wn *WebsocketNetwork) BroadcastArray(ctx context.Context, tags []protocol.Tag, data [][]byte, wait bool, except Peer) error {
+func (wn *msgBroadcaster) BroadcastArray(ctx context.Context, tags []protocol.Tag, data [][]byte, wait bool, except Peer) error {
if wn.config.DisableNetworking {
return nil
}
-
if len(tags) != len(data) {
return errBcastInvalidArray
}
@@ -598,7 +620,7 @@ func (wn *WebsocketNetwork) Relay(ctx context.Context, tag protocol.Tag, data []
// RelayArray relays array of messages
func (wn *WebsocketNetwork) RelayArray(ctx context.Context, tags []protocol.Tag, data [][]byte, wait bool, except Peer) error {
if wn.relayMessages {
- return wn.BroadcastArray(ctx, tags, data, wait, except)
+ return wn.broadcaster.BroadcastArray(ctx, tags, data, wait, except)
}
return nil
}
@@ -691,14 +713,14 @@ func (wn *WebsocketNetwork) GetPeers(options ...PeerOption) []Peer {
var addrs []string
addrs = wn.phonebook.GetAddresses(1000, PhoneBookEntryRelayRole)
for _, addr := range addrs {
- peerCore := makePeerCore(wn, addr, wn.GetRoundTripper(), "" /*origin address*/)
+ peerCore := makePeerCore(wn.ctx, wn, wn.log, wn.handler.readBuffer, addr, wn.GetRoundTripper(), "" /*origin address*/)
outPeers = append(outPeers, &peerCore)
}
case PeersPhonebookArchivalNodes:
var addrs []string
addrs = wn.phonebook.GetAddresses(1000, PhoneBookEntryRelayRole)
for _, addr := range addrs {
- peerCore := makePeerCore(wn, addr, wn.GetRoundTripper(), "" /*origin address*/)
+ peerCore := makePeerCore(wn.ctx, wn, wn.log, wn.handler.readBuffer, addr, wn.GetRoundTripper(), "" /*origin address*/)
outPeers = append(outPeers, &peerCore)
}
case PeersPhonebookArchivers:
@@ -706,7 +728,7 @@ func (wn *WebsocketNetwork) GetPeers(options ...PeerOption) []Peer {
var addrs []string
addrs = wn.phonebook.GetAddresses(1000, PhoneBookEntryArchiverRole)
for _, addr := range addrs {
- peerCore := makePeerCore(wn, addr, wn.GetRoundTripper(), "" /*origin address*/)
+ peerCore := makePeerCore(wn.ctx, wn, wn.log, wn.handler.readBuffer, addr, wn.GetRoundTripper(), "" /*origin address*/)
outPeers = append(outPeers, &peerCore)
}
case PeersConnectedIn:
@@ -784,16 +806,21 @@ func (wn *WebsocketNetwork) setup() {
wn.identityTracker = NewIdentityTracker()
- wn.broadcastQueueHighPrio = make(chan broadcastRequest, wn.outgoingMessagesBufferSize)
- wn.broadcastQueueBulk = make(chan broadcastRequest, 100)
+ wn.broadcaster = msgBroadcaster{
+ ctx: wn.ctx,
+ log: wn.log,
+ config: wn.config,
+ broadcastQueueHighPrio: make(chan broadcastRequest, wn.outgoingMessagesBufferSize),
+ broadcastQueueBulk: make(chan broadcastRequest, 100),
+ }
+ if wn.broadcaster.slowWritingPeerMonitorInterval == 0 {
+ wn.broadcaster.slowWritingPeerMonitorInterval = slowWritingPeerMonitorInterval
+ }
wn.meshUpdateRequests = make(chan meshRequest, 5)
wn.readyChan = make(chan struct{})
wn.tryConnectAddrs = make(map[string]int64)
wn.eventualReadyDelay = time.Minute
wn.prioTracker = newPrioTracker(wn)
- if wn.slowWritingPeerMonitorInterval == 0 {
- wn.slowWritingPeerMonitorInterval = slowWritingPeerMonitorInterval
- }
readBufferLen := wn.config.IncomingConnectionsLimit + wn.config.GossipFanout
if readBufferLen < 100 {
@@ -802,7 +829,12 @@ func (wn *WebsocketNetwork) setup() {
if readBufferLen > 10000 {
readBufferLen = 10000
}
- wn.readBuffer = make(chan IncomingMessage, readBufferLen)
+ wn.handler = msgHandler{
+ ctx: wn.ctx,
+ log: wn.log,
+ config: wn.config,
+ readBuffer: make(chan IncomingMessage, readBufferLen),
+ }
var rbytes [10]byte
crypto.RandBytes(rbytes[:])
@@ -813,7 +845,6 @@ func (wn *WebsocketNetwork) setup() {
}
wn.connPerfMonitor = makeConnectionPerformanceMonitor([]Tag{protocol.AgreementVoteTag, protocol.TxnTag})
wn.lastNetworkAdvance = time.Now().UTC()
- wn.handlers.log = wn.log
// set our supported versions
if wn.config.NetworkProtocolVersion != "" {
@@ -904,10 +935,10 @@ func (wn *WebsocketNetwork) Start() {
for i := 0; i < incomingThreads; i++ {
wn.wg.Add(1)
// We pass the peersConnectivityCheckTicker.C here so that we don't need to syncronize the access to the ticker's data structure.
- go wn.messageHandlerThread(wn.peersConnectivityCheckTicker.C)
+ go wn.handler.messageHandlerThread(&wn.wg, wn.peersConnectivityCheckTicker.C, wn)
}
wn.wg.Add(1)
- go wn.broadcastThread()
+ go wn.broadcaster.broadcastThread(&wn.wg, wn)
if wn.prioScheme != nil {
wn.wg.Add(1)
go wn.prioWeightRefresh()
@@ -950,7 +981,7 @@ func (wn *WebsocketNetwork) innerStop() {
// Stop closes network connections and stops threads.
// Stop blocks until all activity on this node is done.
func (wn *WebsocketNetwork) Stop() {
- wn.handlers.ClearHandlers([]Tag{})
+ wn.handler.ClearHandlers([]Tag{})
// if we have a working ticker, just stop it and clear it out. The access to this variable is safe since the Start()/Stop() are synced by the
// caller, and the WebsocketNetwork doesn't access wn.peersConnectivityCheckTicker directly.
@@ -987,13 +1018,13 @@ func (wn *WebsocketNetwork) Stop() {
// RegisterHandlers registers the set of given message handlers.
func (wn *WebsocketNetwork) RegisterHandlers(dispatch []TaggedMessageHandler) {
- wn.handlers.RegisterHandlers(dispatch)
+ wn.handler.RegisterHandlers(dispatch)
}
// ClearHandlers deregisters all the existing message handlers.
func (wn *WebsocketNetwork) ClearHandlers() {
// exclude the internal handlers. These would get cleared out when Stop is called.
- wn.handlers.ClearHandlers([]Tag{protocol.PingTag, protocol.PingReplyTag, protocol.NetPrioResponseTag})
+ wn.handler.ClearHandlers([]Tag{protocol.PingTag, protocol.PingReplyTag, protocol.NetPrioResponseTag})
}
func (wn *WebsocketNetwork) setHeaders(header http.Header) {
@@ -1233,8 +1264,8 @@ func (wn *WebsocketNetwork) ServeHTTP(response http.ResponseWriter, request *htt
}
peer := &wsPeer{
- wsPeerCore: makePeerCore(wn, trackedRequest.otherPublicAddr, wn.GetRoundTripper(), trackedRequest.remoteHost),
- conn: conn,
+ wsPeerCore: makePeerCore(wn.ctx, wn, wn.log, wn.handler.readBuffer, trackedRequest.otherPublicAddr, wn.GetRoundTripper(), trackedRequest.remoteHost),
+ conn: wsPeerWebsocketConnImpl{conn},
outgoing: false,
InstanceName: trackedRequest.otherInstanceName,
incomingMsgFilter: wn.incomingMsgFilter,
@@ -1281,8 +1312,8 @@ func (wn *WebsocketNetwork) maybeSendMessagesOfInterest(peer *wsPeer, messagesOf
}
}
-func (wn *WebsocketNetwork) messageHandlerThread(peersConnectivityCheckCh <-chan time.Time) {
- defer wn.wg.Done()
+func (wn *msgHandler) messageHandlerThread(wg *sync.WaitGroup, peersConnectivityCheckCh <-chan time.Time, net networkPeerManager) {
+ defer wg.Done()
for {
select {
@@ -1298,13 +1329,13 @@ func (wn *WebsocketNetwork) messageHandlerThread(peersConnectivityCheckCh <-chan
}
}
if wn.config.EnableOutgoingNetworkMessageFiltering && len(msg.Data) >= messageFilterSize {
- wn.sendFilterMessage(msg)
+ wn.sendFilterMessage(msg, net)
}
//wn.log.Debugf("msg handling %#v [%d]byte", msg.Tag, len(msg.Data))
start := time.Now()
// now, send to global handlers
- outmsg := wn.handlers.Handle(msg)
+ outmsg := wn.Handle(msg)
handled := time.Now()
bufferNanos := start.UnixNano() - msg.Received
networkIncomingBufferMicros.AddUint64(uint64(bufferNanos/1000), nil)
@@ -1312,14 +1343,14 @@ func (wn *WebsocketNetwork) messageHandlerThread(peersConnectivityCheckCh <-chan
networkHandleMicros.AddUint64(uint64(handleTime.Nanoseconds()/1000), nil)
switch outmsg.Action {
case Disconnect:
- wn.wg.Add(1)
+ wg.Add(1)
reason := disconnectBadData
if outmsg.reason != disconnectReasonNone {
reason = outmsg.reason
}
- go wn.disconnectThread(msg.Sender, reason)
+ go net.disconnectThread(msg.Sender, reason)
case Broadcast:
- err := wn.Broadcast(wn.ctx, msg.Tag, msg.Data, false, msg.Sender)
+ err := net.Broadcast(wn.ctx, msg.Tag, msg.Data, false, msg.Sender)
if err != nil && err != errBcastQFull {
wn.log.Warnf("WebsocketNetwork.messageHandlerThread: WebsocketNetwork.Broadcast returned unexpected error %v", err)
}
@@ -1332,7 +1363,7 @@ func (wn *WebsocketNetwork) messageHandlerThread(peersConnectivityCheckCh <-chan
}
case <-peersConnectivityCheckCh:
// go over the peers and ensure we have some type of communication going on.
- wn.checkPeersConnectivity()
+ net.checkPeersConnectivity()
}
}
}
@@ -1372,25 +1403,25 @@ func (wn *WebsocketNetwork) checkSlowWritingPeers() {
}
}
-func (wn *WebsocketNetwork) sendFilterMessage(msg IncomingMessage) {
+func (wn *msgHandler) sendFilterMessage(msg IncomingMessage, net networkPeerManager) {
digest := generateMessageDigest(msg.Tag, msg.Data)
//wn.log.Debugf("send filter %s(%d) %v", msg.Tag, len(msg.Data), digest)
- err := wn.Broadcast(context.Background(), protocol.MsgDigestSkipTag, digest[:], false, msg.Sender)
+ err := net.Broadcast(context.Background(), protocol.MsgDigestSkipTag, digest[:], false, msg.Sender)
if err != nil && err != errBcastQFull {
wn.log.Warnf("WebsocketNetwork.sendFilterMessage: WebsocketNetwork.Broadcast returned unexpected error %v", err)
}
}
-func (wn *WebsocketNetwork) broadcastThread() {
- defer wn.wg.Done()
+func (wn *msgBroadcaster) broadcastThread(wg *sync.WaitGroup, net networkPeerManager) {
+ defer wg.Done()
slowWritingPeerCheckTicker := time.NewTicker(wn.slowWritingPeerMonitorInterval)
defer slowWritingPeerCheckTicker.Stop()
- peers, lastPeersChangeCounter := wn.peerSnapshot([]*wsPeer{})
+ peers, lastPeersChangeCounter := net.peerSnapshot([]*wsPeer{})
// updatePeers update the peers list if their peer change counter has changed.
updatePeers := func() {
- if curPeersChangeCounter := atomic.LoadInt32(&wn.peersChangeCounter); curPeersChangeCounter != lastPeersChangeCounter {
- peers, lastPeersChangeCounter = wn.peerSnapshot(peers)
+ if curPeersChangeCounter := net.getPeersChangeCounter(); curPeersChangeCounter != lastPeersChangeCounter {
+ peers, lastPeersChangeCounter = net.peerSnapshot(peers)
}
}
@@ -1481,7 +1512,7 @@ func (wn *WebsocketNetwork) broadcastThread() {
}
wn.innerBroadcast(request, true, peers)
case <-slowWritingPeerCheckTicker.C:
- wn.checkSlowWritingPeers()
+ net.checkSlowWritingPeers()
continue
case request := <-wn.broadcastQueueBulk:
// check if peers need to be updated, since we've been waiting a while.
@@ -1516,13 +1547,16 @@ func (wn *WebsocketNetwork) peerSnapshot(dest []*wsPeer) ([]*wsPeer, int32) {
dest = make([]*wsPeer, len(wn.peers))
}
copy(dest, wn.peers)
- peerChangeCounter := atomic.LoadInt32(&wn.peersChangeCounter)
- return dest, peerChangeCounter
+ return dest, wn.getPeersChangeCounter()
+}
+
+func (wn *WebsocketNetwork) getPeersChangeCounter() int32 {
+ return atomic.LoadInt32(&wn.peersChangeCounter)
}
// preparePeerData prepares batches of data for sending.
// It performs optional zstd compression for proposal massages
-func (wn *WebsocketNetwork) preparePeerData(request broadcastRequest, prio bool, peers []*wsPeer) ([][]byte, [][]byte, []crypto.Digest, bool) {
+func (wn *msgBroadcaster) preparePeerData(request broadcastRequest, prio bool, peers []*wsPeer) ([][]byte, [][]byte, []crypto.Digest, bool) {
// determine if there is a payload proposal and peers supporting compressed payloads
wantCompression := false
containsPrioPPTag := false
@@ -1572,7 +1606,7 @@ func (wn *WebsocketNetwork) preparePeerData(request broadcastRequest, prio bool,
}
// prio is set if the broadcast is a high-priority broadcast.
-func (wn *WebsocketNetwork) innerBroadcast(request broadcastRequest, prio bool, peers []*wsPeer) {
+func (wn *msgBroadcaster) innerBroadcast(request broadcastRequest, prio bool, peers []*wsPeer) {
if request.done != nil {
defer close(request.done)
}
@@ -1946,7 +1980,7 @@ func (wn *WebsocketNetwork) getPeerConnectionTelemetryDetails(now time.Time, pee
connDetail.TCP = *tcpInfo
}
if peer.outgoing {
- connDetail.Address = justHost(peer.conn.RemoteAddr().String())
+ connDetail.Address = justHost(peer.conn.RemoteAddrString())
connDetail.Endpoint = peer.GetAddress()
connDetail.MessageDelay = peer.peerMessageDelay
connectionDetails.OutgoingPeers = append(connectionDetails.OutgoingPeers, connDetail)
@@ -2357,8 +2391,8 @@ func (wn *WebsocketNetwork) tryConnect(addr, gossipAddr string) {
}
peer := &wsPeer{
- wsPeerCore: makePeerCore(wn, addr, wn.GetRoundTripper(), "" /* origin */),
- conn: conn,
+ wsPeerCore: makePeerCore(wn.ctx, wn, wn.log, wn.handler.readBuffer, addr, wn.GetRoundTripper(), "" /* origin */),
+ conn: wsPeerWebsocketConnImpl{conn},
outgoing: true,
incomingMsgFilter: wn.incomingMsgFilter,
createTime: time.Now(),
@@ -2491,9 +2525,9 @@ func (wn *WebsocketNetwork) removePeer(peer *wsPeer, reason disconnectReason) {
peerAddr := peer.OriginAddress()
// we might be able to get addr out of conn, or it might be closed
if peerAddr == "" && peer.conn != nil {
- paddr := peer.conn.RemoteAddr()
- if paddr != nil {
- peerAddr = justHost(paddr.String())
+ paddr := peer.conn.RemoteAddrString()
+ if paddr != "" {
+ peerAddr = justHost(paddr)
}
}
if peerAddr == "" {
@@ -2550,7 +2584,7 @@ func (wn *WebsocketNetwork) addPeer(peer *wsPeer) {
// guard against peers which are closed or closing
if atomic.LoadInt32(&peer.didSignalClose) == 1 {
networkPeerAlreadyClosed.Inc(nil)
- wn.log.Debugf("peer closing %s", peer.conn.RemoteAddr().String())
+ wn.log.Debugf("peer closing %s", peer.conn.RemoteAddrString())
return
}
// simple duplicate *pointer* check. should never trigger given the callers to addPeer
diff --git a/network/wsNetwork_test.go b/network/wsNetwork_test.go
index 05992ec8d..ac59b40b2 100644
--- a/network/wsNetwork_test.go
+++ b/network/wsNetwork_test.go
@@ -543,7 +543,7 @@ func TestWebsocketNetworkArray(t *testing.T) {
tags := []protocol.Tag{protocol.TxnTag, protocol.TxnTag, protocol.TxnTag}
data := [][]byte{[]byte("foo"), []byte("bar"), []byte("algo")}
- netA.BroadcastArray(context.Background(), tags, data, false, nil)
+ netA.broadcaster.BroadcastArray(context.Background(), tags, data, false, nil)
select {
case <-counterDone:
@@ -571,7 +571,7 @@ func TestWebsocketNetworkCancel(t *testing.T) {
cancel()
// try calling BroadcastArray
- netA.BroadcastArray(ctx, tags, data, true, nil)
+ netA.broadcaster.BroadcastArray(ctx, tags, data, true, nil)
select {
case <-counterDone:
@@ -583,7 +583,7 @@ func TestWebsocketNetworkCancel(t *testing.T) {
// try calling innerBroadcast
request := broadcastRequest{tags: tags, data: data, enqueueTime: time.Now(), ctx: ctx}
peers, _ := netA.peerSnapshot([]*wsPeer{})
- netA.innerBroadcast(request, true, peers)
+ netA.broadcaster.innerBroadcast(request, true, peers)
select {
case <-counterDone:
@@ -760,9 +760,11 @@ func TestAddrToGossipAddr(t *testing.T) {
type nopConn struct{}
func (nc *nopConn) RemoteAddr() net.Addr { return nil }
+func (nc *nopConn) RemoteAddrString() string { return "" }
func (nc *nopConn) NextReader() (int, io.Reader, error) { return 0, nil, nil }
func (nc *nopConn) WriteMessage(int, []byte) error { return nil }
func (nc *nopConn) WriteControl(int, []byte, time.Time) error { return nil }
+func (nc *nopConn) CloseWithMessage([]byte, time.Time) error { return nil }
func (nc *nopConn) SetReadLimit(limit int64) {}
func (nc *nopConn) CloseWithoutFlush() error { return nil }
func (nc *nopConn) SetPingHandler(h func(appData string) error) {}
@@ -799,7 +801,7 @@ func TestSlowHandlers(t *testing.T) {
// start slow handler calls that will block all handler threads
for i := 0; i < incomingThreads; i++ {
data := []byte{byte(i)}
- node.readBuffer <- IncomingMessage{Sender: &injectionPeers[ipi], Tag: slowTag, Data: data, Net: node}
+ node.handler.readBuffer <- IncomingMessage{Sender: &injectionPeers[ipi], Tag: slowTag, Data: data, Net: node}
ipi++
}
defer slowCounter.Broadcast()
@@ -807,7 +809,7 @@ func TestSlowHandlers(t *testing.T) {
// start fast handler calls that won't get to run
for i := 0; i < incomingThreads; i++ {
data := []byte{byte(i)}
- node.readBuffer <- IncomingMessage{Sender: &injectionPeers[ipi], Tag: fastTag, Data: data, Net: node}
+ node.handler.readBuffer <- IncomingMessage{Sender: &injectionPeers[ipi], Tag: fastTag, Data: data, Net: node}
ipi++
}
ok := false
@@ -890,7 +892,7 @@ func TestFloodingPeer(t *testing.T) {
}
select {
- case node.readBuffer <- IncomingMessage{Sender: &injectionPeers[myIpi], Tag: slowTag, Data: data, Net: node, processing: processed}:
+ case node.handler.readBuffer <- IncomingMessage{Sender: &injectionPeers[myIpi], Tag: slowTag, Data: data, Net: node, processing: processed}:
case <-ctx.Done():
return
}
@@ -912,7 +914,7 @@ func TestFloodingPeer(t *testing.T) {
fastCounterDone := fastCounter.done
for ipi < len(injectionPeers) {
data := []byte{byte(ipi)}
- node.readBuffer <- IncomingMessage{Sender: &injectionPeers[ipi], Tag: fastTag, Data: data, Net: node}
+ node.handler.readBuffer <- IncomingMessage{Sender: &injectionPeers[ipi], Tag: fastTag, Data: data, Net: node}
numFast++
ipi++
}
@@ -2176,7 +2178,7 @@ func BenchmarkWebsocketNetworkBasic(t *testing.B) {
networkBroadcastsDropped.WriteMetric(&buf, "")
t.Errorf(
"a out queue=%d, metric: %s",
- len(netA.broadcastQueueBulk),
+ len(netA.broadcaster.broadcastQueueBulk),
buf.String(),
)
return
@@ -2450,9 +2452,9 @@ func (wn *WebsocketNetwork) broadcastWithTimestamp(tag protocol.Tag, data []byte
tagArr[0] = tag
request := broadcastRequest{tags: tagArr, data: msgArr, enqueueTime: when, ctx: context.Background()}
- broadcastQueue := wn.broadcastQueueBulk
+ broadcastQueue := wn.broadcaster.broadcastQueueBulk
if highPriorityTag(tagArr) {
- broadcastQueue = wn.broadcastQueueHighPrio
+ broadcastQueue = wn.broadcaster.broadcastQueueHighPrio
}
// no wait
select {
@@ -2508,13 +2510,13 @@ func TestSlowPeerDisconnection(t *testing.T) {
log := logging.TestingLog(t)
log.SetLevel(logging.Info)
wn := &WebsocketNetwork{
- log: log,
- config: defaultConfig,
- phonebook: MakePhonebook(1, 1*time.Millisecond),
- GenesisID: genesisID,
- NetworkID: config.Devtestnet,
- slowWritingPeerMonitorInterval: time.Millisecond * 50,
+ log: log,
+ config: defaultConfig,
+ phonebook: MakePhonebook(1, 1*time.Millisecond),
+ GenesisID: genesisID,
+ NetworkID: config.Devtestnet,
}
+ wn.broadcaster.slowWritingPeerMonitorInterval = time.Millisecond * 50
wn.setup()
wn.eventualReadyDelay = time.Second
wn.messagesOfInterest = nil // clear this before starting the network so that we won't be sending a MOI upon connection.
@@ -3705,7 +3707,7 @@ func TestPreparePeerData(t *testing.T) {
peers := []*wsPeer{}
wn := WebsocketNetwork{}
- data, comp, digests, seenPrioPPTag := wn.preparePeerData(req, false, peers)
+ data, comp, digests, seenPrioPPTag := wn.broadcaster.preparePeerData(req, false, peers)
require.NotEmpty(t, data)
require.Empty(t, comp)
require.NotEmpty(t, digests)
@@ -3725,7 +3727,7 @@ func TestPreparePeerData(t *testing.T) {
features: pfCompressedProposal,
}
peers = []*wsPeer{&peer1, &peer2}
- data, comp, digests, seenPrioPPTag = wn.preparePeerData(req, true, peers)
+ data, comp, digests, seenPrioPPTag = wn.broadcaster.preparePeerData(req, true, peers)
require.NotEmpty(t, data)
require.NotEmpty(t, comp)
require.NotEmpty(t, digests)
diff --git a/network/wsPeer.go b/network/wsPeer.go
index 1c9aad3db..a717eef60 100644
--- a/network/wsPeer.go
+++ b/network/wsPeer.go
@@ -36,6 +36,7 @@ import (
"github.com/algorand/go-algorand/config"
"github.com/algorand/go-algorand/crypto"
"github.com/algorand/go-algorand/data/basics"
+ "github.com/algorand/go-algorand/logging"
"github.com/algorand/go-algorand/protocol"
"github.com/algorand/go-algorand/util"
"github.com/algorand/go-algorand/util/metrics"
@@ -119,17 +120,31 @@ var defaultSendMessageTags = map[protocol.Tag]bool{
// interface allows substituting debug implementation for *websocket.Conn
type wsPeerWebsocketConn interface {
- RemoteAddr() net.Addr
+ RemoteAddrString() string
NextReader() (int, io.Reader, error)
WriteMessage(int, []byte) error
- WriteControl(int, []byte, time.Time) error
+ CloseWithMessage([]byte, time.Time) error
SetReadLimit(int64)
CloseWithoutFlush() error
- SetPingHandler(h func(appData string) error)
- SetPongHandler(h func(appData string) error)
wrappedConn
}
+type wsPeerWebsocketConnImpl struct {
+ *websocket.Conn
+}
+
+func (c wsPeerWebsocketConnImpl) RemoteAddrString() string {
+ addr := c.RemoteAddr()
+ if addr == nil {
+ return ""
+ }
+ return addr.String()
+}
+
+func (c wsPeerWebsocketConnImpl) CloseWithMessage(msg []byte, deadline time.Time) error {
+ return c.WriteControl(websocket.CloseMessage, msg, deadline)
+}
+
type wrappedConn interface {
UnderlyingConn() net.Conn
}
@@ -145,7 +160,10 @@ type sendMessage struct {
// wsPeerCore also works for non-connected peers we want to do HTTP GET from
type wsPeerCore struct {
- net *WebsocketNetwork
+ net GossipNode
+ netCtx context.Context
+ log logging.Logger
+ readBuffer chan<- IncomingMessage
rootURL string
originAddress string // incoming connection remote host
client http.Client
@@ -325,9 +343,12 @@ type TCPInfoUnicastPeer interface {
}
// Create a wsPeerCore object
-func makePeerCore(net *WebsocketNetwork, rootURL string, roundTripper http.RoundTripper, originAddress string) wsPeerCore {
+func makePeerCore(ctx context.Context, net GossipNode, log logging.Logger, readBuffer chan<- IncomingMessage, rootURL string, roundTripper http.RoundTripper, originAddress string) wsPeerCore {
return wsPeerCore{
net: net,
+ netCtx: ctx,
+ log: log,
+ readBuffer: readBuffer,
rootURL: rootURL,
originAddress: originAddress,
client: http.Client{Transport: roundTripper},
@@ -419,7 +440,7 @@ func (wp *wsPeer) Respond(ctx context.Context, reqMsg IncomingMessage, outMsg Ou
if outMsg.OnRelease != nil {
outMsg.OnRelease()
}
- wp.net.log.Debugf("peer closing %s", wp.conn.RemoteAddr().String())
+ wp.log.Debugf("peer closing %s", wp.conn.RemoteAddrString())
return
case <-ctx.Done():
if outMsg.OnRelease != nil {
@@ -432,7 +453,7 @@ func (wp *wsPeer) Respond(ctx context.Context, reqMsg IncomingMessage, outMsg Ou
// setup values not trivially assigned
func (wp *wsPeer) init(config config.Local, sendBufferLength int) {
- wp.net.log.Debugf("wsPeer init outgoing=%v %#v", wp.outgoing, wp.rootURL)
+ wp.log.Debugf("wsPeer init outgoing=%v %#v", wp.outgoing, wp.rootURL)
wp.closing = make(chan struct{})
wp.sendBufferHighPrio = make(chan sendMessages, sendBufferLength)
wp.sendBufferBulk = make(chan sendMessages, sendBufferLength)
@@ -468,7 +489,7 @@ func (wp *wsPeer) reportReadErr(err error) {
// only report error if we haven't already closed the peer
if atomic.LoadInt32(&wp.didInnerClose) == 0 {
_, _, line, _ := runtime.Caller(1)
- wp.net.log.Warnf("peer[%s] line=%d read err: %s", wp.conn.RemoteAddr().String(), line, err)
+ wp.log.Warnf("peer[%s] line=%d read err: %s", wp.conn.RemoteAddrString(), line, err)
networkConnectionsDroppedTotal.Inc(map[string]string{"reason": "reader err"})
}
}
@@ -506,7 +527,7 @@ func (wp *wsPeer) readLoop() {
return
}
if mtype != websocket.BinaryMessage {
- wp.net.log.Errorf("peer sent non websocket-binary message: %#v", mtype)
+ wp.log.Errorf("peer sent non websocket-binary message: %#v", mtype)
networkConnectionsDroppedTotal.Inc(map[string]string{"reason": "protocol"})
return
}
@@ -524,7 +545,7 @@ func (wp *wsPeer) readLoop() {
// This peers has sent us more responses than we have requested. This is a protocol violation and we should disconnect.
if atomic.LoadInt64(&wp.outstandingTopicRequests) < 0 {
- wp.net.log.Errorf("wsPeer readloop: peer %s sent TS response without a request", wp.conn.RemoteAddr().String())
+ wp.log.Errorf("wsPeer readloop: peer %s sent TS response without a request", wp.conn.RemoteAddrString())
networkConnectionsDroppedTotal.Inc(map[string]string{"reason": "unrequestedTS"})
cleanupCloseError = disconnectUnexpectedTopicResp
return
@@ -533,11 +554,11 @@ func (wp *wsPeer) readLoop() {
// Peer sent us a response to a request we made but we've already timed out -- discard
n, err = io.Copy(io.Discard, reader)
if err != nil {
- wp.net.log.Infof("wsPeer readloop: could not discard timed-out TS message from %s : %s", wp.conn.RemoteAddr().String(), err)
+ wp.log.Infof("wsPeer readloop: could not discard timed-out TS message from %s : %s", wp.conn.RemoteAddrString(), err)
wp.reportReadErr(err)
return
}
- wp.net.log.Warnf("wsPeer readLoop: received a TS response for a stale request from %s. %d bytes discarded", wp.conn.RemoteAddr().String(), n)
+ wp.log.Warnf("wsPeer readLoop: received a TS response for a stale request from %s. %d bytes discarded", wp.conn.RemoteAddrString(), n)
continue
}
@@ -586,18 +607,18 @@ func (wp *wsPeer) readLoop() {
atomic.AddInt64(&wp.outstandingTopicRequests, -1)
topics, err := UnmarshallTopics(msg.Data)
if err != nil {
- wp.net.log.Warnf("wsPeer readLoop: could not read the message from: %s %s", wp.conn.RemoteAddr().String(), err)
+ wp.log.Warnf("wsPeer readLoop: could not read the message from: %s %s", wp.conn.RemoteAddrString(), err)
continue
}
requestHash, found := topics.GetValue(requestHashKey)
if !found {
- wp.net.log.Warnf("wsPeer readLoop: message from %s is missing the %s", wp.conn.RemoteAddr().String(), requestHashKey)
+ wp.log.Warnf("wsPeer readLoop: message from %s is missing the %s", wp.conn.RemoteAddrString(), requestHashKey)
continue
}
hashKey, _ := binary.Uvarint(requestHash)
channel, found := wp.getAndRemoveResponseChannel(hashKey)
if !found {
- wp.net.log.Warnf("wsPeer readLoop: received a message response from %s for a stale request", wp.conn.RemoteAddr().String())
+ wp.log.Warnf("wsPeer readLoop: received a message response from %s for a stale request", wp.conn.RemoteAddrString())
continue
}
@@ -605,7 +626,7 @@ func (wp *wsPeer) readLoop() {
case channel <- &Response{Topics: topics}:
// do nothing. writing was successful.
default:
- wp.net.log.Warn("wsPeer readLoop: channel blocked. Could not pass the response to the requester", wp.conn.RemoteAddr().String())
+ wp.log.Warn("wsPeer readLoop: channel blocked. Could not pass the response to the requester", wp.conn.RemoteAddrString())
}
continue
case protocol.MsgDigestSkipTag:
@@ -629,28 +650,28 @@ func (wp *wsPeer) readLoop() {
}
if len(msg.Data) > 0 && wp.incomingMsgFilter != nil && dedupSafeTag(msg.Tag) {
if wp.incomingMsgFilter.CheckIncomingMessage(msg.Tag, msg.Data, true, true) {
- //wp.net.log.Debugf("dropped incoming duplicate %s(%d)", msg.Tag, len(msg.Data))
+ //wp.log.Debugf("dropped incoming duplicate %s(%d)", msg.Tag, len(msg.Data))
duplicateNetworkMessageReceivedTotal.Inc(nil)
duplicateNetworkMessageReceivedBytesTotal.AddUint64(uint64(len(msg.Data)+len(msg.Tag)), nil)
// drop message, skip adding it to queue
continue
}
}
- //wp.net.log.Debugf("got msg %d bytes from %s", len(msg.Data), wp.conn.RemoteAddr().String())
+ //wp.log.Debugf("got msg %d bytes from %s", len(msg.Data), wp.conn.RemoteAddrString())
// Wait for a previous message from this peer to be processed,
// to achieve fairness in wp.net.readBuffer.
select {
case <-wp.processed:
case <-wp.closing:
- wp.net.log.Debugf("peer closing %s", wp.conn.RemoteAddr().String())
+ wp.log.Debugf("peer closing %s", wp.conn.RemoteAddrString())
return
}
select {
- case wp.net.readBuffer <- msg:
+ case wp.readBuffer <- msg:
case <-wp.closing:
- wp.net.log.Debugf("peer closing %s", wp.conn.RemoteAddr().String())
+ wp.log.Debugf("peer closing %s", wp.conn.RemoteAddrString())
return
}
}
@@ -662,7 +683,7 @@ func (wp *wsPeer) handleMessageOfInterest(msg IncomingMessage) (close bool, reas
// decode the message, and ensure it's a valid message.
msgTagsMap, err := unmarshallMessageOfInterest(msg.Data)
if err != nil {
- wp.net.log.Warnf("wsPeer handleMessageOfInterest: could not unmarshall message from: %s %v", wp.conn.RemoteAddr().String(), err)
+ wp.log.Warnf("wsPeer handleMessageOfInterest: could not unmarshall message from: %s %v", wp.conn.RemoteAddrString(), err)
return true, disconnectBadData
}
msgs := make([]sendMessage, 1, 1)
@@ -681,7 +702,7 @@ func (wp *wsPeer) handleMessageOfInterest(msg IncomingMessage) (close bool, reas
case wp.sendBufferHighPrio <- sm:
return
case <-wp.closing:
- wp.net.log.Debugf("peer closing %s", wp.conn.RemoteAddr().String())
+ wp.log.Debugf("peer closing %s", wp.conn.RemoteAddrString())
return true, disconnectReasonNone
default:
}
@@ -690,7 +711,7 @@ func (wp *wsPeer) handleMessageOfInterest(msg IncomingMessage) (close bool, reas
case wp.sendBufferHighPrio <- sm:
case wp.sendBufferBulk <- sm:
case <-wp.closing:
- wp.net.log.Debugf("peer closing %s", wp.conn.RemoteAddr().String())
+ wp.log.Debugf("peer closing %s", wp.conn.RemoteAddrString())
return true, disconnectReasonNone
}
return
@@ -707,12 +728,12 @@ func (wp *wsPeer) handleFilterMessage(msg IncomingMessage) {
return
}
if len(msg.Data) != crypto.DigestSize {
- wp.net.log.Warnf("bad filter message size %d", len(msg.Data))
+ wp.log.Warnf("bad filter message size %d", len(msg.Data))
return
}
var digest crypto.Digest
copy(digest[:], msg.Data)
- //wp.net.log.Debugf("add filter %v", digest)
+ //wp.log.Debugf("add filter %v", digest)
has := wp.outgoingMsgFilter.CheckDigest(digest, true, true)
if has {
// Count that this peer has sent us duplicate filter messages: this means it received the same
@@ -745,7 +766,7 @@ func (wp *wsPeer) writeLoopSend(msgs sendMessages) disconnectReason {
func (wp *wsPeer) writeLoopSendMsg(msg sendMessage) disconnectReason {
if len(msg.data) > MaxMessageLength {
- wp.net.log.Errorf("trying to send a message longer than we would receive: %d > %d tag=%s", len(msg.data), MaxMessageLength, string(msg.data[0:2]))
+ wp.log.Errorf("trying to send a message longer than we would receive: %d > %d tag=%s", len(msg.data), MaxMessageLength, string(msg.data[0:2]))
// just drop it, don't break the connection
return disconnectReasonNone
}
@@ -766,7 +787,7 @@ func (wp *wsPeer) writeLoopSendMsg(msg sendMessage) disconnectReason {
now := time.Now()
msgWaitDuration := now.Sub(msg.enqueued)
if msgWaitDuration > maxMessageQueueDuration {
- wp.net.log.Warnf("peer stale enqueued message %dms", msgWaitDuration.Nanoseconds()/1000000)
+ wp.log.Warnf("peer stale enqueued message %dms", msgWaitDuration.Nanoseconds()/1000000)
networkConnectionsDroppedTotal.Inc(map[string]string{"reason": "stale message"})
return disconnectStaleWrite
}
@@ -776,7 +797,7 @@ func (wp *wsPeer) writeLoopSendMsg(msg sendMessage) disconnectReason {
err := wp.conn.WriteMessage(websocket.BinaryMessage, msg.data)
if err != nil {
if atomic.LoadInt32(&wp.didInnerClose) == 0 {
- wp.net.log.Warn("peer write error ", err)
+ wp.log.Warn("peer write error ", err)
networkConnectionsDroppedTotal.Inc(map[string]string{"reason": "write err"})
}
return disconnectWriteError
@@ -842,7 +863,7 @@ func (wp *wsPeer) writeNonBlockMsgs(ctx context.Context, data [][]byte, highPrio
includeIndices := make([]int, 0, len(data))
for i := range data {
if wp.outgoingMsgFilter != nil && len(data[i]) > messageFilterSize && wp.outgoingMsgFilter.CheckDigest(digest[i], false, false) {
- //wp.net.log.Debugf("msg drop as outbound dup %s(%d) %v", string(data[:2]), len(data)-2, digest)
+ //wp.log.Debugf("msg drop as outbound dup %s(%d) %v", string(data[:2]), len(data)-2, digest)
// peer has notified us it doesn't need this message
outgoingNetworkMessageFilteredOutTotal.Inc(nil)
outgoingNetworkMessageFilteredOutBytesTotal.AddUint64(uint64(len(data)), nil)
@@ -926,13 +947,13 @@ func (wp *wsPeer) Close(deadline time.Time) {
atomic.StoreInt32(&wp.didSignalClose, 1)
if atomic.CompareAndSwapInt32(&wp.didInnerClose, 0, 1) {
close(wp.closing)
- err := wp.conn.WriteControl(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""), deadline)
+ err := wp.conn.CloseWithMessage(websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""), deadline)
if err != nil {
- wp.net.log.Infof("failed to write CloseMessage to connection for %s", wp.conn.RemoteAddr().String())
+ wp.log.Infof("failed to write CloseMessage to connection for %s", wp.conn.RemoteAddrString())
}
err = wp.conn.CloseWithoutFlush()
if err != nil {
- wp.net.log.Infof("failed to CloseWithoutFlush to connection for %s", wp.conn.RemoteAddr().String())
+ wp.log.Infof("failed to CloseWithoutFlush to connection for %s", wp.conn.RemoteAddrString())
}
}
@@ -1019,7 +1040,7 @@ func (wp *wsPeer) Request(ctx context.Context, tag Tag, topics Topics) (resp *Re
case wp.sendBufferBulk <- sendMessages{msgs: msg}:
atomic.AddInt64(&wp.outstandingTopicRequests, 1)
case <-wp.closing:
- e = fmt.Errorf("peer closing %s", wp.conn.RemoteAddr().String())
+ e = fmt.Errorf("peer closing %s", wp.conn.RemoteAddrString())
return
case <-ctx.Done():
return resp, ctx.Err()
@@ -1030,7 +1051,7 @@ func (wp *wsPeer) Request(ctx context.Context, tag Tag, topics Topics) (resp *Re
case resp = <-responseChannel:
return resp, nil
case <-wp.closing:
- e = fmt.Errorf("peer closing %s", wp.conn.RemoteAddr().String())
+ e = fmt.Errorf("peer closing %s", wp.conn.RemoteAddrString())
return
case <-ctx.Done():
return resp, ctx.Err()
@@ -1077,9 +1098,9 @@ func (wp *wsPeer) setPeerData(key string, value interface{}) {
}
func (wp *wsPeer) sendMessagesOfInterest(messagesOfInterestGeneration uint32, messagesOfInterestEnc []byte) {
- err := wp.Unicast(wp.net.ctx, messagesOfInterestEnc, protocol.MsgOfInterestTag)
+ err := wp.Unicast(wp.netCtx, messagesOfInterestEnc, protocol.MsgOfInterestTag)
if err != nil {
- wp.net.log.Errorf("ws send msgOfInterest: %v", err)
+ wp.log.Errorf("ws send msgOfInterest: %v", err)
} else {
atomic.StoreUint32(&wp.messagesOfInterestGeneration, messagesOfInterestGeneration)
}
diff --git a/node/msgp_gen.go b/node/msgp_gen.go
index 88c92b37c..146e8635b 100644
--- a/node/msgp_gen.go
+++ b/node/msgp_gen.go
@@ -77,8 +77,8 @@ func (z *netPrioResponse) UnmarshalMsg(bts []byte) (o []byte, err error) {
err = msgp.WrapError(err, "struct-from-array", "Nonce")
return
}
- if zb0003 > netPrioChallengeSize {
- err = msgp.ErrOverflow(uint64(zb0003), uint64(netPrioChallengeSize))
+ if zb0003 > netPrioChallengeSizeBase64Encoded {
+ err = msgp.ErrOverflow(uint64(zb0003), uint64(netPrioChallengeSizeBase64Encoded))
return
}
(*z).Nonce, bts, err = msgp.ReadStringBytes(bts)
@@ -117,8 +117,8 @@ func (z *netPrioResponse) UnmarshalMsg(bts []byte) (o []byte, err error) {
err = msgp.WrapError(err, "Nonce")
return
}
- if zb0004 > netPrioChallengeSize {
- err = msgp.ErrOverflow(uint64(zb0004), uint64(netPrioChallengeSize))
+ if zb0004 > netPrioChallengeSizeBase64Encoded {
+ err = msgp.ErrOverflow(uint64(zb0004), uint64(netPrioChallengeSizeBase64Encoded))
return
}
(*z).Nonce, bts, err = msgp.ReadStringBytes(bts)
@@ -157,7 +157,7 @@ func (z *netPrioResponse) MsgIsZero() bool {
// MaxSize returns a maximum valid message size for this message type
func NetPrioResponseMaxSize() (s int) {
- s = 1 + 6 + msgp.StringPrefixSize + netPrioChallengeSize
+ s = 1 + 6 + msgp.StringPrefixSize + netPrioChallengeSizeBase64Encoded
return
}
@@ -260,8 +260,8 @@ func (z *netPrioResponseSigned) UnmarshalMsg(bts []byte) (o []byte, err error) {
err = msgp.WrapError(err, "struct-from-array", "Response", "struct-from-array", "Nonce")
return
}
- if zb0005 > netPrioChallengeSize {
- err = msgp.ErrOverflow(uint64(zb0005), uint64(netPrioChallengeSize))
+ if zb0005 > netPrioChallengeSizeBase64Encoded {
+ err = msgp.ErrOverflow(uint64(zb0005), uint64(netPrioChallengeSizeBase64Encoded))
return
}
(*z).Response.Nonce, bts, err = msgp.ReadStringBytes(bts)
@@ -300,8 +300,8 @@ func (z *netPrioResponseSigned) UnmarshalMsg(bts []byte) (o []byte, err error) {
err = msgp.WrapError(err, "struct-from-array", "Response", "Nonce")
return
}
- if zb0006 > netPrioChallengeSize {
- err = msgp.ErrOverflow(uint64(zb0006), uint64(netPrioChallengeSize))
+ if zb0006 > netPrioChallengeSizeBase64Encoded {
+ err = msgp.ErrOverflow(uint64(zb0006), uint64(netPrioChallengeSizeBase64Encoded))
return
}
(*z).Response.Nonce, bts, err = msgp.ReadStringBytes(bts)
@@ -384,8 +384,8 @@ func (z *netPrioResponseSigned) UnmarshalMsg(bts []byte) (o []byte, err error) {
err = msgp.WrapError(err, "Response", "struct-from-array", "Nonce")
return
}
- if zb0009 > netPrioChallengeSize {
- err = msgp.ErrOverflow(uint64(zb0009), uint64(netPrioChallengeSize))
+ if zb0009 > netPrioChallengeSizeBase64Encoded {
+ err = msgp.ErrOverflow(uint64(zb0009), uint64(netPrioChallengeSizeBase64Encoded))
return
}
(*z).Response.Nonce, bts, err = msgp.ReadStringBytes(bts)
@@ -424,8 +424,8 @@ func (z *netPrioResponseSigned) UnmarshalMsg(bts []byte) (o []byte, err error) {
err = msgp.WrapError(err, "Response", "Nonce")
return
}
- if zb0010 > netPrioChallengeSize {
- err = msgp.ErrOverflow(uint64(zb0010), uint64(netPrioChallengeSize))
+ if zb0010 > netPrioChallengeSizeBase64Encoded {
+ err = msgp.ErrOverflow(uint64(zb0010), uint64(netPrioChallengeSizeBase64Encoded))
return
}
(*z).Response.Nonce, bts, err = msgp.ReadStringBytes(bts)
@@ -491,6 +491,6 @@ func (z *netPrioResponseSigned) MsgIsZero() bool {
// MaxSize returns a maximum valid message size for this message type
func NetPrioResponseSignedMaxSize() (s int) {
- s = 1 + 9 + 1 + 6 + msgp.StringPrefixSize + netPrioChallengeSize + 6 + basics.RoundMaxSize() + 7 + basics.AddressMaxSize() + 4 + crypto.OneTimeSignatureMaxSize()
+ s = 1 + 9 + 1 + 6 + msgp.StringPrefixSize + netPrioChallengeSizeBase64Encoded + 6 + basics.RoundMaxSize() + 7 + basics.AddressMaxSize() + 4 + crypto.OneTimeSignatureMaxSize()
return
}
diff --git a/node/netprio.go b/node/netprio.go
index 8b1ab62f1..79a669056 100644
--- a/node/netprio.go
+++ b/node/netprio.go
@@ -28,10 +28,12 @@ import (
const netPrioChallengeSize = 32
+const netPrioChallengeSizeBase64Encoded = 44 // 32 * (4/3) rounded up to nearest multiple of 4 -> 44
+
type netPrioResponse struct {
_struct struct{} `codec:",omitempty,omitemptyarray"`
- Nonce string `codec:"Nonce,allocbound=netPrioChallengeSize"`
+ Nonce string `codec:"Nonce,allocbound=netPrioChallengeSizeBase64Encoded"`
}
type netPrioResponseSigned struct {
diff --git a/node/netprio_test.go b/node/netprio_test.go
new file mode 100644
index 000000000..489099e5c
--- /dev/null
+++ b/node/netprio_test.go
@@ -0,0 +1,47 @@
+// Copyright (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 <https://www.gnu.org/licenses/>.
+
+package node
+
+import (
+ "encoding/base64"
+ "testing"
+
+ "github.com/algorand/go-algorand/protocol"
+ "github.com/algorand/go-algorand/test/partitiontest"
+ "github.com/stretchr/testify/require"
+)
+
+// TestBase64AllocboundSize tests that the base64 encoded size of the Nonce is the same as the allocbound
+// used on the Nonce field in the struct for netprio response messages.
+func TestBase64AllocboundSize(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+
+ require.Equal(t, netPrioChallengeSizeBase64Encoded, base64.StdEncoding.EncodedLen(netPrioChallengeSize))
+ node := AlgorandFullNode{}
+ nonce := node.NewPrioChallenge()
+ require.Len(t, nonce, netPrioChallengeSizeBase64Encoded)
+
+ npr := netPrioResponse{Nonce: nonce}
+ e := protocol.Encode(&npr)
+
+ npr2 := netPrioResponse{}
+ err := protocol.Decode(e, &npr2)
+ require.NoError(t, err)
+ require.Equal(t, nonce, npr2.Nonce)
+
+}
diff --git a/node/node.go b/node/node.go
index 6c74d79ac..1203e3a17 100644
--- a/node/node.go
+++ b/node/node.go
@@ -491,8 +491,19 @@ func (node *AlgorandFullNode) BroadcastInternalSignedTxGroup(txgroup []transacti
return node.broadcastSignedTxGroup(txgroup)
}
+var broadcastTxSucceeded = metrics.MakeCounter(metrics.BroadcastSignedTxGroupSucceeded)
+var broadcastTxFailed = metrics.MakeCounter(metrics.BroadcastSignedTxGroupFailed)
+
// broadcastSignedTxGroup broadcasts a transaction group that has already been signed.
func (node *AlgorandFullNode) broadcastSignedTxGroup(txgroup []transactions.SignedTxn) (err error) {
+ defer func() {
+ if err != nil {
+ broadcastTxFailed.Inc(nil)
+ } else {
+ broadcastTxSucceeded.Inc(nil)
+ }
+ }()
+
lastRound := node.ledger.Latest()
var b bookkeeping.BlockHeader
b, err = node.ledger.BlockHdr(lastRound)
diff --git a/protocol/tags.go b/protocol/tags.go
index 249da317e..a9eb2a6bb 100644
--- a/protocol/tags.go
+++ b/protocol/tags.go
@@ -60,7 +60,7 @@ const MsgOfInterestTagMaxSize = 45
const MsgDigestSkipTagMaxSize = 69
// NetPrioResponseTagMaxSize is the maximum size of a NetPrioResponseTag message
-const NetPrioResponseTagMaxSize = 838
+const NetPrioResponseTagMaxSize = 850
// NetIDVerificationTagMaxSize is the maximum size of a NetIDVerificationTag message
const NetIDVerificationTagMaxSize = 215
diff --git a/rpcs/ledgerService.go b/rpcs/ledgerService.go
index ec7220a28..8abf87e3b 100644
--- a/rpcs/ledgerService.go
+++ b/rpcs/ledgerService.go
@@ -117,7 +117,7 @@ func (ls *LedgerService) ServeHTTP(response http.ResponseWriter, request *http.R
genesisID, hasGenesisID := pathVars["genesisID"]
if hasVersionStr {
if versionStr != "1" {
- logging.Base().Debugf("http ledger bad version '%s'", versionStr)
+ logging.Base().Debugf("LedgerService.ServeHTTP: bad version '%s'", versionStr)
response.WriteHeader(http.StatusBadRequest)
response.Write([]byte(fmt.Sprintf("unsupported version '%s'", versionStr)))
return
@@ -125,13 +125,13 @@ func (ls *LedgerService) ServeHTTP(response http.ResponseWriter, request *http.R
}
if hasGenesisID {
if ls.genesisID != genesisID {
- logging.Base().Debugf("http ledger bad genesisID mine=%#v theirs=%#v", ls.genesisID, genesisID)
+ logging.Base().Debugf("LedgerService.ServeHTTP: bad genesisID mine=%#v theirs=%#v", ls.genesisID, genesisID)
response.WriteHeader(http.StatusBadRequest)
response.Write([]byte(fmt.Sprintf("mismatching genesisID '%s'", genesisID)))
return
}
} else {
- logging.Base().Debug("http ledger no genesisID")
+ logging.Base().Debug("LedgerService.ServeHTTP: no genesisID")
response.WriteHeader(http.StatusBadRequest)
response.Write([]byte("missing genesisID"))
return
@@ -141,14 +141,14 @@ func (ls *LedgerService) ServeHTTP(response http.ResponseWriter, request *http.R
request.Body = http.MaxBytesReader(response, request.Body, ledgerServerMaxBodyLength)
err := request.ParseForm()
if err != nil {
- logging.Base().Debugf("http ledger parse form err : %v", err)
+ logging.Base().Debugf("LedgerService.ServeHTTP: parse form err : %v", err)
response.WriteHeader(http.StatusBadRequest)
response.Write([]byte(fmt.Sprintf("unable to parse form body : %v", err)))
return
}
roundStrs, ok := request.Form["r"]
if !ok || len(roundStrs) != 1 {
- logging.Base().Debugf("http ledger bad round number form arg '%s'", roundStrs)
+ logging.Base().Debugf("LedgerService.ServeHTTP: bad round number form arg '%s'", roundStrs)
response.WriteHeader(http.StatusBadRequest)
response.Write([]byte("invalid round number specified in 'r' form argument"))
return
@@ -158,13 +158,13 @@ func (ls *LedgerService) ServeHTTP(response http.ResponseWriter, request *http.R
if ok {
if len(versionStrs) == 1 {
if versionStrs[0] != "1" {
- logging.Base().Debugf("http ledger bad version '%s'", versionStr)
+ logging.Base().Debugf("LedgerService.ServeHTTP: bad version '%s'", versionStr)
response.WriteHeader(http.StatusBadRequest)
response.Write([]byte(fmt.Sprintf("unsupported version specified '%s'", versionStrs[0])))
return
}
} else {
- logging.Base().Debugf("http ledger wrong number of v=%d args", len(versionStrs))
+ logging.Base().Debugf("LedgerService.ServeHTTP: wrong number of v=%d args", len(versionStrs))
response.WriteHeader(http.StatusBadRequest)
response.Write([]byte(fmt.Sprintf("invalid number of version specified %d", len(versionStrs))))
return
@@ -173,11 +173,13 @@ func (ls *LedgerService) ServeHTTP(response http.ResponseWriter, request *http.R
}
round, err := strconv.ParseUint(roundStr, 36, 64)
if err != nil {
- logging.Base().Debugf("http ledger round parse fail ('%s'): %v", roundStr, err)
+ logging.Base().Debugf("LedgerService.ServeHTTP: round parse fail ('%s'): %v", roundStr, err)
response.WriteHeader(http.StatusBadRequest)
response.Write([]byte(fmt.Sprintf("specified round number could not be parsed using base 36 : %v", err)))
return
}
+ logging.Base().Infof("LedgerService.ServeHTTP: serving catchpoint round %d", round)
+ start := time.Now()
cs, err := ls.ledger.GetCatchpointStream(basics.Round(round))
if err != nil {
switch err.(type) {
@@ -188,7 +190,7 @@ func (ls *LedgerService) ServeHTTP(response http.ResponseWriter, request *http.R
return
default:
// unexpected error.
- logging.Base().Warnf("ServeHTTP : failed to retrieve catchpoint %d %v", round, err)
+ logging.Base().Warnf("LedgerService.ServeHTTP : failed to retrieve catchpoint %d %v", round, err)
response.WriteHeader(http.StatusInternalServerError)
response.Write([]byte(fmt.Sprintf("catchpoint file for round %d could not be retrieved due to internal error : %v", round, err)))
return
@@ -221,6 +223,8 @@ func (ls *LedgerService) ServeHTTP(response http.ResponseWriter, request *http.R
if err != nil {
logging.Base().Infof("LedgerService.ServeHTTP : unable to write compressed catchpoint file for round %d, written bytes %d : %v", round, written, err)
}
+ elapsed := time.Since(start)
+ logging.Base().Infof("LedgerService.ServeHTTP: served catchpoint round %d in %d sec", round, int(elapsed.Seconds()))
return
}
decompressedGzip, err := gzip.NewReader(cs)
@@ -234,5 +238,8 @@ func (ls *LedgerService) ServeHTTP(response http.ResponseWriter, request *http.R
written, err := io.Copy(response, decompressedGzip)
if err != nil {
logging.Base().Infof("LedgerService.ServeHTTP : unable to write decompressed catchpoint file for round %d, written bytes %d : %v", round, written, err)
+ } else {
+ elapsed := time.Since(start)
+ logging.Base().Infof("LedgerService.ServeHTTP: served catchpoint round %d in %d sec", round, int(elapsed.Seconds()))
}
}
diff --git a/scripts/check_deps.sh b/scripts/check_deps.sh
index 95c7599f7..a296c11b9 100755
--- a/scripts/check_deps.sh
+++ b/scripts/check_deps.sh
@@ -73,12 +73,6 @@ check_deps() {
then
missing_dep shellcheck
fi
-
- # Don't print `sqlite3`s location.
- if ! which sqlite3 > /dev/null
- then
- missing_dep sqlite3
- fi
}
check_deps
diff --git a/scripts/configure_dev.sh b/scripts/configure_dev.sh
index 474e869fb..d40b55147 100755
--- a/scripts/configure_dev.sh
+++ b/scripts/configure_dev.sh
@@ -79,15 +79,13 @@ elif [ "${OS}" = "darwin" ]; then
install_or_upgrade pkg-config
install_or_upgrade libtool
install_or_upgrade shellcheck
- if [ "${CIRCLECI}" != "true" ]; then
- install_or_upgrade jq
- install_or_upgrade autoconf
- install_or_upgrade automake
- install_or_upgrade python3
- install_or_upgrade lnav
- install_or_upgrade diffutils
- lnav -i "$SCRIPTPATH/algorand_node_log.json"
- fi
+ install_or_upgrade jq
+ install_or_upgrade autoconf
+ install_or_upgrade automake
+ install_or_upgrade python3
+ install_or_upgrade lnav
+ install_or_upgrade diffutils
+ lnav -i "$SCRIPTPATH/algorand_node_log.json"
elif [ "${OS}" = "windows" ]; then
if ! $msys2 pacman -S --disable-download-timeout --noconfirm git automake autoconf m4 libtool make mingw-w64-x86_64-gcc mingw-w64-x86_64-python mingw-w64-x86_64-jq unzip procps; then
echo "Error installing pacman dependencies"
diff --git a/scripts/release/README.md b/scripts/release/README.md
index 13d46283d..f3fa543f1 100644
--- a/scripts/release/README.md
+++ b/scripts/release/README.md
@@ -52,7 +52,7 @@ This section briefly describes the expected outcomes of the current build pipeli
1. Build (compile) the binaries in a Centos 7 & 8 docker container that will then be used by both `deb` and `rpm` packaging.
- 1. Docker containers will package `deb` and `rpm` artifacts inside of Ubuntu 22.04 and Centos 7 & 8, respectively.
+ 1. Docker containers will package `deb` and `rpm` artifacts inside of Ubuntu 20.04 and Centos 7 & 8, respectively.
1. Jenkins will then pause to wait for [the only manual part of the build/package/test phase], which is to forward the `gpg-agent` that establishes a direct between the local machine that contains the signing keys and the remote ec2 instance.
diff --git a/scripts/release/common/docker/centos.Dockerfile b/scripts/release/common/docker/centos.Dockerfile
index e1bb0188f..a23b446ca 100644
--- a/scripts/release/common/docker/centos.Dockerfile
+++ b/scripts/release/common/docker/centos.Dockerfile
@@ -2,7 +2,7 @@ FROM centos:7
WORKDIR /root
RUN yum install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
-RUN yum install -y autoconf awscli curl git gnupg2 nfs-utils python36 sqlite3 expect jq libtool gcc-c++ libstdc++-devel libstdc++-static rpmdevtools createrepo rpm-sign bzip2 which ShellCheck
+RUN yum install -y autoconf awscli curl git gnupg2 nfs-utils python36 expect jq libtool gcc-c++ libstdc++-devel libstdc++-static rpmdevtools createrepo rpm-sign bzip2 which ShellCheck
ENTRYPOINT ["/bin/bash"]
diff --git a/scripts/release/common/docker/centos8.Dockerfile b/scripts/release/common/docker/centos8.Dockerfile
index 0917aedc5..cf5474cfe 100644
--- a/scripts/release/common/docker/centos8.Dockerfile
+++ b/scripts/release/common/docker/centos8.Dockerfile
@@ -2,7 +2,7 @@ FROM quay.io/centos/centos:stream8
WORKDIR /root
RUN dnf install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm && \
- dnf install -y autoconf awscli curl git gnupg2 nfs-utils python36 sqlite expect jq libtool gcc-c++ libstdc++-devel rpmdevtools createrepo rpm-sign bzip2 which && \
+ dnf install -y autoconf awscli curl git gnupg2 nfs-utils python36 expect jq libtool gcc-c++ libstdc++-devel rpmdevtools createrepo rpm-sign bzip2 which && \
dnf -y --enablerepo=powertools install libstdc++-static
RUN echo "${BOLD}Downloading and installing binaries...${RESET}" && \
diff --git a/scripts/release/common/docker/setup.Dockerfile b/scripts/release/common/docker/setup.Dockerfile
index 514b9be46..7d2988ca9 100644
--- a/scripts/release/common/docker/setup.Dockerfile
+++ b/scripts/release/common/docker/setup.Dockerfile
@@ -9,7 +9,7 @@
# https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=869194
# https://github.com/boto/s3transfer/pull/102
-FROM ubuntu:22.04
+FROM ubuntu:20.04
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y jq git python python-pip python3-boto3 ssh && \
pip install awscli
diff --git a/scripts/release/common/setup.sh b/scripts/release/common/setup.sh
index c4430e9ae..75683262d 100755
--- a/scripts/release/common/setup.sh
+++ b/scripts/release/common/setup.sh
@@ -25,7 +25,7 @@ sudo apt-get upgrade -y
# `apt-get` fails randomly when downloading package, this is a hack that "works" reasonably well.
sudo apt-get update
-sudo apt-get install -y build-essential automake autoconf awscli docker.io git gpg nfs-common python python3 rpm sqlite3 python3-boto3 g++ libtool rng-tools
+sudo apt-get install -y build-essential automake autoconf awscli docker.io git gpg nfs-common python python3 rpm python3-boto3 g++ libtool rng-tools
sudo rngd -r /dev/urandom
#umask 0077
diff --git a/stateproof/abstractions.go b/stateproof/abstractions.go
index 552acd0b8..67f473188 100644
--- a/stateproof/abstractions.go
+++ b/stateproof/abstractions.go
@@ -18,6 +18,7 @@ package stateproof
import (
"context"
+
"github.com/algorand/go-algorand/crypto"
"github.com/algorand/go-algorand/data/account"
"github.com/algorand/go-algorand/data/basics"
diff --git a/test/e2e-go/features/transactions/asset_test.go b/test/e2e-go/features/transactions/asset_test.go
index 7715f01f4..2b38e7d51 100644
--- a/test/e2e-go/features/transactions/asset_test.go
+++ b/test/e2e-go/features/transactions/asset_test.go
@@ -80,7 +80,7 @@ func TestAssetValidRounds(t *testing.T) {
validRounds = cparams.MaxTxnLife + 1
firstValid, lastValid, lastRound, err = client.ComputeValidityRounds(firstValid, lastValid, validRounds)
a.NoError(err)
- a.Equal(lastRound+1, firstValid)
+ a.True(firstValid == 1 || firstValid == lastRound)
a.Equal(firstValid+cparams.MaxTxnLife, lastValid)
firstValid = 0
@@ -163,7 +163,7 @@ func TestAssetValidRounds(t *testing.T) {
// ledger may advance between SuggestedParams and FillUnsignedTxTemplate calls
// expect validity sequence
var firstValidRange, lastValidRange []uint64
- for i := lastRoundBefore + 1; i <= lastRoundAfter+1; i++ {
+ for i := lastRoundBefore; i <= lastRoundAfter+1; i++ {
firstValidRange = append(firstValidRange, i)
lastValidRange = append(lastValidRange, i+cparams.MaxTxnLife)
}
diff --git a/test/e2e-go/features/transactions/lease_test.go b/test/e2e-go/features/transactions/lease_test.go
index 7dbb06190..093ec2273 100644
--- a/test/e2e-go/features/transactions/lease_test.go
+++ b/test/e2e-go/features/transactions/lease_test.go
@@ -136,11 +136,11 @@ func TestLeaseRegressionFaultyFirstValidCheckOld_2f3880f7(t *testing.T) {
a.True(confirmed, "lease txn confirmed")
bal1, _ := fixture.GetBalanceAndRound(account1)
- bal2, _ := fixture.GetBalanceAndRound(account2)
+ bal2, curRound := fixture.GetBalanceAndRound(account2)
a.Equal(bal1, uint64(1000000))
a.Equal(bal2, uint64(0))
- tx2, err := client.ConstructPayment(account0, account2, 0, 2000000, nil, "", lease, 0, 0)
+ tx2, err := client.ConstructPayment(account0, account2, 0, 2000000, nil, "", lease, basics.Round(curRound)+1, 0)
a.NoError(err)
stx2, err := client.SignTransactionWithWallet(wh, nil, tx2)
diff --git a/test/e2e-go/restAPI/restClient_test.go b/test/e2e-go/restAPI/restClient_test.go
index 049d98af8..5d14f64d2 100644
--- a/test/e2e-go/restAPI/restClient_test.go
+++ b/test/e2e-go/restAPI/restClient_test.go
@@ -3031,3 +3031,174 @@ func TestMaxDepthAppWithPCandStackTrace(t *testing.T) {
a.Equal(execTraceConfig, resp.ExecTraceConfig)
}
+
+func TestSimulateScratchSlotChange(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+
+ a := require.New(fixtures.SynchronizedTest(t))
+ var localFixture fixtures.RestClientFixture
+ localFixture.SetupNoStart(t, filepath.Join("nettemplates", "OneNodeFuture.json"))
+
+ // Get primary node
+ primaryNode, err := fixture.GetNodeController("Primary")
+ a.NoError(err)
+
+ fixture.Start()
+ defer primaryNode.FullStop()
+
+ // get lib goal client
+ testClient := fixture.LibGoalFixture.GetLibGoalClientFromNodeController(primaryNode)
+
+ _, err = testClient.WaitForRound(1)
+ a.NoError(err)
+
+ wh, err := testClient.GetUnencryptedWalletHandle()
+ a.NoError(err)
+ addresses, err := testClient.ListAddresses(wh)
+ a.NoError(err)
+ _, senderAddress := getMaxBalAddr(t, testClient, addresses)
+ a.NotEmpty(senderAddress, "no addr with funds")
+ a.NoError(err)
+
+ ops, err := logic.AssembleString(
+ `#pragma version 8
+ global CurrentApplicationID
+ bz end
+ int 1
+ store 1
+ load 1
+ dup
+ stores
+ end:
+ int 1`)
+ a.NoError(err)
+ approval := ops.Program
+ ops, err = logic.AssembleString("#pragma version 8\nint 1")
+ a.NoError(err)
+ clearState := ops.Program
+
+ gl := basics.StateSchema{}
+ lc := basics.StateSchema{}
+
+ MinFee := config.Consensus[protocol.ConsensusFuture].MinTxnFee
+ MinBalance := config.Consensus[protocol.ConsensusFuture].MinBalance
+
+ // create app and get the application ID
+ appCreateTxn, err := testClient.MakeUnsignedAppCreateTx(
+ transactions.NoOpOC, approval, clearState, gl,
+ lc, nil, nil, nil, nil, nil, 0)
+ a.NoError(err)
+ appCreateTxn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, 0, appCreateTxn)
+ a.NoError(err)
+
+ appCreateTxID, err := testClient.SignAndBroadcastTransaction(wh, nil, appCreateTxn)
+ a.NoError(err)
+ submittedAppCreateTxn, err := waitForTransaction(t, testClient, senderAddress, appCreateTxID, 30*time.Second)
+ a.NoError(err)
+ futureAppID := basics.AppIndex(*submittedAppCreateTxn.ApplicationIndex)
+
+ // fund app account
+ appFundTxn, err := testClient.SendPaymentFromWallet(
+ wh, nil, senderAddress, futureAppID.Address().String(),
+ 0, MinBalance, nil, "", 0, 0,
+ )
+ a.NoError(err)
+
+ // construct app calls
+ appCallTxn, err := testClient.MakeUnsignedAppNoOpTx(
+ uint64(futureAppID), [][]byte{}, nil, nil, nil, nil,
+ )
+ a.NoError(err)
+ appCallTxn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, MinFee, appCallTxn)
+ a.NoError(err)
+
+ // Group the transactions
+ gid, err := testClient.GroupID([]transactions.Transaction{appFundTxn, appCallTxn})
+ a.NoError(err)
+ appFundTxn.Group = gid
+ appCallTxn.Group = gid
+
+ appFundTxnSigned, err := testClient.SignTransactionWithWallet(wh, nil, appFundTxn)
+ a.NoError(err)
+ appCallTxnSigned, err := testClient.SignTransactionWithWallet(wh, nil, appCallTxn)
+ a.NoError(err)
+
+ // construct simulation request, with scratch slot change enabled
+ execTraceConfig := simulation.ExecTraceConfig{
+ Enable: true,
+ Scratch: true,
+ }
+ simulateRequest := v2.PreEncodedSimulateRequest{
+ TxnGroups: []v2.PreEncodedSimulateRequestTransactionGroup{
+ {Txns: []transactions.SignedTxn{appFundTxnSigned, appCallTxnSigned}},
+ },
+ ExecTraceConfig: execTraceConfig,
+ }
+
+ // update the configuration file to enable EnableDeveloperAPI
+ err = primaryNode.FullStop()
+ a.NoError(err)
+ cfg, err := config.LoadConfigFromDisk(primaryNode.GetDataDir())
+ a.NoError(err)
+ cfg.EnableDeveloperAPI = true
+ err = cfg.SaveToDisk(primaryNode.GetDataDir())
+ require.NoError(t, err)
+ fixture.Start()
+
+ // simulate with wrong config (not enabled trace), see expected error
+ _, err = testClient.SimulateTransactions(v2.PreEncodedSimulateRequest{
+ TxnGroups: []v2.PreEncodedSimulateRequestTransactionGroup{
+ {Txns: []transactions.SignedTxn{appFundTxnSigned, appCallTxnSigned}},
+ },
+ ExecTraceConfig: simulation.ExecTraceConfig{Scratch: true},
+ })
+ a.ErrorContains(err, "basic trace must be enabled when enabling scratch slot change tracing")
+
+ // start real simulating
+ resp, err := testClient.SimulateTransactions(simulateRequest)
+ a.NoError(err)
+
+ // check if resp match expected result
+ a.Equal(execTraceConfig, resp.ExecTraceConfig)
+ a.Len(resp.TxnGroups[0].Txns, 2)
+ a.Nil(resp.TxnGroups[0].Txns[0].TransactionTrace)
+ a.NotNil(resp.TxnGroups[0].Txns[1].TransactionTrace)
+
+ expectedTraceSecondTxn := &model.SimulationTransactionExecTrace{
+ ApprovalProgramTrace: &[]model.SimulationOpcodeTraceUnit{
+ {Pc: 1},
+ {Pc: 4},
+ {Pc: 6},
+ {Pc: 9},
+ {
+ Pc: 10,
+ ScratchChanges: &[]model.ScratchChange{
+ {
+ Slot: 1,
+ NewValue: model.AvmValue{
+ Type: 2,
+ Uint: toPtr[uint64](1),
+ },
+ },
+ },
+ },
+ {Pc: 12},
+ {Pc: 14},
+ {
+ Pc: 15,
+ ScratchChanges: &[]model.ScratchChange{
+ {
+ Slot: 1,
+ NewValue: model.AvmValue{
+ Type: 2,
+ Uint: toPtr[uint64](1),
+ },
+ },
+ },
+ },
+ {Pc: 16},
+ },
+ }
+ a.Equal(expectedTraceSecondTxn, resp.TxnGroups[0].Txns[1].TransactionTrace)
+}
diff --git a/test/heapwatch/heapWatch.py b/test/heapwatch/heapWatch.py
index 22a5a0e3a..68af9cd3b 100644
--- a/test/heapwatch/heapWatch.py
+++ b/test/heapwatch/heapWatch.py
@@ -165,6 +165,12 @@ class algodDir:
def get_goroutine_snapshot(self, snapshot_name=None, outdir=None):
return self.get_pprof_snapshot('goroutine', snapshot_name, outdir)
+ def get_mutex_snapshot(self, snapshot_name=None, outdir=None):
+ return self.get_pprof_snapshot('mutex', snapshot_name, outdir)
+
+ def get_block_snapshot(self, snapshot_name=None, outdir=None):
+ return self.get_pprof_snapshot('block', snapshot_name, outdir)
+
def get_cpu_profile(self, snapshot_name=None, outdir=None, seconds=90):
seconds = int(seconds)
return self.get_pprof_snapshot('profile?seconds={}'.format(seconds), snapshot_name, outdir, timeout=seconds+20)
@@ -352,6 +358,12 @@ class watcher:
if self.args.goroutine:
for ad in self.they:
ad.get_goroutine_snapshot(snapshot_name, outdir=self.args.out)
+ if self.args.mutex:
+ for ad in self.they:
+ ad.get_mutex_snapshot(snapshot_name, outdir=self.args.out)
+ if self.args.block:
+ for ad in self.they:
+ ad.get_block_snapshot(snapshot_name, outdir=self.args.out)
if self.args.metrics:
threads = []
for ad in self.they:
@@ -427,7 +439,9 @@ def main():
ap = argparse.ArgumentParser()
ap.add_argument('data_dirs', nargs='*', help='list paths to algorand datadirs to grab heap profile from')
ap.add_argument('--no-heap', dest='heaps', default=True, action='store_false', help='disable heap snapshot capture')
+ ap.add_argument('--block', default=False, action='store_true', help='also capture goroutines block profile')
ap.add_argument('--goroutine', default=False, action='store_true', help='also capture goroutine profile')
+ ap.add_argument('--mutex', default=False, action='store_true', help='also capture mutex profile')
ap.add_argument('--metrics', default=False, action='store_true', help='also capture /metrics counts')
ap.add_argument('--blockinfo', default=False, action='store_true', help='also capture block header info')
ap.add_argument('--period', default=None, help='seconds between automatically capturing')
@@ -452,6 +466,12 @@ def main():
else:
logging.basicConfig(level=logging.INFO)
+ if args.block:
+ print('Ensure algod is compiled with `runtime.SetBlockProfileRate()` set')
+
+ if args.mutex:
+ print('Ensure algod is compiled with `runtime.SetMutexProfileFraction()` set')
+
for nre in args.tf_name_re:
try:
# do re.compile just to check
diff --git a/test/scripts/e2e_client_runner.py b/test/scripts/e2e_client_runner.py
index 75059bbe4..233e8244a 100755
--- a/test/scripts/e2e_client_runner.py
+++ b/test/scripts/e2e_client_runner.py
@@ -446,6 +446,7 @@ def main():
retcode = 0
capv = args.version.capitalize()
xrun(['goal', 'network', 'create', '-r', netdir, '-n', 'tbd', '-t', os.path.join(repodir, f'test/testdata/nettemplates/TwoNodes50Each{capv}.json')], timeout=90)
+<<<<<<< HEAD
env['ALGORAND_DATA'] = os.path.join(netdir, 'Node')
env['ALGORAND_DATA2'] = os.path.join(netdir, 'Primary')
cfgpath = os.path.join(netdir, 'Node', 'config.json')
@@ -457,6 +458,27 @@ def main():
xrun(['goal', 'network', 'start', '-r', netdir], timeout=90)
atexit.register(goal_network_stop, netdir, env)
+=======
+ nodeDataDir = os.path.join(netdir, 'Node')
+ primaryDataDir = os.path.join(netdir, 'Primary')
+
+ # Set EnableDeveloperAPI to true for both nodes
+ for dataDir in (nodeDataDir, primaryDataDir):
+ configFile = os.path.join(dataDir, 'config.json')
+ with open(configFile, 'r') as f:
+ configOptions = json.load(f)
+
+ configOptions['EnableDeveloperAPI'] = True
+
+ with open(configFile, 'w') as f:
+ json.dump(configOptions, f)
+
+ xrun(['goal', 'network', 'start', '-r', netdir], timeout=90)
+ atexit.register(goal_network_stop, netdir, env)
+
+ env['ALGORAND_DATA'] = nodeDataDir
+ env['ALGORAND_DATA2'] = primaryDataDir
+>>>>>>> master
if args.unsafe_scrypt:
create_kmd_config_with_unsafe_scrypt(env['ALGORAND_DATA'])
diff --git a/test/scripts/e2e_subs/app-group.py b/test/scripts/e2e_subs/app-group.py
index 738e1cb79..7ad92b92c 100755
--- a/test/scripts/e2e_subs/app-group.py
+++ b/test/scripts/e2e_subs/app-group.py
@@ -74,4 +74,5 @@ assert not err, err
txinfo, err = goal.app_call(joe, app_id, accounts=[goal.account])
assert not err, err
+stamp = datetime.now().strftime("%Y%m%d_%H%M%S")
print(f"{os.path.basename(sys.argv[0])} OK {stamp}")
diff --git a/test/scripts/e2e_subs/app-inner-calls-csp.py b/test/scripts/e2e_subs/app-inner-calls-csp.py
index e5e7d32a9..75d8e0f6f 100755
--- a/test/scripts/e2e_subs/app-inner-calls-csp.py
+++ b/test/scripts/e2e_subs/app-inner-calls-csp.py
@@ -154,4 +154,5 @@ assert not err, err
_, err = goal.app_call(joe, app1ID, app_args=[0x01], foreign_apps=[int(app2ID), int(app3ID)])
assert not err, err
+stamp = datetime.now().strftime("%Y%m%d_%H%M%S")
print(f"{os.path.basename(sys.argv[0])} OK {stamp}")
diff --git a/test/scripts/e2e_subs/app-inner-calls.py b/test/scripts/e2e_subs/app-inner-calls.py
index 02010708e..3268ef87d 100755
--- a/test/scripts/e2e_subs/app-inner-calls.py
+++ b/test/scripts/e2e_subs/app-inner-calls.py
@@ -145,4 +145,5 @@ assert not err, err
assert 10 == goal.balance(app_account, asa_id)
+stamp = datetime.now().strftime("%Y%m%d_%H%M%S")
print(f"{os.path.basename(sys.argv[0])} OK {stamp}")
diff --git a/test/scripts/e2e_subs/app-rekey.py b/test/scripts/e2e_subs/app-rekey.py
index 94bfcd22a..efa1accca 100755
--- a/test/scripts/e2e_subs/app-rekey.py
+++ b/test/scripts/e2e_subs/app-rekey.py
@@ -78,4 +78,5 @@ txinfo, err = goal.pay(flo, joe, amt=1)
assert not err, err
assert goal.balance(joe) == joeb+7, goal.balance(joe)
+stamp = datetime.now().strftime("%Y%m%d_%H%M%S")
print(f"{os.path.basename(sys.argv[0])} OK {stamp}")
diff --git a/test/scripts/e2e_subs/e2e-app-simulate.sh b/test/scripts/e2e_subs/e2e-app-simulate.sh
index 7efc7ae3c..8dbcc849c 100755
--- a/test/scripts/e2e_subs/e2e-app-simulate.sh
+++ b/test/scripts/e2e_subs/e2e-app-simulate.sh
@@ -384,3 +384,50 @@ if [[ $(echo "$RES" | jq '."txn-groups"[0]."app-budget-consumed"') -ne 804 ]]; t
date '+app-simulate-test FAIL the app call to generated large TEAL should be consuming 804 budget %Y%m%d_%H%M%S'
false
fi
+
+###############################################################
+# WE WANT TO TEST STACK AND SCRATCH TRACE IN SIMULATION WORKS #
+###############################################################
+
+RES=$(${gcmd} app create --creator ${ACCOUNT} --approval-prog "${DIR}/tealprogs/stack-scratch.teal" --clear-prog "${TEMPDIR}/simple-v8.teal" --extra-pages 1 2>&1 || true)
+
+EXPSUCCESS='Created app with app index'
+if [[ $RES != *"${EXPSUCCESS}"* ]]; then
+ date '+app-simulate-test FAIL the app creation for generated large TEAL should succeed %Y%m%d_%H%M%S'
+ false
+fi
+
+APPID=$(echo "$RES" | grep Created | awk '{ print $6 }')
+
+${gcmd} app call --app-id $APPID --app-arg "int:10" --from $ACCOUNT 2>&1 -o "${TEMPDIR}/stack-and-scratch.tx"
+${gcmd} clerk sign -i "${TEMPDIR}/stack-and-scratch.tx" -o "${TEMPDIR}/stack-and-scratch.stx"
+RES=$(${gcmd} clerk simulate --full-trace -t "${TEMPDIR}/stack-and-scratch.stx")
+
+if [[ $(echo "$RES" | jq '."txn-groups" | any(has("failure-message"))') != $CONST_FALSE ]]; then
+ date '+app-simulate-test FAIL the app call for stack and scratch trace should pass %Y%m%d_%H%M%S'
+ false
+fi
+
+SCRATCH_STORE_UNIT=$(echo "$RES" | jq '."txn-groups"[0]."txn-results"[0]."exec-trace"."approval-program-trace"[-7]')
+
+if [[ $(echo "$SCRATCH_STORE_UNIT" | jq 'has("scratch-changes")') != $CONST_TRUE ]]; then
+ data '+app-simulate-test FAIL the app call for stack and scratch trace should return scratch changes at this unit %Y%m%d_%H%M%S'
+ false
+fi
+
+if [[ $(echo "$SCRATCH_STORE_UNIT" | jq '."scratch-changes" | length') != 1 ]]; then
+ data '+app-simulate-test FAIL the app call for stack and scratch trace should return scratch changes with length 1 at this unit %Y%m%d_%H%M%S'
+ false
+fi
+
+if [[ $(echo "$SCRATCH_STORE_UNIT" | jq 'has("stack-pop-count")') != $CONST_TRUE ]]; then
+ data '+app-simulate-test FAIL the app call for stack and scratch trace should return stack pop count at this unit %Y%m%d_%H%M%S'
+ false
+fi
+
+if [[ $(echo "$SCRATCH_STORE_UNIT" | jq '."stack-pop-count"') != 1 ]]; then
+ data '+app-simulate-test FAIL the app call for stack and scratch trace should return stack pop count being 1 at this unit %Y%m%d_%H%M%S'
+ false
+fi
+
+# WE DON'T TEST IN DETAILS ABOUT SCRATCH AND TRACE IN E2E SCRIPT TESTS, SEE RESTCLIENT TEST FOR DETAILS
diff --git a/test/scripts/e2e_subs/e2e-teal.sh b/test/scripts/e2e_subs/e2e-teal.sh
index 06caa5efb..48da72951 100755
--- a/test/scripts/e2e_subs/e2e-teal.sh
+++ b/test/scripts/e2e_subs/e2e-teal.sh
@@ -71,7 +71,8 @@ while [ $CROUND -lt $TIMEOUT_ROUND ]; do
CROUND=$(goal node status | grep 'Last committed block:'|awk '{ print $4 }')
done
-${gcmd} clerk send --from-program ${TEMPDIR}/tlhc.teal --to ${ACCOUNT} --close-to ${ACCOUNT} --amount 1 --argb64 AA==
+# send txn that valid right after the TIMEOUT_ROUND
+${gcmd} clerk send --firstvalid $((${TIMEOUT_ROUND} + 1)) --from-program ${TEMPDIR}/tlhc.teal --to ${ACCOUNT} --close-to ${ACCOUNT} --amount 1 --argb64 AA==
cat >${TEMPDIR}/true.teal<<EOF
#pragma version 2
diff --git a/test/scripts/e2e_subs/example.py b/test/scripts/e2e_subs/example.py
index cc7acda2c..4852ac9d7 100755
--- a/test/scripts/e2e_subs/example.py
+++ b/test/scripts/e2e_subs/example.py
@@ -19,7 +19,7 @@ flo = goal.new_account()
pay = goal.pay(goal.account, receiver=joe, amt=10000) # under min balance
txid, err = goal.send(pay, confirm=False) # errors early
-assert err
+assert "balance 10000 below min 100000" in str(err), err
pay = goal.pay(goal.account, receiver=joe, amt=500_000)
txinfo, err = goal.send(pay)
diff --git a/test/scripts/e2e_subs/goal/goal.py b/test/scripts/e2e_subs/goal/goal.py
index 969fc2053..d36241fd7 100755
--- a/test/scripts/e2e_subs/goal/goal.py
+++ b/test/scripts/e2e_subs/goal/goal.py
@@ -144,10 +144,10 @@ class Goal:
self.open_wallet(self.wallet_name)
return self.kmd.sign_transaction(self.handle, "", tx)
- def sign_with_program(self, tx, program, delegator=None):
+ def sign_with_program(self, tx, program, args=None, delegator=None):
if delegator:
raise Exception("haven't implemented delgated logicsig yet")
- return txn.LogicSigTransaction(tx, txn.LogicSig(program))
+ return txn.LogicSigTransaction(tx, txn.LogicSig(program, args))
def send(self, tx, confirm=True):
try:
diff --git a/test/scripts/e2e_subs/hdr-access.py b/test/scripts/e2e_subs/hdr-access.py
index 4da7856a3..bb6c0ad65 100755
--- a/test/scripts/e2e_subs/hdr-access.py
+++ b/test/scripts/e2e_subs/hdr-access.py
@@ -91,4 +91,5 @@ txinfo, err = goal.send(tx)
assert "round 0 is not available" in str(err), err
assert "outside [1-" in str(err), err # confirms that we can look back to 1
+stamp = datetime.now().strftime("%Y%m%d_%H%M%S")
print(f"{os.path.basename(sys.argv[0])} OK {stamp}")
diff --git a/test/scripts/e2e_subs/limit-swap-test.sh b/test/scripts/e2e_subs/limit-swap-test.sh
index 937fb013a..d89331300 100755
--- a/test/scripts/e2e_subs/limit-swap-test.sh
+++ b/test/scripts/e2e_subs/limit-swap-test.sh
@@ -73,7 +73,7 @@ while [ $ROUND -lt $TIMEOUT_ROUND ]; do
done
echo "recover asset"
-${gcmd} asset send --assetid ${ASSET_ID} -t ${ZERO_ADDRESS} -a 0 -c ${ACCOUNT} -f ${ACCOUNT_ASSET_TRADER} -o ${TEMPDIR}/bclose.tx
+${gcmd} asset send --firstvalid $((${TIMEOUT_ROUND} + 1)) --assetid ${ASSET_ID} -t ${ZERO_ADDRESS} -a 0 -c ${ACCOUNT} -f ${ACCOUNT_ASSET_TRADER} -o ${TEMPDIR}/bclose.tx
${gcmd} clerk sign -i ${TEMPDIR}/bclose.tx -p ${TEMPDIR}/limit-order-b.teal -o ${TEMPDIR}/bclose.stx
diff --git a/test/scripts/e2e_subs/lsig-budget.py b/test/scripts/e2e_subs/lsig-budget.py
new file mode 100755
index 000000000..8cab2644a
--- /dev/null
+++ b/test/scripts/e2e_subs/lsig-budget.py
@@ -0,0 +1,87 @@
+#!/usr/bin/env python
+
+import os
+import sys
+from goal import Goal
+import algosdk.logic as logic
+
+from datetime import datetime
+
+stamp = datetime.now().strftime("%Y%m%d_%H%M%S")
+print(f"{os.path.basename(sys.argv[0])} start {stamp}")
+
+goal = Goal(sys.argv[1], autosend=True)
+
+# This lsig runs keccak256 (cost=130) the number of times in its first
+# logicsig arg, then accepts. In effect, it accepts if it has enough
+# budget to run the number of hashes requested. The program consumes 6
+# opcodes in the base case (args[0] == 0), and 137 for each loop.
+run_hashes = """
+#pragma version 6
+ arg 0
+ btoi
+loop:
+ dup
+ bz end
+ byte 0x0102030405060708
+ keccak256
+ pop
+ int 1
+ -
+ b loop
+end:
+ pop // the 0 loop variable
+ int 1
+"""
+
+code = goal.assemble(run_hashes)
+escrow = goal.logic_address(code)
+
+# Fund the lsig's escrow account
+_, err = goal.pay(goal.account, escrow, amt=1_000_000)
+assert not err, err
+
+# Construct a transaction that uses the lsig. Can't send, because we
+# have to fill in the lsig (and args)
+tx = goal.pay(escrow, escrow, amt=0, note=b'5', send=False)
+# 5 loops is fine (20k budget)
+stx = goal.sign_with_program(tx, code, [(5).to_bytes(8, "big")])
+txinfo, err = goal.send(stx)
+assert not err, err
+
+# 145 loops is fine 6+145*137 < 20k budget
+tx = goal.pay(escrow, escrow, amt=0, note=b'145', send=False)
+stx = goal.sign_with_program(tx, code, [(145).to_bytes(8, "big")])
+txinfo, err = goal.send(stx)
+assert not err, err
+
+# 146 is not 6+146*137 = 20,008 (20k budget)
+tx = goal.pay(escrow, escrow, amt=0, note=b'146', send=False)
+stx = goal.sign_with_program(tx, code, [(146).to_bytes(8, "big")])
+txinfo, err = goal.send(stx)
+assert "dynamic cost budget exceeded, executing keccak256" in str(err)
+
+# Now, try pooling across multiple logicsigs 39988/137 = 291.xxx
+tx0 = goal.pay(escrow, escrow, amt=0, note=b'200', send=False)
+tx1 = goal.pay(escrow, escrow, amt=0, note=b'91', send=False)
+stx0 = goal.sign_with_program(tx0, code, [(200).to_bytes(8, "big")])
+stx1 = goal.sign_with_program(tx1, code, [(91).to_bytes(8, "big")])
+txinfo, err = goal.send_group([stx0, stx1])
+assert not err, err
+
+# order doesn't matter
+tx0.group = None
+tx1.group = None
+txinfo, err = goal.send_group([stx1, stx0]) # rearrange
+assert not err, err
+
+# 292 is too much
+tx0 = goal.pay(escrow, escrow, amt=0, note=b'200', send=False)
+tx1 = goal.pay(escrow, escrow, amt=0, note=b'92', send=False)
+stx0 = goal.sign_with_program(tx0, code, [(200).to_bytes(8, "big")])
+stx1 = goal.sign_with_program(tx1, code, [(92).to_bytes(8, "big")])
+txinfo, err = goal.send_group([stx0, stx1])
+assert "dynamic cost budget exceeded, executing keccak256" in str(err)
+
+stamp = datetime.now().strftime("%Y%m%d_%H%M%S")
+print(f"{os.path.basename(sys.argv[0])} OK {stamp}")
diff --git a/test/scripts/e2e_subs/shared-resources.py b/test/scripts/e2e_subs/shared-resources.py
index d4c1c9594..b5a93f96c 100755
--- a/test/scripts/e2e_subs/shared-resources.py
+++ b/test/scripts/e2e_subs/shared-resources.py
@@ -94,4 +94,5 @@ assert len(grp2_info["local-state-delta"]) == 1
assert grp2_info["local-state-delta"][0]["address"] == goal.account
assert grp2_info["local-state-delta"][0]["delta"][0]["value"]["uint"] == 70
+stamp = datetime.now().strftime("%Y%m%d_%H%M%S")
print(f"{os.path.basename(sys.argv[0])} OK {stamp}")
diff --git a/test/scripts/e2e_subs/tealprogs/stack-scratch.teal b/test/scripts/e2e_subs/tealprogs/stack-scratch.teal
new file mode 100644
index 000000000..d7d123f24
--- /dev/null
+++ b/test/scripts/e2e_subs/tealprogs/stack-scratch.teal
@@ -0,0 +1,45 @@
+#pragma version 8
+txn ApplicationID // on creation, always approve
+bz end
+
+txn NumAppArgs
+int 1
+==
+assert
+
+txn ApplicationArgs 0
+btoi
+callsub subroutine_manipulating_stack
+itob
+log
+b end
+
+subroutine_manipulating_stack:
+ proto 1 1
+ int 0 // [0]
+ dup // [0, 0]
+ dupn 4 // [0, 0, 0, 0, 0, 0]
+ frame_dig -1 // [0, 0, 0, 0, 0, 0, arg_0]
+ frame_bury 0 // [arg_0, 0, 0, 0, 0, 0]
+ dig 5 // [arg_0, 0, 0, 0, 0, 0, arg_0]
+ cover 5 // [arg_0, arg_0, 0, 0, 0, 0, 0]
+ frame_dig 0 // [arg_0, arg_0, 0, 0, 0, 0, 0, arg_0]
+ frame_dig 1 // [arg_0, arg_0, 0, 0, 0, 0, 0, arg_0, arg_0]
+ + // [arg_0, arg_0, 0, 0, 0, 0, 0, arg_0 * 2]
+ bury 7 // [arg_0 * 2, arg_0, 0, 0, 0, 0, 0]
+ popn 5 // [arg_0 * 2, arg_0]
+ uncover 1 // [arg_0, arg_0 * 2]
+ swap // [arg_0 * 2, arg_0]
+ + // [arg_0 * 3]
+ pushbytess "1!" "5!" // [arg_0 * 3, "1!", "5!"]
+ pushints 0 2 1 1 5 18446744073709551615 // [arg_0 * 3, "1!", "5!", 0, 2, 1, 1, 5, 18446744073709551615]
+ store 1 // [arg_0 * 3, "1!", "5!", 0, 2, 1, 1, 5]
+ load 1 // [arg_0 * 3, "1!", "5!", 0, 2, 1, 1, 5, 18446744073709551615]
+ stores // [arg_0 * 3, "1!", "5!", 0, 2, 1, 1]
+ load 1 // [arg_0 * 3, "1!", "5!", 0, 2, 1, 1, 18446744073709551615]
+ store 1 // [arg_0 * 3, "1!", "5!", 0, 2, 1, 1]
+ retsub
+
+end:
+ int 1
+ return \ No newline at end of file
diff --git a/test/testdata/configs/config-v29.json b/test/testdata/configs/config-v29.json
new file mode 100644
index 000000000..70bcc758b
--- /dev/null
+++ b/test/testdata/configs/config-v29.json
@@ -0,0 +1,126 @@
+{
+ "Version": 29,
+ "AccountUpdatesStatsInterval": 5000000000,
+ "AccountsRebuildSynchronousMode": 1,
+ "AgreementIncomingBundlesQueueLength": 15,
+ "AgreementIncomingProposalsQueueLength": 50,
+ "AgreementIncomingVotesQueueLength": 20000,
+ "AnnounceParticipationKey": true,
+ "Archival": false,
+ "BaseLoggerDebugLevel": 4,
+ "BlockServiceCustomFallbackEndpoints": "",
+ "BlockServiceMemCap": 500000000,
+ "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": "<network>.algorand.network?backup=<network>.algorand.net&dedup=<name>.algorand-<network>.(network|net)",
+ "DNSSecurityFlags": 1,
+ "DeadlockDetection": 0,
+ "DeadlockDetectionThreshold": 30,
+ "DisableLedgerLRUCache": false,
+ "DisableLocalhostConnectionRateLimit": true,
+ "DisableNetworking": false,
+ "DisableOutgoingConnectionThrottling": false,
+ "EnableAccountUpdatesStats": false,
+ "EnableAgreementReporting": false,
+ "EnableAgreementTimeMetrics": false,
+ "EnableAssembleStats": false,
+ "EnableBlockService": false,
+ "EnableBlockServiceFallbackToArchiver": true,
+ "EnableCatchupFromArchiveServers": false,
+ "EnableDeveloperAPI": false,
+ "EnableExperimentalAPI": false,
+ "EnableFollowMode": 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,
+ "EnableTxnEvalTracer": 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,
+ "LedgerSynchronousMode": 2,
+ "LogArchiveMaxAge": "",
+ "LogArchiveName": "node.archive.log",
+ "LogSizeLimit": 1073741824,
+ "MaxAPIBoxPerApplication": 100000,
+ "MaxAPIResourcesPerAccount": 100000,
+ "MaxAcctLookback": 4,
+ "MaxCatchpointDownloadDuration": 43200000000000,
+ "MaxConnectionsPerIP": 15,
+ "MinCatchpointFileDownloadBytesPerSecond": 20480,
+ "NetAddress": "",
+ "NetworkMessageTraceServer": "",
+ "NetworkProtocolVersion": "",
+ "NodeExporterListenAddress": ":9100",
+ "NodeExporterPath": "./node_exporter",
+ "OptimizeAccountsDatabaseOnStartup": false,
+ "OutgoingMessageFilterBucketCount": 3,
+ "OutgoingMessageFilterBucketSize": 128,
+ "P2PEnable": false,
+ "P2PPersistPeerID": true,
+ "P2PPrivateKeyLocation": "",
+ "ParticipationKeysRefreshInterval": 60000000000,
+ "PeerConnectionsUpdateInterval": 3600,
+ "PeerPingPeriodSeconds": 0,
+ "PriorityPeers": {},
+ "ProposalAssemblyTime": 500000000,
+ "PublicAddress": "",
+ "ReconnectTime": 60000000000,
+ "ReservedFDs": 256,
+ "RestConnectionsHardLimit": 2048,
+ "RestConnectionsSoftLimit": 1024,
+ "RestReadTimeoutSeconds": 15,
+ "RestWriteTimeoutSeconds": 120,
+ "RunHosted": false,
+ "SpeculativeAsmTimeOffset": 0,
+ "SpeculativeAssemblyDisable": false,
+ "StorageEngine": "sqlite",
+ "SuggestedFeeBlockHistory": 3,
+ "SuggestedFeeSlidingWindowSize": 50,
+ "TLSCertFile": "",
+ "TLSKeyFile": "",
+ "TelemetryToLog": true,
+ "TransactionSyncDataExchangeRate": 0,
+ "TransactionSyncSignificantMessageThreshold": 0,
+ "TxBacklogReservedCapacityPerPeer": 20,
+ "TxBacklogServiceRateWindowSeconds": 10,
+ "TxBacklogSize": 26000,
+ "TxIncomingFilterMaxSize": 500000,
+ "TxIncomingFilteringFlags": 1,
+ "TxPoolExponentialIncreaseFactor": 2,
+ "TxPoolSize": 75000,
+ "TxSyncIntervalSeconds": 60,
+ "TxSyncServeResponseSize": 1000000,
+ "TxSyncTimeoutSeconds": 30,
+ "UseXForwardedForAddressField": "",
+ "VerifiedTranscationsCacheSize": 150000
+}
diff --git a/test/testdata/configs/config-v30.json b/test/testdata/configs/config-v30.json
new file mode 100644
index 000000000..e73de9a94
--- /dev/null
+++ b/test/testdata/configs/config-v30.json
@@ -0,0 +1,126 @@
+{
+ "Version": 30,
+ "AccountUpdatesStatsInterval": 5000000000,
+ "AccountsRebuildSynchronousMode": 1,
+ "AgreementIncomingBundlesQueueLength": 15,
+ "AgreementIncomingProposalsQueueLength": 50,
+ "AgreementIncomingVotesQueueLength": 20000,
+ "AnnounceParticipationKey": true,
+ "Archival": false,
+ "BaseLoggerDebugLevel": 4,
+ "BlockServiceCustomFallbackEndpoints": "",
+ "BlockServiceMemCap": 500000000,
+ "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": "<network>.algorand.network?backup=<network>.algorand.net&dedup=<name>.algorand-<network>.(network|net)",
+ "DNSSecurityFlags": 1,
+ "DeadlockDetection": 0,
+ "DeadlockDetectionThreshold": 30,
+ "DisableLedgerLRUCache": false,
+ "DisableLocalhostConnectionRateLimit": true,
+ "DisableNetworking": false,
+ "DisableOutgoingConnectionThrottling": false,
+ "EnableAccountUpdatesStats": false,
+ "EnableAgreementReporting": false,
+ "EnableAgreementTimeMetrics": false,
+ "EnableAssembleStats": false,
+ "EnableBlockService": false,
+ "EnableBlockServiceFallbackToArchiver": true,
+ "EnableCatchupFromArchiveServers": false,
+ "EnableDeveloperAPI": false,
+ "EnableExperimentalAPI": false,
+ "EnableFollowMode": 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,
+ "EnableTxnEvalTracer": 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,
+ "LedgerSynchronousMode": 2,
+ "LogArchiveMaxAge": "",
+ "LogArchiveName": "node.archive.log",
+ "LogSizeLimit": 1073741824,
+ "MaxAPIBoxPerApplication": 100000,
+ "MaxAPIResourcesPerAccount": 100000,
+ "MaxAcctLookback": 4,
+ "MaxCatchpointDownloadDuration": 43200000000000,
+ "MaxConnectionsPerIP": 15,
+ "MinCatchpointFileDownloadBytesPerSecond": 20480,
+ "NetAddress": "",
+ "NetworkMessageTraceServer": "",
+ "NetworkProtocolVersion": "",
+ "NodeExporterListenAddress": ":9100",
+ "NodeExporterPath": "./node_exporter",
+ "OptimizeAccountsDatabaseOnStartup": false,
+ "OutgoingMessageFilterBucketCount": 3,
+ "OutgoingMessageFilterBucketSize": 128,
+ "P2PEnable": false,
+ "P2PPersistPeerID": true,
+ "P2PPrivateKeyLocation": "",
+ "ParticipationKeysRefreshInterval": 60000000000,
+ "PeerConnectionsUpdateInterval": 3600,
+ "PeerPingPeriodSeconds": 0,
+ "PriorityPeers": {},
+ "ProposalAssemblyTime": 500000000,
+ "PublicAddress": "",
+ "ReconnectTime": 60000000000,
+ "ReservedFDs": 256,
+ "RestConnectionsHardLimit": 2048,
+ "RestConnectionsSoftLimit": 1024,
+ "RestReadTimeoutSeconds": 15,
+ "RestWriteTimeoutSeconds": 120,
+ "RunHosted": false,
+ "StorageEngine": "sqlite",
+ "SpeculativeAsmTimeOffset": 400000000,
+ "SpeculativeAssemblyDisable": false,
+ "SuggestedFeeBlockHistory": 3,
+ "SuggestedFeeSlidingWindowSize": 50,
+ "TLSCertFile": "",
+ "TLSKeyFile": "",
+ "TelemetryToLog": true,
+ "TransactionSyncDataExchangeRate": 0,
+ "TransactionSyncSignificantMessageThreshold": 0,
+ "TxBacklogReservedCapacityPerPeer": 20,
+ "TxBacklogServiceRateWindowSeconds": 10,
+ "TxBacklogSize": 26000,
+ "TxIncomingFilterMaxSize": 500000,
+ "TxIncomingFilteringFlags": 1,
+ "TxPoolExponentialIncreaseFactor": 2,
+ "TxPoolSize": 75000,
+ "TxSyncIntervalSeconds": 60,
+ "TxSyncServeResponseSize": 1000000,
+ "TxSyncTimeoutSeconds": 30,
+ "UseXForwardedForAddressField": "",
+ "VerifiedTranscationsCacheSize": 150000
+}
diff --git a/test/testdata/nettemplates/FiveNodesTwoRelays.json b/test/testdata/nettemplates/FiveNodesTwoRelays.json
new file mode 100644
index 000000000..5dabb59a1
--- /dev/null
+++ b/test/testdata/nettemplates/FiveNodesTwoRelays.json
@@ -0,0 +1,56 @@
+{
+ "Genesis": {
+ "NetworkName": "tbd",
+ "LastPartKeyRound": 5000,
+ "Wallets": [
+ {
+ "Name": "LargeWallet",
+ "Stake": 85,
+ "Online": true
+ },
+ {
+ "Name": "SmallWallet",
+ "Stake": 10,
+ "Online": true
+ },
+ {
+ "Name": "NonPartWallet",
+ "Stake": 5,
+ "Online": true
+ }
+ ]
+ },
+ "Nodes": [
+ {
+ "Name": "Relay1",
+ "IsRelay": true
+ },
+ {
+ "Name": "Relay2",
+ "IsRelay": true
+ },
+ {
+ "Name": "PartNode1",
+ "Wallets": [{
+ "Name": "LargeWallet",
+ "ParticipationOnly": true
+ }],
+ "PeerList": "Relay1;Relay2"
+ },
+ {
+ "Name": "PartNode2",
+ "Wallets": [{
+ "Name": "SmallWallet",
+ "ParticipationOnly": true
+ }],
+ "PeerList": "Relay2"
+ },
+ {
+ "Name": "NonPartNode",
+ "Wallets": [{
+ "Name": "NonPartWallet"
+ }],
+ "PeerList": "Relay1"
+ }
+ ]
+}
diff --git a/tools/block-generator/Makefile b/tools/block-generator/Makefile
index 496a7fc99..fdb575421 100644
--- a/tools/block-generator/Makefile
+++ b/tools/block-generator/Makefile
@@ -12,7 +12,7 @@ clean-generator:
rm -f block-generator
debug-blockgen:
- python run_runner.py \
+ python scripts/run_runner.py \
--conduit-binary ./conduit \
--scenario $(SCENARIO) \
--report-directory $(REPORTS) \
@@ -38,7 +38,7 @@ run-runner: block-generator
--report-directory $(REPORTS)
clean-reports:
- rm -rf $(REPORTS)
+ rm -rf $(REPORTS)
pre-git-push:
mv _go.mod go.mod
diff --git a/tools/block-generator/README.md b/tools/block-generator/README.md
index 544dc8009..a26328ec4 100644
--- a/tools/block-generator/README.md
+++ b/tools/block-generator/README.md
@@ -170,7 +170,7 @@ Flags:
-v, --verbose If set the runner will print debugging information from the generator and ledger.
```
-## Example Run using Conduit and Postgres in **bash** via `run_runner.sh`
+## Example Run using Conduit and Postgres
A typical **runner** scenario involves:
@@ -179,30 +179,30 @@ A typical **runner** scenario involves:
* a datastore -such as a postgres database- to collect `conduit`'s output
* a `conduit` config file to define its import/export behavior
-`run_runner.sh` makes the following choices for the previous bullet points:
-
-* it can accept any scenario as its second argument, but defaults to [test_config.yml](./test_config.yml) when this isn't provided (this is a scenario with a lifetime of ~30 seconds)
-* knows how to import through a mock Algod running on port 11112 (which is the port the runner avails)
-* sets up a dockerized postgres database to receive conduit's output
-* configures `conduit` for these specs using [this config template](./runner/template/conduit.yml.tmpl)
+The `block-generator runner` subcommand has a number of options to configure behavion.
### Sample Run
First you'll need to get a `conduit` binary. For example you can follow the [developer portal's instructions](https://developer.algorand.org/docs/get-details/conduit/GettingStarted/#installation) or run `go build .` inside of the directory `cmd/conduit` after downloading the `conduit` repo.
-Assume you've navigated to the `tools/block-generator` directory of
-the `go-algorand` repo, and:
+Run `make install` from the `go-algorand` root, this should add `block-generator` to your path.
-* saved the conduit binary to `tools/block-generator/conduit`
-* created a block generator scenario config at `tools/block-generator/scenario.yml`
+Start a postgres container using `scripts/run_postgres.sh`. This starts a container on port 15432 a database named generator_db and a user with credentials algorand/algorand.
-Then you can execute the following command to run the scenario:
+Now run `block-generator runner` to run the test:
```sh
-./run_runner.sh ./conduit scenario.yml
+block-generator runner \
+ --conduit-binary "$CONDUIT_BINARY" \
+ --report-directory reports \
+ --test-duration 30s \
+ --conduit-log-level trace \
+ --postgres-connection-string "host=localhost user=algorand password=algorand dbname=generator_db port=15432 sslmode=disable" \
+ --scenario generator/test_scenario.yml \
+ --reset-db
```
### Scenario Report
-If all goes well, the run will generate a directory `../../tmp/OUTPUT_RUN_RUNNER_TEST`
-and in that directory you can see the statistics of the run in `scenario.report`.
+If all goes well, the run will generate a directory named reports.
+In that directory you can see the statistics of the run in the file ending with `.report`.
diff --git a/tools/block-generator/generator/config_test.go b/tools/block-generator/generator/config_test.go
index bfdc69595..a595ad6d6 100644
--- a/tools/block-generator/generator/config_test.go
+++ b/tools/block-generator/generator/config_test.go
@@ -27,7 +27,7 @@ import (
func TestInitConfigFile(t *testing.T) {
partitiontest.PartitionTest(t)
- config, err := initializeConfigFile("../test_config.yml")
+ config, err := initializeConfigFile("test_scenario.yml")
require.NoError(t, err)
require.Equal(t, uint64(10), config.NumGenesisAccounts)
require.Equal(t, float32(0.25), config.AssetCloseFraction)
diff --git a/tools/block-generator/generator/generate.go b/tools/block-generator/generator/generate.go
index 1a88ea0fe..3a899a33b 100644
--- a/tools/block-generator/generator/generate.go
+++ b/tools/block-generator/generator/generate.go
@@ -27,15 +27,15 @@ import (
"time"
cconfig "github.com/algorand/go-algorand/config"
- "github.com/algorand/go-algorand/ledger/ledgercore"
- "github.com/algorand/go-algorand/protocol"
- "github.com/algorand/go-algorand/rpcs"
-
"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"
txn "github.com/algorand/go-algorand/data/transactions"
+ "github.com/algorand/go-algorand/ledger/ledgercore"
+ "github.com/algorand/go-algorand/logging"
+ "github.com/algorand/go-algorand/protocol"
+ "github.com/algorand/go-algorand/rpcs"
)
// ---- templates ----
@@ -55,14 +55,19 @@ var clearSwap string
// ---- constructors ----
// MakeGenerator initializes the Generator object.
-func MakeGenerator(dbround uint64, bkGenesis bookkeeping.Genesis, config GenerationConfig, verbose bool) (Generator, error) {
+func MakeGenerator(log logging.Logger, dbround uint64, bkGenesis bookkeeping.Genesis, config GenerationConfig, verbose bool) (Generator, error) {
if err := config.validateWithDefaults(false); err != nil {
return nil, fmt.Errorf("invalid generator configuration: %w", err)
}
+ if log == nil {
+ log = logging.Base()
+ }
+
var proto protocol.ConsensusVersion = "future"
gen := &generator{
verbose: verbose,
+ log: log,
config: config,
protocol: proto,
params: cconfig.Consensus[proto],
@@ -288,7 +293,9 @@ func (g *generator) WriteBlock(output io.Writer, round uint64) error {
if round == cachedRound {
// one round behind, so write the cached block (if non-empty)
- fmt.Printf("Received round request %d, but nextRound=%d. Not finishing round.\n", round, nextRound)
+ if g.verbose {
+ fmt.Printf("Received round request %d, but nextRound=%d. Not finishing round.\n", round, nextRound)
+ }
if len(g.latestBlockMsgp) != 0 {
// write the msgpack bytes for a block
_, err := output.Write(g.latestBlockMsgp)
@@ -304,7 +311,7 @@ func (g *generator) WriteBlock(output io.Writer, round uint64) error {
if err != nil {
return err
}
- if g.round == 0 {
+ if g.verbose && g.round == 0 {
fmt.Printf("starting txnCounter: %d\n", g.txnCounter)
}
minTxnsForBlock := g.minTxnsForBlock(g.round)
@@ -335,8 +342,8 @@ func (g *generator) WriteBlock(output io.Writer, round uint64) error {
if err != nil {
return fmt.Errorf("failed to evaluate block: %w", err)
}
- if ledgerTxnCount != g.txnCounter + intra {
- return fmt.Errorf("evaluateBlock() txn count mismatches theoretical intra: %d != %d", ledgerTxnCount, g.txnCounter + intra)
+ if ledgerTxnCount != g.txnCounter+intra {
+ return fmt.Errorf("evaluateBlock() txn count mismatches theoretical intra: %d != %d", ledgerTxnCount, g.txnCounter+intra)
}
err = g.ledger.AddValidatedBlock(*vBlock, cert.Certificate)
diff --git a/tools/block-generator/generator/generate_test.go b/tools/block-generator/generator/generate_test.go
index 4ae79d9e7..29a2613d6 100644
--- a/tools/block-generator/generator/generate_test.go
+++ b/tools/block-generator/generator/generate_test.go
@@ -24,15 +24,17 @@ import (
"testing"
"time"
+ "github.com/stretchr/testify/require"
+
"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/data/transactions/logic"
+ "github.com/algorand/go-algorand/logging"
"github.com/algorand/go-algorand/protocol"
"github.com/algorand/go-algorand/rpcs"
"github.com/algorand/go-algorand/test/partitiontest"
- "github.com/stretchr/testify/require"
)
func makePrivateGenerator(t *testing.T, round uint64, genesis bookkeeping.Genesis) *generator {
@@ -45,7 +47,7 @@ func makePrivateGenerator(t *testing.T, round uint64, genesis bookkeeping.Genesi
AssetCreateFraction: 1.0,
}
cfg.validateWithDefaults(true)
- publicGenerator, err := MakeGenerator(round, genesis, cfg, true)
+ publicGenerator, err := MakeGenerator(logging.Base(), round, genesis, cfg, true)
require.NoError(t, err)
return publicGenerator.(*generator)
}
@@ -355,7 +357,7 @@ func TestAppBoxesOptin(t *testing.T) {
paySiblingTxn := sgnTxns[1].Txn
require.Equal(t, protocol.PaymentTx, paySiblingTxn.Type)
-
+
g.finishRound()
// 2nd attempt to optin (with new sender) doesn't get replaced
g.startRound()
@@ -723,21 +725,21 @@ func TestCumulativeEffects(t *testing.T) {
partitiontest.PartitionTest(t)
report := Report{
- TxTypeID("app_boxes_optin"): {GenerationCount: uint64(42)},
- TxTypeID("app_boxes_create"): {GenerationCount: uint64(1337)},
- TxTypeID("pay_pay"): {GenerationCount: uint64(999)},
- TxTypeID("asset_optin_total"): {GenerationCount: uint64(13)},
- TxTypeID("app_boxes_call"): {GenerationCount: uint64(413)},
+ TxTypeID("app_boxes_optin"): {GenerationCount: uint64(42)},
+ TxTypeID("app_boxes_create"): {GenerationCount: uint64(1337)},
+ TxTypeID("pay_pay"): {GenerationCount: uint64(999)},
+ TxTypeID("asset_optin_total"): {GenerationCount: uint64(13)},
+ TxTypeID("app_boxes_call"): {GenerationCount: uint64(413)},
}
expectedEffectsReport := EffectsReport{
- "app_boxes_optin": uint64(42),
- "app_boxes_create": uint64(1337),
- "pay_pay": uint64(999),
- "asset_optin_total": uint64(13),
- "app_boxes_call": uint64(413),
- "effect_payment_sibling": uint64(42) + uint64(1337),
- "effect_inner_tx": uint64(2 * 42),
+ "app_boxes_optin": uint64(42),
+ "app_boxes_create": uint64(1337),
+ "pay_pay": uint64(999),
+ "asset_optin_total": uint64(13),
+ "app_boxes_call": uint64(413),
+ "effect_payment_sibling": uint64(42) + uint64(1337),
+ "effect_inner_tx": uint64(2 * 42),
}
require.Equal(t, expectedEffectsReport, CumulativeEffects(report))
@@ -772,7 +774,7 @@ func TestCountInners(t *testing.T) {
InnerTxns: []transactions.SignedTxnWithAD{
{
ApplyData: transactions.ApplyData{
- EvalDelta: transactions.EvalDelta{
+ EvalDelta: transactions.EvalDelta{
InnerTxns: []transactions.SignedTxnWithAD{{}, {}},
},
},
@@ -793,4 +795,3 @@ func TestCountInners(t *testing.T) {
})
}
}
-
diff --git a/tools/block-generator/generator/generator_ledger.go b/tools/block-generator/generator/generator_ledger.go
index c12906566..97fed9b34 100644
--- a/tools/block-generator/generator/generator_ledger.go
+++ b/tools/block-generator/generator/generator_ledger.go
@@ -31,7 +31,6 @@ import (
"github.com/algorand/go-algorand/ledger"
"github.com/algorand/go-algorand/ledger/eval"
"github.com/algorand/go-algorand/ledger/ledgercore"
- "github.com/algorand/go-algorand/logging"
"github.com/algorand/go-algorand/rpcs"
)
@@ -40,7 +39,7 @@ import (
func (g *generator) setBlockHeader(cert *rpcs.EncodedBlockCert) {
cert.Block.BlockHeader = bookkeeping.BlockHeader{
Round: basics.Round(g.round),
- TxnCounter: g.txnCounter,
+ TxnCounter: g.txnCounter,
Branch: bookkeeping.BlockHash{},
Seed: committee.Seed{},
TxnCommitments: bookkeeping.TxnCommitments{NativeSha512_256Commitment: crypto.Digest{}},
@@ -63,10 +62,9 @@ func (g *generator) setBlockHeader(cert *rpcs.EncodedBlockCert) {
}
}
-
// ---- ledger simulation and introspection ----
-// initializeLedger creates a new ledger
+// initializeLedger creates a new ledger
func (g *generator) initializeLedger() {
genBal := convertToGenesisBalances(g.balances)
// add rewards pool with min balance
@@ -85,7 +83,7 @@ func (g *generator) initializeLedger() {
} else {
prefix = g.genesisID
}
- l, err := ledger.OpenLedger(logging.Base(), prefix, true, ledgercore.InitState{
+ l, err := ledger.OpenLedger(g.log, prefix, true, ledgercore.InitState{
Block: block,
Accounts: bal.Balances,
GenesisHash: g.genesisHash,
diff --git a/tools/block-generator/generator/generator_types.go b/tools/block-generator/generator/generator_types.go
index c0ff24b4c..6685ffe7c 100644
--- a/tools/block-generator/generator/generator_types.go
+++ b/tools/block-generator/generator/generator_types.go
@@ -26,6 +26,7 @@ import (
"github.com/algorand/go-algorand/data/bookkeeping"
txn "github.com/algorand/go-algorand/data/transactions"
"github.com/algorand/go-algorand/ledger"
+ "github.com/algorand/go-algorand/logging"
"github.com/algorand/go-algorand/protocol"
)
@@ -42,6 +43,7 @@ type Generator interface {
type generator struct {
verbose bool
+ log logging.Logger
config GenerationConfig
@@ -52,7 +54,7 @@ type generator struct {
numAccounts uint64
// Block stuff
- round uint64
+ round uint64
txnCounter uint64
prevBlockHash string
timestamp int64
diff --git a/tools/block-generator/generator/server.go b/tools/block-generator/generator/server.go
index 2aac4a455..4de0d0832 100644
--- a/tools/block-generator/generator/server.go
+++ b/tools/block-generator/generator/server.go
@@ -24,6 +24,7 @@ import (
"time"
"github.com/algorand/go-algorand/data/bookkeeping"
+ "github.com/algorand/go-algorand/logging"
"github.com/algorand/go-algorand/tools/block-generator/util"
)
@@ -32,7 +33,7 @@ func MakeServer(configFile string, addr string) (*http.Server, Generator) {
noOp := func(next http.Handler) http.Handler {
return next
}
- return MakeServerWithMiddleware(0, "", configFile, false, addr, noOp)
+ return MakeServerWithMiddleware(nil, 0, "", configFile, false, addr, noOp)
}
// BlocksMiddleware is a middleware for the blocks endpoint.
@@ -41,7 +42,7 @@ type BlocksMiddleware func(next http.Handler) http.Handler
// MakeServerWithMiddleware allows injecting a middleware for the blocks handler.
// This is needed to simplify tests by stopping block production while validation
// is done on the data.
-func MakeServerWithMiddleware(dbround uint64, genesisFile string, configFile string, verbose bool, addr string, blocksMiddleware BlocksMiddleware) (*http.Server, Generator) {
+func MakeServerWithMiddleware(log logging.Logger, dbround uint64, genesisFile string, configFile string, verbose bool, addr string, blocksMiddleware BlocksMiddleware) (*http.Server, Generator) {
cfg, err := initializeConfigFile(configFile)
util.MaybeFail(err, "problem loading config file. Use '--config' or create a config file.")
var bkGenesis bookkeeping.Genesis
@@ -50,7 +51,7 @@ func MakeServerWithMiddleware(dbround uint64, genesisFile string, configFile str
// TODO: consider using bkGenesis to set cfg.NumGenesisAccounts and cfg.GenesisAccountInitialBalance
util.MaybeFail(err, "Failed to parse genesis file '%s'", genesisFile)
}
- gen, err := MakeGenerator(dbround, bkGenesis, cfg, verbose)
+ gen, err := MakeGenerator(log, dbround, bkGenesis, cfg, verbose)
util.MaybeFail(err, "Failed to make generator with config file '%s'", configFile)
mux := http.NewServeMux()
diff --git a/tools/block-generator/test_config.yml b/tools/block-generator/generator/test_scenario.yml
index 6d411e9ad..6d411e9ad 100644
--- a/tools/block-generator/test_config.yml
+++ b/tools/block-generator/generator/test_scenario.yml
diff --git a/tools/block-generator/go.mod b/tools/block-generator/go.mod
index 12649d09b..6eadc0421 100644
--- a/tools/block-generator/go.mod
+++ b/tools/block-generator/go.mod
@@ -6,12 +6,12 @@ go 1.20
require (
github.com/algorand/avm-abi v0.2.0
- github.com/algorand/go-algorand v0.0.0-00010101000000-000000000000
+ github.com/algorand/go-algorand v0.0.0
github.com/algorand/go-codec/codec v1.1.10
github.com/algorand/go-deadlock v0.2.2
github.com/lib/pq v1.10.9
github.com/spf13/cobra v1.7.0
- github.com/stretchr/testify v1.8.3
+ github.com/stretchr/testify v1.8.4
gopkg.in/yaml.v3 v3.0.1
)
@@ -26,27 +26,42 @@ require (
github.com/aws/aws-sdk-go v1.33.0 // indirect
github.com/consensys/gnark-crypto v0.7.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
- github.com/davidlazar/go-crypto v0.0.0-20170701192655-dcfb0a7ac018 // indirect
+ github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect
github.com/dchest/siphash v1.2.1 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/gorilla/mux v1.8.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
+ github.com/ipfs/go-cid v0.4.1 // indirect
github.com/jmespath/go-jmespath v0.3.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
+ github.com/klauspost/cpuid/v2 v2.2.5 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-sqlite3 v1.10.0 // indirect
- github.com/miekg/dns v1.1.41 // indirect
+ github.com/miekg/dns v1.1.55 // indirect
+ github.com/minio/sha256-simd v1.0.1 // indirect
github.com/mmcloughlin/addchain v0.4.0 // indirect
+ github.com/mr-tron/base58 v1.2.0 // indirect
+ github.com/multiformats/go-base32 v0.1.0 // indirect
+ github.com/multiformats/go-base36 v0.2.0 // indirect
+ github.com/multiformats/go-multiaddr v0.10.1 // indirect
+ github.com/multiformats/go-multiaddr-dns v0.3.1 // indirect
+ github.com/multiformats/go-multibase v0.2.0 // indirect
+ github.com/multiformats/go-multihash v0.2.3 // indirect
+ github.com/multiformats/go-varint v0.0.7 // indirect
github.com/olivere/elastic v6.2.14+incompatible // indirect
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/sirupsen/logrus v1.8.1 // indirect
+ github.com/spaolacci/murmur3 v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
- golang.org/x/crypto v0.1.0 // indirect
- golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
- golang.org/x/net v0.9.0 // indirect
- golang.org/x/sys v0.7.0 // indirect
+ golang.org/x/crypto v0.11.0 // indirect
+ golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 // indirect
+ golang.org/x/mod v0.12.0 // indirect
+ golang.org/x/net v0.12.0 // indirect
+ golang.org/x/sys v0.10.0 // indirect
+ golang.org/x/tools v0.11.0 // indirect
gopkg.in/sohlich/elogrus.v3 v3.0.0-20180410122755-1fa29e2f2009 // indirect
+ lukechampine.com/blake3 v1.2.1 // indirect
)
diff --git a/tools/block-generator/go.sum b/tools/block-generator/go.sum
index 86dd5643d..7c2b24188 100644
--- a/tools/block-generator/go.sum
+++ b/tools/block-generator/go.sum
@@ -27,8 +27,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/davidlazar/go-crypto v0.0.0-20170701192655-dcfb0a7ac018 h1:6xT9KW8zLC5IlbaIF5Q7JNieBoACT7iW0YTxQHR0in0=
-github.com/davidlazar/go-crypto v0.0.0-20170701192655-dcfb0a7ac018/go.mod h1:rQYf4tfk5sSwFsnDg3qYaBxSjsD9S8+59vW0dKUgme4=
+github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c h1:pFUpOrbxDR6AkioZ1ySsx5yxlDQZ8stG2b88gTPxgJU=
+github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c/go.mod h1:6UhI8N9EjYm1c2odKpFpAYeR8dsBeM7PtzQhRgxRr9U=
github.com/dchest/siphash v1.2.1 h1:4cLinnzVJDKxTCl9B01807Yiy+W7ZzVHj/KIroQRvT4=
github.com/dchest/siphash v1.2.1/go.mod h1:q+IRvb2gOSrUnYoPqHiyHXS0FOBBOdl6tONBlVnOnt4=
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
@@ -42,10 +42,14 @@ github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
+github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s=
+github.com/ipfs/go-cid v0.4.1/go.mod h1:uQHwDeX4c6CtyrFwdqyhpNcxVewur1M7l7fNU7LKwZk=
github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc=
github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
+github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
+github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
@@ -54,11 +58,36 @@ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o=
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
-github.com/miekg/dns v1.1.41 h1:WMszZWJG0XmzbK9FEmzH2TVcqYzFesusSIB41b8KHxY=
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
+github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo=
+github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
+github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ=
+github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
+github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=
+github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8=
github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY=
github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU=
github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU=
+github.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
+github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
+github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
+github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE=
+github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI=
+github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0=
+github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4=
+github.com/multiformats/go-multiaddr v0.2.0/go.mod h1:0nO36NvPpyV4QzvTLi/lafl2y95ncPj0vFwVF6k6wJ4=
+github.com/multiformats/go-multiaddr v0.10.1 h1:HghtFrWyZEPrpTvgAMFJi6gFdgHfs2cb0pyfDsk+lqU=
+github.com/multiformats/go-multiaddr v0.10.1/go.mod h1:jLEZsA61rwWNZQTHHnqq2HNa+4os/Hz54eqiRnsRqYQ=
+github.com/multiformats/go-multiaddr-dns v0.3.1 h1:QgQgR+LQVt3NPTjbrLLpsaT2ufAA2y0Mkk+QRVJbW3A=
+github.com/multiformats/go-multiaddr-dns v0.3.1/go.mod h1:G/245BRQ6FJGmryJCrOuTdB37AMA5AMOVuO6NY3JwTk=
+github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g=
+github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk=
+github.com/multiformats/go-multihash v0.0.8/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew=
+github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U=
+github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM=
+github.com/multiformats/go-varint v0.0.1/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE=
+github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8=
+github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/olivere/elastic v6.2.14+incompatible h1:k+KadwNP/dkXE0/eu+T6otk1+5fe0tEpPyQJ4XVm5i8=
github.com/olivere/elastic v6.2.14+incompatible/go.mod h1:J+q1zQJTgAz9woqsbVRqGeB5G1iqDKVBWLNSYW8yfJ8=
@@ -71,6 +100,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
+github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
+github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
@@ -79,43 +110,53 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
-github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
-github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/yuin/goldmark v1.4.1/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-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200602180216-279210d13fed/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
-golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
-golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
-golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
+golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
+golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
+golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 h1:MGwJjxBy0HJshjDNfLsYO8xppfqWlA5ZT9OhtUUhTNw=
+golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
+golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
+golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+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-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
-golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
+golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
+golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
golang.org/x/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/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
+golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
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-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
-golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
+golang.org/x/sys v0.10.0/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/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
-golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
+golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
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.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
+golang.org/x/tools v0.11.0 h1:EMCa6U9S2LtZXLAMoWiR/R8dAQFRqbAitmbJ2UKhoi8=
+golang.org/x/tools v0.11.0/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8=
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/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -126,4 +167,6 @@ gopkg.in/sohlich/elogrus.v3 v3.0.0-20180410122755-1fa29e2f2009/go.mod h1:O0bY1e/
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI=
+lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
pgregory.net/rapid v0.6.2 h1:ErW5sL+UKtfBfUTsWHDCoeB+eZKLKMxrSd1VJY6W4bw=
diff --git a/tools/block-generator/run_tests.sh b/tools/block-generator/run_tests.sh
deleted file mode 100755
index 48c1490b4..000000000
--- a/tools/block-generator/run_tests.sh
+++ /dev/null
@@ -1,97 +0,0 @@
-#!/usr/bin/env bash
-
-CONNECTION_STRING=""
-CONDUIT_BINARY=""
-REPORT_DIR=""
-DURATION="1h"
-LOG_LEVEL="error"
-SCENARIOS=""
-
-help() {
- echo "Usage:"
- echo " -v|--verbose enable verbose script output."
- echo " -c|--connection-string"
- echo " PostgreSQL connection string."
- echo " -i|--conduit path to conduit binary."
- echo " -s|--scenarios path to conduit test scenarios."
- echo " -r|--report-dir directory where the report should be written."
- echo " -d|--duration test duration."
- echo " -l|--level log level to pass to conduit."
- echo " -g|--generator block-generator binary to run the generator."
- exit
-}
-
-while :; do
- case "${1-}" in
- -h | --help) help ;;
- -v | --verbose) set -x ;;
- -c | --connection-string)
- CONNECTION_STRING="${2-}"
- shift
- ;;
- -g | --generator)
- GENERATOR_BINARY="${2-}"
- shift
- ;;
- -i | --conduit)
- CONDUIT_BINARY="${2-}"
- shift
- ;;
- -r | --report-dir)
- REPORT_DIR="${2-}"
- shift
- ;;
- -s | --scenarios)
- SCENARIOS="${2-}"
- shift
- ;;
- -d | --duration)
- DURATION="${2-}"
- shift
- ;;
- -l | --level)
- LOG_LEVEL="${2-}"
- shift
- ;;
- -?*) echo "Unknown option: $1" && exit 1;;
- *) break ;;
- esac
- shift
-done
-
-args=("$@")
-
-if [ -z "$CONNECTION_STRING" ]; then
- echo "Missing required connection string parameter (-c / --connection-string)."
- exit 1
-fi
-
-if [ -z "$CONDUIT_BINARY" ]; then
- echo "Missing required conduit binary parameter (-i / --conduit)."
- exit 1
-fi
-
-if [ -z "$SCENARIOS" ]; then
- echo "Missing required conduit test scenario parameter (-s / --scenarios)."
- exit 1
-fi
-
-if [ -z "$GENERATOR_BINARY" ]; then
- echo "path to block-generator binary is required"
- exit 1
-fi
-
-echo "Running with binary: $CONDUIT_BINARY"
-echo "Report directory: $REPORT_DIR"
-echo "Duration: $DURATION"
-echo "Log Level: $LOG_LEVEL"
-
-"$GENERATOR_BINARY" runner \
- -i "$CONDUIT_BINARY" \
- -s "$SCENARIOS" \
- -d "$DURATION" \
- -c "$CONNECTION_STRING" \
- --report-directory "$REPORT_DIR" \
- --conduit-log-level "$LOG_LEVEL" \
- --reset-report-dir
-
diff --git a/tools/block-generator/runner/run.go b/tools/block-generator/runner/run.go
index 929bdd35e..31c83dc91 100644
--- a/tools/block-generator/runner/run.go
+++ b/tools/block-generator/runner/run.go
@@ -35,14 +35,18 @@ import (
"text/template"
"time"
+ "github.com/algorand/go-deadlock"
+
+ "github.com/algorand/go-algorand/logging"
"github.com/algorand/go-algorand/tools/block-generator/generator"
"github.com/algorand/go-algorand/tools/block-generator/util"
- "github.com/algorand/go-deadlock"
)
//go:embed template/conduit.yml.tmpl
var conduitConfigTmpl string
+const pad = " "
+
// Args are all the things needed to run a performance test.
type Args struct {
// Path is a directory when passed to RunBatch, otherwise a file path.
@@ -54,12 +58,13 @@ type Args struct {
RunDuration time.Duration
RunnerVerbose bool
ConduitLogLevel string
- ReportDirectory string
+ BaseReportDirectory string
ResetReportDir bool
RunValidation bool
KeepDataDir bool
GenesisFile string
ResetDB bool
+ Times uint64
}
type config struct {
@@ -74,43 +79,57 @@ type config struct {
// The test will run against the generator configuration file specified by 'args.Path'.
// If 'args.Path' is a directory it should contain generator configuration files, a test will run using each file.
func Run(args Args) error {
- if _, err := os.Stat(args.ReportDirectory); !os.IsNotExist(err) {
- if args.ResetReportDir {
- fmt.Printf("Resetting existing report directory '%s'\n", args.ReportDirectory)
- if err := os.RemoveAll(args.ReportDirectory); err != nil {
- return fmt.Errorf("failed to reset report directory: %w", err)
+ defer fmt.Println("Done running tests!")
+ for i := uint64(0); i < args.Times; i++ {
+ reportDirectory := args.BaseReportDirectory
+ if args.Times != 1 {
+ fmt.Println("* Starting test", i+1, "of", args.Times, "times")
+ reportDirectory = fmt.Sprintf("%s_%d", args.BaseReportDirectory, i+1)
+ }
+ if _, err := os.Stat(reportDirectory); !os.IsNotExist(err) {
+ if args.ResetReportDir {
+ fmt.Printf("Resetting existing report directory '%s'\n", reportDirectory)
+ if err := os.RemoveAll(reportDirectory); err != nil {
+ return fmt.Errorf("failed to reset report directory: %w", err)
+ }
+ } else {
+ return fmt.Errorf("report directory '%s' already exists", reportDirectory)
}
- } else {
- return fmt.Errorf("report directory '%s' already exists", args.ReportDirectory)
}
- }
- err := os.Mkdir(args.ReportDirectory, os.ModeDir|os.ModePerm)
- if err != nil {
- return err
- }
-
- defer fmt.Println("Done running tests!")
- return filepath.Walk(args.Path, func(path string, info os.FileInfo, err error) error {
+ err := os.Mkdir(reportDirectory, os.ModeDir|os.ModePerm)
if err != nil {
- return fmt.Errorf("run.go Run(): failed to walk path: %w", err)
+ return err
}
- // Ignore the directory
- if info.IsDir() {
- return nil
+
+ err = filepath.Walk(args.Path, func(path string, info os.FileInfo, err error) error {
+ if err != nil {
+ return fmt.Errorf("run.go Run(): failed to walk path: %w", err)
+ }
+ // Ignore the directory
+ if info.IsDir() {
+ return nil
+ }
+ runnerArgs := args
+ runnerArgs.Path = path
+ fmt.Println("----------------------------------------")
+ fmt.Printf("%sRunning test for configuration: %s\n", pad, info.Name())
+ fmt.Println("----------------------------------------")
+ return runnerArgs.run(reportDirectory)
+ })
+ if err != nil {
+ return fmt.Errorf("failed to walk path: %w", err)
}
- runnerArgs := args
- runnerArgs.Path = path
- fmt.Printf("Running test for configuration '%s'\n", path)
- return runnerArgs.run()
- })
+ }
+ return nil
}
-func (r *Args) run() error {
+func (r *Args) run(reportDirectory string) error {
baseName := filepath.Base(r.Path)
baseNameNoExt := strings.TrimSuffix(baseName, filepath.Ext(baseName))
- reportfile := path.Join(r.ReportDirectory, fmt.Sprintf("%s.report", baseNameNoExt))
- logfile := path.Join(r.ReportDirectory, fmt.Sprintf("%s.conduit-log", baseNameNoExt))
- dataDir := path.Join(r.ReportDirectory, fmt.Sprintf("%s_data", baseNameNoExt))
+ reportfile := path.Join(reportDirectory, fmt.Sprintf("%s.report", baseNameNoExt))
+ conduitlogfile := path.Join(reportDirectory, fmt.Sprintf("%s.conduit-log", baseNameNoExt))
+ ledgerlogfile := path.Join(reportDirectory, fmt.Sprintf("%s.ledger-log", baseNameNoExt))
+ dataDir := path.Join(reportDirectory, fmt.Sprintf("%s_data", baseNameNoExt))
// create the data directory.
if err := os.Mkdir(dataDir, os.ModeDir|os.ModePerm); err != nil {
return fmt.Errorf("failed to create data directory: %w", err)
@@ -143,35 +162,38 @@ func (r *Args) run() error {
} else if err != nil {
return fmt.Errorf("getNextRound err: %w", err)
}
+ fmt.Printf("%sPostgreSQL next round: %d\n", pad, nextRound)
}
// Start services
algodNet := fmt.Sprintf("localhost:%d", 11112)
metricsNet := fmt.Sprintf("localhost:%d", r.MetricsPort)
- generatorShutdownFunc, _ := startGenerator(r.Path, nextRound, r.GenesisFile, r.RunnerVerbose, algodNet, blockMiddleware)
+ generatorShutdownFunc, _ := startGenerator(ledgerlogfile, r.Path, nextRound, r.GenesisFile, r.RunnerVerbose, algodNet, blockMiddleware)
defer func() {
// Shutdown generator.
+ fmt.Printf("%sShutting down generator...\n", pad)
if err := generatorShutdownFunc(); err != nil {
fmt.Printf("failed to shutdown generator: %s\n", err)
}
}()
- // get conduit config template
+
+ // create conduit config from template
t, err := template.New("conduit").Parse(conduitConfigTmpl)
if err != nil {
return fmt.Errorf("unable to parse conduit config template: %w", err)
}
-
// create config file in the right data directory
f, err := os.Create(path.Join(dataDir, "conduit.yml"))
if err != nil {
return fmt.Errorf("problem creating conduit.yml: %w", err)
}
defer f.Close()
-
- conduitConfig := config{r.ConduitLogLevel, logfile,
- fmt.Sprintf(":%d", r.MetricsPort),
- algodNet, r.PostgresConnectionString,
+ conduitConfig := config{
+ LogLevel: r.ConduitLogLevel,
+ LogFile: conduitlogfile,
+ MetricsPort: fmt.Sprintf(":%d", r.MetricsPort),
+ AlgodNet: algodNet,
+ PostgresConnectionString: r.PostgresConnectionString,
}
-
err = t.Execute(f, conduitConfig)
if err != nil {
return fmt.Errorf("problem executing template file: %w", err)
@@ -184,6 +206,7 @@ func (r *Args) run() error {
}
defer func() {
// Shutdown conduit
+ fmt.Printf("%sShutting down Conduit...\n", pad)
if sdErr := conduitShutdownFunc(); sdErr != nil {
fmt.Printf("failed to shutdown Conduit: %s\n", sdErr)
}
@@ -213,6 +236,7 @@ func (r *Args) run() error {
if err = r.runTest(report, metricsNet, algodNet); err != nil {
return err
}
+ fmt.Printf("%sTest completed successfully\n", pad)
return nil
}
@@ -350,15 +374,19 @@ func (r *Args) runTest(report *os.File, metricsURL string, generatorURL string)
// Run for r.RunDuration
start := time.Now()
+ fmt.Printf("%sduration starting now: %s\n", pad, start)
count := 1
for time.Since(start) < r.RunDuration {
time.Sleep(r.RunDuration / 10)
+ fmt.Printf("%scollecting metrics (%d)\n", pad, count)
if err := collector.Collect(AllMetricNames...); err != nil {
return fmt.Errorf("problem collecting metrics (%d / %s): %w", count, time.Since(start), err)
}
count++
}
+
+ fmt.Printf("%scollecting final metrics\n", pad)
if err := collector.Collect(AllMetricNames...); err != nil {
return fmt.Errorf("problem collecting final metrics (%d / %s): %w", count, time.Since(start), err)
}
@@ -426,14 +454,20 @@ func (r *Args) runTest(report *os.File, metricsURL string, generatorURL string)
}
// startGenerator starts the generator server.
-func startGenerator(configFile string, dbround uint64, genesisFile string, verbose bool, addr string, blockMiddleware func(http.Handler) http.Handler) (func() error, generator.Generator) {
+func startGenerator(ledgerLogFile, configFile string, dbround uint64, genesisFile string, verbose bool, addr string, blockMiddleware func(http.Handler) http.Handler) (func() error, generator.Generator) {
+ f, err := os.OpenFile(ledgerLogFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
+ util.MaybeFail(err, "unable to open ledger log file '%s'", ledgerLogFile)
+ log := logging.NewLogger()
+ log.SetLevel(logging.Warn)
+ log.SetOutput(f)
+
// Start generator.
- server, generator := generator.MakeServerWithMiddleware(dbround, genesisFile, configFile, verbose, addr, blockMiddleware)
+ server, generator := generator.MakeServerWithMiddleware(log, dbround, genesisFile, configFile, verbose, addr, blockMiddleware)
// Start the server
go func() {
// always returns error. ErrServerClosed on graceful close
- fmt.Printf("generator serving on %s\n", server.Addr)
+ fmt.Printf("%sgenerator serving on %s\n", pad, server.Addr)
if err := server.ListenAndServe(); err != http.ErrServerClosed {
util.MaybeFail(err, "ListenAndServe() failure to start with config file '%s'", configFile)
}
@@ -452,6 +486,7 @@ func startGenerator(configFile string, dbround uint64, genesisFile string, verbo
// startConduit starts the conduit binary.
func startConduit(dataDir string, conduitBinary string, round uint64) (func() error, error) {
+ fmt.Printf("%sConduit starting with data directory: %s\n", pad, dataDir)
cmd := exec.Command(
conduitBinary,
"-r", strconv.FormatUint(round, 10),
@@ -459,9 +494,8 @@ func startConduit(dataDir string, conduitBinary string, round uint64) (func() er
)
var stdout bytes.Buffer
- var stderr bytes.Buffer
cmd.Stdout = &stdout
- cmd.Stderr = &stderr
+ cmd.Stderr = os.Stderr // pass errors to Stderr
if err := cmd.Start(); err != nil {
return nil, fmt.Errorf("failure calling Start(): %w", err)
@@ -470,13 +504,13 @@ func startConduit(dataDir string, conduitBinary string, round uint64) (func() er
return func() error {
if err := cmd.Process.Signal(os.Interrupt); err != nil {
- fmt.Printf("failed to kill conduit process: %s\n", err)
+ fmt.Printf("failed to interrupt conduit process: %s\n", err)
if err := cmd.Process.Kill(); err != nil {
return fmt.Errorf("failed to kill conduit process: %w", err)
}
}
if err := cmd.Wait(); err != nil {
- fmt.Printf("exiting block generator runner: %s\n", err)
+ fmt.Printf("%sConduit exiting: %s\n", pad, err)
}
return nil
}, nil
diff --git a/tools/block-generator/runner/runner.go b/tools/block-generator/runner/runner.go
index 1bb3cd9cf..ff2b49948 100644
--- a/tools/block-generator/runner/runner.go
+++ b/tools/block-generator/runner/runner.go
@@ -48,7 +48,7 @@ func init() {
RunnerCmd.Flags().Uint64VarP(&runnerArgs.MetricsPort, "metrics-port", "p", 9999, "Port to start the metrics server at.")
RunnerCmd.Flags().StringVarP(&runnerArgs.PostgresConnectionString, "postgres-connection-string", "c", "", "Postgres connection string.")
RunnerCmd.Flags().DurationVarP(&runnerArgs.RunDuration, "test-duration", "d", 5*time.Minute, "Duration to use for each scenario.")
- RunnerCmd.Flags().StringVarP(&runnerArgs.ReportDirectory, "report-directory", "r", "", "Location to place test reports.")
+ RunnerCmd.Flags().StringVarP(&runnerArgs.BaseReportDirectory, "report-directory", "r", "", "Location to place test reports. If --times is used, this is the prefix for multiple report directories.")
RunnerCmd.Flags().BoolVarP(&runnerArgs.RunnerVerbose, "verbose", "v", false, "If set the runner will print debugging information from the generator and ledger.")
RunnerCmd.Flags().StringVarP(&runnerArgs.ConduitLogLevel, "conduit-log-level", "l", "error", "LogLevel to use when starting Conduit. [panic, fatal, error, warn, info, debug, trace]")
RunnerCmd.Flags().StringVarP(&runnerArgs.CPUProfilePath, "cpuprofile", "", "", "Path where Conduit writes its CPU profile.")
@@ -57,6 +57,7 @@ func init() {
RunnerCmd.Flags().BoolVarP(&runnerArgs.KeepDataDir, "keep-data-dir", "k", false, "If set the validator will not delete the data directory after tests complete.")
RunnerCmd.Flags().StringVarP(&runnerArgs.GenesisFile, "genesis-file", "f", "", "file path to the genesis associated with the db snapshot")
RunnerCmd.Flags().BoolVarP(&runnerArgs.ResetDB, "reset-db", "", false, "If set database will be deleted before running tests.")
+ RunnerCmd.Flags().Uint64VarP(&runnerArgs.Times, "times", "t", 1, "Number of times to run the scenario(s).")
RunnerCmd.MarkFlagRequired("scenario")
RunnerCmd.MarkFlagRequired("conduit-binary")
diff --git a/tools/block-generator/runner/template/conduit.yml.tmpl b/tools/block-generator/runner/template/conduit.yml.tmpl
index c361426ee..d461dd564 100644
--- a/tools/block-generator/runner/template/conduit.yml.tmpl
+++ b/tools/block-generator/runner/template/conduit.yml.tmpl
@@ -5,7 +5,7 @@ log-level: {{.LogLevel}}
log-file: {{.LogFile}}
# Number of retries to perform after a pipeline plugin error.
-retry-count: 10
+retry-count: 120
# Time duration to wait between retry attempts.
retry-delay: "1s"
diff --git a/tools/block-generator/print_tps.py b/tools/block-generator/scripts/print_tps.py
index 3401c5515..3401c5515 100644
--- a/tools/block-generator/print_tps.py
+++ b/tools/block-generator/scripts/print_tps.py
diff --git a/tools/block-generator/run_postgres.sh b/tools/block-generator/scripts/run_postgres.sh
index 2c8175bb9..490404aff 100755
--- a/tools/block-generator/run_postgres.sh
+++ b/tools/block-generator/scripts/run_postgres.sh
@@ -14,8 +14,6 @@ set -e
POSTGRES_CONTAINER=generator-test-container
POSTGRES_PORT=15432
POSTGRES_DATABASE=generator_db
-CONFIG=${1:-"$(dirname $0)/test_config.yml"}
-echo "Using config file: $CONFIG"
function start_postgres() {
docker rm -f $POSTGRES_CONTAINER > /dev/null 2>&1 || true
diff --git a/tools/block-generator/run_runner.py b/tools/block-generator/scripts/run_runner.py
index 914a38d46..5f0753930 100644
--- a/tools/block-generator/run_runner.py
+++ b/tools/block-generator/scripts/run_runner.py
@@ -82,7 +82,7 @@ def parse_args():
parser.add_argument("--conduit-binary", help="Path to conduit binary")
parser.add_argument(
"--scenario",
- default=(default := CWD / "test_config.yml"),
+ default=(default := CWD.parents[1] / "test_scenario.yml"),
help=f"Scenario configuration file ({default=!s})",
)
parser.add_argument(
diff --git a/tools/block-generator/run_runner.sh b/tools/block-generator/scripts/run_runner.sh
index 236d7b2bb..5643ab9c8 100755
--- a/tools/block-generator/run_runner.sh
+++ b/tools/block-generator/scripts/run_runner.sh
@@ -15,7 +15,7 @@ fi
POSTGRES_CONTAINER=generator-test-container
POSTGRES_PORT=15432
POSTGRES_DATABASE=generator_db
-SCENARIO=${2:-"$(dirname $0)/test_config.yml"}
+SCENARIO=${2:-"$(dirname $0)/../test_scenario.yml"}
echo "Using scenario config file: $SCENARIO"
function start_postgres() {
@@ -51,10 +51,10 @@ echo "Starting postgres container."
start_postgres
echo "Starting test runner"
$(dirname "$0")/block-generator runner \
- --conduit-binary "$CONDUIT_BINARY" \
- --report-directory $OUTPUT \
- --test-duration 30s \
- --conduit-log-level trace \
- --postgres-connection-string "host=localhost user=algorand password=algorand dbname=generator_db port=15432 sslmode=disable" \
- --scenario ${SCENARIO} \
+ --conduit-binary "$CONDUIT_BINARY" \
+ --report-directory $OUTPUT \
+ --test-duration 30s \
+ --conduit-log-level trace \
+ --postgres-connection-string "host=localhost user=algorand password=algorand dbname=generator_db port=15432 sslmode=disable" \
+ --scenario ${SCENARIO} \
--reset-db
diff --git a/tools/network/resolveController.go b/tools/network/resolveController.go
index a36914f21..b9b75fb7b 100644
--- a/tools/network/resolveController.go
+++ b/tools/network/resolveController.go
@@ -20,6 +20,8 @@ import (
"net"
"time"
+ madns "github.com/multiformats/go-multiaddr-dns"
+
"github.com/algorand/go-algorand/logging"
"github.com/algorand/go-algorand/tools/network/dnssec"
)
@@ -85,3 +87,26 @@ func (c *ResolveController) DefaultResolver() ResolverIf {
}
return &Resolver{}
}
+
+// SystemDnsaddrResolver returns the dnsaddr resolver that uses OS-defined DNS server
+func (c *ResolveController) SystemDnsaddrResolver() *madns.Resolver {
+ resolver := c.SystemResolver()
+ // ignore errors here since we madns.WithDefaultResolver can't error
+ mResv, _ := madns.NewResolver(madns.WithDefaultResolver(resolver))
+ return mResv
+}
+
+// FallbackDnsaddrResolver returns the dnsaddr resolver w/ fallback DNS address
+func (c *ResolveController) FallbackDnsaddrResolver() *madns.Resolver {
+ resolver := c.FallbackResolver()
+ // ignore errors here since we madns.WithDefaultResolver can't error
+ mResv, _ := madns.NewResolver(madns.WithDefaultResolver(resolver))
+ return mResv
+}
+
+// DefaultDnsaddrResolver returns the dnsaddr resolver w/ default DNS address
+func (c *ResolveController) DefaultDnsaddrResolver() *madns.Resolver {
+ resolver := c.DefaultResolver()
+ mResv, _ := madns.NewResolver(madns.WithDefaultResolver(resolver))
+ return mResv
+}
diff --git a/tools/x-repo-types/Makefile b/tools/x-repo-types/Makefile
index 05094a848..900b49271 100644
--- a/tools/x-repo-types/Makefile
+++ b/tools/x-repo-types/Makefile
@@ -1,38 +1,51 @@
-all: goal-v-sdk goal-v-spv
+all: clean goal-v-sdk goal-v-spv
+
+clean:
+ rm x-repo-types
+
+x-repo-types:
+ go build
# go-algorand vs go-algorand-sdk:
goal-v-sdk: goal-v-sdk-state-delta goal-v-sdk-genesis goal-v-sdk-block goal-v-sdk-blockheader goal-v-sdk-stateproof
-goal-v-sdk-state-delta:
+goal-v-sdk-state-delta: x-repo-types
x-repo-types --x-package "github.com/algorand/go-algorand/ledger/ledgercore" \
--x-type "StateDelta" \
--y-branch "develop" \
--y-package "github.com/algorand/go-algorand-sdk/v2/types" \
--y-type "LedgerStateDelta"
-goal-v-sdk-genesis:
+goal-v-sdk-genesis: x-repo-types
x-repo-types --x-package "github.com/algorand/go-algorand/data/bookkeeping" \
--x-type "Genesis" \
--y-branch "develop" \
--y-package "github.com/algorand/go-algorand-sdk/v2/types" \
--y-type "Genesis"
-goal-v-sdk-block:
+goal-v-sdk-block: x-repo-types
x-repo-types --x-package "github.com/algorand/go-algorand/data/bookkeeping" \
--x-type "Block" \
--y-branch "develop" \
--y-package "github.com/algorand/go-algorand-sdk/v2/types" \
--y-type "Block"
-goal-v-sdk-blockheader:
+goal-v-sdk-blockheader: x-repo-types
x-repo-types --x-package "github.com/algorand/go-algorand/data/bookkeeping" \
--x-type "BlockHeader" \
--y-branch "develop" \
--y-package "github.com/algorand/go-algorand-sdk/v2/types" \
--y-type "BlockHeader"
-goal-v-sdk-stateproof:
+goal-v-sdk-consensus: x-repo-types
+ x-repo-types --x-package "github.com/algorand/go-algorand/config" \
+ --x-type "ConsensusParams" \
+ --y-branch "develop" \
+ --y-package "github.com/algorand/go-algorand-sdk/v2/protocol/config" \
+ --y-type "ConsensusParams"
+
+goal-v-sdk-stateproof: x-repo-types
x-repo-types --x-package "github.com/algorand/go-algorand/crypto/stateproof" \
--x-type "StateProof" \
--y-branch "develop" \
diff --git a/tools/x-repo-types/go.mod b/tools/x-repo-types/go.mod
index 776b76a0e..df9ddf011 100644
--- a/tools/x-repo-types/go.mod
+++ b/tools/x-repo-types/go.mod
@@ -5,15 +5,14 @@ go 1.20
replace github.com/algorand/go-algorand => ../..
require (
- github.com/algorand/go-algorand v0.0.0-20230502140608-e24a35add0bb
+ github.com/algorand/go-algorand v0.0.0
github.com/spf13/cobra v1.7.0
- github.com/stretchr/testify v1.8.2
+ github.com/stretchr/testify v1.8.4
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
- github.com/kr/text v0.2.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
diff --git a/tools/x-repo-types/go.sum b/tools/x-repo-types/go.sum
index dba66ee26..7929646e0 100644
--- a/tools/x-repo-types/go.sum
+++ b/tools/x-repo-types/go.sum
@@ -1,12 +1,9 @@
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
-github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
-github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
-github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -15,15 +12,9 @@ github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
-github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
-github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
-github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
-github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
-github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
-gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/util/metrics/metrics.go b/util/metrics/metrics.go
index bd68ff4c1..cb376eb0a 100644
--- a/util/metrics/metrics.go
+++ b/util/metrics/metrics.go
@@ -132,4 +132,9 @@ var (
TransactionGroupTxSyncRemember = MetricName{Name: "algod_transaction_group_txsync_remember", Description: "Number of transaction groups remembered via txsync"}
// TransactionGroupTxSyncAlreadyCommitted "Number of duplicate or error transaction groups received via txsync"
TransactionGroupTxSyncAlreadyCommitted = MetricName{Name: "algod_transaction_group_txsync_err_or_committed", Description: "Number of duplicate or error transaction groups received via txsync"}
+
+ // BroadcastSignedTxGroupSucceeded "Number of successful broadcasts of local signed transaction groups"
+ BroadcastSignedTxGroupSucceeded = MetricName{Name: "algod_broadcast_txgroup_succeeded", Description: "Number of successful broadcasts of local signed transaction groups"}
+ // BroadcastSignedTxGroupFailed "Number of failed broadcasts of local signed transaction groups"
+ BroadcastSignedTxGroupFailed = MetricName{Name: "algod_broadcast_txgroup_failed", Description: "Number of failed broadcasts of local signed transaction groups"}
)