From 1fe68f41599baa269e0f72fc0fc63ecf316f9af2 Mon Sep 17 00:00:00 2001 From: cce <51567+cce@users.noreply.github.com> Date: Fri, 10 Mar 2023 19:55:10 -0500 Subject: [PATCH 001/125] CI: set reviewdog golangci-lint go version (#5196) --- .github/workflows/reviewdog.yml | 1 + ledger/internal/appcow.go | 2 +- logging/telemetryspec/metric.go | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/reviewdog.yml b/.github/workflows/reviewdog.yml index ec1a859436..fcba97537f 100644 --- a/.github/workflows/reviewdog.yml +++ b/.github/workflows/reviewdog.yml @@ -21,6 +21,7 @@ jobs: - name: reviewdog-golangci-lint uses: reviewdog/action-golangci-lint@v2 with: + go_version_file: go.mod golangci_lint_version: "v1.47.3" golangci_lint_flags: "-c .golangci.yml --allow-parallel-runners" reporter: "github-pr-check" diff --git a/ledger/internal/appcow.go b/ledger/internal/appcow.go index f843e17064..5b53687e4a 100644 --- a/ledger/internal/appcow.go +++ b/ledger/internal/appcow.go @@ -458,7 +458,7 @@ func (cb *roundCowState) StatefulEval(gi int, params *logic.EvalParams, aidx bas calf := cb.child(1) defer func() { // get rid of references to the object that is about to be recycled - params.Ledger = nil + params.Ledger = nil params.SigLedger = nil calf.recycle() }() diff --git a/logging/telemetryspec/metric.go b/logging/telemetryspec/metric.go index da5f4a7536..bdd504e2f4 100644 --- a/logging/telemetryspec/metric.go +++ b/logging/telemetryspec/metric.go @@ -80,7 +80,7 @@ type StateProofStats struct { // AssembleBlockTimeout represents AssembleBlock exiting due to timeout const AssembleBlockTimeout = "timeout" -// AssembleBlockTimeout represents AssembleBlock giving up after a timeout and returning an empty block +// AssembleBlockTimeoutEmpty represents AssembleBlock giving up after a timeout and returning an empty block const AssembleBlockTimeoutEmpty = "timeout-empty" // AssembleBlockFull represents AssembleBlock exiting due to block being full From 7b11fc7f00f8b9a6e162caf2a1f4d28485b4e6ce Mon Sep 17 00:00:00 2001 From: Kurnia D Win Date: Sat, 11 Mar 2023 22:54:48 +0700 Subject: [PATCH 002/125] goal: user defined scheme to connect to remote host (#4922) --- nodecontrol/algodControl.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/nodecontrol/algodControl.go b/nodecontrol/algodControl.go index 85c9df30b4..3702e21167 100644 --- a/nodecontrol/algodControl.go +++ b/nodecontrol/algodControl.go @@ -23,6 +23,7 @@ import ( "os/exec" "path/filepath" "strconv" + "strings" "time" "github.com/algorand/go-algorand/config" @@ -85,6 +86,13 @@ func (nc NodeController) ServerURL() (url.URL, error) { if err != nil { return url.URL{}, err } + if strings.HasPrefix(addr, "http:") || strings.HasPrefix(addr, "https:") { + u, err := url.Parse(addr) + if err != nil { + return url.URL{}, err + } + return *u, nil + } return url.URL{Scheme: "http", Host: addr}, nil } From 5214d4b8e906536dcb5ab41a5ae6e5fb494b061e Mon Sep 17 00:00:00 2001 From: AlgoAxel <113933518+AlgoAxel@users.noreply.github.com> Date: Mon, 13 Mar 2023 11:56:04 -0400 Subject: [PATCH 003/125] ERL: Support zero-size reservations (#5192) --- util/rateLimit.go | 20 +++++++++++++------- util/rateLimit_test.go | 24 ++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/util/rateLimit.go b/util/rateLimit.go index c4e85c71e7..c62264c32e 100644 --- a/util/rateLimit.go +++ b/util/rateLimit.go @@ -171,6 +171,7 @@ func (erl *ElasticRateLimiter) DisableCongestionControl() { // - there is not sufficient free capacity to assign a reserved capacity block // - there is no reserved or shared capacity available for the client func (erl *ElasticRateLimiter) ConsumeCapacity(c ErlClient) (*ErlCapacityGuard, error) { + var cg ErlCapacityGuard var q capacityQueue var err error var exists bool @@ -182,7 +183,8 @@ func (erl *ElasticRateLimiter) ConsumeCapacity(c ErlClient) (*ErlCapacityGuard, erl.clientLock.RUnlock() // Step 0: Check for, and create a capacity reservation if needed - if !exists { + // Don't interact with reservations if the capacity-per-reservation is zero + if !exists && erl.CapacityPerReservation > 0 { q, err = erl.openReservation(c) if err != nil { return nil, err @@ -195,14 +197,17 @@ func (erl *ElasticRateLimiter) ConsumeCapacity(c ErlClient) (*ErlCapacityGuard, return &ErlCapacityGuard{cq: q, cm: erl.cm}, nil } - // Step 1: Attempt consumption from the reserved queue - cg, err := q.consume(erl.cm) - if err == nil { - if erl.cm != nil { - erl.cm.Consumed(c, time.Now()) // notify the congestion manager that this client consumed from this queue + // Step 1: Attempt consumption from the reserved queue if one exists + if q != nil { + cg, err = q.consume(erl.cm) + if err == nil { + if erl.cm != nil { + erl.cm.Consumed(c, time.Now()) // notify the congestion manager that this client consumed from this queue + } + return &cg, nil } - return &cg, nil } + // Step 2: Potentially gate shared queue access if the congestion manager disallows it if erl.cm != nil && enableCM && @@ -212,6 +217,7 @@ func (erl *ElasticRateLimiter) ConsumeCapacity(c ErlClient) (*ErlCapacityGuard, } return nil, errConManDropped } + // Step 3: Attempt consumption from the shared queue cg, err = erl.sharedCapacity.consume(erl.cm) if err != nil { diff --git a/util/rateLimit_test.go b/util/rateLimit_test.go index 669960ecfc..fd7a031408 100644 --- a/util/rateLimit_test.go +++ b/util/rateLimit_test.go @@ -105,6 +105,30 @@ func TestReservations(t *testing.T) { assert.Equal(t, 0, len(erl.capacityByClient)) } +// When there is no reservation per client, the reservation map is not used +// This is so we never wait on a capacity queue which would not ever vend +func TestZeroSizeReservations(t *testing.T) { + partitiontest.PartitionTest(t) + client1 := mockClient("client1") + client2 := mockClient("client2") + erl := NewElasticRateLimiter(4, 0, time.Second, nil) + + _, err := erl.ConsumeCapacity(client1) + time.Sleep(100 * time.Millisecond) + assert.Equal(t, 0, len(erl.capacityByClient)) + assert.NoError(t, err) + + _, err = erl.ConsumeCapacity(client2) + time.Sleep(100 * time.Millisecond) + assert.Equal(t, 0, len(erl.capacityByClient)) + assert.NoError(t, err) + + erl.closeReservation(client1) + assert.Equal(t, 0, len(erl.capacityByClient)) + erl.closeReservation(client2) + assert.Equal(t, 0, len(erl.capacityByClient)) +} + func TestConsumeReleaseCapacity(t *testing.T) { partitiontest.PartitionTest(t) client := mockClient("client") From 9e121f8cfb7bcdc2d6530d439399b1124f33439f Mon Sep 17 00:00:00 2001 From: Shant Karakashian <55754073+algonautshant@users.noreply.github.com> Date: Mon, 13 Mar 2023 13:05:20 -0400 Subject: [PATCH 004/125] refactor: Move streamVerifier to util/execpool/stream (#5168) --- data/transactions/verify/txnBatch.go | 11 +- data/transactions/verify/txnBatch_test.go | 48 +- data/txHandler.go | 8 +- .../execpool/stream.go | 9 +- util/execpool/stream_test.go | 442 ++++++++++++++++++ 5 files changed, 481 insertions(+), 37 deletions(-) rename data/transactions/verify/streamverifier.go => util/execpool/stream.go (97%) create mode 100644 util/execpool/stream_test.go diff --git a/data/transactions/verify/txnBatch.go b/data/transactions/verify/txnBatch.go index 206ee221fd..40ffb4d35a 100644 --- a/data/transactions/verify/txnBatch.go +++ b/data/transactions/verify/txnBatch.go @@ -26,6 +26,7 @@ import ( "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/data/transactions/logic" "github.com/algorand/go-algorand/ledger/ledgercore" + "github.com/algorand/go-algorand/util/execpool" ) // UnverifiedTxnSigJob is the sig verification job passed to the Stream verifier @@ -113,7 +114,7 @@ type LedgerForStreamVerifier interface { BlockHdr(rnd basics.Round) (blk bookkeeping.BlockHeader, err error) } -func (tbp *txnSigBatchProcessor) Cleanup(pending []InputJob, err error) { +func (tbp *txnSigBatchProcessor) Cleanup(pending []execpool.InputJob, err error) { // report an error for the unchecked txns // drop the messages without reporting if the receiver does not consume for i := range pending { @@ -122,7 +123,7 @@ func (tbp *txnSigBatchProcessor) Cleanup(pending []InputJob, err error) { } } -func (tbp txnSigBatchProcessor) GetErredUnprocessed(ue InputJob, err error) { +func (tbp txnSigBatchProcessor) GetErredUnprocessed(ue execpool.InputJob, err error) { uelt := ue.(*UnverifiedTxnSigJob) tbp.sendResult(uelt.TxnGroup, uelt.BacklogMessage, err) } @@ -143,7 +144,7 @@ func (tbp txnSigBatchProcessor) sendResult(veTxnGroup []transactions.SignedTxn, // MakeSigVerifyJobProcessor returns the object implementing the stream verifier Helper interface func MakeSigVerifyJobProcessor(ledger LedgerForStreamVerifier, cache VerifiedTransactionCache, - resultChan chan<- *VerificationResult, droppedChan chan<- *UnverifiedTxnSigJob) (svp BatchProcessor, err error) { + resultChan chan<- *VerificationResult, droppedChan chan<- *UnverifiedTxnSigJob) (svp execpool.BatchProcessor, err error) { latest := ledger.Latest() latestHdr, err := ledger.BlockHdr(latest) if err != nil { @@ -162,14 +163,14 @@ func MakeSigVerifyJobProcessor(ledger LedgerForStreamVerifier, cache VerifiedTra }, nil } -func (tbp *txnSigBatchProcessor) ProcessBatch(txns []InputJob) { +func (tbp *txnSigBatchProcessor) ProcessBatch(txns []execpool.InputJob) { batchVerifier, ctx := tbp.preProcessUnverifiedTxns(txns) failed, err := batchVerifier.VerifyWithFeedback() // this error can only be crypto.ErrBatchHasFailedSigs tbp.postProcessVerifiedJobs(ctx, failed, err) } -func (tbp *txnSigBatchProcessor) preProcessUnverifiedTxns(uTxns []InputJob) (batchVerifier *crypto.BatchVerifier, ctx interface{}) { +func (tbp *txnSigBatchProcessor) preProcessUnverifiedTxns(uTxns []execpool.InputJob) (batchVerifier *crypto.BatchVerifier, ctx interface{}) { batchVerifier = crypto.MakeBatchVerifier() bl := makeBatchLoad(len(uTxns)) // TODO: separate operations here, and get the sig verification inside the LogicSig to the batch here diff --git a/data/transactions/verify/txnBatch_test.go b/data/transactions/verify/txnBatch_test.go index a196a9dcd2..aeced948cd 100644 --- a/data/transactions/verify/txnBatch_test.go +++ b/data/transactions/verify/txnBatch_test.go @@ -46,7 +46,7 @@ import ( var droppedFromPool = metrics.MakeCounter(metrics.MetricName{Name: "test_streamVerifierTestCore_messages_dropped_pool", Description: "Test streamVerifierTestCore messages dropped from pool"}) func streamVerifierTestCore(txnGroups [][]transactions.SignedTxn, badTxnGroups map[uint64]struct{}, - expectedError error, t *testing.T) (sv *StreamToBatch) { + expectedError error, t *testing.T) (sv *execpool.StreamToBatch) { numOfTxnGroups := len(txnGroups) verificationPool := execpool.MakeBacklog(nil, 0, execpool.LowPriority, t) @@ -57,12 +57,12 @@ func streamVerifierTestCore(txnGroups [][]transactions.SignedTxn, badTxnGroups m defer cancel() - inputChan := make(chan InputJob) + inputChan := make(chan execpool.InputJob) resultChan := make(chan *VerificationResult, txBacklogSize) droppedChan := make(chan *UnverifiedTxnSigJob) ep, err := MakeSigVerifyJobProcessor(&DummyLedgerForSignature{}, cache, resultChan, droppedChan) require.NoError(t, err) - sv = MakeStreamToBatch(inputChan, verificationPool, ep) + sv = execpool.MakeStreamToBatch(inputChan, verificationPool, ep) sv.Start(ctx) wg := sync.WaitGroup{} @@ -408,12 +408,12 @@ func TestStreamToBatchPoolShutdown(t *testing.T) { //nolint:paralleltest // Not ctx, cancel := context.WithCancel(context.Background()) cache := MakeVerifiedTransactionCache(50000) - inputChan := make(chan InputJob) + inputChan := make(chan execpool.InputJob) resultChan := make(chan *VerificationResult, txBacklogSize) droppedChan := make(chan *UnverifiedTxnSigJob) ep, err := MakeSigVerifyJobProcessor(&DummyLedgerForSignature{}, cache, resultChan, droppedChan) require.NoError(t, err) - sv := MakeStreamToBatch(inputChan, verificationPool, ep) + sv := execpool.MakeStreamToBatch(inputChan, verificationPool, ep) sv.Start(ctx) errChan := make(chan error) @@ -446,7 +446,7 @@ func TestStreamToBatchPoolShutdown(t *testing.T) { //nolint:paralleltest // Not } }() for err := range errChan { - require.ErrorIs(t, err, ErrShuttingDownError) + require.ErrorIs(t, err, execpool.ErrShuttingDownError) } require.Contains(t, logBuffer.String(), "addBatchToThePoolNow: EnqueueBacklog returned an error and StreamToBatch will stop: context canceled") wg.Wait() @@ -468,14 +468,14 @@ func TestStreamToBatchRestart(t *testing.T) { cache := MakeVerifiedTransactionCache(50) - inputChan := make(chan InputJob) + inputChan := make(chan execpool.InputJob) resultChan := make(chan *VerificationResult, txBacklogSize) droppedChan := make(chan *UnverifiedTxnSigJob) ctx, cancel := context.WithCancel(context.Background()) ep, err := MakeSigVerifyJobProcessor(&DummyLedgerForSignature{}, cache, resultChan, droppedChan) require.NoError(t, err) - sv := MakeStreamToBatch(inputChan, verificationPool, ep) + sv := execpool.MakeStreamToBatch(inputChan, verificationPool, ep) sv.Start(ctx) errChan := make(chan error) @@ -509,7 +509,7 @@ func TestStreamToBatchRestart(t *testing.T) { cancel() }() for err := range errChan { - require.ErrorIs(t, err, ErrShuttingDownError) + require.ErrorIs(t, err, execpool.ErrShuttingDownError) } wg.Wait() sv.WaitForStop() @@ -588,12 +588,12 @@ func TestStreamToBatchCtxCancel(t *testing.T) { defer verificationPool.Shutdown() ctx, cancel := context.WithCancel(context.Background()) cache := MakeVerifiedTransactionCache(50) - inputChan := make(chan InputJob) + inputChan := make(chan execpool.InputJob) resultChan := make(chan *VerificationResult, txBacklogSize) droppedChan := make(chan *UnverifiedTxnSigJob) ep, err := MakeSigVerifyJobProcessor(&DummyLedgerForSignature{}, cache, resultChan, droppedChan) require.NoError(t, err) - sv := MakeStreamToBatch(inputChan, verificationPool, ep) + sv := execpool.MakeStreamToBatch(inputChan, verificationPool, ep) sv.Start(ctx) var result *VerificationResult @@ -620,7 +620,7 @@ func TestStreamToBatchCtxCancel(t *testing.T) { close(holdTasks) wg.Wait() - require.ErrorIs(t, result.Err, ErrShuttingDownError) + require.ErrorIs(t, result.Err, execpool.ErrShuttingDownError) } // TestStreamToBatchCtxCancelPoolQueue tests the termination when the ctx is canceled @@ -643,12 +643,12 @@ func TestStreamToBatchCtxCancelPoolQueue(t *testing.T) { //nolint:paralleltest / ctx, cancel := context.WithCancel(context.Background()) cache := MakeVerifiedTransactionCache(50) - inputChan := make(chan InputJob) + inputChan := make(chan execpool.InputJob) resultChan := make(chan *VerificationResult, txBacklogSize) droppedChan := make(chan *UnverifiedTxnSigJob) ep, err := MakeSigVerifyJobProcessor(&DummyLedgerForSignature{}, cache, resultChan, droppedChan) require.NoError(t, err) - sv := MakeStreamToBatch(inputChan, verificationPool, ep) + sv := execpool.MakeStreamToBatch(inputChan, verificationPool, ep) sv.Start(ctx) var result *VerificationResult @@ -659,7 +659,7 @@ func TestStreamToBatchCtxCancelPoolQueue(t *testing.T) { //nolint:paralleltest / for { result = <-resultChan // at least one ErrShuttingDownError is expected - if result.Err != ErrShuttingDownError { + if result.Err != execpool.ErrShuttingDownError { continue } break @@ -690,7 +690,7 @@ func TestStreamToBatchCtxCancelPoolQueue(t *testing.T) { //nolint:paralleltest / // cancel the ctx as the sig is not yet sent to the exec pool // the test might sporadically fail if between sending the txn above // and the cancelation, 2 x waitForNextTxnDuration elapses (10ms) - time.Sleep(6 * waitForNextJobDuration) + time.Sleep(12) go func() { // wait a bit before releasing the tasks, so that the verificationPool ctx first gets canceled time.Sleep(20 * time.Millisecond) @@ -703,7 +703,7 @@ func TestStreamToBatchCtxCancelPoolQueue(t *testing.T) { //nolint:paralleltest / cancel() wg.Wait() - require.ErrorIs(t, result.Err, ErrShuttingDownError) + require.ErrorIs(t, result.Err, execpool.ErrShuttingDownError) require.Contains(t, logBuffer.String(), "addBatchToThePoolNow: EnqueueBacklog returned an error and StreamToBatch will stop: context canceled") } @@ -725,12 +725,12 @@ func TestStreamToBatchPostVBlocked(t *testing.T) { txBacklogSizeMod := txBacklogSize / 20 - inputChan := make(chan InputJob) + inputChan := make(chan execpool.InputJob) resultChan := make(chan *VerificationResult, txBacklogSizeMod) droppedChan := make(chan *UnverifiedTxnSigJob) ep, err := MakeSigVerifyJobProcessor(&DummyLedgerForSignature{}, cache, resultChan, droppedChan) require.NoError(t, err) - sv := MakeStreamToBatch(inputChan, verificationPool, ep) + sv := execpool.MakeStreamToBatch(inputChan, verificationPool, ep) defer close(droppedChan) go func() { @@ -768,7 +768,7 @@ func TestStreamToBatchPostVBlocked(t *testing.T) { go processResults(ctx, errChan, resultChan, numOfTxnGroups-overflow, badTxnGroups, &badSigResultCounter, &goodSigResultCounter, &wg) for err := range errChan { - require.ErrorIs(t, err, ErrShuttingDownError) + require.ErrorIs(t, err, execpool.ErrShuttingDownError) fmt.Println(badTxnGroups) } @@ -789,7 +789,7 @@ func TestStreamToBatchPostVBlocked(t *testing.T) { } for err := range errChan { - require.ErrorIs(t, err, ErrShuttingDownError) + require.ErrorIs(t, err, execpool.ErrShuttingDownError) fmt.Println(badTxnGroups) } @@ -818,13 +818,13 @@ func TestStreamToBatchCancelWhenPooled(t *testing.T) { cache := MakeVerifiedTransactionCache(50) - inputChan := make(chan InputJob) + inputChan := make(chan execpool.InputJob) resultChan := make(chan *VerificationResult, txBacklogSize) droppedChan := make(chan *UnverifiedTxnSigJob) ctx, cancel := context.WithCancel(context.Background()) ep, err := MakeSigVerifyJobProcessor(&DummyLedgerForSignature{}, cache, resultChan, droppedChan) require.NoError(t, err) - sv := MakeStreamToBatch(inputChan, verificationPool, ep) + sv := execpool.MakeStreamToBatch(inputChan, verificationPool, ep) sv.Start(ctx) errChan := make(chan error) @@ -849,7 +849,7 @@ func TestStreamToBatchCancelWhenPooled(t *testing.T) { cancel() }() for err := range errChan { - require.ErrorIs(t, err, ErrShuttingDownError) + require.ErrorIs(t, err, execpool.ErrShuttingDownError) } wg.Wait() sv.WaitForStop() diff --git a/data/txHandler.go b/data/txHandler.go index 9f18044442..1d3578d3cf 100644 --- a/data/txHandler.go +++ b/data/txHandler.go @@ -125,8 +125,8 @@ type TxHandler struct { cacheConfig txHandlerConfig ctx context.Context ctxCancel context.CancelFunc - streamVerifier *verify.StreamToBatch - streamVerifierChan chan verify.InputJob + streamVerifier *execpool.StreamToBatch + streamVerifierChan chan execpool.InputJob streamVerifierDropped chan *verify.UnverifiedTxnSigJob erl *util.ElasticRateLimiter } @@ -177,7 +177,7 @@ func MakeTxHandler(opts TxHandlerOpts) (*TxHandler, error) { msgCache: makeSaltedCache(2 * txBacklogSize), txCanonicalCache: makeDigestCache(2 * txBacklogSize), cacheConfig: txHandlerConfig{opts.Config.TxFilterRawMsgEnabled(), opts.Config.TxFilterCanonicalEnabled()}, - streamVerifierChan: make(chan verify.InputJob), + streamVerifierChan: make(chan execpool.InputJob), streamVerifierDropped: make(chan *verify.UnverifiedTxnSigJob), } @@ -198,7 +198,7 @@ func MakeTxHandler(opts TxHandlerOpts) (*TxHandler, error) { if err != nil { return nil, err } - handler.streamVerifier = verify.MakeStreamToBatch(handler.streamVerifierChan, handler.txVerificationPool, txnElementProcessor) + handler.streamVerifier = execpool.MakeStreamToBatch(handler.streamVerifierChan, handler.txVerificationPool, txnElementProcessor) go handler.droppedTxnWatcher() return handler, nil } diff --git a/data/transactions/verify/streamverifier.go b/util/execpool/stream.go similarity index 97% rename from data/transactions/verify/streamverifier.go rename to util/execpool/stream.go index a5a5fe512f..af2bb08091 100644 --- a/data/transactions/verify/streamverifier.go +++ b/util/execpool/stream.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with go-algorand. If not, see . -package verify +package execpool import ( "context" @@ -23,7 +23,6 @@ import ( "time" "github.com/algorand/go-algorand/logging" - "github.com/algorand/go-algorand/util/execpool" ) // ErrShuttingDownError is the error returned when a job is not processed because the service is shutting down @@ -38,6 +37,8 @@ var ErrShuttingDownError = errors.New("not processed, execpool service is shutti // for processing before waitForNextJobDuration. const waitForNextJobDuration = 2 * time.Millisecond +const txnPerWorksetThreshold = 32 + // batchSizeBlockLimit is the limit when the batch exceeds, will be added to the exec pool, even if the pool is saturated // and the stream will be blocked until the exec pool accepts the batch const batchSizeBlockLimit = 1024 @@ -61,14 +62,14 @@ type BatchProcessor interface { // StreamToBatch makes batches from incoming stream of jobs, and submits the batches to the exec pool type StreamToBatch struct { inputChan <-chan InputJob - executionPool execpool.BacklogPool + executionPool BacklogPool ctx context.Context activeLoopWg sync.WaitGroup batchProcessor BatchProcessor } // MakeStreamToBatch creates a new stream to batch converter -func MakeStreamToBatch(inputChan <-chan InputJob, execPool execpool.BacklogPool, +func MakeStreamToBatch(inputChan <-chan InputJob, execPool BacklogPool, batchProcessor BatchProcessor) *StreamToBatch { return &StreamToBatch{ diff --git a/util/execpool/stream_test.go b/util/execpool/stream_test.go new file mode 100644 index 0000000000..a6f3b17da4 --- /dev/null +++ b/util/execpool/stream_test.go @@ -0,0 +1,442 @@ +// Copyright (C) 2019-2023 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package execpool + +import ( + "context" + "fmt" + "sync" + "testing" + "time" + + "github.com/algorand/go-algorand/test/partitiontest" + "github.com/stretchr/testify/require" +) + +// implements BatchProcessor interface for testing purposes +type mockBatchProcessor struct { + notify chan struct{} // notify the test that cleanup was called +} + +func (mbp *mockBatchProcessor) ProcessBatch(jobs []InputJob) { + for i := range jobs { + job := jobs[i].(*mockJob) + job.processed = true + job.batchSize = len(jobs) + job.batchOrder = i + if job.callback != nil { + job.callback(job.id) + } + } +} + +func (mbp *mockBatchProcessor) GetErredUnprocessed(ue InputJob, err error) { + job := ue.(*mockJob) + job.returnError = err +} + +func (mbp *mockBatchProcessor) Cleanup(ue []InputJob, err error) { + for i := range ue { + mbp.GetErredUnprocessed(ue[i], err) + } + if mbp.notify != nil && len(ue) > 0 { + mbp.notify <- struct{}{} + } +} + +// implements InputJob interface +type mockJob struct { + id int + numberOfItems uint64 + jobError error + returnError error + processed bool + batchSize int + batchOrder int + callback func(id int) +} + +func (mj *mockJob) GetNumberOfBatchableItems() (count uint64, err error) { + return mj.numberOfItems, mj.jobError +} + +type mockPool struct { + pool + hold chan struct{} // used to sync the EnqueueBacklog call with the test + err error // when not nil, EnqueueBacklog will return the err instead of executing the task + poolCapacity chan struct{} // mimics the pool capacity which blocks EnqueueBacklog + asyncDelay chan struct{} // used to control when the task gets executed after EnqueueBacklog queues and returns +} + +func (mp *mockPool) EnqueueBacklog(enqueueCtx context.Context, t ExecFunc, arg interface{}, out chan interface{}) error { + // allow the test to know when the exec pool is executing the job + <-mp.hold + // simulate the execution of the job by the pool + if mp.err != nil { + // return the mock error + return mp.err + } + mp.poolCapacity <- struct{}{} + go func() { + mp.asyncDelay <- struct{}{} + t(arg) + }() + return nil +} + +func (mp *mockPool) BufferSize() (length, capacity int) { + return len(mp.poolCapacity), cap(mp.poolCapacity) +} + +func testStreamToBatchCore(wg *sync.WaitGroup, mockJobs <-chan *mockJob, done <-chan struct{}, t *testing.T) { + defer wg.Done() + ctx, cancel := context.WithCancel(context.Background()) + verificationPool := MakeBacklog(nil, 0, LowPriority, t) + defer verificationPool.Shutdown() + + inputChan := make(chan InputJob) + mbp := mockBatchProcessor{} + sv := MakeStreamToBatch(inputChan, verificationPool, &mbp) + sv.Start(ctx) + + for j := range mockJobs { + inputChan <- j + } + <-done + cancel() + sv.WaitForStop() +} + +// TestStreamToBatchBasic tests the basic functionality +func TestStreamToBatchBasic(t *testing.T) { + partitiontest.PartitionTest(t) + + numJobs := 400 + // for GetNumberOfBatchableItems errors: 400 / 99 + numJobsToProcess := 400 - (400/99 + 1) + // processedChan will notify whenn all the jobs are processed + processedChan := make(chan struct{}, numJobsToProcess-1) + done := make(chan struct{}) + // callback is needed to know when the processing should stop + callback := func(id int) { + select { + case processedChan <- struct{}{}: + default: + // this was the last job + close(done) + } + } + numError := fmt.Errorf("err on GetNumberOfBatchableItems") + mockJobs := make([]*mockJob, numJobs, numJobs) + for i := 0; i < numJobs; i++ { + mockJobs[i] = &mockJob{ + id: i, + // get some jobs with 0 items too + numberOfItems: uint64(i % 5), + callback: callback} + + if i%99 == 0 { + // get GetNumberOfBatchableItems to report an error + mockJobs[i].jobError = numError + } + if i%101 == 0 { + // have a batch exceeding batchSizeBlockLimit limit + mockJobs[i].numberOfItems = batchSizeBlockLimit + 1 + } + } + jobChan := make(chan *mockJob) + wg := sync.WaitGroup{} + wg.Add(2) + go testStreamToBatchCore(&wg, jobChan, done, t) + + go func() { + defer wg.Done() + for i := range mockJobs { + jobChan <- mockJobs[i] + } + close(jobChan) + <-done + }() + wg.Wait() + for i := 0; i < numJobs; i++ { + if i%99 == 0 { + // this should be GetNumberOfBatchableItems + require.ErrorIs(t, mockJobs[i].returnError, numError) + require.False(t, mockJobs[i].processed) + continue + } + if i%5 == 0 { + // this should be processed alone + if 1 != mockJobs[i].batchSize { + require.Equal(t, 1, mockJobs[i].batchSize) + } + } + if i%101 == 0 { + // this should be the last in the batch + require.Equal(t, mockJobs[i].batchSize-1, mockJobs[i].batchOrder) + } + if mockJobs[i].returnError != nil { + require.Nil(t, mockJobs[i].returnError) + } + require.True(t, mockJobs[i].processed) + } +} + +// TestNoInputYet let the service start and get to the timeout without any inputs +func TestNoInputYet(t *testing.T) { + partitiontest.PartitionTest(t) + + numJobs := 1 + done := make(chan struct{}) + jobChan := make(chan *mockJob) + wg := sync.WaitGroup{} + wg.Add(1) + go testStreamToBatchCore(&wg, jobChan, done, t) + callback := func(id int) { + if id == numJobs-1 { + close(done) + } + } + // Wait to trigger the timer once with 0 elements + time.Sleep(2 * waitForNextJobDuration) + + // send a job, make sure it goes through + mockJob := &mockJob{ + numberOfItems: uint64(0), + callback: callback} + jobChan <- mockJob + <-done + require.Nil(t, mockJob.returnError) + require.True(t, mockJob.processed) + require.Equal(t, 1, mockJob.batchSize) + close(jobChan) + wg.Wait() +} + +// TestMutipleBatchAttempts tests the behavior when multiple batch attempts will fail and the stream blocks +func TestMutipleBatchAttempts(t *testing.T) { + partitiontest.PartitionTest(t) + + mp := mockPool{ + hold: make(chan struct{}), + err: nil, + poolCapacity: make(chan struct{}, 1), + asyncDelay: make(chan struct{}, 10), + } + + ctx, cancel := context.WithCancel(context.Background()) + + inputChan := make(chan InputJob) + mbp := mockBatchProcessor{} + sv := MakeStreamToBatch(inputChan, &mp, &mbp) + sv.Start(ctx) + + var jobCalled int + jobCalledRef := &jobCalled + callbackFeedback := make(chan struct{}) + + mj := mockJob{ + numberOfItems: uint64(txnPerWorksetThreshold + 1), + id: 1, + callback: func(id int) { + *jobCalledRef = *jobCalledRef + id + <-callbackFeedback + }, + } + // first saturate the pool + mp.poolCapacity <- struct{}{} + inputChan <- &mj + + // wait for the job to be submitted to the pool + // since this is only a single job with 1 task, and the pool is at capacity, + // this will only happen when the numberOfBatchAttempts == 1 + mp.hold <- struct{}{} + + // here, the pool is saturated, and the stream should be blocked + select { + case inputChan <- &mj: + require.Fail(t, "the stream should be blocked here") + default: + } + + // now let the pool regian capacity + <-mp.poolCapacity + + // make sure it is processed before reading the value + callbackFeedback <- struct{}{} + require.Equal(t, 1, jobCalled) + + // the stream should be unblocked now + inputChan <- &mj + + // let the next job go through + mp.hold <- struct{}{} + // give the pool the capacity for it to process + <-mp.poolCapacity + + // make sure it is processed before reading the value + callbackFeedback <- struct{}{} + require.Equal(t, 2, jobCalled) + + cancel() + sv.WaitForStop() +} + +// TestErrors tests all the cases where exec pool returned error is handled +// by ending the stream processing +func TestErrors(t *testing.T) { + partitiontest.PartitionTest(t) + + mp := mockPool{ + hold: make(chan struct{}), + err: fmt.Errorf("Test error"), + poolCapacity: make(chan struct{}, 5), + asyncDelay: make(chan struct{}, 10), + } + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + inputChan := make(chan InputJob) + mbp := mockBatchProcessor{} + sv := MakeStreamToBatch(inputChan, &mp, &mbp) + + /***************************************************/ + // error adding to the pool when numberOfBatchable=0 + /***************************************************/ + sv.Start(ctx) + mj := mockJob{ + numberOfItems: 0, + } + inputChan <- &mj + // let the enqueue pool process and return an error + mp.hold <- struct{}{} + // if errored, should not process the callback on the job + // This is based on the mockPool EnqueueBacklog behavior + require.False(t, mj.processed) + // the service should end + sv.WaitForStop() + + /***************************************************/ + // error adding to the pool when < txnPerWorksetThreshold + /***************************************************/ + // Case where the timer ticks + sv.Start(ctx) + mj.numberOfItems = txnPerWorksetThreshold - 1 + inputChan <- &mj + // let the enqueue pool process and return an error + mp.hold <- struct{}{} + require.False(t, mj.processed) + // the service should end + sv.WaitForStop() + + /***************************************************/ + // error adding to the pool when <= batchSizeBlockLimit + /***************************************************/ + // Case where the timer ticks + sv.Start(ctx) + mj.numberOfItems = batchSizeBlockLimit + inputChan <- &mj + // let the enqueue pool process and return an error + mp.hold <- struct{}{} + require.False(t, mj.processed) + // the service should end + sv.WaitForStop() + + /***************************************************/ + // error adding to the pool when > batchSizeBlockLimit + /***************************************************/ + // Case where the timer ticks + sv.Start(ctx) + mj.numberOfItems = batchSizeBlockLimit + 1 + inputChan <- &mj + // let the enqueue pool process and return an error + mp.hold <- struct{}{} + require.False(t, mj.processed) + // the service should end + sv.WaitForStop() +} + +// TestPendingJobOnRestart makes sure a pending job in the exec pool is cancled +// when the Stream ctx is cancled, and a now one started with a new ctx +func TestPendingJobOnRestart(t *testing.T) { + partitiontest.PartitionTest(t) + + mp := mockPool{ + hold: make(chan struct{}), + poolCapacity: make(chan struct{}, 2), + asyncDelay: make(chan struct{}), + } + + ctx, cancel := context.WithCancel(context.Background()) + inputChan := make(chan InputJob) + mbp := mockBatchProcessor{ + notify: make(chan struct{}, 1), + } + sv := MakeStreamToBatch(inputChan, &mp, &mbp) + + // start with a saturated pool so that the job will not go through before + // the ctx is cancled + mp.poolCapacity <- struct{}{} + + sv.Start(ctx) + mj := mockJob{ + numberOfItems: 1, + } + inputChan <- &mj + // wait for the job to be submitted to the exec pool, waiting for capacity + mp.hold <- struct{}{} + + // now the job should be waiting in the exec pool queue waiting to be executed + + // cancel the ctx + cancel() + // make sure EnqueueBacklog has returned and the stream can terminate + sv.WaitForStop() + + // start a new session + ctx, cancel = context.WithCancel(context.Background()) + sv.Start(ctx) + + // submit a new job + callbackFeedback := make(chan struct{}, 1) + mjNew := mockJob{ + numberOfItems: 1, + callback: func(id int) { + callbackFeedback <- struct{}{} + }, + } + inputChan <- &mjNew + mp.hold <- struct{}{} + <-mp.poolCapacity + + // when the exec pool tries to execute the jobs, + // the function in addBatchToThePoolNow should abort the old and process the new + <-mp.asyncDelay + <-mp.asyncDelay + + // wait for the notifiation from cleanup before checking the TestPendingJobOnRestart + <-mbp.notify + require.Error(t, mj.returnError) + require.False(t, mj.processed) + + <-callbackFeedback + require.True(t, mjNew.processed) + + cancel() + sv.WaitForStop() +} From c556cfc41894a3e5d52850cf20de8b89ff4d5ba0 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Mon, 13 Mar 2023 14:11:57 -0400 Subject: [PATCH 005/125] catchpoints: small tweaks, mostly to comments (#5195) --- crypto/merkletrie/cache.go | 26 ++++---- crypto/merkletrie/cache_test.go | 4 +- crypto/merkletrie/committer.go | 2 +- crypto/merkletrie/committer_test.go | 10 +-- crypto/merkletrie/node.go | 20 +++--- crypto/merkletrie/node_test.go | 100 ++++++++++++++++++++++++---- crypto/merkletrie/trie.go | 50 +++++++------- ledger/store/merkle_committer.go | 75 +++++++++++++++++++++ 8 files changed, 217 insertions(+), 70 deletions(-) create mode 100644 ledger/store/merkle_committer.go diff --git a/crypto/merkletrie/cache.go b/crypto/merkletrie/cache.go index 208397edb9..01648d73a6 100644 --- a/crypto/merkletrie/cache.go +++ b/crypto/merkletrie/cache.go @@ -39,13 +39,13 @@ const ( // be found in neither the in-memory cache or on the persistent storage. var ErrLoadedPageMissingNode = errors.New("loaded page is missing a node") -// ErrPageDecodingFailuire is returned if the decoding of a page has failed. -var ErrPageDecodingFailuire = errors.New("error encountered while decoding page") +// ErrPageDecodingFailure is returned if the decoding of a page has failed. +var ErrPageDecodingFailure = errors.New("error encountered while decoding page") type merkleTrieCache struct { - // mt is a point to the originating trie + // mt is a pointer to the originating trie mt *Trie - // committer is the backing up storage for the cache. ( memory, database, etc. ) + // committer is the backing store for the cache. ( memory, database, etc. ) committer Committer // cachedNodeCount is the number of currently cached, in-memory, nodes stored in the pageToNIDsPtr structure. cachedNodeCount int @@ -293,7 +293,7 @@ func (mtc *merkleTrieCache) beginTransaction() { mtc.txNextNodeID = mtc.mt.nextNodeID } -// commitTransaction - used internaly by the Trie +// commitTransaction - used internally by the Trie func (mtc *merkleTrieCache) commitTransaction() { // the created nodes are already on the list. for nodeID := range mtc.txCreatedNodeIDs { @@ -426,7 +426,7 @@ func (mtc *merkleTrieCache) commit() (CommitStats, error) { // reallocatePendingPages is called by the commit() function, and is responsible for performing two tasks - // 1. calculate the hashes of all the newly created nodes -// 2. reornigize the pending flush nodes into an optimal page list, and construct a list of pages that need to be created, deleted and updated. +// 2. reorganize the pending flush nodes into an optimal page list, and construct a list of pages that need to be created, deleted and updated. func (mtc *merkleTrieCache) reallocatePendingPages(stats *CommitStats) (pagesToCreate []uint64, pagesToDelete map[uint64]bool, pagesToUpdate map[uint64]map[storedNodeIdentifier]*node, err error) { // newPageThreshold is the threshold at which all the pages are newly created pages that were never committed. newPageThreshold := uint64(mtc.mt.lastCommittedNodeID) / uint64(mtc.nodesPerPage) @@ -669,26 +669,26 @@ func (mtc *merkleTrieCache) reallocateNode(nid storedNodeIdentifier) storedNodeI func decodePage(bytes []byte) (nodesMap map[storedNodeIdentifier]*node, err error) { version, versionLength := binary.Uvarint(bytes[:]) if versionLength <= 0 { - return nil, ErrPageDecodingFailuire + return nil, ErrPageDecodingFailure } - if version != NodePageVersion { - return nil, ErrPageDecodingFailuire + if version != nodePageVersion { + return nil, ErrPageDecodingFailure } nodesCount, nodesCountLength := binary.Varint(bytes[versionLength:]) if nodesCountLength <= 0 { - return nil, ErrPageDecodingFailuire + return nil, ErrPageDecodingFailure } nodesMap = make(map[storedNodeIdentifier]*node) walk := nodesCountLength + versionLength for i := int64(0); i < nodesCount; i++ { nodeID, nodesIDLength := binary.Uvarint(bytes[walk:]) if nodesIDLength <= 0 { - return nil, ErrPageDecodingFailuire + return nil, ErrPageDecodingFailure } walk += nodesIDLength pnode, nodeLength := deserializeNode(bytes[walk:]) if nodeLength <= 0 { - return nil, ErrPageDecodingFailuire + return nil, ErrPageDecodingFailure } walk += nodeLength nodesMap[storedNodeIdentifier(nodeID)] = pnode @@ -699,7 +699,7 @@ func decodePage(bytes []byte) (nodesMap map[storedNodeIdentifier]*node, err erro // decodePage encodes a page contents into a byte array func (mtc *merkleTrieCache) encodePage(nodeIDs map[storedNodeIdentifier]*node, serializedBuffer []byte) []byte { - version := binary.PutUvarint(serializedBuffer[:], NodePageVersion) + version := binary.PutUvarint(serializedBuffer[:], nodePageVersion) length := binary.PutVarint(serializedBuffer[version:], int64(len(nodeIDs))) walk := version + length for nodeID, pnode := range nodeIDs { diff --git a/crypto/merkletrie/cache_test.go b/crypto/merkletrie/cache_test.go index d9c7a23e30..5967f01145 100644 --- a/crypto/merkletrie/cache_test.go +++ b/crypto/merkletrie/cache_test.go @@ -298,7 +298,7 @@ func (mt *Trie) TestDeleteRollback(d []byte) (bool, error) { if err != nil { return false, err } - found, err := pnode.find(mt.cache, d[:]) + found, err := pnode.find(&mt.cache, d[:]) if !found || err != nil { return false, err } @@ -311,7 +311,7 @@ func (mt *Trie) TestDeleteRollback(d []byte) (bool, error) { mt.elementLength = 0 return true, nil } - _, err = pnode.remove(mt.cache, d[:], make([]byte, 0, len(d))) + _, err = pnode.remove(&mt.cache, d[:], make([]byte, 0, len(d))) // unlike the "real" function, we want always to fail here to test the rollbackTransaction() functionality. mt.cache.rollbackTransaction() return false, fmt.Errorf("this is a test for failing a Delete request") diff --git a/crypto/merkletrie/committer.go b/crypto/merkletrie/committer.go index bad5fe7e0a..5e5ae758ab 100644 --- a/crypto/merkletrie/committer.go +++ b/crypto/merkletrie/committer.go @@ -26,7 +26,7 @@ const ( inMemoryCommitterPageSize = int64(512) ) -// InMemoryCommitter is a fully function in-memory committer, supporting +// InMemoryCommitter is a fully functional in-memory committer, supporting // persistence of pages. type InMemoryCommitter struct { memStore map[uint64][]byte diff --git a/crypto/merkletrie/committer_test.go b/crypto/merkletrie/committer_test.go index 6f8dfb2a78..c8bfd71435 100644 --- a/crypto/merkletrie/committer_test.go +++ b/crypto/merkletrie/committer_test.go @@ -140,18 +140,18 @@ func TestNoRedundentPages(t *testing.T) { require.Equal(t, nodesCount, mt1.cache.cachedNodeCount) } -// decodePage decodes a byte array into a page content +// decodePageHeaderSize decodes a page header at the start of a byte array func decodePageHeaderSize(bytes []byte) (headerSize int, err error) { version, versionLength := binary.Uvarint(bytes[:]) if versionLength <= 0 { - return 0, ErrPageDecodingFailuire + return 0, ErrPageDecodingFailure } - if version != NodePageVersion { - return 0, ErrPageDecodingFailuire + if version != nodePageVersion { + return 0, ErrPageDecodingFailure } _, nodesCountLength := binary.Varint(bytes[versionLength:]) if nodesCountLength <= 0 { - return 0, ErrPageDecodingFailuire + return 0, ErrPageDecodingFailure } return nodesCountLength + versionLength, nil } diff --git a/crypto/merkletrie/node.go b/crypto/merkletrie/node.go index 33c2f673ab..de765793dd 100644 --- a/crypto/merkletrie/node.go +++ b/crypto/merkletrie/node.go @@ -140,8 +140,9 @@ func (n *node) add(cache *merkleTrieCache, d []byte, path []byte) (nodeID stored } pnode.hash = append(path, d[:idiff]...) + // create ancestors from pnode up to the new split for i := idiff - 1; i >= 0; i-- { - // create a parent node for pnode. + // create a parent node for pnode, and move up pnode2, nodeID2 := cache.allocateNewNode() pnode2.childrenMask.SetBit(d[i]) pnode2.children = []childEntry{ @@ -152,7 +153,6 @@ func (n *node) add(cache *merkleTrieCache, d []byte, path []byte) (nodeID stored } pnode2.hash = append(path, d[:i]...) - pnode = pnode2 nodeID = nodeID2 } return nodeID, nil @@ -160,16 +160,14 @@ func (n *node) add(cache *merkleTrieCache, d []byte, path []byte) (nodeID stored if n.childrenMask.Bit(d[0]) == false { // no such child. - var childNode *node - var childNodeID storedNodeIdentifier - childNode, childNodeID = cache.allocateNewNode() + childNode, childNodeID := cache.allocateNewNode() childNode.hash = d[1:] pnode, nodeID = cache.allocateNewNode() pnode.childrenMask = n.childrenMask pnode.childrenMask.SetBit(d[0]) - pnode.children = make([]childEntry, len(n.children)+1, len(n.children)+1) + pnode.children = make([]childEntry, len(n.children)+1) if d[0] > n.children[len(n.children)-1].hashIndex { // the new entry comes after all the existing ones. for i, child := range n.children { @@ -183,8 +181,8 @@ func (n *node) add(cache *merkleTrieCache, d []byte, path []byte) (nodeID stored for i, child := range n.children { if d[0] < child.hashIndex { pnode.children[i] = childEntry{ - hashIndex: d[0], id: childNodeID, + hashIndex: d[0], } // copy the rest of the items. for ; i < len(n.children); i++ { @@ -211,7 +209,7 @@ func (n *node) add(cache *merkleTrieCache, d []byte, path []byte) (nodeID stored pnode, nodeID = childNode, cache.refurbishNode(curNodeID) pnode.childrenMask = n.childrenMask if len(pnode.children) < len(n.children) { - pnode.children = make([]childEntry, len(n.children), len(n.children)) + pnode.children = make([]childEntry, len(n.children)) } else { pnode.children = pnode.children[:len(n.children)] } @@ -270,7 +268,7 @@ func (n *node) remove(cache *merkleTrieCache, key []byte, path []byte) (nodeID s pnode, nodeID = childNode, cache.refurbishNode(childNodeID) pnode.childrenMask = n.childrenMask // we are guaranteed to have other children, because our tree forbids nodes that have exactly one leaf child and no other children. - pnode.children = make([]childEntry, len(n.children)-1, len(n.children)-1) + pnode.children = make([]childEntry, len(n.children)-1) copy(pnode.children, append(n.children[:childIndex], n.children[childIndex+1:]...)) pnode.childrenMask.ClearBit(key[0]) } else { @@ -283,7 +281,7 @@ func (n *node) remove(cache *merkleTrieCache, key []byte, path []byte) (nodeID s pnode, nodeID = childNode, cache.refurbishNode(childNodeID) pnode.childrenMask = n.childrenMask if len(pnode.children) < len(n.children) { - pnode.children = make([]childEntry, len(n.children), len(n.children)) + pnode.children = make([]childEntry, len(n.children)) } else { pnode.children = pnode.children[:len(n.children)] } @@ -371,7 +369,7 @@ func deserializeNode(buf []byte) (n *node, s int) { prevChildIndex = childIndex i++ } - n.children = make([]childEntry, i, i) + n.children = make([]childEntry, i) copy(n.children, childEntries[:i]) return } diff --git a/crypto/merkletrie/node_test.go b/crypto/merkletrie/node_test.go index 1495a8e9c0..893fbc86b2 100644 --- a/crypto/merkletrie/node_test.go +++ b/crypto/merkletrie/node_test.go @@ -17,6 +17,8 @@ package merkletrie import ( + "crypto/sha512" + "encoding/binary" "testing" "github.com/stretchr/testify/require" @@ -67,17 +69,24 @@ func (n *node) leafUsingChildrenLength() bool { return len(n.children) == 0 } +func makeHashes(n int) [][]byte { + hashes := make([][]byte, n) + for i := 0; i < len(hashes); i++ { + buf := make([]byte, 32) + binary.BigEndian.PutUint64(buf, uint64(i)) + h := crypto.Hash(buf) + hashes[i] = h[:] + } + return hashes +} + func BenchmarkNodeLeafImplementation(b *testing.B) { + hashes := makeHashes(100000) + b.Run("leaf-ChildrenMask", func(b *testing.B) { var memoryCommitter InMemoryCommitter memConfig := defaultTestMemoryConfig mt1, _ := MakeTrie(&memoryCommitter, memConfig) - // create 100000 hashes. - leafsCount := 100000 - hashes := make([]crypto.Digest, leafsCount) - for i := 0; i < len(hashes); i++ { - hashes[i] = crypto.Hash([]byte{byte(i % 256), byte((i / 256) % 256), byte(i / 65536)}) - } for i := 0; i < len(hashes); i++ { mt1.Add(hashes[i][:]) @@ -100,12 +109,6 @@ func BenchmarkNodeLeafImplementation(b *testing.B) { var memoryCommitter InMemoryCommitter memConfig := defaultTestMemoryConfig mt1, _ := MakeTrie(&memoryCommitter, memConfig) - // create 100000 hashes. - leafsCount := 100000 - hashes := make([]crypto.Digest, leafsCount) - for i := 0; i < len(hashes); i++ { - hashes[i] = crypto.Hash([]byte{byte(i % 256), byte((i / 256) % 256), byte(i / 65536)}) - } for i := 0; i < len(hashes); i++ { mt1.Add(hashes[i][:]) @@ -125,3 +128,76 @@ func BenchmarkNodeLeafImplementation(b *testing.B) { } }) } + +// calculateHashIncrementally uses the Writer interface to the crypto digest to +// avoid accumulating in a buffer. Yet it's slower! I don't know why, but +// leaving it here to benchmark more carefully later. (The final use of +// d.Sum(nil) instead of d.Sum(n.hash[:0]) is needed because we share the +// backing array for the slices in node hashes. But that is not the cause of the +// slow down.) +func (n *node) calculateHashIncrementally(cache *merkleTrieCache) error { + if n.leaf() { + return nil + } + path := n.hash + + d := sha512.New512_256() + + // we add this string length before the actual string so it could get "decoded"; in practice, it makes a good domain separator. + d.Write([]byte{byte(len(path))}) + d.Write(path) + for _, child := range n.children { + childNode, err := cache.getNode(child.id) + if err != nil { + return err + } + if childNode.leaf() { + d.Write([]byte{0}) + } else { + d.Write([]byte{1}) + } + // we add this string length before the actual string so it could get "decoded"; in practice, it makes a good domain separator. + d.Write([]byte{byte(len(childNode.hash))}) + d.Write([]byte{child.hashIndex}) // adding the first byte of the child + d.Write(childNode.hash) // adding the reminder of the child + } + n.hash = d.Sum(nil) + return nil +} + +func BenchmarkAdd(b *testing.B) { + b.ReportAllocs() + + memConfig := defaultTestMemoryConfig + mt, _ := MakeTrie(&InMemoryCommitter{}, memConfig) + hashes := makeHashes(b.N) + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + mt.Add(hashes[i]) + if i%1000 == 999 { + mt.Commit() // not sure how often we should Commit for a nice benchmark + } + } +} + +func BenchmarkDelete(b *testing.B) { + b.ReportAllocs() + + memConfig := defaultTestMemoryConfig + mt, _ := MakeTrie(&InMemoryCommitter{}, memConfig) + hashes := makeHashes(b.N) + for i := 0; i < b.N; i++ { + mt.Add(hashes[i]) + } + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + mt.Delete(hashes[i]) + if i%1000 == 999 { // not sure how often we should Commit for a nice benchmark + mt.Commit() + } + } +} diff --git a/crypto/merkletrie/trie.go b/crypto/merkletrie/trie.go index 7a214e7097..39051699f3 100644 --- a/crypto/merkletrie/trie.go +++ b/crypto/merkletrie/trie.go @@ -24,16 +24,16 @@ import ( ) const ( - // MerkleTreeVersion is the version of the encoded trie. If we ever want to make changes and want to have upgrade path, + // merkleTreeVersion is the version of the encoded trie. If we ever want to make changes and want to have upgrade path, // this would give us the ability to do so. - MerkleTreeVersion = uint64(0x1000000010000000) - // NodePageVersion is the version of the encoded node. If we ever want to make changes and want to have upgrade path, + merkleTreeVersion = uint64(0x1000000010000000) + // nodePageVersion is the version of the encoded node. If we ever want to make changes and want to have upgrade path, // this would give us the ability to do so. - NodePageVersion = uint64(0x1000000010000000) + nodePageVersion = uint64(0x1000000010000000) ) -// ErrRootPageDecodingFailuire is returned if the decoding the root page has failed. -var ErrRootPageDecodingFailuire = errors.New("error encountered while decoding root page") +// ErrRootPageDecodingFailure is returned if the decoding the root page has failed. +var ErrRootPageDecodingFailure = errors.New("error encountered while decoding root page") // ErrMismatchingElementLength is returned when an element is being added/removed from the trie that doesn't align with the trie's previous elements length var ErrMismatchingElementLength = errors.New("mismatching element length") @@ -63,7 +63,7 @@ type Trie struct { root storedNodeIdentifier nextNodeID storedNodeIdentifier lastCommittedNodeID storedNodeIdentifier - cache *merkleTrieCache + cache merkleTrieCache elementLength int } @@ -79,7 +79,7 @@ type Stats struct { func MakeTrie(committer Committer, memoryConfig MemoryConfig) (*Trie, error) { mt := &Trie{ root: storedNodeIdentifierNull, - cache: &merkleTrieCache{}, + cache: merkleTrieCache{}, nextNodeID: storedNodeIdentifierBase, lastCommittedNodeID: storedNodeIdentifierBase, } @@ -106,11 +106,9 @@ func MakeTrie(committer Committer, memoryConfig MemoryConfig) (*Trie, error) { return mt, nil } -// SetCommitter set the provided committter as the current committer, and return the old one. -func (mt *Trie) SetCommitter(committer Committer) (prevCommitter Committer) { - prevCommitter = mt.cache.committer +// SetCommitter sets the provided committer as the current committer +func (mt *Trie) SetCommitter(committer Committer) { mt.cache.committer = committer - return } // RootHash returns the root hash of all the elements in the trie @@ -154,13 +152,13 @@ func (mt *Trie) Add(d []byte) (bool, error) { if err != nil { return false, err } - found, err := pnode.find(mt.cache, d[:]) + found, err := pnode.find(&mt.cache, d[:]) if found || (err != nil) { return false, err } mt.cache.beginTransaction() var updatedRoot storedNodeIdentifier - updatedRoot, err = pnode.add(mt.cache, d[:], make([]byte, 0, len(d))) + updatedRoot, err = pnode.add(&mt.cache, d[:], make([]byte, 0, len(d))) if err != nil { mt.cache.rollbackTransaction() return false, err @@ -184,7 +182,7 @@ func (mt *Trie) Delete(d []byte) (bool, error) { if err != nil { return false, err } - found, err := pnode.find(mt.cache, d[:]) + found, err := pnode.find(&mt.cache, d[:]) if !found || err != nil { return false, err } @@ -198,7 +196,7 @@ func (mt *Trie) Delete(d []byte) (bool, error) { return true, nil } var updatedRoot storedNodeIdentifier - updatedRoot, err = pnode.remove(mt.cache, d[:], make([]byte, 0, len(d))) + updatedRoot, err = pnode.remove(&mt.cache, d[:], make([]byte, 0, len(d))) if err != nil { mt.cache.rollbackTransaction() return false, err @@ -218,7 +216,7 @@ func (mt *Trie) GetStats() (stats Stats, err error) { if err != nil { return Stats{}, err } - err = pnode.stats(mt.cache, &stats, 1) + err = pnode.stats(&mt.cache, &stats, 1) return } @@ -252,7 +250,7 @@ func (mt *Trie) Evict(commit bool) (int, error) { // serialize serializes the trie root func (mt *Trie) serialize() []byte { serializedBuffer := make([]byte, 5*binary.MaxVarintLen64) // allocate the worst-case scenario for the trie header. - version := binary.PutUvarint(serializedBuffer[:], MerkleTreeVersion) + version := binary.PutUvarint(serializedBuffer[:], merkleTreeVersion) root := binary.PutUvarint(serializedBuffer[version:], uint64(mt.root)) next := binary.PutUvarint(serializedBuffer[version+root:], uint64(mt.nextNodeID)) elementLength := binary.PutUvarint(serializedBuffer[version+root+next:], uint64(mt.elementLength)) @@ -260,30 +258,30 @@ func (mt *Trie) serialize() []byte { return serializedBuffer[:version+root+next+elementLength+pageSizeLength] } -// serialize serializes the trie root +// deserialize deserializes the trie root func (mt *Trie) deserialize(bytes []byte) (int64, error) { version, versionLen := binary.Uvarint(bytes[:]) if versionLen <= 0 { - return 0, ErrRootPageDecodingFailuire + return 0, ErrRootPageDecodingFailure } - if version != MerkleTreeVersion { - return 0, ErrRootPageDecodingFailuire + if version != merkleTreeVersion { + return 0, ErrRootPageDecodingFailure } root, rootLen := binary.Uvarint(bytes[versionLen:]) if rootLen <= 0 { - return 0, ErrRootPageDecodingFailuire + return 0, ErrRootPageDecodingFailure } nextNodeID, nextNodeIDLen := binary.Uvarint(bytes[versionLen+rootLen:]) if nextNodeIDLen <= 0 { - return 0, ErrRootPageDecodingFailuire + return 0, ErrRootPageDecodingFailure } elemLength, elemLengthLength := binary.Uvarint(bytes[versionLen+rootLen+nextNodeIDLen:]) if elemLengthLength <= 0 { - return 0, ErrRootPageDecodingFailuire + return 0, ErrRootPageDecodingFailure } pageSize, pageSizeLength := binary.Uvarint(bytes[versionLen+rootLen+nextNodeIDLen+elemLengthLength:]) if pageSizeLength <= 0 { - return 0, ErrRootPageDecodingFailuire + return 0, ErrRootPageDecodingFailure } mt.root = storedNodeIdentifier(root) mt.nextNodeID = storedNodeIdentifier(nextNodeID) diff --git a/ledger/store/merkle_committer.go b/ledger/store/merkle_committer.go new file mode 100644 index 0000000000..bc7502dac6 --- /dev/null +++ b/ledger/store/merkle_committer.go @@ -0,0 +1,75 @@ +// Copyright (C) 2019-2023 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package store + +import "database/sql" + +// MerkleCommitter allows storing and loading merkletrie pages from a sqlite database. +// +//msgp:ignore MerkleCommitter +type MerkleCommitter struct { + tx *sql.Tx + deleteStmt *sql.Stmt + insertStmt *sql.Stmt + selectStmt *sql.Stmt +} + +// MakeMerkleCommitter creates a MerkleCommitter object that implements the merkletrie.Committer interface allowing storing and loading +// merkletrie pages from a sqlite database. +func MakeMerkleCommitter(tx *sql.Tx, staging bool) (mc *MerkleCommitter, err error) { + mc = &MerkleCommitter{tx: tx} + accountHashesTable := "accounthashes" + if staging { + accountHashesTable = "catchpointaccounthashes" + } + mc.deleteStmt, err = tx.Prepare("DELETE FROM " + accountHashesTable + " WHERE id=?") + if err != nil { + return nil, err + } + mc.insertStmt, err = tx.Prepare("INSERT OR REPLACE INTO " + accountHashesTable + "(id, data) VALUES(?, ?)") + if err != nil { + return nil, err + } + mc.selectStmt, err = tx.Prepare("SELECT data FROM " + accountHashesTable + " WHERE id = ?") + if err != nil { + return nil, err + } + return mc, nil +} + +// StorePage is the merkletrie.Committer interface implementation, stores a single page in a sqlite database table. +func (mc *MerkleCommitter) StorePage(page uint64, content []byte) error { + if len(content) == 0 { + _, err := mc.deleteStmt.Exec(page) + return err + } + _, err := mc.insertStmt.Exec(page, content) + return err +} + +// LoadPage is the merkletrie.Committer interface implementation, load a single page from a sqlite database table. +func (mc *MerkleCommitter) LoadPage(page uint64) (content []byte, err error) { + err = mc.selectStmt.QueryRow(page).Scan(&content) + if err == sql.ErrNoRows { + content = nil + err = nil + return + } else if err != nil { + return nil, err + } + return content, nil +} From 942eee15737705673b6d9b52272a2466a8f52de4 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Tue, 14 Mar 2023 14:24:42 -0400 Subject: [PATCH 006/125] txHandler: make dedup working set independent from ERL (#5200) --- config/config.go | 2 +- data/txHandler.go | 41 +++++++++++---------- data/txHandler_test.go | 62 +++++++++++++++++++++++--------- ledger/catchpointtracker_test.go | 1 + 4 files changed, 69 insertions(+), 37 deletions(-) diff --git a/config/config.go b/config/config.go index a180a9a479..1da9dd0597 100644 --- a/config/config.go +++ b/config/config.go @@ -72,7 +72,7 @@ const StateProofFileName = "stateproof.sqlite" // It is used for tracking participation key metadata. const ParticipationRegistryFilename = "partregistry.sqlite" -// ConfigurableConsensusProtocolsFilename defines a set of consensus prototocols that +// ConfigurableConsensusProtocolsFilename defines a set of consensus protocols that // are to be loaded from the data directory ( if present ), to override the // built-in supported consensus protocols. const ConfigurableConsensusProtocolsFilename = "consensus.json" diff --git a/data/txHandler.go b/data/txHandler.go index 1d3578d3cf..3ad2716702 100644 --- a/data/txHandler.go +++ b/data/txHandler.go @@ -122,7 +122,6 @@ type TxHandler struct { net network.GossipNode msgCache *txSaltedCache txCanonicalCache *digestCache - cacheConfig txHandlerConfig ctx context.Context ctxCancel context.CancelFunc streamVerifier *execpool.StreamToBatch @@ -142,12 +141,6 @@ type TxHandlerOpts struct { Config config.Local } -// txHandlerConfig is a subset of tx handler related options from config.Local -type txHandlerConfig struct { - enableFilteringRawMsg bool - enableFilteringCanonical bool -} - // MakeTxHandler makes a new handler for transaction messages func MakeTxHandler(opts TxHandlerOpts) (*TxHandler, error) { @@ -174,13 +167,19 @@ func MakeTxHandler(opts TxHandlerOpts) (*TxHandler, error) { backlogQueue: make(chan *txBacklogMsg, txBacklogSize), postVerificationQueue: make(chan *verify.VerificationResult, txBacklogSize), net: opts.Net, - msgCache: makeSaltedCache(2 * txBacklogSize), - txCanonicalCache: makeDigestCache(2 * txBacklogSize), - cacheConfig: txHandlerConfig{opts.Config.TxFilterRawMsgEnabled(), opts.Config.TxFilterCanonicalEnabled()}, streamVerifierChan: make(chan execpool.InputJob), streamVerifierDropped: make(chan *verify.UnverifiedTxnSigJob), } + // use defaultBacklogSize = approx number of txns in a full block as a parameter for the dedup cache size + defaultBacklogSize := config.GetDefaultLocal().TxBacklogSize + if opts.Config.TxFilterRawMsgEnabled() { + handler.msgCache = makeSaltedCache(2 * defaultBacklogSize) + } + if opts.Config.TxFilterCanonicalEnabled() { + handler.txCanonicalCache = makeDigestCache(2 * defaultBacklogSize) + } + if opts.Config.EnableTxBacklogRateLimiting { rateLimiter := util.NewElasticRateLimiter( txBacklogSize, @@ -191,7 +190,7 @@ func MakeTxHandler(opts TxHandlerOpts) (*TxHandler, error) { handler.erl = rateLimiter } - // prepare the transaction stream verifer + // prepare the transaction stream verifier var err error txnElementProcessor, err := verify.MakeSigVerifyJobProcessor(handler.ledger, handler.ledger.VerifiedTransactionCache(), handler.postVerificationQueue, handler.streamVerifierDropped) @@ -219,7 +218,9 @@ func (handler *TxHandler) droppedTxnWatcher() { // Start enables the processing of incoming messages at the transaction handler func (handler *TxHandler) Start() { handler.ctx, handler.ctxCancel = context.WithCancel(context.Background()) - handler.msgCache.Start(handler.ctx, 60*time.Second) + if handler.msgCache != nil { + handler.msgCache.Start(handler.ctx, 60*time.Second) + } handler.net.RegisterHandlers([]network.TaggedMessageHandler{ {Tag: protocol.TxnTag, MessageHandler: network.HandlerFunc(handler.processIncomingTxn)}, }) @@ -240,7 +241,9 @@ func (handler *TxHandler) Stop() { } handler.backlogWg.Wait() handler.streamVerifier.WaitForStop() - handler.msgCache.WaitForStop() + if handler.msgCache != nil { + handler.msgCache.WaitForStop() + } } func reencode(stxns []transactions.SignedTxn) []byte { @@ -497,11 +500,11 @@ func (handler *TxHandler) postProcessCheckedTxn(wi *txBacklogMsg) { } func (handler *TxHandler) deleteFromCaches(msgKey *crypto.Digest, canonicalKey *crypto.Digest) { - if handler.cacheConfig.enableFilteringCanonical && canonicalKey != nil { + if handler.txCanonicalCache != nil && canonicalKey != nil { handler.txCanonicalCache.Delete(canonicalKey) } - if handler.cacheConfig.enableFilteringRawMsg && msgKey != nil { + if handler.msgCache != nil && msgKey != nil { handler.msgCache.DeleteByKey(msgKey) } } @@ -561,7 +564,7 @@ func (handler *TxHandler) dedupCanonical(ntx int, unverifiedTxGroup []transactio func (handler *TxHandler) processIncomingTxn(rawmsg network.IncomingMessage) network.OutgoingMessage { var msgKey *crypto.Digest var isDup bool - if handler.cacheConfig.enableFilteringRawMsg { + if handler.msgCache != nil { // check for duplicate messages // this helps against relaying duplicates if msgKey, isDup = handler.msgCache.CheckAndPut(rawmsg.Data); isDup { @@ -632,7 +635,7 @@ func (handler *TxHandler) processIncomingTxn(rawmsg network.IncomingMessage) net } var canonicalKey *crypto.Digest - if handler.cacheConfig.enableFilteringCanonical { + if handler.txCanonicalCache != nil { if canonicalKey, isDup = handler.dedupCanonical(ntx, unverifiedTxGroup, consumed); isDup { transactionMessagesDupCanonical.Inc(nil) return network.OutgoingMessage{Action: network.Ignore} @@ -653,10 +656,10 @@ func (handler *TxHandler) processIncomingTxn(rawmsg network.IncomingMessage) net transactionMessagesDroppedFromBacklog.Inc(nil) // additionally, remove the txn from duplicate caches to ensure it can be re-submitted - if canonicalKey != nil { + if handler.txCanonicalCache != nil && canonicalKey != nil { handler.txCanonicalCache.Delete(canonicalKey) } - if msgKey != nil { + if handler.msgCache != nil && msgKey != nil { handler.msgCache.DeleteByKey(msgKey) } } diff --git a/data/txHandler_test.go b/data/txHandler_test.go index f0ea2d5b40..f51290a374 100644 --- a/data/txHandler_test.go +++ b/data/txHandler_test.go @@ -55,13 +55,19 @@ import ( ) // txHandler uses config values to determine backlog size. Tests should use a static value -var txBacklogSize = 26000 +var txBacklogSize = config.GetDefaultLocal().TxBacklogSize // mock sender is used to implement OnClose, since TXHandlers expect to use Senders and ERL Clients type mockSender struct{} func (m mockSender) OnClose(func()) {} +// txHandlerConfig is a subset of tx handler related options from config.Local +type txHandlerConfig struct { + enableFilteringRawMsg bool + enableFilteringCanonical bool +} + func makeTestGenesisAccounts(tb testing.TB, numUsers int) ([]basics.Address, []*crypto.SignatureSecrets, map[basics.Address]basics.AccountData) { addresses := make([]basics.Address, numUsers) secrets := make([]*crypto.SignatureSecrets, numUsers) @@ -529,12 +535,25 @@ func BenchmarkTxHandlerIncDeDup(b *testing.B) { numPoolWorkers := runtime.NumCPU() dupFactor := test.dupFactor avgDelay := test.workerDelay / time.Duration(numPoolWorkers) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() - handler := makeTestTxHandlerOrphaned(txBacklogSize) + var handler *TxHandler if test.firstLevelOnly { - handler.cacheConfig = txHandlerConfig{enableFilteringRawMsg: true, enableFilteringCanonical: false} + handler = makeTestTxHandlerOrphanedWithContext( + ctx, txBacklogSize, txBacklogSize, + txHandlerConfig{enableFilteringRawMsg: true, enableFilteringCanonical: false}, 0, + ) } else if !test.dedup { - handler.cacheConfig = txHandlerConfig{} + handler = makeTestTxHandlerOrphanedWithContext( + ctx, txBacklogSize, 0, + txHandlerConfig{}, 0, + ) + } else { + handler = makeTestTxHandlerOrphanedWithContext( + ctx, txBacklogSize, txBacklogSize, + txHandlerConfig{enableFilteringRawMsg: true, enableFilteringCanonical: true}, 0, + ) } // prepare tx groups @@ -783,12 +802,17 @@ func makeTestTxHandlerOrphanedWithContext(ctx context.Context, backlogSize int, cacheSize = txBacklogSize } handler := &TxHandler{ - backlogQueue: make(chan *txBacklogMsg, backlogSize), - msgCache: makeSaltedCache(cacheSize), - txCanonicalCache: makeDigestCache(cacheSize), - cacheConfig: txHandlerConfig, + backlogQueue: make(chan *txBacklogMsg, backlogSize), + } + + if txHandlerConfig.enableFilteringRawMsg { + handler.msgCache = makeSaltedCache(cacheSize) + handler.msgCache.Start(ctx, refreshInterval) } - handler.msgCache.Start(ctx, refreshInterval) + if txHandlerConfig.enableFilteringCanonical { + handler.txCanonicalCache = makeDigestCache(cacheSize) + } + return handler } @@ -892,6 +916,8 @@ func TestTxHandlerProcessIncomingCacheRotation(t *testing.T) { t.Run("scheduled", func(t *testing.T) { // double enqueue a single txn message, ensure it discarded ctx, cancelFunc := context.WithCancel(context.Background()) + defer cancelFunc() + handler := makeTestTxHandlerOrphanedWithContext(ctx, txBacklogSize, txBacklogSize, txHandlerConfig{true, true}, 10*time.Millisecond) var action network.OutgoingMessage @@ -907,12 +933,15 @@ func TestTxHandlerProcessIncomingCacheRotation(t *testing.T) { msg = <-handler.backlogQueue require.Equal(t, 1, len(msg.unverifiedTxGroup)) require.Equal(t, stxns1[0], msg.unverifiedTxGroup[0]) - cancelFunc() }) t.Run("manual", func(t *testing.T) { // double enqueue a single txn message, ensure it discarded - handler := makeTestTxHandlerOrphaned(txBacklogSize) + ctx, cancelFunc := context.WithCancel(context.Background()) + defer cancelFunc() + + handler := makeTestTxHandlerOrphanedWithContext(ctx, txBacklogSize, txBacklogSize, txHandlerConfig{true, true}, 10*time.Millisecond) + var action network.OutgoingMessage var msg *txBacklogMsg @@ -1684,17 +1713,16 @@ func runHandlerBenchmarkWithBacklog(b *testing.B, txGen txGenIf, tps int, useBac cfg.IncomingConnectionsLimit = 10 ledger := txGen.makeLedger(b, cfg, log, fmt.Sprintf("%s-%d", b.Name(), b.N)) defer ledger.Close() - handler, err := makeTestTxHandler(ledger, cfg) - require.NoError(b, err) - defer handler.txVerificationPool.Shutdown() - defer close(handler.streamVerifierDropped) // The benchmark generates only 1000 txns, and reuses them. This is done for faster benchmark time and the // ability to have long runs without being limited to the memory. The dedup will block the txns once the same // ones are rotated again. If the purpose is to test dedup, then this can be changed by setting // genTCount = b.N - handler.cacheConfig.enableFilteringRawMsg = false - handler.cacheConfig.enableFilteringCanonical = false + cfg.TxIncomingFilteringFlags = 0 + handler, err := makeTestTxHandler(ledger, cfg) + require.NoError(b, err) + defer handler.txVerificationPool.Shutdown() + defer close(handler.streamVerifierDropped) // since Start is not called, set the context here handler.ctx, handler.ctxCancel = context.WithCancel(context.Background()) diff --git a/ledger/catchpointtracker_test.go b/ledger/catchpointtracker_test.go index 0775d36dad..3ba865e1f9 100644 --- a/ledger/catchpointtracker_test.go +++ b/ledger/catchpointtracker_test.go @@ -467,6 +467,7 @@ func TestReproducibleCatchpointLabels(t *testing.T) { if (uint64(i) >= cfg.MaxAcctLookback) && (uint64(i)-cfg.MaxAcctLookback > protoParams.CatchpointLookback) && ((uint64(i)-cfg.MaxAcctLookback)%cfg.CatchpointInterval == 0) { ml.trackers.waitAccountsWriting() catchpointLabels[i] = ct.GetLastCatchpointLabel() + require.NotEmpty(t, catchpointLabels[i]) require.NotEqual(t, lastCatchpointLabel, catchpointLabels[i]) lastCatchpointLabel = catchpointLabels[i] ledgerHistory[i] = ml.fork(t) From ca04d83d4103e3a749b65699b63dfddd3c033d62 Mon Sep 17 00:00:00 2001 From: Hang Su <87964331+ahangsu@users.noreply.github.com> Date: Thu, 16 Mar 2023 14:56:17 -0400 Subject: [PATCH 007/125] Algod: Remove unused 404 resp in simulate endpoint (#5211) --- daemon/algod/api/algod.oas2.json | 3 --- daemon/algod/api/algod.oas3.yml | 4 ---- .../server/v2/generated/nonparticipating/public/routes.go | 7 +++---- 3 files changed, 3 insertions(+), 11 deletions(-) diff --git a/daemon/algod/api/algod.oas2.json b/daemon/algod/api/algod.oas2.json index ce40f9ab63..ed48f36663 100644 --- a/daemon/algod/api/algod.oas2.json +++ b/daemon/algod/api/algod.oas2.json @@ -1254,9 +1254,6 @@ "$ref": "#/definitions/ErrorResponse" } }, - "404": { - "description": "Transaction simulator not enabled" - }, "500": { "description": "Internal Error", "schema": { diff --git a/daemon/algod/api/algod.oas3.yml b/daemon/algod/api/algod.oas3.yml index 11435901dd..846f60a156 100644 --- a/daemon/algod/api/algod.oas3.yml +++ b/daemon/algod/api/algod.oas3.yml @@ -5803,10 +5803,6 @@ }, "description": "Invalid API Token" }, - "404": { - "content": {}, - "description": "Transaction simulator not enabled" - }, "500": { "content": { "application/json": { 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 7c89fbd8b4..c5e3638286 100644 --- a/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go +++ b/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go @@ -797,10 +797,9 @@ var swaggerSpec = []string{ "48ARsco6T17m6GHBwxCd2NlyZ/Oo9Ki1CRWGPBsjx+F7M2bfK3I86pUSDVLPa98ui5zmGYlj4UKUeZao", "Mk0h5qFxHPWaqpbaFOBwLCu7uQEhI1kpbWVsQlNdYg2aKebFFLbkFOXr5vWPtbuEDPJnEkoUm3OqSwlj", "uzb0FJsCQYPjJOI+07rcG9d0sPM1Stuo2Ed5gdtjcHsM/nzHoHPpvHVEMmupEixlhAT4pyoMUWeOO3Il", - "Ntq50f6sRSL61clus4W8TtXZ9Zdi+JTPi0+9mk/1WvFsXBGK9X5DTtDKpmgZA1WEacctp0DgnOYlclOX", - "L9694SfktOa1lZd4qVwa03RBGXfZa6p4BoRDu1TL2ud23JdCk17oFXf6TMs2UZFpsAFpKZle40OGFuy3", - "MzD/f29eArb+qH3jlDIfHY4WWheHBwdYsn8hlD4YfRyH31Tr4/sK/A/+eVJIdo4VfN5//P8BAAD//6G6", - "O0DCSQEA", + "Ntq50W7r0n/WBRU+5SPhU6/mU705PDNWhGLV3vA8t3Ii2uNNFWHa8bwpEDineYk80WV9dy/xCTmtOWbl", + "610ql4w0XVDGXQ6aKioB4dAuYbL2GRr3pZakF3rFnVbSMj9URxpsQFpKptf4HKEF++0MzP/fG3neVhG1", + "L5VS5qPD0ULr4vDgAAvvL4TSB6OP4/Cban18X4H/wT8yCsnOsQ7P+4//PwAA//+FikGliEkBAA==", } // GetSwagger returns the content of the embedded swagger specification file From 6cad66e41b38108ec86c7e5249df36bca7813030 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Fri, 17 Mar 2023 15:47:41 -0400 Subject: [PATCH 008/125] catchpointdump: fix panic when processing gzip files (#5210) --- cmd/catchpointdump/file.go | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/cmd/catchpointdump/file.go b/cmd/catchpointdump/file.go index 50ae82fe95..5661cf9ebb 100644 --- a/cmd/catchpointdump/file.go +++ b/cmd/catchpointdump/file.go @@ -167,31 +167,36 @@ func isGzipCompressed(catchpointReader *bufio.Reader, catchpointFileSize int64) return prefixBytes[0] == gzipPrefix[0] && prefixBytes[1] == gzipPrefix[1] } -func getCatchpointTarReader(catchpointReader *bufio.Reader, catchpointFileSize int64) (*tar.Reader, error) { +func getCatchpointTarReader(catchpointReader *bufio.Reader, catchpointFileSize int64) (*tar.Reader, bool, error) { if isGzipCompressed(catchpointReader, catchpointFileSize) { gzipReader, err := gzip.NewReader(catchpointReader) if err != nil { - return nil, err + return nil, false, err } - - return tar.NewReader(gzipReader), nil + return tar.NewReader(gzipReader), true, nil } - return tar.NewReader(catchpointReader), nil + return tar.NewReader(catchpointReader), false, nil } func loadCatchpointIntoDatabase(ctx context.Context, catchupAccessor ledger.CatchpointCatchupAccessor, catchpointFile io.Reader, catchpointFileSize int64) (fileHeader ledger.CatchpointFileHeader, err error) { fmt.Printf("\n") - printLoadCatchpointProgressLine(0, 50, 0) + const barLength = 50 + printLoadCatchpointProgressLine(0, barLength, 0) lastProgressUpdate := time.Now() progress := uint64(0) defer printLoadCatchpointProgressLine(0, 0, 0) catchpointReader := bufio.NewReader(catchpointFile) - tarReader, err := getCatchpointTarReader(catchpointReader, catchpointFileSize) + tarReader, isCompressed, err := getCatchpointTarReader(catchpointReader, catchpointFileSize) if err != nil { return fileHeader, err } + if isCompressed { + // gzip'ed file is about 3-6 times smaller than tar + // modify catchpointFileSize to make the progress bar more-less reflecting the state + catchpointFileSize = 4 * catchpointFileSize + } var downloadProgress ledger.CatchpointCatchupAccessorProgress for { @@ -229,7 +234,11 @@ func loadCatchpointIntoDatabase(ctx context.Context, catchupAccessor ledger.Catc } if time.Since(lastProgressUpdate) > 50*time.Millisecond && catchpointFileSize > 0 { lastProgressUpdate = time.Now() - printLoadCatchpointProgressLine(int(float64(progress)*50.0/float64(catchpointFileSize)), 50, int64(progress)) + progressRatio := int(float64(progress) * barLength / float64(catchpointFileSize)) + if progressRatio > barLength { + progressRatio = barLength + } + printLoadCatchpointProgressLine(progressRatio, barLength, int64(progress)) } } } From f4fa3cb37f99f374880237e2af073e61ff9199d6 Mon Sep 17 00:00:00 2001 From: AlgoAxel <113933518+AlgoAxel@users.noreply.github.com> Date: Fri, 17 Mar 2023 18:12:06 -0400 Subject: [PATCH 009/125] Fix: Tech Debt (wsNetwork "auto" and OnlineAccountsDelete comment) (#5207) --- .../trackerdb/sqlitedriver/accountsV2.go | 7 +++- network/wsNetwork.go | 9 ++--- network/wsNetwork_test.go | 36 +++++++++---------- 3 files changed, 29 insertions(+), 23 deletions(-) diff --git a/ledger/store/trackerdb/sqlitedriver/accountsV2.go b/ledger/store/trackerdb/sqlitedriver/accountsV2.go index 78083c95ac..3ffb069999 100644 --- a/ledger/store/trackerdb/sqlitedriver/accountsV2.go +++ b/ledger/store/trackerdb/sqlitedriver/accountsV2.go @@ -651,7 +651,12 @@ func (w *accountsV2Writer) TxtailNewRound(ctx context.Context, baseRound basics. return err } -// OnlineAccountsDelete deleted entries with updRound <= expRound +// OnlineAccountsDelete cleans up the Online Accounts table to prune expired entires. +// it will delete entries with an updRound <= expRound +// EXCEPT, it will not delete the *latest* entry for an account, no matter how old. +// this is so that accounts whos last update is before expRound still maintain an Online Account Balance +// After this cleanup runs, accounts in this table will have either one entry (if all entries besides the latest are expired), +// or will have more than one entry (if multiple entries are not yet expired). func (w *accountsV2Writer) OnlineAccountsDelete(forgetBefore basics.Round) (err error) { rows, err := w.e.Query("SELECT rowid, address, updRound, data FROM onlineaccounts WHERE updRound < ? ORDER BY address, updRound DESC", forgetBefore) if err != nil { diff --git a/network/wsNetwork.go b/network/wsNetwork.go index 0280952d19..7339bbde61 100644 --- a/network/wsNetwork.go +++ b/network/wsNetwork.go @@ -101,8 +101,9 @@ const slowWritingPeerMonitorInterval = 5 * time.Second // to the log file. Note that the log file itself would also json-encode these before placing them in the log file. const unprintableCharacterGlyph = "▯" -// match config.PublicAddress to this string to automatically set PublicAddress from Address() -const autoconfigPublicAddress = "auto" +// testingPublicAddress is used in identity exchange tests for a predictable +// PublicAddress (which will match HTTP Listener's Address) in tests only. +const testingPublicAddress = "testing" var networkIncomingConnections = metrics.MakeGauge(metrics.NetworkIncomingConnections) var networkOutgoingConnections = metrics.MakeGauge(metrics.NetworkOutgoingConnections) @@ -842,8 +843,8 @@ func (wn *WebsocketNetwork) Start() { wn.scheme = "http" } - // if PublicAddress set to automatic, pull the name from Address() - if wn.config.PublicAddress == autoconfigPublicAddress { + // if PublicAddress set to testing, pull the name from Address() + if wn.config.PublicAddress == testingPublicAddress { addr, ok := wn.Address() if ok { url, err := url.Parse(addr) diff --git a/network/wsNetwork_test.go b/network/wsNetwork_test.go index c08042c465..4f7d01c95b 100644 --- a/network/wsNetwork_test.go +++ b/network/wsNetwork_test.go @@ -1131,25 +1131,25 @@ func TestGetPeers(t *testing.T) { assert.Equal(t, expectAddrs, peerAddrs) } -// confirms that if the config PublicAddress is set to "auto", +// confirms that if the config PublicAddress is set to "testing", // PublicAddress is loaded when possible with the value of Address() -func TestAutoPublicAddress(t *testing.T) { +func TestTestingPublicAddress(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() netA := makeTestWebsocketNode(t) - netA.config.PublicAddress = "auto" + netA.config.PublicAddress = "testing" netA.config.GossipFanout = 1 netA.Start() time.Sleep(100 * time.Millisecond) - // check that "auto" has been overloaded + // check that "testing" has been overloaded addr, ok := netA.Address() addr = hostAndPort(addr) require.True(t, ok) - require.NotEqual(t, "auto", netA.PublicAddress()) + require.NotEqual(t, "testing", netA.PublicAddress()) require.Equal(t, addr, netA.PublicAddress()) } @@ -1231,12 +1231,12 @@ func TestPeeringWithIdentityChallenge(t *testing.T) { netA := makeTestWebsocketNode(t, testWebsocketLogNameOption{"netA"}) netA.identityTracker = newMockIdentityTracker(netA.identityTracker) - netA.config.PublicAddress = "auto" + netA.config.PublicAddress = "testing" netA.config.GossipFanout = 1 netB := makeTestWebsocketNode(t, testWebsocketLogNameOption{"netB"}) netB.identityTracker = newMockIdentityTracker(netB.identityTracker) - netB.config.PublicAddress = "auto" + netB.config.PublicAddress = "testing" netB.config.GossipFanout = 1 netA.Start() @@ -1380,12 +1380,12 @@ func TestPeeringSenderIdentityChallengeOnly(t *testing.T) { netA := makeTestWebsocketNode(t, testWebsocketLogNameOption{"netA"}) netA.identityTracker = newMockIdentityTracker(netA.identityTracker) - netA.config.PublicAddress = "auto" + netA.config.PublicAddress = "testing" netA.config.GossipFanout = 1 netB := makeTestWebsocketNode(t, testWebsocketLogNameOption{"netB"}) netB.identityTracker = newMockIdentityTracker(netB.identityTracker) - //netB.config.PublicAddress = "auto" + //netB.config.PublicAddress = "testing" netB.config.GossipFanout = 1 netA.Start() @@ -1445,12 +1445,12 @@ func TestPeeringReceiverIdentityChallengeOnly(t *testing.T) { netA := makeTestWebsocketNode(t, testWebsocketLogNameOption{"netA"}) netA.identityTracker = newMockIdentityTracker(netA.identityTracker) - //netA.config.PublicAddress = "auto" + //netA.config.PublicAddress = "testing" netA.config.GossipFanout = 1 netB := makeTestWebsocketNode(t, testWebsocketLogNameOption{"netB"}) netB.identityTracker = newMockIdentityTracker(netB.identityTracker) - netB.config.PublicAddress = "auto" + netB.config.PublicAddress = "testing" netB.config.GossipFanout = 1 netA.Start() @@ -1512,7 +1512,7 @@ func TestPeeringIncorrectDeduplicationName(t *testing.T) { netA := makeTestWebsocketNode(t, testWebsocketLogNameOption{"netA"}) netA.identityTracker = newMockIdentityTracker(netA.identityTracker) - netA.config.PublicAddress = "auto" + netA.config.PublicAddress = "testing" netA.config.GossipFanout = 1 netB := makeTestWebsocketNode(t, testWebsocketLogNameOption{"netB"}) @@ -1701,7 +1701,7 @@ func TestPeeringWithBadIdentityChallenge(t *testing.T) { t.Logf("Running Peering with Identity Challenge Test: %s", tc.name) netA := makeTestWebsocketNode(t, testWebsocketLogNameOption{"netA"}) netA.identityTracker = newMockIdentityTracker(netA.identityTracker) - netA.config.PublicAddress = "auto" + netA.config.PublicAddress = "testing" netA.config.GossipFanout = 1 scheme := newMockIdentityScheme(t) @@ -1710,7 +1710,7 @@ func TestPeeringWithBadIdentityChallenge(t *testing.T) { netB := makeTestWebsocketNode(t, testWebsocketLogNameOption{"netB"}) netB.identityTracker = newMockIdentityTracker(netB.identityTracker) - netB.config.PublicAddress = "auto" + netB.config.PublicAddress = "testing" netB.config.GossipFanout = 1 netA.Start() @@ -1844,12 +1844,12 @@ func TestPeeringWithBadIdentityChallengeResponse(t *testing.T) { t.Logf("Running Peering with Identity Challenge Response Test: %s", tc.name) netA := makeTestWebsocketNode(t, testWebsocketLogNameOption{"netA"}) netA.identityTracker = newMockIdentityTracker(netA.identityTracker) - netA.config.PublicAddress = "auto" + netA.config.PublicAddress = "testing" netA.config.GossipFanout = 1 netB := makeTestWebsocketNode(t, testWebsocketLogNameOption{"netB"}) netB.identityTracker = newMockIdentityTracker(netB.identityTracker) - netB.config.PublicAddress = "auto" + netB.config.PublicAddress = "testing" netB.config.GossipFanout = 1 scheme := newMockIdentityScheme(t) @@ -1997,7 +1997,7 @@ func TestPeeringWithBadIdentityVerification(t *testing.T) { t.Logf("Running Peering with Identity Verification Test: %s", tc.name) netA := makeTestWebsocketNode(t, testWebsocketLogNameOption{"netA"}) netA.identityTracker = newMockIdentityTracker(netA.identityTracker) - netA.config.PublicAddress = "auto" + netA.config.PublicAddress = "testing" netA.config.GossipFanout = 1 scheme := newMockIdentityScheme(t) @@ -2006,7 +2006,7 @@ func TestPeeringWithBadIdentityVerification(t *testing.T) { netB := makeTestWebsocketNode(t, testWebsocketLogNameOption{"netB"}) netB.identityTracker = newMockIdentityTracker(netB.identityTracker) - netB.config.PublicAddress = "auto" + netB.config.PublicAddress = "testing" netB.config.GossipFanout = 1 // if the key is occupied, make the tracker fail to insert the peer if tc.occupied { From f8edd66356fb36ed0b95aabda8330d3750830c3a Mon Sep 17 00:00:00 2001 From: Hang Su <87964331+ahangsu@users.noreply.github.com> Date: Tue, 21 Mar 2023 09:39:40 -0400 Subject: [PATCH 010/125] Goal: Introduce new command for simulate (#5213) --- cmd/goal/application.go | 2 +- cmd/goal/clerk.go | 58 +++++++-- daemon/algod/api/client/restClient.go | 18 ++- libgoal/libgoal.go | 19 ++- test/scripts/e2e_subs/e2e-app-simulate.sh | 139 ++++++++++++++++++++++ 5 files changed, 211 insertions(+), 25 deletions(-) create mode 100755 test/scripts/e2e_subs/e2e-app-simulate.sh diff --git a/cmd/goal/application.go b/cmd/goal/application.go index 0920793300..0c5e9e0cbf 100644 --- a/cmd/goal/application.go +++ b/cmd/goal/application.go @@ -1063,7 +1063,7 @@ var infoAppCmd = &cobra.Command{ } // populateMethodCallTxnArgs parses and loads transactions from the files indicated by the values -// slice. An error will occur if the transaction does not matched the expected type, it has a nonzero +// slice. An error will occur if the transaction does not match the expected type, it has a nonzero // group ID, or if it is signed by a normal signature or Msig signature (but not Lsig signature) func populateMethodCallTxnArgs(types []string, values []string) ([]transactions.SignedTxn, error) { loadedTxns := make([]transactions.SignedTxn, len(values)) diff --git a/cmd/goal/clerk.go b/cmd/goal/clerk.go index 2147828947..f044915fba 100644 --- a/cmd/goal/clerk.go +++ b/cmd/goal/clerk.go @@ -76,6 +76,7 @@ func init() { clerkCmd.AddCommand(compileCmd) clerkCmd.AddCommand(dryrunCmd) clerkCmd.AddCommand(dryrunRemoteCmd) + clerkCmd.AddCommand(simulateCmd) // Wallet to be used for the clerk operation clerkCmd.PersistentFlags().StringVarP(&walletName, "wallet", "w", "", "Set the wallet to be used for the selected operation") @@ -88,7 +89,7 @@ func init() { 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(&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().StringSliceVar(&argB64Strings, "argb64", nil, "Base64 encoded args to pass to transaction logic") sendCmd.Flags().StringVarP(&logicSigFile, "logic-sig", "L", "", "LogicSig to apply to transaction") sendCmd.Flags().StringVar(&msigParams, "msig-params", "", "Multisig preimage parameters - [threshold] [Address 1] [Address 2] ...\nUsed to add the necessary fields in case the account was rekeyed to a multisig account") sendCmd.MarkFlagRequired("to") @@ -108,8 +109,8 @@ func init() { 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(&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") + 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") signCmd.MarkFlagRequired("infile") signCmd.MarkFlagRequired("outfile") @@ -123,26 +124,29 @@ func init() { splitCmd.MarkFlagRequired("infile") splitCmd.MarkFlagRequired("outfile") - compileCmd.Flags().BoolVarP(&disassemble, "disassemble", "D", false, "disassemble a compiled program") - compileCmd.Flags().BoolVarP(&noProgramOutput, "no-out", "n", false, "don't write contract program binary") - compileCmd.Flags().BoolVarP(&writeSourceMap, "map", "m", false, "write out source map") - compileCmd.Flags().BoolVarP(&signProgram, "sign", "s", false, "sign program, output is a binary signed LogicSig record") + compileCmd.Flags().BoolVarP(&disassemble, "disassemble", "D", false, "Disassemble a compiled program") + compileCmd.Flags().BoolVarP(&noProgramOutput, "no-out", "n", false, "Don't write contract program binary") + compileCmd.Flags().BoolVarP(&writeSourceMap, "map", "m", false, "Write out source map") + compileCmd.Flags().BoolVarP(&signProgram, "sign", "s", false, "Sign program, output is a binary signed LogicSig record") compileCmd.Flags().StringVarP(&outFilename, "outfile", "o", "", "Filename to write program bytes or signed LogicSig to") compileCmd.Flags().StringVarP(&account, "account", "a", "", "Account address to sign the program (If not specified, uses default account)") - dryrunCmd.Flags().StringVarP(&txFilename, "txfile", "t", "", "transaction or transaction-group to test") - dryrunCmd.Flags().StringVarP(&protoVersion, "proto", "P", "", "consensus protocol version id string") + dryrunCmd.Flags().StringVarP(&txFilename, "txfile", "t", "", "Transaction or transaction-group to test") + dryrunCmd.Flags().StringVarP(&protoVersion, "proto", "P", "", "Consensus protocol version id string") dryrunCmd.Flags().BoolVar(&dumpForDryrun, "dryrun-dump", false, "Dump in dryrun format acceptable by dryrun REST api instead of running") dryrunCmd.Flags().Var(&dumpForDryrunFormat, "dryrun-dump-format", "Dryrun dump format: "+dumpForDryrunFormat.AllowedString()) - dryrunCmd.Flags().StringSliceVar(&dumpForDryrunAccts, "dryrun-accounts", nil, "additional accounts to include into dryrun request obj") + dryrunCmd.Flags().StringSliceVar(&dumpForDryrunAccts, "dryrun-accounts", nil, "Additional accounts to include into dryrun request obj") dryrunCmd.Flags().StringVarP(&outFilename, "outfile", "o", "", "Filename for writing dryrun state object") dryrunCmd.MarkFlagRequired("txfile") - dryrunRemoteCmd.Flags().StringVarP(&txFilename, "dryrun-state", "D", "", "dryrun request object to run") - dryrunRemoteCmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "print more info") - dryrunRemoteCmd.Flags().BoolVarP(&rawOutput, "raw", "r", false, "output raw response from algod") + dryrunRemoteCmd.Flags().StringVarP(&txFilename, "dryrun-state", "D", "", "Dryrun request object to run") + dryrunRemoteCmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "Print more info") + dryrunRemoteCmd.Flags().BoolVarP(&rawOutput, "raw", "r", false, "Output raw response from algod") dryrunRemoteCmd.MarkFlagRequired("dryrun-state") + simulateCmd.Flags().StringVarP(&txFilename, "txfile", "t", "", "Transaction or transaction-group to test") + simulateCmd.Flags().StringVarP(&outFilename, "outfile", "o", "", "Filename for writing simulation result") + panicIfErr(simulateCmd.MarkFlagRequired("txfile")) } var clerkCmd = &cobra.Command{ @@ -1253,6 +1257,34 @@ var dryrunRemoteCmd = &cobra.Command{ }, } +var simulateCmd = &cobra.Command{ + Use: "simulate", + Short: "Simulate a transaction or transaction group with algod's simulate REST endpoint", + Long: `Simulate a transaction or transaction group with algod's simulate REST endpoint under various configurations.`, + Run: func(cmd *cobra.Command, args []string) { + data, err := readFile(txFilename) + if err != nil { + reportErrorf(fileReadError, txFilename, err) + } + + dataDir := datadir.EnsureSingleDataDir() + client := ensureFullClient(dataDir) + resp, err := client.TransactionSimulation(data) + if err != nil { + reportErrorf("simulation error: %s", err.Error()) + } + + if outFilename != "" { + err = writeFile(outFilename, protocol.EncodeJSON(&resp), 0600) + if err != nil { + reportErrorf("write file error: %s", err.Error()) + } + } else { + fmt.Println(string(protocol.EncodeJSON(&resp))) + } + }, +} + // unmarshalSlice converts string addresses to basics.Address func unmarshalSlice(accts []string) ([]basics.Address, error) { result := make([]basics.Address, 0, len(accts)) diff --git a/daemon/algod/api/client/restClient.go b/daemon/algod/api/client/restClient.go index f3f5415734..c9bdb0a833 100644 --- a/daemon/algod/api/client/restClient.go +++ b/daemon/algod/api/client/restClient.go @@ -45,10 +45,11 @@ const ( // rawRequestPaths is a set of paths where the body should not be urlencoded var rawRequestPaths = map[string]bool{ - "/v2/transactions": true, - "/v2/teal/dryrun": true, - "/v2/teal/compile": true, - "/v2/participation": true, + "/v2/transactions": true, + "/v2/teal/dryrun": true, + "/v2/teal/compile": true, + "/v2/participation": true, + "/v2/transactions/simulate": true, } // unauthorizedRequestError is generated when we receive 401 error from the server. This error includes the inner error @@ -284,7 +285,7 @@ func (client RestClient) WaitForBlock(round basics.Round) (response model.NodeSt return } -// HealthCheck does a health check on the the potentially running node, +// HealthCheck does a health check on the potentially running node, // returning an error if the API is down func (client RestClient) HealthCheck() error { return client.get(nil, "/health", nil) @@ -629,6 +630,12 @@ func (client RestClient) RawDryrun(data []byte) (response []byte, err error) { return } +// SimulateRawTransaction gets the raw transaction or raw transaction group, and returns relevant simulation results. +func (client RestClient) SimulateRawTransaction(data []byte) (response model.SimulateResponse, err error) { + err = client.submitForm(&response, "/v2/transactions/simulate", data, "POST", false /* encodeJSON */, true /* decodeJSON */, false) + return +} + // StateProofs gets a state proof that covers a given round func (client RestClient) StateProofs(round uint64) (response model.StateProofResponse, err error) { err = client.get(&response, fmt.Sprintf("/v2/stateproofs/%d", round), nil) @@ -670,7 +677,6 @@ func (client RestClient) GetParticipationKeyByID(participationID string) (respon func (client RestClient) RemoveParticipationKeyByID(participationID string) (err error) { err = client.delete(nil, fmt.Sprintf("/v2/participation/%s", participationID), nil, true) return - } /* Endpoint registered for follower nodes */ diff --git a/libgoal/libgoal.go b/libgoal/libgoal.go index 4756aa155f..5951793de3 100644 --- a/libgoal/libgoal.go +++ b/libgoal/libgoal.go @@ -504,11 +504,10 @@ func (c *Client) signAndBroadcastTransactionWithWallet(walletHandle, pw []byte, // // validRounds | lastValid | result (lastValid) // ------------------------------------------------- -// 0 | 0 | firstValid + maxTxnLife -// 0 | N | lastValid -// M | 0 | first + validRounds - 1 -// M | M | error -// +// 0 | 0 | firstValid + maxTxnLife +// 0 | N | lastValid +// M | 0 | first + validRounds - 1 +// M | M | error func (c *Client) ComputeValidityRounds(firstValid, lastValid, validRounds uint64) (first, last, latest uint64, err error) { params, err := c.cachedSuggestedParams() if err != nil { @@ -1270,6 +1269,16 @@ func (c *Client) Dryrun(data []byte) (resp model.DryrunResponse, err error) { return } +// TransactionSimulation takes raw transaction or raw transaction group, and returns relevant simulation results. +func (c *Client) TransactionSimulation(data []byte) (resp model.SimulateResponse, err error) { + algod, err := c.ensureAlgodClient() + if err != nil { + return + } + resp, err = algod.SimulateRawTransaction(data) + return +} + // TransactionProof returns a Merkle proof for a transaction in a block. func (c *Client) TransactionProof(txid string, round uint64, hashType crypto.HashType) (resp model.TransactionProofResponse, err error) { algod, err := c.ensureAlgodClient() diff --git a/test/scripts/e2e_subs/e2e-app-simulate.sh b/test/scripts/e2e_subs/e2e-app-simulate.sh new file mode 100755 index 0000000000..cae8ebeab3 --- /dev/null +++ b/test/scripts/e2e_subs/e2e-app-simulate.sh @@ -0,0 +1,139 @@ +#!/bin/bash + +date '+app-simple-test start %Y%m%d_%H%M%S' + +set -e +set -x +set -o pipefail +set -o nounset +export SHELLOPTS + +WALLET=$1 + +# Directory of this bash program +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" + +gcmd="goal -w ${WALLET}" + +ACCOUNT=$(${gcmd} account list|awk '{ print $3 }') + +CONST_TRUE="true" +CONST_FALSE="false" + +############################################## +# WE FIRST TEST TRANSACTION GROUP SIMULATION # +############################################## + +${gcmd} clerk send -a 10000 -f ${ACCOUNT} -t ${ACCOUNT} -o pay1.tx +${gcmd} clerk send -a 10000 -f ${ACCOUNT} -t ${ACCOUNT} -o pay2.tx + +cat pay1.tx pay2.tx | ${gcmd} clerk group -i - -o grouped.tx + +# We first test transaction group simulation WITHOUT signatures +RES=$(${gcmd} clerk simulate -t grouped.tx) + +if [[ $(echo "$RES" | jq '."would-succeed"') != $CONST_FALSE ]]; then + date '+app-simulate-test FAIL the simulation transaction group without signatures should not succeed %Y%m%d_%H%M%S' + false +fi + +# check the simulation failing reason, first transaction has no signature +if [[ $(echo "$RES" | jq '."txn-groups"[0]."txn-results"[0]."missing-signature"') != $CONST_TRUE ]]; then + date '+app-simulate-test FAIL the simulation transaction group FAIL for first transaction has NO signature %Y%m%d_%H%M%S' + false +fi + +# check the simulation failing reason, second transaction has no signature +if [[ $(echo "$RES" | jq '."txn-groups"[0]."txn-results"[1]."missing-signature"') != $CONST_TRUE ]]; then + date '+app-simulate-test FAIL the simulation transaction group FAIL for second transaction has NO signature %Y%m%d_%H%M%S' + false +fi + +# We then test transaction group simulation WITH signatures +${gcmd} clerk split -i grouped.tx -o grouped.tx + +${gcmd} clerk sign -i grouped-0.tx -o grouped-0.stx +${gcmd} clerk sign -i grouped-1.tx -o grouped-1.stx + +cat grouped-0.stx grouped-1.stx > grouped.stx + +RES=$(${gcmd} clerk simulate -t grouped.stx | jq '."would-succeed"') + +if [[ $RES != $CONST_TRUE ]]; then + date '+app-simulate-test FAIL should pass to simulate self pay transaction group %Y%m%d_%H%M%S' + false +fi + +############################################### +# WE ALSO TEST OVERSPEND IN TRANSACTION GROUP # +############################################### + +${gcmd} clerk send -a 1000000000000 -f ${ACCOUNT} -t ${ACCOUNT} -o pay1.tx +${gcmd} clerk send -a 10000 -f ${ACCOUNT} -t ${ACCOUNT} -o pay2.tx + +cat pay1.tx pay2.tx | ${gcmd} clerk group -i - -o grouped.tx + +${gcmd} clerk split -i grouped.tx -o grouped.tx + +${gcmd} clerk sign -i grouped-0.tx -o grouped-0.stx +${gcmd} clerk sign -i grouped-1.tx -o grouped-1.stx + +cat grouped-0.stx grouped-1.stx > grouped.stx + +RES=$(${gcmd} clerk simulate -t grouped.stx) + +if [[ $(echo "$RES" | jq '."would-succeed"') != $CONST_FALSE ]]; then + data '+app-simulate-test FAIL should FAIL for overspending in simulate self pay transaction group %Y%m%d_%H%M%S' + false +fi + +OVERSPEND_INFO="overspend" + +if [[ $(echo "$RES" | jq '."txn-groups"[0]."failure-message"') != *"$OVERSPEND_INFO"* ]]; then + data '+app-simulate-test FAIL first overspending transaction in transaction group should contain message OVERSPEND %Y%m%d_%H%M%S' + false +fi + +####################################################### +# NOW WE TRY TO TEST SIMULATION WITH ABI METHOD CALLS # +####################################################### + +printf '#pragma version 2\nint 1' > "${TEMPDIR}/simple-v2.teal" + +# Real Create +RES=$(${gcmd} app method --method "create(uint64)uint64" --arg "1234" --create --approval-prog ${DIR}/tealprogs/app-abi-method-example.teal --clear-prog ${TEMPDIR}/simple-v2.teal --global-byteslices 0 --global-ints 0 --local-byteslices 1 --local-ints 0 --extra-pages 0 --from $ACCOUNT 2>&1 || true) +EXPECTED="method create(uint64)uint64 succeeded with output: 2468" +if [[ $RES != *"${EXPECTED}"* ]]; then + date '+app-simulate-test FAIL the method call to create(uint64)uint64 should not fail %Y%m%d_%H%M%S' + false +fi + +APPID=$(echo "$RES" | grep Created | awk '{ print $6 }') + +# SIMULATION! empty()void +${gcmd} app method --method "empty()void" --app-id $APPID --from $ACCOUNT 2>&1 -o empty.tx + +# SIMULATE without a signature first +RES=$(${gcmd} clerk simulate -t empty.tx) + +# confirm that without signature, the simulation should fail +if [[ $(echo "$RES" | jq '."would-succeed"') != $CONST_FALSE ]]; then + date '+app-simulate-test FAIL the simulation call to empty()void without signature should not succeed %Y%m%d_%H%M%S' + false +fi + +# check again the simulation failing reason +if [[ $(echo "$RES" | jq '."txn-groups"[0]."txn-results"[0]."missing-signature"') != $CONST_TRUE ]]; then + date '+app-simulate-test FAIL the simulation call to empty()void without signature should fail with missing-signature %Y%m%d_%H%M%S' + false +fi + +# SIMULATE with a signature +${gcmd} clerk sign -i empty.tx -o empty.stx +RES=$(${gcmd} clerk simulate -t empty.stx | jq '."would-succeed"') + +# with signature, simulation app-call should succeed +if [[ $RES != $CONST_TRUE ]]; then + date '+app-simulate-test FAIL the simulation call to empty()void should succeed %Y%m%d_%H%M%S' + false +fi From 0368e1470e1ab1f1bea7a106ac02130bb365033f Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Tue, 21 Mar 2023 16:28:47 -0400 Subject: [PATCH 011/125] tools: compare node.log for matching catchpoint roots (#5205) --- tools/debug/chopper/main.go | 228 ++++++++++++++++++++++++++++++++++++ tools/debug/jslog | 2 +- 2 files changed, 229 insertions(+), 1 deletion(-) create mode 100644 tools/debug/chopper/main.go diff --git a/tools/debug/chopper/main.go b/tools/debug/chopper/main.go new file mode 100644 index 0000000000..000f6e1cf4 --- /dev/null +++ b/tools/debug/chopper/main.go @@ -0,0 +1,228 @@ +// Copyright (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 . + +// chopper compares raw Algorand logs for matching catchpoint (balance trie) roots and labels +package main + +import ( + "bufio" + "encoding/json" + "flag" + "fmt" + "os" + "regexp" + "strconv" + "strings" + + "github.com/fatih/color" + + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/logging/telemetryspec" +) + +const ( + red = color.FgRed + green = color.FgGreen + yellow = color.FgYellow +) + +var help = flag.Bool("help", false, "Show help") +var helpShort = flag.Bool("h", false, "Show help") +var labels = flag.Bool("labels", false, "Compare catchpoint labels in addition to roots") +var labelsShort = flag.Bool("l", false, "Compare catchpoint labels in addition to roots") + +func usage() { + fmt.Fprintln(os.Stderr, `Utility to extract and compare balance root and catchpoint labels messages from algod log files (node.log) +Usage: ./chopper [--labels] file1 file2`) +} + +// logEntry is json representing catchpoint root message telemetry +type logEntry struct { + Details telemetryspec.CatchpointRootUpdateEventDetails +} + +// rootLabelInfo is parsed roots/labels from a log file +type rootLabelInfo struct { + roots map[basics.Round]*telemetryspec.CatchpointRootUpdateEventDetails + labels map[basics.Round]string +} + +// extractEntries reads the log file line by line and collects root and labels entries +func extractEntries(filename string, checkLabels bool) rootLabelInfo { + f, err := os.Open(filename) + if err != nil { + fmt.Fprintf(os.Stderr, "Error opening %s: %s\n", filename, err.Error()) + os.Exit(1) + } + defer f.Close() + + s := bufio.NewScanner(f) + + var re *regexp.Regexp + if checkLabels { + re = regexp.MustCompile(`Creating a catchpoint label (\d+#[A-Z0-9]+)\s+for round=(\d+).*`) + } + + result := rootLabelInfo{ + roots: make(map[basics.Round]*telemetryspec.CatchpointRootUpdateEventDetails), + } + if checkLabels { + result.labels = make(map[basics.Round]string) + } + + for s.Scan() { + line := s.Text() + if line[0] == '{' && strings.Contains(line[:20], "Root") { + var entry logEntry + if err := json.Unmarshal([]byte(line), &entry); err != nil { + fmt.Fprintf(os.Stderr, "Error reading catchpoint root entry %s: %s\n", filename, err.Error()) + continue + } + result.roots[basics.Round(entry.Details.NewBase)] = &entry.Details + } else if checkLabels && strings.HasPrefix(line, `{"file":"catchpointlabel.go"`) { + entry := map[string]interface{}{} + if err := json.Unmarshal([]byte(line), &entry); err != nil { + fmt.Fprintf(os.Stderr, "Error reading catchpoint label entry %s: %s\n", filename, err.Error()) + continue + } + matches := re.FindStringSubmatch(entry["msg"].(string)) + if len(matches) != 3 { + fmt.Fprintf(os.Stderr, "No catchpoint label match %s: %s %s\n", filename, matches, entry["msg"]) + continue + } + uintRound, err := strconv.ParseUint(matches[2], 10, 64) + if err != nil { + fmt.Fprintf(os.Stderr, "Cannot parse round %s: %s\n", filename, matches[1]) + continue + } + result.labels[basics.Round(uintRound)] = matches[1] + } + } + + if err := s.Err(); err != nil { + fmt.Fprintf(os.Stderr, "Error reading lines from %s: %s\n", filename, err.Error()) + os.Exit(1) + } + + return result +} + +type reportData struct { + what string + size1 int + size2 int + matched int + mismatched []interface{} + errReporter func(interface{}) +} + +// report prints out stats about matched and mismatched roots or labels +func report(rd reportData) { + fmt.Printf("%s in first: %d, second: %d\n", strings.Title(rd.what), rd.size1, rd.size2) + + const matchedStr = "Matched %s: %d" + c := yellow + if rd.matched > 0 { + c = green + } + fmt.Println(color.New(c).Sprintf(matchedStr, rd.what, rd.matched)) + + const mismatchedStr = "Mismatched %s: %d" + c = green + if len(rd.mismatched) > 0 { + c = red + } + fmt.Println(color.New(c).Sprintf(mismatchedStr, rd.what, len(rd.mismatched))) + if len(rd.mismatched) > 0 { + for _, entry := range rd.mismatched { + rd.errReporter(entry) + } + } + fmt.Printf("Other %s in first: %d, second: %d\n", rd.what, rd.size1-rd.matched-len(rd.mismatched), rd.size2-rd.matched-len(rd.mismatched)) +} + +func main() { + flag.Parse() + + if *help || *helpShort || len(flag.Args()) < 2 { + usage() + os.Exit(1) + } + + checkLabels := *labels || *labelsShort + + file1 := flag.Args()[0] + file2 := flag.Args()[1] + + // load data + info1 := extractEntries(file1, checkLabels) + info2 := extractEntries(file2, checkLabels) + + // match roots + matchedRoots := 0 + var mismatchedRoots []interface{} + for rnd, tree1 := range info1.roots { + if tree2, ok := info2.roots[rnd]; ok { + if tree1.Root == tree2.Root { + matchedRoots++ + } else { + mismatchedRoots = append(mismatchedRoots, [2]*telemetryspec.CatchpointRootUpdateEventDetails{tree1, tree2}) + } + } + } + + // match labels + matchedLabels := 0 + var mismatchedLabels []interface{} + if checkLabels { + for rnd, label1 := range info1.labels { + if label2, ok := info2.labels[rnd]; ok { + if label1 == label2 { + matchedLabels++ + } else { + mismatchedLabels = append(mismatchedLabels, [2]string{label1, label2}) + } + } + } + + } + + report(reportData{ + what: "roots", + size1: len(info1.roots), + size2: len(info2.roots), + matched: matchedRoots, + mismatched: mismatchedRoots, + errReporter: func(e interface{}) { + entry := e.([2]*telemetryspec.CatchpointRootUpdateEventDetails) + fmt.Printf("NewBase: %d, first: (%d, %s), second (%d,%s)\n", entry[0].NewBase, entry[0].OldBase, entry[0].Root, entry[1].OldBase, entry[1].Root) + }, + }) + + if checkLabels { + report(reportData{ + what: "labels", + size1: len(info1.labels), + size2: len(info2.labels), + matched: matchedLabels, + mismatched: mismatchedLabels, + errReporter: func(e interface{}) { + entry := e.([2]string) + fmt.Printf("first: %s != %s second\n", entry[0], entry[1]) + }, + }) + } +} diff --git a/tools/debug/jslog b/tools/debug/jslog index 540ebb66c2..fe27719f03 100755 --- a/tools/debug/jslog +++ b/tools/debug/jslog @@ -96,7 +96,7 @@ class LogFile: return None if when: - when = datetime.datetime.strptime(when, '%Y-%m-%dT%H:%M:%S%z') + when = datetime.datetime.strptime(when, '%Y-%m-%dT%H:%M:%S.%f%z') now = time.time() dt = when.timestamp() - now # TODO: format sub-second if available From ac69a5329ebb19cd45f605a85176dc3fcc78f8f2 Mon Sep 17 00:00:00 2001 From: DevOps Service Date: Wed, 22 Mar 2023 15:29:49 +0000 Subject: [PATCH 012/125] Bump Version, Remove buildnumber.dat and genesistimestamp.dat files. --- buildnumber.dat | 1 - config/version.go | 2 +- genesistimestamp.dat | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) delete mode 100644 buildnumber.dat delete mode 100644 genesistimestamp.dat diff --git a/buildnumber.dat b/buildnumber.dat deleted file mode 100644 index 573541ac97..0000000000 --- a/buildnumber.dat +++ /dev/null @@ -1 +0,0 @@ -0 diff --git a/config/version.go b/config/version.go index 06701e9f1f..e4c5f57df8 100644 --- a/config/version.go +++ b/config/version.go @@ -33,7 +33,7 @@ const VersionMajor = 3 // VersionMinor is the Minor semantic version number (x.#.z) - changed when backwards-compatible features are introduced. // Not enforced until after initial public release (x > 0). -const VersionMinor = 15 +const VersionMinor = 16 // Version is the type holding our full version information. type Version struct { diff --git a/genesistimestamp.dat b/genesistimestamp.dat deleted file mode 100644 index c72c6a7795..0000000000 --- a/genesistimestamp.dat +++ /dev/null @@ -1 +0,0 @@ -1558657885 From e31b978ecebd46f5e45ee852e29764913d99d404 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Wed, 22 Mar 2023 16:56:45 -0400 Subject: [PATCH 013/125] util/s3: improve patch number capacity in version comparison (#5224) --- cmd/updater/version_test.go | 9 +++-- util/s3/s3Helper.go | 22 +++++------ util/s3/s3Helper_test.go | 73 ++++++++++++++++++++++++++++++------- 3 files changed, 76 insertions(+), 28 deletions(-) diff --git a/cmd/updater/version_test.go b/cmd/updater/version_test.go index 62954d51cf..e46bd2f3e4 100644 --- a/cmd/updater/version_test.go +++ b/cmd/updater/version_test.go @@ -29,10 +29,11 @@ func TestGetVersion(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - testValidVersion(t, "algonode_update_0.1.0.log", uint64(0x00010000)) - testValidVersion(t, "algo_update_0.1.0", uint64(0x00010000)) - testValidVersion(t, "algo_update_65535.1.0", uint64(0xFFFF00010000)) - testValidVersion(t, "algo_update_65535.65535.65535", uint64(0xFFFFFFFFFFFF)) + testValidVersion(t, "algonode_update_0.1.0.log", uint64(0x01000000)) + testValidVersion(t, "algo_update_0.1.0", uint64(0x01000000)) + testValidVersion(t, "algo_update_65535.1.0", uint64(0x00FFFF0001000000)) + testValidVersion(t, "algo_update_65535.65535.65535", uint64(0xFFFFFFFF00FFFF)) + testValidVersion(t, "algo_update_65535.65535.16777215", uint64(0xFFFFFFFFFFFFFF)) testInvalidVersion(t, "algo_update_0.-1.0") testInvalidVersion(t, "algo_update_1e5.0.0") diff --git a/util/s3/s3Helper.go b/util/s3/s3Helper.go index 8c70f14eda..9fc6f06918 100644 --- a/util/s3/s3Helper.go +++ b/util/s3/s3Helper.go @@ -245,15 +245,15 @@ func GetVersionFromName(name string) (version uint64, err error) { return } var val uint64 - for index, match := range submatchAll[0] { - if index > 0 { - version <<= 16 - val, err = strconv.ParseUint(match, 10, 0) - if err != nil { - return - } - version += val + submatch := submatchAll[0][1:] // skip the first match which is the whole string + offsets := []int{0, 16, 24} // some bits for major (not really restricted), 16 bits for minor, 24 bits for patch + for index, match := range submatch { + version <<= offsets[index] + val, err = strconv.ParseUint(match, 10, 0) + if err != nil { + return } + version += val } return } @@ -262,13 +262,13 @@ func GetVersionFromName(name string) (version uint64, err error) { func GetVersionPartsFromVersion(version uint64) (major uint64, minor uint64, patch uint64, err error) { val := version - if val < 1<<32 { + if val < 1<<40 { err = errors.New("versions below 1.0.0 not supported") return } - patch = val & 0xffff - val >>= 16 + patch = val & 0xffffff + val >>= 24 minor = val & 0xffff val >>= 16 major = val diff --git a/util/s3/s3Helper_test.go b/util/s3/s3Helper_test.go index c0502a3b01..89cf091881 100644 --- a/util/s3/s3Helper_test.go +++ b/util/s3/s3Helper_test.go @@ -17,6 +17,7 @@ package s3 import ( + "fmt" "os" "reflect" "testing" @@ -174,6 +175,7 @@ func TestMakeS3SessionForDownloadWithBucket(t *testing.T) { func TestGetVersionFromName(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() type args struct { name string @@ -181,12 +183,12 @@ func TestGetVersionFromName(t *testing.T) { expected uint64 } tests := []args{ - {name: "test 1 (major)", version: "_1.0.0", expected: 1 * 1 << 32}, - {name: "test 2 (major)", version: "_2.0.0", expected: 2 * 1 << 32}, - {name: "test 3 (minor)", version: "_1.1.0", expected: 1*1<<32 + 1*1<<16}, - {name: "test 4 (minor)", version: "_1.2.0", expected: 1*1<<32 + 2*1<<16}, - {name: "test 5 (patch)", version: "_1.0.1", expected: 1*1<<32 + 1}, - {name: "test 6 (patch)", version: "_1.0.2", expected: 1*1<<32 + 2}, + {name: "test 1 (major)", version: "_1.0.0", expected: 1 * 1 << 40}, + {name: "test 2 (major)", version: "_2.0.0", expected: 2 * 1 << 40}, + {name: "test 3 (minor)", version: "_1.1.0", expected: 1*1<<40 + 1*1<<24}, + {name: "test 4 (minor)", version: "_1.2.0", expected: 1*1<<40 + 2*1<<24}, + {name: "test 5 (patch)", version: "_1.0.1", expected: 1*1<<40 + 1}, + {name: "test 6 (patch)", version: "_1.0.2", expected: 1*1<<40 + 2}, } for _, test := range tests { @@ -196,8 +198,24 @@ func TestGetVersionFromName(t *testing.T) { } } +func TestGetVersionFromNameCompare(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + name1 := "config_3.13.170018.tar.gz" + name2 := "config_3.15.157.tar.gz" + + ver1, err := GetVersionFromName(name1) + require.NoError(t, err) + ver2, err := GetVersionFromName(name2) + require.NoError(t, err) + + require.Less(t, ver1, ver2) +} + func TestGetPartsFromVersion(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() type args struct { name string @@ -207,12 +225,12 @@ func TestGetPartsFromVersion(t *testing.T) { expPatch uint64 } tests := []args{ - {name: "test 1 (major)", version: 1 * 1 << 32, expMajor: 1, expMinor: 0, expPatch: 0}, - {name: "test 2 (major)", version: 2 * 1 << 32, expMajor: 2, expMinor: 0, expPatch: 0}, - {name: "test 3 (minor)", version: 1*1<<32 + 1*1<<16, expMajor: 1, expMinor: 1, expPatch: 0}, - {name: "test 4 (minor)", version: 1*1<<32 + 2*1<<16, expMajor: 1, expMinor: 2, expPatch: 0}, - {name: "test 5 (patch)", version: 1*1<<32 + 1, expMajor: 1, expMinor: 0, expPatch: 1}, - {name: "test 6 (patch)", version: 1*1<<32 + 2, expMajor: 1, expMinor: 0, expPatch: 2}, + {name: "test 1 (major)", version: 1 * 1 << 40, expMajor: 1, expMinor: 0, expPatch: 0}, + {name: "test 2 (major)", version: 2 * 1 << 40, expMajor: 2, expMinor: 0, expPatch: 0}, + {name: "test 3 (minor)", version: 1*1<<40 + 1*1<<24, expMajor: 1, expMinor: 1, expPatch: 0}, + {name: "test 4 (minor)", version: 1*1<<40 + 2*1<<24, expMajor: 1, expMinor: 2, expPatch: 0}, + {name: "test 5 (patch)", version: 1*1<<40 + 1, expMajor: 1, expMinor: 0, expPatch: 1}, + {name: "test 6 (patch)", version: 1*1<<40 + 2, expMajor: 1, expMinor: 0, expPatch: 2}, } for _, test := range tests { @@ -223,6 +241,35 @@ func TestGetPartsFromVersion(t *testing.T) { require.Equal(t, test.expPatch, actualPatch, test.name) } - _, _, _, err := GetVersionPartsFromVersion(1<<32 - 1) + _, _, _, err := GetVersionPartsFromVersion(1<<40 - 1) require.Error(t, err, "Versions less than 1.0.0 should not be parsed.") } + +func TestGetPartsFromVersionEndToEnd(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + type args struct { + major uint64 + minor uint64 + patch uint64 + } + tests := []args{ + {major: 1, minor: 0, patch: 0}, + {major: 3, minor: 13, patch: 170018}, + {major: 3, minor: 15, patch: 157}, + } + + for _, test := range tests { + name := fmt.Sprintf("config_%d.%d.%d.tar.gz", test.major, test.minor, test.patch) + t.Run(name, func(t *testing.T) { + ver, err := GetVersionFromName(name) + require.NoError(t, err) + actualMajor, actualMinor, actualPatch, err := GetVersionPartsFromVersion(ver) + require.NoError(t, err) + require.Equal(t, test.major, actualMajor) + require.Equal(t, test.minor, actualMinor) + require.Equal(t, test.patch, actualPatch) + }) + } +} From 88cf74ac67b8b10f43e0885c955ad3f9fcc89e77 Mon Sep 17 00:00:00 2001 From: Or Aharonee <17099688+Aharonee@users.noreply.github.com> Date: Fri, 24 Mar 2023 19:31:53 +0300 Subject: [PATCH 014/125] Dev Tools: Integrate Logfile Navigator (lnav) (#5000) Co-authored-by: Bob Broderick <118225939+bbroder-algo@users.noreply.github.com> Co-authored-by: Hang Su <87964331+ahangsu@users.noreply.github.com> --- daemon/algod/server.go | 12 +++---- scripts/algorand_node_log.json | 58 ++++++++++++++++++++++++++++++++++ scripts/configure_dev.sh | 2 ++ 3 files changed, 66 insertions(+), 6 deletions(-) create mode 100644 scripts/algorand_node_log.json diff --git a/daemon/algod/server.go b/daemon/algod/server.go index e116f206b3..2283ef8e0d 100644 --- a/daemon/algod/server.go +++ b/daemon/algod/server.go @@ -184,17 +184,17 @@ func (s *Server) Initialize(cfg config.Local, phonebookAddresses []string, genes // if we have the telemetry enabled, we want to use it's sessionid as part of the // collected metrics decorations. - fmt.Fprintln(logWriter, "++++++++++++++++++++++++++++++++++++++++") - fmt.Fprintln(logWriter, "Logging Starting") + s.log.Infoln("++++++++++++++++++++++++++++++++++++++++") + s.log.Infoln("Logging Starting") if s.log.GetTelemetryUploadingEnabled() { // May or may not be logging to node.log - fmt.Fprintf(logWriter, "Telemetry Enabled: %s\n", s.log.GetTelemetryGUID()) - fmt.Fprintf(logWriter, "Session: %s\n", s.log.GetTelemetrySession()) + s.log.Infof("Telemetry Enabled: %s\n", s.log.GetTelemetryGUID()) + s.log.Infof("Session: %s\n", s.log.GetTelemetrySession()) } else { // May or may not be logging to node.log - fmt.Fprintln(logWriter, "Telemetry Disabled") + s.log.Infoln("Telemetry Disabled") } - fmt.Fprintln(logWriter, "++++++++++++++++++++++++++++++++++++++++") + s.log.Infoln("++++++++++++++++++++++++++++++++++++++++") metricLabels := map[string]string{} if s.log.GetTelemetryEnabled() { diff --git a/scripts/algorand_node_log.json b/scripts/algorand_node_log.json new file mode 100644 index 0000000000..78659dc3a7 --- /dev/null +++ b/scripts/algorand_node_log.json @@ -0,0 +1,58 @@ +{ + "algorand_node_log" : { + "title" : "Algorand Node Log Format", + "description" : "Log format for logrus, used by go-algorand.", + "url" : "https://github.com/sirupsen/logrus", + "level-field" : "level", + "timestamp-field" : "time", + "json": true, + "body-field": "msg", + "line-format" : [ + { "field" : "time", "timestamp-format": "%b %d %H:%M:%S" }, + " ", + { "field" : "level", "text-transform": "uppercase", "min-width": 4, "max-width": 4, "overflow": "truncate"}, + " ", + "[", { "field": "file" }, ":", {"field": "line"}, "]", + " ", + { "field": "Context" }, + " ", + { "field" : "msg" }, + " [", { "field": "function" }, "]" + ], + "hide-extra": true, + "level" : { + "info" : "info", + "error" : "error", + "warning" : "warning", + "debug" : "debug" + }, + "value": { + "file": { + "kind": "string", + "identifier": true + }, + "line": { + "kind": "integer", + "foreign-key": true + }, + "function": { + "kind": "string", + "identifier": true + }, + "Context": { + "kind": "string", + "identifier": true + } + }, + "sample": [ + { + "line": "{\"file\":\"trackerdbV2.go\",\"function\":\"github.com/algorand/go-algorand/ledger/store.(*trackerDBSchemaInitializer).upgradeDatabaseSchema0\",\"level\":\"info\",\"line\":203,\"msg\":\"upgradeDatabaseSchema0 initializing schema\",\"name\":\"\",\"time\":\"2022-12-29T16:26:58.478738+02:00\"}", + "level": "info" + }, + { + "line": "{\"Context\":\"sync\",\"details\":{\"StartRound\":0},\"file\":\"telemetry.go\",\"function\":\"github.com/algorand/go-algorand/logging.(*telemetryState).logTelemetry\",\"instanceName\":\"iFepr+AcMdoqEg+2\",\"level\":\"info\",\"line\":261,\"msg\":\"/ApplicationState/CatchupStart\",\"name\":\"\",\"session\":\"\",\"time\":\"2022-12-29T16:26:58.763458+02:00\",\"v\":\"3.14.167910\"}", + "level": "info" + } + ] + } +} diff --git a/scripts/configure_dev.sh b/scripts/configure_dev.sh index 6eb00df3d4..c1190bf5bd 100755 --- a/scripts/configure_dev.sh +++ b/scripts/configure_dev.sh @@ -85,6 +85,8 @@ elif [ "${OS}" = "darwin" ]; then install_or_upgrade autoconf install_or_upgrade automake install_or_upgrade python3 + install_or_upgrade lnav + lnav -i "$SCRIPTPATH/algorand_node_log.json" fi 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-boost mingw-w64-x86_64-python mingw-w64-x86_64-jq unzip procps; then From e659181f75fb8b054c2c902fbc1d7af33febd043 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Fri, 24 Mar 2023 15:31:49 -0400 Subject: [PATCH 015/125] test: enable runtime metrics in custom templates (#5231) --- .../recipes/custom/configs/nonPartNode.json | 2 +- .../deployednettemplates/recipes/custom/configs/relay.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/testdata/deployednettemplates/recipes/custom/configs/nonPartNode.json b/test/testdata/deployednettemplates/recipes/custom/configs/nonPartNode.json index 5b0a52d9d9..42d491b2e0 100644 --- a/test/testdata/deployednettemplates/recipes/custom/configs/nonPartNode.json +++ b/test/testdata/deployednettemplates/recipes/custom/configs/nonPartNode.json @@ -1,5 +1,5 @@ { "APIEndpoint": "{{APIEndpoint}}", "APIToken": "{{APIToken}}", - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"BaseLoggerDebugLevel\": 4, \"CadaverSizeTarget\": 0 }" + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"BaseLoggerDebugLevel\": 4, \"EnableRuntimeMetrics\": true, \"CadaverSizeTarget\": 0 }" } diff --git a/test/testdata/deployednettemplates/recipes/custom/configs/relay.json b/test/testdata/deployednettemplates/recipes/custom/configs/relay.json index 25bb6b5a26..2f621f1a29 100644 --- a/test/testdata/deployednettemplates/recipes/custom/configs/relay.json +++ b/test/testdata/deployednettemplates/recipes/custom/configs/relay.json @@ -7,5 +7,5 @@ "TelemetryURI": "{{TelemetryURI}}", "EnableMetrics": true, "MetricsURI": "{{MetricsURI}}", - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"EnableIncomingMessageFilter\": true, \"CadaverSizeTarget\": 0, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true }" + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"EnableIncomingMessageFilter\": true, \"EnableRuntimeMetrics\": true, \"CadaverSizeTarget\": 0, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true }" } From 9a0f374be6b829dfc0afc61f50948adbbf08da86 Mon Sep 17 00:00:00 2001 From: algochoi <86622919+algochoi@users.noreply.github.com> Date: Mon, 27 Mar 2023 14:26:07 -0400 Subject: [PATCH 016/125] Simulate: Add opcode costs and budgets into response (#5221) --- daemon/algod/api/algod.oas2.json | 16 + daemon/algod/api/algod.oas3.yml | 16 + .../api/server/v2/generated/data/routes.go | 324 +++++++------ .../v2/generated/experimental/routes.go | 50 +- .../api/server/v2/generated/model/types.go | 12 + .../nonparticipating/private/routes.go | 60 +-- .../nonparticipating/public/routes.go | 458 +++++++++--------- .../generated/participating/private/routes.go | 336 ++++++------- .../generated/participating/public/routes.go | 353 +++++++------- daemon/algod/api/server/v2/handlers.go | 14 +- .../algod/api/server/v2/test/handlers_test.go | 20 +- daemon/algod/api/server/v2/utils.go | 12 +- .../logic/mocktracer/scenarios.go | 24 + ledger/simulation/simulation_eval_test.go | 308 +++++++++++- ledger/simulation/simulator.go | 7 + ledger/simulation/trace.go | 15 +- ledger/simulation/tracer.go | 17 +- 17 files changed, 1227 insertions(+), 815 deletions(-) diff --git a/daemon/algod/api/algod.oas2.json b/daemon/algod/api/algod.oas2.json index ed48f36663..738eeddf8b 100644 --- a/daemon/algod/api/algod.oas2.json +++ b/daemon/algod/api/algod.oas2.json @@ -3377,6 +3377,14 @@ "items": { "type": "integer" } + }, + "app-budget-added": { + "description": "Total budget added during execution of app calls in the transaction group.", + "type": "integer" + }, + "app-budget-consumed": { + "description": "Total budget consumed during execution of app calls in the transaction group.", + "type": "integer" } } }, @@ -3393,6 +3401,14 @@ "missing-signature": { "description": "A boolean indicating whether this transaction is missing signatures", "type": "boolean" + }, + "app-budget-consumed": { + "description": "Budget used during execution of an app call transaction. This value includes budged used by inner app calls spawned by this transaction.", + "type": "integer" + }, + "logic-sig-budget-consumed": { + "description": "Budget used during execution of a logic sig transaction.", + "type": "integer" } } }, diff --git a/daemon/algod/api/algod.oas3.yml b/daemon/algod/api/algod.oas3.yml index 846f60a156..3b7df350f4 100644 --- a/daemon/algod/api/algod.oas3.yml +++ b/daemon/algod/api/algod.oas3.yml @@ -1838,6 +1838,14 @@ "SimulateTransactionGroupResult": { "description": "Simulation result for an atomic transaction group", "properties": { + "app-budget-added": { + "description": "Total budget added during execution of app calls in the transaction group.", + "type": "integer" + }, + "app-budget-consumed": { + "description": "Total budget consumed during execution of app calls in the transaction group.", + "type": "integer" + }, "failed-at": { "description": "If present, indicates which transaction in this group caused the failure. This array represents the path to the failing transaction. Indexes are zero based, the first element indicates the top-level transaction, and successive elements indicate deeper inner transactions.", "items": { @@ -1865,6 +1873,14 @@ "SimulateTransactionResult": { "description": "Simulation result for an individual transaction", "properties": { + "app-budget-consumed": { + "description": "Budget used during execution of an app call transaction. This value includes budged used by inner app calls spawned by this transaction.", + "type": "integer" + }, + "logic-sig-budget-consumed": { + "description": "Budget used during execution of a logic sig transaction.", + "type": "integer" + }, "missing-signature": { "description": "A boolean indicating whether this transaction is missing signatures", "type": "boolean" diff --git a/daemon/algod/api/server/v2/generated/data/routes.go b/daemon/algod/api/server/v2/generated/data/routes.go index 4556670809..192dc5095c 100644 --- a/daemon/algod/api/server/v2/generated/data/routes.go +++ b/daemon/algod/api/server/v2/generated/data/routes.go @@ -145,174 +145,176 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", + "xwAA", } // 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 3e8edb58bd..cce4c207c2 100644 --- a/daemon/algod/api/server/v2/generated/experimental/routes.go +++ b/daemon/algod/api/server/v2/generated/experimental/routes.go @@ -211,30 +211,32 @@ var swaggerSpec = []string{ "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", + "AA==", } // 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 5837057832..e838323e7f 100644 --- a/daemon/algod/api/server/v2/generated/model/types.go +++ b/daemon/algod/api/server/v2/generated/model/types.go @@ -610,6 +610,12 @@ type PendingTransactionResponse struct { // SimulateTransactionGroupResult Simulation result for an atomic transaction group type SimulateTransactionGroupResult struct { + // AppBudgetAdded Total budget added during execution of app calls in the transaction group. + AppBudgetAdded *uint64 `json:"app-budget-added,omitempty"` + + // AppBudgetConsumed Total budget consumed during execution of app calls in the transaction group. + AppBudgetConsumed *uint64 `json:"app-budget-consumed,omitempty"` + // FailedAt If present, indicates which transaction in this group caused the failure. This array represents the path to the failing transaction. Indexes are zero based, the first element indicates the top-level transaction, and successive elements indicate deeper inner transactions. FailedAt *[]uint64 `json:"failed-at,omitempty"` @@ -622,6 +628,12 @@ type SimulateTransactionGroupResult struct { // SimulateTransactionResult Simulation result for an individual transaction type SimulateTransactionResult struct { + // AppBudgetConsumed Budget used during execution of an app call transaction. This value includes budged used by inner app calls spawned by this transaction. + AppBudgetConsumed *uint64 `json:"app-budget-consumed,omitempty"` + + // LogicSigBudgetConsumed Budget used during execution of a logic sig transaction. + LogicSigBudgetConsumed *uint64 `json:"logic-sig-budget-consumed,omitempty"` + // MissingSignature A boolean indicating whether this transaction is missing signatures MissingSignature *bool `json:"missing-signature,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 5bf532b490..3645c50400 100644 --- a/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go +++ b/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go @@ -266,35 +266,37 @@ var swaggerSpec = []string{ "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", + "4/X/BAAA///WXlTym8AAAA==", } // 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 c5e3638286..36c2df859b 100644 --- a/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go +++ b/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go @@ -572,234 +572,236 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", + "Ojxv3///AQAA//9KJ5oXyksBAA==", } // 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 b513cffac6..ff9eb0f79d 100644 --- a/daemon/algod/api/server/v2/generated/participating/private/routes.go +++ b/daemon/algod/api/server/v2/generated/participating/private/routes.go @@ -158,173 +158,175 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "XwAAAP//qWbn/XHIAAA=", + "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", + "fvxYk8Cn+q5wpHD98fp/AgAA//+cgrUFs8oAAA==", } // 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 f197fd7aa7..7cf1dcd13f 100644 --- a/daemon/algod/api/server/v2/generated/participating/public/routes.go +++ b/daemon/algod/api/server/v2/generated/participating/public/routes.go @@ -177,181 +177,184 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "dfgnpWTXWNbv/cf/CQAA///C44qjANcAAA==", + "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", + "QtkAAA==", } // 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 d9e53216d9..08b14631cf 100644 --- a/daemon/algod/api/server/v2/handlers.go +++ b/daemon/algod/api/server/v2/handlers.go @@ -920,15 +920,19 @@ func (v2 *Handlers) RawTransaction(ctx echo.Context) error { // preEncodedSimulateTxnResult mirrors model.SimulateTransactionResult type preEncodedSimulateTxnResult struct { - Txn PreEncodedTxInfo `codec:"txn-result"` - MissingSignature *bool `codec:"missing-signature,omitempty"` + Txn PreEncodedTxInfo `codec:"txn-result"` + MissingSignature *bool `codec:"missing-signature,omitempty"` + AppBudgetConsumed *uint64 `codec:"app-budget-consumed,omitempty"` + LogicSigBudgetConsumed *uint64 `codec:"logic-sig-budget-consumed,omitempty"` } // preEncodedSimulateTxnGroupResult mirrors model.SimulateTransactionGroupResult type preEncodedSimulateTxnGroupResult struct { - Txns []preEncodedSimulateTxnResult `codec:"txn-results"` - FailureMessage *string `codec:"failure-message,omitempty"` - FailedAt *[]uint64 `codec:"failed-at,omitempty"` + Txns []preEncodedSimulateTxnResult `codec:"txn-results"` + FailureMessage *string `codec:"failure-message,omitempty"` + FailedAt *[]uint64 `codec:"failed-at,omitempty"` + AppBudgetAdded *uint64 `codec:"app-budget-added,omitempty"` + AppBudgetConsumed *uint64 `codec:"app-budget-consumed,omitempty"` } // preEncodedSimulateResponse mirrors model.SimulateResponse diff --git a/daemon/algod/api/server/v2/test/handlers_test.go b/daemon/algod/api/server/v2/test/handlers_test.go index 0c7c5135c4..ed9b069c82 100644 --- a/daemon/algod/api/server/v2/test/handlers_test.go +++ b/daemon/algod/api/server/v2/test/handlers_test.go @@ -82,6 +82,13 @@ func setupTestForMethodGet(t *testing.T, consensusUpgrade bool) (v2.Handlers, ec return handler, c, rec, rootkeys, stxns, releasefunc } +func numOrNil(n uint64) *uint64 { + if n == 0 { + return nil + } + return &n +} + func TestSimpleMockBuilding(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() @@ -960,23 +967,34 @@ int 1`, clone[0]++ expectedFailedAt = &clone } + + var txnAppBudgetUsed []*uint64 + appBudgetAdded := numOrNil(scenario.AppBudgetAdded) + appBudgetConsumed := numOrNil(scenario.AppBudgetConsumed) + for i := range scenario.TxnAppBudgetConsumed { + txnAppBudgetUsed = append(txnAppBudgetUsed, numOrNil(scenario.TxnAppBudgetConsumed[i])) + } expectedBody := model.SimulateResponse{ Version: 1, TxnGroups: []model.SimulateTransactionGroupResult{ { - FailedAt: expectedFailedAt, + AppBudgetAdded: appBudgetAdded, + AppBudgetConsumed: appBudgetConsumed, + FailedAt: expectedFailedAt, TxnResults: []model.SimulateTransactionResult{ { TxnResult: makePendingTxnResponse(t, transactions.SignedTxnWithAD{ SignedTxn: stxns[0], // expect no ApplyData info }, responseFormat.handle), + AppBudgetConsumed: txnAppBudgetUsed[0], }, { TxnResult: makePendingTxnResponse(t, transactions.SignedTxnWithAD{ SignedTxn: stxns[1], ApplyData: scenario.ExpectedSimulationAD, }, responseFormat.handle), + AppBudgetConsumed: txnAppBudgetUsed[1], }, }, }, diff --git a/daemon/algod/api/server/v2/utils.go b/daemon/algod/api/server/v2/utils.go index b2b4e4a351..a78e333330 100644 --- a/daemon/algod/api/server/v2/utils.go +++ b/daemon/algod/api/server/v2/utils.go @@ -355,8 +355,10 @@ func ConvertInnerTxn(txn *transactions.SignedTxnWithAD) PreEncodedTxInfo { func convertTxnResult(txnResult simulation.TxnResult) preEncodedSimulateTxnResult { return preEncodedSimulateTxnResult{ - Txn: ConvertInnerTxn(&txnResult.Txn), - MissingSignature: trueOrNil(txnResult.MissingSignature), + Txn: ConvertInnerTxn(&txnResult.Txn), + MissingSignature: trueOrNil(txnResult.MissingSignature), + AppBudgetConsumed: numOrNil(txnResult.AppBudgetConsumed), + LogicSigBudgetConsumed: numOrNil(txnResult.LogicSigBudgetConsumed), } } @@ -367,8 +369,10 @@ func convertTxnGroupResult(txnGroupResult simulation.TxnGroupResult) preEncodedS } encoded := preEncodedSimulateTxnGroupResult{ - Txns: txnResults, - FailureMessage: strOrNil(txnGroupResult.FailureMessage), + Txns: txnResults, + FailureMessage: strOrNil(txnGroupResult.FailureMessage), + AppBudgetAdded: numOrNil(txnGroupResult.AppBudgetAdded), + AppBudgetConsumed: numOrNil(txnGroupResult.AppBudgetConsumed), } if len(txnGroupResult.FailedAt) > 0 { diff --git a/data/transactions/logic/mocktracer/scenarios.go b/data/transactions/logic/mocktracer/scenarios.go index f89a1d1396..89fbdafe9c 100644 --- a/data/transactions/logic/mocktracer/scenarios.go +++ b/data/transactions/logic/mocktracer/scenarios.go @@ -172,6 +172,9 @@ type TestScenario struct { FailedAt []uint64 ExpectedEvents []Event ExpectedSimulationAD transactions.ApplyData + AppBudgetAdded uint64 + AppBudgetConsumed uint64 + TxnAppBudgetConsumed []uint64 } // TestScenarioGenerator is a function which instantiates a TestScenario @@ -240,6 +243,9 @@ func GetTestScenarios() map[string]TestScenarioGenerator { }, }), ExpectedSimulationAD: expectedAD, + AppBudgetAdded: 2100, + AppBudgetConsumed: 35, + TxnAppBudgetConsumed: []uint64{0, 35}, } } @@ -292,6 +298,9 @@ func GetTestScenarios() map[string]TestScenarioGenerator { }, }), ExpectedSimulationAD: expectedAD, + AppBudgetAdded: 700, + AppBudgetConsumed: 4, + TxnAppBudgetConsumed: []uint64{0, 4}, } } scenarios[beforeInnersName] = beforeInners @@ -341,6 +350,9 @@ func GetTestScenarios() map[string]TestScenarioGenerator { }, }), ExpectedSimulationAD: expectedAD, + AppBudgetAdded: 1400, + AppBudgetConsumed: 15, + TxnAppBudgetConsumed: []uint64{0, 15}, } } scenarios[firstInnerName] = firstInner @@ -391,6 +403,9 @@ func GetTestScenarios() map[string]TestScenarioGenerator { }, }), ExpectedSimulationAD: expectedAD, + AppBudgetAdded: 1400, + AppBudgetConsumed: 19, + TxnAppBudgetConsumed: []uint64{0, 19}, } } scenarios[betweenInnersName] = betweenInners @@ -449,6 +464,9 @@ func GetTestScenarios() map[string]TestScenarioGenerator { }, }), ExpectedSimulationAD: expectedAD, + AppBudgetAdded: 2100, + AppBudgetConsumed: 32, + TxnAppBudgetConsumed: []uint64{0, 32}, } } scenarios[secondInnerName] = secondInner @@ -510,6 +528,9 @@ func GetTestScenarios() map[string]TestScenarioGenerator { }, }), ExpectedSimulationAD: expectedAD, + AppBudgetAdded: 2100, + AppBudgetConsumed: 32, + TxnAppBudgetConsumed: []uint64{0, 32}, } } scenarios[thirdInnerName] = thirdInner @@ -567,6 +588,9 @@ func GetTestScenarios() map[string]TestScenarioGenerator { }, }), ExpectedSimulationAD: expectedAD, + AppBudgetAdded: 2100, + AppBudgetConsumed: 35, + TxnAppBudgetConsumed: []uint64{0, 35}, } } scenarios[afterInnersName] = afterInners diff --git a/ledger/simulation/simulation_eval_test.go b/ledger/simulation/simulation_eval_test.go index 0af8b5ae01..134fdd2339 100644 --- a/ledger/simulation/simulation_eval_test.go +++ b/ledger/simulation/simulation_eval_test.go @@ -20,6 +20,7 @@ import ( "encoding/binary" "encoding/hex" "fmt" + "strings" "testing" "github.com/algorand/go-algorand/crypto" @@ -541,21 +542,25 @@ btoi`) name string arguments [][]byte expectedError string + cost uint64 }{ { name: "approval", arguments: [][]byte{{1}}, expectedError: "", // no error + cost: 2, }, { name: "rejection", arguments: [][]byte{{0}}, expectedError: "rejected by logic", + cost: 2, }, { name: "error", arguments: [][]byte{}, expectedError: "rejected by logic err=cannot load arg[0] of 0", + cost: 1, }, } @@ -595,6 +600,7 @@ int 1`, expectedSuccess := len(testCase.expectedError) == 0 var expectedAppCallAD transactions.ApplyData expectedFailedAt := simulation.TxnPath{1} + var AppBudgetConsumed, AppBudgetAdded uint64 if expectedSuccess { expectedAppCallAD = transactions.ApplyData{ ApplicationID: 2, @@ -603,6 +609,8 @@ int 1`, }, } expectedFailedAt = nil + AppBudgetConsumed = 3 + AppBudgetAdded = 700 } return simulationTestCase{ @@ -619,9 +627,13 @@ int 1`, Txn: transactions.SignedTxnWithAD{ ApplyData: expectedAppCallAD, }, + AppBudgetConsumed: AppBudgetConsumed, + LogicSigBudgetConsumed: testCase.cost, }, }, - FailedAt: expectedFailedAt, + FailedAt: expectedFailedAt, + AppBudgetAdded: AppBudgetAdded, + AppBudgetConsumed: AppBudgetConsumed, }, }, WouldSucceed: expectedSuccess, @@ -698,7 +710,8 @@ int 0 }, }, }, - MissingSignature: !signed, + MissingSignature: !signed, + AppBudgetConsumed: 5, }, { Txn: transactions.SignedTxnWithAD{ @@ -708,9 +721,12 @@ int 0 }, }, }, - MissingSignature: !signed, + MissingSignature: !signed, + AppBudgetConsumed: 6, }, }, + AppBudgetAdded: 1400, + AppBudgetConsumed: 11, }, }, WouldSucceed: signed, @@ -771,10 +787,13 @@ int 0 }, }, }, - MissingSignature: !signed, + MissingSignature: !signed, + AppBudgetConsumed: 3, }, }, - FailedAt: simulation.TxnPath{0}, + FailedAt: simulation.TxnPath{0}, + AppBudgetAdded: 700, + AppBudgetConsumed: 3, }, }, WouldSucceed: false, @@ -835,10 +854,13 @@ int 0 }, }, }, - MissingSignature: !signed, + MissingSignature: !signed, + AppBudgetConsumed: 3, }, }, - FailedAt: simulation.TxnPath{0}, + FailedAt: simulation.TxnPath{0}, + AppBudgetAdded: 700, + AppBudgetConsumed: 3, }, }, WouldSucceed: false, @@ -849,6 +871,237 @@ int 0 } } +func TestAppCallOverBudget(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + // Transaction group has a cost of 4 + 1398 + expensiveAppSource := `#pragma version 6 + txn ApplicationID // [appId] + bz end // [] +` + strings.Repeat(`int 1 + pop +`, 697) + `end: + int 1` + + simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { + sender := accounts[0] + receiver := accounts[1] + + futureAppID := basics.AppIndex(1) + // App create with cost 4 + createTxn := txnInfo.NewTxn(txntest.Txn{ + Type: protocol.ApplicationCallTx, + Sender: sender.Addr, + ApplicationID: 0, + ApprovalProgram: expensiveAppSource, + ClearStateProgram: `#pragma version 6 +int 0 +`, + }) + // App call with cost 1398 - will cause a budget exceeded error, + // but will only report a cost up to 1396. + expensiveTxn := txnInfo.NewTxn(txntest.Txn{ + Type: protocol.ApplicationCallTx, + Sender: sender.Addr, + ApplicationID: futureAppID, + Accounts: []basics.Address{receiver.Addr}, + }) + + txntest.Group(&createTxn, &expensiveTxn) + + signedCreateTxn := createTxn.Txn().Sign(sender.Sk) + signedExpensiveTxn := expensiveTxn.Txn().Sign(sender.Sk) + + return simulationTestCase{ + input: []transactions.SignedTxn{signedCreateTxn, signedExpensiveTxn}, + expectedError: "dynamic cost budget exceeded", + expected: simulation.Result{ + Version: 1, + LastRound: txnInfo.LatestRound(), + TxnGroups: []simulation.TxnGroupResult{ + { + Txns: []simulation.TxnResult{ + { + Txn: transactions.SignedTxnWithAD{ + ApplyData: transactions.ApplyData{ + ApplicationID: futureAppID, + }, + }, + AppBudgetConsumed: 4, + }, + { + AppBudgetConsumed: 1396, + }, + }, + FailedAt: simulation.TxnPath{1}, + AppBudgetAdded: 1400, + AppBudgetConsumed: 1400, + }, + }, + WouldSucceed: false, + }, + } + }) +} + +func TestLogicSigOverBudget(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + op, err := logic.AssembleString(`#pragma version 6 +` + strings.Repeat(`byte "a" +keccak256 +pop +`, 200) + `int 1`) + require.NoError(t, err) + program := logic.Program(op.Program) + lsigAddr := basics.Address(crypto.HashObj(&program)) + + simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { + sender := accounts[0] + + payTxn := txnInfo.NewTxn(txntest.Txn{ + Type: protocol.PaymentTx, + Sender: sender.Addr, + Receiver: lsigAddr, + Amount: 1_000_000, + }) + appCallTxn := txnInfo.NewTxn(txntest.Txn{ + Type: protocol.ApplicationCallTx, + Sender: lsigAddr, + ApprovalProgram: `#pragma version 8 +byte "hello" +log +int 1`, + ClearStateProgram: `#pragma version 8 +int 1`, + }) + + txntest.Group(&payTxn, &appCallTxn) + + signedPayTxn := payTxn.Txn().Sign(sender.Sk) + signedAppCallTxn := appCallTxn.SignedTxn() + signedAppCallTxn.Lsig = transactions.LogicSig{ + Logic: program, + } + + var expectedAppCallAD transactions.ApplyData + expectedFailedAt := simulation.TxnPath{1} + + // Opcode cost exceeded, but report current cost of LogicSig before it went over the limit. + return simulationTestCase{ + input: []transactions.SignedTxn{signedPayTxn, signedAppCallTxn}, + expectedError: "dynamic cost budget exceeded", + expected: simulation.Result{ + Version: 1, + LastRound: txnInfo.LatestRound(), + TxnGroups: []simulation.TxnGroupResult{ + { + Txns: []simulation.TxnResult{ + {}, + { + Txn: transactions.SignedTxnWithAD{ + ApplyData: expectedAppCallAD, + }, + AppBudgetConsumed: 0, + LogicSigBudgetConsumed: 19934, + }, + }, + FailedAt: expectedFailedAt, + AppBudgetAdded: 0, + AppBudgetConsumed: 0, + }, + }, + WouldSucceed: false, + }, + } + }) +} + +func TestAppAtBudget(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + // Transaction has a cost of 700 and invokes an inner transaction + exactly700AndCallInner := fmt.Sprintf(`#pragma version 6 +pushint 1 +cover 0 // This is a noop, just to fix an odd number of ops +%s +itxn_begin +int appl +itxn_field TypeEnum +byte 0x068101 +dup +itxn_field ClearStateProgram +itxn_field ApprovalProgram +itxn_submit +`, strings.Repeat(`pushint 1 +pop +`, 345)) + + simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { + sender := accounts[0] + + futureAppID := basics.AppIndex(2) + // fund outer app + fund := txnInfo.NewTxn(txntest.Txn{ + Type: protocol.PaymentTx, + Sender: sender.Addr, + Receiver: futureAppID.Address(), + Amount: 401_000, + }) + // create app + appCall := txnInfo.NewTxn(txntest.Txn{ + Type: protocol.ApplicationCallTx, + Sender: sender.Addr, + ApprovalProgram: exactly700AndCallInner, + ClearStateProgram: `#pragma version 6 +int 1`, + }) + + txntest.Group(&fund, &appCall) + + signedFundTxn := fund.Txn().Sign(sender.Sk) + signedAppCall := appCall.Txn().Sign(sender.Sk) + + return simulationTestCase{ + input: []transactions.SignedTxn{signedFundTxn, signedAppCall}, + expected: simulation.Result{ + Version: 1, + LastRound: txnInfo.LatestRound(), + TxnGroups: []simulation.TxnGroupResult{ + { + Txns: []simulation.TxnResult{ + {}, + { + Txn: transactions.SignedTxnWithAD{ + ApplyData: transactions.ApplyData{ + ApplicationID: futureAppID, + EvalDelta: transactions.EvalDelta{ + InnerTxns: []transactions.SignedTxnWithAD{ + { + ApplyData: transactions.ApplyData{ + ApplicationID: futureAppID + 1, + }, + }, + }, + }, + }, + }, + AppBudgetConsumed: 701, + }, + }, + AppBudgetAdded: 1400, + AppBudgetConsumed: 701, + }, + }, + WouldSucceed: true, + }, + } + }) +} + func TestSignatureCheck(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() @@ -1013,18 +1266,23 @@ int 1`, ApplicationID: futureAppID, }, }, - MissingSignature: !signed, + MissingSignature: !signed, + AppBudgetConsumed: 4, }, { - MissingSignature: !signed, + MissingSignature: !signed, + AppBudgetConsumed: 10, }, { MissingSignature: !signed, }, { - MissingSignature: !signed, + MissingSignature: !signed, + AppBudgetConsumed: 10, }, }, + AppBudgetAdded: 2100, + AppBudgetConsumed: 24, }, }, WouldSucceed: signed, @@ -1275,10 +1533,13 @@ int 1`, }, }, }, - MissingSignature: true, + AppBudgetConsumed: 27, + MissingSignature: true, }, }, - FailedAt: simulation.TxnPath{2, 0, 0}, + AppBudgetAdded: 2100, + AppBudgetConsumed: 27, + FailedAt: simulation.TxnPath{2, 0, 0}, }, }, WouldSucceed: false, @@ -1363,10 +1624,13 @@ int 1`, }, }, }, - MissingSignature: true, + AppBudgetConsumed: 23, + MissingSignature: true, }, }, - FailedAt: simulation.TxnPath{1, 1}, + AppBudgetAdded: 2100, + AppBudgetConsumed: 23, + FailedAt: simulation.TxnPath{1, 1}, }, }, WouldSucceed: false, @@ -1446,10 +1710,13 @@ int 1`, }, }, }, - MissingSignature: true, + AppBudgetConsumed: 17, + MissingSignature: true, }, }, - FailedAt: simulation.TxnPath{1, 1}, + AppBudgetAdded: 2100, + AppBudgetConsumed: 17, + FailedAt: simulation.TxnPath{1, 1}, }, }, WouldSucceed: false, @@ -1506,13 +1773,18 @@ func TestMockTracerScenarios(t *testing.T) { LastRound: txnInfo.LatestRound(), TxnGroups: []simulation.TxnGroupResult{ { - FailedAt: expectedFailedAt, + AppBudgetAdded: scenario.AppBudgetAdded, + AppBudgetConsumed: scenario.AppBudgetConsumed, + FailedAt: expectedFailedAt, Txns: []simulation.TxnResult{ - {}, + { + AppBudgetConsumed: scenario.TxnAppBudgetConsumed[0], + }, { Txn: transactions.SignedTxnWithAD{ ApplyData: scenario.ExpectedSimulationAD, }, + AppBudgetConsumed: scenario.TxnAppBudgetConsumed[1], }, }, }, diff --git a/ledger/simulation/simulator.go b/ledger/simulation/simulator.go index 5ae93b0195..e2b8ffd115 100644 --- a/ledger/simulation/simulator.go +++ b/ledger/simulation/simulator.go @@ -218,5 +218,12 @@ func (s Simulator) Simulate(txgroup []transactions.SignedTxn) (Result, error) { simulatorTracer.result.WouldSucceed = false } + // Update total cost by aggregating individual txn costs + totalCost := uint64(0) + for _, txn := range simulatorTracer.result.TxnGroups[0].Txns { + totalCost += txn.AppBudgetConsumed + } + simulatorTracer.result.TxnGroups[0].AppBudgetConsumed = totalCost + return *simulatorTracer.result, nil } diff --git a/ledger/simulation/trace.go b/ledger/simulation/trace.go index d9c2e5f6a0..6313e0539c 100644 --- a/ledger/simulation/trace.go +++ b/ledger/simulation/trace.go @@ -30,8 +30,10 @@ type TxnPath []uint64 // TxnResult contains the simulation result for a single transaction type TxnResult struct { - Txn transactions.SignedTxnWithAD - MissingSignature bool + Txn transactions.SignedTxnWithAD + MissingSignature bool + AppBudgetConsumed uint64 + LogicSigBudgetConsumed uint64 } // TxnGroupResult contains the simulation result for a single transaction group @@ -41,6 +43,15 @@ type TxnGroupResult struct { // FailedAt is the path to the txn that failed inside of this group FailedAt TxnPath + + // AppBudgetAdded is the total opcode budget for this group + AppBudgetAdded uint64 + + // AppBudgetConsumed is the total opcode cost used for this group + AppBudgetConsumed uint64 + + // FeeCredit is the fees left over after covering fees for this group + FeeCredit uint64 } func makeTxnGroupResult(txgroup []transactions.SignedTxn) TxnGroupResult { diff --git a/ledger/simulation/tracer.go b/ledger/simulation/tracer.go index 7f5840753f..9a42e617c2 100644 --- a/ledger/simulation/tracer.go +++ b/ledger/simulation/tracer.go @@ -130,8 +130,17 @@ func (tracer *evalTracer) BeforeTxnGroup(ep *logic.EvalParams) { if ep.GetCaller() != nil { // If this is an inner txn group, save the txns tracer.populateInnerTransactions(ep.TxnGroup) + tracer.result.TxnGroups[0].AppBudgetAdded += uint64(ep.Proto.MaxAppProgramCost) } tracer.cursorEvalTracer.BeforeTxnGroup(ep) + + // Currently only supports one (first) txn group + if ep.PooledApplicationBudget != nil && tracer.result.TxnGroups[0].AppBudgetAdded == 0 { + tracer.result.TxnGroups[0].AppBudgetAdded = uint64(*ep.PooledApplicationBudget) + } + if ep.FeeCredit != nil { + tracer.result.TxnGroups[0].FeeCredit = *ep.FeeCredit + } } func (tracer *evalTracer) AfterTxnGroup(ep *logic.EvalParams, evalError error) { @@ -186,8 +195,14 @@ func (tracer *evalTracer) AfterOpcode(cx *logic.EvalContext, evalError error) { func (tracer *evalTracer) AfterProgram(cx *logic.EvalContext, evalError error) { if cx.RunMode() != logic.ModeApp { - // do nothing for LogicSig programs + // Report cost for LogicSig program and exit + tracer.result.TxnGroups[0].Txns[cx.GroupIndex()].LogicSigBudgetConsumed = uint64(cx.Cost()) return } + + // Report cost of this program. + // If it is an inner app call, roll up its cost to the top level transaction. + tracer.result.TxnGroups[0].Txns[tracer.relativeCursor[0]].AppBudgetConsumed += uint64(cx.Cost()) + tracer.handleError(evalError) } From 773869194d2d5847a127ddbe48e217a9e6993538 Mon Sep 17 00:00:00 2001 From: shiqizng <80276844+shiqizng@users.noreply.github.com> Date: Wed, 29 Mar 2023 11:01:17 -0400 Subject: [PATCH 017/125] docker: option to override topology file and peer address. (#5209) --- docker/README.md | 22 ++++----- docker/files/run/followermode_template.json | 51 +++++++++++++++++++++ docker/files/run/run.sh | 21 +++++++-- 3 files changed, 78 insertions(+), 16 deletions(-) create mode 100644 docker/files/run/followermode_template.json diff --git a/docker/README.md b/docker/README.md index 5db6b20a1d..0ea18b0c55 100644 --- a/docker/README.md +++ b/docker/README.md @@ -22,15 +22,16 @@ The following environment variables can be supplied. Except when noted, it is po | Variable | Description | | -------- | ----------- | -| NETWORK | Leave blank for a private network, otherwise specify one of mainnet, betanet, testnet, or devnet. Only used during a data directory initialization. | -| FAST_CATCHUP | If set to 1 on a public network, attempt to start fast-catchup during initial config. | -| TELEMETRY_NAME| If set on a public network, telemetry is reported with this name. | -| DEV_MODE | If set to 1 on a private network, enable dev mode. Only used during data directory initialization. | -| NUM_ROUNDS | If set on a private network, override default of 30000 participation keys. | -| TOKEN | If set, overrides the REST API token. | -| ADMIN_TOKEN | If set, overrides the REST API admin token. | -| KMD_TOKEN | If set along with `START_KMD`, override the KMD REST API token. | -| START_KMD | When set to 1, start kmd service with no timeout. THIS SHOULD NOT BE USED IN PRODUCTION. | +| NETWORK | Leave blank for a private network, otherwise specify one of mainnet, betanet, testnet, or devnet. Only used during a data directory initialization. | +| FAST_CATCHUP | If set to 1 on a public network, attempt to start fast-catchup during initial config. | +| TELEMETRY_NAME | If set on a public network, telemetry is reported with this name. | +| DEV_MODE | If set to 1 on a private network, enable dev mode. Only used during data directory initialization. | +| NUM_ROUNDS | If set on a private network, override default of 30000 participation keys. | +| TOKEN | If set, overrides the REST API token. | +| ADMIN_TOKEN | If set, overrides the REST API admin token. | +| KMD_TOKEN | If set along with `START_KMD`, override the KMD REST API token. | +| START_KMD | When set to 1, start kmd service with no timeout. THIS SHOULD NOT BE USED IN PRODUCTION. | +| PEER_ADDRESS | If set, override phonebook with peer ip:port (or semicolon separated list: ip:port;ip:port;ip:port...) | ### Special Files @@ -42,8 +43,7 @@ Configuration can be modified by specifying certain files. These can be changed | /etc/algorand/algod.token | Override default randomized REST API token. | | /etc/algorand/algod.admin.token | Override default randomized REST API admin token. | | /etc/algorand/logging.config | Use a custom [logging.config](https://developer.algorand.org/docs/run-a-node/reference/telemetry-config/#configuration) file for configuring telemetry. | - -TODO: `/etc/algorand/template.json` for overriding the private network topology. + | /etc/algorand/template.json | Override default private network topology. One of the nodes in the template must be named "data".| ## Example Configuration diff --git a/docker/files/run/followermode_template.json b/docker/files/run/followermode_template.json new file mode 100644 index 0000000000..ed074587e7 --- /dev/null +++ b/docker/files/run/followermode_template.json @@ -0,0 +1,51 @@ +{ + "Genesis": { + "ConsensusProtocol": "future", + "NetworkName": "followermodenet", + "FirstPartKeyRound": 0, + "LastPartKeyRound": NUM_ROUNDS, + "Wallets": [ + { + "Name": "Wallet1", + "Stake": 40, + "Online": true + }, + { + "Name": "Wallet2", + "Stake": 40, + "Online": true + }, + { + "Name": "Wallet3", + "Stake": 20, + "Online": true + } + ], + "DevMode": true + }, + "Nodes": [ + { + "Name": "data", + "IsRelay": true, + "Wallets": [ + { + "Name": "Wallet1", + "ParticipationOnly": false + }, + { + "Name": "Wallet2", + "ParticipationOnly": false + }, + { + "Name": "Wallet3", + "ParticipationOnly": false + } + ] + }, + { + "Name": "follower", + "IsRelay": false, + "ConfigJSONOverride": "{\"EnableFollowMode\":true,\"EndpointAddress\":\"0.0.0.0:8081\"}" + } + ] +} diff --git a/docker/files/run/run.sh b/docker/files/run/run.sh index bd4e79fe04..f9caa98818 100755 --- a/docker/files/run/run.sh +++ b/docker/files/run/run.sh @@ -35,8 +35,13 @@ function start_public_network() { catchup & fi - # redirect output to stdout - algod -o + if [ "$PEER_ADDRESS" != "" ]; then + printf "$PEER_ADDRESS" + algod -o -p $PEER_ADDRESS + else + # redirect output to stdout + algod -o + fi } function configure_data_dir() { @@ -59,7 +64,9 @@ function configure_data_dir() { # check for token overrides if [ "$TOKEN" != "" ]; then - echo "$TOKEN" >algod.token + for dir in ${ALGORAND_DATA}/../*/; do + echo "$TOKEN" > "$dir/algod.token" + done fi if [ "$ADMIN_TOKEN" != "" ]; then echo "$ADMIN_TOKEN" >algod.admin.token @@ -142,8 +149,12 @@ function start_private_network() { function start_new_private_network() { local TEMPLATE="template.json" - if [ "$DEV_MODE" = "1" ]; then - TEMPLATE="devmode_template.json" + if [ -f "/etc/algorand/template.json" ]; then + cp /etc/algorand/template.json "/node/run/$TEMPLATE" + else + if [ "$DEV_MODE" = "1" ]; then + TEMPLATE="devmode_template.json" + fi fi sed -i "s/NUM_ROUNDS/${NUM_ROUNDS:-30000}/" "/node/run/$TEMPLATE" goal network create --noclean -n dockernet -r "${ALGORAND_DATA}/.." -t "/node/run/$TEMPLATE" From 02f6d27f7848cf23cf53df23b837664444c5c2e4 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Wed, 29 Mar 2023 15:45:06 -0400 Subject: [PATCH 018/125] algod: search phonebook in data directory in addition to bindir (#5235) --- cmd/algod/main.go | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/cmd/algod/main.go b/cmd/algod/main.go index cb0960e61b..8ff24a9704 100644 --- a/cmd/algod/main.go +++ b/cmd/algod/main.go @@ -284,10 +284,15 @@ func run() int { if err != nil { log.Errorf("cannot locate node executable: %s", err) } else { - phonebookDir := filepath.Dir(ex) - phonebookAddresses, err = config.LoadPhonebook(phonebookDir) - if err != nil { - log.Debugf("Cannot load static phonebook: %v", err) + phonebookDirs := []string{filepath.Dir(ex), dataDir} + for _, phonebookDir := range phonebookDirs { + phonebookAddresses, err = config.LoadPhonebook(phonebookDir) + if err == nil { + log.Debugf("Static phonebook loaded from %s", phonebookDir) + break + } else { + log.Debugf("Cannot load static phonebook from %s dir: %v", phonebookDir, err) + } } } } From 122817157aaf859cfa9cfea688d1b9e8170e8755 Mon Sep 17 00:00:00 2001 From: cce <51567+cce@users.noreply.github.com> Date: Wed, 29 Mar 2023 15:45:46 -0400 Subject: [PATCH 019/125] ledger: rename "internal" to "eval" (#5236) --- ledger/acctupdates_test.go | 4 ++-- ledger/archival_test.go | 4 ++-- ledger/double_test.go | 10 +++++----- ledger/{internal => eval}/appcow.go | 5 +++-- ledger/{internal => eval}/appcow_test.go | 2 +- ledger/{internal => eval}/applications.go | 2 +- ledger/{internal => eval}/assetcow.go | 2 +- ledger/{internal => eval}/cow.go | 2 +- ledger/{internal => eval}/cow_creatables.go | 2 +- ledger/{internal => eval}/cow_test.go | 2 +- ledger/{internal => eval}/eval.go | 4 ++-- ledger/{internal => eval}/eval_test.go | 2 +- ledger/{internal => eval}/evalindexer.go | 2 +- ledger/{internal => eval}/prefetcher/error.go | 0 .../prefetcher/prefetcher.go | 0 .../prefetcher/prefetcher_alignment_test.go | 6 +++--- .../prefetcher/prefetcher_test.go | 2 +- .../prefetcher/prefetcher_whitebox_test.go | 0 ledger/evalbench_test.go | 4 ++-- ledger/evalindexer.go | 6 +++--- ledger/fullblock_perf_test.go | 12 +++++------ ledger/ledger.go | 20 +++++++++---------- ledger/ledger_perf_test.go | 4 ++-- ledger/simple_test.go | 16 +++++++-------- ledger/simulation/simulator_test.go | 4 ++-- ledger/tracker.go | 4 ++-- 26 files changed, 61 insertions(+), 60 deletions(-) rename ledger/{internal => eval}/appcow.go (99%) rename ledger/{internal => eval}/appcow_test.go (99%) rename ledger/{internal => eval}/applications.go (99%) rename ledger/{internal => eval}/assetcow.go (98%) rename ledger/{internal => eval}/cow.go (99%) rename ledger/{internal => eval}/cow_creatables.go (99%) rename ledger/{internal => eval}/cow_test.go (99%) rename ledger/{internal => eval}/eval.go (99%) rename ledger/{internal => eval}/eval_test.go (99%) rename ledger/{internal => eval}/evalindexer.go (99%) rename ledger/{internal => eval}/prefetcher/error.go (100%) rename ledger/{internal => eval}/prefetcher/prefetcher.go (100%) rename ledger/{internal => eval}/prefetcher/prefetcher_alignment_test.go (99%) rename ledger/{internal => eval}/prefetcher/prefetcher_test.go (99%) rename ledger/{internal => eval}/prefetcher/prefetcher_whitebox_test.go (100%) diff --git a/ledger/acctupdates_test.go b/ledger/acctupdates_test.go index ff9ff9efeb..904761f1dd 100644 --- a/ledger/acctupdates_test.go +++ b/ledger/acctupdates_test.go @@ -35,7 +35,7 @@ import ( "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/bookkeeping" - "github.com/algorand/go-algorand/ledger/internal" + "github.com/algorand/go-algorand/ledger/eval" "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/ledger/store/trackerdb" "github.com/algorand/go-algorand/ledger/store/trackerdb/sqlitedriver" @@ -191,7 +191,7 @@ func (ml *mockLedgerForTracker) addMockBlock(be blockEntry, delta ledgercore.Sta return nil } -func (ml *mockLedgerForTracker) trackerEvalVerified(blk bookkeeping.Block, accUpdatesLedger internal.LedgerForEvaluator) (ledgercore.StateDelta, error) { +func (ml *mockLedgerForTracker) trackerEvalVerified(blk bookkeeping.Block, accUpdatesLedger eval.LedgerForEvaluator) (ledgercore.StateDelta, error) { // support returning the deltas if the client explicitly provided them by calling addMockBlock, otherwise, // just return an empty state delta ( since the client clearly didn't care about these ) if len(ml.deltas) > int(blk.Round()) { diff --git a/ledger/archival_test.go b/ledger/archival_test.go index f30821a351..de483e227c 100644 --- a/ledger/archival_test.go +++ b/ledger/archival_test.go @@ -37,7 +37,7 @@ import ( "github.com/algorand/go-algorand/data/bookkeeping" "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/data/transactions/logic" - "github.com/algorand/go-algorand/ledger/internal" + "github.com/algorand/go-algorand/ledger/eval" "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/ledger/store/blockdb" "github.com/algorand/go-algorand/ledger/store/trackerdb" @@ -68,7 +68,7 @@ func (wl *wrappedLedger) BlockHdr(rnd basics.Round) (bookkeeping.BlockHeader, er return wl.l.BlockHdr(rnd) } -func (wl *wrappedLedger) trackerEvalVerified(blk bookkeeping.Block, accUpdatesLedger internal.LedgerForEvaluator) (ledgercore.StateDelta, error) { +func (wl *wrappedLedger) trackerEvalVerified(blk bookkeeping.Block, accUpdatesLedger eval.LedgerForEvaluator) (ledgercore.StateDelta, error) { return wl.l.trackerEvalVerified(blk, accUpdatesLedger) } diff --git a/ledger/double_test.go b/ledger/double_test.go index bbc5e95206..86ae3b6600 100644 --- a/ledger/double_test.go +++ b/ledger/double_test.go @@ -24,7 +24,7 @@ import ( "github.com/algorand/go-algorand/data/bookkeeping" "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/data/txntest" - "github.com/algorand/go-algorand/ledger/internal" + "github.com/algorand/go-algorand/ledger/eval" "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/protocol" "github.com/stretchr/testify/require" @@ -47,7 +47,7 @@ type DoubleLedger struct { generator *Ledger validator *Ledger - eval *internal.BlockEvaluator + eval *eval.BlockEvaluator } func (dl DoubleLedger) Close() { @@ -62,7 +62,7 @@ func NewDoubleLedger(t *testing.T, balances bookkeeping.GenesisBalances, cv prot return DoubleLedger{t, g, v, nil} } -func (dl *DoubleLedger) beginBlock() *internal.BlockEvaluator { +func (dl *DoubleLedger) beginBlock() *eval.BlockEvaluator { dl.eval = nextBlock(dl.t, dl.generator) return dl.eval } @@ -202,7 +202,7 @@ func checkBlock(t *testing.T, checkLedger *Ledger, vb *ledgercore.ValidatedBlock // require.Equal(t, vb.Delta().Accts, cb.Delta().Accts) } -func nextCheckBlock(t testing.TB, ledger *Ledger, rs bookkeeping.RewardsState) *internal.BlockEvaluator { +func nextCheckBlock(t testing.TB, ledger *Ledger, rs bookkeeping.RewardsState) *eval.BlockEvaluator { rnd := ledger.Latest() hdr, err := ledger.BlockHdr(rnd) require.NoError(t, err) @@ -211,7 +211,7 @@ func nextCheckBlock(t testing.TB, ledger *Ledger, rs bookkeeping.RewardsState) * nextHdr.RewardsState = rs // follow nextBlock, which does this for determinism nextHdr.TimeStamp = hdr.TimeStamp + 1 - eval, err := internal.StartEvaluator(ledger, nextHdr, internal.EvaluatorOptions{ + eval, err := eval.StartEvaluator(ledger, nextHdr, eval.EvaluatorOptions{ Generate: false, Validate: true, // Do the complete checks that a new txn would be subject to }) diff --git a/ledger/internal/appcow.go b/ledger/eval/appcow.go similarity index 99% rename from ledger/internal/appcow.go rename to ledger/eval/appcow.go index 5b53687e4a..0e22704521 100644 --- a/ledger/internal/appcow.go +++ b/ledger/eval/appcow.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with go-algorand. If not, see . -package internal +package eval import ( "fmt" @@ -29,7 +29,7 @@ import ( "github.com/algorand/go-algorand/protocol" ) -//msgp: ignore storageAction +//msgp:ignore storageAction type storageAction uint64 const ( @@ -74,6 +74,7 @@ func (vd valueDelta) serialize() (vdelta basics.ValueDelta, ok bool) { } // stateDelta is similar to basics.StateDelta but stores both values before and after change +// //msgp:ignore stateDelta type stateDelta map[string]valueDelta diff --git a/ledger/internal/appcow_test.go b/ledger/eval/appcow_test.go similarity index 99% rename from ledger/internal/appcow_test.go rename to ledger/eval/appcow_test.go index d4025524c4..87b7316299 100644 --- a/ledger/internal/appcow_test.go +++ b/ledger/eval/appcow_test.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with go-algorand. If not, see . -package internal +package eval import ( "fmt" diff --git a/ledger/internal/applications.go b/ledger/eval/applications.go similarity index 99% rename from ledger/internal/applications.go rename to ledger/eval/applications.go index f0466b7d4a..7263425e33 100644 --- a/ledger/internal/applications.go +++ b/ledger/eval/applications.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with go-algorand. If not, see . -package internal +package eval import ( "fmt" diff --git a/ledger/internal/assetcow.go b/ledger/eval/assetcow.go similarity index 98% rename from ledger/internal/assetcow.go rename to ledger/eval/assetcow.go index 3813dad7ce..50b7106757 100644 --- a/ledger/internal/assetcow.go +++ b/ledger/eval/assetcow.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with go-algorand. If not, see . -package internal +package eval import ( "github.com/algorand/go-algorand/data/basics" diff --git a/ledger/internal/cow.go b/ledger/eval/cow.go similarity index 99% rename from ledger/internal/cow.go rename to ledger/eval/cow.go index ade61f8204..ec7c21aa2f 100644 --- a/ledger/internal/cow.go +++ b/ledger/eval/cow.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with go-algorand. If not, see . -package internal +package eval import ( "errors" diff --git a/ledger/internal/cow_creatables.go b/ledger/eval/cow_creatables.go similarity index 99% rename from ledger/internal/cow_creatables.go rename to ledger/eval/cow_creatables.go index d43135cf89..1c5651a18a 100644 --- a/ledger/internal/cow_creatables.go +++ b/ledger/eval/cow_creatables.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with go-algorand. If not, see . -package internal +package eval import ( "fmt" diff --git a/ledger/internal/cow_test.go b/ledger/eval/cow_test.go similarity index 99% rename from ledger/internal/cow_test.go rename to ledger/eval/cow_test.go index 562c60f925..fffc80208e 100644 --- a/ledger/internal/cow_test.go +++ b/ledger/eval/cow_test.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with go-algorand. If not, see . -package internal +package eval import ( "reflect" diff --git a/ledger/internal/eval.go b/ledger/eval/eval.go similarity index 99% rename from ledger/internal/eval.go rename to ledger/eval/eval.go index 2d184e4b4c..2f9b1771df 100644 --- a/ledger/internal/eval.go +++ b/ledger/eval/eval.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with go-algorand. If not, see . -package internal +package eval import ( "context" @@ -30,7 +30,7 @@ import ( "github.com/algorand/go-algorand/data/transactions/logic" "github.com/algorand/go-algorand/data/transactions/verify" "github.com/algorand/go-algorand/ledger/apply" - "github.com/algorand/go-algorand/ledger/internal/prefetcher" + "github.com/algorand/go-algorand/ledger/eval/prefetcher" "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/protocol" diff --git a/ledger/internal/eval_test.go b/ledger/eval/eval_test.go similarity index 99% rename from ledger/internal/eval_test.go rename to ledger/eval/eval_test.go index 0d4e6aaa39..6dcc02d1c1 100644 --- a/ledger/internal/eval_test.go +++ b/ledger/eval/eval_test.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with go-algorand. If not, see . -package internal +package eval import ( "bytes" diff --git a/ledger/internal/evalindexer.go b/ledger/eval/evalindexer.go similarity index 99% rename from ledger/internal/evalindexer.go rename to ledger/eval/evalindexer.go index babf6c87db..af3a5546c6 100644 --- a/ledger/internal/evalindexer.go +++ b/ledger/eval/evalindexer.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with go-algorand. If not, see . -package internal +package eval import ( "fmt" diff --git a/ledger/internal/prefetcher/error.go b/ledger/eval/prefetcher/error.go similarity index 100% rename from ledger/internal/prefetcher/error.go rename to ledger/eval/prefetcher/error.go diff --git a/ledger/internal/prefetcher/prefetcher.go b/ledger/eval/prefetcher/prefetcher.go similarity index 100% rename from ledger/internal/prefetcher/prefetcher.go rename to ledger/eval/prefetcher/prefetcher.go diff --git a/ledger/internal/prefetcher/prefetcher_alignment_test.go b/ledger/eval/prefetcher/prefetcher_alignment_test.go similarity index 99% rename from ledger/internal/prefetcher/prefetcher_alignment_test.go rename to ledger/eval/prefetcher/prefetcher_alignment_test.go index 1d5291cf17..d7949f9b9d 100644 --- a/ledger/internal/prefetcher/prefetcher_alignment_test.go +++ b/ledger/eval/prefetcher/prefetcher_alignment_test.go @@ -29,8 +29,8 @@ 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/ledger/internal" - "github.com/algorand/go-algorand/ledger/internal/prefetcher" + "github.com/algorand/go-algorand/ledger/eval" + "github.com/algorand/go-algorand/ledger/eval/prefetcher" "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" @@ -290,7 +290,7 @@ func runEval(t *testing.T, l *prefetcherAlignmentTestLedger, txn transactions.Tr require.NoError(t, err) block := bookkeeping.MakeBlock(genesisBlockHeader) - eval, err := internal.StartEvaluator(l, block.BlockHeader, internal.EvaluatorOptions{}) + eval, err := eval.StartEvaluator(l, block.BlockHeader, eval.EvaluatorOptions{}) require.NoError(t, err) err = eval.TransactionGroup(makeGroupFromTxn(txn)) diff --git a/ledger/internal/prefetcher/prefetcher_test.go b/ledger/eval/prefetcher/prefetcher_test.go similarity index 99% rename from ledger/internal/prefetcher/prefetcher_test.go rename to ledger/eval/prefetcher/prefetcher_test.go index b9c1d80eb3..2eff5f3449 100644 --- a/ledger/internal/prefetcher/prefetcher_test.go +++ b/ledger/eval/prefetcher/prefetcher_test.go @@ -28,7 +28,7 @@ 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/ledger/internal/prefetcher" + "github.com/algorand/go-algorand/ledger/eval/prefetcher" "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" diff --git a/ledger/internal/prefetcher/prefetcher_whitebox_test.go b/ledger/eval/prefetcher/prefetcher_whitebox_test.go similarity index 100% rename from ledger/internal/prefetcher/prefetcher_whitebox_test.go rename to ledger/eval/prefetcher/prefetcher_whitebox_test.go diff --git a/ledger/evalbench_test.go b/ledger/evalbench_test.go index 9b827d83fb..407c6f67f1 100644 --- a/ledger/evalbench_test.go +++ b/ledger/evalbench_test.go @@ -37,7 +37,7 @@ import ( "github.com/algorand/go-algorand/data/bookkeeping" "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/data/transactions/logic" - "github.com/algorand/go-algorand/ledger/internal" + "github.com/algorand/go-algorand/ledger/eval" "github.com/algorand/go-algorand/ledger/ledgercore" ledgertesting "github.com/algorand/go-algorand/ledger/testing" "github.com/algorand/go-algorand/logging" @@ -491,7 +491,7 @@ func benchmarkBlockEvaluator(b *testing.B, inMem bool, withCrypto bool, proto pr if withCrypto { _, err = l2.Validate(context.Background(), validatedBlock.Block(), backlogPool) } else { - _, err = internal.Eval(context.Background(), l2, validatedBlock.Block(), false, nil, nil) + _, err = eval.Eval(context.Background(), l2, validatedBlock.Block(), false, nil, nil) } require.NoError(b, err) } diff --git a/ledger/evalindexer.go b/ledger/evalindexer.go index a575c638bd..68527729c0 100644 --- a/ledger/evalindexer.go +++ b/ledger/evalindexer.go @@ -25,7 +25,7 @@ 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/ledger/internal" + "github.com/algorand/go-algorand/ledger/eval" "github.com/algorand/go-algorand/ledger/ledgercore" ) @@ -233,9 +233,9 @@ func makeIndexerLedgerConnector(il indexerLedgerForEval, genesisHash crypto.Dige func EvalForIndexer(il indexerLedgerForEval, block *bookkeeping.Block, proto config.ConsensusParams, resources EvalForIndexerResources) (ledgercore.StateDelta, []transactions.SignedTxnInBlock, error) { ilc := makeIndexerLedgerConnector(il, block.GenesisHash(), proto, block.Round()-1, resources) - eval, err := internal.StartEvaluator( + eval, err := eval.StartEvaluator( ilc, block.BlockHeader, - internal.EvaluatorOptions{ + eval.EvaluatorOptions{ PaysetHint: len(block.Payset), ProtoParams: &proto, Generate: false, diff --git a/ledger/fullblock_perf_test.go b/ledger/fullblock_perf_test.go index 9bea4a00a0..0de160970a 100644 --- a/ledger/fullblock_perf_test.go +++ b/ledger/fullblock_perf_test.go @@ -37,7 +37,7 @@ import ( "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/data/transactions/logic" "github.com/algorand/go-algorand/data/transactions/verify" - "github.com/algorand/go-algorand/ledger/internal" + "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/protocol" @@ -53,7 +53,7 @@ type benchConfig struct { acctToApp map[basics.Address]map[basics.AppIndex]struct{} l0 *Ledger l1 *Ledger - eval *internal.BlockEvaluator + eval *eval.BlockEvaluator numPay uint64 numAst uint64 numApp uint64 @@ -119,7 +119,7 @@ func setupEnv(b *testing.B, numAccts int) (bc *benchConfig) { require.NoError(b, err) newBlk := bookkeeping.MakeBlock(blk.BlockHeader) - eval, err := l0.StartEvaluator(newBlk.BlockHeader, 5000, 0) + blockEvaluator, err := l0.StartEvaluator(newBlk.BlockHeader, 5000, 0) require.NoError(b, err) bc = &benchConfig{ @@ -132,7 +132,7 @@ func setupEnv(b *testing.B, numAccts int) (bc *benchConfig) { acctToApp: acctToApp, l0: l0, l1: l1, - eval: eval, + eval: blockEvaluator, } // start the ledger with a pool of accounts @@ -144,7 +144,7 @@ func setupEnv(b *testing.B, numAccts int) (bc *benchConfig) { addBlock(bc) vc := verify.GetMockedCache(true) for _, blk := range bc.blocks { - _, err := internal.Eval(context.Background(), bc.l1, blk, true, vc, nil) + _, err := eval.Eval(context.Background(), bc.l1, blk, true, vc, nil) require.NoError(b, err) err = bc.l1.AddBlock(blk, cert) require.NoError(b, err) @@ -424,7 +424,7 @@ func benchmarkBlockValidationMix(b *testing.B, newAcctProb, payProb, astProb flo tt := time.Now() b.ResetTimer() for _, blk := range bc.blocks { - _, err := internal.Eval(context.Background(), bc.l1, blk, true, vc, nil) + _, err := eval.Eval(context.Background(), bc.l1, blk, true, vc, nil) require.NoError(b, err) err = bc.l1.AddBlock(blk, cert) require.NoError(b, err) diff --git a/ledger/ledger.go b/ledger/ledger.go index 50222d2f9e..f3dba794d5 100644 --- a/ledger/ledger.go +++ b/ledger/ledger.go @@ -33,7 +33,7 @@ import ( "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/data/transactions/verify" "github.com/algorand/go-algorand/ledger/apply" - "github.com/algorand/go-algorand/ledger/internal" + "github.com/algorand/go-algorand/ledger/eval" "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/ledger/store/blockdb" "github.com/algorand/go-algorand/ledger/store/trackerdb" @@ -655,7 +655,7 @@ func (l *Ledger) BlockCert(rnd basics.Round) (blk bookkeeping.Block, cert agreem func (l *Ledger) AddBlock(blk bookkeeping.Block, cert agreement.Certificate) error { // passing nil as the executionPool is ok since we've asking the evaluator to skip verification. - updates, err := internal.Eval(context.Background(), l, blk, false, l.verifiedTxnCache, nil) + updates, err := eval.Eval(context.Background(), l, blk, false, l.verifiedTxnCache, nil) if err != nil { if errNSBE, ok := err.(ledgercore.ErrNonSequentialBlockEval); ok && errNSBE.EvaluatorRound <= errNSBE.LatestRound { return ledgercore.BlockInLedgerError{ @@ -782,9 +782,9 @@ func (l *Ledger) trackerLog() logging.Logger { // trackerEvalVerified is used by the accountUpdates to reconstruct the ledgercore.StateDelta from a given block during it's loadFromDisk execution. // when this function is called, the trackers mutex is expected already to be taken. The provided accUpdatesLedger would allow the // evaluator to shortcut the "main" ledger ( i.e. this struct ) and avoid taking the trackers lock a second time. -func (l *Ledger) trackerEvalVerified(blk bookkeeping.Block, accUpdatesLedger internal.LedgerForEvaluator) (ledgercore.StateDelta, error) { +func (l *Ledger) trackerEvalVerified(blk bookkeeping.Block, accUpdatesLedger eval.LedgerForEvaluator) (ledgercore.StateDelta, error) { // passing nil as the executionPool is ok since we've asking the evaluator to skip verification. - return internal.Eval(context.Background(), accUpdatesLedger, blk, false, l.verifiedTxnCache, nil) + return eval.Eval(context.Background(), accUpdatesLedger, blk, false, l.verifiedTxnCache, nil) } // IsWritingCatchpointDataFile returns true when a catchpoint file is being generated. @@ -808,9 +808,9 @@ func (l *Ledger) VerifiedTransactionCache() verify.VerifiedTransactionCache { // provides a cap on the size of a single generated block size, when a non-zero value is passed. // If a value of zero or less is passed to maxTxnBytesPerBlock, the consensus MaxTxnBytesPerBlock would // be used instead. -func (l *Ledger) StartEvaluator(hdr bookkeeping.BlockHeader, paysetHint, maxTxnBytesPerBlock int) (*internal.BlockEvaluator, error) { - return internal.StartEvaluator(l, hdr, - internal.EvaluatorOptions{ +func (l *Ledger) StartEvaluator(hdr bookkeeping.BlockHeader, paysetHint, maxTxnBytesPerBlock int) (*eval.BlockEvaluator, error) { + return eval.StartEvaluator(l, hdr, + eval.EvaluatorOptions{ PaysetHint: paysetHint, Generate: true, Validate: true, @@ -828,7 +828,7 @@ func (l *Ledger) FlushCaches() { // not a valid block (e.g., it has duplicate transactions, overspends some // account, etc). func (l *Ledger) Validate(ctx context.Context, blk bookkeeping.Block, executionPool execpool.BacklogPool) (*ledgercore.ValidatedBlock, error) { - delta, err := internal.Eval(ctx, l, blk, true, l.verifiedTxnCache, executionPool) + delta, err := eval.Eval(ctx, l, blk, true, l.verifiedTxnCache, executionPool) if err != nil { return nil, err } @@ -838,11 +838,11 @@ func (l *Ledger) Validate(ctx context.Context, blk bookkeeping.Block, executionP } // DebuggerLedger defines the minimal set of method required for creating a debug balances. -type DebuggerLedger = internal.LedgerForCowBase +type DebuggerLedger = eval.LedgerForCowBase // MakeDebugBalances creates a ledger suitable for dryrun and debugger func MakeDebugBalances(l DebuggerLedger, round basics.Round, proto protocol.ConsensusVersion, prevTimestamp int64) apply.Balances { - return internal.MakeDebugBalances(l, round, proto, prevTimestamp) + return eval.MakeDebugBalances(l, round, proto, prevTimestamp) } var ledgerInitblocksdbCount = metrics.NewCounter("ledger_initblocksdb_count", "calls") diff --git a/ledger/ledger_perf_test.go b/ledger/ledger_perf_test.go index 2c5cb8ec5f..b2f3b2437f 100644 --- a/ledger/ledger_perf_test.go +++ b/ledger/ledger_perf_test.go @@ -36,7 +36,7 @@ import ( "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/data/transactions/logic" "github.com/algorand/go-algorand/data/transactions/verify" - "github.com/algorand/go-algorand/ledger/internal" + "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/protocol" @@ -319,7 +319,7 @@ func benchmarkFullBlocks(params testParams, b *testing.B) { vc := verify.GetMockedCache(true) b.ResetTimer() for _, blk := range blocks { - _, err = internal.Eval(context.Background(), l1, blk, true, vc, nil) + _, err = eval.Eval(context.Background(), l1, blk, true, vc, nil) require.NoError(b, err) err = l1.AddBlock(blk, cert) require.NoError(b, err) diff --git a/ledger/simple_test.go b/ledger/simple_test.go index d4e44f0c3f..8a41cb4a6d 100644 --- a/ledger/simple_test.go +++ b/ledger/simple_test.go @@ -28,7 +28,7 @@ import ( "github.com/algorand/go-algorand/data/bookkeeping" "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/data/txntest" - "github.com/algorand/go-algorand/ledger/internal" + "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/protocol" @@ -58,14 +58,14 @@ func newSimpleLedgerFull(t testing.TB, balances bookkeeping.GenesisBalances, cv } // nextBlock begins evaluation of a new block, after ledger creation or endBlock() -func nextBlock(t testing.TB, ledger *Ledger) *internal.BlockEvaluator { +func nextBlock(t testing.TB, ledger *Ledger) *eval.BlockEvaluator { rnd := ledger.Latest() hdr, err := ledger.BlockHdr(rnd) require.NoError(t, err) nextHdr := bookkeeping.MakeBlock(hdr).BlockHeader nextHdr.TimeStamp = hdr.TimeStamp + 1 // ensure deterministic tests - eval, err := internal.StartEvaluator(ledger, nextHdr, internal.EvaluatorOptions{ + eval, err := eval.StartEvaluator(ledger, nextHdr, eval.EvaluatorOptions{ Generate: true, Validate: true, // Do the complete checks that a new txn would be subject to }) @@ -73,7 +73,7 @@ func nextBlock(t testing.TB, ledger *Ledger) *internal.BlockEvaluator { return eval } -func fillDefaults(t testing.TB, ledger *Ledger, eval *internal.BlockEvaluator, txn *txntest.Txn) { +func fillDefaults(t testing.TB, ledger *Ledger, eval *eval.BlockEvaluator, txn *txntest.Txn) { if txn.GenesisHash.IsZero() && ledger.GenesisProto().SupportGenesisHash { txn.GenesisHash = ledger.GenesisHash() } @@ -84,14 +84,14 @@ func fillDefaults(t testing.TB, ledger *Ledger, eval *internal.BlockEvaluator, t txn.FillDefaults(ledger.GenesisProto()) } -func txns(t testing.TB, ledger *Ledger, eval *internal.BlockEvaluator, txns ...*txntest.Txn) { +func txns(t testing.TB, ledger *Ledger, eval *eval.BlockEvaluator, txns ...*txntest.Txn) { t.Helper() for _, txn1 := range txns { txn(t, ledger, eval, txn1) } } -func txn(t testing.TB, ledger *Ledger, eval *internal.BlockEvaluator, txn *txntest.Txn, problem ...string) { +func txn(t testing.TB, ledger *Ledger, eval *eval.BlockEvaluator, txn *txntest.Txn, problem ...string) { t.Helper() fillDefaults(t, ledger, eval, txn) err := eval.Transaction(txn.SignedTxn(), transactions.ApplyData{}) @@ -106,7 +106,7 @@ func txn(t testing.TB, ledger *Ledger, eval *internal.BlockEvaluator, txn *txnte require.True(t, len(problem) == 0 || problem[0] == "") } -func txgroup(t testing.TB, ledger *Ledger, eval *internal.BlockEvaluator, txns ...*txntest.Txn) error { +func txgroup(t testing.TB, ledger *Ledger, eval *eval.BlockEvaluator, txns ...*txntest.Txn) error { t.Helper() for _, txn := range txns { fillDefaults(t, ledger, eval, txn) @@ -117,7 +117,7 @@ func txgroup(t testing.TB, ledger *Ledger, eval *internal.BlockEvaluator, txns . } // endBlock completes the block being created, returns the ValidatedBlock for inspection -func endBlock(t testing.TB, ledger *Ledger, eval *internal.BlockEvaluator) *ledgercore.ValidatedBlock { +func endBlock(t testing.TB, ledger *Ledger, eval *eval.BlockEvaluator) *ledgercore.ValidatedBlock { validatedBlock, err := eval.GenerateBlock() require.NoError(t, err) err = ledger.AddValidatedBlock(*validatedBlock, agreement.Certificate{}) diff --git a/ledger/simulation/simulator_test.go b/ledger/simulation/simulator_test.go index 856fabe25b..76ae0ef1b2 100644 --- a/ledger/simulation/simulator_test.go +++ b/ledger/simulation/simulator_test.go @@ -26,7 +26,7 @@ import ( "github.com/algorand/go-algorand/data/transactions/logic" "github.com/algorand/go-algorand/data/transactions/logic/mocktracer" "github.com/algorand/go-algorand/data/txntest" - "github.com/algorand/go-algorand/ledger/internal" + "github.com/algorand/go-algorand/ledger/eval" simulationtesting "github.com/algorand/go-algorand/ledger/simulation/testing" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" @@ -71,7 +71,7 @@ func TestNonOverridenDataLedgerMethodsUseRoundParameter(t *testing.T) { } methodExistsInEvalLedger := func(methodName string) bool { - evalLedgerType := reflect.TypeOf((*internal.LedgerForEvaluator)(nil)).Elem() + evalLedgerType := reflect.TypeOf((*eval.LedgerForEvaluator)(nil)).Elem() for i := 0; i < evalLedgerType.NumMethod(); i++ { if evalLedgerType.Method(i).Name == methodName { return true diff --git a/ledger/tracker.go b/ledger/tracker.go index 26f7f25b2d..2209ca8751 100644 --- a/ledger/tracker.go +++ b/ledger/tracker.go @@ -28,7 +28,7 @@ import ( "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/bookkeeping" - "github.com/algorand/go-algorand/ledger/internal" + "github.com/algorand/go-algorand/ledger/eval" "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/ledger/store/trackerdb" "github.com/algorand/go-algorand/logging" @@ -136,7 +136,7 @@ type ledgerForTracker interface { trackerDB() trackerdb.TrackerStore blockDB() db.Pair trackerLog() logging.Logger - trackerEvalVerified(bookkeeping.Block, internal.LedgerForEvaluator) (ledgercore.StateDelta, error) + trackerEvalVerified(bookkeeping.Block, eval.LedgerForEvaluator) (ledgercore.StateDelta, error) Latest() basics.Round Block(basics.Round) (bookkeeping.Block, error) From 19862ba4f2862d0c286b3636e0faf88c331cbd5e Mon Sep 17 00:00:00 2001 From: Eric Warehime Date: Wed, 29 Mar 2023 12:59:33 -0700 Subject: [PATCH 020/125] tools: Don't use strings.Title in chopper (#5239) --- tools/debug/chopper/main.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/debug/chopper/main.go b/tools/debug/chopper/main.go index 000f6e1cf4..5e70292f07 100644 --- a/tools/debug/chopper/main.go +++ b/tools/debug/chopper/main.go @@ -28,6 +28,8 @@ import ( "strings" "github.com/fatih/color" + "golang.org/x/text/cases" + "golang.org/x/text/language" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/logging/telemetryspec" @@ -131,7 +133,7 @@ type reportData struct { // report prints out stats about matched and mismatched roots or labels func report(rd reportData) { - fmt.Printf("%s in first: %d, second: %d\n", strings.Title(rd.what), rd.size1, rd.size2) + fmt.Printf("%s in first: %d, second: %d\n", cases.Title(language.English).String(rd.what), rd.size1, rd.size2) const matchedStr = "Matched %s: %d" c := yellow From 14ce6596ddf410fd19b987ba511199144e391b21 Mon Sep 17 00:00:00 2001 From: algoidan <79864820+algoidan@users.noreply.github.com> Date: Thu, 30 Mar 2023 17:47:41 +0300 Subject: [PATCH 021/125] Algod: support state proofs recoverability (#4803) Nodes aggregate and save state proof information to be able to recover a stalled state proof subchain. In case of a problem nodes will not lose old state proof data so it will be possible to recover the stalled state proof chain. Data being saved: * state proof builder state is persisted * a new state proof verification tracker also persists some state proof related data from block headers Co-authored-by: Or Aharonee <17099688+Aharonee@users.noreply.github.com> Co-authored-by: Almog Tal <107349997+almog-t@users.noreply.github.com> Co-authored-by: Jonathan Weiss <85506383+algonathan@users.noreply.github.com> Co-authored-by: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> --- catchup/catchpointService.go | 4 +- cmd/catchpointdump/database.go | 55 + cmd/catchpointdump/file.go | 45 +- cmd/catchpointdump/net.go | 4 + cmd/tealdbg/localLedger.go | 4 + config/consensus.go | 11 + crypto/stateproof/builder.go | 87 +- crypto/stateproof/builder_test.go | 18 +- crypto/stateproof/msgp_gen.go | 677 +++++++++ crypto/stateproof/msgp_gen_test.go | 120 ++ crypto/util.go | 7 + daemon/algod/api/server/v2/dryrun.go | 6 +- .../algod/api/server/v2/test/handlers_test.go | 84 +- data/account/participationRegistry.go | 4 +- data/accountManager.go | 5 +- data/accountManager_test.go | 67 +- data/basics/units.go | 5 + data/basics/units_test.go | 19 + data/pools/transactionPool.go | 14 +- data/pools/transactionPool_test.go | 2 +- data/transactions/verify/txn_test.go | 9 +- ledger/acctonline.go | 12 + ledger/acctonline_test.go | 2 + ledger/acctupdates.go | 7 +- ledger/acctupdates_test.go | 19 +- ledger/apply/apply.go | 2 + ledger/apply/stateproof.go | 43 +- ledger/apply/stateproof_test.go | 275 ++++ ledger/bulletin.go | 5 +- ledger/catchpointtracker.go | 113 +- ledger/catchpointtracker_test.go | 132 +- ledger/catchpointwriter.go | 49 +- ledger/catchpointwriter_test.go | 232 ++- ledger/catchupaccessor.go | 69 +- ledger/catchupaccessor_test.go | 125 +- ledger/eval/appcow_test.go | 4 + ledger/eval/cow.go | 16 +- ledger/eval/cow_test.go | 50 +- ledger/eval/eval.go | 16 +- ledger/eval/eval_test.go | 86 +- .../prefetcher/prefetcher_alignment_test.go | 19 +- ledger/evalindexer.go | 5 + ledger/ledger.go | 55 +- ledger/ledger_test.go | 473 +++++- ledger/ledgercore/catchpointlabel.go | 89 +- ledger/ledgercore/catchpointlabel_test.go | 30 +- ledger/ledgercore/msgp_gen.go | 183 +++ ledger/ledgercore/msgp_gen_test.go | 60 + ledger/ledgercore/statedelta.go | 11 +- ledger/ledgercore/statedelta_test.go | 22 +- ledger/ledgercore/stateproofverification.go | 51 + ledger/ledgercore/votersForRound.go | 13 + ledger/msgp_gen.go | 167 +++ ledger/msgp_gen_test.go | 60 + ledger/spverificationtracker.go | 313 ++++ ledger/spverificationtracker_test.go | 492 +++++++ ledger/store/trackerdb/catchpoint.go | 5 + ledger/store/trackerdb/interface.go | 23 + ledger/store/trackerdb/msgp_gen.go | 33 +- .../trackerdb/sqlitedriver/catchpoint.go | 4 + ledger/store/trackerdb/sqlitedriver/schema.go | 11 + .../sqlitedriver/spVerificationAccessor.go | 164 +++ .../sqlitedriver/store_sqlite_impl.go | 12 + .../store/trackerdb/sqlitedriver/testing.go | 3 + .../trackerdb/sqlitedriver/trackerdbV2.go | 18 + ledger/store/trackerdb/store.go | 4 + ledger/store/trackerdb/version.go | 2 +- ledger/tracker.go | 9 + ledger/voters.go | 130 +- ledger/voters_test.go | 103 +- logging/telemetryspec/event.go | 8 +- node/node.go | 17 +- protocol/hash.go | 1 + scripts/dump_genesis.sh | 3 + stateproof/abstractions.go | 2 + stateproof/builder.go | 486 ++++-- stateproof/db.go | 184 ++- stateproof/db_test.go | 195 ++- stateproof/msgp_gen.go | 298 ++++ stateproof/msgp_gen_test.go | 60 + stateproof/signer.go | 122 +- stateproof/stateproofMessageGenerator.go | 8 +- stateproof/stateproofMessageGenerator_test.go | 324 ++-- stateproof/verify/stateproof.go | 66 +- stateproof/verify/stateproof_test.go | 74 +- stateproof/worker.go | 121 +- stateproof/worker_test.go | 1305 +++++++++++++---- .../catchup/catchpointCatchup_test.go | 436 +++--- .../catchup/stateproofsCatchup_test.go | 286 ++++ .../onlineOfflineParticipation_test.go | 4 +- .../overlappingParticipationKeys_test.go | 4 +- .../features/stateproofs/stateproofs_test.go | 138 +- test/framework/fixtures/restClientFixture.go | 4 +- .../consensus/catchpointtestingprotocol.json | 1 + .../nettemplates/RichAccountStateProof.json | 2 +- test/testdata/nettemplates/StateProof.json | 2 +- .../nettemplates/ThreeNodesWithRichAcct.json | 48 + 97 files changed, 7561 insertions(+), 1706 deletions(-) create mode 100644 ledger/apply/stateproof_test.go create mode 100644 ledger/ledgercore/stateproofverification.go create mode 100644 ledger/spverificationtracker.go create mode 100644 ledger/spverificationtracker_test.go create mode 100644 ledger/store/trackerdb/sqlitedriver/spVerificationAccessor.go create mode 100644 test/e2e-go/features/catchup/stateproofsCatchup_test.go create mode 100644 test/testdata/nettemplates/ThreeNodesWithRichAcct.json diff --git a/catchup/catchpointService.go b/catchup/catchpointService.go index 974b76ef94..720f25790d 100644 --- a/catchup/catchpointService.go +++ b/catchup/catchpointService.go @@ -22,8 +22,6 @@ import ( "sync" "time" - "github.com/algorand/go-algorand/stateproof" - "github.com/algorand/go-deadlock" "github.com/algorand/go-algorand/config" @@ -33,6 +31,7 @@ import ( "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/network" + "github.com/algorand/go-algorand/stateproof" ) const ( @@ -259,6 +258,7 @@ func (cs *CatchpointCatchupService) processStageInactive() (err error) { if err != nil { return cs.abort(fmt.Errorf("processStageInactive failed to set a catchpoint label : %v", err)) } + err = cs.updateStage(ledger.CatchpointCatchupStateLedgerDownload) if err != nil { return cs.abort(fmt.Errorf("processStageInactive failed to update stage : %v", err)) diff --git a/cmd/catchpointdump/database.go b/cmd/catchpointdump/database.go index a45b300e5e..7706bcd3dd 100644 --- a/cmd/catchpointdump/database.go +++ b/cmd/catchpointdump/database.go @@ -17,6 +17,7 @@ package main import ( + "bufio" "context" "database/sql" "fmt" @@ -63,6 +64,13 @@ var databaseCmd = &cobra.Command{ } defer outFile.Close() } + + var version uint64 + version, err = getVersion(ledgerTrackerFilename, ledgerTrackerStaging) + if err != nil { + reportErrorf("Unable to read version : %v", err) + } + printDbVersion(ledgerTrackerStaging, version, outFile) err = printAccountsDatabase(ledgerTrackerFilename, ledgerTrackerStaging, ledger.CatchpointFileHeader{}, outFile, nil) if err != nil { reportErrorf("Unable to print account database : %v", err) @@ -71,9 +79,56 @@ var databaseCmd = &cobra.Command{ if err != nil { reportErrorf("Unable to print key value store : %v", err) } + // state proof verification can be found on tracker db version >= 10 or + // catchpoint file version >= 7 (i.e staging tables) + if !ledgerTrackerStaging && version < 10 || ledgerTrackerStaging && version < ledger.CatchpointFileVersionV7 { + return + } + err = printStateProofVerificationContext(ledgerTrackerFilename, ledgerTrackerStaging, outFile) + if err != nil { + reportErrorf("Unable to print state proof verification database : %v", err) + } }, } +func printDbVersion(staging bool, version uint64, outFile *os.File) { + fileWriter := bufio.NewWriterSize(outFile, 1024*1024) + defer fileWriter.Flush() + + if staging { + fmt.Fprintf(outFile, "Catchpoint version: %d \n", version) + } else { + fmt.Fprintf(outFile, "Ledger db version: %d \n", version) + } +} + +func getVersion(filename string, staging bool) (uint64, error) { + dbAccessor, err := db.MakeAccessor(filename, true, false) + if err != nil || dbAccessor.Handle == nil { + return 0, err + } + defer dbAccessor.Close() + var version uint64 + err = dbAccessor.Atomic(func(ctx context.Context, tx *sql.Tx) (err error) { + if staging { + // writing the version of the catchpoint file start only on ver >= CatchpointFileVersionV7. + // in case the catchpoint version does not exists ReadCatchpointStateUint64 returns 0 + cw := sqlitedriver.NewCatchpointSQLReaderWriter(tx) + version, err = cw.ReadCatchpointStateUint64(ctx, trackerdb.CatchpointStateCatchupVersion) + return err + } + + versionAsInt32, err := db.GetUserVersion(ctx, tx) + version = uint64(versionAsInt32) + return err + }) + if err != nil { + return 0, err + } + + return version, nil +} + var checkCmd = &cobra.Command{ Use: "check", Short: "Performs a consistency checking on the accounts merkle trie", diff --git a/cmd/catchpointdump/file.go b/cmd/catchpointdump/file.go index 5661cf9ebb..e99a0b4067 100644 --- a/cmd/catchpointdump/file.go +++ b/cmd/catchpointdump/file.go @@ -47,7 +47,7 @@ import ( var catchpointFile string var outFileName string -var excludedFields *cmdutil.CobraStringSliceValue = cmdutil.MakeCobraStringSliceValue(nil, []string{"version", "catchpoint"}) +var excludedFields = cmdutil.MakeCobraStringSliceValue(nil, []string{"version", "catchpoint"}) func init() { fileCmd.Flags().StringVarP(&catchpointFile, "tar", "t", "", "Specify the catchpoint file (either .tar or .tar.gz) to process") @@ -127,7 +127,6 @@ var fileCmd = &cobra.Command{ } defer outFile.Close() } - err = printAccountsDatabase("./ledger.tracker.sqlite", true, fileHeader, outFile, excludedFields.GetSlice()) if err != nil { reportErrorf("Unable to print account database : %v", err) @@ -136,6 +135,10 @@ var fileCmd = &cobra.Command{ if err != nil { reportErrorf("Unable to print key value store : %v", err) } + err = printStateProofVerificationContext("./ledger.tracker.sqlite", true, outFile) + if err != nil { + reportErrorf("Unable to print state proof verification database : %v", err) + } } }, } @@ -228,7 +231,7 @@ func loadCatchpointIntoDatabase(ctx context.Context, catchupAccessor ledger.Catc if err != nil { return fileHeader, err } - if header.Name == "content.msgpack" { + if header.Name == ledger.CatchpointContentFileName { // we already know it's valid, since we validated that above. protocol.Decode(balancesBlockBytes, &fileHeader) } @@ -443,6 +446,42 @@ func printAccountsDatabase(databaseName string, stagingTables bool, fileHeader l }) } +func printStateProofVerificationContext(databaseName string, stagingTables bool, outFile *os.File) error { + fileWriter := bufio.NewWriterSize(outFile, 1024*1024) + defer fileWriter.Flush() + + dbAccessor, err := db.MakeAccessor(databaseName, true, false) + if err != nil || dbAccessor.Handle == nil { + return err + } + defer dbAccessor.Close() + + var stateProofVerificationContext []ledgercore.StateProofVerificationContext + err = dbAccessor.Atomic(func(ctx context.Context, tx *sql.Tx) (err error) { + if stagingTables { + stateProofVerificationContext, err = sqlitedriver.MakeStateProofVerificationReader(tx).GetAllSPContextsFromCatchpointTbl(ctx) + } else { + stateProofVerificationContext, err = sqlitedriver.MakeStateProofVerificationReader(tx).GetAllSPContexts(ctx) + } + return err + }) + + if err != nil { + return err + } + + fmt.Fprintf(fileWriter, "State Proof Verification Data:\n") + for _, ctx := range stateProofVerificationContext { + jsonData, err := json.Marshal(ctx) + if err != nil { + return err + } + fmt.Fprintf(fileWriter, "%d : %s\n", ctx.LastAttestedRound, string(jsonData)) + } + + return nil +} + func printKeyValue(writer *bufio.Writer, key, value []byte) { var pretty string ai, rest, err := apps.SplitBoxKey(string(key)) diff --git a/cmd/catchpointdump/net.go b/cmd/catchpointdump/net.go index d30cfc297d..e0fdb7f890 100644 --- a/cmd/catchpointdump/net.go +++ b/cmd/catchpointdump/net.go @@ -361,6 +361,10 @@ func loadAndDump(addr string, tarFile string, genesisInitState ledgercore.InitSt if err != nil { return err } + err = printStateProofVerificationContext("./ledger.tracker.sqlite", true, outFile) + if err != nil { + return err + } } return nil diff --git a/cmd/tealdbg/localLedger.go b/cmd/tealdbg/localLedger.go index c76039c7b3..03cf088f42 100644 --- a/cmd/tealdbg/localLedger.go +++ b/cmd/tealdbg/localLedger.go @@ -285,6 +285,10 @@ func (l *localLedger) BlockHdrCached(basics.Round) (bookkeeping.BlockHeader, err 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") +} + func (l *localLedger) CheckDup(config.ConsensusParams, basics.Round, basics.Round, basics.Round, transactions.Txid, ledgercore.Txlease) error { return nil } diff --git a/config/consensus.go b/config/consensus.go index f6e831493a..c3739a5903 100644 --- a/config/consensus.go +++ b/config/consensus.go @@ -487,6 +487,14 @@ type ConsensusParams struct { // EnablePrecheckECDSACurve means that ecdsa_verify opcode will bail early, // returning false, if pubkey is not on the curve. EnablePrecheckECDSACurve bool + + // StateProofUseTrackerVerification specifies whether the node will use data from state proof verification tracker + // in order to verify state proofs. + StateProofUseTrackerVerification bool + + // EnableCatchpointsWithSPContexts specifies when to re-enable version 7 catchpoints. + // Version 7 includes state proof verification contexts + EnableCatchpointsWithSPContexts bool } // PaysetCommitType enumerates possible ways for the block header to commit to @@ -1255,6 +1263,9 @@ func initConsensusProtocols() { vFuture.LogicSigVersion = 9 // When moving this to a release, put a new higher LogicSigVersion here vFuture.EnablePrecheckECDSACurve = true + vFuture.StateProofUseTrackerVerification = true + vFuture.EnableCatchpointsWithSPContexts = true + Consensus[protocol.ConsensusFuture] = vFuture // vAlphaX versions are an separate series of consensus parameters and versions for alphanet diff --git a/crypto/stateproof/builder.go b/crypto/stateproof/builder.go index 0e86fa8942..84f82a9c91 100644 --- a/crypto/stateproof/builder.go +++ b/crypto/stateproof/builder.go @@ -34,19 +34,28 @@ var ( ErrCoinIndexError = errors.New("could not find corresponding index for a given coin") ) +// VotersAllocBound should be equal to config.Consensus[protocol.ConsensusCurrentVersion].StateProofTopVoters +const VotersAllocBound = 1024 + +// BuilderPersistedFields is the set of fields of a Builder that are persisted to disk. +type BuilderPersistedFields struct { + _struct struct{} `codec:",omitempty,omitemptyarray"` + Data MessageHash `codec:"data"` + Round uint64 `codec:"rnd"` + Participants []basics.Participant `codec:"parts,allocbound=VotersAllocBound"` + Parttree *merklearray.Tree `codec:"parttree"` + LnProvenWeight uint64 `codec:"lnprv"` + ProvenWeight uint64 `codec:"prv"` + StrengthTarget uint64 `codec:"str"` +} + // Builder keeps track of signatures on a message and eventually produces // a state proof for that message. type Builder struct { - data MessageHash - round uint64 - sigs []sigslot // Indexed by pos in participants - signedWeight uint64 // Total weight of signatures so far - participants []basics.Participant - parttree *merklearray.Tree - lnProvenWeight uint64 - provenWeight uint64 - strengthTarget uint64 - cachedProof *StateProof + BuilderPersistedFields + sigs []sigslot // Indexed by pos in Participants + signedWeight uint64 // Total weight of signatures so far + cachedProof *StateProof } // MakeBuilder constructs an empty builder. After adding enough signatures and signed weight, this builder is used to create a stateproof. @@ -58,16 +67,19 @@ func MakeBuilder(data MessageHash, round uint64, provenWeight uint64, part []bas } b := &Builder{ - data: data, - round: round, - sigs: make([]sigslot, npart), - signedWeight: 0, - participants: part, - parttree: parttree, - lnProvenWeight: lnProvenWt, - provenWeight: provenWeight, - strengthTarget: strengthTarget, - cachedProof: nil, + BuilderPersistedFields: BuilderPersistedFields{ + Data: data, + Round: round, + Participants: part, + Parttree: parttree, + LnProvenWeight: lnProvenWt, + ProvenWeight: provenWeight, + StrengthTarget: strengthTarget, + }, + + sigs: make([]sigslot, npart), + signedWeight: 0, + cachedProof: nil, } return b, nil @@ -86,11 +98,11 @@ func (b *Builder) Present(pos uint64) (bool, error) { // IsValid verifies that the participant along with the signature can be inserted to the builder. // verifySig can be set to false when the signature is already verified (e.g. loaded from the DB) func (b *Builder) IsValid(pos uint64, sig *merklesignature.Signature, verifySig bool) error { - if pos >= uint64(len(b.participants)) { - return fmt.Errorf("%w pos %d >= len(participants) %d", ErrPositionOutOfBound, pos, len(b.participants)) + if pos >= uint64(len(b.Participants)) { + return fmt.Errorf("%w pos %d >= len(participants) %d", ErrPositionOutOfBound, pos, len(b.Participants)) } - p := b.participants[pos] + p := b.Participants[pos] if p.Weight == 0 { return fmt.Errorf("builder.IsValid: %w: position = %d", ErrPositionWithZeroWeight, pos) @@ -101,7 +113,7 @@ func (b *Builder) IsValid(pos uint64, sig *merklesignature.Signature, verifySig if err := sig.ValidateSaltVersion(merklesignature.SchemeSaltVersion); err != nil { return err } - if err := p.PK.VerifyBytes(b.round, b.data[:], sig); err != nil { + if err := p.PK.VerifyBytes(b.Round, b.Data[:], sig); err != nil { return err } } @@ -118,7 +130,7 @@ func (b *Builder) Add(pos uint64, sig merklesignature.Signature) error { return ErrPositionAlreadyPresent } - p := b.participants[pos] + p := b.Participants[pos] // Remember the signature b.sigs[pos].Weight = p.Weight @@ -130,7 +142,7 @@ func (b *Builder) Add(pos uint64, sig merklesignature.Signature) error { // Ready returns whether the state proof is ready to be built. func (b *Builder) Ready() bool { - return b.cachedProof != nil || b.signedWeight > b.provenWeight + return b.cachedProof != nil || b.signedWeight > b.ProvenWeight } // SignedWeight returns the total weight of signatures added so far. @@ -175,7 +187,7 @@ func (b *Builder) Build() (*StateProof, error) { } if !b.Ready() { - return nil, fmt.Errorf("%w: %d <= %d", ErrSignedWeightLessThanProvenWeight, b.signedWeight, b.provenWeight) + return nil, fmt.Errorf("%w: %d <= %d", ErrSignedWeightLessThanProvenWeight, b.signedWeight, b.ProvenWeight) } // Commit to the sigs array @@ -197,17 +209,17 @@ func (b *Builder) Build() (*StateProof, error) { MerkleSignatureSaltVersion: merklesignature.SchemeSaltVersion, } - nr, err := numReveals(b.signedWeight, b.lnProvenWeight, b.strengthTarget) + nr, err := numReveals(b.signedWeight, b.LnProvenWeight, b.StrengthTarget) if err != nil { return nil, err } choice := coinChoiceSeed{ - partCommitment: b.parttree.Root(), - lnProvenWeight: b.lnProvenWeight, + partCommitment: b.Parttree.Root(), + lnProvenWeight: b.LnProvenWeight, sigCommitment: s.SigCommit, signedWeight: s.SignedWeight, - data: b.data, + data: b.Data, } coinHash := makeCoinGenerator(&choice) @@ -221,8 +233,8 @@ func (b *Builder) Build() (*StateProof, error) { return nil, err } - if pos >= uint64(len(b.participants)) { - return nil, fmt.Errorf("%w pos %d >= len(participants) %d", ErrPositionOutOfBound, pos, len(b.participants)) + if pos >= uint64(len(b.Participants)) { + return nil, fmt.Errorf("%w pos %d >= len(participants) %d", ErrPositionOutOfBound, pos, len(b.Participants)) } revealsSequence[j] = pos @@ -236,7 +248,7 @@ func (b *Builder) Build() (*StateProof, error) { // Generate the reveal for pos s.Reveals[pos] = Reveal{ SigSlot: b.sigs[pos].sigslotCommit, - Part: b.participants[pos], + Part: b.Participants[pos], } proofPositions = append(proofPositions, pos) @@ -247,7 +259,7 @@ func (b *Builder) Build() (*StateProof, error) { return nil, err } - partProofs, err := b.parttree.Prove(proofPositions) + partProofs, err := b.Parttree.Prove(proofPositions) if err != nil { return nil, err } @@ -258,3 +270,8 @@ func (b *Builder) Build() (*StateProof, error) { b.cachedProof = s return s, nil } + +// AllocSigs should only be used after decoding msgpacked Builder, as the sigs field is not exported and encoded +func (b *Builder) AllocSigs() { + b.sigs = make([]sigslot, len(b.Participants)) +} diff --git a/crypto/stateproof/builder_test.go b/crypto/stateproof/builder_test.go index 6938a4b17e..0bd587be70 100644 --- a/crypto/stateproof/builder_test.go +++ b/crypto/stateproof/builder_test.go @@ -28,6 +28,7 @@ import ( "github.com/algorand/falcon" + "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/crypto/merklearray" "github.com/algorand/go-algorand/crypto/merklesignature" @@ -496,12 +497,12 @@ func TestBuildAndReady(t *testing.T) { _, err = builder.Build() a.ErrorIs(err, ErrSignedWeightLessThanProvenWeight) - builder.signedWeight = builder.provenWeight + builder.signedWeight = builder.ProvenWeight a.False(builder.Ready()) _, err = builder.Build() a.ErrorIs(err, ErrSignedWeightLessThanProvenWeight) - builder.signedWeight = builder.provenWeight + 1 + builder.signedWeight = builder.ProvenWeight + 1 a.True(builder.Ready()) _, err = builder.Build() a.NotErrorIs(err, ErrSignedWeightLessThanProvenWeight) @@ -516,7 +517,7 @@ func TestErrorCases(t *testing.T) { _, err := builder.Present(1) a.ErrorIs(err, ErrPositionOutOfBound) - builder.participants = make([]basics.Participant, 1, 1) + builder.Participants = make([]basics.Participant, 1, 1) builder.sigs = make([]sigslot, 1, 1) err = builder.IsValid(1, &merklesignature.Signature{}, false) a.ErrorIs(err, ErrPositionOutOfBound) @@ -524,11 +525,11 @@ func TestErrorCases(t *testing.T) { err = builder.IsValid(0, &merklesignature.Signature{}, false) require.ErrorIs(t, err, ErrPositionWithZeroWeight) - builder.participants[0].Weight = 1 + builder.Participants[0].Weight = 1 err = builder.IsValid(0, &merklesignature.Signature{}, true) a.ErrorIs(err, merklesignature.ErrKeyLifetimeIsZero) - builder.participants[0].PK.KeyLifetime = 20 + builder.Participants[0].PK.KeyLifetime = 20 err = builder.IsValid(0, &merklesignature.Signature{}, true) a.ErrorIs(err, merklesignature.ErrSignatureSchemeVerificationFailed) @@ -631,6 +632,13 @@ func TestBuilder_BuildStateProofCache(t *testing.T) { return } +// Verifies that the VotersAllocBound constant is equal to the current consensus parameters. +// It is used for msgpack allocbound (needs to be static) +func TestBuilder_StateProofTopVoters(t *testing.T) { + partitiontest.PartitionTest(t) + require.Equal(t, config.Consensus[protocol.ConsensusCurrentVersion].StateProofTopVoters, uint64(VotersAllocBound)) +} + func BenchmarkBuildVerify(b *testing.B) { totalWeight := 1000000 npart := 1000 diff --git a/crypto/stateproof/msgp_gen.go b/crypto/stateproof/msgp_gen.go index 93e383c428..8b90154a81 100644 --- a/crypto/stateproof/msgp_gen.go +++ b/crypto/stateproof/msgp_gen.go @@ -6,9 +6,28 @@ import ( "sort" "github.com/algorand/msgp/msgp" + + "github.com/algorand/go-algorand/crypto/merklearray" + "github.com/algorand/go-algorand/data/basics" ) // The following msgp objects are implemented in this file: +// Builder +// |-----> (*) MarshalMsg +// |-----> (*) CanMarshalMsg +// |-----> (*) UnmarshalMsg +// |-----> (*) CanUnmarshalMsg +// |-----> (*) Msgsize +// |-----> (*) MsgIsZero +// +// BuilderPersistedFields +// |-----> (*) MarshalMsg +// |-----> (*) CanMarshalMsg +// |-----> (*) UnmarshalMsg +// |-----> (*) CanUnmarshalMsg +// |-----> (*) Msgsize +// |-----> (*) MsgIsZero +// // MessageHash // |-----> (*) MarshalMsg // |-----> (*) CanMarshalMsg @@ -42,6 +61,664 @@ import ( // |-----> (*) MsgIsZero // +// MarshalMsg implements msgp.Marshaler +func (z *Builder) MarshalMsg(b []byte) (o []byte) { + o = msgp.Require(b, z.Msgsize()) + // omitempty: check for empty values + zb0004Len := uint32(7) + var zb0004Mask uint16 /* 11 bits */ + if (*z).BuilderPersistedFields.Data == (MessageHash{}) { + zb0004Len-- + zb0004Mask |= 0x4 + } + if (*z).BuilderPersistedFields.LnProvenWeight == 0 { + zb0004Len-- + zb0004Mask |= 0x8 + } + if len((*z).BuilderPersistedFields.Participants) == 0 { + zb0004Len-- + zb0004Mask |= 0x10 + } + if (*z).BuilderPersistedFields.Parttree == nil { + zb0004Len-- + zb0004Mask |= 0x20 + } + if (*z).BuilderPersistedFields.ProvenWeight == 0 { + zb0004Len-- + zb0004Mask |= 0x40 + } + if (*z).BuilderPersistedFields.Round == 0 { + zb0004Len-- + zb0004Mask |= 0x80 + } + if (*z).BuilderPersistedFields.StrengthTarget == 0 { + zb0004Len-- + zb0004Mask |= 0x400 + } + // variable map header, size zb0004Len + o = append(o, 0x80|uint8(zb0004Len)) + if zb0004Len != 0 { + if (zb0004Mask & 0x4) == 0 { // if not empty + // string "data" + o = append(o, 0xa4, 0x64, 0x61, 0x74, 0x61) + o = msgp.AppendBytes(o, ((*z).BuilderPersistedFields.Data)[:]) + } + if (zb0004Mask & 0x8) == 0 { // if not empty + // string "lnprv" + o = append(o, 0xa5, 0x6c, 0x6e, 0x70, 0x72, 0x76) + o = msgp.AppendUint64(o, (*z).BuilderPersistedFields.LnProvenWeight) + } + if (zb0004Mask & 0x10) == 0 { // if not empty + // string "parts" + o = append(o, 0xa5, 0x70, 0x61, 0x72, 0x74, 0x73) + if (*z).BuilderPersistedFields.Participants == nil { + o = msgp.AppendNil(o) + } else { + o = msgp.AppendArrayHeader(o, uint32(len((*z).BuilderPersistedFields.Participants))) + } + for zb0002 := range (*z).BuilderPersistedFields.Participants { + o = (*z).BuilderPersistedFields.Participants[zb0002].MarshalMsg(o) + } + } + if (zb0004Mask & 0x20) == 0 { // if not empty + // string "parttree" + o = append(o, 0xa8, 0x70, 0x61, 0x72, 0x74, 0x74, 0x72, 0x65, 0x65) + if (*z).BuilderPersistedFields.Parttree == nil { + o = msgp.AppendNil(o) + } else { + o = (*z).BuilderPersistedFields.Parttree.MarshalMsg(o) + } + } + if (zb0004Mask & 0x40) == 0 { // if not empty + // string "prv" + o = append(o, 0xa3, 0x70, 0x72, 0x76) + o = msgp.AppendUint64(o, (*z).BuilderPersistedFields.ProvenWeight) + } + if (zb0004Mask & 0x80) == 0 { // if not empty + // string "rnd" + o = append(o, 0xa3, 0x72, 0x6e, 0x64) + o = msgp.AppendUint64(o, (*z).BuilderPersistedFields.Round) + } + if (zb0004Mask & 0x400) == 0 { // if not empty + // string "str" + o = append(o, 0xa3, 0x73, 0x74, 0x72) + o = msgp.AppendUint64(o, (*z).BuilderPersistedFields.StrengthTarget) + } + } + return +} + +func (_ *Builder) CanMarshalMsg(z interface{}) bool { + _, ok := (z).(*Builder) + return ok +} + +// UnmarshalMsg implements msgp.Unmarshaler +func (z *Builder) UnmarshalMsg(bts []byte) (o []byte, err error) { + var field []byte + _ = field + var zb0004 int + var zb0005 bool + zb0004, zb0005, bts, err = msgp.ReadMapHeaderBytes(bts) + if _, ok := err.(msgp.TypeError); ok { + zb0004, zb0005, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + if zb0004 > 0 { + zb0004-- + bts, err = msgp.ReadExactBytes(bts, ((*z).BuilderPersistedFields.Data)[:]) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Data") + return + } + } + if zb0004 > 0 { + zb0004-- + (*z).BuilderPersistedFields.Round, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Round") + return + } + } + if zb0004 > 0 { + zb0004-- + var zb0006 int + var zb0007 bool + zb0006, zb0007, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Participants") + return + } + if zb0006 > VotersAllocBound { + err = msgp.ErrOverflow(uint64(zb0006), uint64(VotersAllocBound)) + err = msgp.WrapError(err, "struct-from-array", "Participants") + return + } + if zb0007 { + (*z).BuilderPersistedFields.Participants = nil + } else if (*z).BuilderPersistedFields.Participants != nil && cap((*z).BuilderPersistedFields.Participants) >= zb0006 { + (*z).BuilderPersistedFields.Participants = ((*z).BuilderPersistedFields.Participants)[:zb0006] + } else { + (*z).BuilderPersistedFields.Participants = make([]basics.Participant, zb0006) + } + for zb0002 := range (*z).BuilderPersistedFields.Participants { + bts, err = (*z).BuilderPersistedFields.Participants[zb0002].UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Participants", zb0002) + return + } + } + } + if zb0004 > 0 { + zb0004-- + if msgp.IsNil(bts) { + bts, err = msgp.ReadNilBytes(bts) + if err != nil { + return + } + (*z).BuilderPersistedFields.Parttree = nil + } else { + if (*z).BuilderPersistedFields.Parttree == nil { + (*z).BuilderPersistedFields.Parttree = new(merklearray.Tree) + } + bts, err = (*z).BuilderPersistedFields.Parttree.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Parttree") + return + } + } + } + if zb0004 > 0 { + zb0004-- + (*z).BuilderPersistedFields.LnProvenWeight, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "LnProvenWeight") + return + } + } + if zb0004 > 0 { + zb0004-- + (*z).BuilderPersistedFields.ProvenWeight, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "ProvenWeight") + return + } + } + if zb0004 > 0 { + zb0004-- + (*z).BuilderPersistedFields.StrengthTarget, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "StrengthTarget") + return + } + } + if zb0004 > 0 { + err = msgp.ErrTooManyArrayFields(zb0004) + if err != nil { + err = msgp.WrapError(err, "struct-from-array") + return + } + } + } else { + if err != nil { + err = msgp.WrapError(err) + return + } + if zb0005 { + (*z) = Builder{} + } + for zb0004 > 0 { + zb0004-- + field, bts, err = msgp.ReadMapKeyZC(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + switch string(field) { + case "data": + bts, err = msgp.ReadExactBytes(bts, ((*z).BuilderPersistedFields.Data)[:]) + if err != nil { + err = msgp.WrapError(err, "Data") + return + } + case "rnd": + (*z).BuilderPersistedFields.Round, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "Round") + return + } + case "parts": + var zb0008 int + var zb0009 bool + zb0008, zb0009, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "Participants") + return + } + if zb0008 > VotersAllocBound { + err = msgp.ErrOverflow(uint64(zb0008), uint64(VotersAllocBound)) + err = msgp.WrapError(err, "Participants") + return + } + if zb0009 { + (*z).BuilderPersistedFields.Participants = nil + } else if (*z).BuilderPersistedFields.Participants != nil && cap((*z).BuilderPersistedFields.Participants) >= zb0008 { + (*z).BuilderPersistedFields.Participants = ((*z).BuilderPersistedFields.Participants)[:zb0008] + } else { + (*z).BuilderPersistedFields.Participants = make([]basics.Participant, zb0008) + } + for zb0002 := range (*z).BuilderPersistedFields.Participants { + bts, err = (*z).BuilderPersistedFields.Participants[zb0002].UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "Participants", zb0002) + return + } + } + case "parttree": + if msgp.IsNil(bts) { + bts, err = msgp.ReadNilBytes(bts) + if err != nil { + return + } + (*z).BuilderPersistedFields.Parttree = nil + } else { + if (*z).BuilderPersistedFields.Parttree == nil { + (*z).BuilderPersistedFields.Parttree = new(merklearray.Tree) + } + bts, err = (*z).BuilderPersistedFields.Parttree.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "Parttree") + return + } + } + case "lnprv": + (*z).BuilderPersistedFields.LnProvenWeight, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "LnProvenWeight") + return + } + case "prv": + (*z).BuilderPersistedFields.ProvenWeight, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "ProvenWeight") + return + } + case "str": + (*z).BuilderPersistedFields.StrengthTarget, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "StrengthTarget") + return + } + default: + err = msgp.ErrNoField(string(field)) + if err != nil { + err = msgp.WrapError(err) + return + } + } + } + } + o = bts + return +} + +func (_ *Builder) CanUnmarshalMsg(z interface{}) bool { + _, ok := (z).(*Builder) + return ok +} + +// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message +func (z *Builder) Msgsize() (s int) { + s = 1 + 5 + msgp.ArrayHeaderSize + (32 * (msgp.ByteSize)) + 4 + msgp.Uint64Size + 6 + msgp.ArrayHeaderSize + for zb0002 := range (*z).BuilderPersistedFields.Participants { + s += (*z).BuilderPersistedFields.Participants[zb0002].Msgsize() + } + s += 9 + if (*z).BuilderPersistedFields.Parttree == nil { + s += msgp.NilSize + } else { + s += (*z).BuilderPersistedFields.Parttree.Msgsize() + } + s += 6 + msgp.Uint64Size + 4 + msgp.Uint64Size + 4 + msgp.Uint64Size + return +} + +// MsgIsZero returns whether this is a zero value +func (z *Builder) MsgIsZero() bool { + return ((*z).BuilderPersistedFields.Data == (MessageHash{})) && ((*z).BuilderPersistedFields.Round == 0) && (len((*z).BuilderPersistedFields.Participants) == 0) && ((*z).BuilderPersistedFields.Parttree == nil) && ((*z).BuilderPersistedFields.LnProvenWeight == 0) && ((*z).BuilderPersistedFields.ProvenWeight == 0) && ((*z).BuilderPersistedFields.StrengthTarget == 0) +} + +// MarshalMsg implements msgp.Marshaler +func (z *BuilderPersistedFields) MarshalMsg(b []byte) (o []byte) { + o = msgp.Require(b, z.Msgsize()) + // omitempty: check for empty values + zb0003Len := uint32(7) + var zb0003Mask uint8 /* 8 bits */ + if (*z).Data == (MessageHash{}) { + zb0003Len-- + zb0003Mask |= 0x2 + } + if (*z).LnProvenWeight == 0 { + zb0003Len-- + zb0003Mask |= 0x4 + } + if len((*z).Participants) == 0 { + zb0003Len-- + zb0003Mask |= 0x8 + } + if (*z).Parttree == nil { + zb0003Len-- + zb0003Mask |= 0x10 + } + if (*z).ProvenWeight == 0 { + zb0003Len-- + zb0003Mask |= 0x20 + } + if (*z).Round == 0 { + zb0003Len-- + zb0003Mask |= 0x40 + } + if (*z).StrengthTarget == 0 { + zb0003Len-- + zb0003Mask |= 0x80 + } + // variable map header, size zb0003Len + o = append(o, 0x80|uint8(zb0003Len)) + if zb0003Len != 0 { + if (zb0003Mask & 0x2) == 0 { // if not empty + // string "data" + o = append(o, 0xa4, 0x64, 0x61, 0x74, 0x61) + o = msgp.AppendBytes(o, ((*z).Data)[:]) + } + if (zb0003Mask & 0x4) == 0 { // if not empty + // string "lnprv" + o = append(o, 0xa5, 0x6c, 0x6e, 0x70, 0x72, 0x76) + o = msgp.AppendUint64(o, (*z).LnProvenWeight) + } + if (zb0003Mask & 0x8) == 0 { // if not empty + // string "parts" + o = append(o, 0xa5, 0x70, 0x61, 0x72, 0x74, 0x73) + if (*z).Participants == nil { + o = msgp.AppendNil(o) + } else { + o = msgp.AppendArrayHeader(o, uint32(len((*z).Participants))) + } + for zb0002 := range (*z).Participants { + o = (*z).Participants[zb0002].MarshalMsg(o) + } + } + if (zb0003Mask & 0x10) == 0 { // if not empty + // string "parttree" + o = append(o, 0xa8, 0x70, 0x61, 0x72, 0x74, 0x74, 0x72, 0x65, 0x65) + if (*z).Parttree == nil { + o = msgp.AppendNil(o) + } else { + o = (*z).Parttree.MarshalMsg(o) + } + } + if (zb0003Mask & 0x20) == 0 { // if not empty + // string "prv" + o = append(o, 0xa3, 0x70, 0x72, 0x76) + o = msgp.AppendUint64(o, (*z).ProvenWeight) + } + if (zb0003Mask & 0x40) == 0 { // if not empty + // string "rnd" + o = append(o, 0xa3, 0x72, 0x6e, 0x64) + o = msgp.AppendUint64(o, (*z).Round) + } + if (zb0003Mask & 0x80) == 0 { // if not empty + // string "str" + o = append(o, 0xa3, 0x73, 0x74, 0x72) + o = msgp.AppendUint64(o, (*z).StrengthTarget) + } + } + return +} + +func (_ *BuilderPersistedFields) CanMarshalMsg(z interface{}) bool { + _, ok := (z).(*BuilderPersistedFields) + return ok +} + +// UnmarshalMsg implements msgp.Unmarshaler +func (z *BuilderPersistedFields) UnmarshalMsg(bts []byte) (o []byte, err error) { + var field []byte + _ = field + var zb0003 int + var zb0004 bool + zb0003, zb0004, bts, err = msgp.ReadMapHeaderBytes(bts) + if _, ok := err.(msgp.TypeError); ok { + zb0003, zb0004, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + if zb0003 > 0 { + zb0003-- + bts, err = msgp.ReadExactBytes(bts, ((*z).Data)[:]) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Data") + return + } + } + if zb0003 > 0 { + zb0003-- + (*z).Round, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Round") + return + } + } + if zb0003 > 0 { + zb0003-- + var zb0005 int + var zb0006 bool + zb0005, zb0006, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Participants") + return + } + if zb0005 > VotersAllocBound { + err = msgp.ErrOverflow(uint64(zb0005), uint64(VotersAllocBound)) + err = msgp.WrapError(err, "struct-from-array", "Participants") + return + } + if zb0006 { + (*z).Participants = nil + } else if (*z).Participants != nil && cap((*z).Participants) >= zb0005 { + (*z).Participants = ((*z).Participants)[:zb0005] + } else { + (*z).Participants = make([]basics.Participant, zb0005) + } + for zb0002 := range (*z).Participants { + bts, err = (*z).Participants[zb0002].UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Participants", zb0002) + return + } + } + } + if zb0003 > 0 { + zb0003-- + if msgp.IsNil(bts) { + bts, err = msgp.ReadNilBytes(bts) + if err != nil { + return + } + (*z).Parttree = nil + } else { + if (*z).Parttree == nil { + (*z).Parttree = new(merklearray.Tree) + } + bts, err = (*z).Parttree.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Parttree") + return + } + } + } + if zb0003 > 0 { + zb0003-- + (*z).LnProvenWeight, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "LnProvenWeight") + return + } + } + if zb0003 > 0 { + zb0003-- + (*z).ProvenWeight, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "ProvenWeight") + return + } + } + if zb0003 > 0 { + zb0003-- + (*z).StrengthTarget, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "StrengthTarget") + return + } + } + if zb0003 > 0 { + err = msgp.ErrTooManyArrayFields(zb0003) + if err != nil { + err = msgp.WrapError(err, "struct-from-array") + return + } + } + } else { + if err != nil { + err = msgp.WrapError(err) + return + } + if zb0004 { + (*z) = BuilderPersistedFields{} + } + for zb0003 > 0 { + zb0003-- + field, bts, err = msgp.ReadMapKeyZC(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + switch string(field) { + case "data": + bts, err = msgp.ReadExactBytes(bts, ((*z).Data)[:]) + if err != nil { + err = msgp.WrapError(err, "Data") + return + } + case "rnd": + (*z).Round, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "Round") + return + } + case "parts": + var zb0007 int + var zb0008 bool + zb0007, zb0008, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "Participants") + return + } + if zb0007 > VotersAllocBound { + err = msgp.ErrOverflow(uint64(zb0007), uint64(VotersAllocBound)) + err = msgp.WrapError(err, "Participants") + return + } + if zb0008 { + (*z).Participants = nil + } else if (*z).Participants != nil && cap((*z).Participants) >= zb0007 { + (*z).Participants = ((*z).Participants)[:zb0007] + } else { + (*z).Participants = make([]basics.Participant, zb0007) + } + for zb0002 := range (*z).Participants { + bts, err = (*z).Participants[zb0002].UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "Participants", zb0002) + return + } + } + case "parttree": + if msgp.IsNil(bts) { + bts, err = msgp.ReadNilBytes(bts) + if err != nil { + return + } + (*z).Parttree = nil + } else { + if (*z).Parttree == nil { + (*z).Parttree = new(merklearray.Tree) + } + bts, err = (*z).Parttree.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "Parttree") + return + } + } + case "lnprv": + (*z).LnProvenWeight, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "LnProvenWeight") + return + } + case "prv": + (*z).ProvenWeight, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "ProvenWeight") + return + } + case "str": + (*z).StrengthTarget, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "StrengthTarget") + return + } + default: + err = msgp.ErrNoField(string(field)) + if err != nil { + err = msgp.WrapError(err) + return + } + } + } + } + o = bts + return +} + +func (_ *BuilderPersistedFields) CanUnmarshalMsg(z interface{}) bool { + _, ok := (z).(*BuilderPersistedFields) + return ok +} + +// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message +func (z *BuilderPersistedFields) Msgsize() (s int) { + s = 1 + 5 + msgp.ArrayHeaderSize + (32 * (msgp.ByteSize)) + 4 + msgp.Uint64Size + 6 + msgp.ArrayHeaderSize + for zb0002 := range (*z).Participants { + s += (*z).Participants[zb0002].Msgsize() + } + s += 9 + if (*z).Parttree == nil { + s += msgp.NilSize + } else { + s += (*z).Parttree.Msgsize() + } + s += 6 + msgp.Uint64Size + 4 + msgp.Uint64Size + 4 + msgp.Uint64Size + return +} + +// MsgIsZero returns whether this is a zero value +func (z *BuilderPersistedFields) MsgIsZero() bool { + return ((*z).Data == (MessageHash{})) && ((*z).Round == 0) && (len((*z).Participants) == 0) && ((*z).Parttree == nil) && ((*z).LnProvenWeight == 0) && ((*z).ProvenWeight == 0) && ((*z).StrengthTarget == 0) +} + // MarshalMsg implements msgp.Marshaler func (z *MessageHash) MarshalMsg(b []byte) (o []byte) { o = msgp.Require(b, z.Msgsize()) diff --git a/crypto/stateproof/msgp_gen_test.go b/crypto/stateproof/msgp_gen_test.go index 30dd0b1d50..c9e9699668 100644 --- a/crypto/stateproof/msgp_gen_test.go +++ b/crypto/stateproof/msgp_gen_test.go @@ -14,6 +14,126 @@ import ( "github.com/algorand/go-algorand/test/partitiontest" ) +func TestMarshalUnmarshalBuilder(t *testing.T) { + partitiontest.PartitionTest(t) + v := Builder{} + 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 TestRandomizedEncodingBuilder(t *testing.T) { + protocol.RunEncodingTest(t, &Builder{}) +} + +func BenchmarkMarshalMsgBuilder(b *testing.B) { + v := Builder{} + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + v.MarshalMsg(nil) + } +} + +func BenchmarkAppendMsgBuilder(b *testing.B) { + v := Builder{} + 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 BenchmarkUnmarshalBuilder(b *testing.B) { + v := Builder{} + bts := v.MarshalMsg(nil) + b.ReportAllocs() + b.SetBytes(int64(len(bts))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := v.UnmarshalMsg(bts) + if err != nil { + b.Fatal(err) + } + } +} + +func TestMarshalUnmarshalBuilderPersistedFields(t *testing.T) { + partitiontest.PartitionTest(t) + v := BuilderPersistedFields{} + 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 TestRandomizedEncodingBuilderPersistedFields(t *testing.T) { + protocol.RunEncodingTest(t, &BuilderPersistedFields{}) +} + +func BenchmarkMarshalMsgBuilderPersistedFields(b *testing.B) { + v := BuilderPersistedFields{} + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + v.MarshalMsg(nil) + } +} + +func BenchmarkAppendMsgBuilderPersistedFields(b *testing.B) { + v := BuilderPersistedFields{} + 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 BenchmarkUnmarshalBuilderPersistedFields(b *testing.B) { + v := BuilderPersistedFields{} + bts := v.MarshalMsg(nil) + b.ReportAllocs() + b.SetBytes(int64(len(bts))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := v.UnmarshalMsg(bts) + if err != nil { + b.Fatal(err) + } + } +} + func TestMarshalUnmarshalMessageHash(t *testing.T) { partitiontest.PartitionTest(t) v := MessageHash{} diff --git a/crypto/util.go b/crypto/util.go index aa8dd3cfc4..60bb12aef0 100644 --- a/crypto/util.go +++ b/crypto/util.go @@ -94,3 +94,10 @@ func HashObj(h Hashable) Digest { func NewHash() hash.Hash { return sha512.New512_256() } + +// EncodeAndHash returns both the packed representation of the object and its hash. +func EncodeAndHash(h Hashable) (Digest, []byte) { + hashid, encodedData := h.ToBeHashed() + hashrep := append([]byte(hashid), encodedData...) + return Hash(hashrep), encodedData +} diff --git a/daemon/algod/api/server/v2/dryrun.go b/daemon/algod/api/server/v2/dryrun.go index 49a5a427da..685ec1e689 100644 --- a/daemon/algod/api/server/v2/dryrun.go +++ b/daemon/algod/api/server/v2/dryrun.go @@ -249,6 +249,10 @@ func (dl *dryrunLedger) CheckDup(config.ConsensusParams, basics.Round, basics.Ro return nil } +func (dl *dryrunLedger) GetStateProofVerificationContext(_ basics.Round) (*ledgercore.StateProofVerificationContext, error) { + return nil, fmt.Errorf("dryrunLedger: GetStateProofVerificationContext, needed for state proof verification, is not implemented in dryrun") +} + func (dl *dryrunLedger) lookup(rnd basics.Round, addr basics.Address) (basics.AccountData, basics.Round, error) { // check accounts from debug records uploaded out := basics.AccountData{} @@ -472,7 +476,7 @@ func doDryrunRequest(dr *DryrunRequest, response *model.DryrunResponse) { } } if !found { - (*acct.AppsLocalState) = append(*acct.AppsLocalState, ls) + *acct.AppsLocalState = append(*acct.AppsLocalState, ls) } } dl.dr.Accounts[idx] = acct diff --git a/daemon/algod/api/server/v2/test/handlers_test.go b/daemon/algod/api/server/v2/test/handlers_test.go index ed9b069c82..19d4bea1be 100644 --- a/daemon/algod/api/server/v2/test/handlers_test.go +++ b/daemon/algod/api/server/v2/test/handlers_test.go @@ -61,7 +61,7 @@ import ( "github.com/algorand/go-algorand/util/execpool" ) -const stateProofIntervalForHandlerTests = uint64(256) +const stateProofInterval = uint64(256) func setupTestForMethodGet(t *testing.T, consensusUpgrade bool) (v2.Handlers, echo.Context, *httptest.ResponseRecorder, []account.Root, []transactions.SignedTxn, func()) { numAccounts := 1 @@ -1576,12 +1576,13 @@ func newEmptyBlock(a *require.Assertions, lastBlock bookkeeping.Block, genBlk bo var blk bookkeeping.Block blk.BlockHeader = bookkeeping.BlockHeader{ - GenesisID: genBlk.GenesisID(), - GenesisHash: genBlk.GenesisHash(), - Round: l.Latest() + 1, - Branch: latestBlock.Hash(), - RewardsState: latestBlock.NextRewardsState(l.Latest()+1, proto, poolBal.MicroAlgos, totalRewardUnits, logging.Base()), - UpgradeState: latestBlock.UpgradeState, + GenesisID: genBlk.GenesisID(), + GenesisHash: genBlk.GenesisHash(), + Round: l.Latest() + 1, + Branch: latestBlock.Hash(), + RewardsState: latestBlock.NextRewardsState(l.Latest()+1, proto, poolBal.MicroAlgos, totalRewardUnits, logging.Base()), + UpgradeState: latestBlock.UpgradeState, + StateProofTracking: latestBlock.StateProofTracking, } blk.BlockHeader.TxnCounter = latestBlock.TxnCounter @@ -1598,22 +1599,19 @@ func newEmptyBlock(a *require.Assertions, lastBlock bookkeeping.Block, genBlk bo return blk } -func addStateProofIfNeeded(blk bookkeeping.Block) bookkeeping.Block { +func addStateProof(blk bookkeeping.Block) bookkeeping.Block { round := uint64(blk.Round()) - if round%stateProofIntervalForHandlerTests == (stateProofIntervalForHandlerTests/2+18) && round > stateProofIntervalForHandlerTests*2 { - return blk - } - stateProofRound := (round - round%stateProofIntervalForHandlerTests) - stateProofIntervalForHandlerTests + stateProofRound := (round/stateProofInterval - 1) * stateProofInterval tx := transactions.SignedTxn{ Txn: transactions.Transaction{ Type: protocol.StateProofTx, - Header: transactions.Header{Sender: transactions.StateProofSender}, + Header: transactions.Header{Sender: transactions.StateProofSender, FirstValid: blk.Round()}, StateProofTxnFields: transactions.StateProofTxnFields{ StateProofType: 0, Message: stateproofmsg.Message{ BlockHeadersCommitment: []byte{0x0, 0x1, 0x2}, FirstAttestedRound: stateProofRound + 1, - LastAttestedRound: stateProofRound + stateProofIntervalForHandlerTests, + LastAttestedRound: stateProofRound + stateProofInterval, }, }, }, @@ -1621,19 +1619,39 @@ func addStateProofIfNeeded(blk bookkeeping.Block) bookkeeping.Block { txnib := transactions.SignedTxnInBlock{SignedTxnWithAD: transactions.SignedTxnWithAD{SignedTxn: tx}} blk.Payset = append(blk.Payset, txnib) + updatedStateProofTracking := bookkeeping.StateProofTrackingData{ + StateProofVotersCommitment: blk.BlockHeader.StateProofTracking[protocol.StateProofBasic].StateProofVotersCommitment, + StateProofOnlineTotalWeight: blk.BlockHeader.StateProofTracking[protocol.StateProofBasic].StateProofOnlineTotalWeight, + StateProofNextRound: blk.BlockHeader.StateProofTracking[protocol.StateProofBasic].StateProofNextRound + basics.Round(stateProofInterval), + } + blk.BlockHeader.StateProofTracking = make(map[protocol.StateProofType]bookkeeping.StateProofTrackingData) + blk.BlockHeader.StateProofTracking[protocol.StateProofBasic] = updatedStateProofTracking + return blk } func insertRounds(a *require.Assertions, h v2.Handlers, numRounds int) { ledger := h.Node.LedgerForAPI() + firstStateProof := basics.Round(stateProofInterval * 2) genBlk, err := ledger.Block(0) a.NoError(err) + genBlk.BlockHeader.StateProofTracking = make(map[protocol.StateProofType]bookkeeping.StateProofTrackingData) + genBlk.BlockHeader.StateProofTracking[protocol.StateProofBasic] = bookkeeping.StateProofTrackingData{ + StateProofVotersCommitment: nil, + StateProofOnlineTotalWeight: basics.MicroAlgos{}, + StateProofNextRound: firstStateProof, + } lastBlk := genBlk for i := 0; i < numRounds; i++ { blk := newEmptyBlock(a, lastBlk, genBlk, ledger) - blk = addStateProofIfNeeded(blk) + round := uint64(blk.Round()) + // Add a StateProof transaction after half of the interval has passed (128 rounds) and add another 18 round for good measure + // First StateProof should be 2*Interval, since the first commitment cannot be in genesis + if blk.Round() > firstStateProof && (round%stateProofInterval == (stateProofInterval/2 + 18)) { + blk = addStateProof(blk) + } blk.BlockHeader.CurrentProtocol = protocol.ConsensusCurrentVersion a.NoError(ledger.(*data.Ledger).AddBlock(blk, agreement.Certificate{})) lastBlk = blk @@ -1673,7 +1691,7 @@ func TestStateProof200(t *testing.T) { insertRounds(a, handler, 1000) - a.NoError(handler.GetStateProof(ctx, stateProofIntervalForHandlerTests+1)) + a.NoError(handler.GetStateProof(ctx, stateProofInterval+1)) a.Equal(200, responseRecorder.Code) stprfResp := model.StateProofResponse{} @@ -1715,13 +1733,13 @@ func TestGetBlockProof200(t *testing.T) { insertRounds(a, handler, 1000) - a.NoError(handler.GetLightBlockHeaderProof(ctx, stateProofIntervalForHandlerTests*2+2)) + a.NoError(handler.GetLightBlockHeaderProof(ctx, stateProofInterval*2+2)) a.Equal(200, responseRecorder.Code) - blkHdrArr, err := stateproof.FetchLightHeaders(handler.Node.LedgerForAPI(), stateProofIntervalForHandlerTests, basics.Round(stateProofIntervalForHandlerTests*3)) + blkHdrArr, err := stateproof.FetchLightHeaders(handler.Node.LedgerForAPI(), stateProofInterval, basics.Round(stateProofInterval*3)) a.NoError(err) - leafproof, err := stateproof.GenerateProofOfLightBlockHeaders(stateProofIntervalForHandlerTests, blkHdrArr, 1) + leafproof, err := stateproof.GenerateProofOfLightBlockHeaders(stateProofInterval, blkHdrArr, 1) a.NoError(err) proofResp := model.LightBlockHeaderProofResponse{} @@ -1743,27 +1761,27 @@ func TestStateproofTransactionForRound(t *testing.T) { CurrentProtocol: protocol.ConsensusCurrentVersion, }, } - blk = addStateProofIfNeeded(blk) + blk = addStateProof(blk) ledger.blocks = append(ledger.blocks, blk) } ctx, cncl := context.WithTimeout(context.Background(), time.Minute*2) defer cncl() - txn, err := v2.GetStateProofTransactionForRound(ctx, &ledger, basics.Round(stateProofIntervalForHandlerTests*2+1), 1000, nil) + txn, err := v2.GetStateProofTransactionForRound(ctx, &ledger, basics.Round(stateProofInterval*2+1), 1000, nil) a.NoError(err) - a.Equal(2*stateProofIntervalForHandlerTests+1, txn.Message.FirstAttestedRound) - a.Equal(3*stateProofIntervalForHandlerTests, txn.Message.LastAttestedRound) + a.Equal(2*stateProofInterval+1, txn.Message.FirstAttestedRound) + a.Equal(3*stateProofInterval, txn.Message.LastAttestedRound) a.Equal([]byte{0x0, 0x1, 0x2}, txn.Message.BlockHeadersCommitment) - txn, err = v2.GetStateProofTransactionForRound(ctx, &ledger, basics.Round(2*stateProofIntervalForHandlerTests), 1000, nil) + txn, err = v2.GetStateProofTransactionForRound(ctx, &ledger, basics.Round(2*stateProofInterval), 1000, nil) a.NoError(err) - a.Equal(stateProofIntervalForHandlerTests+1, txn.Message.FirstAttestedRound) - a.Equal(2*stateProofIntervalForHandlerTests, txn.Message.LastAttestedRound) + a.Equal(stateProofInterval+1, txn.Message.FirstAttestedRound) + a.Equal(2*stateProofInterval, txn.Message.LastAttestedRound) txn, err = v2.GetStateProofTransactionForRound(ctx, &ledger, 999, 1000, nil) a.ErrorIs(err, v2.ErrNoStateProofForRound) - txn, err = v2.GetStateProofTransactionForRound(ctx, &ledger, basics.Round(2*stateProofIntervalForHandlerTests), basics.Round(2*stateProofIntervalForHandlerTests), nil) + txn, err = v2.GetStateProofTransactionForRound(ctx, &ledger, basics.Round(2*stateProofInterval), basics.Round(2*stateProofInterval), nil) a.ErrorIs(err, v2.ErrNoStateProofForRound) } @@ -1780,12 +1798,12 @@ func TestStateproofTransactionForRoundWithoutStateproofs(t *testing.T) { CurrentProtocol: protocol.ConsensusV30, // should have StateProofInterval == 0 . }, } - blk = addStateProofIfNeeded(blk) + blk = addStateProof(blk) ledger.blocks = append(ledger.blocks, blk) } ctx, cncl := context.WithTimeout(context.Background(), time.Minute) defer cncl() - _, err := v2.GetStateProofTransactionForRound(ctx, &ledger, basics.Round(stateProofIntervalForHandlerTests*2+1), 1000, nil) + _, err := v2.GetStateProofTransactionForRound(ctx, &ledger, basics.Round(stateProofInterval*2+1), 1000, nil) a.ErrorIs(err, v2.ErrNoStateProofForRound) } @@ -1802,13 +1820,13 @@ func TestStateproofTransactionForRoundTimeouts(t *testing.T) { CurrentProtocol: protocol.ConsensusCurrentVersion, // should have StateProofInterval != 0 . }, } - blk = addStateProofIfNeeded(blk) + blk = addStateProof(blk) ledger.blocks = append(ledger.blocks, blk) } ctx, cncl := context.WithTimeout(context.Background(), time.Nanosecond) defer cncl() - _, err := v2.GetStateProofTransactionForRound(ctx, &ledger, basics.Round(stateProofIntervalForHandlerTests*2+1), 1000, nil) + _, err := v2.GetStateProofTransactionForRound(ctx, &ledger, basics.Round(stateProofInterval*2+1), 1000, nil) a.ErrorIs(err, v2.ErrTimeout) } @@ -1825,7 +1843,7 @@ func TestStateproofTransactionForRoundShutsDown(t *testing.T) { CurrentProtocol: protocol.ConsensusCurrentVersion, // should have StateProofInterval != 0 . }, } - blk = addStateProofIfNeeded(blk) + blk = addStateProof(blk) ledger.blocks = append(ledger.blocks, blk) } @@ -1833,7 +1851,7 @@ func TestStateproofTransactionForRoundShutsDown(t *testing.T) { close(stoppedChan) ctx, cncl := context.WithTimeout(context.Background(), time.Minute) defer cncl() - _, err := v2.GetStateProofTransactionForRound(ctx, &ledger, basics.Round(stateProofIntervalForHandlerTests*2+1), 1000, stoppedChan) + _, err := v2.GetStateProofTransactionForRound(ctx, &ledger, basics.Round(stateProofInterval*2+1), 1000, stoppedChan) a.ErrorIs(err, v2.ErrShutdown) } diff --git a/data/account/participationRegistry.go b/data/account/participationRegistry.go index 1381ddbafd..bc9d0b301c 100644 --- a/data/account/participationRegistry.go +++ b/data/account/participationRegistry.go @@ -232,7 +232,7 @@ type ParticipationRegistry interface { // once, an error will occur when the data is flushed when inserting a duplicate key. AppendKeys(id ParticipationID, keys StateProofKeys) error - // DeleteStateProofKeys removes all stateproof keys preceding a given round (including) + // DeleteStateProofKeys removes all stateproof keys up to, and not including, a given round DeleteStateProofKeys(id ParticipationID, round basics.Round) error // Delete removes a record from storage. @@ -347,7 +347,7 @@ const ( insertKeysetQuery = `INSERT INTO Keysets (participationID, account, firstValidRound, lastValidRound, keyDilution, vrf, stateProof) VALUES (?, ?, ?, ?, ?, ?, ?)` insertRollingQuery = `INSERT INTO Rolling (pk, voting) VALUES (?, ?)` appendStateProofKeysQuery = `INSERT INTO StateProofKeys (pk, round, key) VALUES(?, ?, ?)` - deleteStateProofKeysQuery = `DELETE FROM StateProofKeys WHERE pk=? AND round<=?` + deleteStateProofKeysQuery = `DELETE FROM StateProofKeys WHERE pk=? AND round int(blk.Round()) { @@ -207,6 +218,9 @@ func (ml *mockLedgerForTracker) Block(rnd basics.Round) (bookkeeping.Block, erro return bookkeeping.Block{}, fmt.Errorf("rnd %d out of bounds", rnd) } + ml.mu.Lock() + defer ml.mu.Unlock() + return ml.blocks[int(rnd)].block, nil } @@ -215,6 +229,9 @@ func (ml *mockLedgerForTracker) BlockHdr(rnd basics.Round) (bookkeeping.BlockHea return bookkeeping.BlockHeader{}, fmt.Errorf("rnd %d out of bounds", rnd) } + ml.mu.RLock() + defer ml.mu.RUnlock() + return ml.blocks[int(rnd)].block.BlockHeader, nil } diff --git a/ledger/apply/apply.go b/ledger/apply/apply.go index b694d9ed5d..2be394b8ff 100644 --- a/ledger/apply/apply.go +++ b/ledger/apply/apply.go @@ -30,6 +30,8 @@ type StateProofsApplier interface { BlockHdr(r basics.Round) (bookkeeping.BlockHeader, error) GetStateProofNextRound() basics.Round SetStateProofNextRound(rnd basics.Round) + GetStateProofVerificationContext(stateProofLastAttestedRound basics.Round) (*ledgercore.StateProofVerificationContext, error) + ConsensusParams() config.ConsensusParams } // Balances allow to move MicroAlgos from one address to another and to update balance records, or to access and modify individual balance records diff --git a/ledger/apply/stateproof.go b/ledger/apply/stateproof.go index dba21a7241..2901f4e857 100644 --- a/ledger/apply/stateproof.go +++ b/ledger/apply/stateproof.go @@ -23,6 +23,7 @@ import ( "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/ledger/ledgercore" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/stateproof/verify" ) @@ -41,31 +42,51 @@ func StateProof(tx transactions.StateProofTxnFields, atRound basics.Round, sp St } lastRoundInInterval := basics.Round(tx.Message.LastAttestedRound) - lastRoundHdr, err := sp.BlockHdr(lastRoundInInterval) - if err != nil { - return err - } - nextStateProofRnd := sp.GetStateProofNextRound() if nextStateProofRnd == 0 || nextStateProofRnd != lastRoundInInterval { return fmt.Errorf("applyStateProof: %w - expecting state proof for %d, but new state proof is for %d", ErrExpectedDifferentStateProofRound, nextStateProofRnd, lastRoundInInterval) } - proto := config.Consensus[lastRoundHdr.CurrentProtocol] if validate { - votersRnd := lastRoundInInterval.SubSaturate(basics.Round(proto.StateProofInterval)) - votersHdr, err := sp.BlockHdr(votersRnd) + var verificationContext *ledgercore.StateProofVerificationContext + var err error + if sp.ConsensusParams().StateProofUseTrackerVerification { + verificationContext, err = sp.GetStateProofVerificationContext(lastRoundInInterval) + } else { + verificationContext, err = gatherVerificationContextUsingBlockHeaders(sp, lastRoundInInterval) + } if err != nil { return err } - err = verify.ValidateStateProof(&lastRoundHdr, &tx.StateProof, &votersHdr, atRound, &tx.Message) - if err != nil { + if err = verify.ValidateStateProof(verificationContext, &tx.StateProof, atRound, &tx.Message); err != nil { return err } } - sp.SetStateProofNextRound(lastRoundInInterval + basics.Round(proto.StateProofInterval)) + // IMPORTANT: this line does not support changing the StateProofInterval consensus param; + // Ideally the protocol version should be taken from the votersHeader (or even the lastRoundInInterval header). + // However, when replaying the past 320 blocks we might not be able to fetch this header (only X+320+1000 past headers are available). + // So for now we will use the current protocol version parameter, and when support for changing StateProofInterval arises + // we shall revisit this decision. + sp.SetStateProofNextRound(lastRoundInInterval + basics.Round(sp.ConsensusParams().StateProofInterval)) return nil } + +func gatherVerificationContextUsingBlockHeaders(sp StateProofsApplier, lastRoundInInterval basics.Round) (*ledgercore.StateProofVerificationContext, error) { + lastRoundHdr, err := sp.BlockHdr(lastRoundInInterval) + if err != nil { + return nil, err + } + proto := config.Consensus[lastRoundHdr.CurrentProtocol] + votersRnd := lastRoundInInterval.SubSaturate(basics.Round(proto.StateProofInterval)) + votersHdr, err := sp.BlockHdr(votersRnd) + if err != nil { + return nil, err + } + + verificationContext := ledgercore.MakeStateProofVerificationContext(&votersHdr, lastRoundInInterval) + + return verificationContext, nil +} diff --git a/ledger/apply/stateproof_test.go b/ledger/apply/stateproof_test.go new file mode 100644 index 0000000000..3160b9d057 --- /dev/null +++ b/ledger/apply/stateproof_test.go @@ -0,0 +1,275 @@ +// Copyright (C) 2019-2023 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package apply + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/config" + "github.com/algorand/go-algorand/crypto/stateproof" + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/data/bookkeeping" + "github.com/algorand/go-algorand/data/stateproofmsg" + "github.com/algorand/go-algorand/data/transactions" + "github.com/algorand/go-algorand/ledger/ledgercore" + "github.com/algorand/go-algorand/protocol" + "github.com/algorand/go-algorand/test/partitiontest" +) + +var ErrVerificationContextNotFound = errors.New("requested state proof verification data not found") + +type stateProofApplierMock struct { + spNext basics.Round + blocks map[basics.Round]bookkeeping.BlockHeader + blockErr map[basics.Round]error + stateProofVerification map[basics.Round]*ledgercore.StateProofVerificationContext + version protocol.ConsensusVersion +} + +func (s *stateProofApplierMock) BlockHdr(rnd basics.Round) (bookkeeping.BlockHeader, error) { + err, hit := s.blockErr[rnd] + if hit { + return bookkeeping.BlockHeader{}, err + } + hdr := s.blocks[rnd] // default struct is fine if nothing found + return hdr, nil +} + +func (s *stateProofApplierMock) GetStateProofNextRound() basics.Round { + return s.spNext +} + +func (s *stateProofApplierMock) SetStateProofNextRound(rnd basics.Round) { + s.spNext = rnd +} + +func (s *stateProofApplierMock) GetStateProofVerificationContext(stateProofLastAttestedRound basics.Round) (*ledgercore.StateProofVerificationContext, error) { + element, exists := s.stateProofVerification[stateProofLastAttestedRound] + if !exists { + return nil, ErrVerificationContextNotFound + } + return element, nil +} + +func (s *stateProofApplierMock) ConsensusParams() config.ConsensusParams { + return config.Consensus[s.version] +} + +func TestApplyStateProofV34(t *testing.T) { + partitiontest.PartitionTest(t) + a := require.New(t) + + var spType protocol.StateProofType + var stateProof stateproof.StateProof + var atRound basics.Round + var validate bool + msg := stateproofmsg.Message{} + + const version = protocol.ConsensusV34 + + blocks := make(map[basics.Round]bookkeeping.BlockHeader) + blockErr := make(map[basics.Round]error) + applier := &stateProofApplierMock{ + spNext: 0, + blocks: blocks, + blockErr: blockErr, + stateProofVerification: nil, + version: version, + } + + spType = protocol.StateProofType(1234) // bad stateproof type + stateProofTx := transactions.StateProofTxnFields{ + StateProofType: spType, + StateProof: stateProof, + Message: msg, + } + err := StateProof(stateProofTx, atRound, applier, validate) + a.ErrorIs(err, ErrStateProofTypeNotSupported) + + stateProofTx.StateProofType = protocol.StateProofBasic + // stateproof txn doesn't confirm the next state proof round. expected is in the past + validate = true + stateProofTx.Message.LastAttestedRound = uint64(16) + applier.SetStateProofNextRound(8) + err = StateProof(stateProofTx, atRound, applier, validate) + a.ErrorIs(err, ErrExpectedDifferentStateProofRound) + applier.SetStateProofNextRound(32) + + // stateproof txn doesn't confirm the next state proof round. expected is in the future + validate = true + stateProofTx.Message.LastAttestedRound = uint64(16) + applier.SetStateProofNextRound(32) + err = StateProof(stateProofTx, atRound, applier, validate) + a.ErrorIs(err, ErrExpectedDifferentStateProofRound) + + // no atRound and lastAttested block + stateProofTx.Message.LastAttestedRound = 32 + noBlockErr := errors.New("no block") + blockErr[atRound] = noBlockErr + blockErr[basics.Round(stateProofTx.Message.LastAttestedRound)] = noBlockErr + err = StateProof(stateProofTx, atRound, applier, validate) + a.ErrorIs(err, noBlockErr) + delete(blockErr, atRound) + + atRoundBlock := bookkeeping.BlockHeader{} + atRoundBlock.CurrentProtocol = version + blocks[atRound] = atRoundBlock + + // no spRnd block + noBlockErr = errors.New("no block") + blockErr[32] = noBlockErr + stateProofTx.Message.LastAttestedRound = 32 + err = StateProof(stateProofTx, atRound, applier, validate) + a.ErrorIs(err, noBlockErr) + + // no votersRnd block + // this is slightly a mess of things that don't quite line up with likely usage + validate = true + var spHdr bookkeeping.BlockHeader + spHdr.CurrentProtocol = "TestCowStateProof" + spHdr.Round = 1 + proto := config.Consensus[spHdr.CurrentProtocol] + proto.StateProofInterval = 2 + config.Consensus[spHdr.CurrentProtocol] = proto + blocks[spHdr.Round] = spHdr + + spHdr.Round = 15 + blocks[spHdr.Round] = spHdr + stateProofTx.Message.LastAttestedRound = uint64(spHdr.Round) + applier.SetStateProofNextRound(15) + blockErr[13] = noBlockErr + err = StateProof(stateProofTx, atRound, applier, validate) + a.Contains(err.Error(), "no block") + delete(blockErr, 13) + + // check the happy flow - we should fail only on crypto + atRound = 800 + spHdr = bookkeeping.BlockHeader{} + spHdr.CurrentProtocol = version + blocks[basics.Round(2*config.Consensus[version].StateProofInterval)] = spHdr + + votersHdr := bookkeeping.BlockHeader{} + votersHdr.CurrentProtocol = version + stateproofTracking := bookkeeping.StateProofTrackingData{ + StateProofVotersCommitment: []byte{0x1}[:], + StateProofOnlineTotalWeight: basics.MicroAlgos{Raw: 5}, + } + votersHdr.StateProofTracking = make(map[protocol.StateProofType]bookkeeping.StateProofTrackingData) + votersHdr.StateProofTracking[protocol.StateProofBasic] = stateproofTracking + + blocks[basics.Round(config.Consensus[version].StateProofInterval)] = votersHdr + atRoundBlock = bookkeeping.BlockHeader{} + atRoundBlock.CurrentProtocol = version + blocks[atRound] = atRoundBlock + + stateProofTx.Message.LastAttestedRound = 2 * config.Consensus[version].StateProofInterval + stateProofTx.StateProof.SignedWeight = 100 + applier.SetStateProofNextRound(basics.Round(2 * config.Consensus[version].StateProofInterval)) + + err = StateProof(stateProofTx, atRound, applier, validate) + a.Contains(err.Error(), "crypto error") +} + +func TestApplyStateProof(t *testing.T) { + partitiontest.PartitionTest(t) + a := require.New(t) + + var spType protocol.StateProofType + var stateProof stateproof.StateProof + atRound := basics.Round(600) + var validate bool + msg := stateproofmsg.Message{} + + blocks := make(map[basics.Round]bookkeeping.BlockHeader) + blockErr := make(map[basics.Round]error) + stateProofVerification := make(map[basics.Round]*ledgercore.StateProofVerificationContext) + applier := &stateProofApplierMock{ + spNext: 0, + blocks: blocks, + blockErr: blockErr, + stateProofVerification: stateProofVerification, + version: protocol.ConsensusFuture, + } + + spType = protocol.StateProofType(1234) // bad stateproof type + stateProofTx := transactions.StateProofTxnFields{ + StateProofType: spType, + StateProof: stateProof, + Message: msg, + } + err := StateProof(stateProofTx, atRound, applier, validate) + a.ErrorIs(err, ErrStateProofTypeNotSupported) + + stateProofTx.StateProofType = protocol.StateProofBasic + // stateproof txn doesn't confirm the next state proof round. expected is in the past + validate = true + stateProofTx.Message.LastAttestedRound = uint64(16) + applier.SetStateProofNextRound(8) + err = StateProof(stateProofTx, atRound, applier, validate) + a.ErrorIs(err, ErrExpectedDifferentStateProofRound) + applier.SetStateProofNextRound(32) + + // stateproof txn doesn't confirm the next state proof round. expected is in the future + validate = true + stateProofTx.Message.LastAttestedRound = uint64(16) + applier.SetStateProofNextRound(32) + err = StateProof(stateProofTx, atRound, applier, validate) + a.ErrorIs(err, ErrExpectedDifferentStateProofRound) + + atRoundBlock := bookkeeping.BlockHeader{} + atRoundBlock.CurrentProtocol = protocol.ConsensusFuture + blocks[atRound] = atRoundBlock + + validate = true + // no Verification Context for rounds 32 + stateProofTx.Message.LastAttestedRound = 32 + err = StateProof(stateProofTx, atRound, applier, validate) + a.ErrorIs(err, ErrVerificationContextNotFound) + delete(blockErr, atRound) + + // the behavior has changed and we no longer require the voters blockheader to verify the transaction + // still, this test should assure the error returned is the one expected and not "no block" + noBlockErr := errors.New("no block") + + // removing blocks for the ledger so if apply.stateproof uses the tracker it should pass + applier.SetStateProofNextRound(512) + blockErr[512] = noBlockErr + blockErr[256] = noBlockErr + stateProofTx.Message.LastAttestedRound = 512 + stateProofTx.StateProof.SignedWeight = 100 + stateProofVerification[basics.Round(stateProofTx.Message.LastAttestedRound)] = &ledgercore.StateProofVerificationContext{ + LastAttestedRound: basics.Round(stateProofTx.Message.LastAttestedRound), + VotersCommitment: []byte{0x1}[:], + OnlineTotalWeight: basics.MicroAlgos{Raw: 5}, + Version: protocol.ConsensusFuture, + } + + // crypto verification should fail since it is not a valid stateproof + err = StateProof(stateProofTx, atRound, applier, validate) + a.Error(err) + a.Contains(err.Error(), "crypto error") + + a.Equal(basics.Round(512), applier.GetStateProofNextRound()) + // transaction should be applied without stateproof validation (no context, blockheader or valid stateproof needed as it represents a node catching up) + err = StateProof(stateProofTx, atRound, applier, false) + a.NoError(err) + // make sure that the ModStateProofNextRound was updated correctly after applying + a.Equal(basics.Round(512+config.Consensus[protocol.ConsensusFuture].StateProofInterval), applier.GetStateProofNextRound()) +} diff --git a/ledger/bulletin.go b/ledger/bulletin.go index cea20f1e70..49b1951b76 100644 --- a/ledger/bulletin.go +++ b/ledger/bulletin.go @@ -81,10 +81,7 @@ func (b *bulletin) Wait(round basics.Round) chan struct{} { } func (b *bulletin) loadFromDisk(l ledgerForTracker, _ basics.Round) error { - // We want to keep existing notification requests in memory if this flow is triggered by reloadLedger. - if b.pendingNotificationRequests == nil { - b.pendingNotificationRequests = make(map[basics.Round]notifier) - } + b.pendingNotificationRequests = make(map[basics.Round]notifier) b.latestRound = l.Latest() return nil } diff --git a/ledger/catchpointtracker.go b/ledger/catchpointtracker.go index ef3834c125..26e7972e9d 100644 --- a/ledger/catchpointtracker.go +++ b/ledger/catchpointtracker.go @@ -22,6 +22,7 @@ import ( "compress/gzip" "context" "database/sql" + "encoding/base32" "encoding/hex" "errors" "fmt" @@ -56,10 +57,22 @@ const ( // CatchpointFileVersionV5 is the catchpoint file version that was used when the database schema was V0-V5. CatchpointFileVersionV5 = uint64(0200) - // CatchpointFileVersionV6 is the catchpoint file version that is matching database schema V6. + // CatchpointFileVersionV6 is the catchpoint file version that is matching database schema since V6. // This version introduced accounts and resources separation. The first catchpoint // round of this version is >= `reenableCatchpointsRound`. CatchpointFileVersionV6 = uint64(0201) + // CatchpointFileVersionV7 is the catchpoint file version that is matching database schema V10. + // This version introduced state proof verification data and versioning for CatchpointLabel. + CatchpointFileVersionV7 = uint64(0202) + + // CatchpointContentFileName is a name of a file with catchpoint header info inside tar archive + CatchpointContentFileName = "content.msgpack" + // catchpointSPVerificationFileName is a name of a file with stateproof verification data + catchpointSPVerificationFileName = "stateProofVerificationContext.msgpack" + // catchpointBalancesFileNameTemplate is a template name of files with balances data + catchpointBalancesFileNameTemplate = "balances.%d.msgpack" + catchpointBalancesFileNamePrefix = "balances." + catchpointBalancesFileNameSuffix = ".msgpack" ) func catchpointStage1Encoder(w io.Writer) (io.WriteCloser, error) { @@ -123,11 +136,11 @@ type catchpointTracker struct { // roundDigest stores the digest of the block for every round starting with dbRound+1 and every round after it. roundDigest []crypto.Digest - // reenableCatchpointsRound is a round where the EnableOnlineAccountCatchpoints feature was enabled via the consensus. + // reenableCatchpointsRound is a round where the EnableCatchpointsWithSPContexts feature was enabled via the consensus. // we avoid generating catchpoints before that round in order to ensure the network remain consistent in the catchpoint // label being produced. This variable could be "wrong" in two cases - - // 1. It's zero, meaning that the EnableOnlineAccountCatchpoints has yet to be seen. - // 2. It's non-zero meaning that it the given round is after the EnableOnlineAccountCatchpoints was enabled ( it might be exact round + // 1. It's zero, meaning that the EnableCatchpointsWithSPContexts has yet to be seen. + // 2. It's non-zero meaning that it the given round is after the EnableCatchpointsWithSPContexts was enabled ( it might be exact round // but that's only if newBlock was called with that round ), plus the lookback. reenableCatchpointsRound basics.Round @@ -195,6 +208,8 @@ func (ct *catchpointTracker) finishFirstStage(ctx context.Context, dbRound basic var totalAccounts uint64 var totalChunks uint64 var biggestChunkLen uint64 + var spVerificationHash crypto.Digest + var catchpointGenerationStats telemetryspec.CatchpointGenerationEventDetails if ct.enableGeneratingCatchpointFiles { // Generate the catchpoint file. This is done inline so that it will @@ -202,8 +217,10 @@ func (ct *catchpointTracker) finishFirstStage(ctx context.Context, dbRound basic // expects that the accounts data would not be modified in the // background during its execution. var err error - totalKVs, totalAccounts, totalChunks, biggestChunkLen, err = ct.generateCatchpointData( - ctx, dbRound, updatingBalancesDuration) + + catchpointGenerationStats.BalancesWriteTime = uint64(updatingBalancesDuration.Nanoseconds()) + totalKVs, totalAccounts, totalChunks, biggestChunkLen, spVerificationHash, err = ct.generateCatchpointData( + ctx, dbRound, &catchpointGenerationStats) atomic.StoreInt32(&ct.catchpointDataWriting, 0) if err != nil { return err @@ -216,7 +233,7 @@ func (ct *catchpointTracker) finishFirstStage(ctx context.Context, dbRound basic return err } - err = ct.recordFirstStageInfo(ctx, tx, dbRound, totalKVs, totalAccounts, totalChunks, biggestChunkLen) + err = ct.recordFirstStageInfo(ctx, tx, &catchpointGenerationStats, dbRound, totalKVs, totalAccounts, totalChunks, biggestChunkLen, spVerificationHash) if err != nil { return err } @@ -356,7 +373,7 @@ func (ct *catchpointTracker) newBlock(blk bookkeeping.Block, delta ledgercore.St ct.roundDigest = append(ct.roundDigest, blk.Digest()) - if (config.Consensus[blk.CurrentProtocol].EnableOnlineAccountCatchpoints || ct.forceCatchpointFileWriting) && ct.reenableCatchpointsRound == 0 { + if (config.Consensus[blk.CurrentProtocol].EnableCatchpointsWithSPContexts || ct.forceCatchpointFileWriting) && ct.reenableCatchpointsRound == 0 { catchpointLookback := config.Consensus[blk.CurrentProtocol].CatchpointLookback if catchpointLookback == 0 { catchpointLookback = config.Consensus[blk.CurrentProtocol].MaxBalLookback @@ -587,7 +604,7 @@ func doRepackCatchpoint(ctx context.Context, header CatchpointFileHeader, bigges bytes := protocol.Encode(&header) err := out.WriteHeader(&tar.Header{ - Name: "content.msgpack", + Name: CatchpointContentFileName, Mode: 0600, Size: int64(len(bytes)), }) @@ -711,8 +728,8 @@ func repackCatchpoint(ctx context.Context, header CatchpointFileHeader, biggestC // the unfinished catchpoint record. func (ct *catchpointTracker) createCatchpoint(ctx context.Context, accountsRound basics.Round, round basics.Round, dataInfo trackerdb.CatchpointFirstStageInfo, blockHash crypto.Digest) error { startTime := time.Now() - label := ledgercore.MakeCatchpointLabel( - round, blockHash, dataInfo.TrieBalancesHash, dataInfo.Totals).String() + labelMaker := ledgercore.MakeCatchpointLabelMakerCurrent(round, &blockHash, &dataInfo.TrieBalancesHash, dataInfo.Totals, &dataInfo.StateProofVerificationHash) + label := ledgercore.MakeLabel(labelMaker) ct.log.Infof( "creating catchpoint round: %d accountsRound: %d label: %s", @@ -747,7 +764,7 @@ func (ct *catchpointTracker) createCatchpoint(ctx context.Context, accountsRound // Make a catchpoint file. header := CatchpointFileHeader{ - Version: CatchpointFileVersionV6, + Version: CatchpointFileVersionV7, BalancesRound: accountsRound, BlocksRound: round, Totals: dataInfo.Totals, @@ -1075,13 +1092,17 @@ func (ct *catchpointTracker) IsWritingCatchpointDataFile() bool { } // Generates a (first stage) catchpoint data file. -func (ct *catchpointTracker) generateCatchpointData(ctx context.Context, accountsRound basics.Round, updatingBalancesDuration time.Duration) (totalKVs, totalAccounts, totalChunks, biggestChunkLen uint64, err error) { +// The file is built in the following order: +// - Catchpoint file header (named content.msgpack). The header is generated and appended to the file at the end of the +// second stage of catchpoint generation. +// - State proof verification data chunk (named stateProofVerificationContext.msgpack). +// - 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) { ct.log.Debugf("catchpointTracker.generateCatchpointData() writing catchpoint accounts for round %d", accountsRound) startTime := time.Now() - catchpointGenerationStats := telemetryspec.CatchpointGenerationEventDetails{ - BalancesWriteTime: uint64(updatingBalancesDuration.Nanoseconds()), - } catchpointDataFilePath := filepath.Join(ct.dbDirectory, trackerdb.CatchpointDirName) catchpointDataFilePath = @@ -1099,6 +1120,7 @@ func (ct *catchpointTracker) generateCatchpointData(ctx context.Context, account } var catchpointWriter *catchpointWriter + start := time.Now() ledgerGeneratecatchpointCount.Inc(nil) err = ct.dbs.TransactionContext(ctx, func(dbCtx context.Context, tx trackerdb.TransactionScope) (err error) { @@ -1106,6 +1128,12 @@ func (ct *catchpointTracker) generateCatchpointData(ctx context.Context, account if err != nil { return } + + spVerificationHash, err = catchpointWriter.WriteStateProofVerificationContext() + if err != nil { + return + } + for more { stepCtx, stepCancelFunction := context.WithTimeout(dbCtx, chunkExecutionDuration) writeStepStartTime := time.Now() @@ -1156,28 +1184,19 @@ 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, err + return 0, 0, 0, 0, crypto.Digest{}, err } catchpointGenerationStats.FileSize = uint64(catchpointWriter.writtenBytes) catchpointGenerationStats.WritingDuration = uint64(time.Since(startTime).Nanoseconds()) catchpointGenerationStats.AccountsCount = catchpointWriter.totalAccounts catchpointGenerationStats.KVsCount = catchpointWriter.totalKVs - ct.log.EventWithDetails(telemetryspec.Accounts, telemetryspec.CatchpointGenerationEvent, catchpointGenerationStats) - ct.log.With("accountsRound", accountsRound). - With("writingDuration", catchpointGenerationStats.WritingDuration). - With("CPUTime", catchpointGenerationStats.CPUTime). - With("balancesWriteTime", catchpointGenerationStats.BalancesWriteTime). - With("accountsCount", catchpointGenerationStats.AccountsCount). - With("kvsCount", catchpointGenerationStats.KVsCount). - With("fileSize", catchpointGenerationStats.FileSize). - With("catchpointLabel", catchpointGenerationStats.CatchpointLabel). - Infof("Catchpoint data file was generated") + catchpointGenerationStats.AccountsRound = uint64(accountsRound) - return catchpointWriter.totalKVs, catchpointWriter.totalAccounts, catchpointWriter.chunkNum, catchpointWriter.biggestChunkLen, nil + return catchpointWriter.totalKVs, catchpointWriter.totalAccounts, catchpointWriter.chunkNum, catchpointWriter.biggestChunkLen, spVerificationHash, nil } -func (ct *catchpointTracker) recordFirstStageInfo(ctx context.Context, tx trackerdb.TransactionScope, accountsRound basics.Round, totalKVs uint64, totalAccounts uint64, totalChunks uint64, biggestChunkLen uint64) error { +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 { arw, err := tx.MakeAccountsReaderWriter() if err != nil { return err @@ -1214,14 +1233,34 @@ func (ct *catchpointTracker) recordFirstStageInfo(ctx context.Context, tx tracke } info := trackerdb.CatchpointFirstStageInfo{ - Totals: accountTotals, - TotalAccounts: totalAccounts, - TotalKVs: totalKVs, - TotalChunks: totalChunks, - BiggestChunkLen: biggestChunkLen, - TrieBalancesHash: trieBalancesHash, - } - return crw.InsertOrReplaceCatchpointFirstStageInfo(ctx, accountsRound, &info) + Totals: accountTotals, + TotalAccounts: totalAccounts, + TotalKVs: totalKVs, + TotalChunks: totalChunks, + BiggestChunkLen: biggestChunkLen, + TrieBalancesHash: trieBalancesHash, + StateProofVerificationHash: stateProofVerificationHash, + } + + err = crw.InsertOrReplaceCatchpointFirstStageInfo(ctx, accountsRound, &info) + if err != nil { + return err + } + + catchpointGenerationStats.MerkleTrieRootHash = base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(trieBalancesHash[:]) + catchpointGenerationStats.SPVerificationCtxsHash = base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(stateProofVerificationHash[:]) + ct.log.EventWithDetails(telemetryspec.Accounts, telemetryspec.CatchpointGenerationEvent, catchpointGenerationStats) + ct.log.With("accountsRound", catchpointGenerationStats.AccountsRound). + With("writingDuration", catchpointGenerationStats.WritingDuration). + With("CPUTime", catchpointGenerationStats.CPUTime). + With("balancesWriteTime", catchpointGenerationStats.BalancesWriteTime). + With("accountsCount", catchpointGenerationStats.AccountsCount). + With("kvsCount", catchpointGenerationStats.KVsCount). + With("fileSize", catchpointGenerationStats.FileSize). + With("MerkleTrieRootHash", catchpointGenerationStats.MerkleTrieRootHash). + With("SPVerificationCtxsHash", catchpointGenerationStats.SPVerificationCtxsHash). + Infof("Catchpoint data file was generated") + return nil } func makeCatchpointDataFilePath(accountsRound basics.Round) string { diff --git a/ledger/catchpointtracker_test.go b/ledger/catchpointtracker_test.go index 3ba865e1f9..d24efac19a 100644 --- a/ledger/catchpointtracker_test.go +++ b/ledger/catchpointtracker_test.go @@ -42,6 +42,7 @@ import ( "github.com/algorand/go-algorand/ledger/store/trackerdb" ledgertesting "github.com/algorand/go-algorand/ledger/testing" "github.com/algorand/go-algorand/logging" + "github.com/algorand/go-algorand/logging/telemetryspec" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" ) @@ -267,6 +268,20 @@ func getNumberOfCatchpointFilesInDir(catchpointDir string) (int, error) { return numberOfCatchpointFiles, err } +func calculateStateProofVerificationHash(t *testing.T, ml *mockLedgerForTracker) crypto.Digest { + var digest crypto.Digest + err := ml.dbs.Snapshot(func(dbCtx context.Context, tx trackerdb.SnapshotScope) (err error) { + rawData, err := tx.MakeSpVerificationCtxReader().GetAllSPContexts(dbCtx) + require.NoError(t, err) + + wrappedData := catchpointStateProofVerificationContext{Data: rawData} + digest = crypto.HashObj(wrappedData) + return nil + }) + require.NoError(t, err) + return digest +} + // The goal of this test is to check that we are saving at most X catchpoint files. // If algod needs to create a new catchpoint file it will delete the oldest. // In addition, when deleting old catchpoint files an empty directory should be deleted @@ -297,13 +312,7 @@ func TestRecordCatchpointFile(t *testing.T) { for _, round := range []basics.Round{2000000, 3000010, 3000015, 3000020} { accountsRound := round - 1 - - _, _, _, biggestChunkLen, err := ct.generateCatchpointData( - context.Background(), accountsRound, time.Second) - require.NoError(t, err) - - err = ct.createCatchpoint(context.Background(), accountsRound, round, trackerdb.CatchpointFirstStageInfo{BiggestChunkLen: biggestChunkLen}, crypto.Digest{}) - require.NoError(t, err) + createCatchpoint(t, ct, accountsRound, ml, round) } numberOfCatchpointFiles, err := getNumberOfCatchpointFilesInDir(temporaryDirectory) @@ -317,6 +326,87 @@ func TestRecordCatchpointFile(t *testing.T) { require.Equalf(t, onlyCatchpointDirEmpty, true, "Directories: %v", emptyDirs) } +func createCatchpoint(t *testing.T, ct *catchpointTracker, accountsRound basics.Round, ml *mockLedgerForTracker, round basics.Round) { + var catchpointGenerationStats telemetryspec.CatchpointGenerationEventDetails + _, _, _, biggestChunkLen, stateProofVerificationHash, err := ct.generateCatchpointData( + context.Background(), accountsRound, &catchpointGenerationStats) + require.NoError(t, err) + + require.Equal(t, calculateStateProofVerificationHash(t, ml), stateProofVerificationHash) + + err = ct.createCatchpoint(context.Background(), accountsRound, round, trackerdb.CatchpointFirstStageInfo{BiggestChunkLen: biggestChunkLen}, crypto.Digest{}) + require.NoError(t, err) +} + +// TestCatchpointFileWithLargeSpVerification makes sure that CatchpointFirstStageInfo.BiggestChunkLen is calculated based on state proof verification contexts +// as well as other chunks in the catchpoint files. +func TestCatchpointFileWithLargeSpVerification(t *testing.T) { + partitiontest.PartitionTest(t) + + temporaryDirectory := t.TempDir() + + accts := []map[basics.Address]basics.AccountData{ledgertesting.RandomAccounts(20, true)} + ml := makeMockLedgerForTracker(t, true, 10, protocol.ConsensusCurrentVersion, accts) + defer ml.Close() + + ct := &catchpointTracker{} + conf := config.GetDefaultLocal() + + conf.Archival = true + ct.initialize(conf, ".") + defer ct.close() + ct.dbDirectory = temporaryDirectory + + _, err := trackerDBInitialize(ml, true, ct.dbDirectory) + require.NoError(t, err) + + err = ct.loadFromDisk(ml, ml.Latest()) + require.NoError(t, err) + + // create catpoint with no sp verification data + round := basics.Round(2000000) + createCatchpoint(t, ct, round-1, ml, round) + + numberOfCatchpointFiles, err := getNumberOfCatchpointFilesInDir(temporaryDirectory) + require.NoError(t, err) + require.Equal(t, 1, numberOfCatchpointFiles) + // create catpoint with 2 sp verification data + writeDummySpVerification(t, 0, 3, ml) + + round = basics.Round(3000000) + createCatchpoint(t, ct, round-1, ml, round) + + numberOfCatchpointFiles, err = getNumberOfCatchpointFilesInDir(temporaryDirectory) + require.NoError(t, err) + require.Equal(t, 2, numberOfCatchpointFiles) + + // create catpoint with 500 sp verification data - the sp verification chunk should be the largest + writeDummySpVerification(t, 4, 500, ml) + + round = basics.Round(4000000) + createCatchpoint(t, ct, round-1, ml, round) + + numberOfCatchpointFiles, err = getNumberOfCatchpointFilesInDir(temporaryDirectory) + require.NoError(t, err) + require.Equal(t, 3, numberOfCatchpointFiles) +} + +func writeDummySpVerification(t *testing.T, nextIndexForContext uint64, numberOfContexts uint64, ml *mockLedgerForTracker) { + err := ml.dbs.Transaction(func(ctx context.Context, tx trackerdb.TransactionScope) error { + + contexts := make([]*ledgercore.StateProofVerificationContext, numberOfContexts) + for i := uint64(0); i < numberOfContexts; i++ { + e := ledgercore.StateProofVerificationContext{} + e.LastAttestedRound = basics.Round(nextIndexForContext + i) + contexts[i] = &e + } + writer := tx.MakeSpVerificationCtxReaderWriter() + + return writer.StoreSPContexts(ctx, contexts[:]) + }) + require.NoError(t, err) +} + func BenchmarkLargeCatchpointDataWriting(b *testing.B) { proto := config.Consensus[protocol.ConsensusCurrentVersion] @@ -370,8 +460,9 @@ func BenchmarkLargeCatchpointDataWriting(b *testing.B) { }) require.NoError(b, err) + var catchpointGenerationStats telemetryspec.CatchpointGenerationEventDetails b.ResetTimer() - ct.generateCatchpointData(context.Background(), basics.Round(0), time.Second) + ct.generateCatchpointData(context.Background(), basics.Round(0), &catchpointGenerationStats) b.StopTimer() b.ReportMetric(float64(accountsNumber), "accounts") } @@ -387,7 +478,7 @@ func TestReproducibleCatchpointLabels(t *testing.T) { testProtocolVersion := protocol.ConsensusVersion("test-protocol-TestReproducibleCatchpointLabels") protoParams := config.Consensus[protocol.ConsensusCurrentVersion] protoParams.CatchpointLookback = 32 - protoParams.EnableOnlineAccountCatchpoints = true + protoParams.EnableCatchpointsWithSPContexts = true config.Consensus[testProtocolVersion] = protoParams defer func() { delete(config.Consensus, testProtocolVersion) @@ -457,8 +548,8 @@ func TestReproducibleCatchpointLabels(t *testing.T) { delta.Totals = newTotals ml.trackers.newBlock(blk, delta) - ml.trackers.committedUpTo(i) ml.addMockBlock(blockEntry{block: blk}, delta) + ml.trackers.committedUpTo(i) accts = append(accts, newAccts) rewardsLevels = append(rewardsLevels, rewardLevel) roundDeltas[i] = delta @@ -502,6 +593,8 @@ func TestReproducibleCatchpointLabels(t *testing.T) { delta := roundDeltas[i] ml2.trackers.newBlock(blk, delta) + err := ml2.addMockBlock(blockEntry{block: blk}, delta) + require.NoError(t, err) ml2.trackers.committedUpTo(i) // if this is a catchpoint round, check the label. @@ -600,7 +693,7 @@ func TestCatchpointTrackerNonblockingCatchpointWriting(t *testing.T) { testProtocolVersion := protocol.ConsensusVersion("test-protocol-TestReproducibleCatchpointLabels") protoParams := config.Consensus[protocol.ConsensusCurrentVersion] - protoParams.EnableOnlineAccountCatchpoints = true + protoParams.EnableCatchpointsWithSPContexts = true protoParams.CatchpointLookback = protoParams.MaxBalLookback config.Consensus[testProtocolVersion] = protoParams defer func() { @@ -836,7 +929,7 @@ func TestFirstStageInfoPruning(t *testing.T) { protocol.ConsensusVersion("test-protocol-TestFirstStageInfoPruning") protoParams := config.Consensus[protocol.ConsensusCurrentVersion] protoParams.CatchpointLookback = 32 - protoParams.EnableOnlineAccountCatchpoints = true + protoParams.EnableCatchpointsWithSPContexts = true config.Consensus[testProtocolVersion] = protoParams defer func() { delete(config.Consensus, testProtocolVersion) @@ -930,7 +1023,7 @@ func TestFirstStagePersistence(t *testing.T) { protocol.ConsensusVersion("test-protocol-TestFirstStagePersistence") protoParams := config.Consensus[protocol.ConsensusCurrentVersion] protoParams.CatchpointLookback = 32 - protoParams.EnableOnlineAccountCatchpoints = true + protoParams.EnableCatchpointsWithSPContexts = true config.Consensus[testProtocolVersion] = protoParams defer func() { delete(config.Consensus, testProtocolVersion) @@ -1033,7 +1126,7 @@ func TestSecondStagePersistence(t *testing.T) { protocol.ConsensusVersion("test-protocol-TestFirstStagePersistence") protoParams := config.Consensus[protocol.ConsensusCurrentVersion] protoParams.CatchpointLookback = 32 - protoParams.EnableOnlineAccountCatchpoints = true + protoParams.EnableCatchpointsWithSPContexts = true config.Consensus[testProtocolVersion] = protoParams defer func() { delete(config.Consensus, testProtocolVersion) @@ -1170,7 +1263,7 @@ func TestSecondStageDeletesUnfinishedCatchpointRecord(t *testing.T) { protocol.ConsensusVersion("test-protocol-TestFirstStagePersistence") protoParams := config.Consensus[protocol.ConsensusCurrentVersion] protoParams.CatchpointLookback = 32 - protoParams.EnableOnlineAccountCatchpoints = true + protoParams.EnableCatchpointsWithSPContexts = true config.Consensus[testProtocolVersion] = protoParams defer func() { delete(config.Consensus, testProtocolVersion) @@ -1259,7 +1352,7 @@ func TestSecondStageDeletesUnfinishedCatchpointRecordAfterRestart(t *testing.T) protocol.ConsensusVersion("test-protocol-TestFirstStagePersistence") protoParams := config.Consensus[protocol.ConsensusCurrentVersion] protoParams.CatchpointLookback = 32 - protoParams.EnableOnlineAccountCatchpoints = true + protoParams.EnableCatchpointsWithSPContexts = true config.Consensus[testProtocolVersion] = protoParams defer func() { delete(config.Consensus, testProtocolVersion) @@ -1481,7 +1574,7 @@ func TestCatchpointFastUpdates(t *testing.T) { t.Skip("This test is too slow on ARM and causes CI builds to time out") } - proto := config.Consensus[protocol.ConsensusCurrentVersion] + proto := config.Consensus[protocol.ConsensusFuture] accts := []map[basics.Address]basics.AccountData{ledgertesting.RandomAccounts(20, true)} addSinkAndPoolAccounts(accts) @@ -1491,7 +1584,7 @@ func TestCatchpointFastUpdates(t *testing.T) { conf.CatchpointInterval = 1 conf.CatchpointTracking = 1 initialBlocksCount := int(conf.MaxAcctLookback) - ml := makeMockLedgerForTracker(t, true, initialBlocksCount, protocol.ConsensusCurrentVersion, accts) + ml := makeMockLedgerForTracker(t, true, initialBlocksCount, protocol.ConsensusFuture, accts) defer ml.Close() ct := newCatchpointTracker(t, ml, conf, ".") @@ -1540,7 +1633,7 @@ func TestCatchpointFastUpdates(t *testing.T) { }, } blk.RewardsLevel = rewardLevel - blk.CurrentProtocol = protocol.ConsensusCurrentVersion + blk.CurrentProtocol = protocol.ConsensusFuture delta := ledgercore.MakeStateDelta(&blk.BlockHeader, 0, updates.Len(), 0) delta.Accts.MergeAccounts(updates) @@ -1578,6 +1671,7 @@ func TestCatchpointLargeAccountCountCatchpointGeneration(t *testing.T) { testProtocolVersion := protocol.ConsensusVersion("test-protocol-TestLargeAccountCountCatchpointGeneration") protoParams := config.Consensus[protocol.ConsensusCurrentVersion] protoParams.CatchpointLookback = 16 + protoParams.EnableCatchpointsWithSPContexts = true config.Consensus[testProtocolVersion] = protoParams defer func() { delete(config.Consensus, testProtocolVersion) diff --git a/ledger/catchpointwriter.go b/ledger/catchpointwriter.go index f912fbf6f9..df8b72ba6f 100644 --- a/ledger/catchpointwriter.go +++ b/ledger/catchpointwriter.go @@ -24,7 +24,9 @@ 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" "github.com/algorand/go-algorand/protocol" ) @@ -38,6 +40,11 @@ const ( // 100,000 resources * 20KB/resource => roughly max 2GB per chunk if all of them are max'ed out apps. // In reality most entries are asset holdings, and they are very small. ResourcesPerCatchpointFileChunk = 100_000 + + // SPContextPerCatchpointFile defines the maximum number of state proof verification data stored + // in the catchpoint file. + // (2 years * 31536000 seconds per year) / (256 rounds per state proof verification data * 3.6 seconds per round) ~= 70000 + SPContextPerCatchpointFile = 70000 ) // catchpointWriter is the struct managing the persistence of accounts data into the catchpoint file. @@ -91,6 +98,15 @@ func (chunk catchpointFileChunkV6) empty() bool { return len(chunk.Balances) == 0 && len(chunk.KVs) == 0 } +type catchpointStateProofVerificationContext struct { + _struct struct{} `codec:",omitempty,omitemptyarray"` + Data []ledgercore.StateProofVerificationContext `codec:"spd,allocbound=SPContextPerCatchpointFile"` +} + +func (data catchpointStateProofVerificationContext) ToBeHashed() (protocol.HashID, []byte) { + return protocol.StateProofVerCtx, protocol.Encode(&data) +} + func makeCatchpointWriter(ctx context.Context, filePath string, tx trackerdb.TransactionScope, maxResourcesPerChunk int) (*catchpointWriter, error) { arw, err := tx.MakeAccountsReaderWriter() if err != nil { @@ -144,6 +160,37 @@ func (cw *catchpointWriter) Abort() error { return os.Remove(cw.filePath) } +func (cw *catchpointWriter) WriteStateProofVerificationContext() (crypto.Digest, error) { + rawData, err := cw.tx.MakeSpVerificationCtxReaderWriter().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{ + Name: catchpointSPVerificationFileName, + Mode: 0600, + Size: int64(len(encodedData)), + }) + + if err != nil { + return crypto.Digest{}, err + } + + _, err = cw.tar.Write(encodedData) + if err != nil { + return crypto.Digest{}, err + } + + if chunkLen := uint64(len(encodedData)); cw.biggestChunkLen < chunkLen { + cw.biggestChunkLen = chunkLen + } + + return dataHash, nil +} + // WriteStep 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 @@ -247,7 +294,7 @@ func (cw *catchpointWriter) asyncWriter(chunks chan catchpointFileChunkV6, respo } encodedChunk := protocol.Encode(&chk) err := cw.tar.WriteHeader(&tar.Header{ - Name: fmt.Sprintf("balances.%d.msgpack", chunkNum), + Name: fmt.Sprintf(catchpointBalancesFileNameTemplate, chunkNum), Mode: 0600, Size: int64(len(encodedChunk)), }) diff --git a/ledger/catchpointwriter_test.go b/ledger/catchpointwriter_test.go index 5fd0518bbc..db54b4527c 100644 --- a/ledger/catchpointwriter_test.go +++ b/ledger/catchpointwriter_test.go @@ -49,6 +49,133 @@ import ( "github.com/algorand/msgp/msgp" ) +type decodedCatchpointChunkData struct { + headerName string + data []byte +} + +func readCatchpointContent(t *testing.T, tarReader *tar.Reader) []decodedCatchpointChunkData { + result := make([]decodedCatchpointChunkData, 0) + for { + header, err := tarReader.Next() + if err != nil { + if err == io.EOF { + break + } + require.NoError(t, err) + break + } + data := make([]byte, header.Size) + readComplete := int64(0) + + for readComplete < header.Size { + bytesRead, err := tarReader.Read(data[readComplete:]) + readComplete += int64(bytesRead) + if err != nil { + if err == io.EOF { + if readComplete == header.Size { + break + } + require.NoError(t, err) + } + break + } + } + + result = append(result, decodedCatchpointChunkData{headerName: header.Name, data: data}) + } + + return result +} + +func readCatchpointDataFile(t *testing.T, catchpointDataPath string) []decodedCatchpointChunkData { + fileContent, err := os.ReadFile(catchpointDataPath) + require.NoError(t, err) + + compressorReader, err := catchpointStage1Decoder(bytes.NewBuffer(fileContent)) + require.NoError(t, err) + + tarReader := tar.NewReader(compressorReader) + return readCatchpointContent(t, tarReader) +} + +func readCatchpointFile(t *testing.T, catchpointPath string) []decodedCatchpointChunkData { + fileContent, err := os.ReadFile(catchpointPath) + require.NoError(t, err) + + gzipReader, err := gzip.NewReader(bytes.NewBuffer(fileContent)) + require.NoError(t, err) + defer gzipReader.Close() + + tarReader := tar.NewReader(gzipReader) + return readCatchpointContent(t, tarReader) +} + +func verifyStateProofVerificationContextWrite(t *testing.T, data []ledgercore.StateProofVerificationContext) { + // create new protocol version, which has lower lookback + testProtocolVersion := protocol.ConsensusVersion("test-protocol-TestBasicCatchpointWriter") + protoParams := config.Consensus[protocol.ConsensusCurrentVersion] + protoParams.CatchpointLookback = 32 + config.Consensus[testProtocolVersion] = protoParams + temporaryDirectory := t.TempDir() + defer func() { + delete(config.Consensus, testProtocolVersion) + }() + accts := ledgertesting.RandomAccounts(300, false) + + ml := makeMockLedgerForTracker(t, true, 10, testProtocolVersion, []map[basics.Address]basics.AccountData{accts}) + defer ml.Close() + + conf := config.GetDefaultLocal() + conf.CatchpointInterval = 1 + conf.Archival = true + au, _ := newAcctUpdates(t, ml, conf) + err := au.loadFromDisk(ml, 0) + require.NoError(t, err) + au.close() + fileName := filepath.Join(temporaryDirectory, "15.data") + + mockCommitData := make([]verificationCommitContext, 0) + for _, element := range data { + mockCommitData = append(mockCommitData, verificationCommitContext{verificationContext: element}) + } + + err = ml.dbs.Transaction(func(ctx context.Context, tx trackerdb.TransactionScope) error { + return commitSPContexts(ctx, tx, mockCommitData) + }) + + 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) + if err != nil { + return err + } + _, err = writer.WriteStateProofVerificationContext() + if err != nil { + return err + } + for { + more, err := writer.WriteStep(context.Background()) + require.NoError(t, err) + if !more { + break + } + } + return + }) + + catchpointData := readCatchpointDataFile(t, fileName) + require.Equal(t, catchpointSPVerificationFileName, catchpointData[0].headerName) + var wrappedData catchpointStateProofVerificationContext + err = protocol.Decode(catchpointData[0].data, &wrappedData) + require.NoError(t, err) + + for index, verificationContext := range wrappedData.Data { + require.Equal(t, data[index], verificationContext) + } +} + func TestCatchpointFileBalancesChunkEncoding(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() @@ -133,6 +260,10 @@ func TestBasicCatchpointWriter(t *testing.T) { if err != nil { return err } + _, err = writer.WriteStateProofVerificationContext() + if err != nil { + return err + } for { more, err := writer.WriteStep(context.Background()) require.NoError(t, err) @@ -142,45 +273,15 @@ func TestBasicCatchpointWriter(t *testing.T) { } return }) - require.NoError(t, err) - // load the file from disk. - fileContent, err := os.ReadFile(fileName) - require.NoError(t, err) - compressorReader, err := catchpointStage1Decoder(bytes.NewBuffer(fileContent)) - require.NoError(t, err) - defer compressorReader.Close() - tarReader := tar.NewReader(compressorReader) - - header, err := tarReader.Next() - require.NoError(t, err) - - balancesBlockBytes := make([]byte, header.Size) - readComplete := int64(0) - - for readComplete < header.Size { - bytesRead, err := tarReader.Read(balancesBlockBytes[readComplete:]) - readComplete += int64(bytesRead) - if err != nil { - if err == io.EOF { - if readComplete == header.Size { - break - } - require.NoError(t, err) - } - break - } - } - - require.Equal(t, "balances.1.msgpack", header.Name) + catchpointContent := readCatchpointDataFile(t, fileName) + balanceFileName := fmt.Sprintf(catchpointBalancesFileNameTemplate, 1) + require.Equal(t, balanceFileName, catchpointContent[1].headerName) var chunk catchpointFileChunkV6 - err = protocol.Decode(balancesBlockBytes, &chunk) + err = protocol.Decode(catchpointContent[1].data, &chunk) require.NoError(t, err) require.Equal(t, uint64(len(accts)), uint64(len(chunk.Balances))) - - _, err = tarReader.Next() - require.Equal(t, io.EOF, err) } func testWriteCatchpoint(t *testing.T, rdb trackerdb.TrackerStore, datapath string, filepath string, maxResourcesPerChunk int) CatchpointFileHeader { @@ -203,7 +304,10 @@ func testWriteCatchpoint(t *testing.T, rdb trackerdb.TrackerStore, datapath stri if err != nil { return err } - + _, err = writer.WriteStateProofVerificationContext() + if err != nil { + return err + } for { more, err := writer.WriteStep(context.Background()) require.NoError(t, err) @@ -226,7 +330,7 @@ func testWriteCatchpoint(t *testing.T, rdb trackerdb.TrackerStore, datapath stri blockHeaderDigest := crypto.Hash([]byte{1, 2, 3}) catchpointLabel := fmt.Sprintf("%d#%v", blocksRound, blockHeaderDigest) // this is not a correct way to create a label, but it's good enough for this unit test catchpointFileHeader := CatchpointFileHeader{ - Version: CatchpointFileVersionV6, + Version: CatchpointFileVersionV7, BalancesRound: accountsRnd, BlocksRound: blocksRound, Totals: totals, @@ -246,6 +350,26 @@ func testWriteCatchpoint(t *testing.T, rdb trackerdb.TrackerStore, datapath stri return catchpointFileHeader } +func TestStateProofVerificationContextWrite(t *testing.T) { + partitiontest.PartitionTest(t) + //t.Parallel() verifyStateProofVerificationContextWrite changes consensus + + verificationContext := ledgercore.StateProofVerificationContext{ + LastAttestedRound: 120, + VotersCommitment: nil, + OnlineTotalWeight: basics.MicroAlgos{Raw: 100}, + } + + verifyStateProofVerificationContextWrite(t, []ledgercore.StateProofVerificationContext{verificationContext}) +} + +func TestEmptyStateProofVerificationContextWrite(t *testing.T) { + partitiontest.PartitionTest(t) + //t.Parallel() verifyStateProofVerificationContextWrite changes consensus + + verifyStateProofVerificationContextWrite(t, []ledgercore.StateProofVerificationContext{}) +} + func TestCatchpointReadDatabaseOverflowSingleAccount(t *testing.T) { partitiontest.PartitionTest(t) @@ -552,40 +676,10 @@ func testNewLedgerFromCatchpoint(t *testing.T, catchpointWriterReadAccess tracke err = accessor.ResetStagingBalances(context.Background(), true) require.NoError(t, err) - // load the file from disk. - fileContent, err := os.ReadFile(filepath) - require.NoError(t, err) - gzipReader, err := gzip.NewReader(bytes.NewBuffer(fileContent)) - require.NoError(t, err) - tarReader := tar.NewReader(gzipReader) var catchupProgress CatchpointCatchupAccessorProgress - defer gzipReader.Close() - for { - header, err := tarReader.Next() - if err != nil { - if err == io.EOF { - break - } - require.NoError(t, err) - break - } - balancesBlockBytes := make([]byte, header.Size) - readComplete := int64(0) - - for readComplete < header.Size { - bytesRead, err := tarReader.Read(balancesBlockBytes[readComplete:]) - readComplete += int64(bytesRead) - if err != nil { - if err == io.EOF { - if readComplete == header.Size { - break - } - require.NoError(t, err) - } - break - } - } - err = accessor.ProcessStagingBalances(context.Background(), header.Name, balancesBlockBytes, &catchupProgress) + catchpointContent := readCatchpointFile(t, filepath) + for _, catchpointData := range catchpointContent { + err = accessor.ProcessStagingBalances(context.Background(), catchpointData.headerName, catchpointData.data, &catchupProgress) require.NoError(t, err) } diff --git a/ledger/catchupaccessor.go b/ledger/catchupaccessor.go index 0a6dd4b5c4..11b58ce962 100644 --- a/ledger/catchupaccessor.go +++ b/ledger/catchupaccessor.go @@ -339,10 +339,14 @@ type CatchpointCatchupAccessorProgress struct { // ProcessStagingBalances deserialize the given bytes as a temporary staging balances func (c *catchpointCatchupAccessorImpl) ProcessStagingBalances(ctx context.Context, sectionName string, bytes []byte, progress *CatchpointCatchupAccessorProgress) (err error) { - if sectionName == "content.msgpack" { + // content.msgpack comes first, followed by stateProofVerificationContext.msgpack and then by balances.x.msgpack. + if sectionName == CatchpointContentFileName { return c.processStagingContent(ctx, bytes, progress) } - if strings.HasPrefix(sectionName, "balances.") && strings.HasSuffix(sectionName, ".msgpack") { + if sectionName == catchpointSPVerificationFileName { + return c.processStagingStateProofVerificationContext(bytes) + } + if strings.HasPrefix(sectionName, catchpointBalancesFileNamePrefix) && strings.HasSuffix(sectionName, catchpointBalancesFileNameSuffix) { return c.processStagingBalances(ctx, bytes, progress) } // we want to allow undefined sections to support backward compatibility. @@ -350,6 +354,27 @@ func (c *catchpointCatchupAccessorImpl) ProcessStagingBalances(ctx context.Conte return nil } +// processStagingStateProofVerificationContext deserialize the given bytes as a temporary staging state proof verification data +func (c *catchpointCatchupAccessorImpl) processStagingStateProofVerificationContext(bytes []byte) (err error) { + var decodedData catchpointStateProofVerificationContext + err = protocol.Decode(bytes, &decodedData) + if err != nil { + return err + } + + if len(decodedData.Data) == 0 { + return + } + + // 6 months of stuck state proofs should lead to about 1.5 MB of data, so we avoid redundant timers + // and progress reports. + err = c.ledger.trackerDB().Batch(func(ctx context.Context, tx trackerdb.BatchScope) (err error) { + return tx.MakeSpVerificationCtxWriter().StoreSPContextsToCatchpointTbl(ctx, decodedData.Data) + }) + + return err +} + // processStagingContent deserialize the given bytes as a temporary staging balances content func (c *catchpointCatchupAccessorImpl) processStagingContent(ctx context.Context, bytes []byte, progress *CatchpointCatchupAccessorProgress) (err error) { if progress.SeenHeader { @@ -363,6 +388,7 @@ func (c *catchpointCatchupAccessorImpl) processStagingContent(ctx context.Contex switch fileHeader.Version { case CatchpointFileVersionV5: case CatchpointFileVersionV6: + case CatchpointFileVersionV7: default: return fmt.Errorf("CatchpointCatchupAccessorImpl::processStagingContent: unable to process catchpoint - version %d is not supported", fileHeader.Version) } @@ -377,7 +403,10 @@ func (c *catchpointCatchupAccessorImpl) processStagingContent(ctx context.Contex if err != nil { return err } - + err = cw.WriteCatchpointStateUint64(ctx, trackerdb.CatchpointStateCatchupVersion, fileHeader.Version) + if err != nil { + return fmt.Errorf("CatchpointCatchupAccessorImpl::processStagingContent: unable to write catchpoint catchup version '%s': %v", trackerdb.CatchpointStateCatchupVersion, err) + } aw, err := tx.MakeAccountsWriter() if err != nil { return err @@ -387,7 +416,7 @@ func (c *catchpointCatchupAccessorImpl) processStagingContent(ctx context.Contex if err != nil { return fmt.Errorf("CatchpointCatchupAccessorImpl::processStagingContent: unable to write catchpoint catchup state '%s': %v", trackerdb.CatchpointStateCatchupBlockRound, err) } - if fileHeader.Version == CatchpointFileVersionV6 { + if fileHeader.Version >= CatchpointFileVersionV6 { err = cw.WriteCatchpointStateUint64(ctx, trackerdb.CatchpointStateCatchupHashRound, uint64(fileHeader.BlocksRound)) if err != nil { return fmt.Errorf("CatchpointCatchupAccessorImpl::processStagingContent: unable to write catchpoint catchup state '%s': %v", trackerdb.CatchpointStateCatchupHashRound, err) @@ -406,6 +435,7 @@ func (c *catchpointCatchupAccessorImpl) processStagingContent(ctx context.Contex progress.Version = fileHeader.Version c.ledger.setSynchronousMode(ctx, c.ledger.accountsRebuildSynchronousMode) } + return err } @@ -442,6 +472,8 @@ func (c *catchpointCatchupAccessorImpl) processStagingBalances(ctx context.Conte expectingMoreEntries = make([]bool, len(balances.Balances)) case CatchpointFileVersionV6: + fallthrough + case CatchpointFileVersionV7: var chunk catchpointFileChunkV6 err = protocol.Decode(bytes, &chunk) if err != nil { @@ -872,15 +904,22 @@ func (c *catchpointCatchupAccessorImpl) GetCatchupBlockRound(ctx context.Context // 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 + 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 { @@ -916,6 +955,12 @@ func (c *catchpointCatchupAccessorImpl) VerifyCatchpoint(ctx context.Context, bl if err != nil { return fmt.Errorf("unable to get accounts totals: %v", err) } + + rawStateProofVerificationContext, err = tx.MakeSpVerificationCtxReaderWriter().GetAllSPContextsFromCatchpointTbl(ctx) + if err != nil { + return fmt.Errorf("unable to get state proof verification data: %v", err) + } + return }) ledgerVerifycatchpointMicros.AddMicrosecondsSince(start, nil) @@ -926,10 +971,20 @@ 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()) } - catchpointLabelMaker := ledgercore.MakeCatchpointLabel(blockRound, blk.Digest(), balancesHash, totals) + wrappedContext := catchpointStateProofVerificationContext{Data: rawStateProofVerificationContext} + spVerificationHash := crypto.HashObj(wrappedContext) + + var catchpointLabelMaker ledgercore.CatchpointLabelMaker + blockDigest := blk.Digest() + if version <= CatchpointFileVersionV6 { + catchpointLabelMaker = ledgercore.MakeCatchpointLabelMakerV6(blockRound, &blockDigest, &balancesHash, totals) + } else { + catchpointLabelMaker = ledgercore.MakeCatchpointLabelMakerCurrent(blockRound, &blockDigest, &balancesHash, totals, &spVerificationHash) + } + generatedLabel := ledgercore.MakeLabel(catchpointLabelMaker) - if catchpointLabel != catchpointLabelMaker.String() { - return fmt.Errorf("catchpoint hash mismatch; expected %s, calculated %s", catchpointLabel, catchpointLabelMaker.String()) + if catchpointLabel != generatedLabel { + return fmt.Errorf("catchpoint hash mismatch; expected %s, calculated %s", catchpointLabel, generatedLabel) } return nil } diff --git a/ledger/catchupaccessor_test.go b/ledger/catchupaccessor_test.go index 5cbf169874..a97377bd3e 100644 --- a/ledger/catchupaccessor_test.go +++ b/ledger/catchupaccessor_test.go @@ -101,7 +101,7 @@ func benchmarkRestoringFromCatchpointFileHelper(b *testing.B) { accountsCount := uint64(b.N) fileHeader := CatchpointFileHeader{ - Version: CatchpointFileVersionV6, + Version: CatchpointFileVersionV7, BalancesRound: basics.Round(0), BlocksRound: basics.Round(0), Totals: ledgercore.AccountTotals{}, @@ -112,7 +112,7 @@ func benchmarkRestoringFromCatchpointFileHelper(b *testing.B) { } encodedFileHeader := protocol.Encode(&fileHeader) var progress CatchpointCatchupAccessorProgress - err = catchpointAccessor.ProcessStagingBalances(context.Background(), "content.msgpack", encodedFileHeader, &progress) + err = catchpointAccessor.ProcessStagingBalances(context.Background(), CatchpointContentFileName, encodedFileHeader, &progress) require.NoError(b, err) // pre-create all encoded chunks. @@ -128,7 +128,8 @@ func benchmarkRestoringFromCatchpointFileHelper(b *testing.B) { last64KStart = time.Now() } - err = catchpointAccessor.ProcessStagingBalances(context.Background(), "balances.XX.msgpack", encodedAccounts, &progress) + balancesFileName := fmt.Sprintf("%s%s%s", catchpointBalancesFileNamePrefix, "XX", catchpointBalancesFileNameSuffix) + err = catchpointAccessor.ProcessStagingBalances(context.Background(), balancesFileName, encodedAccounts, &progress) require.NoError(b, err) last64KIndex-- } @@ -148,6 +149,83 @@ func BenchmarkRestoringFromCatchpointFile(b *testing.B) { } } +func initializeTestCatchupAccessor(t *testing.T, l *Ledger, accountsCount uint64) (CatchpointCatchupAccessor, CatchpointCatchupAccessorProgress) { + log := logging.TestingLog(t) + catchpointAccessor := MakeCatchpointCatchupAccessor(l, log) + + var progress CatchpointCatchupAccessorProgress + + ctx := context.Background() + + // We do this to create catchpoint staging tables. + err := catchpointAccessor.ResetStagingBalances(ctx, true) + require.NoError(t, err) + + // We do this to initialize the catchpointblocks table. Needed to be able to use CompleteCatchup. + err = catchpointAccessor.StoreFirstBlock(ctx, &bookkeeping.Block{}) + require.NoError(t, err) + + // We do this to initialize the accounttotals table. Needed to be able to use CompleteCatchup. + fileHeader := CatchpointFileHeader{ + Version: CatchpointFileVersionV7, + BalancesRound: basics.Round(0), + BlocksRound: basics.Round(0), + Totals: ledgercore.AccountTotals{}, + TotalAccounts: accountsCount, + TotalChunks: (accountsCount + BalancesPerCatchpointFileChunk - 1) / BalancesPerCatchpointFileChunk, + Catchpoint: "", + BlockHeaderDigest: crypto.Digest{}, + } + encodedFileHeader := protocol.Encode(&fileHeader) + err = catchpointAccessor.ProcessStagingBalances(ctx, CatchpointContentFileName, encodedFileHeader, &progress) + + return catchpointAccessor, progress +} + +func verifyStateProofVerificationCatchupAccessor(t *testing.T, targetData []ledgercore.StateProofVerificationContext) { + // setup boilerplate + log := logging.TestingLog(t) + dbBaseFileName := t.Name() + const inMem = true + genesisInitState, initkeys := ledgertesting.GenerateInitState(t, protocol.ConsensusCurrentVersion, 100) + cfg := config.GetDefaultLocal() + l, err := OpenLedger(log, dbBaseFileName, inMem, genesisInitState, cfg) + require.NoError(t, err, "could not open ledger") + defer func() { + l.Close() + }() + + catchpointAccessor, progress := initializeTestCatchupAccessor(t, l, uint64(len(initkeys))) + + require.NoError(t, err) + + wrappedData := catchpointStateProofVerificationContext{ + Data: targetData, + } + blob := protocol.Encode(&wrappedData) + + ctx := context.Background() + err = catchpointAccessor.ProcessStagingBalances(ctx, catchpointSPVerificationFileName, blob, &progress) + require.NoError(t, err) + + err = catchpointAccessor.CompleteCatchup(ctx) + require.NoError(t, err) + + var trackedStateProofVerificationContext []ledgercore.StateProofVerificationContext + err = l.trackerDBs.Snapshot(func(ctx context.Context, tx trackerdb.SnapshotScope) error { + dbData, err := tx.MakeSpVerificationCtxReader().GetAllSPContexts(ctx) + trackedStateProofVerificationContext = dbData + return err + }) + + require.NoError(t, err) + require.Equal(t, len(targetData), len(trackedStateProofVerificationContext)) + for index, data := range targetData { + require.Equal(t, data, trackedStateProofVerificationContext[index]) + } + require.NoError(t, err) +} + func TestCatchupAccessorFoo(t *testing.T) { partitiontest.PartitionTest(t) @@ -247,16 +325,17 @@ func TestBuildMerkleTrie(t *testing.T) { err = catchpointAccessor.ProcessStagingBalances(ctx, "ignoredContent", blob, &progress) require.NoError(t, err) // this shouldn't work yet - err = catchpointAccessor.ProcessStagingBalances(ctx, "balances.FAKE.msgpack", blob, &progress) + balancesFileName := fmt.Sprintf("%s%s%s", catchpointBalancesFileNamePrefix, "FAKE", catchpointBalancesFileNameSuffix) + err = catchpointAccessor.ProcessStagingBalances(ctx, balancesFileName, blob, &progress) require.Error(t, err) // this needs content - err = catchpointAccessor.ProcessStagingBalances(ctx, "content.msgpack", blob, &progress) + err = catchpointAccessor.ProcessStagingBalances(ctx, CatchpointContentFileName, blob, &progress) require.Error(t, err) // content.msgpack from this: accountsCount := uint64(len(initKeys)) fileHeader := CatchpointFileHeader{ - Version: CatchpointFileVersionV6, + Version: CatchpointFileVersionV7, BalancesRound: basics.Round(0), BlocksRound: basics.Round(0), Totals: ledgercore.AccountTotals{}, @@ -266,14 +345,15 @@ func TestBuildMerkleTrie(t *testing.T) { BlockHeaderDigest: crypto.Digest{}, } encodedFileHeader := protocol.Encode(&fileHeader) - err = catchpointAccessor.ProcessStagingBalances(ctx, "content.msgpack", encodedFileHeader, &progress) + err = catchpointAccessor.ProcessStagingBalances(ctx, CatchpointContentFileName, encodedFileHeader, &progress) require.NoError(t, err) // shouldn't work a second time - err = catchpointAccessor.ProcessStagingBalances(ctx, "content.msgpack", encodedFileHeader, &progress) + err = catchpointAccessor.ProcessStagingBalances(ctx, CatchpointContentFileName, encodedFileHeader, &progress) require.Error(t, err) // This should still fail, but slightly different coverage path - err = catchpointAccessor.ProcessStagingBalances(ctx, "balances.FAKE.msgpack", blob, &progress) + balancesFileName = fmt.Sprintf("%s%s%s", catchpointBalancesFileNamePrefix, "FAKE", catchpointBalancesFileNameSuffix) + err = catchpointAccessor.ProcessStagingBalances(ctx, balancesFileName, blob, &progress) require.Error(t, err) // create some catchpoint data @@ -294,6 +374,24 @@ func TestBuildMerkleTrie(t *testing.T) { require.Equal(t, basics.Round(0), blockRound) } +func TestCatchupAccessorStateProofVerificationContext(t *testing.T) { + partitiontest.PartitionTest(t) + + verificationContext := ledgercore.StateProofVerificationContext{ + LastAttestedRound: 120, + VotersCommitment: nil, + OnlineTotalWeight: basics.MicroAlgos{Raw: 100}, + } + + verifyStateProofVerificationCatchupAccessor(t, []ledgercore.StateProofVerificationContext{verificationContext}) +} + +func TestCatchupAccessorEmptyStateProofVerificationContext(t *testing.T) { + partitiontest.PartitionTest(t) + + verifyStateProofVerificationCatchupAccessor(t, []ledgercore.StateProofVerificationContext{}) +} + // blockdb.go code // TODO: blockStartCatchupStaging called from StoreFirstBlock() // TODO: blockCompleteCatchup called from FinishBlocks() @@ -393,7 +491,7 @@ func TestCatchupAccessorResourceCountMismatch(t *testing.T) { // content.msgpack from this: fileHeader := CatchpointFileHeader{ - Version: CatchpointFileVersionV6, + Version: CatchpointFileVersionV7, BalancesRound: basics.Round(0), BlocksRound: basics.Round(0), Totals: ledgercore.AccountTotals{}, @@ -403,7 +501,7 @@ func TestCatchupAccessorResourceCountMismatch(t *testing.T) { BlockHeaderDigest: crypto.Digest{}, } encodedFileHeader := protocol.Encode(&fileHeader) - err = catchpointAccessor.ProcessStagingBalances(ctx, "content.msgpack", encodedFileHeader, &progress) + err = catchpointAccessor.ProcessStagingBalances(ctx, CatchpointContentFileName, encodedFileHeader, &progress) require.NoError(t, err) var balances catchpointFileChunkV6 @@ -419,7 +517,8 @@ func TestCatchupAccessorResourceCountMismatch(t *testing.T) { encodedAccounts := protocol.Encode(&balances) // expect error since there is a resource count mismatch - err = catchpointAccessor.ProcessStagingBalances(ctx, "balances.XX.msgpack", encodedAccounts, &progress) + balancesFileName := fmt.Sprintf("%s%s%s", catchpointBalancesFileNamePrefix, "XX", catchpointBalancesFileNameSuffix) + err = catchpointAccessor.ProcessStagingBalances(ctx, balancesFileName, encodedAccounts, &progress) require.Error(t, err) } @@ -503,7 +602,7 @@ func TestCatchupAccessorProcessStagingBalances(t *testing.T) { TotalAccounts: numAccounts, TotalChunks: 2, SeenHeader: true, - Version: CatchpointFileVersionV6, + Version: CatchpointFileVersionV7, } // create some walking gentlemen diff --git a/ledger/eval/appcow_test.go b/ledger/eval/appcow_test.go index 87b7316299..2c46d02c09 100644 --- a/ledger/eval/appcow_test.go +++ b/ledger/eval/appcow_test.go @@ -107,6 +107,10 @@ func (ml *emptyLedger) GetStateProofNextRound() basics.Round { return basics.Round(0) } +func (ml *emptyLedger) GetStateProofVerificationContext(_ basics.Round) (*ledgercore.StateProofVerificationContext, error) { + return nil, fmt.Errorf("emptyLedger does not implement GetStateProofVerificationContext") +} + type modsData struct { addr basics.Address cidx basics.CreatableIndex diff --git a/ledger/eval/cow.go b/ledger/eval/cow.go index ec7c21aa2f..66824ce978 100644 --- a/ledger/eval/cow.go +++ b/ledger/eval/cow.go @@ -63,8 +63,8 @@ type roundCowParent interface { getStorageLimits(addr basics.Address, aidx basics.AppIndex, global bool) (basics.StateSchema, error) allocated(addr basics.Address, aidx basics.AppIndex, global bool) (bool, error) getKey(addr basics.Address, aidx basics.AppIndex, global bool, key string, accountIdx uint64) (basics.TealValue, bool, error) - kvGet(key string) ([]byte, bool, error) + GetStateProofVerificationContext(stateProofLastAttestedRound basics.Round) (*ledgercore.StateProofVerificationContext, error) } // When adding new fields make sure to clear them in the roundCowState.recycle() as well to avoid dirty state @@ -235,8 +235,8 @@ func (cb *roundCowState) Counter() uint64 { } func (cb *roundCowState) GetStateProofNextRound() basics.Round { - if cb.mods.StateProofNext != 0 { - return cb.mods.StateProofNext + if cb.mods.ModStateProofNextRound != 0 { + return cb.mods.ModStateProofNextRound } return cb.lookupParent.GetStateProofNextRound() } @@ -245,6 +245,10 @@ func (cb *roundCowState) BlockHdr(r basics.Round) (bookkeeping.BlockHeader, erro return cb.lookupParent.BlockHdr(r) } +func (cb *roundCowState) GetStateProofVerificationContext(stateProofLastAttestedRound basics.Round) (*ledgercore.StateProofVerificationContext, error) { + return cb.lookupParent.GetStateProofVerificationContext(stateProofLastAttestedRound) +} + func (cb *roundCowState) blockHdrCached(r basics.Round) (bookkeeping.BlockHeader, error) { return cb.lookupParent.blockHdrCached(r) } @@ -262,7 +266,7 @@ func (cb *roundCowState) addTx(txn transactions.Transaction, txid transactions.T } func (cb *roundCowState) SetStateProofNextRound(rnd basics.Round) { - cb.mods.StateProofNext = rnd + cb.mods.ModStateProofNextRound = rnd } func (cb *roundCowState) child(hint int) *roundCowState { @@ -270,7 +274,7 @@ func (cb *roundCowState) child(hint int) *roundCowState { ch.lookupParent = cb ch.commitParent = cb ch.proto = cb.proto - ch.mods.PopulateStateDelta(cb.mods.Hdr, cb.mods.PrevTimestamp, hint, cb.mods.StateProofNext) + ch.mods.PopulateStateDelta(cb.mods.Hdr, cb.mods.PrevTimestamp, hint, cb.mods.ModStateProofNextRound) if ch.sdeltas == nil { ch.sdeltas = make(map[basics.Address]map[storagePtr]*storageDelta) @@ -314,7 +318,7 @@ func (cb *roundCowState) commitToParent() { } } } - cb.commitParent.mods.StateProofNext = cb.mods.StateProofNext + cb.commitParent.mods.ModStateProofNextRound = cb.mods.ModStateProofNextRound for key, value := range cb.mods.KvMods { cb.commitParent.mods.AddKvMod(key, value) diff --git a/ledger/eval/cow_test.go b/ledger/eval/cow_test.go index fffc80208e..f5e4fe25af 100644 --- a/ledger/eval/cow_test.go +++ b/ledger/eval/cow_test.go @@ -17,6 +17,7 @@ package eval import ( + "errors" "reflect" "testing" @@ -25,7 +26,9 @@ import ( "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/bookkeeping" + "github.com/algorand/go-algorand/data/stateproofmsg" "github.com/algorand/go-algorand/data/transactions" + "github.com/algorand/go-algorand/ledger/apply" "github.com/algorand/go-algorand/ledger/ledgercore" ledgertesting "github.com/algorand/go-algorand/ledger/testing" "github.com/algorand/go-algorand/protocol" @@ -34,8 +37,6 @@ import ( type mockLedger struct { balanceMap map[basics.Address]basics.AccountData - blocks map[basics.Round]bookkeeping.BlockHeader - blockErr map[basics.Round]error } func (ml *mockLedger) lookup(addr basics.Address) (ledgercore.AccountData, error) { @@ -99,18 +100,17 @@ func (ml *mockLedger) GetStateProofNextRound() basics.Round { } func (ml *mockLedger) BlockHdr(rnd basics.Round) (bookkeeping.BlockHeader, error) { - err, hit := ml.blockErr[rnd] - if hit { - return bookkeeping.BlockHeader{}, err - } - hdr := ml.blocks[rnd] // default struct is fine if nothing found - return hdr, nil + return bookkeeping.BlockHeader{}, errors.New("requested blockheader not found") } func (ml *mockLedger) blockHdrCached(rnd basics.Round) (bookkeeping.BlockHeader, error) { return ml.blockHdrCached(rnd) } +func (ml *mockLedger) GetStateProofVerificationContext(rnd basics.Round) (*ledgercore.StateProofVerificationContext, error) { + return nil, errors.New("requested state proof verification data not found") +} + func checkCowByUpdate(t *testing.T, cow *roundCowState, delta ledgercore.AccountDeltas) { for i := 0; i < delta.Len(); i++ { addr, data := delta.GetByIdx(i) @@ -246,3 +246,37 @@ func TestCowChildReflect(t *testing.T) { require.Containsf(t, cowFieldNames, reflectedCowName, "new field:\"%v\" added to roundCowState, please update roundCowState.reset() to handle it before fixing the test", reflectedCowName) } } + +func TestCowStateProof(t *testing.T) { + partitiontest.PartitionTest(t) + a := require.New(t) + + version := config.Consensus[protocol.ConsensusCurrentVersion] + firstStateproof := basics.Round(version.StateProofInterval * 2) + accts0 := ledgertesting.RandomAccounts(20, true) + ml := mockLedger{balanceMap: accts0} + c0 := makeRoundCowState( + &ml, bookkeeping.BlockHeader{}, version, + 0, ledgercore.AccountTotals{}, 0) + + c0.SetStateProofNextRound(firstStateproof) + stateproofTxn := transactions.StateProofTxnFields{ + StateProofType: protocol.StateProofBasic, + Message: stateproofmsg.Message{LastAttestedRound: uint64(firstStateproof) + version.StateProofInterval}, + } + + // can not apply state proof for 3*version.StateProofInterval when we expect 2*version.StateProofInterval + err := apply.StateProof(stateproofTxn, firstStateproof+1, c0, false) + a.ErrorIs(err, apply.ErrExpectedDifferentStateProofRound) + + stateproofTxn.Message.LastAttestedRound = uint64(firstStateproof) + err = apply.StateProof(stateproofTxn, firstStateproof+1, c0, false) + a.NoError(err) + a.Equal(3*basics.Round(version.StateProofInterval), c0.GetStateProofNextRound()) + + // try to apply the next stateproof 3*version.StateProofInterval + stateproofTxn.Message.LastAttestedRound = 3 * version.StateProofInterval + err = apply.StateProof(stateproofTxn, firstStateproof+1, c0, false) + a.NoError(err) + a.Equal(4*basics.Round(version.StateProofInterval), c0.GetStateProofNextRound()) +} diff --git a/ledger/eval/eval.go b/ledger/eval/eval.go index 2f9b1771df..62533fe14f 100644 --- a/ledger/eval/eval.go +++ b/ledger/eval/eval.go @@ -47,6 +47,7 @@ type LedgerForCowBase interface { LookupApplication(basics.Round, basics.Address, basics.AppIndex) (ledgercore.AppResource, error) LookupKv(basics.Round, string) ([]byte, error) GetCreatorForRound(basics.Round, basics.CreatableIndex, basics.CreatableType) (basics.Address, bool, error) + GetStateProofVerificationContext(stateProofLastAttestedRound basics.Round) (*ledgercore.StateProofVerificationContext, error) } // ErrRoundZero is self-explanatory @@ -337,6 +338,10 @@ func (x *roundCowBase) BlockHdr(r basics.Round) (bookkeeping.BlockHeader, error) return x.l.BlockHdr(r) } +func (x *roundCowBase) GetStateProofVerificationContext(stateProofLastAttestedRound basics.Round) (*ledgercore.StateProofVerificationContext, error) { + return x.l.GetStateProofVerificationContext(stateProofLastAttestedRound) +} + func (x *roundCowBase) blockHdrCached(r basics.Round) (bookkeeping.BlockHeader, error) { return x.l.BlockHdrCached(r) } @@ -1188,14 +1193,9 @@ func (eval *BlockEvaluator) applyTransaction(tx transactions.Transaction, cow *r err = apply.ApplicationCall(tx.ApplicationCallTxnFields, tx.Header, cow, &ad, gi, evalParams, ctr) case protocol.StateProofTx: - // in case of a StateProofTx transaction, we want to "apply" it only in validate or generate mode. This will deviate the cow's StateProofNextRound depending on - // whether we're in validate/generate mode or not, however - given that this variable is only being used in these modes, it would be safe. - // The reason for making this into an exception is that during initialization time, the accounts update is "converting" the recent 320 blocks into deltas to - // be stored in memory. These deltas don't care about the state proofs, and so we can improve the node load time. Additionally, it save us from - // performing the validation during catchup, which is another performance boost. - if eval.validate || eval.generate { - err = apply.StateProof(tx.StateProofTxnFields, tx.Header.FirstValid, cow, eval.validate) - } + // Applying the StateProof transaction will advance the cow's StateProofNextRound field. + // Validation of the StateProof transaction before applying will only occur in validate mode. + err = apply.StateProof(tx.StateProofTxnFields, tx.Header.FirstValid, cow, eval.validate) default: err = fmt.Errorf("unknown transaction type %v", tx.Type) diff --git a/ledger/eval/eval_test.go b/ledger/eval/eval_test.go index 6dcc02d1c1..57860c1e92 100644 --- a/ledger/eval/eval_test.go +++ b/ledger/eval/eval_test.go @@ -31,17 +31,14 @@ import ( "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/crypto/merklesignature" - "github.com/algorand/go-algorand/crypto/stateproof" "github.com/algorand/go-algorand/data/basics" basics_testing "github.com/algorand/go-algorand/data/basics/testing" "github.com/algorand/go-algorand/data/bookkeeping" - "github.com/algorand/go-algorand/data/stateproofmsg" "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/data/transactions/logic" "github.com/algorand/go-algorand/data/transactions/logic/mocktracer" "github.com/algorand/go-algorand/data/transactions/verify" "github.com/algorand/go-algorand/data/txntest" - "github.com/algorand/go-algorand/ledger/apply" "github.com/algorand/go-algorand/ledger/ledgercore" ledgertesting "github.com/algorand/go-algorand/ledger/testing" "github.com/algorand/go-algorand/protocol" @@ -203,81 +200,6 @@ func TestEvalAppAllocStateWithTxnGroup(t *testing.T) { require.Equal(t, basics.TealValue{Type: basics.TealBytesType, Bytes: string(addr[:])}, state["creator"]) } -func TestCowStateProof(t *testing.T) { - partitiontest.PartitionTest(t) - - var spType protocol.StateProofType - var stateProof stateproof.StateProof - var atRound basics.Round - var validate bool - msg := stateproofmsg.Message{} - - accts0 := ledgertesting.RandomAccounts(20, true) - blocks := make(map[basics.Round]bookkeeping.BlockHeader) - blockErr := make(map[basics.Round]error) - ml := mockLedger{balanceMap: accts0, blocks: blocks, blockErr: blockErr} - c0 := makeRoundCowState( - &ml, bookkeeping.BlockHeader{}, config.Consensus[protocol.ConsensusCurrentVersion], - 0, ledgercore.AccountTotals{}, 0) - - spType = protocol.StateProofType(1234) // bad stateproof type - stateProofTx := transactions.StateProofTxnFields{ - StateProofType: spType, - StateProof: stateProof, - Message: msg, - } - err := apply.StateProof(stateProofTx, atRound, c0, validate) - require.ErrorIs(t, err, apply.ErrStateProofTypeNotSupported) - - // no spRnd block - stateProofTx.StateProofType = protocol.StateProofBasic - noBlockErr := errors.New("no block") - blockErr[3] = noBlockErr - stateProofTx.Message.LastAttestedRound = 3 - err = apply.StateProof(stateProofTx, atRound, c0, validate) - require.Contains(t, err.Error(), "no block") - - // stateproof txn doesn't confirm the next state proof round. expected is in the past - validate = true - stateProofTx.Message.LastAttestedRound = uint64(16) - c0.SetStateProofNextRound(8) - err = apply.StateProof(stateProofTx, atRound, c0, validate) - require.ErrorIs(t, err, apply.ErrExpectedDifferentStateProofRound) - - // stateproof txn doesn't confirm the next state proof round. expected is in the future - validate = true - stateProofTx.Message.LastAttestedRound = uint64(16) - c0.SetStateProofNextRound(32) - err = apply.StateProof(stateProofTx, atRound, c0, validate) - require.ErrorIs(t, err, apply.ErrExpectedDifferentStateProofRound) - - // no votersRnd block - // this is slightly a mess of things that don't quite line up with likely usage - validate = true - var spHdr bookkeeping.BlockHeader - spHdr.CurrentProtocol = "TestCowStateProof" - spHdr.Round = 1 - proto := config.Consensus[spHdr.CurrentProtocol] - proto.StateProofInterval = 2 - config.Consensus[spHdr.CurrentProtocol] = proto - blocks[spHdr.Round] = spHdr - - spHdr.Round = 15 - blocks[spHdr.Round] = spHdr - stateProofTx.Message.LastAttestedRound = uint64(spHdr.Round) - c0.SetStateProofNextRound(15) - blockErr[13] = noBlockErr - err = apply.StateProof(stateProofTx, atRound, c0, validate) - require.Contains(t, err.Error(), "no block") - - // fall through to no err - validate = false - err = apply.StateProof(stateProofTx, atRound, c0, validate) - require.NoError(t, err) - - // 100% coverage -} - // a couple trivial tests that don't need setup // see TestBlockEvaluator for more func TestTestTransactionGroup(t *testing.T) { @@ -820,6 +742,10 @@ func (ledger *evalTestLedger) Latest() basics.Round { return basics.Round(len(ledger.blocks)).SubSaturate(1) } +func (ledger *evalTestLedger) GetStateProofVerificationContext(_ basics.Round) (*ledgercore.StateProofVerificationContext, error) { + return nil, errors.New("evalTestLedger does not implement GetStateProofVerificationContext") +} + // AddValidatedBlock adds a new block to the ledger, after the block has // been validated by calling Ledger.Validate(). This saves the cost of // having to re-compute the effect of the block on the ledger state, if @@ -995,6 +921,10 @@ func (l *testCowBaseLedger) LookupKv(rnd basics.Round, key string) ([]byte, erro return nil, errors.New("not implemented") } +func (l *testCowBaseLedger) GetStateProofVerificationContext(_ basics.Round) (*ledgercore.StateProofVerificationContext, error) { + return nil, errors.New("testCowBaseLedger does not implement GetStateProofVerificationContext") +} + func (l *testCowBaseLedger) GetCreatorForRound(_ basics.Round, cindex basics.CreatableIndex, ctype basics.CreatableType) (basics.Address, bool, error) { res := l.creators[0] l.creators = l.creators[1:] diff --git a/ledger/eval/prefetcher/prefetcher_alignment_test.go b/ledger/eval/prefetcher/prefetcher_alignment_test.go index d7949f9b9d..efb9e683bf 100644 --- a/ledger/eval/prefetcher/prefetcher_alignment_test.go +++ b/ledger/eval/prefetcher/prefetcher_alignment_test.go @@ -26,8 +26,10 @@ import ( "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/crypto/merklesignature" + "github.com/algorand/go-algorand/crypto/stateproof" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/bookkeeping" + "github.com/algorand/go-algorand/data/stateproofmsg" "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/ledger/eval" "github.com/algorand/go-algorand/ledger/eval/prefetcher" @@ -102,6 +104,11 @@ func (l *prefetcherAlignmentTestLedger) BlockHdrCached(round basics.Round) (book func (l *prefetcherAlignmentTestLedger) CheckDup(config.ConsensusParams, basics.Round, basics.Round, basics.Round, transactions.Txid, ledgercore.Txlease) error { return nil } + +func (l *prefetcherAlignmentTestLedger) GetStateProofVerificationContext(_ basics.Round) (*ledgercore.StateProofVerificationContext, error) { + return nil, fmt.Errorf("prefetcherAlignmentTestLedger does not implement GetStateProofVerificationContext") +} + func (l *prefetcherAlignmentTestLedger) LookupWithoutRewards(_ basics.Round, addr basics.Address) (ledgercore.AccountData, basics.Round, error) { l.mu.Lock() if l.requestedBalances == nil { @@ -1367,7 +1374,17 @@ func TestEvaluatorPrefetcherAlignmentStateProof(t *testing.T) { Sender: addr, GenesisHash: genesisHash(), }, - StateProofTxnFields: transactions.StateProofTxnFields{}, + StateProofTxnFields: transactions.StateProofTxnFields{ + StateProofType: 0, + StateProof: stateproof.StateProof{}, + Message: stateproofmsg.Message{ + BlockHeadersCommitment: nil, + VotersCommitment: nil, + LnProvenWeight: 0, + FirstAttestedRound: 257, + LastAttestedRound: 512, + }, + }, } requested, prefetched := run(t, l, txn) diff --git a/ledger/evalindexer.go b/ledger/evalindexer.go index 68527729c0..5ac28ce814 100644 --- a/ledger/evalindexer.go +++ b/ledger/evalindexer.go @@ -215,6 +215,11 @@ func (l indexerLedgerConnector) VotersForStateProof(_ basics.Round) (*ledgercore return nil, errors.New("VotersForStateProof() not implemented") } +// GetStateProofVerificationContext is part of LedgerForEvaluator interface. +func (l indexerLedgerConnector) GetStateProofVerificationContext(_ basics.Round) (*ledgercore.StateProofVerificationContext, error) { + return nil, errors.New("GetStateProofVerificationContext() not implemented") +} + func makeIndexerLedgerConnector(il indexerLedgerForEval, genesisHash crypto.Digest, genesisProto config.ConsensusParams, latestRound basics.Round, roundResources EvalForIndexerResources) indexerLedgerConnector { return indexerLedgerConnector{ il: il, diff --git a/ledger/ledger.go b/ledger/ledger.go index f3dba794d5..8ad2cb9ce9 100644 --- a/ledger/ledger.go +++ b/ledger/ledger.go @@ -78,13 +78,14 @@ type Ledger struct { genesisProtoVersion protocol.ConsensusVersion // State-machine trackers - accts accountUpdates - acctsOnline onlineAccounts - catchpoint catchpointTracker - txTail txTail - bulletin bulletin - notifier blockNotifier - metrics metricsTracker + accts accountUpdates + acctsOnline onlineAccounts + catchpoint catchpointTracker + txTail txTail + bulletin bulletin + notifier blockNotifier + metrics metricsTracker + spVerification spVerificationTracker trackers trackerRegistry trackerMu deadlock.RWMutex @@ -205,13 +206,14 @@ func (l *Ledger) reloadLedger() error { // set account updates tracker as a driver to calculate tracker db round and committing offsets trackers := []ledgerTracker{ - &l.accts, // update the balances - &l.catchpoint, // catchpoints tracker : update catchpoint labels, create catchpoint files - &l.acctsOnline, // update online account balances history - &l.txTail, // update the transaction tail, tracking the recent 1000 txn - &l.bulletin, // provide closed channel signaling support for completed rounds - &l.notifier, // send OnNewBlocks to subscribers - &l.metrics, // provides metrics reporting support + &l.accts, // update the balances + &l.catchpoint, // catchpoints tracker : update catchpoint labels, create catchpoint files + &l.acctsOnline, // update online account balances history + &l.txTail, // update the transaction tail, tracking the recent 1000 txn + &l.bulletin, // provide closed channel signaling support for completed rounds + &l.notifier, // send OnNewBlocks to subscribers + &l.metrics, // provides metrics reporting support + &l.spVerification, // provides state proof verification support } l.accts.initialize(l.cfg) @@ -401,6 +403,21 @@ func (l *Ledger) RegisterBlockListeners(listeners []ledgercore.BlockListener) { l.notifier.register(listeners) } +// RegisterVotersCommitListener registers a listener that will be called when a +// commit is about to cover a round. +func (l *Ledger) RegisterVotersCommitListener(listener ledgercore.VotersCommitListener) { + l.trackerMu.RLock() + defer l.trackerMu.RUnlock() + l.acctsOnline.voters.registerPrepareCommitListener(listener) +} + +// UnregisterVotersCommitListener unregisters the commit listener. +func (l *Ledger) UnregisterVotersCommitListener() { + l.trackerMu.RLock() + defer l.trackerMu.RUnlock() + l.acctsOnline.voters.unregisterPrepareCommitListener() +} + // notifyCommit informs the trackers that all blocks up to r have been // written to disk. Returns the minimum block number that must be kept // in the database. @@ -455,7 +472,15 @@ func (l *Ledger) GetStateDeltaForRound(rnd basics.Round) (ledgercore.StateDelta, func (l *Ledger) VotersForStateProof(rnd basics.Round) (*ledgercore.VotersForRound, error) { l.trackerMu.RLock() defer l.trackerMu.RUnlock() - return l.acctsOnline.voters.getVoters(rnd) + return l.acctsOnline.voters.VotersForStateProof(rnd) +} + +// GetStateProofVerificationContext returns the data required to verify the state proof whose last attested round is +// stateProofLastAttestedRound. +func (l *Ledger) GetStateProofVerificationContext(stateProofLastAttestedRound basics.Round) (*ledgercore.StateProofVerificationContext, error) { + l.trackerMu.RLock() + defer l.trackerMu.RUnlock() + return l.spVerification.LookupVerificationContext(stateProofLastAttestedRound) } // ListAssets takes a maximum asset index and maximum result length, and diff --git a/ledger/ledger_test.go b/ledger/ledger_test.go index d0324e249a..9dab45207f 100644 --- a/ledger/ledger_test.go +++ b/ledger/ledger_test.go @@ -21,8 +21,10 @@ import ( "context" "errors" "fmt" + "github.com/algorand/go-algorand/ledger/eval" "math/rand" "os" + "path/filepath" "runtime" "sort" "testing" @@ -1482,11 +1484,11 @@ func benchLedgerCache(b *testing.B, startRound basics.Round) { } func triggerTrackerFlush(t *testing.T, l *Ledger, genesisInitState ledgercore.InitState) { - l.trackers.mu.RLock() + l.trackers.mu.Lock() initialDbRound := l.trackers.dbRound currentDbRound := initialDbRound l.trackers.lastFlushTime = time.Time{} - l.trackers.mu.RUnlock() + l.trackers.mu.Unlock() addEmptyValidatedBlock(t, l, genesisInitState.Accounts) @@ -1498,7 +1500,7 @@ func triggerTrackerFlush(t *testing.T, l *Ledger, genesisInitState ledgercore.In // The solution is to wait for the advancement of l.trackers.dbRound, which is a side effect of postCommit's success. for currentDbRound == initialDbRound { time.Sleep(50 * time.Microsecond) - require.True(t, time.Now().Sub(started) < timeout) + require.True(t, time.Since(started) < timeout) l.trackers.mu.RLock() currentDbRound = l.trackers.dbRound l.trackers.mu.RUnlock() @@ -1545,36 +1547,6 @@ func TestLedgerReload(t *testing.T) { ledgertesting.WithAndWithoutLRUCache(t, cfg, testLedgerReload) } -func TestWaitLedgerReload(t *testing.T) { - partitiontest.PartitionTest(t) - a := require.New(t) - - dbName := fmt.Sprintf("%s.%d", t.Name(), crypto.RandUint64()) - genesisInitState, _ := ledgertesting.GenerateInitState(t, protocol.ConsensusCurrentVersion, 100) - const inMem = true - cfg := config.GetDefaultLocal() - cfg.MaxAcctLookback = 0 - log := logging.TestingLog(t) - log.SetLevel(logging.Info) - l, err := OpenLedger(log, dbName, inMem, genesisInitState, cfg) - require.NoError(t, err) - defer l.Close() - - waitRound := l.Latest() + 1 - waitChannel := l.Wait(waitRound) - - err = l.reloadLedger() - a.NoError(err) - triggerTrackerFlush(t, l, genesisInitState) - - select { - case <-waitChannel: - return - default: - a.Failf("", "Wait channel did not receive an expected signal for round %d", waitRound) - } -} - // TestGetLastCatchpointLabel tests ledger.GetLastCatchpointLabel is returning the correct value. func TestGetLastCatchpointLabel(t *testing.T) { partitiontest.PartitionTest(t) @@ -1715,18 +1687,16 @@ func TestListAssetsAndApplications(t *testing.T) { require.Equal(t, appCount, len(results)) } -// TestLedgerKeepsOldBlocksForStateProof test that if stateproof chain is delayed for X intervals, the ledger will not -// remove old blocks from the database. When verifying old stateproof transaction, nodes must have the header of the corresponding -// voters round, if this won't be available the verification would fail. -// the voter tracker should prevent the remove needed blocks from the database. -func TestLedgerKeepsOldBlocksForStateProof(t *testing.T) { +// TestLedgerVerifiesOldStateProofs test that if stateproof chain is delayed for X intervals (pass StateProofMaxRecoveryIntervals), +// The ledger will still be able to verify the state proof - i.e the ledger has the necessary data to verify it. +func TestLedgerVerifiesOldStateProofs(t *testing.T) { partitiontest.PartitionTest(t) - // since the first state proof is expected to happen on stateproofInterval*2 we would start give-up on state proofs we would - // give up on old state proofs only after stateproofInterval*3 - maxBlocks := int((config.Consensus[protocol.ConsensusCurrentVersion].StateProofMaxRecoveryIntervals + 2) * config.Consensus[protocol.ConsensusCurrentVersion].StateProofInterval) + // since the first state proof is expected to happen on stateproofInterval*2 we would start + // give-up on state proofs only after stateproofInterval*3 + maxBlocks := int((config.Consensus[protocol.ConsensusFuture].StateProofMaxRecoveryIntervals + 2) * config.Consensus[protocol.ConsensusFuture].StateProofInterval) dbName := fmt.Sprintf("%s.%d", t.Name(), crypto.RandUint64()) - genesisInitState, initKeys := ledgertesting.GenerateInitState(t, protocol.ConsensusCurrentVersion, 10000000000) + genesisInitState, initKeys := ledgertesting.GenerateInitState(t, protocol.ConsensusFuture, 10000000000) // place real values on the participation period, so we would create a commitment with some stake. accountsWithValid := make(map[basics.Address]basics.AccountData) @@ -1743,14 +1713,18 @@ func TestLedgerKeepsOldBlocksForStateProof(t *testing.T) { } genesisInitState.Accounts = accountsWithValid - const inMem = true cfg := config.GetDefaultLocal() cfg.Archival = false log := logging.TestingLog(t) log.SetLevel(logging.Info) + const inMem = false l, err := OpenLedger(log, dbName, inMem, genesisInitState, cfg) require.NoError(t, err) - defer l.Close() + defer func() { + l.Close() + os.Remove(dbName + ".block.sqlite") + os.Remove(dbName + ".tracker.sqlite") + }() lastBlock, err := l.Block(l.Latest()) require.NoError(t, err) @@ -1760,7 +1734,7 @@ func TestLedgerKeepsOldBlocksForStateProof(t *testing.T) { // regular addresses: all init accounts minus pools addresses := make([]basics.Address, len(genesisInitState.Accounts)-2, len(genesisInitState.Accounts)+maxBlocks) - i := 0 + i := uint64(0) for addr := range genesisInitState.Accounts { if addr != testPoolAddr && addr != testSinkAddr { addresses[i] = addr @@ -1770,32 +1744,42 @@ func TestLedgerKeepsOldBlocksForStateProof(t *testing.T) { keys[addr] = initKeys[addr] } - for i := 0; i < maxBlocks; i++ { + for i = 0; i < uint64(maxBlocks)+proto.StateProofInterval; i++ { addDummyBlock(t, addresses, proto, l, initKeys, genesisInitState) } backlogPool := execpool.MakeBacklog(nil, 0, execpool.LowPriority, nil) defer backlogPool.Shutdown() - // On this round there is no give up on any state proof - so we would be able to verify an old state proof txn. - - // We now create block with stateproof transaction. since we don't want to complicate the test and create - // a cryptographically correct stateproof we would make sure that only the crypto part of the verification fails. + triggerTrackerFlush(t, l, genesisInitState) + l.WaitForCommit(l.Latest()) blk := createBlkWithStateproof(t, maxBlocks, proto, genesisInitState, l, accounts) _, err = l.Validate(context.Background(), blk, backlogPool) require.ErrorContains(t, err, "state proof crypto error") - for i := uint64(0); i < proto.StateProofInterval; i++ { + for i = 0; i < proto.StateProofInterval; i++ { addDummyBlock(t, addresses, proto, l, initKeys, genesisInitState) } + triggerTrackerFlush(t, l, genesisInitState) l.WaitForCommit(l.Latest()) - // at the point the ledger would remove the voters round for the database. - // that will cause the stateproof transaction verification to fail because there are - // missing blocks - blk = createBlkWithStateproof(t, maxBlocks, proto, genesisInitState, l, accounts) - _, err = l.Validate(context.Background(), blk, backlogPool) + + // we make sure that the voters header does not exist and that the voters tracker + // lost tracking of the top voters. + _, err = l.BlockHdr(basics.Round(proto.StateProofInterval)) + require.Error(t, err) expectedErr := &ledgercore.ErrNoEntry{} require.True(t, errors.As(err, expectedErr), fmt.Sprintf("got error %s", err)) + + l.acctsOnline.voters.votersMu.Lock() + for k := range l.acctsOnline.voters.votersForRoundCache { + require.NotEqual(t, k, basics.Round(proto.StateProofInterval-proto.StateProofVotersLookback), "found voters for round 200, it should have been removed") + } + l.acctsOnline.voters.votersMu.Unlock() + + // However, we are still able to very a state proof sicne we use the tracker + blk = createBlkWithStateproof(t, maxBlocks, proto, genesisInitState, l, accounts) + _, err = l.Validate(context.Background(), blk, backlogPool) + require.ErrorContains(t, err, "state proof crypto error") } func createBlkWithStateproof(t *testing.T, maxBlocks int, proto config.ConsensusParams, genesisInitState ledgercore.InitState, l *Ledger, accounts map[basics.Address]basics.AccountData) bookkeeping.Block { @@ -1828,8 +1812,9 @@ func createBlkWithStateproof(t *testing.T, maxBlocks int, proto config.Consensus } func addDummyBlock(t *testing.T, addresses []basics.Address, proto config.ConsensusParams, l *Ledger, initKeys map[basics.Address]*crypto.SignatureSecrets, genesisInitState ledgercore.InitState) { - stxns := make([]transactions.SignedTxn, 2) - for j := 0; j < 2; j++ { + numOfTransactions := 2 + stxns := make([]transactions.SignedTxn, numOfTransactions) + for j := 0; j < numOfTransactions; j++ { txHeader := transactions.Header{ Sender: addresses[0], Fee: basics.MicroAlgos{Raw: proto.MinTxnFee * 2}, @@ -2868,7 +2853,17 @@ func verifyVotersContent(t *testing.T, expected map[basics.Round]*ledgercore.Vot } } +func triggerDeleteVoters(t *testing.T, l *Ledger, genesisInitState ledgercore.InitState) { + // We make the ledger flush tracker data to allow votersTracker to advance lowestRound + triggerTrackerFlush(t, l, genesisInitState) + + // We add another block to make the block queue query the voter's tracker lowest round again, which allows it to forget + // rounds based on the new lowest round. + triggerTrackerFlush(t, l, genesisInitState) +} + func testVotersReloadFromDisk(t *testing.T, cfg config.Local) { + proto := config.Consensus[protocol.ConsensusCurrentVersion] dbName := fmt.Sprintf("%s.%d", t.Name(), crypto.RandUint64()) genesisInitState := getInitState() @@ -2928,7 +2923,7 @@ func testVotersReloadFromDiskAfterOneStateProofCommitted(t *testing.T, cfg confi proto := config.Consensus[protocol.ConsensusCurrentVersion] dbName := fmt.Sprintf("%s.%d", t.Name(), crypto.RandUint64()) - genesisInitState := getInitState() + genesisInitState, _ := ledgertesting.GenerateInitState(t, protocol.ConsensusCurrentVersion, 100) genesisInitState.Block.CurrentProtocol = protocol.ConsensusCurrentVersion const inMem = true @@ -2950,7 +2945,6 @@ func testVotersReloadFromDiskAfterOneStateProofCommitted(t *testing.T, cfg confi for i := uint64(0); i < (proto.StateProofInterval*3 - proto.StateProofVotersLookback); i++ { blk.BlockHeader.Round++ - blk.BlockHeader.TimeStamp += 10 err = l.AddBlock(blk, agreement.Certificate{}) require.NoError(t, err) } @@ -2963,12 +2957,11 @@ func testVotersReloadFromDiskAfterOneStateProofCommitted(t *testing.T, cfg confi for i := uint64(0); i < proto.StateProofInterval; i++ { blk.BlockHeader.Round++ - blk.BlockHeader.TimeStamp += 10 err = l.AddBlock(blk, agreement.Certificate{}) require.NoError(t, err) } - l.WaitForCommit(blk.BlockHeader.Round) + triggerDeleteVoters(t, l, genesisInitState) vtSnapshot := l.acctsOnline.voters.votersForRoundCache // verifying that the tree for round 512 is still in the cache, but the tree for round 256 is evicted. @@ -2996,7 +2989,7 @@ func testVotersReloadFromDiskPassRecoveryPeriod(t *testing.T, cfg config.Local) proto := config.Consensus[protocol.ConsensusCurrentVersion] dbName := fmt.Sprintf("%s.%d", t.Name(), crypto.RandUint64()) - genesisInitState := getInitState() + genesisInitState, _ := ledgertesting.GenerateInitState(t, protocol.ConsensusCurrentVersion, 100) genesisInitState.Block.CurrentProtocol = protocol.ConsensusCurrentVersion const inMem = true @@ -3016,10 +3009,7 @@ func testVotersReloadFromDiskPassRecoveryPeriod(t *testing.T, cfg config.Local) // we push proto.StateProofInterval * (proto.StateProofMaxRecoveryIntervals + 2) block into the ledger // the reason for + 2 is the first state proof is on 2*stateproofinterval. for i := uint64(0); i < (proto.StateProofInterval * (proto.StateProofMaxRecoveryIntervals + 2)); i++ { - blk.BlockHeader.Round++ - blk.BlockHeader.TimeStamp += 10 - err = l.AddBlock(blk, agreement.Certificate{}) - require.NoError(t, err) + addEmptyValidatedBlock(t, l, genesisInitState.Accounts) } // the voters tracker should contain all the voters for each stateproof round. nothing should be removed @@ -3033,14 +3023,15 @@ func testVotersReloadFromDiskPassRecoveryPeriod(t *testing.T, cfg config.Local) verifyVotersContent(t, vtSnapshot, l.acctsOnline.voters.votersForRoundCache) for i := uint64(0); i < proto.StateProofInterval; i++ { - blk.BlockHeader.Round++ - blk.BlockHeader.TimeStamp += 10 - err = l.AddBlock(blk, agreement.Certificate{}) - require.NoError(t, err) + addEmptyValidatedBlock(t, l, genesisInitState.Accounts) } - // the voters tracker should give up on voters for round 512 - l.WaitForCommit(blk.BlockHeader.Round) + triggerDeleteVoters(t, l, genesisInitState) + + // round 512 should now be forgotten. + _, found = l.acctsOnline.voters.votersForRoundCache[basics.Round(proto.StateProofInterval-proto.StateProofVotersLookback)] + require.False(t, found) + vtSnapshot = l.acctsOnline.voters.votersForRoundCache err = l.reloadLedger() require.NoError(t, err) @@ -3054,11 +3045,343 @@ func testVotersReloadFromDiskPassRecoveryPeriod(t *testing.T, cfg config.Local) func TestVotersReloadFromDiskPassRecoveryPeriod(t *testing.T) { partitiontest.PartitionTest(t) + cfg := config.GetDefaultLocal() + cfg.Archival = false + cfg.MaxAcctLookback = 0 + + ledgertesting.WithAndWithoutLRUCache(t, cfg, testVotersReloadFromDiskPassRecoveryPeriod) +} + +type mockCommitListener struct{} + +func (l *mockCommitListener) OnPrepareVoterCommit(oldBase basics.Round, newBase basics.Round, _ ledgercore.LedgerForSPBuilder) { +} + +func TestVotersCallbackPersistsAfterLedgerReload(t *testing.T) { + partitiontest.PartitionTest(t) + + dbName := fmt.Sprintf("%s.%d", t.Name(), crypto.RandUint64()) + genesisInitState, _ := ledgertesting.GenerateInitState(t, protocol.ConsensusCurrentVersion, 100) + genesisInitState.Block.CurrentProtocol = protocol.ConsensusCurrentVersion + const inMem = true + cfg := config.GetDefaultLocal() + log := logging.TestingLog(t) + log.SetLevel(logging.Info) + l, err := OpenLedger(log, dbName, inMem, genesisInitState, cfg) + require.NoError(t, err) + defer l.Close() + + commitListener := mockCommitListener{} + l.RegisterVotersCommitListener(&commitListener) + listenerBeforeReload := l.acctsOnline.voters.commitListener + + require.NotNil(t, listenerBeforeReload) + err = l.reloadLedger() + require.NoError(t, err) + + listenerAfterReload := l.acctsOnline.voters.commitListener + require.Equal(t, listenerBeforeReload, listenerAfterReload) +} + +type errorCommitListener struct{} + +func (l *errorCommitListener) OnPrepareVoterCommit(oldBase basics.Round, newBase basics.Round, _ ledgercore.LedgerForSPBuilder) { +} + +func TestLedgerContinuesOnVotersCallbackFailure(t *testing.T) { + partitiontest.PartitionTest(t) + + dbName := fmt.Sprintf("%s.%d", t.Name(), crypto.RandUint64()) + genesisInitState, _ := ledgertesting.GenerateInitState(t, protocol.ConsensusCurrentVersion, 100) + genesisInitState.Block.CurrentProtocol = protocol.ConsensusCurrentVersion + const inMem = true + cfg := config.GetDefaultLocal() + cfg.MaxAcctLookback = 0 + log := logging.TestingLog(t) + log.SetLevel(logging.Info) + l, err := OpenLedger(log, dbName, inMem, genesisInitState, cfg) + require.NoError(t, err) + defer l.Close() + + commitListener := errorCommitListener{} + l.RegisterVotersCommitListener(&commitListener) + + previousCachedDbRound := l.trackers.dbRound + triggerTrackerFlush(t, l, genesisInitState) + require.Equal(t, previousCachedDbRound+1, l.trackers.dbRound) +} + +func TestLedgerSPVerificationTracker(t *testing.T) { + partitiontest.PartitionTest(t) proto := config.Consensus[protocol.ConsensusCurrentVersion] + dbName := fmt.Sprintf("%s.%d", t.Name(), crypto.RandUint64()) + genesisInitState := getInitState() + genesisInitState.Block.CurrentProtocol = protocol.ConsensusCurrentVersion + const inMem = true cfg := config.GetDefaultLocal() cfg.Archival = false - cfg.MaxAcctLookback = proto.StateProofInterval - proto.StateProofVotersLookback - 10 + log := logging.TestingLog(t) + log.SetLevel(logging.Info) + l, err := OpenLedger(log, dbName, inMem, genesisInitState, cfg) + require.NoError(t, err) + defer l.Close() - ledgertesting.WithAndWithoutLRUCache(t, cfg, testVotersReloadFromDiskPassRecoveryPeriod) + numOfStateProofs := uint64(3) + firstStateProofContextConfirmedRound := proto.StateProofInterval + firstStateProofContextTargetRound := firstStateProofContextConfirmedRound + proto.StateProofInterval + + lastStateProofContextConfirmedRound := firstStateProofContextConfirmedRound + proto.StateProofInterval*(numOfStateProofs-1) + lastStateProofContextTargetRound := lastStateProofContextConfirmedRound + proto.StateProofInterval + + blk := genesisInitState.Block + var sp bookkeeping.StateProofTrackingData + sp.StateProofNextRound = basics.Round(firstStateProofContextTargetRound) + blk.BlockHeader.StateProofTracking = map[protocol.StateProofType]bookkeeping.StateProofTrackingData{ + protocol.StateProofBasic: sp, + } + + for i := uint64(0); i < firstStateProofContextConfirmedRound-1; i++ { + blk.BlockHeader.Round++ + blk.BlockHeader.TimeStamp += 10 + err = l.AddBlock(blk, agreement.Certificate{}) + require.NoError(t, err) + } + + verifyStateProofVerificationTracking(t, &l.spVerification, basics.Round(firstStateProofContextTargetRound), + 1, proto.StateProofInterval, false, any) + + blk.BlockHeader.Round++ + blk.BlockHeader.TimeStamp += 10 + err = l.AddBlock(blk, agreement.Certificate{}) + require.NoError(t, err) + + verifyStateProofVerificationTracking(t, &l.spVerification, basics.Round(firstStateProofContextTargetRound), + 1, proto.StateProofInterval, true, trackerMemory) + + for i := firstStateProofContextConfirmedRound; i < lastStateProofContextConfirmedRound; i++ { + blk.BlockHeader.Round++ + blk.BlockHeader.TimeStamp += 10 + err = l.AddBlock(blk, agreement.Certificate{}) + require.NoError(t, err) + } + + verifyStateProofVerificationTracking(t, &l.spVerification, basics.Round(firstStateProofContextTargetRound), + numOfStateProofs-1, proto.StateProofInterval, true, trackerDB) + // Last one should be in memory as a result of cfg.MaxAcctLookback not being equal to 0. + verifyStateProofVerificationTracking(t, &l.spVerification, basics.Round(lastStateProofContextTargetRound), + 1, proto.StateProofInterval, true, trackerMemory) + + l.WaitForCommit(blk.BlockHeader.Round) + + verifyStateProofVerificationTracking(t, &l.spVerification, basics.Round(firstStateProofContextTargetRound), + numOfStateProofs, proto.StateProofInterval, true, any) + + var stateProofReceived bookkeeping.StateProofTrackingData + stateProofReceived.StateProofNextRound = basics.Round(firstStateProofContextTargetRound + proto.StateProofInterval) + blk.BlockHeader.StateProofTracking = map[protocol.StateProofType]bookkeeping.StateProofTrackingData{ + protocol.StateProofBasic: stateProofReceived, + } + blk.BlockHeader.Round++ + blk.BlockHeader.TimeStamp += 10 + + // This implementation is an easy way to feed the delta, which the state proof verification tracker relies on, + // to the ledger. + delta, err := eval.Eval(context.Background(), l, blk, false, l.verifiedTxnCache, nil) + require.NoError(t, err) + delta.ModStateProofNextRound = stateProofReceived.StateProofNextRound + vb := ledgercore.MakeValidatedBlock(blk, delta) + err = l.AddValidatedBlock(vb, agreement.Certificate{}) + require.NoError(t, err) + + for i := uint64(0); i < proto.MaxBalLookback; i++ { + blk.BlockHeader.Round++ + blk.BlockHeader.TimeStamp += 10 + err = l.AddBlock(blk, agreement.Certificate{}) + require.NoError(t, err) + } + + l.WaitForCommit(blk.BlockHeader.Round) + + verifyStateProofVerificationTracking(t, &l.spVerification, basics.Round(firstStateProofContextTargetRound), + 1, proto.StateProofInterval, false, any) + verifyStateProofVerificationTracking(t, &l.spVerification, basics.Round(firstStateProofContextTargetRound+proto.StateProofInterval), + numOfStateProofs-1, proto.StateProofInterval, true, any) +} + +func TestLedgerReloadStateProofVerificationTracker(t *testing.T) { + partitiontest.PartitionTest(t) + proto := config.Consensus[protocol.ConsensusCurrentVersion] + + dbName := fmt.Sprintf("%s.%d", t.Name(), crypto.RandUint64()) + genesisInitState := getInitState() + genesisInitState.Block.CurrentProtocol = protocol.ConsensusCurrentVersion + const inMem = true + cfg := config.GetDefaultLocal() + cfg.Archival = false + log := logging.TestingLog(t) + log.SetLevel(logging.Info) + l, err := OpenLedger(log, dbName, inMem, genesisInitState, cfg) + require.NoError(t, err) + defer l.Close() + + numOfStateProofs := uint64(3) + firstStateProofContextConfirmedRound := proto.StateProofInterval + firstStateProofContextTargetRound := firstStateProofContextConfirmedRound + proto.StateProofInterval + + lastStateProofContextConfirmedRound := firstStateProofContextConfirmedRound + proto.StateProofInterval*(numOfStateProofs-1) + lastStateProofContextTargetRound := lastStateProofContextConfirmedRound + proto.StateProofInterval + + blk := genesisInitState.Block + var sp bookkeeping.StateProofTrackingData + sp.StateProofNextRound = basics.Round(firstStateProofContextTargetRound) + blk.BlockHeader.StateProofTracking = map[protocol.StateProofType]bookkeeping.StateProofTrackingData{ + protocol.StateProofBasic: sp, + } + + for i := uint64(0); i < lastStateProofContextConfirmedRound; i++ { + blk.BlockHeader.Round++ + blk.BlockHeader.TimeStamp += 10 + err = l.AddBlock(blk, agreement.Certificate{}) + require.NoError(t, err) + } + + l.WaitForCommit(blk.BlockHeader.Round) + + verifyStateProofVerificationTracking(t, &l.spVerification, basics.Round(firstStateProofContextTargetRound), + numOfStateProofs-1, proto.StateProofInterval, true, trackerDB) + verifyStateProofVerificationTracking(t, &l.spVerification, basics.Round(lastStateProofContextTargetRound), + 1, proto.StateProofInterval, true, trackerMemory) + + err = l.reloadLedger() + require.NoError(t, err) + + verifyStateProofVerificationTracking(t, &l.spVerification, basics.Round(firstStateProofContextTargetRound), + numOfStateProofs-1, proto.StateProofInterval, true, trackerDB) + verifyStateProofVerificationTracking(t, &l.spVerification, basics.Round(lastStateProofContextTargetRound), + 1, proto.StateProofInterval, true, trackerMemory) +} + +func feedBlocksUntilRound(t *testing.T, l *Ledger, prevBlk bookkeeping.Block, targetRound basics.Round) bookkeeping.Block { + for prevBlk.Round() < targetRound { + prevBlk.BlockHeader.Round++ + prevBlk.BlockHeader.TimeStamp += 10 + err := l.AddBlock(prevBlk, agreement.Certificate{}) + require.NoError(t, err) + } + + return prevBlk +} + +func TestLedgerCatchpointSPVerificationTracker(t *testing.T) { + partitiontest.PartitionTest(t) + proto := config.Consensus[protocol.ConsensusFuture] + + dbName := fmt.Sprintf("%s.%d", t.Name(), crypto.RandUint64()) + genesisInitState, initkeys := ledgertesting.GenerateInitState(t, protocol.ConsensusFuture, 100) + genesisInitState.Block.CurrentProtocol = protocol.ConsensusFuture + const inMem = true + cfg := config.GetDefaultLocal() + cfg.Archival = true + // This assures us that the first catchpoint file will contain exactly 1 state proof data. + cfg.CatchpointInterval = proto.StateProofInterval + proto.MaxBalLookback + cfg.MaxAcctLookback = 4 + log := logging.TestingLog(t) + log.SetLevel(logging.Info) + l, err := OpenLedger(log, dbName, inMem, genesisInitState, cfg) + require.NoError(t, err) + + firstStateProofDataConfirmedRound := proto.StateProofInterval + firstStateProofDataTargetRound := firstStateProofDataConfirmedRound + proto.StateProofInterval + + blk := genesisInitState.Block + var sp bookkeeping.StateProofTrackingData + sp.StateProofNextRound = basics.Round(firstStateProofDataTargetRound) + blk.BlockHeader.StateProofTracking = map[protocol.StateProofType]bookkeeping.StateProofTrackingData{ + protocol.StateProofBasic: sp, + } + + // Feeding blocks until we can know for sure we have at least one catchpoint written. + blk = feedBlocksUntilRound(t, l, blk, basics.Round(cfg.CatchpointInterval*2)) + l.WaitForCommit(basics.Round(cfg.CatchpointInterval * 2)) + + numTrackedDataFirstCatchpoint := (cfg.CatchpointInterval - proto.MaxBalLookback) / proto.StateProofInterval + + verifyStateProofVerificationTracking(t, &l.spVerification, basics.Round(firstStateProofDataTargetRound), + numTrackedDataFirstCatchpoint, proto.StateProofInterval, true, any) + l.Close() + + l, err = OpenLedger(log, dbName, inMem, genesisInitState, cfg) + require.NoError(t, err) + defer l.Close() + + verifyStateProofVerificationTracking(t, &l.spVerification, basics.Round(firstStateProofDataTargetRound), + numTrackedDataFirstCatchpoint, proto.StateProofInterval, false, any) + + catchpointAccessor, accessorProgress := initializeTestCatchupAccessor(t, l, uint64(len(initkeys))) + + relCatchpointFilePath := filepath.Join(trackerdb.CatchpointDirName, trackerdb.MakeCatchpointFilePath(basics.Round(cfg.CatchpointInterval))) + catchpointData := readCatchpointFile(t, relCatchpointFilePath) + + err = catchpointAccessor.ProcessStagingBalances(context.Background(), catchpointData[1].headerName, catchpointData[1].data, &accessorProgress) + require.NoError(t, err) + err = catchpointAccessor.CompleteCatchup(context.Background()) + require.NoError(t, err) + + verifyStateProofVerificationTracking(t, &l.spVerification, basics.Round(firstStateProofDataTargetRound), + numTrackedDataFirstCatchpoint, proto.StateProofInterval, true, any) +} + +func TestLedgerSPTrackerAfterReplay(t *testing.T) { + partitiontest.PartitionTest(t) + a := require.New(t) + proto := config.Consensus[protocol.ConsensusCurrentVersion] + + dbName := fmt.Sprintf("%s.%d", t.Name(), crypto.RandUint64()) + genesisInitState, _ := ledgertesting.GenerateInitState(t, protocol.ConsensusCurrentVersion, 100) + genesisInitState.Block.CurrentProtocol = protocol.ConsensusCurrentVersion + 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() + + // Add 1024 empty block without advancing NextStateProofRound + firstStateProofRound := basics.Round(proto.StateProofInterval * 2) // 512 + blk := genesisInitState.Block + var sp bookkeeping.StateProofTrackingData + sp.StateProofNextRound = firstStateProofRound // 512 + blk.BlockHeader.StateProofTracking = map[protocol.StateProofType]bookkeeping.StateProofTrackingData{ + protocol.StateProofBasic: sp, + } + + for i := uint64(0); i < proto.StateProofInterval*4; i++ { + blk.BlockHeader.Round++ + blk.BlockHeader.TimeStamp += 10 + err = l.AddBlock(blk, agreement.Certificate{}) + a.NoError(err) + } + + // 1024 + verifyStateProofVerificationTracking(t, &l.spVerification, firstStateProofRound, 1, proto.StateProofInterval, true, any) + a.Equal(0, len(l.spVerification.pendingDeleteContexts)) + + // Add StateProof transaction (for round 512) and apply without validating, advancing the NextStateProofRound to 768 + spblk := createBlkWithStateproof(t, int(blk.BlockHeader.Round), proto, genesisInitState, l, genesisInitState.Accounts) + err = l.AddBlock(spblk, agreement.Certificate{}) + a.NoError(err) + a.Equal(1, len(l.spVerification.pendingDeleteContexts)) + // To be deleted, but not yet deleted (waiting for commit) + verifyStateProofVerificationTracking(t, &l.spVerification, firstStateProofRound, 1, proto.StateProofInterval, true, any) + + l.WaitForCommit(l.Latest()) + + err = l.reloadLedger() + a.NoError(err) + + a.Equal(1, len(l.spVerification.pendingDeleteContexts)) + verifyStateProofVerificationTracking(t, &l.spVerification, firstStateProofRound, 1, proto.StateProofInterval, true, any) } diff --git a/ledger/ledgercore/catchpointlabel.go b/ledger/ledgercore/catchpointlabel.go index 6f0b90f9d9..6ed5cfd277 100644 --- a/ledger/ledgercore/catchpointlabel.go +++ b/ledger/ledgercore/catchpointlabel.go @@ -34,44 +34,93 @@ var base32Encoder = base32.StdEncoding.WithPadding(base32.NoPadding) // ErrCatchpointParsingFailed is used when we attempt to parse and catchpoint label and failing doing so. var ErrCatchpointParsingFailed = errors.New("catchpoint parsing failed") -// CatchpointLabel represent a single catchpoint label. It will "assemble" a label based on the components -type CatchpointLabel struct { +// CatchpointLabelMaker is used for abstract the creation of different catchpoints versions. +// Different catchpoint version might hash different fields. +type CatchpointLabelMaker interface { + // buffer returns a image used for hashing. (concatenating all fields in the label) + buffer() []byte + // round returns the catchpoint label round + round() basics.Round + // message returns a printable string containing all the relevant fields in the label. + message() string +} + +// CatchpointLabelMakerV6 represent a single catchpoint label maker, matching catchpoints of version V6 and below. +type CatchpointLabelMakerV6 struct { ledgerRound basics.Round ledgerRoundBlockHash crypto.Digest balancesMerkleRoot crypto.Digest totals AccountTotals } -// MakeCatchpointLabel creates a catchpoint label given the catchpoint label parameters. -func MakeCatchpointLabel(ledgerRound basics.Round, ledgerRoundBlockHash crypto.Digest, balancesMerkleRoot crypto.Digest, totals AccountTotals) CatchpointLabel { - return CatchpointLabel{ +// MakeCatchpointLabelMakerV6 creates a V6 catchpoint label given the catchpoint label parameters. +func MakeCatchpointLabelMakerV6(ledgerRound basics.Round, ledgerRoundBlockHash *crypto.Digest, + balancesMerkleRoot *crypto.Digest, totals AccountTotals) *CatchpointLabelMakerV6 { + return &CatchpointLabelMakerV6{ ledgerRound: ledgerRound, - ledgerRoundBlockHash: ledgerRoundBlockHash, - balancesMerkleRoot: balancesMerkleRoot, + ledgerRoundBlockHash: *ledgerRoundBlockHash, + balancesMerkleRoot: *balancesMerkleRoot, totals: totals, } } -// String return the user-facing representation of this catchpoint label. ( i.e. the "label" ) -func (l CatchpointLabel) String() string { - hash := l.Hash() - encodedHash := base32Encoder.EncodeToString(hash[:]) - out := fmt.Sprintf("%d#%s", l.ledgerRound, encodedHash) - logging.Base().Infof("Creating a catchpoint label %s for round=%d, block digest=%s, accounts digest=%s", out, l.ledgerRound, l.ledgerRoundBlockHash, l.balancesMerkleRoot) - return out -} - -// Hash return the hash portion of this catchpoint label -func (l CatchpointLabel) Hash() crypto.Digest { +func (l *CatchpointLabelMakerV6) buffer() []byte { encodedTotals := protocol.EncodeReflect(&l.totals) buffer := make([]byte, 2*crypto.DigestSize+len(encodedTotals)) copy(buffer[:], l.ledgerRoundBlockHash[:]) copy(buffer[crypto.DigestSize:], l.balancesMerkleRoot[:]) copy(buffer[crypto.DigestSize*2:], encodedTotals) - return crypto.Hash(buffer[:crypto.DigestSize*2+len(encodedTotals)]) + + return buffer +} + +func (l *CatchpointLabelMakerV6) round() basics.Round { + return l.ledgerRound +} + +func (l *CatchpointLabelMakerV6) message() string { + return fmt.Sprintf("round=%d, block digest=%s, accounts digest=%s", l.ledgerRound, l.ledgerRoundBlockHash, l.balancesMerkleRoot) +} + +// CatchpointLabelMakerCurrent represent a single catchpoint maker, matching catchpoints of version V7 and above. +type CatchpointLabelMakerCurrent struct { + v6Label CatchpointLabelMakerV6 + spVerificationHash crypto.Digest +} + +// MakeCatchpointLabelMakerCurrent creates a catchpoint label given the catchpoint label parameters. +func MakeCatchpointLabelMakerCurrent(ledgerRound basics.Round, ledgerRoundBlockHash *crypto.Digest, + balancesMerkleRoot *crypto.Digest, totals AccountTotals, spVerificationContextHash *crypto.Digest) *CatchpointLabelMakerCurrent { + return &CatchpointLabelMakerCurrent{ + v6Label: *MakeCatchpointLabelMakerV6(ledgerRound, ledgerRoundBlockHash, balancesMerkleRoot, totals), + spVerificationHash: *spVerificationContextHash, + } +} + +func (l *CatchpointLabelMakerCurrent) buffer() []byte { + v6Buffer := l.v6Label.buffer() + + return append(v6Buffer, l.spVerificationHash[:]...) +} + +func (l *CatchpointLabelMakerCurrent) round() basics.Round { + return l.v6Label.round() +} + +func (l *CatchpointLabelMakerCurrent) message() string { + return fmt.Sprintf("%s spver digest=%s", l.v6Label.message(), l.spVerificationHash) +} + +// MakeLabel returns the user-facing representation of this catchpoint label. ( i.e. the "label" ) +func MakeLabel(l CatchpointLabelMaker) string { + hash := crypto.Hash(l.buffer()) + encodedHash := base32Encoder.EncodeToString(hash[:]) + out := fmt.Sprintf("%d#%s", l.round(), encodedHash) + logging.Base().Infof("Creating a catchpoint label %s for %s", out, l.message()) + return out } -// ParseCatchpointLabel parse the given label and breaks it into the round and hash components. In case of a parsing failuire, +// ParseCatchpointLabel parse the given label and breaks it into the round and hash components. In case of a parsing failure, // the returned err is non-nil. func ParseCatchpointLabel(label string) (round basics.Round, hash crypto.Digest, err error) { err = ErrCatchpointParsingFailed diff --git a/ledger/ledgercore/catchpointlabel_test.go b/ledger/ledgercore/catchpointlabel_test.go index 1bfbe31158..d2b0ad41f0 100644 --- a/ledger/ledgercore/catchpointlabel_test.go +++ b/ledger/ledgercore/catchpointlabel_test.go @@ -32,10 +32,12 @@ func TestUniqueCatchpointLabel(t *testing.T) { uniqueSet := make(map[string]bool) ledgerRoundBlockHashes := []crypto.Digest{} + stateProofVerificationContextHashes := []crypto.Digest{} balancesMerkleRoots := []crypto.Digest{} totals := []AccountTotals{} for i := 0; i < 10; i++ { ledgerRoundBlockHashes = append(ledgerRoundBlockHashes, crypto.Hash([]byte{byte(i)})) + stateProofVerificationContextHashes = append(stateProofVerificationContextHashes, crypto.Hash([]byte{byte(i), byte(1)})) balancesMerkleRoots = append(balancesMerkleRoots, crypto.Hash([]byte{byte(i), byte(i), byte(1)})) totals = append(totals, AccountTotals{ @@ -47,10 +49,13 @@ func TestUniqueCatchpointLabel(t *testing.T) { for r := basics.Round(0); r <= basics.Round(100); r += basics.Round(7) { for _, ledgerRoundHash := range ledgerRoundBlockHashes { for _, balancesMerkleRoot := range balancesMerkleRoots { - for _, total := range totals { - label := MakeCatchpointLabel(r, ledgerRoundHash, balancesMerkleRoot, total) - require.False(t, uniqueSet[label.String()]) - uniqueSet[label.String()] = true + for _, stateProofVerificationContextHash := range stateProofVerificationContextHashes { + for _, total := range totals { + labelMaker := MakeCatchpointLabelMakerCurrent(r, &ledgerRoundHash, &balancesMerkleRoot, total, &stateProofVerificationContextHash) + labelString := MakeLabel(labelMaker) + require.False(t, uniqueSet[labelString]) + uniqueSet[labelString] = true + } } } } @@ -61,10 +66,12 @@ func TestCatchpointLabelParsing(t *testing.T) { partitiontest.PartitionTest(t) ledgerRoundBlockHashes := []crypto.Digest{} + stateProofVerificationContextHashes := []crypto.Digest{} balancesMerkleRoots := []crypto.Digest{} totals := []AccountTotals{} for i := 0; i < 10; i++ { ledgerRoundBlockHashes = append(ledgerRoundBlockHashes, crypto.Hash([]byte{byte(i)})) + stateProofVerificationContextHashes = append(stateProofVerificationContextHashes, crypto.Hash([]byte{byte(i), byte(1)})) balancesMerkleRoots = append(balancesMerkleRoots, crypto.Hash([]byte{byte(i), byte(i), byte(1)})) totals = append(totals, AccountTotals{ @@ -76,12 +83,15 @@ func TestCatchpointLabelParsing(t *testing.T) { for r := basics.Round(0); r <= basics.Round(100); r += basics.Round(7) { for _, ledgerRoundHash := range ledgerRoundBlockHashes { for _, balancesMerkleRoot := range balancesMerkleRoots { - for _, total := range totals { - label := MakeCatchpointLabel(r, ledgerRoundHash, balancesMerkleRoot, total) - parsedRound, parsedHash, err := ParseCatchpointLabel(label.String()) - require.Equal(t, r, parsedRound) - require.NotEqual(t, crypto.Digest{}, parsedHash) - require.NoError(t, err) + for _, stateProofVerificationContextHash := range stateProofVerificationContextHashes { + for _, total := range totals { + labelMaker := MakeCatchpointLabelMakerCurrent(r, &ledgerRoundHash, &balancesMerkleRoot, total, &stateProofVerificationContextHash) + labelString := MakeLabel(labelMaker) + parsedRound, parsedHash, err := ParseCatchpointLabel(labelString) + require.Equal(t, r, parsedRound) + require.NotEqual(t, crypto.Digest{}, parsedHash) + require.NoError(t, err) + } } } } diff --git a/ledger/ledgercore/msgp_gen.go b/ledger/ledgercore/msgp_gen.go index 0d6447a803..709658f436 100644 --- a/ledger/ledgercore/msgp_gen.go +++ b/ledger/ledgercore/msgp_gen.go @@ -31,6 +31,14 @@ import ( // |-----> (*) Msgsize // |-----> (*) MsgIsZero // +// StateProofVerificationContext +// |-----> (*) MarshalMsg +// |-----> (*) CanMarshalMsg +// |-----> (*) UnmarshalMsg +// |-----> (*) CanUnmarshalMsg +// |-----> (*) Msgsize +// |-----> (*) MsgIsZero +// // MarshalMsg implements msgp.Marshaler func (z *AccountTotals) MarshalMsg(b []byte) (o []byte) { @@ -937,3 +945,178 @@ func (z *OnlineRoundParamsData) Msgsize() (s int) { func (z *OnlineRoundParamsData) MsgIsZero() bool { return ((*z).OnlineSupply == 0) && ((*z).RewardsLevel == 0) && ((*z).CurrentProtocol.MsgIsZero()) } + +// MarshalMsg implements msgp.Marshaler +func (z *StateProofVerificationContext) MarshalMsg(b []byte) (o []byte) { + o = msgp.Require(b, z.Msgsize()) + // omitempty: check for empty values + zb0001Len := uint32(4) + var zb0001Mask uint8 /* 5 bits */ + if (*z).OnlineTotalWeight.MsgIsZero() { + zb0001Len-- + zb0001Mask |= 0x2 + } + if (*z).LastAttestedRound.MsgIsZero() { + zb0001Len-- + zb0001Mask |= 0x4 + } + if (*z).Version.MsgIsZero() { + zb0001Len-- + zb0001Mask |= 0x8 + } + if (*z).VotersCommitment.MsgIsZero() { + zb0001Len-- + zb0001Mask |= 0x10 + } + // variable map header, size zb0001Len + o = append(o, 0x80|uint8(zb0001Len)) + if zb0001Len != 0 { + if (zb0001Mask & 0x2) == 0 { // if not empty + // string "pw" + o = append(o, 0xa2, 0x70, 0x77) + o = (*z).OnlineTotalWeight.MarshalMsg(o) + } + if (zb0001Mask & 0x4) == 0 { // if not empty + // string "spround" + o = append(o, 0xa7, 0x73, 0x70, 0x72, 0x6f, 0x75, 0x6e, 0x64) + o = (*z).LastAttestedRound.MarshalMsg(o) + } + if (zb0001Mask & 0x8) == 0 { // if not empty + // string "v" + o = append(o, 0xa1, 0x76) + o = (*z).Version.MarshalMsg(o) + } + if (zb0001Mask & 0x10) == 0 { // if not empty + // string "vc" + o = append(o, 0xa2, 0x76, 0x63) + o = (*z).VotersCommitment.MarshalMsg(o) + } + } + return +} + +func (_ *StateProofVerificationContext) CanMarshalMsg(z interface{}) bool { + _, ok := (z).(*StateProofVerificationContext) + return ok +} + +// UnmarshalMsg implements msgp.Unmarshaler +func (z *StateProofVerificationContext) 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).LastAttestedRound.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "LastAttestedRound") + return + } + } + if zb0001 > 0 { + zb0001-- + bts, err = (*z).VotersCommitment.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "VotersCommitment") + return + } + } + if zb0001 > 0 { + zb0001-- + bts, err = (*z).OnlineTotalWeight.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "OnlineTotalWeight") + return + } + } + if zb0001 > 0 { + zb0001-- + bts, err = (*z).Version.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Version") + 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) = StateProofVerificationContext{} + } + for zb0001 > 0 { + zb0001-- + field, bts, err = msgp.ReadMapKeyZC(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + switch string(field) { + case "spround": + bts, err = (*z).LastAttestedRound.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "LastAttestedRound") + return + } + case "vc": + bts, err = (*z).VotersCommitment.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "VotersCommitment") + return + } + case "pw": + bts, err = (*z).OnlineTotalWeight.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "OnlineTotalWeight") + return + } + case "v": + bts, err = (*z).Version.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "Version") + return + } + default: + err = msgp.ErrNoField(string(field)) + if err != nil { + err = msgp.WrapError(err) + return + } + } + } + } + o = bts + return +} + +func (_ *StateProofVerificationContext) CanUnmarshalMsg(z interface{}) bool { + _, ok := (z).(*StateProofVerificationContext) + return ok +} + +// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message +func (z *StateProofVerificationContext) Msgsize() (s int) { + s = 1 + 8 + (*z).LastAttestedRound.Msgsize() + 3 + (*z).VotersCommitment.Msgsize() + 3 + (*z).OnlineTotalWeight.Msgsize() + 2 + (*z).Version.Msgsize() + return +} + +// MsgIsZero returns whether this is a zero value +func (z *StateProofVerificationContext) MsgIsZero() bool { + return ((*z).LastAttestedRound.MsgIsZero()) && ((*z).VotersCommitment.MsgIsZero()) && ((*z).OnlineTotalWeight.MsgIsZero()) && ((*z).Version.MsgIsZero()) +} diff --git a/ledger/ledgercore/msgp_gen_test.go b/ledger/ledgercore/msgp_gen_test.go index 1f3c3a3d06..8478b9d8bc 100644 --- a/ledger/ledgercore/msgp_gen_test.go +++ b/ledger/ledgercore/msgp_gen_test.go @@ -193,3 +193,63 @@ func BenchmarkUnmarshalOnlineRoundParamsData(b *testing.B) { } } } + +func TestMarshalUnmarshalStateProofVerificationContext(t *testing.T) { + partitiontest.PartitionTest(t) + v := StateProofVerificationContext{} + 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 TestRandomizedEncodingStateProofVerificationContext(t *testing.T) { + protocol.RunEncodingTest(t, &StateProofVerificationContext{}) +} + +func BenchmarkMarshalMsgStateProofVerificationContext(b *testing.B) { + v := StateProofVerificationContext{} + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + v.MarshalMsg(nil) + } +} + +func BenchmarkAppendMsgStateProofVerificationContext(b *testing.B) { + v := StateProofVerificationContext{} + 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 BenchmarkUnmarshalStateProofVerificationContext(b *testing.B) { + v := StateProofVerificationContext{} + 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/ledgercore/statedelta.go b/ledger/ledgercore/statedelta.go index 7672f0a864..0c940e32cf 100644 --- a/ledger/ledgercore/statedelta.go +++ b/ledger/ledgercore/statedelta.go @@ -110,9 +110,10 @@ type StateDelta struct { // new block header; read-only Hdr *bookkeeping.BlockHeader - // next round for which we expect a state proof. - // zero if no state proof is expected. - StateProofNext basics.Round + // ModStateProofNextRound represents modification on StateProofNextRound field in the block header. If the block contains + // a valid state proof transaction, this field will contain the next round for state proof. + // otherwise it will be set to 0. + ModStateProofNextRound basics.Round // previous block timestamp PrevTimestamp int64 @@ -214,7 +215,7 @@ func (sd *StateDelta) PopulateStateDelta(hdr *bookkeeping.BlockHeader, prevTimes sd.initialHint = hint } sd.Hdr = hdr - sd.StateProofNext = stateProofNext + sd.ModStateProofNextRound = stateProofNext sd.PrevTimestamp = prevTimestamp } @@ -247,7 +248,7 @@ func (sd *StateDelta) Reset() { // these fields are going to be populated on next use but resetting them anyway for safety. // we are not resetting sd.initialHint since it should only be reset if reallocating AccountDeltas sd.Hdr = nil - sd.StateProofNext = basics.Round(0) + sd.ModStateProofNextRound = basics.Round(0) sd.PrevTimestamp = 0 } diff --git a/ledger/ledgercore/statedelta_test.go b/ledger/ledgercore/statedelta_test.go index dd0f3e201d..95f3bcb325 100644 --- a/ledger/ledgercore/statedelta_test.go +++ b/ledger/ledgercore/statedelta_test.go @@ -150,7 +150,7 @@ func TestStateDeltaReset(t *testing.T) { // StateDeltas simple fields require.Zero(t, sd.Hdr) - require.Zero(t, sd.StateProofNext) + require.Zero(t, sd.ModStateProofNextRound) require.Zero(t, sd.PrevTimestamp) require.Zero(t, sd.Totals) @@ -184,16 +184,16 @@ func TestStateDeltaReflect(t *testing.T) { partitiontest.PartitionTest(t) stateDeltaFieldNames := map[string]struct{}{ - "Accts": {}, - "KvMods": {}, - "Txids": {}, - "Txleases": {}, - "Creatables": {}, - "Hdr": {}, - "StateProofNext": {}, - "PrevTimestamp": {}, - "initialHint": {}, - "Totals": {}, + "Accts": {}, + "KvMods": {}, + "Txids": {}, + "Txleases": {}, + "Creatables": {}, + "Hdr": {}, + "ModStateProofNextRound": {}, + "PrevTimestamp": {}, + "initialHint": {}, + "Totals": {}, } sd := StateDelta{} diff --git a/ledger/ledgercore/stateproofverification.go b/ledger/ledgercore/stateproofverification.go new file mode 100644 index 0000000000..0220185a20 --- /dev/null +++ b/ledger/ledgercore/stateproofverification.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 . + +package ledgercore + +import ( + "github.com/algorand/go-algorand/crypto" + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/data/bookkeeping" + "github.com/algorand/go-algorand/protocol" +) + +// StateProofVerificationContext represents the context provided by the ledger to verify a state proof transaction. +type StateProofVerificationContext struct { + _struct struct{} `codec:",omitempty,omitemptyarray"` + + // LastAttestedRound is the last attested round of the state proof verified using this data. + LastAttestedRound basics.Round `codec:"spround"` + + // VotersCommitment is the vector commitment root of the top N accounts to sign the next state proof. + VotersCommitment crypto.GenericDigest `codec:"vc"` + + // OnlineTotalWeight is the total amount of stake attesting to the next state proof. + OnlineTotalWeight basics.MicroAlgos `codec:"pw"` + + // Version is the protocol version that would be used to verify the state proof + Version protocol.ConsensusVersion `codec:"v"` +} + +// MakeStateProofVerificationContext produces a new StateProofVerificationContext instance from a block header and last attested round +func MakeStateProofVerificationContext(votersHdr *bookkeeping.BlockHeader, lastAttested basics.Round) *StateProofVerificationContext { + return &StateProofVerificationContext{ + VotersCommitment: votersHdr.StateProofTracking[protocol.StateProofBasic].StateProofVotersCommitment, + OnlineTotalWeight: votersHdr.StateProofTracking[protocol.StateProofBasic].StateProofOnlineTotalWeight, + LastAttestedRound: lastAttested, + Version: votersHdr.CurrentProtocol, + } +} diff --git a/ledger/ledgercore/votersForRound.go b/ledger/ledgercore/votersForRound.go index c1afd9eb5f..94901bf203 100644 --- a/ledger/ledgercore/votersForRound.go +++ b/ledger/ledgercore/votersForRound.go @@ -39,6 +39,19 @@ type OnlineAccountsFetcher interface { TopOnlineAccounts(rnd basics.Round, voteRnd basics.Round, n uint64, params *config.ConsensusParams, rewardsLevel uint64) (topOnlineAccounts []*OnlineAccount, totalOnlineStake basics.MicroAlgos, err error) } +// LedgerForSPBuilder captures the functionality needed for the creation of the cryptographic state proof builder. +type LedgerForSPBuilder interface { + VotersForStateProof(rnd basics.Round) (*VotersForRound, error) + BlockHdr(basics.Round) (bookkeeping.BlockHeader, error) +} + +// VotersCommitListener represents an object that needs to get notified on commit stages in the voters tracker. +type VotersCommitListener interface { + // OnPrepareVoterCommit gives the listener the opportunity to backup VotersForRound data related to rounds (oldBase, newBase] before it is being removed. + // The implementation should log any errors that might occur. + OnPrepareVoterCommit(oldBase basics.Round, newBase basics.Round, voters LedgerForSPBuilder) +} + // VotersForRound tracks the top online voting accounts as of a particular // round, along with a Merkle tree commitment to those voting accounts. type VotersForRound struct { diff --git a/ledger/msgp_gen.go b/ledger/msgp_gen.go index 39c489e281..4164a526d3 100644 --- a/ledger/msgp_gen.go +++ b/ledger/msgp_gen.go @@ -6,6 +6,7 @@ import ( "github.com/algorand/msgp/msgp" "github.com/algorand/go-algorand/ledger/encoded" + "github.com/algorand/go-algorand/ledger/ledgercore" ) // The following msgp objects are implemented in this file: @@ -41,6 +42,14 @@ import ( // |-----> (*) Msgsize // |-----> (*) MsgIsZero // +// catchpointStateProofVerificationContext +// |-----> (*) MarshalMsg +// |-----> (*) CanMarshalMsg +// |-----> (*) UnmarshalMsg +// |-----> (*) CanUnmarshalMsg +// |-----> (*) Msgsize +// |-----> (*) MsgIsZero +// // MarshalMsg implements msgp.Marshaler func (z CatchpointCatchupState) MarshalMsg(b []byte) (o []byte) { @@ -769,3 +778,161 @@ func (z *catchpointFileChunkV6) Msgsize() (s int) { func (z *catchpointFileChunkV6) MsgIsZero() bool { return (len((*z).Balances) == 0) && (len((*z).KVs) == 0) } + +// MarshalMsg implements msgp.Marshaler +func (z *catchpointStateProofVerificationContext) MarshalMsg(b []byte) (o []byte) { + o = msgp.Require(b, z.Msgsize()) + // omitempty: check for empty values + zb0002Len := uint32(1) + var zb0002Mask uint8 /* 2 bits */ + if len((*z).Data) == 0 { + zb0002Len-- + zb0002Mask |= 0x2 + } + // variable map header, size zb0002Len + o = append(o, 0x80|uint8(zb0002Len)) + if zb0002Len != 0 { + if (zb0002Mask & 0x2) == 0 { // if not empty + // string "spd" + o = append(o, 0xa3, 0x73, 0x70, 0x64) + if (*z).Data == nil { + o = msgp.AppendNil(o) + } else { + o = msgp.AppendArrayHeader(o, uint32(len((*z).Data))) + } + for zb0001 := range (*z).Data { + o = (*z).Data[zb0001].MarshalMsg(o) + } + } + } + return +} + +func (_ *catchpointStateProofVerificationContext) CanMarshalMsg(z interface{}) bool { + _, ok := (z).(*catchpointStateProofVerificationContext) + return ok +} + +// UnmarshalMsg implements msgp.Unmarshaler +func (z *catchpointStateProofVerificationContext) UnmarshalMsg(bts []byte) (o []byte, err error) { + var field []byte + _ = field + var zb0002 int + var zb0003 bool + zb0002, zb0003, bts, err = msgp.ReadMapHeaderBytes(bts) + if _, ok := err.(msgp.TypeError); ok { + zb0002, zb0003, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + if zb0002 > 0 { + zb0002-- + var zb0004 int + var zb0005 bool + zb0004, zb0005, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Data") + return + } + if zb0004 > SPContextPerCatchpointFile { + err = msgp.ErrOverflow(uint64(zb0004), uint64(SPContextPerCatchpointFile)) + err = msgp.WrapError(err, "struct-from-array", "Data") + return + } + if zb0005 { + (*z).Data = nil + } else if (*z).Data != nil && cap((*z).Data) >= zb0004 { + (*z).Data = ((*z).Data)[:zb0004] + } else { + (*z).Data = make([]ledgercore.StateProofVerificationContext, zb0004) + } + for zb0001 := range (*z).Data { + bts, err = (*z).Data[zb0001].UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Data", zb0001) + return + } + } + } + if zb0002 > 0 { + err = msgp.ErrTooManyArrayFields(zb0002) + if err != nil { + err = msgp.WrapError(err, "struct-from-array") + return + } + } + } else { + if err != nil { + err = msgp.WrapError(err) + return + } + if zb0003 { + (*z) = catchpointStateProofVerificationContext{} + } + for zb0002 > 0 { + zb0002-- + field, bts, err = msgp.ReadMapKeyZC(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + switch string(field) { + case "spd": + var zb0006 int + var zb0007 bool + zb0006, zb0007, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "Data") + return + } + if zb0006 > SPContextPerCatchpointFile { + err = msgp.ErrOverflow(uint64(zb0006), uint64(SPContextPerCatchpointFile)) + err = msgp.WrapError(err, "Data") + return + } + if zb0007 { + (*z).Data = nil + } else if (*z).Data != nil && cap((*z).Data) >= zb0006 { + (*z).Data = ((*z).Data)[:zb0006] + } else { + (*z).Data = make([]ledgercore.StateProofVerificationContext, zb0006) + } + for zb0001 := range (*z).Data { + bts, err = (*z).Data[zb0001].UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "Data", zb0001) + return + } + } + default: + err = msgp.ErrNoField(string(field)) + if err != nil { + err = msgp.WrapError(err) + return + } + } + } + } + o = bts + return +} + +func (_ *catchpointStateProofVerificationContext) CanUnmarshalMsg(z interface{}) bool { + _, ok := (z).(*catchpointStateProofVerificationContext) + return ok +} + +// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message +func (z *catchpointStateProofVerificationContext) Msgsize() (s int) { + s = 1 + 4 + msgp.ArrayHeaderSize + for zb0001 := range (*z).Data { + s += (*z).Data[zb0001].Msgsize() + } + return +} + +// MsgIsZero returns whether this is a zero value +func (z *catchpointStateProofVerificationContext) MsgIsZero() bool { + return (len((*z).Data) == 0) +} diff --git a/ledger/msgp_gen_test.go b/ledger/msgp_gen_test.go index de29b5f11d..729d6efef4 100644 --- a/ledger/msgp_gen_test.go +++ b/ledger/msgp_gen_test.go @@ -193,3 +193,63 @@ func BenchmarkUnmarshalcatchpointFileChunkV6(b *testing.B) { } } } + +func TestMarshalUnmarshalcatchpointStateProofVerificationContext(t *testing.T) { + partitiontest.PartitionTest(t) + v := catchpointStateProofVerificationContext{} + 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 TestRandomizedEncodingcatchpointStateProofVerificationContext(t *testing.T) { + protocol.RunEncodingTest(t, &catchpointStateProofVerificationContext{}) +} + +func BenchmarkMarshalMsgcatchpointStateProofVerificationContext(b *testing.B) { + v := catchpointStateProofVerificationContext{} + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + v.MarshalMsg(nil) + } +} + +func BenchmarkAppendMsgcatchpointStateProofVerificationContext(b *testing.B) { + v := catchpointStateProofVerificationContext{} + 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 BenchmarkUnmarshalcatchpointStateProofVerificationContext(b *testing.B) { + v := catchpointStateProofVerificationContext{} + 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/spverificationtracker.go b/ledger/spverificationtracker.go new file mode 100644 index 0000000000..4dda2b7057 --- /dev/null +++ b/ledger/spverificationtracker.go @@ -0,0 +1,313 @@ +// Copyright (C) 2019-2023 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package ledger + +import ( + "context" + "errors" + "fmt" + + "github.com/algorand/go-deadlock" + + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/data/bookkeeping" + "github.com/algorand/go-algorand/ledger/ledgercore" + "github.com/algorand/go-algorand/ledger/store/trackerdb" + "github.com/algorand/go-algorand/logging" +) + +var ( + errSPVerificationContextNotFound = errors.New("requested state proof verification context not found") +) + +type verificationDeleteContext struct { + confirmedRound basics.Round + stateProofNextRound basics.Round +} + +type verificationCommitContext struct { + confirmedRound basics.Round + verificationContext ledgercore.StateProofVerificationContext +} + +// spVerificationTracker is in charge of tracking context required to verify state proofs until such a time +// as the context is no longer needed. +type spVerificationTracker struct { + // pendingCommitContexts represents the part of the tracked verification context currently in memory. Each element in this + // array contains both the context required to verify a single state proof and context to decide whether it's possible to + // commit the verification context to the database. + pendingCommitContexts []verificationCommitContext + + // pendingDeleteContexts represents the context required to delete committed state proof verification context from the + // database. + pendingDeleteContexts []verificationDeleteContext + + // mu protects pendingCommitContexts and pendingDeleteContexts. + mu deadlock.RWMutex + + // log copied from ledger + log logging.Logger + + l ledgerForTracker + + // lastLookedUpVerificationContext should store the last verification context that was looked up. + lastLookedUpVerificationContext ledgercore.StateProofVerificationContext +} + +func (spt *spVerificationTracker) loadFromDisk(l ledgerForTracker, _ basics.Round) error { + spt.log = l.trackerLog() + spt.l = l + + spt.mu.Lock() + defer spt.mu.Unlock() + + // reset the cache + spt.lastLookedUpVerificationContext = ledgercore.StateProofVerificationContext{} + + const initialContextArraySize = 10 + spt.pendingCommitContexts = make([]verificationCommitContext, 0, initialContextArraySize) + spt.pendingDeleteContexts = make([]verificationDeleteContext, 0, initialContextArraySize) + + return nil +} + +func (spt *spVerificationTracker) newBlock(blk bookkeeping.Block, delta ledgercore.StateDelta) { + currentStateProofInterval := basics.Round(blk.ConsensusProtocol().StateProofInterval) + + if currentStateProofInterval == 0 { + return + } + + if blk.Round()%currentStateProofInterval == 0 { + spt.appendCommitContext(&blk) + } + + if delta.ModStateProofNextRound != 0 { + spt.appendDeleteContext(&blk, &delta) + } +} + +func (spt *spVerificationTracker) committedUpTo(round basics.Round) (minRound, lookback basics.Round) { + return round, 0 +} + +func (spt *spVerificationTracker) produceCommittingTask(_ basics.Round, _ basics.Round, dcr *deferredCommitRange) *deferredCommitRange { + return dcr +} + +func (spt *spVerificationTracker) prepareCommit(dcc *deferredCommitContext) error { + spt.mu.RLock() + defer spt.mu.RUnlock() + + lastContextToCommitIndex := spt.roundToLatestCommitContextIndex(dcc.newBase()) + dcc.spVerification.commitContext = make([]verificationCommitContext, lastContextToCommitIndex+1) + copy(dcc.spVerification.commitContext, spt.pendingCommitContexts[:lastContextToCommitIndex+1]) + + dcc.spVerification.lastDeleteIndex = spt.roundToLatestDeleteContextIndex(dcc.newBase()) + if dcc.spVerification.lastDeleteIndex >= 0 { + dcc.spVerification.earliestLastAttestedRound = spt.pendingDeleteContexts[dcc.spVerification.lastDeleteIndex].stateProofNextRound + } + + return nil +} + +func (spt *spVerificationTracker) commitRound(ctx context.Context, tx trackerdb.TransactionScope, dcc *deferredCommitContext) (err error) { + if len(dcc.spVerification.commitContext) != 0 { + err = commitSPContexts(ctx, tx, dcc.spVerification.commitContext) + if err != nil { + return err + } + } + + if dcc.spVerification.lastDeleteIndex >= 0 { + err = tx.MakeSpVerificationCtxReaderWriter().DeleteOldSPContexts(ctx, dcc.spVerification.earliestLastAttestedRound) + } + + return err +} + +func commitSPContexts(ctx context.Context, tx trackerdb.TransactionScope, commitData []verificationCommitContext) error { + ptrToCtxs := make([]*ledgercore.StateProofVerificationContext, len(commitData)) + for i := 0; i < len(commitData); i++ { + ptrToCtxs[i] = &commitData[i].verificationContext + } + + return tx.MakeSpVerificationCtxReaderWriter().StoreSPContexts(ctx, ptrToCtxs) +} + +func (spt *spVerificationTracker) postCommit(_ context.Context, dcc *deferredCommitContext) { + spt.mu.Lock() + defer spt.mu.Unlock() + + spt.pendingCommitContexts = spt.pendingCommitContexts[len(dcc.spVerification.commitContext):] + spt.pendingDeleteContexts = spt.pendingDeleteContexts[dcc.spVerification.lastDeleteIndex+1:] +} + +func (spt *spVerificationTracker) postCommitUnlocked(context.Context, *deferredCommitContext) { +} + +func (spt *spVerificationTracker) handleUnorderedCommit(*deferredCommitContext) { +} + +func (spt *spVerificationTracker) close() { +} + +func (spt *spVerificationTracker) LookupVerificationContext(stateProofLastAttestedRound basics.Round) (*ledgercore.StateProofVerificationContext, error) { + if lstlookup := spt.retrieveFromCache(stateProofLastAttestedRound); lstlookup != nil { + return lstlookup, nil + } + + verificationContext, err := spt.lookupVerificationContext(stateProofLastAttestedRound) + if err != nil { + return nil, err + } + + // before return, update the cache + spt.mu.Lock() + spt.lastLookedUpVerificationContext = *verificationContext + spt.mu.Unlock() + + return verificationContext, nil +} + +func (spt *spVerificationTracker) retrieveFromCache(stateProofLastAttestedRound basics.Round) *ledgercore.StateProofVerificationContext { + spt.mu.RLock() + defer spt.mu.RUnlock() + + if spt.lastLookedUpVerificationContext.LastAttestedRound == stateProofLastAttestedRound && + !spt.lastLookedUpVerificationContext.MsgIsZero() { + cpy := spt.lastLookedUpVerificationContext + + return &cpy + } + + return nil +} + +func (spt *spVerificationTracker) lookupVerificationContext(stateProofLastAttestedRound basics.Round) (*ledgercore.StateProofVerificationContext, error) { + spt.mu.RLock() + defer spt.mu.RUnlock() + + if len(spt.pendingCommitContexts) > 0 && + stateProofLastAttestedRound >= spt.pendingCommitContexts[0].verificationContext.LastAttestedRound && + stateProofLastAttestedRound <= spt.pendingCommitContexts[len(spt.pendingCommitContexts)-1].verificationContext.LastAttestedRound { + return spt.lookupContextInTrackedMemory(stateProofLastAttestedRound) + } + + if len(spt.pendingCommitContexts) == 0 || stateProofLastAttestedRound < spt.pendingCommitContexts[0].verificationContext.LastAttestedRound { + return spt.lookupContextInDB(stateProofLastAttestedRound) + } + + return &ledgercore.StateProofVerificationContext{}, fmt.Errorf("requested context for round %d, greater than maximum context round %d: %w", + stateProofLastAttestedRound, + spt.pendingCommitContexts[len(spt.pendingCommitContexts)-1].verificationContext.LastAttestedRound, + errSPVerificationContextNotFound) +} + +func (spt *spVerificationTracker) lookupContextInTrackedMemory(stateProofLastAttestedRound basics.Round) (*ledgercore.StateProofVerificationContext, error) { + for _, commitContext := range spt.pendingCommitContexts { + if commitContext.verificationContext.LastAttestedRound == stateProofLastAttestedRound { + verificationContextCopy := commitContext.verificationContext + return &verificationContextCopy, nil + } + } + + return &ledgercore.StateProofVerificationContext{}, fmt.Errorf("%w for round %d: memory lookup failed", + errSPVerificationContextNotFound, stateProofLastAttestedRound) +} + +func (spt *spVerificationTracker) lookupContextInDB(stateProofLastAttestedRound basics.Round) (*ledgercore.StateProofVerificationContext, error) { + var spContext *ledgercore.StateProofVerificationContext + err := spt.l.trackerDB().Snapshot(func(ctx context.Context, tx trackerdb.SnapshotScope) (err error) { + spContext, err = tx.MakeSpVerificationCtxReader().LookupSPContext(stateProofLastAttestedRound) + if err != nil { + err = fmt.Errorf("%w for round %d: %s", errSPVerificationContextNotFound, stateProofLastAttestedRound, err) + } + + return err + }) + + return spContext, err +} + +func (spt *spVerificationTracker) roundToLatestCommitContextIndex(committedRound basics.Round) int { + latestCommittedContextIndex := -1 + + for index, ctx := range spt.pendingCommitContexts { + if ctx.confirmedRound > committedRound { + break + } + + latestCommittedContextIndex = index + } + + return latestCommittedContextIndex +} + +func (spt *spVerificationTracker) roundToLatestDeleteContextIndex(committedRound basics.Round) int { + latestCommittedContextIndex := -1 + + for index, ctx := range spt.pendingDeleteContexts { + if ctx.confirmedRound > committedRound { + break + } + + latestCommittedContextIndex = index + } + + return latestCommittedContextIndex +} + +func (spt *spVerificationTracker) appendCommitContext(blk *bookkeeping.Block) { + spt.mu.Lock() + defer spt.mu.Unlock() + + if len(spt.pendingCommitContexts) > 0 { + lastCommitConfirmedRound := spt.pendingCommitContexts[len(spt.pendingCommitContexts)-1].confirmedRound + if blk.Round() <= lastCommitConfirmedRound { + spt.log.Panicf("state proof verification: attempted to append commit context confirmed earlier than latest"+ + "commit context, round: %d, last confirmed commit context round: %d", blk.Round(), lastCommitConfirmedRound) + } + } + latestRound := blk.Round() + basics.Round(blk.ConsensusProtocol().StateProofInterval) + commitContext := verificationCommitContext{ + confirmedRound: blk.Round(), + verificationContext: *ledgercore.MakeStateProofVerificationContext(&blk.BlockHeader, latestRound), + } + + spt.pendingCommitContexts = append(spt.pendingCommitContexts, commitContext) +} + +func (spt *spVerificationTracker) appendDeleteContext(blk *bookkeeping.Block, delta *ledgercore.StateDelta) { + spt.mu.Lock() + defer spt.mu.Unlock() + + if len(spt.pendingDeleteContexts) > 0 { + lastDeleteConfirmedRound := spt.pendingDeleteContexts[len(spt.pendingDeleteContexts)-1].confirmedRound + if blk.Round() <= lastDeleteConfirmedRound { + spt.log.Panicf("state proof verification: attempted to append delete context confirmed earlier than latest"+ + "delete context, round: %d, last confirmed delete context round: %d", blk.Round(), lastDeleteConfirmedRound) + } + } + + deletionContext := verificationDeleteContext{ + confirmedRound: blk.Round(), + stateProofNextRound: delta.ModStateProofNextRound, + } + + spt.pendingDeleteContexts = append(spt.pendingDeleteContexts, deletionContext) +} diff --git a/ledger/spverificationtracker_test.go b/ledger/spverificationtracker_test.go new file mode 100644 index 0000000000..53e532a067 --- /dev/null +++ b/ledger/spverificationtracker_test.go @@ -0,0 +1,492 @@ +// Copyright (C) 2019-2023 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package ledger + +import ( + "context" + "testing" + + "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/ledger/ledgercore" + "github.com/algorand/go-algorand/ledger/store/trackerdb" + "github.com/algorand/go-algorand/protocol" + "github.com/algorand/go-algorand/test/partitiontest" + "github.com/stretchr/testify/require" +) + +const defaultStateProofInterval = uint64(256) +const defaultFirstStateProofContextRound = basics.Round(defaultStateProofInterval * 2) +const defaultFirstStateProofContextInterval = basics.Round(2) +const unusedByStateProofTracker = basics.Round(0) + +type StateProofTrackingLocation uint64 + +const ( + any StateProofTrackingLocation = iota + trackerDB + trackerMemory +) + +func initializeLedgerSpt(t *testing.T) (*mockLedgerForTracker, *spVerificationTracker) { + a := require.New(t) + accts := []map[basics.Address]basics.AccountData{makeRandomOnlineAccounts(20)} + + ml := makeMockLedgerForTracker(t, true, 1, protocol.ConsensusCurrentVersion, accts) + + spt := spVerificationTracker{} + + conf := config.GetDefaultLocal() + + _, err := trackerDBInitialize(ml, false, ".") + a.NoError(err) + + err = ml.trackers.initialize(ml, []ledgerTracker{&spt}, conf) + a.NoError(err) + err = spt.loadFromDisk(ml, unusedByStateProofTracker) + a.NoError(err) + + return ml, &spt +} + +func mockCommit(t *testing.T, spt *spVerificationTracker, ml *mockLedgerForTracker, dbRound basics.Round, newBase basics.Round) { + a := require.New(t) + + offset := uint64(newBase - dbRound) + + dcr := deferredCommitRange{offset: offset} + + dcc := deferredCommitContext{ + deferredCommitRange: dcr, + } + + spt.committedUpTo(newBase) + spt.produceCommittingTask(newBase, dbRound, &dcr) + err := spt.prepareCommit(&dcc) + a.NoError(err) + + err = ml.dbs.Transaction(func(ctx context.Context, tx trackerdb.TransactionScope) (err error) { + return spt.commitRound(ctx, tx, &dcc) + }) + a.NoError(err) + + postCommitCtx, cancel := context.WithCancel(context.Background()) + defer cancel() + spt.postCommit(postCommitCtx, &dcc) + spt.postCommitUnlocked(postCommitCtx, &dcc) +} + +func genesisBlock() *blockEntry { + initialRound := basics.Round(0) + block := randomBlock(initialRound) + + var stateTracking bookkeeping.StateProofTrackingData + block.block.BlockHeader.StateProofTracking = make(map[protocol.StateProofType]bookkeeping.StateProofTrackingData) + + stateTracking.StateProofNextRound = basics.Round(defaultStateProofInterval * 2) + block.block.BlockHeader.StateProofTracking[protocol.StateProofBasic] = stateTracking + + return &block +} + +func blockStateProofsEnabled(prevBlock *blockEntry, stateProofInterval uint64, stuckStateProofs bool) blockEntry { + round := prevBlock.block.Round() + 1 + prevBlockLastAttestedRound := prevBlock.block.StateProofTracking[protocol.StateProofBasic].StateProofNextRound + + modifiedConsensus := config.Consensus[protocol.ConsensusCurrentVersion] + modifiedConsensus.StateProofInterval = stateProofInterval + config.Consensus[protocol.ConsensusCurrentVersion] = modifiedConsensus + + block := randomBlock(round) + block.block.CurrentProtocol = protocol.ConsensusCurrentVersion + + var stateTracking bookkeeping.StateProofTrackingData + block.block.BlockHeader.StateProofTracking = make(map[protocol.StateProofType]bookkeeping.StateProofTrackingData) + + if !stuckStateProofs && round > prevBlockLastAttestedRound { + stateTracking.StateProofNextRound = prevBlockLastAttestedRound + basics.Round(block.block.ConsensusProtocol().StateProofInterval) + } else { + stateTracking.StateProofNextRound = prevBlockLastAttestedRound + } + + block.block.BlockHeader.StateProofTracking[protocol.StateProofBasic] = stateTracking + return block +} + +func feedBlocksUpToRound(spt *spVerificationTracker, prevBlock *blockEntry, targetRound basics.Round, + stateProofInterval uint64, stuckStateProofs bool) *blockEntry { + for i := prevBlock.block.Round(); i < targetRound; i++ { + block := blockStateProofsEnabled(prevBlock, stateProofInterval, stuckStateProofs) + stateProofDelta := basics.Round(0) + + prevStateProofNextRound := prevBlock.block.StateProofTracking[protocol.StateProofBasic].StateProofNextRound + currentStateProofNextRound := block.block.StateProofTracking[protocol.StateProofBasic].StateProofNextRound + + if currentStateProofNextRound != prevStateProofNextRound { + stateProofDelta = currentStateProofNextRound + } + + spt.newBlock(block.block, ledgercore.StateDelta{ModStateProofNextRound: stateProofDelta}) + prevBlock = &block + } + + return prevBlock +} + +func verifyStateProofVerificationTracking(t *testing.T, spt *spVerificationTracker, + startRound basics.Round, contextAmount uint64, stateProofInterval uint64, contextPresenceExpected bool, trackingLocation StateProofTrackingLocation) { + a := require.New(t) + + finalLastAttestedRound := startRound + basics.Round((contextAmount-1)*stateProofInterval) + + for lastAttestedRound := startRound; lastAttestedRound <= finalLastAttestedRound; lastAttestedRound += basics.Round(stateProofInterval) { + var err error + switch trackingLocation { + case any: + _, err = spt.LookupVerificationContext(lastAttestedRound) + case trackerDB: + _, err = spt.lookupContextInDB(lastAttestedRound) + case trackerMemory: + _, err = spt.lookupContextInTrackedMemory(lastAttestedRound) + } + + if contextPresenceExpected { + a.NoError(err) + } else { + a.ErrorIs(err, errSPVerificationContextNotFound) + } + } +} + +func TestStateProofVerificationTracker_StateProofsDisabled(t *testing.T) { + partitiontest.PartitionTest(t) + + ml, spt := initializeLedgerSpt(t) + defer ml.Close() + defer spt.close() + + roundsAmount := basics.Round(1000) + for round := basics.Round(1); round <= roundsAmount; round++ { + block := randomBlock(round) + // Last protocol version without state proofs. + block.block.CurrentProtocol = protocol.ConsensusV33 + spt.newBlock(block.block, ledgercore.StateDelta{}) + } + + mockCommit(t, spt, ml, 0, roundsAmount) + + verifyStateProofVerificationTracking(t, spt, defaultFirstStateProofContextRound, uint64(roundsAmount)/defaultStateProofInterval, defaultStateProofInterval, false, any) +} + +func TestStateProofVerificationTracker_StateProofsNotStuck(t *testing.T) { + partitiontest.PartitionTest(t) + + ml, spt := initializeLedgerSpt(t) + defer ml.Close() + defer spt.close() + + expectedContextNum := uint64(12) + lastBlock := feedBlocksUpToRound(spt, genesisBlock(), + basics.Round(expectedContextNum*defaultStateProofInterval+defaultStateProofInterval-1), + defaultStateProofInterval, false) + + mockCommit(t, spt, ml, 0, lastBlock.block.Round()) + + expectedRemainingContextNum := expectedContextNum - 1 + verifyStateProofVerificationTracking(t, spt, defaultFirstStateProofContextRound, expectedRemainingContextNum, defaultStateProofInterval, false, any) + + finalLastAttestedRound := defaultFirstStateProofContextRound + basics.Round(expectedRemainingContextNum*defaultStateProofInterval) + // The last verification context should still be tracked since the round with the state proof transaction it is used + // to verify has not yet been committed. + verifyStateProofVerificationTracking(t, spt, finalLastAttestedRound, 1, defaultStateProofInterval, true, any) +} + +func TestStateProofVerificationTracker_CommitFUllDbFlush(t *testing.T) { + partitiontest.PartitionTest(t) + + ml, spt := initializeLedgerSpt(t) + defer ml.Close() + defer spt.close() + + expectedContextNum := uint64(10) + + lastBlock := feedBlocksUpToRound(spt, genesisBlock(), basics.Round(expectedContextNum*defaultStateProofInterval), + defaultStateProofInterval, true) + + mockCommit(t, spt, ml, 0, lastBlock.block.Round()) + + spt.lastLookedUpVerificationContext = ledgercore.StateProofVerificationContext{} + verifyStateProofVerificationTracking(t, spt, defaultFirstStateProofContextRound, expectedContextNum, defaultStateProofInterval, false, trackerMemory) + verifyStateProofVerificationTracking(t, spt, defaultFirstStateProofContextRound, expectedContextNum, defaultStateProofInterval, true, trackerDB) +} + +func TestStateProofVerificationTracker_CommitPartialDbFlush(t *testing.T) { + partitiontest.PartitionTest(t) + + ml, spt := initializeLedgerSpt(t) + defer ml.Close() + defer spt.close() + + contextToAdd := uint64(10) + _ = feedBlocksUpToRound(spt, genesisBlock(), basics.Round(contextToAdd*defaultStateProofInterval), + defaultStateProofInterval, true) + + expectedContextInDbNum := uint64(2) + expectedContextInMemoryNum := contextToAdd - expectedContextInDbNum + + mockCommit(t, spt, ml, 0, basics.Round(defaultStateProofInterval*expectedContextInDbNum)) + + verifyStateProofVerificationTracking(t, spt, defaultFirstStateProofContextRound, expectedContextInDbNum, defaultStateProofInterval, true, trackerDB) + verifyStateProofVerificationTracking(t, spt, defaultFirstStateProofContextRound, expectedContextInDbNum, defaultStateProofInterval, false, trackerMemory) + + firstNonFlushedContextTargetRound := defaultFirstStateProofContextRound + basics.Round(expectedContextInDbNum*defaultStateProofInterval) + verifyStateProofVerificationTracking(t, spt, firstNonFlushedContextTargetRound, expectedContextInMemoryNum, defaultStateProofInterval, false, trackerDB) + verifyStateProofVerificationTracking(t, spt, firstNonFlushedContextTargetRound, expectedContextInMemoryNum, defaultStateProofInterval, true, trackerMemory) +} + +func TestStateProofVerificationTracker_CommitNoDbFlush(t *testing.T) { + partitiontest.PartitionTest(t) + + ml, spt := initializeLedgerSpt(t) + defer ml.Close() + defer spt.close() + + contextToAdd := uint64(10) + _ = feedBlocksUpToRound(spt, genesisBlock(), basics.Round(contextToAdd*defaultStateProofInterval), + defaultStateProofInterval, true) + + mockCommit(t, spt, ml, 0, basics.Round(defaultStateProofInterval-1)) + + verifyStateProofVerificationTracking(t, spt, defaultFirstStateProofContextRound, contextToAdd, defaultStateProofInterval, true, trackerMemory) + verifyStateProofVerificationTracking(t, spt, defaultFirstStateProofContextRound, contextToAdd, defaultStateProofInterval, false, trackerDB) +} + +func TestStateProofVerificationTracker_CommitFullDbPruning(t *testing.T) { + partitiontest.PartitionTest(t) + + ml, spt := initializeLedgerSpt(t) + defer ml.Close() + defer spt.close() + + contextToAdd := uint64(6) + maxStateProofsToGenerate := contextToAdd - 1 + + lastStuckBlock := feedBlocksUpToRound(spt, genesisBlock(), basics.Round(contextToAdd*defaultStateProofInterval), + defaultStateProofInterval, true) + lastBlock := feedBlocksUpToRound(spt, lastStuckBlock, lastStuckBlock.block.Round()+basics.Round(maxStateProofsToGenerate), + defaultStateProofInterval, false) + + verifyStateProofVerificationTracking(t, spt, defaultFirstStateProofContextRound, contextToAdd, defaultStateProofInterval, true, trackerMemory) + + mockCommit(t, spt, ml, 0, lastBlock.block.Round()) + + verifyStateProofVerificationTracking(t, spt, defaultFirstStateProofContextRound, maxStateProofsToGenerate, defaultStateProofInterval, false, any) + + finalLastAttestedRound := defaultFirstStateProofContextRound + basics.Round(maxStateProofsToGenerate*defaultStateProofInterval) + // The last verification context should still be tracked since the round with the state proof transaction it is used + // to verify has not yet been committed. + verifyStateProofVerificationTracking(t, spt, finalLastAttestedRound, 1, defaultStateProofInterval, true, any) +} + +func TestStateProofVerificationTracker_CommitPartialDbPruning(t *testing.T) { + partitiontest.PartitionTest(t) + + ml, spt := initializeLedgerSpt(t) + defer ml.Close() + defer spt.close() + + contextToAdd := uint64(6) + maxStateProofsToGenerate := contextToAdd - 1 + contextToRemove := maxStateProofsToGenerate - 1 + + lastStuckBlock := feedBlocksUpToRound(spt, genesisBlock(), basics.Round(contextToAdd*defaultStateProofInterval), + defaultStateProofInterval, true) + _ = feedBlocksUpToRound(spt, lastStuckBlock, + lastStuckBlock.block.Round()+basics.Round(maxStateProofsToGenerate*defaultStateProofInterval), + defaultStateProofInterval, false) + + verifyStateProofVerificationTracking(t, spt, defaultFirstStateProofContextRound, contextToAdd, defaultStateProofInterval, true, trackerMemory) + + mockCommit(t, spt, ml, 0, lastStuckBlock.block.Round()+basics.Round(contextToRemove)) + + verifyStateProofVerificationTracking(t, spt, defaultFirstStateProofContextRound, contextToRemove, defaultStateProofInterval, false, any) + verifyStateProofVerificationTracking(t, spt, defaultFirstStateProofContextRound+basics.Round(contextToRemove*defaultStateProofInterval), + contextToAdd-contextToRemove, defaultStateProofInterval, true, trackerDB) +} + +func TestStateProofVerificationTracker_CommitNoDbPruning(t *testing.T) { + partitiontest.PartitionTest(t) + a := require.New(t) + + ml, spt := initializeLedgerSpt(t) + defer ml.Close() + defer spt.close() + + contextToAdd := uint64(6) + maxStateProofsToGenerate := contextToAdd - 1 + offsetBeforeStateProofs := basics.Round(defaultStateProofInterval / 2) + + lastStuckBlock := feedBlocksUpToRound(spt, genesisBlock(), basics.Round(contextToAdd*defaultStateProofInterval), + defaultStateProofInterval, true) + + lastStuckBlockRound := lastStuckBlock.block.Round() + var block blockEntry + for round := lastStuckBlockRound + 1; round <= lastStuckBlockRound+offsetBeforeStateProofs; round++ { + block = randomBlock(round) + block.block.CurrentProtocol = protocol.ConsensusCurrentVersion + spt.newBlock(block.block, ledgercore.StateDelta{}) + } + + _ = feedBlocksUpToRound(spt, &block, block.block.Round()+basics.Round(maxStateProofsToGenerate), defaultStateProofInterval, false) + + verifyStateProofVerificationTracking(t, spt, defaultFirstStateProofContextRound, contextToAdd, defaultStateProofInterval, true, trackerMemory) + + mockCommit(t, spt, ml, 0, lastStuckBlockRound) + + verifyStateProofVerificationTracking(t, spt, defaultFirstStateProofContextRound, contextToAdd, defaultStateProofInterval, true, trackerDB) + a.Equal(maxStateProofsToGenerate, uint64(len(spt.pendingDeleteContexts))) +} + +func TestStateProofVerificationTracker_StateProofIntervalChange(t *testing.T) { + partitiontest.PartitionTest(t) + + ml, spt := initializeLedgerSpt(t) + defer ml.Close() + defer spt.close() + + newStateProofInterval := defaultStateProofInterval * 2 + + oldIntervalContext := uint64(5) + newIntervalContext := uint64(6) + + lastOldIntervalBlock := feedBlocksUpToRound(spt, genesisBlock(), basics.Round(oldIntervalContext*defaultStateProofInterval), + defaultStateProofInterval, true) + lastStuckBlock := feedBlocksUpToRound(spt, lastOldIntervalBlock, lastOldIntervalBlock.block.Round()+basics.Round(newIntervalContext*newStateProofInterval), + newStateProofInterval, true) + + verifyStateProofVerificationTracking(t, spt, defaultFirstStateProofContextRound, oldIntervalContext, defaultStateProofInterval, + true, any) + firstNewIntervalLastAttestedRound := lastOldIntervalBlock.block.Round() + basics.Round(defaultStateProofInterval) + verifyStateProofVerificationTracking(t, spt, firstNewIntervalLastAttestedRound, newIntervalContext, + newStateProofInterval, true, any) + + newIntervalRemovedStateProofs := newIntervalContext - (newIntervalContext / 2) + // State Proofs for old blocks should be generated using the old interval. + lastOldIntervalStateProofBlock := feedBlocksUpToRound(spt, lastStuckBlock, + lastStuckBlock.block.Round()+basics.Round(oldIntervalContext)-1, + defaultStateProofInterval, false) + lastBlock := feedBlocksUpToRound(spt, lastOldIntervalStateProofBlock, + lastOldIntervalStateProofBlock.block.Round()+basics.Round(newIntervalRemovedStateProofs), + newStateProofInterval, false) + + mockCommit(t, spt, ml, 0, lastBlock.block.Round()) + + firstRemainingLastAttestedRound := firstNewIntervalLastAttestedRound + + basics.Round(newIntervalRemovedStateProofs*newStateProofInterval) + verifyStateProofVerificationTracking(t, spt, defaultFirstStateProofContextRound, oldIntervalContext, defaultStateProofInterval, + false, any) + verifyStateProofVerificationTracking(t, spt, firstNewIntervalLastAttestedRound, + newIntervalRemovedStateProofs, newStateProofInterval, false, any) + verifyStateProofVerificationTracking(t, spt, firstRemainingLastAttestedRound, newIntervalContext-newIntervalRemovedStateProofs, + newStateProofInterval, true, any) +} + +func TestStateProofVerificationTracker_LookupVerificationContext(t *testing.T) { + partitiontest.PartitionTest(t) + a := require.New(t) + + ml, spt := initializeLedgerSpt(t) + defer ml.Close() + defer spt.close() + + contextToAdd := uint64(10) + _ = feedBlocksUpToRound(spt, genesisBlock(), basics.Round(contextToAdd*defaultStateProofInterval), + defaultStateProofInterval, true) + + expectedContextInDbNum := uint64(2) + + mockCommit(t, spt, ml, 0, basics.Round(defaultStateProofInterval*expectedContextInDbNum)) + + _, err := spt.LookupVerificationContext(basics.Round(0)) + a.ErrorIs(err, errSPVerificationContextNotFound) + a.ErrorContains(err, "no rows") + + finalLastAttestedRound := basics.Round(defaultStateProofInterval + contextToAdd*defaultStateProofInterval) + _, err = spt.LookupVerificationContext(finalLastAttestedRound + basics.Round(defaultStateProofInterval)) + a.ErrorIs(err, errSPVerificationContextNotFound) + a.ErrorContains(err, "greater than maximum") + + dbContextRound := basics.Round(defaultStateProofInterval + expectedContextInDbNum*defaultStateProofInterval) + dbContext, err := spt.LookupVerificationContext(dbContextRound) + a.NoError(err) + a.Equal(dbContextRound, dbContext.LastAttestedRound) + + memoryContextRound := basics.Round(defaultStateProofInterval + (expectedContextInDbNum+1)*defaultStateProofInterval) + + memoryContext, err := spt.LookupVerificationContext(memoryContextRound) + a.NoError(err) + a.Equal(memoryContextRound, memoryContext.LastAttestedRound) + + // This error shouldn't happen in normal flow - we force it to happen for the test. + spt.pendingCommitContexts[0].verificationContext.LastAttestedRound = 0 + spt.lastLookedUpVerificationContext = ledgercore.StateProofVerificationContext{} + _, err = spt.LookupVerificationContext(memoryContextRound) + a.ErrorIs(err, errSPVerificationContextNotFound) + a.ErrorContains(err, "memory lookup failed") +} + +func TestStateProofVerificationTracker_PanicInvalidBlockInsertion(t *testing.T) { + partitiontest.PartitionTest(t) + a := require.New(t) + + ml, spt := initializeLedgerSpt(t) + defer ml.Close() + defer spt.close() + + contextToAdd := uint64(1) + _ = feedBlocksUpToRound(spt, genesisBlock(), basics.Round(contextToAdd*defaultStateProofInterval), + defaultStateProofInterval, true) + + pastBlock := randomBlock(0) + a.Panics(func() { spt.appendCommitContext(&pastBlock.block) }) +} + +func TestStateProofVerificationTracker_lastLookupContextUpdatedAfterLookup(t *testing.T) { + partitiontest.PartitionTest(t) + a := require.New(t) + + mockLedger, spt := initializeLedgerSpt(t) + defer mockLedger.Close() + defer spt.close() + + a.Empty(spt.lastLookedUpVerificationContext) + + NumberOfVerificationContextToAdd := uint64(10) + _ = feedBlocksUpToRound(spt, genesisBlock(), basics.Round(NumberOfVerificationContextToAdd*defaultStateProofInterval), + defaultStateProofInterval, true) + + a.Empty(spt.lastLookedUpVerificationContext) + + expectedContextInDbNum := NumberOfVerificationContextToAdd + for i := uint64(defaultFirstStateProofContextInterval); i < expectedContextInDbNum; i++ { + vf, err := spt.LookupVerificationContext(basics.Round(defaultStateProofInterval * i)) + a.NoError(err) + + a.Equal(*vf, spt.lastLookedUpVerificationContext) + } +} diff --git a/ledger/store/trackerdb/catchpoint.go b/ledger/store/trackerdb/catchpoint.go index b371e0d347..dfbacbd86f 100644 --- a/ledger/store/trackerdb/catchpoint.go +++ b/ledger/store/trackerdb/catchpoint.go @@ -80,6 +80,8 @@ const ( CatchpointStateCatchupHashRound = CatchpointState("catchpointCatchupHashRound") // CatchpointStateCatchpointLookback is the number of rounds we keep catchpoints for CatchpointStateCatchpointLookback = CatchpointState("catchpointLookback") + // CatchpointStateCatchupVersion is the catchpoint version which the currently catchpoint catchup process is trying to catchup to. + CatchpointStateCatchupVersion = CatchpointState("catchpointCatchupVersion") ) // UnfinishedCatchpointRecord represents a stored record of an unfinished catchpoint. @@ -128,6 +130,9 @@ type CatchpointFirstStageInfo struct { TotalChunks uint64 `codec:"chunksCount"` // BiggestChunkLen is the size in the bytes of the largest chunk, used when re-packing. BiggestChunkLen uint64 `codec:"biggestChunk"` + + // StateProofVerificationHash is the hash of the state proof verification data contained in the catchpoint data file. + StateProofVerificationHash crypto.Digest `codec:"spVerificationHash"` } // MakeCatchpointFilePath builds the path of a catchpoint file. diff --git a/ledger/store/trackerdb/interface.go b/ledger/store/trackerdb/interface.go index b5c93cb017..f65dc8695e 100644 --- a/ledger/store/trackerdb/interface.go +++ b/ledger/store/trackerdb/interface.go @@ -227,3 +227,26 @@ type CatchpointPendingHashesIter interface { Next(ctx context.Context) (hashes [][]byte, err error) Close() } + +// SpVerificationCtxReader is a reader abstraction for stateproof verification tracker +// Use with SnapshotScope +type SpVerificationCtxReader interface { + LookupSPContext(stateProofLastAttestedRound basics.Round) (*ledgercore.StateProofVerificationContext, error) + GetAllSPContexts(ctx context.Context) ([]ledgercore.StateProofVerificationContext, error) + GetAllSPContextsFromCatchpointTbl(ctx context.Context) ([]ledgercore.StateProofVerificationContext, error) +} + +// SpVerificationCtxWriter is a writer abstraction for stateproof verification tracker +// Use with BatchScope +type SpVerificationCtxWriter interface { + DeleteOldSPContexts(ctx context.Context, earliestLastAttestedRound basics.Round) error + StoreSPContexts(ctx context.Context, verificationContext []*ledgercore.StateProofVerificationContext) error + StoreSPContextsToCatchpointTbl(ctx context.Context, verificationContexts []ledgercore.StateProofVerificationContext) error +} + +// SpVerificationCtxReaderWriter is SpVerificationCtxReader+SpVerificationCtxWriter +// Use with TransactionScope +type SpVerificationCtxReaderWriter interface { + SpVerificationCtxReader + SpVerificationCtxWriter +} diff --git a/ledger/store/trackerdb/msgp_gen.go b/ledger/store/trackerdb/msgp_gen.go index ad4df82ef0..e6fa865d65 100644 --- a/ledger/store/trackerdb/msgp_gen.go +++ b/ledger/store/trackerdb/msgp_gen.go @@ -1134,8 +1134,8 @@ func (z *BaseVotingData) MsgIsZero() bool { func (z *CatchpointFirstStageInfo) MarshalMsg(b []byte) (o []byte) { o = msgp.Require(b, z.Msgsize()) // omitempty: check for empty values - zb0001Len := uint32(6) - var zb0001Mask uint8 /* 7 bits */ + zb0001Len := uint32(7) + var zb0001Mask uint8 /* 8 bits */ if (*z).Totals.MsgIsZero() { zb0001Len-- zb0001Mask |= 0x2 @@ -1156,10 +1156,14 @@ func (z *CatchpointFirstStageInfo) MarshalMsg(b []byte) (o []byte) { zb0001Len-- zb0001Mask |= 0x20 } - if (*z).TrieBalancesHash.MsgIsZero() { + if (*z).StateProofVerificationHash.MsgIsZero() { zb0001Len-- zb0001Mask |= 0x40 } + if (*z).TrieBalancesHash.MsgIsZero() { + zb0001Len-- + zb0001Mask |= 0x80 + } // variable map header, size zb0001Len o = append(o, 0x80|uint8(zb0001Len)) if zb0001Len != 0 { @@ -1189,6 +1193,11 @@ func (z *CatchpointFirstStageInfo) MarshalMsg(b []byte) (o []byte) { o = msgp.AppendUint64(o, (*z).TotalKVs) } if (zb0001Mask & 0x40) == 0 { // if not empty + // string "spVerificationHash" + o = append(o, 0xb2, 0x73, 0x70, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x61, 0x73, 0x68) + o = (*z).StateProofVerificationHash.MarshalMsg(o) + } + if (zb0001Mask & 0x80) == 0 { // if not empty // string "trieBalancesHash" o = append(o, 0xb0, 0x74, 0x72, 0x69, 0x65, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x48, 0x61, 0x73, 0x68) o = (*z).TrieBalancesHash.MarshalMsg(o) @@ -1263,6 +1272,14 @@ func (z *CatchpointFirstStageInfo) UnmarshalMsg(bts []byte) (o []byte, err error return } } + if zb0001 > 0 { + zb0001-- + bts, err = (*z).StateProofVerificationHash.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "StateProofVerificationHash") + return + } + } if zb0001 > 0 { err = msgp.ErrTooManyArrayFields(zb0001) if err != nil { @@ -1322,6 +1339,12 @@ func (z *CatchpointFirstStageInfo) UnmarshalMsg(bts []byte) (o []byte, err error err = msgp.WrapError(err, "BiggestChunkLen") return } + case "spVerificationHash": + bts, err = (*z).StateProofVerificationHash.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "StateProofVerificationHash") + return + } default: err = msgp.ErrNoField(string(field)) if err != nil { @@ -1342,13 +1365,13 @@ func (_ *CatchpointFirstStageInfo) CanUnmarshalMsg(z interface{}) bool { // Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message func (z *CatchpointFirstStageInfo) Msgsize() (s int) { - s = 1 + 14 + (*z).Totals.Msgsize() + 17 + (*z).TrieBalancesHash.Msgsize() + 14 + msgp.Uint64Size + 9 + msgp.Uint64Size + 12 + msgp.Uint64Size + 13 + msgp.Uint64Size + s = 1 + 14 + (*z).Totals.Msgsize() + 17 + (*z).TrieBalancesHash.Msgsize() + 14 + msgp.Uint64Size + 9 + msgp.Uint64Size + 12 + msgp.Uint64Size + 13 + msgp.Uint64Size + 19 + (*z).StateProofVerificationHash.Msgsize() return } // MsgIsZero returns whether this is a zero value func (z *CatchpointFirstStageInfo) MsgIsZero() bool { - return ((*z).Totals.MsgIsZero()) && ((*z).TrieBalancesHash.MsgIsZero()) && ((*z).TotalAccounts == 0) && ((*z).TotalKVs == 0) && ((*z).TotalChunks == 0) && ((*z).BiggestChunkLen == 0) + return ((*z).Totals.MsgIsZero()) && ((*z).TrieBalancesHash.MsgIsZero()) && ((*z).TotalAccounts == 0) && ((*z).TotalKVs == 0) && ((*z).TotalChunks == 0) && ((*z).BiggestChunkLen == 0) && ((*z).StateProofVerificationHash.MsgIsZero()) } // MarshalMsg implements msgp.Marshaler diff --git a/ledger/store/trackerdb/sqlitedriver/catchpoint.go b/ledger/store/trackerdb/sqlitedriver/catchpoint.go index 9bf15eb47f..388749858d 100644 --- a/ledger/store/trackerdb/sqlitedriver/catchpoint.go +++ b/ledger/store/trackerdb/sqlitedriver/catchpoint.go @@ -468,6 +468,7 @@ func (cw *catchpointWriter) ResetCatchpointStagingBalances(ctx context.Context, "DROP TABLE IF EXISTS catchpointpendinghashes", "DROP TABLE IF EXISTS catchpointresources", "DROP TABLE IF EXISTS catchpointkvstore", + "DROP TABLE IF EXISTS catchpointstateproofverification", "DELETE FROM accounttotals where id='catchpointStaging'", } @@ -489,6 +490,7 @@ func (cw *catchpointWriter) ResetCatchpointStagingBalances(ctx context.Context, "CREATE TABLE IF NOT EXISTS catchpointaccounthashes (id integer primary key, data blob)", "CREATE TABLE IF NOT EXISTS catchpointresources (addrid INTEGER NOT NULL, aidx INTEGER NOT NULL, data BLOB NOT NULL, PRIMARY KEY (addrid, aidx) ) WITHOUT ROWID", "CREATE TABLE IF NOT EXISTS catchpointkvstore (key blob primary key, value blob)", + "CREATE TABLE IF NOT EXISTS catchpointstateproofverification (lastattestedround INTEGER PRIMARY KEY NOT NULL, verificationContext BLOB NOT NULL)", createNormalizedOnlineBalanceIndex(idxnameBalances, "catchpointbalances"), // should this be removed ? createUniqueAddressBalanceIndex(idxnameAddress, "catchpointbalances"), @@ -514,12 +516,14 @@ func (cw *catchpointWriter) ApplyCatchpointStagingBalances(ctx context.Context, "DROP TABLE IF EXISTS accounthashes", "DROP TABLE IF EXISTS resources", "DROP TABLE IF EXISTS kvstore", + "DROP TABLE IF EXISTS stateproofverification", "ALTER TABLE catchpointbalances RENAME TO accountbase", "ALTER TABLE catchpointassetcreators RENAME TO assetcreators", "ALTER TABLE catchpointaccounthashes RENAME TO accounthashes", "ALTER TABLE catchpointresources RENAME TO resources", "ALTER TABLE catchpointkvstore RENAME TO kvstore", + "ALTER TABLE catchpointstateproofverification RENAME TO stateproofverification", } for _, stmt := range stmts { diff --git a/ledger/store/trackerdb/sqlitedriver/schema.go b/ledger/store/trackerdb/sqlitedriver/schema.go index 5ca781e009..783077965d 100644 --- a/ledger/store/trackerdb/sqlitedriver/schema.go +++ b/ledger/store/trackerdb/sqlitedriver/schema.go @@ -149,6 +149,11 @@ const createUnfinishedCatchpointsTable = ` round integer primary key NOT NULL, blockhash blob NOT NULL)` +const createStateProofVerificationTableQuery = ` + CREATE TABLE IF NOT EXISTS stateproofverification ( + lastattestedround integer primary key NOT NULL, + verificationcontext blob NOT NULL)` + var accountsResetExprs = []string{ `DROP TABLE IF EXISTS acctrounds`, `DROP TABLE IF EXISTS accounttotals`, @@ -164,6 +169,7 @@ var accountsResetExprs = []string{ `DROP TABLE IF EXISTS onlineroundparamstail`, `DROP TABLE IF EXISTS catchpointfirststageinfo`, `DROP TABLE IF EXISTS unfinishedcatchpoints`, + `DROP TABLE IF EXISTS stateproofverification`, } // accountsInit fills the database using tx with initAccounts if the @@ -374,6 +380,11 @@ func accountsCreateUnfinishedCatchpointsTable(ctx context.Context, e db.Executab return err } +func createStateProofVerificationTable(ctx context.Context, e db.Executable) error { + _, err := e.ExecContext(ctx, createStateProofVerificationTableQuery) + return err +} + // performResourceTableMigration migrate the database to use the resources table. func performResourceTableMigration(ctx context.Context, tx *sql.Tx, log func(processed, total uint64)) (err error) { now := time.Now().UnixNano() diff --git a/ledger/store/trackerdb/sqlitedriver/spVerificationAccessor.go b/ledger/store/trackerdb/sqlitedriver/spVerificationAccessor.go new file mode 100644 index 0000000000..3a024e0a98 --- /dev/null +++ b/ledger/store/trackerdb/sqlitedriver/spVerificationAccessor.go @@ -0,0 +1,164 @@ +// Copyright (C) 2019-2023 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package sqlitedriver + +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" + "github.com/algorand/go-algorand/util/db" +) + +type stateProofVerificationReader struct { + q db.Queryable +} + +type stateProofVerificationWriter struct { + e db.Executable +} + +type stateProofVerificationReaderWriter struct { + stateProofVerificationReader + stateProofVerificationWriter +} + +func makeStateProofVerificationReader(q db.Queryable) *stateProofVerificationReader { + return &stateProofVerificationReader{q: q} +} + +func makeStateProofVerificationWriter(e db.Executable) *stateProofVerificationWriter { + return &stateProofVerificationWriter{e: e} +} + +func makeStateProofVerificationReaderWriter(q db.Queryable, e db.Executable) *stateProofVerificationReaderWriter { + return &stateProofVerificationReaderWriter{ + stateProofVerificationReader{q: q}, + stateProofVerificationWriter{e: e}, + } +} + +// MakeStateProofVerificationReader returns SpVerificationCtxReader for accessing from outside of ledger +func MakeStateProofVerificationReader(q db.Queryable) trackerdb.SpVerificationCtxReader { + return makeStateProofVerificationReader(q) +} + +// LookupSPContext retrieves stateproof verification context from the database. +func (spa *stateProofVerificationReader) LookupSPContext(stateProofLastAttestedRound basics.Round) (*ledgercore.StateProofVerificationContext, error) { + verificationContext := ledgercore.StateProofVerificationContext{} + queryFunc := func() error { + row := spa.q.QueryRow("SELECT verificationcontext FROM stateproofverification WHERE lastattestedround=?", stateProofLastAttestedRound) + var buf []byte + err := row.Scan(&buf) + if err != nil { + return err + } + err = protocol.Decode(buf, &verificationContext) + if err != nil { + return err + } + return nil + } + + err := db.Retry(queryFunc) + return &verificationContext, err +} + +// DeleteOldSPContexts removes a single state proof verification data from the database. +func (spa *stateProofVerificationWriter) DeleteOldSPContexts(ctx context.Context, earliestLastAttestedRound basics.Round) error { + _, err := spa.e.ExecContext(ctx, "DELETE FROM stateproofverification WHERE lastattestedround < ?", earliestLastAttestedRound) + return err +} + +// StoreSPContexts stores a single state proof verification context to database +func (spa *stateProofVerificationWriter) StoreSPContexts(ctx context.Context, verificationContext []*ledgercore.StateProofVerificationContext) error { + spWriteStmt, err := spa.e.PrepareContext(ctx, "INSERT INTO stateProofVerification(lastattestedround, verificationContext) VALUES(?, ?)") + if err != nil { + return err + } + for i := range verificationContext { + _, err = spWriteStmt.ExecContext(ctx, verificationContext[i].LastAttestedRound, protocol.Encode(verificationContext[i])) + if err != nil { + return err + } + } + + return nil +} + +// StoreSPContextsToCatchpointTbl stores state proof verification contexts to catchpoint staging table +func (spa *stateProofVerificationWriter) StoreSPContextsToCatchpointTbl(ctx context.Context, verificationContexts []ledgercore.StateProofVerificationContext) error { + spWriteStmt, err := spa.e.PrepareContext(ctx, "INSERT INTO catchpointstateproofverification(lastattestedround, verificationContext) VALUES(?, ?)") + if err != nil { + return err + } + + for i := range verificationContexts { + _, err = spWriteStmt.ExecContext(ctx, verificationContexts[i].LastAttestedRound, protocol.Encode(&verificationContexts[i])) + if err != nil { + return err + } + } + return nil +} + +// GetAllSPContexts returns all contexts needed to verify state proofs. +func (spa *stateProofVerificationReader) GetAllSPContexts(ctx context.Context) ([]ledgercore.StateProofVerificationContext, error) { + return spa.getAllSPContextsInternal(ctx, "SELECT verificationContext FROM stateProofVerification ORDER BY lastattestedround") +} + +// GetAllSPContextsFromCatchpointTbl returns all state proof verification data from the catchpointStateProofVerification table. +func (spa *stateProofVerificationReader) GetAllSPContextsFromCatchpointTbl(ctx context.Context) ([]ledgercore.StateProofVerificationContext, error) { + return spa.getAllSPContextsInternal(ctx, "SELECT verificationContext FROM catchpointStateProofVerification ORDER BY lastattestedround") +} + +func (spa *stateProofVerificationReader) getAllSPContextsInternal(ctx context.Context, query string) ([]ledgercore.StateProofVerificationContext, error) { + var result []ledgercore.StateProofVerificationContext + queryFunc := func() error { + rows, err := spa.q.QueryContext(ctx, query) + if err != nil { + return err + } + + defer rows.Close() + + // Clear `res` in case this function is repeated. + result = result[:0] + for rows.Next() { + var rawData []byte + err = rows.Scan(&rawData) + if err != nil { + return err + } + + var record ledgercore.StateProofVerificationContext + err = protocol.Decode(rawData, &record) + if err != nil { + return err + } + + result = append(result, record) + } + + return nil + } + + err := db.Retry(queryFunc) + return result, err +} diff --git a/ledger/store/trackerdb/sqlitedriver/store_sqlite_impl.go b/ledger/store/trackerdb/sqlitedriver/store_sqlite_impl.go index 3696ed37e6..74327be2bc 100644 --- a/ledger/store/trackerdb/sqlitedriver/store_sqlite_impl.go +++ b/ledger/store/trackerdb/sqlitedriver/store_sqlite_impl.go @@ -204,6 +204,10 @@ func (txs sqlTransactionScope) MakeEncodedAccoutsBatchIter() trackerdb.EncodedAc return MakeEncodedAccoutsBatchIter(txs.tx) } +func (txs sqlTransactionScope) MakeSpVerificationCtxReaderWriter() trackerdb.SpVerificationCtxReaderWriter { + return makeStateProofVerificationReaderWriter(txs.tx, txs.tx) +} + func (txs sqlTransactionScope) RunMigrations(ctx context.Context, params trackerdb.Params, log logging.Logger, targetVersion int32) (mgr trackerdb.InitParams, err error) { return RunMigrations(ctx, txs.tx, params, log, targetVersion) } @@ -263,6 +267,10 @@ func (bs sqlBatchScope) AccountsUpdateSchemaTest(ctx context.Context) (err error return AccountsUpdateSchemaTest(ctx, bs.tx) } +func (bs sqlBatchScope) MakeSpVerificationCtxWriter() trackerdb.SpVerificationCtxWriter { + return makeStateProofVerificationWriter(bs.tx) +} + func (ss sqlSnapshotScope) MakeAccountsReader() (trackerdb.AccountsReaderExt, error) { return NewAccountsSQLReaderWriter(ss.tx), nil } @@ -274,3 +282,7 @@ func (ss sqlSnapshotScope) MakeCatchpointReader() (trackerdb.CatchpointReader, e func (ss sqlSnapshotScope) MakeCatchpointPendingHashesIterator(hashCount int) trackerdb.CatchpointPendingHashesIter { return MakeCatchpointPendingHashesIterator(hashCount, ss.tx) } + +func (ss sqlSnapshotScope) MakeSpVerificationCtxReader() trackerdb.SpVerificationCtxReader { + return makeStateProofVerificationReader(ss.tx) +} diff --git a/ledger/store/trackerdb/sqlitedriver/testing.go b/ledger/store/trackerdb/sqlitedriver/testing.go index 2a96a661b1..28d4fb9cd9 100644 --- a/ledger/store/trackerdb/sqlitedriver/testing.go +++ b/ledger/store/trackerdb/sqlitedriver/testing.go @@ -125,5 +125,8 @@ func AccountsUpdateSchemaTest(ctx context.Context, tx *sql.Tx) (err error) { if err := accountsCreateBoxTable(ctx, tx); err != nil { return err } + if err := createStateProofVerificationTable(ctx, tx); err != nil { + return err + } return nil } diff --git a/ledger/store/trackerdb/sqlitedriver/trackerdbV2.go b/ledger/store/trackerdb/sqlitedriver/trackerdbV2.go index 358d33d2d9..268c97d8d8 100644 --- a/ledger/store/trackerdb/sqlitedriver/trackerdbV2.go +++ b/ledger/store/trackerdb/sqlitedriver/trackerdbV2.go @@ -131,6 +131,12 @@ func RunMigrations(ctx context.Context, tx *sql.Tx, params trackerdb.Params, log tu.log.Warnf("trackerDBInitialize failed to upgrade accounts database (ledger.tracker.sqlite) from schema 8 : %v", err) return } + case 9: + err = tu.upgradeDatabaseSchema9(ctx, tx) + if err != nil { + tu.log.Warnf("trackerDBInitialize failed to upgrade accounts database (ledger.tracker.sqlite) from schema 9 : %v", err) + return + } default: return trackerdb.InitParams{}, fmt.Errorf("trackerDBInitialize unable to upgrade database from schema version %d", tu.schemaVersion) } @@ -479,6 +485,18 @@ func (tu *trackerDBSchemaInitializer) upgradeDatabaseSchema8(ctx context.Context return tu.setVersion(ctx, tx, 9) } +// upgradeDatabaseSchema9 upgrades the database schema from version 8 to version 9, +// adding a new stateproofverification table. +func (tu *trackerDBSchemaInitializer) upgradeDatabaseSchema9(ctx context.Context, tx *sql.Tx) (err error) { + err = createStateProofVerificationTable(ctx, tx) + if err != nil { + return err + } + + // update version + return tu.setVersion(ctx, tx, 10) +} + func removeEmptyDirsOnSchemaUpgrade(dbDirectory string) (err error) { catchpointRootDir := filepath.Join(dbDirectory, trackerdb.CatchpointDirName) if _, err := os.Stat(catchpointRootDir); os.IsNotExist(err) { diff --git a/ledger/store/trackerdb/store.go b/ledger/store/trackerdb/store.go index 879f0cf95f..17e0e720aa 100644 --- a/ledger/store/trackerdb/store.go +++ b/ledger/store/trackerdb/store.go @@ -31,6 +31,7 @@ type BatchScope interface { MakeAccountsOptimizedWriter(hasAccounts, hasResources, hasKvPairs, hasCreatables bool) (AccountsWriter, error) ResetTransactionWarnDeadline(ctx context.Context, deadline time.Time) (prevDeadline time.Time, err error) Testing() TestBatchScope + MakeSpVerificationCtxWriter() SpVerificationCtxWriter } // SnapshotScope is the read scope to the store. @@ -38,6 +39,8 @@ type SnapshotScope interface { MakeAccountsReader() (AccountsReaderExt, error) MakeCatchpointReader() (CatchpointReader, error) MakeCatchpointPendingHashesIterator(hashCount int) CatchpointPendingHashesIter + + MakeSpVerificationCtxReader() SpVerificationCtxReader } // TransactionScope is the read/write scope to the store. @@ -53,6 +56,7 @@ type TransactionScope interface { RunMigrations(ctx context.Context, params Params, log logging.Logger, targetVersion int32) (mgr InitParams, err error) ResetTransactionWarnDeadline(ctx context.Context, deadline time.Time) (prevDeadline time.Time, err error) Testing() TestTransactionScope + MakeSpVerificationCtxReaderWriter() SpVerificationCtxReaderWriter } // BatchFn is the callback lambda used in `Batch`. diff --git a/ledger/store/trackerdb/version.go b/ledger/store/trackerdb/version.go index 42917f2620..e619d2dd55 100644 --- a/ledger/store/trackerdb/version.go +++ b/ledger/store/trackerdb/version.go @@ -19,4 +19,4 @@ package trackerdb // AccountDBVersion is the database version that this binary would know how to support and how to upgrade to. // details about the content of each of the versions can be found in the upgrade functions upgradeDatabaseSchemaXXXX // and their descriptions. -var AccountDBVersion = int32(9) +var AccountDBVersion = int32(10) diff --git a/ledger/tracker.go b/ledger/tracker.go index 2209ca8751..f03c5b8061 100644 --- a/ledger/tracker.go +++ b/ledger/tracker.go @@ -269,6 +269,15 @@ type deferredCommitContext struct { stats telemetryspec.AccountsUpdateMetrics updateStats bool + + spVerification struct { + // state proof verification deletion information + lastDeleteIndex int + earliestLastAttestedRound basics.Round + + // state proof verification commit information + commitContext []verificationCommitContext + } } func (dcc deferredCommitContext) newBase() basics.Round { diff --git a/ledger/voters.go b/ledger/voters.go index 1648e707f4..d5645ede60 100644 --- a/ledger/voters.go +++ b/ledger/voters.go @@ -20,15 +20,30 @@ import ( "fmt" "sync" - "github.com/algorand/go-algorand/stateproof" + "github.com/algorand/go-deadlock" "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/ledger/ledgercore" "github.com/algorand/go-algorand/protocol" + "github.com/algorand/go-algorand/stateproof" ) +// votersFetcher is used to provide safe access to the ledger while creating the state proof builder. Since the operation +// is being run under the ledger's commit operation, this implementation guarantees lockless access to the VotersForStateProof function. +type votersFetcher struct { + vt *votersTracker +} + +func (vf *votersFetcher) VotersForStateProof(rnd basics.Round) (*ledgercore.VotersForRound, error) { + return vf.vt.VotersForStateProof(rnd) +} + +func (vf *votersFetcher) BlockHdr(rnd basics.Round) (bookkeeping.BlockHeader, error) { + return vf.vt.l.BlockHdr(rnd) +} + // The votersTracker maintains the vector commitment for the most recent // commitments to online accounts for state proofs. // @@ -62,14 +77,18 @@ type votersTracker struct { // Thus, we maintain X in the votersForRoundCache map until we form a stateproof // for round X+StateProofVotersLookback+StateProofInterval. // - // In case state proof chain stalls this map would be bounded to StateProofMaxRecoveryIntervals + 3 + // In case state proof chain stalls this map would be bounded to StateProofMaxRecoveryIntervals + 3 in respect + // to the db round. // + 1 - since votersForRoundCache needs to contain an entry for a future state proof // + 1 - since votersForRoundCache needs to contain an entry to verify the earliest state proof // in the recovery interval. i.e. it needs to have an entry for R-StateProofMaxRecoveryIntervals-StateProofInterval // to verify R-StateProofMaxRecoveryIntervals // + 1 would only appear if the sampled round R is: interval - lookback < R < interval. // in this case, the tracker would not yet remove the old one but will create a new one for future state proof. + // Additionally, the tracker would contain an entry for every state proof interval between the latest round in the + // ledger and the db round. votersForRoundCache map[basics.Round]*ledgercore.VotersForRound + votersMu deadlock.RWMutex l ledgerForTracker onlineAccountsFetcher ledgercore.OnlineAccountsFetcher @@ -77,6 +96,11 @@ type votersTracker struct { // loadWaitGroup syncronizing the completion of the loadTree call so that we can // shutdown the tracker without leaving any running go-routines. loadWaitGroup sync.WaitGroup + + // commitListener provides a callback to call on each prepare commit. This callback receives access to the voters + // cache. + commitListener ledgercore.VotersCommitListener + commitListenerMu deadlock.RWMutex } // votersRoundForStateProofRound computes the round number whose voting participants @@ -90,9 +114,11 @@ func votersRoundForStateProofRound(stateProofRnd basics.Round, proto *config.Con } func (vt *votersTracker) loadFromDisk(l ledgerForTracker, fetcher ledgercore.OnlineAccountsFetcher, latestDbRound basics.Round) error { + vt.votersMu.Lock() vt.l = l - vt.votersForRoundCache = make(map[basics.Round]*ledgercore.VotersForRound) vt.onlineAccountsFetcher = fetcher + vt.votersForRoundCache = make(map[basics.Round]*ledgercore.VotersForRound) + vt.votersMu.Unlock() latestRoundInLedger := l.Latest() hdr, err := l.BlockHdr(latestRoundInLedger) @@ -132,8 +158,8 @@ func (vt *votersTracker) loadFromDisk(l ledgerForTracker, fetcher ledgercore.Onl func (vt *votersTracker) loadTree(hdr bookkeeping.BlockHeader) { r := hdr.Round - _, ok := vt.votersForRoundCache[r] - if ok { + _, exists := vt.getVoters(r) + if exists { // Already loaded. return } @@ -147,7 +173,7 @@ func (vt *votersTracker) loadTree(hdr bookkeeping.BlockHeader) { tr := ledgercore.MakeVotersForRound() tr.Proto = proto - vt.votersForRoundCache[r] = tr + vt.setVoters(r, tr) vt.loadWaitGroup.Add(1) go func() { @@ -174,18 +200,16 @@ func (vt *votersTracker) newBlock(hdr bookkeeping.BlockHeader) { return } - vt.removeOldVoters(hdr) - // This might be a block where we snapshot the online participants, // to eventually construct a vector commitment in a later // block. - r := uint64(hdr.Round) - if (r+proto.StateProofVotersLookback)%proto.StateProofInterval != 0 { + r := hdr.Round + if (uint64(r)+proto.StateProofVotersLookback)%proto.StateProofInterval != 0 { return } - _, ok := vt.votersForRoundCache[basics.Round(r)] - if ok { + _, exists := vt.getVoters(r) + if exists { vt.l.trackerLog().Errorf("votersTracker.newBlock: round %d already present", r) } else { vt.loadTree(hdr) @@ -193,20 +217,49 @@ func (vt *votersTracker) newBlock(hdr bookkeeping.BlockHeader) { } +func (vt *votersTracker) prepareCommit(dcc *deferredCommitContext) error { + vt.commitListenerMu.RLock() + defer vt.commitListenerMu.RUnlock() + + if vt.commitListener == nil { + return nil + } + + commitListener := vt.commitListener + vf := votersFetcher{vt: vt} + // In case the listener's function fails, we do not want to break the commit process. + // To implement this hierarchy we've decided to not include a return value in OnPrepareVoterCommit function + commitListener.OnPrepareVoterCommit(dcc.oldBase, dcc.newBase(), &vf) + + return nil +} + +func (vt *votersTracker) postCommit(dcc *deferredCommitContext) { + lastHeaderCommitted, err := vt.l.BlockHdr(dcc.newBase()) + if err != nil { + vt.l.trackerLog().Errorf("votersTracker.postCommit: could not retrieve header for round %d: %v", dcc.newBase(), err) + return + } + + // Voters older than lastHeaderCommitted.Round() - StateProofMaxRecoveryIntervals * StateProofInterval are + // guaranteed to be removed here. + vt.removeOldVoters(lastHeaderCommitted) +} + // removeOldVoters removes voters data form the tracker and allows the database to commit previous rounds. // voters would be removed if one of the two condition is met // 1 - Voters are for a round which was already been confirmed by stateproof // 2 - Voters are for a round which is older than the allowed recovery interval. -// notice that if state proof chain is delayed, votersForRoundCache will not be larger than -// StateProofMaxRecoveryIntervals + 1 -// ( In order to be able to build and verify X stateproofs back we need X + 1 voters data ) // // It is possible to optimize this function and not to travers votersForRoundCache on every round. -// Since the map is small (Usually 0 - 2 elements and up to StateProofMaxRecoveryIntervals) we decided to keep the code simple +// Since the map is small (Usually 0 - 2 elements) we decided to keep the code simple // and check for deletion in every round. func (vt *votersTracker) removeOldVoters(hdr bookkeeping.BlockHeader) { lowestStateProofRound := stateproof.GetOldestExpectedStateProof(&hdr) + vt.votersMu.Lock() + defer vt.votersMu.Unlock() + for r, tr := range vt.votersForRoundCache { commitRound := r + basics.Round(tr.Proto.StateProofVotersLookback) stateProofRound := commitRound + basics.Round(tr.Proto.StateProofInterval) @@ -225,18 +278,23 @@ func (vt *votersTracker) removeOldVoters(hdr bookkeeping.BlockHeader) { // not need any blocks, it returns base. func (vt *votersTracker) lowestRound(base basics.Round) basics.Round { minRound := base + + vt.votersMu.RLock() + defer vt.votersMu.RUnlock() + for r := range vt.votersForRoundCache { if r < minRound { minRound = r } } + return minRound } -// getVoters() returns the top online participants from round r. -func (vt *votersTracker) getVoters(r basics.Round) (*ledgercore.VotersForRound, error) { - tr, ok := vt.votersForRoundCache[r] - if !ok { +// VotersForStateProof returns the top online participants from round r. +func (vt *votersTracker) VotersForStateProof(r basics.Round) (*ledgercore.VotersForRound, error) { + tr, exists := vt.getVoters(r) + if !exists { // Not tracked: stateproofs not enabled. return nil, nil } @@ -249,3 +307,35 @@ func (vt *votersTracker) getVoters(r basics.Round) (*ledgercore.VotersForRound, return tr, nil } + +func (vt *votersTracker) registerPrepareCommitListener(commitListener ledgercore.VotersCommitListener) { + vt.commitListenerMu.Lock() + defer vt.commitListenerMu.Unlock() + + if vt.commitListener != nil { + vt.l.trackerLog().Error("votersTracker.registerPrepareCommitListener: overriding existing listener.") + } + vt.commitListener = commitListener +} + +func (vt *votersTracker) unregisterPrepareCommitListener() { + vt.commitListenerMu.Lock() + defer vt.commitListenerMu.Unlock() + + vt.commitListener = nil +} + +func (vt *votersTracker) getVoters(round basics.Round) (*ledgercore.VotersForRound, bool) { + vt.votersMu.RLock() + defer vt.votersMu.RUnlock() + + tr, ok := vt.votersForRoundCache[round] + return tr, ok +} + +func (vt *votersTracker) setVoters(round basics.Round, voters *ledgercore.VotersForRound) { + vt.votersMu.Lock() + defer vt.votersMu.Unlock() + + vt.votersForRoundCache[round] = voters +} diff --git a/ledger/voters_test.go b/ledger/voters_test.go index 892d3679c8..363f8225f6 100644 --- a/ledger/voters_test.go +++ b/ledger/voters_test.go @@ -31,12 +31,39 @@ import ( "github.com/stretchr/testify/require" ) -func addBlockToAccountsUpdate(blk bookkeeping.Block, ao *onlineAccounts, totals ledgercore.AccountTotals) { +func addBlockToAccountsUpdate(t *testing.T, blk bookkeeping.Block, ml *mockLedgerForTracker) { updates := ledgercore.MakeAccountDeltas(1) delta := ledgercore.MakeStateDelta(&blk.BlockHeader, 0, updates.Len(), 0) delta.Accts.MergeAccounts(updates) + _, totals, err := ml.trackers.accts.LatestTotals() + require.NoError(t, err) delta.Totals = totals - ao.newBlock(blk, delta) + err = ml.addMockBlock(blockEntry{block: blk}, delta) + require.NoError(t, err) + ml.trackers.newBlock(blk, delta) +} + +func addRandomBlock(t *testing.T, ml *mockLedgerForTracker) { + block := randomBlock(ml.Latest() + 1) + block.block.CurrentProtocol = protocol.ConsensusCurrentVersion + addBlockToAccountsUpdate(t, block.block, ml) +} + +func commitStateProofBlock(t *testing.T, ml *mockLedgerForTracker, stateProofNextRound basics.Round) { + var stateTracking bookkeeping.StateProofTrackingData + block := randomBlock(ml.Latest() + 1) + block.block.CurrentProtocol = protocol.ConsensusCurrentVersion + stateTracking.StateProofNextRound = stateProofNextRound + block.block.BlockHeader.StateProofTracking = make(map[protocol.StateProofType]bookkeeping.StateProofTrackingData) + block.block.BlockHeader.StateProofTracking[protocol.StateProofBasic] = stateTracking + + addBlockToAccountsUpdate(t, block.block, ml) + commitAll(t, ml) +} + +func commitAll(t *testing.T, ml *mockLedgerForTracker) { + dcc := commitSyncPartial(t, ml.trackers.acctsOnline, ml, ml.Latest()) + commitSyncPartialComplete(t, ml.trackers.acctsOnline, ml, dcc) } func checkVoters(a *require.Assertions, ao *onlineAccounts, expectedSize uint64) { @@ -95,34 +122,23 @@ func TestVoterTrackerDeleteVotersAfterStateproofConfirmed(t *testing.T) { defer ml.Close() conf := config.GetDefaultLocal() + // To cause all blocks to be committed, for easier processing by the voters tracker. + conf.MaxAcctLookback = 0 au, ao := newAcctUpdates(t, ml, conf) defer au.close() defer ao.close() - _, totals, err := au.LatestTotals() - require.NoError(t, err) - i := uint64(1) // adding blocks to the voterstracker (in order to pass the numOfIntervals*stateproofInterval we add 1) for ; i < (numOfIntervals*intervalForTest)+1; i++ { - block := randomBlock(basics.Round(i)) - block.block.CurrentProtocol = protocol.ConsensusCurrentVersion - addBlockToAccountsUpdate(block.block, ao, totals) + addRandomBlock(t, ml) } checkVoters(a, ao, numOfIntervals) a.Equal(basics.Round(intervalForTest-lookbackForTest), ao.voters.lowestRound(basics.Round(i))) - block := randomBlock(basics.Round(i)) - i++ - block.block.CurrentProtocol = protocol.ConsensusCurrentVersion - // committing stateproof that confirm the (numOfIntervals - 1)th interval - var stateTracking bookkeeping.StateProofTrackingData - stateTracking.StateProofNextRound = basics.Round((numOfIntervals - 1) * intervalForTest) - block.block.BlockHeader.StateProofTracking = make(map[protocol.StateProofType]bookkeeping.StateProofTrackingData) - block.block.BlockHeader.StateProofTracking[protocol.StateProofBasic] = stateTracking - addBlockToAccountsUpdate(block.block, ao, totals) + commitStateProofBlock(t, ml, basics.Round((numOfIntervals-1)*intervalForTest)) // the tracker should have 3 entries // - voters to confirm the numOfIntervals - 1 th interval @@ -131,12 +147,7 @@ func TestVoterTrackerDeleteVotersAfterStateproofConfirmed(t *testing.T) { checkVoters(a, ao, 3) a.Equal(basics.Round((numOfIntervals-2)*intervalForTest-lookbackForTest), ao.voters.lowestRound(basics.Round(i))) - block = randomBlock(basics.Round(i)) - block.block.CurrentProtocol = protocol.ConsensusCurrentVersion - stateTracking.StateProofNextRound = basics.Round(numOfIntervals * intervalForTest) - block.block.BlockHeader.StateProofTracking = make(map[protocol.StateProofType]bookkeeping.StateProofTrackingData) - block.block.BlockHeader.StateProofTracking[protocol.StateProofBasic] = stateTracking - addBlockToAccountsUpdate(block.block, ao, totals) + commitStateProofBlock(t, ml, basics.Round(numOfIntervals*intervalForTest)) checkVoters(a, ao, 2) a.Equal(basics.Round((numOfIntervals-1)*intervalForTest-lookbackForTest), ao.voters.lowestRound(basics.Round(i))) @@ -166,25 +177,24 @@ func TestLimitVoterTracker(t *testing.T) { defer ml.Close() conf := config.GetDefaultLocal() + // To cause all blocks to be committed, for easier processing by the voters tracker. + conf.MaxAcctLookback = 0 au, ao := newAcctUpdates(t, ml, conf) defer au.close() defer ao.close() - _, totals, err := au.LatestTotals() - require.NoError(t, err) - i := uint64(1) // since the first state proof is expected to happen on stateproofInterval*2 we would start give-up on state proofs - // after intervalForTest*(recoveryIntervalForTests+3) + // after intervalForTest*(recoveryIntervalForTests+3) are committed // should not give up on any state proof for ; i < intervalForTest*(recoveryIntervalForTests+2); i++ { - block := randomBlock(basics.Round(i)) - block.block.CurrentProtocol = protocol.ConsensusCurrentVersion - addBlockToAccountsUpdate(block.block, ao, totals) + addRandomBlock(t, ml) } + commitAll(t, ml) + // the votersForRoundCache should contains recoveryIntervalForTests+2 elements: // recoveryIntervalForTests - since this is the recovery interval // + 1 - since votersForRoundCache would contain the votersForRound for the next state proof to come @@ -194,38 +204,38 @@ func TestLimitVoterTracker(t *testing.T) { // after adding the round intervalForTest*(recoveryIntervalForTests+3)+1 we expect the voter tracker to remove voters for ; i < intervalForTest*(recoveryIntervalForTests+3)+1; i++ { - block := randomBlock(basics.Round(i)) - block.block.CurrentProtocol = protocol.ConsensusCurrentVersion - addBlockToAccountsUpdate(block.block, ao, totals) + addRandomBlock(t, ml) } + commitAll(t, ml) + checkVoters(a, ao, recoveryIntervalForTests+2) a.Equal(basics.Round(config.Consensus[protocol.ConsensusCurrentVersion].StateProofInterval*2-lookbackForTest), ao.voters.lowestRound(basics.Round(i))) // after adding the round intervalForTest*(recoveryIntervalForTests+3)+1 we expect the voter tracker to remove voters for ; i < intervalForTest*(recoveryIntervalForTests+4)+1; i++ { - block := randomBlock(basics.Round(i)) - block.block.CurrentProtocol = protocol.ConsensusCurrentVersion - addBlockToAccountsUpdate(block.block, ao, totals) + addRandomBlock(t, ml) } + + commitAll(t, ml) checkVoters(a, ao, recoveryIntervalForTests+2) a.Equal(basics.Round(config.Consensus[protocol.ConsensusCurrentVersion].StateProofInterval*3-lookbackForTest), ao.voters.lowestRound(basics.Round(i))) // if the last round of the intervalForTest has not been added to the ledger the votersTracker would // retain one more element for ; i < intervalForTest*(recoveryIntervalForTests+5); i++ { - block := randomBlock(basics.Round(i)) - block.block.CurrentProtocol = protocol.ConsensusCurrentVersion - addBlockToAccountsUpdate(block.block, ao, totals) + addRandomBlock(t, ml) } + + commitAll(t, ml) checkVoters(a, ao, recoveryIntervalForTests+3) a.Equal(basics.Round(config.Consensus[protocol.ConsensusCurrentVersion].StateProofInterval*3-lookbackForTest), ao.voters.lowestRound(basics.Round(i))) for ; i < intervalForTest*(recoveryIntervalForTests+5)+1; i++ { - block := randomBlock(basics.Round(i)) - block.block.CurrentProtocol = protocol.ConsensusCurrentVersion - addBlockToAccountsUpdate(block.block, ao, totals) + addRandomBlock(t, ml) } + + commitAll(t, ml) checkVoters(a, ao, recoveryIntervalForTests+2) a.Equal(basics.Round(config.Consensus[protocol.ConsensusCurrentVersion].StateProofInterval*4-lookbackForTest), ao.voters.lowestRound(basics.Round(i))) } @@ -257,17 +267,12 @@ func TestTopNAccountsThatHaveNoMssKeys(t *testing.T) { defer au.close() defer ao.close() - _, totals, err := au.LatestTotals() - require.NoError(t, err) - i := uint64(1) for ; i < (intervalForTest)+1; i++ { - block := randomBlock(basics.Round(i)) - block.block.CurrentProtocol = protocol.ConsensusCurrentVersion - addBlockToAccountsUpdate(block.block, ao, totals) + addRandomBlock(t, ml) } - top, err := ao.voters.getVoters(basics.Round(intervalForTest - lookbackForTest)) + top, err := ao.voters.VotersForStateProof(basics.Round(intervalForTest - lookbackForTest)) a.NoError(err) for j := 0; j < len(top.Participants); j++ { a.Equal(merklesignature.NoKeysCommitment, top.Participants[j].PK.Commitment) diff --git a/logging/telemetryspec/event.go b/logging/telemetryspec/event.go index b50ac7eab7..77a8ea1d76 100644 --- a/logging/telemetryspec/event.go +++ b/logging/telemetryspec/event.go @@ -307,6 +307,8 @@ const CatchpointGenerationEvent Event = "CatchpointGeneration" // CatchpointGenerationEventDetails is generated once a catchpoint file is being created, and provide // some statistics about that event. type CatchpointGenerationEventDetails struct { + // AccountsRound the round in which the account snapshot is taken + AccountsRound uint64 // WritingDuration is the total elapsed time it took to write the catchpoint file. WritingDuration uint64 // CPUTime is the single-core time spent waiting to the catchpoint file to be written. @@ -322,8 +324,10 @@ type CatchpointGenerationEventDetails struct { KVsCount uint64 // FileSize is the size of the catchpoint file, in bytes. FileSize uint64 - // CatchpointLabel is the catchpoint label for which the catchpoint file was generated. - CatchpointLabel string + // MerkleTrieRootHash is the merkle trie root hash represents all accounts and kvs + MerkleTrieRootHash string + // SPVerificationCtxsHash is the hash of all the state proof verification contexts in the catchpoint + SPVerificationCtxsHash string } // CatchpointRootUpdateEvent event diff --git a/node/node.go b/node/node.go index abff864d4e..0009e02128 100644 --- a/node/node.go +++ b/node/node.go @@ -326,17 +326,7 @@ func MakeFull(log logging.Logger, rootDir string, cfg config.Local, phonebookAdd node.tracer = messagetracer.NewTracer(log).Init(cfg) gossip.SetTrace(agreementParameters.Network, node.tracer) - // Delete the deprecated database file if it exists. This can be removed in future updates since this file should not exist by then. - oldCompactCertPath := filepath.Join(genesisDir, "compactcert.sqlite") - os.Remove(oldCompactCertPath) - - stateProofPathname := filepath.Join(genesisDir, config.StateProofFileName) - stateProofAccess, err := db.MakeAccessor(stateProofPathname, false, false) - if err != nil { - log.Errorf("Cannot load state proof data: %v", err) - return nil, err - } - node.stateProofWorker = stateproof.NewWorker(stateProofAccess, node.log, node.accountManager, node.ledger.Ledger, node.net, node) + node.stateProofWorker = stateproof.NewWorker(genesisDir, node.log, node.accountManager, node.ledger.Ledger, node.net, node) return node, err } @@ -428,8 +418,6 @@ func (node *AlgorandFullNode) Stop() { defer func() { node.mu.Unlock() node.waitMonitoringRoutines() - node.stateProofWorker.Shutdown() - node.stateProofWorker = nil }() node.net.ClearHandlers() @@ -439,6 +427,7 @@ func (node *AlgorandFullNode) Stop() { if node.catchpointCatchupService != nil { node.catchpointCatchupService.Stop() } else { + node.stateProofWorker.Stop() node.txHandler.Stop() node.agreementService.Shutdown() node.catchupService.Stop() @@ -1208,6 +1197,7 @@ func (node *AlgorandFullNode) SetCatchpointCatchupMode(catchpointCatchupMode boo node.waitMonitoringRoutines() }() node.net.ClearHandlers() + node.stateProofWorker.Stop() node.txHandler.Stop() node.agreementService.Shutdown() node.catchupService.Stop() @@ -1233,6 +1223,7 @@ func (node *AlgorandFullNode) SetCatchpointCatchupMode(catchpointCatchupMode boo node.blockService.Start() node.ledgerService.Start() node.txHandler.Start() + node.stateProofWorker.Start() // start indexer if idx, err := node.Indexer(); err == nil { diff --git a/protocol/hash.go b/protocol/hash.go index 26975a402f..434deaf8c3 100644 --- a/protocol/hash.go +++ b/protocol/hash.go @@ -68,6 +68,7 @@ const ( StateProofMessage HashID = "spm" StateProofPart HashID = "spp" StateProofSig HashID = "sps" + StateProofVerCtx HashID = "spv" TestHashable HashID = "TE" TxGroup HashID = "TG" diff --git a/scripts/dump_genesis.sh b/scripts/dump_genesis.sh index 3869245883..cf45dd5936 100755 --- a/scripts/dump_genesis.sh +++ b/scripts/dump_genesis.sh @@ -76,6 +76,9 @@ for LEDGER in $LEDGERS; do unfinishedcatchpoints) SORT=round ;; + stateproofverification) + SORT=lastattestedround + ;; kvstore) SORT=key ;; diff --git a/stateproof/abstractions.go b/stateproof/abstractions.go index 8527060ae9..552acd0b82 100644 --- a/stateproof/abstractions.go +++ b/stateproof/abstractions.go @@ -41,6 +41,8 @@ type Ledger interface { GenesisHash() crypto.Digest BlockHdr(basics.Round) (bookkeeping.BlockHeader, error) VotersForStateProof(basics.Round) (*ledgercore.VotersForRound, error) + RegisterVotersCommitListener(listener ledgercore.VotersCommitListener) + UnregisterVotersCommitListener() } // Network captures the aspects of the gossip network protocol that are diff --git a/stateproof/builder.go b/stateproof/builder.go index fbb6813a94..caf9c7ba08 100644 --- a/stateproof/builder.go +++ b/stateproof/builder.go @@ -20,48 +20,175 @@ import ( "context" "database/sql" "encoding/binary" + "errors" "fmt" "sort" "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto/stateproof" "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/ledger/ledgercore" "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/network" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/stateproof/verify" ) -// makeBuilderForRound not threadsafe, should be called in a lock environment -func (spw *Worker) makeBuilderForRound(rnd basics.Round) (builder, error) { - l := spw.ledger - hdr, err := l.BlockHdr(rnd) +var errVotersNotTracked = errors.New("voters not tracked for the given lookback round") + +// OnPrepareVoterCommit is a function called by the voters tracker when it's preparing to commit rnd. It gives the builder +// the chance to persist the data it needs. +func (spw *Worker) OnPrepareVoterCommit(oldBase basics.Round, newBase basics.Round, votersFetcher ledgercore.LedgerForSPBuilder) { + for rnd := oldBase + 1; rnd <= newBase; rnd++ { + header, err := votersFetcher.BlockHdr(rnd) + if err != nil { + spw.log.Errorf("OnPrepareVoterCommit(%d): could not fetch round header: %v", rnd, err) + continue + } + + proto := config.Consensus[header.CurrentProtocol] + if proto.StateProofInterval == 0 || uint64(rnd)%proto.StateProofInterval != 0 { + continue + } + + var builderExists bool + err = spw.db.Atomic(func(ctx context.Context, tx *sql.Tx) (err error) { + builderExists, err = builderExistInDB(tx, rnd) + return err + }) + if err != nil { + spw.log.Warnf("OnPrepareVoterCommit(%d): could not check builder existence, assuming it doesn't exist: %v\n", rnd, err) + } else if builderExists { + continue + } + + buildr, err := createBuilder(rnd, votersFetcher) + if err != nil { + if errors.Is(err, errVotersNotTracked) { + // There are few reasons why we might encounter a situation where we don't + // have voters for a state proof round. + // + // 1 - When state proof chain starts, the first round s.t round % proto.stateproofInterval == 0 will not + // have voters (since they are not enable). For this round we will not create a state proof. + // e.g if proto.stateproofInterval == 10, and round = 10. We skip the state proof for that round + // (since there are not voters on round 0) + // + // 2 - When a node uses fastcatchup to some round, and immediately tries to create a builder. + // Node might fail to create the builder since MaxBalLookback (in catchpoint) might not be large enough + spw.log.Warnf("OnPrepareVoterCommit(%d): %v", rnd, err) + continue + } + + spw.log.Errorf("OnPrepareVoterCommit(%d): could not create builder: %v", rnd, err) + continue + } + + // At this point, there is a possibility that the signer has already created this specific builder + // (signer created the builder after builderExistInDB was called and was fast enough to persist it). + // In this case we will rewrite the new builder + err = spw.db.Atomic(func(_ context.Context, tx *sql.Tx) error { + return persistBuilder(tx, rnd, &buildr) + }) + if err != nil { + spw.log.Errorf("OnPrepareVoterCommit(%d): could not persist builder: %v", rnd, err) + } + } +} + +// loadOrCreateBuilderWithSignatures either loads a builder from the DB or creates a new builder. +// this function fills the builder with all the available signatures +func (spw *Worker) loadOrCreateBuilderWithSignatures(rnd basics.Round) (builder, error) { + b, err := spw.loadOrCreateBuilder(rnd) if err != nil { return builder{}, err } - hdrProto := config.Consensus[hdr.CurrentProtocol] - votersRnd := rnd.SubSaturate(basics.Round(hdrProto.StateProofInterval)) - votersHdr, err := l.BlockHdr(votersRnd) + if err := spw.loadSignaturesIntoBuilder(&b); err != nil { + return builder{}, err + } + return b, nil +} + +func (spw *Worker) loadOrCreateBuilder(rnd basics.Round) (builder, error) { + var buildr builder + err := spw.db.Atomic(func(ctx context.Context, tx *sql.Tx) (err error) { + buildr, err = getBuilder(tx, rnd) + return err + }) + + if err == nil { + return buildr, nil + } + + if !errors.Is(err, sql.ErrNoRows) { + spw.log.Errorf("loadOrCreateBuilder: error while fetching builder from DB: %v", err) + } + + buildr, err = createBuilder(rnd, spw.ledger) if err != nil { return builder{}, err } - lookback := votersRnd.SubSaturate(basics.Round(hdrProto.StateProofVotersLookback)) - voters, err := l.VotersForStateProof(lookback) + err = spw.db.Atomic(func(_ context.Context, tx *sql.Tx) error { + return persistBuilder(tx, rnd, &buildr) + }) + + // We ignore persisting errors because we still want to try and use our successfully generated builder, + // even if, for some reason, persisting it failed. + if err != nil { + spw.log.Errorf("loadOrCreateBuilder(%d): failed to insert builder into database: %v", rnd, err) + } + + return buildr, nil +} + +func (spw *Worker) loadSignaturesIntoBuilder(buildr *builder) error { + var sigs []pendingSig + err := spw.db.Atomic(func(ctx context.Context, tx *sql.Tx) error { + var err2 error + sigs, err2 = getPendingSigsForRound(tx, basics.Round(buildr.Round)) + return err2 + }) + if err != nil { + return err + } + + for i := range sigs { + err = buildr.insertSig(&sigs[i], false) + if err != nil { + spw.log.Warn(err) + } + } + return nil +} + +func createBuilder(rnd basics.Round, votersFetcher ledgercore.LedgerForSPBuilder) (builder, error) { + // since this function might be invoked under tracker commit context (i.e invoked from the ledger code ), + // it is important that we do not use the ledger directly. + + hdr, err := votersFetcher.BlockHdr(rnd) if err != nil { return builder{}, err } + hdrProto := config.Consensus[hdr.CurrentProtocol] + votersRnd := rnd.SubSaturate(basics.Round(hdrProto.StateProofInterval)) + lookback := votersRnd.SubSaturate(basics.Round(hdrProto.StateProofVotersLookback)) + voters, err := votersFetcher.VotersForStateProof(lookback) + if err != nil { + return builder{}, err + } if voters == nil { - // Voters not tracked for that round. Might not be a valid - // state proof round; state proofs might not be enabled; etc. - return builder{}, fmt.Errorf("voters not tracked for lookback round %d", lookback) + return builder{}, fmt.Errorf("lookback round %d: %w", lookback, errVotersNotTracked) + } + + votersHdr, err := votersFetcher.BlockHdr(votersRnd) + if err != nil { + return builder{}, err } - msg, err := GenerateStateProofMessage(l, uint64(votersHdr.Round), hdr) + msg, err := GenerateStateProofMessage(votersFetcher, rnd) if err != nil { return builder{}, err } @@ -72,11 +199,11 @@ func (spw *Worker) makeBuilderForRound(rnd basics.Round) (builder, error) { } var res builder - res.votersHdr = votersHdr - res.voters = voters - res.message = msg + res.VotersHdr = votersHdr + res.AddrToPos = voters.AddrToPos + res.Message = msg res.Builder, err = stateproof.MakeBuilder(msg.Hash(), - uint64(hdr.Round), + uint64(rnd), provenWeight, voters.Participants, voters.Tree, @@ -89,62 +216,82 @@ func (spw *Worker) makeBuilderForRound(rnd basics.Round) (builder, error) { } func (spw *Worker) initBuilders() { - spw.mu.Lock() - defer spw.mu.Unlock() - - var roundSigs map[basics.Round][]pendingSig - err := spw.db.Atomic(func(ctx context.Context, tx *sql.Tx) (err error) { - roundSigs, err = getPendingSigs(tx) - return - }) + spw.builders = make(map[basics.Round]builder) + rnds, err := spw.getAllOnlineBuilderRounds() if err != nil { - spw.log.Warnf("initBuilders: getPendingSigs: %v", err) + spw.log.Errorf("initBuilders: failed to load rounds: %v", err) return } - for rnd, sigs := range roundSigs { + for _, rnd := range rnds { if _, ok := spw.builders[rnd]; ok { spw.log.Warnf("initBuilders: round %d already present", rnd) continue } - spw.addSigsToBuilder(sigs, rnd) + + buildr, err := spw.loadOrCreateBuilderWithSignatures(rnd) + if err != nil { + spw.log.Warnf("initBuilders: failed to load builder for round %d", rnd) + continue + } + spw.builders[rnd] = buildr } } -func (spw *Worker) addSigsToBuilder(sigs []pendingSig, rnd basics.Round) { - builderForRound, err := spw.makeBuilderForRound(rnd) +func (spw *Worker) getAllOnlineBuilderRounds() ([]basics.Round, error) { + // Some state proof databases might only contain a signature table. For that reason, when trying to create builders for possible state proof + // rounds we search the signature table and not the builder table + latest := spw.ledger.Latest() + latestHdr, err := spw.ledger.BlockHdr(latest) if err != nil { - spw.log.Warnf("addSigsToBuilder: makeBuilderForRound(%d): %v", rnd, err) - return + return nil, err + } + proto := config.Consensus[latestHdr.CurrentProtocol] + if proto.StateProofInterval == 0 { // StateProofs are not enabled yet + return nil, err } - spw.builders[rnd] = builderForRound - for _, sig := range sigs { - pos, ok := builderForRound.voters.AddrToPos[sig.signer] - if !ok { - spw.log.Warnf("addSigsToBuilder: cannot find %v in round %d", sig.signer, rnd) - continue - } + latestStateProofRound := latest.RoundDownToMultipleOf(basics.Round(proto.StateProofInterval)) + threshold := onlineBuildersThreshold(&proto, latestHdr.StateProofTracking[protocol.StateProofBasic].StateProofNextRound) - isPresent, err := builderForRound.Present(pos) - if err != nil { - spw.log.Warnf("addSigsToBuilder: failed to invoke builderForRound.Present on pos %d - %v", pos, err) - continue - } - if isPresent { - spw.log.Warnf("addSigsToBuilder: cannot add %v in round %d: position %d already added", sig.signer, rnd, pos) - continue - } + var rnds []basics.Round + err = spw.db.Atomic(func(_ context.Context, tx *sql.Tx) error { + var err error + rnds, err = getSignatureRounds(tx, threshold, latestStateProofRound) + return err + }) - if err := builderForRound.IsValid(pos, &sig.sig, false); err != nil { - spw.log.Warnf("addSigsToBuilder: cannot add %v in round %d: %v", sig.signer, rnd, err) - continue - } - if err := builderForRound.Add(pos, sig.sig); err != nil { - spw.log.Warnf("addSigsToBuilder: error while adding sig. inner error: %v", err) - continue - } + return rnds, err +} + +var errAddressNotInVoters = errors.New("cannot find address in builder") // Address was not a part of the voters for this StateProof (top N accounts) +var errFailedToAddSigAtPos = errors.New("could not add signature to builder") // Position was out of array bounds or signature already present +var errSigAlreadyPresentAtPos = errors.New("signature already present at this position") // Signature already present at this position +var errSignatureVerification = errors.New("error while verifying signature") // Signature failed cryptographic verification + +func (b *builder) insertSig(s *pendingSig, verify bool) error { + rnd := b.Round + pos, ok := b.AddrToPos[s.signer] + if !ok { + return fmt.Errorf("insertSig: %w (%v not in participants for round %d)", errAddressNotInVoters, s.signer, rnd) + } + + isPresent, err := b.Present(pos) + if err != nil { + return fmt.Errorf("insertSig: %w (failed to invoke builderForRound.Present on pos %d - %v)", errFailedToAddSigAtPos, pos, err) + } + if isPresent { + return errSigAlreadyPresentAtPos + } + + if err = b.IsValid(pos, &s.sig, verify); err != nil { + return fmt.Errorf("insertSig: %w (cannot add %v in round %d: %v)", errSignatureVerification, s.signer, rnd, err) + } + if err = b.Add(pos, s.sig); err != nil { + return fmt.Errorf("insertSig: %w (%v)", errFailedToAddSigAtPos, err) } + + return nil } func (spw *Worker) handleSigMessage(msg network.IncomingMessage) network.OutgoingMessage { @@ -163,6 +310,18 @@ func (spw *Worker) handleSigMessage(msg network.IncomingMessage) network.Outgoin return network.OutgoingMessage{Action: fwd} } +// meetsBroadcastPolicy verifies that the signature's round is either under the threshold round or equal to the +// latest StateProof round. +// This signature filtering is only relevant when the StateProof chain is stalled and many signatures may be spammed. +func (spw *Worker) meetsBroadcastPolicy(sfa sigFromAddr, latestRound basics.Round, proto *config.ConsensusParams, stateProofNextRound basics.Round) bool { + if sfa.Round <= onlineBuildersThreshold(proto, stateProofNextRound) { + return true + } + + latestStateProofRound := latestRound.RoundDownToMultipleOf(basics.Round(proto.StateProofInterval)) + return sfa.Round == latestStateProofRound +} + // handleSig adds a signature to the pending in-memory state proof provers (builders). This function is // also responsible for making sure that the signature is valid, and not duplicated. // if a signature passes all verification it is written into the database. @@ -170,6 +329,11 @@ func (spw *Worker) handleSig(sfa sigFromAddr, sender network.Peer) (network.Forw spw.mu.Lock() defer spw.mu.Unlock() + // might happen if the state proof worker is stopping + if spw.builders == nil { + return network.Ignore, fmt.Errorf("handleSig: no builders loaded") + } + builderForRound, ok := spw.builders[sfa.Round] if !ok { latest := spw.ledger.Latest() @@ -178,16 +342,15 @@ func (spw *Worker) handleSig(sfa sigFromAddr, sender network.Peer) (network.Forw return network.Ignore, err } - if sfa.Round < latestHdr.StateProofTracking[protocol.StateProofBasic].StateProofNextRound { + stateProofNextRound := latestHdr.StateProofTracking[protocol.StateProofBasic].StateProofNextRound + + if sfa.Round < stateProofNextRound { // Already have a complete state proof in ledger. // Ignore this sig. return network.Ignore, nil } - // The sig should be for a round which is a multiple of StateProofInterval - // using the latestHdr protocol, since changing StateProofInterval is not supported proto := config.Consensus[latestHdr.CurrentProtocol] - // proto.StateProofInterval is not expected to be 0 after passing StateProofNextRound // checking anyway, otherwise will panic if proto.StateProofInterval == 0 { @@ -201,7 +364,19 @@ func (spw *Worker) handleSig(sfa sigFromAddr, sender network.Peer) (network.Forw sfa.Round, proto.StateProofInterval) } - builderForRound, err = spw.makeBuilderForRound(sfa.Round) + if sfa.Round > latest { + // avoiding an inspection in DB in case we haven't reached the round. + // Avoiding disconnecting the peer, since it might've been sent to this node while it recovers. + return network.Ignore, fmt.Errorf("handleSig: latest round is smaller than given round %d", sfa.Round) + } + + // We want to save the signature in the DB if we know we generated it. However, if the signature's source is + // external, we only want to process it if we know for sure it meets our broadcast policy. + if sender != nil && !spw.meetsBroadcastPolicy(sfa, latestHdr.Round, &proto, stateProofNextRound) { + return network.Ignore, nil + } + + builderForRound, err = spw.loadOrCreateBuilderWithSignatures(sfa.Round) if err != nil { // Should not disconnect this peer, since this is a fault of the relay // The peer could have other signatures what the relay is interested in @@ -211,36 +386,30 @@ func (spw *Worker) handleSig(sfa sigFromAddr, sender network.Peer) (network.Forw spw.log.Infof("spw.handleSig: starts gathering signatures for round %d", sfa.Round) } - pos, ok := builderForRound.voters.AddrToPos[sfa.SignerAddress] - if !ok { - return network.Disconnect, fmt.Errorf("handleSig: %v not in participants for %d", sfa.SignerAddress, sfa.Round) + sig := pendingSig{ + signer: sfa.SignerAddress, + sig: sfa.Sig, + fromThisNode: sender == nil, } - - if isPresent, err := builderForRound.Present(pos); err != nil || isPresent { - // Signature already part of the builderForRound, ignore. + err := builderForRound.insertSig(&sig, true) + if errors.Is(err, errSigAlreadyPresentAtPos) { + // Safe to ignore this error as it means we already have a valid signature for this address return network.Ignore, nil } - - if err := builderForRound.IsValid(pos, &sfa.Sig, true); err != nil { + if errors.Is(err, errAddressNotInVoters) || errors.Is(err, errSignatureVerification) { return network.Disconnect, err } + if err != nil { // errFailedToAddSigAtPos and fallback in case of unknown error + return network.Ignore, err + } - err := spw.db.Atomic(func(ctx context.Context, tx *sql.Tx) error { - return addPendingSig(tx, sfa.Round, pendingSig{ - signer: sfa.SignerAddress, - sig: sfa.Sig, - fromThisNode: sender == nil, - }) + err = spw.db.Atomic(func(ctx context.Context, tx *sql.Tx) error { + return addPendingSig(tx, sfa.Round, sig) }) if err != nil { return network.Ignore, err } - // validated that we can add the sig previously. - if err := builderForRound.Add(pos, sfa.Sig); err != nil { - // only Present called from Add returns an error which is already - // passed in the call above. - return network.Ignore, err - } + return network.Broadcast, nil } @@ -252,48 +421,48 @@ func (spw *Worker) builder(latest basics.Round) { // will settle for a larger proof. New blocks also tell us // if a state proof has been committed, so that we can stop trying // to build it. + + nextBroadcastRnd := latest for { spw.tryBroadcast() - nextrnd := latest + 1 select { case <-spw.ctx.Done(): spw.wg.Done() return - case <-spw.ledger.Wait(nextrnd): + case <-spw.ledger.Wait(nextBroadcastRnd + 1): // Continue on } - // See if any new state proofs were formed, according to - // the new block, which would mean we can clean up some builders. - hdr, err := spw.ledger.BlockHdr(nextrnd) + newLatest := spw.ledger.Latest() + newLatestHdr, err := spw.ledger.BlockHdr(newLatest) + if err != nil { - spw.log.Warnf("spw.builder: BlockHdr(%d): %v", nextrnd, err) + spw.log.Warnf("spw.builder: BlockHdr(%d): %v", newLatest, err) continue } - spw.deleteOldSigs(&hdr) - spw.deleteOldBuilders(&hdr) + proto := config.Consensus[newLatestHdr.CurrentProtocol] + stateProofNextRound := newLatestHdr.StateProofTracking[protocol.StateProofBasic].StateProofNextRound + + spw.deleteBuildData(&proto, stateProofNextRound) // Broadcast signatures based on the previous block(s) that // were agreed upon. This ensures that, if we send a signature // for block R, nodes will have already verified block R, because // block R+1 has been formed. - proto := config.Consensus[hdr.CurrentProtocol] - newLatest := spw.ledger.Latest() - for r := latest; r < newLatest; r++ { + for r := nextBroadcastRnd; r < newLatest; r++ { // Wait for the signer to catch up; mostly relevant in tests. spw.waitForSignature(r) - - spw.broadcastSigs(r, proto) + spw.broadcastSigs(r, stateProofNextRound, proto) } - latest = newLatest + nextBroadcastRnd = newLatest } } // broadcastSigs periodically broadcasts pending signatures for rounds -// that have not been able to form a state proof. +// that have not been able to form a state proof, with correlation to builderCacheLength. // // Signature re-broadcasting happens in periods of proto.StateProofInterval // rounds. @@ -306,7 +475,7 @@ func (spw *Worker) builder(latest basics.Round) { // // The broadcast schedule is randomized by the address of the block signer, // for load-balancing over time. -func (spw *Worker) broadcastSigs(brnd basics.Round, proto config.ConsensusParams) { +func (spw *Worker) broadcastSigs(brnd basics.Round, stateProofNextRound basics.Round, proto config.ConsensusParams) { if proto.StateProofInterval == 0 { return } @@ -314,12 +483,14 @@ func (spw *Worker) broadcastSigs(brnd basics.Round, proto config.ConsensusParams spw.mu.Lock() defer spw.mu.Unlock() + latestStateProofRound := brnd.RoundDownToMultipleOf(basics.Round(proto.StateProofInterval)) + threshold := onlineBuildersThreshold(&proto, stateProofNextRound) var roundSigs map[basics.Round][]pendingSig err := spw.db.Atomic(func(ctx context.Context, tx *sql.Tx) (err error) { if brnd%basics.Round(proto.StateProofInterval) < basics.Round(proto.StateProofInterval/2) { - roundSigs, err = getPendingSigsFromThisNode(tx) + roundSigs, err = getPendingSigs(tx, threshold, latestStateProofRound, true) } else { - roundSigs, err = getPendingSigs(tx) + roundSigs, err = getPendingSigs(tx, threshold, latestStateProofRound, false) } return }) @@ -357,25 +528,96 @@ func (spw *Worker) broadcastSigs(brnd basics.Round, proto config.ConsensusParams } } -func (spw *Worker) deleteOldSigs(currentHdr *bookkeeping.BlockHeader) { - oldestRoundToRemove := GetOldestExpectedStateProof(currentHdr) +func (spw *Worker) deleteBuildData(proto *config.ConsensusParams, stateProofNextRound basics.Round) { + if proto.StateProofInterval == 0 || stateProofNextRound == 0 { + return + } + + // Delete from memory (already stored on disk). Practically, There are two scenarios where builders gets removed from memory + // 1. When a state proof is committed, the earliest will get removed and later on will be removed from disk. + // (when calling deleteStaleBuilders) + // 2. If state proofs are stalled, and consensus is moving forward, a new latest builder will be created and + // the older builder will be swapped out from memory. (i.e will be removed from memory but stays on disk). + spw.trimBuildersCache(proto, stateProofNextRound) + + if spw.lastCleanupRound == stateProofNextRound { + return + } + + // Delete from disk (database) + spw.deleteStaleSigs(stateProofNextRound) + spw.deleteStaleKeys(stateProofNextRound) + spw.deleteStaleBuilders(stateProofNextRound) + spw.lastCleanupRound = stateProofNextRound +} +func (spw *Worker) deleteStaleSigs(retainRound basics.Round) { err := spw.db.Atomic(func(ctx context.Context, tx *sql.Tx) error { - return deletePendingSigsBeforeRound(tx, oldestRoundToRemove) + return deletePendingSigsBeforeRound(tx, retainRound) }) if err != nil { - spw.log.Warnf("deletePendingSigsBeforeRound(%d): %v", oldestRoundToRemove, err) + spw.log.Warnf("deleteStaleSigs(%d): %v", retainRound, err) } } -func (spw *Worker) deleteOldBuilders(currentHdr *bookkeeping.BlockHeader) { - oldestRoundToRemove := GetOldestExpectedStateProof(currentHdr) +func (spw *Worker) deleteStaleKeys(retainRound basics.Round) { + keys := spw.accts.StateProofKeys(retainRound) + for _, key := range keys { + firstRoundAtKeyLifeTime, err := key.StateProofSecrets.FirstRoundInKeyLifetime() + if err != nil { + spw.log.Errorf("deleteStaleKeys: could not calculate keylifetime for account %v on round %d: %v", key.ParticipationID, firstRoundAtKeyLifeTime, err) + continue + } + err = spw.accts.DeleteStateProofKey(key.ParticipationID, basics.Round(firstRoundAtKeyLifeTime)) + if err != nil { + spw.log.Warnf("deleteStaleKeys: could not remove key for account %v on round %d: %v", key.ParticipationID, firstRoundAtKeyLifeTime, err) + } + } +} +func (spw *Worker) deleteStaleBuilders(retainRound basics.Round) { + err := spw.db.Atomic(func(ctx context.Context, tx *sql.Tx) error { + return deleteBuilders(tx, retainRound) + }) + if err != nil { + spw.log.Warnf("deleteOldBuilders: failed to delete builders from database: %v", err) + } +} + +// onlineBuildersThreshold returns the highest round for which the builder should be stored in memory (cache). +// This is mostly relevant in case the StateProof chain is stalled. +// The threshold is also used to limit the StateProof signatures broadcasted over the network. +func onlineBuildersThreshold(proto *config.ConsensusParams, stateProofNextRound basics.Round) basics.Round { + /* + builderCacheLength - 2: + let buildersCacheLength <- 5, StateProofNextRound <- 1024, LatestRound <- 4096 + threshold = StateProofNextRound + 3 * StateProofInterval (for a total of 4 early StateProofs) + the 5th builder in the cache is reserved for the LatestRound stateproof. + */ + threshold := stateProofNextRound + basics.Round((buildersCacheLength-2)*proto.StateProofInterval) + return threshold +} + +// trimBuildersCache reduces the number of builders stored in memory to X earliest as well as 1 latest, to an overall amount of X+1 builders +func (spw *Worker) trimBuildersCache(proto *config.ConsensusParams, stateProofNextRound basics.Round) { spw.mu.Lock() defer spw.mu.Unlock() + var maxBuilderRound basics.Round for rnd := range spw.builders { - if rnd < oldestRoundToRemove { + if rnd > maxBuilderRound { + maxBuilderRound = rnd + } + } + + threshold := onlineBuildersThreshold(proto, stateProofNextRound) + /* + For example, builders currently stored in memory are for these rounds: + [..., StateProofNextRound-256, StateProofNextRound, StateProofNextRound+256, ..., Threshold, ..., MaxBuilderRound] + [StateProofNextRound, ..., Threshold, MaxBuilderRound] <- Only builders that should be stored in memory after trim + */ + for rnd := range spw.builders { + if rnd < stateProofNextRound || (threshold < rnd && rnd < maxBuilderRound) { delete(spw.builders, rnd) } } @@ -391,24 +633,32 @@ func (spw *Worker) tryBroadcast() { } sort.Slice(sortedRounds, func(i, j int) bool { return sortedRounds[i] < sortedRounds[j] }) - for _, rnd := range sortedRounds { // Iterate over the builders in a sequential manner + for _, rnd := range sortedRounds { + // Iterate over the builders in a sequential manner. If the earlist state proof is not ready/rejected + // it won't be possible to add a later one. For that reason, we break the loop b := spw.builders[rnd] firstValid := spw.ledger.Latest() - acceptableWeight := verify.AcceptableStateProofWeight(&b.votersHdr, firstValid, logging.Base()) + acceptableWeight := verify.AcceptableStateProofWeight(&b.VotersHdr, firstValid, logging.Base()) if b.SignedWeight() < acceptableWeight { // Haven't signed enough to build the state proof at this time.. - continue + break } if !b.Ready() { // Haven't gotten enough signatures to get past ProvenWeight - continue + break } sp, err := b.Build() if err != nil { spw.log.Warnf("spw.tryBroadcast: building state proof for %d failed: %v", rnd, err) - continue + break + } + + latestHeader, err := spw.ledger.BlockHdr(firstValid) + if err != nil { + spw.log.Warnf("spw.tryBroadcast: could not fetch block header for round %d failed: %v", firstValid, err) + break } spw.log.Infof("spw.tryBroadcast: building state proof transaction for round %d", rnd) @@ -416,11 +666,11 @@ func (spw *Worker) tryBroadcast() { stxn.Txn.Type = protocol.StateProofTx stxn.Txn.Sender = transactions.StateProofSender stxn.Txn.FirstValid = firstValid - stxn.Txn.LastValid = firstValid + basics.Round(b.voters.Proto.MaxTxnLife) + stxn.Txn.LastValid = firstValid + basics.Round(config.Consensus[latestHeader.CurrentProtocol].MaxTxnLife) stxn.Txn.GenesisHash = spw.ledger.GenesisHash() stxn.Txn.StateProofTxnFields.StateProofType = protocol.StateProofBasic stxn.Txn.StateProofTxnFields.StateProof = *sp - stxn.Txn.StateProofTxnFields.Message = b.message + stxn.Txn.StateProofTxnFields.Message = b.Message err = spw.txnSender.BroadcastInternalSignedTxGroup([]transactions.SignedTxn{stxn}) if err != nil { spw.log.Warnf("spw.tryBroadcast: broadcasting state proof txn for %d: %v", rnd, err) @@ -432,9 +682,9 @@ func (spw *Worker) tryBroadcast() { } func (spw *Worker) invokeBuilder(r basics.Round) { - spw.mu.Lock() + spw.signedMu.Lock() spw.signed = r - spw.mu.Unlock() + spw.signedMu.Unlock() select { case spw.signedCh <- struct{}{}: @@ -443,8 +693,8 @@ func (spw *Worker) invokeBuilder(r basics.Round) { } func (spw *Worker) lastSignedBlock() basics.Round { - spw.mu.Lock() - defer spw.mu.Unlock() + spw.signedMu.RLock() + defer spw.signedMu.RUnlock() return spw.signed } diff --git a/stateproof/db.go b/stateproof/db.go index b1883f5916..31674514bc 100644 --- a/stateproof/db.go +++ b/stateproof/db.go @@ -17,15 +17,18 @@ package stateproof import ( + "context" "database/sql" "fmt" "github.com/algorand/go-algorand/crypto/merklesignature" "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/data/stateproofmsg" "github.com/algorand/go-algorand/protocol" + "github.com/algorand/go-algorand/util/db" ) -var schema = []string{ +const ( // sigs tracks signatures used to build a state proofs, for // rounds that have not formed a state proofs yet. // @@ -35,33 +38,68 @@ var schema = []string{ // // Signatures produced by this node are special because we broadcast // them early; other signatures are retransmitted later on. - `CREATE TABLE IF NOT EXISTS sigs ( + createSigsTable = `CREATE TABLE IF NOT EXISTS sigs ( sprnd integer, signer blob, sig blob, from_this_node integer, - UNIQUE (sprnd, signer))`, + UNIQUE (sprnd, signer))` - `CREATE INDEX IF NOT EXISTS sigs_from_this_node ON sigs (from_this_node)`, + createSigsIdx = `CREATE INDEX IF NOT EXISTS sigs_from_this_node ON sigs (from_this_node)` + + // builders table stored a serialization of each BuilderForRound data, without the sigs (stored separately) + createBuildersTable = `CREATE TABLE IF NOT EXISTS builders ( + round INTEGER PRIMARY KEY NOT NULL, + builder BLOB NOT NULL + )` + + insertOrReplaceBuilderForRound = `INSERT OR REPLACE INTO builders (round,builder) VALUES (?,?)` + + selectBuilderForRound = `SELECT builder FROM builders WHERE round=?` + + deleteBuilderForRound = `DELETE FROM builders WHERE round (*) MarshalMsg +// |-----> (*) CanMarshalMsg +// |-----> (*) UnmarshalMsg +// |-----> (*) CanUnmarshalMsg +// |-----> (*) Msgsize +// |-----> (*) MsgIsZero +// // sigFromAddr // |-----> (*) MarshalMsg // |-----> (*) CanMarshalMsg @@ -16,6 +28,292 @@ import ( // |-----> (*) MsgIsZero // +// MarshalMsg implements msgp.Marshaler +func (z *builder) MarshalMsg(b []byte) (o []byte) { + o = msgp.Require(b, z.Msgsize()) + // omitempty: check for empty values + zb0003Len := uint32(4) + var zb0003Mask uint8 /* 5 bits */ + if len((*z).AddrToPos) == 0 { + zb0003Len-- + zb0003Mask |= 0x2 + } + if (*z).Builder == nil { + zb0003Len-- + zb0003Mask |= 0x4 + } + if (*z).VotersHdr.MsgIsZero() { + zb0003Len-- + zb0003Mask |= 0x8 + } + if (*z).Message.MsgIsZero() { + zb0003Len-- + zb0003Mask |= 0x10 + } + // variable map header, size zb0003Len + o = append(o, 0x80|uint8(zb0003Len)) + if zb0003Len != 0 { + if (zb0003Mask & 0x2) == 0 { // if not empty + // string "addr" + o = append(o, 0xa4, 0x61, 0x64, 0x64, 0x72) + if (*z).AddrToPos == nil { + o = msgp.AppendNil(o) + } else { + o = msgp.AppendMapHeader(o, uint32(len((*z).AddrToPos))) + } + zb0001_keys := make([]Address, 0, len((*z).AddrToPos)) + for zb0001 := range (*z).AddrToPos { + zb0001_keys = append(zb0001_keys, zb0001) + } + sort.Sort(SortAddress(zb0001_keys)) + for _, zb0001 := range zb0001_keys { + zb0002 := (*z).AddrToPos[zb0001] + _ = zb0002 + o = zb0001.MarshalMsg(o) + o = msgp.AppendUint64(o, zb0002) + } + } + if (zb0003Mask & 0x4) == 0 { // if not empty + // string "bldr" + o = append(o, 0xa4, 0x62, 0x6c, 0x64, 0x72) + if (*z).Builder == nil { + o = msgp.AppendNil(o) + } else { + o = (*z).Builder.MarshalMsg(o) + } + } + if (zb0003Mask & 0x8) == 0 { // if not empty + // string "hdr" + o = append(o, 0xa3, 0x68, 0x64, 0x72) + o = (*z).VotersHdr.MarshalMsg(o) + } + if (zb0003Mask & 0x10) == 0 { // if not empty + // string "msg" + o = append(o, 0xa3, 0x6d, 0x73, 0x67) + o = (*z).Message.MarshalMsg(o) + } + } + return +} + +func (_ *builder) CanMarshalMsg(z interface{}) bool { + _, ok := (z).(*builder) + return ok +} + +// UnmarshalMsg implements msgp.Unmarshaler +func (z *builder) UnmarshalMsg(bts []byte) (o []byte, err error) { + var field []byte + _ = field + var zb0003 int + var zb0004 bool + zb0003, zb0004, bts, err = msgp.ReadMapHeaderBytes(bts) + if _, ok := err.(msgp.TypeError); ok { + zb0003, zb0004, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + if zb0003 > 0 { + zb0003-- + if msgp.IsNil(bts) { + bts, err = msgp.ReadNilBytes(bts) + if err != nil { + return + } + (*z).Builder = nil + } else { + if (*z).Builder == nil { + (*z).Builder = new(stateproof.Builder) + } + bts, err = (*z).Builder.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Builder") + return + } + } + } + if zb0003 > 0 { + zb0003-- + var zb0005 int + var zb0006 bool + zb0005, zb0006, bts, err = msgp.ReadMapHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "AddrToPos") + return + } + if zb0005 > stateproof.VotersAllocBound { + err = msgp.ErrOverflow(uint64(zb0005), uint64(stateproof.VotersAllocBound)) + err = msgp.WrapError(err, "struct-from-array", "AddrToPos") + return + } + if zb0006 { + (*z).AddrToPos = nil + } else if (*z).AddrToPos == nil { + (*z).AddrToPos = make(map[Address]uint64, zb0005) + } + for zb0005 > 0 { + var zb0001 Address + var zb0002 uint64 + zb0005-- + bts, err = zb0001.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "AddrToPos") + return + } + zb0002, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "AddrToPos", zb0001) + return + } + (*z).AddrToPos[zb0001] = zb0002 + } + } + if zb0003 > 0 { + zb0003-- + bts, err = (*z).VotersHdr.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "VotersHdr") + return + } + } + if zb0003 > 0 { + zb0003-- + bts, err = (*z).Message.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Message") + return + } + } + if zb0003 > 0 { + err = msgp.ErrTooManyArrayFields(zb0003) + if err != nil { + err = msgp.WrapError(err, "struct-from-array") + return + } + } + } else { + if err != nil { + err = msgp.WrapError(err) + return + } + if zb0004 { + (*z) = builder{} + } + for zb0003 > 0 { + zb0003-- + field, bts, err = msgp.ReadMapKeyZC(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + switch string(field) { + case "bldr": + if msgp.IsNil(bts) { + bts, err = msgp.ReadNilBytes(bts) + if err != nil { + return + } + (*z).Builder = nil + } else { + if (*z).Builder == nil { + (*z).Builder = new(stateproof.Builder) + } + bts, err = (*z).Builder.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "Builder") + return + } + } + case "addr": + var zb0007 int + var zb0008 bool + zb0007, zb0008, bts, err = msgp.ReadMapHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "AddrToPos") + return + } + if zb0007 > stateproof.VotersAllocBound { + err = msgp.ErrOverflow(uint64(zb0007), uint64(stateproof.VotersAllocBound)) + err = msgp.WrapError(err, "AddrToPos") + return + } + if zb0008 { + (*z).AddrToPos = nil + } else if (*z).AddrToPos == nil { + (*z).AddrToPos = make(map[Address]uint64, zb0007) + } + for zb0007 > 0 { + var zb0001 Address + var zb0002 uint64 + zb0007-- + bts, err = zb0001.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "AddrToPos") + return + } + zb0002, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "AddrToPos", zb0001) + return + } + (*z).AddrToPos[zb0001] = zb0002 + } + case "hdr": + bts, err = (*z).VotersHdr.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "VotersHdr") + return + } + case "msg": + bts, err = (*z).Message.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "Message") + return + } + default: + err = msgp.ErrNoField(string(field)) + if err != nil { + err = msgp.WrapError(err) + return + } + } + } + } + o = bts + return +} + +func (_ *builder) CanUnmarshalMsg(z interface{}) bool { + _, ok := (z).(*builder) + return ok +} + +// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message +func (z *builder) Msgsize() (s int) { + s = 1 + 5 + if (*z).Builder == nil { + s += msgp.NilSize + } else { + s += (*z).Builder.Msgsize() + } + s += 5 + msgp.MapHeaderSize + if (*z).AddrToPos != nil { + for zb0001, zb0002 := range (*z).AddrToPos { + _ = zb0001 + _ = zb0002 + s += 0 + zb0001.Msgsize() + msgp.Uint64Size + } + } + s += 4 + (*z).VotersHdr.Msgsize() + 4 + (*z).Message.Msgsize() + return +} + +// MsgIsZero returns whether this is a zero value +func (z *builder) MsgIsZero() bool { + return ((*z).Builder == nil) && (len((*z).AddrToPos) == 0) && ((*z).VotersHdr.MsgIsZero()) && ((*z).Message.MsgIsZero()) +} + // MarshalMsg implements msgp.Marshaler func (z *sigFromAddr) MarshalMsg(b []byte) (o []byte) { o = msgp.Require(b, z.Msgsize()) diff --git a/stateproof/msgp_gen_test.go b/stateproof/msgp_gen_test.go index ed1bd8206c..ed4b196b32 100644 --- a/stateproof/msgp_gen_test.go +++ b/stateproof/msgp_gen_test.go @@ -14,6 +14,66 @@ import ( "github.com/algorand/go-algorand/test/partitiontest" ) +func TestMarshalUnmarshalbuilder(t *testing.T) { + partitiontest.PartitionTest(t) + v := builder{} + 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 TestRandomizedEncodingbuilder(t *testing.T) { + protocol.RunEncodingTest(t, &builder{}) +} + +func BenchmarkMarshalMsgbuilder(b *testing.B) { + v := builder{} + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + v.MarshalMsg(nil) + } +} + +func BenchmarkAppendMsgbuilder(b *testing.B) { + v := builder{} + 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 BenchmarkUnmarshalbuilder(b *testing.B) { + v := builder{} + bts := v.MarshalMsg(nil) + b.ReportAllocs() + b.SetBytes(int64(len(bts))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := v.UnmarshalMsg(bts) + if err != nil { + b.Fatal(err) + } + } +} + func TestMarshalUnmarshalsigFromAddr(t *testing.T) { partitiontest.PartitionTest(t) v := sigFromAddr{} diff --git a/stateproof/signer.go b/stateproof/signer.go index 5c762720a2..c5a0d153c5 100644 --- a/stateproof/signer.go +++ b/stateproof/signer.go @@ -17,13 +17,16 @@ package stateproof import ( + "context" + "database/sql" + "errors" "time" "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto/merklesignature" "github.com/algorand/go-algorand/data/account" "github.com/algorand/go-algorand/data/basics" - "github.com/algorand/go-algorand/data/bookkeeping" + "github.com/algorand/go-algorand/data/stateproofmsg" "github.com/algorand/go-algorand/protocol" ) @@ -43,14 +46,7 @@ func (spw *Worker) signer(latest basics.Round) { for { // Start signing StateProofs from nextRnd onwards select { case <-spw.ledger.Wait(nextRnd): - hdr, err := spw.ledger.BlockHdr(nextRnd) - if err != nil { - spw.log.Warnf("spw.signer(): BlockHdr(next %d): %v", nextRnd, err) - time.Sleep(1 * time.Second) - nextRnd = spw.nextStateProofRound(spw.ledger.Latest()) - continue - } - spw.signStateProof(hdr) + spw.signStateProof(nextRnd) spw.invokeBuilder(nextRnd) nextRnd++ @@ -84,94 +80,114 @@ func (spw *Worker) nextStateProofRound(latest basics.Round) basics.Round { return nextrnd } -func (spw *Worker) signStateProof(hdr bookkeeping.BlockHeader) { - proto := config.Consensus[hdr.CurrentProtocol] +func (spw *Worker) signStateProof(round basics.Round) { + proto, err := spw.getProto(round) + if err != nil { + spw.log.Warnf("spw.signStateProof(%d): getProto: %v", round, err) + return + } + if proto.StateProofInterval == 0 { return } // Only sign blocks that are a multiple of StateProofInterval. - if hdr.Round%basics.Round(proto.StateProofInterval) != 0 { + if round%basics.Round(proto.StateProofInterval) != 0 { return } - keys := spw.accts.StateProofKeys(hdr.Round) + keys := spw.accts.StateProofKeys(round) if len(keys) == 0 { // No keys, nothing to do. return } - // votersRound is the round containing the merkle root commitment - // for the voters that are going to sign this block. - votersRound := hdr.Round.SubSaturate(basics.Round(proto.StateProofInterval)) - votersHdr, err := spw.ledger.BlockHdr(votersRound) + stateProofMessage, err := spw.getStateProofMessage(round) if err != nil { - spw.log.Warnf("spw.signBlock(%d): BlockHdr(%d): %v", hdr.Round, votersRound, err) + spw.log.Warnf("spw.signStateProof(%d): getStateProofMessage: %v", round, err) return } - if votersHdr.StateProofTracking[protocol.StateProofBasic].StateProofVotersCommitment.IsEmpty() { - // No voter commitment, perhaps because state proofs were - // just enabled. - return + spw.signStateProofMessage(&stateProofMessage, round, keys) +} + +func (spw *Worker) getProto(round basics.Round) (*config.ConsensusParams, error) { + protoHdr, err := spw.ledger.BlockHdr(round) + if err != nil { + // IMPORTANT: This doesn't support modification of the state proof interval at the moment. Actually supporting + // it will probably require using (and slightly modifying) the stateProofVerificationTracker. + latestRound := spw.ledger.Latest() + protoHdr, err = spw.ledger.BlockHdr(latestRound) + if err != nil { + return nil, err + } } - sigs := make([]sigFromAddr, 0, len(keys)) - ids := make([]account.ParticipationID, 0, len(keys)) - usedSigners := make([]*merklesignature.Signer, 0, len(keys)) + proto := config.Consensus[protoHdr.CurrentProtocol] + return &proto, nil +} - stateproofMessage, err := GenerateStateProofMessage(spw.ledger, uint64(votersHdr.Round), hdr) - if err != nil { - spw.log.Warnf("spw.signBlock(%d): GenerateStateProofMessage: %v", hdr.Round, err) - return +func (spw *Worker) getStateProofMessage(round basics.Round) (stateproofmsg.Message, error) { + var msg stateproofmsg.Message + err := spw.db.Atomic(func(ctx context.Context, tx *sql.Tx) (err error) { + msg, err = getMessage(tx, round) + return err + }) + if err == nil { + return msg, nil } - hashedStateproofMessage := stateproofMessage.Hash() + if !errors.Is(err, sql.ErrNoRows) { + spw.log.Errorf("getStateProofMessage(%d): error while fetching builder from DB: %v", round, err) + } + + return GenerateStateProofMessage(spw.ledger, round) +} + +func (spw *Worker) signStateProofMessage(message *stateproofmsg.Message, round basics.Round, keys []account.StateProofSecretsForRound) { + hashedStateproofMessage := message.Hash() + + sigs := make([]sigFromAddr, 0, len(keys)) for _, key := range keys { - if key.FirstValid > hdr.Round || hdr.Round > key.LastValid { + if key.FirstValid > round || round > key.LastValid { continue } if key.StateProofSecrets == nil { - spw.log.Warnf("spw.signBlock(%d): empty state proof secrets for round", hdr.Round) + spw.log.Warnf("spw.signStateProofMessage(%d): empty state proof secrets for round", round) + continue + } + + var exists bool + err := spw.db.Atomic(func(ctx context.Context, tx *sql.Tx) (err error) { + exists, err = sigExistsInDB(tx, round, key.Account) + return err + }) + if err != nil { + spw.log.Warnf("spw.signStateProofMessage(%d): couldn't figure if sig exists in DB: %v", round, err) + } else if exists { continue } sig, err := key.StateProofSecrets.SignBytes(hashedStateproofMessage[:]) if err != nil { - spw.log.Warnf("spw.signBlock(%d): StateProofSecrets.Sign: %v", hdr.Round, err) + spw.log.Warnf("spw.signStateProofMessage(%d): StateProofSecrets.Sign: %v", round, err) continue } sigs = append(sigs, sigFromAddr{ SignerAddress: key.Account, - Round: hdr.Round, + Round: round, Sig: sig, }) - ids = append(ids, key.ParticipationID) - usedSigners = append(usedSigners, key.StateProofSecrets) } // any error in handle sig indicates the signature wasn't stored in disk, thus we cannot delete the key. - for i, sfa := range sigs { + for _, sfa := range sigs { if _, err := spw.handleSig(sfa, nil); err != nil { - spw.log.Warnf("spw.signBlock(%d): handleSig: %v", hdr.Round, err) - continue - } - - spw.log.Infof("spw.signBlock(%d): sp message was signed with address %v", hdr.Round, sfa.SignerAddress) - firstRoundInKeyLifetime, err := usedSigners[i].FirstRoundInKeyLifetime() // Calculate first round of the key in order to delete all previous keys (and keep the current one for now) - if err != nil { - spw.log.Warnf("spw.signBlock(%d): Signer.FirstRoundInKeyLifetime: %v", hdr.Round, err) + spw.log.Warnf("spw.signStateProofMessage(%d): handleSig: %v", round, err) continue } - if firstRoundInKeyLifetime == 0 { - continue // No previous keys to delete (also underflows when subtracting 1) - } - - // Safe to delete key for sfa.Round because the signature is now stored in the disk. - if err := spw.accts.DeleteStateProofKey(ids[i], basics.Round(firstRoundInKeyLifetime-1)); err != nil { // Subtract 1 to delete all keys up to this one - spw.log.Warnf("spw.signBlock(%d): DeleteStateProofKey: %v", hdr.Round, err) - } + spw.log.Infof("spw.signStateProofMessage(%d): sp message was signed with address %v", round, sfa.SignerAddress) } } diff --git a/stateproof/stateproofMessageGenerator.go b/stateproof/stateproofMessageGenerator.go index 92befc631f..3b5008f2a4 100644 --- a/stateproof/stateproofMessageGenerator.go +++ b/stateproof/stateproofMessageGenerator.go @@ -51,8 +51,14 @@ func (b lightBlockHeaders) Marshal(pos uint64) (crypto.Hashable, error) { // GenerateStateProofMessage returns a stateproof message that contains all the necessary data for proving on Algorand's state. // In addition, it also includes the trusted data for the next stateproof verification -func GenerateStateProofMessage(l BlockHeaderFetcher, votersRound uint64, latestRoundHeader bookkeeping.BlockHeader) (stateproofmsg.Message, error) { +func GenerateStateProofMessage(l BlockHeaderFetcher, round basics.Round) (stateproofmsg.Message, error) { + latestRoundHeader, err := l.BlockHdr(round) + if err != nil { + return stateproofmsg.Message{}, err + } + proto := config.Consensus[latestRoundHeader.CurrentProtocol] + votersRound := uint64(round.SubSaturate(basics.Round(proto.StateProofInterval))) commitment, err := createHeaderCommitment(l, &proto, &latestRoundHeader) if err != nil { return stateproofmsg.Message{}, err diff --git a/stateproof/stateproofMessageGenerator_test.go b/stateproof/stateproofMessageGenerator_test.go index 26c47ec8a2..a990143b05 100644 --- a/stateproof/stateproofMessageGenerator_test.go +++ b/stateproof/stateproofMessageGenerator_test.go @@ -17,14 +17,10 @@ package stateproof import ( - "context" + "github.com/stretchr/testify/require" "testing" "time" - "github.com/stretchr/testify/require" - - "github.com/algorand/go-deadlock" - "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/crypto/merklearray" @@ -35,146 +31,10 @@ import ( "github.com/algorand/go-algorand/data/stateproofmsg" "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/network" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" ) -type workerForStateProofMessageTests struct { - w *testWorkerStubs -} - -func (s *workerForStateProofMessageTests) StateProofKeys(round basics.Round) []account.StateProofSecretsForRound { - return s.w.StateProofKeys(round) -} - -func (s *workerForStateProofMessageTests) DeleteStateProofKey(id account.ParticipationID, round basics.Round) error { - return s.w.DeleteStateProofKey(id, round) -} - -func (s *workerForStateProofMessageTests) Latest() basics.Round { - return s.w.Latest() -} - -func (s *workerForStateProofMessageTests) Wait(round basics.Round) chan struct{} { - return s.w.Wait(round) -} - -func (s *workerForStateProofMessageTests) GenesisHash() crypto.Digest { - return s.w.GenesisHash() -} - -func (s *workerForStateProofMessageTests) BlockHdr(round basics.Round) (bookkeeping.BlockHeader, error) { - s.w.mu.Lock() - defer s.w.mu.Unlock() - - element, ok := s.w.blocks[round] - if !ok { - return bookkeeping.BlockHeader{}, ledgercore.ErrNoEntry{Round: round} - } - return element, nil -} - -func (s *workerForStateProofMessageTests) VotersForStateProof(round basics.Round) (*ledgercore.VotersForRound, error) { - voters := &ledgercore.VotersForRound{ - Proto: config.Consensus[protocol.ConsensusCurrentVersion], - AddrToPos: make(map[basics.Address]uint64), - } - - wt := uint64(0) - for i, k := range s.w.keysForVoters { - partWe := uint64((len(s.w.keysForVoters) + int(round) - i) * 10000) - voters.AddrToPos[k.Parent] = uint64(i) - voters.Participants = append(voters.Participants, basics.Participant{ - PK: *k.StateProofSecrets.GetVerifier(), - Weight: partWe, - }) - wt += partWe - } - - tree, err := merklearray.BuildVectorCommitmentTree(voters.Participants, crypto.HashFactory{HashType: stateproof.HashType}) - if err != nil { - return nil, err - } - - voters.Tree = tree - voters.TotalWeight = basics.MicroAlgos{Raw: wt} - return voters, nil -} - -func (s *workerForStateProofMessageTests) Broadcast(ctx context.Context, tag protocol.Tag, bytes []byte, b bool, peer network.Peer) error { - return s.w.Broadcast(ctx, tag, bytes, b, peer) -} - -func (s *workerForStateProofMessageTests) RegisterHandlers(handlers []network.TaggedMessageHandler) { - s.w.RegisterHandlers(handlers) -} - -func (s *workerForStateProofMessageTests) BroadcastInternalSignedTxGroup(txns []transactions.SignedTxn) error { - return s.w.BroadcastInternalSignedTxGroup(txns) -} - -func (s *workerForStateProofMessageTests) addBlockWithStateProofHeaders(ccNextRound basics.Round) { - - s.w.latest++ - - hdr := bookkeeping.BlockHeader{} - hdr.Round = s.w.latest - hdr.CurrentProtocol = protocol.ConsensusCurrentVersion - - var ccBasic = bookkeeping.StateProofTrackingData{ - StateProofVotersCommitment: make([]byte, stateproof.HashSize), - StateProofOnlineTotalWeight: basics.MicroAlgos{}, - StateProofNextRound: 0, - } - - if uint64(hdr.Round)%config.Consensus[hdr.CurrentProtocol].StateProofInterval == 0 { - voters, _ := s.VotersForStateProof(hdr.Round.SubSaturate(basics.Round(config.Consensus[hdr.CurrentProtocol].StateProofVotersLookback))) - ccBasic.StateProofVotersCommitment = voters.Tree.Root() - ccBasic.StateProofOnlineTotalWeight = voters.TotalWeight - - } - - ccBasic.StateProofNextRound = ccNextRound - hdr.StateProofTracking = map[protocol.StateProofType]bookkeeping.StateProofTrackingData{ - protocol.StateProofBasic: ccBasic, - } - - s.w.blocks[s.w.latest] = hdr - if s.w.waiters[s.w.latest] != nil { - close(s.w.waiters[s.w.latest]) - } -} - -func newWorkerForStateProofMessageStubs(keys []account.Participation, totalWeight int) *workerForStateProofMessageTests { - s := &testWorkerStubs{ - t: nil, - mu: deadlock.Mutex{}, - latest: 0, - waiters: make(map[basics.Round]chan struct{}), - waitersCount: make(map[basics.Round]int), - blocks: make(map[basics.Round]bookkeeping.BlockHeader), - keys: keys, - keysForVoters: keys, - sigmsg: make(chan []byte, 1024), - txmsg: make(chan transactions.SignedTxn, 1024), - totalWeight: totalWeight, - deletedStateProofKeys: map[account.ParticipationID]basics.Round{}, - } - sm := workerForStateProofMessageTests{w: s} - return &sm -} - -func (s *workerForStateProofMessageTests) advanceLatest(delta uint64) { - s.w.mu.Lock() - defer s.w.mu.Unlock() - - for r := uint64(0); r < delta; r++ { - s.addBlockWithStateProofHeaders(s.w.blocks[s.w.latest].StateProofTracking[protocol.StateProofBasic].StateProofNextRound) - } -} - func TestStateProofMessage(t *testing.T) { partitiontest.PartitionTest(t) a := require.New(t) @@ -188,51 +48,41 @@ func TestStateProofMessage(t *testing.T) { keys = append(keys, p.Participation) } - s := newWorkerForStateProofMessageStubs(keys, len(keys)) - dbs, _ := dbOpenTest(t, true) - w := NewWorker(dbs.Wdb, logging.TestingLog(t), s, s, s, s) - - proto := config.Consensus[protocol.ConsensusCurrentVersion] - s.w.latest-- - s.addBlockWithStateProofHeaders(2 * basics.Round(proto.StateProofInterval)) - + s := newWorkerStubsWithChannel(t, keys, len(keys)) + s.sigmsg = nil + w := newTestWorker(t, s) w.Start() - defer w.Shutdown() - - s.advanceLatest(proto.StateProofInterval + proto.StateProofInterval/2) + defer w.Stop() + proto := config.Consensus[protocol.ConsensusCurrentVersion] + s.advanceRoundsWithoutStateProof(t, 1) var lastMessage stateproofmsg.Message + for i := 0; i < 5; i++ { + s.advanceRoundsWithoutStateProof(t, proto.StateProofInterval-1) - for iter := uint64(0); iter < 5; iter++ { - s.advanceLatest(proto.StateProofInterval) - + var tx transactions.SignedTxn + // there will be several state proof txn. we extract them for { - tx, err := s.w.waitOnTxnWithTimeout(time.Second * 5) + var err error + tx, err = s.waitOnTxnWithTimeout(time.Second * 5) a.NoError(err) - - a.Equal(tx.Txn.Type, protocol.StateProofTx) - - lastAttestedRound := basics.Round(tx.Txn.Message.LastAttestedRound) - if lastAttestedRound < basics.Round(iter+2)*basics.Round(proto.StateProofInterval) { - continue + if lastMessage.LastAttestedRound == 0 || lastMessage.LastAttestedRound < tx.Txn.Message.LastAttestedRound { + break } - a.Equal(lastAttestedRound, basics.Round(iter+2)*basics.Round(proto.StateProofInterval)) - a.Equal(tx.Txn.Message.FirstAttestedRound, (iter+1)*proto.StateProofInterval+1) - - verifySha256BlockHeadersCommitments(a, tx.Txn.Message, s.w.blocks) - - if !lastMessage.MsgIsZero() { - verifier := stateproof.MkVerifierWithLnProvenWeight(lastMessage.VotersCommitment, lastMessage.LnProvenWeight, proto.StateProofStrengthTarget) - - err := verifier.Verify(uint64(lastAttestedRound), tx.Txn.Message.Hash(), &tx.Txn.StateProof) - a.NoError(err) + } - } + verifySha256BlockHeadersCommitments(a, tx.Txn.Message, s.blocks) + if !lastMessage.MsgIsZero() { + verifier := stateproof.MkVerifierWithLnProvenWeight(lastMessage.VotersCommitment, lastMessage.LnProvenWeight, proto.StateProofStrengthTarget) - lastMessage = tx.Txn.Message - break + err := verifier.Verify(tx.Txn.Message.LastAttestedRound, tx.Txn.Message.Hash(), &tx.Txn.StateProof) + a.NoError(err) } + // since a state proof txn was created, we update the header with the next state proof round + // i.e network has accepted the state proof. + s.addBlock(basics.Round(tx.Txn.Message.LastAttestedRound + proto.StateProofInterval)) + lastMessage = tx.Txn.Message } } @@ -262,12 +112,11 @@ func TestGenerateStateProofMessageForSmallRound(t *testing.T) { keys = append(keys, p.Participation) } - s := newWorkerForStateProofMessageStubs(keys[:], len(keys)) + s := newWorkerStubAtGenesis(t, keys[:], len(keys)) proto := config.Consensus[protocol.ConsensusCurrentVersion] - s.w.latest-- - s.addBlockWithStateProofHeaders(2 * basics.Round(proto.StateProofInterval)) + s.addBlock(2 * basics.Round(proto.StateProofInterval)) - _, err := GenerateStateProofMessage(s, 240, s.w.blocks[s.w.latest]) + _, err := GenerateStateProofMessage(s, s.latest) a.ErrorIs(err, errInvalidParams) } @@ -284,18 +133,19 @@ func TestMessageLnApproxError(t *testing.T) { keys = append(keys, p.Participation) } - s := newWorkerForStateProofMessageStubs(keys[:], len(keys)) - proto := config.Consensus[protocol.ConsensusCurrentVersion] - s.w.latest-- - s.addBlockWithStateProofHeaders(2 * basics.Round(proto.StateProofInterval)) + s := newWorkerStubs(t, keys[:], len(keys)) + w := newTestWorker(t, s) + w.Start() + defer w.Stop() - s.advanceLatest(2*proto.StateProofInterval + proto.StateProofInterval/2) - tracking := s.w.blocks[512].StateProofTracking[protocol.StateProofBasic] + proto := config.Consensus[protocol.ConsensusCurrentVersion] + s.advanceRoundsWithoutStateProof(t, proto.StateProofInterval) + tracking := s.blocks[512].StateProofTracking[protocol.StateProofBasic] tracking.StateProofOnlineTotalWeight = basics.MicroAlgos{} newtracking := tracking - s.w.blocks[512].StateProofTracking[protocol.StateProofBasic] = newtracking + s.blocks[512].StateProofTracking[protocol.StateProofBasic] = newtracking - _, err := GenerateStateProofMessage(s, 256, s.w.blocks[512]) + _, err := GenerateStateProofMessage(s, basics.Round(2*proto.StateProofInterval)) a.ErrorIs(err, stateproof.ErrIllegalInputForLnApprox) } @@ -312,16 +162,17 @@ func TestMessageMissingHeaderOnInterval(t *testing.T) { keys = append(keys, p.Participation) } - s := newWorkerForStateProofMessageStubs(keys[:], len(keys)) - s.w.latest-- - proto := config.Consensus[protocol.ConsensusCurrentVersion] - s.addBlockWithStateProofHeaders(2 * basics.Round(proto.StateProofInterval)) + s := newWorkerStubs(t, keys[:], len(keys)) + w := newTestWorker(t, s) + w.Start() + defer w.Stop() - s.advanceLatest(2*proto.StateProofInterval + proto.StateProofInterval/2) - delete(s.w.blocks, 510) + proto := config.Consensus[protocol.ConsensusCurrentVersion] + s.advanceRoundsWithoutStateProof(t, proto.StateProofInterval) + delete(s.blocks, 510) - _, err := GenerateStateProofMessage(s, 256, s.w.blocks[512]) - a.ErrorIs(err, ledgercore.ErrNoEntry{Round: 510}) + _, err := GenerateStateProofMessage(s, basics.Round(2*proto.StateProofInterval)) + a.ErrorIs(err, ledgercore.ErrNoEntry{Round: 510, Latest: s.latest, Committed: s.latest}) } func TestGenerateBlockProof(t *testing.T) { @@ -337,46 +188,55 @@ func TestGenerateBlockProof(t *testing.T) { keys = append(keys, p.Participation) } - s := newWorkerForStateProofMessageStubs(keys, len(keys)) - dbs, _ := dbOpenTest(t, true) - w := NewWorker(dbs.Wdb, logging.TestingLog(t), s, s, s, s) + s := newWorkerStubsWithChannel(t, keys, len(keys)) + s.sigmsg = nil + w := newTestWorker(t, s) + w.Start() + defer w.Stop() proto := config.Consensus[protocol.ConsensusCurrentVersion] - s.w.latest-- - s.addBlockWithStateProofHeaders(2 * basics.Round(proto.StateProofInterval)) + s.advanceRoundsWithoutStateProof(t, 1) + var lastAttestedRound basics.Round + for i := 0; i < 5; i++ { + s.advanceRoundsWithoutStateProof(t, proto.StateProofInterval-1) - w.Start() - defer w.Shutdown() + var tx transactions.SignedTxn + // there will be several state proof txn. we extract them + for { + var err error + tx, err = s.waitOnTxnWithTimeout(time.Second * 5) + a.NoError(err) + if lastAttestedRound == 0 || lastAttestedRound < basics.Round(tx.Txn.Message.LastAttestedRound) { + break + } - s.advanceLatest(proto.StateProofInterval + proto.StateProofInterval/2) + } + headers, err := FetchLightHeaders(s, proto.StateProofInterval, basics.Round(tx.Txn.Message.LastAttestedRound)) + a.NoError(err) + a.Equal(proto.StateProofInterval, uint64(len(headers))) - for iter := uint64(0); iter < 5; iter++ { - s.advanceLatest(proto.StateProofInterval) + verifyLightBlockHeaderProof(&tx, &proto, headers, a) - tx := <-s.w.txmsg - // we have a new tx. now attempt to fetch a block proof. - firstAttestedRound := tx.Txn.Message.FirstAttestedRound - lastAttestedRound := tx.Txn.Message.LastAttestedRound + s.addBlock(basics.Round(tx.Txn.Message.LastAttestedRound + proto.StateProofInterval)) + lastAttestedRound = basics.Round(tx.Txn.Message.LastAttestedRound) + } +} - headers, err := FetchLightHeaders(s, proto.StateProofInterval, basics.Round(lastAttestedRound)) +func verifyLightBlockHeaderProof(tx *transactions.SignedTxn, proto *config.ConsensusParams, headers []bookkeeping.LightBlockHeader, a *require.Assertions) { + // attempting to get block proof for every block in the interval + for j := tx.Txn.Message.FirstAttestedRound; j < tx.Txn.Message.LastAttestedRound; j++ { + headerIndex := j - tx.Txn.Message.FirstAttestedRound + proof, err := GenerateProofOfLightBlockHeaders(proto.StateProofInterval, headers, headerIndex) a.NoError(err) - a.Equal(proto.StateProofInterval, uint64(len(headers))) - - // attempting to get block proof for every block in the interval - for i := firstAttestedRound; i < lastAttestedRound; i++ { - headerIndex := i - firstAttestedRound - proof, err := GenerateProofOfLightBlockHeaders(proto.StateProofInterval, headers, headerIndex) - a.NoError(err) - a.NotNil(proof) + a.NotNil(proof) - lightheader := headers[headerIndex] - err = merklearray.VerifyVectorCommitment( - tx.Txn.Message.BlockHeadersCommitment, - map[uint64]crypto.Hashable{headerIndex: &lightheader}, - proof.ToProof()) + lightheader := headers[headerIndex] + err = merklearray.VerifyVectorCommitment( + tx.Txn.Message.BlockHeadersCommitment, + map[uint64]crypto.Hashable{headerIndex: &lightheader}, + proof.ToProof()) - a.NoError(err) - } + a.NoError(err) } } @@ -385,7 +245,7 @@ func TestGenerateBlockProofOnSmallArray(t *testing.T) { a := require.New(t) var keys []account.Participation - for i := 0; i < 10; i++ { + for i := 0; i < 2; i++ { var parent basics.Address crypto.RandBytes(parent[:]) p := newPartKey(t, parent) @@ -393,13 +253,13 @@ func TestGenerateBlockProofOnSmallArray(t *testing.T) { keys = append(keys, p.Participation) } - s := newWorkerForStateProofMessageStubs(keys, len(keys)) - proto := config.Consensus[protocol.ConsensusCurrentVersion] - - s.w.latest-- - s.addBlockWithStateProofHeaders(2 * basics.Round(proto.StateProofInterval)) + s := newWorkerStubs(t, keys[:], len(keys)) + w := newTestWorker(t, s) + w.Start() + defer w.Stop() - s.advanceLatest(2 * proto.StateProofInterval) + proto := config.Consensus[protocol.ConsensusCurrentVersion] + s.advanceRoundsWithoutStateProof(t, proto.StateProofInterval) headers, err := FetchLightHeaders(s, proto.StateProofInterval, basics.Round(2*proto.StateProofInterval)) a.NoError(err) headers = headers[1:] diff --git a/stateproof/verify/stateproof.go b/stateproof/verify/stateproof.go index fa3641c08c..2535c905ed 100644 --- a/stateproof/verify/stateproof.go +++ b/stateproof/verify/stateproof.go @@ -25,17 +25,16 @@ import ( "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/bookkeeping" "github.com/algorand/go-algorand/data/stateproofmsg" + "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/protocol" ) var ( - errStateProofCrypto = errors.New("state proof crypto error") - errStateProofParamCreation = errors.New("state proof param creation error") - errStateProofNotEnabled = errors.New("state proofs are not enabled") - errNotAtRightMultiple = errors.New("state proof is not in a valid round multiple") - errInvalidVotersRound = errors.New("invalid voters round") - errInsufficientWeight = errors.New("insufficient state proof weight") + errStateProofCrypto = errors.New("state proof crypto error") + errStateProofNotEnabled = errors.New("state proofs are not enabled") + errNotAtRightMultiple = errors.New("state proof is not in a valid round multiple") + errInsufficientWeight = errors.New("insufficient state proof weight") ) // AcceptableStateProofWeight computes the acceptable signed weight @@ -51,11 +50,16 @@ func AcceptableStateProofWeight(votersHdr *bookkeeping.BlockHeader, firstValid b latestRoundInProof := votersHdr.Round + basics.Round(proto.StateProofInterval) total := votersHdr.StateProofTracking[protocol.StateProofBasic].StateProofOnlineTotalWeight + return calculateAcceptableStateProofWeight(total, &proto, latestRoundInProof, firstValid, logger) +} + +func calculateAcceptableStateProofWeight(total basics.MicroAlgos, proto *config.ConsensusParams, lastAttestedRound basics.Round, firstValid basics.Round, logger logging.Logger) uint64 { + halfPeriodForInterval := proto.StateProofInterval / 2 // The acceptable weight depends on the elapsed time (in rounds) // from the block we are trying to construct a proof for. // Start by subtracting the latest round number in the state proof interval. // If that round hasn't even passed yet, require 100% votes in proof. - offset := firstValid.SubSaturate(latestRoundInProof) + offset := firstValid.SubSaturate(lastAttestedRound) if offset == 0 { return total.ToUint64() } @@ -63,7 +67,7 @@ func AcceptableStateProofWeight(votersHdr *bookkeeping.BlockHeader, firstValid b // During the first proto.StateProofInterval/2 blocks, the // signatures are still being broadcast, so, continue requiring // 100% votes. - offset = offset.SubSaturate(basics.Round(proto.StateProofInterval / 2)) + offset = offset.SubSaturate(basics.Round(halfPeriodForInterval)) if offset == 0 { return total.ToUint64() } @@ -75,28 +79,28 @@ func AcceptableStateProofWeight(votersHdr *bookkeeping.BlockHeader, firstValid b provenWeight, overflowed := basics.Muldiv(total.ToUint64(), uint64(proto.StateProofWeightThreshold), 1<<32) if overflowed || provenWeight > total.ToUint64() { // Shouldn't happen, but a safe fallback is to accept a larger proof. - logger.Warnf("AcceptableStateProofWeight(%d, %d, %d, %d) overflow provenWeight", - total, proto.StateProofInterval, latestRoundInProof, firstValid) + logger.Warnf("calculateAcceptableStateProofWeight(%d, %d, %d, %d) overflow provenWeight", + total, proto.StateProofInterval, lastAttestedRound, firstValid) return 0 } - if offset >= basics.Round(proto.StateProofInterval/2) { + if offset >= basics.Round(halfPeriodForInterval) { return provenWeight } - scaledWeight, overflowed := basics.Muldiv(total.ToUint64()-provenWeight, proto.StateProofInterval/2-uint64(offset), proto.StateProofInterval/2) + scaledWeight, overflowed := basics.Muldiv(total.ToUint64()-provenWeight, halfPeriodForInterval-uint64(offset), halfPeriodForInterval) if overflowed { // Shouldn't happen, but a safe fallback is to accept a larger state proof. - logger.Warnf("AcceptableStateProofWeight(%d, %d, %d, %d) overflow scaledWeight", - total, proto.StateProofInterval, latestRoundInProof, firstValid) + logger.Warnf("calculateAcceptableStateProofWeight(%d, %d, %d, %d) overflow scaledWeight", + total, proto.StateProofInterval, lastAttestedRound, firstValid) return 0 } w, overflowed := basics.OAdd(provenWeight, scaledWeight) if overflowed { // Shouldn't happen, but a safe fallback is to accept a larger state proof. - logger.Warnf("AcceptableStateProofWeight(%d, %d, %d, %d) overflow provenWeight (%d) + scaledWeight (%d)", - total, proto.StateProofInterval, latestRoundInProof, firstValid, provenWeight, scaledWeight) + logger.Warnf("calculateAcceptableStateProofWeight(%d, %d, %d, %d) overflow provenWeight (%d) + scaledWeight (%d)", + total, proto.StateProofInterval, lastAttestedRound, firstValid, provenWeight, scaledWeight) return 0 } @@ -136,42 +140,38 @@ func GetProvenWeight(votersHdr *bookkeeping.BlockHeader, latestRoundInProofHdr * } // ValidateStateProof checks that a state proof is valid. -func ValidateStateProof(latestRoundInIntervalHdr *bookkeeping.BlockHeader, stateProof *stateproof.StateProof, votersHdr *bookkeeping.BlockHeader, atRound basics.Round, msg *stateproofmsg.Message) error { - proto := config.Consensus[latestRoundInIntervalHdr.CurrentProtocol] +func ValidateStateProof(verificationContext *ledgercore.StateProofVerificationContext, stateProof *stateproof.StateProof, atRound basics.Round, msg *stateproofmsg.Message) error { + proto := config.Consensus[verificationContext.Version] if proto.StateProofInterval == 0 { return fmt.Errorf("rounds = %d: %w", proto.StateProofInterval, errStateProofNotEnabled) } - if latestRoundInIntervalHdr.Round%basics.Round(proto.StateProofInterval) != 0 { - return fmt.Errorf("state proof at %d for non-multiple of %d: %w", latestRoundInIntervalHdr.Round, proto.StateProofInterval, errNotAtRightMultiple) - } - - votersRound := latestRoundInIntervalHdr.Round.SubSaturate(basics.Round(proto.StateProofInterval)) - if votersRound != votersHdr.Round { - return fmt.Errorf("new state proof is for %d (voters %d), but votersHdr from %d: %w", - latestRoundInIntervalHdr.Round, votersRound, votersHdr.Round, errInvalidVotersRound) + if verificationContext.LastAttestedRound%basics.Round(proto.StateProofInterval) != 0 { + return fmt.Errorf("state proof at %d for non-multiple of %d: %w", verificationContext.LastAttestedRound, proto.StateProofInterval, errNotAtRightMultiple) } - acceptableWeight := AcceptableStateProofWeight(votersHdr, atRound, logging.Base()) + acceptableWeight := calculateAcceptableStateProofWeight(verificationContext.OnlineTotalWeight, &proto, verificationContext.LastAttestedRound, atRound, logging.Base()) if stateProof.SignedWeight < acceptableWeight { return fmt.Errorf("insufficient weight at round %d: %d < %d: %w", atRound, stateProof.SignedWeight, acceptableWeight, errInsufficientWeight) } - provenWeight, err := GetProvenWeight(votersHdr, latestRoundInIntervalHdr) - if err != nil { - return fmt.Errorf("%v: %w", err, errStateProofParamCreation) + provenWeight, overflowed := basics.Muldiv(verificationContext.OnlineTotalWeight.ToUint64(), uint64(proto.StateProofWeightThreshold), 1<<32) + if overflowed { + return fmt.Errorf("overflow computing provenWeight[%d]: %d * %d / (1<<32)", + verificationContext.LastAttestedRound, verificationContext.OnlineTotalWeight.ToUint64(), proto.StateProofWeightThreshold) + } - verifier, err := stateproof.MkVerifier(votersHdr.StateProofTracking[protocol.StateProofBasic].StateProofVotersCommitment, + verifier, err := stateproof.MkVerifier(verificationContext.VotersCommitment, provenWeight, - config.Consensus[votersHdr.CurrentProtocol].StateProofStrengthTarget) + proto.StateProofStrengthTarget) if err != nil { return err } - err = verifier.Verify(uint64(latestRoundInIntervalHdr.Round), msg.Hash(), stateProof) + err = verifier.Verify(uint64(verificationContext.LastAttestedRound), msg.Hash(), stateProof) if err != nil { return fmt.Errorf("%v: %w", err, errStateProofCrypto) } diff --git a/stateproof/verify/stateproof_test.go b/stateproof/verify/stateproof_test.go index 0495729dd7..b092bad8fe 100644 --- a/stateproof/verify/stateproof_test.go +++ b/stateproof/verify/stateproof_test.go @@ -17,20 +17,33 @@ package verify import ( - "testing" - - "github.com/stretchr/testify/require" - "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto/stateproof" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/bookkeeping" "github.com/algorand/go-algorand/data/stateproofmsg" + "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" + "github.com/stretchr/testify/require" + "testing" ) +func invokeValidateStateProof(latestRoundInIntervalHdr *bookkeeping.BlockHeader, + stateProof *stateproof.StateProof, + votersHdr *bookkeeping.BlockHeader, + atRound basics.Round, + msg *stateproofmsg.Message) error { + verificationContext := ledgercore.StateProofVerificationContext{ + LastAttestedRound: latestRoundInIntervalHdr.Round, + VotersCommitment: votersHdr.StateProofTracking[protocol.StateProofBasic].StateProofVotersCommitment, + OnlineTotalWeight: votersHdr.StateProofTracking[protocol.StateProofBasic].StateProofOnlineTotalWeight, + Version: votersHdr.CurrentProtocol, + } + return ValidateStateProof(&verificationContext, stateProof, atRound, msg) +} + func TestValidateStateProof(t *testing.T) { partitiontest.PartitionTest(t) @@ -41,55 +54,52 @@ func TestValidateStateProof(t *testing.T) { msg := &stateproofmsg.Message{BlockHeadersCommitment: []byte("this is an arbitrary message")} // will definitely fail with nothing set up - err := ValidateStateProof(spHdr, sp, votersHdr, atRound, msg) + err := invokeValidateStateProof(spHdr, sp, votersHdr, atRound, msg) require.ErrorIs(t, err, errStateProofNotEnabled) - spHdr.CurrentProtocol = "TestValidateStateProof" - spHdr.Round = 1 + votersHdr.CurrentProtocol = "TestValidateStateProof" proto := config.Consensus[spHdr.CurrentProtocol] - proto.StateProofInterval = 2 + proto.StateProofInterval = 256 proto.StateProofStrengthTarget = 256 proto.StateProofWeightThreshold = (1 << 32) * 30 / 100 - config.Consensus[spHdr.CurrentProtocol] = proto + config.Consensus[votersHdr.CurrentProtocol] = proto - err = ValidateStateProof(spHdr, sp, votersHdr, atRound, msg) + spHdr.Round = 1 + err = invokeValidateStateProof(spHdr, sp, votersHdr, atRound, msg) require.ErrorIs(t, err, errNotAtRightMultiple) - spHdr.Round = 4 - votersHdr.Round = 4 - err = ValidateStateProof(spHdr, sp, votersHdr, atRound, msg) - require.ErrorIs(t, err, errInvalidVotersRound) - - votersHdr.Round = 2 - err = ValidateStateProof(spHdr, sp, votersHdr, atRound, msg) - require.ErrorIs(t, err, errStateProofParamCreation) - - votersHdr.CurrentProtocol = spHdr.CurrentProtocol - err = ValidateStateProof(spHdr, sp, votersHdr, atRound, msg) - // since proven weight is zero, we cann't create the verifier + votersHdr.Round = 256 + spHdr.Round = votersHdr.Round + basics.Round(proto.StateProofInterval) + sp.SignedWeight = 1 + atRound = 800 + err = invokeValidateStateProof(spHdr, sp, votersHdr, atRound, msg) require.ErrorIs(t, err, stateproof.ErrIllegalInputForLnApprox) votersHdr.StateProofTracking = make(map[protocol.StateProofType]bookkeeping.StateProofTrackingData) cc := votersHdr.StateProofTracking[protocol.StateProofBasic] cc.StateProofOnlineTotalWeight.Raw = 100 votersHdr.StateProofTracking[protocol.StateProofBasic] = cc - err = ValidateStateProof(spHdr, sp, votersHdr, atRound, msg) - require.ErrorIs(t, err, errInsufficientWeight) - // Require 100% of the weight to be signed in order to accept stateproof before interval/2 rounds has passed from the latest round attested (optimal case) sp.SignedWeight = 99 // suboptimal signed weight - err = ValidateStateProof(spHdr, sp, votersHdr, atRound, msg) + atRound = votersHdr.Round + basics.Round(proto.StateProofInterval) + err = invokeValidateStateProof(spHdr, sp, votersHdr, atRound, msg) require.ErrorIs(t, err, errInsufficientWeight) + atRound++ + err = invokeValidateStateProof(spHdr, sp, votersHdr, atRound, msg) + require.ErrorIs(t, err, errInsufficientWeight) + + // we don't pass the scaled weight + sp.SignedWeight = 96 latestRoundInProof := votersHdr.Round + basics.Round(proto.StateProofInterval) - atRound = latestRoundInProof + basics.Round(proto.StateProofInterval/2) - err = ValidateStateProof(spHdr, sp, votersHdr, atRound, msg) + atRound = latestRoundInProof + basics.Round(proto.StateProofInterval/2) + 5 + err = invokeValidateStateProof(spHdr, sp, votersHdr, atRound, msg) require.ErrorIs(t, err, errInsufficientWeight) - // This suboptimal signed weight should be enough for this round - atRound++ - err = ValidateStateProof(spHdr, sp, votersHdr, atRound, msg) - // still err, but a different err case to cover + // we will pass the threshold since the network is now willing to take any state proof that has signedWeight over the threshold + sp.SignedWeight = 30 + atRound = votersHdr.Round + 2*basics.Round(proto.StateProofInterval) + err = invokeValidateStateProof(spHdr, sp, votersHdr, atRound, msg) require.ErrorIs(t, err, errStateProofCrypto) // Above cases leave validateStateProof() with 100% coverage. diff --git a/stateproof/worker.go b/stateproof/worker.go index 857431f04b..14a024d1a0 100644 --- a/stateproof/worker.go +++ b/stateproof/worker.go @@ -18,16 +18,18 @@ package stateproof import ( "context" - "database/sql" + "fmt" + "os" + "path/filepath" "sync" "github.com/algorand/go-deadlock" + "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto/stateproof" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/bookkeeping" "github.com/algorand/go-algorand/data/stateproofmsg" - "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/network" "github.com/algorand/go-algorand/protocol" @@ -35,13 +37,21 @@ import ( ) type builder struct { - *stateproof.Builder + _struct struct{} `codec:",omitempty,omitemptyarray"` - voters *ledgercore.VotersForRound - votersHdr bookkeeping.BlockHeader - message stateproofmsg.Message + *stateproof.Builder `codec:"bldr"` + + AddrToPos map[Address]uint64 `codec:"addr,allocbound=stateproof.VotersAllocBound"` + VotersHdr bookkeeping.BlockHeader `codec:"hdr"` + Message stateproofmsg.Message `codec:"msg"` } +// This is a soft limit on how many builders should be kept in memory, the rest shall be fetched from DB. +// At most times only 1 should builder should be stored (both in memory and on disk), as this feature +// is mostly used for recoverability purposes - in case the StateProof chain is stalled. +// The builders cache is composed of the X earliest builders as well as the latest builder, for a total of X+1 (in case of stalled chain). +const buildersCacheLength = 5 // must be at least 2 to function properly (earliest stateproof + latest stateproof) + // Worker builds state proofs, by broadcasting // signatures using this node's participation keys, by collecting // signatures sent by others, and by sending out the resulting @@ -51,12 +61,13 @@ type Worker struct { // from the network stack. mu deadlock.Mutex - db db.Accessor - log logging.Logger - accts Accounts - ledger Ledger - net Network - txnSender TransactionSender + spDbFileName string + db db.Accessor + log logging.Logger + accts Accounts + ledger Ledger + net Network + txnSender TransactionSender // builders is indexed by the round of the block being signed. builders map[basics.Round]builder @@ -65,40 +76,53 @@ type Worker struct { shutdown context.CancelFunc wg sync.WaitGroup + // Mutex for protecting access to the signed field + signedMu deadlock.RWMutex signed basics.Round signedCh chan struct{} + + lastCleanupRound basics.Round + + // inMemory indicates whether the state proof db should in memory. used for testing. + inMemory bool } // NewWorker constructs a new Worker, as used by the node. -func NewWorker(db db.Accessor, log logging.Logger, accts Accounts, ledger Ledger, net Network, txnSender TransactionSender) *Worker { - ctx, cancel := context.WithCancel(context.Background()) +func NewWorker(genesisDir string, log logging.Logger, accts Accounts, ledger Ledger, net Network, txnSender TransactionSender) *Worker { + // Delete the deprecated database file if it exists. This can be removed in future updates since this file should not exist by then. + oldCompactCertPath := filepath.Join(genesisDir, "compactcert.sqlite") + os.Remove(oldCompactCertPath) + + stateProofPathname := filepath.Join(genesisDir, config.StateProofFileName) return &Worker{ - db: db, - log: log, - accts: accts, - ledger: ledger, - net: net, - txnSender: txnSender, - builders: make(map[basics.Round]builder), - ctx: ctx, - shutdown: cancel, - signedCh: make(chan struct{}, 1), + spDbFileName: stateProofPathname, + log: log, + accts: accts, + ledger: ledger, + net: net, + txnSender: txnSender, + inMemory: false, } } // Start starts the goroutines for the worker. func (spw *Worker) Start() { - err := spw.db.Atomic(func(ctx context.Context, tx *sql.Tx) error { - return initDB(tx) - }) + ctx, cancel := context.WithCancel(context.Background()) + spw.ctx = ctx + spw.shutdown = cancel + spw.signedCh = make(chan struct{}, 1) + + err := spw.initDb(spw.inMemory) if err != nil { - spw.log.Warnf("spw.Start(): initDB: %v", err) + spw.log.Warn(err) return } spw.initBuilders() + spw.ledger.RegisterVotersCommitListener(spw) + handlers := []network.TaggedMessageHandler{ {Tag: protocol.StateProofSigTag, MessageHandler: network.HandlerFunc(spw.handleSigMessage)}, } @@ -113,9 +137,44 @@ func (spw *Worker) Start() { go spw.builder(latest) } -// Shutdown stops any goroutines associated with this worker. -func (spw *Worker) Shutdown() { +func (spw *Worker) initDb(inMemory bool) error { + stateProofAccess, err := db.MakeAccessor(spw.spDbFileName, false, inMemory) + if err != nil { + return fmt.Errorf("spw.initDb(): cannot load state proof data: %w", err) + + } + + spw.db = stateProofAccess + err = makeStateProofDB(spw.db) + if err != nil { + return fmt.Errorf("spw.initDb(): makeStateProofDB failed: %w", err) + } + return nil +} + +// Stop stops any goroutines associated with this worker. It is the caller responsibility to remove the register +// network handlers +func (spw *Worker) Stop() { spw.shutdown() spw.wg.Wait() - spw.db.Close() + + spw.ledger.UnregisterVotersCommitListener() + + // we take the lock in case the network handler currently running handleSig + spw.mu.Lock() + defer spw.mu.Unlock() + + spw.builders = nil + spw.signedCh = nil + + if spw.db.Handle != nil { + spw.db.Close() + } } + +// SortAddress implements sorting by Address keys for +// canonical encoding of maps in msgpack format. +type SortAddress = basics.SortAddress + +// Address is required for the msgpack sort binding, since it looks for Address and not basics.Address +type Address = basics.Address diff --git a/stateproof/worker_test.go b/stateproof/worker_test.go index bf1ec2636b..251c1f6b76 100644 --- a/stateproof/worker_test.go +++ b/stateproof/worker_test.go @@ -20,8 +20,9 @@ import ( "context" "database/sql" "encoding/binary" + "errors" "fmt" - "io" + "os" "strings" "sync" "testing" @@ -49,57 +50,111 @@ import ( ) type testWorkerStubs struct { - t testing.TB - mu deadlock.Mutex - latest basics.Round - waiters map[basics.Round]chan struct{} - waitersCount map[basics.Round]int - blocks map[basics.Round]bookkeeping.BlockHeader - keys []account.Participation - keysForVoters []account.Participation - sigmsg chan []byte - txmsg chan transactions.SignedTxn - totalWeight int - deletedStateProofKeys map[account.ParticipationID]basics.Round -} - -func newWorkerStubs(t testing.TB, keys []account.Participation, totalWeight int) *testWorkerStubs { + t testing.TB + mu deadlock.Mutex + listenerMu deadlock.RWMutex + latest basics.Round + waiters map[basics.Round]chan struct{} + waitersCount map[basics.Round]int + blocks map[basics.Round]bookkeeping.BlockHeader + keys []account.Participation + keysForVoters []account.Participation + sigmsg chan []byte + txmsg chan transactions.SignedTxn + totalWeight int + deletedKeysBeforeRoundMap map[account.ParticipationID]basics.Round + version protocol.ConsensusVersion + commitListener ledgercore.VotersCommitListener +} + +func newWorkerStubs(t *testing.T, keys []account.Participation, totalWeight int) *testWorkerStubs { + return newWorkerStubsWithVersion(t, keys, protocol.ConsensusCurrentVersion, totalWeight) +} + +func newWorkerStubsWithChannel(t *testing.T, keys []account.Participation, totalWeight int) *testWorkerStubs { + worker := newWorkerStubsWithVersion(t, keys, protocol.ConsensusCurrentVersion, totalWeight) + worker.sigmsg = make(chan []byte, 1024*1024) + worker.txmsg = make(chan transactions.SignedTxn, 1024) + return worker +} + +func newWorkerStubAtGenesis(t *testing.T, keys []account.Participation, totalWeight int) *testWorkerStubs { s := &testWorkerStubs{ - t: nil, - mu: deadlock.Mutex{}, - latest: 0, - waiters: make(map[basics.Round]chan struct{}), - waitersCount: make(map[basics.Round]int), - blocks: make(map[basics.Round]bookkeeping.BlockHeader), - keys: keys, - keysForVoters: keys, - sigmsg: make(chan []byte, 1024*1024), - txmsg: make(chan transactions.SignedTxn, 1024), - totalWeight: totalWeight, - deletedStateProofKeys: map[account.ParticipationID]basics.Round{}, + t: t, + mu: deadlock.Mutex{}, + listenerMu: deadlock.RWMutex{}, + latest: 0, + waiters: make(map[basics.Round]chan struct{}), + waitersCount: make(map[basics.Round]int), + blocks: make(map[basics.Round]bookkeeping.BlockHeader), + keys: keys, + keysForVoters: keys, + sigmsg: nil, + txmsg: nil, + totalWeight: totalWeight, + deletedKeysBeforeRoundMap: map[account.ParticipationID]basics.Round{}, + version: protocol.ConsensusCurrentVersion, } s.latest-- s.addBlock(2 * basics.Round(config.Consensus[protocol.ConsensusCurrentVersion].StateProofInterval)) return s } +func newWorkerStubsWithVersion(t *testing.T, keys []account.Participation, version protocol.ConsensusVersion, totalWeight int) *testWorkerStubs { + proto := config.Consensus[version] + s := &testWorkerStubs{ + t: t, + mu: deadlock.Mutex{}, + listenerMu: deadlock.RWMutex{}, + latest: 0, + waiters: make(map[basics.Round]chan struct{}), + waitersCount: make(map[basics.Round]int), + blocks: make(map[basics.Round]bookkeeping.BlockHeader), + keys: keys, + keysForVoters: keys, + sigmsg: nil, + txmsg: nil, + totalWeight: totalWeight, + deletedKeysBeforeRoundMap: map[account.ParticipationID]basics.Round{}, + version: version, + } + s.latest-- + s.addBlock(2 * basics.Round(proto.StateProofInterval)) + s.advanceRoundsBeforeFirstStateProof(&proto) + return s +} + +func (s *testWorkerStubs) notifyPrepareVoterCommit(oldBase, newBase basics.Round) { + s.listenerMu.RLock() + defer s.listenerMu.RUnlock() + + if s.commitListener == nil { + return + } + + s.commitListener.OnPrepareVoterCommit(oldBase, newBase, s) +} + func (s *testWorkerStubs) addBlock(spNextRound basics.Round) { + s.mu.Lock() + defer s.mu.Unlock() + s.latest++ hdr := bookkeeping.BlockHeader{} hdr.Round = s.latest - hdr.CurrentProtocol = protocol.ConsensusCurrentVersion - + hdr.CurrentProtocol = s.version var stateProofBasic = bookkeeping.StateProofTrackingData{ StateProofVotersCommitment: make([]byte, stateproof.HashSize), StateProofOnlineTotalWeight: basics.MicroAlgos{}, StateProofNextRound: 0, } - stateProofBasic.StateProofOnlineTotalWeight.Raw = uint64(s.totalWeight) - if hdr.Round > 0 { - // Just so it's not zero, since the signer logic checks for all-zeroes - stateProofBasic.StateProofVotersCommitment[1] = 0x12 + spInterval := config.Consensus[s.version].StateProofInterval + if spInterval != 0 && (hdr.Round > 0 && uint64(hdr.Round)%spInterval == 0) { + vt, _ := s.VotersForStateProof(hdr.Round) + stateProofBasic.StateProofOnlineTotalWeight = vt.TotalWeight + stateProofBasic.StateProofVotersCommitment = vt.Tree.Root() } stateProofBasic.StateProofNextRound = spNextRound @@ -109,12 +164,16 @@ func (s *testWorkerStubs) addBlock(spNextRound basics.Round) { s.blocks[s.latest] = hdr + s.waitersCount[s.latest] = 0 if s.waiters[s.latest] != nil { close(s.waiters[s.latest]) + s.waiters[s.latest] = nil } } func (s *testWorkerStubs) StateProofKeys(rnd basics.Round) (out []account.StateProofSecretsForRound) { + s.mu.Lock() + defer s.mu.Unlock() for _, part := range s.keys { partRecord := account.ParticipationRecord{ ParticipationID: part.ID(), @@ -131,6 +190,18 @@ func (s *testWorkerStubs) StateProofKeys(rnd basics.Round) (out []account.StateP Voting: part.Voting, } signerInRound := part.StateProofSecrets.GetSigner(uint64(rnd)) + if signerInRound == nil { + continue + } + KeyInLifeTime, _ := signerInRound.FirstRoundInKeyLifetime() + + // simulate that the key was removed + if basics.Round(KeyInLifeTime) < s.deletedKeysBeforeRoundMap[part.ID()] { + continue + } + if part.LastValid < rnd { + continue + } partRecordForRound := account.StateProofSecretsForRound{ ParticipationRecord: partRecord, StateProofSecrets: signerInRound, @@ -142,14 +213,14 @@ func (s *testWorkerStubs) StateProofKeys(rnd basics.Round) (out []account.StateP func (s *testWorkerStubs) DeleteStateProofKey(id account.ParticipationID, round basics.Round) error { s.mu.Lock() - s.deletedStateProofKeys[id] = round + s.deletedKeysBeforeRoundMap[id] = round s.mu.Unlock() return nil } func (s *testWorkerStubs) GetNumDeletedKeys() int { s.mu.Lock() - numDeltedKeys := len(s.deletedStateProofKeys) + numDeltedKeys := len(s.deletedKeysBeforeRoundMap) s.mu.Unlock() return numDeltedKeys @@ -171,7 +242,32 @@ func (s *testWorkerStubs) BlockHdr(r basics.Round) (bookkeeping.BlockHeader, err return hdr, nil } +var errEmptyVoters = errors.New("ledger does not have voters") + +func (s *testWorkerStubs) RegisterVotersCommitListener(listener ledgercore.VotersCommitListener) { + s.listenerMu.Lock() + defer s.listenerMu.Unlock() + if s.commitListener != nil { + panic("re-register commit Listener") + } + s.commitListener = listener +} + +func (s *testWorkerStubs) UnregisterVotersCommitListener() { + s.listenerMu.Lock() + defer s.listenerMu.Unlock() + s.commitListener = nil +} + func (s *testWorkerStubs) VotersForStateProof(r basics.Round) (*ledgercore.VotersForRound, error) { + if r == 0 { + return nil, nil + } + + if len(s.keysForVoters) == 0 { + return nil, errEmptyVoters + } + voters := &ledgercore.VotersForRound{ Proto: config.Consensus[protocol.ConsensusCurrentVersion], AddrToPos: make(map[basics.Address]uint64), @@ -195,6 +291,17 @@ func (s *testWorkerStubs) VotersForStateProof(r basics.Round) (*ledgercore.Voter return voters, nil } +func (s *testWorkerStubs) StateProofVerificationContext(stateProofLastAttestedRound basics.Round) (*ledgercore.StateProofVerificationContext, error) { + dummyContext := ledgercore.StateProofVerificationContext{ + LastAttestedRound: stateProofLastAttestedRound, + VotersCommitment: crypto.GenericDigest{0x1}, + OnlineTotalWeight: basics.MicroAlgos{}, + Version: protocol.ConsensusCurrentVersion, + } + + return &dummyContext, nil +} + func (s *testWorkerStubs) GenesisHash() crypto.Digest { return crypto.Digest{0x01, 0x02, 0x03, 0x04} } @@ -208,25 +315,37 @@ func (s *testWorkerStubs) Latest() basics.Round { func (s *testWorkerStubs) Wait(r basics.Round) chan struct{} { s.mu.Lock() defer s.mu.Unlock() + if s.waiters[r] == nil { s.waiters[r] = make(chan struct{}) s.waitersCount[r] = 0 - if r <= s.latest { - close(s.waiters[r]) - } } - s.waitersCount[r] = s.waitersCount[r] + 1 + + if r <= s.latest { + s.waitersCount[r] = 0 + close(s.waiters[r]) + retChan := s.waiters[r] + s.waiters[r] = nil + return retChan + } + s.waitersCount[r]++ return s.waiters[r] } func (s *testWorkerStubs) Broadcast(ctx context.Context, tag protocol.Tag, data []byte, wait bool, except network.Peer) error { require.Equal(s.t, tag, protocol.StateProofSigTag) + if s.sigmsg == nil { + return nil + } s.sigmsg <- data return nil } func (s *testWorkerStubs) BroadcastInternalSignedTxGroup(tx []transactions.SignedTxn) error { require.Equal(s.t, len(tx), 1) + if s.txmsg == nil { + return nil + } s.txmsg <- tx[0] return nil } @@ -234,12 +353,79 @@ func (s *testWorkerStubs) BroadcastInternalSignedTxGroup(tx []transactions.Signe func (s *testWorkerStubs) RegisterHandlers([]network.TaggedMessageHandler) { } -func (s *testWorkerStubs) advanceLatest(delta uint64) { - s.mu.Lock() - defer s.mu.Unlock() +func (s *testWorkerStubs) waitForSignerAndBuilder(t *testing.T) { + const maxRetries = 1000000 + i := 0 + for { + numberOfWaiters := 0 + s.mu.Lock() + for _, v := range s.waitersCount { + numberOfWaiters += v + } + s.mu.Unlock() + if numberOfWaiters == 2 { + break + } + if numberOfWaiters > 2 { + t.Error("found numberOfWaiters > 2. Might be bug in the test") + } + if i == maxRetries { + t.Error("timeout waiting for builder and signer") + } + i++ + time.Sleep(time.Millisecond) + } +} + +func (s *testWorkerStubs) advanceRoundsBeforeFirstStateProof(proto *config.ConsensusParams) { + if proto.StateProofInterval*2 <= 1 { + return + } + + for r := uint64(0); r < proto.StateProofInterval*2-1; r++ { + s.addBlock(s.blocks[s.latest].StateProofTracking[protocol.StateProofBasic].StateProofNextRound) + } +} +func (s *testWorkerStubs) advanceRoundsWithoutStateProof(t *testing.T, delta uint64) { for r := uint64(0); r < delta; r++ { s.addBlock(s.blocks[s.latest].StateProofTracking[protocol.StateProofBasic].StateProofNextRound) + s.waitForSignerAndBuilder(t) + } +} + +// used to simulate to workers that rounds have advanced, and stateproofs were created. +func (s *testWorkerStubs) advanceRoundsAndCreateStateProofs(t *testing.T, delta uint64) { + for r := uint64(0); r < delta; r++ { + s.mu.Lock() + interval := basics.Round(config.Consensus[s.blocks[s.latest].CurrentProtocol].StateProofInterval) + blk := s.blocks[s.latest] + stateProofNextRound := s.blocks[s.latest].StateProofTracking[protocol.StateProofBasic].StateProofNextRound + if blk.Round%interval == 0 && stateProofNextRound-interval < blk.Round { + stateProofNextRound += interval + } + s.mu.Unlock() + s.addBlock(stateProofNextRound) + s.waitForSignerAndBuilder(t) + } +} + +func (s *testWorkerStubs) mockCommit(upTo basics.Round) { + startRound := upTo + + s.mu.Lock() + for round := range s.blocks { + if round < startRound { + startRound = round + } + } + s.mu.Unlock() + s.notifyPrepareVoterCommit(startRound, upTo) + + for round := startRound; round <= upTo; round++ { + s.mu.Lock() + delete(s.blocks, round) + s.mu.Unlock() } } @@ -248,7 +434,7 @@ func (s *testWorkerStubs) waitOnSigWithTimeout(timeout time.Duration) ([]byte, e case sig := <-s.sigmsg: return sig, nil case <-time.After(timeout): - return nil, fmt.Errorf("timeout waiting on sigmsg") + return nil, errors.New("timeout waiting on sigmsg") } } @@ -257,33 +443,123 @@ func (s *testWorkerStubs) waitOnTxnWithTimeout(timeout time.Duration) (transacti case signedTx := <-s.txmsg: return signedTx, nil case <-time.After(timeout): - return transactions.SignedTxn{}, fmt.Errorf("timeout waiting on sigmsg") + return transactions.SignedTxn{}, errors.New("timeout waiting on stateproof txn") } } -func newTestWorkerDB(t testing.TB, s *testWorkerStubs, dba db.Accessor) *Worker { - return NewWorker(dba, logging.TestingLog(t), s, s, s, s) +func (s *testWorkerStubs) isRoundSigned(a *require.Assertions, w *Worker, round basics.Round) bool { + s.mu.Lock() + defer s.mu.Unlock() + for _, key := range s.keys { + var accountSigExists bool + err := w.db.Atomic(func(ctx context.Context, tx *sql.Tx) (err error) { + accountSigExists, err = sigExistsInDB(tx, round, key.Parent) + return err + }) + a.NoError(err) + if accountSigExists { + return true + } + } + + return false +} + +func newTestWorkerOnDiskDb(t testing.TB, s *testWorkerStubs) *Worker { + fn := fmt.Sprintf("%s.%d", strings.ReplaceAll(t.Name(), "/", "."), crypto.RandUint64()) + + ctx, cancel := context.WithCancel(context.Background()) + return &Worker{ + spDbFileName: fn, + log: logging.TestingLog(t), + accts: s, + ledger: s, + net: s, + txnSender: s, + builders: make(map[basics.Round]builder), + ctx: ctx, + shutdown: cancel, + signedCh: make(chan struct{}, 1), + } + } func newTestWorker(t testing.TB, s *testWorkerStubs) *Worker { - dbs, _ := dbOpenTest(t, true) - return newTestWorkerDB(t, s, dbs.Wdb) + worker := newTestWorkerOnDiskDb(t, s) + worker.inMemory = true + return worker +} + +func newPartKey(t testing.TB, parent basics.Address) account.PersistedParticipation { + version := config.Consensus[protocol.ConsensusCurrentVersion] + return newPartKeyWithVersion(t, version, parent) } // You must call defer part.Close() after calling this function, // since it creates a DB accessor but the caller must close it (required for mss) -func newPartKey(t testing.TB, parent basics.Address) account.PersistedParticipation { +func newPartKeyWithVersion(t testing.TB, protoParam config.ConsensusParams, parent basics.Address) account.PersistedParticipation { fn := fmt.Sprintf("%s.%d", strings.ReplaceAll(t.Name(), "/", "."), crypto.RandUint64()) partDB, err := db.MakeAccessor(fn, false, true) require.NoError(t, err) - proto := config.Consensus[protocol.ConsensusCurrentVersion] - part, err := account.FillDBWithParticipationKeys(partDB, parent, 0, basics.Round(15*proto.StateProofInterval), proto.DefaultKeyDilution) + part, err := account.FillDBWithParticipationKeys(partDB, parent, 0, basics.Round(15*protoParam.StateProofInterval), protoParam.DefaultKeyDilution) require.NoError(t, err) return part } +func countBuildersInDB(store db.Accessor) (nrows int, err error) { + err = store.Atomic(func(ctx context.Context, tx *sql.Tx) error { + row := tx.QueryRow("SELECT COUNT(*) FROM builders") + err := row.Scan(&nrows) + if err != nil { + return err + } + return nil + }) + + return +} + +func expectedNumberOfBuilders(stateproofInterval uint64, atRound basics.Round, nextStateProof basics.Round) int { + if nextStateProof > atRound { + return 0 + } + + return int((atRound-nextStateProof)/basics.Round(stateproofInterval) + 1) +} + +func createWorkerAndParticipants(t *testing.T, version protocol.ConsensusVersion, proto config.ConsensusParams) ([]account.Participation, *testWorkerStubs, *Worker) { + var keys []account.Participation + for i := 0; i < 2; i++ { + var parent basics.Address + crypto.RandBytes(parent[:]) + p := newPartKeyWithVersion(t, proto, parent) + defer p.Close() + keys = append(keys, p.Participation) + } + + s := newWorkerStubsWithVersion(t, keys, version, 10) + w := newTestWorker(t, s) + w.Start() + return keys, s, w +} + +// threshold == 0 meaning nothing was deleted. +func requireDeletedKeysToBeDeletedBefore(t *testing.T, s *testWorkerStubs, threshold basics.Round) { + s.mu.Lock() + defer s.mu.Unlock() + + for _, prt := range s.keys { + if threshold == 0 { + require.Equal(t, threshold, s.deletedKeysBeforeRoundMap[prt.ID()]) + continue + } + // minus one because we delete keys up to the round stated in the map but not including! + require.Greater(t, threshold, s.deletedKeysBeforeRoundMap[prt.ID()]-1) + } +} + func TestWorkerAllSigs(t *testing.T) { partitiontest.PartitionTest(t) @@ -296,20 +572,20 @@ func TestWorkerAllSigs(t *testing.T) { keys = append(keys, p.Participation) } - s := newWorkerStubs(t, keys, len(keys)) + s := newWorkerStubsWithChannel(t, keys, len(keys)) w := newTestWorker(t, s) w.Start() - defer w.Shutdown() + defer w.Stop() proto := config.Consensus[protocol.ConsensusCurrentVersion] - s.advanceLatest(proto.StateProofInterval + proto.StateProofInterval/2) + // at this point the ledger is at round 511 - we add 2 blocks to pass the state proof interval + s.advanceRoundsWithoutStateProof(t, 2) // Go through several iterations, making sure that we get // the signatures and certs broadcast at each round. for iter := 0; iter < 5; iter++ { - s.advanceLatest(proto.StateProofInterval) - - for i := 0; i < len(keys); i++ { + s.advanceRoundsWithoutStateProof(t, proto.StateProofInterval-1) + for i := 0; i < 2*len(keys); i++ { // Expect all signatures to be broadcast. _, err := s.waitOnSigWithTimeout(time.Second * 2) require.NoError(t, err) @@ -325,15 +601,9 @@ func TestWorkerAllSigs(t *testing.T) { if lastAttestedRound < basics.Round(iter+2)*basics.Round(proto.StateProofInterval) { continue } - require.Equal(t, lastAttestedRound, basics.Round(iter+2)*basics.Round(proto.StateProofInterval)) - stateProofLatestRound, err := s.BlockHdr(lastAttestedRound) - require.NoError(t, err) - - votersRound := lastAttestedRound.SubSaturate(basics.Round(proto.StateProofInterval)) - - msg, err := GenerateStateProofMessage(s, uint64(votersRound), stateProofLatestRound) + msg, err := GenerateStateProofMessage(s, lastAttestedRound) require.NoError(t, err) require.Equal(t, msg, tx.Txn.Message) @@ -350,6 +620,7 @@ func TestWorkerAllSigs(t *testing.T) { require.NoError(t, err) break } + s.advanceRoundsAndCreateStateProofs(t, 1) } } @@ -365,14 +636,16 @@ func TestWorkerPartialSigs(t *testing.T) { keys = append(keys, p.Participation) } - s := newWorkerStubs(t, keys, 10) + s := newWorkerStubsWithChannel(t, keys, 10) w := newTestWorker(t, s) w.Start() - defer w.Shutdown() + defer w.Stop() + + // at this point the ledger is at round 511 - we push add one block, so it will start to create state proofs + s.advanceRoundsWithoutStateProof(t, 1) proto := config.Consensus[protocol.ConsensusCurrentVersion] - s.advanceLatest(proto.StateProofInterval + proto.StateProofInterval/2) - s.advanceLatest(proto.StateProofInterval) + s.advanceRoundsWithoutStateProof(t, proto.StateProofInterval/2+1) for i := 0; i < len(keys); i++ { // Expect all signatures to be broadcast. @@ -388,7 +661,7 @@ func TestWorkerPartialSigs(t *testing.T) { } // Expect a state proof to be formed in the next StateProofInterval/2. - s.advanceLatest(proto.StateProofInterval / 2) + s.advanceRoundsWithoutStateProof(t, proto.StateProofInterval/2) tx, err := s.waitOnTxnWithTimeout(time.Second * 5) require.NoError(t, err) @@ -397,12 +670,7 @@ func TestWorkerPartialSigs(t *testing.T) { require.Equal(t, tx.Txn.Type, protocol.StateProofTx) require.Equal(t, lastAttestedRound, 2*basics.Round(proto.StateProofInterval)) - stateProofLatestRound, err := s.BlockHdr(lastAttestedRound) - require.NoError(t, err) - - votersRound := lastAttestedRound.SubSaturate(basics.Round(proto.StateProofInterval)) - - msg, err := GenerateStateProofMessage(s, uint64(votersRound), stateProofLatestRound) + msg, err := GenerateStateProofMessage(s, lastAttestedRound) require.NoError(t, err) require.Equal(t, msg, tx.Txn.Message) @@ -430,13 +698,13 @@ func TestWorkerInsufficientSigs(t *testing.T) { keys = append(keys, p.Participation) } - s := newWorkerStubs(t, keys, 10) + s := newWorkerStubsWithChannel(t, keys, 10) w := newTestWorker(t, s) w.Start() - defer w.Shutdown() + defer w.Stop() proto := config.Consensus[protocol.ConsensusCurrentVersion] - s.advanceLatest(3 * proto.StateProofInterval) + s.advanceRoundsWithoutStateProof(t, 3*proto.StateProofInterval) for i := 0; i < len(keys); i++ { // Expect all signatures to be broadcast. @@ -454,6 +722,8 @@ func TestWorkerInsufficientSigs(t *testing.T) { func TestWorkerRestart(t *testing.T) { partitiontest.PartitionTest(t) + a := require.New(t) + const expectedStateProofs = 5 var keys []account.Participation for i := 0; i < 10; i++ { @@ -464,35 +734,39 @@ func TestWorkerRestart(t *testing.T) { keys = append(keys, p.Participation) } - s := newWorkerStubs(t, keys, 10) + s := newWorkerStubsWithChannel(t, keys, len(keys)) + s.sigmsg = nil + w := newTestWorker(t, s) + w.Start() + defer w.Stop() proto := config.Consensus[protocol.ConsensusCurrentVersion] - s.advanceLatest(3*proto.StateProofInterval - 1) - - dbRand := crypto.RandUint64() - - formedAt := -1 - for i := 0; formedAt < 0 && i < len(keys); i++ { - // Give one key at a time to the worker, and then shut it down, - // to make sure that it will correctly save and restore these - // signatures across restart. - s.keys = keys[i : i+1] - dbs, _ := dbOpenTestRand(t, true, dbRand) - w := newTestWorkerDB(t, s, dbs.Wdb) + s.advanceRoundsWithoutStateProof(t, 1) + lastRound := uint64(0) + for i := 0; i < expectedStateProofs; i++ { + s.advanceRoundsWithoutStateProof(t, proto.StateProofInterval/2-1) + w.Stop() w.Start() + s.advanceRoundsWithoutStateProof(t, proto.StateProofInterval/2) + + var tx transactions.SignedTxn + // there will be several state proof txn. we extract them + for { + var err error + tx, err = s.waitOnTxnWithTimeout(time.Second * 5) + a.NoError(err) + if lastRound == 0 || lastRound < tx.Txn.Message.LastAttestedRound { + break + } - // Check if the cert formed - select { - case <-s.txmsg: - formedAt = i - case <-time.After(time.Second): } - w.Shutdown() + // since a state proof txn was created, we update the header with the next state proof round + // i.e network has accepted the state proof. + s.addBlock(basics.Round(tx.Txn.Message.LastAttestedRound + proto.StateProofInterval)) + lastRound = tx.Txn.Message.LastAttestedRound } - - require.True(t, formedAt > 1) - require.True(t, formedAt < 5) + a.Equal(uint64(expectedStateProofs+1), lastRound/proto.StateProofInterval) } func TestWorkerHandleSig(t *testing.T) { @@ -507,13 +781,13 @@ func TestWorkerHandleSig(t *testing.T) { keys = append(keys, p.Participation) } - s := newWorkerStubs(t, keys, 10) + s := newWorkerStubsWithChannel(t, keys, 10) w := newTestWorker(t, s) w.Start() - defer w.Shutdown() + defer w.Stop() proto := config.Consensus[protocol.ConsensusCurrentVersion] - s.advanceLatest(3 * proto.StateProofInterval) + s.advanceRoundsWithoutStateProof(t, 3*proto.StateProofInterval) for i := 0; i < len(keys); i++ { // Expect all signatures to be broadcast. @@ -530,12 +804,12 @@ func TestWorkerHandleSig(t *testing.T) { } } -func TestSignerDeletesUnneededStateProofKeys(t *testing.T) { +func TestWorkerIgnoresSignatureForNonCacheBuilders(t *testing.T) { partitiontest.PartitionTest(t) + a := require.New(t) var keys []account.Participation - nParticipants := 2 - for i := 0; i < nParticipants; i++ { + for i := 0; i < 2; i++ { var parent basics.Address crypto.RandBytes(parent[:]) p := newPartKey(t, parent) @@ -546,59 +820,176 @@ func TestSignerDeletesUnneededStateProofKeys(t *testing.T) { s := newWorkerStubs(t, keys, 10) w := newTestWorker(t, s) w.Start() - defer w.Shutdown() + defer w.Stop() proto := config.Consensus[protocol.ConsensusCurrentVersion] - s.advanceLatest(3 * proto.StateProofInterval) - // Expect all signatures to be broadcast. + targetRound := (buildersCacheLength + 1) * proto.StateProofInterval + + s.advanceRoundsWithoutStateProof(t, targetRound) + + // clean up the cache and clean up the signatures database so the handler will accept our signatures. + s.mu.Lock() + w.builders = make(map[basics.Round]builder) + a.NoError(w.db.Atomic(func(_ context.Context, tx *sql.Tx) error { + _, err := tx.Exec("DELETE from sigs") + return err + })) + s.mu.Unlock() + + // rounds [2*proto.StateProofInterval, 3*proto.StateProofInterval, ... (buildersCacheLength - 1)*proto.StateProofInterval] should be + // accepted by handleSig + i := uint64(0) + for ; i < (buildersCacheLength - 1); i++ { + fwd, err := sendSigToHandler(proto, i, w, a, s) + a.Equal(network.Broadcast, fwd) + a.NoError(err) + } + + // signature for (buildersCacheLength)*proto.StateProofInterval should be rejected - due to cache limit + fwd, err := sendSigToHandler(proto, i, w, a, s) + a.Equal(network.Ignore, fwd) + a.NoError(err) + i++ + + // newest signature should be accepted + fwd, err = sendSigToHandler(proto, i, w, a, s) + a.Equal(network.Broadcast, fwd) + a.NoError(err) - require.Zero(t, s.GetNumDeletedKeys()) - w.signStateProof(s.blocks[basics.Round(proto.StateProofInterval)]) - require.Equal(t, s.GetNumDeletedKeys(), nParticipants) } -func TestSignerDoesntDeleteKeysWhenDBDoesntStoreSigs(t *testing.T) { - partitiontest.PartitionTest(t) +func sendSigToHandler(proto config.ConsensusParams, i uint64, w *Worker, a *require.Assertions, s *testWorkerStubs) (network.ForwardingPolicy, error) { + rnd := basics.Round(2*proto.StateProofInterval + i*proto.StateProofInterval) + stateproofMessage, err := GenerateStateProofMessage(w.ledger, rnd) + a.NoError(err) - var keys []account.Participation - for i := 0; i < 2; i++ { - var parent basics.Address - crypto.RandBytes(parent[:]) - p := newPartKey(t, parent) - defer p.Close() - keys = append(keys, p.Participation) + hashedStateproofMessage := stateproofMessage.Hash() + spRecords := s.StateProofKeys(rnd) + sig, err := spRecords[0].StateProofSecrets.SignBytes(hashedStateproofMessage[:]) + a.NoError(err) + + msg := sigFromAddr{ + SignerAddress: spRecords[0].Account, + Round: rnd, + Sig: sig, } - s := newWorkerStubs(t, keys, 10) - dbs, _ := dbOpenTest(t, true) + fwd, err := w.handleSig(msg, msg.SignerAddress) + return fwd, err +} - logger := logging.NewLogger() - logger.SetOutput(io.Discard) +func TestKeysRemoveOnlyAfterStateProofAccepted(t *testing.T) { + partitiontest.PartitionTest(t) + + proto := config.Consensus[protocol.ConsensusCurrentVersion] + const expectedNumberOfStateProofs = uint64(3) + firstExpectedStateproof := basics.Round(proto.StateProofInterval * 2) - w := NewWorker(dbs.Wdb, logger, s, s, s, s) + keys, s, w := createWorkerAndParticipants(t, protocol.ConsensusCurrentVersion, proto) + defer w.Stop() - w.Start() - defer w.Shutdown() + s.advanceRoundsWithoutStateProof(t, expectedNumberOfStateProofs*proto.StateProofInterval) + + // since no state proof was confirmed (i.e the next state proof round == firstExpectedStateproof), we expect a node + // to keep its keys to sign the state proof firstExpectedStateproof. every participant should have keys for that round + checkedKeys := s.StateProofKeys(firstExpectedStateproof) + require.Equal(t, len(keys), len(checkedKeys)) + requireDeletedKeysToBeDeletedBefore(t, s, firstExpectedStateproof) // i should at this point have the keys to sign on round 512.... how come they were deleted? + + s.advanceRoundsAndCreateStateProofs(t, proto.StateProofInterval) + + // the first state proof was confirmed keys for that state proof can be removed + // So we should have the not deleted keys for proto.StateProofInterval + firstExpectedStateproof + requireDeletedKeysToBeDeletedBefore(t, s, firstExpectedStateproof+basics.Round(proto.StateProofInterval)) + checkedKeys = s.StateProofKeys(firstExpectedStateproof) + require.Equal(t, 0, len(checkedKeys)) + checkedKeys = s.StateProofKeys(firstExpectedStateproof + basics.Round(proto.StateProofInterval)) + require.Equal(t, len(keys), len(checkedKeys)) +} + +func TestKeysRemoveOnlyAfterStateProofAcceptedSmallIntervals(t *testing.T) { + partitiontest.PartitionTest(t) + + const stateProofIntervalForTest = 64 + const smallIntervalVersionName = "TestKeysRemoveOnlyAfterStateProofAcceptedSmallIntervals" proto := config.Consensus[protocol.ConsensusCurrentVersion] - s.advanceLatest(3 * proto.StateProofInterval) - // Expect all signatures to be broadcast. + proto.StateProofInterval = stateProofIntervalForTest + config.Consensus[smallIntervalVersionName] = proto + defer func() { + delete(config.Consensus, smallIntervalVersionName) + }() - require.NoError(t, w.db.Atomic( - func(ctx context.Context, tx *sql.Tx) error { - _, err := tx.Exec("DROP TABLE sigs") - return err - }), - ) + partitiontest.PartitionTest(t) + + const expectedNumberOfStateProofs = uint64(3) + firstExpectedStateproof := basics.Round(proto.StateProofInterval * 2) + + keys, s, w := createWorkerAndParticipants(t, smallIntervalVersionName, proto) + defer w.Stop() + + s.advanceRoundsWithoutStateProof(t, expectedNumberOfStateProofs*proto.StateProofInterval) + + // since no state proof was confirmed (i.e the next state proof round == firstExpectedStateproof), we expect a node + // to keep its keys to sign the state proof firstExpectedStateproof. every participant should have keys for that round + requireDeletedKeysToBeDeletedBefore(t, s, 0) + checkedKeys := s.StateProofKeys(firstExpectedStateproof) + require.Equal(t, len(keys), len(checkedKeys)) + + // confirm stateproof for firstExpectedStateproof + s.advanceRoundsAndCreateStateProofs(t, proto.StateProofInterval) + + // the first state proof was confirmed. However, since keylifetime is greater than the state proof interval + // the key for firstExpectedStateproof should be kept (since it is being reused on 3 * proto.StateProofInterval) + requireDeletedKeysToBeDeletedBefore(t, s, 0) + + checkedKeys = s.StateProofKeys(firstExpectedStateproof) + require.Equal(t, len(keys), len(checkedKeys)) + checkedKeys = s.StateProofKeys(firstExpectedStateproof + basics.Round(proto.StateProofInterval)) + require.Equal(t, len(keys), len(checkedKeys)) +} + +func TestKeysRemoveOnlyAfterStateProofAcceptedLargeIntervals(t *testing.T) { + partitiontest.PartitionTest(t) + + const stateProofIntervalForTest = 260 + const smallIntervalVersionName = "TestKeysRemoveOnlyAfterStateProofAcceptedLargeIntervals" + proto := config.Consensus[protocol.ConsensusCurrentVersion] + proto.StateProofInterval = stateProofIntervalForTest + config.Consensus[smallIntervalVersionName] = proto + defer func() { + delete(config.Consensus, smallIntervalVersionName) + }() + + const expectedNumberOfStateProofs = uint64(3) + firstExpectedStateproof := basics.Round(proto.StateProofInterval * 2) + + keys, s, w := createWorkerAndParticipants(t, protocol.ConsensusCurrentVersion, proto) + defer w.Stop() - w.signStateProof(s.blocks[3*basics.Round(proto.StateProofInterval)]) - require.Zero(t, s.GetNumDeletedKeys()) + s.advanceRoundsWithoutStateProof(t, expectedNumberOfStateProofs*proto.StateProofInterval) + // since no state proof was confirmed (i.e the next state proof round == firstExpectedStateproof), we expect a node + // to keep its keys to sign the state proof firstExpectedStateproof. every participant should have keys for that round + requireDeletedKeysToBeDeletedBefore(t, s, firstExpectedStateproof) + checkedKeys := s.StateProofKeys(firstExpectedStateproof) + require.Equal(t, len(keys), len(checkedKeys)) + + // confirm stateproof for firstExpectedStateproof + s.advanceRoundsAndCreateStateProofs(t, proto.StateProofInterval) + + // the first state proof was confirmed keys for that state proof can be removed + requireDeletedKeysToBeDeletedBefore(t, s, basics.Round(proto.StateProofInterval)+firstExpectedStateproof) + + checkedKeys = s.StateProofKeys(firstExpectedStateproof) + require.Equal(t, 0, len(checkedKeys)) + checkedKeys = s.StateProofKeys(firstExpectedStateproof + basics.Round(proto.StateProofInterval)) + require.Equal(t, len(keys), len(checkedKeys)) } -func TestWorkerRemoveBuildersAndSignatures(t *testing.T) { +func TestWorkersBuildersCacheAndSignatures(t *testing.T) { partitiontest.PartitionTest(t) a := require.New(t) - const expectedStateProofs = 8 + const expectedStateProofs = buildersCacheLength + 2 var keys []account.Participation for i := 0; i < 10; i++ { var parent basics.Address @@ -611,53 +1002,73 @@ func TestWorkerRemoveBuildersAndSignatures(t *testing.T) { s := newWorkerStubs(t, keys, len(keys)) w := newTestWorker(t, s) w.Start() - defer w.Shutdown() + defer w.Stop() proto := config.Consensus[protocol.ConsensusCurrentVersion] - s.advanceLatest(proto.StateProofInterval + proto.StateProofInterval/2) - for iter := 0; iter < expectedStateProofs; iter++ { - s.advanceLatest(proto.StateProofInterval) - tx := <-s.txmsg - a.Equal(tx.Txn.Type, protocol.StateProofTx) + // we break the loop into two part since we don't want to add a state proof round (Round % 256 == 0) + for iter := 0; iter < expectedStateProofs-1; iter++ { + s.advanceRoundsWithoutStateProof(t, proto.StateProofInterval) } + s.advanceRoundsWithoutStateProof(t, proto.StateProofInterval/2) + + a.Equal(buildersCacheLength, len(w.builders)) + verifyBuilderCache(proto, w, a, expectedStateProofs) - err := waitForBuilderAndSignerToWaitOnRound(s) + countDB, err := countBuildersInDB(w.db) a.NoError(err) - a.Equal(expectedStateProofs, len(w.builders)) + a.Equal(expectedStateProofs, countDB) + threshold := onlineBuildersThreshold(&proto, basics.Round(512)) // 512 since no StateProofs are confirmed yet (512 is the first, commitment at 256) var roundSigs map[basics.Round][]pendingSig err = w.db.Atomic(func(ctx context.Context, tx *sql.Tx) (err error) { - roundSigs, err = getPendingSigs(tx) + roundSigs, err = getPendingSigs(tx, threshold, basics.Round(256+proto.StateProofInterval*expectedStateProofs), false) return }) + a.NoError(err) + a.Equal(buildersCacheLength, len(roundSigs)) // Number of broadcasted sigs should be the same as number of (online) cached builders. - a.Equal(expectedStateProofs, len(roundSigs)) - - // add block that confirm a state proof for interval: expectedStateProofs - 1 - s.mu.Lock() - s.addBlock(basics.Round((expectedStateProofs - 1) * config.Consensus[protocol.ConsensusCurrentVersion].StateProofInterval)) - s.mu.Unlock() + /* + add block that confirm a state proof for interval: expectedStateProofs + */ + s.addBlock(basics.Round((expectedStateProofs) * config.Consensus[protocol.ConsensusCurrentVersion].StateProofInterval)) + s.waitForSignerAndBuilder(t) - err = waitForBuilderAndSignerToWaitOnRound(s) + count := expectedNumberOfBuilders(proto.StateProofInterval, s.latest, basics.Round((expectedStateProofs)*config.Consensus[protocol.ConsensusCurrentVersion].StateProofInterval)) + countDB, err = countBuildersInDB(w.db) a.NoError(err) - a.Equal(3, len(w.builders)) + a.Equal(count, countDB) + threshold = onlineBuildersThreshold(&proto, s.blocks[s.latest].StateProofTracking[protocol.StateProofBasic].StateProofNextRound) + maxStateProofRnd := s.latest.RoundDownToMultipleOf(basics.Round(proto.StateProofInterval)) err = w.db.Atomic(func(ctx context.Context, tx *sql.Tx) (err error) { - roundSigs, err = getPendingSigs(tx) + roundSigs, err = getPendingSigs(tx, threshold, maxStateProofRnd, false) return }) + a.NoError(err) + a.Equal(count, len(roundSigs)) +} - a.Equal(3, len(roundSigs)) +func verifyBuilderCache(proto config.ConsensusParams, w *Worker, a *require.Assertions, expectedStateProofs uint64) { + for i := uint64(0); i < buildersCacheLength-1; i++ { + rnd := proto.StateProofInterval*2 + proto.StateProofInterval*i + _, exists := w.builders[basics.Round(rnd)] + a.True(exists) + } + _, exists := w.builders[basics.Round(proto.StateProofInterval*(expectedStateProofs+1))] + a.True(exists) } -func TestWorkerBuildersRecoveryLimit(t *testing.T) { +// TestSignatureBroadcastPolicy makes sure that during half of a state proof interval, every online account +// will broadcast only buildersCacheLength amount of signatures +func TestSignatureBroadcastPolicy(t *testing.T) { partitiontest.PartitionTest(t) a := require.New(t) - proto := config.Consensus[protocol.ConsensusCurrentVersion] + const numberOfParticipants = 5 + const expectedStateProofs = buildersCacheLength + 2 var keys []account.Participation - for i := 0; i < 10; i++ { + for i := 0; i < numberOfParticipants; i++ { var parent basics.Address crypto.RandBytes(parent[:]) p := newPartKey(t, parent) @@ -668,83 +1079,98 @@ func TestWorkerBuildersRecoveryLimit(t *testing.T) { s := newWorkerStubs(t, keys, len(keys)) w := newTestWorker(t, s) w.Start() - defer w.Shutdown() + defer w.Stop() - s.advanceLatest(proto.StateProofInterval + proto.StateProofInterval/2) + proto := config.Consensus[protocol.ConsensusCurrentVersion] - for iter := uint64(0); iter < proto.StateProofMaxRecoveryIntervals+1; iter++ { - s.advanceLatest(proto.StateProofInterval) - tx := <-s.txmsg - a.Equal(tx.Txn.Type, protocol.StateProofTx) + for iter := 0; iter < expectedStateProofs-1; iter++ { + s.advanceRoundsWithoutStateProof(t, proto.StateProofInterval) } + // set the latest block to be at round r, where r % 256 == 0 + s.advanceRoundsWithoutStateProof(t, 1) - // since this test involves go routine, we would like to make sure that when - // we sample the builder it already processed our current round. - // in order to that, we wait for singer and the builder to wait. - // then we push one more round so the builder could process it (since the builder might skip rounds) - err := waitForBuilderAndSignerToWaitOnRound(s) - a.NoError(err) - s.mu.Lock() - s.addBlock(basics.Round(proto.StateProofInterval * 2)) - s.mu.Unlock() - err = waitForBuilderAndSignerToWaitOnRound(s) - a.NoError(err) + checkSignatureBroadcastHalfInterval(t, proto, expectedStateProofs, s, numberOfParticipants, a) + checkSignatureBroadcastHalfInterval(t, proto, expectedStateProofs, s, numberOfParticipants, a) +} - // should not give up on rounds - a.Equal(proto.StateProofMaxRecoveryIntervals+1, uint64(len(w.builders))) +func checkSignatureBroadcastHalfInterval(t *testing.T, proto config.ConsensusParams, expectedStateProofs uint64, s *testWorkerStubs, numberOfParticipants int, a *require.Assertions) { + roundSigs := make(map[basics.Round]int) + for i := uint64(2); i < buildersCacheLength; i++ { + roundSigs[basics.Round(i*proto.StateProofInterval)] = 0 + } + roundSigs[basics.Round((expectedStateProofs+1)*proto.StateProofInterval)] = 0 - var roundSigs map[basics.Round][]pendingSig - err = w.db.Atomic(func(ctx context.Context, tx *sql.Tx) (err error) { - roundSigs, err = getPendingSigs(tx) - return - }) - a.Equal(proto.StateProofMaxRecoveryIntervals+1, uint64(len(roundSigs))) + // empty all pending sigs + s.sigmsg = make(chan []byte, 1024*1024) - s.advanceLatest(proto.StateProofInterval) - tx := <-s.txmsg - a.Equal(tx.Txn.Type, protocol.StateProofTx) + s.advanceRoundsWithoutStateProof(t, proto.StateProofInterval/2) + for i := 0; i < numberOfParticipants*buildersCacheLength; i++ { + sigMessage := sigFromAddr{} + sigMessageBytes, err := s.waitOnSigWithTimeout(time.Second * 2) + a.NoError(err) - err = waitForBuilderAndSignerToWaitOnRound(s) - a.NoError(err) - s.mu.Lock() - s.addBlock(basics.Round(proto.StateProofInterval * 2)) - s.mu.Unlock() - err = waitForBuilderAndSignerToWaitOnRound(s) - a.NoError(err) + err = protocol.Decode(sigMessageBytes, &sigMessage) + a.NoError(err) + + roundSigs[sigMessage.Round]++ + } + + a.Equal(buildersCacheLength, len(roundSigs)) + for _, numOfSignatures := range roundSigs { + a.Equal(numberOfParticipants, numOfSignatures) + } +} + +func TestWorkerDoesNotLimitBuildersAndSignaturesOnDisk(t *testing.T) { + partitiontest.PartitionTest(t) + a := require.New(t) + + proto := config.Consensus[protocol.ConsensusCurrentVersion] + var keys []account.Participation + for i := 0; i < 10; i++ { + var parent basics.Address + crypto.RandBytes(parent[:]) + p := newPartKey(t, parent) + defer p.Close() + keys = append(keys, p.Participation) + } + + s := newWorkerStubs(t, keys, len(keys)) + w := newTestWorker(t, s) + w.Start() + defer w.Stop() + + for iter := uint64(0); iter < proto.StateProofMaxRecoveryIntervals+1; iter++ { + s.advanceRoundsWithoutStateProof(t, proto.StateProofInterval) + } // should not give up on rounds - a.Equal(proto.StateProofMaxRecoveryIntervals+1, uint64(len(w.builders))) + a.Equal(buildersCacheLength, len(w.builders)) + countDB, err := countBuildersInDB(w.db) + a.NoError(err) + a.Equal(proto.StateProofMaxRecoveryIntervals+1, uint64(countDB)) - roundSigs = make(map[basics.Round][]pendingSig) - err = w.db.Atomic(func(ctx context.Context, tx *sql.Tx) (err error) { - roundSigs, err = getPendingSigs(tx) - return - }) - a.Equal(proto.StateProofMaxRecoveryIntervals+1, uint64(len(roundSigs))) + sigsCount := countAllSignaturesInDB(t, w.db) + a.Equal(proto.StateProofMaxRecoveryIntervals+1, sigsCount) } -func waitForBuilderAndSignerToWaitOnRound(s *testWorkerStubs) error { - const maxRetries = 10000 - i := 0 - for { - s.mu.Lock() - r := s.latest + 1 - // in order to make sure the builder and the signer are waiting for a round we need to make sure - // that round r has c channel and r +1 doesn't have. - // we also want to make sure that the builder and the singer are waiting - isWaitingForRound := s.waiters[r] != nil && s.waiters[r+1] == nil - isWaitingForRound = isWaitingForRound && (s.waitersCount[r] == 2) - s.mu.Unlock() - if !isWaitingForRound { - if i == maxRetries { - return fmt.Errorf("timeout while waiting for round") - } - i++ - time.Sleep(time.Millisecond) - continue +func countAllSignaturesInDB(t *testing.T, accessor db.Accessor) uint64 { + var roundSigs map[basics.Round][]pendingSig + err := accessor.Atomic(func(ctx context.Context, tx *sql.Tx) (err error) { + query := "SELECT sprnd, signer, sig, from_this_node FROM sigs " + rows, err := tx.Query(query) + if err != nil { + return err + } + defer rows.Close() + roundSigs, err = rowsToPendingSigs(rows) + if err != nil { + return err } return nil - } + }) + require.NoError(t, err) + return uint64(len(roundSigs)) } type sigOrigin int @@ -778,14 +1204,11 @@ func getSignaturesInDatabase(t *testing.T, numAddresses int, sigFrom sigOrigin) signatureBcasted[parent] = 0 } - tns = newWorkerStubs(t, keys, len(keys)) - spw = newTestWorker(t, tns) + tns = newWorkerStubsWithChannel(t, keys, len(keys)) + spw = newTestWorkerOnDiskDb(t, tns) - // Prepare the database - err := spw.db.Atomic(func(ctx context.Context, tx *sql.Tx) error { - return initDB(tx) - }) - require.NoError(t, err) + // we don't need go routines to run so just create the db + spw.initDb(false) // All the keys are for round 255. This way, starting the period at 256, // there will be no disqualified signatures from broadcasting because they are @@ -835,6 +1258,8 @@ func getSignaturesInDatabase(t *testing.T, numAddresses int, sigFrom sigOrigin) func TestSigBroacastTwoPerSig(t *testing.T) { partitiontest.PartitionTest(t) signatureBcasted, fromThisNode, tns, spw := getSignaturesInDatabase(t, 10, sigAlternateOrigin) + defer os.Remove(spw.spDbFileName) + defer spw.db.Close() for periods := 1; periods < 10; periods += 3 { sendReceiveCountMessages(t, tns, signatureBcasted, fromThisNode, spw, periods) @@ -867,7 +1292,7 @@ func sendReceiveCountMessages(t *testing.T, tns *testWorkerStubs, signatureBcast // Broadcast the messages for brnd := 257; brnd < 257+int(proto.StateProofInterval)*periods; brnd++ { - spw.broadcastSigs(basics.Round(brnd), proto) + spw.broadcastSigs(basics.Round(brnd), basics.Round(512), proto) } close(tns.sigmsg) @@ -896,15 +1321,14 @@ func TestBuilderGeneratesValidStateProofTXN(t *testing.T) { keys = append(keys, p.Participation) } - s := newWorkerStubs(t, keys, len(keys)) + s := newWorkerStubsWithChannel(t, keys, len(keys)) w := newTestWorker(t, s) w.Start() - defer w.Shutdown() + defer w.Stop() proto := config.Consensus[protocol.ConsensusCurrentVersion] - s.advanceLatest(proto.StateProofInterval + proto.StateProofInterval/2) - s.advanceLatest(proto.StateProofInterval) + s.advanceRoundsWithoutStateProof(t, proto.StateProofInterval) for i := 0; i < len(keys); i++ { // Expect all signatures to be broadcast. @@ -924,10 +1348,13 @@ func TestForwardNotFromThisNodeSecondHalf(t *testing.T) { partitiontest.PartitionTest(t) _, _, tns, spw := getSignaturesInDatabase(t, 10, sigNotFromThisNode) + defer os.Remove(spw.spDbFileName) + defer spw.db.Close() proto := config.Consensus[protocol.ConsensusCurrentVersion] for brnd := 0; brnd < int(proto.StateProofInterval*10); brnd++ { - spw.broadcastSigs(basics.Round(brnd), proto) + stateProofNextRound := basics.Round(brnd).RoundDownToMultipleOf(basics.Round(proto.StateProofInterval)) + spw.broadcastSigs(basics.Round(brnd), stateProofNextRound, proto) select { case <-tns.sigmsg: // The message is broadcast in the second half of the period @@ -943,10 +1370,13 @@ func TestForwardNotFromThisNodeFirstHalf(t *testing.T) { partitiontest.PartitionTest(t) signatureBcasted, fromThisNode, tns, spw := getSignaturesInDatabase(t, 10, sigAlternateOrigin) + defer os.Remove(spw.spDbFileName) + defer spw.db.Close() proto := config.Consensus[protocol.ConsensusCurrentVersion] for brnd := 0; brnd < int(proto.StateProofInterval*10); brnd++ { - spw.broadcastSigs(basics.Round(brnd), proto) + stateProofNextRound := basics.Round(brnd).RoundDownToMultipleOf(basics.Round(proto.StateProofInterval)) + spw.broadcastSigs(basics.Round(brnd), stateProofNextRound, proto) select { case bMsg := <-tns.sigmsg: sfa := sigFromAddr{} @@ -981,10 +1411,9 @@ func setBlocksAndMessage(t *testing.T, sigRound basics.Round) (s *testWorkerStub s = newWorkerStubs(t, []account.Participation{p.Participation}, 10) w = newTestWorker(t, s) + w.initDb(w.inMemory) - for r := 0; r < int(proto.StateProofInterval)*2; r++ { - s.addBlock(basics.Round(proto.StateProofInterval * 2)) - } + s.addBlock(basics.Round(proto.StateProofInterval * 2)) msg = sigFromAddr{ SignerAddress: address, @@ -1028,12 +1457,7 @@ func TestWorkerHandleSigRoundNotInLedger(t *testing.T) { fwd, err := w.handleSig(msg, msg.SignerAddress) require.Equal(t, network.Ignore, fwd) - expected := ledgercore.ErrNoEntry{ - Round: msg.Round, - Latest: w.ledger.Latest(), - Committed: w.ledger.Latest(), - } - require.Equal(t, expected, err) + require.ErrorContains(t, err, "latest round is smaller than given") } // relays reject signatures for wrong message (sig verification fails) @@ -1051,10 +1475,7 @@ func TestWorkerHandleSigWrongSignature(t *testing.T) { fwd, err := w.handleSig(msg, msg.SignerAddress) require.Equal(t, network.Disconnect, fwd) - expected2 := fmt.Errorf("%w: %v", - merklesignature.ErrSignatureSchemeVerificationFailed, - merklearray.ErrRootMismatch) - require.Equal(t, expected2, err) + require.ErrorIs(t, err, errSignatureVerification) } // relays reject signatures for address not in top N @@ -1076,8 +1497,9 @@ func TestWorkerHandleSigAddrsNotInTopN(t *testing.T) { keys = append(keys, p.Participation) } - s := newWorkerStubs(t, keys[0:proto.StateProofTopVoters], 10) + s := newWorkerStubsWithChannel(t, keys[0:proto.StateProofTopVoters], 10) w := newTestWorker(t, s) + w.initDb(w.inMemory) for r := 0; r < int(proto.StateProofInterval)*2; r++ { s.addBlock(basics.Round(r)) @@ -1097,9 +1519,7 @@ func TestWorkerHandleSigAddrsNotInTopN(t *testing.T) { fwd, err := w.handleSig(msg, msg.SignerAddress) require.Equal(t, network.Disconnect, fwd) - expected3 := fmt.Errorf("handleSig: %v not in participants for %d", - msg.SignerAddress, msg.Round) - require.Equal(t, expected3, err) + require.ErrorIs(t, err, errAddressNotInVoters) } // Signature already part of the builderForRound, ignore @@ -1110,9 +1530,7 @@ func TestWorkerHandleSigAlreadyIn(t *testing.T) { lastRound := proto.StateProofInterval * 2 s, w, msg, _ := setBlocksAndMessage(t, basics.Round(lastRound)) - latestBlockHeader, err := w.ledger.BlockHdr(basics.Round(lastRound)) - require.NoError(t, err) - stateproofMessage, err := GenerateStateProofMessage(w.ledger, proto.StateProofInterval, latestBlockHeader) + stateproofMessage, err := GenerateStateProofMessage(w.ledger, basics.Round(lastRound)) require.NoError(t, err) hashedStateproofMessage := stateproofMessage.Hash() @@ -1122,9 +1540,7 @@ func TestWorkerHandleSigAlreadyIn(t *testing.T) { msg.Sig = sig // Create the database - err = w.db.Atomic(func(ctx context.Context, tx *sql.Tx) error { - return initDB(tx) - }) + err = makeStateProofDB(w.db) require.NoError(t, err) msgBytes := protocol.Encode(&msg) @@ -1134,7 +1550,7 @@ func TestWorkerHandleSigAlreadyIn(t *testing.T) { }) require.Equal(t, network.OutgoingMessage{Action: network.Broadcast}, reply) - // The sig is already there. Shoud get error + // The sig is already there. Should get error reply = w.handleSigMessage(network.IncomingMessage{ Data: msgBytes, }) @@ -1152,9 +1568,13 @@ func TestWorkerHandleSigExceptionsDbError(t *testing.T) { proto := config.Consensus[protocol.ConsensusCurrentVersion] lastRound := proto.StateProofInterval * 2 s, w, msg, _ := setBlocksAndMessage(t, basics.Round(lastRound)) - latestBlockHeader, err := w.ledger.BlockHdr(basics.Round(lastRound)) - require.NoError(t, err) - stateproofMessage, err := GenerateStateProofMessage(w.ledger, proto.StateProofInterval, latestBlockHeader) + + require.NoError(t, w.db.Atomic(func(_ context.Context, tx *sql.Tx) error { + _, err := tx.Exec("drop table sigs ") + return err + })) + + stateproofMessage, err := GenerateStateProofMessage(w.ledger, basics.Round(lastRound)) require.NoError(t, err) hashedStateproofMessage := stateproofMessage.Hash() @@ -1174,17 +1594,11 @@ func TestWorkerHandleSigExceptionsDbError(t *testing.T) { require.Contains(t, "no such table: sigs", err.Error()) } -// relays reject signatures when could not makeBuilderForRound +// relays reject signatures when could not createAndPersistBuilder func TestWorkerHandleSigCantMakeBuilder(t *testing.T) { partitiontest.PartitionTest(t) proto := config.Consensus[protocol.ConsensusCurrentVersion] - origProto := proto - defer func() { - config.Consensus[protocol.ConsensusCurrentVersion] = origProto - }() - proto.StateProofInterval = 512 - config.Consensus[protocol.ConsensusCurrentVersion] = proto var address basics.Address crypto.RandBytes(address[:]) @@ -1193,16 +1607,19 @@ func TestWorkerHandleSigCantMakeBuilder(t *testing.T) { s := newWorkerStubs(t, []account.Participation{p.Participation}, 10) w := newTestWorker(t, s) + w.Start() + defer w.Stop() - for r := 0; r < int(proto.StateProofInterval)*2; r++ { - s.addBlock(basics.Round(512)) - } + s.addBlock(basics.Round(proto.StateProofInterval * 2)) + + s.mu.Lock() // remove the first block from the ledger - delete(s.blocks, 0) + delete(s.blocks, 256) + s.mu.Unlock() msg := sigFromAddr{ SignerAddress: address, - Round: basics.Round(proto.StateProofInterval), + Round: basics.Round(proto.StateProofInterval * 2), Sig: merklesignature.Signature{}, } @@ -1215,14 +1632,14 @@ func TestWorkerHandleSigCantMakeBuilder(t *testing.T) { fwd, err := w.handleSig(msg, msg.SignerAddress) require.Equal(t, network.Ignore, fwd) expected := ledgercore.ErrNoEntry{ - Round: 0, + Round: 256, Latest: w.ledger.Latest(), Committed: w.ledger.Latest(), } require.Equal(t, expected, err) } -// relays reject signiture for a round where StateProofInterval is 0 +// relays reject signature for a round where StateProofInterval is 0 func TestWorkerHandleSigIntervalZero(t *testing.T) { partitiontest.PartitionTest(t) @@ -1234,8 +1651,7 @@ func TestWorkerHandleSigIntervalZero(t *testing.T) { proto.StateProofInterval = 0 config.Consensus[protocol.ConsensusCurrentVersion] = proto - intervalRound := basics.Round(proto.StateProofInterval) - _, w, msg, msgBytes := setBlocksAndMessage(t, intervalRound*2) + _, w, msg, msgBytes := setBlocksAndMessage(t, 1) reply := w.handleSigMessage(network.IncomingMessage{ Data: msgBytes, @@ -1249,7 +1665,7 @@ func TestWorkerHandleSigIntervalZero(t *testing.T) { require.Equal(t, expected, err) } -// relays reject signiture for a round not multiple of StateProofInterval +// relays reject signature for a round not multiple of StateProofInterval func TestWorkerHandleSigNotOnInterval(t *testing.T) { partitiontest.PartitionTest(t) @@ -1289,3 +1705,326 @@ func TestWorkerHandleSigCorrupt(t *testing.T) { }) require.Equal(t, network.OutgoingMessage{Action: network.Disconnect}, reply) } + +func verifyPersistedBuilders(a *require.Assertions, w *Worker) { + w.mu.Lock() + + defer w.mu.Unlock() + for k, v := range w.builders { + var builderFromDisk builder + a.NoError( + w.db.Atomic(func(_ context.Context, tx *sql.Tx) error { + var err error + builderFromDisk, err = getBuilder(tx, k) + return err + })) + a.Equal(v.BuilderPersistedFields, builderFromDisk.BuilderPersistedFields) + } +} + +func TestWorkerCacheAndDiskAfterRestart(t *testing.T) { + partitiontest.PartitionTest(t) + a := require.New(t) + + const expectedStateProofs = buildersCacheLength + 1 + var keys []account.Participation + for i := 0; i < 10; i++ { + var parent basics.Address + crypto.RandBytes(parent[:]) + p := newPartKey(t, parent) + defer p.Close() + keys = append(keys, p.Participation) + } + + s := newWorkerStubs(t, keys, len(keys)) + w := newTestWorkerOnDiskDb(t, s) + defer os.Remove(w.spDbFileName) + w.Start() + + proto := config.Consensus[protocol.ConsensusCurrentVersion] + + // we break the loop into two part since we don't want to add a state proof round (Round % 256 == 0) + for iter := 0; iter < expectedStateProofs-1; iter++ { + s.advanceRoundsWithoutStateProof(t, proto.StateProofInterval) + } + s.advanceRoundsWithoutStateProof(t, proto.StateProofInterval/2) + + // at this point we expect the number of builders in memory to be equal to buildersCacheLength + a.Equal(buildersCacheLength, len(w.builders)) + countDB, err := countBuildersInDB(w.db) + a.NoError(err) + a.Equal(expectedStateProofs, countDB) + + threshold := onlineBuildersThreshold(&proto, basics.Round(512)) // 512 since no StateProofs are confirmed yet (512 is the first, commitment at 256) + var roundSigs map[basics.Round][]pendingSig + err = w.db.Atomic(func(ctx context.Context, tx *sql.Tx) (err error) { + roundSigs, err = getPendingSigs(tx, threshold, basics.Round(256+proto.StateProofInterval*expectedStateProofs), false) + return + }) + a.NoError(err) + a.Equal(buildersCacheLength, len(roundSigs)) // Number of broadcasted sigs should be the same as number of (online) cached builders. + + // restart worker + w.Stop() + // we make sure that the worker will not be able to create a builder by disabling the mock ledger + s.keysForVoters = []account.Participation{} + + w.Start() + defer w.Stop() + + a.Equal(buildersCacheLength, len(w.builders)) + countDB, err = countBuildersInDB(w.db) + a.NoError(err) + a.Equal(expectedStateProofs, countDB) + + verifyPersistedBuilders(a, w) +} + +func TestWorkerInitOnlySignaturesInDatabase(t *testing.T) { + partitiontest.PartitionTest(t) + a := require.New(t) + + const expectedStateProofs = buildersCacheLength + 1 + var keys []account.Participation + for i := 0; i < 10; i++ { + var parent basics.Address + crypto.RandBytes(parent[:]) + p := newPartKey(t, parent) + defer p.Close() + keys = append(keys, p.Participation) + } + + s := newWorkerStubs(t, keys, len(keys)) + w := newTestWorkerOnDiskDb(t, s) + defer os.Remove(w.spDbFileName) + w.Start() + + proto := config.Consensus[protocol.ConsensusCurrentVersion] + + // we break the loop into two part since we don't want to add a state proof round (Round % 256 == 0) + for iter := 0; iter < expectedStateProofs-1; iter++ { + s.advanceRoundsWithoutStateProof(t, proto.StateProofInterval) + } + s.advanceRoundsWithoutStateProof(t, proto.StateProofInterval/2) + + // at this point we expect the number of builders in memory to be bound with buildersCacheLength + a.Equal(buildersCacheLength, len(w.builders)) + countDB, err := countBuildersInDB(w.db) + a.NoError(err) + a.Equal(expectedStateProofs, countDB) + + threshold := onlineBuildersThreshold(&proto, basics.Round(512)) // 512 since no StateProofs are confirmed yet (512 is the first, commitment at 256) + var roundSigs map[basics.Round][]pendingSig + err = w.db.Atomic(func(ctx context.Context, tx *sql.Tx) (err error) { + roundSigs, err = getPendingSigs(tx, threshold, basics.Round(256+proto.StateProofInterval*expectedStateProofs), false) + return + }) + a.NoError(err) + a.Equal(buildersCacheLength, len(roundSigs)) // Number of broadcasted sigs should be the same as number of (online) cached builders. + + w.Stop() + + accessor, err := db.MakeAccessor(w.spDbFileName, false, false) + a.NoError(err) + // we now remove all builders from the table. This will cause the worker to create the builders from the ledger. + a.NoError(accessor.Atomic(func(_ context.Context, tx *sql.Tx) error { + _, err := tx.Exec("DELETE from builders") + return err + })) + accessor.Close() + + w.Start() + defer w.Stop() + + a.Equal(buildersCacheLength, len(w.builders)) + countDB, err = countBuildersInDB(w.db) + a.NoError(err) + a.Equal(buildersCacheLength, countDB) + + verifyPersistedBuilders(a, w) +} + +func TestWorkerLoadsBuilderAndSignatureUponMsgRecv(t *testing.T) { + partitiontest.PartitionTest(t) + + proto := config.Consensus[protocol.ConsensusCurrentVersion] + lastRound := proto.StateProofInterval * 2 + s, w, msg, _ := setBlocksAndMessage(t, basics.Round(lastRound)) + + stateproofMessage, err := GenerateStateProofMessage(w.ledger, basics.Round(lastRound)) + require.NoError(t, err) + + hashedStateproofMessage := stateproofMessage.Hash() + spRecords := s.StateProofKeys(basics.Round(proto.StateProofInterval * 2)) + sig, err := spRecords[0].StateProofSecrets.SignBytes(hashedStateproofMessage[:]) + require.NoError(t, err) + + msg.Sig = sig + // Create the database + err = makeStateProofDB(w.db) + require.NoError(t, err) + + msgBytes := protocol.Encode(&msg) + // add signature so builder will get loaded + reply := w.handleSigMessage(network.IncomingMessage{ + Data: msgBytes, + }) + require.Equal(t, network.OutgoingMessage{Action: network.Broadcast}, reply) + + // we make sure that the worker will not be able to create a builder by disabling the mock ledger + s.keysForVoters = []account.Participation{} + + // removing the builder from memory will force the worker to load it from disk + w.builders = make(map[basics.Round]builder) + _, exists := w.builders[msg.Round] + require.False(t, exists) + fwd, err := w.handleSig(msg, msg.SignerAddress) + // we expect the handler to ignore the signature since the builder and the old signature were loaded + require.Equal(t, network.Ignore, fwd) + require.NoError(t, err) + _, exists = w.builders[msg.Round] + require.True(t, exists) + + // verify that builders can be loaded even if there are no signatures + w.builders = make(map[basics.Round]builder) + _, exists = w.builders[msg.Round] + require.False(t, exists) + require.NoError(t, w.db.Atomic(func(_ context.Context, tx *sql.Tx) error { + _, err := tx.Exec("DELETE from sigs") + return err + })) + fwd, err = w.handleSig(msg, msg.SignerAddress) + require.Equal(t, network.Broadcast, fwd) + require.NoError(t, err) + _, exists = w.builders[msg.Round] + require.True(t, exists) + + // remove builder from disk and memory we fail the builder creation (since the ledger also returns error) + require.NoError(t, w.db.Atomic(func(_ context.Context, tx *sql.Tx) error { + _, err := tx.Exec("DELETE from builders") + return err + })) + w.builders = make(map[basics.Round]builder) + _, err = w.handleSig(msg, msg.SignerAddress) + require.ErrorIs(t, err, errEmptyVoters) + _, exists = w.builders[msg.Round] + require.False(t, exists) +} + +func TestWorkerCreatesBuildersOnCommit(t *testing.T) { + partitiontest.PartitionTest(t) + a := require.New(t) + + proto := config.Consensus[protocol.ConsensusCurrentVersion] + + _, s, w := createWorkerAndParticipants(t, protocol.ConsensusCurrentVersion, proto) + defer w.Stop() + + // We remove the signer's keys to stop it from generating builders. + s.keys = []account.Participation{} + + firstBuilderRound := basics.Round(proto.StateProofInterval * 2) + + // We start on round 511, so the callback should be called when committing the next round. + s.advanceRoundsWithoutStateProof(t, 2) + + var builderExists bool + err := w.db.Atomic(func(ctx context.Context, tx *sql.Tx) (err error) { + builderExists, err = builderExistInDB(tx, firstBuilderRound) + return err + }) + a.NoError(err) + a.False(builderExists) + + // We leave one round uncommitted to be able to easily discern the stateProofNextRound. + s.mockCommit(firstBuilderRound) + + builderExists = false + err = w.db.Atomic(func(ctx context.Context, tx *sql.Tx) (err error) { + builderExists, err = builderExistInDB(tx, firstBuilderRound) + return err + }) + a.NoError(err) + a.True(builderExists) +} + +func TestSignerUsesPersistedBuilderLatestProto(t *testing.T) { + partitiontest.PartitionTest(t) + a := require.New(t) + + proto := config.Consensus[protocol.ConsensusCurrentVersion] + + var keys []account.Participation + for i := 0; i < 2; i++ { + var parent basics.Address + crypto.RandBytes(parent[:]) + p := newPartKey(t, parent) + defer p.Close() + keys = append(keys, p.Participation) + } + + s := newWorkerStubs(t, keys, 10) + w := newTestWorkerOnDiskDb(t, s) + defer os.Remove(w.spDbFileName) + w.Start() + + // We remove the signer's keys to stop it from generating builders and signing. + prevKeys := s.keys + s.keys = []account.Participation{} + + firstBuilderRound := basics.Round(proto.StateProofInterval * 2) + + // We start on round 511, so the callback should be called on the next round. + s.advanceRoundsWithoutStateProof(t, 2) + s.mockCommit(firstBuilderRound) + s.waitForSignerAndBuilder(t) + + a.False(s.isRoundSigned(a, w, firstBuilderRound)) + + // We restart the signing process. + s.keys = prevKeys + w.Stop() + + w.Start() + defer w.Stop() + + // We advance another round to allow us to wait for the signer, allowing it time to finish signing. + s.advanceRoundsWithoutStateProof(t, 1) + s.waitForSignerAndBuilder(t) + + a.True(s.isRoundSigned(a, w, firstBuilderRound)) +} + +func TestRegisterCommitListener(t *testing.T) { + partitiontest.PartitionTest(t) + a := require.New(t) + + const expectedStateProofs = 3 + var keys []account.Participation + for i := 0; i < 10; i++ { + var parent basics.Address + crypto.RandBytes(parent[:]) + p := newPartKey(t, parent) + defer p.Close() + keys = append(keys, p.Participation) + } + + s := newWorkerStubs(t, keys, len(keys)) + a.Nil(s.commitListener) + + w := newTestWorker(t, s) + w.Start() + + a.NotNil(s.commitListener) + proto := config.Consensus[protocol.ConsensusCurrentVersion] + + // we break the loop into two part since we don't want to add a state proof round (Round % 256 == 0) + for iter := 0; iter < expectedStateProofs-1; iter++ { + s.advanceRoundsAndCreateStateProofs(t, proto.StateProofInterval) + } + s.advanceRoundsAndCreateStateProofs(t, proto.StateProofInterval/2) + + w.Stop() + + a.Nil(s.commitListener) +} diff --git a/test/e2e-go/features/catchup/catchpointCatchup_test.go b/test/e2e-go/features/catchup/catchpointCatchup_test.go index e8de54e66c..1663a14192 100644 --- a/test/e2e-go/features/catchup/catchpointCatchup_test.go +++ b/test/e2e-go/features/catchup/catchpointCatchup_test.go @@ -26,11 +26,10 @@ import ( "testing" "time" - "github.com/algorand/go-deadlock" "github.com/stretchr/testify/require" "github.com/algorand/go-algorand/config" - algodclient "github.com/algorand/go-algorand/daemon/algod/api/client" + "github.com/algorand/go-algorand/daemon/algod/api/client" "github.com/algorand/go-algorand/daemon/algod/api/server/v2/generated/model" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/ledger/ledgercore" @@ -39,13 +38,122 @@ import ( "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/framework/fixtures" "github.com/algorand/go-algorand/test/partitiontest" + "github.com/algorand/go-deadlock" ) +const basicTestCatchpointInterval = 4 + +func waitForCatchpointGeneration(fixture *fixtures.RestClientFixture, client client.RestClient, catchpointRound basics.Round) (string, error) { + err := fixture.ClientWaitForRoundWithTimeout(client, uint64(catchpointRound+1)) + if err != nil { + return "", err + } + + var status model.NodeStatusResponse + timer := time.NewTimer(10 * time.Second) + for { + status, err = client.Status() + if err != nil { + return "", err + } + + var round basics.Round + if status.LastCatchpoint != nil && len(*status.LastCatchpoint) > 0 { + round, _, err = ledgercore.ParseCatchpointLabel(*status.LastCatchpoint) + if err != nil { + return "", err + } + if round >= catchpointRound { + break + } + } + select { + case <-timer.C: + return "", fmt.Errorf("timeout while waiting for catchpoint, target: %d, got %d", catchpointRound, round) + default: + time.Sleep(250 * time.Millisecond) + } + } + + return *status.LastCatchpoint, nil +} + +func denyRoundRequestsWebProxy(a *require.Assertions, listeningAddress string, round basics.Round) *fixtures.WebProxy { + log := logging.NewLogger() + log.SetLevel(logging.Info) + + wp, err := fixtures.MakeWebProxy(listeningAddress, log, func(response http.ResponseWriter, request *http.Request, next http.HandlerFunc) { + // prevent requests for the given block to go through. + if request.URL.String() == fmt.Sprintf("/v1/test-v1/block/%d", round) { + response.WriteHeader(http.StatusBadRequest) + response.Write([]byte(fmt.Sprintf("webProxy prevents block %d from serving", round))) + return + } + next(response, request) + }) + a.NoError(err) + log.Infof("web proxy listens at %s\n", wp.GetListenAddress()) + return wp +} + +func getFirstCatchpointRound(consensusParams *config.ConsensusParams) basics.Round { + // fast catchup downloads some blocks back from catchpoint round - CatchpointLookback + expectedBlocksToDownload := consensusParams.MaxTxnLife + consensusParams.DeeperBlockHeaderHistory + const restrictedBlockRound = 2 // block number that is rejected to be downloaded to ensure fast catchup and not regular catchup is running + // calculate the target round: this is the next round after catchpoint + // that is greater than expectedBlocksToDownload before the restrictedBlock block number + minRound := restrictedBlockRound + consensusParams.CatchpointLookback + return basics.Round(((expectedBlocksToDownload+minRound)/basicTestCatchpointInterval + 1) * basicTestCatchpointInterval) +} + +func applyCatchpointConsensusChanges(consensusParams *config.ConsensusParams) { + // MaxBalLookback = 2 x SeedRefreshInterval x SeedLookback + // ref. https://github.com/algorandfoundation/specs/blob/master/dev/abft.md + consensusParams.SeedLookback = 2 + consensusParams.SeedRefreshInterval = 2 + consensusParams.MaxBalLookback = 2 * consensusParams.SeedLookback * consensusParams.SeedRefreshInterval // 8 + consensusParams.MaxTxnLife = 13 + consensusParams.CatchpointLookback = consensusParams.MaxBalLookback + consensusParams.EnableCatchpointsWithSPContexts = true + if runtime.GOARCH == "amd64" || runtime.GOARCH == "arm64" { + // amd64 and arm64 platforms are generally quite capable, so accelerate the round times to make the test run faster. + consensusParams.AgreementFilterTimeoutPeriod0 = 1 * time.Second + consensusParams.AgreementFilterTimeout = 1 * time.Second + } + consensusParams.ApprovedUpgrades = map[protocol.ConsensusVersion]uint64{} +} + +func configureCatchpointGeneration(a *require.Assertions, nodeController *nodecontrol.NodeController) { + cfg, err := config.LoadConfigFromDisk(nodeController.GetDataDir()) + a.NoError(err) + + cfg.CatchpointInterval = basicTestCatchpointInterval + cfg.MaxAcctLookback = 2 + err = cfg.SaveToDisk(nodeController.GetDataDir()) + a.NoError(err) +} + +func configureCatchpointUsage(a *require.Assertions, nodeController *nodecontrol.NodeController) { + cfg, err := config.LoadConfigFromDisk(nodeController.GetDataDir()) + a.NoError(err) + + cfg.MaxAcctLookback = 2 + cfg.Archival = false + cfg.CatchpointInterval = 0 + cfg.NetAddress = "" + cfg.EnableLedgerService = false + cfg.EnableBlockService = false + cfg.BaseLoggerDebugLevel = uint32(logging.Debug) + cfg.CatchupBlockValidateMode = 12 + err = cfg.SaveToDisk(nodeController.GetDataDir()) + a.NoError(err) +} + type nodeExitErrorCollector struct { errors []error messages []string mu deadlock.Mutex - t fixtures.TestingTB + a *require.Assertions } func (ec *nodeExitErrorCollector) nodeExitWithError(nc *nodecontrol.NodeController, err error) { @@ -78,106 +186,47 @@ func (ec *nodeExitErrorCollector) Print() { ec.mu.Lock() defer ec.mu.Unlock() for i, err := range ec.errors { - require.NoError(ec.t, err, ec.messages[i]) + ec.a.NoError(err, ec.messages[i]) } } -// awaitCatchpointCreation attempts catchpoint retrieval with retries when the catchpoint is not yet available. -func awaitCatchpointCreation(client algodclient.RestClient, fixture *fixtures.RestClientFixture, roundWaitCount uint8) (model.NodeStatusResponse, error) { - s, err := client.Status() - if err != nil { - return model.NodeStatusResponse{}, err - } - - if len(*s.LastCatchpoint) > 0 { - return s, nil +func startCatchpointGeneratingNode(a *require.Assertions, fixture *fixtures.RestClientFixture, nodeName string) ( + nodecontrol.NodeController, client.RestClient, *nodeExitErrorCollector) { + nodeController, err := fixture.GetNodeController(nodeName) + a.NoError(err) - } + configureCatchpointGeneration(a, &nodeController) - if roundWaitCount-1 > 0 { - err = fixture.ClientWaitForRound(client, s.LastRound+1, 10*time.Second) - if err != nil { - return model.NodeStatusResponse{}, err - } + errorsCollector := nodeExitErrorCollector{a: a} + _, err = nodeController.StartAlgod(nodecontrol.AlgodStartArgs{ + PeerAddress: "", + ListenIP: "", + RedirectOutput: true, + RunUnderHost: false, + TelemetryOverride: "", + ExitErrorCallback: errorsCollector.nodeExitWithError, + }) + a.NoError(err) - return awaitCatchpointCreation(client, fixture, roundWaitCount-1) - } + restClient := fixture.GetAlgodClientForController(nodeController) + // We don't want to start using the node without it being properly initialized. + err = fixture.ClientWaitForRoundWithTimeout(restClient, 1) + a.NoError(err) - return model.NodeStatusResponse{}, fmt.Errorf("No catchpoint exists") + return nodeController, restClient, &errorsCollector } -func TestBasicCatchpointCatchup(t *testing.T) { - partitiontest.PartitionTest(t) - defer fixtures.ShutdownSynchronizedTest(t) - - if testing.Short() { - t.Skip() - } - a := require.New(fixtures.SynchronizedTest(t)) - log := logging.TestingLog(t) - - // Overview of this test: - // Start a two-node network (primary has 100%, secondary has 0%) - // Nodes are having a consensus allowing balances history of 8 rounds and transaction history of 13 rounds. - // Let it run for 21 rounds. - // create a web proxy, and connect it to the primary node, blocking all requests for round #2. ( and allowing everything else ) - // start a secondary node, and instuct it to catchpoint catchup from the proxy. ( which would be for round 20 ) - // wait until the clone node cought up, skipping the "impossible" hole of round #2. - - consensus := make(config.ConsensusProtocols) - const consensusCatchpointCatchupTestProtocol = protocol.ConsensusVersion("catchpointtestingprotocol") - catchpointCatchupProtocol := config.Consensus[protocol.ConsensusCurrentVersion] - catchpointCatchupProtocol.ApprovedUpgrades = map[protocol.ConsensusVersion]uint64{} - // MaxBalLookback = 2 x SeedRefreshInterval x SeedLookback - // ref. https://github.com/algorandfoundation/specs/blob/master/dev/abft.md - catchpointCatchupProtocol.SeedLookback = 2 - catchpointCatchupProtocol.SeedRefreshInterval = 2 - catchpointCatchupProtocol.MaxBalLookback = 2 * catchpointCatchupProtocol.SeedLookback * catchpointCatchupProtocol.SeedRefreshInterval // 8 - catchpointCatchupProtocol.MaxTxnLife = 13 - catchpointCatchupProtocol.CatchpointLookback = catchpointCatchupProtocol.MaxBalLookback - catchpointCatchupProtocol.EnableOnlineAccountCatchpoints = true - - if runtime.GOARCH == "amd64" { - // amd64 platforms are generally quite capable, so accelerate the round times to make the test run faster. - catchpointCatchupProtocol.AgreementFilterTimeoutPeriod0 = 1 * time.Second - catchpointCatchupProtocol.AgreementFilterTimeout = 1 * time.Second - } - - consensus[consensusCatchpointCatchupTestProtocol] = catchpointCatchupProtocol - - var fixture fixtures.RestClientFixture - fixture.SetConsensus(consensus) - - errorsCollector := nodeExitErrorCollector{t: fixtures.SynchronizedTest(t)} - defer errorsCollector.Print() - - fixture.SetupNoStart(t, filepath.Join("nettemplates", "CatchpointCatchupTestNetwork.json")) - - // Get primary node - primaryNode, err := fixture.GetNodeController("Primary") - a.NoError(err) - // Get secondary node - secondNode, err := fixture.GetNodeController("Node") +func startCatchpointUsingNode(a *require.Assertions, fixture *fixtures.RestClientFixture, nodeName string, peerAddress string) ( + nodecontrol.NodeController, client.RestClient, *fixtures.WebProxy, *nodeExitErrorCollector) { + nodeController, err := fixture.GetNodeController(nodeName) a.NoError(err) - // prepare it's configuration file to set it to generate a catchpoint every 4 rounds. - cfg, err := config.LoadConfigFromDisk(primaryNode.GetDataDir()) - a.NoError(err) - const catchpointInterval = 4 - cfg.CatchpointInterval = catchpointInterval - cfg.MaxAcctLookback = 2 - cfg.SaveToDisk(primaryNode.GetDataDir()) - cfg.Archival = false - cfg.CatchpointInterval = 0 - cfg.NetAddress = "" - cfg.EnableLedgerService = false - cfg.EnableBlockService = false - cfg.BaseLoggerDebugLevel = uint32(logging.Debug) - cfg.SaveToDisk(secondNode.GetDataDir()) + configureCatchpointUsage(a, &nodeController) - // start the primary node - _, err = primaryNode.StartAlgod(nodecontrol.AlgodStartArgs{ - PeerAddress: "", + wp := denyRoundRequestsWebProxy(a, peerAddress, 2) + errorsCollector := nodeExitErrorCollector{a: a} + _, err = nodeController.StartAlgod(nodecontrol.AlgodStartArgs{ + PeerAddress: wp.GetListenAddress(), ListenIP: "", RedirectOutput: true, RunUnderHost: false, @@ -185,49 +234,23 @@ func TestBasicCatchpointCatchup(t *testing.T) { ExitErrorCallback: errorsCollector.nodeExitWithError, }) a.NoError(err) - defer primaryNode.StopAlgod() - // Let the network make some progress - currentRound := uint64(1) - // fast catchup downloads some blocks back from catchpoint round - CatchpointLookback - expectedBlocksToDownload := catchpointCatchupProtocol.MaxTxnLife + catchpointCatchupProtocol.DeeperBlockHeaderHistory - const restrictedBlockRound = 2 // block number that is rejected to be downloaded to ensure fast catchup and not regular catchup is running - // calculate the target round: this is the next round after catchpoint - // that is greater than expectedBlocksToDownload before the restrictedBlock block number - minRound := restrictedBlockRound + catchpointCatchupProtocol.CatchpointLookback - targetCatchpointRound := (basics.Round(expectedBlocksToDownload+minRound)/catchpointInterval + 1) * catchpointInterval - targetRound := uint64(targetCatchpointRound) + 1 - primaryNodeRestClient := fixture.GetAlgodClientForController(primaryNode) - log.Infof("Building ledger history..") - for { - err = fixture.ClientWaitForRound(primaryNodeRestClient, currentRound, 45*time.Second) - a.NoError(err) - if targetRound <= currentRound { - break - } - currentRound++ - } - log.Infof("done building!\n") - - primaryListeningAddress, err := primaryNode.GetListeningAddress() + restClient := fixture.GetAlgodClientForController(nodeController) + // We don't want to start using the node without it being properly initialized. + err = fixture.ClientWaitForRoundWithTimeout(restClient, 1) a.NoError(err) - wp, err := fixtures.MakeWebProxy(primaryListeningAddress, log, func(response http.ResponseWriter, request *http.Request, next http.HandlerFunc) { - // prevent requests for block #2 to go through. - if request.URL.String() == "/v1/test-v1/block/2" { - response.WriteHeader(http.StatusBadRequest) - response.Write([]byte("webProxy prevents block 2 from serving")) - return - } - next(response, request) - }) + return nodeController, restClient, wp, &errorsCollector +} + +func startCatchpointNormalNode(a *require.Assertions, fixture *fixtures.RestClientFixture, nodeName string, peerAddress string) ( + nodecontrol.NodeController, client.RestClient, *nodeExitErrorCollector) { + nodeController, err := fixture.GetNodeController(nodeName) a.NoError(err) - defer wp.Close() - log.Infof("web proxy listens at %s\n", wp.GetListenAddress()) - // start the second node - _, err = secondNode.StartAlgod(nodecontrol.AlgodStartArgs{ - PeerAddress: wp.GetListenAddress(), + errorsCollector := nodeExitErrorCollector{a: a} + _, err = nodeController.StartAlgod(nodecontrol.AlgodStartArgs{ + PeerAddress: peerAddress, ListenIP: "", RedirectOutput: true, RunUnderHost: false, @@ -235,67 +258,69 @@ func TestBasicCatchpointCatchup(t *testing.T) { ExitErrorCallback: errorsCollector.nodeExitWithError, }) a.NoError(err) - defer secondNode.StopAlgod() - // wait until node is caught up. - secondNodeRestClient := fixture.GetAlgodClientForController(secondNode) + restClient := fixture.GetAlgodClientForController(nodeController) + // We don't want to start using the node without it being properly initialized. + err = fixture.ClientWaitForRoundWithTimeout(restClient, 1) + a.NoError(err) - currentRound = uint64(1) - secondNodeTargetRound := uint64(1) - log.Infof("Second node catching up to round 1") - for { - err = fixture.ClientWaitForRound(secondNodeRestClient, currentRound, 10*time.Second) - a.NoError(err) - if secondNodeTargetRound <= currentRound { - break - } - currentRound++ + return nodeController, restClient, &errorsCollector +} - } - log.Infof(" - done catching up!\n") +func getFixture(consensusParams *config.ConsensusParams) *fixtures.RestClientFixture { + consensus := make(config.ConsensusProtocols) + const consensusCatchpointCatchupTestProtocol = protocol.ConsensusVersion("catchpointtestingprotocol") + consensus[consensusCatchpointCatchupTestProtocol] = *consensusParams - // ensure the catchpoint is created for targetCatchpointRound - var status model.NodeStatusResponse - timer := time.NewTimer(10 * time.Second) -outer: - for { - status, err = primaryNodeRestClient.Status() - a.NoError(err) + var fixture fixtures.RestClientFixture + fixture.SetConsensus(consensus) + return &fixture +} - var round basics.Round - if status.LastCatchpoint != nil && len(*status.LastCatchpoint) > 0 { - round, _, err = ledgercore.ParseCatchpointLabel(*status.LastCatchpoint) - a.NoError(err) - if round >= targetCatchpointRound { - break - } - } - select { - case <-timer.C: - a.Failf("timeout waiting a catchpoint", "target: %d, got %d", targetCatchpointRound, round) - break outer - default: - time.Sleep(250 * time.Millisecond) - } +func TestBasicCatchpointCatchup(t *testing.T) { + // Overview of this test: + // Start a two-node network (primary has 100%, using has 0%) + // create a web proxy, have the using node use it as a peer, blocking all requests for round #2. ( and allowing everything else ) + // Let it run until the first usable catchpoint, as computed in getFirstCatchpointRound, is generated. + // instruct the using node to catchpoint catchup from the proxy. + // wait until the using node is caught up to catchpointRound+1, skipping the "impossible" hole of round #2 and + // participating in consensus. + partitiontest.PartitionTest(t) + defer fixtures.ShutdownSynchronizedTest(t) + + if testing.Short() { + t.Skip() } - log.Infof("primary node latest catchpoint - %s!\n", *status.LastCatchpoint) - _, err = secondNodeRestClient.Catchup(*status.LastCatchpoint) + consensusParams := config.Consensus[protocol.ConsensusCurrentVersion] + applyCatchpointConsensusChanges(&consensusParams) + a := require.New(fixtures.SynchronizedTest(t)) + + fixture := getFixture(&consensusParams) + fixture.SetupNoStart(t, filepath.Join("nettemplates", "CatchpointCatchupTestNetwork.json")) + + primaryNode, primaryNodeRestClient, primaryErrorsCollector := startCatchpointGeneratingNode(a, fixture, "Primary") + defer primaryErrorsCollector.Print() + defer primaryNode.StopAlgod() + + primaryNodeAddr, err := primaryNode.GetListeningAddress() a.NoError(err) - currentRound = status.LastRound - a.LessOrEqual(targetRound, currentRound) - fixtureTargetRound := targetRound + 1 - log.Infof("Second node catching up to round %v", currentRound) - for { - err = fixture.ClientWaitForRound(secondNodeRestClient, currentRound, 10*time.Second) - a.NoError(err) - if fixtureTargetRound <= currentRound { - break - } - currentRound++ - } - log.Infof("done catching up!\n") + usingNode, usingNodeRestClient, wp, usingNodeErrorsCollector := startCatchpointUsingNode(a, fixture, "Node", primaryNodeAddr) + defer usingNodeErrorsCollector.Print() + defer wp.Close() + defer usingNode.StopAlgod() + + targetCatchpointRound := getFirstCatchpointRound(&consensusParams) + + catchpointLabel, err := waitForCatchpointGeneration(fixture, primaryNodeRestClient, targetCatchpointRound) + a.NoError(err) + + _, err = usingNodeRestClient.Catchup(catchpointLabel) + a.NoError(err) + + err = fixture.ClientWaitForRoundWithTimeout(usingNodeRestClient, uint64(targetCatchpointRound+1)) + a.NoError(err) } func TestCatchpointLabelGeneration(t *testing.T) { @@ -324,28 +349,13 @@ func TestCatchpointLabelGeneration(t *testing.T) { consensus := make(config.ConsensusProtocols) const consensusCatchpointCatchupTestProtocol = protocol.ConsensusVersion("catchpointtestingprotocol") catchpointCatchupProtocol := config.Consensus[protocol.ConsensusCurrentVersion] - catchpointCatchupProtocol.ApprovedUpgrades = map[protocol.ConsensusVersion]uint64{} - // MaxBalLookback = 2 x SeedRefreshInterval x SeedLookback - // ref. https://github.com/algorandfoundation/specs/blob/master/dev/abft.md - catchpointCatchupProtocol.SeedLookback = 2 - catchpointCatchupProtocol.SeedRefreshInterval = 2 - catchpointCatchupProtocol.MaxBalLookback = 2 * catchpointCatchupProtocol.SeedLookback * catchpointCatchupProtocol.SeedRefreshInterval // 8 - catchpointCatchupProtocol.MaxTxnLife = 13 - catchpointCatchupProtocol.CatchpointLookback = catchpointCatchupProtocol.MaxBalLookback - catchpointCatchupProtocol.EnableOnlineAccountCatchpoints = true - - if runtime.GOARCH == "amd64" { - // amd64 platforms are generally quite capable, so accelerate the round times to make the test run faster. - catchpointCatchupProtocol.AgreementFilterTimeoutPeriod0 = 1 * time.Second - catchpointCatchupProtocol.AgreementFilterTimeout = 1 * time.Second - } - + applyCatchpointConsensusChanges(&catchpointCatchupProtocol) consensus[consensusCatchpointCatchupTestProtocol] = catchpointCatchupProtocol var fixture fixtures.RestClientFixture fixture.SetConsensus(consensus) - errorsCollector := nodeExitErrorCollector{t: fixtures.SynchronizedTest(t)} + errorsCollector := nodeExitErrorCollector{a: a} defer errorsCollector.Print() fixture.SetupNoStart(t, filepath.Join("nettemplates", "CatchpointCatchupTestNetwork.json")) @@ -417,20 +427,8 @@ func TestNodeTxHandlerRestart(t *testing.T) { consensus := make(config.ConsensusProtocols) protoVersion := protocol.ConsensusCurrentVersion catchpointCatchupProtocol := config.Consensus[protoVersion] - catchpointCatchupProtocol.ApprovedUpgrades = map[protocol.ConsensusVersion]uint64{} - // MaxBalLookback = 2 x SeedRefreshInterval x SeedLookback - // ref. https://github.com/algorandfoundation/specs/blob/master/dev/abft.md - catchpointCatchupProtocol.SeedLookback = 2 - catchpointCatchupProtocol.SeedRefreshInterval = 2 - catchpointCatchupProtocol.MaxBalLookback = 2 * catchpointCatchupProtocol.SeedLookback * catchpointCatchupProtocol.SeedRefreshInterval // 8 - catchpointCatchupProtocol.CatchpointLookback = catchpointCatchupProtocol.MaxBalLookback - catchpointCatchupProtocol.EnableOnlineAccountCatchpoints = true + applyCatchpointConsensusChanges(&catchpointCatchupProtocol) catchpointCatchupProtocol.StateProofInterval = 0 - if runtime.GOOS == "darwin" || runtime.GOARCH == "amd64" { - // amd64/macos platforms are generally quite capable, so accelerate the round times to make the test run faster. - catchpointCatchupProtocol.AgreementFilterTimeoutPeriod0 = 1 * time.Second - catchpointCatchupProtocol.AgreementFilterTimeout = 1 * time.Second - } consensus[protoVersion] = catchpointCatchupProtocol var fixture fixtures.RestClientFixture @@ -556,20 +554,10 @@ func TestNodeTxSyncRestart(t *testing.T) { consensus := make(config.ConsensusProtocols) protoVersion := protocol.ConsensusCurrentVersion catchpointCatchupProtocol := config.Consensus[protoVersion] - catchpointCatchupProtocol.ApprovedUpgrades = map[protocol.ConsensusVersion]uint64{} - // MaxBalLookback = 2 x SeedRefreshInterval x SeedLookback - // ref. https://github.com/algorandfoundation/specs/blob/master/dev/abft.md - catchpointCatchupProtocol.SeedLookback = 2 - catchpointCatchupProtocol.SeedRefreshInterval = 2 - catchpointCatchupProtocol.MaxBalLookback = 2 * catchpointCatchupProtocol.SeedLookback * catchpointCatchupProtocol.SeedRefreshInterval - catchpointCatchupProtocol.CatchpointLookback = catchpointCatchupProtocol.MaxBalLookback - catchpointCatchupProtocol.EnableOnlineAccountCatchpoints = true + prevMaxTxnLife := catchpointCatchupProtocol.MaxTxnLife + applyCatchpointConsensusChanges(&catchpointCatchupProtocol) + catchpointCatchupProtocol.MaxTxnLife = prevMaxTxnLife catchpointCatchupProtocol.StateProofInterval = 0 - if runtime.GOOS == "darwin" || runtime.GOARCH == "amd64" { - // amd64/macos platforms are generally quite capable, so accelerate the round times to make the test run faster. - catchpointCatchupProtocol.AgreementFilterTimeoutPeriod0 = 1 * time.Second - catchpointCatchupProtocol.AgreementFilterTimeout = 1 * time.Second - } consensus[protoVersion] = catchpointCatchupProtocol var fixture fixtures.RestClientFixture diff --git a/test/e2e-go/features/catchup/stateproofsCatchup_test.go b/test/e2e-go/features/catchup/stateproofsCatchup_test.go new file mode 100644 index 0000000000..a0d20f5f1e --- /dev/null +++ b/test/e2e-go/features/catchup/stateproofsCatchup_test.go @@ -0,0 +1,286 @@ +// Copyright (C) 2019-2023 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package catchup + +import ( + "context" + "database/sql" + "path/filepath" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/config" + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/libgoal" + "github.com/algorand/go-algorand/protocol" + "github.com/algorand/go-algorand/test/framework/fixtures" + "github.com/algorand/go-algorand/test/partitiontest" + "github.com/algorand/go-algorand/util/db" +) + +func applyCatchpointStateProofConsensusChanges(consensusParams *config.ConsensusParams) { + // we decrease the StateProofStrengthTarget creating a "weak cert" to allow state proofs to be generated when the + // signed weight and proven weight are very close to each other. + consensusParams.StateProofStrengthTarget = 4 + consensusParams.StateProofInterval = 8 + consensusParams.StateProofVotersLookback = 2 + consensusParams.EnableStateProofKeyregCheck = true + consensusParams.StateProofUseTrackerVerification = true +} + +func getStateProofNextRound(a *require.Assertions, goalClient *libgoal.Client, round basics.Round) basics.Round { + block, err := goalClient.BookkeepingBlock(uint64(round)) + a.NoError(err) + return block.StateProofTracking[protocol.StateProofBasic].StateProofNextRound +} + +func TestStateProofInReplayCatchpoint(t *testing.T) { + // Overview of this test: + // Configure consensus to generate a state proof in the target catchpoint's replay rounds. + // i.e the node will have to "replay" the state proof transaction after fast catchup. + // Start a two-node network (primary has 100%, using has 0%) + // create a web proxy, have the using node use it as a peer, blocking all requests for round #2 and allowing everything else + // (This disables the node's ability to use regular catchup) + // Let it run until the first usable catchpoint, as computed in getFirstCatchpointRound, is generated. + // instruct the using node to fast catchup. + // wait until the using node is caught up to catchpointRound+1, skipping the "impossible" hole of round #2. + // Verify that the blocks replayed to the using node contained a state proof transaction. + partitiontest.PartitionTest(t) + defer fixtures.ShutdownSynchronizedTest(t) + + if testing.Short() { + t.Skip() + } + + a := require.New(fixtures.SynchronizedTest(t)) + + consensusParams := config.Consensus[protocol.ConsensusCurrentVersion] + applyCatchpointConsensusChanges(&consensusParams) + applyCatchpointStateProofConsensusChanges(&consensusParams) + + fixture := getFixture(&consensusParams) + fixture.SetupNoStart(t, filepath.Join("nettemplates", "CatchpointCatchupTestNetwork.json")) + + primaryNode, primaryNodeRestClient, primaryErrorsCollector := startCatchpointGeneratingNode(a, fixture, "Primary") + defer primaryErrorsCollector.Print() + defer primaryNode.StopAlgod() + + primaryNodeAddr, err := primaryNode.GetListeningAddress() + a.NoError(err) + + usingNode, usingNodeRestClient, wp, usingNodeErrorsCollector := startCatchpointUsingNode(a, fixture, "Node", primaryNodeAddr) + defer usingNodeErrorsCollector.Print() + defer wp.Close() + defer usingNode.StopAlgod() + + targetCatchpointRound := getFirstCatchpointRound(&consensusParams) + + catchpointLabel, err := waitForCatchpointGeneration(fixture, primaryNodeRestClient, targetCatchpointRound) + a.NoError(err) + + _, err = usingNodeRestClient.Catchup(catchpointLabel) + a.NoError(err) + + // waiting for fastcatchup to start + attempt := 0 + const sleepTime = 1 * time.Millisecond // too large duration makes catchup to complete + const maxAttempts = 500 + for { + status, err := usingNodeRestClient.Status() + a.NoError(err) + if status.Catchpoint != nil && len(*status.Catchpoint) > 0 { + t.Logf("Fast catchup from %d to %s is in progress", status.LastRound, *status.Catchpoint) + break + } + if attempt > maxAttempts { + a.FailNow("Failed to start fast catchup in %d seconds", sleepTime*maxAttempts/1000) + } + time.Sleep(sleepTime) + attempt++ + } + + // wait for fastcatchup to complete and the node is synced + err = fixture.ClientWaitForRoundWithTimeout(usingNodeRestClient, uint64(targetCatchpointRound+1)) + a.NoError(err) + + primaryLibGoal := fixture.GetLibGoalClientFromNodeController(primaryNode) + + dbRoundAfterCatchpoint := targetCatchpointRound - basics.Round(consensusParams.MaxBalLookback) + a.True(getStateProofNextRound(a, &primaryLibGoal, dbRoundAfterCatchpoint) < getStateProofNextRound(a, &primaryLibGoal, targetCatchpointRound), + "No state proof transaction in replay, rounds were %d to %d", dbRoundAfterCatchpoint+1, targetCatchpointRound) +} + +func TestStateProofAfterCatchpoint(t *testing.T) { + // Overview of this test: + // Configure consensus to generate a state proof transaction after the target catchpoint round, with voters from before + // the target state proof round. + // Start a two-node network (primary has 100%, using has 0%) + // create a web proxy, have the using node use it as a peer, blocking all requests for round #2. ( and allowing everything else ) + // Let it run until the first usable catchpoint, as computed in getFirstCatchpointRound, is generated. + // instruct the using node to catchpoint catchup from the proxy. + // wait until the using node is caught up to catchpointRound+1, skipping the "impossible" hole of round #2 and + // participating in consensus. + // Wait until the next state proof has most likely been generated. + // Verify that the state proof's voters data came from the state proof tracker and that the state proof transaction + // itself happened after catchpoint catchup was completed. + partitiontest.PartitionTest(t) + defer fixtures.ShutdownSynchronizedTest(t) + + if testing.Short() { + t.Skip() + } + a := require.New(fixtures.SynchronizedTest(t)) + + consensusParams := config.Consensus[protocol.ConsensusCurrentVersion] + applyCatchpointConsensusChanges(&consensusParams) + applyCatchpointStateProofConsensusChanges(&consensusParams) + consensusParams.StateProofInterval = 16 + fixture := getFixture(&consensusParams) + fixture.SetupNoStart(t, filepath.Join("nettemplates", "CatchpointCatchupTestNetwork.json")) + + primaryNode, primaryNodeRestClient, primaryErrorsCollector := startCatchpointGeneratingNode(a, fixture, "Primary") + defer primaryErrorsCollector.Print() + defer primaryNode.StopAlgod() + + primaryNodeAddr, err := primaryNode.GetListeningAddress() + a.NoError(err) + + usingNode, usingNodeRestClient, wp, usingNodeErrorsCollector := startCatchpointUsingNode(a, fixture, "Node", primaryNodeAddr) + defer usingNodeErrorsCollector.Print() + defer wp.Close() + defer usingNode.StopAlgod() + + targetCatchpointRound := getFirstCatchpointRound(&consensusParams) + + catchpointLabel, err := waitForCatchpointGeneration(fixture, primaryNodeRestClient, targetCatchpointRound) + a.NoError(err) + + _, err = usingNodeRestClient.Catchup(catchpointLabel) + a.NoError(err) + + roundAfterSPGeneration := targetCatchpointRound.RoundUpToMultipleOf(basics.Round(consensusParams.StateProofInterval)) + + basics.Round(consensusParams.StateProofInterval/2) + err = fixture.ClientWaitForRoundWithTimeout(usingNodeRestClient, uint64(roundAfterSPGeneration)) + a.NoError(err) + + primaryLibGoal := fixture.GetLibGoalClientFromNodeController(primaryNode) + + dbRoundAfterCatchpoint := targetCatchpointRound - basics.Round(consensusParams.MaxBalLookback) + firstReplayRound := dbRoundAfterCatchpoint + 1 + currentCoveredLastAttestedRound := getStateProofNextRound(a, &primaryLibGoal, roundAfterSPGeneration).SubSaturate(basics.Round(consensusParams.StateProofInterval)) + votersRound := currentCoveredLastAttestedRound.SubSaturate(basics.Round(consensusParams.StateProofInterval)) + + // We do this to make sure the verification data came from the tracker. + a.True(votersRound < firstReplayRound) + a.True(currentCoveredLastAttestedRound > targetCatchpointRound) +} + +func TestSendSigsAfterCatchpointCatchup(t *testing.T) { + // Overview of this test: + // Start a three-node network (primary has 80%, using has 10% and normal has 10%). + // Configure consensus to require the primary node and at least on other node to generate state proofs. + // Start the primary node and a normal node and wait for the network to reach round 3. + // We remove block number 2 from primary database, this will prevent node2 from catching up and force it to use fast-catchup + // Let it run until the first usable catchpoint, as computed in getFirstCatchpointRound, is generated. + // Run Node2 + // wait until the using node is caught up to catchpointRound+1, skipping the "impossible" hole of round #2 and + // participating in consensus. + // Stop the normal node. + // Verify that a state proof transaction on which the normal node could not have signed is accepted. + partitiontest.PartitionTest(t) + defer fixtures.ShutdownSynchronizedTest(t) + + if testing.Short() { + t.Skip() + } + a := require.New(fixtures.SynchronizedTest(t)) + + configurableConsensus := make(config.ConsensusProtocols) + consensusVersion := protocol.ConsensusVersion("catchpointtestingprotocol") + consensusParams := config.Consensus[protocol.ConsensusCurrentVersion] + applyCatchpointStateProofConsensusChanges(&consensusParams) + applyCatchpointConsensusChanges(&consensusParams) + // Weight threshold allows creation of state proofs using the primary node and at least one other node. + consensusParams.StateProofWeightThreshold = (1 << 32) * 85 / 100 + configurableConsensus[consensusVersion] = consensusParams + + var fixture fixtures.RestClientFixture + fixture.SetConsensus(configurableConsensus) + fixture.SetupNoStart(t, filepath.Join("nettemplates", "ThreeNodesWithRichAcct.json")) + + primaryNode, primaryNodeRestClient, primaryEC := startCatchpointGeneratingNode(a, &fixture, "Primary") + defer primaryEC.Print() + defer primaryNode.StopAlgod() + primaryNodeAddr, err := primaryNode.GetListeningAddress() + a.NoError(err) + + err = fixture.ClientWaitForRoundWithTimeout(primaryNodeRestClient, 3) + a.NoError(err) + + normalNode, normalNodeRestClient, normalNodeEC := startCatchpointNormalNode(a, &fixture, "Node1", primaryNodeAddr) + defer normalNodeEC.Print() + defer normalNode.StopAlgod() + + err = fixture.ClientWaitForRoundWithTimeout(normalNodeRestClient, 3) + a.NoError(err) + + // at this point PrimaryNode and Node1 would pass round 3. Before running Node2 we remove block 2 from Primary database. + // this will force Node2 to use fastcatchup + primNodeGenDir, err := primaryNode.GetGenesisDir() + a.NoError(err) + acc, err := db.MakeAccessor(filepath.Join(primNodeGenDir, "ledger.block.sqlite"), false, false) + require.NoError(t, err) + err = acc.Atomic(func(ctx context.Context, tx *sql.Tx) error { + _, err := tx.Exec("delete from blocks where rnd =2 ") + return err + }) + require.NoError(t, err) + acc.Close() + + usingNode, usingNodeRestClient, usingNodeEC := startCatchpointNormalNode(a, &fixture, "Node2", primaryNodeAddr) + defer usingNodeEC.Print() + defer usingNode.StopAlgod() + + targetCatchpointRound := getFirstCatchpointRound(&consensusParams) + + catchpointLabel, err := waitForCatchpointGeneration(&fixture, primaryNodeRestClient, targetCatchpointRound) + a.NoError(err) + _, err = usingNodeRestClient.Catchup(catchpointLabel) + a.NoError(err) + + err = fixture.ClientWaitForRoundWithTimeout(usingNodeRestClient, uint64(targetCatchpointRound)+1) + a.NoError(err) + + lastNormalRound, err := fixture.GetLibGoalClientFromNodeController(normalNode).CurrentRound() + a.NoError(err) + normalNode.StopAlgod() + + // We wait until we know for sure that we're in a round that contains a state proof signed + // by the usingNode. we give the test 2*basics.Round(consensusParams.StateProofInterval) worth of time + // to prevent it from being flaky, since receiving signatures from the newly caught up node might take a while. + lastNormalNodeSignedRound := basics.Round(lastNormalRound).RoundDownToMultipleOf(basics.Round(consensusParams.StateProofInterval)) + lastNormalNextStateProofRound := lastNormalNodeSignedRound + basics.Round(consensusParams.StateProofInterval) + targetRound := lastNormalNextStateProofRound + basics.Round(consensusParams.StateProofInterval*2) + err = fixture.ClientWaitForRoundWithTimeout(usingNodeRestClient, uint64(targetRound)) + a.NoError(err) + + primaryClient := fixture.GetLibGoalClientFromNodeController(primaryNode) + spNextRound := getStateProofNextRound(a, &primaryClient, targetRound) + a.True(spNextRound > lastNormalNextStateProofRound) +} diff --git a/test/e2e-go/features/participation/onlineOfflineParticipation_test.go b/test/e2e-go/features/participation/onlineOfflineParticipation_test.go index ed593b765b..767ee86b34 100644 --- a/test/e2e-go/features/participation/onlineOfflineParticipation_test.go +++ b/test/e2e-go/features/participation/onlineOfflineParticipation_test.go @@ -124,8 +124,8 @@ func TestNewAccountCanGoOnlineAndParticipate(t *testing.T) { shortPartKeysProtocol.ApprovedUpgrades = map[protocol.ConsensusVersion]uint64{} shortPartKeysProtocol.SeedLookback = 2 shortPartKeysProtocol.SeedRefreshInterval = 8 - if runtime.GOARCH == "amd64" { - // amd64 platforms are generally quite capable, so accelerate the round times to make the test run faster. + if runtime.GOARCH == "amd64" || runtime.GOARCH == "arm64" { + // amd64 and arm64 platforms are generally quite capable, so accelerate the round times to make the test run faster. shortPartKeysProtocol.AgreementFilterTimeoutPeriod0 = 1 * time.Second shortPartKeysProtocol.AgreementFilterTimeout = 1 * time.Second } diff --git a/test/e2e-go/features/participation/overlappingParticipationKeys_test.go b/test/e2e-go/features/participation/overlappingParticipationKeys_test.go index 8e4df5fb3e..7565a821a8 100644 --- a/test/e2e-go/features/participation/overlappingParticipationKeys_test.go +++ b/test/e2e-go/features/participation/overlappingParticipationKeys_test.go @@ -65,8 +65,8 @@ func TestOverlappingParticipationKeys(t *testing.T) { // new keys must exist at least 4 rounds prior use shortPartKeysProtocol.SeedLookback = 2 shortPartKeysProtocol.SeedRefreshInterval = 1 - if runtime.GOARCH == "amd64" { - // amd64 platforms are generally quite capable, so accelerate the round times to make the test run faster. + if runtime.GOARCH == "amd64" || runtime.GOARCH == "arm64" { + // amd64 and arm64 platforms are generally quite capable, so accelerate the round times to make the test run faster. shortPartKeysProtocol.AgreementFilterTimeoutPeriod0 = 1 * time.Second shortPartKeysProtocol.AgreementFilterTimeout = 1 * time.Second } diff --git a/test/e2e-go/features/stateproofs/stateproofs_test.go b/test/e2e-go/features/stateproofs/stateproofs_test.go index e293006f40..4d4bc9f51b 100644 --- a/test/e2e-go/features/stateproofs/stateproofs_test.go +++ b/test/e2e-go/features/stateproofs/stateproofs_test.go @@ -21,7 +21,6 @@ import ( "fmt" "os" "path/filepath" - "runtime" "strings" "sync" "sync/atomic" @@ -113,7 +112,7 @@ func TestStateProofs(t *testing.T) { var fixture fixtures.RestClientFixture fixture.SetConsensus(configurableConsensus) - if runtime.GOARCH == "arm" || runtime.GOARCH == "arm64" { + if testing.Short() { fixture.Setup(t, filepath.Join("nettemplates", "StateProofSmall.json")) } else { fixture.Setup(t, filepath.Join("nettemplates", "StateProof.json")) @@ -231,12 +230,8 @@ func TestStateProofOverlappingKeys(t *testing.T) { var fixture fixtures.RestClientFixture pNodes := 5 fixture.SetConsensus(configurableConsensus) - if runtime.GOARCH == "arm" || runtime.GOARCH == "arm64" { - fixture.Setup(t, filepath.Join("nettemplates", "StateProofSmall.json")) - pNodes = 2 - } else { - fixture.Setup(t, filepath.Join("nettemplates", "StateProof.json")) - } + fixture.Setup(t, filepath.Join("nettemplates", "StateProof.json")) + defer fixture.Shutdown() // Get node libgoal clients in order to update their participation keys @@ -260,16 +255,14 @@ func TestStateProofOverlappingKeys(t *testing.T) { var lastStateProofMessage stateproofmsg.Message libgoalClient := fixture.LibGoalClient - k, err := libgoalNodeClients[0].GetParticipationKeys() - r.NoError(err) - voteLastValid := k[0].Key.VoteLastValid - expectedNumberOfStateProofs := uint64(10) + expectedNumberOfStateProofs := uint64(8) for rnd := uint64(1); rnd <= consensusParams.StateProofInterval*(expectedNumberOfStateProofs+1); rnd++ { - if rnd == voteLastValid-64 { // allow some buffer period before the voting keys are expired (for the keyreg to take effect) + if rnd == consensusParams.StateProofInterval*(5) { // allow some buffer period before the voting keys are expired (for the keyreg to take effect) + fmt.Println("at round.. installing", rnd) // Generate participation keys (for the same accounts) for i := 0; i < pNodes; i++ { // Overlapping stateproof keys (the key for round 0 is valid up to 256) - _, part, err := installParticipationKey(t, libgoalNodeClients[i], accounts[i], 0, 200) + _, part, err := installParticipationKey(t, libgoalNodeClients[i], accounts[i], 0, 400) r.NoError(err) participations[i] = part } @@ -286,7 +279,7 @@ func TestStateProofOverlappingKeys(t *testing.T) { amount: 1, }.sendPayment(r, &fixture, rnd) - err = fixture.WaitForRound(rnd, timeoutUntilNextRound) + err := fixture.WaitForRound(rnd, timeoutUntilNextRound) r.NoError(err) blk, err := libgoalClient.BookkeepingBlock(rnd) @@ -331,7 +324,7 @@ func TestStateProofMessageCommitmentVerification(t *testing.T) { var fixture fixtures.RestClientFixture fixture.SetConsensus(configurableConsensus) - if runtime.GOARCH == "arm" || runtime.GOARCH == "arm64" { + if testing.Short() { fixture.Setup(t, filepath.Join("nettemplates", "StateProofSmall.json")) } else { fixture.Setup(t, filepath.Join("nettemplates", "StateProof.json")) @@ -380,8 +373,8 @@ func TestStateProofMessageCommitmentVerification(t *testing.T) { } func getDefaultStateProofConsensusParams() config.ConsensusParams { - consensusParams := config.Consensus[protocol.ConsensusCurrentVersion] - consensusParams.StateProofInterval = 16 + consensusParams := config.Consensus[protocol.ConsensusFuture] + consensusParams.StateProofTopVoters = 1024 consensusParams.StateProofVotersLookback = 2 consensusParams.StateProofWeightThreshold = (1 << 32) * 30 / 100 @@ -391,6 +384,12 @@ func getDefaultStateProofConsensusParams() config.ConsensusParams { consensusParams.AgreementFilterTimeout = 1500 * time.Millisecond consensusParams.AgreementFilterTimeoutPeriod0 = 1500 * time.Millisecond + if testing.Short() { + consensusParams.StateProofInterval = 16 + } else { + consensusParams.StateProofInterval = 32 + } + return consensusParams } @@ -443,15 +442,15 @@ func verifyStateProofForRound(r *require.Assertions, fixture *fixtures.RestClien return stateProofMessage, nextStateProofBlock } -// TestRecoverFromLaggingStateProofChain simulates a situation where the stateproof chain is lagging after the main chain. +// TestStateProofRecoveryDuringRecoveryInterval simulates a situation where the stateproof chain is lagging after the main chain. // If the missing data is being accepted before StateProofMaxRecoveryIntervals * StateProofInterval rounds have passed, nodes should // be able to produce stateproofs and continue as normal -func TestRecoverFromLaggingStateProofChain(t *testing.T) { +func TestStateProofRecoveryDuringRecoveryPeriod(t *testing.T) { partitiontest.PartitionTest(t) defer fixtures.ShutdownSynchronizedTest(t) - if runtime.GOARCH == "arm" || runtime.GOARCH == "arm64" { - t.Skip("This test is difficult for ARM") + if testing.Short() { + t.Skip() } r := require.New(fixtures.SynchronizedTest(t)) @@ -542,14 +541,13 @@ func TestRecoverFromLaggingStateProofChain(t *testing.T) { r.Equalf(int(consensusParams.StateProofInterval*expectedNumberOfStateProofs), int(lastStateProofBlock.Round()), "the expected last state proof block wasn't the one that was observed") } -// TestUnableToRecoverFromLaggingStateProofChain simulates a situation where the stateproof chain is lagging after the main chain. -// unlike TestRecoverFromLaggingStateProofChain, in this test the node will start at a later round and the network will not be able to produce stateproofs/ -func TestUnableToRecoverFromLaggingStateProofChain(t *testing.T) { +// TestStateProofRecovery test that the state proof chain can be recovered even after the StateProofMaxRecoveryIntervals has passed. +func TestStateProofRecovery(t *testing.T) { partitiontest.PartitionTest(t) defer fixtures.ShutdownSynchronizedTest(t) - if runtime.GOARCH == "arm" || runtime.GOARCH == "arm64" { - t.Skip("This test is difficult for ARM") + if testing.Short() { + t.Skip() } r := require.New(fixtures.SynchronizedTest(t)) @@ -566,7 +564,12 @@ func TestUnableToRecoverFromLaggingStateProofChain(t *testing.T) { // for that reason we need to the decrease the StateProofStrengthTarget creating a "weak cert" consensusParams.StateProofWeightThreshold = (1 << 32) * 90 / 100 consensusParams.StateProofStrengthTarget = 4 - consensusParams.StateProofMaxRecoveryIntervals = 4 + consensusParams.StateProofMaxRecoveryIntervals = 2 + consensusParams.StateProofUseTrackerVerification = true + consensusParams.SeedLookback = 2 + consensusParams.SeedRefreshInterval = 2 + consensusParams.MaxBalLookback = 2 * consensusParams.SeedLookback * consensusParams.SeedRefreshInterval // 8 + consensusParams.MaxTxnLife = 13 configurableConsensus[consensusVersion] = consensusParams var fixture fixtures.RestClientFixture @@ -585,10 +588,13 @@ func TestUnableToRecoverFromLaggingStateProofChain(t *testing.T) { var lastStateProofBlock bookkeeping.Block libgoal := fixture.LibGoalClient - expectedNumberOfStateProofs := uint64(4) - // Loop through the rounds enough to check for expectedNumberOfStateProofs state proofs - for rnd := uint64(2); rnd <= consensusParams.StateProofInterval*(expectedNumberOfStateProofs+1); rnd++ { - if rnd == (consensusParams.StateProofMaxRecoveryIntervals+2)*consensusParams.StateProofInterval { + var lastStateProofMessage stateproofmsg.Message + + expectedNumberOfStateProofs := uint64(7) + numberOfGraceIntervals := uint64(3) + rnd := uint64(2) + for ; rnd <= consensusParams.StateProofInterval*(expectedNumberOfStateProofs); rnd++ { + if rnd == (consensusParams.StateProofMaxRecoveryIntervals+4)*consensusParams.StateProofInterval { t.Logf("at round %d starting node\n", rnd) dir, err = fixture.GetNodeDir("Node4") r.NoError(err) @@ -622,9 +628,46 @@ func TestUnableToRecoverFromLaggingStateProofChain(t *testing.T) { if lastStateProofBlock.Round()+basics.Round(consensusParams.StateProofInterval) < blk.StateProofTracking[protocol.StateProofBasic].StateProofNextRound && lastStateProofBlock.Round() != 0 { - r.FailNow("found a state proof at round %d", blk.Round()) + nextStateProofRound := uint64(lastStateProofBlock.Round()) + consensusParams.StateProofInterval + + t.Logf("found a state proof for round %d at round %d", nextStateProofRound, blk.Round()) + // Find the state proof transaction + stateProofMessage, nextStateProofBlock := verifyStateProofForRound(r, &fixture, nextStateProofRound, lastStateProofMessage, lastStateProofBlock, consensusParams) + lastStateProofMessage = stateProofMessage + lastStateProofBlock = nextStateProofBlock } } + + // at this point we expect the state proof chain to be completely caught up. However, In order to avoid flakiness on + // heavily loaded machines, we would wait some extra round for the state proofs to catch up + for ; rnd <= consensusParams.StateProofInterval*(expectedNumberOfStateProofs+numberOfGraceIntervals); rnd++ { + + err = fixture.WaitForRound(rnd, timeoutUntilNextRound) + r.NoError(err) + + blk, err := libgoal.BookkeepingBlock(rnd) + r.NoErrorf(err, "failed to retrieve block from algod on round %d", rnd) + + if lastStateProofBlock.Round() == 0 { + lastStateProofBlock = blk + } + + if lastStateProofBlock.Round()+basics.Round(consensusParams.StateProofInterval) < blk.StateProofTracking[protocol.StateProofBasic].StateProofNextRound && + lastStateProofBlock.Round() != 0 { + nextStateProofRound := uint64(lastStateProofBlock.Round()) + consensusParams.StateProofInterval + + t.Logf("found a state proof for round %d at round %d", nextStateProofRound, blk.Round()) + // Find the state proof transaction + stateProofMessage, nextStateProofBlock := verifyStateProofForRound(r, &fixture, nextStateProofRound, lastStateProofMessage, lastStateProofBlock, consensusParams) + lastStateProofMessage = stateProofMessage + lastStateProofBlock = nextStateProofBlock + } + + if int(consensusParams.StateProofInterval*expectedNumberOfStateProofs) <= int(lastStateProofBlock.Round()) { + return + } + } + r.Equalf(int(consensusParams.StateProofInterval*expectedNumberOfStateProofs), int(lastStateProofBlock.Round()), "the expected last state proof block wasn't the one that was observed") } // installParticipationKey generates a new key for a given account and installs it with the client. @@ -702,7 +745,7 @@ func TestAttestorsChange(t *testing.T) { from: accountFetcher{nodeName: "richNode", accountNumber: 0}, to: accountFetcher{nodeName: "poorNode", accountNumber: 0}, } - sum := uint64(0) + for rnd := uint64(1); rnd <= consensusParams.StateProofInterval*(expectedNumberOfStateProofs+1); rnd++ { // Changing the amount to pay. This should transfer most of the money from the rich node to the poor node. if consensusParams.StateProofInterval*2 == rnd { @@ -720,31 +763,15 @@ func TestAttestorsChange(t *testing.T) { } a.NoError(fixture.WaitForRound(rnd, timeoutUntilNextRound)) + blk, err := libgoal.BookkeepingBlock(rnd) a.NoErrorf(err, "failed to retrieve block from algod on round %d", rnd) - // We sample the accounts' balances StateProofVotersLookback rounds before state proof round. - if (rnd+consensusParams.StateProofVotersLookback)%consensusParams.StateProofInterval == 0 { - sum = 0 - // the main part of the test (computing the total stake of the nodes): - for i := 1; i <= 3; i++ { - sum += accountFetcher{fmt.Sprintf("Node%d", i), 0}.getBalance(a, &fixture) - } - - richNodeStake := accountFetcher{"richNode", 0}.getBalance(a, &fixture) - poorNodeStake := accountFetcher{"poorNode", 0}.getBalance(a, &fixture) - sum = sum + richNodeStake + poorNodeStake - } - if (rnd % consensusParams.StateProofInterval) == 0 { // Must have a merkle commitment for participants a.True(len(blk.StateProofTracking[protocol.StateProofBasic].StateProofVotersCommitment) > 0) a.True(blk.StateProofTracking[protocol.StateProofBasic].StateProofOnlineTotalWeight != basics.MicroAlgos{}) - stake := blk.BlockHeader.StateProofTracking[protocol.StateProofBasic].StateProofOnlineTotalWeight.ToUint64() - - a.Equal(sum, stake) - // Special case: bootstrap validation with the first block // that has a merkle root. if lastStateProofBlock.Round() == 0 { @@ -779,7 +806,6 @@ func TestTotalWeightChanges(t *testing.T) { consensusParams.StateProofWeightThreshold = (1 << 32) * 90 / 100 consensusParams.StateProofStrengthTarget = 4 consensusParams.StateProofTopVoters = 4 - //consensusParams.StateProofInterval = 32 configurableConsensus := config.ConsensusProtocols{ protocol.ConsensusVersion("test-fast-stateproofs"): consensusParams, @@ -787,7 +813,7 @@ func TestTotalWeightChanges(t *testing.T) { var fixture fixtures.RestClientFixture fixture.SetConsensus(configurableConsensus) - if runtime.GOARCH == "arm" || runtime.GOARCH == "arm64" { + if testing.Short() { fixture.Setup(t, filepath.Join("nettemplates", "RichAccountStateProofSmall.json")) } else { fixture.Setup(t, filepath.Join("nettemplates", "RichAccountStateProof.json")) @@ -805,7 +831,7 @@ func TestTotalWeightChanges(t *testing.T) { for rnd := uint64(1); rnd <= consensusParams.StateProofInterval*(expectedNumberOfStateProofs+1); rnd++ { // Rich node goes offline - if consensusParams.StateProofInterval*2-8 == rnd { + if consensusParams.StateProofInterval*2-(consensusParams.StateProofInterval/2) == rnd { // subtract 8 rounds since the total online stake is calculated prior to the actual state proof round (lookback) richNode.goOffline(a, &fixture, rnd) } @@ -1189,8 +1215,8 @@ func TestStateProofCheckTotalStake(t *testing.T) { var lastStateProofBlock bookkeeping.Block libgoalClient := fixture.LibGoalClient - var totalSupplyAtRound [100]model.SupplyResponse - var accountSnapshotAtRound [100][]model.Account + var totalSupplyAtRound [1000]model.SupplyResponse + var accountSnapshotAtRound [1000][]model.Account for rnd := uint64(1); rnd <= consensusParams.StateProofInterval*(expectedNumberOfStateProofs+1); rnd++ { if rnd == consensusParams.StateProofInterval+consensusParams.StateProofVotersLookback { // here we register the keys of address 0 so it won't be able the sign a state proof (its stake would be removed for the total) diff --git a/test/framework/fixtures/restClientFixture.go b/test/framework/fixtures/restClientFixture.go index 37d78797a3..e0151630a4 100644 --- a/test/framework/fixtures/restClientFixture.go +++ b/test/framework/fixtures/restClientFixture.go @@ -22,11 +22,11 @@ import ( "time" "unicode" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/protocol" - "github.com/stretchr/testify/require" - "github.com/algorand/go-algorand/daemon/algod/api/client" v2 "github.com/algorand/go-algorand/daemon/algod/api/server/v2" "github.com/algorand/go-algorand/daemon/algod/api/server/v2/generated/model" diff --git a/test/testdata/consensus/catchpointtestingprotocol.json b/test/testdata/consensus/catchpointtestingprotocol.json index c591dce170..8422388685 100644 --- a/test/testdata/consensus/catchpointtestingprotocol.json +++ b/test/testdata/consensus/catchpointtestingprotocol.json @@ -99,6 +99,7 @@ "SupportTransactionLeases": true, "SupportTxGroups": true, "TxnCounter": true, + "EnableCatchpointsWithSPContexts": true, "UpgradeThreshold": 9000, "UpgradeVoteRounds": 10000 } diff --git a/test/testdata/nettemplates/RichAccountStateProof.json b/test/testdata/nettemplates/RichAccountStateProof.json index e908ec807d..578fb0e3c5 100644 --- a/test/testdata/nettemplates/RichAccountStateProof.json +++ b/test/testdata/nettemplates/RichAccountStateProof.json @@ -2,7 +2,7 @@ "Genesis": { "NetworkName": "tbd", "ConsensusProtocol": "test-fast-stateproofs", - "LastPartKeyRound": 100, + "LastPartKeyRound": 500, "Wallets": [ { "Name": "richWallet", "Stake": 39, "Online": true }, { "Name": "Wallet1", "Stake": 20, "Online": true }, diff --git a/test/testdata/nettemplates/StateProof.json b/test/testdata/nettemplates/StateProof.json index 1194af6434..97f4165607 100644 --- a/test/testdata/nettemplates/StateProof.json +++ b/test/testdata/nettemplates/StateProof.json @@ -2,7 +2,7 @@ "Genesis": { "NetworkName": "tbd", "ConsensusProtocol": "test-fast-stateproofs", - "LastPartKeyRound": 100, + "LastPartKeyRound": 500, "Wallets": [ { "Name": "Wallet0", "Stake": 20, "Online": true }, { "Name": "Wallet1", "Stake": 20, "Online": true }, diff --git a/test/testdata/nettemplates/ThreeNodesWithRichAcct.json b/test/testdata/nettemplates/ThreeNodesWithRichAcct.json new file mode 100644 index 0000000000..548da60f69 --- /dev/null +++ b/test/testdata/nettemplates/ThreeNodesWithRichAcct.json @@ -0,0 +1,48 @@ +{ + "Genesis": { + "NetworkName": "tbd", + "ConsensusProtocol": "catchpointtestingprotocol", + "LastPartKeyRound": 3000, + "Wallets": [ + { + "Name": "Wallet1", + "Stake": 80, + "Online": true + }, + { + "Name": "Wallet2", + "Stake": 10, + "Online": true + }, + { + "Name": "Wallet3", + "Stake": 10, + "Online": true + } + ] + }, + "Nodes": [ + { + "Name": "Primary", + "IsRelay": true, + "Wallets": [ + { "Name": "Wallet1", + "ParticipationOnly": false } + ] + }, + { + "Name": "Node1", + "Wallets": [ + { "Name": "Wallet2", + "ParticipationOnly": false } + ] + }, + { + "Name": "Node2", + "Wallets": [ + { "Name": "Wallet3", + "ParticipationOnly": false } + ] + } + ] +} From b7234a38eeba64a5114261a44caffefaac6e82d1 Mon Sep 17 00:00:00 2001 From: Will Winder Date: Thu, 30 Mar 2023 11:15:47 -0400 Subject: [PATCH 022/125] api: Fix vote status computation. (#5228) --- cmd/goal/messages.go | 2 +- cmd/goal/node.go | 6 +- daemon/algod/api/algod.oas2.json | 2 +- daemon/algod/api/algod.oas3.yml | 6 +- .../api/server/v2/generated/data/routes.go | 178 ++++++------- .../v2/generated/experimental/routes.go | 176 ++++++------- .../api/server/v2/generated/model/types.go | 2 +- .../nonparticipating/private/routes.go | 242 ++++++++--------- .../nonparticipating/public/routes.go | 240 ++++++++--------- .../generated/participating/private/routes.go | 246 +++++++++--------- .../generated/participating/public/routes.go | 186 ++++++------- daemon/algod/api/server/v2/handlers.go | 21 +- .../server/v2/test/handlers_resources_test.go | 2 +- .../algod/api/server/v2/test/handlers_test.go | 127 ++++++--- daemon/algod/api/server/v2/test/helpers.go | 63 ++--- 15 files changed, 766 insertions(+), 733 deletions(-) diff --git a/cmd/goal/messages.go b/cmd/goal/messages.go index 6bf976f6a9..bcb7b3fd52 100644 --- a/cmd/goal/messages.go +++ b/cmd/goal/messages.go @@ -62,7 +62,7 @@ const ( infoNodeShuttingDown = "Algorand node is shutting down..." infoNodeSuccessfullyStopped = "The node was successfully stopped." infoNodeStatus = "Last committed block: %d\nTime since last block: %s\nSync Time: %s\nLast consensus protocol: %s\nNext consensus protocol: %s\nRound for next consensus protocol: %d\nNext consensus protocol supported: %v" - infoNodeStatusConsensusUpgradeVoting = "Consensus upgrate state: Voting\nYes votes: %d\nNo votes: %d\nVotes remaining: %d\nYes votes required: %d\nVote window close round: %d" + infoNodeStatusConsensusUpgradeVoting = "Consensus upgrade state: Voting\nYes votes: %d\nNo votes: %d\nVotes remaining: %d\nYes votes required: %d\nVote window close round: %d" infoNodeStatusConsensusUpgradeScheduled = "Consensus upgrade state: Scheduled" catchupStoppedOnUnsupported = "Last supported block (%d) is committed. The next block consensus protocol is not supported. Catchup service is stopped." infoNodeCatchpointCatchupStatus = "Last committed block: %d\nSync Time: %s\nCatchpoint: %s" diff --git a/cmd/goal/node.go b/cmd/goal/node.go index b8b5f2efa4..2b70c25327 100644 --- a/cmd/goal/node.go +++ b/cmd/goal/node.go @@ -482,6 +482,7 @@ func makeStatusString(stat model.NodeStatusResponse) string { upgradeVotesRequired := uint64(0) upgradeNoVotes := uint64(0) upgradeYesVotes := uint64(0) + upgradeVoteRounds := uint64(0) if stat.UpgradeVotesRequired != nil { upgradeVotesRequired = *stat.UpgradeVotesRequired } @@ -491,11 +492,14 @@ func makeStatusString(stat model.NodeStatusResponse) string { if stat.UpgradeYesVotes != nil { upgradeYesVotes = *stat.UpgradeYesVotes } + if stat.UpgradeVoteRounds != nil { + upgradeVoteRounds = *stat.UpgradeVoteRounds + } statusString = statusString + "\n" + fmt.Sprintf( infoNodeStatusConsensusUpgradeVoting, upgradeYesVotes, upgradeNoVotes, - upgradeNextProtocolVoteBefore-stat.LastRound, + upgradeVoteRounds-upgradeYesVotes-upgradeNoVotes, upgradeVotesRequired, upgradeNextProtocolVoteBefore, ) diff --git a/daemon/algod/api/algod.oas2.json b/daemon/algod/api/algod.oas2.json index 738eeddf8b..fc8ae526cf 100644 --- a/daemon/algod/api/algod.oas2.json +++ b/daemon/algod/api/algod.oas2.json @@ -3979,7 +3979,7 @@ "type": "integer" }, "upgrade-vote-rounds": { - "description": "Total voting ounds for current upgrade", + "description": "Total voting rounds for current upgrade", "type": "integer" } } diff --git a/daemon/algod/api/algod.oas3.yml b/daemon/algod/api/algod.oas3.yml index 3b7df350f4..b8ae23ef22 100644 --- a/daemon/algod/api/algod.oas3.yml +++ b/daemon/algod/api/algod.oas3.yml @@ -631,7 +631,7 @@ "type": "boolean" }, "upgrade-vote-rounds": { - "description": "Total voting ounds for current upgrade", + "description": "Total voting rounds for current upgrade", "type": "integer" }, "upgrade-votes": { @@ -4727,7 +4727,7 @@ "type": "boolean" }, "upgrade-vote-rounds": { - "description": "Total voting ounds for current upgrade", + "description": "Total voting rounds for current upgrade", "type": "integer" }, "upgrade-votes": { @@ -4902,7 +4902,7 @@ "type": "boolean" }, "upgrade-vote-rounds": { - "description": "Total voting ounds for current upgrade", + "description": "Total voting rounds for current upgrade", "type": "integer" }, "upgrade-votes": { diff --git a/daemon/algod/api/server/v2/generated/data/routes.go b/daemon/algod/api/server/v2/generated/data/routes.go index 192dc5095c..3630513e70 100644 --- a/daemon/algod/api/server/v2/generated/data/routes.go +++ b/daemon/algod/api/server/v2/generated/data/routes.go @@ -191,106 +191,106 @@ var swaggerSpec = []string{ "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", @@ -313,7 +313,7 @@ var swaggerSpec = []string{ "Qf+0DG+6pYhV7DkbERauTsmQrDhry4o/9X33w5/ifH9Bud/PrRW3UYxU5gxkzQWU90vH/EsK/I+RArYG", "lrMpzYmGPFfh3tcC974NJXC5QtyGeEyUA90nYmM/H35sP1HUMu6pdaUzsQn6okPYRjP0bX71o52tvw83", "lOlkKaTLE8IS0v3OGmh+6CrgdH5tks57XzCTPvgxDG6L/npYV+iPfuwaXmNfneFxoJGvX+Y/N46X0JGB", - "ErJ2Ybz7YOQT1n91wrOxyx8fHmLs/VoofTi7mn/s2OzDjx9qlvCFAWelZJdYZ+DD1f8PAAD//9XD1JGR", + "ErJ2Ybz7YOQT1n91wrOxyx8fHmLs/VoofTi7mn/s2OzDjx9qlvCFAWelZJdYZ+DD1f8PAAD//5glD8aS", "xwAA", } diff --git a/daemon/algod/api/server/v2/generated/experimental/routes.go b/daemon/algod/api/server/v2/generated/experimental/routes.go index cce4c207c2..dbd1717c60 100644 --- a/daemon/algod/api/server/v2/generated/experimental/routes.go +++ b/daemon/algod/api/server/v2/generated/experimental/routes.go @@ -121,106 +121,106 @@ var swaggerSpec = []string{ "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", @@ -235,7 +235,7 @@ var swaggerSpec = []string{ "ZIbPnx89n9CBCx12csVg+h1/5pdcbDjBIFwrN6uioHKH+oiuJFfkpx8IWxLoDsGUHwH3Kl0pNPZjPd/Z", "fNYiz8drRzSboHlo35BtaOl/3vE0+mOf+t23TGI/H35q19JtcaFaVzoTm6Av3lzstbs/Xv26ROvvww1l", "2ugiLqAFax31O2ug+aFL1er82kRH975gyHfwY2iFjf56WJeSi37sSojYV7dDBhr5RFv/udEQwhN3dvwh", - "OGs/fLz+aL5J0xo/NQfI8eEhOonXQunD2fX8U+dwCT9+rHnMZ7DPSsmuMCD+4/X/CwAA///IdC2zOrYA", + "OGs/fLz+aL5J0xo/NQfI8eEhOonXQunD2fX8U+dwCT9+rHnMZ7DPSsmuMCD+4/X/CwAA//9Ow4gVO7YA", "AA==", } diff --git a/daemon/algod/api/server/v2/generated/model/types.go b/daemon/algod/api/server/v2/generated/model/types.go index e838323e7f..ccf5419425 100644 --- a/daemon/algod/api/server/v2/generated/model/types.go +++ b/daemon/algod/api/server/v2/generated/model/types.go @@ -954,7 +954,7 @@ type NodeStatusResponse struct { // UpgradeNodeVote This node's upgrade vote UpgradeNodeVote *bool `json:"upgrade-node-vote,omitempty"` - // UpgradeVoteRounds Total voting ounds for current upgrade + // UpgradeVoteRounds Total voting rounds for current upgrade UpgradeVoteRounds *uint64 `json:"upgrade-vote-rounds,omitempty"` // UpgradeVotes Total votes cast for consensus upgrade 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 3645c50400..5a78c685d7 100644 --- a/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go +++ b/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go @@ -176,127 +176,127 @@ var swaggerSpec = []string{ "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", - "4/X/BAAA///WXlTym8AAAA==", + "EU/DS2Xsijt+QJSdRB+D8AAdatA3JwV2Tpp7WXeI/wLtBqj1iP0H2YAemkIDf68JDFjTXKhwsF864r0j", + "gaNic1CM7ZAjQ1t2wLT3lpdGpKLAu873sLnzq193gKjDiWVguMghY8EHugYWYX9GkRhdmDe7Co6ywvTR", + "75lhItPJhUaVp438BWzwzv2WQvzOg8DAO7jLRqDa84lLhoj6wCGrgodNYM1Tk2+somaWsGFXUALT1Wwl", + "jKHQ3fZV16giCQFELdxbRnTuHAqP8yswxr90hqCC6fWXYjqhO8F2/M47F4MWOdxdoFAqH2E96hEjisEo", + "zz8rlF114aKIfRyp56QWkk5ooy+vPv7v6RaZcQbsv1TFUi7xylUZqHUaVaKigAqkHcGqYPWYzsffUAhy", + "WAHdJPHLw4fdiT986NZcaDaHKx96bxt2yfHwIdpx3iptWpvrDmyFdrudRo4PNP3bg8/dQroyZbeP2UEe", + "s5JvO8Brf4HdU1o7xrXTv7UA6OzM9Zi5hzwyzr+OcEdZ9QPQsXnjup+JVZVzcxf+i60KaX2hEKsVZIIb", + "yDesKCEFCq+2GpYmXCxqjAKv0iWXC1SsS1UtXOQPwUHBWGkyYZSV7IGIKh9mLZNFqaoiJihdtKePsLdq", + "B3B79QkIiZ1J0b/i9XguqWLMCeYJHqzOdxbmkFthOhm8GVqiXjY3QyJOO00gTgXMe0h0laYA0Rjg2J2r", + "nmonHbJJcHEArdpQlRQExXhqKp6HXMdO54zLTTtPkotcWykoNMN2tnMTWDulufkkljnPyTkbyaoId0pL", + "4wtWviFplxQjHQ/IJFYb6nNGyIB2e1k2/mOM+A3oGJb9gYOoq+bjUOCVvYDnmztQgwgQK6EoQeOhFRqu", + "NH1V8zD5yZ1qeqMNrPq2fer6y4CgeTd4g1QyFxKSlZKwieb7Cgk/4Meo4MCDc6AzqjBDfbu3khb+HbTa", + "44zhxtvSF1c7kEVv64jDO1j8LtyOWydM+0KzJeQF4yzNBRo1ldSmrFLzQXI0mwSbLRKZ4e+Hw4a0l75J", + "3HIXMaw5UB8kx6ic2pgS9SbPIWI5+BbA29N0tViA7shPNgf4IF0rIVklhcGxVna9ElqwAkoMjzigliu+", + "sSIQ7X6/Q6nYrDJtmYypJ9pYcUk+JjsMU/MPkhuWg71T/yDk+RrBeR+t5xkJ5kqVFzUV4kfIAiRooZN4", + "BMl39BWD+9z0ly7QD1OF6TN5JSz8Jj9lg1aVJv31/93/z+P3J8l/8+T3o+TF/zr8+Onp9YOHvR8fX3/1", + "1f9v//Tk+qsH//kfsZXyuMcSIxzmp6/cZe30FWrkjVuih/tnM0mvhEyiTBY63zu8xe5jEqBjoAdte41Z", + "wgdp1tIy0iXPRWZVrpuwQ1fE9fYi7Y4O17QWomOf8XPdU8+9hZRhESHTEY03Psb7QVfxFCT0k7msItwv", + "80rSUnpFlyLsffCLmk/rNDOqQHHMMAdpyX3klvvz8bPnk2mTO1R/n0wn7uvHCCeLbB3VDmEdu764DYIb", + "455mBd9oGFBAEfdonA+FG4RgV2DvvXopis8vKbQRs7iE83HLzgyylqeSAort/kGv28YZ89X88+NtSquH", + "F2YZy0xvaQrYqllNgE4kRFGqS5BTJg7goGuGyOzVzEUc5cDnmCGNFz01Jg+j3gfEaJ4rAqqHExl114/x", + "Dyq3TlpfTyfu8Nd3ro87wDG8umPWLjb/t1Hs3nffnLNDJzD1PUpWJNBBelnk1uoyKFoxMlaaUT0Oytb8", + "ID/IVzAXUtjvxx9kxg0/nHEtUn1YaSi/5jmXKRwsFDv2SRmvuOEfZE/TGiyZE6TDsKKa5SJlF6FG3LAn", + "lUHoQ/jw4T3PF+rDh4+9cIG+/uqGisoXGiC5EmapKpO4JO6khCtextwxuk7iRchUpWHbqFPmYJModkni", + "Dn5c5vGi0N1kvv70iyK30w/YULtUNbtkTBtVel3EKiiEDa7vG+UOhpJfeRNGpUGzX1e8eC+k+ciSD9XR", + "0RNgrey2X92Rb3lyU8BoQ8ZgsmHXfoETp3sNrE3Jk4IvYl6fDx/eG+AFrj7qyyu8ZOc5w26trDofNYyg", + "mgl4egwvAOGxd4YQTu6MevmCPfEp4CdcQmxj1Y3GF33T9Qry7G68XJ1cvd4qVWaZ2L0dnZW2LO5Xpq7j", + "sbBKlg8Q0GKBQZiu5MkMWLqE9MLVooBVYTbTVncfg+IUTS86hKYqJZQlg3nyaDOfAauKjDtVvGtBmm2Y", + "BmN8FOg7uIDNuWrS7PfJUG4nzOqhjYqcGmiXllnDbetgdBffBTqhiasofN4pJiB5tjiu+cL3Gd7IpPLe", + "wSaOMUUroXOIELyMEIKYf4AEN5iohXcr1o9Nz94yZnTyRSqWeNnPXJPm8uRiksLZoIGbvq8ASx6pK81m", + "3OrtylXroaTQQIpVmi9gQEMO3RYjUy9brg4Esuvci550at490HrnTRRlapzYOUc5BewXyyp4melEovmR", + "yDPmnABYhM8RbJajmlSH7JHQ4WXLfURVxYZQizMwlLJRODwabYqEms2Sa19ICOst+b08Sgf4A5Oct5W2", + "CA36QVGl2r7uZW53n/Zul67Aha9q4UtZhFfLEWUprIaPcdux5VASFaAMcljQxKmxZ5Qm4bpZIIvHj/N5", + "LiSwJBaPxbVWqaBKUM0x48YAqx8/ZIxMwGw0hBgbB2ijxxcBszcq3JtysQ+S0iWMcw8bfcXB3xDPbaEI", + "ZavyqMKKcDHgQEq9BOAuiK8+vzqhpAiGCTllVsxd8tyKOXfja4D0Kiyg2tqpp+BiDh4MqbNbLPB0sOw1", + "JzqKbjKbUGfySMcVui0Yz9Q6oeS2qMY7W88sv0eDtjHVLrYxqZbFPc1mao1xLHi0UJDwDlyG8fBoBDf8", + "tdDIr9hv6DQnZLYNu12binGhRpZx5ryaXYbUiTFDD2gwQ+xyPyhPcSMEOsaOptaru/zuvKS21ZP+Yd6c", + "atOm7JLPh4lt/6EtFF2lAfr1rTB1QYm3XY0laqdoh2O0a2kEKmSM6a2Y6Dtp+q4gDTngpSBpKVHJRcx1", + "Z+82gCfOme8WGC+wYgeXmwdBjE8JC6ENNEZ0H5LwJcyTHAuFKTUfnp0pyrmd3zul6mOKKtFgx9Y0P/sM", + "MEh2LkptEvRARKdgG32r8VL9rW0a15XaUURUVlNkcdmAw17AJslEXsX51Y37/Ss77JtaJOpqhvJWSIoN", + "mWEZ2Ghs4ZahKfx064Rf04Rf8zub77jdYJvagUvLLu0x/iT7oiN5t4mDCAPGmKO/aoMk3SIgg5zQvnQM", + "9CbanJgTerDN+trbTJmHvTNsxGemDp1RBCk6l8BgsHUWAt1EVi0RJqii2k/WHNgDvChEtu7YQgnq4I2Z", + "72Xw8LWnOlTA1XXAdlAgsHvG8kVK0O0yY42CT/VwW1U+DkZR5rxdDCwUCOFQQvtq7n1C1flku2h1Djz/", + "HjY/27Y4ncn1dHI702mM1g7iDlq/rZc3Smd0zZMpreUJ2ZPkvChKdcnzxBmYh1izVJeONbG5t0d/ZlEX", + "N2Oef3Py+q1D/3o6SXPgZVKrCoOzwnbFn2ZWVNFsYIP4atH2zud1dlIlg8WvyzCFRumrJbiyu4E22qsP", + "2Dgcgq3ojNTzeITQTpOz843QFLf4SKCoXSSN+Y48JG2vCL/kIvd2M4/tQDQPTm5ckcmoVAgB3Nq7EjjJ", + "kjsVN73dHd8dDXftkEnhWFsKA6+o9rVmSnZd6BhevCmc133FsbofWUX6wklWK7QkJDoXadzGKmfaMock", + "35ltzLDxgDJqIVZiwBUrKxHAss30iItuB8lgjCgxfaXIIdrNlHvXpJLitwqYyEAa+6nEXdnZqFhO0Vnb", + "+8ep1R36YznAZKFvwN9GxwgrW3ZPPERiu4IReup66L6qr8x+orVFCsOtG5fEHg7/cMTekbjFWe/4w3Ez", + "BS8u2x638BmSvvyzjEH1qHe/geIvr67E5sAY0TdNhE7mpfod4vc8vB5HUnF8LU+BUS6/gxwRc95Yd5qn", + "WZrRB5d7SLsJrVDtIIUBrseVD9xyWFTQW6i5pKWmJwZasW5xhgmjSg8JfsMwDudeJG7Or2Y8VnHRKhkW", + "p5PGAdyypRvFfGdPe10nNtDoLPAl120FpVkXUDZZcv2SLTdUGGjY0apCoxkg14Y6wZT8f7lWETCVvOKS", + "Xqqw/Wgrud4ayPhle12pEosk6LjZP4NUrHge1xyytG/izcRC0CMMlYagyr8DRA/cEBe5lxLqdB1HmtM5", + "O5oGT4241cjEpdBilgO2eEQtZlyjJK8NUXUXOz2QZqmx+eMRzZeVzErIzFITYbVitVKH15vaeTUDcwUg", + "2RG2e/SC3Ue3nRaX8MBS0Z3Pk+NHL9DoSn8cxQ4A94jGNmmSoTj5hxMncT5GvyXBsILbQT2I5pPTK1rD", + "gmvLbqKuY/YStnSybvdeWnHJFxCPFFntwIn64mqiIa1DF5nREzDalGrDhImPD4Zb+TQQfW7FH6HBUrVa", + "CbNyzh2tVpafmhL+NKgHR+/JuOqrHi//EX2khXcRdS6Rn9doSudbbNboyX7DV9Am65RxqoyRiyZ6wdeE", + "Zqe+8A6Wo62r0BJt7Fh26qjmYDDDnBWlkAYvFpWZJ39j6ZKXPLXi72AI3WT2/GmkBG+7FKTcD/HPTvcS", + "NJSXcdKXA2zvdQjXl92XSiYrK1GyB022R7ArB525cbfdkO9wO+ixSpmFkgyyW9ViNx5I6lsxntwC8Jas", + "WM9nL37ce2afnTOrMs4evLIr9NO7107LWKkyVk2v2e5O4yjBlAIuMXYvvkgW5i3XosxHrcJtsP+yngev", + "cgZqmd/LsYvA1ypyO/VloWtLuotVj1gHhrap/WDZYOZATVm7BO/nd/p543Pf+WS/eFzxjy6yX3hJkch+", + "BgOLGJQHjy5nVn8P/N+cfa3WYxe1s0P8wv4LkCZKkkrk2c9NVman+nrJZbqM+rNmtuMvzTtR9eTofIoW", + "rVtyKSGPgiNd8BevM0a02n+qseOshBzZtlsQnqbbmVyDeBtNj5Qf0JJXmNwOEFK1nfBWB1TnC5UxHKep", + "kNZIz/5DAkG5598q0CaWPIQfKKgL7Zb2vkvVhhnIDG+LB+w7egp2CaxV/gZvaXUVAVf7lgzqVZErnk2x", + "kMP5NyevGY1Kfei1E6p2vMBLSnsWHXtVUPxxXHiwf7gknrowHs72WGo7a22wGpU2fFXEkkNti3PfADNQ", + "Qxs+Xl9C6hywV8GjjpRHakFYfpiLcmVvXDU00l2QJ+x/jOHpEq9kLZE6zPLjy3R7rtTB03j1Ezd1RUTc", + "dxZvV6mbCnVPmbL35iuh6QVQuIR2PmqdnO1MAj4/tT29spKSOCWqe2wrHnATsnvkKFDDm/mjmHUIv6dC", + "TlXu961afoa9ogWauiXQe2/iUXZj/XSJf9k55VJJkWJ5pNjR7J4KHeMDG1FJqmtk9Vvc7dDI5ooWXq/D", + "5BwVB0uxe0HoCNc3wgdf7aISd9CfBt+kXHLDFmC0k2yQTf37Ac4OKKQGV+ESH5YN5KQqW35FlJBRV3VS", + "uzT2ZCNMixm42H1rv71x136MF78QEhV8RzYXmk6WOnzJ0NhbgTBsoUC7+bRzg/V72+cA02QzWH888C8f", + "UjUYdMvZaZMPug/qxHuknQfYtn1p27o6QfXPrQhkGvSkKNygw69LRPUBs5aDBI54FhPv2gmIW8MPoW1h", + "t62hJHieWkaDS3REQ4HncI8x6pcWOq/4WKWVOApbMArhilYwEDKCxmshoXmXM3JApNEjARcG9+tAP52W", + "3JAKOEqmnQPP0fscE2jaONfDbUF1awlZkuAc/RjDy9g8EjEgOOoGjeLG5aZ+DtRyd6BMvMR3iB0h+08+", + "oFbllKgMMwo6j0DEBIcV3P6ZmfYB0N8GfZ2IupuS087Z5yQaShKdVdkCTMKzLFaR6mv8yvCrLy4Fa0ir", + "ujBlUbAUa6K0i8T0uc0NlCqpq9WWsXyDWw4XvKoS4YbwZRe/wpiEMtvgv7GqjMMr44Iw9g4D9BEX7hmK", + "PfXmNqSe1mt5OtFikYynBJ4ptydHM/TNGL3pf6ecnqtFG5HPXBpim5QL1ygm376xB0dYOaFXapSOlrqw", + "AQbdKf8WHl4b65TctlTCo6xXexSdPfVbW9sNEMOvZk3x8BsIvQ0KYnA6X8l7OBSAmw7Gi3PjMtcMZ1tF", + "0GA2EEXvUN4PYhG3nA5F7FDAjv3c6z1OM+zp2Qh7K0F9KFgfoe99nCkruHCu8UZY9CnrItKHzYXbNl2z", + "wN1JuDjvQYvd95dDMdlMC7nIgeH37jtDF+DS2euH5mmuPirJXwnpV/fOK8Gro+Kj8+9HJ+BQX9YMOmi0", + "PXc17Wma7k7+/c8Uw8ZAmnLzL2DC7S1675WmvrZL5qmmCavLIY8qj9w6FeMPLg3XP2pqHiE/FUqLpgR3", + "7CWmkbFu5/iYUlC/qQ/LB5pcQmqw7nrjQC8B9qnmZAcLXvn7dx2kgbtjHRLoyh9tq3nUL7a+40DrpSUF", + "qXVUqPpgfIWfkzpMCoUSVsBdgHQP7bUTDkaHPc/nkBpxuSMN7B9LkEGK0dQbIejB3CArTNRhtFhFZH8T", + "W4PQtiytrfgE1fxujc5QEsgFbO5p1uKGaOXsqT9XblJAAimA0iGxLKJ0LAyBrKbOMyx0zRlIBR/2Q92h", + "KcU1+OhOkNR4w7E8S9oTt0l03DJk/NWPUWPZrnul/2JE6FCmWP/RgGFl+xW+0aDrB/F8AYrwSspO+2X6", + "rlwBC0zaqx0FvpQFaP+bz9ClUXJxAeGzQOiWueJl5ltE7QzehJFsOY966V2+4H0X6Xk9smiCNPsJPZHC", + "TxiKm+bK6l/JUDxzOy4yfD0foz+o5DdGfFq85lC659NQ2cuVhsQoH9S5DY9tpHAvvd+ECHqw2CIhN1gC", + "5V1T4wWLznIsecJdZEs4QVbCilvsyqASy/CY24j9kr77DBZfdHSnOaXm192F5n14rtA9IoZcP2futNyd", + "GXMTy4qQkh5r1bGyLNKSMjT9F6XKqpQO6HBj1Nan0UWPtoiSqFEi7c+yd7/MsQTY6yDP8AI2h6T6+1L9", + "filD7EmFojkEef2d1b5To1P8fp0vaAKLO8HzSxpuppNCqTwZsPWf9qvLdPfAhUgvIGP27PCBbQPPlrD7", + "aGKunblXy42vplIUICF7cMDYiaRQYu/XbZc37gwu75lt469x1Kyigk/OpnTwQcZjMrEUU3lL+ebBbJdq", + "Gqzwu+VQBGRH7ZL1QGWbkl9FHvE5GHsp7Xtauw+rNExFWMS0lB1PWES8yP5NBP/Chs9YMWol0v4rClGX", + "xXYPAb0iNBvrJ6jrefbGHqwdmez0HLRwGOU/2BeNOb7KlfAIkU/rg2zaejRRdB4w8bWW6LmKlJMiay9R", + "XORVCS6Dgp4P6jwrUHCz9Gxsm/fVTau6gMb0BipNzzVdjvwlzb1d1JUPqkhyuISWQ8WldVRpClqLSwjf", + "PaLOLAMo0GTRPUhjnoJwf3VkqZt7Etiax1A3KlyJsLRSbIfkHHiVPqFtosduJYvRpcgq3qKfvsWTNCMf", + "uQ9xHSkp9hYS8cltExE7fXvI89F9KeOuvTCrqL4n4WhZbU8hJmx2ti74lRzWIiJX0drfdPt5MATGdCfL", + "byCWER/cSepygDFTuEtI8sxvR2weB+qo2MEjPjXMgedMa/65jTo7yJRxnrxZGZZRO6nvFYkIm+Dhnu22", + "u7BKUxP+XZJzDe/6Xl51N8MPjRwb94SQ77ADvdCkGzwi5HVph84XjtH+oSZKMJVBTmhNf5eV2E2wEfzB", + "EtGpZ6dJNfMovq+9LoELQL+sLetDL3t1DfBYkklJLFPXN9xrdLZitfuQcexGLy95/vmN71ir6wTp4Z5K", + "jk80tN6GRCZS6psFSr7mo8YOLLV3N7R8i86Cf4Bdo6iX3IFyJ0qtZXnfIopMnlshXr+RiCDZFcIkt/qj", + "52zmkryKElKhuyfVlS/EXRsr8V2K5gXt7dbRXfP8WZlbsPHcK37sTVPU1yg8MRoMmy36hYXKwM6NcnmM", + "+3psEaFfTEaF1VZ2HBcXLX87FUnvBJKqEu7Y7x5E0O3pd+/XkRk7PfIt20On0tCf5+jTukXbyEHdzG1s", + "0EifuNsqv46J9YgXdLbdMdiECILV0Bmiyn599CsrYY7PHSn28CEO8PDh1DX99XH7s93ODx/GH+r+XGEm", + "RCMHw40b45ifhxIPKLh+IMelsx6VyLNdjNHKWGoeDMOcnF9czuIXebLsF/IG9reqezZmnwC37iIgYSJz", + "bQ0eDBXkIo1IQ3LdIklHaFdLq1KYDZZS8tc58Us0IOa72t/s4hXq4hvu7DPqAupiXI13utL+dP1O8RzP", + "I6tTY3ihwceZv1nzVZGD2yhf3Zv9FZ787Wl29OTRX2d/O3p2lMLTZy+OjviLp/zRiyeP4PHfnj09gkfz", + "5y9mj7PHTx/Pnj5++vzZi/TJ00ezp89f/PWelUMWZUJ04hP3J/8X3/VLTt6eJucW2YYmvBD1m+yWjf3j", + "RDzFnQgrLvLJsf/pf/sddpCqVQPe/zpxecGTpTGFPj48vLq6Ogi7HC7QHZUYVaXLQz9O/y3st6d1bhdd", + "ynFFKW3HG1s8K5zgt3ffnJ2zk7enB8Fbq8eTo4Ojg0f4FGcBkhdicjx5gj/h7lniuh86Zpscf7qeTg6X", + "wHOM3rB/rMCUIvWf9BVfLKA8cK802Z8uHx96VeLwk3PFXW/7dhgWPD/81PJYZjt6YkHkw0++zs/21q1C", + "Os5TG3QYicW2ZoczTB8e2xR00Hh4KnjB0IefUEUe/P3Q5VTGP+JVhfbAoXfrx1u2qPTJrC2unR4pN+my", + "Kg4/4X+QJ69JSOQQc+JTKiJnTfMpE4bxmSqxwI5Jl1Yu+MoeQgctJ8ipxOSnmWVu2+slYeBreFFR0+P3", + "fZMJAmIeEkoCy+bNRm2N1MhiU1YQ1tmsT5pW++a8eX+UvPj46dH00dH1X+x54v589uR6ZDTOyxouO6sP", + "i5ENP2JZDDTE4P59fHR0iwdjT2RAflqk4F3iXqUpWolhq61bqg4gVhNjR/p+B3zsBbrr6eTpnjPeaj9q", + "xVdHXpL7mmfMZ+fi2I8+39inEmOhrFxndG5dTyfPPufsT6VleZ4zbBnUY+ov/U/yQqor6VtaJaNarXi5", + "8dtYt4QCc4uNRxlfaPSFleKSo24nlWw9MjP5iP7XWIb0gLzRht9A3pzZXv+WN59L3uAi3YW8aQO6Y3nz", + "eM89/+ef8b8l7J9Nwp6RuLuVhHUKHyWl9TVQWBdQihVIKo3lfqWiEof07n3/541Moz/2wXffX4v9fPip", + "Xf+/pTnrZWUydUUVT6JHBRa55bmriIem0fqaZRTzAJpAbfajS6TKN2gPFhkwjhUeVGWae7DtXHvIa0+F", + "hdC8zbgQEgdAkzOOQqUfeRACqSFVkl4y6xxLDrM3KoP+sYQHz28VlJvm5HE4TqYtueQYK1Jo8dZivi9G", + "rvdjOzSNk1+nzxz182Wtvw+vuDD28HIR00jRfmcDPD90tQA6vzbpd70vmFMY/Bi6+aO/Hta1iqMfu1fQ", + "2Fd3BRto5Cu5+M+NCSo06SBL1Mac9x/tymIlPMctjYXi+PAQoxCXSpvDyfX0U8d6EX78WC+mL5FUL+r1", + "x+v/CQAA//8iVBu0nMAAAA==", } // 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 36c2df859b..05c0d6704b 100644 --- a/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go +++ b/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go @@ -618,106 +618,106 @@ var swaggerSpec = []string{ "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", @@ -773,35 +773,35 @@ var swaggerSpec = []string{ "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", - "Ojxv3///AQAA//9KJ5oXyksBAA==", + "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", + "Sjxv3///AQAA//9vBJZkzUsBAA==", } // 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 ff9eb0f79d..0e5265896a 100644 --- a/daemon/algod/api/server/v2/generated/participating/private/routes.go +++ b/daemon/algod/api/server/v2/generated/participating/private/routes.go @@ -204,129 +204,129 @@ var swaggerSpec = []string{ "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", - "fvxYk8Cn+q5wpHD98fp/AgAA//+cgrUFs8oAAA==", + "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", + "P36sSeBTfVc4Urj+eP0/AQAA///yZJv4tMoAAA==", } // 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 7cf1dcd13f..446e3e3558 100644 --- a/daemon/algod/api/server/v2/generated/participating/public/routes.go +++ b/daemon/algod/api/server/v2/generated/participating/public/routes.go @@ -223,106 +223,106 @@ var swaggerSpec = []string{ "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", @@ -353,8 +353,8 @@ var swaggerSpec = []string{ "G3I5uZx0RpKwEteQ2cwLYciY7bVz2P+vGvfHTvQpBu1jtXAfqkFUOZ+zlFmU54IvCF2I2hPY8G3CBX4B", "aYCzOTwI01OXFIYpsjaLd/lsm5FtTcm9KwGc1Vu405LeIpe4Ed0Q3p4W9P8YYz7/Xy2l3yKE4VaMdHDs", "Dlf9wlU+BVf57Hzlj26bDNSH/yPFzCfHT/6wCwqVza+FJt+hl/vtxLEqR3gslclNBS2fcN6r+2pP2dDz", - "FG/Ryuf03XtzEWDBHnfB1o6UJ0dHmCxhKZQ+mpjrr+lkGX58X8HsKzlMCsmuMTHk+4//LwAA//8ccqHh", - "QtkAAA==", + "FG/Ryuf03XtzEWDBHnfB1o6UJ0dHmCxhKZQ+mpjrr+lkGX58X8HsKzlMCsmuMTHk+4//LwAA//9j2xfO", + "Q9kAAA==", } // 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 08b14631cf..0bf02974d1 100644 --- a/daemon/algod/api/server/v2/handlers.go +++ b/daemon/algod/api/server/v2/handlers.go @@ -793,26 +793,29 @@ func (v2 *Handlers) GetStatus(ctx echo.Context) error { CatchpointAcquiredBlocks: &stat.CatchpointCatchupAcquiredBlocks, } - nextProtocolVoteBefore := uint64(stat.NextProtocolVoteBefore) - var votesToGo int64 = int64(nextProtocolVoteBefore) - int64(stat.LastRound) - if votesToGo < 0 { - votesToGo = 0 - } - if nextProtocolVoteBefore > 0 { + // Make sure a vote is happening + if stat.NextProtocolVoteBefore > 0 { + votesToGo := uint64(0) + // Check if the vote window is still open. + if stat.NextProtocolVoteBefore > stat.LastRound { + // subtract 1 because the variables are referring to "Last" round and "VoteBefore" + votesToGo = uint64(stat.NextProtocolVoteBefore - stat.LastRound - 1) + } + consensus := config.Consensus[protocol.ConsensusCurrentVersion] upgradeVoteRounds := consensus.UpgradeVoteRounds upgradeThreshold := consensus.UpgradeThreshold - votes := uint64(consensus.UpgradeVoteRounds) - uint64(votesToGo) + votes := consensus.UpgradeVoteRounds - votesToGo votesYes := stat.NextProtocolApprovals votesNo := votes - votesYes - upgradeDelay := uint64(stat.UpgradeDelay) + upgradeDelay := stat.UpgradeDelay response.UpgradeVotesRequired = &upgradeThreshold response.UpgradeNodeVote = &stat.UpgradeApprove response.UpgradeDelay = &upgradeDelay response.UpgradeVotes = &votes response.UpgradeYesVotes = &votesYes response.UpgradeNoVotes = &votesNo - response.UpgradeNextProtocolVoteBefore = &nextProtocolVoteBefore + response.UpgradeNextProtocolVoteBefore = numOrNil(uint64(stat.NextProtocolVoteBefore)) response.UpgradeVoteRounds = &upgradeVoteRounds } diff --git a/daemon/algod/api/server/v2/test/handlers_resources_test.go b/daemon/algod/api/server/v2/test/handlers_resources_test.go index bef0291006..f57121473d 100644 --- a/daemon/algod/api/server/v2/test/handlers_resources_test.go +++ b/daemon/algod/api/server/v2/test/handlers_resources_test.go @@ -229,7 +229,7 @@ func setupTestForLargeResources(t *testing.T, acctSize, maxResults int, accountM acctData = accountMaker(acctSize) ml.accounts[fakeAddr] = acctData - mockNode := makeMockNode(&ml, t.Name(), nil, false) + mockNode := makeMockNode(&ml, t.Name(), nil, cannedStatusReportGolden) mockNode.config.MaxAPIResourcesPerAccount = uint64(maxResults) dummyShutdownChan := make(chan struct{}) handlers = v2.Handlers{ diff --git a/daemon/algod/api/server/v2/test/handlers_test.go b/daemon/algod/api/server/v2/test/handlers_test.go index 19d4bea1be..bbb1994d99 100644 --- a/daemon/algod/api/server/v2/test/handlers_test.go +++ b/daemon/algod/api/server/v2/test/handlers_test.go @@ -63,12 +63,12 @@ import ( const stateProofInterval = uint64(256) -func setupTestForMethodGet(t *testing.T, consensusUpgrade bool) (v2.Handlers, echo.Context, *httptest.ResponseRecorder, []account.Root, []transactions.SignedTxn, func()) { +func setupTestForMethodGet(t *testing.T, status node.StatusReport) (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, consensusUpgrade) + mockNode := makeMockNode(mockLedger, t.Name(), nil, status) dummyShutdownChan := make(chan struct{}) handler := v2.Handlers{ Node: mockNode, @@ -93,13 +93,13 @@ func TestSimpleMockBuilding(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - handler, _, _, _, _, releasefunc := setupTestForMethodGet(t, false) + handler, _, _, _, _, releasefunc := setupTestForMethodGet(t, cannedStatusReportGolden) defer releasefunc() require.Equal(t, t.Name(), handler.Node.GenesisID()) } func accountInformationTest(t *testing.T, address string, expectedCode int) { - handler, c, rec, _, _, releasefunc := setupTestForMethodGet(t, false) + handler, c, rec, _, _, releasefunc := setupTestForMethodGet(t, cannedStatusReportGolden) defer releasefunc() err := handler.AccountInformation(c, address, model.AccountInformationParams{}) require.NoError(t, err) @@ -122,7 +122,7 @@ func TestAccountInformation(t *testing.T) { } func getBlockTest(t *testing.T, blockNum uint64, format string, expectedCode int) { - handler, c, rec, _, _, releasefunc := setupTestForMethodGet(t, false) + handler, c, rec, _, _, releasefunc := setupTestForMethodGet(t, cannedStatusReportGolden) defer releasefunc() err := handler.GetBlock(c, blockNum, model.GetBlockParams{Format: (*model.GetBlockParamsFormat)(&format)}) require.NoError(t, err) @@ -141,7 +141,7 @@ func TestGetBlock(t *testing.T) { } func testGetLedgerStateDelta(t *testing.T, round uint64, format string, expectedCode int) { - handler, c, rec, _, _, releasefunc := setupTestForMethodGet(t, false) + handler, c, rec, _, _, releasefunc := setupTestForMethodGet(t, cannedStatusReportGolden) defer releasefunc() insertRounds(require.New(t), handler, 3) err := handler.GetLedgerStateDelta(c, round, model.GetLedgerStateDeltaParams{Format: (*model.GetLedgerStateDeltaParamsFormat)(&format)}) @@ -186,7 +186,7 @@ func TestSyncRound(t *testing.T) { numTransactions := 1 offlineAccounts := true mockLedger, _, _, _, releasefunc := testingenv(t, numAccounts, numTransactions, offlineAccounts) - mockNode := makeMockNode(mockLedger, t.Name(), nil, false) + mockNode := makeMockNode(mockLedger, t.Name(), nil, cannedStatusReportGolden) dummyShutdownChan := make(chan struct{}) handler := v2.Handlers{ Node: mockNode, @@ -247,7 +247,7 @@ func TestSyncRound(t *testing.T) { } func addBlockHelper(t *testing.T) (v2.Handlers, echo.Context, *httptest.ResponseRecorder, transactions.SignedTxn, func()) { - handler, c, rec, _, _, releasefunc := setupTestForMethodGet(t, false) + handler, c, rec, _, _, releasefunc := setupTestForMethodGet(t, cannedStatusReportGolden) l := handler.Node.LedgerForAPI() @@ -331,7 +331,7 @@ func TestGetBlockHash(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - handler, c, rec, _, _, releasefunc := setupTestForMethodGet(t, false) + handler, c, rec, _, _, releasefunc := setupTestForMethodGet(t, cannedStatusReportGolden) defer releasefunc() err := handler.GetBlockHash(c, 0) @@ -349,7 +349,7 @@ func TestGetBlockGetBlockHash(t *testing.T) { t.Parallel() a := require.New(t) - handler, c, rec, _, _, releasefunc := setupTestForMethodGet(t, false) + handler, c, rec, _, _, releasefunc := setupTestForMethodGet(t, cannedStatusReportGolden) defer releasefunc() insertRounds(a, handler, 2) @@ -421,7 +421,7 @@ func TestGetSupply(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - handler, c, _, _, _, releasefunc := setupTestForMethodGet(t, false) + handler, c, _, _, _, releasefunc := setupTestForMethodGet(t, cannedStatusReportGolden) defer releasefunc() err := handler.GetSupply(c) require.NoError(t, err) @@ -431,7 +431,7 @@ func TestGetStatus(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - handler, c, rec, _, _, releasefunc := setupTestForMethodGet(t, false) + handler, c, rec, _, _, releasefunc := setupTestForMethodGet(t, cannedStatusReportGolden) defer releasefunc() err := handler.GetStatus(c) require.NoError(t, err) @@ -462,17 +462,72 @@ func TestGetStatus(t *testing.T) { require.Equal(t, expectedResult, actualResult) } +func TestGetStatusConsensusUpgradeUnderflow(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + // Setup status report with unanimous YES votes. + proto := config.Consensus[protocol.ConsensusCurrentVersion] + currentRound := basics.Round(1000000) + stat := node.StatusReport{ + LastRound: currentRound - 1, + LastVersion: protocol.ConsensusCurrentVersion, + NextVersion: protocol.ConsensusCurrentVersion, + UpgradePropose: "upgradePropose", + NextProtocolVoteBefore: currentRound, + NextProtocolApprovals: proto.UpgradeVoteRounds, + } + + handler, c, rec, _, _, releasefunc := setupTestForMethodGet(t, stat) + defer releasefunc() + err := handler.GetStatus(c) + require.NoError(t, err) + actualResult := model.NodeStatusResponse{} + err = protocol.DecodeJSON(rec.Body.Bytes(), &actualResult) + require.NoError(t, err) + + // Make sure the votes are all yes, and 0 no. + require.Equal(t, uint64(0), *actualResult.UpgradeNoVotes) + require.Equal(t, proto.UpgradeVoteRounds, *actualResult.UpgradeYesVotes) + require.Equal(t, proto.UpgradeVoteRounds, *actualResult.UpgradeVotes) + require.Equal(t, proto.UpgradeThreshold, *actualResult.UpgradeVotesRequired) +} + func TestGetStatusConsensusUpgrade(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - handler, c, rec, _, _, releasefunc := setupTestForMethodGet(t, true) + cannedStatusReportConsensusUpgradeGolden := node.StatusReport{ + LastRound: basics.Round(97000), + LastVersion: protocol.ConsensusCurrentVersion, + NextVersion: protocol.ConsensusCurrentVersion, + NextVersionRound: 200000, + NextVersionSupported: true, + StoppedAtUnsupportedRound: true, + Catchpoint: "", + CatchpointCatchupAcquiredBlocks: 0, + CatchpointCatchupProcessedAccounts: 0, + CatchpointCatchupVerifiedAccounts: 0, + CatchpointCatchupTotalAccounts: 0, + CatchpointCatchupTotalKVs: 0, + CatchpointCatchupProcessedKVs: 0, + CatchpointCatchupVerifiedKVs: 0, + CatchpointCatchupTotalBlocks: 0, + LastCatchpoint: "", + UpgradePropose: "upgradePropose", + UpgradeApprove: false, + UpgradeDelay: 0, + NextProtocolVoteBefore: 100000, + NextProtocolApprovals: 5000, + } + + handler, c, rec, _, _, releasefunc := setupTestForMethodGet(t, cannedStatusReportConsensusUpgradeGolden) defer releasefunc() err := handler.GetStatus(c) require.NoError(t, err) stat := cannedStatusReportConsensusUpgradeGolden consensus := config.Consensus[protocol.ConsensusCurrentVersion] - votesToGo := uint64(stat.NextProtocolVoteBefore) - uint64(stat.LastRound) + votesToGo := uint64(stat.NextProtocolVoteBefore) - uint64(stat.LastRound) - 1 nextProtocolVoteBefore := uint64(stat.NextProtocolVoteBefore) votes := uint64(consensus.UpgradeVoteRounds) - votesToGo votesNo := votes - stat.NextProtocolApprovals @@ -515,7 +570,7 @@ func TestGetStatusAfterBlock(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - handler, c, rec, _, _, releasefunc := setupTestForMethodGet(t, false) + handler, c, rec, _, _, releasefunc := setupTestForMethodGet(t, cannedStatusReportGolden) defer releasefunc() err := handler.WaitForBlock(c, 0) require.NoError(t, err) @@ -528,7 +583,7 @@ func TestGetTransactionParams(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - handler, c, rec, _, _, releasefunc := setupTestForMethodGet(t, false) + handler, c, rec, _, _, releasefunc := setupTestForMethodGet(t, cannedStatusReportGolden) defer releasefunc() err := handler.TransactionParams(c) require.NoError(t, err) @@ -536,7 +591,7 @@ func TestGetTransactionParams(t *testing.T) { } func pendingTransactionInformationTest(t *testing.T, txidToUse int, format string, expectedCode int) { - handler, c, rec, _, stxns, releasefunc := setupTestForMethodGet(t, false) + handler, c, rec, _, stxns, releasefunc := setupTestForMethodGet(t, cannedStatusReportGolden) defer releasefunc() txid := "bad txid" if txidToUse >= 0 { @@ -559,7 +614,7 @@ func TestPendingTransactionInformation(t *testing.T) { } func getPendingTransactionsTest(t *testing.T, format string, max uint64, expectedCode int) { - handler, c, rec, _, _, releasefunc := setupTestForMethodGet(t, false) + handler, c, rec, _, _, releasefunc := setupTestForMethodGet(t, cannedStatusReportGolden) defer releasefunc() params := model.GetPendingTransactionsParams{Format: (*model.GetPendingTransactionsParamsFormat)(&format), Max: &max} err := handler.GetPendingTransactions(c, params) @@ -638,7 +693,7 @@ func TestPendingTransactions(t *testing.T) { } func pendingTransactionsByAddressTest(t *testing.T, rootkeyToUse int, format string, expectedCode int) { - handler, c, rec, rootkeys, _, releasefunc := setupTestForMethodGet(t, false) + handler, c, rec, rootkeys, _, releasefunc := setupTestForMethodGet(t, cannedStatusReportGolden) defer releasefunc() address := "bad address" if rootkeyToUse >= 0 { @@ -666,7 +721,7 @@ func prepareTransactionTest(t *testing.T, txnToUse, expectedCode int) (handler v offlineAccounts := true mockLedger, _, _, stxns, releasefunc := testingenv(t, numAccounts, numTransactions, offlineAccounts) dummyShutdownChan := make(chan struct{}) - mockNode := makeMockNode(mockLedger, t.Name(), nil, false) + mockNode := makeMockNode(mockLedger, t.Name(), nil, cannedStatusReportGolden) handler = v2.Handlers{ Node: mockNode, @@ -859,7 +914,7 @@ func TestSimulateTransaction(t *testing.T) { mockLedger, roots, _, _, releasefunc := testingenvWithBalances(t, 999_998, 999_999, numAccounts, 1, offlineAccounts) defer releasefunc() dummyShutdownChan := make(chan struct{}) - mockNode := makeMockNode(mockLedger, t.Name(), nil, false) + mockNode := makeMockNode(mockLedger, t.Name(), nil, cannedStatusReportGolden) handler := v2.Handlers{ Node: mockNode, Log: logging.Base(), @@ -1018,7 +1073,7 @@ func TestSimulateTransactionVerificationFailure(t *testing.T) { mockLedger, roots, _, _, releasefunc := testingenv(t, numAccounts, 1, offlineAccounts) defer releasefunc() dummyShutdownChan := make(chan struct{}) - mockNode := makeMockNode(mockLedger, t.Name(), nil, false) + mockNode := makeMockNode(mockLedger, t.Name(), nil, cannedStatusReportGolden) handler := v2.Handlers{ Node: mockNode, Log: logging.Base(), @@ -1065,7 +1120,7 @@ func startCatchupTest(t *testing.T, catchpoint string, nodeError error, expected mockLedger, _, _, _, releasefunc := testingenv(t, numAccounts, numTransactions, offlineAccounts) defer releasefunc() dummyShutdownChan := make(chan struct{}) - mockNode := makeMockNode(mockLedger, t.Name(), nodeError, false) + mockNode := makeMockNode(mockLedger, t.Name(), nodeError, cannedStatusReportGolden) handler := v2.Handlers{Node: mockNode, Log: logging.Base(), Shutdown: dummyShutdownChan} e := echo.New() req := httptest.NewRequest(http.MethodPost, "/", nil) @@ -1106,7 +1161,7 @@ func abortCatchupTest(t *testing.T, catchpoint string, expectedCode int) { mockLedger, _, _, _, releasefunc := testingenv(t, numAccounts, numTransactions, offlineAccounts) defer releasefunc() dummyShutdownChan := make(chan struct{}) - mockNode := makeMockNode(mockLedger, t.Name(), nil, false) + mockNode := makeMockNode(mockLedger, t.Name(), nil, cannedStatusReportGolden) handler := v2.Handlers{ Node: mockNode, Log: logging.Base(), @@ -1141,7 +1196,7 @@ func tealCompileTest(t *testing.T, bytesToUse []byte, expectedCode int, mockLedger, _, _, _, releasefunc := testingenv(t, numAccounts, numTransactions, offlineAccounts) defer releasefunc() dummyShutdownChan := make(chan struct{}) - mockNode := makeMockNode(mockLedger, t.Name(), nil, false) + mockNode := makeMockNode(mockLedger, t.Name(), nil, cannedStatusReportGolden) mockNode.config.EnableDeveloperAPI = enableDeveloperAPI handler := v2.Handlers{ Node: mockNode, @@ -1212,7 +1267,7 @@ func tealDisassembleTest(t *testing.T, program []byte, expectedCode int, mockLedger, _, _, _, releasefunc := testingenv(t, numAccounts, numTransactions, offlineAccounts) defer releasefunc() dummyShutdownChan := make(chan struct{}) - mockNode := makeMockNode(mockLedger, t.Name(), nil, false) + mockNode := makeMockNode(mockLedger, t.Name(), nil, cannedStatusReportGolden) mockNode.config.EnableDeveloperAPI = enableDeveloperAPI handler := v2.Handlers{ Node: mockNode, @@ -1275,7 +1330,7 @@ func tealDryrunTest( mockLedger, _, _, _, releasefunc := testingenv(t, numAccounts, numTransactions, offlineAccounts) defer releasefunc() dummyShutdownChan := make(chan struct{}) - mockNode := makeMockNode(mockLedger, t.Name(), nil, false) + mockNode := makeMockNode(mockLedger, t.Name(), nil, cannedStatusReportGolden) mockNode.config.EnableDeveloperAPI = enableDeveloperAPI handler := v2.Handlers{ Node: mockNode, @@ -1394,7 +1449,7 @@ func TestAppendParticipationKeys(t *testing.T) { mockLedger, _, _, _, releasefunc := testingenv(t, 1, 1, true) defer releasefunc() - mockNode := makeMockNode(mockLedger, t.Name(), nil, false) + mockNode := makeMockNode(mockLedger, t.Name(), nil, cannedStatusReportGolden) handler := v2.Handlers{ Node: mockNode, Log: logging.Base(), @@ -1478,7 +1533,7 @@ func TestAppendParticipationKeys(t *testing.T) { t.Run("Internal error", func(t *testing.T) { // Create mock node with an error. expectedErr := errors.New("expected error") - mockNode := makeMockNode(mockLedger, t.Name(), expectedErr, false) + mockNode := makeMockNode(mockLedger, t.Name(), expectedErr, cannedStatusReportGolden) handler := v2.Handlers{ Node: mockNode, Log: logging.Base(), @@ -1662,7 +1717,7 @@ func TestStateProofNotFound(t *testing.T) { partitiontest.PartitionTest(t) a := require.New(t) - handler, ctx, responseRecorder, _, _, releasefunc := setupTestForMethodGet(t, false) + handler, ctx, responseRecorder, _, _, releasefunc := setupTestForMethodGet(t, cannedStatusReportGolden) defer releasefunc() insertRounds(a, handler, 700) @@ -1675,7 +1730,7 @@ func TestStateProofHigherRoundThanLatest(t *testing.T) { partitiontest.PartitionTest(t) a := require.New(t) - handler, ctx, responseRecorder, _, _, releasefunc := setupTestForMethodGet(t, false) + handler, ctx, responseRecorder, _, _, releasefunc := setupTestForMethodGet(t, cannedStatusReportGolden) defer releasefunc() a.NoError(handler.GetStateProof(ctx, 2)) @@ -1686,7 +1741,7 @@ func TestStateProof200(t *testing.T) { partitiontest.PartitionTest(t) a := require.New(t) - handler, ctx, responseRecorder, _, _, releasefunc := setupTestForMethodGet(t, false) + handler, ctx, responseRecorder, _, _, releasefunc := setupTestForMethodGet(t, cannedStatusReportGolden) defer releasefunc() insertRounds(a, handler, 1000) @@ -1704,7 +1759,7 @@ func TestHeaderProofRoundTooHigh(t *testing.T) { partitiontest.PartitionTest(t) a := require.New(t) - handler, ctx, responseRecorder, _, _, releasefunc := setupTestForMethodGet(t, false) + handler, ctx, responseRecorder, _, _, releasefunc := setupTestForMethodGet(t, cannedStatusReportGolden) defer releasefunc() a.NoError(handler.GetLightBlockHeaderProof(ctx, 2)) @@ -1715,7 +1770,7 @@ func TestHeaderProofStateProofNotFound(t *testing.T) { partitiontest.PartitionTest(t) a := require.New(t) - handler, ctx, responseRecorder, _, _, releasefunc := setupTestForMethodGet(t, false) + handler, ctx, responseRecorder, _, _, releasefunc := setupTestForMethodGet(t, cannedStatusReportGolden) defer releasefunc() insertRounds(a, handler, 700) @@ -1728,7 +1783,7 @@ func TestGetBlockProof200(t *testing.T) { partitiontest.PartitionTest(t) a := require.New(t) - handler, ctx, responseRecorder, _, _, releasefunc := setupTestForMethodGet(t, false) + handler, ctx, responseRecorder, _, _, releasefunc := setupTestForMethodGet(t, cannedStatusReportGolden) defer releasefunc() insertRounds(a, handler, 1000) @@ -1859,7 +1914,7 @@ func TestExperimentalCheck(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - handler, c, rec, _, _, releasefunc := setupTestForMethodGet(t, false) + handler, c, rec, _, _, releasefunc := setupTestForMethodGet(t, cannedStatusReportGolden) defer releasefunc() // Since we are invoking the method directly, it doesn't matter if EnableExperimentalAPI is true. diff --git a/daemon/algod/api/server/v2/test/helpers.go b/daemon/algod/api/server/v2/test/helpers.go index 8b3ac5f8fc..e8837e3f97 100644 --- a/daemon/algod/api/server/v2/test/helpers.go +++ b/daemon/algod/api/server/v2/test/helpers.go @@ -64,30 +64,6 @@ var cannedStatusReportGolden = node.StatusReport{ LastCatchpoint: "", } -var cannedStatusReportConsensusUpgradeGolden = node.StatusReport{ - LastRound: basics.Round(97000), - LastVersion: protocol.ConsensusCurrentVersion, - NextVersion: protocol.ConsensusCurrentVersion, - NextVersionRound: 200000, - NextVersionSupported: true, - StoppedAtUnsupportedRound: true, - Catchpoint: "", - CatchpointCatchupAcquiredBlocks: 0, - CatchpointCatchupProcessedAccounts: 0, - CatchpointCatchupVerifiedAccounts: 0, - CatchpointCatchupTotalAccounts: 0, - CatchpointCatchupTotalKVs: 0, - CatchpointCatchupProcessedKVs: 0, - CatchpointCatchupVerifiedKVs: 0, - CatchpointCatchupTotalBlocks: 0, - LastCatchpoint: "", - UpgradePropose: "upgradePropose", - UpgradeApprove: false, - UpgradeDelay: 0, - NextProtocolVoteBefore: 100000, - NextProtocolApprovals: 5000, -} - var poolAddrRewardBaseGolden = uint64(0) var poolAddrAssetsGolden = make([]model.AssetHolding, 0) var poolAddrCreatedAssetsGolden = make([]model.Asset, 0) @@ -114,14 +90,14 @@ var txnPoolGolden = make([]transactions.SignedTxn, 2) // package `data` and package `node`, which themselves import `mocks` type mockNode struct { mock.Mock - ledger v2.LedgerForAPI - genesisID string - config config.Local - err error - id account.ParticipationID - keys account.StateProofKeys - usertxns map[basics.Address][]node.TxnWithStatus - consensusUpgrade bool + ledger v2.LedgerForAPI + genesisID string + config config.Local + err error + id account.ParticipationID + keys account.StateProofKeys + usertxns map[basics.Address][]node.TxnWithStatus + status node.StatusReport } func (m *mockNode) InstallParticipationKey(partKeyBinary []byte) (account.ParticipationID, error) { @@ -159,14 +135,14 @@ func (m *mockNode) AppendParticipationKeys(id account.ParticipationID, keys acco return m.err } -func makeMockNode(ledger v2.LedgerForAPI, genesisID string, nodeError error, consensusUpgrade bool) *mockNode { +func makeMockNode(ledger v2.LedgerForAPI, genesisID string, nodeError error, status node.StatusReport) *mockNode { return &mockNode{ - ledger: ledger, - genesisID: genesisID, - config: config.GetDefaultLocal(), - err: nodeError, - usertxns: map[basics.Address][]node.TxnWithStatus{}, - consensusUpgrade: consensusUpgrade, + ledger: ledger, + genesisID: genesisID, + config: config.GetDefaultLocal(), + err: nodeError, + usertxns: map[basics.Address][]node.TxnWithStatus{}, + status: status, } } @@ -174,13 +150,8 @@ func (m *mockNode) LedgerForAPI() v2.LedgerForAPI { return m.ledger } -func (m mockNode) Status() (s node.StatusReport, err error) { - if m.consensusUpgrade { - s = cannedStatusReportConsensusUpgradeGolden - } else { - s = cannedStatusReportGolden - } - return +func (m *mockNode) Status() (node.StatusReport, error) { + return m.status, nil } func (m *mockNode) GenesisID() string { return m.genesisID From 36ffb596c1e3a89975083362f866f566e828ac41 Mon Sep 17 00:00:00 2001 From: Hang Su <87964331+ahangsu@users.noreply.github.com> Date: Thu, 30 Mar 2023 14:13:35 -0400 Subject: [PATCH 023/125] ledger: convert FC unmarshalled nil value to empty byte slice on DB write KVs (#5225) --- ledger/acctdeltas_test.go | 138 ++++++++++++++++++ ledger/catchupaccessor.go | 25 ++++ ledger/store/trackerdb/sqlitedriver/schema.go | 6 + .../store/trackerdb/sqlitedriver/testing.go | 3 + .../trackerdb/sqlitedriver/trackerdbV2.go | 10 +- 5 files changed, 180 insertions(+), 2 deletions(-) diff --git a/ledger/acctdeltas_test.go b/ledger/acctdeltas_test.go index 146a8f1ac8..e272983847 100644 --- a/ledger/acctdeltas_test.go +++ b/ledger/acctdeltas_test.go @@ -850,6 +850,7 @@ func TestLookupKeysByPrefix(t *testing.T) { {key: []byte("DingHo-StandardPack"), value: []byte("5bucks25cents")}, {key: []byte("BostonKitchen-CheeseSlice"), value: []byte("3bucks50cents")}, {key: []byte(`™£´´∂ƒ∂ƒßƒ©∑®ƒß∂†¬∆`), value: []byte("random Bluh")}, + {key: []byte(`a-random-box-key`), value: []byte{}}, } err = dbs.Transaction(func(ctx context.Context, tx trackerdb.TransactionScope) (err error) { @@ -1067,6 +1068,143 @@ func BenchmarkLookupKeyByPrefix(b *testing.B) { } } +func TestKVStoreNilBlobConversion(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + // +-------------------------------------------------------------+ + // | Section 1: Create a ledger with tracer DB of user_version 9 | + // +-------------------------------------------------------------+ + + const inMem = false + + log := logging.TestingLog(t) + log.SetLevel(logging.Info) + + dbs, dbName := storetesting.DbOpenTest(t, inMem) + storetesting.SetDbLogging(t, dbs) + + err := dbs.Wdb.Atomic(func(ctx context.Context, tx *sql.Tx) error { + sqlitedriver.AccountsInitTest(t, tx, make(map[basics.Address]basics.AccountData), protocol.ConsensusCurrentVersion) + return nil + }) + require.NoError(t, err) + + defer func() { + dbs.Close() + require.NoError(t, os.Remove(dbName)) + }() + + targetVersion := int32(10) + + err = dbs.Wdb.Atomic(func(ctx context.Context, tx *sql.Tx) (err0 error) { + _, err0 = tx.ExecContext(ctx, fmt.Sprintf("PRAGMA user_version = %d", targetVersion-1)) + return + }) + require.NoError(t, err) + + // +-----------------------------------------------------------------+ + // | ^ Section 1 finishes above | + // | | + // | Section 2: jams a bunch of key value with value nil into the DB | + // +-----------------------------------------------------------------+ + + kvPairDBPrepareSet := []struct{ key []byte }{ + {key: []byte{0xFF, 0x12, 0x34, 0x56, 0x78}}, + {key: []byte{0xFF, 0xFF, 0x34, 0x56, 0x78}}, + {key: []byte{0xFF, 0xFF, 0xFF, 0x56, 0x78}}, + {key: []byte{0xFF, 0xFF, 0xFF, 0xFF, 0x78}}, + {key: []byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF}}, + {key: []byte{0xFF, 0xFE, 0xFF}}, + {key: []byte{0xFF, 0xFF, 0x00, 0xFF, 0xFF}}, + {key: []byte{0xFF, 0xFF}}, + {key: []byte{0xBA, 0xDD, 0xAD, 0xFF, 0xFF}}, + {key: []byte{0xBA, 0xDD, 0xAE, 0x00}}, + {key: []byte{0xBA, 0xDD, 0xAE}}, + {key: []byte("TACOCAT")}, + {key: []byte("TACOBELL")}, + {key: []byte("DingHo-SmallPack")}, + {key: []byte("DingHo-StandardPack")}, + {key: []byte("BostonKitchen-CheeseSlice")}, + {key: []byte(`™£´´∂ƒ∂ƒßƒ©∑®ƒß∂†¬∆`)}, + } + + err = dbs.Wdb.Atomic(func(ctx context.Context, tx *sql.Tx) (err0 error) { + writer, err0 := sqlitedriver.MakeAccountsSQLWriter(tx, false, false, true, false) + defer writer.Close() + if err0 != nil { + return + } + for i := 0; i < len(kvPairDBPrepareSet); i++ { + err0 = writer.UpsertKvPair(string(kvPairDBPrepareSet[i].key), nil) + if err0 != nil { + return + } + } + return + }) + require.NoError(t, err) + + // +---------------------------------------------------------------------------+ + // | ^ Section 2 finishes above | + // | | + // | Section 3: Confirm that tracker DB has value being nil, not anything else | + // +---------------------------------------------------------------------------+ + + nilRowCounter := func() (nilRowCount int, err error) { + err = dbs.Wdb.Atomic(func(ctx context.Context, tx *sql.Tx) (err0 error) { + stmt, err0 := tx.PrepareContext(ctx, "SELECT key FROM kvstore WHERE value IS NULL;") + if err0 != nil { + return + } + rows, err0 := stmt.QueryContext(ctx) + if err0 != nil { + return + } + for rows.Next() { + var key sql.NullString + if err0 = rows.Scan(&key); err0 != nil { + return + } + if !key.Valid { + err0 = fmt.Errorf("scan from db get invalid key: %#v", key) + return + } + nilRowCount++ + } + return + }) + return + } + + nilRowCount, err := nilRowCounter() + require.NoError(t, err) + require.Equal(t, len(kvPairDBPrepareSet), nilRowCount) + + // +---------------------------------------------------------------------+ + // | ^ Section 3 finishes above | + // | | + // | Section 4: Run migration to see replace nils with empty byte slices | + // +---------------------------------------------------------------------+ + + trackerDBWrapper := sqlitedriver.CreateTrackerSQLStore(dbs) + err = trackerDBWrapper.Transaction(func(ctx context.Context, tx trackerdb.TransactionScope) (err0 error) { + _, err0 = tx.RunMigrations(ctx, trackerdb.Params{}, log, targetVersion) + return + }) + require.NoError(t, err) + + // +------------------------------------------------------------------------------------------------+ + // | ^ Section 4 finishes above | + // | | + // | After that, we can confirm the DB migration found all nil strings and executed the conversions | + // +------------------------------------------------------------------------------------------------+ + + nilRowCount, err = nilRowCounter() + require.NoError(t, err) + require.Equal(t, 0, nilRowCount) +} + // upsert updates existing or inserts a new entry func (a *compactResourcesDeltas) upsert(delta resourceDelta) { if idx, exist := a.cache[accountCreatable{address: delta.address, index: delta.oldResource.Aidx}]; exist { diff --git a/ledger/catchupaccessor.go b/ledger/catchupaccessor.go index 11b58ce962..ec33f2ac56 100644 --- a/ledger/catchupaccessor.go +++ b/ledger/catchupaccessor.go @@ -128,6 +128,31 @@ func (w *stagingWriterImpl) writeKVs(ctx context.Context, kvrs []encoded.KVRecor hashes := make([][]byte, len(kvrs)) for i := 0; i < len(kvrs); i++ { keys[i] = kvrs[i].Key + + // Since `encoded.KVRecordV6` is `omitempty` and `omitemptyarray`, + // when we have an instance of `encoded.KVRecordV6` with nil value, + // an empty box is unmarshalled to have `nil` value, + // while this might be mistaken to be a box deletion. + // + // We don't want to mistake this to be a deleted box: + // We are (and should be) during Fast Catchup (FC) + // writing to DB with empty byte string, rather than writing nil. + // + // This matters in sqlite3, + // for sqlite3 differs on writing nil byte slice to table from writing []byte{}: + // - writing nil byte slice is true that `value is NULL` + // - writing []byte{} is false on `value is NULL`. + // + // For the sake of consistency, we convert nil to []byte{}. + // + // Also, from a round by round catchup perspective, + // when we delete a box, in accountsNewRoundImpl method, + // the kv pair with value = nil will be deleted from kvstore table. + // Thus, it seems more consistent and appropriate to write as []byte{}. + + if kvrs[i].Value == nil { + kvrs[i].Value = []byte{} + } values[i] = kvrs[i].Value hashes[i] = trackerdb.KvHashBuilderV6(string(keys[i]), values[i]) } diff --git a/ledger/store/trackerdb/sqlitedriver/schema.go b/ledger/store/trackerdb/sqlitedriver/schema.go index 783077965d..e99e9c68e2 100644 --- a/ledger/store/trackerdb/sqlitedriver/schema.go +++ b/ledger/store/trackerdb/sqlitedriver/schema.go @@ -350,6 +350,12 @@ func accountsCreateBoxTable(ctx context.Context, tx *sql.Tx) error { return nil } +// performKVStoreNullBlobConversion scans keys with null blob value, and convert the value to `[]byte{}`. +func performKVStoreNullBlobConversion(ctx context.Context, tx *sql.Tx) error { + _, err := tx.ExecContext(ctx, "UPDATE kvstore SET value = '' WHERE value is NULL") + return err +} + func accountsCreateTxTailTable(ctx context.Context, tx *sql.Tx) (err error) { for _, stmt := range createTxTailTable { _, err = tx.ExecContext(ctx, stmt) diff --git a/ledger/store/trackerdb/sqlitedriver/testing.go b/ledger/store/trackerdb/sqlitedriver/testing.go index 28d4fb9cd9..8d0a61afd7 100644 --- a/ledger/store/trackerdb/sqlitedriver/testing.go +++ b/ledger/store/trackerdb/sqlitedriver/testing.go @@ -103,6 +103,9 @@ func AccountsInitTest(tb testing.TB, tx *sql.Tx, initAccounts map[basics.Address err = accountsCreateBoxTable(context.Background(), tx) require.NoError(tb, err) + err = performKVStoreNullBlobConversion(context.Background(), tx) + require.NoError(tb, err) + return newDB } diff --git a/ledger/store/trackerdb/sqlitedriver/trackerdbV2.go b/ledger/store/trackerdb/sqlitedriver/trackerdbV2.go index 268c97d8d8..1940e54a21 100644 --- a/ledger/store/trackerdb/sqlitedriver/trackerdbV2.go +++ b/ledger/store/trackerdb/sqlitedriver/trackerdbV2.go @@ -485,14 +485,20 @@ func (tu *trackerDBSchemaInitializer) upgradeDatabaseSchema8(ctx context.Context return tu.setVersion(ctx, tx, 9) } -// upgradeDatabaseSchema9 upgrades the database schema from version 8 to version 9, -// adding a new stateproofverification table. +// upgradeDatabaseSchema9 upgrades the database schema from version 9 to version 10, +// adding a new stateproofverification table, +// scrubbing out all nil values from kvstore table and replace with empty byte slice. func (tu *trackerDBSchemaInitializer) upgradeDatabaseSchema9(ctx context.Context, tx *sql.Tx) (err error) { err = createStateProofVerificationTable(ctx, tx) if err != nil { return err } + err = performKVStoreNullBlobConversion(ctx, tx) + if err != nil { + return fmt.Errorf("upgradeDatabaseSchema9 unable to replace kvstore nil entries with empty byte slices : %v", err) + } + // update version return tu.setVersion(ctx, tx, 10) } From 1d8e2a4f718b0e213103b9fb082309b46c7a923a Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Fri, 31 Mar 2023 12:48:34 -0400 Subject: [PATCH 024/125] tests: fix expect test regex and logger (#5244) --- .../cli/goal/expect/goalExpectCommon.exp | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/test/e2e-go/cli/goal/expect/goalExpectCommon.exp b/test/e2e-go/cli/goal/expect/goalExpectCommon.exp index d8ed3f4e4d..4728f445df 100644 --- a/test/e2e-go/cli/goal/expect/goalExpectCommon.exp +++ b/test/e2e-go/cli/goal/expect/goalExpectCommon.exp @@ -56,7 +56,7 @@ proc ::AlgorandGoal::Abort { ERROR } { if { $::tcl_platform(os) == "Darwin" } { exec >@stdout 2>@stderr top -l 1 } elseif { $::tcl_platform(os) == "Linux" } { - exec >@stdout 2>@stderr top -n 1 + exec >@stdout 2>@stderr top -b -n 1 } else { # no logging for other platforms } @@ -67,37 +67,37 @@ proc ::AlgorandGoal::Abort { ERROR } { set NODE_DATA_DIR $::GLOBAL_TEST_ROOT_DIR/Primary if { [file exists $NODE_DATA_DIR] } { set outLog [exec cat $NODE_DATA_DIR/algod-out.log] - puts "\n$NODE_DATA_DIR/algod-out.log:\r\n$outLog" + puts "\n$NODE_DATA_DIR/algod-out.log:\n$outLog" set errLog [exec cat $NODE_DATA_DIR/algod-err.log] - puts "\n$NODE_DATA_DIR/algod-err.log:\r\n$errLog" + puts "\n$NODE_DATA_DIR/algod-err.log:\n$errLog" set nodeLog [exec -- tail -n 50 $NODE_DATA_DIR/node.log] - puts "\n$NODE_DATA_DIR/node.log:\r\n$nodeLog" + puts "\n$NODE_DATA_DIR/node.log:\n$nodeLog" set LOGS_COLLECTED 1 set outLog [exec cat $NODE_DATA_DIR/kmd-v0.5/kmd-out.log] - puts "\n$NODE_DATA_DIR/kmd-v0.5/kmd-out.log:\r\n$outLog" + puts "\n$NODE_DATA_DIR/kmd-v0.5/kmd-out.log:\n$outLog" set errLog [exec cat $NODE_DATA_DIR/kmd-v0.5/kmd-err.log] - puts "\n$NODE_DATA_DIR/kmd-v0.5/kmd-err.log:\r\n$errLog" + puts "\n$NODE_DATA_DIR/kmd-v0.5/kmd-err.log:\n$errLog" set kmdLog [exec -- tail -n 50 $NODE_DATA_DIR/kmd-v0.5/kmd.log] - puts "\n$NODE_DATA_DIR/kmd-v0.5/kmd.log:\r\n$kmdLog" + puts "\n$NODE_DATA_DIR/kmd-v0.5/kmd.log:\n$kmdLog" } set NODE_DATA_DIR $::GLOBAL_TEST_ROOT_DIR/Node puts "Node path $NODE_DATA_DIR" if { [file exists $NODE_DATA_DIR] } { set outLog [exec cat $NODE_DATA_DIR/algod-out.log] - puts "\n$NODE_DATA_DIR/algod-out.log:\r\n$outLog" + puts "\n$NODE_DATA_DIR/algod-out.log:\n$outLog" set errLog [exec cat $NODE_DATA_DIR/algod-err.log] - puts "\n$NODE_DATA_DIR/algod-err.log:\r\n$errLog" + puts "\n$NODE_DATA_DIR/algod-err.log:\n$errLog" set nodeLog [exec -- tail -n 50 $NODE_DATA_DIR/node.log] - puts "\n$NODE_DATA_DIR/node.log:\r\n$nodeLog" + puts "\n$NODE_DATA_DIR/node.log:\n$nodeLog" set LOGS_COLLECTED 1 set outLog [exec cat $NODE_DATA_DIR/kmd-v0.5/kmd-out.log] - puts "\n$NODE_DATA_DIR/kmd-v0.5/kmd-out.log:\r\n$outLog" + puts "\n$NODE_DATA_DIR/kmd-v0.5/kmd-out.log:\n$outLog" set errLog [exec cat $NODE_DATA_DIR/kmd-v0.5/kmd-err.log] - puts "\n$NODE_DATA_DIR/kmd-v0.5/kmd-err.log:\r\n$errLog" + puts "\n$NODE_DATA_DIR/kmd-v0.5/kmd-err.log:\n$errLog" set kmdLog [exec -- tail -n 50 $NODE_DATA_DIR/kmd-v0.5/kmd.log] - puts "\n$NODE_DATA_DIR/kmd-v0.5/kmd.log:\r\n$kmdLog" + puts "\n$NODE_DATA_DIR/kmd-v0.5/kmd.log:\n$kmdLog" } } @@ -109,18 +109,18 @@ proc ::AlgorandGoal::Abort { ERROR } { if { $LOGS_COLLECTED == 0 } { log_user 1 set outLog [exec cat $::GLOBAL_TEST_ALGO_DIR/algod-out.log] - puts "\n$::GLOBAL_TEST_ALGO_DIR/algod-out.log:\r\n$outLog" + puts "\n$::GLOBAL_TEST_ALGO_DIR/algod-out.log:\n$outLog" set errLog [exec cat $::GLOBAL_TEST_ALGO_DIR/algod-err.log] - puts "\n$::GLOBAL_TEST_ALGO_DIR/algod-err.log:\r\n$errLog" + puts "\n$::GLOBAL_TEST_ALGO_DIR/algod-err.log:\n$errLog" set nodeLog [exec -- tail -n 50 $::GLOBAL_TEST_ALGO_DIR/node.log] - puts "\n$::GLOBAL_TEST_ALGO_DIR/node.log:\r\n$nodeLog" + puts "\n$::GLOBAL_TEST_ALGO_DIR/node.log:\n$nodeLog" set outLog [exec cat $GLOBAL_TEST_ALGO_DIR/kmd-v0.5/kmd-out.log] - puts "\n$GLOBAL_TEST_ALGO_DIR/kmd-v0.5/kmd-out.log:\r\n$outLog" + puts "\n$GLOBAL_TEST_ALGO_DIR/kmd-v0.5/kmd-out.log:\n$outLog" set errLog [exec cat $GLOBAL_TEST_ALGO_DIR/kmd-v0.5/kmd-err.log] - puts "\n$GLOBAL_TEST_ALGO_DIR/kmd-v0.5/kmd-err.log:\r\n$errLog" + puts "\n$GLOBAL_TEST_ALGO_DIR/kmd-v0.5/kmd-err.log:\n$errLog" set kmdLog [exec -- tail -n 50 $GLOBAL_TEST_ALGO_DIR/kmd-v0.5/kmd.log] - puts "\n$GLOBAL_TEST_ALGO_DIR/kmd-v0.5/kmd.log:\r\n$kmdLog" + puts "\n$GLOBAL_TEST_ALGO_DIR/kmd-v0.5/kmd.log:\n$kmdLog" } } @@ -859,7 +859,7 @@ proc ::AlgorandGoal::VerifyMultisigInfoForOneOfTwoMultisig { MULTISIG_ADDRESS AD spawn goal account multisig info --address $MULTISIG_ADDRESS -d $TEST_PRIMARY_NODE_DIR -w $WALLET_NAME expect { timeout { ::AlgorandGoal::Abort "Timed out querying info about multisig account $MULTISIG_ADDRESS" } - -re {Version: (\d+)\r\nThreshold: (\d+)\r\nPublic keys:\r\n ([a-zA-Z0-9]+)\r\n ([a-zA-Z0-9]+)\r\n} { + -re {Version: (\d+)\s+Threshold: (\d+)\s+Public keys:\s+([a-zA-Z0-9]+)\s+([a-zA-Z0-9]+)\s+} { set VERSION $expect_out(1,string); set THRESHOLD $expect_out(2,string); set ADDRESS_RESPONSE_1 $expect_out(3,string); From d27b674ccc3daac47a4abf97a0a589de947ce23a Mon Sep 17 00:00:00 2001 From: shiqizng <80276844+shiqizng@users.noreply.github.com> Date: Fri, 31 Mar 2023 16:17:42 -0400 Subject: [PATCH 025/125] tools: block generator (#5245) --- .gitignore | 3 + tools/block-generator/README.md | 115 +++ tools/block-generator/core/commands.go | 35 + tools/block-generator/generator/daemon.go | 52 ++ tools/block-generator/generator/generate.go | 765 ++++++++++++++++++ .../generator/generate_test.go | 216 +++++ .../generator/make_transactions.go | 97 +++ tools/block-generator/generator/server.go | 174 ++++ .../block-generator/generator/server_test.go | 108 +++ tools/block-generator/generator/utils.go | 45 ++ tools/block-generator/generator/utils_test.go | 111 +++ tools/block-generator/main.go | 26 + tools/block-generator/metrics/metrics.go | 42 + tools/block-generator/run_generator.sh | 56 ++ tools/block-generator/run_postgres.sh | 49 ++ tools/block-generator/run_runner.sh | 54 ++ tools/block-generator/run_tests.sh | 98 +++ .../runner/metrics_collector.go | 88 ++ tools/block-generator/runner/run.go | 350 ++++++++ tools/block-generator/runner/runner.go | 61 ++ .../scenarios/config.asset.close.yml | 16 + .../scenarios/config.asset.destroy.yml | 16 + .../scenarios/config.asset.xfer.yml | 15 + .../scenarios/config.mixed.jumbo.yml | 19 + .../scenarios/config.mixed.yml | 19 + .../scenarios/config.payment.full.yml | 14 + .../scenarios/config.payment.jumbo.yml | 14 + .../scenarios/config.payment.small.yml | 14 + tools/block-generator/test_config.yml | 23 + tools/block-generator/util/util.go | 32 + 30 files changed, 2727 insertions(+) create mode 100644 tools/block-generator/README.md create mode 100644 tools/block-generator/core/commands.go create mode 100644 tools/block-generator/generator/daemon.go create mode 100644 tools/block-generator/generator/generate.go create mode 100644 tools/block-generator/generator/generate_test.go create mode 100644 tools/block-generator/generator/make_transactions.go create mode 100644 tools/block-generator/generator/server.go create mode 100644 tools/block-generator/generator/server_test.go create mode 100644 tools/block-generator/generator/utils.go create mode 100644 tools/block-generator/generator/utils_test.go create mode 100644 tools/block-generator/main.go create mode 100644 tools/block-generator/metrics/metrics.go create mode 100755 tools/block-generator/run_generator.sh create mode 100755 tools/block-generator/run_postgres.sh create mode 100755 tools/block-generator/run_runner.sh create mode 100755 tools/block-generator/run_tests.sh create mode 100644 tools/block-generator/runner/metrics_collector.go create mode 100644 tools/block-generator/runner/run.go create mode 100644 tools/block-generator/runner/runner.go create mode 100644 tools/block-generator/scenarios/config.asset.close.yml create mode 100644 tools/block-generator/scenarios/config.asset.destroy.yml create mode 100644 tools/block-generator/scenarios/config.asset.xfer.yml create mode 100644 tools/block-generator/scenarios/config.mixed.jumbo.yml create mode 100644 tools/block-generator/scenarios/config.mixed.yml create mode 100644 tools/block-generator/scenarios/config.payment.full.yml create mode 100644 tools/block-generator/scenarios/config.payment.jumbo.yml create mode 100644 tools/block-generator/scenarios/config.payment.small.yml create mode 100644 tools/block-generator/test_config.yml create mode 100644 tools/block-generator/util/util.go diff --git a/.gitignore b/.gitignore index 9781154449..39abf02ee5 100644 --- a/.gitignore +++ b/.gitignore @@ -68,3 +68,6 @@ index.html # test summary testresults.json + +# block generator binary +tools/block-generator/block-generator diff --git a/tools/block-generator/README.md b/tools/block-generator/README.md new file mode 100644 index 0000000000..5caff65225 --- /dev/null +++ b/tools/block-generator/README.md @@ -0,0 +1,115 @@ +# Block Generator + +This tool is used for testing Indexer import performance. It does this by generating synthetic blocks which are sent by mocking the Algod REST API endpoints that Indexer uses. + +## Scenario Configuration + +Block generator uses a YAML config file to describe the composition of each randomly generated block. There are three levels of configuration: +1. Setup +2. Transaction type distribution +3. Transaction type specific configuration + +At the time of writing, the block generator supports **payment** and **asset** transactions. The settings are hopefully, more or less, obvious. Distributions are specified as fractions of one, and the sum of all options must add up to one. + +Here is an example which uses all of the current options. Notice that the synthetic blocks are not required to follow algod limits, in this case the block size is specified as 19999, or four times larger than the current block size limit: +``` +name: "Mixed (jumbo)" +genesis_accounts: 10000 +genesis_account_balance: 1000000000000 +tx_per_block: 19999 + +# transaction distribution +tx_pay_fraction: 0.3 +tx_asset_fraction: 0.7 + +# payment config +pay_acct_create_fraction: 0.02 +pay_xfer_fraction: 0.98 + +# asset config +asset_create_fraction: 0.001 +asset_optin_fraction: 0.1 +asset_close_fraction: 0.05 +asset_xfer_fraction: 0.849 +asset_delete_fraction: 0 +``` + +## Modes + +The block generator can run in one of two modes, a standalone **daemon**, or a test suite **runner** + +### daemon + +In standalone mode, a block-generator process starts and exposes the mock algod endpoints for **/genesis** and **/v2/blocks/{block}**. If you choose to query them manually, it only supports fetching blocks sequentially. This is due to the fact that it generates a pseudorandom stream of transactions and after each random transaction the state increments to the next. + +Here is the help output: +```bash +~$ ./block-generator daemon -h +Start the generator daemon in standalone mode. + +Usage: + block-generator daemon [flags] + +Flags: + -c, --config string Specify the block configuration yaml file. + -h, --help help for daemon + -p, --port uint Port to start the server at. (default 4010) +``` + +### runner + +For our usage, we want to run the same set of tests consistently across many scenarios and with many different releases. The runner mode automates this process by starting the **daemon** with many different configurations, managing a postgres database, and running a separate indexer process configured to use them. + +The results of the testing are written to the directory specified by the **--report-directory** option, and include many different metrics. In addition to the report, the indexer log is written to this directory. The files are named according to the scenario file, and end in "report" or "log". + +Here is an example report from running with a test duration of "1h": +``` +test_duration_seconds:3600 +test_duration_actual_seconds:3600.056457 +transaction_pay_total:30024226 +transaction_pay_create_total:614242 +early_average_import_time_sec:2.13 +early_cumulative_import_time_sec:1083.26 +early_average_imported_tx_per_block:19999.00 +early_cumulative_imported_tx_per_block:10179491 +early_average_block_upload_time_sec:NaN +early_cumulative_block_upload_time_sec:0.00 +early_average_postgres_eval_time_sec:0.33 +early_cumulative_postgres_eval_time_sec:167.41 +early_imported_round:509 +early_overall_transactions_per_second:9397.09 +early_uptime_seconds:3600.06 +final_average_import_time_sec:2.35 +final_cumulative_import_time_sec:3602.62 +final_average_imported_tx_per_block:19999.00 +final_cumulative_imported_tx_per_block:30598470 +final_average_block_upload_time_sec:NaN +final_cumulative_block_upload_time_sec:0.00 +final_average_postgres_eval_time_sec:0.33 +final_cumulative_postgres_eval_time_sec:507.38 +final_imported_round:1530 +final_overall_transactions_per_second:8493.40 +final_uptime_seconds:3600.06 +``` + +Here is the help output: +```bash +~$ ./block-generator runner -h +Run test suite and collect results. + +Usage: + block-generator runner [flags] + +Flags: + --cpuprofile string Path where Indexer writes its CPU profile. + -h, --help help for runner + -i, --indexer-binary string Path to indexer binary. + -p, --indexer-port uint Port to start the server at. This is useful if you have a prometheus server for collecting additional data. (default 4010) + -l, --log-level string LogLevel to use when starting Indexer. [error, warn, info, debug, trace] (default "error") + -c, --postgres-connection-string string Postgres connection string. + -r, --report-directory string Location to place test reports. + --reset If set any existing report directory will be deleted before running tests. + -s, --scenario string Directory containing scenarios, or specific scenario file. + -d, --test-duration duration Duration to use for each scenario. (default 5m0s) + --validate If set the validator will run after test-duration has elapsed to verify data is correct. An extra line in each report indicates validator success or failure. +``` diff --git a/tools/block-generator/core/commands.go b/tools/block-generator/core/commands.go new file mode 100644 index 0000000000..af64c8a5d7 --- /dev/null +++ b/tools/block-generator/core/commands.go @@ -0,0 +1,35 @@ +// Copyright (C) 2019-2023 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package core + +import ( + "github.com/algorand/go-algorand/tools/block-generator/generator" + "github.com/algorand/go-algorand/tools/block-generator/runner" + "github.com/spf13/cobra" +) + +// BlockGenerator related cobra commands, ready to be executed or included as subcommands. +var BlockGenerator *cobra.Command + +func init() { + BlockGenerator = &cobra.Command{ + Use: `block-generator`, + Short: `Block generator testing tools.`, + } + BlockGenerator.AddCommand(runner.RunnerCmd) + BlockGenerator.AddCommand(generator.DaemonCmd) +} diff --git a/tools/block-generator/generator/daemon.go b/tools/block-generator/generator/daemon.go new file mode 100644 index 0000000000..36c49cac51 --- /dev/null +++ b/tools/block-generator/generator/daemon.go @@ -0,0 +1,52 @@ +// Copyright (C) 2019-2023 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package generator + +import ( + "fmt" + "math/rand" + + "github.com/spf13/cobra" +) + +// DaemonCmd starts a block generator daemon. +var DaemonCmd *cobra.Command + +func init() { + rand.Seed(12345) + + var configFile string + var port uint64 + + DaemonCmd = &cobra.Command{ + Use: "daemon", + Short: "Start the generator daemon in standalone mode.", + Run: func(cmd *cobra.Command, args []string) { + addr := fmt.Sprintf(":%d", port) + srv, _ := MakeServer(configFile, addr) + err := srv.ListenAndServe() + if err != nil { + panic(err) + } + }, + } + + DaemonCmd.Flags().StringVarP(&configFile, "config", "c", "", "Specify the block configuration yaml file.") + DaemonCmd.Flags().Uint64VarP(&port, "port", "p", 4010, "Port to start the server at.") + + DaemonCmd.MarkFlagRequired("config") +} diff --git a/tools/block-generator/generator/generate.go b/tools/block-generator/generator/generate.go new file mode 100644 index 0000000000..41fad8736e --- /dev/null +++ b/tools/block-generator/generator/generate.go @@ -0,0 +1,765 @@ +// Copyright (C) 2019-2023 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package generator + +import ( + "encoding/binary" + "encoding/json" + "fmt" + "io" + "math/rand" + "os" + "time" + + cconfig "github.com/algorand/go-algorand/config" + "github.com/algorand/go-algorand/protocol" + + "github.com/algorand/go-algorand/agreement" + "github.com/algorand/go-algorand/crypto" + "github.com/algorand/go-algorand/daemon/algod/api/server/v2/generated/model" + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/data/bookkeeping" + "github.com/algorand/go-algorand/data/committee" + "github.com/algorand/go-algorand/data/transactions" + "github.com/algorand/go-algorand/rpcs" +) + +// TxTypeID is the transaction type. +type TxTypeID string + +const ( + genesis TxTypeID = "genesis" + + // Payment Tx IDs + paymentTx TxTypeID = "pay" + paymentAcctCreateTx TxTypeID = "pay_create" + assetTx TxTypeID = "asset" + //keyRegistrationTx TxTypeID = "keyreg" + //applicationCallTx TxTypeID = "appl" + + // Asset Tx IDs + assetCreate TxTypeID = "asset_create" + assetOptin TxTypeID = "asset_optin" + assetXfer TxTypeID = "asset_xfer" + assetClose TxTypeID = "asset_close" + assetDestroy TxTypeID = "asset_destroy" + + assetTotal = uint64(100000000000000000) + + consensusTimeMilli int64 = 4500 +) + +// GenerationConfig defines the tunable parameters for block generation. +type GenerationConfig struct { + Name string `yaml:"name"` + NumGenesisAccounts uint64 `yaml:"genesis_accounts"` + GenesisAccountInitialBalance uint64 `yaml:"genesis_account_balance"` + + // Block generation + TxnPerBlock uint64 `mapstructure:"tx_per_block"` + + // TX Distribution + PaymentTransactionFraction float32 `yaml:"tx_pay_fraction"` + AssetTransactionFraction float32 `yaml:"tx_asset_fraction"` + + // Payment configuration + PaymentNewAccountFraction float32 `yaml:"pay_acct_create_fraction"` + PaymentFraction float32 `yaml:"pay_xfer_fraction"` + + // Asset configuration + AssetCreateFraction float32 `yaml:"asset_create_fraction"` + AssetDestroyFraction float32 `yaml:"asset_destroy_fraction"` + AssetOptinFraction float32 `yaml:"asset_optin_fraction"` + AssetCloseFraction float32 `yaml:"asset_close_fraction"` + AssetXferFraction float32 `yaml:"asset_xfer_fraction"` +} + +func sumIsCloseToOne(numbers ...float32) bool { + var sum float32 + for _, num := range numbers { + sum += num + } + return sum > 0.99 && sum < 1.01 +} + +// MakeGenerator initializes the Generator object. +func MakeGenerator(config GenerationConfig) (Generator, error) { + if !sumIsCloseToOne(config.PaymentTransactionFraction, config.AssetTransactionFraction) { + return nil, fmt.Errorf("transaction distribution ratios should equal 1") + } + + if !sumIsCloseToOne(config.PaymentNewAccountFraction, config.PaymentFraction) { + return nil, fmt.Errorf("payment configuration ratios should equal 1") + } + + if !sumIsCloseToOne(config.AssetCreateFraction, config.AssetDestroyFraction, config.AssetOptinFraction, config.AssetCloseFraction, config.AssetXferFraction) { + return nil, fmt.Errorf("asset configuration ratios should equal 1") + } + + var proto protocol.ConsensusVersion = "future" + gen := &generator{ + config: config, + protocol: proto, + params: cconfig.Consensus[proto], + genesisHash: [32]byte{}, + genesisID: "blockgen-test", + prevBlockHash: "", + round: 1, + txnCounter: 0, + timestamp: 0, + rewardsLevel: 0, + rewardsResidue: 0, + rewardsRate: 0, + rewardsRecalculationRound: 0, + reportData: make(map[TxTypeID]TxData), + } + + gen.feeSink[31] = 1 + gen.rewardsPool[31] = 2 + gen.genesisHash[31] = 3 + + gen.initializeAccounting() + + for _, val := range getTransactionOptions() { + switch val { + case paymentTx: + gen.transactionWeights = append(gen.transactionWeights, config.PaymentTransactionFraction) + case assetTx: + gen.transactionWeights = append(gen.transactionWeights, config.AssetTransactionFraction) + } + } + + for _, val := range getPaymentTxOptions() { + switch val { + case paymentTx: + gen.payTxWeights = append(gen.payTxWeights, config.PaymentFraction) + case paymentAcctCreateTx: + gen.payTxWeights = append(gen.payTxWeights, config.PaymentNewAccountFraction) + } + } + + for _, val := range getAssetTxOptions() { + switch val { + case assetCreate: + gen.assetTxWeights = append(gen.assetTxWeights, config.AssetCreateFraction) + case assetDestroy: + gen.assetTxWeights = append(gen.assetTxWeights, config.AssetDestroyFraction) + case assetOptin: + gen.assetTxWeights = append(gen.assetTxWeights, config.AssetOptinFraction) + case assetXfer: + gen.assetTxWeights = append(gen.assetTxWeights, config.AssetXferFraction) + case assetClose: + gen.assetTxWeights = append(gen.assetTxWeights, config.AssetCloseFraction) + } + } + + return gen, nil +} + +// Generator is the interface needed to generate blocks. +type Generator interface { + WriteReport(output io.Writer) error + WriteGenesis(output io.Writer) error + WriteBlock(output io.Writer, round uint64) error + WriteAccount(output io.Writer, accountString string) error + WriteStatus(output io.Writer) error + Accounts() <-chan basics.Address +} + +type generator struct { + config GenerationConfig + + // payment transaction metadata + numPayments uint64 + + // Number of algorand accounts + numAccounts uint64 + + // Block stuff + round uint64 + txnCounter uint64 + prevBlockHash string + timestamp int64 + protocol protocol.ConsensusVersion + params cconfig.ConsensusParams + genesisID string + genesisHash crypto.Digest + + // Rewards stuff + feeSink basics.Address + rewardsPool basics.Address + rewardsLevel uint64 + rewardsResidue uint64 + rewardsRate uint64 + rewardsRecalculationRound uint64 + + // balances for all accounts. To avoid crypto and reduce storage, accounts are faked. + // The account is based on the index into the balances array. + balances []uint64 + + // assets is a minimal representation of the asset holdings, it doesn't + // include the frozen state. + assets []*assetData + // pendingAssets is used to hold newly created assets so that they are not used before + // being created. + pendingAssets []*assetData + + transactionWeights []float32 + payTxWeights []float32 + assetTxWeights []float32 + + // Reporting information from transaction type to data + reportData Report +} + +type assetData struct { + assetID uint64 + creator uint64 + name string + // Holding at index 0 is the creator. + holdings []*assetHolding + // Set of holders in the holdings array for easy reference. + holders map[uint64]*assetHolding +} + +type assetHolding struct { + acctIndex uint64 + balance uint64 +} + +// Report is the generation report. +type Report map[TxTypeID]TxData + +// TxData is the generator report data. +type TxData struct { + GenerationTime time.Duration `json:"generation_time_milli"` + GenerationCount uint64 `json:"num_generated"` +} + +func track(id TxTypeID) (TxTypeID, time.Time) { + return id, time.Now() +} +func (g *generator) recordData(id TxTypeID, start time.Time) { + data := g.reportData[id] + data.GenerationCount++ + data.GenerationTime += time.Since(start) + g.reportData[id] = data +} + +func (g *generator) WriteReport(output io.Writer) error { + return json.NewEncoder(output).Encode(g.reportData) +} + +func (g *generator) WriteStatus(output io.Writer) error { + response := model.NodeStatusResponse{ + LastRound: g.round, + } + return json.NewEncoder(output).Encode(response) +} + +func (g *generator) WriteGenesis(output io.Writer) error { + defer g.recordData(track(genesis)) + var allocations []bookkeeping.GenesisAllocation + + for i := uint64(0); i < g.config.NumGenesisAccounts; i++ { + addr := indexToAccount(i) + allocations = append(allocations, bookkeeping.GenesisAllocation{ + Address: addr.String(), + State: basics.AccountData{ + MicroAlgos: basics.MicroAlgos{Raw: g.config.GenesisAccountInitialBalance}, + }, + }) + } + // Also add the rewards pool account with minimum balance. Without it, the evaluator + // crashes. + allocations = append(allocations, bookkeeping.GenesisAllocation{ + Address: g.rewardsPool.String(), + Comment: "RewardsPool", + State: basics.AccountData{ + MicroAlgos: basics.MicroAlgos{Raw: g.params.MinBalance}, + Status: basics.NotParticipating, + }, + }) + + gen := bookkeeping.Genesis{ + SchemaID: "v1", + Network: "generated-network", + Proto: g.protocol, + Allocation: allocations, + RewardsPool: g.rewardsPool.String(), + FeeSink: g.feeSink.String(), + Timestamp: g.timestamp, + } + return json.NewEncoder(output).Encode(gen) +} + +func getTransactionOptions() []interface{} { + return []interface{}{paymentTx, assetTx} +} + +func (g *generator) generateTransaction(round uint64, intra uint64) (transactions.SignedTxn, transactions.ApplyData, error) { + selection, err := weightedSelection(g.transactionWeights, getTransactionOptions(), paymentTx) + if err != nil { + return transactions.SignedTxn{}, transactions.ApplyData{}, err + } + + switch selection { + case paymentTx: + return g.generatePaymentTxn(round, intra) + case assetTx: + return g.generateAssetTxn(round, intra) + default: + return transactions.SignedTxn{}, transactions.ApplyData{}, fmt.Errorf("no generator available for %s", selection) + } +} + +func (g *generator) txnForRound(round uint64) uint64 { + // There are no transactions in the 0th round + if round == 0 { + return 0 + } + return g.config.TxnPerBlock +} + +// finishRound tells the generator it can apply any pending state. +func (g *generator) finishRound(txnCount uint64) { + g.txnCounter += txnCount + + g.timestamp += consensusTimeMilli + g.round++ + + // Apply pending assets... + g.assets = append(g.assets, g.pendingAssets...) + g.pendingAssets = nil +} + +// WriteBlock generates a block full of new transactions and writes it to the writer. +func (g *generator) WriteBlock(output io.Writer, round uint64) error { + if round != g.round { + fmt.Printf("Generator only supports sequential block access. Expected %d but received request for %d.\n", g.round, round) + } + + numTxnForBlock := g.txnForRound(round) + + header := bookkeeping.BlockHeader{ + Round: basics.Round(g.round), + Branch: bookkeeping.BlockHash{}, + Seed: committee.Seed{}, + TxnCommitments: bookkeeping.TxnCommitments{NativeSha512_256Commitment: crypto.Digest{}}, + TimeStamp: g.timestamp, + GenesisID: g.genesisID, + GenesisHash: g.genesisHash, + RewardsState: bookkeeping.RewardsState{ + FeeSink: g.feeSink, + RewardsPool: g.rewardsPool, + RewardsLevel: 0, + RewardsRate: 0, + RewardsResidue: 0, + RewardsRecalculationRound: 0, + }, + UpgradeState: bookkeeping.UpgradeState{ + CurrentProtocol: g.protocol, + }, + UpgradeVote: bookkeeping.UpgradeVote{}, + TxnCounter: g.txnCounter + numTxnForBlock, + StateProofTracking: nil, + } + + // Generate the transactions + transactions := make([]transactions.SignedTxnInBlock, 0, numTxnForBlock) + + for i := uint64(0); i < numTxnForBlock; i++ { + txn, ad, err := g.generateTransaction(g.round, i) + if err != nil { + panic(fmt.Sprintf("failed to generate transaction: %v\n", err)) + } + stib, err := header.EncodeSignedTxn(txn, ad) + if err != nil { + panic(fmt.Sprintf("failed to encode transaction: %v\n", err)) + } + transactions = append(transactions, stib) + } + + if numTxnForBlock != uint64(len(transactions)) { + panic("Unexpected number of transactions.") + } + + cert := rpcs.EncodedBlockCert{ + Block: bookkeeping.Block{ + BlockHeader: header, + Payset: transactions, + }, + Certificate: agreement.Certificate{}, + } + + err := json.NewEncoder(output).Encode(cert) + if err != nil { + return err + } + + g.finishRound(numTxnForBlock) + return nil +} + +func indexToAccount(i uint64) (addr basics.Address) { + // Make sure we don't generate a zero address by adding 1 to i + binary.LittleEndian.PutUint64(addr[:], i+1) + return +} + +func accountToIndex(a basics.Address) (addr uint64) { + // Make sure we don't generate a zero address by adding 1 to i + return binary.LittleEndian.Uint64(a[:]) - 1 +} + +// initializeAccounting creates the genesis accounts. +func (g *generator) initializeAccounting() { + if g.config.NumGenesisAccounts == 0 { + panic("Number of genesis accounts must be > 0.") + } + + g.numPayments = 0 + g.numAccounts = g.config.NumGenesisAccounts + for i := uint64(0); i < g.config.NumGenesisAccounts; i++ { + g.balances = append(g.balances, g.config.GenesisAccountInitialBalance) + } +} + +func signTxn(txn transactions.Transaction) transactions.SignedTxn { + stxn := transactions.SignedTxn{ + Sig: crypto.Signature{}, + Msig: crypto.MultisigSig{}, + Lsig: transactions.LogicSig{}, + Txn: txn, + AuthAddr: basics.Address{}, + } + + // TODO: Would it be useful to generate a random signature? + stxn.Sig[32] = 50 + + return stxn +} + +func getPaymentTxOptions() []interface{} { + return []interface{}{paymentTx, paymentAcctCreateTx} +} + +// generatePaymentTxn creates a new payment transaction. The sender is always a genesis account, the receiver is random, +// or a new account. +func (g *generator) generatePaymentTxn(round uint64, intra uint64) (transactions.SignedTxn, transactions.ApplyData, error) { + selection, err := weightedSelection(g.payTxWeights, getPaymentTxOptions(), paymentTx) + if err != nil { + return transactions.SignedTxn{}, transactions.ApplyData{}, err + } + return g.generatePaymentTxnInternal(selection.(TxTypeID), round, intra) +} + +func (g *generator) generatePaymentTxnInternal(selection TxTypeID, round uint64, intra uint64) (transactions.SignedTxn, transactions.ApplyData, error) { + defer g.recordData(track(selection)) + minBal := g.params.MinBalance + + // default amount + amount := uint64(1) + + // Select a receiver + var receiveIndex uint64 + switch selection { + case paymentTx: + receiveIndex = rand.Uint64() % g.numAccounts + case paymentAcctCreateTx: + // give new accounts get extra algos for sending other transactions + amount = minBal * 100 + g.balances = append(g.balances, 0) + receiveIndex = g.numAccounts + g.numAccounts++ + } + total := amount + g.params.MinTxnFee + + // Select a sender from genesis account + sendIndex := g.numPayments % g.config.NumGenesisAccounts + if g.balances[sendIndex] < (total + minBal) { + fmt.Printf("\n\ngeneratePaymentTxnInternal(): the sender account does not have enough algos for the transfer. idx %d, payment number %d\n\n", sendIndex, g.numPayments) + os.Exit(1) + } + + sender := indexToAccount(sendIndex) + receiver := indexToAccount(receiveIndex) + + g.balances[sendIndex] -= total + g.balances[receiveIndex] += amount + + g.numPayments++ + + txn := g.makePaymentTxn(g.makeTxnHeader(sender, round, intra), receiver, amount, basics.Address{}) + return signTxn(txn), transactions.ApplyData{}, nil +} + +func getAssetTxOptions() []interface{} { + return []interface{}{assetCreate, assetDestroy, assetOptin, assetXfer, assetClose} +} + +func (g *generator) generateAssetTxnInternal(txType TxTypeID, round uint64, intra uint64) (actual TxTypeID, txn transactions.Transaction) { + return g.generateAssetTxnInternalHint(txType, round, intra, 0, nil) +} + +func (g *generator) generateAssetTxnInternalHint(txType TxTypeID, round uint64, intra uint64, hintIndex uint64, hint *assetData) (actual TxTypeID, txn transactions.Transaction) { + actual = txType + // If there are no assets the next operation needs to be a create. + if len(g.assets) == 0 { + actual = assetCreate + } + + numAssets := uint64(len(g.assets)) + var senderIndex uint64 + + if actual == assetCreate { + numAssets = uint64(len(g.assets)) + uint64(len(g.pendingAssets)) + senderIndex = numAssets % g.config.NumGenesisAccounts + senderAcct := indexToAccount(senderIndex) + + total := assetTotal + assetID := g.txnCounter + intra + 1 + assetName := fmt.Sprintf("asset #%d", assetID) + txn = g.makeAssetCreateTxn(g.makeTxnHeader(senderAcct, round, intra), total, false, assetName) + + // Compute asset ID and initialize holdings + holding := assetHolding{ + acctIndex: senderIndex, + balance: total, + } + a := assetData{ + name: assetName, + assetID: assetID, + creator: senderIndex, + holdings: []*assetHolding{&holding}, + holders: map[uint64]*assetHolding{senderIndex: &holding}, + } + + g.pendingAssets = append(g.pendingAssets, &a) + } else { + assetIndex := rand.Uint64() % numAssets + asset := g.assets[assetIndex] + if hint != nil { + assetIndex = hintIndex + asset = hint + } + + switch actual { + case assetDestroy: + // delete asset + + // If the creator doesn't have all of them, close instead + if asset.holdings[0].balance != assetTotal { + return g.generateAssetTxnInternalHint(assetClose, round, intra, assetIndex, asset) + } + + senderIndex = asset.creator + creator := indexToAccount(senderIndex) + txn = g.makeAssetDestroyTxn(g.makeTxnHeader(creator, round, intra), asset.assetID) + + // Remove asset by moving the last element to the deleted index then trimming the slice. + g.assets[assetIndex] = g.assets[numAssets-1] + g.assets = g.assets[:numAssets-1] + case assetOptin: + // select a random account from asset to optin + + // If every account holds the asset, close instead of optin + if uint64(len(asset.holdings)) == g.numAccounts { + return g.generateAssetTxnInternalHint(assetClose, round, intra, assetIndex, asset) + } + + // look for an account that does not hold the asset + exists := true + for exists { + senderIndex = rand.Uint64() % g.numAccounts + exists = asset.holders[senderIndex] != nil + } + account := indexToAccount(senderIndex) + txn = g.makeAssetAcceptanceTxn(g.makeTxnHeader(account, round, intra), asset.assetID) + + holding := assetHolding{ + acctIndex: senderIndex, + balance: 0, + } + asset.holdings = append(asset.holdings, &holding) + asset.holders[senderIndex] = &holding + case assetXfer: + // send from creator (holder[0]) to another random holder (same address is valid) + + // If there aren't enough assets to close one, optin an account instead + if len(asset.holdings) == 1 { + return g.generateAssetTxnInternalHint(assetOptin, round, intra, assetIndex, asset) + } + + senderIndex = asset.holdings[0].acctIndex + sender := indexToAccount(senderIndex) + + receiverArrayIndex := (rand.Uint64() % (uint64(len(asset.holdings)) - uint64(1))) + uint64(1) + receiver := indexToAccount(asset.holdings[receiverArrayIndex].acctIndex) + + amount := uint64(10) + + txn = g.makeAssetTransferTxn(g.makeTxnHeader(sender, round, intra), receiver, amount, basics.Address{}, asset.assetID) + + if asset.holdings[0].balance < amount { + fmt.Printf("\n\ncreator doesn't have enough funds for asset %d\n\n", asset.assetID) + os.Exit(1) + } + if g.balances[asset.holdings[0].acctIndex] < g.params.MinTxnFee { + fmt.Printf("\n\ncreator doesn't have enough funds for transaction %d\n\n", asset.assetID) + os.Exit(1) + } + + asset.holdings[0].balance -= amount + asset.holdings[receiverArrayIndex].balance += amount + case assetClose: + // select a holder of a random asset to close out + // If there aren't enough assets to close one, optin an account instead + if len(asset.holdings) == 1 { + return g.generateAssetTxnInternalHint( + assetOptin, round, intra, assetIndex, asset) + } + + numHoldings := uint64(len(asset.holdings)) + closeIndex := (rand.Uint64() % (numHoldings - 1)) + uint64(1) + senderIndex = asset.holdings[closeIndex].acctIndex + sender := indexToAccount(senderIndex) + + closeToAcctIndex := asset.holdings[0].acctIndex + closeToAcct := indexToAccount(closeToAcctIndex) + + txn = g.makeAssetTransferTxn( + g.makeTxnHeader(sender, round, intra), closeToAcct, 0, closeToAcct, asset.assetID) + + asset.holdings[0].balance += asset.holdings[closeIndex].balance + + // Remove asset by moving the last element to the deleted index then trimming the slice. + asset.holdings[closeIndex] = asset.holdings[numHoldings-1] + asset.holdings = asset.holdings[:numHoldings-1] + delete(asset.holders, senderIndex) + default: + } + } + + if indexToAccount(senderIndex) != txn.Sender { + fmt.Printf("failed to properly set sender index.") + os.Exit(1) + } + + if g.balances[senderIndex] < txn.Fee.ToUint64() { + fmt.Printf("\n\nthe sender account does not have enough algos for the transfer. idx %d, asset transaction type %v, num %d\n\n", senderIndex, actual, g.reportData[actual].GenerationCount) + os.Exit(1) + } + g.balances[senderIndex] -= txn.Fee.ToUint64() + return +} + +func (g *generator) generateAssetTxn(round uint64, intra uint64) (transactions.SignedTxn, transactions.ApplyData, error) { + start := time.Now() + selection, err := weightedSelection(g.assetTxWeights, getAssetTxOptions(), assetXfer) + if err != nil { + return transactions.SignedTxn{}, transactions.ApplyData{}, err + } + + actual, txn := g.generateAssetTxnInternal(selection.(TxTypeID), round, intra) + defer g.recordData(actual, start) + + if txn.Type == "" { + fmt.Println("Empty asset transaction.") + os.Exit(1) + } + + return signTxn(txn), transactions.ApplyData{}, nil +} + +func (g *generator) WriteAccount(output io.Writer, accountString string) error { + addr, err := basics.UnmarshalChecksumAddress(accountString) + if err != nil { + return fmt.Errorf("failed to unmarshal address: %w", err) + } + + idx := accountToIndex(addr) + + // Asset Holdings + assets := make([]model.AssetHolding, 0) + createdAssets := make([]model.Asset, 0) + for _, a := range g.assets { + // holdings + if holding := a.holders[idx]; holding != nil { + assets = append(assets, model.AssetHolding{ + Amount: holding.balance, + AssetID: a.assetID, + IsFrozen: false, + }) + } + // creator + if len(a.holdings) > 0 && a.holdings[0].acctIndex == idx { + nameBytes := []byte(a.name) + asset := model.Asset{ + Index: a.assetID, + Params: model.AssetParams{ + Creator: accountString, + Decimals: 0, + Clawback: &accountString, + Freeze: &accountString, + Manager: &accountString, + Reserve: &accountString, + Name: &a.name, + NameB64: &nameBytes, + Total: assetTotal, + }, + } + asset.Params.DefaultFrozen = new(bool) + *(asset.Params.DefaultFrozen) = false + createdAssets = append(createdAssets, asset) + } + } + + data := model.Account{ + Address: accountString, + Amount: g.balances[idx], + AmountWithoutPendingRewards: g.balances[idx], + AppsLocalState: nil, + AppsTotalExtraPages: nil, + AppsTotalSchema: nil, + Assets: &assets, + AuthAddr: nil, + CreatedApps: nil, + CreatedAssets: &createdAssets, + Participation: nil, + PendingRewards: 0, + RewardBase: nil, + Rewards: 0, + Round: g.round - 1, + SigType: nil, + Status: "Offline", + } + + return json.NewEncoder(output).Encode(data) +} + +// Accounts is used in the runner to generate a list of addresses. +func (g *generator) Accounts() <-chan basics.Address { + results := make(chan basics.Address, 10) + go func() { + defer close(results) + for i := uint64(0); i < g.numAccounts; i++ { + results <- indexToAccount(i) + } + }() + return results +} diff --git a/tools/block-generator/generator/generate_test.go b/tools/block-generator/generator/generate_test.go new file mode 100644 index 0000000000..4f25b4d0fb --- /dev/null +++ b/tools/block-generator/generator/generate_test.go @@ -0,0 +1,216 @@ +// Copyright (C) 2019-2023 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package generator + +import ( + "bytes" + "testing" + + "github.com/algorand/go-algorand/data/transactions" + "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) *generator { + partitiontest.PartitionTest(t) + publicGenerator, err := MakeGenerator(GenerationConfig{ + NumGenesisAccounts: 10, + GenesisAccountInitialBalance: 10000000000000000000, + PaymentTransactionFraction: 1.0, + PaymentNewAccountFraction: 1.0, + AssetCreateFraction: 1.0, + }) + require.NoError(t, err) + return publicGenerator.(*generator) +} + +func TestPaymentAcctCreate(t *testing.T) { + partitiontest.PartitionTest(t) + g := makePrivateGenerator(t) + g.generatePaymentTxnInternal(paymentAcctCreateTx, 0, 0) + require.Len(t, g.balances, int(g.config.NumGenesisAccounts+1)) +} + +func TestPaymentTransfer(t *testing.T) { + partitiontest.PartitionTest(t) + g := makePrivateGenerator(t) + g.generatePaymentTxnInternal(paymentTx, 0, 0) + require.Len(t, g.balances, int(g.config.NumGenesisAccounts)) +} + +func TestAssetXferNoAssetsOverride(t *testing.T) { + partitiontest.PartitionTest(t) + g := makePrivateGenerator(t) + + // First asset transaction must create. + actual, txn := g.generateAssetTxnInternal(assetXfer, 1, 0) + require.Equal(t, assetCreate, actual) + require.Equal(t, protocol.AssetConfigTx, txn.Type) + require.Len(t, g.assets, 0) + require.Len(t, g.pendingAssets, 1) + require.Len(t, g.pendingAssets[0].holdings, 1) + require.Len(t, g.pendingAssets[0].holders, 1) +} + +func TestAssetXferOneHolderOverride(t *testing.T) { + partitiontest.PartitionTest(t) + g := makePrivateGenerator(t) + g.finishRound(0) + g.generateAssetTxnInternal(assetCreate, 1, 0) + g.finishRound(1) + + // Transfer converted to optin if there is only 1 holder. + actual, txn := g.generateAssetTxnInternal(assetXfer, 2, 0) + require.Equal(t, assetOptin, actual) + require.Equal(t, protocol.AssetTransferTx, txn.Type) + require.Len(t, g.assets, 1) + // A new holding is created, indicating the optin + require.Len(t, g.assets[0].holdings, 2) + require.Len(t, g.assets[0].holders, 2) +} + +func TestAssetCloseCreatorOverride(t *testing.T) { + partitiontest.PartitionTest(t) + g := makePrivateGenerator(t) + g.finishRound(0) + g.generateAssetTxnInternal(assetCreate, 1, 0) + g.finishRound(1) + + // Instead of closing the creator, optin a new account + actual, txn := g.generateAssetTxnInternal(assetClose, 2, 0) + require.Equal(t, assetOptin, actual) + require.Equal(t, protocol.AssetTransferTx, txn.Type) + require.Len(t, g.assets, 1) + // A new holding is created, indicating the optin + require.Len(t, g.assets[0].holdings, 2) + require.Len(t, g.assets[0].holders, 2) +} + +func TestAssetOptinEveryAccountOverride(t *testing.T) { + partitiontest.PartitionTest(t) + g := makePrivateGenerator(t) + g.finishRound(0) + g.generateAssetTxnInternal(assetCreate, 1, 0) + g.finishRound(1) + + // Opt all the accounts in, this also verifies that no account is opted in twice + var txn transactions.Transaction + var actual TxTypeID + for i := 2; uint64(i) <= g.numAccounts; i++ { + actual, txn = g.generateAssetTxnInternal(assetOptin, 2, uint64(1+i)) + require.Equal(t, assetOptin, actual) + require.Equal(t, protocol.AssetTransferTx, txn.Type) + require.Len(t, g.assets, 1) + require.Len(t, g.assets[0].holdings, i) + require.Len(t, g.assets[0].holders, i) + } + g.finishRound(2) + + // All accounts have opted in + require.Equal(t, g.numAccounts, uint64(len(g.assets[0].holdings))) + + // The next optin closes instead + actual, txn = g.generateAssetTxnInternal(assetOptin, 3, 0) + g.finishRound(3) + require.Equal(t, assetClose, actual) + require.Equal(t, protocol.AssetTransferTx, txn.Type) + require.Len(t, g.assets, 1) + require.Len(t, g.assets[0].holdings, int(g.numAccounts-1)) + require.Len(t, g.assets[0].holders, int(g.numAccounts-1)) +} + +func TestAssetDestroyWithHoldingsOverride(t *testing.T) { + partitiontest.PartitionTest(t) + g := makePrivateGenerator(t) + g.finishRound(0) + g.generateAssetTxnInternal(assetCreate, 1, 0) + g.finishRound(1) + g.generateAssetTxnInternal(assetOptin, 2, 0) + g.finishRound(2) + g.generateAssetTxnInternal(assetXfer, 3, 0) + g.finishRound(3) + require.Len(t, g.assets[0].holdings, 2) + require.Len(t, g.assets[0].holders, 2) + + actual, txn := g.generateAssetTxnInternal(assetDestroy, 4, 0) + require.Equal(t, assetClose, actual) + require.Equal(t, protocol.AssetTransferTx, txn.Type) + require.Len(t, g.assets, 1) + require.Len(t, g.assets[0].holdings, 1) + require.Len(t, g.assets[0].holders, 1) +} + +func TestAssetTransfer(t *testing.T) { + partitiontest.PartitionTest(t) + g := makePrivateGenerator(t) + g.finishRound(0) + + g.generateAssetTxnInternal(assetCreate, 1, 0) + g.finishRound(1) + g.generateAssetTxnInternal(assetOptin, 2, 0) + g.finishRound(2) + g.generateAssetTxnInternal(assetXfer, 3, 0) + g.finishRound(3) + require.Greater(t, g.assets[0].holdings[1].balance, uint64(0)) +} + +func TestAssetDestroy(t *testing.T) { + partitiontest.PartitionTest(t) + g := makePrivateGenerator(t) + g.finishRound(0) + g.generateAssetTxnInternal(assetCreate, 1, 0) + g.finishRound(1) + require.Len(t, g.assets, 1) + + actual, txn := g.generateAssetTxnInternal(assetDestroy, 2, 0) + require.Equal(t, assetDestroy, actual) + require.Equal(t, protocol.AssetConfigTx, txn.Type) + require.Len(t, g.assets, 0) +} + +func TestWriteRoundZero(t *testing.T) { + partitiontest.PartitionTest(t) + g := makePrivateGenerator(t) + var data []byte + writer := bytes.NewBuffer(data) + g.WriteBlock(writer, 0) + var block rpcs.EncodedBlockCert + protocol.Decode(data, &block) + require.Len(t, block.Block.Payset, 0) +} + +func TestWriteRound(t *testing.T) { + partitiontest.PartitionTest(t) + g := makePrivateGenerator(t) + var data []byte + writer := bytes.NewBuffer(data) + g.WriteBlock(writer, 1) + var block rpcs.EncodedBlockCert + protocol.Decode(data, &block) + require.Len(t, block.Block.Payset, int(g.config.TxnPerBlock)) +} + +func TestIndexToAccountAndAccountToIndex(t *testing.T) { + partitiontest.PartitionTest(t) + for i := uint64(0); i < uint64(100000); i++ { + acct := indexToAccount(i) + result := accountToIndex(acct) + require.Equal(t, i, result) + } +} diff --git a/tools/block-generator/generator/make_transactions.go b/tools/block-generator/generator/make_transactions.go new file mode 100644 index 0000000000..cd316779ec --- /dev/null +++ b/tools/block-generator/generator/make_transactions.go @@ -0,0 +1,97 @@ +// Copyright (C) 2019-2023 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package generator + +import ( + "encoding/binary" + + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/data/transactions" + "github.com/algorand/go-algorand/protocol" +) + +func (g *generator) makeTxnHeader(sender basics.Address, round, intra uint64) transactions.Header { + note := make([]byte, 8) + binary.LittleEndian.PutUint64(note, uint64(g.txnCounter+intra)) + + return transactions.Header{ + Sender: sender, + Fee: basics.MicroAlgos{Raw: g.params.MinTxnFee}, + FirstValid: basics.Round(round), + LastValid: basics.Round(round + 1000), + GenesisID: g.genesisID, + GenesisHash: g.genesisHash, + Note: note, + } +} + +func (g *generator) makePaymentTxn(header transactions.Header, receiver basics.Address, amount uint64, closeRemainderTo basics.Address) transactions.Transaction { + return transactions.Transaction{ + Type: protocol.PaymentTx, + Header: header, + PaymentTxnFields: transactions.PaymentTxnFields{ + Receiver: receiver, + Amount: basics.MicroAlgos{Raw: amount}, + CloseRemainderTo: closeRemainderTo, + }, + } +} + +func (g *generator) makeAssetCreateTxn(header transactions.Header, total uint64, defaultFrozen bool, assetName string) transactions.Transaction { + return transactions.Transaction{ + Type: protocol.AssetConfigTx, + Header: header, + AssetConfigTxnFields: transactions.AssetConfigTxnFields{ + AssetParams: basics.AssetParams{ + Total: total, + DefaultFrozen: defaultFrozen, + AssetName: assetName, + Manager: header.Sender, + Freeze: header.Sender, + Clawback: header.Sender, + Reserve: header.Sender, + }, + }, + } +} + +func (g *generator) makeAssetDestroyTxn(header transactions.Header, index uint64) transactions.Transaction { + return transactions.Transaction{ + Type: protocol.AssetConfigTx, + Header: header, + AssetConfigTxnFields: transactions.AssetConfigTxnFields{ + ConfigAsset: basics.AssetIndex(index), + }, + } +} + +func (g *generator) makeAssetTransferTxn(header transactions.Header, receiver basics.Address, amount uint64, closeAssetsTo basics.Address, index uint64) transactions.Transaction { + return transactions.Transaction{ + Type: protocol.AssetTransferTx, + Header: header, + AssetTransferTxnFields: transactions.AssetTransferTxnFields{ + XferAsset: basics.AssetIndex(index), + AssetAmount: amount, + AssetReceiver: receiver, + AssetCloseTo: closeAssetsTo, + }, + } +} + +func (g *generator) makeAssetAcceptanceTxn(header transactions.Header, index uint64) transactions.Transaction { + return g.makeAssetTransferTxn(header, header.Sender, 0, basics.Address{}, index) +} diff --git a/tools/block-generator/generator/server.go b/tools/block-generator/generator/server.go new file mode 100644 index 0000000000..10adc04728 --- /dev/null +++ b/tools/block-generator/generator/server.go @@ -0,0 +1,174 @@ +// Copyright (C) 2019-2023 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package generator + +import ( + "fmt" + "net/http" + "os" + "strings" + "time" + + "github.com/algorand/go-algorand/tools/block-generator/util" + "gopkg.in/yaml.v3" +) + +func initializeConfigFile(configFile string) (config GenerationConfig, err error) { + data, err := os.ReadFile(configFile) + if err != nil { + return + } + err = yaml.Unmarshal(data, &config) + if err != nil { + return + } + return +} + +// MakeServer configures http handlers. Returns the http server. +func MakeServer(configFile string, addr string) (*http.Server, Generator) { + noOp := func(next http.Handler) http.Handler { + return next + } + return MakeServerWithMiddleware(configFile, addr, noOp) +} + +// BlocksMiddleware is a middleware for the blocks endpoint. +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(configFile string, addr string, blocksMiddleware BlocksMiddleware) (*http.Server, Generator) { + config, err := initializeConfigFile(configFile) + util.MaybeFail(err, "problem loading config file. Use '--config' or create a config file.") + + gen, err := MakeGenerator(config) + util.MaybeFail(err, "Failed to make generator with config file '%s'", configFile) + + mux := http.NewServeMux() + mux.HandleFunc("/", help) + mux.Handle("/v2/blocks/", blocksMiddleware(http.HandlerFunc(getBlockHandler(gen)))) + mux.HandleFunc("/v2/accounts/", getAccountHandler(gen)) + mux.HandleFunc("/genesis", getGenesisHandler(gen)) + mux.HandleFunc("/report", getReportHandler(gen)) + mux.HandleFunc("/v2/status/wait-for-block-after/", getStatusWaitHandler(gen)) + + return &http.Server{ + Addr: addr, + Handler: mux, + ReadHeaderTimeout: 3 * time.Second, + }, gen +} + +func help(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "Use /v2/blocks/:blocknum: to get a block.") +} + +func maybeWriteError(w http.ResponseWriter, err error) { + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } +} + +func getReportHandler(gen Generator) func(w http.ResponseWriter, r *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + maybeWriteError(w, gen.WriteReport(w)) + } +} + +func getStatusWaitHandler(gen Generator) func(w http.ResponseWriter, r *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + maybeWriteError(w, gen.WriteStatus(w)) + } +} + +func getGenesisHandler(gen Generator) func(w http.ResponseWriter, r *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + maybeWriteError(w, gen.WriteGenesis(w)) + } +} + +func getBlockHandler(gen Generator) func(w http.ResponseWriter, r *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + // The generator doesn't actually care about the block... + round, err := parseRound(r.URL.Path) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + maybeWriteError(w, gen.WriteBlock(w, round)) + } +} + +func getAccountHandler(gen Generator) func(w http.ResponseWriter, r *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + // The generator doesn't actually care about the block... + account, err := parseAccount(r.URL.Path) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + maybeWriteError(w, gen.WriteAccount(w, account)) + } +} + +const blockQueryPrefix = "/v2/blocks/" +const blockQueryBlockIdx = len(blockQueryPrefix) +const accountsQueryPrefix = "/v2/accounts/" +const accountsQueryAccountIdx = len(accountsQueryPrefix) + +func parseRound(path string) (uint64, error) { + if !strings.HasPrefix(path, blockQueryPrefix) { + return 0, fmt.Errorf("not a blocks query: %s", path) + } + + result := uint64(0) + pathlen := len(path) + + if pathlen == blockQueryBlockIdx { + return 0, fmt.Errorf("no block in path") + } + + for i := blockQueryBlockIdx; i < pathlen; i++ { + if path[i] < '0' || path[i] > '9' { + if i == blockQueryBlockIdx { + return 0, fmt.Errorf("no block in path") + } + break + } + result = (uint64(10) * result) + uint64(int(path[i])-'0') + } + return result, nil +} + +func parseAccount(path string) (string, error) { + if !strings.HasPrefix(path, accountsQueryPrefix) { + return "", fmt.Errorf("not a accounts query: %s", path) + } + + pathlen := len(path) + + if pathlen == accountsQueryAccountIdx { + return "", fmt.Errorf("no address in path") + } + + return path[accountsQueryAccountIdx:], nil +} diff --git a/tools/block-generator/generator/server_test.go b/tools/block-generator/generator/server_test.go new file mode 100644 index 0000000000..4d2863aeff --- /dev/null +++ b/tools/block-generator/generator/server_test.go @@ -0,0 +1,108 @@ +// Copyright (C) 2019-2023 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package generator + +import ( + "fmt" + "os" + "strings" + "testing" + + "github.com/algorand/go-algorand/test/partitiontest" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestInitConfigFile(t *testing.T) { + partitiontest.PartitionTest(t) + config, err := initializeConfigFile("../test_config.yml") + require.NoError(t, err) + require.Equal(t, uint64(10), config.NumGenesisAccounts) + require.Equal(t, float32(0.25), config.AssetCloseFraction) + require.Equal(t, float32(0.0), config.AssetDestroyFraction) +} + +func TestInitConfigFileNotExist(t *testing.T) { + partitiontest.PartitionTest(t) + _, err := initializeConfigFile("this_is_not_a_config_file") + + if _, ok := err.(*os.PathError); !ok { + require.Fail(t, "This should generate a path error") + } +} + +func TestParseRound(t *testing.T) { + partitiontest.PartitionTest(t) + var testcases = []struct { + name string + url string + expectedRound uint64 + err string + }{ + { + name: "no block", + url: "/v2/blocks/", + expectedRound: 0, + err: "no block in path", + }, + { + name: "no block 2", + url: "/v2/blocks/?nothing", + expectedRound: 0, + err: "no block in path", + }, + { + name: "invalid prefix", + url: "/v2/wrong/prefix/1", + expectedRound: 0, + err: "not a blocks query", + }, + { + name: "normal one digit", + url: fmt.Sprintf("%s1", blockQueryPrefix), + expectedRound: 1, + err: "", + }, + { + name: "normal long number", + url: fmt.Sprintf("%s12345678", blockQueryPrefix), + expectedRound: 12345678, + err: "", + }, + { + name: "with query parameters", + url: fmt.Sprintf("%s1234?pretty", blockQueryPrefix), + expectedRound: 1234, + err: "", + }, + } + + for _, testcase := range testcases { + t.Run(testcase.name, func(t *testing.T) { + round, err := parseRound(testcase.url) + if len(testcase.err) == 0 { + msg := fmt.Sprintf("Unexpected error parsing '%s', expected round '%d' received error: %v", + testcase.url, testcase.expectedRound, err) + require.NoError(t, err, msg) + assert.Equal(t, testcase.expectedRound, round) + } else { + require.Error(t, err, fmt.Sprintf("Expected an error containing: %s", testcase.err)) + require.True(t, strings.Contains(err.Error(), testcase.err)) + } + }) + } +} diff --git a/tools/block-generator/generator/utils.go b/tools/block-generator/generator/utils.go new file mode 100644 index 0000000000..7c7a3980ce --- /dev/null +++ b/tools/block-generator/generator/utils.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 . + +package generator + +import ( + "fmt" + "math/rand" +) + +func weightedSelection(weights []float32, options []interface{}, defaultOption interface{}) (selection interface{}, err error) { + return weightedSelectionInternal(rand.Float32(), weights, options, defaultOption) +} + +func weightedSelectionInternal(selectionNumber float32, weights []float32, options []interface{}, defaultOption interface{}) (selection interface{}, err error) { + if len(weights) != len(options) { + err = fmt.Errorf("number of weights must equal number of options: %d != %d", len(weights), len(options)) + return + } + + total := float32(0) + for i := 0; i < len(weights); i++ { + if selectionNumber-total < weights[i] { + selection = options[i] + return + } + total += weights[i] + } + + selection = defaultOption + return +} diff --git a/tools/block-generator/generator/utils_test.go b/tools/block-generator/generator/utils_test.go new file mode 100644 index 0000000000..7fc289d695 --- /dev/null +++ b/tools/block-generator/generator/utils_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 . + +package generator + +import ( + "fmt" + "testing" + + "github.com/algorand/go-algorand/test/partitiontest" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestWeightedSelectionInternalBadInput(t *testing.T) { + partitiontest.PartitionTest(t) + weights := []float32{0.10, 0.30} + options := []interface{}{"10"} + _, err := weightedSelectionInternal(0, weights, options, nil) + require.EqualError(t, err, "number of weights must equal number of options: 2 != 1") +} + +func TestWeightedSelectionInternal(t *testing.T) { + partitiontest.PartitionTest(t) + weights := []float32{0.10, 0.30, 0.60} + options := []interface{}{"10", "30", "60"} + + testcases := []struct { + selectionNum float32 + expected interface{} + }{ + { + selectionNum: 0.0, + expected: options[0], + }, + { + selectionNum: 0.099, + expected: options[0], + }, + { + selectionNum: 0.1, + expected: options[1], + }, + { + selectionNum: 0.399, + expected: options[1], + }, + { + selectionNum: 0.4, + expected: options[2], + }, + { + selectionNum: 0.999, + expected: options[2], + }, + } + + for _, test := range testcases { + name := fmt.Sprintf("selectionNum %f - expected %v", test.selectionNum, test.expected) + t.Run(name, func(t *testing.T) { + actual, err := weightedSelectionInternal(test.selectionNum, weights, options, nil) + require.NoError(t, err) + require.Equal(t, test.expected, actual) + }) + } +} + +func TestWeightedSelection(t *testing.T) { + partitiontest.PartitionTest(t) + weights := []float32{0.10, 0.30, 0.60} + options := []interface{}{"10", "30", "60"} + selections := make(map[interface{}]int) + + for i := 0; i < 100; i++ { + selected, err := weightedSelection(weights, options, nil) + require.NoError(t, err) + selections[selected]++ + } + + assert.Less(t, selections[options[0]], selections[options[1]]) + assert.Less(t, selections[options[1]], selections[options[2]]) +} + +func TestWeightedSelectionOutOfRange(t *testing.T) { + partitiontest.PartitionTest(t) + weights := []float32{0.1} + options := []interface{}{"1"} + defaultOption := "DEFAULT!" + + for i := 0; i < 10000; i++ { + selection, err := weightedSelection(weights, options, defaultOption) + require.NoError(t, err) + if selection == defaultOption { + return + } + } + assert.Fail(t, "Expected an out of range error by this point.") +} diff --git a/tools/block-generator/main.go b/tools/block-generator/main.go new file mode 100644 index 0000000000..6525bc4a6c --- /dev/null +++ b/tools/block-generator/main.go @@ -0,0 +1,26 @@ +// Copyright (C) 2019-2023 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package main + +import "github.com/algorand/go-algorand/tools/block-generator/core" + +func main() { + err := core.BlockGenerator.Execute() + if err != nil { + panic(err) + } +} diff --git a/tools/block-generator/metrics/metrics.go b/tools/block-generator/metrics/metrics.go new file mode 100644 index 0000000000..6c60fbabc2 --- /dev/null +++ b/tools/block-generator/metrics/metrics.go @@ -0,0 +1,42 @@ +// Copyright (C) 2019-2023 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package metrics + +// Prometheus metrics collected in Conduit. +const ( + BlockImportTimeName = "import_time_sec" + ImportedTxnsPerBlockName = "imported_tx_per_block" + ImportedRoundGaugeName = "imported_round" + GetAlgodRawBlockTimeName = "get_algod_raw_block_time_sec" + ImportedTxnsName = "imported_txns" + ImporterTimeName = "importer_time_sec" + ProcessorTimeName = "processor_time_sec" + ExporterTimeName = "exporter_time_sec" + PipelineRetryCountName = "pipeline_retry_count" +) + +// AllMetricNames is a reference for all the custom metric names. +var AllMetricNames = []string{ + BlockImportTimeName, + ImportedTxnsPerBlockName, + ImportedRoundGaugeName, + GetAlgodRawBlockTimeName, + ImporterTimeName, + ProcessorTimeName, + ExporterTimeName, + PipelineRetryCountName, +} diff --git a/tools/block-generator/run_generator.sh b/tools/block-generator/run_generator.sh new file mode 100755 index 0000000000..47e87b2e0d --- /dev/null +++ b/tools/block-generator/run_generator.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash + +# Demonstrate how to run the generator and connect it to indexer. + +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 + + # Start postgres container... + docker run \ + -d \ + --name $POSTGRES_CONTAINER \ + -e POSTGRES_USER=algorand \ + -e POSTGRES_PASSWORD=algorand \ + -e PGPASSWORD=algorand \ + -p $POSTGRES_PORT:5432 \ + postgres + + sleep 5 + + docker exec -it $POSTGRES_CONTAINER psql -Ualgorand -c "create database $POSTGRES_DATABASE" +} + +function shutdown() { + docker rm -f $POSTGRES_CONTAINER > /dev/null 2>&1 || true + kill -9 $GENERATOR_PID +} + +trap shutdown EXIT + +echo "Building generator." +pushd $(dirname "$0") > /dev/null +go build +cd ../.. > /dev/null +echo "Building indexer." +make +popd +echo "Starting postgres container." +start_postgres +echo "Starting block generator (see generator.log)" +$(dirname "$0")/block-generator daemon --port 11111 --config "${CONFIG}" & +GENERATOR_PID=$! +echo "Starting indexer" +$(dirname "$0")/../../cmd/algorand-indexer/algorand-indexer daemon \ + -S localhost:8980 \ + --algod-net localhost:11111 \ + --algod-token security-is-our-number-one-priority \ + --metrics-mode VERBOSE \ + -P "host=localhost user=algorand password=algorand dbname=generator_db port=15432 sslmode=disable" diff --git a/tools/block-generator/run_postgres.sh b/tools/block-generator/run_postgres.sh new file mode 100755 index 0000000000..c6a967132b --- /dev/null +++ b/tools/block-generator/run_postgres.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash + +# This script is useful if you want to launch the runner +# in a debugger. Simply start this script and run with: +# ./block-generator runner \ +# -d 5s \ +# -i ./../algorand-indexer/algorand-indexer \ +# -c "host=localhost user=algorand password=algorand dbname=algorand port=15432 sslmode=disable" \ +# -r results \ +# -s scenarios/config.payment.small.yml + +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 + + # Start postgres container... + docker run \ + -d \ + --name $POSTGRES_CONTAINER \ + -e POSTGRES_USER=algorand \ + -e POSTGRES_PASSWORD=algorand \ + -e PGPASSWORD=algorand \ + -p $POSTGRES_PORT:5432 \ + postgres + + sleep 5 + + docker exec -it $POSTGRES_CONTAINER psql -Ualgorand -c "create database $POSTGRES_DATABASE" +} + +function shutdown() { + docker rm -f $POSTGRES_CONTAINER > /dev/null 2>&1 || true +} + +trap shutdown EXIT + +pushd $(dirname "$0") > /dev/null +echo "Starting postgres container at: \n\t\"host=localhost user=algorand password=algorand dbname=algorand port=15432\"" +start_postgres +echo "Sleeping, use Ctrl-C to end test." +sleep 1000000000000000 + diff --git a/tools/block-generator/run_runner.sh b/tools/block-generator/run_runner.sh new file mode 100755 index 0000000000..d90749b96b --- /dev/null +++ b/tools/block-generator/run_runner.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash + +# Demonstrate how to run the block-generator runner. + +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 + + # Start postgres container... + docker run \ + -d \ + --name $POSTGRES_CONTAINER \ + -e POSTGRES_USER=algorand \ + -e POSTGRES_PASSWORD=algorand \ + -e PGPASSWORD=algorand \ + -p $POSTGRES_PORT:5432 \ + postgres + + sleep 5 + + docker exec -it $POSTGRES_CONTAINER psql -Ualgorand -c "create database $POSTGRES_DATABASE" +} + +function shutdown() { + docker rm -f $POSTGRES_CONTAINER > /dev/null 2>&1 || true +} + +trap shutdown EXIT + +rm -rf OUTPUT_RUN_RUNNER_TEST > /dev/null 2>&1 +echo "Building generator." +pushd $(dirname "$0") > /dev/null +go build +cd ../.. > /dev/null +echo "Building indexer." +make +popd +echo "Starting postgres container." +start_postgres +echo "Starting test runner" +$(dirname "$0")/block-generator runner \ + --indexer-binary ../algorand-indexer/algorand-indexer \ + --report-directory OUTPUT_RUN_RUNNER_TEST \ + --test-duration 30s \ + --log-level trace \ + --postgres-connection-string "host=localhost user=algorand password=algorand dbname=generator_db port=15432 sslmode=disable" \ + --scenario ${CONFIG} diff --git a/tools/block-generator/run_tests.sh b/tools/block-generator/run_tests.sh new file mode 100755 index 0000000000..0a1c6df49e --- /dev/null +++ b/tools/block-generator/run_tests.sh @@ -0,0 +1,98 @@ +#!/usr/bin/env bash + +CONNECTION_STRING="" +INDEXER_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|--indexer path to indexer binary." + echo " -s|--scenarios path to indexer 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 Indexer." + echo " -g|--generator use a different indexer 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 | --indexer) + INDEXER_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 "$INDEXER_BINARY" ]; then + echo "Missing required indexer binary parameter (-i / --indexer)." + exit 1 +fi + +if [ -z "$SCENARIOS" ]; then + echo "Missing required indexer test scenario parameter (-s / --scenarios)." + exit 1 +fi + +if [ -z "$GENERATOR_BINARY" ]; then + echo "Using indexer binary for generator, override with (-g / --generator)." + GENERATOR_BINARY="$INDEXER_BINARY" +fi + +echo "Running with binary: $INDEXER_BINARY" +echo "Report directory: $REPORT_DIR" +echo "Duration: $DURATION" +echo "Log Level: $LOG_LEVEL" + +"$GENERATOR_BINARY" \ + util block-generator runner \ + -i "$INDEXER_BINARY" \ + -s "$SCENARIOS" \ + -d "$DURATION" \ + -c "$CONNECTION_STRING" \ + --report-directory "$REPORT_DIR" \ + --log-level "$LOG_LEVEL" \ + --reset + diff --git a/tools/block-generator/runner/metrics_collector.go b/tools/block-generator/runner/metrics_collector.go new file mode 100644 index 0000000000..327ca9e33b --- /dev/null +++ b/tools/block-generator/runner/metrics_collector.go @@ -0,0 +1,88 @@ +// Copyright (C) 2019-2023 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package runner + +import ( + "bufio" + "fmt" + "net/http" + "strings" + "time" +) + +// MetricsCollector queries a /metrics endpoint for prometheus style metrics and saves metrics matching a pattern. +type MetricsCollector struct { + // MetricsURL where metrics can be queried. + MetricsURL string + // Data is all of the results. + Data []Entry +} + +// Entry is the raw data pulled from the endpoint along with a timestamp. +type Entry struct { + Timestamp time.Time + Data []string +} + +// Collect fetches the metrics. +func (r *MetricsCollector) Collect(substrings ...string) error { + metrics, err := r.getMetrics(substrings...) + if err != nil { + return err + } + + if len(metrics) > 0 { + entry := Entry{ + Timestamp: time.Now(), + Data: metrics, + } + r.Data = append(r.Data, entry) + } + + return nil +} + +func (r MetricsCollector) getMetrics(substrings ...string) (result []string, err error) { + resp, err := http.Get(r.MetricsURL) + if err != nil { + err = fmt.Errorf("unable to read metrics url '%s'", r.MetricsURL) + return + } + defer resp.Body.Close() + + scanner := bufio.NewScanner(resp.Body) + for scanner.Scan() { + str := scanner.Text() + + if strings.HasPrefix(str, "#") { + continue + } + + for _, substring := range substrings { + if strings.Contains(str, substring) { + result = append(result, str) + break + } + } + } + + if scanner.Err() != nil { + err = fmt.Errorf("problem reading metrics response: %w", scanner.Err()) + } + + return +} diff --git a/tools/block-generator/runner/run.go b/tools/block-generator/runner/run.go new file mode 100644 index 0000000000..7d933c557b --- /dev/null +++ b/tools/block-generator/runner/run.go @@ -0,0 +1,350 @@ +// Copyright (C) 2019-2023 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package runner + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "os" + "path" + "path/filepath" + "strconv" + "strings" + "time" + + "github.com/algorand/go-algorand/tools/block-generator/generator" + "github.com/algorand/go-algorand/tools/block-generator/metrics" + "github.com/algorand/go-algorand/tools/block-generator/util" + "github.com/algorand/go-deadlock" +) + +// 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. + Path string + IndexerBinary string + IndexerPort uint64 + PostgresConnectionString string + CPUProfilePath string + RunDuration time.Duration + LogLevel string + ReportDirectory string + ResetReportDir bool + RunValidation bool + KeepDataDir bool +} + +// Run is a publi8c helper to run the tests. +// 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) + } + } 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 { + // Ignore the directory + if info.IsDir() { + return nil + } + runnerArgs := args + runnerArgs.Path = path + fmt.Printf("Running test for configuration '%s'\n", path) + return runnerArgs.run() + }) +} + +func (r *Args) run() 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.indexer-log", baseNameNoExt)) + dataDir := path.Join(r.ReportDirectory, fmt.Sprintf("%s_data", baseNameNoExt)) + if !r.KeepDataDir { + defer os.RemoveAll(dataDir) + } + + // This middleware allows us to lock the block endpoint + var freezeMutex deadlock.Mutex + blockMiddleware := func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + freezeMutex.Lock() + defer freezeMutex.Unlock() + next.ServeHTTP(w, r) + }) + } + // Start services + algodNet := fmt.Sprintf("localhost:%d", 11112) + indexerNet := fmt.Sprintf("localhost:%d", r.IndexerPort) + generatorShutdownFunc, _ := startGenerator(r.Path, algodNet, blockMiddleware) + defer func() { + // Shutdown generator. + if err := generatorShutdownFunc(); err != nil { + fmt.Printf("Failed to shutdown generator: %s\n", err) + } + }() + + //indexerShutdownFunc, err := startIndexer(dataDir, logfile, r.LogLevel, r.IndexerBinary, algodNet, indexerNet, r.PostgresConnectionString, r.CPUProfilePath) + //if err != nil { + // return fmt.Errorf("failed to start indexer: %w", err) + //} + //defer func() { + // // Shutdown indexer + // if err := indexerShutdownFunc(); err != nil { + // fmt.Printf("Failed to shutdown indexer: %s\n", err) + // } + //}() + + // Create the report file + report, err := os.Create(reportfile) + if err != nil { + return fmt.Errorf("unable to create report: %w", err) + } + defer report.Close() + + // Run the test, collecting results. + if err := r.runTest(report, indexerNet, algodNet); err != nil { + return err + } + + return nil +} + +type metricType int + +const ( + rate metricType = iota + intTotal + floatTotal +) + +// Helper to record metrics. Supports rates (sum/count) and counters. +func recordDataToFile(start time.Time, entry Entry, prefix string, out *os.File) error { + var writeErrors []string + var writeErr error + record := func(prefix2, name string, t metricType) { + key := fmt.Sprintf("%s%s_%s", prefix, prefix2, name) + if err := recordMetricToFile(entry, key, name, t, out); err != nil { + writeErr = err + writeErrors = append(writeErrors, name) + } + } + + record("_average", metrics.BlockImportTimeName, rate) + record("_cumulative", metrics.BlockImportTimeName, floatTotal) + record("_average", metrics.ImportedTxnsPerBlockName, rate) + record("_cumulative", metrics.ImportedTxnsPerBlockName, intTotal) + record("", metrics.ImportedRoundGaugeName, intTotal) + + if len(writeErrors) > 0 { + return fmt.Errorf("error writing metrics (%s): %w", strings.Join(writeErrors, ", "), writeErr) + } + + // Calculate import transactions per second. + totalTxn, err := getMetric(entry, metrics.ImportedTxnsPerBlockName, false) + if err != nil { + return err + } + + importTimeS, err := getMetric(entry, metrics.BlockImportTimeName, false) + if err != nil { + return err + } + tps := totalTxn / importTimeS + key := "overall_transactions_per_second" + msg := fmt.Sprintf("%s_%s:%.2f\n", prefix, key, tps) + if _, err := out.WriteString(msg); err != nil { + return fmt.Errorf("unable to write metric '%s': %w", key, err) + } + + // Uptime + key = "uptime_seconds" + msg = fmt.Sprintf("%s_%s:%.2f\n", prefix, key, time.Since(start).Seconds()) + if _, err := out.WriteString(msg); err != nil { + return fmt.Errorf("unable to write metric '%s': %w", key, err) + } + + return nil +} + +func recordMetricToFile(entry Entry, outputKey, metricSuffix string, t metricType, out *os.File) error { + value, err := getMetric(entry, metricSuffix, t == rate) + if err != nil { + return err + } + + var msg string + if t == intTotal { + msg = fmt.Sprintf("%s:%d\n", outputKey, uint64(value)) + } else { + msg = fmt.Sprintf("%s:%.2f\n", outputKey, value) + } + + if _, err := out.WriteString(msg); err != nil { + return fmt.Errorf("unable to write metric '%s': %w", outputKey, err) + } + + return nil +} + +func getMetric(entry Entry, suffix string, rateMetric bool) (float64, error) { + total := 0.0 + sum := 0.0 + count := 0.0 + hasSum := false + hasCount := false + hasTotal := false + + for _, metric := range entry.Data { + var err error + + if strings.Contains(metric, suffix) { + split := strings.Split(metric, " ") + if len(split) != 2 { + return 0.0, fmt.Errorf("unknown metric format, expected 'key value' received: %s", metric) + } + + // Check for _sum / _count for summary (rateMetric) metrics. + // Otherwise grab the total value. + if strings.HasSuffix(split[0], "_sum") { + sum, err = strconv.ParseFloat(split[1], 64) + hasSum = true + } else if strings.HasSuffix(split[0], "_count") { + count, err = strconv.ParseFloat(split[1], 64) + hasCount = true + } else if strings.HasSuffix(split[0], suffix) { + total, err = strconv.ParseFloat(split[1], 64) + hasTotal = true + } + + if err != nil { + return 0.0, fmt.Errorf("unable to parse metric '%s': %w", metric, err) + } + + if rateMetric && hasSum && hasCount { + return sum / count, nil + } else if !rateMetric { + if hasSum { + return sum, nil + } + if hasTotal { + return total, nil + } + } + } + } + + return 0.0, fmt.Errorf("metric incomplete or not found: %s", suffix) +} + +// Run the test for 'RunDuration', collect metrics and write them to the 'ReportDirectory' +func (r *Args) runTest(report *os.File, indexerURL string, generatorURL string) error { + collector := &MetricsCollector{MetricsURL: fmt.Sprintf("http://%s/metrics", indexerURL)} + + // Run for r.RunDuration + start := time.Now() + count := 1 + for time.Since(start) < r.RunDuration { + time.Sleep(r.RunDuration / 10) + + if err := collector.Collect(metrics.AllMetricNames...); err != nil { + return fmt.Errorf("problem collecting metrics (%d / %s): %w", count, time.Since(start), err) + } + count++ + } + if err := collector.Collect(metrics.AllMetricNames...); err != nil { + return fmt.Errorf("problem collecting final metrics (%d / %s): %w", count, time.Since(start), err) + } + + // Collect results. + durationStr := fmt.Sprintf("test_duration_seconds:%d\ntest_duration_actual_seconds:%f\n", + uint64(r.RunDuration.Seconds()), + time.Since(start).Seconds()) + if _, err := report.WriteString(durationStr); err != nil { + return fmt.Errorf("unable to write duration metric: %w", err) + } + + resp, err := http.Get(fmt.Sprintf("http://%s/report", generatorURL)) + if err != nil { + return fmt.Errorf("generator report query failed") + } + defer resp.Body.Close() + var generatorReport generator.Report + if err = json.NewDecoder(resp.Body).Decode(&generatorReport); err != nil { + return fmt.Errorf("problem decoding generator report: %w", err) + } + for metric, entry := range generatorReport { + // Skip this one + if metric == "genesis" { + continue + } + str := fmt.Sprintf("transaction_%s_total:%d\n", metric, entry.GenerationCount) + if _, err = report.WriteString(str); err != nil { + return fmt.Errorf("unable to write transaction_count metric: %w", err) + } + } + + // Record a rate from one of the first data points. + if len(collector.Data) > 5 { + if err = recordDataToFile(start, collector.Data[2], "early", report); err != nil { + return err + } + } + + // Also record the final metrics. + if err = recordDataToFile(start, collector.Data[len(collector.Data)-1], "final", report); err != nil { + return err + } + + return nil +} + +// startGenerator starts the generator server. +func startGenerator(configFile string, addr string, blockMiddleware func(http.Handler) http.Handler) (func() error, generator.Generator) { + // Start generator. + server, generator := generator.MakeServerWithMiddleware(configFile, addr, blockMiddleware) + + // Start the server + go func() { + // always returns error. ErrServerClosed on graceful close + if err := server.ListenAndServe(); err != http.ErrServerClosed { + util.MaybeFail(err, "ListenAndServe() failure to start with config file '%s'", configFile) + } + }() + + return func() error { + // Shutdown blocks until the server has stopped. + if err := server.Shutdown(context.Background()); err != nil { + return fmt.Errorf("failed during generator graceful shutdown: %w", err) + } + return nil + }, generator +} diff --git a/tools/block-generator/runner/runner.go b/tools/block-generator/runner/runner.go new file mode 100644 index 0000000000..94df877660 --- /dev/null +++ b/tools/block-generator/runner/runner.go @@ -0,0 +1,61 @@ +// Copyright (C) 2019-2023 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package runner + +import ( + "fmt" + "math/rand" + "time" + + "github.com/spf13/cobra" +) + +// RunnerCmd launches the block-generator test suite runner. +var RunnerCmd *cobra.Command + +func init() { + rand.Seed(12345) + var runnerArgs Args + + RunnerCmd = &cobra.Command{ + Use: "runner", + Short: "Run test suite and collect results.", + Long: "Run an automated test suite using the block-generator daemon and a provided algorand-indexer binary. Results are captured to a specified output directory.", + Run: func(cmd *cobra.Command, args []string) { + if err := Run(runnerArgs); err != nil { + fmt.Println(err) + } + }, + } + + RunnerCmd.Flags().StringVarP(&runnerArgs.Path, "scenario", "s", "", "Directory containing scenarios, or specific scenario file.") + RunnerCmd.Flags().StringVarP(&runnerArgs.IndexerBinary, "indexer-binary", "i", "", "Path to indexer binary.") + RunnerCmd.Flags().Uint64VarP(&runnerArgs.IndexerPort, "indexer-port", "p", 4010, "Port to start the server at. This is useful if you have a prometheus server for collecting additional data.") + 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.LogLevel, "log-level", "l", "error", "LogLevel to use when starting Indexer. [error, warn, info, debug, trace]") + RunnerCmd.Flags().StringVarP(&runnerArgs.CPUProfilePath, "cpuprofile", "", "", "Path where Indexer writes its CPU profile.") + RunnerCmd.Flags().BoolVarP(&runnerArgs.ResetReportDir, "reset", "", false, "If set any existing report directory will be deleted before running tests.") + RunnerCmd.Flags().BoolVarP(&runnerArgs.RunValidation, "validate", "", false, "If set the validator will run after test-duration has elapsed to verify data is correct. An extra line in each report indicates validator success or failure.") + RunnerCmd.Flags().BoolVarP(&runnerArgs.KeepDataDir, "keep-data-dir", "k", false, "If set the validator will not delete the data directory after tests complete.") + + RunnerCmd.MarkFlagRequired("scenario") + RunnerCmd.MarkFlagRequired("indexer-binary") + RunnerCmd.MarkFlagRequired("postgres-connection-string") + RunnerCmd.MarkFlagRequired("report-directory") +} diff --git a/tools/block-generator/scenarios/config.asset.close.yml b/tools/block-generator/scenarios/config.asset.close.yml new file mode 100644 index 0000000000..9a8b86bf5d --- /dev/null +++ b/tools/block-generator/scenarios/config.asset.close.yml @@ -0,0 +1,16 @@ +name: "Asset Close" +genesis_accounts: 10000 +genesis_account_balance: 1000000000000 +tx_per_block: 5000 + +# transaction distribution +tx_asset_fraction: 1.0 + +# asset config +asset_create_fraction: 0.001 +asset_optin_fraction: 0.50 +asset_close_fraction: 0.40 +asset_xfer_fraction: 0.099 + +# pay config must equal 1.0 +pay_xfer_fraction: 1.0 diff --git a/tools/block-generator/scenarios/config.asset.destroy.yml b/tools/block-generator/scenarios/config.asset.destroy.yml new file mode 100644 index 0000000000..953572efb4 --- /dev/null +++ b/tools/block-generator/scenarios/config.asset.destroy.yml @@ -0,0 +1,16 @@ +name: "Asset Destroy" +genesis_accounts: 10000 +genesis_account_balance: 1000000000000 +tx_per_block: 5000 + +# transaction distribution +tx_asset_fraction: 1.0 + +# asset config +asset_create_fraction: 0.8 +asset_destroy_fraction: 0.2 +asset_optin_fraction: 0.0 +asset_close_fraction: 0.0 + +# pay config must equal 1.0 +pay_xfer_fraction: 1.0 diff --git a/tools/block-generator/scenarios/config.asset.xfer.yml b/tools/block-generator/scenarios/config.asset.xfer.yml new file mode 100644 index 0000000000..3e6643ba34 --- /dev/null +++ b/tools/block-generator/scenarios/config.asset.xfer.yml @@ -0,0 +1,15 @@ +name: "Asset Xfer" +genesis_accounts: 10000 +genesis_account_balance: 1000000000000 +tx_per_block: 5000 + +# transaction distribution +tx_asset_fraction: 1.0 + +# asset config +asset_create_fraction: 0.001 +asset_optin_fraction: 0.10 +asset_xfer_fraction: 0.899 + +# pay config must equal 1.0 +pay_xfer_fraction: 1.0 diff --git a/tools/block-generator/scenarios/config.mixed.jumbo.yml b/tools/block-generator/scenarios/config.mixed.jumbo.yml new file mode 100644 index 0000000000..d02ee4045d --- /dev/null +++ b/tools/block-generator/scenarios/config.mixed.jumbo.yml @@ -0,0 +1,19 @@ +name: "Mixed (jumbo)" +genesis_accounts: 10000 +genesis_account_balance: 1000000000000 +tx_per_block: 19999 + +# transaction distribution +tx_pay_fraction: 0.3 +tx_asset_fraction: 0.7 + +# payment config +pay_acct_create_fraction: 0.02 +pay_xfer_fraction: 0.98 + +# asset config +asset_create_fraction: 0.001 +asset_optin_fraction: 0.1 +asset_close_fraction: 0.05 +asset_xfer_fraction: 0.849 +asset_delete_fraction: 0 diff --git a/tools/block-generator/scenarios/config.mixed.yml b/tools/block-generator/scenarios/config.mixed.yml new file mode 100644 index 0000000000..d6e1eea606 --- /dev/null +++ b/tools/block-generator/scenarios/config.mixed.yml @@ -0,0 +1,19 @@ +name: "Mixed" +genesis_accounts: 10000 +genesis_account_balance: 1000000000000 +tx_per_block: 5000 + +# transaction distribution +tx_pay_fraction: 0.3 +tx_asset_fraction: 0.7 + +# payment config +pay_acct_create_fraction: 0.02 +pay_xfer_fraction: 0.98 + +# asset config +asset_create_fraction: 0.001 +asset_optin_fraction: 0.1 +asset_close_fraction: 0.05 +asset_xfer_fraction: 0.849 +asset_delete_fraction: 0 diff --git a/tools/block-generator/scenarios/config.payment.full.yml b/tools/block-generator/scenarios/config.payment.full.yml new file mode 100644 index 0000000000..22aa0e9701 --- /dev/null +++ b/tools/block-generator/scenarios/config.payment.full.yml @@ -0,0 +1,14 @@ +name: "Pay (full)" +genesis_accounts: 10 +genesis_account_balance: 1000000000000000 +tx_per_block: 5000 + +# transaction distribution +tx_pay_fraction: 1.0 + +# payment config +pay_acct_create_fraction: 0.02 +pay_xfer_fraction: 0.98 + +# asset config must be 1.0 +asset_create_fraction: 1.0 diff --git a/tools/block-generator/scenarios/config.payment.jumbo.yml b/tools/block-generator/scenarios/config.payment.jumbo.yml new file mode 100644 index 0000000000..2d1f0dba2f --- /dev/null +++ b/tools/block-generator/scenarios/config.payment.jumbo.yml @@ -0,0 +1,14 @@ +name: "Pay (jumbo)" +genesis_accounts: 10 +genesis_account_balance: 1000000000000000 +tx_per_block: 19999 + +# transaction distribution +tx_pay_fraction: 1.0 + +# payment config +pay_acct_create_fraction: 0.02 +pay_xfer_fraction: 0.98 + +# asset config must be 1.0 +asset_create_fraction: 1.0 diff --git a/tools/block-generator/scenarios/config.payment.small.yml b/tools/block-generator/scenarios/config.payment.small.yml new file mode 100644 index 0000000000..e250b2f016 --- /dev/null +++ b/tools/block-generator/scenarios/config.payment.small.yml @@ -0,0 +1,14 @@ +name: "Pay (small)" +genesis_accounts: 10 +genesis_account_balance: 1000000000000000 +tx_per_block: 100 + +# transaction distribution +tx_pay_fraction: 1.0 + +# payment config +pay_acct_create_fraction: 0.02 +pay_xfer_fraction: 0.98 + +# asset config must be 1.0 +asset_create_fraction: 1.0 diff --git a/tools/block-generator/test_config.yml b/tools/block-generator/test_config.yml new file mode 100644 index 0000000000..6d411e9ad4 --- /dev/null +++ b/tools/block-generator/test_config.yml @@ -0,0 +1,23 @@ +name: Test Config +# genesis accounts +genesis_accounts: 10 +genesis_account_balance: 40000000000000 + +tx_per_block: 10 + +# every 100 payment transactions, create an account +tx_pay_fraction: 0.5 +tx_asset_fraction: 0.5 + +# transaction distribution +pay_acct_create_fraction: 0.5 +pay_xfer_fraction: 0.5 + +# asset distribution +asset_create_fraction: 0.25 +asset_optin_fraction: 0.25 +asset_close_fraction: 0.25 +asset_xfer_fraction: 0.25 +asset_destroy_fraction: 0.0 + + diff --git a/tools/block-generator/util/util.go b/tools/block-generator/util/util.go new file mode 100644 index 0000000000..ad7c73baa5 --- /dev/null +++ b/tools/block-generator/util/util.go @@ -0,0 +1,32 @@ +// Copyright (C) 2019-2023 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package util + +import ( + "fmt" + "os" +) + +// MaybeFail exits if there was an error. +func MaybeFail(err error, errfmt string, params ...interface{}) { + if err == nil { + return + } + fmt.Fprintf(os.Stderr, errfmt, params...) + fmt.Fprintf(os.Stderr, "\nError: %v\n", err) + os.Exit(1) +} From 7703bc405c763c3daea3a915e8441a3241ad2dd4 Mon Sep 17 00:00:00 2001 From: Hang Su <87964331+ahangsu@users.noreply.github.com> Date: Fri, 31 Mar 2023 17:37:29 -0400 Subject: [PATCH 026/125] Algod: New health endpoint (k8s `/ready` endpoint) (#4844) Add a `/ready` readiness probe endpoint for kubernetes deployments --- catchup/service.go | 22 +- daemon/algod/api/algod.oas2.json | 30 ++ daemon/algod/api/algod.oas3.yml | 28 ++ daemon/algod/api/client/restClient.go | 6 + daemon/algod/api/server/common/handlers.go | 60 +++ daemon/algod/api/server/common/routes.go | 7 + .../api/server/common/test/handlers_test.go | 88 +++++ .../algod/api/server/common/test/helpers.go | 139 +++++++ daemon/algod/api/server/lib/common.go | 6 +- .../api/server/v2/generated/data/routes.go | 32 +- .../v2/generated/experimental/routes.go | 16 +- .../nonparticipating/private/routes.go | 26 +- .../nonparticipating/public/routes.go | 152 ++++---- .../generated/participating/private/routes.go | 30 +- .../generated/participating/public/routes.go | 356 +++++++++--------- daemon/algod/api/server/v2/test/helpers.go | 1 - node/follower_node.go | 2 +- .../features/catchup/basicCatchup_test.go | 25 +- .../catchup/catchpointCatchup_test.go | 155 ++++++++ .../features/followerNode/syncDeltas_test.go | 4 +- 20 files changed, 857 insertions(+), 328 deletions(-) create mode 100644 daemon/algod/api/server/common/test/handlers_test.go create mode 100644 daemon/algod/api/server/common/test/helpers.go diff --git a/catchup/service.go b/catchup/service.go index 21c104ea48..2178f92eec 100644 --- a/catchup/service.go +++ b/catchup/service.go @@ -83,7 +83,7 @@ type Service struct { deadlineTimeout time.Duration blockValidationPool execpool.BacklogPool - // suspendForCatchpointWriting defines whether we've ran into a state where the ledger is currently busy writing the + // suspendForCatchpointWriting defines whether we've run into a state where the ledger is currently busy writing the // catchpoint file. If so, we want to suspend the catchup process until the catchpoint file writing is complete, // and resume from there without stopping the catchup timer. suspendForCatchpointWriting bool @@ -233,10 +233,10 @@ func (s *Service) innerFetch(r basics.Round, peer network.Peer) (blk *bookkeepin // fetchAndWrite fetches a block, checks the cert, and writes it to the ledger. Cert checking and ledger writing both wait for the ledger to advance if necessary. // Returns false if we should stop trying to catch up. This may occur for several reasons: -// - If the context is canceled (e.g. if the node is shutting down) -// - If we couldn't fetch the block (e.g. if there are no peers available or we've reached the catchupRetryLimit) -// - If the block is already in the ledger (e.g. if agreement service has already written it) -// - If the retrieval of the previous block was unsuccessful +// - If the context is canceled (e.g. if the node is shutting down) +// - If we couldn't fetch the block (e.g. if there are no peers available, or we've reached the catchupRetryLimit) +// - If the block is already in the ledger (e.g. if agreement service has already written it) +// - If the retrieval of the previous block was unsuccessful func (s *Service) fetchAndWrite(r basics.Round, prevFetchCompleteChan chan bool, lookbackComplete chan bool, peerSelector *peerSelector) bool { // If sync-ing this round is not intended, don't fetch it if dontSyncRound := s.GetDisableSyncRound(); dontSyncRound != 0 && r >= basics.Round(dontSyncRound) { @@ -258,10 +258,10 @@ func (s *Service) fetchAndWrite(r basics.Round, prevFetchCompleteChan chan bool, loggedMessage := fmt.Sprintf("fetchAndWrite(%d): block retrieval exceeded retry limit", r) if _, initialSync := s.IsSynchronizing(); initialSync { // on the initial sync, it's completly expected that we won't be able to get all the "next" blocks. - // Therefore info should suffice. + // Therefore, info should suffice. s.log.Info(loggedMessage) } else { - // On any subsequent sync, we migth be looking for multiple rounds into the future, so it's completly + // On any subsequent sync, we might be looking for multiple rounds into the future, so it's completely // reasonable that we would fail retrieving the future block. // Generate a warning here only if we're failing to retrieve X+1 or below. // All other block retrievals should not generate a warning. @@ -294,7 +294,7 @@ func (s *Service) fetchAndWrite(r basics.Round, prevFetchCompleteChan chan bool, s.log.Debugf("fetchAndWrite(%v): Could not fetch: %v (attempt %d)", r, err, i) peerSelector.rankPeer(psp, peerRankDownloadFailed) // we've just failed to retrieve a block; wait until the previous block is fetched before trying again - // to avoid the usecase where the first block doesn't exists and we're making many requests down the chain + // to avoid the usecase where the first block doesn't exist, and we're making many requests down the chain // for no reason. if !hasLookback { select { @@ -479,7 +479,7 @@ func (s *Service) pipelinedFetch(seedLookback uint64) { go func() { defer wg.Done() for t := range taskCh { - completed <- t() // This write to completed comes after a read from taskCh, so the invariant is preserved. + completed <- t() // This write comes after a read from taskCh, so the invariant is preserved. } }() } @@ -632,10 +632,10 @@ func (s *Service) periodicSync() { } // Syncs the client with the network. sync asks the network for last known block and tries to sync the system -// up the to the highest number it gets. +// up to the highest number it gets. func (s *Service) sync() { // Only run sync once at a time - // Store start time of sync - in NS so we can compute time.Duration (which is based on NS) + // Store start time of sync - in NS, so we can compute time.Duration (which is based on NS) start := time.Now() timeInNS := start.UnixNano() diff --git a/daemon/algod/api/algod.oas2.json b/daemon/algod/api/algod.oas2.json index fc8ae526cf..9c82d14c77 100644 --- a/daemon/algod/api/algod.oas2.json +++ b/daemon/algod/api/algod.oas2.json @@ -47,6 +47,36 @@ } } }, + "/ready": { + "get": { + "tags": [ + "public", + "common" + ], + "produces": [ + "application/json" + ], + "scheme": [ + "http" + ], + "summary": "Returns OK if healthy and fully caught up.", + "operationId": "GetReady", + "responses": { + "200": { + "description": "OK." + }, + "500": { + "description": "Internal Error" + }, + "503": { + "description": "Node not ready yet" + }, + "default": { + "description": "Unknown Error" + } + } + } + }, "/metrics": { "get": { "tags": [ diff --git a/daemon/algod/api/algod.oas3.yml b/daemon/algod/api/algod.oas3.yml index b8ae23ef22..687c0c8a6a 100644 --- a/daemon/algod/api/algod.oas3.yml +++ b/daemon/algod/api/algod.oas3.yml @@ -2126,6 +2126,34 @@ ] } }, + "/ready": { + "get": { + "operationId": "GetReady", + "responses": { + "200": { + "content": {}, + "description": "OK." + }, + "500": { + "content": {}, + "description": "Internal Error" + }, + "503": { + "content": {}, + "description": "Node not ready yet" + }, + "default": { + "content": {}, + "description": "Unknown Error" + } + }, + "summary": "Returns OK if healthy and fully caught up.", + "tags": [ + "public", + "common" + ] + } + }, "/swagger.json": { "get": { "description": "Returns the entire swagger spec in json.", diff --git a/daemon/algod/api/client/restClient.go b/daemon/algod/api/client/restClient.go index c9bdb0a833..65c895c50d 100644 --- a/daemon/algod/api/client/restClient.go +++ b/daemon/algod/api/client/restClient.go @@ -291,6 +291,12 @@ func (client RestClient) HealthCheck() error { return client.get(nil, "/health", nil) } +// ReadyCheck does a readiness check on the potentially running node, +// returning an error if the node is not ready (caught up and healthy) +func (client RestClient) ReadyCheck() error { + return client.get(nil, "/ready", nil) +} + // StatusAfterBlock waits for a block to occur then returns the StatusResponse after that block // blocks on the node end // Not supported diff --git a/daemon/algod/api/server/common/handlers.go b/daemon/algod/api/server/common/handlers.go index 0aa107f427..95a1dbcce1 100644 --- a/daemon/algod/api/server/common/handlers.go +++ b/daemon/algod/api/server/common/handlers.go @@ -18,14 +18,17 @@ package common import ( "encoding/json" + "fmt" "net/http" "github.com/labstack/echo/v4" + "github.com/algorand/go-algorand/agreement" "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/daemon/algod/api" "github.com/algorand/go-algorand/daemon/algod/api/server/lib" "github.com/algorand/go-algorand/daemon/algod/api/spec/common" + "github.com/algorand/go-algorand/node" ) // GenesisJSON is an httpHandler for route GET /genesis @@ -89,6 +92,63 @@ func HealthCheck(ctx lib.ReqContext, context echo.Context) { json.NewEncoder(w).Encode(nil) } +// Ready is a httpHandler for route GET /ready +// it serves "readiness" probe on if the node is healthy and fully caught-up. +func Ready(ctx lib.ReqContext, context echo.Context) { + // swagger:operation GET /ready Ready + //--- + // Summary: Returns OK if healthy and fully caught up. + // Produces: + // - application/json + // Schemes: + // - http + // Responses: + // 200: + // description: OK. + // 500: + // description: Internal Error. + // 503: + // description: Node not ready yet. + // default: { description: Unknown Error } + w := context.Response().Writer + w.Header().Set("Content-Type", "application/json") + + stat, err := ctx.Node.Status() + code := http.StatusOK + + // isReadyFromStat checks the `Node.Status()` result + // and decide if the node is at the latest round + // must satisfy following sub conditions: + // 1. the node is not in a fast-catchup stage + // 2. the node's time since last round should be [0, deadline), + // while deadline = bigLambda + smallLambda = 17s + // 3. the node's catchup time is 0 + isReadyFromStat := func(status node.StatusReport) bool { + timeSinceLastRound := status.TimeSinceLastRound().Milliseconds() + + return len(status.Catchpoint) == 0 && + timeSinceLastRound >= 0 && + timeSinceLastRound < agreement.DeadlineTimeout().Milliseconds() && + status.CatchupTime.Milliseconds() == 0 + } + + if err != nil { + code = http.StatusInternalServerError + ctx.Log.Error(err) + } else if stat.StoppedAtUnsupportedRound { + code = http.StatusInternalServerError + err = fmt.Errorf("stopped at an unsupported round") + ctx.Log.Error(err) + } else if !isReadyFromStat(stat) { + code = http.StatusServiceUnavailable + err = fmt.Errorf("ready failed as the node is catching up") + ctx.Log.Info(err) + } + + w.WriteHeader(code) + _ = json.NewEncoder(w).Encode(nil) +} + // VersionsHandler is an httpHandler for route GET /versions func VersionsHandler(ctx lib.ReqContext, context echo.Context) { // swagger:route GET /versions GetVersion diff --git a/daemon/algod/api/server/common/routes.go b/daemon/algod/api/server/common/routes.go index 545d92bb27..1a78fe00ac 100644 --- a/daemon/algod/api/server/common/routes.go +++ b/daemon/algod/api/server/common/routes.go @@ -41,6 +41,13 @@ var Routes = lib.Routes{ HandlerFunc: HealthCheck, }, + lib.Route{ + Name: "ready", + Method: "GET", + Path: "/ready", + HandlerFunc: Ready, + }, + lib.Route{ Name: "swagger.json", Method: "GET", diff --git a/daemon/algod/api/server/common/test/handlers_test.go b/daemon/algod/api/server/common/test/handlers_test.go new file mode 100644 index 0000000000..c602b5bde0 --- /dev/null +++ b/daemon/algod/api/server/common/test/handlers_test.go @@ -0,0 +1,88 @@ +// Copyright (C) 2019-2023 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package test + +import ( + "fmt" + "net/http" + "net/http/httptest" + "testing" + + "github.com/labstack/echo/v4" + "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/daemon/algod/api/server/common" + "github.com/algorand/go-algorand/daemon/algod/api/server/lib" + "github.com/algorand/go-algorand/logging" + "github.com/algorand/go-algorand/node" + "github.com/algorand/go-algorand/test/partitiontest" +) + +func mockNodeStatusInRangeHelper( + t *testing.T, statusCode MockNodeCatchupStatus, + expectedErr error, expectedStatus node.StatusReport) { + mockNodeInstance := makeMockNode(statusCode) + status, err := mockNodeInstance.Status() + if expectedErr != nil { + require.Error(t, err, expectedErr) + } else { + require.Equal(t, expectedStatus, status) + } +} + +func TestMockNodeStatus(t *testing.T) { + partitiontest.PartitionTest(t) + + mockNodeStatusInRangeHelper( + t, CaughtUpAndReady, nil, cannedStatusReportCaughtUpAndReadyGolden) + mockNodeStatusInRangeHelper( + t, CatchingUpFast, nil, cannedStatusReportCatchingUpFastGolden) + mockNodeStatusInRangeHelper( + t, StoppedAtUnsupported, nil, cannedStatusReportStoppedAtUnsupportedGolden) + mockNodeStatusInRangeHelper( + t, 399, fmt.Errorf("catchup status out of scope error"), node.StatusReport{}) +} + +func readyEndpointTestHelper( + t *testing.T, node *mockNode, expectedCode int) { + reqCtx := lib.ReqContext{ + Node: node, + Log: logging.NewLogger(), + Shutdown: make(chan struct{}), + } + + e := echo.New() + req := httptest.NewRequest(http.MethodGet, "/", nil) + rec := httptest.NewRecorder() + c := e.NewContext(req, rec) + + common.Ready(reqCtx, c) + require.Equal(t, expectedCode, rec.Code) +} + +func TestReadyEndpoint(t *testing.T) { + partitiontest.PartitionTest(t) + + mockNodeInstance := makeMockNode(CaughtUpAndReady) + readyEndpointTestHelper(t, mockNodeInstance, http.StatusOK) + + mockNodeInstance.catchupStatus = CatchingUpFast + readyEndpointTestHelper(t, mockNodeInstance, http.StatusServiceUnavailable) + + mockNodeInstance.catchupStatus = StoppedAtUnsupported + readyEndpointTestHelper(t, mockNodeInstance, http.StatusInternalServerError) +} diff --git a/daemon/algod/api/server/common/test/helpers.go b/daemon/algod/api/server/common/test/helpers.go new file mode 100644 index 0000000000..27d534ad53 --- /dev/null +++ b/daemon/algod/api/server/common/test/helpers.go @@ -0,0 +1,139 @@ +// Copyright (C) 2019-2023 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package test + +import ( + "fmt" + + "github.com/stretchr/testify/mock" + + "github.com/algorand/go-algorand/crypto" + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/node" + "github.com/algorand/go-algorand/protocol" +) + +var cannedStatusReportCaughtUpAndReadyGolden = node.StatusReport{ + LastRound: basics.Round(1), + LastVersion: protocol.ConsensusCurrentVersion, + NextVersion: protocol.ConsensusCurrentVersion, + NextVersionRound: basics.Round(2), + NextVersionSupported: true, + StoppedAtUnsupportedRound: false, + Catchpoint: "", + CatchpointCatchupAcquiredBlocks: 0, + CatchpointCatchupProcessedAccounts: 0, + CatchpointCatchupVerifiedAccounts: 0, + CatchpointCatchupTotalAccounts: 0, + CatchpointCatchupTotalKVs: 0, + CatchpointCatchupProcessedKVs: 0, + CatchpointCatchupVerifiedKVs: 0, + CatchpointCatchupTotalBlocks: 0, + LastCatchpoint: "", + CatchupTime: 0, +} + +var cannedStatusReportCatchingUpFastGolden = node.StatusReport{ + LastRound: basics.Round(97000), + LastVersion: protocol.ConsensusCurrentVersion, + NextVersion: protocol.ConsensusCurrentVersion, + NextVersionRound: 200000, + NextVersionSupported: true, + StoppedAtUnsupportedRound: false, + Catchpoint: "5894690#DVFRZUYHEFKRLK5N6DNJRR4IABEVN2D6H76F3ZSEPIE6MKXMQWQA", + CatchpointCatchupAcquiredBlocks: 0, + CatchpointCatchupProcessedAccounts: 0, + CatchpointCatchupVerifiedAccounts: 0, + CatchpointCatchupTotalAccounts: 0, + CatchpointCatchupTotalKVs: 0, + CatchpointCatchupProcessedKVs: 0, + CatchpointCatchupVerifiedKVs: 0, + CatchpointCatchupTotalBlocks: 0, + LastCatchpoint: "", + UpgradePropose: "upgradePropose", + UpgradeApprove: false, + UpgradeDelay: 0, + NextProtocolVoteBefore: 100000, + NextProtocolApprovals: 5000, + CatchupTime: 10000, +} + +var cannedStatusReportStoppedAtUnsupportedGolden = node.StatusReport{ + LastRound: basics.Round(97000), + LastVersion: protocol.ConsensusCurrentVersion, + NextVersion: protocol.ConsensusCurrentVersion, + NextVersionRound: 200000, + NextVersionSupported: true, + StoppedAtUnsupportedRound: true, + Catchpoint: "", + CatchpointCatchupAcquiredBlocks: 0, + CatchpointCatchupProcessedAccounts: 0, + CatchpointCatchupVerifiedAccounts: 0, + CatchpointCatchupTotalAccounts: 0, + CatchpointCatchupTotalKVs: 0, + CatchpointCatchupProcessedKVs: 0, + CatchpointCatchupVerifiedKVs: 0, + CatchpointCatchupTotalBlocks: 0, + LastCatchpoint: "", + UpgradePropose: "upgradePropose", + UpgradeApprove: false, + UpgradeDelay: 0, + NextProtocolVoteBefore: 100000, + NextProtocolApprovals: 5000, + CatchupTime: 0, +} + +// MockNodeCatchupStatus enumerates over possible mock status of a mock node in testing +type MockNodeCatchupStatus uint + +const ( + // CaughtUpAndReady stands for testing mock node is finishing catching up, /ready should return 200 + CaughtUpAndReady = iota + // CatchingUpFast stands for mock node is mocking fast catch up state, /ready should return 400 + CatchingUpFast + // StoppedAtUnsupported stands for mock node stopped at unsupported round, /ready should return 500 + StoppedAtUnsupported +) + +// mockNode is the "node" we use in common endpoint testing, implements NodeInterface +type mockNode struct { + mock.Mock + catchupStatus MockNodeCatchupStatus +} + +// makeMockNode creates a mock common node for ready endpoint testing. +func makeMockNode(catchupStatus MockNodeCatchupStatus) *mockNode { + return &mockNode{catchupStatus: catchupStatus} +} + +func (m *mockNode) Status() (s node.StatusReport, err error) { + switch m.catchupStatus { + case CaughtUpAndReady: + s = cannedStatusReportCaughtUpAndReadyGolden + case CatchingUpFast: + s = cannedStatusReportCatchingUpFastGolden + case StoppedAtUnsupported: + s = cannedStatusReportStoppedAtUnsupportedGolden + default: + err = fmt.Errorf("catchup status out of scope error") + } + return +} + +func (m *mockNode) GenesisID() string { panic("not implemented") } + +func (m *mockNode) GenesisHash() crypto.Digest { panic("not implemented") } diff --git a/daemon/algod/api/server/lib/common.go b/daemon/algod/api/server/lib/common.go index a04d85a951..e44e624c1b 100644 --- a/daemon/algod/api/server/lib/common.go +++ b/daemon/algod/api/server/lib/common.go @@ -23,6 +23,7 @@ import ( "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/logging" + "github.com/algorand/go-algorand/node" ) // GenesisJSONText is initialized when the node starts. @@ -32,6 +33,7 @@ var GenesisJSONText string type NodeInterface interface { GenesisHash() crypto.Digest GenesisID() string + Status() (s node.StatusReport, err error) } // HandlerFunc defines a wrapper for http.HandlerFunc that includes a context @@ -57,8 +59,8 @@ type ReqContext struct { Shutdown <-chan struct{} } -// ErrorResponse sets the specified status code (should != 200), and fills in the -// a human readable error. +// ErrorResponse sets the specified status code (should != 200), and fills in +// a human-readable error. func ErrorResponse(w http.ResponseWriter, status int, internalErr error, publicErr string, logger logging.Logger) { logger.Info(internalErr) diff --git a/daemon/algod/api/server/v2/generated/data/routes.go b/daemon/algod/api/server/v2/generated/data/routes.go index 3630513e70..3400c168d6 100644 --- a/daemon/algod/api/server/v2/generated/data/routes.go +++ b/daemon/algod/api/server/v2/generated/data/routes.go @@ -299,22 +299,22 @@ var swaggerSpec = []string{ "L54vHmePnz5ePH389Itnz9MnTx8tnn7x/G/3jBwyKFtEZz5xf/Z/8F2/5OTNaXJukG1oQktWv8lu2Ng/", "TkRT3IlQUJbPjv1P/9vvsINUFA14/+vM5QXP1lqX6vjwcLPZHIRdDlfojkq0qNL1oR+n/xb2m9M6t8te", "ynFFbdqON7Z4VjjBb2+/OTsnJ29OD4K3Vo9nRwdHB4/wKc4SOC3Z7Hj2BH/C3bPGdT90zDY7/ng1nx2u", - "geYYvWH+KEBLlvpPakNXK5AH7pUm89Pl40OvShx+dK64q7Fvh2HB88OPLY9ltqcnFkQ+/Ojr/Iy3bhXS", - "cZ7aoMNELMaaHS4wfXhqU1BB4+Gp4AVDHX5EFXnw90OXUxn/iFcVuwcOvVs/3rJFpY96a3Dt9EipTtdV", - "efgR/4M8GaBlI5gDdGerWHmx70D79EPbw8Xn1YF5NW+fZrZ5L17MVfCyJU2P30170QH8cOammIFirswb", - "SgmzBZpN7FOVGhGtZQVh+c2xQjVX88jjp0u2qmTnUefOc9GEKfKfZz++JkISdyd+Q9OLOnKPnC5teRgp", - "LhkmNWVBJpzpWU/ntwrkrpmPOy7DCfgnI1yqWKFWZTuvolbFP2DtDUQUhcTjo6M7e7att7JX1txfg/N4", - "3QZiT6a+8Cdliw2NbHx69OjO5tYO1771xLrgerM65RgtZSQ/sScbTujpX3ZCL/AGbTh+yXhm39zQFPe0", - "3aA4vy//svPTrPA+WI7PF4FCBeDZHW6wT8+ERg+nOcGWdjZP/rKzOQN5yVIg51CUQlLJ8h35idf5yUFN", - "sv4x9xO/4GLDPSGMol0VBZU7dwRS0hVV/hiwx2HwgqTR8OhKoYsYq8DP5jOXfAjbEiQrgGNdkyt3Erd+", - "rc9nK+0O7bPk/Z933KUQ5hALyPuJK7BmV187YMfToUMbG5/tePq2Pkl7Jwju3j+O0/uLWeOLMgUjtv5g", - "kT9NRj/7lFT4tBv0k+2ot1CIS1D1S9wNcxoty9xc7KPcUhQBDx8M7qwPeO2Pa6/OG9EfyXtiGuA9VXbP", - "nrjpq9Yj8XiT8NwTQGvBT3kEuH5kt5OwY4e6F1ug2b8Ewb8EwR0KAl1JPrhFg/MLg8qhtPFyJKXpGsbk", - "Qf+0DG+6pYhV7DkbERauTsmQrDhry4o/9X33w5/ifH9Bud/PrRW3UYxU5gxkzQWU90vH/EsK/I+RArYG", - "lrMpzYmGPFfh3tcC974NJXC5QtyGeEyUA90nYmM/H35sP1HUMu6pdaUzsQn6okPYRjP0bX71o52tvw83", - "lOlkKaTLE8IS0v3OGmh+6CrgdH5tks57XzCTPvgxDG6L/npYV+iPfuwaXmNfneFxoJGvX+Y/N46X0JGB", - "ErJ2Ybz7YOQT1n91wrOxyx8fHmLs/VoofTi7mn/s2OzDjx9qlvCFAWelZJdYZ+DD1f8PAAD//5glD8aS", - "xwAA", + "geYYvWH+KEBLlvpPEmi2c/9XG7pagTxwLzaZny4fH3q14vCjc8tdjX07DIufH35seS+zPT2xOPLhR1/z", + "Z7x1q6iO89oGHSZiMdbscIGpxFObggoaD08FLxvq8COqy4O/H7r8yvhHvLbY/XDoXfzxli0qfdRbg2un", + "R0p1uq7Kw4/4H+TPAC0bzRygO1vFSo19B9qnItoeLlavDtKr+fw0s817sWOumpctb3r8btrrDuCHM7fG", + "DBRzJd9QYpjt0Gxon7bUiGstKwhLcY4VrbmaRx5CXbJVJTsPPHeejiZMkf88+/E1EZK4+/Ebml7UUXzk", + "dGlLxUhxyTDBKQuy4kzPejq/VSB3zXzc0RlOwD8f4dLGCrUq2zkWtVr+AetwIKIoMB4fHd3ZE269lb2y", + "pv8anMfrNhB78vWFPzVbbGjk5NOjR3c2t3bo9q0n1gXXm9Upx8gpcwoQe8rhhJ7+ZSf0Am/ThuOXjGf2", + "/Q1NcU/bDYrz+/IvOz/NCu+P5fiUEShUBp7d4Qb79ExodHKaE2xpZ/PkLzubM5CXLAVyDkUpJJUs35Gf", + "eJ2rHNQn6x9zP/ELLjbcE8Io3VVRULlzRyAlXVHljwF7HAavSRptj64UuouxIvxsPnOJiLAtQbICONY4", + "uXIncevX+ny20u7QPlHe/3nHXTphDrHgvJ+4AmuC9XUEdjwdOrSx8dmOp2/rk7R3guDu/eM4vb+YNb4o", + "UzB66w8W+dNk9LNPSYVPu0E/2Y56C4W4BFW/yt0wp9GyzC3GPtAtRRHw8MHgzvqAJoC49uo8E/2RvFem", + "Ad5TZffsiZu+cD0SmzcJzz3BtBb8lAeB6wd3O8k7dqh7sQWa/UsQ/EsQ3KEg0JXkg1s0OL8wwBxKGztH", + "UpquYUwe9E/L8KZbilj1nrMRYeFqlgzJirO2rPhT33c//CnO9xeU+/3cWnEb0UhlzkDWXEB5v4zMv6TA", + "/xgpYOthOZvSnGjIcxXufS1w79uwApc3xG24x0Q50H0uNvbz4cf2c0Ut455aVzoTm6AvOodtZEPf5lc/", + "4Nn6+3BDmU6WQrqcISwn3e+sgeaHrhpO59cmAb33BbPqgx/DQLfor4d1tf7ox67hNfbVGR4HGvlaZv5z", + "44QJnRooIWt3xrsPRj5hLVgnPBsb/fHhIcbhr4XSh7Or+ceO/T78+KFmCV8kcFZKdok1Bz5c/f8AAAD/", + "/12EssOexwAA", } // 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 dbd1717c60..5978473978 100644 --- a/daemon/algod/api/server/v2/generated/experimental/routes.go +++ b/daemon/algod/api/server/v2/generated/experimental/routes.go @@ -229,14 +229,14 @@ var swaggerSpec = []string{ "Fy8XT7Onz58unj99/uKrl+mz508Wz1+8/MsDI4cMyhbRmU/cn/1vfNcvOXl3mpwbZBua0JLVb7IbNvaP", "E9EUdyIUlOWzY//T//Q77CAVRQPe/zpzecGztdalOj483Gw2B2GXwxW6oxItqnR96Mfpv4X97rTO7bKX", "clxRm7bjjS2eFU7w2/tvz87JybvTg+Ct1ePZ0cHRwRN8irMETks2O549w59w96xx3Q8ds82OP13PZ4dr", - "oDlGb5g/CtCSpf6T2tDVCuSBe6XJ/HT19NCrEoefnCvueuzbYVjw/PBTy2OZ7emJBZEPP/k6P+OtW4V0", - "nKc26DARi7FmhwtMH57aFFTQeHgqeMFQh59QRR78/dDlVMY/4lXF7oFD79aPt2xR6ZPeGlw7PVKq03VV", - "Hn7C/yBPBmjZCOY+urAtQTKjZNkAhpUtOlaz8Gk2O559GzR6tYb0EktsWyMD8ubTo6NIWkXQi9itQhc5", - "ZIbPnx89n9CBCx12csVg+h1/5pdcbDjBIFwrN6uioHKH+oiuJFfkpx8IWxLoDsGUHwH3Kl0pNPZjPd/Z", - "fNYiz8drRzSboHlo35BtaOl/3vE0+mOf+t23TGI/H35q19JtcaFaVzoTm6Av3lzstbs/Xv26ROvvww1l", - "2ugiLqAFax31O2ug+aFL1er82kRH975gyHfwY2iFjf56WJeSi37sSojYV7dDBhr5RFv/udEQwhN3dvwh", - "OGs/fLz+aL5J0xo/NQfI8eEhOonXQunD2fX8U+dwCT9+rHnMZ7DPSsmuMCD+4/X/CwAA//9Ow4gVO7YA", - "AA==", + "oDlGb5g/CtCSpf6TBJrt3P/Vhq5WIA/ci03mp6unh16tOPzk3HLXY98Ow+Lnh59a3stsT08sjnz4ydf8", + "GW/dKqrjvLZBh4lYjDU7XGAq8dSmoILGw1PBy4Y6/ITq8uDvhy6/Mv4Rry12Pxx6F3+8ZYtKn/TW4Nrp", + "kVKdrqvy8BP+B/kzQMtGM/fRhW0JkhmFywYzrGwBspqdT7PZ8ezboNGrNaSXWG7bGhyQT58eHUVSLIJe", + "xG4busghMzz//Oj5hA5c6LCTKwzT7/gzv+RiwwkG5FoZWhUFlTvUTXQluSI//UDYkkB3CKb8CLhv6Uqh", + "4R9r+87msxZ5Pl47otlkzUP7nmxDS//zjqfRH/vU775rEvv58FO7rm6LC9W60pnYBH3xFmOv4P3x6pcm", + "Wn8fbijTRi9xwS1Y96jfWQPND13aVufXJlK69wXDv4MfQ4ts9NfDuqxc9GNXQsS+uh0y0Mgn3frPjbYQ", + "nr6z4w/Bufvh4/VH802a1vipOUyODw/RYbwWSh/OruefOgdN+PFjzWM+m31WSnaFwfEfr/9fAAAA//8X", + "+h8qR7YAAA==", } // GetSwagger returns the content of the embedded swagger specification file 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 5a78c685d7..4644cf9f12 100644 --- a/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go +++ b/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go @@ -284,19 +284,19 @@ var swaggerSpec = []string{ "5y9mj7PHTx/Pnj5++vzZi/TJ00ezp89f/PWelUMWZUJ04hP3J/8X3/VLTt6eJucW2YYmvBD1m+yWjf3j", "RDzFnQgrLvLJsf/pf/sddpCqVQPe/zpxecGTpTGFPj48vLq6Ogi7HC7QHZUYVaXLQz9O/y3st6d1bhdd", "ynFFKW3HG1s8K5zgt3ffnJ2zk7enB8Fbq8eTo4Ojg0f4FGcBkhdicjx5gj/h7lniuh86Zpscf7qeTg6X", - "wHOM3rB/rMCUIvWf9BVfLKA8cK802Z8uHx96VeLwk3PFXW/7dhgWPD/81PJYZjt6YkHkw0++zs/21q1C", - "Os5TG3QYicW2ZoczTB8e2xR00Hh4KnjB0IefUEUe/P3Q5VTGP+JVhfbAoXfrx1u2qPTJrC2unR4pN+my", - "Kg4/4X+QJ69JSOQQc+JTKiJnTfMpE4bxmSqxwI5Jl1Yu+MoeQgctJ8ipxOSnmWVu2+slYeBreFFR0+P3", - "fZMJAmIeEkoCy+bNRm2N1MhiU1YQ1tmsT5pW++a8eX+UvPj46dH00dH1X+x54v589uR6ZDTOyxouO6sP", - "i5ENP2JZDDTE4P59fHR0iwdjT2RAflqk4F3iXqUpWolhq61bqg4gVhNjR/p+B3zsBbrr6eTpnjPeaj9q", - "xVdHXpL7mmfMZ+fi2I8+39inEmOhrFxndG5dTyfPPufsT6VleZ4zbBnUY+ov/U/yQqor6VtaJaNarXi5", - "8dtYt4QCc4uNRxlfaPSFleKSo24nlWw9MjP5iP7XWIb0gLzRht9A3pzZXv+WN59L3uAi3YW8aQO6Y3nz", - "eM89/+ef8b8l7J9Nwp6RuLuVhHUKHyWl9TVQWBdQihVIKo3lfqWiEof07n3/541Moz/2wXffX4v9fPip", - "Xf+/pTnrZWUydUUVT6JHBRa55bmriIem0fqaZRTzAJpAbfajS6TKN2gPFhkwjhUeVGWae7DtXHvIa0+F", - "hdC8zbgQEgdAkzOOQqUfeRACqSFVkl4y6xxLDrM3KoP+sYQHz28VlJvm5HE4TqYtueQYK1Jo8dZivi9G", - "rvdjOzSNk1+nzxz182Wtvw+vuDD28HIR00jRfmcDPD90tQA6vzbpd70vmFMY/Bi6+aO/Hta1iqMfu1fQ", - "2Fd3BRto5Cu5+M+NCSo06SBL1Mac9x/tymIlPMctjYXi+PAQoxCXSpvDyfX0U8d6EX78WC+mL5FUL+r1", - "x+v/CQAA//8iVBu0nMAAAA==", + "wHOM3rB/rMCUIvWfSuDZxv1fX/HFAsoD92KT/eny8aFXKw4/Obfc9bZvh2Hx88NPLe9ltqMnFkc+/ORr", + "/mxv3Sqq47y2QYeRWGxrdjjDVOKxTUEHjYengpcNffgJ1eXB3w9dfmX8I15baD8cehd/vGWLSp/M2uLa", + "6ZFyky6r4vAT/gf585oERg4xhz6lJXLWNJ8yYRifqRKL7Zh0aWWEr/IhdNByglxLDH+aWUa3vV4SBr6e", + "FxU4PX7fN58gIOYhoVSwLN9s2tZIjVw2ZQVhzc361Gm1b86e90fJi4+fHk0fHV3/xZ4t7s9nT65HRua8", + "rOGys/rgGNnwI5bIQKMM7uXHR0e3eDz2RAbkp0UK3ijuVZ2ilRi24Lql6gBiNTF2pPJ3wMdeo7ueTp7u", + "OeOttqRWrHXkVbmvecZ8pi6O/ejzjX0qMS7KynhGZ9j1dPLsc87+VFqW5znDlkFtpv7S/yQvpLqSvqVV", + "OKrVipcbv411Sygwt9h4rPGFRr9YKS456nlSydaDM5OP6IuNZUsPyBtt+A3kzZnt9W9587nkDS7SXcib", + "NqA7ljeP99zzf/4Z/1vC/tkk7BmJu1tJWKfwUYJaXwOFdQGlWIGkMlnuVyowcUhv4Pd/3sg0+mMffPct", + "ttjPh5/abwG0NGe9rEymrqj6SfSowIK3PHfV8dBMWl+5jGIeQBO0zX50SVX5Bm3DIgPGsdqDqkxzJ7ad", + "a2957bWwEJp3GhdC4gBofsZRqAwkD8IhNaRK0qtmnWPJYfZGZdA/lvDg+a2CctOcPA7HybQllxxjRYou", + "3lrM98XI9X5sh2Zy8vH0maN+yqz19+EVF8YeXi56Gina72yA54euLkDn1yYVr/cF8wuDH0OXf/TXw7pu", + "cfRj9woa++quYAONfFUX/7kxR4XmHWSJ2rDz/qNdWayK57ilsVYcHx5iROJSaXM4uZ5+6lgywo8f68X0", + "5ZLqRb3+eP0/AQAA//8BfTNVqMAAAA==", } // 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 05c0d6704b..92f483a663 100644 --- a/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go +++ b/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go @@ -726,82 +726,82 @@ var swaggerSpec = []string{ "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", - "Sjxv3///AQAA//9vBJZkzUsBAA==", + "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", + "j4xKsgusxPP2/f8fAAD//3fZs+vZSwEA", } // 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 0e5265896a..e36f4942e9 100644 --- a/daemon/algod/api/server/v2/generated/participating/private/routes.go +++ b/daemon/algod/api/server/v2/generated/participating/private/routes.go @@ -312,21 +312,21 @@ var swaggerSpec = []string{ "8pvni8fZ46ePF08fP/3m2fP0ydNHi6ffPP/TPcOHDMgW0JlP3J/9X3zXLzl5e5qcG2AbnNCS1W+yGzL2", "jxPRFE8iFJTls2P/0//2J+wgFUUzvP915vKCZ2utS3V8eHh1dXUQdjlcoTsq0aJK14d+nv5b2G9P69wu", "q5Tjjtq0HW9s8aRwgt/efXd2Tk7enh4Eb60ez44Ojg4e4VOcJXBastnx7An+hKdnjft+6Ihtdvzpej47", - "XAPNMXrD/FGAliz1n9QVXa1AHrhXmsxPl48PvShx+Mm54q7Hvh2GBc8PP7U8ltmOnlgQ+fCTr/Mz3rpV", - "SMd5aoMOE6EYa3a4wPThqU1BBY2Hl4IKhjr8hCLy4O+HLqcy/hFVFXsGDr1bP96yhaVPemNg7fRIqU7X", - "VXn4Cf+DNBmAZSOY++DCpgTJjJCFAQzuV5uBeGgfSe3/vOVp9Mf+8L3HOlYQTZ7ENEZaPyLfLxk8w7Nh", - "j9VphtxOdwN+bOVva/vAI/P46GivR8ymuQ+7YUb9+yPyaP7Iyq7ns6d7AjpqaWlFIkeAeUEz4vNYce5H", - "n2/uU45RQ4YDEsvhEYKnnw+Cdpn1H2FL3ghNvkdV5Ho+e/Y5d+KUG8GI5gRbBlWU+kfkZ37BxRX3LY1o", - "UBUFldvJx0fTlUJHlmSX1Almwcsbs4/oObW5ze2jdpJlPaK3IhIo/UJk2xGMFWpVuryjBmmNhMi4WUJf", - "xew/ZbqGSMSejSvxjhcuMpiFspuWFVzfkie0hWQDwmnEYoKmP3wLY+nrngWgRsPPuv5FO/Kkxxw7g9e1", - "6+un5L/ylK88peYpz46efL7pz0BeshTIORSlkFSyfEt+5nXW+I153EmWRWN220d/J48z2ncqMlgBTxwD", - "SxYi2/rKmK0JLsAqgz1B5vBTu7y9FQxnGeSgo/GI5vf64dH+IhZbcvqqJ+HYbl3O+2KLTYOy8cfvP1lt", - "yqgKjbLTBbHHGcOK5V3e9DHONcfI3ixkJTSxWMjcor4yoq+M6FbCzeTDM0W+iWoftiYL7d3Zc19eJVZY", - "i+o+KFN0lC96fO9k4/v6T0zfsbHPkJHgg/W/d9H8lUV8ZRG3YxE/QOQw4ql1TCNCdPvpQ1MZBgYvZd1H", - "pNCh4JtXOZVEwVQzxwmO6Iwbn4NrfG6lLoorq9NR3ryzF9nAu9XzvrK8ryzv34flnexmNG3B5Naa0QVs", - "C1rW+pBaVzoTV4F/AWGx8T59O3D9rG3r78MrynSyFNJl0mGR9X5nDTQ/dDWiOr82ZRl6X7DWRPBjGP4Z", - "/fWwfsMi+rHrmoh9dab5gUa+wp//3LgmQ1cfsvbayff+o2HLWCHZcf3Gc3V8eIjZKWuh9OHsev6p49UK", - "P36sSeBTfVc4Urj+eP0/AQAA///yZJv4tMoAAA==", + "XAPNMXrD/FGAliz1nyTQbOv+r67oagXywL3YZH66fHzoxYrDT84tdz327TAsfn74qeW9zHb0xOLIh598", + "zZ/x1q2iOs5rG3SYCMVYs8MFphJPbQoqaDy8FFQ21OEnFJcHfz90+ZXxj6i22PNw6F388ZYtLH3SGwNr", + "p0dKdbquysNP+B+kzwAsG83cBxc2JUhmBC4MZnC/2mzEQ/tgav/nLU+jP/aH7z3csYJoIiWmNNL6Qfl+", + "+eAZnhN7xE4z5Hy6G/xjq4BbOwgen8dHR3s9aDbNldgNOerfJZEH9EdWdj2fPd0T0FGrSysqOQLMC5oR", + "n9OKcz/6fHOfcowgMtyQWG6PEDz9fBC0S67/CFvyRmjyPaol1/PZs8+5E6fcCEk0J9gyqKjUPyI/8wsu", + "rrhvacSEqiio3E4+PpquFDq1JLukTkgLXuGYfUQvqs1zbh+1kyzrEb0Vl0DpFwLvnSGMFWpVuhykBmmN", + "tMi4WUJf3ew/a7qGSPSejTHxThguMpiFcpyWFVzfkie0BWYDwmnEeoJmQHwXY+lroAWgRkPRur5GO/Kk", + "hx07g9d17Otn5b/ylK88peYpz46efL7pz0BeshTIORSlkFSyfEt+5nUG+Y153EmWReN320d/J48zmngq", + "MlgBTxwDSxYi2/oqma0JLsAqhj1B5vBTu9S9FQxnGeSgo7GJ5vf6EdL+IhZbcvqqJ+HYbl3O+2KLTYMS", + "8sfvP1nNyqgNjeLTBbHHGcPq5V3e9DHONcfI3ixkJTSxWMjcor4yoq+M6FbCzeTDM0W+iWoftj4L7d3Z", + "c19qJVZki+o+KFN0lC96fO9k4/v6T0zfsXHQkJHgg/XFd9H8lUV8ZRG3YxE/QOQw4ql1TCNCdPvpQ1MZ", + "BgYyZd0HpdC54JtXOZVEwVQzxwmO6Iwbn4NrfG6lLoorq9NR3ry5F9nAu9XzvrK8ryzv34flnexmNG3B", + "5Naa0QVsC1rW+pBaVzoTV4F/AWGxsT99O3D9xG3r78MrynSyFNJl1WHB9X5nDTQ/dPWiOr82JRp6X7Du", + "RPBjGAoa/fWwfs8i+rHrmoh9dab5gUa+2p//3LgpQ7cfsvba4ff+o2HLWC3Zcf3Gi3V8eIiZKmuh9OHs", + "ev6p4+EKP36sSeBTfVc4Urj+eP0/AQAA//9LIy4uwMoAAA==", } // 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 446e3e3558..d510355028 100644 --- a/daemon/algod/api/server/v2/generated/participating/public/routes.go +++ b/daemon/algod/api/server/v2/generated/participating/public/routes.go @@ -177,184 +177,184 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "Q9kAAA==", + "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", + "AP//DNixnk/ZAAA=", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/test/helpers.go b/daemon/algod/api/server/v2/test/helpers.go index e8837e3f97..d0409eadac 100644 --- a/daemon/algod/api/server/v2/test/helpers.go +++ b/daemon/algod/api/server/v2/test/helpers.go @@ -149,7 +149,6 @@ func makeMockNode(ledger v2.LedgerForAPI, genesisID string, nodeError error, sta func (m *mockNode) LedgerForAPI() v2.LedgerForAPI { return m.ledger } - func (m *mockNode) Status() (node.StatusReport, error) { return m.status, nil } diff --git a/node/follower_node.go b/node/follower_node.go index 34dc48c241..46b9136785 100644 --- a/node/follower_node.go +++ b/node/follower_node.go @@ -389,7 +389,7 @@ func (node *AlgorandFollowerNode) SetCatchpointCatchupMode(catchpointCatchupMode outCtxCh = ctxCh go func() { node.mu.Lock() - // check that the node wasn't canceled. If it have been canceled, it means that the node.Stop() was called, in which case + // check that the node wasn't canceled. If it has been canceled, it means that the node.Stop() was called, in which case // we should close the channel. if node.ctx.Err() == context.Canceled { close(ctxCh) diff --git a/test/e2e-go/features/catchup/basicCatchup_test.go b/test/e2e-go/features/catchup/basicCatchup_test.go index 435393f70d..7663e54b3e 100644 --- a/test/e2e-go/features/catchup/basicCatchup_test.go +++ b/test/e2e-go/features/catchup/basicCatchup_test.go @@ -35,9 +35,6 @@ func TestBasicCatchup(t *testing.T) { partitiontest.PartitionTest(t) defer fixtures.ShutdownSynchronizedTest(t) - if testing.Short() { - t.Skip() - } t.Parallel() a := require.New(fixtures.SynchronizedTest(t)) @@ -48,12 +45,12 @@ func TestBasicCatchup(t *testing.T) { var fixture fixtures.RestClientFixture // Give the second node (which starts up last) all the stake so that its proposal always has better credentials, - // and so that its proposal isn't dropped. Otherwise the test burns 17s to recover. We don't care about stake + // and so that its proposal isn't dropped. Otherwise, the test burns 17s to recover. We don't care about stake // distribution for catchup so this is fine. fixture.Setup(t, filepath.Join("nettemplates", "TwoNodes100Second.json")) defer fixture.Shutdown() - // Get 2nd node so we wait until we know they're at target block + // Get 2nd node, so we wait until we know they're at target block nc, err := fixture.GetNodeController("Node") a.NoError(err) @@ -75,6 +72,24 @@ func TestBasicCatchup(t *testing.T) { // Now, catch up err = fixture.LibGoalFixture.ClientWaitForRoundWithTimeout(cloneClient, waitForRound) a.NoError(err) + + cloneNC := fixture.GetNodeControllerForDataDir(cloneDataDir) + cloneRestClient := fixture.GetAlgodClientForController(cloneNC) + + // an immediate call for ready will error, for sync time != 0 + a.Error(cloneRestClient.ReadyCheck()) + + for { + status, err := cloneRestClient.Status() + a.NoError(err) + + if status.LastRound < 10 { + time.Sleep(250 * time.Millisecond) + continue + } + a.NoError(cloneRestClient.ReadyCheck()) + break + } } // TestCatchupOverGossip tests catchup across network versions diff --git a/test/e2e-go/features/catchup/catchpointCatchup_test.go b/test/e2e-go/features/catchup/catchpointCatchup_test.go index 1663a14192..5d4e9b1940 100644 --- a/test/e2e-go/features/catchup/catchpointCatchup_test.go +++ b/test/e2e-go/features/catchup/catchpointCatchup_test.go @@ -535,6 +535,161 @@ outer: a.NoError(err) } +// TestReadyEndpoint starts a two-node network (derived mainly from TestNodeTxHandlerRestart) +// Lets the primary node have the majority of the stake +// Waits until a catchpoint is created +// Let primary node catch up against the catchpoint, confirm ready endpoint is 503 +// Wait the primary node catch up to target round, and confirm ready endpoint is 200 +func TestReadyEndpoint(t *testing.T) { + partitiontest.PartitionTest(t) + defer fixtures.ShutdownSynchronizedTest(t) + + if testing.Short() { + t.Skip() + } + a := require.New(fixtures.SynchronizedTest(t)) + + consensus := make(config.ConsensusProtocols) + protoVersion := protocol.ConsensusCurrentVersion + catchpointCatchupProtocol := config.Consensus[protoVersion] + applyCatchpointConsensusChanges(&catchpointCatchupProtocol) + catchpointCatchupProtocol.StateProofInterval = 0 + consensus[protoVersion] = catchpointCatchupProtocol + + var fixture fixtures.RestClientFixture + fixture.SetConsensus(consensus) + fixture.SetupNoStart(t, filepath.Join("nettemplates", "TwoNodes50EachWithRelay.json")) + + // Get primary node + primaryNode, err := fixture.GetNodeController("Node1") + a.NoError(err) + // Get secondary node + secondNode, err := fixture.GetNodeController("Node2") + a.NoError(err) + // Get the relay + relayNode, err := fixture.GetNodeController("Relay") + a.NoError(err) + + // prepare its configuration file to set it to generate a catchpoint every 16 rounds. + cfg, err := config.LoadConfigFromDisk(primaryNode.GetDataDir()) + a.NoError(err) + const catchpointInterval = 16 + cfg.CatchpointInterval = catchpointInterval + cfg.CatchpointTracking = 2 + cfg.MaxAcctLookback = 2 + cfg.Archival = false + cfg.TxSyncIntervalSeconds = 200000 // disable txSync + + err = cfg.SaveToDisk(primaryNode.GetDataDir()) + a.NoError(err) + err = cfg.SaveToDisk(secondNode.GetDataDir()) + a.NoError(err) + + cfg, err = config.LoadConfigFromDisk(relayNode.GetDataDir()) + a.NoError(err) + cfg.TxSyncIntervalSeconds = 200000 // disable txSync + cfg.SaveToDisk(relayNode.GetDataDir()) + + fixture.Start() + defer fixture.LibGoalFixture.Shutdown() + + client1 := fixture.GetLibGoalClientFromNodeController(primaryNode) + client2 := fixture.GetLibGoalClientFromNodeController(secondNode) + wallet1, err := client1.GetUnencryptedWalletHandle() + a.NoError(err) + wallet2, err := client2.GetUnencryptedWalletHandle() + a.NoError(err) + addrs1, err := client1.ListAddresses(wallet1) + a.NoError(err) + addrs2, err := client2.ListAddresses(wallet2) + a.NoError(err) + + // let the second node have insufficient stake for proposing a block + tx, err := client2.SendPaymentFromUnencryptedWallet(addrs2[0], addrs1[0], 1000, 4999999999000000, nil) + a.NoError(err) + status, err := client1.Status() + a.NoError(err) + _, err = fixture.WaitForConfirmedTxn(status.LastRound+100, addrs1[0], tx.ID().String()) + a.NoError(err) + targetCatchpointRound := status.LastRound + + // ensure the catchpoint is created for targetCatchpointRound + timer := time.NewTimer(100 * time.Second) +outer: + for { + status, err = client1.Status() + a.NoError(err) + + var round basics.Round + if status.LastCatchpoint != nil && len(*status.LastCatchpoint) > 0 { + round, _, err = ledgercore.ParseCatchpointLabel(*status.LastCatchpoint) + a.NoError(err) + if uint64(round) >= targetCatchpointRound { + break + } + } + select { + case <-timer.C: + a.Failf("timeout waiting a catchpoint", "target: %d, got %d", targetCatchpointRound, round) + break outer + default: + time.Sleep(250 * time.Millisecond) + } + } + + ////////// + // NOTE // + ////////// + // THE *REAL* TEST STARTS HERE: + // We first ensure when a primary node is catching up, it is not ready + // Then when the primary node is at target round, it should satisfy ready 200 condition + + // let the primary node catchup + err = client1.Catchup(*status.LastCatchpoint) + a.NoError(err) + + // The primary node is catching up with its previous catchpoint + // Its status contain a catchpoint it is catching-up against, + // so it should not be ready, and ready-ness endpoint should 503 err. + a.Error(fixture.GetAlgodClientForController(primaryNode).ReadyCheck()) + + status1, err := client1.Status() + a.NoError(err) + targetRound := status1.LastRound + 5 + + // Wait for the network to start making progress again + primaryNodeRestClient := fixture.GetAlgodClientForController(primaryNode) + err = fixture.ClientWaitForRound(primaryNodeRestClient, targetRound, + 10*catchpointCatchupProtocol.AgreementFilterTimeout) + a.NoError(err) + + // The primary node has reached the target round, + // - the sync-time (aka catchup time should be 0.0) + // - the catchpoint should be empty (len == 0) + timer = time.NewTimer(100 * time.Second) + + for { + err = primaryNodeRestClient.ReadyCheck() + + if err != nil { + select { + case <-timer.C: + a.Fail("timeout") + break + default: + time.Sleep(250 * time.Millisecond) + continue + } + } + + status1, err = client1.Status() + a.NoError(err) + a.Equal(status1.CatchupTime, uint64(0)) + a.Empty(status1.Catchpoint) + break + } +} + // TestNodeTxSyncRestart starts a two-node and one relay network // Waits until a catchpoint is created // Lets the primary node have the majority of the stake diff --git a/test/e2e-go/features/followerNode/syncDeltas_test.go b/test/e2e-go/features/followerNode/syncDeltas_test.go index 83c131512a..2a8d2b9618 100644 --- a/test/e2e-go/features/followerNode/syncDeltas_test.go +++ b/test/e2e-go/features/followerNode/syncDeltas_test.go @@ -43,7 +43,7 @@ func TestBasicSyncMode(t *testing.T) { var fixture fixtures.RestClientFixture // Give the second node (which starts up last) all the stake so that its proposal always has better credentials, - // and so that its proposal isn't dropped. Otherwise the test burns 17s to recover. We don't care about stake + // and so that its proposal isn't dropped. Otherwise, the test burns 17s to recover. We don't care about stake // distribution so this is fine. fixture.Setup(t, filepath.Join("nettemplates", "TwoNodesFollower100Second.json")) defer fixture.Shutdown() @@ -53,7 +53,6 @@ func TestBasicSyncMode(t *testing.T) { a.NoError(err) // Let the network make some progress - a.NoError(err) waitForRound := uint64(5) err = fixture.ClientWaitForRoundWithTimeout(fixture.GetAlgodClientForController(nc), waitForRound) a.NoError(err) @@ -68,6 +67,7 @@ func TestBasicSyncMode(t *testing.T) { rResp, err := followClient.GetSyncRound() a.NoError(err) a.Equal(round, rResp.Round) + // make some progress to round err = fixture.ClientWaitForRoundWithTimeout(followClient, round) a.NoError(err) // retrieve state delta From f8a130ed53006473de823756383f76eeda3faade Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Mon, 3 Apr 2023 10:48:24 -0400 Subject: [PATCH 027/125] ledger: fix commit tasks enqueueing (#5214) --- ledger/catchpointtracker_test.go | 227 ++++++++++++++++++++++++------- ledger/tracker.go | 32 ++++- ledger/txtail_test.go | 2 - 3 files changed, 204 insertions(+), 57 deletions(-) diff --git a/ledger/catchpointtracker_test.go b/ledger/catchpointtracker_test.go index d24efac19a..bcb9f81d2e 100644 --- a/ledger/catchpointtracker_test.go +++ b/ledger/catchpointtracker_test.go @@ -33,6 +33,7 @@ import ( "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/agreement" "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/basics" @@ -47,7 +48,7 @@ import ( "github.com/algorand/go-algorand/test/partitiontest" ) -func TestIsWritingCatchpointFile(t *testing.T) { +func TestCatchpointIsWritingCatchpointFile(t *testing.T) { partitiontest.PartitionTest(t) ct := &catchpointTracker{} @@ -78,7 +79,7 @@ func newCatchpointTracker(tb testing.TB, l *mockLedgerForTracker, conf config.Lo return ct } -func TestGetCatchpointStream(t *testing.T) { +func TestCatchpointGetCatchpointStream(t *testing.T) { partitiontest.PartitionTest(t) accts := []map[basics.Address]basics.AccountData{ledgertesting.RandomAccounts(20, true)} @@ -149,12 +150,12 @@ func TestGetCatchpointStream(t *testing.T) { require.NoError(t, err) } -// TestAcctUpdatesDeleteStoredCatchpoints - The goal of this test is to verify that the deleteStoredCatchpoints function works correctly. +// TestCatchpointsDeleteStored - The goal of this test is to verify that the deleteStoredCatchpoints function works correctly. // It does so by filling up the storedcatchpoints with dummy catchpoint file entries, as well as creating these dummy files on disk. // ( the term dummy is only because these aren't real catchpoint files, but rather a zero-length file ). Then, the test calls the function // and ensures that it did not error, the catchpoint files were correctly deleted, and that deleteStoredCatchpoints contains no more // entries. -func TestAcctUpdatesDeleteStoredCatchpoints(t *testing.T) { +func TestCatchpointsDeleteStored(t *testing.T) { partitiontest.PartitionTest(t) accts := []map[basics.Address]basics.AccountData{ledgertesting.RandomAccounts(20, true)} @@ -206,7 +207,7 @@ func TestAcctUpdatesDeleteStoredCatchpoints(t *testing.T) { // The test validate that when algod boots up it cleans empty catchpoint directories. // It is done by creating empty directories in the catchpoint root directory. // When algod boots up it should remove those directories. -func TestSchemaUpdateDeleteStoredCatchpoints(t *testing.T) { +func TestCatchpointsDeleteStoredOnSchemaUpdate(t *testing.T) { partitiontest.PartitionTest(t) // we don't want to run this test before the binary is compiled against the latest database upgrade schema. @@ -467,7 +468,7 @@ func BenchmarkLargeCatchpointDataWriting(b *testing.B) { b.ReportMetric(float64(accountsNumber), "accounts") } -func TestReproducibleCatchpointLabels(t *testing.T) { +func TestCatchpointReproducibleLabels(t *testing.T) { partitiontest.PartitionTest(t) if runtime.GOARCH == "arm" || runtime.GOARCH == "arm64" { @@ -492,6 +493,7 @@ func TestReproducibleCatchpointLabels(t *testing.T) { defer ml.Close() cfg := config.GetDefaultLocal() + cfg.MaxAcctLookback = 2 cfg.CatchpointInterval = 50 cfg.CatchpointTracking = 1 ct := newCatchpointTracker(t, ml, cfg, ".") @@ -508,10 +510,19 @@ func TestReproducibleCatchpointLabels(t *testing.T) { catchpointLabels := make(map[basics.Round]string) ledgerHistory := make(map[basics.Round]*mockLedgerForTracker) roundDeltas := make(map[basics.Round]ledgercore.StateDelta) - numCatchpointsCreated := 0 + + isCatchpointRound := func(rnd basics.Round) bool { + return (uint64(rnd) >= cfg.MaxAcctLookback) && + (uint64(rnd)-cfg.MaxAcctLookback > protoParams.CatchpointLookback) && + ((uint64(rnd)-cfg.MaxAcctLookback)%cfg.CatchpointInterval == 0) + } + isDataFileRound := func(rnd basics.Round) bool { + return ((uint64(rnd)-cfg.MaxAcctLookback+protoParams.CatchpointLookback)%cfg.CatchpointInterval == 0) + } + i := basics.Round(0) + numCatchpointsCreated := 0 lastCatchpointLabel := "" - for numCatchpointsCreated < testCatchpointLabelsCount { i++ rewardLevelDelta := crypto.RandUint64() % 5 @@ -549,14 +560,23 @@ func TestReproducibleCatchpointLabels(t *testing.T) { ml.trackers.newBlock(blk, delta) ml.addMockBlock(blockEntry{block: blk}, delta) - ml.trackers.committedUpTo(i) accts = append(accts, newAccts) rewardsLevels = append(rewardsLevels, rewardLevel) roundDeltas[i] = delta - // If we made a catchpoint, save the label. - if (uint64(i) >= cfg.MaxAcctLookback) && (uint64(i)-cfg.MaxAcctLookback > protoParams.CatchpointLookback) && ((uint64(i)-cfg.MaxAcctLookback)%cfg.CatchpointInterval == 0) { + // determine if there is a data file round and commit + if isDataFileRound(i) || isCatchpointRound(i) { + ml.trackers.committedUpTo(i) ml.trackers.waitAccountsWriting() + + // Let catchpoint data generation finish so that nothing gets skipped. + for ct.IsWritingCatchpointDataFile() { + time.Sleep(time.Millisecond) + } + } + + // If we made a catchpoint, save the label. + if isCatchpointRound(i) { catchpointLabels[i] = ct.GetLastCatchpointLabel() require.NotEmpty(t, catchpointLabels[i]) require.NotEqual(t, lastCatchpointLabel, catchpointLabels[i]) @@ -565,24 +585,19 @@ func TestReproducibleCatchpointLabels(t *testing.T) { defer ledgerHistory[i].Close() numCatchpointsCreated++ } - - // Let catchpoint data generation finish so that nothing gets skipped. - for ct.IsWritingCatchpointDataFile() { - time.Sleep(time.Millisecond) - } } lastRound := i // Test in reverse what happens when we try to repeat the exact same blocks. // Start off with the catchpoint before the last one. - for startingRound := lastRound - basics.Round(cfg.CatchpointInterval); uint64(startingRound) > protoParams.CatchpointLookback; startingRound -= basics.Round(cfg.CatchpointInterval) { + for rnd := lastRound - basics.Round(cfg.CatchpointInterval); uint64(rnd) > protoParams.CatchpointLookback; rnd -= basics.Round(cfg.CatchpointInterval) { au.close() - ml2 := ledgerHistory[startingRound] + ml2 := ledgerHistory[rnd] require.NotNil(t, ml2) ct2 := newCatchpointTracker(t, ml2, cfg, ".") defer ct2.close() - for i := startingRound + 1; i <= lastRound; i++ { + for i := rnd + 1; i <= lastRound; i++ { blk := bookkeeping.Block{ BlockHeader: bookkeeping.BlockHeader{ Round: basics.Round(i), @@ -593,19 +608,18 @@ func TestReproducibleCatchpointLabels(t *testing.T) { delta := roundDeltas[i] ml2.trackers.newBlock(blk, delta) - err := ml2.addMockBlock(blockEntry{block: blk}, delta) - require.NoError(t, err) - ml2.trackers.committedUpTo(i) - // if this is a catchpoint round, check the label. - if (uint64(i) >= cfg.MaxAcctLookback) && (uint64(i)-cfg.MaxAcctLookback > protoParams.CatchpointLookback) && ((uint64(i)-cfg.MaxAcctLookback)%cfg.CatchpointInterval == 0) { + if isDataFileRound(i) || isCatchpointRound(i) { + ml2.trackers.committedUpTo(i) ml2.trackers.waitAccountsWriting() - require.Equal(t, catchpointLabels[i], ct2.GetLastCatchpointLabel()) + // Let catchpoint data generation finish so that nothing gets skipped. + for ct.IsWritingCatchpointDataFile() { + time.Sleep(time.Millisecond) + } } - - // Let catchpoint data generation finish so that nothing gets skipped. - for ct.IsWritingCatchpointDataFile() { - time.Sleep(time.Millisecond) + // if this is a catchpoint round, check the label. + if isCatchpointRound(i) { + require.Equal(t, catchpointLabels[i], ct2.GetLastCatchpointLabel()) } } } @@ -632,6 +646,7 @@ type blockingTracker struct { committedUpToRound int64 alwaysLock bool shouldLockPostCommit bool + shouldLockPostCommitUnlocked bool } // loadFromDisk is not implemented in the blockingTracker. @@ -674,7 +689,7 @@ func (bt *blockingTracker) postCommit(ctx context.Context, dcc *deferredCommitCo // postCommitUnlocked implements entry/exit blockers, designed for testing. func (bt *blockingTracker) postCommitUnlocked(ctx context.Context, dcc *deferredCommitContext) { - if bt.alwaysLock || dcc.catchpointFirstStage { + if bt.alwaysLock || dcc.catchpointFirstStage || bt.shouldLockPostCommitUnlocked { bt.postCommitUnlockedEntryLock <- struct{}{} <-bt.postCommitUnlockedReleaseLock } @@ -691,7 +706,7 @@ func (bt *blockingTracker) close() { func TestCatchpointTrackerNonblockingCatchpointWriting(t *testing.T) { partitiontest.PartitionTest(t) - testProtocolVersion := protocol.ConsensusVersion("test-protocol-TestReproducibleCatchpointLabels") + testProtocolVersion := protocol.ConsensusVersion("test-protocol-TestCatchpointTrackerNonblockingCatchpointWriting") protoParams := config.Consensus[protocol.ConsensusCurrentVersion] protoParams.EnableCatchpointsWithSPContexts = true protoParams.CatchpointLookback = protoParams.MaxBalLookback @@ -834,6 +849,94 @@ func TestCatchpointTrackerNonblockingCatchpointWriting(t *testing.T) { } } +// TestCatchpointTrackerWaitNotBlocking checks a tracker with long postCommitUnlocked does not block blockq (notifyCommit) goroutine +func TestCatchpointTrackerWaitNotBlocking(t *testing.T) { + partitiontest.PartitionTest(t) + + genesisInitState, _ := ledgertesting.GenerateInitState(t, protocol.ConsensusCurrentVersion, 10) + const inMem = true + log := logging.TestingLog(t) + log.SetLevel(logging.Warn) + cfg := config.GetDefaultLocal() + cfg.Archival = true + ledger, err := OpenLedger(log, t.Name(), inMem, genesisInitState, cfg) + require.NoError(t, err) + defer ledger.Close() + + writeStallingTracker := &blockingTracker{ + postCommitUnlockedEntryLock: make(chan struct{}), + postCommitUnlockedReleaseLock: make(chan struct{}), + shouldLockPostCommitUnlocked: true, + } + ledger.trackerMu.Lock() + ledger.trackers.mu.Lock() + ledger.trackers.trackers = append(ledger.trackers.trackers, writeStallingTracker) + ledger.trackers.mu.Unlock() + ledger.trackerMu.Unlock() + + startRound := ledger.Latest() + 1 + endRound := basics.Round(20) + addBlockDone := make(chan struct{}) + + // release the blocking tracker when the test is done + defer func() { + // unblocking from another goroutine is a bit complicated: + // this function should not quit until postCommitUnlockedReleaseLock is consumed + // to do that, write to it first and do not exit until consumed, + // otherwise we might exit and leave the tracker registry's syncer goroutine blocked + wg := sync.WaitGroup{} + wg.Add(1) + go func() { + writeStallingTracker.postCommitUnlockedReleaseLock <- struct{}{} + wg.Done() + }() + + // consume to unblock + <-writeStallingTracker.postCommitUnlockedEntryLock + // disable further blocking + writeStallingTracker.shouldLockPostCommitUnlocked = false + + // wait the writeStallingTracker.postCommitUnlockedReleaseLock passes + wg.Wait() + + // at the end, what while the addBlock goroutine finishes + // consume to unblock + <-addBlockDone + }() + + // tracker commits are now blocked, add some blocks + timer := time.NewTimer(1 * time.Second) + go func() { + defer close(addBlockDone) + blk := genesisInitState.Block + for rnd := startRound; rnd <= endRound; rnd++ { + blk.BlockHeader.Round = rnd + blk.BlockHeader.TimeStamp = int64(blk.BlockHeader.Round) + err := ledger.AddBlock(blk, agreement.Certificate{}) + require.NoError(t, err) + } + }() + + select { + case <-timer.C: + require.FailNow(t, "timeout") + case <-addBlockDone: + } + + // switch context one more time to give the blockqueue syncer to run + time.Sleep(1 * time.Millisecond) + + // ensure Ledger.Wait() is non-blocked for all rounds except the last one (due to possible races) + for rnd := startRound; rnd < endRound; rnd++ { + done := ledger.Wait(rnd) + select { + case <-done: + default: + require.FailNow(t, fmt.Sprintf("Wait(%d) is blocked", rnd)) + } + } +} + func TestCalculateFirstStageRounds(t *testing.T) { partitiontest.PartitionTest(t) @@ -921,7 +1024,7 @@ func TestCalculateCatchpointRounds(t *testing.T) { // Test that pruning first stage catchpoint database records and catchpoint data files // works. -func TestFirstStageInfoPruning(t *testing.T) { +func TestCatchpointFirstStageInfoPruning(t *testing.T) { partitiontest.PartitionTest(t) // create new protocol version, which has lower lookback @@ -955,6 +1058,15 @@ func TestFirstStageInfoPruning(t *testing.T) { expectedNumEntries := protoParams.CatchpointLookback / cfg.CatchpointInterval + isCatchpointRound := func(rnd basics.Round) bool { + return (uint64(rnd) >= cfg.MaxAcctLookback) && + (uint64(rnd)-cfg.MaxAcctLookback > protoParams.CatchpointLookback) && + ((uint64(rnd)-cfg.MaxAcctLookback)%cfg.CatchpointInterval == 0) + } + isDataFileRound := func(rnd basics.Round) bool { + return ((uint64(rnd)-cfg.MaxAcctLookback+protoParams.CatchpointLookback)%cfg.CatchpointInterval == 0) + } + numCatchpointsCreated := uint64(0) i := basics.Round(0) lastCatchpointLabel := "" @@ -973,21 +1085,23 @@ func TestFirstStageInfoPruning(t *testing.T) { delta := ledgercore.MakeStateDelta(&blk.BlockHeader, 0, 0, 0) ml.trackers.newBlock(blk, delta) - ml.trackers.committedUpTo(i) ml.addMockBlock(blockEntry{block: blk}, delta) - if (uint64(i) >= cfg.MaxAcctLookback) && (uint64(i)-cfg.MaxAcctLookback > protoParams.CatchpointLookback) && ((uint64(i)-cfg.MaxAcctLookback)%cfg.CatchpointInterval == 0) { + if isDataFileRound(i) || isCatchpointRound(i) { + ml.trackers.committedUpTo(i) ml.trackers.waitAccountsWriting() + // Let catchpoint data generation finish so that nothing gets skipped. + for ct.IsWritingCatchpointDataFile() { + time.Sleep(time.Millisecond) + } + } + + if isCatchpointRound(i) { catchpointLabel := ct.GetLastCatchpointLabel() require.NotEqual(t, lastCatchpointLabel, catchpointLabel) lastCatchpointLabel = catchpointLabel numCatchpointsCreated++ } - - // Let catchpoint data generation finish so that nothing gets skipped. - for ct.IsWritingCatchpointDataFile() { - time.Sleep(time.Millisecond) - } } numEntries := uint64(0) @@ -1015,7 +1129,7 @@ func TestFirstStageInfoPruning(t *testing.T) { // Test that on startup the catchpoint tracker restarts catchpoint's first stage if // there is an unfinished first stage record in the database. -func TestFirstStagePersistence(t *testing.T) { +func TestCatchpointFirstStagePersistence(t *testing.T) { partitiontest.PartitionTest(t) // create new protocol version, which has lower lookback @@ -1059,10 +1173,9 @@ func TestFirstStagePersistence(t *testing.T) { delta := ledgercore.MakeStateDelta(&blk.BlockHeader, 0, 0, 0) ml.trackers.newBlock(blk, delta) - ml.trackers.committedUpTo(i) ml.addMockBlock(blockEntry{block: blk}, delta) } - + ml.trackers.committedUpTo(firstStageRound) ml.trackers.waitAccountsWriting() // Check that the data file exists. @@ -1118,7 +1231,7 @@ func TestFirstStagePersistence(t *testing.T) { // Test that on startup the catchpoint tracker restarts catchpoint's second stage if // there is an unfinished catchpoint record in the database. -func TestSecondStagePersistence(t *testing.T) { +func TestCatchpointSecondStagePersistence(t *testing.T) { partitiontest.PartitionTest(t) // create new protocol version, which has lower lookback @@ -1148,6 +1261,15 @@ func TestSecondStagePersistence(t *testing.T) { t, ml, cfg, filepath.Join(tempDirectory, config.LedgerFilenamePrefix)) defer ct.close() + isCatchpointRound := func(rnd basics.Round) bool { + return (uint64(rnd) >= cfg.MaxAcctLookback) && + (uint64(rnd)-cfg.MaxAcctLookback > protoParams.CatchpointLookback) && + ((uint64(rnd)-cfg.MaxAcctLookback)%cfg.CatchpointInterval == 0) + } + isDataFileRound := func(rnd basics.Round) bool { + return ((uint64(rnd)-cfg.MaxAcctLookback+protoParams.CatchpointLookback)%cfg.CatchpointInterval == 0) + } + secondStageRound := basics.Round(36) firstStageRound := secondStageRound - basics.Round(protoParams.CatchpointLookback) catchpointDataFilePath := @@ -1180,17 +1302,18 @@ func TestSecondStagePersistence(t *testing.T) { delta := ledgercore.MakeStateDelta(&blk.BlockHeader, 0, 0, 0) ml.trackers.newBlock(blk, delta) - ml.trackers.committedUpTo(i) ml.addMockBlock(blockEntry{block: blk}, delta) - // Let catchpoint data generation finish so that nothing gets skipped. - for ct.IsWritingCatchpointDataFile() { - time.Sleep(time.Millisecond) + if isDataFileRound(i) || isCatchpointRound(i) { + ml.trackers.committedUpTo(i) + ml.trackers.waitAccountsWriting() + // Let catchpoint data generation finish so that nothing gets skipped. + for ct.IsWritingCatchpointDataFile() { + time.Sleep(time.Millisecond) + } } } - ml.trackers.waitAccountsWriting() - // Check that the data file exists. catchpointFilePath := filepath.Join(catchpointsDirectory, trackerdb.MakeCatchpointFilePath(secondStageRound)) @@ -1255,7 +1378,7 @@ func TestSecondStagePersistence(t *testing.T) { // Test that when catchpoint's first stage record is unavailable // (e.g. catchpoints were disabled at first stage), the unfinished catchpoint // database record is deleted. -func TestSecondStageDeletesUnfinishedCatchpointRecord(t *testing.T) { +func TestCatchpointSecondStageDeletesUnfinishedCatchpointRecord(t *testing.T) { partitiontest.PartitionTest(t) // create new protocol version, which has lower lookback @@ -1344,7 +1467,7 @@ func TestSecondStageDeletesUnfinishedCatchpointRecord(t *testing.T) { // Test that on startup the catchpoint tracker deletes the unfinished catchpoint // database record when the first stage database record is missing. -func TestSecondStageDeletesUnfinishedCatchpointRecordAfterRestart(t *testing.T) { +func TestCatchpointSecondStageDeletesUnfinishedCatchpointRecordAfterRestart(t *testing.T) { partitiontest.PartitionTest(t) // create new protocol version, which has lower lookback @@ -1647,8 +1770,8 @@ func TestCatchpointFastUpdates(t *testing.T) { ml.trackers.committedUpTo(round) }(i) } - ml.trackers.waitAccountsWriting() wg.Wait() + ml.trackers.waitAccountsWriting() require.NotEmpty(t, ct.GetLastCatchpointLabel()) } diff --git a/ledger/tracker.go b/ledger/tracker.go index f03c5b8061..23f5f18949 100644 --- a/ledger/tracker.go +++ b/ledger/tracker.go @@ -411,14 +411,40 @@ func (tr *trackerRegistry) scheduleCommit(blockqRound, maxLookback basics.Round) // ( unless we're creating a catchpoint, in which case we want to flush it right away // so that all the instances of the catchpoint would contain exactly the same data ) flushTime := time.Now() - if dcc != nil && !flushTime.After(tr.lastFlushTime.Add(balancesFlushInterval)) && !dcc.catchpointFirstStage && !dcc.catchpointSecondStage && dcc.pendingDeltas < pendingDeltasFlushThreshold { - dcc = nil + + // Some tracker want to flush + if dcc != nil { + // skip this flush if none of these conditions met: + // - has it been at least balancesFlushInterval since the last flush? + flushIntervalPassed := flushTime.After(tr.lastFlushTime.Add(balancesFlushInterval)) + // - does this commit task also include catchpoint file creation activity for the dcc.oldBase+dcc.offset? + flushForCatchpoint := dcc.catchpointFirstStage || dcc.catchpointSecondStage + // - have more than pendingDeltasFlushThreshold accounts been modified since the last flush? + flushAccounts := dcc.pendingDeltas >= pendingDeltasFlushThreshold + if !(flushIntervalPassed || flushForCatchpoint || flushAccounts) { + dcc = nil + } } tr.mu.RUnlock() if dcc != nil { + // Increment the waitgroup first, otherwise this goroutine can be interrupted + // and commitSyncer attempts calling Done() on empty wait group. tr.accountsWriting.Add(1) - tr.deferredCommits <- dcc + select { + case tr.deferredCommits <- dcc: + default: + // Do NOT block if deferredCommits cannot accept this task, skip it. + // Note: the next attempt will include these rounds plus some extra rounds. + // The main reason for slow commits is catchpoint file creation (when commitSyncer calls + // commitRound, which calls postCommitUnlocked). This producer thread is called by + // blockQueue.syncer() upon successful block DB flush, which calls ledger.notifyCommit() + // and trackerRegistry.committedUpTo() after taking the trackerMu.Lock(). + // This means a blocking write to deferredCommits will block Ledger reads (TODO use more fine-grained locks). + // Dropping this dcc allows the blockqueue syncer to continue persisting other blocks + // and ledger reads to proceed without being blocked by trackerMu lock. + tr.accountsWriting.Done() + } } } diff --git a/ledger/txtail_test.go b/ledger/txtail_test.go index ba4a09755d..a21af51208 100644 --- a/ledger/txtail_test.go +++ b/ledger/txtail_test.go @@ -19,7 +19,6 @@ package ledger import ( "context" "errors" - "fmt" "testing" "github.com/stretchr/testify/require" @@ -259,7 +258,6 @@ func TestTxTailDeltaTracking(t *testing.T) { err := txtail.loadFromDisk(&ledger, ledger.Latest()) require.NoError(t, err) - fmt.Printf("%d, %s\n", len(txtail.recent), protoVersion) require.Equal(t, int(config.Consensus[protoVersion].MaxTxnLife), len(txtail.recent)) require.Equal(t, testTxTailValidityRange, len(txtail.lastValid)) require.Equal(t, ledger.Latest(), txtail.lowWaterMark) From c27422c424480a8abcc8b2a69c820b03062920c2 Mon Sep 17 00:00:00 2001 From: algochoi <86622919+algochoi@users.noreply.github.com> Date: Mon, 3 Apr 2023 14:41:09 -0400 Subject: [PATCH 028/125] api: Limit request body size to 10MB (#5246) --- daemon/algod/api/server/router.go | 6 +++++- daemon/algod/api/server/v2/handlers.go | 18 ++++++++++-------- .../algod/api/server/v2/test/handlers_test.go | 18 +++++++++++++++++- test/scripts/e2e_subs/e2e-app-simulate.sh | 14 ++++++++++++-- 4 files changed, 44 insertions(+), 12 deletions(-) diff --git a/daemon/algod/api/server/router.go b/daemon/algod/api/server/router.go index 632e8cbb9f..6797bd988c 100644 --- a/daemon/algod/api/server/router.go +++ b/daemon/algod/api/server/router.go @@ -51,6 +51,8 @@ const ( apiV1Tag = "/v1" // TokenHeader is the header where we put the token. TokenHeader = "X-Algo-API-Token" + // maxRequestBodyBytes is the maximum request body size that we allow in our APIs. + maxRequestBodyBytes = "10MB" ) // wrapCtx passes a common context to each request without a global variable. @@ -90,7 +92,9 @@ func NewRouter(logger logging.Logger, node APINodeInterface, shutdown <-chan str middleware.RemoveTrailingSlash()) e.Use( middlewares.MakeLogger(logger), - middlewares.MakeCORS(TokenHeader)) + middlewares.MakeCORS(TokenHeader), + middleware.BodyLimit(maxRequestBodyBytes), + ) // Request Context ctx := lib.ReqContext{Node: node, Log: logger, Shutdown: shutdown} diff --git a/daemon/algod/api/server/v2/handlers.go b/daemon/algod/api/server/v2/handlers.go index 0bf02974d1..8e31f89902 100644 --- a/daemon/algod/api/server/v2/handlers.go +++ b/daemon/algod/api/server/v2/handlers.go @@ -54,14 +54,16 @@ import ( "github.com/algorand/go-algorand/stateproof" ) -// max compiled teal program is currently 8k +// MaxTealSourceBytes sets a size limit for TEAL source programs for requests +// Max TEAL program size is currently 8k // but we allow for comments, spacing, and repeated consts -// in the source teal, allow up to 200kb -const maxTealSourceBytes = 200_000 +// in the source TEAL, so we allow up to 200KB +const MaxTealSourceBytes = 200_000 +// MaxTealDryrunBytes sets a size limit for dryrun requests // With the ability to hold unlimited assets DryrunRequests can -// become quite large, allow up to 1mb -const maxTealDryrunBytes = 1_000_000 +// become quite large, so we allow up to 1MB +const MaxTealDryrunBytes = 1_000_000 // Handlers is an implementation to the V2 route handler interface defined by the generated code. type Handlers struct { @@ -998,7 +1000,7 @@ func (v2 *Handlers) TealDryrun(ctx echo.Context) error { } req := ctx.Request() buf := new(bytes.Buffer) - req.Body = http.MaxBytesReader(nil, req.Body, maxTealDryrunBytes) + req.Body = http.MaxBytesReader(nil, req.Body, MaxTealDryrunBytes) _, err := buf.ReadFrom(ctx.Request().Body) if err != nil { return badRequest(ctx, err, err.Error(), v2.Log) @@ -1531,7 +1533,7 @@ func (v2 *Handlers) TealCompile(ctx echo.Context, params model.TealCompileParams } buf := new(bytes.Buffer) - ctx.Request().Body = http.MaxBytesReader(nil, ctx.Request().Body, maxTealSourceBytes) + ctx.Request().Body = http.MaxBytesReader(nil, ctx.Request().Body, MaxTealSourceBytes) _, err = buf.ReadFrom(ctx.Request().Body) if err != nil { return badRequest(ctx, err, err.Error(), v2.Log) @@ -1648,7 +1650,7 @@ func (v2 *Handlers) TealDisassemble(ctx echo.Context) error { return ctx.String(http.StatusNotFound, "/teal/disassemble was not enabled in the configuration file by setting the EnableDeveloperAPI to true") } buf := new(bytes.Buffer) - ctx.Request().Body = http.MaxBytesReader(nil, ctx.Request().Body, maxTealSourceBytes) + ctx.Request().Body = http.MaxBytesReader(nil, ctx.Request().Body, MaxTealSourceBytes) _, err := buf.ReadFrom(ctx.Request().Body) if err != nil { return badRequest(ctx, err, err.Error(), v2.Log) diff --git a/daemon/algod/api/server/v2/test/handlers_test.go b/daemon/algod/api/server/v2/test/handlers_test.go index bbb1994d99..b5aeb8e6ef 100644 --- a/daemon/algod/api/server/v2/test/handlers_test.go +++ b/daemon/algod/api/server/v2/test/handlers_test.go @@ -25,6 +25,7 @@ import ( "io" "net/http" "net/http/httptest" + "strings" "testing" "time" @@ -1318,6 +1319,11 @@ func TestTealDisassemble(t *testing.T) { // Test bad program. badProgram := []byte{1, 99} tealDisassembleTest(t, badProgram, 400, "invalid opcode", true) + + // Create a program with MaxTealSourceBytes+1 bytes + // This should fail inside the handler when reading the bytes from the request body. + largeProgram := []byte(strings.Repeat("a", v2.MaxTealSourceBytes+1)) + tealDisassembleTest(t, largeProgram, 400, "http: request body too large", true) } func tealDryrunTest( @@ -1364,6 +1370,12 @@ func tealDryrunTest( messages := *response.Txns[0].AppCallMessages require.GreaterOrEqual(t, len(messages), 1) require.Equal(t, expResult, messages[len(messages)-1]) + } else if rec.Code == 400 { + var response model.ErrorResponse + data := rec.Body.Bytes() + err = protocol.DecodeJSON(data, &response) + require.NoError(t, err, string(data)) + require.Contains(t, response.Message, expResult) } return } @@ -1429,7 +1441,7 @@ func TestTealDryrun(t *testing.T) { tealDryrunTest(t, &gdr, "msgp", 404, "", false) gdr.ProtocolVersion = "unk" - tealDryrunTest(t, &gdr, "json", 400, "", true) + tealDryrunTest(t, &gdr, "json", 400, "unsupported protocol version", true) gdr.ProtocolVersion = "" ddr := tealDryrunTest(t, &gdr, "json", 200, "PASS", true) @@ -1442,6 +1454,10 @@ func TestTealDryrun(t *testing.T) { tealDryrunTest(t, &gdr, "json", 200, "REJECT", true) tealDryrunTest(t, &gdr, "msgp", 200, "REJECT", true) tealDryrunTest(t, &gdr, "json", 404, "", false) + + // This should fail inside the handler when reading the bytes from the request body. + gdr.ProtocolVersion = strings.Repeat("a", v2.MaxTealDryrunBytes+1) + tealDryrunTest(t, &gdr, "json", 400, "http: request body too large", true) } func TestAppendParticipationKeys(t *testing.T) { diff --git a/test/scripts/e2e_subs/e2e-app-simulate.sh b/test/scripts/e2e_subs/e2e-app-simulate.sh index cae8ebeab3..9f057beb21 100755 --- a/test/scripts/e2e_subs/e2e-app-simulate.sh +++ b/test/scripts/e2e_subs/e2e-app-simulate.sh @@ -1,6 +1,6 @@ #!/bin/bash -date '+app-simple-test start %Y%m%d_%H%M%S' +date '+app-simulate-test start %Y%m%d_%H%M%S' set -e set -x @@ -20,6 +20,16 @@ ACCOUNT=$(${gcmd} account list|awk '{ print $3 }') CONST_TRUE="true" CONST_FALSE="false" +# First, try to send an extremely large "transaction" in the request body. +# This should fail with a 413 error. +truncate -s 11MB "${TEMPDIR}/toolarge.tx" +RES=$(${gcmd} clerk simulate -t "${TEMPDIR}/toolarge.tx" 2>&1 || true) +EXPERROR="simulation error: HTTP 413 Request Entity Too Large:" +if [[ $RES != *"${EXPERROR}"* ]]; then + date '+app-simulate-test FAIL the simulate API should fail for request bodies exceeding 10MB %Y%m%d_%H%M%S' + false +fi + ############################################## # WE FIRST TEST TRANSACTION GROUP SIMULATION # ############################################## @@ -29,7 +39,7 @@ ${gcmd} clerk send -a 10000 -f ${ACCOUNT} -t ${ACCOUNT} -o pay2.tx cat pay1.tx pay2.tx | ${gcmd} clerk group -i - -o grouped.tx -# We first test transaction group simulation WITHOUT signatures +# We test transaction group simulation WITHOUT signatures RES=$(${gcmd} clerk simulate -t grouped.tx) if [[ $(echo "$RES" | jq '."would-succeed"') != $CONST_FALSE ]]; then From 1bc1252c867b938a6638a99604e5e29a140e4cdd Mon Sep 17 00:00:00 2001 From: algoidan <79864820+algoidan@users.noreply.github.com> Date: Tue, 4 Apr 2023 17:54:38 +0300 Subject: [PATCH 029/125] Algod: rename crypto-stateproof-builder to crypto-stateproof-prover (#5243) --- crypto/stateproof/msgp_gen.go | 252 +++++------ crypto/stateproof/msgp_gen_test.go | 60 +-- crypto/stateproof/{builder.go => prover.go} | 46 +- .../{builder_test.go => prover_test.go} | 38 +- data/pools/transactionPool_test.go | 4 +- stateproof/builder.go | 208 ++++----- stateproof/db.go | 70 ++-- stateproof/db_test.go | 62 +-- stateproof/msgp_gen.go | 394 +++++++++--------- stateproof/msgp_gen_test.go | 40 +- stateproof/signer.go | 2 +- stateproof/verify/stateproof.go | 2 +- stateproof/worker.go | 29 +- stateproof/worker_test.go | 190 ++++----- 14 files changed, 699 insertions(+), 698 deletions(-) rename crypto/stateproof/{builder.go => prover.go} (82%) rename crypto/stateproof/{builder_test.go => prover_test.go} (94%) diff --git a/crypto/stateproof/msgp_gen.go b/crypto/stateproof/msgp_gen.go index 8b90154a81..8d4d8a3506 100644 --- a/crypto/stateproof/msgp_gen.go +++ b/crypto/stateproof/msgp_gen.go @@ -12,22 +12,6 @@ import ( ) // The following msgp objects are implemented in this file: -// Builder -// |-----> (*) MarshalMsg -// |-----> (*) CanMarshalMsg -// |-----> (*) UnmarshalMsg -// |-----> (*) CanUnmarshalMsg -// |-----> (*) Msgsize -// |-----> (*) MsgIsZero -// -// BuilderPersistedFields -// |-----> (*) MarshalMsg -// |-----> (*) CanMarshalMsg -// |-----> (*) UnmarshalMsg -// |-----> (*) CanUnmarshalMsg -// |-----> (*) Msgsize -// |-----> (*) MsgIsZero -// // MessageHash // |-----> (*) MarshalMsg // |-----> (*) CanMarshalMsg @@ -36,6 +20,22 @@ import ( // |-----> (*) Msgsize // |-----> (*) MsgIsZero // +// Prover +// |-----> (*) MarshalMsg +// |-----> (*) CanMarshalMsg +// |-----> (*) UnmarshalMsg +// |-----> (*) CanUnmarshalMsg +// |-----> (*) Msgsize +// |-----> (*) MsgIsZero +// +// ProverPersistedFields +// |-----> (*) MarshalMsg +// |-----> (*) CanMarshalMsg +// |-----> (*) UnmarshalMsg +// |-----> (*) CanUnmarshalMsg +// |-----> (*) Msgsize +// |-----> (*) MsgIsZero +// // Reveal // |-----> (*) MarshalMsg // |-----> (*) CanMarshalMsg @@ -62,36 +62,75 @@ import ( // // MarshalMsg implements msgp.Marshaler -func (z *Builder) MarshalMsg(b []byte) (o []byte) { +func (z *MessageHash) MarshalMsg(b []byte) (o []byte) { + o = msgp.Require(b, z.Msgsize()) + o = msgp.AppendBytes(o, (*z)[:]) + return +} + +func (_ *MessageHash) CanMarshalMsg(z interface{}) bool { + _, ok := (z).(*MessageHash) + return ok +} + +// UnmarshalMsg implements msgp.Unmarshaler +func (z *MessageHash) UnmarshalMsg(bts []byte) (o []byte, err error) { + bts, err = msgp.ReadExactBytes(bts, (*z)[:]) + if err != nil { + err = msgp.WrapError(err) + return + } + o = bts + return +} + +func (_ *MessageHash) CanUnmarshalMsg(z interface{}) bool { + _, ok := (z).(*MessageHash) + return ok +} + +// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message +func (z *MessageHash) Msgsize() (s int) { + s = msgp.ArrayHeaderSize + (32 * (msgp.ByteSize)) + return +} + +// MsgIsZero returns whether this is a zero value +func (z *MessageHash) MsgIsZero() bool { + return (*z) == (MessageHash{}) +} + +// MarshalMsg implements msgp.Marshaler +func (z *Prover) MarshalMsg(b []byte) (o []byte) { o = msgp.Require(b, z.Msgsize()) // omitempty: check for empty values zb0004Len := uint32(7) var zb0004Mask uint16 /* 11 bits */ - if (*z).BuilderPersistedFields.Data == (MessageHash{}) { + if (*z).ProverPersistedFields.Data == (MessageHash{}) { zb0004Len-- zb0004Mask |= 0x4 } - if (*z).BuilderPersistedFields.LnProvenWeight == 0 { + if (*z).ProverPersistedFields.LnProvenWeight == 0 { zb0004Len-- zb0004Mask |= 0x8 } - if len((*z).BuilderPersistedFields.Participants) == 0 { + if len((*z).ProverPersistedFields.Participants) == 0 { zb0004Len-- zb0004Mask |= 0x10 } - if (*z).BuilderPersistedFields.Parttree == nil { + if (*z).ProverPersistedFields.Parttree == nil { zb0004Len-- zb0004Mask |= 0x20 } - if (*z).BuilderPersistedFields.ProvenWeight == 0 { + if (*z).ProverPersistedFields.ProvenWeight == 0 { zb0004Len-- zb0004Mask |= 0x40 } - if (*z).BuilderPersistedFields.Round == 0 { + if (*z).ProverPersistedFields.Round == 0 { zb0004Len-- zb0004Mask |= 0x80 } - if (*z).BuilderPersistedFields.StrengthTarget == 0 { + if (*z).ProverPersistedFields.StrengthTarget == 0 { zb0004Len-- zb0004Mask |= 0x400 } @@ -101,60 +140,60 @@ func (z *Builder) MarshalMsg(b []byte) (o []byte) { if (zb0004Mask & 0x4) == 0 { // if not empty // string "data" o = append(o, 0xa4, 0x64, 0x61, 0x74, 0x61) - o = msgp.AppendBytes(o, ((*z).BuilderPersistedFields.Data)[:]) + o = msgp.AppendBytes(o, ((*z).ProverPersistedFields.Data)[:]) } if (zb0004Mask & 0x8) == 0 { // if not empty // string "lnprv" o = append(o, 0xa5, 0x6c, 0x6e, 0x70, 0x72, 0x76) - o = msgp.AppendUint64(o, (*z).BuilderPersistedFields.LnProvenWeight) + o = msgp.AppendUint64(o, (*z).ProverPersistedFields.LnProvenWeight) } if (zb0004Mask & 0x10) == 0 { // if not empty // string "parts" o = append(o, 0xa5, 0x70, 0x61, 0x72, 0x74, 0x73) - if (*z).BuilderPersistedFields.Participants == nil { + if (*z).ProverPersistedFields.Participants == nil { o = msgp.AppendNil(o) } else { - o = msgp.AppendArrayHeader(o, uint32(len((*z).BuilderPersistedFields.Participants))) + o = msgp.AppendArrayHeader(o, uint32(len((*z).ProverPersistedFields.Participants))) } - for zb0002 := range (*z).BuilderPersistedFields.Participants { - o = (*z).BuilderPersistedFields.Participants[zb0002].MarshalMsg(o) + for zb0002 := range (*z).ProverPersistedFields.Participants { + o = (*z).ProverPersistedFields.Participants[zb0002].MarshalMsg(o) } } if (zb0004Mask & 0x20) == 0 { // if not empty // string "parttree" o = append(o, 0xa8, 0x70, 0x61, 0x72, 0x74, 0x74, 0x72, 0x65, 0x65) - if (*z).BuilderPersistedFields.Parttree == nil { + if (*z).ProverPersistedFields.Parttree == nil { o = msgp.AppendNil(o) } else { - o = (*z).BuilderPersistedFields.Parttree.MarshalMsg(o) + o = (*z).ProverPersistedFields.Parttree.MarshalMsg(o) } } if (zb0004Mask & 0x40) == 0 { // if not empty // string "prv" o = append(o, 0xa3, 0x70, 0x72, 0x76) - o = msgp.AppendUint64(o, (*z).BuilderPersistedFields.ProvenWeight) + o = msgp.AppendUint64(o, (*z).ProverPersistedFields.ProvenWeight) } if (zb0004Mask & 0x80) == 0 { // if not empty // string "rnd" o = append(o, 0xa3, 0x72, 0x6e, 0x64) - o = msgp.AppendUint64(o, (*z).BuilderPersistedFields.Round) + o = msgp.AppendUint64(o, (*z).ProverPersistedFields.Round) } if (zb0004Mask & 0x400) == 0 { // if not empty // string "str" o = append(o, 0xa3, 0x73, 0x74, 0x72) - o = msgp.AppendUint64(o, (*z).BuilderPersistedFields.StrengthTarget) + o = msgp.AppendUint64(o, (*z).ProverPersistedFields.StrengthTarget) } } return } -func (_ *Builder) CanMarshalMsg(z interface{}) bool { - _, ok := (z).(*Builder) +func (_ *Prover) CanMarshalMsg(z interface{}) bool { + _, ok := (z).(*Prover) return ok } // UnmarshalMsg implements msgp.Unmarshaler -func (z *Builder) UnmarshalMsg(bts []byte) (o []byte, err error) { +func (z *Prover) UnmarshalMsg(bts []byte) (o []byte, err error) { var field []byte _ = field var zb0004 int @@ -168,7 +207,7 @@ func (z *Builder) UnmarshalMsg(bts []byte) (o []byte, err error) { } if zb0004 > 0 { zb0004-- - bts, err = msgp.ReadExactBytes(bts, ((*z).BuilderPersistedFields.Data)[:]) + bts, err = msgp.ReadExactBytes(bts, ((*z).ProverPersistedFields.Data)[:]) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Data") return @@ -176,7 +215,7 @@ func (z *Builder) UnmarshalMsg(bts []byte) (o []byte, err error) { } if zb0004 > 0 { zb0004-- - (*z).BuilderPersistedFields.Round, bts, err = msgp.ReadUint64Bytes(bts) + (*z).ProverPersistedFields.Round, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Round") return @@ -197,14 +236,14 @@ func (z *Builder) UnmarshalMsg(bts []byte) (o []byte, err error) { return } if zb0007 { - (*z).BuilderPersistedFields.Participants = nil - } else if (*z).BuilderPersistedFields.Participants != nil && cap((*z).BuilderPersistedFields.Participants) >= zb0006 { - (*z).BuilderPersistedFields.Participants = ((*z).BuilderPersistedFields.Participants)[:zb0006] + (*z).ProverPersistedFields.Participants = nil + } else if (*z).ProverPersistedFields.Participants != nil && cap((*z).ProverPersistedFields.Participants) >= zb0006 { + (*z).ProverPersistedFields.Participants = ((*z).ProverPersistedFields.Participants)[:zb0006] } else { - (*z).BuilderPersistedFields.Participants = make([]basics.Participant, zb0006) + (*z).ProverPersistedFields.Participants = make([]basics.Participant, zb0006) } - for zb0002 := range (*z).BuilderPersistedFields.Participants { - bts, err = (*z).BuilderPersistedFields.Participants[zb0002].UnmarshalMsg(bts) + for zb0002 := range (*z).ProverPersistedFields.Participants { + bts, err = (*z).ProverPersistedFields.Participants[zb0002].UnmarshalMsg(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Participants", zb0002) return @@ -218,12 +257,12 @@ func (z *Builder) UnmarshalMsg(bts []byte) (o []byte, err error) { if err != nil { return } - (*z).BuilderPersistedFields.Parttree = nil + (*z).ProverPersistedFields.Parttree = nil } else { - if (*z).BuilderPersistedFields.Parttree == nil { - (*z).BuilderPersistedFields.Parttree = new(merklearray.Tree) + if (*z).ProverPersistedFields.Parttree == nil { + (*z).ProverPersistedFields.Parttree = new(merklearray.Tree) } - bts, err = (*z).BuilderPersistedFields.Parttree.UnmarshalMsg(bts) + bts, err = (*z).ProverPersistedFields.Parttree.UnmarshalMsg(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Parttree") return @@ -232,7 +271,7 @@ func (z *Builder) UnmarshalMsg(bts []byte) (o []byte, err error) { } if zb0004 > 0 { zb0004-- - (*z).BuilderPersistedFields.LnProvenWeight, bts, err = msgp.ReadUint64Bytes(bts) + (*z).ProverPersistedFields.LnProvenWeight, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "LnProvenWeight") return @@ -240,7 +279,7 @@ func (z *Builder) UnmarshalMsg(bts []byte) (o []byte, err error) { } if zb0004 > 0 { zb0004-- - (*z).BuilderPersistedFields.ProvenWeight, bts, err = msgp.ReadUint64Bytes(bts) + (*z).ProverPersistedFields.ProvenWeight, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "ProvenWeight") return @@ -248,7 +287,7 @@ func (z *Builder) UnmarshalMsg(bts []byte) (o []byte, err error) { } if zb0004 > 0 { zb0004-- - (*z).BuilderPersistedFields.StrengthTarget, bts, err = msgp.ReadUint64Bytes(bts) + (*z).ProverPersistedFields.StrengthTarget, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "StrengthTarget") return @@ -267,7 +306,7 @@ func (z *Builder) UnmarshalMsg(bts []byte) (o []byte, err error) { return } if zb0005 { - (*z) = Builder{} + (*z) = Prover{} } for zb0004 > 0 { zb0004-- @@ -278,13 +317,13 @@ func (z *Builder) UnmarshalMsg(bts []byte) (o []byte, err error) { } switch string(field) { case "data": - bts, err = msgp.ReadExactBytes(bts, ((*z).BuilderPersistedFields.Data)[:]) + bts, err = msgp.ReadExactBytes(bts, ((*z).ProverPersistedFields.Data)[:]) if err != nil { err = msgp.WrapError(err, "Data") return } case "rnd": - (*z).BuilderPersistedFields.Round, bts, err = msgp.ReadUint64Bytes(bts) + (*z).ProverPersistedFields.Round, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "Round") return @@ -303,14 +342,14 @@ func (z *Builder) UnmarshalMsg(bts []byte) (o []byte, err error) { return } if zb0009 { - (*z).BuilderPersistedFields.Participants = nil - } else if (*z).BuilderPersistedFields.Participants != nil && cap((*z).BuilderPersistedFields.Participants) >= zb0008 { - (*z).BuilderPersistedFields.Participants = ((*z).BuilderPersistedFields.Participants)[:zb0008] + (*z).ProverPersistedFields.Participants = nil + } else if (*z).ProverPersistedFields.Participants != nil && cap((*z).ProverPersistedFields.Participants) >= zb0008 { + (*z).ProverPersistedFields.Participants = ((*z).ProverPersistedFields.Participants)[:zb0008] } else { - (*z).BuilderPersistedFields.Participants = make([]basics.Participant, zb0008) + (*z).ProverPersistedFields.Participants = make([]basics.Participant, zb0008) } - for zb0002 := range (*z).BuilderPersistedFields.Participants { - bts, err = (*z).BuilderPersistedFields.Participants[zb0002].UnmarshalMsg(bts) + for zb0002 := range (*z).ProverPersistedFields.Participants { + bts, err = (*z).ProverPersistedFields.Participants[zb0002].UnmarshalMsg(bts) if err != nil { err = msgp.WrapError(err, "Participants", zb0002) return @@ -322,31 +361,31 @@ func (z *Builder) UnmarshalMsg(bts []byte) (o []byte, err error) { if err != nil { return } - (*z).BuilderPersistedFields.Parttree = nil + (*z).ProverPersistedFields.Parttree = nil } else { - if (*z).BuilderPersistedFields.Parttree == nil { - (*z).BuilderPersistedFields.Parttree = new(merklearray.Tree) + if (*z).ProverPersistedFields.Parttree == nil { + (*z).ProverPersistedFields.Parttree = new(merklearray.Tree) } - bts, err = (*z).BuilderPersistedFields.Parttree.UnmarshalMsg(bts) + bts, err = (*z).ProverPersistedFields.Parttree.UnmarshalMsg(bts) if err != nil { err = msgp.WrapError(err, "Parttree") return } } case "lnprv": - (*z).BuilderPersistedFields.LnProvenWeight, bts, err = msgp.ReadUint64Bytes(bts) + (*z).ProverPersistedFields.LnProvenWeight, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "LnProvenWeight") return } case "prv": - (*z).BuilderPersistedFields.ProvenWeight, bts, err = msgp.ReadUint64Bytes(bts) + (*z).ProverPersistedFields.ProvenWeight, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "ProvenWeight") return } case "str": - (*z).BuilderPersistedFields.StrengthTarget, bts, err = msgp.ReadUint64Bytes(bts) + (*z).ProverPersistedFields.StrengthTarget, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "StrengthTarget") return @@ -364,34 +403,34 @@ func (z *Builder) UnmarshalMsg(bts []byte) (o []byte, err error) { return } -func (_ *Builder) CanUnmarshalMsg(z interface{}) bool { - _, ok := (z).(*Builder) +func (_ *Prover) CanUnmarshalMsg(z interface{}) bool { + _, ok := (z).(*Prover) return ok } // Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message -func (z *Builder) Msgsize() (s int) { +func (z *Prover) Msgsize() (s int) { s = 1 + 5 + msgp.ArrayHeaderSize + (32 * (msgp.ByteSize)) + 4 + msgp.Uint64Size + 6 + msgp.ArrayHeaderSize - for zb0002 := range (*z).BuilderPersistedFields.Participants { - s += (*z).BuilderPersistedFields.Participants[zb0002].Msgsize() + for zb0002 := range (*z).ProverPersistedFields.Participants { + s += (*z).ProverPersistedFields.Participants[zb0002].Msgsize() } s += 9 - if (*z).BuilderPersistedFields.Parttree == nil { + if (*z).ProverPersistedFields.Parttree == nil { s += msgp.NilSize } else { - s += (*z).BuilderPersistedFields.Parttree.Msgsize() + s += (*z).ProverPersistedFields.Parttree.Msgsize() } s += 6 + msgp.Uint64Size + 4 + msgp.Uint64Size + 4 + msgp.Uint64Size return } // MsgIsZero returns whether this is a zero value -func (z *Builder) MsgIsZero() bool { - return ((*z).BuilderPersistedFields.Data == (MessageHash{})) && ((*z).BuilderPersistedFields.Round == 0) && (len((*z).BuilderPersistedFields.Participants) == 0) && ((*z).BuilderPersistedFields.Parttree == nil) && ((*z).BuilderPersistedFields.LnProvenWeight == 0) && ((*z).BuilderPersistedFields.ProvenWeight == 0) && ((*z).BuilderPersistedFields.StrengthTarget == 0) +func (z *Prover) MsgIsZero() bool { + return ((*z).ProverPersistedFields.Data == (MessageHash{})) && ((*z).ProverPersistedFields.Round == 0) && (len((*z).ProverPersistedFields.Participants) == 0) && ((*z).ProverPersistedFields.Parttree == nil) && ((*z).ProverPersistedFields.LnProvenWeight == 0) && ((*z).ProverPersistedFields.ProvenWeight == 0) && ((*z).ProverPersistedFields.StrengthTarget == 0) } // MarshalMsg implements msgp.Marshaler -func (z *BuilderPersistedFields) MarshalMsg(b []byte) (o []byte) { +func (z *ProverPersistedFields) MarshalMsg(b []byte) (o []byte) { o = msgp.Require(b, z.Msgsize()) // omitempty: check for empty values zb0003Len := uint32(7) @@ -477,13 +516,13 @@ func (z *BuilderPersistedFields) MarshalMsg(b []byte) (o []byte) { return } -func (_ *BuilderPersistedFields) CanMarshalMsg(z interface{}) bool { - _, ok := (z).(*BuilderPersistedFields) +func (_ *ProverPersistedFields) CanMarshalMsg(z interface{}) bool { + _, ok := (z).(*ProverPersistedFields) return ok } // UnmarshalMsg implements msgp.Unmarshaler -func (z *BuilderPersistedFields) UnmarshalMsg(bts []byte) (o []byte, err error) { +func (z *ProverPersistedFields) UnmarshalMsg(bts []byte) (o []byte, err error) { var field []byte _ = field var zb0003 int @@ -596,7 +635,7 @@ func (z *BuilderPersistedFields) UnmarshalMsg(bts []byte) (o []byte, err error) return } if zb0004 { - (*z) = BuilderPersistedFields{} + (*z) = ProverPersistedFields{} } for zb0003 > 0 { zb0003-- @@ -693,13 +732,13 @@ func (z *BuilderPersistedFields) UnmarshalMsg(bts []byte) (o []byte, err error) return } -func (_ *BuilderPersistedFields) CanUnmarshalMsg(z interface{}) bool { - _, ok := (z).(*BuilderPersistedFields) +func (_ *ProverPersistedFields) CanUnmarshalMsg(z interface{}) bool { + _, ok := (z).(*ProverPersistedFields) return ok } // Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message -func (z *BuilderPersistedFields) Msgsize() (s int) { +func (z *ProverPersistedFields) Msgsize() (s int) { s = 1 + 5 + msgp.ArrayHeaderSize + (32 * (msgp.ByteSize)) + 4 + msgp.Uint64Size + 6 + msgp.ArrayHeaderSize for zb0002 := range (*z).Participants { s += (*z).Participants[zb0002].Msgsize() @@ -715,49 +754,10 @@ func (z *BuilderPersistedFields) Msgsize() (s int) { } // MsgIsZero returns whether this is a zero value -func (z *BuilderPersistedFields) MsgIsZero() bool { +func (z *ProverPersistedFields) MsgIsZero() bool { return ((*z).Data == (MessageHash{})) && ((*z).Round == 0) && (len((*z).Participants) == 0) && ((*z).Parttree == nil) && ((*z).LnProvenWeight == 0) && ((*z).ProvenWeight == 0) && ((*z).StrengthTarget == 0) } -// MarshalMsg implements msgp.Marshaler -func (z *MessageHash) MarshalMsg(b []byte) (o []byte) { - o = msgp.Require(b, z.Msgsize()) - o = msgp.AppendBytes(o, (*z)[:]) - return -} - -func (_ *MessageHash) CanMarshalMsg(z interface{}) bool { - _, ok := (z).(*MessageHash) - return ok -} - -// UnmarshalMsg implements msgp.Unmarshaler -func (z *MessageHash) UnmarshalMsg(bts []byte) (o []byte, err error) { - bts, err = msgp.ReadExactBytes(bts, (*z)[:]) - if err != nil { - err = msgp.WrapError(err) - return - } - o = bts - return -} - -func (_ *MessageHash) CanUnmarshalMsg(z interface{}) bool { - _, ok := (z).(*MessageHash) - return ok -} - -// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message -func (z *MessageHash) Msgsize() (s int) { - s = msgp.ArrayHeaderSize + (32 * (msgp.ByteSize)) - return -} - -// MsgIsZero returns whether this is a zero value -func (z *MessageHash) MsgIsZero() bool { - return (*z) == (MessageHash{}) -} - // MarshalMsg implements msgp.Marshaler func (z *Reveal) MarshalMsg(b []byte) (o []byte) { o = msgp.Require(b, z.Msgsize()) diff --git a/crypto/stateproof/msgp_gen_test.go b/crypto/stateproof/msgp_gen_test.go index c9e9699668..2812908b34 100644 --- a/crypto/stateproof/msgp_gen_test.go +++ b/crypto/stateproof/msgp_gen_test.go @@ -14,9 +14,9 @@ import ( "github.com/algorand/go-algorand/test/partitiontest" ) -func TestMarshalUnmarshalBuilder(t *testing.T) { +func TestMarshalUnmarshalMessageHash(t *testing.T) { partitiontest.PartitionTest(t) - v := Builder{} + v := MessageHash{} bts := v.MarshalMsg(nil) left, err := v.UnmarshalMsg(bts) if err != nil { @@ -35,12 +35,12 @@ func TestMarshalUnmarshalBuilder(t *testing.T) { } } -func TestRandomizedEncodingBuilder(t *testing.T) { - protocol.RunEncodingTest(t, &Builder{}) +func TestRandomizedEncodingMessageHash(t *testing.T) { + protocol.RunEncodingTest(t, &MessageHash{}) } -func BenchmarkMarshalMsgBuilder(b *testing.B) { - v := Builder{} +func BenchmarkMarshalMsgMessageHash(b *testing.B) { + v := MessageHash{} b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { @@ -48,8 +48,8 @@ func BenchmarkMarshalMsgBuilder(b *testing.B) { } } -func BenchmarkAppendMsgBuilder(b *testing.B) { - v := Builder{} +func BenchmarkAppendMsgMessageHash(b *testing.B) { + v := MessageHash{} bts := make([]byte, 0, v.Msgsize()) bts = v.MarshalMsg(bts[0:0]) b.SetBytes(int64(len(bts))) @@ -60,8 +60,8 @@ func BenchmarkAppendMsgBuilder(b *testing.B) { } } -func BenchmarkUnmarshalBuilder(b *testing.B) { - v := Builder{} +func BenchmarkUnmarshalMessageHash(b *testing.B) { + v := MessageHash{} bts := v.MarshalMsg(nil) b.ReportAllocs() b.SetBytes(int64(len(bts))) @@ -74,9 +74,9 @@ func BenchmarkUnmarshalBuilder(b *testing.B) { } } -func TestMarshalUnmarshalBuilderPersistedFields(t *testing.T) { +func TestMarshalUnmarshalProver(t *testing.T) { partitiontest.PartitionTest(t) - v := BuilderPersistedFields{} + v := Prover{} bts := v.MarshalMsg(nil) left, err := v.UnmarshalMsg(bts) if err != nil { @@ -95,12 +95,12 @@ func TestMarshalUnmarshalBuilderPersistedFields(t *testing.T) { } } -func TestRandomizedEncodingBuilderPersistedFields(t *testing.T) { - protocol.RunEncodingTest(t, &BuilderPersistedFields{}) +func TestRandomizedEncodingProver(t *testing.T) { + protocol.RunEncodingTest(t, &Prover{}) } -func BenchmarkMarshalMsgBuilderPersistedFields(b *testing.B) { - v := BuilderPersistedFields{} +func BenchmarkMarshalMsgProver(b *testing.B) { + v := Prover{} b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { @@ -108,8 +108,8 @@ func BenchmarkMarshalMsgBuilderPersistedFields(b *testing.B) { } } -func BenchmarkAppendMsgBuilderPersistedFields(b *testing.B) { - v := BuilderPersistedFields{} +func BenchmarkAppendMsgProver(b *testing.B) { + v := Prover{} bts := make([]byte, 0, v.Msgsize()) bts = v.MarshalMsg(bts[0:0]) b.SetBytes(int64(len(bts))) @@ -120,8 +120,8 @@ func BenchmarkAppendMsgBuilderPersistedFields(b *testing.B) { } } -func BenchmarkUnmarshalBuilderPersistedFields(b *testing.B) { - v := BuilderPersistedFields{} +func BenchmarkUnmarshalProver(b *testing.B) { + v := Prover{} bts := v.MarshalMsg(nil) b.ReportAllocs() b.SetBytes(int64(len(bts))) @@ -134,9 +134,9 @@ func BenchmarkUnmarshalBuilderPersistedFields(b *testing.B) { } } -func TestMarshalUnmarshalMessageHash(t *testing.T) { +func TestMarshalUnmarshalProverPersistedFields(t *testing.T) { partitiontest.PartitionTest(t) - v := MessageHash{} + v := ProverPersistedFields{} bts := v.MarshalMsg(nil) left, err := v.UnmarshalMsg(bts) if err != nil { @@ -155,12 +155,12 @@ func TestMarshalUnmarshalMessageHash(t *testing.T) { } } -func TestRandomizedEncodingMessageHash(t *testing.T) { - protocol.RunEncodingTest(t, &MessageHash{}) +func TestRandomizedEncodingProverPersistedFields(t *testing.T) { + protocol.RunEncodingTest(t, &ProverPersistedFields{}) } -func BenchmarkMarshalMsgMessageHash(b *testing.B) { - v := MessageHash{} +func BenchmarkMarshalMsgProverPersistedFields(b *testing.B) { + v := ProverPersistedFields{} b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { @@ -168,8 +168,8 @@ func BenchmarkMarshalMsgMessageHash(b *testing.B) { } } -func BenchmarkAppendMsgMessageHash(b *testing.B) { - v := MessageHash{} +func BenchmarkAppendMsgProverPersistedFields(b *testing.B) { + v := ProverPersistedFields{} bts := make([]byte, 0, v.Msgsize()) bts = v.MarshalMsg(bts[0:0]) b.SetBytes(int64(len(bts))) @@ -180,8 +180,8 @@ func BenchmarkAppendMsgMessageHash(b *testing.B) { } } -func BenchmarkUnmarshalMessageHash(b *testing.B) { - v := MessageHash{} +func BenchmarkUnmarshalProverPersistedFields(b *testing.B) { + v := ProverPersistedFields{} bts := v.MarshalMsg(nil) b.ReportAllocs() b.SetBytes(int64(len(bts))) diff --git a/crypto/stateproof/builder.go b/crypto/stateproof/prover.go similarity index 82% rename from crypto/stateproof/builder.go rename to crypto/stateproof/prover.go index 84f82a9c91..64484ce35c 100644 --- a/crypto/stateproof/builder.go +++ b/crypto/stateproof/prover.go @@ -26,7 +26,7 @@ import ( "github.com/algorand/go-algorand/data/basics" ) -// Errors for the StateProof builder +// Errors for the StateProof prover var ( ErrPositionOutOfBound = errors.New("requested position is out of bounds") ErrPositionAlreadyPresent = errors.New("requested position is already present") @@ -37,8 +37,8 @@ var ( // VotersAllocBound should be equal to config.Consensus[protocol.ConsensusCurrentVersion].StateProofTopVoters const VotersAllocBound = 1024 -// BuilderPersistedFields is the set of fields of a Builder that are persisted to disk. -type BuilderPersistedFields struct { +// ProverPersistedFields is the set of fields from the crypto state proof prover that are persisted to disk. +type ProverPersistedFields struct { _struct struct{} `codec:",omitempty,omitemptyarray"` Data MessageHash `codec:"data"` Round uint64 `codec:"rnd"` @@ -49,25 +49,25 @@ type BuilderPersistedFields struct { StrengthTarget uint64 `codec:"str"` } -// Builder keeps track of signatures on a message and eventually produces +// Prover keeps track of signatures on a message and eventually produces // a state proof for that message. -type Builder struct { - BuilderPersistedFields +type Prover struct { + ProverPersistedFields sigs []sigslot // Indexed by pos in Participants signedWeight uint64 // Total weight of signatures so far cachedProof *StateProof } -// MakeBuilder constructs an empty builder. After adding enough signatures and signed weight, this builder is used to create a stateproof. -func MakeBuilder(data MessageHash, round uint64, provenWeight uint64, part []basics.Participant, parttree *merklearray.Tree, strengthTarget uint64) (*Builder, error) { +// MakeProver constructs an empty sp-prover. After adding enough signatures and signed weight, this sp-prover is used to create a stateproof. +func MakeProver(data MessageHash, round uint64, provenWeight uint64, part []basics.Participant, parttree *merklearray.Tree, strengthTarget uint64) (*Prover, error) { npart := len(part) lnProvenWt, err := LnIntApproximation(provenWeight) if err != nil { return nil, err } - b := &Builder{ - BuilderPersistedFields: BuilderPersistedFields{ + b := &Prover{ + ProverPersistedFields: ProverPersistedFields{ Data: data, Round: round, Participants: part, @@ -85,9 +85,9 @@ func MakeBuilder(data MessageHash, round uint64, provenWeight uint64, part []bas return b, nil } -// Present checks if the builder already contains a signature at a particular +// Present checks if the prover already contains a signature at a particular // offset. -func (b *Builder) Present(pos uint64) (bool, error) { +func (b *Prover) Present(pos uint64) (bool, error) { if pos >= uint64(len(b.sigs)) { return false, fmt.Errorf("%w pos %d >= len(b.sigs) %d", ErrPositionOutOfBound, pos, len(b.sigs)) } @@ -95,9 +95,9 @@ func (b *Builder) Present(pos uint64) (bool, error) { return b.sigs[pos].Weight != 0, nil } -// IsValid verifies that the participant along with the signature can be inserted to the builder. +// IsValid verifies that the participant along with the signature can be inserted to the prover. // verifySig can be set to false when the signature is already verified (e.g. loaded from the DB) -func (b *Builder) IsValid(pos uint64, sig *merklesignature.Signature, verifySig bool) error { +func (b *Prover) IsValid(pos uint64, sig *merklesignature.Signature, verifySig bool) error { if pos >= uint64(len(b.Participants)) { return fmt.Errorf("%w pos %d >= len(participants) %d", ErrPositionOutOfBound, pos, len(b.Participants)) } @@ -105,7 +105,7 @@ func (b *Builder) IsValid(pos uint64, sig *merklesignature.Signature, verifySig p := b.Participants[pos] if p.Weight == 0 { - return fmt.Errorf("builder.IsValid: %w: position = %d", ErrPositionWithZeroWeight, pos) + return fmt.Errorf("prover.IsValid: %w: position = %d", ErrPositionWithZeroWeight, pos) } // Check signature @@ -121,7 +121,7 @@ func (b *Builder) IsValid(pos uint64, sig *merklesignature.Signature, verifySig } // Add a signature to the set of signatures available for building a proof. -func (b *Builder) Add(pos uint64, sig merklesignature.Signature) error { +func (b *Prover) Add(pos uint64, sig merklesignature.Signature) error { isPresent, err := b.Present(pos) if err != nil { return err @@ -141,12 +141,12 @@ func (b *Builder) Add(pos uint64, sig merklesignature.Signature) error { } // Ready returns whether the state proof is ready to be built. -func (b *Builder) Ready() bool { +func (b *Prover) Ready() bool { return b.cachedProof != nil || b.signedWeight > b.ProvenWeight } // SignedWeight returns the total weight of signatures added so far. -func (b *Builder) SignedWeight() uint64 { +func (b *Prover) SignedWeight() uint64 { return b.signedWeight } @@ -156,7 +156,7 @@ func (b *Builder) SignedWeight() uint64 { // coinWeight. // // coinIndex works by doing a binary search on the sigs array. -func (b *Builder) coinIndex(coinWeight uint64) (uint64, error) { +func (b *Prover) coinIndex(coinWeight uint64) (uint64, error) { lo := uint64(0) hi := uint64(len(b.sigs)) @@ -179,9 +179,9 @@ again: goto again } -// Build returns a state proof, if the builder has accumulated +// CreateProof returns a state proof, if the prover has accumulated // enough signatures to construct it. -func (b *Builder) Build() (*StateProof, error) { +func (b *Prover) CreateProof() (*StateProof, error) { if b.cachedProof != nil { return b.cachedProof, nil } @@ -271,7 +271,7 @@ func (b *Builder) Build() (*StateProof, error) { return s, nil } -// AllocSigs should only be used after decoding msgpacked Builder, as the sigs field is not exported and encoded -func (b *Builder) AllocSigs() { +// AllocSigs should only be used after decoding msgpacked Prover, as the sigs field is not exported and encoded +func (b *Prover) AllocSigs() { b.sigs = make([]sigslot, len(b.Participants)) } diff --git a/crypto/stateproof/builder_test.go b/crypto/stateproof/prover_test.go similarity index 94% rename from crypto/stateproof/builder_test.go rename to crypto/stateproof/prover_test.go index 0bd587be70..6d374e0599 100644 --- a/crypto/stateproof/builder_test.go +++ b/crypto/stateproof/prover_test.go @@ -51,7 +51,7 @@ type paramsForTest struct { partCommitment crypto.GenericDigest numberOfParticipnets uint64 data MessageHash - builder *Builder + builder *Prover sig merklesignature.Signature } @@ -122,7 +122,7 @@ func generateProofForTesting(a *require.Assertions, doLargeTest bool) paramsForT partcom, err := merklearray.BuildVectorCommitmentTree(basics.ParticipantsArray(parts), crypto.HashFactory{HashType: HashType}) a.NoError(err) - b, err := MakeBuilder(data, stateProofIntervalForTests, uint64(totalWeight/2), parts, partcom, stateProofStrengthTargetForTests) + b, err := MakeProver(data, stateProofIntervalForTests, uint64(totalWeight/2), parts, partcom, stateProofStrengthTargetForTests) a.NoError(err) for i := uint64(0); i < uint64(npart)/2+10; i++ { // leave some signature to be added later in the test (if needed) @@ -136,7 +136,7 @@ func generateProofForTesting(a *require.Assertions, doLargeTest bool) paramsForT a.True(isPresent) } - proof, err := b.Build() + proof, err := b.CreateProof() a.NoError(err) p := paramsForTest{ @@ -285,7 +285,7 @@ func TestSignatureCommitmentBinaryFormat(t *testing.T) { partcom, err := merklearray.BuildVectorCommitmentTree(basics.ParticipantsArray(parts), crypto.HashFactory{HashType: HashType}) a.NoError(err) - b, err := MakeBuilder(data, stateProofIntervalForTests, uint64(totalWeight/(2*numPart)), parts, partcom, stateProofStrengthTargetForTests) + b, err := MakeProver(data, stateProofIntervalForTests, uint64(totalWeight/(2*numPart)), parts, partcom, stateProofStrengthTargetForTests) a.NoError(err) for i := 0; i < numPart; i++ { @@ -294,7 +294,7 @@ func TestSignatureCommitmentBinaryFormat(t *testing.T) { b.Add(uint64(i), sigs[i]) } - sProof, err := b.Build() + sProof, err := b.CreateProof() a.NoError(err) leaf0 := calculateHashOnSigLeaf(t, sigs[0], findLInProof(a, sigs[0], sProof)) @@ -466,7 +466,7 @@ func TestBuilder_AddRejectsInvalidSigVersion(t *testing.T) { partcom, err := merklearray.BuildVectorCommitmentTree(basics.ParticipantsArray(parts), crypto.HashFactory{HashType: HashType}) a.NoError(err) - builder, err := MakeBuilder(data, stateProofIntervalForTests, uint64(totalWeight/2), parts, partcom, stateProofStrengthTargetForTests) + builder, err := MakeProver(data, stateProofIntervalForTests, uint64(totalWeight/2), parts, partcom, stateProofStrengthTargetForTests) a.NoError(err) // actual test: @@ -490,21 +490,21 @@ func TestBuildAndReady(t *testing.T) { partcom, err := merklearray.BuildVectorCommitmentTree(basics.ParticipantsArray(parts), crypto.HashFactory{HashType: HashType}) a.NoError(err) - builder, err := MakeBuilder(data, stateProofIntervalForTests, uint64(totalWeight/2), parts, partcom, stateProofStrengthTargetForTests) + builder, err := MakeProver(data, stateProofIntervalForTests, uint64(totalWeight/2), parts, partcom, stateProofStrengthTargetForTests) a.NoError(err) a.False(builder.Ready()) - _, err = builder.Build() + _, err = builder.CreateProof() a.ErrorIs(err, ErrSignedWeightLessThanProvenWeight) builder.signedWeight = builder.ProvenWeight a.False(builder.Ready()) - _, err = builder.Build() + _, err = builder.CreateProof() a.ErrorIs(err, ErrSignedWeightLessThanProvenWeight) builder.signedWeight = builder.ProvenWeight + 1 a.True(builder.Ready()) - _, err = builder.Build() + _, err = builder.CreateProof() a.NotErrorIs(err, ErrSignedWeightLessThanProvenWeight) } @@ -513,7 +513,7 @@ func TestErrorCases(t *testing.T) { partitiontest.PartitionTest(t) a := require.New(t) - builder := Builder{} + builder := Prover{} _, err := builder.Present(1) a.ErrorIs(err, ErrPositionOutOfBound) @@ -542,7 +542,7 @@ func TestErrorCases(t *testing.T) { } func checkSigsArray(n int, a *require.Assertions) { - b := &Builder{ + b := &Prover{ sigs: make([]sigslot, n), } for i := 0; i < n; i++ { @@ -578,7 +578,7 @@ func TestCoinIndexBetweenWeights(t *testing.T) { a := require.New(t) n := 1000 - b := &Builder{ + b := &Prover{ sigs: make([]sigslot, n), } for i := 0; i < n; i++ { @@ -605,7 +605,7 @@ func TestBuilderWithZeroProvenWeight(t *testing.T) { data := testMessage("hello world").IntoStateProofMessageHash() - _, err := MakeBuilder(data, stateProofIntervalForTests, 0, nil, nil, stateProofStrengthTargetForTests) + _, err := MakeProver(data, stateProofIntervalForTests, 0, nil, nil, stateProofStrengthTargetForTests) a.ErrorIs(err, ErrIllegalInputForLnApprox) } @@ -615,17 +615,17 @@ func TestBuilder_BuildStateProofCache(t *testing.T) { a := require.New(t) p := generateProofForTesting(a, true) sp1 := &p.sp - sp2, err := p.builder.Build() + sp2, err := p.builder.CreateProof() a.NoError(err) a.Equal(sp1, sp2) // already built, no signatures added err = p.builder.Add(p.numberOfParticipnets-1, p.sig) a.NoError(err) - sp3, err := p.builder.Build() + sp3, err := p.builder.CreateProof() a.NoError(err) a.NotEqual(sp1, sp3) // better StateProof with added signature should have been built - sp4, err := p.builder.Build() + sp4, err := p.builder.CreateProof() a.NoError(err) a.Equal(sp3, sp4) @@ -675,7 +675,7 @@ func BenchmarkBuildVerify(b *testing.B) { b.Run("AddBuild", func(b *testing.B) { for i := 0; i < b.N; i++ { - builder, err := MakeBuilder(data, stateProofIntervalForTests, provenWeight, parts, partcom, stateProofStrengthTargetForTests) + builder, err := MakeProver(data, stateProofIntervalForTests, provenWeight, parts, partcom, stateProofStrengthTargetForTests) if err != nil { b.Error(err) } @@ -686,7 +686,7 @@ func BenchmarkBuildVerify(b *testing.B) { builder.Add(uint64(i), sigs[i]) } - sp, err = builder.Build() + sp, err = builder.CreateProof() if err != nil { b.Error(err) } diff --git a/data/pools/transactionPool_test.go b/data/pools/transactionPool_test.go index 78d3a79bc7..2952fb6f01 100644 --- a/data/pools/transactionPool_test.go +++ b/data/pools/transactionPool_test.go @@ -1586,7 +1586,7 @@ func generateProofForTesting( // Prepare the builder stateProofStrengthTargetForTests := config.Consensus[protocol.ConsensusCurrentVersion].StateProofStrengthTarget - b, err := cryptostateproof.MakeBuilder(data, round, provenWeight, + b, err := cryptostateproof.MakeProver(data, round, provenWeight, partArray, partTree, stateProofStrengthTargetForTests) require.NoError(t, err) @@ -1607,7 +1607,7 @@ func generateProofForTesting( } // Build the SP - proof, err := b.Build() + proof, err := b.CreateProof() require.NoError(t, err) return proof diff --git a/stateproof/builder.go b/stateproof/builder.go index caf9c7ba08..28d5d2b47f 100644 --- a/stateproof/builder.go +++ b/stateproof/builder.go @@ -27,6 +27,8 @@ import ( "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto/stateproof" "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/data/bookkeeping" + "github.com/algorand/go-algorand/data/stateproofmsg" "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/logging" @@ -37,6 +39,18 @@ import ( var errVotersNotTracked = errors.New("voters not tracked for the given lookback round") +// spProver captures the state proof cryptographic prover in addition to data needed for +// signatures aggregation. +type spProver struct { + _struct struct{} `codec:",omitempty,omitemptyarray"` + + *stateproof.Prover `codec:"prv"` + + AddrToPos map[Address]uint64 `codec:"addr,allocbound=stateproof.VotersAllocBound"` + VotersHdr bookkeeping.BlockHeader `codec:"hdr"` + Message stateproofmsg.Message `codec:"msg"` +} + // OnPrepareVoterCommit is a function called by the voters tracker when it's preparing to commit rnd. It gives the builder // the chance to persist the data it needs. func (spw *Worker) OnPrepareVoterCommit(oldBase basics.Round, newBase basics.Round, votersFetcher ledgercore.LedgerForSPBuilder) { @@ -52,18 +66,18 @@ func (spw *Worker) OnPrepareVoterCommit(oldBase basics.Round, newBase basics.Rou continue } - var builderExists bool + var proverExists bool err = spw.db.Atomic(func(ctx context.Context, tx *sql.Tx) (err error) { - builderExists, err = builderExistInDB(tx, rnd) + proverExists, err = proverExistInDB(tx, rnd) return err }) if err != nil { - spw.log.Warnf("OnPrepareVoterCommit(%d): could not check builder existence, assuming it doesn't exist: %v\n", rnd, err) - } else if builderExists { + spw.log.Warnf("OnPrepareVoterCommit(%d): could not check prover existence, assuming it doesn't exist: %v\n", rnd, err) + } else if proverExists { continue } - buildr, err := createBuilder(rnd, votersFetcher) + provr, err := createProver(rnd, votersFetcher) if err != nil { if errors.Is(err, errVotersNotTracked) { // There are few reasons why we might encounter a situation where we don't @@ -80,74 +94,74 @@ func (spw *Worker) OnPrepareVoterCommit(oldBase basics.Round, newBase basics.Rou continue } - spw.log.Errorf("OnPrepareVoterCommit(%d): could not create builder: %v", rnd, err) + spw.log.Errorf("OnPrepareVoterCommit(%d): could not create prover: %v", rnd, err) continue } // At this point, there is a possibility that the signer has already created this specific builder - // (signer created the builder after builderExistInDB was called and was fast enough to persist it). + // (signer created the builder after proverExistInDB was called and was fast enough to persist it). // In this case we will rewrite the new builder err = spw.db.Atomic(func(_ context.Context, tx *sql.Tx) error { - return persistBuilder(tx, rnd, &buildr) + return persistProver(tx, rnd, &provr) }) if err != nil { - spw.log.Errorf("OnPrepareVoterCommit(%d): could not persist builder: %v", rnd, err) + spw.log.Errorf("OnPrepareVoterCommit(%d): could not persist prover: %v", rnd, err) } } } -// loadOrCreateBuilderWithSignatures either loads a builder from the DB or creates a new builder. -// this function fills the builder with all the available signatures -func (spw *Worker) loadOrCreateBuilderWithSignatures(rnd basics.Round) (builder, error) { - b, err := spw.loadOrCreateBuilder(rnd) +// loadOrCreateProverWithSignatures either loads a state proof prover from the DB or creates a new prover. +// this function fills the prover with all the available signatures +func (spw *Worker) loadOrCreateProverWithSignatures(rnd basics.Round) (spProver, error) { + b, err := spw.loadOrCreateProver(rnd) if err != nil { - return builder{}, err + return spProver{}, err } - if err := spw.loadSignaturesIntoBuilder(&b); err != nil { - return builder{}, err + if err := spw.loadSignaturesIntoProver(&b); err != nil { + return spProver{}, err } return b, nil } -func (spw *Worker) loadOrCreateBuilder(rnd basics.Round) (builder, error) { - var buildr builder +func (spw *Worker) loadOrCreateProver(rnd basics.Round) (spProver, error) { + var prover spProver err := spw.db.Atomic(func(ctx context.Context, tx *sql.Tx) (err error) { - buildr, err = getBuilder(tx, rnd) + prover, err = getProver(tx, rnd) return err }) if err == nil { - return buildr, nil + return prover, nil } if !errors.Is(err, sql.ErrNoRows) { - spw.log.Errorf("loadOrCreateBuilder: error while fetching builder from DB: %v", err) + spw.log.Errorf("loadOrCreateProver: error while fetching builder from DB: %v", err) } - buildr, err = createBuilder(rnd, spw.ledger) + prover, err = createProver(rnd, spw.ledger) if err != nil { - return builder{}, err + return spProver{}, err } err = spw.db.Atomic(func(_ context.Context, tx *sql.Tx) error { - return persistBuilder(tx, rnd, &buildr) + return persistProver(tx, rnd, &prover) }) // We ignore persisting errors because we still want to try and use our successfully generated builder, // even if, for some reason, persisting it failed. if err != nil { - spw.log.Errorf("loadOrCreateBuilder(%d): failed to insert builder into database: %v", rnd, err) + spw.log.Errorf("loadOrCreateProver(%d): failed to insert prover into database: %v", rnd, err) } - return buildr, nil + return prover, nil } -func (spw *Worker) loadSignaturesIntoBuilder(buildr *builder) error { +func (spw *Worker) loadSignaturesIntoProver(prover *spProver) error { var sigs []pendingSig err := spw.db.Atomic(func(ctx context.Context, tx *sql.Tx) error { var err2 error - sigs, err2 = getPendingSigsForRound(tx, basics.Round(buildr.Round)) + sigs, err2 = getPendingSigsForRound(tx, basics.Round(prover.Round)) return err2 }) if err != nil { @@ -155,7 +169,7 @@ func (spw *Worker) loadSignaturesIntoBuilder(buildr *builder) error { } for i := range sigs { - err = buildr.insertSig(&sigs[i], false) + err = prover.insertSig(&sigs[i], false) if err != nil { spw.log.Warn(err) } @@ -163,13 +177,13 @@ func (spw *Worker) loadSignaturesIntoBuilder(buildr *builder) error { return nil } -func createBuilder(rnd basics.Round, votersFetcher ledgercore.LedgerForSPBuilder) (builder, error) { +func createProver(rnd basics.Round, votersFetcher ledgercore.LedgerForSPBuilder) (spProver, error) { // since this function might be invoked under tracker commit context (i.e invoked from the ledger code ), // it is important that we do not use the ledger directly. hdr, err := votersFetcher.BlockHdr(rnd) if err != nil { - return builder{}, err + return spProver{}, err } hdrProto := config.Consensus[hdr.CurrentProtocol] @@ -177,70 +191,70 @@ func createBuilder(rnd basics.Round, votersFetcher ledgercore.LedgerForSPBuilder lookback := votersRnd.SubSaturate(basics.Round(hdrProto.StateProofVotersLookback)) voters, err := votersFetcher.VotersForStateProof(lookback) if err != nil { - return builder{}, err + return spProver{}, err } if voters == nil { - return builder{}, fmt.Errorf("lookback round %d: %w", lookback, errVotersNotTracked) + return spProver{}, fmt.Errorf("lookback round %d: %w", lookback, errVotersNotTracked) } votersHdr, err := votersFetcher.BlockHdr(votersRnd) if err != nil { - return builder{}, err + return spProver{}, err } msg, err := GenerateStateProofMessage(votersFetcher, rnd) if err != nil { - return builder{}, err + return spProver{}, err } provenWeight, err := verify.GetProvenWeight(&votersHdr, &hdr) if err != nil { - return builder{}, err + return spProver{}, err } - var res builder + var res spProver res.VotersHdr = votersHdr res.AddrToPos = voters.AddrToPos res.Message = msg - res.Builder, err = stateproof.MakeBuilder(msg.Hash(), + res.Prover, err = stateproof.MakeProver(msg.Hash(), uint64(rnd), provenWeight, voters.Participants, voters.Tree, config.Consensus[votersHdr.CurrentProtocol].StateProofStrengthTarget) if err != nil { - return builder{}, err + return spProver{}, err } return res, nil } -func (spw *Worker) initBuilders() { - spw.builders = make(map[basics.Round]builder) - rnds, err := spw.getAllOnlineBuilderRounds() +func (spw *Worker) initProvers() { + spw.provers = make(map[basics.Round]spProver) + rnds, err := spw.getAllOnlineProverRounds() if err != nil { - spw.log.Errorf("initBuilders: failed to load rounds: %v", err) + spw.log.Errorf("initProvers: failed to load rounds: %v", err) return } for _, rnd := range rnds { - if _, ok := spw.builders[rnd]; ok { - spw.log.Warnf("initBuilders: round %d already present", rnd) + if _, ok := spw.provers[rnd]; ok { + spw.log.Warnf("initProvers: round %d already present", rnd) continue } - buildr, err := spw.loadOrCreateBuilderWithSignatures(rnd) + prover, err := spw.loadOrCreateProverWithSignatures(rnd) if err != nil { - spw.log.Warnf("initBuilders: failed to load builder for round %d", rnd) + spw.log.Warnf("initProvers: failed to load prover for round %d", rnd) continue } - spw.builders[rnd] = buildr + spw.provers[rnd] = prover } } -func (spw *Worker) getAllOnlineBuilderRounds() ([]basics.Round, error) { - // Some state proof databases might only contain a signature table. For that reason, when trying to create builders for possible state proof - // rounds we search the signature table and not the builder table +func (spw *Worker) getAllOnlineProverRounds() ([]basics.Round, error) { + // Some state proof databases might only contain a signature table. For that reason, when trying to create provers for possible state proof + // rounds we search the signature table and not the prover table latest := spw.ledger.Latest() latestHdr, err := spw.ledger.BlockHdr(latest) if err != nil { @@ -252,7 +266,7 @@ func (spw *Worker) getAllOnlineBuilderRounds() ([]basics.Round, error) { } latestStateProofRound := latest.RoundDownToMultipleOf(basics.Round(proto.StateProofInterval)) - threshold := onlineBuildersThreshold(&proto, latestHdr.StateProofTracking[protocol.StateProofBasic].StateProofNextRound) + threshold := onlineProversThreshold(&proto, latestHdr.StateProofTracking[protocol.StateProofBasic].StateProofNextRound) var rnds []basics.Round err = spw.db.Atomic(func(_ context.Context, tx *sql.Tx) error { @@ -269,7 +283,7 @@ var errFailedToAddSigAtPos = errors.New("could not add signature to builder") var errSigAlreadyPresentAtPos = errors.New("signature already present at this position") // Signature already present at this position var errSignatureVerification = errors.New("error while verifying signature") // Signature failed cryptographic verification -func (b *builder) insertSig(s *pendingSig, verify bool) error { +func (b *spProver) insertSig(s *pendingSig, verify bool) error { rnd := b.Round pos, ok := b.AddrToPos[s.signer] if !ok { @@ -314,7 +328,7 @@ func (spw *Worker) handleSigMessage(msg network.IncomingMessage) network.Outgoin // latest StateProof round. // This signature filtering is only relevant when the StateProof chain is stalled and many signatures may be spammed. func (spw *Worker) meetsBroadcastPolicy(sfa sigFromAddr, latestRound basics.Round, proto *config.ConsensusParams, stateProofNextRound basics.Round) bool { - if sfa.Round <= onlineBuildersThreshold(proto, stateProofNextRound) { + if sfa.Round <= onlineProversThreshold(proto, stateProofNextRound) { return true } @@ -322,7 +336,7 @@ func (spw *Worker) meetsBroadcastPolicy(sfa sigFromAddr, latestRound basics.Roun return sfa.Round == latestStateProofRound } -// handleSig adds a signature to the pending in-memory state proof provers (builders). This function is +// handleSig adds a signature to the pending in-memory state proof provers (provers). This function is // also responsible for making sure that the signature is valid, and not duplicated. // if a signature passes all verification it is written into the database. func (spw *Worker) handleSig(sfa sigFromAddr, sender network.Peer) (network.ForwardingPolicy, error) { @@ -330,11 +344,11 @@ func (spw *Worker) handleSig(sfa sigFromAddr, sender network.Peer) (network.Forw defer spw.mu.Unlock() // might happen if the state proof worker is stopping - if spw.builders == nil { - return network.Ignore, fmt.Errorf("handleSig: no builders loaded") + if spw.provers == nil { + return network.Ignore, fmt.Errorf("handleSig: no provers loaded") } - builderForRound, ok := spw.builders[sfa.Round] + proverForRound, ok := spw.provers[sfa.Round] if !ok { latest := spw.ledger.Latest() latestHdr, err := spw.ledger.BlockHdr(latest) @@ -376,13 +390,13 @@ func (spw *Worker) handleSig(sfa sigFromAddr, sender network.Peer) (network.Forw return network.Ignore, nil } - builderForRound, err = spw.loadOrCreateBuilderWithSignatures(sfa.Round) + proverForRound, err = spw.loadOrCreateProverWithSignatures(sfa.Round) if err != nil { // Should not disconnect this peer, since this is a fault of the relay // The peer could have other signatures what the relay is interested in return network.Ignore, err } - spw.builders[sfa.Round] = builderForRound + spw.provers[sfa.Round] = proverForRound spw.log.Infof("spw.handleSig: starts gathering signatures for round %d", sfa.Round) } @@ -391,7 +405,7 @@ func (spw *Worker) handleSig(sfa sigFromAddr, sender network.Peer) (network.Forw sig: sfa.Sig, fromThisNode: sender == nil, } - err := builderForRound.insertSig(&sig, true) + err := proverForRound.insertSig(&sig, true) if errors.Is(err, errSigAlreadyPresentAtPos) { // Safe to ignore this error as it means we already have a valid signature for this address return network.Ignore, nil @@ -446,7 +460,7 @@ func (spw *Worker) builder(latest basics.Round) { proto := config.Consensus[newLatestHdr.CurrentProtocol] stateProofNextRound := newLatestHdr.StateProofTracking[protocol.StateProofBasic].StateProofNextRound - spw.deleteBuildData(&proto, stateProofNextRound) + spw.deleteProverData(&proto, stateProofNextRound) // Broadcast signatures based on the previous block(s) that // were agreed upon. This ensures that, if we send a signature @@ -484,7 +498,7 @@ func (spw *Worker) broadcastSigs(brnd basics.Round, stateProofNextRound basics.R defer spw.mu.Unlock() latestStateProofRound := brnd.RoundDownToMultipleOf(basics.Round(proto.StateProofInterval)) - threshold := onlineBuildersThreshold(&proto, stateProofNextRound) + threshold := onlineProversThreshold(&proto, stateProofNextRound) var roundSigs map[basics.Round][]pendingSig err := spw.db.Atomic(func(ctx context.Context, tx *sql.Tx) (err error) { if brnd%basics.Round(proto.StateProofInterval) < basics.Round(proto.StateProofInterval/2) { @@ -528,17 +542,17 @@ func (spw *Worker) broadcastSigs(brnd basics.Round, stateProofNextRound basics.R } } -func (spw *Worker) deleteBuildData(proto *config.ConsensusParams, stateProofNextRound basics.Round) { +func (spw *Worker) deleteProverData(proto *config.ConsensusParams, stateProofNextRound basics.Round) { if proto.StateProofInterval == 0 || stateProofNextRound == 0 { return } - // Delete from memory (already stored on disk). Practically, There are two scenarios where builders gets removed from memory + // Delete from memory (already stored on disk). Practically, There are two scenarios where provers gets removed from memory // 1. When a state proof is committed, the earliest will get removed and later on will be removed from disk. - // (when calling deleteStaleBuilders) - // 2. If state proofs are stalled, and consensus is moving forward, a new latest builder will be created and - // the older builder will be swapped out from memory. (i.e will be removed from memory but stays on disk). - spw.trimBuildersCache(proto, stateProofNextRound) + // (when calling deleteStaleProver) + // 2. If state proofs are stalled, and consensus is moving forward, a new latest prover will be created and + // the older provers will be swapped out from memory. (i.e will be removed from memory but stays on disk). + spw.trimProversCache(proto, stateProofNextRound) if spw.lastCleanupRound == stateProofNextRound { return @@ -547,7 +561,7 @@ func (spw *Worker) deleteBuildData(proto *config.ConsensusParams, stateProofNext // Delete from disk (database) spw.deleteStaleSigs(stateProofNextRound) spw.deleteStaleKeys(stateProofNextRound) - spw.deleteStaleBuilders(stateProofNextRound) + spw.deleteStaleProver(stateProofNextRound) spw.lastCleanupRound = stateProofNextRound } @@ -575,50 +589,50 @@ func (spw *Worker) deleteStaleKeys(retainRound basics.Round) { } } -func (spw *Worker) deleteStaleBuilders(retainRound basics.Round) { +func (spw *Worker) deleteStaleProver(retainRound basics.Round) { err := spw.db.Atomic(func(ctx context.Context, tx *sql.Tx) error { - return deleteBuilders(tx, retainRound) + return deleteProvers(tx, retainRound) }) if err != nil { - spw.log.Warnf("deleteOldBuilders: failed to delete builders from database: %v", err) + spw.log.Warnf("deleteStaleProver: failed to delete provers from database: %v", err) } } -// onlineBuildersThreshold returns the highest round for which the builder should be stored in memory (cache). +// onlineProversThreshold returns the highest round for which the prover should be stored in memory (cache). // This is mostly relevant in case the StateProof chain is stalled. // The threshold is also used to limit the StateProof signatures broadcasted over the network. -func onlineBuildersThreshold(proto *config.ConsensusParams, stateProofNextRound basics.Round) basics.Round { +func onlineProversThreshold(proto *config.ConsensusParams, stateProofNextRound basics.Round) basics.Round { /* - builderCacheLength - 2: - let buildersCacheLength <- 5, StateProofNextRound <- 1024, LatestRound <- 4096 + proverCacheLength - 2: + let proversCacheLength <- 5, StateProofNextRound <- 1024, LatestRound <- 4096 threshold = StateProofNextRound + 3 * StateProofInterval (for a total of 4 early StateProofs) - the 5th builder in the cache is reserved for the LatestRound stateproof. + the 5th prover in the cache is reserved for the LatestRound stateproof. */ - threshold := stateProofNextRound + basics.Round((buildersCacheLength-2)*proto.StateProofInterval) + threshold := stateProofNextRound + basics.Round((proversCacheLength-2)*proto.StateProofInterval) return threshold } -// trimBuildersCache reduces the number of builders stored in memory to X earliest as well as 1 latest, to an overall amount of X+1 builders -func (spw *Worker) trimBuildersCache(proto *config.ConsensusParams, stateProofNextRound basics.Round) { +// trimProversCache reduces the number of provers stored in memory to X earliest as well as 1 latest, to an overall amount of X+1 provers +func (spw *Worker) trimProversCache(proto *config.ConsensusParams, stateProofNextRound basics.Round) { spw.mu.Lock() defer spw.mu.Unlock() - var maxBuilderRound basics.Round - for rnd := range spw.builders { - if rnd > maxBuilderRound { - maxBuilderRound = rnd + var maxProverRound basics.Round + for rnd := range spw.provers { + if rnd > maxProverRound { + maxProverRound = rnd } } - threshold := onlineBuildersThreshold(proto, stateProofNextRound) + threshold := onlineProversThreshold(proto, stateProofNextRound) /* - For example, builders currently stored in memory are for these rounds: - [..., StateProofNextRound-256, StateProofNextRound, StateProofNextRound+256, ..., Threshold, ..., MaxBuilderRound] - [StateProofNextRound, ..., Threshold, MaxBuilderRound] <- Only builders that should be stored in memory after trim + For example, provers currently stored in memory are for these rounds: + [..., StateProofNextRound-256, StateProofNextRound, StateProofNextRound+256, ..., Threshold, ..., maxProverRound] + [StateProofNextRound, ..., Threshold, maxProverRound] <- Only provers that should be stored in memory after trim */ - for rnd := range spw.builders { - if rnd < stateProofNextRound || (threshold < rnd && rnd < maxBuilderRound) { - delete(spw.builders, rnd) + for rnd := range spw.provers { + if rnd < stateProofNextRound || (threshold < rnd && rnd < maxProverRound) { + delete(spw.provers, rnd) } } } @@ -627,16 +641,16 @@ func (spw *Worker) tryBroadcast() { spw.mu.Lock() defer spw.mu.Unlock() - sortedRounds := make([]basics.Round, 0, len(spw.builders)) - for rnd := range spw.builders { + sortedRounds := make([]basics.Round, 0, len(spw.provers)) + for rnd := range spw.provers { sortedRounds = append(sortedRounds, rnd) } sort.Slice(sortedRounds, func(i, j int) bool { return sortedRounds[i] < sortedRounds[j] }) for _, rnd := range sortedRounds { - // Iterate over the builders in a sequential manner. If the earlist state proof is not ready/rejected + // Iterate over the provers in a sequential manner. If the earlist state proof is not ready/rejected // it won't be possible to add a later one. For that reason, we break the loop - b := spw.builders[rnd] + b := spw.provers[rnd] firstValid := spw.ledger.Latest() acceptableWeight := verify.AcceptableStateProofWeight(&b.VotersHdr, firstValid, logging.Base()) if b.SignedWeight() < acceptableWeight { @@ -649,7 +663,7 @@ func (spw *Worker) tryBroadcast() { break } - sp, err := b.Build() + sp, err := b.CreateProof() if err != nil { spw.log.Warnf("spw.tryBroadcast: building state proof for %d failed: %v", rnd, err) break diff --git a/stateproof/db.go b/stateproof/db.go index 31674514bc..f0b0e5cc16 100644 --- a/stateproof/db.go +++ b/stateproof/db.go @@ -47,17 +47,17 @@ const ( createSigsIdx = `CREATE INDEX IF NOT EXISTS sigs_from_this_node ON sigs (from_this_node)` - // builders table stored a serialization of each BuilderForRound data, without the sigs (stored separately) - createBuildersTable = `CREATE TABLE IF NOT EXISTS builders ( + // provers table stored a serialization of each ProverForRound data, without the sigs (stored separately) + createProverTable = `CREATE TABLE IF NOT EXISTS provers ( round INTEGER PRIMARY KEY NOT NULL, - builder BLOB NOT NULL + prover BLOB NOT NULL )` - insertOrReplaceBuilderForRound = `INSERT OR REPLACE INTO builders (round,builder) VALUES (?,?)` + insertOrReplaceProverForRound = `INSERT OR REPLACE INTO provers (round,prover) VALUES (?,?)` - selectBuilderForRound = `SELECT builder FROM builders WHERE round=?` + selectProverForRound = `SELECT prover FROM provers WHERE round=?` - deleteBuilderForRound = `DELETE FROM builders WHERE round (*) MarshalMsg -// |-----> (*) CanMarshalMsg -// |-----> (*) UnmarshalMsg -// |-----> (*) CanUnmarshalMsg -// |-----> (*) Msgsize -// |-----> (*) MsgIsZero -// // sigFromAddr // |-----> (*) MarshalMsg // |-----> (*) CanMarshalMsg @@ -27,9 +19,169 @@ import ( // |-----> (*) Msgsize // |-----> (*) MsgIsZero // +// spProver +// |-----> (*) MarshalMsg +// |-----> (*) CanMarshalMsg +// |-----> (*) UnmarshalMsg +// |-----> (*) CanUnmarshalMsg +// |-----> (*) Msgsize +// |-----> (*) MsgIsZero +// // MarshalMsg implements msgp.Marshaler -func (z *builder) MarshalMsg(b []byte) (o []byte) { +func (z *sigFromAddr) MarshalMsg(b []byte) (o []byte) { + o = msgp.Require(b, z.Msgsize()) + // omitempty: check for empty values + zb0001Len := uint32(3) + var zb0001Mask uint8 /* 4 bits */ + if (*z).SignerAddress.MsgIsZero() { + zb0001Len-- + zb0001Mask |= 0x2 + } + if (*z).Round.MsgIsZero() { + zb0001Len-- + zb0001Mask |= 0x4 + } + if (*z).Sig.MsgIsZero() { + zb0001Len-- + zb0001Mask |= 0x8 + } + // variable map header, size zb0001Len + o = append(o, 0x80|uint8(zb0001Len)) + if zb0001Len != 0 { + if (zb0001Mask & 0x2) == 0 { // if not empty + // string "a" + o = append(o, 0xa1, 0x61) + o = (*z).SignerAddress.MarshalMsg(o) + } + if (zb0001Mask & 0x4) == 0 { // if not empty + // string "r" + o = append(o, 0xa1, 0x72) + o = (*z).Round.MarshalMsg(o) + } + if (zb0001Mask & 0x8) == 0 { // if not empty + // string "s" + o = append(o, 0xa1, 0x73) + o = (*z).Sig.MarshalMsg(o) + } + } + return +} + +func (_ *sigFromAddr) CanMarshalMsg(z interface{}) bool { + _, ok := (z).(*sigFromAddr) + return ok +} + +// UnmarshalMsg implements msgp.Unmarshaler +func (z *sigFromAddr) 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).SignerAddress.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "SignerAddress") + return + } + } + if zb0001 > 0 { + zb0001-- + bts, err = (*z).Round.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Round") + return + } + } + if zb0001 > 0 { + zb0001-- + bts, err = (*z).Sig.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Sig") + 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) = sigFromAddr{} + } + for zb0001 > 0 { + zb0001-- + field, bts, err = msgp.ReadMapKeyZC(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + switch string(field) { + case "a": + bts, err = (*z).SignerAddress.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "SignerAddress") + return + } + case "r": + bts, err = (*z).Round.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "Round") + return + } + case "s": + bts, err = (*z).Sig.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "Sig") + return + } + default: + err = msgp.ErrNoField(string(field)) + if err != nil { + err = msgp.WrapError(err) + return + } + } + } + } + o = bts + return +} + +func (_ *sigFromAddr) CanUnmarshalMsg(z interface{}) bool { + _, ok := (z).(*sigFromAddr) + return ok +} + +// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message +func (z *sigFromAddr) Msgsize() (s int) { + s = 1 + 2 + (*z).SignerAddress.Msgsize() + 2 + (*z).Round.Msgsize() + 2 + (*z).Sig.Msgsize() + return +} + +// MsgIsZero returns whether this is a zero value +func (z *sigFromAddr) MsgIsZero() bool { + return ((*z).SignerAddress.MsgIsZero()) && ((*z).Round.MsgIsZero()) && ((*z).Sig.MsgIsZero()) +} + +// MarshalMsg implements msgp.Marshaler +func (z *spProver) MarshalMsg(b []byte) (o []byte) { o = msgp.Require(b, z.Msgsize()) // omitempty: check for empty values zb0003Len := uint32(4) @@ -38,15 +190,15 @@ func (z *builder) MarshalMsg(b []byte) (o []byte) { zb0003Len-- zb0003Mask |= 0x2 } - if (*z).Builder == nil { + if (*z).VotersHdr.MsgIsZero() { zb0003Len-- zb0003Mask |= 0x4 } - if (*z).VotersHdr.MsgIsZero() { + if (*z).Message.MsgIsZero() { zb0003Len-- zb0003Mask |= 0x8 } - if (*z).Message.MsgIsZero() { + if (*z).Prover == nil { zb0003Len-- zb0003Mask |= 0x10 } @@ -74,35 +226,35 @@ func (z *builder) MarshalMsg(b []byte) (o []byte) { } } if (zb0003Mask & 0x4) == 0 { // if not empty - // string "bldr" - o = append(o, 0xa4, 0x62, 0x6c, 0x64, 0x72) - if (*z).Builder == nil { - o = msgp.AppendNil(o) - } else { - o = (*z).Builder.MarshalMsg(o) - } - } - if (zb0003Mask & 0x8) == 0 { // if not empty // string "hdr" o = append(o, 0xa3, 0x68, 0x64, 0x72) o = (*z).VotersHdr.MarshalMsg(o) } - if (zb0003Mask & 0x10) == 0 { // if not empty + if (zb0003Mask & 0x8) == 0 { // if not empty // string "msg" o = append(o, 0xa3, 0x6d, 0x73, 0x67) o = (*z).Message.MarshalMsg(o) } + if (zb0003Mask & 0x10) == 0 { // if not empty + // string "prv" + o = append(o, 0xa3, 0x70, 0x72, 0x76) + if (*z).Prover == nil { + o = msgp.AppendNil(o) + } else { + o = (*z).Prover.MarshalMsg(o) + } + } } return } -func (_ *builder) CanMarshalMsg(z interface{}) bool { - _, ok := (z).(*builder) +func (_ *spProver) CanMarshalMsg(z interface{}) bool { + _, ok := (z).(*spProver) return ok } // UnmarshalMsg implements msgp.Unmarshaler -func (z *builder) UnmarshalMsg(bts []byte) (o []byte, err error) { +func (z *spProver) UnmarshalMsg(bts []byte) (o []byte, err error) { var field []byte _ = field var zb0003 int @@ -121,14 +273,14 @@ func (z *builder) UnmarshalMsg(bts []byte) (o []byte, err error) { if err != nil { return } - (*z).Builder = nil + (*z).Prover = nil } else { - if (*z).Builder == nil { - (*z).Builder = new(stateproof.Builder) + if (*z).Prover == nil { + (*z).Prover = new(stateproof.Prover) } - bts, err = (*z).Builder.UnmarshalMsg(bts) + bts, err = (*z).Prover.UnmarshalMsg(bts) if err != nil { - err = msgp.WrapError(err, "struct-from-array", "Builder") + err = msgp.WrapError(err, "struct-from-array", "Prover") return } } @@ -198,7 +350,7 @@ func (z *builder) UnmarshalMsg(bts []byte) (o []byte, err error) { return } if zb0004 { - (*z) = builder{} + (*z) = spProver{} } for zb0003 > 0 { zb0003-- @@ -208,20 +360,20 @@ func (z *builder) UnmarshalMsg(bts []byte) (o []byte, err error) { return } switch string(field) { - case "bldr": + case "prv": if msgp.IsNil(bts) { bts, err = msgp.ReadNilBytes(bts) if err != nil { return } - (*z).Builder = nil + (*z).Prover = nil } else { - if (*z).Builder == nil { - (*z).Builder = new(stateproof.Builder) + if (*z).Prover == nil { + (*z).Prover = new(stateproof.Prover) } - bts, err = (*z).Builder.UnmarshalMsg(bts) + bts, err = (*z).Prover.UnmarshalMsg(bts) if err != nil { - err = msgp.WrapError(err, "Builder") + err = msgp.WrapError(err, "Prover") return } } @@ -284,18 +436,18 @@ func (z *builder) UnmarshalMsg(bts []byte) (o []byte, err error) { return } -func (_ *builder) CanUnmarshalMsg(z interface{}) bool { - _, ok := (z).(*builder) +func (_ *spProver) CanUnmarshalMsg(z interface{}) bool { + _, ok := (z).(*spProver) return ok } // Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message -func (z *builder) Msgsize() (s int) { - s = 1 + 5 - if (*z).Builder == nil { +func (z *spProver) Msgsize() (s int) { + s = 1 + 4 + if (*z).Prover == nil { s += msgp.NilSize } else { - s += (*z).Builder.Msgsize() + s += (*z).Prover.Msgsize() } s += 5 + msgp.MapHeaderSize if (*z).AddrToPos != nil { @@ -310,158 +462,6 @@ func (z *builder) Msgsize() (s int) { } // MsgIsZero returns whether this is a zero value -func (z *builder) MsgIsZero() bool { - return ((*z).Builder == nil) && (len((*z).AddrToPos) == 0) && ((*z).VotersHdr.MsgIsZero()) && ((*z).Message.MsgIsZero()) -} - -// MarshalMsg implements msgp.Marshaler -func (z *sigFromAddr) MarshalMsg(b []byte) (o []byte) { - o = msgp.Require(b, z.Msgsize()) - // omitempty: check for empty values - zb0001Len := uint32(3) - var zb0001Mask uint8 /* 4 bits */ - if (*z).SignerAddress.MsgIsZero() { - zb0001Len-- - zb0001Mask |= 0x2 - } - if (*z).Round.MsgIsZero() { - zb0001Len-- - zb0001Mask |= 0x4 - } - if (*z).Sig.MsgIsZero() { - zb0001Len-- - zb0001Mask |= 0x8 - } - // variable map header, size zb0001Len - o = append(o, 0x80|uint8(zb0001Len)) - if zb0001Len != 0 { - if (zb0001Mask & 0x2) == 0 { // if not empty - // string "a" - o = append(o, 0xa1, 0x61) - o = (*z).SignerAddress.MarshalMsg(o) - } - if (zb0001Mask & 0x4) == 0 { // if not empty - // string "r" - o = append(o, 0xa1, 0x72) - o = (*z).Round.MarshalMsg(o) - } - if (zb0001Mask & 0x8) == 0 { // if not empty - // string "s" - o = append(o, 0xa1, 0x73) - o = (*z).Sig.MarshalMsg(o) - } - } - return -} - -func (_ *sigFromAddr) CanMarshalMsg(z interface{}) bool { - _, ok := (z).(*sigFromAddr) - return ok -} - -// UnmarshalMsg implements msgp.Unmarshaler -func (z *sigFromAddr) 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).SignerAddress.UnmarshalMsg(bts) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "SignerAddress") - return - } - } - if zb0001 > 0 { - zb0001-- - bts, err = (*z).Round.UnmarshalMsg(bts) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "Round") - return - } - } - if zb0001 > 0 { - zb0001-- - bts, err = (*z).Sig.UnmarshalMsg(bts) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "Sig") - 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) = sigFromAddr{} - } - for zb0001 > 0 { - zb0001-- - field, bts, err = msgp.ReadMapKeyZC(bts) - if err != nil { - err = msgp.WrapError(err) - return - } - switch string(field) { - case "a": - bts, err = (*z).SignerAddress.UnmarshalMsg(bts) - if err != nil { - err = msgp.WrapError(err, "SignerAddress") - return - } - case "r": - bts, err = (*z).Round.UnmarshalMsg(bts) - if err != nil { - err = msgp.WrapError(err, "Round") - return - } - case "s": - bts, err = (*z).Sig.UnmarshalMsg(bts) - if err != nil { - err = msgp.WrapError(err, "Sig") - return - } - default: - err = msgp.ErrNoField(string(field)) - if err != nil { - err = msgp.WrapError(err) - return - } - } - } - } - o = bts - return -} - -func (_ *sigFromAddr) CanUnmarshalMsg(z interface{}) bool { - _, ok := (z).(*sigFromAddr) - return ok -} - -// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message -func (z *sigFromAddr) Msgsize() (s int) { - s = 1 + 2 + (*z).SignerAddress.Msgsize() + 2 + (*z).Round.Msgsize() + 2 + (*z).Sig.Msgsize() - return -} - -// MsgIsZero returns whether this is a zero value -func (z *sigFromAddr) MsgIsZero() bool { - return ((*z).SignerAddress.MsgIsZero()) && ((*z).Round.MsgIsZero()) && ((*z).Sig.MsgIsZero()) +func (z *spProver) MsgIsZero() bool { + return ((*z).Prover == nil) && (len((*z).AddrToPos) == 0) && ((*z).VotersHdr.MsgIsZero()) && ((*z).Message.MsgIsZero()) } diff --git a/stateproof/msgp_gen_test.go b/stateproof/msgp_gen_test.go index ed4b196b32..0a2c025304 100644 --- a/stateproof/msgp_gen_test.go +++ b/stateproof/msgp_gen_test.go @@ -14,9 +14,9 @@ import ( "github.com/algorand/go-algorand/test/partitiontest" ) -func TestMarshalUnmarshalbuilder(t *testing.T) { +func TestMarshalUnmarshalsigFromAddr(t *testing.T) { partitiontest.PartitionTest(t) - v := builder{} + v := sigFromAddr{} bts := v.MarshalMsg(nil) left, err := v.UnmarshalMsg(bts) if err != nil { @@ -35,12 +35,12 @@ func TestMarshalUnmarshalbuilder(t *testing.T) { } } -func TestRandomizedEncodingbuilder(t *testing.T) { - protocol.RunEncodingTest(t, &builder{}) +func TestRandomizedEncodingsigFromAddr(t *testing.T) { + protocol.RunEncodingTest(t, &sigFromAddr{}) } -func BenchmarkMarshalMsgbuilder(b *testing.B) { - v := builder{} +func BenchmarkMarshalMsgsigFromAddr(b *testing.B) { + v := sigFromAddr{} b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { @@ -48,8 +48,8 @@ func BenchmarkMarshalMsgbuilder(b *testing.B) { } } -func BenchmarkAppendMsgbuilder(b *testing.B) { - v := builder{} +func BenchmarkAppendMsgsigFromAddr(b *testing.B) { + v := sigFromAddr{} bts := make([]byte, 0, v.Msgsize()) bts = v.MarshalMsg(bts[0:0]) b.SetBytes(int64(len(bts))) @@ -60,8 +60,8 @@ func BenchmarkAppendMsgbuilder(b *testing.B) { } } -func BenchmarkUnmarshalbuilder(b *testing.B) { - v := builder{} +func BenchmarkUnmarshalsigFromAddr(b *testing.B) { + v := sigFromAddr{} bts := v.MarshalMsg(nil) b.ReportAllocs() b.SetBytes(int64(len(bts))) @@ -74,9 +74,9 @@ func BenchmarkUnmarshalbuilder(b *testing.B) { } } -func TestMarshalUnmarshalsigFromAddr(t *testing.T) { +func TestMarshalUnmarshalspProver(t *testing.T) { partitiontest.PartitionTest(t) - v := sigFromAddr{} + v := spProver{} bts := v.MarshalMsg(nil) left, err := v.UnmarshalMsg(bts) if err != nil { @@ -95,12 +95,12 @@ func TestMarshalUnmarshalsigFromAddr(t *testing.T) { } } -func TestRandomizedEncodingsigFromAddr(t *testing.T) { - protocol.RunEncodingTest(t, &sigFromAddr{}) +func TestRandomizedEncodingspProver(t *testing.T) { + protocol.RunEncodingTest(t, &spProver{}) } -func BenchmarkMarshalMsgsigFromAddr(b *testing.B) { - v := sigFromAddr{} +func BenchmarkMarshalMsgspProver(b *testing.B) { + v := spProver{} b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { @@ -108,8 +108,8 @@ func BenchmarkMarshalMsgsigFromAddr(b *testing.B) { } } -func BenchmarkAppendMsgsigFromAddr(b *testing.B) { - v := sigFromAddr{} +func BenchmarkAppendMsgspProver(b *testing.B) { + v := spProver{} bts := make([]byte, 0, v.Msgsize()) bts = v.MarshalMsg(bts[0:0]) b.SetBytes(int64(len(bts))) @@ -120,8 +120,8 @@ func BenchmarkAppendMsgsigFromAddr(b *testing.B) { } } -func BenchmarkUnmarshalsigFromAddr(b *testing.B) { - v := sigFromAddr{} +func BenchmarkUnmarshalspProver(b *testing.B) { + v := spProver{} bts := v.MarshalMsg(nil) b.ReportAllocs() b.SetBytes(int64(len(bts))) diff --git a/stateproof/signer.go b/stateproof/signer.go index c5a0d153c5..99c715fb91 100644 --- a/stateproof/signer.go +++ b/stateproof/signer.go @@ -137,7 +137,7 @@ func (spw *Worker) getStateProofMessage(round basics.Round) (stateproofmsg.Messa return msg, nil } if !errors.Is(err, sql.ErrNoRows) { - spw.log.Errorf("getStateProofMessage(%d): error while fetching builder from DB: %v", round, err) + spw.log.Errorf("getStateProofMessage(%d): error while fetching prover from DB: %v", round, err) } return GenerateStateProofMessage(spw.ledger, round) diff --git a/stateproof/verify/stateproof.go b/stateproof/verify/stateproof.go index 2535c905ed..3caaabac86 100644 --- a/stateproof/verify/stateproof.go +++ b/stateproof/verify/stateproof.go @@ -107,7 +107,7 @@ func calculateAcceptableStateProofWeight(total basics.MicroAlgos, proto *config. return w } -// GetProvenWeight computes the parameters for building or verifying +// GetProvenWeight computes the parameters for proving or verifying // a state proof for the interval (votersHdr, latestRoundInProofHdr], using voters from block votersHdr. func GetProvenWeight(votersHdr *bookkeeping.BlockHeader, latestRoundInProofHdr *bookkeeping.BlockHeader) (uint64, error) { proto := config.Consensus[votersHdr.CurrentProtocol] diff --git a/stateproof/worker.go b/stateproof/worker.go index 14a024d1a0..acc55a5d68 100644 --- a/stateproof/worker.go +++ b/stateproof/worker.go @@ -26,31 +26,18 @@ import ( "github.com/algorand/go-deadlock" "github.com/algorand/go-algorand/config" - "github.com/algorand/go-algorand/crypto/stateproof" "github.com/algorand/go-algorand/data/basics" - "github.com/algorand/go-algorand/data/bookkeeping" - "github.com/algorand/go-algorand/data/stateproofmsg" "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/network" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/util/db" ) -type builder struct { - _struct struct{} `codec:",omitempty,omitemptyarray"` - - *stateproof.Builder `codec:"bldr"` - - AddrToPos map[Address]uint64 `codec:"addr,allocbound=stateproof.VotersAllocBound"` - VotersHdr bookkeeping.BlockHeader `codec:"hdr"` - Message stateproofmsg.Message `codec:"msg"` -} - -// This is a soft limit on how many builders should be kept in memory, the rest shall be fetched from DB. -// At most times only 1 should builder should be stored (both in memory and on disk), as this feature +// This is a soft limit on how many provers should be kept in memory, the rest shall be fetched from DB. +// At most times only 1 should prover should be stored (both in memory and on disk), as this feature // is mostly used for recoverability purposes - in case the StateProof chain is stalled. -// The builders cache is composed of the X earliest builders as well as the latest builder, for a total of X+1 (in case of stalled chain). -const buildersCacheLength = 5 // must be at least 2 to function properly (earliest stateproof + latest stateproof) +// The provers cache is composed of the X earliest provers as well as the latest prover, for a total of X+1 (in case of stalled chain). +const proversCacheLength = 5 // must be at least 2 to function properly (earliest stateproof + latest stateproof) // Worker builds state proofs, by broadcasting // signatures using this node's participation keys, by collecting @@ -69,8 +56,8 @@ type Worker struct { net Network txnSender TransactionSender - // builders is indexed by the round of the block being signed. - builders map[basics.Round]builder + // provers is indexed by the round of the block being signed. + provers map[basics.Round]spProver ctx context.Context shutdown context.CancelFunc @@ -119,7 +106,7 @@ func (spw *Worker) Start() { return } - spw.initBuilders() + spw.initProvers() spw.ledger.RegisterVotersCommitListener(spw) @@ -164,7 +151,7 @@ func (spw *Worker) Stop() { spw.mu.Lock() defer spw.mu.Unlock() - spw.builders = nil + spw.provers = nil spw.signedCh = nil if spw.db.Handle != nil { diff --git a/stateproof/worker_test.go b/stateproof/worker_test.go index 251c1f6b76..68a19dcd21 100644 --- a/stateproof/worker_test.go +++ b/stateproof/worker_test.go @@ -476,7 +476,7 @@ func newTestWorkerOnDiskDb(t testing.TB, s *testWorkerStubs) *Worker { ledger: s, net: s, txnSender: s, - builders: make(map[basics.Round]builder), + provers: make(map[basics.Round]spProver), ctx: ctx, shutdown: cancel, signedCh: make(chan struct{}, 1), @@ -508,9 +508,9 @@ func newPartKeyWithVersion(t testing.TB, protoParam config.ConsensusParams, pare return part } -func countBuildersInDB(store db.Accessor) (nrows int, err error) { +func countProversInDB(store db.Accessor) (nrows int, err error) { err = store.Atomic(func(ctx context.Context, tx *sql.Tx) error { - row := tx.QueryRow("SELECT COUNT(*) FROM builders") + row := tx.QueryRow("SELECT COUNT(*) FROM provers") err := row.Scan(&nrows) if err != nil { return err @@ -521,7 +521,7 @@ func countBuildersInDB(store db.Accessor) (nrows int, err error) { return } -func expectedNumberOfBuilders(stateproofInterval uint64, atRound basics.Round, nextStateProof basics.Round) int { +func expectedNumberOfProvers(stateproofInterval uint64, atRound basics.Round, nextStateProof basics.Round) int { if nextStateProof > atRound { return 0 } @@ -804,7 +804,7 @@ func TestWorkerHandleSig(t *testing.T) { } } -func TestWorkerIgnoresSignatureForNonCacheBuilders(t *testing.T) { +func TestWorkerIgnoresSignatureForNonCacheProvers(t *testing.T) { partitiontest.PartitionTest(t) a := require.New(t) @@ -823,29 +823,29 @@ func TestWorkerIgnoresSignatureForNonCacheBuilders(t *testing.T) { defer w.Stop() proto := config.Consensus[protocol.ConsensusCurrentVersion] - targetRound := (buildersCacheLength + 1) * proto.StateProofInterval + targetRound := (proversCacheLength + 1) * proto.StateProofInterval s.advanceRoundsWithoutStateProof(t, targetRound) // clean up the cache and clean up the signatures database so the handler will accept our signatures. s.mu.Lock() - w.builders = make(map[basics.Round]builder) + w.provers = make(map[basics.Round]spProver) a.NoError(w.db.Atomic(func(_ context.Context, tx *sql.Tx) error { _, err := tx.Exec("DELETE from sigs") return err })) s.mu.Unlock() - // rounds [2*proto.StateProofInterval, 3*proto.StateProofInterval, ... (buildersCacheLength - 1)*proto.StateProofInterval] should be + // rounds [2*proto.StateProofInterval, 3*proto.StateProofInterval, ... (proversCacheLength - 1)*proto.StateProofInterval] should be // accepted by handleSig i := uint64(0) - for ; i < (buildersCacheLength - 1); i++ { + for ; i < (proversCacheLength - 1); i++ { fwd, err := sendSigToHandler(proto, i, w, a, s) a.Equal(network.Broadcast, fwd) a.NoError(err) } - // signature for (buildersCacheLength)*proto.StateProofInterval should be rejected - due to cache limit + // signature for (proversCacheLength)*proto.StateProofInterval should be rejected - due to cache limit fwd, err := sendSigToHandler(proto, i, w, a, s) a.Equal(network.Ignore, fwd) a.NoError(err) @@ -985,11 +985,11 @@ func TestKeysRemoveOnlyAfterStateProofAcceptedLargeIntervals(t *testing.T) { require.Equal(t, len(keys), len(checkedKeys)) } -func TestWorkersBuildersCacheAndSignatures(t *testing.T) { +func TestWorkersProversCacheAndSignatures(t *testing.T) { partitiontest.PartitionTest(t) a := require.New(t) - const expectedStateProofs = buildersCacheLength + 2 + const expectedStateProofs = proversCacheLength + 2 var keys []account.Participation for i := 0; i < 10; i++ { var parent basics.Address @@ -1012,21 +1012,21 @@ func TestWorkersBuildersCacheAndSignatures(t *testing.T) { } s.advanceRoundsWithoutStateProof(t, proto.StateProofInterval/2) - a.Equal(buildersCacheLength, len(w.builders)) - verifyBuilderCache(proto, w, a, expectedStateProofs) + a.Equal(proversCacheLength, len(w.provers)) + verifyProverCache(proto, w, a, expectedStateProofs) - countDB, err := countBuildersInDB(w.db) + countDB, err := countProversInDB(w.db) a.NoError(err) a.Equal(expectedStateProofs, countDB) - threshold := onlineBuildersThreshold(&proto, basics.Round(512)) // 512 since no StateProofs are confirmed yet (512 is the first, commitment at 256) + threshold := onlineProversThreshold(&proto, basics.Round(512)) // 512 since no StateProofs are confirmed yet (512 is the first, commitment at 256) var roundSigs map[basics.Round][]pendingSig err = w.db.Atomic(func(ctx context.Context, tx *sql.Tx) (err error) { roundSigs, err = getPendingSigs(tx, threshold, basics.Round(256+proto.StateProofInterval*expectedStateProofs), false) return }) a.NoError(err) - a.Equal(buildersCacheLength, len(roundSigs)) // Number of broadcasted sigs should be the same as number of (online) cached builders. + a.Equal(proversCacheLength, len(roundSigs)) // Number of broadcasted sigs should be the same as number of (online) cached provers. /* add block that confirm a state proof for interval: expectedStateProofs @@ -1034,12 +1034,12 @@ func TestWorkersBuildersCacheAndSignatures(t *testing.T) { s.addBlock(basics.Round((expectedStateProofs) * config.Consensus[protocol.ConsensusCurrentVersion].StateProofInterval)) s.waitForSignerAndBuilder(t) - count := expectedNumberOfBuilders(proto.StateProofInterval, s.latest, basics.Round((expectedStateProofs)*config.Consensus[protocol.ConsensusCurrentVersion].StateProofInterval)) - countDB, err = countBuildersInDB(w.db) + count := expectedNumberOfProvers(proto.StateProofInterval, s.latest, basics.Round((expectedStateProofs)*config.Consensus[protocol.ConsensusCurrentVersion].StateProofInterval)) + countDB, err = countProversInDB(w.db) a.NoError(err) a.Equal(count, countDB) - threshold = onlineBuildersThreshold(&proto, s.blocks[s.latest].StateProofTracking[protocol.StateProofBasic].StateProofNextRound) + threshold = onlineProversThreshold(&proto, s.blocks[s.latest].StateProofTracking[protocol.StateProofBasic].StateProofNextRound) maxStateProofRnd := s.latest.RoundDownToMultipleOf(basics.Round(proto.StateProofInterval)) err = w.db.Atomic(func(ctx context.Context, tx *sql.Tx) (err error) { roundSigs, err = getPendingSigs(tx, threshold, maxStateProofRnd, false) @@ -1049,24 +1049,24 @@ func TestWorkersBuildersCacheAndSignatures(t *testing.T) { a.Equal(count, len(roundSigs)) } -func verifyBuilderCache(proto config.ConsensusParams, w *Worker, a *require.Assertions, expectedStateProofs uint64) { - for i := uint64(0); i < buildersCacheLength-1; i++ { +func verifyProverCache(proto config.ConsensusParams, w *Worker, a *require.Assertions, expectedStateProofs uint64) { + for i := uint64(0); i < proversCacheLength-1; i++ { rnd := proto.StateProofInterval*2 + proto.StateProofInterval*i - _, exists := w.builders[basics.Round(rnd)] + _, exists := w.provers[basics.Round(rnd)] a.True(exists) } - _, exists := w.builders[basics.Round(proto.StateProofInterval*(expectedStateProofs+1))] + _, exists := w.provers[basics.Round(proto.StateProofInterval*(expectedStateProofs+1))] a.True(exists) } // TestSignatureBroadcastPolicy makes sure that during half of a state proof interval, every online account -// will broadcast only buildersCacheLength amount of signatures +// will broadcast only proversCacheLength amount of signatures func TestSignatureBroadcastPolicy(t *testing.T) { partitiontest.PartitionTest(t) a := require.New(t) const numberOfParticipants = 5 - const expectedStateProofs = buildersCacheLength + 2 + const expectedStateProofs = proversCacheLength + 2 var keys []account.Participation for i := 0; i < numberOfParticipants; i++ { var parent basics.Address @@ -1095,7 +1095,7 @@ func TestSignatureBroadcastPolicy(t *testing.T) { func checkSignatureBroadcastHalfInterval(t *testing.T, proto config.ConsensusParams, expectedStateProofs uint64, s *testWorkerStubs, numberOfParticipants int, a *require.Assertions) { roundSigs := make(map[basics.Round]int) - for i := uint64(2); i < buildersCacheLength; i++ { + for i := uint64(2); i < proversCacheLength; i++ { roundSigs[basics.Round(i*proto.StateProofInterval)] = 0 } roundSigs[basics.Round((expectedStateProofs+1)*proto.StateProofInterval)] = 0 @@ -1104,7 +1104,7 @@ func checkSignatureBroadcastHalfInterval(t *testing.T, proto config.ConsensusPar s.sigmsg = make(chan []byte, 1024*1024) s.advanceRoundsWithoutStateProof(t, proto.StateProofInterval/2) - for i := 0; i < numberOfParticipants*buildersCacheLength; i++ { + for i := 0; i < numberOfParticipants*proversCacheLength; i++ { sigMessage := sigFromAddr{} sigMessageBytes, err := s.waitOnSigWithTimeout(time.Second * 2) a.NoError(err) @@ -1115,13 +1115,13 @@ func checkSignatureBroadcastHalfInterval(t *testing.T, proto config.ConsensusPar roundSigs[sigMessage.Round]++ } - a.Equal(buildersCacheLength, len(roundSigs)) + a.Equal(proversCacheLength, len(roundSigs)) for _, numOfSignatures := range roundSigs { a.Equal(numberOfParticipants, numOfSignatures) } } -func TestWorkerDoesNotLimitBuildersAndSignaturesOnDisk(t *testing.T) { +func TestWorkerDoesNotLimitProversAndSignaturesOnDisk(t *testing.T) { partitiontest.PartitionTest(t) a := require.New(t) @@ -1145,8 +1145,8 @@ func TestWorkerDoesNotLimitBuildersAndSignaturesOnDisk(t *testing.T) { } // should not give up on rounds - a.Equal(buildersCacheLength, len(w.builders)) - countDB, err := countBuildersInDB(w.db) + a.Equal(proversCacheLength, len(w.provers)) + countDB, err := countProversInDB(w.db) a.NoError(err) a.Equal(proto.StateProofMaxRecoveryIntervals+1, uint64(countDB)) @@ -1308,7 +1308,7 @@ func sendReceiveCountMessages(t *testing.T, tns *testWorkerStubs, signatureBcast } } -func TestBuilderGeneratesValidStateProofTXN(t *testing.T) { +func TestProverGeneratesValidStateProofTXN(t *testing.T) { partitiontest.PartitionTest(t) a := require.New(t) @@ -1522,7 +1522,7 @@ func TestWorkerHandleSigAddrsNotInTopN(t *testing.T) { require.ErrorIs(t, err, errAddressNotInVoters) } -// Signature already part of the builderForRound, ignore +// Signature already part of the proverForRound, ignore func TestWorkerHandleSigAlreadyIn(t *testing.T) { partitiontest.PartitionTest(t) @@ -1594,8 +1594,8 @@ func TestWorkerHandleSigExceptionsDbError(t *testing.T) { require.Contains(t, "no such table: sigs", err.Error()) } -// relays reject signatures when could not createAndPersistBuilder -func TestWorkerHandleSigCantMakeBuilder(t *testing.T) { +// relays reject signatures when could not createAndPersistProver +func TestWorkerHandleSigCantMakeProver(t *testing.T) { partitiontest.PartitionTest(t) proto := config.Consensus[protocol.ConsensusCurrentVersion] @@ -1706,19 +1706,19 @@ func TestWorkerHandleSigCorrupt(t *testing.T) { require.Equal(t, network.OutgoingMessage{Action: network.Disconnect}, reply) } -func verifyPersistedBuilders(a *require.Assertions, w *Worker) { +func verifyPersistedProvers(a *require.Assertions, w *Worker) { w.mu.Lock() defer w.mu.Unlock() - for k, v := range w.builders { - var builderFromDisk builder + for k, v := range w.provers { + var proverFromDisk spProver a.NoError( w.db.Atomic(func(_ context.Context, tx *sql.Tx) error { var err error - builderFromDisk, err = getBuilder(tx, k) + proverFromDisk, err = getProver(tx, k) return err })) - a.Equal(v.BuilderPersistedFields, builderFromDisk.BuilderPersistedFields) + a.Equal(v.ProverPersistedFields, proverFromDisk.ProverPersistedFields) } } @@ -1726,7 +1726,7 @@ func TestWorkerCacheAndDiskAfterRestart(t *testing.T) { partitiontest.PartitionTest(t) a := require.New(t) - const expectedStateProofs = buildersCacheLength + 1 + const expectedStateProofs = proversCacheLength + 1 var keys []account.Participation for i := 0; i < 10; i++ { var parent basics.Address @@ -1749,42 +1749,42 @@ func TestWorkerCacheAndDiskAfterRestart(t *testing.T) { } s.advanceRoundsWithoutStateProof(t, proto.StateProofInterval/2) - // at this point we expect the number of builders in memory to be equal to buildersCacheLength - a.Equal(buildersCacheLength, len(w.builders)) - countDB, err := countBuildersInDB(w.db) + // at this point we expect the number of provers in memory to be equal to proversCacheLength + a.Equal(proversCacheLength, len(w.provers)) + countDB, err := countProversInDB(w.db) a.NoError(err) a.Equal(expectedStateProofs, countDB) - threshold := onlineBuildersThreshold(&proto, basics.Round(512)) // 512 since no StateProofs are confirmed yet (512 is the first, commitment at 256) + threshold := onlineProversThreshold(&proto, basics.Round(512)) // 512 since no StateProofs are confirmed yet (512 is the first, commitment at 256) var roundSigs map[basics.Round][]pendingSig err = w.db.Atomic(func(ctx context.Context, tx *sql.Tx) (err error) { roundSigs, err = getPendingSigs(tx, threshold, basics.Round(256+proto.StateProofInterval*expectedStateProofs), false) return }) a.NoError(err) - a.Equal(buildersCacheLength, len(roundSigs)) // Number of broadcasted sigs should be the same as number of (online) cached builders. + a.Equal(proversCacheLength, len(roundSigs)) // Number of broadcasted sigs should be the same as number of (online) cached provers. // restart worker w.Stop() - // we make sure that the worker will not be able to create a builder by disabling the mock ledger + // we make sure that the worker will not be able to create a prover by disabling the mock ledger s.keysForVoters = []account.Participation{} w.Start() defer w.Stop() - a.Equal(buildersCacheLength, len(w.builders)) - countDB, err = countBuildersInDB(w.db) + a.Equal(proversCacheLength, len(w.provers)) + countDB, err = countProversInDB(w.db) a.NoError(err) a.Equal(expectedStateProofs, countDB) - verifyPersistedBuilders(a, w) + verifyPersistedProvers(a, w) } func TestWorkerInitOnlySignaturesInDatabase(t *testing.T) { partitiontest.PartitionTest(t) a := require.New(t) - const expectedStateProofs = buildersCacheLength + 1 + const expectedStateProofs = proversCacheLength + 1 var keys []account.Participation for i := 0; i < 10; i++ { var parent basics.Address @@ -1807,28 +1807,28 @@ func TestWorkerInitOnlySignaturesInDatabase(t *testing.T) { } s.advanceRoundsWithoutStateProof(t, proto.StateProofInterval/2) - // at this point we expect the number of builders in memory to be bound with buildersCacheLength - a.Equal(buildersCacheLength, len(w.builders)) - countDB, err := countBuildersInDB(w.db) + // at this point we expect the number of provers in memory to be bound with proversCacheLength + a.Equal(proversCacheLength, len(w.provers)) + countDB, err := countProversInDB(w.db) a.NoError(err) a.Equal(expectedStateProofs, countDB) - threshold := onlineBuildersThreshold(&proto, basics.Round(512)) // 512 since no StateProofs are confirmed yet (512 is the first, commitment at 256) + threshold := onlineProversThreshold(&proto, basics.Round(512)) // 512 since no StateProofs are confirmed yet (512 is the first, commitment at 256) var roundSigs map[basics.Round][]pendingSig err = w.db.Atomic(func(ctx context.Context, tx *sql.Tx) (err error) { roundSigs, err = getPendingSigs(tx, threshold, basics.Round(256+proto.StateProofInterval*expectedStateProofs), false) return }) a.NoError(err) - a.Equal(buildersCacheLength, len(roundSigs)) // Number of broadcasted sigs should be the same as number of (online) cached builders. + a.Equal(proversCacheLength, len(roundSigs)) // Number of broadcasted sigs should be the same as number of (online) cached provers. w.Stop() accessor, err := db.MakeAccessor(w.spDbFileName, false, false) a.NoError(err) - // we now remove all builders from the table. This will cause the worker to create the builders from the ledger. + // we now remove all provers from the table. This will cause the worker to create the provers from the ledger. a.NoError(accessor.Atomic(func(_ context.Context, tx *sql.Tx) error { - _, err := tx.Exec("DELETE from builders") + _, err := tx.Exec("DELETE from provers") return err })) accessor.Close() @@ -1836,15 +1836,15 @@ func TestWorkerInitOnlySignaturesInDatabase(t *testing.T) { w.Start() defer w.Stop() - a.Equal(buildersCacheLength, len(w.builders)) - countDB, err = countBuildersInDB(w.db) + a.Equal(proversCacheLength, len(w.provers)) + countDB, err = countProversInDB(w.db) a.NoError(err) - a.Equal(buildersCacheLength, countDB) + a.Equal(proversCacheLength, countDB) - verifyPersistedBuilders(a, w) + verifyPersistedProvers(a, w) } -func TestWorkerLoadsBuilderAndSignatureUponMsgRecv(t *testing.T) { +func TestWorkerLoadsProverAndSignatureUponMsgRecv(t *testing.T) { partitiontest.PartitionTest(t) proto := config.Consensus[protocol.ConsensusCurrentVersion] @@ -1865,29 +1865,29 @@ func TestWorkerLoadsBuilderAndSignatureUponMsgRecv(t *testing.T) { require.NoError(t, err) msgBytes := protocol.Encode(&msg) - // add signature so builder will get loaded + // add signature so prover will get loaded reply := w.handleSigMessage(network.IncomingMessage{ Data: msgBytes, }) require.Equal(t, network.OutgoingMessage{Action: network.Broadcast}, reply) - // we make sure that the worker will not be able to create a builder by disabling the mock ledger + // we make sure that the worker will not be able to create a prover by disabling the mock ledger s.keysForVoters = []account.Participation{} - // removing the builder from memory will force the worker to load it from disk - w.builders = make(map[basics.Round]builder) - _, exists := w.builders[msg.Round] + // removing the prover from memory will force the worker to load it from disk + w.provers = make(map[basics.Round]spProver) + _, exists := w.provers[msg.Round] require.False(t, exists) fwd, err := w.handleSig(msg, msg.SignerAddress) - // we expect the handler to ignore the signature since the builder and the old signature were loaded + // we expect the handler to ignore the signature since the prover and the old signature were loaded require.Equal(t, network.Ignore, fwd) require.NoError(t, err) - _, exists = w.builders[msg.Round] + _, exists = w.provers[msg.Round] require.True(t, exists) - // verify that builders can be loaded even if there are no signatures - w.builders = make(map[basics.Round]builder) - _, exists = w.builders[msg.Round] + // verify that provers can be loaded even if there are no signatures + w.provers = make(map[basics.Round]spProver) + _, exists = w.provers[msg.Round] require.False(t, exists) require.NoError(t, w.db.Atomic(func(_ context.Context, tx *sql.Tx) error { _, err := tx.Exec("DELETE from sigs") @@ -1896,22 +1896,22 @@ func TestWorkerLoadsBuilderAndSignatureUponMsgRecv(t *testing.T) { fwd, err = w.handleSig(msg, msg.SignerAddress) require.Equal(t, network.Broadcast, fwd) require.NoError(t, err) - _, exists = w.builders[msg.Round] + _, exists = w.provers[msg.Round] require.True(t, exists) - // remove builder from disk and memory we fail the builder creation (since the ledger also returns error) + // remove prover from disk and memory we fail the prover creation (since the ledger also returns error) require.NoError(t, w.db.Atomic(func(_ context.Context, tx *sql.Tx) error { - _, err := tx.Exec("DELETE from builders") + _, err := tx.Exec("DELETE from provers") return err })) - w.builders = make(map[basics.Round]builder) + w.provers = make(map[basics.Round]spProver) _, err = w.handleSig(msg, msg.SignerAddress) require.ErrorIs(t, err, errEmptyVoters) - _, exists = w.builders[msg.Round] + _, exists = w.provers[msg.Round] require.False(t, exists) } -func TestWorkerCreatesBuildersOnCommit(t *testing.T) { +func TestWorkerCreatesProversOnCommit(t *testing.T) { partitiontest.PartitionTest(t) a := require.New(t) @@ -1920,35 +1920,35 @@ func TestWorkerCreatesBuildersOnCommit(t *testing.T) { _, s, w := createWorkerAndParticipants(t, protocol.ConsensusCurrentVersion, proto) defer w.Stop() - // We remove the signer's keys to stop it from generating builders. + // We remove the signer's keys to stop it from generating provers. s.keys = []account.Participation{} - firstBuilderRound := basics.Round(proto.StateProofInterval * 2) + ProverRound := basics.Round(proto.StateProofInterval * 2) // We start on round 511, so the callback should be called when committing the next round. s.advanceRoundsWithoutStateProof(t, 2) - var builderExists bool + var proverExists bool err := w.db.Atomic(func(ctx context.Context, tx *sql.Tx) (err error) { - builderExists, err = builderExistInDB(tx, firstBuilderRound) + proverExists, err = proverExistInDB(tx, ProverRound) return err }) a.NoError(err) - a.False(builderExists) + a.False(proverExists) // We leave one round uncommitted to be able to easily discern the stateProofNextRound. - s.mockCommit(firstBuilderRound) + s.mockCommit(ProverRound) - builderExists = false + proverExists = false err = w.db.Atomic(func(ctx context.Context, tx *sql.Tx) (err error) { - builderExists, err = builderExistInDB(tx, firstBuilderRound) + proverExists, err = proverExistInDB(tx, ProverRound) return err }) a.NoError(err) - a.True(builderExists) + a.True(proverExists) } -func TestSignerUsesPersistedBuilderLatestProto(t *testing.T) { +func TestSignerUsesPersistedProverLatestProto(t *testing.T) { partitiontest.PartitionTest(t) a := require.New(t) @@ -1968,18 +1968,18 @@ func TestSignerUsesPersistedBuilderLatestProto(t *testing.T) { defer os.Remove(w.spDbFileName) w.Start() - // We remove the signer's keys to stop it from generating builders and signing. + // We remove the signer's keys to stop it from generating provers and signing. prevKeys := s.keys s.keys = []account.Participation{} - firstBuilderRound := basics.Round(proto.StateProofInterval * 2) + firstProverRound := basics.Round(proto.StateProofInterval * 2) // We start on round 511, so the callback should be called on the next round. s.advanceRoundsWithoutStateProof(t, 2) - s.mockCommit(firstBuilderRound) + s.mockCommit(firstProverRound) s.waitForSignerAndBuilder(t) - a.False(s.isRoundSigned(a, w, firstBuilderRound)) + a.False(s.isRoundSigned(a, w, firstProverRound)) // We restart the signing process. s.keys = prevKeys @@ -1992,7 +1992,7 @@ func TestSignerUsesPersistedBuilderLatestProto(t *testing.T) { s.advanceRoundsWithoutStateProof(t, 1) s.waitForSignerAndBuilder(t) - a.True(s.isRoundSigned(a, w, firstBuilderRound)) + a.True(s.isRoundSigned(a, w, firstProverRound)) } func TestRegisterCommitListener(t *testing.T) { From 896015f3ecc7592a43d3119cd8ad501a165f6470 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Tue, 4 Apr 2023 13:14:38 -0400 Subject: [PATCH 030/125] AVM: Avoid panics in disassembly when branch instructions are short (#5252) Additional error information on poorly formed programs. --- data/transactions/logic/assembler.go | 20 ++++-- data/transactions/logic/assembler_test.go | 81 +++++++++++++++++++++++ 2 files changed, 97 insertions(+), 4 deletions(-) diff --git a/data/transactions/logic/assembler.go b/data/transactions/logic/assembler.go index f7979e6828..9e726a977e 100644 --- a/data/transactions/logic/assembler.go +++ b/data/transactions/logic/assembler.go @@ -2650,7 +2650,7 @@ func disassemble(dis *disassembleState, spec *OpSpec) (string, error) { out += " " switch imm.kind { case immByte, immInt8: - if pc >= len(dis.program) { + if pc+1 > len(dis.program) { return "", fmt.Errorf("program end while reading immediate %s for %s", imm.Name, spec.Name) } @@ -2680,6 +2680,10 @@ func disassemble(dis *disassembleState, spec *OpSpec) (string, error) { pc++ case immLabel: + // decodeBranchOffset assumes it has two bytes to work with + if pc+2 > len(dis.program) { + return "", fmt.Errorf("program end while reading label for %s", spec.Name) + } offset := decodeBranchOffset(dis.program, pc) target := offset + pc + 2 var label string @@ -2744,9 +2748,9 @@ func disassemble(dis *disassembleState, spec *OpSpec) (string, error) { } pc = nextpc case immLabels: - targets, nextpc, err := parseSwitch(dis.program, pc) + targets, nextpc, err := parseLabels(dis.program, pc) if err != nil { - return "", err + return "", fmt.Errorf("%w for %s", err, spec.Name) } var labels []string @@ -2873,10 +2877,18 @@ func checkByteImmArgs(cx *EvalContext) error { return err } -func parseSwitch(program []byte, pos int) (targets []int, nextpc int, err error) { +func parseLabels(program []byte, pos int) (targets []int, nextpc int, err error) { + if pos >= len(program) { + err = errors.New("could not decode label count") + return + } numOffsets := int(program[pos]) pos++ end := pos + 2*numOffsets // end of op: offset is applied to this position + if end > len(program) { + err = errors.New("could not decode labels") + return + } for i := 0; i < numOffsets; i++ { offset := decodeBranchOffset(program, pos) target := end + offset diff --git a/data/transactions/logic/assembler_test.go b/data/transactions/logic/assembler_test.go index 665741e96e..0eaefa9489 100644 --- a/data/transactions/logic/assembler_test.go +++ b/data/transactions/logic/assembler_test.go @@ -3487,3 +3487,84 @@ warning 2 expected = "1 error: 42: super annoying error\n" assertWithMsg(t, expected, b) } + +// TestDisassembleBadBranch ensures a clean error when a branch has no target. +func TestDisassembleBadBranch(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + for _, br := range []byte{0x40, 0x41, 0x42} { + dis, err := Disassemble([]byte{2, br}) + require.Error(t, err, dis) + dis, err = Disassemble([]byte{2, br, 0x01}) + require.Error(t, err, dis) + + // It would be reasonable to error here, since it's a jump past the end. + dis, err = Disassemble([]byte{2, br, 0x00, 0x05}) + require.NoError(t, err, dis) + + // It would be reasonable to error here, since it's a back jump in v2. + dis, err = Disassemble([]byte{2, br, 0xff, 0x02}) + require.NoError(t, err, dis) + + dis, err = Disassemble([]byte{2, br, 0x00, 0x01, 0x00}) + require.NoError(t, err) + } +} + +// TestDisassembleBadSwitch ensures a clean error when a switch ends early +func TestDisassembleBadSwitch(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + source := ` + int 1 + switch label1 label2 + label1: + label2: + ` + ops, err := AssembleStringWithVersion(source, AssemblerMaxVersion) + require.NoError(t, err) + + dis, err := Disassemble(ops.Program) + require.NoError(t, err, dis) + + // chop off all the labels, but keep the label count + dis, err = Disassemble(ops.Program[:len(ops.Program)-4]) + require.ErrorContains(t, err, "could not decode labels for switch", dis) + + // chop off before the label count + dis, err = Disassemble(ops.Program[:len(ops.Program)-5]) + require.ErrorContains(t, err, "could not decode label count for switch", dis) + + // chop off half of a label + dis, err = Disassemble(ops.Program[:len(ops.Program)-1]) + require.ErrorContains(t, err, "could not decode labels for switch", dis) +} + +// TestDisassembleBadMatch ensures a clean error when a match ends early +func TestDisassembleBadMatch(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + source := ` + int 40 + int 45 + int 40 + match label1 label2 + label1: + label2: + ` + ops, err := AssembleStringWithVersion(source, AssemblerMaxVersion) + require.NoError(t, err) + + dis, err := Disassemble(ops.Program) + require.NoError(t, err, dis) + + // return the label count, but chop off the labels themselves + dis, err = Disassemble(ops.Program[:len(ops.Program)-5]) + require.ErrorContains(t, err, "could not decode label count for match", dis) + + dis, err = Disassemble(ops.Program[:len(ops.Program)-1]) + require.ErrorContains(t, err, "could not decode labels for match", dis) +} From ff2997b14e1706d1a49ca71ee56c48b6b1638d12 Mon Sep 17 00:00:00 2001 From: algochoi <86622919+algochoi@users.noreply.github.com> Date: Wed, 5 Apr 2023 13:51:23 -0400 Subject: [PATCH 031/125] tests: Change truncate to dd for e2e_subs tests (#5256) --- test/scripts/e2e_subs/e2e-app-simulate.sh | 44 +++++++++++------------ 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/test/scripts/e2e_subs/e2e-app-simulate.sh b/test/scripts/e2e_subs/e2e-app-simulate.sh index 9f057beb21..223b1b65d8 100755 --- a/test/scripts/e2e_subs/e2e-app-simulate.sh +++ b/test/scripts/e2e_subs/e2e-app-simulate.sh @@ -22,7 +22,7 @@ CONST_FALSE="false" # First, try to send an extremely large "transaction" in the request body. # This should fail with a 413 error. -truncate -s 11MB "${TEMPDIR}/toolarge.tx" +dd if=/dev/zero of="${TEMPDIR}/toolarge.tx" bs=11M count=1 RES=$(${gcmd} clerk simulate -t "${TEMPDIR}/toolarge.tx" 2>&1 || true) EXPERROR="simulation error: HTTP 413 Request Entity Too Large:" if [[ $RES != *"${EXPERROR}"* ]]; then @@ -34,13 +34,13 @@ fi # WE FIRST TEST TRANSACTION GROUP SIMULATION # ############################################## -${gcmd} clerk send -a 10000 -f ${ACCOUNT} -t ${ACCOUNT} -o pay1.tx -${gcmd} clerk send -a 10000 -f ${ACCOUNT} -t ${ACCOUNT} -o pay2.tx +${gcmd} clerk send -a 10000 -f ${ACCOUNT} -t ${ACCOUNT} -o "${TEMPDIR}/pay1.tx" +${gcmd} clerk send -a 10000 -f ${ACCOUNT} -t ${ACCOUNT} -o "${TEMPDIR}/pay2.tx" -cat pay1.tx pay2.tx | ${gcmd} clerk group -i - -o grouped.tx +cat "${TEMPDIR}/pay1.tx" "${TEMPDIR}/pay2.tx" | ${gcmd} clerk group -i - -o "${TEMPDIR}/grouped.tx" # We test transaction group simulation WITHOUT signatures -RES=$(${gcmd} clerk simulate -t grouped.tx) +RES=$(${gcmd} clerk simulate -t "${TEMPDIR}/grouped.tx") if [[ $(echo "$RES" | jq '."would-succeed"') != $CONST_FALSE ]]; then date '+app-simulate-test FAIL the simulation transaction group without signatures should not succeed %Y%m%d_%H%M%S' @@ -60,14 +60,14 @@ if [[ $(echo "$RES" | jq '."txn-groups"[0]."txn-results"[1]."missing-signature"' fi # We then test transaction group simulation WITH signatures -${gcmd} clerk split -i grouped.tx -o grouped.tx +${gcmd} clerk split -i "${TEMPDIR}/grouped.tx" -o "${TEMPDIR}/grouped.tx" -${gcmd} clerk sign -i grouped-0.tx -o grouped-0.stx -${gcmd} clerk sign -i grouped-1.tx -o grouped-1.stx +${gcmd} clerk sign -i "${TEMPDIR}/grouped-0.tx" -o "${TEMPDIR}/grouped-0.stx" +${gcmd} clerk sign -i "${TEMPDIR}/grouped-1.tx" -o "${TEMPDIR}/grouped-1.stx" -cat grouped-0.stx grouped-1.stx > grouped.stx +cat "${TEMPDIR}/grouped-0.stx" "${TEMPDIR}/grouped-1.stx" > "${TEMPDIR}/grouped.stx" -RES=$(${gcmd} clerk simulate -t grouped.stx | jq '."would-succeed"') +RES=$(${gcmd} clerk simulate -t "${TEMPDIR}/grouped.stx" | jq '."would-succeed"') if [[ $RES != $CONST_TRUE ]]; then date '+app-simulate-test FAIL should pass to simulate self pay transaction group %Y%m%d_%H%M%S' @@ -78,19 +78,19 @@ fi # WE ALSO TEST OVERSPEND IN TRANSACTION GROUP # ############################################### -${gcmd} clerk send -a 1000000000000 -f ${ACCOUNT} -t ${ACCOUNT} -o pay1.tx -${gcmd} clerk send -a 10000 -f ${ACCOUNT} -t ${ACCOUNT} -o pay2.tx +${gcmd} clerk send -a 1000000000000 -f ${ACCOUNT} -t ${ACCOUNT} -o "${TEMPDIR}/pay1.tx" +${gcmd} clerk send -a 10000 -f ${ACCOUNT} -t ${ACCOUNT} -o "${TEMPDIR}/pay2.tx" -cat pay1.tx pay2.tx | ${gcmd} clerk group -i - -o grouped.tx +cat "${TEMPDIR}/pay1.tx" "${TEMPDIR}/pay2.tx" | ${gcmd} clerk group -i - -o "${TEMPDIR}/grouped.tx" -${gcmd} clerk split -i grouped.tx -o grouped.tx +${gcmd} clerk split -i "${TEMPDIR}/grouped.tx" -o "${TEMPDIR}/grouped.tx" -${gcmd} clerk sign -i grouped-0.tx -o grouped-0.stx -${gcmd} clerk sign -i grouped-1.tx -o grouped-1.stx +${gcmd} clerk sign -i "${TEMPDIR}/grouped-0.tx" -o "${TEMPDIR}/grouped-0.stx" +${gcmd} clerk sign -i "${TEMPDIR}/grouped-1.tx" -o "${TEMPDIR}/grouped-1.stx" -cat grouped-0.stx grouped-1.stx > grouped.stx +cat "${TEMPDIR}/grouped-0.stx" "${TEMPDIR}/grouped-1.stx" > "${TEMPDIR}/grouped.stx" -RES=$(${gcmd} clerk simulate -t grouped.stx) +RES=$(${gcmd} clerk simulate -t "${TEMPDIR}/grouped.stx") if [[ $(echo "$RES" | jq '."would-succeed"') != $CONST_FALSE ]]; then data '+app-simulate-test FAIL should FAIL for overspending in simulate self pay transaction group %Y%m%d_%H%M%S' @@ -121,10 +121,10 @@ fi APPID=$(echo "$RES" | grep Created | awk '{ print $6 }') # SIMULATION! empty()void -${gcmd} app method --method "empty()void" --app-id $APPID --from $ACCOUNT 2>&1 -o empty.tx +${gcmd} app method --method "empty()void" --app-id $APPID --from $ACCOUNT 2>&1 -o "${TEMPDIR}/empty.tx" # SIMULATE without a signature first -RES=$(${gcmd} clerk simulate -t empty.tx) +RES=$(${gcmd} clerk simulate -t "${TEMPDIR}/empty.tx") # confirm that without signature, the simulation should fail if [[ $(echo "$RES" | jq '."would-succeed"') != $CONST_FALSE ]]; then @@ -139,8 +139,8 @@ if [[ $(echo "$RES" | jq '."txn-groups"[0]."txn-results"[0]."missing-signature"' fi # SIMULATE with a signature -${gcmd} clerk sign -i empty.tx -o empty.stx -RES=$(${gcmd} clerk simulate -t empty.stx | jq '."would-succeed"') +${gcmd} clerk sign -i "${TEMPDIR}/empty.tx" -o "${TEMPDIR}/empty.stx" +RES=$(${gcmd} clerk simulate -t "${TEMPDIR}/empty.stx" | jq '."would-succeed"') # with signature, simulation app-call should succeed if [[ $RES != $CONST_TRUE ]]; then From f4f5ec6d058262eba330ba2c4674032e4286c691 Mon Sep 17 00:00:00 2001 From: Hang Su <87964331+ahangsu@users.noreply.github.com> Date: Wed, 5 Apr 2023 14:16:59 -0400 Subject: [PATCH 032/125] Algod: Minor refactoring REST client `submitForm` from go-sdk PR #335 (#5253) break out client support for body and parameters --- daemon/algod/api/client/restClient.go | 119 +++++++++++++++------- data/transactions/logic/sourcemap.go | 2 +- libgoal/teal.go | 7 +- test/e2e-go/features/teal/compile_test.go | 12 ++- 4 files changed, 93 insertions(+), 47 deletions(-) diff --git a/daemon/algod/api/client/restClient.go b/daemon/algod/api/client/restClient.go index 65c895c50d..9495ad59e0 100644 --- a/daemon/algod/api/client/restClient.go +++ b/daemon/algod/api/client/restClient.go @@ -34,6 +34,7 @@ import ( "github.com/algorand/go-algorand/daemon/algod/api/spec/common" "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/protocol" ) @@ -163,38 +164,49 @@ type RawResponse interface { SetBytes([]byte) } +// mergeRawQueries merges two raw queries, appending an "&" if both are non-empty +func mergeRawQueries(q1, q2 string) string { + if q1 == "" || q2 == "" { + return q1 + q2 + } + return q1 + "&" + q2 +} + // submitForm is a helper used for submitting (ex.) GETs and POSTs to the server // if expectNoContent is true, then it is expected that the response received will have a content length of zero -func (client RestClient) submitForm(response interface{}, path string, request interface{}, requestMethod string, encodeJSON bool, decodeJSON bool, expectNoContent bool) error { +func (client RestClient) submitForm( + response interface{}, path string, params interface{}, body interface{}, + requestMethod string, encodeJSON bool, decodeJSON bool, expectNoContent bool) error { + var err error queryURL := client.serverURL queryURL.Path = path var req *http.Request - var body io.Reader + var bodyReader io.Reader + var v url.Values - if request != nil { - if rawRequestPaths[path] { - reqBytes, ok := request.([]byte) - if !ok { - return fmt.Errorf("couldn't decode raw request as bytes") - } - body = bytes.NewBuffer(reqBytes) - } else { - v, err := query.Values(request) - if err != nil { - return err - } + if params != nil { + v, err = query.Values(params) + if err != nil { + return err + } + } - queryURL.RawQuery = v.Encode() - if encodeJSON { - jsonValue, _ := json.Marshal(request) - body = bytes.NewBuffer(jsonValue) - } + if requestMethod == "POST" && rawRequestPaths[path] { + reqBytes, ok := body.([]byte) + if !ok { + return fmt.Errorf("couldn't decode raw request as bytes") } + bodyReader = bytes.NewBuffer(reqBytes) + } else if encodeJSON { + jsonValue, _ := json.Marshal(params) + bodyReader = bytes.NewBuffer(jsonValue) } - req, err = http.NewRequest(requestMethod, queryURL.String(), body) + queryURL.RawQuery = mergeRawQueries(queryURL.RawQuery, v.Encode()) + + req, err = http.NewRequest(requestMethod, queryURL.String(), bodyReader) if err != nil { return err } @@ -249,26 +261,26 @@ func (client RestClient) submitForm(response interface{}, path string, request i // get performs a GET request to the specific path against the server func (client RestClient) get(response interface{}, path string, request interface{}) error { - return client.submitForm(response, path, request, "GET", false /* encodeJSON */, true /* decodeJSON */, false) + return client.submitForm(response, path, request, nil, "GET", false /* encodeJSON */, true /* decodeJSON */, false) } // delete performs a DELETE request to the specific path against the server // when expectNoContent is true, then no content is expected to be returned from the endpoint func (client RestClient) delete(response interface{}, path string, request interface{}, expectNoContent bool) error { - return client.submitForm(response, path, request, "DELETE", false /* encodeJSON */, true /* decodeJSON */, expectNoContent) + return client.submitForm(response, path, request, nil, "DELETE", false /* encodeJSON */, true /* decodeJSON */, expectNoContent) } // getRaw behaves identically to get but doesn't json decode the response, and // the response must implement the RawResponse interface func (client RestClient) getRaw(response RawResponse, path string, request interface{}) error { - return client.submitForm(response, path, request, "GET", false /* encodeJSON */, false /* decodeJSON */, false) + return client.submitForm(response, path, request, nil, "GET", false /* encodeJSON */, false /* decodeJSON */, false) } // post sends a POST request to the given path with the given request object. // No query parameters will be sent if request is nil. // response must be a pointer to an object as post writes the response there. -func (client RestClient) post(response interface{}, path string, request interface{}, expectNoContent bool) error { - return client.submitForm(response, path, request, "POST", true /* encodeJSON */, true /* decodeJSON */, expectNoContent) +func (client RestClient) post(response interface{}, path string, params interface{}, body interface{}, expectNoContent bool) error { + return client.submitForm(response, path, params, body, "POST", true /* encodeJSON */, true /* decodeJSON */, expectNoContent) } // Status retrieves the StatusResponse from the running node @@ -511,7 +523,7 @@ func (client RestClient) SuggestedParams() (response model.TransactionParameters // SendRawTransaction gets a SignedTxn and broadcasts it to the network func (client RestClient) SendRawTransaction(txn transactions.SignedTxn) (response model.PostTransactionsResponse, err error) { - err = client.post(&response, "/v2/transactions", protocol.Encode(&txn), false) + err = client.post(&response, "/v2/transactions", nil, protocol.Encode(&txn), false) return } @@ -525,7 +537,7 @@ func (client RestClient) SendRawTransactionGroup(txgroup []transactions.SignedTx } var response model.PostTransactionsResponse - return client.post(&response, "/v2/transactions", enc, false) + return client.post(&response, "/v2/transactions", nil, enc, false) } // Block gets the block info for the given round @@ -545,19 +557,19 @@ func (client RestClient) RawBlock(round uint64) (response []byte, err error) { // Shutdown requests the node to shut itself down func (client RestClient) Shutdown() (err error) { response := 1 - err = client.post(&response, "/v2/shutdown", nil, false) + err = client.post(&response, "/v2/shutdown", nil, nil, false) return } // AbortCatchup aborts the currently running catchup func (client RestClient) AbortCatchup(catchpointLabel string) (response model.CatchpointAbortResponse, err error) { - err = client.submitForm(&response, fmt.Sprintf("/v2/catchup/%s", catchpointLabel), nil, "DELETE", false, true, false) + err = client.submitForm(&response, fmt.Sprintf("/v2/catchup/%s", catchpointLabel), nil, nil, "DELETE", false, true, false) return } // Catchup start catching up to the give catchpoint label func (client RestClient) Catchup(catchpointLabel string) (response model.CatchpointStartResponse, err error) { - err = client.submitForm(&response, fmt.Sprintf("/v2/catchup/%s", catchpointLabel), nil, "POST", false, true, false) + err = client.submitForm(&response, fmt.Sprintf("/v2/catchup/%s", catchpointLabel), nil, nil, "POST", false, true, false) return } @@ -572,23 +584,52 @@ func (client RestClient) GetGoRoutines(ctx context.Context) (goRoutines string, return } +type compileParams struct { + SourceMap bool `url:"sourcemap,omitempty"` +} + // Compile compiles the given program and returned the compiled program -func (client RestClient) Compile(program []byte) (compiledProgram []byte, programHash crypto.Digest, err error) { +func (client RestClient) Compile(program []byte, useSourceMap bool) (compiledProgram []byte, programHash crypto.Digest, sourceMap *logic.SourceMap, err error) { var compileResponse model.CompileResponse - err = client.submitForm(&compileResponse, "/v2/teal/compile", program, "POST", false, true, false) + + compileRequest := compileParams{SourceMap: useSourceMap} + + err = client.submitForm(&compileResponse, "/v2/teal/compile", compileRequest, program, "POST", false, true, false) if err != nil { - return nil, crypto.Digest{}, err + return nil, crypto.Digest{}, nil, err } compiledProgram, err = base64.StdEncoding.DecodeString(compileResponse.Result) if err != nil { - return nil, crypto.Digest{}, err + return nil, crypto.Digest{}, nil, err } var progAddr basics.Address progAddr, err = basics.UnmarshalChecksumAddress(compileResponse.Hash) if err != nil { - return nil, crypto.Digest{}, err + return nil, crypto.Digest{}, nil, err } programHash = crypto.Digest(progAddr) + + // fast exit if we don't want sourcemap, then exit with what we have so far + if !useSourceMap { + return + } + + // if we want sourcemap, then we convert the *map[string]interface{} into *logic.SourceMap + if compileResponse.Sourcemap == nil { + return nil, crypto.Digest{}, nil, fmt.Errorf("requesting for sourcemap but get nothing") + } + + var srcMapInstance logic.SourceMap + var jsonBytes []byte + + if jsonBytes, err = json.Marshal(*compileResponse.Sourcemap); err != nil { + return nil, crypto.Digest{}, nil, err + } + if err = json.Unmarshal(jsonBytes, &srcMapInstance); err != nil { + return nil, crypto.Digest{}, nil, err + } + sourceMap = &srcMapInstance + return } @@ -631,14 +672,14 @@ func (client RestClient) doGetWithQuery(ctx context.Context, path string, queryA // RawDryrun gets the raw DryrunResponse associated with the passed address func (client RestClient) RawDryrun(data []byte) (response []byte, err error) { var blob Blob - err = client.submitForm(&blob, "/v2/teal/dryrun", data, "POST", false /* encodeJSON */, false /* decodeJSON */, false) + err = client.submitForm(&blob, "/v2/teal/dryrun", nil, data, "POST", false /* encodeJSON */, false /* decodeJSON */, false) response = blob return } // SimulateRawTransaction gets the raw transaction or raw transaction group, and returns relevant simulation results. func (client RestClient) SimulateRawTransaction(data []byte) (response model.SimulateResponse, err error) { - err = client.submitForm(&response, "/v2/transactions/simulate", data, "POST", false /* encodeJSON */, true /* decodeJSON */, false) + err = client.submitForm(&response, "/v2/transactions/simulate", nil, data, "POST", false /* encodeJSON */, true /* decodeJSON */, false) return } @@ -663,7 +704,7 @@ func (client RestClient) TransactionProof(txid string, round uint64, hashType cr // PostParticipationKey sends a key file to the node. func (client RestClient) PostParticipationKey(file []byte) (response model.PostParticipationResponse, err error) { - err = client.post(&response, "/v2/participation", file, false) + err = client.post(&response, "/v2/participation", nil, file, false) return } @@ -689,7 +730,7 @@ func (client RestClient) RemoveParticipationKeyByID(participationID string) (err // SetSyncRound sets the sync round for the catchup service func (client RestClient) SetSyncRound(round uint64) (err error) { - err = client.post(nil, fmt.Sprintf("/v2/ledger/sync/%d", round), nil, true) + err = client.post(nil, fmt.Sprintf("/v2/ledger/sync/%d", round), nil, nil, true) return } diff --git a/data/transactions/logic/sourcemap.go b/data/transactions/logic/sourcemap.go index 3580a120e7..6bc516749a 100644 --- a/data/transactions/logic/sourcemap.go +++ b/data/transactions/logic/sourcemap.go @@ -27,7 +27,7 @@ const sourceMapVersion = 3 const b64table string = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" // SourceMap contains details from the source to assembly process. -// Currently contains the map between TEAL source line to +// Currently, contains the map between TEAL source line to // the assembled bytecode position and details about // the template variables contained in the source file. type SourceMap struct { diff --git a/libgoal/teal.go b/libgoal/teal.go index 90295799c2..26fcbaa638 100644 --- a/libgoal/teal.go +++ b/libgoal/teal.go @@ -18,13 +18,14 @@ package libgoal import ( "github.com/algorand/go-algorand/crypto" + "github.com/algorand/go-algorand/data/transactions/logic" ) // Compile compiles the given program and returned the compiled program -func (c *Client) Compile(program []byte) (compiledProgram []byte, compiledProgramHash crypto.Digest, err error) { +func (c *Client) Compile(program []byte, useSourceMap bool) (compiledProgram []byte, compiledProgramHash crypto.Digest, sourcemap *logic.SourceMap, err error) { algod, err2 := c.ensureAlgodClient() if err2 != nil { - return nil, crypto.Digest{}, err2 + return nil, crypto.Digest{}, nil, err2 } - return algod.Compile(program) + return algod.Compile(program, useSourceMap) } diff --git a/test/e2e-go/features/teal/compile_test.go b/test/e2e-go/features/teal/compile_test.go index 760551f285..da15381120 100644 --- a/test/e2e-go/features/teal/compile_test.go +++ b/test/e2e-go/features/teal/compile_test.go @@ -24,6 +24,7 @@ import ( "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" + "github.com/algorand/go-algorand/data/transactions/logic" "github.com/algorand/go-algorand/test/framework/fixtures" "github.com/algorand/go-algorand/test/partitiontest" ) @@ -50,7 +51,7 @@ func TestTealCompile(t *testing.T) { // get lib goal client libGoalClient := fixture.LibGoalFixture.GetLibGoalClientFromNodeController(primaryNode) - compiledProgram, _, err := libGoalClient.Compile([]byte("")) + compiledProgram, _, _, err := libGoalClient.Compile([]byte(""), false) a.Nil(compiledProgram) a.Equal(err.Error(), "HTTP 404 Not Found: /teal/compile was not enabled in the configuration file by setting the EnableDeveloperAPI to true") @@ -65,19 +66,22 @@ func TestTealCompile(t *testing.T) { fixture.Start() var hash crypto.Digest - compiledProgram, hash, err = libGoalClient.Compile([]byte("int 1")) + var srcMap *logic.SourceMap + compiledProgram, hash, srcMap, err = libGoalClient.Compile([]byte("int 1"), true) a.NotNil(compiledProgram) a.NoError(err, "A valid v1 program should result in a compilation success") + a.NotNil(srcMap) a.Equal([]byte{0x1, 0x20, 0x1, 0x1, 0x22}, compiledProgram) a.Equal("6Z3C3LDVWGMX23BMSYMANACQOSINPFIRF77H7N3AWJZYV6OH6GWQ", hash.String()) - compiledProgram, hash, err = libGoalClient.Compile([]byte("#pragma version 2\nint 1")) + compiledProgram, hash, srcMap, err = libGoalClient.Compile([]byte("#pragma version 2\nint 1"), true) a.NotNil(compiledProgram) a.NoError(err, "A valid v2 program should result in a compilation success") + a.NotNil(srcMap) a.Equal([]byte{0x2, 0x20, 0x1, 0x1, 0x22}, compiledProgram) a.Equal("YOE6C22GHCTKAN3HU4SE5PGIPN5UKXAJTXCQUPJ3KKF5HOAH646A", hash.String()) - compiledProgram, hash, err = libGoalClient.Compile([]byte("bad program")) + compiledProgram, hash, _, err = libGoalClient.Compile([]byte("bad program"), false) a.Error(err, "An invalid program should result in a compilation failure") a.Nil(compiledProgram) a.Equal(crypto.Digest{}, hash) From 8068d8e16d04a84185fb99d981ac4215391d2dbe Mon Sep 17 00:00:00 2001 From: Will Winder Date: Thu, 6 Apr 2023 11:38:32 -0400 Subject: [PATCH 033/125] algocfg(profile): Add file name to config file conflict message. (#5262) --- cmd/algocfg/profileCommand.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/algocfg/profileCommand.go b/cmd/algocfg/profileCommand.go index bbddfa26c7..ea907afb20 100644 --- a/cmd/algocfg/profileCommand.go +++ b/cmd/algocfg/profileCommand.go @@ -143,7 +143,7 @@ var setProfileCmd = &cobra.Command{ } file := filepath.Join(dataDir, config.ConfigFilename) if _, err := os.Stat(file); !forceUpdate && err == nil { - fmt.Printf("A config.json file already exists for this data directory. Would you like to overwrite it? (Y/n)") + fmt.Printf("A config.json file already exists at %s\nWould you like to overwrite it? (Y/n)", file) reader := bufio.NewReader(os.Stdin) resp, err := reader.ReadString('\n') resp = strings.TrimSpace(resp) From a80008f6eb1a7411de50d85acb9bd09c5e686b2c Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Thu, 6 Apr 2023 11:57:24 -0700 Subject: [PATCH 034/125] goal: Better formatting in `goal clerk simulate` (#5259) --- cmd/goal/clerk.go | 2 +- daemon/algod/api/client/restClient.go | 8 +- daemon/algod/api/server/v2/handlers.go | 16 ++-- daemon/algod/api/server/v2/handlers_test.go | 4 +- daemon/algod/api/server/v2/utils.go | 16 ++-- libgoal/libgoal.go | 20 ++++- test/e2e-go/restAPI/restClient_test.go | 82 +++++++++++++++++++++ 7 files changed, 123 insertions(+), 25 deletions(-) diff --git a/cmd/goal/clerk.go b/cmd/goal/clerk.go index f044915fba..94d12c345f 100644 --- a/cmd/goal/clerk.go +++ b/cmd/goal/clerk.go @@ -1269,7 +1269,7 @@ var simulateCmd = &cobra.Command{ dataDir := datadir.EnsureSingleDataDir() client := ensureFullClient(dataDir) - resp, err := client.TransactionSimulation(data) + resp, err := client.SimulateRawTransaction(data) if err != nil { reportErrorf("simulation error: %s", err.Error()) } diff --git a/daemon/algod/api/client/restClient.go b/daemon/algod/api/client/restClient.go index 9495ad59e0..e3a8ea6ea1 100644 --- a/daemon/algod/api/client/restClient.go +++ b/daemon/algod/api/client/restClient.go @@ -677,9 +677,11 @@ func (client RestClient) RawDryrun(data []byte) (response []byte, err error) { return } -// SimulateRawTransaction gets the raw transaction or raw transaction group, and returns relevant simulation results. -func (client RestClient) SimulateRawTransaction(data []byte) (response model.SimulateResponse, err error) { - err = client.submitForm(&response, "/v2/transactions/simulate", nil, data, "POST", false /* encodeJSON */, true /* decodeJSON */, false) +// RawSimulateRawTransaction simulates the raw transaction or raw transaction group and returns relevant simulation results as raw bytes. +func (client RestClient) RawSimulateRawTransaction(data []byte) (response []byte, err error) { + var blob Blob + err = client.submitForm(&blob, "/v2/transactions/simulate", rawFormat{Format: "msgpack"}, data, "POST", false /* encodeJSON */, false /* decodeJSON */, false) + response = blob return } diff --git a/daemon/algod/api/server/v2/handlers.go b/daemon/algod/api/server/v2/handlers.go index 8e31f89902..e04217e779 100644 --- a/daemon/algod/api/server/v2/handlers.go +++ b/daemon/algod/api/server/v2/handlers.go @@ -923,28 +923,28 @@ func (v2 *Handlers) RawTransaction(ctx echo.Context) error { return ctx.JSON(http.StatusOK, model.PostTransactionsResponse{TxId: txid.String()}) } -// preEncodedSimulateTxnResult mirrors model.SimulateTransactionResult -type preEncodedSimulateTxnResult struct { +// PreEncodedSimulateTxnResult mirrors model.SimulateTransactionResult +type PreEncodedSimulateTxnResult struct { Txn PreEncodedTxInfo `codec:"txn-result"` MissingSignature *bool `codec:"missing-signature,omitempty"` AppBudgetConsumed *uint64 `codec:"app-budget-consumed,omitempty"` LogicSigBudgetConsumed *uint64 `codec:"logic-sig-budget-consumed,omitempty"` } -// preEncodedSimulateTxnGroupResult mirrors model.SimulateTransactionGroupResult -type preEncodedSimulateTxnGroupResult struct { - Txns []preEncodedSimulateTxnResult `codec:"txn-results"` +// PreEncodedSimulateTxnGroupResult mirrors model.SimulateTransactionGroupResult +type PreEncodedSimulateTxnGroupResult struct { + Txns []PreEncodedSimulateTxnResult `codec:"txn-results"` FailureMessage *string `codec:"failure-message,omitempty"` FailedAt *[]uint64 `codec:"failed-at,omitempty"` AppBudgetAdded *uint64 `codec:"app-budget-added,omitempty"` AppBudgetConsumed *uint64 `codec:"app-budget-consumed,omitempty"` } -// preEncodedSimulateResponse mirrors model.SimulateResponse -type preEncodedSimulateResponse struct { +// PreEncodedSimulateResponse mirrors model.SimulateResponse +type PreEncodedSimulateResponse struct { Version uint64 `codec:"version"` LastRound uint64 `codec:"last-round"` - TxnGroups []preEncodedSimulateTxnGroupResult `codec:"txn-groups"` + TxnGroups []PreEncodedSimulateTxnGroupResult `codec:"txn-groups"` WouldSucceed bool `codec:"would-succeed"` } diff --git a/daemon/algod/api/server/v2/handlers_test.go b/daemon/algod/api/server/v2/handlers_test.go index 42570db4c1..4dc20abe4c 100644 --- a/daemon/algod/api/server/v2/handlers_test.go +++ b/daemon/algod/api/server/v2/handlers_test.go @@ -178,7 +178,7 @@ func TestPendingTransactionResponseStruct(t *testing.T) { generatedResponseGraph.AssertEquals(t, customResponseGraph) } -// TestSimulateResponseStruct ensures that the hand-written preEncodedSimulateResponse has the same +// TestSimulateResponseStruct ensures that the hand-written PreEncodedSimulateResponse has the same // encoding structure as the generated model.SimulateResponse func TestSimulateResponseStruct(t *testing.T) { partitiontest.PartitionTest(t) @@ -187,7 +187,7 @@ func TestSimulateResponseStruct(t *testing.T) { generatedResponseType := reflect.TypeOf(model.SimulateResponse{}) generatedResponseGraph := makeTagGraph(generatedResponseType, make(map[reflect.Type]*tagNode)) - customResponseType := reflect.TypeOf(preEncodedSimulateResponse{}) + customResponseType := reflect.TypeOf(PreEncodedSimulateResponse{}) customResponseGraph := makeTagGraph(customResponseType, make(map[reflect.Type]*tagNode)) expectedGeneratedTxnGraph := map[string]*tagNode{ diff --git a/daemon/algod/api/server/v2/utils.go b/daemon/algod/api/server/v2/utils.go index a78e333330..c3e3219cf3 100644 --- a/daemon/algod/api/server/v2/utils.go +++ b/daemon/algod/api/server/v2/utils.go @@ -353,8 +353,8 @@ func ConvertInnerTxn(txn *transactions.SignedTxnWithAD) PreEncodedTxInfo { return response } -func convertTxnResult(txnResult simulation.TxnResult) preEncodedSimulateTxnResult { - return preEncodedSimulateTxnResult{ +func convertTxnResult(txnResult simulation.TxnResult) PreEncodedSimulateTxnResult { + return PreEncodedSimulateTxnResult{ Txn: ConvertInnerTxn(&txnResult.Txn), MissingSignature: trueOrNil(txnResult.MissingSignature), AppBudgetConsumed: numOrNil(txnResult.AppBudgetConsumed), @@ -362,13 +362,13 @@ func convertTxnResult(txnResult simulation.TxnResult) preEncodedSimulateTxnResul } } -func convertTxnGroupResult(txnGroupResult simulation.TxnGroupResult) preEncodedSimulateTxnGroupResult { - txnResults := make([]preEncodedSimulateTxnResult, len(txnGroupResult.Txns)) +func convertTxnGroupResult(txnGroupResult simulation.TxnGroupResult) PreEncodedSimulateTxnGroupResult { + txnResults := make([]PreEncodedSimulateTxnResult, len(txnGroupResult.Txns)) for i, txnResult := range txnGroupResult.Txns { txnResults[i] = convertTxnResult(txnResult) } - encoded := preEncodedSimulateTxnGroupResult{ + encoded := PreEncodedSimulateTxnGroupResult{ Txns: txnResults, FailureMessage: strOrNil(txnGroupResult.FailureMessage), AppBudgetAdded: numOrNil(txnGroupResult.AppBudgetAdded), @@ -384,12 +384,12 @@ func convertTxnGroupResult(txnGroupResult simulation.TxnGroupResult) preEncodedS return encoded } -func convertSimulationResult(result simulation.Result) preEncodedSimulateResponse { - encodedSimulationResult := preEncodedSimulateResponse{ +func convertSimulationResult(result simulation.Result) PreEncodedSimulateResponse { + encodedSimulationResult := PreEncodedSimulateResponse{ Version: result.Version, LastRound: uint64(result.LastRound), WouldSucceed: result.WouldSucceed, - TxnGroups: make([]preEncodedSimulateTxnGroupResult, len(result.TxnGroups)), + TxnGroups: make([]PreEncodedSimulateTxnGroupResult, len(result.TxnGroups)), } for i, txnGroup := range result.TxnGroups { diff --git a/libgoal/libgoal.go b/libgoal/libgoal.go index 5951793de3..143cfde028 100644 --- a/libgoal/libgoal.go +++ b/libgoal/libgoal.go @@ -1269,16 +1269,30 @@ func (c *Client) Dryrun(data []byte) (resp model.DryrunResponse, err error) { return } -// TransactionSimulation takes raw transaction or raw transaction group, and returns relevant simulation results. -func (c *Client) TransactionSimulation(data []byte) (resp model.SimulateResponse, err error) { +// SimulateRawTransaction simulates a raw transaction or raw transaction group and returns relevant simulation results. +func (c *Client) SimulateRawTransaction(data []byte) (result v2.PreEncodedSimulateResponse, err error) { algod, err := c.ensureAlgodClient() if err != nil { return } - resp, err = algod.SimulateRawTransaction(data) + var resp []byte + resp, err = algod.RawSimulateRawTransaction(data) + if err != nil { + return + } + err = protocol.DecodeReflect(resp, &result) return } +// SimulateTransactionGroup simulates a transaction group and returns relevant simulation results. +func (c *Client) SimulateTransactionGroup(txgroup []transactions.SignedTxn) (result v2.PreEncodedSimulateResponse, err error) { + var enc []byte + for i := range txgroup { + enc = append(enc, protocol.Encode(&txgroup[i])...) + } + return c.SimulateRawTransaction(enc) +} + // TransactionProof returns a Merkle proof for a transaction in a block. func (c *Client) TransactionProof(txid string, round uint64, hashType crypto.HashType) (resp model.TransactionProofResponse, err error) { algod, err := c.ensureAlgodClient() diff --git a/test/e2e-go/restAPI/restClient_test.go b/test/e2e-go/restAPI/restClient_test.go index 146d1585c0..f8d712f773 100644 --- a/test/e2e-go/restAPI/restClient_test.go +++ b/test/e2e-go/restAPI/restClient_test.go @@ -1557,3 +1557,85 @@ end: assertBoxCount(numberOfBoxesRemaining) } + +func TestSimulateTransaction(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + a := require.New(fixtures.SynchronizedTest(t)) + var localFixture fixtures.RestClientFixture + localFixture.Setup(t, filepath.Join("nettemplates", "TwoNodes50EachFuture.json")) + defer localFixture.Shutdown() + + testClient := localFixture.LibGoalClient + + _, err := testClient.WaitForRound(1) + a.NoError(err) + + wh, err := testClient.GetUnencryptedWalletHandle() + a.NoError(err) + addresses, err := testClient.ListAddresses(wh) + a.NoError(err) + senderBalance, senderAddress := getMaxBalAddr(t, testClient, addresses) + if senderAddress == "" { + t.Error("no addr with funds") + } + a.NoError(err) + + toAddress := getDestAddr(t, testClient, nil, senderAddress, wh) + closeToAddress := getDestAddr(t, testClient, nil, senderAddress, wh) + + // Ensure these accounts don't exist + receiverBalance, err := testClient.GetBalance(toAddress) + a.NoError(err) + a.Zero(receiverBalance) + closeToBalance, err := testClient.GetBalance(closeToAddress) + a.NoError(err) + a.Zero(closeToBalance) + + txn, err := testClient.ConstructPayment(senderAddress, toAddress, 0, senderBalance/2, nil, closeToAddress, [32]byte{}, 0, 0) + a.NoError(err) + stxn, err := testClient.SignTransactionWithWallet(wh, nil, txn) + a.NoError(err) + + currentRoundBeforeSimulate, err := testClient.CurrentRound() + a.NoError(err) + + result, err := testClient.SimulateTransactionGroup([]transactions.SignedTxn{stxn}) + a.NoError(err) + + currentAfterAfterSimulate, err := testClient.CurrentRound() + a.NoError(err) + + // To reduce flakiness, only check the round from simulate is within a range. + a.GreaterOrEqual(result.LastRound, currentRoundBeforeSimulate) + a.LessOrEqual(result.LastRound, currentAfterAfterSimulate) + + closingAmount := senderBalance - txn.Fee.Raw - txn.Amount.Raw + expectedResult := v2.PreEncodedSimulateResponse{ + Version: 1, + LastRound: result.LastRound, // checked above + WouldSucceed: true, + TxnGroups: []v2.PreEncodedSimulateTxnGroupResult{ + { + Txns: []v2.PreEncodedSimulateTxnResult{ + { + Txn: v2.PreEncodedTxInfo{ + Txn: stxn, + ClosingAmount: &closingAmount, + }, + }, + }, + }, + }, + } + a.Equal(expectedResult, result) + + // Ensure the transaction did not actually get applied to the ledger + receiverBalance, err = testClient.GetBalance(toAddress) + a.NoError(err) + a.Zero(receiverBalance) + closeToBalance, err = testClient.GetBalance(closeToAddress) + a.NoError(err) + a.Zero(closeToBalance) +} From d71fa3821b8b776d741ff413a7f5f0e5666f3155 Mon Sep 17 00:00:00 2001 From: algochoi <86622919+algochoi@users.noreply.github.com> Date: Thu, 6 Apr 2023 15:16:13 -0400 Subject: [PATCH 035/125] tests: Change `dd` argument to use a bytes value (#5263) --- test/scripts/e2e_subs/e2e-app-simulate.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/scripts/e2e_subs/e2e-app-simulate.sh b/test/scripts/e2e_subs/e2e-app-simulate.sh index 223b1b65d8..c68ab9ee98 100755 --- a/test/scripts/e2e_subs/e2e-app-simulate.sh +++ b/test/scripts/e2e_subs/e2e-app-simulate.sh @@ -22,7 +22,10 @@ CONST_FALSE="false" # First, try to send an extremely large "transaction" in the request body. # This should fail with a 413 error. -dd if=/dev/zero of="${TEMPDIR}/toolarge.tx" bs=11M count=1 +# Some of our MacOS nightly tests fail for specifying the bs (block size) +# value in capital letters (i.e. 11M), so just specify it as 1024 bytes and +# allocate 11K blocks so we get a 11MB sized file. +dd if=/dev/zero of="${TEMPDIR}/toolarge.tx" bs=1024 count=11000 RES=$(${gcmd} clerk simulate -t "${TEMPDIR}/toolarge.tx" 2>&1 || true) EXPERROR="simulation error: HTTP 413 Request Entity Too Large:" if [[ $RES != *"${EXPERROR}"* ]]; then From 9ff7947f32e7085867f4b6ddec4f05b09765ef2f Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Fri, 7 Apr 2023 12:36:12 -0500 Subject: [PATCH 036/125] follower: Set initial sync round to latest tracker committed round. (#5251) --- ledger/ledger.go | 5 + ledger/tracker.go | 8 ++ node/follower_node.go | 6 +- node/follower_node_test.go | 95 ++++++++++++- .../features/followerNode/syncRestart_test.go | 126 ++++++++++++++++++ ...sFollower100SecondMaxAccountLookback2.json | 28 ++++ 6 files changed, 260 insertions(+), 8 deletions(-) create mode 100644 test/e2e-go/features/followerNode/syncRestart_test.go create mode 100644 test/testdata/nettemplates/TwoNodesFollower100SecondMaxAccountLookback2.json diff --git a/ledger/ledger.go b/ledger/ledger.go index 8ad2cb9ce9..769edbe864 100644 --- a/ledger/ledger.go +++ b/ledger/ledger.go @@ -862,6 +862,11 @@ func (l *Ledger) Validate(ctx context.Context, blk bookkeeping.Block, executionP return &vb, nil } +// LatestTrackerCommitted returns the trackers' dbRound which "is always exactly accountsRound()" +func (l *Ledger) LatestTrackerCommitted() basics.Round { + return l.trackers.getDbRound() +} + // DebuggerLedger defines the minimal set of method required for creating a debug balances. type DebuggerLedger = eval.LedgerForCowBase diff --git a/ledger/tracker.go b/ledger/tracker.go index 23f5f18949..375b63c98c 100644 --- a/ledger/tracker.go +++ b/ledger/tracker.go @@ -763,3 +763,11 @@ func (tr *trackerRegistry) replay(l ledgerForTracker) (err error) { } return } + +// getDbRound accesses dbRound with protection by the trackerRegistry's mutex. +func (tr *trackerRegistry) getDbRound() basics.Round { + tr.mu.RLock() + dbRound := tr.dbRound + tr.mu.RUnlock() + return dbRound +} diff --git a/node/follower_node.go b/node/follower_node.go index 46b9136785..b2e6b58295 100644 --- a/node/follower_node.go +++ b/node/follower_node.go @@ -135,10 +135,10 @@ func MakeFollower(log logging.Logger, rootDir string, cfg config.Local, phoneboo node.catchupBlockAuth = blockAuthenticatorImpl{Ledger: node.ledger, AsyncVoteVerifier: agreement.MakeAsyncVoteVerifier(node.lowPriorityCryptoVerificationPool)} node.catchupService = catchup.MakeService(node.log, node.config, p2pNode, node.ledger, node.catchupBlockAuth, make(chan catchup.PendingUnmatchedCertificate), node.lowPriorityCryptoVerificationPool) - // Initialize sync round to the next round so that nothing falls out of the cache on Start - err = node.SetSyncRound(uint64(node.Ledger().NextRound())) + // Initialize sync round to the latest db round + 1 so that nothing falls out of the cache on Start + err = node.SetSyncRound(uint64(node.Ledger().LatestTrackerCommitted() + 1)) if err != nil { - log.Errorf("unable to set sync round to Ledger.NextRound %v", err) + log.Errorf("unable to set sync round to Ledger.DBRound %v", err) return nil, err } diff --git a/node/follower_node_test.go b/node/follower_node_test.go index 4389ed3e0f..33a004acd2 100644 --- a/node/follower_node_test.go +++ b/node/follower_node_test.go @@ -48,6 +48,12 @@ func followNodeDefaultGenesis() bookkeeping.Genesis { MicroAlgos: basics.MicroAlgos{Raw: 1000000000}, }, }, + { + Address: sinkAddr.String(), + State: basics.AccountData{ + MicroAlgos: basics.MicroAlgos{Raw: 1000000}, + }, + }, }, } } @@ -61,6 +67,20 @@ func setupFollowNode(t *testing.T) *AlgorandFollowerNode { return node } +func remakeableFollowNode(t *testing.T, tempDir string, maxAcctLookback uint64) (*AlgorandFollowerNode, string) { + cfg := config.GetDefaultLocal() + cfg.EnableFollowMode = true + cfg.DisableNetworking = true + cfg.MaxAcctLookback = maxAcctLookback + genesis := followNodeDefaultGenesis() + if tempDir == "" { + tempDir = t.TempDir() + } + followNode, err := MakeFollower(logging.Base(), tempDir, cfg, []string{}, genesis) + require.NoError(t, err) + return followNode, tempDir +} + func TestSyncRound(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() @@ -73,13 +93,13 @@ func TestSyncRound(t *testing.T) { b.CurrentProtocol = protocol.ConsensusCurrentVersion err := node.Ledger().AddBlock(b, agreement.Certificate{}) require.NoError(t, err) - latestRound := uint64(node.Ledger().Latest()) - // Sync Round should be initialized to the ledger's latest round - require.Equal(t, latestRound, node.GetSyncRound()) + dbRound := uint64(node.Ledger().LatestTrackerCommitted()) + // Sync Round should be initialized to the ledger's dbRound + 1 + require.Equal(t, dbRound+1, node.GetSyncRound()) // Set a new sync round - require.NoError(t, node.SetSyncRound(latestRound+10)) + require.NoError(t, node.SetSyncRound(dbRound+11)) // Ensure it is persisted - require.Equal(t, latestRound+10, node.GetSyncRound()) + require.Equal(t, dbRound+11, node.GetSyncRound()) // Unset the sync round and make sure get returns 0 node.UnsetSyncRound() require.Equal(t, uint64(0), node.GetSyncRound()) @@ -127,3 +147,68 @@ func TestDevModeWarning(t *testing.T) { require.NotNil(t, foundEntry) require.Contains(t, foundEntry.Message, "Follower running on a devMode network. Must submit txns to a different node.") } + +// TestSyncRoundWithRemake extends TestSyncRound to simulate starting and stopping the network +func TestSyncRoundWithRemake(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + maxAcctLookback := uint64(100) + + followNode, tempDir := remakeableFollowNode(t, "", maxAcctLookback) + addBlock := func(round basics.Round) { + b := bookkeeping.Block{ + BlockHeader: bookkeeping.BlockHeader{ + GenesisHash: followNode.ledger.GenesisHash(), + Round: round, + RewardsState: bookkeeping.RewardsState{ + RewardsRate: 0, + RewardsPool: poolAddr, + FeeSink: sinkAddr, + }, + }, + } + b.CurrentProtocol = protocol.ConsensusCurrentVersion + err := followNode.Ledger().AddBlock(b, agreement.Certificate{}) + require.NoError(t, err) + + status, err := followNode.Status() + require.NoError(t, err) + require.Equal(t, round, status.LastRound) + } + + // Part I. redo TestSyncRound + // main differences are: + // * cfg.DisableNetworking = true + // * cfg.MaxAcctLookback = 100 (instead of 4) + + addBlock(basics.Round(1)) + + dbRound := uint64(followNode.Ledger().LatestTrackerCommitted()) + // Sync Round should be initialized to the ledger's dbRound + 1 + require.Equal(t, dbRound+1, followNode.GetSyncRound()) + // Set a new sync round + require.NoError(t, followNode.SetSyncRound(dbRound+11)) + // Ensure it is persisted + require.Equal(t, dbRound+11, followNode.GetSyncRound()) + // Unset the sync round and make sure get returns 0 + followNode.UnsetSyncRound() + require.Equal(t, uint64(0), followNode.GetSyncRound()) + + // Part II. fast forward and then remake the node + + newRound := basics.Round(2 * maxAcctLookback) + for i := basics.Round(2); i <= newRound; i++ { + addBlock(i) + } + + followNode, _ = remakeableFollowNode(t, tempDir, maxAcctLookback) + status, err := followNode.Status() + require.NoError(t, err) + require.Equal(t, newRound, status.LastRound) + + // syncRound should be at + // newRound - maxAcctLookback + 1 = maxAcctLookback + 1 + syncRound := followNode.GetSyncRound() + require.Equal(t, uint64(maxAcctLookback+1), syncRound) +} diff --git a/test/e2e-go/features/followerNode/syncRestart_test.go b/test/e2e-go/features/followerNode/syncRestart_test.go new file mode 100644 index 0000000000..8137f9e240 --- /dev/null +++ b/test/e2e-go/features/followerNode/syncRestart_test.go @@ -0,0 +1,126 @@ +// Copyright (C) 2019-2023 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package followerNode + +import ( + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/config" + "github.com/algorand/go-algorand/daemon/algod/api/client" + "github.com/algorand/go-algorand/test/framework/fixtures" + "github.com/algorand/go-algorand/test/partitiontest" +) + +// Overview of this test: +// Start a two-node network--one in follower mode (follower has 0%, secondary has 100%) +// with the nodes having a max account lookback of 2. +// Advance the primary node to particular rounds, set the follower's sync round +// and then advance the follower node as much as possible. +// Restart the network and verify that the sync round hasn't advanced. +// +// NOTE: with a max account lookback of MAL, and the follower's sync round at SR: +// the follower cannot advance past round SR - 1 + MAL +func TestSyncRestart(t *testing.T) { + partitiontest.PartitionTest(t) + defer fixtures.ShutdownSynchronizedTest(t) + + if testing.Short() { + t.Skip() + } + t.Parallel() + a := require.New(fixtures.SynchronizedTest(t)) + + var fixture fixtures.RestClientFixture + fixture.Setup(t, filepath.Join("nettemplates", "TwoNodesFollower100SecondMaxAccountLookback2.json")) + + defer fixture.Shutdown() + + // sanity check that the follower has the expected max account lookback of 2: + followerCtrl, err := fixture.GetNodeController("Follower") + a.NoError(err) + cfg, err := config.LoadConfigFromDisk(followerCtrl.GetDataDir()) + a.NoError(err) + a.Equal(uint64(2), cfg.MaxAcctLookback) + + waitTill := func(node string, round uint64) { + controller, err := fixture.GetNodeController(node) + a.NoError(err) + err = fixture.ClientWaitForRoundWithTimeout(fixture.GetAlgodClientForController(controller), round) + a.NoError(err) + } + + getAlgod := func(node string) client.RestClient { + controller, err := fixture.GetNodeController(node) + a.NoError(err) + algod := fixture.GetAlgodClientForController(controller) + return algod + } + + getRound := func(node string) uint64 { + algod := getAlgod(node) + status, err := algod.Status() + a.NoError(err) + return status.LastRound + } + + getSyncRound := func() uint64 { + followClient := getAlgod("Follower") + rResp, err := followClient.GetSyncRound() + a.NoError(err) + return rResp.Round + } + + a.Equal(uint64(1), getSyncRound()) + + waitTill("Primary", 3) + // with a max account lookback of 2, and the sync round at 1, + // the follower cannot advance past round 2 = 1 - 1 + 2 + waitTill("Follower", 2) + a.LessOrEqual(uint64(3), getRound("Primary")) + a.Equal(uint64(2), getRound("Follower")) + a.Equal(uint64(1), getSyncRound()) + + /** restart the network **/ + fixture.ShutdownImpl(true) + fixture.Start() + + a.LessOrEqual(uint64(3), getRound("Primary")) + a.Equal(uint64(1), getSyncRound()) + a.Equal(uint64(2), getRound("Follower")) + + waitTill("Primary", 6) + followerClient := getAlgod("Follower") + err = followerClient.SetSyncRound(uint64(3)) + a.NoError(err) + a.Equal(uint64(3), getSyncRound()) + // with a max account lookback of 2, and the sync round at 3, + // the follower cannot advance past round 4 = 3 - 1 + 2 + waitTill("Follower", 4) + a.LessOrEqual(uint64(6), getRound("Primary")) + a.Equal(uint64(4), getRound("Follower")) + a.Equal(uint64(3), getSyncRound()) + + fixture.ShutdownImpl(true) + fixture.Start() + + a.LessOrEqual(uint64(6), getRound("Primary")) + a.Equal(uint64(4), getRound("Follower")) + a.Equal(uint64(3), getSyncRound()) +} diff --git a/test/testdata/nettemplates/TwoNodesFollower100SecondMaxAccountLookback2.json b/test/testdata/nettemplates/TwoNodesFollower100SecondMaxAccountLookback2.json new file mode 100644 index 0000000000..bae2201d14 --- /dev/null +++ b/test/testdata/nettemplates/TwoNodesFollower100SecondMaxAccountLookback2.json @@ -0,0 +1,28 @@ +{ + "Genesis": { + "LastPartKeyRound": 3000, + "NetworkName": "tbd", + "Wallets": [ + { + "Name": "Wallet1", + "Stake": 100, + "Online": true + } + ] + }, + "Nodes": [ + { + "Name": "Follower", + "Wallets": [], + "ConfigJSONOverride": "{\"EnableFollowMode\":true, \"MaxAcctLookback\":2}" + }, + { + "Name": "Primary", + "IsRelay": true, + "Wallets": [ + { "Name": "Wallet1", + "ParticipationOnly": false } + ] + } + ] +} From edbc577636e8c75c454818d463daa1aecf5ebdff Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Fri, 7 Apr 2023 16:23:13 -0400 Subject: [PATCH 037/125] AVM: Share resource arrays across transactions (#5035) v9 apps access resources named in any top-level transaction of the group, rather than only the resources named by this transaction. Non-appl transactions are treated as though they name of accounts and resources that are required to process the transaction. --- daemon/algod/api/algod.oas2.json | 12 +- daemon/algod/api/algod.oas3.yml | 4 +- .../api/server/v2/generated/data/routes.go | 73 +- .../v2/generated/experimental/routes.go | 57 +- .../api/server/v2/generated/model/types.go | 4 +- .../nonparticipating/private/routes.go | 68 +- .../nonparticipating/public/routes.go | 194 +- .../generated/participating/private/routes.go | 72 +- .../generated/participating/public/routes.go | 90 +- daemon/algod/api/server/v2/utils.go | 29 +- data/transactions/application.go | 45 +- data/transactions/application_test.go | 27 - data/transactions/logic/README.md | 35 + data/transactions/logic/README_in.md | 35 + data/transactions/logic/TEAL_opcodes.md | 4 +- data/transactions/logic/assembler_test.go | 59 +- data/transactions/logic/box_test.go | 2 +- data/transactions/logic/doc.go | 4 +- data/transactions/logic/eval.go | 540 ++++-- data/transactions/logic/evalAppTxn_test.go | 332 ++-- data/transactions/logic/evalStateful_test.go | 1665 ++++++++++------- data/transactions/logic/eval_test.go | 13 +- data/transactions/logic/export_test.go | 1 + data/transactions/logic/fields_test.go | 21 +- data/transactions/logic/langspec.json | 4 +- data/transactions/logic/ledger_test.go | 30 +- data/transactions/logic/opcodes.go | 13 +- data/transactions/logic/resources.go | 359 ++++ data/transactions/logic/resources_test.go | 909 +++++++++ data/transactions/msgp_gen.go | 294 +-- data/transactions/teal.go | 7 +- ledger/apptxn_test.go | 144 +- ledger/boxtxn_test.go | 3 +- ledger/eval/cow.go | 4 +- ledger/eval/cow_creatables.go | 4 +- ledger/eval_simple_test.go | 194 +- ledger/simple_test.go | 16 + ledger/testing/consensusRange.go | 4 +- test/scripts/e2e_subs/shared-resources.py | 97 + 39 files changed, 3904 insertions(+), 1564 deletions(-) create mode 100644 data/transactions/logic/resources.go create mode 100644 data/transactions/logic/resources_test.go create mode 100755 test/scripts/e2e_subs/shared-resources.py diff --git a/daemon/algod/api/algod.oas2.json b/daemon/algod/api/algod.oas2.json index 9c82d14c77..3f63053140 100644 --- a/daemon/algod/api/algod.oas2.json +++ b/daemon/algod/api/algod.oas2.json @@ -2973,15 +2973,15 @@ "type": "integer" }, "local-state-schema": { - "description": "[\\lsch\\] local schema", + "description": "\\[lsch\\] local schema", "$ref": "#/definitions/ApplicationStateSchema" }, "global-state-schema": { - "description": "[\\gsch\\] global schema", + "description": "\\[gsch\\] global schema", "$ref": "#/definitions/ApplicationStateSchema" }, "global-state": { - "description": "[\\gs\\] global schema", + "description": "\\[gs\\] global state", "$ref": "#/definitions/TealKeyValueStore" } } @@ -3351,18 +3351,18 @@ "type": "integer" }, "local-state-delta": { - "description": "\\[ld\\] Local state key/value changes for the application being executed by this transaction.", + "description": "Local state key/value changes for the application being executed by this transaction.", "type": "array", "items": { "$ref": "#/definitions/AccountStateDelta" } }, "global-state-delta": { - "description": "\\[gd\\] Global state key/value changes for the application being executed by this transaction.", + "description": "Global state key/value changes for the application being executed by this transaction.", "$ref": "#/definitions/StateDelta" }, "logs": { - "description": "\\[lg\\] Logs for the application being executed by this transaction.", + "description": "Logs for the application being executed by this transaction.", "type": "array", "items": { "type": "string", diff --git a/daemon/algod/api/algod.oas3.yml b/daemon/algod/api/algod.oas3.yml index 687c0c8a6a..09ed0b08cf 100644 --- a/daemon/algod/api/algod.oas3.yml +++ b/daemon/algod/api/algod.oas3.yml @@ -1795,14 +1795,14 @@ "type": "array" }, "local-state-delta": { - "description": "\\[ld\\] Local state key/value changes for the application being executed by this transaction.", + "description": "Local state key/value changes for the application being executed by this transaction.", "items": { "$ref": "#/components/schemas/AccountStateDelta" }, "type": "array" }, "logs": { - "description": "\\[lg\\] Logs for the application being executed by this transaction.", + "description": "Logs for the application being executed by this transaction.", "items": { "format": "byte", "pattern": "^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$", diff --git a/daemon/algod/api/server/v2/generated/data/routes.go b/daemon/algod/api/server/v2/generated/data/routes.go index 3400c168d6..4ad51c96da 100644 --- a/daemon/algod/api/server/v2/generated/data/routes.go +++ b/daemon/algod/api/server/v2/generated/data/routes.go @@ -278,43 +278,42 @@ var swaggerSpec = []string{ "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", - "/12EssOexwAA", + "3MSywji3j7WqWFkWbkgZmv5LKbIqtQd0uDFq69PkokcjoiRqlEj7s+woxEF64QXsDq3G7yv0+xUMkbaa", + "k0U9SOfvLPKd2ppUDO/VnaD3Oc0081kpRJ4MWPZP+7Vkuhx/wdILyIg5KXwY28AjJeQ+GpRr1+1mvfO1", + "U8oSOGQPDgg54TZw2Htx28WMO4Pze3ps/C2OmlW2vJOzIB285/EITCy8JG8pzTyYcRmmwIi6Ww5lgeyp", + "VLIdqGMj6SbyZM/B1Cto36/afUalYSqLRUwn2fNgRcRn7F9A8O9p+PwULQqW9t9MiDooxv0B9s2gxVSv", + "QF29szf2YKXIZK+foIXDJG/BddFY4htcCY0Q+bQ+tuatJxJZ57kSX1nJPk6RUqu2misTZXklweVL2MeC", + "Oo8IlFSvPRub5n3l0igqoDCZwRaip8pehfyVzL1U1JUPokxyuISW+8QlcVRpCkqxSwhfObKdSQZQooGi", + "e2zG/ALh/urIUjf3JLAsT6FuVLhawtqVInsk58Ab9IndJmrqVjIYXbKsoi36qVs8QDPxSfsQ14mS4tpC", + "Ij65MRGx15OHPB/dlzzuyAtziOpbEY6W1dYTy4TNzlYl3fBhLSJy8ay9S7efB0FgRHVy+gYiF/F5naQu", + "/hczfLv0I8/8ZsTmKaCOQh082VPDHHi8tOaf2yivg0wZ58mbFV2ZtJP6PpCIsAme6Rm31IU1mZpgb2ld", + "aXiz9/Kquxl+aOTYtAeDfIc96IUG3ODJIK9LO3Q+c0T2DzVRgqkMckJr+vtswm6CjeAPlsieemaatkKe", + "jeZrr0tg8Fcvajv60DteXXM7FmASHIvS9c30Cl2rWNs+ZByz0eUlzT+9qR0rc50gPdzDyPGJhrbakMiW", + "lOpmYZGv6KSxA7vs3Q3N36Br4B9g1ijqE3eg3IlSa1nek4gik+ZGiNcvIiJIskGY1on+6AuycCldpYSU", + "qe5JtfFlt2vTJL5C0byXPW4L3TfPn4W+BRsvveJHXjclfLXAE6PBsNmin1moDOzcKJfHuK/HFhH6xWRU", + "WFtlz3Fx0fKu25LonbBRIeGOvexBvNw1vez9qjFTp2c9yebQqRT05zn5tG7RNnJQN3ObGiLSJ+5Yndcp", + "kR3x8s2mO4aWWIJg7XOCqJJfH/1KJCzxcSNBHj7EAR4+nLumvz5ufzbb+eHD+LPcnyqoxNLIwXDjxjjm", + "56E0AxtKP5DR0lmPiuXZPsZo5Sc1z4NhBs4vLkPxszxQ9ov1/fW3qnsk5jrhbN1FQMJE5toaPBgqyDya", + "kHTkukVSjNCullaS6R0WTvLXOfZLNPzlu9q77KIT6lIb7uzT4gLq0luNL7pS/nT9TtAczyOjU2Mwocan", + "mL/Z0qLMwW2Ur+4t/gZPvnyaHT159LfFl0fPjlJ4+uz50RF9/pQ+ev7kETz+8tnTI3i0/OL54nH2+Onj", + "xdPHT7949jx98vTR4ukXz/92z8ghg7JFdObT9Gf/B1/xS07enCbnBtmGJrRk9Qvsho39U0Q0xZ0IBWX5", + "7Nj/9L/9DjtIRdGA97/OXBbwbK11qY4PDzebzUHY5XCFzqdEiypdH/px+i9fvzmtM7nspRxX1CbpeGOL", + "Z4UT/Pb2m7NzcvLm9CB4WfV4dnRwdPAIH94sgdOSzY5nT/An3D1rXPdDx2yz449X89nhGmiOsRrmjwK0", + "ZKn/JIFmO/d/taGrFcgD9z6T+eny8aFXKw4/Oifc1di3w7DU+eHHlq8y29MTSyEffvQVfsZbt0roOB9t", + "0GEiFmPNDheYODy1Kaig8fBU8LKhDj+iujz4+6HLpox/xGuL3Q+H3qEfb9mi0ke9Nbh2eqRUp+uqPPyI", + "/0H+DNCyscsBurNVrLDYd6B94qHt4SLz6pC8ms9PM9u8FynmanfZYqbH76a95QB+OHNrzEAxV+ANJYbZ", + "Ds2G9klKjbjWsoKw8OZYiZqreeTZ0yVbVbLznHPnoWjCFPnPsx9fEyGJux+/oelFHbNHTpe2MIwUlwzT", + "mbIgB870rKfzWwVy18zHHZ3hBPxjES5JrFCrsp1RUavlH7DqBiKKAuPx0dGdPdjWW9kra/qvwXm8bgOx", + "J19f+FOzxYZGTj49enRnc2sHat96Yl1wvVmdcoyTMqcAsaccTujpX3ZCL/A2bTh+yXhmX9vQFPe03aA4", + "vy//svPTrPD+WI4PF4FCZeDZHW6wT8+ERienOcGWdjZP/rKzOQN5yVIg51CUQlLJ8h35ideZyUE1sv4x", + "9xO/4GLDPSGM0l0VBZU7dwRS0hVV/hiwx2HwdqTR9uhKobsY67/P5jOXdgjbEiQrgGNFkyt3Erd+rc9n", + "K+0O7YPk/Z933CUP5hALxfuJK7AmWF81YMfToUMbG5/tePq2Pkl7Jwju3j+O0/uLWeOLMgVjtf5gkT9N", + "Rj/7lFT4tBv0k+2ot1CIS1D1G9wNcxoty9xi7HPcUhQBDx8M7qwPaAKIa6/OM9EfyXtlGuA9VXbPnrjp", + "e9YjkXiT8NwTOmvBT3n+t35et5OqY4e6F1ug2b8Ewb8EwR0KAl1JPrhFg/MLw8mhtLFzJKXpGsbkQf+0", + "DG+6pYjV6jkbERauQsmQrDhry4o/9X33w5/ifH9Bud/PrRW3EY1U5gxkzQWU94vG/EsK/I+RArb6lbMp", + "zYmGPFfh3tcC974NK3BZQtyGe0yUA93HYWM/H35sP07UMu6pdaUzsQn6onPYRjb0bX71c52tvw83lOlk", + "KaTLEMLi0f3OGmh+6GrfdH5t0s17XzCHPvgxDHSL/npY1+aPfuwaXmNfneFxoJGvXOY/N06Y0KmBErJ2", + "Z7z7YOQTVn51wrOx0R8fHmLU/VoofTi7mn/s2O/Djx9qlvAlAWelZJdYYeDD1f8PAAD//2GweNWMxwAA", } // 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 5978473978..c61cd7d877 100644 --- a/daemon/algod/api/server/v2/generated/experimental/routes.go +++ b/daemon/algod/api/server/v2/generated/experimental/routes.go @@ -208,35 +208,34 @@ var swaggerSpec = []string{ "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", - "+h8qR7YAAA==", + "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", } // 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 ccf5419425..eb61d6ad66 100644 --- a/daemon/algod/api/server/v2/generated/model/types.go +++ b/daemon/algod/api/server/v2/generated/model/types.go @@ -589,10 +589,10 @@ type PendingTransactionResponse struct { // InnerTxns Inner transactions produced by application execution. InnerTxns *[]PendingTransactionResponse `json:"inner-txns,omitempty"` - // LocalStateDelta \[ld\] Local state key/value changes for the application being executed by this transaction. + // LocalStateDelta Local state key/value changes for the application being executed by this transaction. LocalStateDelta *[]AccountStateDelta `json:"local-state-delta,omitempty"` - // Logs \[lg\] Logs for the application being executed by this transaction. + // Logs Logs for the application being executed by this transaction. Logs *[][]byte `json:"logs,omitempty"` // PoolError Indicates that the transaction was kicked out of this node's transaction pool (and specifies why that happened). An empty string indicates the transaction wasn't kicked out of this node's txpool due to an error. 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 4644cf9f12..f4994e1eb0 100644 --- a/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go +++ b/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go @@ -263,40 +263,40 @@ var swaggerSpec = []string{ "rlwBC0zaqx0FvpQFaP+bz9ClUXJxAeGzQOiWueJl5ltE7QzehJFsOY966V2+4H0X6Xk9smiCNPsJPZHC", "TxiKm+bK6l/JUDxzOy4yfD0foz+o5DdGfFq85lC659NQ2cuVhsQoH9S5DY9tpHAvvd+ECHqw2CIhN1gC", "5V1T4wWLznIsecJdZEs4QVbCilvsyqASy/CY24j9kr77DBZfdHSnOaXm192F5n14rtA9IoZcP2futNyd", - "GXMTy4qQkh5r1bGyLNKSMjT9F6XKqpQO6HBj1Nan0UWPtoiSqFEi7c+yd7/MsQTY6yDP8AI2h6T6+1L9", - "filD7EmFojkEef2d1b5To1P8fp0vaAKLO8HzSxpuppNCqTwZsPWf9qvLdPfAhUgvIGP27PCBbQPPlrD7", - "aGKunblXy42vplIUICF7cMDYiaRQYu/XbZc37gwu75lt469x1Kyigk/OpnTwQcZjMrEUU3lL+ebBbJdq", - "Gqzwu+VQBGRH7ZL1QGWbkl9FHvE5GHsp7Xtauw+rNExFWMS0lB1PWES8yP5NBP/Chs9YMWol0v4rClGX", - "xXYPAb0iNBvrJ6jrefbGHqwdmez0HLRwGOU/2BeNOb7KlfAIkU/rg2zaejRRdB4w8bWW6LmKlJMiay9R", - "XORVCS6Dgp4P6jwrUHCz9Gxsm/fVTau6gMb0BipNzzVdjvwlzb1d1JUPqkhyuISWQ8WldVRpClqLSwjf", - "PaLOLAMo0GTRPUhjnoJwf3VkqZt7Etiax1A3KlyJsLRSbIfkHHiVPqFtosduJYvRpcgq3qKfvsWTNCMf", - "uQ9xHSkp9hYS8cltExE7fXvI89F9KeOuvTCrqL4n4WhZbU8hJmx2ti74lRzWIiJX0drfdPt5MATGdCfL", - "byCWER/cSepygDFTuEtI8sxvR2weB+qo2MEjPjXMgedMa/65jTo7yJRxnrxZGZZRO6nvFYkIm+Dhnu22", - "u7BKUxP+XZJzDe/6Xl51N8MPjRwb94SQ77ADvdCkGzwi5HVph84XjtH+oSZKMJVBTmhNf5eV2E2wEfzB", - "EtGpZ6dJNfMovq+9LoELQL+sLetDL3t1DfBYkklJLFPXN9xrdLZitfuQcexGLy95/vmN71ir6wTp4Z5K", - "jk80tN6GRCZS6psFSr7mo8YOLLV3N7R8i86Cf4Bdo6iX3IFyJ0qtZXnfIopMnlshXr+RiCDZFcIkt/qj", - "52zmkryKElKhuyfVlS/EXRsr8V2K5gXt7dbRXfP8WZlbsPHcK37sTVPU1yg8MRoMmy36hYXKwM6NcnmM", - "+3psEaFfTEaF1VZ2HBcXLX87FUnvBJKqEu7Y7x5E0O3pd+/XkRk7PfIt20On0tCf5+jTukXbyEHdzG1s", - "0EifuNsqv46J9YgXdLbdMdiECILV0Bmiyn599CsrYY7PHSn28CEO8PDh1DX99XH7s93ODx/GH+r+XGEm", - "RCMHw40b45ifhxIPKLh+IMelsx6VyLNdjNHKWGoeDMOcnF9czuIXebLsF/IG9reqezZmnwC37iIgYSJz", - "bQ0eDBXkIo1IQ3LdIklHaFdLq1KYDZZS8tc58Us0IOa72t/s4hXq4hvu7DPqAupiXI13utL+dP1O8RzP", - "I6tTY3ihwceZv1nzVZGD2yhf3Zv9FZ787Wl29OTRX2d/O3p2lMLTZy+OjviLp/zRiyeP4PHfnj09gkfz", - "5y9mj7PHTx/Pnj5++vzZi/TJ00ezp89f/PWelUMWZUJ04hP3J/8X3/VLTt6eJucW2YYmvBD1m+yWjf3j", - "RDzFnQgrLvLJsf/pf/sddpCqVQPe/zpxecGTpTGFPj48vLq6Ogi7HC7QHZUYVaXLQz9O/y3st6d1bhdd", - "ynFFKW3HG1s8K5zgt3ffnJ2zk7enB8Fbq8eTo4Ojg0f4FGcBkhdicjx5gj/h7lniuh86Zpscf7qeTg6X", - "wHOM3rB/rMCUIvWfSuDZxv1fX/HFAsoD92KT/eny8aFXKw4/Obfc9bZvh2Hx88NPLe9ltqMnFkc+/ORr", - "/mxv3Sqq47y2QYeRWGxrdjjDVOKxTUEHjYengpcNffgJ1eXB3w9dfmX8I15baD8cehd/vGWLSp/M2uLa", - "6ZFyky6r4vAT/gf585oERg4xhz6lJXLWNJ8yYRifqRKL7Zh0aWWEr/IhdNByglxLDH+aWUa3vV4SBr6e", - "FxU4PX7fN58gIOYhoVSwLN9s2tZIjVw2ZQVhzc361Gm1b86e90fJi4+fHk0fHV3/xZ4t7s9nT65HRua8", - "rOGys/rgGNnwI5bIQKMM7uXHR0e3eDz2RAbkp0UK3ijuVZ2ilRi24Lql6gBiNTF2pPJ3wMdeo7ueTp7u", - "OeOttqRWrHXkVbmvecZ8pi6O/ejzjX0qMS7KynhGZ9j1dPLsc87+VFqW5znDlkFtpv7S/yQvpLqSvqVV", - "OKrVipcbv411Sygwt9h4rPGFRr9YKS456nlSydaDM5OP6IuNZUsPyBtt+A3kzZnt9W9587nkDS7SXcib", - "NqA7ljeP99zzf/4Z/1vC/tkk7BmJu1tJWKfwUYJaXwOFdQGlWIGkMlnuVyowcUhv4Pd/3sg0+mMffPct", - "ttjPh5/abwG0NGe9rEymrqj6SfSowIK3PHfV8dBMWl+5jGIeQBO0zX50SVX5Bm3DIgPGsdqDqkxzJ7ad", - "a2957bWwEJp3GhdC4gBofsZRqAwkD8IhNaRK0qtmnWPJYfZGZdA/lvDg+a2CctOcPA7HybQllxxjRYou", - "3lrM98XI9X5sh2Zy8vH0maN+yqz19+EVF8YeXi56Gina72yA54euLkDn1yYVr/cF8wuDH0OXf/TXw7pu", - "cfRj9woa++quYAONfFUX/7kxR4XmHWSJ2rDz/qNdWayK57ilsVYcHx5iROJSaXM4uZ5+6lgywo8f68X0", - "5ZLqRb3+eP0/AQAA//8BfTNVqMAAAA==", + "GXMTy4qQkh5r1bGyLNKSMjT9F6XKqpQO6HBj1Nan0UWPtoiSqFEi7c+yoxAH6YUXsDkkjd9X6PcrGCJN", + "mhOhHqTzdxb5Tm1NOob34k7Q+5JmmumkUCpPBiz7p/1aMl2OvxDpBWTMnhQ+jG3gkRJ2Hw3Ktev2arnx", + "tVOKAiRkDw4YO5EUOOy9uO1ixp3B5T2zbfw1jppVVN7JWZAOPsh4BCYWXipvKc08mO0yTIMVdbccioDs", + "qFSyHqhjU/KryJM9B2OvoH2/avcZlYapCIuYTrLjwYqIz9i/gODf0/D5KUatRNp/MyHqoNjuD6A3g2Zj", + "vQJ19c7e2IOVIpOdfoIWDqO8BfuiMcc3uBIeIfJpfWxNW08kis5zJb6yEj1OkXJSW+2ViYu8KsHlS9Bj", + "QZ1HBApulp6NbfO+cmkVFdCYzECF6Lmmq5C/krmXirryQRVJDpfQcp+4JI4qTUFrcQnhK0fUmWUABRoo", + "usdmzC8Q7q+OLHVzTwLL8hjqRoUrEZZWiu2QnANv0Ce0TfTYrWQxuhRZxVv007d4gGbkk/YhriMlxd5C", + "Ij65bSJipycPeT66L2XckRfmENW3Ihwtq60nxITNztYFv5LDWkTk4ll7l24/D4bAmO7k9A1ELuLzOkld", + "/C9m+HbpR5757YjNU0AdhTp4sqeGOfB4ac0/t1FeB5kyzpM3K7oyaif1fSARYRM807PdUhfWZGqCvUty", + "peHN3sur7mb4oZFj4x4M8h12oBcacIMng7wu7dD5whHZP9RECaYyyAmt6e+yCbsJNoI/WCI69ew0qUIe", + "RfO11yUw+OuXtR196B2vrrkdCzApiUXp+mZ6ja5VrG0fMo7d6OUlzz+/qR0rc50gPdzDyPGJhrbakMhE", + "Sn2zsMjXfNTYgV327oaWb9E18A+waxT1iTtQ7kSptSzvSUSRyXMrxOsXEREku0KY5ER/9JzNXEpXUUIq", + "dPekuvJlt2vTJL5C0byXvd0WumuePytzCzaee8WPvWlK+BqFJ0aDYbNFv7BQGdi5US6PcV+PLSL0i8mo", + "sLbKjuPiouVdp5LonbBRVcIde9mDeLk9vez9qjFjp0eeZHvoVBr68xx9WrdoGzmom7mNDRHpE3dbndcx", + "kR3x8s22O4aWEEGw9jlDVNmvj35lJczxcSPFHj7EAR4+nLqmvz5uf7bb+eHD+LPcnyuohGjkYLhxYxzz", + "81CaAYXSD2S0dNajEnm2izFa+UnN82CYgfOLy1D8Ig+U/UK+v/5WdY/E7BPO1l0EJExkrq3Bg6GCzKMR", + "SUeuWyTFCO1qaVUKs8HCSf46J36Jhr98V3uXXXRCXWrDnX1GXUBdeqvxRVfan67fKZ7jeWR1agwmNPgU", + "8zdrvipycBvlq3uzv8KTvz3Njp48+uvsb0fPjlJ4+uzF0RF/8ZQ/evHkETz+27OnR/Bo/vzF7HH2+Onj", + "2dPHT58/e5E+efpo9vT5i7/es3LIokyITnya/uT/4it+ycnb0+TcItvQhBeifoHdsrF/ioinuBNhxUU+", + "OfY//W+/ww5StWrA+18nLgt4sjSm0MeHh1dXVwdhl8MFOp8So6p0eejH6b98/fa0zuSiSzmuKCXpeGOL", + "Z4UT/Pbum7NzdvL29CB4WfV4cnRwdPAIH94sQPJCTI4nT/An3D1LXPdDx2yT40/X08nhEniOsRr2jxWY", + "UqT+Uwk827j/6yu+WEB54N5nsj9dPj70asXhJ+eEu9727TAsdX74qeWrzHb0xFLIh598hZ/trVsldJyP", + "NugwEottzQ5nmDg8tinooPHwVPCyoQ8/obo8+Puhy6aMf8RrC+2HQ+/Qj7dsUemTWVtcOz1SbtJlVRx+", + "wv8gf16TwMgh5r6nJETOmuZTJgzjM1ViaR2TLq2M8DU9hA5aTpBrieFPM8vottdLwsBX76Jypsfv++YT", + "BMQ8JJQKluWbTdsaqZHLpqwgrLBZnzqt9s3Z8/4oefHx06Ppo6Prv9izxf357Mn1yDiclzVcdlYfHCMb", + "fsSCGGiUwb38+OjoFk/FnsiA/LRIwYvEvRpTtBLDFly3VB1ArCbGjsT9DvjY23PX08nTPWe81ZbUiqyO", + "vCH3Nc+Yz8vFsR99vrFPJUZBWRnP6Ay7nk6efc7Zn0rL8jxn2DKoxNRf+p/khVRX0re0Cke1WvFy47ex", + "bgkF5hYbjzW+0OgXK8UlRz1PKtl6XmbyEX2xsdzoAXmjDb+BvDmzvf4tbz6XvMFFugt50wZ0x/Lm8Z57", + "/s8/439L2D+bhD0jcXcrCesUPkpH62ugsC6gFCuQVBTL/UrlJA7pxfv+zxuZRn/sg+++vBb7+fBTu/J/", + "S3PWy8pk6opqnUSPCixvy3NXCw/NpPWVyyjmATQh2uxHl0KVb9A2LDJgHGs7qMo0d2LbufaW114LC6F5", + "lXEhJA6A5mcchYo+8iD4UUOqJL1h1jmWHGZvVAb9YwkPnt8qKDfNyeNwnExbcskxVqTE4q3FfF+MXO/H", + "dmgmJx9Pnznqh8tafx9ecWHs4eVipZGi/c4GeH7oqgB0fm0S73pfMJsw+DF0+Ud/PayrFEc/dq+gsa/u", + "CjbQyNdw8Z8bc1Ro3kGWqA077z/alcUaeI5bGmvF8eEhxh8ulTaHk+vpp44lI/z4sV5MXxypXtTrj9f/", + "EwAA//8f+8KPlsAAAA==", } // 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 92f483a663..5f575ff99b 100644 --- a/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go +++ b/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go @@ -705,103 +705,103 @@ var swaggerSpec = []string{ "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", - "j4xKsgusxPP2/f8fAAD//3fZs+vZSwEA", + "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", + "3///AQAA//8WqsRKx0sBAA==", } // 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 e36f4942e9..d3ef2011e1 100644 --- a/daemon/algod/api/server/v2/generated/participating/private/routes.go +++ b/daemon/algod/api/server/v2/generated/participating/private/routes.go @@ -291,42 +291,42 @@ var swaggerSpec = []string{ "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", + "axL4VN8VjhSuP17/TwAAAP//fbJmDq7KAAA=", } // 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 d510355028..1bb7c09d39 100644 --- a/daemon/algod/api/server/v2/generated/participating/public/routes.go +++ b/daemon/algod/api/server/v2/generated/participating/public/routes.go @@ -310,51 +310,51 @@ var swaggerSpec = []string{ "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", - "AP//DNixnk/ZAAA=", + "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", + "AAA=", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/utils.go b/daemon/algod/api/server/v2/utils.go index c3e3219cf3..bce9371e2b 100644 --- a/daemon/algod/api/server/v2/utils.go +++ b/daemon/algod/api/server/v2/utils.go @@ -272,26 +272,29 @@ func stateDeltaToStateDelta(d basics.StateDelta) *model.StateDelta { return &delta } +func edIndexToAddress(index uint64, txn *transactions.Transaction, shared []basics.Address) string { + // index into [Sender, txn.Accounts[0], txn.Accounts[1], ..., shared[0], shared[1], ...] + switch { + case index == 0: + return txn.Sender.String() + case int(index-1) < len(txn.Accounts): + return txn.Accounts[index-1].String() + case int(index-1)-len(txn.Accounts) < len(shared): + return shared[int(index-1)-len(txn.Accounts)].String() + default: + return fmt.Sprintf("Invalid Account Index %d in LocalDelta", index) + } +} + func convertToDeltas(txn node.TxnWithStatus) (*[]model.AccountStateDelta, *model.StateDelta) { var localStateDelta *[]model.AccountStateDelta if len(txn.ApplyData.EvalDelta.LocalDeltas) > 0 { d := make([]model.AccountStateDelta, 0) - accounts := txn.Txn.Txn.Accounts + shared := txn.ApplyData.EvalDelta.SharedAccts for k, v := range txn.ApplyData.EvalDelta.LocalDeltas { - // Resolve address from index - var addr string - if k == 0 { - addr = txn.Txn.Txn.Sender.String() - } else { - if int(k-1) < len(accounts) { - addr = txn.Txn.Txn.Accounts[k-1].String() - } else { - addr = fmt.Sprintf("Invalid Address Index: %d", k-1) - } - } d = append(d, model.AccountStateDelta{ - Address: addr, + Address: edIndexToAddress(k, &txn.Txn.Txn, shared), Delta: *(stateDeltaToStateDelta(v)), }) } diff --git a/data/transactions/application.go b/data/transactions/application.go index 6ae70ed651..c303093c8d 100644 --- a/data/transactions/application.go +++ b/data/transactions/application.go @@ -230,8 +230,7 @@ func (ac *ApplicationCallTxnFields) AddressByIndex(accountIdx uint64, sender bas // An index > 0 corresponds to an offset into txn.Accounts. Check to // make sure the index is valid. if accountIdx > uint64(len(ac.Accounts)) { - err := fmt.Errorf("invalid Account reference %d", accountIdx) - return basics.Address{}, err + return basics.Address{}, fmt.Errorf("invalid Account reference %d", accountIdx) } // accountIdx must be in [1, len(ac.Accounts)] @@ -256,45 +255,3 @@ func (ac *ApplicationCallTxnFields) IndexByAddress(target basics.Address, sender return 0, fmt.Errorf("invalid Account reference %s", target) } - -// AppIDByIndex converts an integer index into an application id associated with the -// transaction. Index 0 corresponds to the current app, and an index > 0 -// corresponds to an offset into txn.ForeignApps. Returns an error if the index is -// not valid. -func (ac *ApplicationCallTxnFields) AppIDByIndex(i uint64) (basics.AppIndex, error) { - - // Index 0 always corresponds to the current app - if i == 0 { - return ac.ApplicationID, nil - } - - // An index > 0 corresponds to an offset into txn.ForeignApps. Check to - // make sure the index is valid. - if i > uint64(len(ac.ForeignApps)) { - err := fmt.Errorf("invalid Foreign App reference %d", i) - return basics.AppIndex(0), err - } - - // aidx must be in [1, len(ac.ForeignApps)] - return ac.ForeignApps[i-1], nil -} - -// IndexByAppID converts an application id into an integer offset into [current app, -// txn.ForeignApps[0], ...], returning the index at the first match. It returns -// an error if there is no such match. -func (ac *ApplicationCallTxnFields) IndexByAppID(appID basics.AppIndex) (uint64, error) { - - // Index 0 always corresponds to the current app - if appID == ac.ApplicationID { - return 0, nil - } - - // Otherwise we index into ac.ForeignApps - for i, id := range ac.ForeignApps { - if appID == id { - return uint64(i) + 1, nil - } - } - - return 0, fmt.Errorf("invalid Foreign App reference %d", appID) -} diff --git a/data/transactions/application_test.go b/data/transactions/application_test.go index 24bff87a02..a0a839b132 100644 --- a/data/transactions/application_test.go +++ b/data/transactions/application_test.go @@ -124,30 +124,3 @@ func TestEncodedAppTxnAllocationBounds(t *testing.T) { } } } - -func TestIDByIndex(t *testing.T) { - partitiontest.PartitionTest(t) - - a := require.New(t) - ac := ApplicationCallTxnFields{} - ac.ApplicationID = 1 - appID, err := ac.AppIDByIndex(0) - a.NoError(err) - a.Equal(basics.AppIndex(1), appID) - appID, err = ac.AppIDByIndex(1) - a.Contains(err.Error(), "invalid Foreign App reference") - -} - -func TestIndexByID(t *testing.T) { - partitiontest.PartitionTest(t) - - a := require.New(t) - ac := ApplicationCallTxnFields{} - ac.ApplicationID = 1 - aidx, err := ac.IndexByAppID(1) - a.NoError(err) - a.Equal(uint64(0), aidx) - aidx, err = ac.IndexByAppID(2) - a.Contains(err.Error(), "invalid Foreign App reference") -} diff --git a/data/transactions/logic/README.md b/data/transactions/logic/README.md index 324cd7d536..0e5dc8ff27 100644 --- a/data/transactions/logic/README.md +++ b/data/transactions/logic/README.md @@ -193,6 +193,41 @@ _available_. * Since v7, the account associated with any contract present in the `txn.ForeignApplications` field is _available_. + + * Since v9, there is group-level resource sharing. Any resource that + is available in _some_ top-level transaction in a transaction group + is available in _all_ v9 or later application calls in the group, + whether those application calls are top-level or inner. + + * When considering whether an asset holding or application local + state is available by group-level resource sharing, the holding or + local state must be available in a top-level transaction without + considering group sharing. For example, if account A is made + available in one transaction, and asset X is made available in + another, group resource sharing does _not_ make A's X holding + available. + + * Top-level transactions that are not application calls also make + resources available to group-level resource sharing. The following + resources are made available by other transaction types. + + 1. `pay` - `txn.Sender`, `txn.Receiver`, and + `txn.CloseRemainderTo` (if set). + + 1. `keyreg` - `txn.Sender` + + 1. `acfg` - `txn.Sender`, `txn.ConfigAsset`, and the + `txn.ConfigAsset` holding of `txn.Sender`. + + 1. `axfer` - `txn.Sender`, `txn.AssetReceiver`, `txn.AssetSender` + (if set), `txnAssetCloseTo` (if set), `txn.XferAsset`, and the + `txn.XferAsset` holding of each of those accounts. + + 1. `afrz` - `txn.Sender`, `txn.FreezeAccount`, `txn.FreezeAsset`, + and the `txn.FreezeAsset` holding of `txn.FreezeAccount`. The + `txn.FreezeAsset` holding of `txn.Sender` is _not_ made + available. + * A Box is _available_ to an Approval Program if _any_ transaction in the same group contains a box reference (`txn.Boxes`) that denotes diff --git a/data/transactions/logic/README_in.md b/data/transactions/logic/README_in.md index 58652ebcf7..ff5b524c80 100644 --- a/data/transactions/logic/README_in.md +++ b/data/transactions/logic/README_in.md @@ -193,6 +193,41 @@ _available_. * Since v7, the account associated with any contract present in the `txn.ForeignApplications` field is _available_. + + * Since v9, there is group-level resource sharing. Any resource that + is available in _some_ top-level transaction in a transaction group + is available in _all_ v9 or later application calls in the group, + whether those application calls are top-level or inner. + + * When considering whether an asset holding or application local + state is available by group-level resource sharing, the holding or + local state must be available in a top-level transaction without + considering group sharing. For example, if account A is made + available in one transaction, and asset X is made available in + another, group resource sharing does _not_ make A's X holding + available. + + * Top-level transactions that are not application calls also make + resources available to group-level resource sharing. The following + resources are made available by other transaction types. + + 1. `pay` - `txn.Sender`, `txn.Receiver`, and + `txn.CloseRemainderTo` (if set). + + 1. `keyreg` - `txn.Sender` + + 1. `acfg` - `txn.Sender`, `txn.ConfigAsset`, and the + `txn.ConfigAsset` holding of `txn.Sender`. + + 1. `axfer` - `txn.Sender`, `txn.AssetReceiver`, `txn.AssetSender` + (if set), `txnAssetCloseTo` (if set), `txn.XferAsset`, and the + `txn.XferAsset` holding of each of those accounts. + + 1. `afrz` - `txn.Sender`, `txn.FreezeAccount`, `txn.FreezeAsset`, + and the `txn.FreezeAsset` holding of `txn.FreezeAccount`. The + `txn.FreezeAsset` holding of `txn.Sender` is _not_ made + available. + * A Box is _available_ to an Approval Program if _any_ transaction in the same group contains a box reference (`txn.Boxes`) that denotes diff --git a/data/transactions/logic/TEAL_opcodes.md b/data/transactions/logic/TEAL_opcodes.md index 54a80db8f9..7752b1c182 100644 --- a/data/transactions/logic/TEAL_opcodes.md +++ b/data/transactions/logic/TEAL_opcodes.md @@ -838,7 +838,7 @@ Almost all smart contracts should use simpler and smaller methods (such as the [ - Availability: v2 - Mode: Application -params: Txn.Accounts offset (or, since v4, an _available_ account address), _available_ application id (or, since v4, a Txn.ForeignApps offset). Return: value. +params: Txn.Accounts offset (or, since v4, an _available_ account address). Return: value. ## app_opted_in @@ -1037,7 +1037,7 @@ params: Txn.ForeignApps offset or an _available_ app id. Return: did_exist flag - Availability: v3 - Mode: Application -params: Txn.Accounts offset (or, since v4, an _available_ account address), _available_ application id (or, since v4, a Txn.ForeignApps offset). Return: value. +params: Txn.Accounts offset (or, since v4, an _available_ account address). Return: value. ## pushbytes bytes diff --git a/data/transactions/logic/assembler_test.go b/data/transactions/logic/assembler_test.go index 0eaefa9489..78bc420334 100644 --- a/data/transactions/logic/assembler_test.go +++ b/data/transactions/logic/assembler_test.go @@ -428,7 +428,8 @@ pushbytess "1" "2" "1" const v8Nonsense = v7Nonsense + switchNonsense + frameNonsense + matchNonsense + boxNonsense -const v9Nonsense = v8Nonsense + pairingNonsense +const v9Nonsense = v8Nonsense +const v10Nonsense = v9Nonsense + pairingNonsense const v6Compiled = "2004010002b7a60c26050242420c68656c6c6f20776f726c6421070123456789abcd208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d047465737400320032013202320380021234292929292b0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e01022581f8acd19181cf959a1281f8acd19181cf951a81f8acd19181cf1581f8acd191810f082209240a220b230c240d250e230f2310231123122313231418191a1b1c28171615400003290349483403350222231d4a484848482b50512a632223524100034200004322602261222704634848222862482864286548482228246628226723286828692322700048482371004848361c0037001a0031183119311b311d311e311f312023221e312131223123312431253126312731283129312a312b312c312d312e312f447825225314225427042455220824564c4d4b0222382124391c0081e80780046a6f686e2281d00f23241f880003420001892224902291922494249593a0a1a2a3a4a5a6a7a8a9aaabacadae24af3a00003b003c003d816472064e014f012a57000823810858235b235a2359b03139330039b1b200b322c01a23c1001a2323c21a23c3233e233f8120af06002a494905002a49490700b400b53a03b6b7043cb8033a0c2349c42a9631007300810881088120978101c53a8101c6003a" @@ -444,18 +445,20 @@ const matchCompiled = "83030102018e02fff500008203013101320131" const v8Compiled = v7Compiled + switchCompiled + frameCompiled + matchCompiled + boxCompiled -const v9Compiled = v8Compiled + pairingCompiled +const v9Compiled = v8Compiled +const v10Compiled = v9Compiled + pairingCompiled var nonsense = map[uint64]string{ - 1: v1Nonsense, - 2: v2Nonsense, - 3: v3Nonsense, - 4: v4Nonsense, - 5: v5Nonsense, - 6: v6Nonsense, - 7: v7Nonsense, - 8: v8Nonsense, - 9: v9Nonsense, + 1: v1Nonsense, + 2: v2Nonsense, + 3: v3Nonsense, + 4: v4Nonsense, + 5: v5Nonsense, + 6: v6Nonsense, + 7: v7Nonsense, + 8: v8Nonsense, + 9: v9Nonsense, + 10: v10Nonsense, } var compiled = map[uint64]string{ @@ -468,6 +471,8 @@ var compiled = map[uint64]string{ 7: "07" + v7Compiled, 8: "08" + v8Compiled, 9: "09" + v9Compiled, + + 10: "10" + v10Compiled, } func pseudoOp(opcode string) bool { @@ -1774,7 +1779,7 @@ func TestAssembleDisassembleErrors(t *testing.T) { require.Contains(t, err.Error(), "invalid opcode") source = "int 0\nint 0\nasset_holding_get AssetFrozen" - ops, err = AssembleStringWithVersion(source, AssemblerMaxVersion) + ops, err = AssembleStringWithVersion(source, sharedResourcesVersion-1) require.NoError(t, err) ops.Program[7] = 0x50 // holding field _, err = Disassemble(ops.Program) @@ -1885,10 +1890,20 @@ func TestAssembleAsset(t *testing.T) { Expect{1, "asset_holding_get ABC 1 expects 2 stack arguments..."}) testProg(t, "int 1; asset_holding_get ABC 1", v, Expect{1, "asset_holding_get ABC 1 expects 2 stack arguments..."}) - testProg(t, "int 1; int 1; asset_holding_get ABC 1", v, - Expect{1, "asset_holding_get expects 1 immediate argument"}) - testProg(t, "int 1; int 1; asset_holding_get ABC", v, - Expect{1, "asset_holding_get unknown field: \"ABC\""}) + + if v < sharedResourcesVersion { + testProg(t, "int 1; int 1; asset_holding_get ABC 1", v, + Expect{1, "asset_holding_get expects 1 immediate argument"}) + testProg(t, "int 1; int 1; asset_holding_get ABC", v, + Expect{1, "asset_holding_get unknown field: \"ABC\""}) + } else { + testProg(t, "int 1; int 1; asset_holding_get ABC 1", v, + Expect{1, "asset_holding_get ABC 1 arg 0 wanted type..."}) + testProg(t, "txn Sender; int 1; asset_holding_get ABC 1", v, + Expect{1, "asset_holding_get expects 1 immediate argument"}) + testProg(t, "txn Sender; int 1; asset_holding_get ABC", v, + Expect{1, "asset_holding_get unknown field: \"ABC\""}) + } testProg(t, "byte 0x1234; asset_params_get ABC 1", v, Expect{1, "asset_params_get ABC 1 arg 0 wanted type uint64..."}) @@ -2159,15 +2174,17 @@ func TestHasStatefulOps(t *testing.T) { require.NoError(t, err) require.False(t, has) - source = `int 1 -int 1 -app_opted_in -err -` + source = `txn Sender; int 1; app_opted_in; err` ops = testProg(t, source, AssemblerMaxVersion) has, err = HasStatefulOps(ops.Program) require.NoError(t, err) require.True(t, has) + + source = `int 1; int 1; app_opted_in; err` + ops = testProg(t, source, sharedResourcesVersion-1) + has, err = HasStatefulOps(ops.Program) + require.NoError(t, err) + require.True(t, has) } func TestStringLiteralParsing(t *testing.T) { diff --git a/data/transactions/logic/box_test.go b/data/transactions/logic/box_test.go index 77c3adf1b2..f73cdf52a9 100644 --- a/data/transactions/logic/box_test.go +++ b/data/transactions/logic/box_test.go @@ -556,7 +556,7 @@ func TestBoxTotals(t *testing.T) { ledger.NewApp(txn.Sender, 888, basics.AppParams{}) // The SENDER certainly has no boxes (but does exist) - logic.TestApp(t, `int 0; acct_params_get AcctTotalBoxes; pop; !`, ep) + logic.TestApp(t, `txn Sender; acct_params_get AcctTotalBoxes; pop; !`, ep) // Nor does the app account, to start logic.TestApp(t, `int 888; app_params_get AppAddress; assert; acct_params_get AcctTotalBoxes; pop; !; `, ep) diff --git a/data/transactions/logic/doc.go b/data/transactions/logic/doc.go index 31a0412fa3..c64dbff672 100644 --- a/data/transactions/logic/doc.go +++ b/data/transactions/logic/doc.go @@ -337,8 +337,8 @@ var opDocExtras = map[string]string{ "pushints": "pushints args are not added to the intcblock during assembly processes", "getbit": "see explanation of bit ordering in setbit", "setbit": "When A is a uint64, index 0 is the least significant bit. Setting bit 3 to 1 on the integer 0 yields 8, or 2^3. When A is a byte array, index 0 is the leftmost bit of the leftmost byte. Setting bits 0 through 11 to 1 in a 4-byte-array of 0s yields the byte array 0xfff00000. Setting bit 3 to 1 on the 1-byte-array 0x00 yields the byte array 0x10.", - "balance": "params: Txn.Accounts offset (or, since v4, an _available_ account address), _available_ application id (or, since v4, a Txn.ForeignApps offset). Return: value.", - "min_balance": "params: Txn.Accounts offset (or, since v4, an _available_ account address), _available_ application id (or, since v4, a Txn.ForeignApps offset). Return: value.", + "balance": "params: Txn.Accounts offset (or, since v4, an _available_ account address). Return: value.", + "min_balance": "params: Txn.Accounts offset (or, since v4, an _available_ account address). Return: value.", "app_opted_in": "params: Txn.Accounts offset (or, since v4, an _available_ account address), _available_ application id (or, since v4, a Txn.ForeignApps offset). Return: 1 if opted in and 0 otherwise.", "app_local_get": "params: Txn.Accounts offset (or, since v4, an _available_ account address), state key. Return: value. The value is zero (of type uint64) if the key does not exist.", "app_local_get_ex": "params: Txn.Accounts offset (or, since v4, an _available_ account address), _available_ application id (or, since v4, a Txn.ForeignApps offset), state key. Return: did_exist flag (top of the stack, 1 if the application and key existed and 0 otherwise), value. The value is zero (of type uint64) if the key does not exist.", diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index 3de0f101d6..14ab082c50 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -242,25 +242,6 @@ type LedgerForLogic interface { Counter() uint64 } -// resources contains a catalog of available resources. It's used to track the -// apps, assets, and boxes that are available to a transaction, outside the -// direct foreign array mechanism. -type resources struct { - asas []basics.AssetIndex - apps []basics.AppIndex - - // boxes are all of the top-level box refs from the txgroup. Most are added - // during NewEvalParams(). refs using 0 on an appl create are resolved and - // added when the appl executes. The boolean value indicates the "dirtiness" - // of the box - has it been modified in this txngroup? If yes, the size of - // the box counts against the group writeBudget. So delete is NOT a dirtying - // operation. - boxes map[boxRef]bool - - // dirtyBytes maintains a running count of the number of dirty bytes in `boxes` - dirtyBytes uint64 -} - // boxRef is the "hydrated" form of a BoxRef - it has the actual app id, not an index type boxRef struct { app basics.AppIndex @@ -349,30 +330,9 @@ func copyWithClearAD(txgroup []transactions.SignedTxnWithAD) []transactions.Sign // 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 - var allBoxes map[boxRef]bool for _, tx := range txgroup { if tx.Txn.Type == protocol.ApplicationCallTx { apps++ - if allBoxes == nil && len(tx.Txn.Boxes) > 0 { - allBoxes = make(map[boxRef]bool) - } - for _, br := range tx.Txn.Boxes { - var app basics.AppIndex - if br.Index == 0 { - // "current app": Ignore if this is a create, else use ApplicationID - if tx.Txn.ApplicationID == 0 { - // When the create actually happens, and we learn the appID, we'll add it. - continue - } - app = tx.Txn.ApplicationID - } else { - // Bounds check will already have been done by - // WellFormed. For testing purposes, it's better to panic - // now than after returning a nil. - app = tx.Txn.ForeignApps[br.Index-1] // shift for the 0=this convention - } - allBoxes[boxRef{app, string(br.Name)}] = false - } } } @@ -392,17 +352,17 @@ func NewEvalParams(txgroup []transactions.SignedTxnWithAD, proto *config.Consens credit := feeCredit(txgroup, proto.MinTxnFee) - if proto.EnableAppCostPooling && apps > 0 { + if proto.EnableAppCostPooling { pooledApplicationBudget = new(int) *pooledApplicationBudget = apps * proto.MaxAppProgramCost } - if proto.EnableInnerTransactionPooling && apps > 0 { + if proto.EnableInnerTransactionPooling { pooledAllowedInners = new(int) *pooledAllowedInners = proto.MaxTxGroupSize * proto.MaxInnerTransactions } - return &EvalParams{ + ep := &EvalParams{ TxnGroup: copyWithClearAD(txgroup), Proto: proto, Specials: specials, @@ -411,9 +371,29 @@ func NewEvalParams(txgroup []transactions.SignedTxnWithAD, proto *config.Consens FeeCredit: &credit, PooledApplicationBudget: pooledApplicationBudget, pooledAllowedInners: pooledAllowedInners, - available: &resources{boxes: allBoxes}, appAddrCache: make(map[basics.AppIndex]basics.Address), } + // 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 +} + +func (ep *EvalParams) computeAvailability() *resources { + available := &resources{ + sharedAccounts: make(map[basics.Address]struct{}), + sharedAsas: make(map[basics.AssetIndex]struct{}), + sharedApps: make(map[basics.AppIndex]struct{}), + sharedHoldings: make(map[ledgercore.AccountAsset]struct{}), + sharedLocals: make(map[ledgercore.AccountApp]struct{}), + boxes: make(map[boxRef]bool), + } + for i := range ep.TxnGroup { + available.fill(&ep.TxnGroup[i].Txn, ep) + } + return available } // feeCredit returns the extra fee supplied in this top-level txgroup compared @@ -531,10 +511,10 @@ func (ep *EvalParams) RecordAD(gi int, ad transactions.ApplyData) { } ep.TxnGroup[gi].ApplyData = ad if aid := ad.ConfigAsset; aid != 0 { - ep.available.asas = append(ep.available.asas, aid) + ep.available.createdAsas = append(ep.available.createdAsas, aid) } if aid := ad.ApplicationID; aid != 0 { - ep.available.apps = append(ep.available.apps, aid) + ep.available.createdApps = append(ep.available.createdApps, aid) } } @@ -1276,6 +1256,10 @@ func (cx *EvalContext) ensureStackCap(targetCap int) { } } +func opDeprecated(cx *EvalContext) error { + return fmt.Errorf("deprecated opcode %d executed", cx.program[cx.pc]) +} + func opErr(cx *EvalContext) error { return errors.New("err opcode executed") } @@ -3256,14 +3240,14 @@ func (cx *EvalContext) getLatestTimestamp() (uint64, error) { } // getApplicationAddress memoizes app.Address() across a tx group's evaluation -func (cx *EvalContext) getApplicationAddress(app basics.AppIndex) basics.Address { +func (ep *EvalParams) getApplicationAddress(app basics.AppIndex) basics.Address { /* Do not instantiate the cache here, that would mask a programming error. The cache must be instantiated at EvalParams construction time, so that proper sharing with inner EvalParams can work. */ - appAddr, ok := cx.appAddrCache[app] + appAddr, ok := ep.appAddrCache[app] if !ok { appAddr = app.Address() - cx.appAddrCache[app] = appAddr + ep.appAddrCache[app] = appAddr } return appAddr @@ -4003,70 +3987,118 @@ func opExtract64Bits(cx *EvalContext) error { return opExtractNBytes(cx, 8) // extract 8 bytes } +// assignAccount is used to convert a stackValue into a 32-byte account value, +// enforcing any "availability" restrictions in force. +func (cx *EvalContext) assignAccount(sv stackValue) (basics.Address, error) { + _, err := sv.address() + if err != nil { + return basics.Address{}, err + } + + addr, _, err := cx.accountReference(sv) + return addr, err +} + // accountReference yields the address and Accounts offset designated by a // stackValue. If the stackValue is the app account, an account of an app in -// created.apps, or an account of an app in foreignApps, and it is not in the -// Accounts array, then len(Accounts) + 1 is returned as the index. This would -// let us catch the mistake if the index is used for set/del. If the txn somehow -// "psychically" predicted the address, and therefore it IS in txn.Accounts, -// then happy day, we can set/del it. Return the proper index. - -// If we ever want apps to be able to change local state on these accounts -// (which includes this app's own account!), we will need a change to -// EvalDelta's on disk format, so that the addr can be encoded explicitly rather -// than by index into txn.Accounts. +// created.apps, an account of an app in foreignApps, or an account made +// available by another txn, and it is not in the Accounts array, then +// len(Accounts) + 1 is returned as the index. This would let us catch the +// mistake if the index is used for set/del. If the txn somehow "psychically" +// predicted the address, and therefore it IS in txn.Accounts, then happy day, +// we can set/del it. Return the proper index. + +// Starting in v9, apps can change local state on these accounts by adding the +// address to EvalDelta.SharedAccounts and indexing it there. But at this level, +// we still report the "failure" to find an index with `len(Accounts)+1` That +// value allows mutableAccountReference to decide whether to report an error or +// not, based on version. func (cx *EvalContext) accountReference(account stackValue) (basics.Address, uint64, error) { - if account.argType() == StackUint64 { - addr, err := cx.txn.Txn.AddressByIndex(account.Uint, cx.txn.Txn.Sender) - return addr, account.Uint, err + if cx.version < sharedResourcesVersion { + if account.argType() == StackUint64 { + addr, err := cx.txn.Txn.AddressByIndex(account.Uint, cx.txn.Txn.Sender) + return addr, account.Uint, err + } } addr, err := account.address() if err != nil { return addr, 0, err } + idx, err := cx.txn.Txn.IndexByAddress(addr, cx.txn.Txn.Sender) + if err == nil { + return addr, idx, nil + } + + // IndexByAddress's `err` tells us we can't return the idx into + // txn.Accounts, but the account might still be available (because it was + // created in earlier in the group, or because of group sharing) + ok := cx.availableAccount(addr) + if !ok { + // nope, it's not available at all. So return the `err` we have from + // above, indicating that it was not found. + return addr, 0, err + } + return addr, uint64(len(cx.txn.Txn.Accounts) + 1), nil +} + +func (cx *EvalContext) availableAccount(addr basics.Address) bool { + _, err := cx.txn.Txn.IndexByAddress(addr, cx.txn.Txn.Sender) + if err == nil { + return true + } - invalidIndex := uint64(len(cx.txn.Txn.Accounts) + 1) // Allow an address for an app that was created in group - if err != nil && cx.version >= createdResourcesVersion { - for _, appID := range cx.available.apps { + if cx.version >= createdResourcesVersion { + for _, appID := range cx.available.createdApps { createdAddress := cx.getApplicationAddress(appID) if addr == createdAddress { - return addr, invalidIndex, nil + return true } } } + // or some other txn mentioned it + if cx.version >= sharedResourcesVersion { + if _, ok := cx.available.sharedAccounts[addr]; ok { + return true + } + } + // Allow an address for an app that was provided in the foreign apps array. - if err != nil && cx.version >= appAddressAvailableVersion { + if cx.version >= appAddressAvailableVersion { for _, appID := range cx.txn.Txn.ForeignApps { foreignAddress := cx.getApplicationAddress(appID) if addr == foreignAddress { - return addr, invalidIndex, nil + return true } } } - // this app's address is also allowed - if err != nil { - appAddr := cx.getApplicationAddress(cx.appID) - if appAddr == addr { - return addr, invalidIndex, nil - } + if cx.getApplicationAddress(cx.appID) == addr { + return true } - return addr, idx, err + return false } func (cx *EvalContext) mutableAccountReference(account stackValue) (basics.Address, uint64, error) { addr, accountIdx, err := cx.accountReference(account) - if err == nil && accountIdx > uint64(len(cx.txn.Txn.Accounts)) { + if err != nil { + return basics.Address{}, 0, err + } + if accountIdx > uint64(len(cx.txn.Txn.Accounts)) { // There was no error, but accountReference has signaled that accountIdx // is not for mutable ops (because it can't encode it in EvalDelta) - // This also tells us that account.address() will work. - addr, _ := account.address() - err = fmt.Errorf("invalid Account reference for mutation %s", addr) + if cx.version < sharedResourcesVersion { + return basics.Address{}, 0, fmt.Errorf("invalid Account reference for mutation %s", addr) + } + // fall through, which means that starting in v9, the accountIdx + // returned can be > len(tx.Accounts). It will end up getting passed to + // GetLocal, which can record that index in order to produce old-style + // EDS. But those EDs are only made in old consenus versions - at that + // point v9 did not exist, so no backward incompatible change occurs. } return addr, accountIdx, err } @@ -4111,12 +4143,7 @@ func opAppOptedIn(cx *EvalContext) error { last := len(cx.stack) - 1 // app prev := last - 1 // account - addr, _, err := cx.accountReference(cx.stack[prev]) - if err != nil { - return err - } - - app, err := appReference(cx, cx.stack[last].Uint, false) + addr, app, _, err := cx.localsReference(cx.stack[prev], cx.stack[last].Uint) if err != nil { return err } @@ -4167,12 +4194,7 @@ func opAppLocalGetEx(cx *EvalContext) error { } func opAppLocalGetImpl(cx *EvalContext, appID uint64, key []byte, acct stackValue) (result stackValue, ok bool, err error) { - addr, accountIdx, err := cx.accountReference(acct) - if err != nil { - return - } - - app, err := appReference(cx, appID, false) + addr, app, accountIdx, err := cx.localsReference(acct, appID) if err != nil { return } @@ -4189,7 +4211,7 @@ func opAppLocalGetImpl(cx *EvalContext, appID uint64, key []byte, acct stackValu } func opAppGetGlobalStateImpl(cx *EvalContext, appIndex uint64, key []byte) (result stackValue, ok bool, err error) { - app, err := appReference(cx, appIndex, true) + app, err := cx.appReference(appIndex, true) if err != nil { return } @@ -4235,6 +4257,32 @@ func opAppGlobalGetEx(cx *EvalContext) error { return nil } +// ensureLocalDelta is used to get accountIdx that is usable in the LocalDeltas +// of the EvalDelta. The input accountIdx is "tentative" - if it's longer than +// txn.Accounts, then we may need to add the address into SharedAccounts, and +// index into it. +func (cx *EvalContext) ensureLocalDelta(accountIdx uint64, addr basics.Address) uint64 { + if accountIdx > uint64(len(cx.txn.Txn.Accounts)) { + // the returned accountIdx was just a signal that the account was + // not in txn, so we look in SharedAccounts, allocating space if needed. + found := false + for i, shared := range cx.txn.EvalDelta.SharedAccts { + if shared == addr { + found = true + accountIdx = uint64(len(cx.txn.Txn.Accounts) + 1 + i) + } + } + if !found { + cx.txn.EvalDelta.SharedAccts = append(cx.txn.EvalDelta.SharedAccts, addr) + accountIdx = uint64(len(cx.txn.Txn.Accounts) + len(cx.txn.EvalDelta.SharedAccts)) + } + } + if _, ok := cx.txn.EvalDelta.LocalDeltas[accountIdx]; !ok { + cx.txn.EvalDelta.LocalDeltas[accountIdx] = basics.StateDelta{} + } + return accountIdx +} + func opAppLocalPut(cx *EvalContext) error { last := len(cx.stack) - 1 // value prev := last - 1 // state key @@ -4264,9 +4312,7 @@ func opAppLocalPut(cx *EvalContext) error { tv := sv.toTealValue() if !ok || tv != etv { - if _, ok := cx.txn.EvalDelta.LocalDeltas[accountIdx]; !ok { - cx.txn.EvalDelta.LocalDeltas[accountIdx] = basics.StateDelta{} - } + accountIdx = cx.ensureLocalDelta(accountIdx, addr) cx.txn.EvalDelta.LocalDeltas[accountIdx][key] = tv.ToValueDelta() } @@ -4351,9 +4397,7 @@ func opAppLocalDel(cx *EvalContext) error { if err != nil { return err } - if _, ok := cx.txn.EvalDelta.LocalDeltas[accountIdx]; !ok { - cx.txn.EvalDelta.LocalDeltas[accountIdx] = basics.StateDelta{} - } + accountIdx = cx.ensureLocalDelta(accountIdx, addr) cx.txn.EvalDelta.LocalDeltas[accountIdx][key] = basics.ValueDelta{ Action: basics.DeleteAction, } @@ -4398,30 +4442,24 @@ func opAppGlobalDel(cx *EvalContext) error { // more than 2 or so, and was often called an "index". But it was not a // basics.AssetIndex or basics.ApplicationIndex. -func appReference(cx *EvalContext, ref uint64, foreign bool) (basics.AppIndex, error) { +func (cx *EvalContext) appReference(ref uint64, foreign bool) (basics.AppIndex, error) { if cx.version >= directRefEnabledVersion { if ref == 0 || ref == uint64(cx.appID) { return cx.appID, nil } - for _, appID := range cx.txn.Txn.ForeignApps { - if appID == basics.AppIndex(ref) { - return appID, nil - } + aid := basics.AppIndex(ref) + if cx.availableApp(aid) { + return aid, nil } - // or was created in group - if cx.version >= createdResourcesVersion { - for _, appID := range cx.available.apps { - if appID == basics.AppIndex(ref) { - return appID, nil - } + + if cx.version < sharedResourcesVersion { + // Allow use of indexes, but this comes last so that clear advice can be + // given to anyone who cares about semantics in the first few rounds of + // a new network - don't use indexes for references, use the App ID + if ref <= uint64(len(cx.txn.Txn.ForeignApps)) { + return basics.AppIndex(cx.txn.Txn.ForeignApps[ref-1]), nil } } - // Allow use of indexes, but this comes last so that clear advice can be - // given to anyone who cares about semantics in the first few rounds of - // a new network - don't use indexes for references, use the App ID - if ref <= uint64(len(cx.txn.Txn.ForeignApps)) { - return basics.AppIndex(cx.txn.Txn.ForeignApps[ref-1]), nil - } } else { // Old rules if ref == 0 { // Even back when expected to be a real ID, ref = 0 was current app @@ -4437,30 +4475,76 @@ func appReference(cx *EvalContext, ref uint64, foreign bool) (basics.AppIndex, e return basics.AppIndex(ref), nil } } - return basics.AppIndex(0), fmt.Errorf("invalid App reference %d", ref) + return 0, fmt.Errorf("invalid App reference %d", ref) } -func asaReference(cx *EvalContext, ref uint64, foreign bool) (basics.AssetIndex, error) { +// localsReference has the main job of resolving the account (as bytes or u64) +// and the App, taking access rules into account. It has the funny side job of +// also reporting which "slot" the address appears in, if it is in txn.Accounts +// (or is the Sender, which yields 0). But it only needs to do this funny side +// job in certainly old versions that need the slot index while doing a lookup. +func (cx *EvalContext) localsReference(account stackValue, ref uint64) (basics.Address, basics.AppIndex, uint64, error) { + if cx.version >= sharedResourcesVersion { + unused := uint64(0) // see function comment + addr, err := account.address() + if err != nil { + return basics.Address{}, 0, 0, err + } + aid := basics.AppIndex(ref) + if ref == 0 { + aid = cx.appID + } + if cx.allowsLocals(addr, aid) { + return addr, aid, unused, nil + } + + // Do some extra lookups to give a more concise err. Whenever a locals + // is available, its account and app must be as well (but not vice + // versa, anymore). So, if (only) one of them is not available, yell + // about it, specifically. + + _, _, acctErr := cx.accountReference(account) + _, appErr := cx.appReference(ref, false) + switch { + case acctErr != nil && appErr == nil: + err = acctErr + case acctErr == nil && appErr != nil: + err = appErr + default: + err = fmt.Errorf("invalid Local State access %s x %d", addr, aid) + } + + return basics.Address{}, 0, 0, err + } + + // Pre group resource sharing, the rule is just that account and app are + // each available. + addr, addrIdx, err := cx.accountReference(account) + if err != nil { + return basics.Address{}, 0, 0, err + } + app, err := cx.appReference(ref, false) + if err != nil { + return basics.Address{}, 0, 0, err + } + return addr, app, addrIdx, nil +} + +func (cx *EvalContext) assetReference(ref uint64, foreign bool) (basics.AssetIndex, error) { if cx.version >= directRefEnabledVersion { - for _, assetID := range cx.txn.Txn.ForeignAssets { - if assetID == basics.AssetIndex(ref) { - return assetID, nil - } + aid := basics.AssetIndex(ref) + if cx.availableAsset(aid) { + return aid, nil } - // or was created in group - if cx.version >= createdResourcesVersion { - for _, assetID := range cx.available.asas { - if assetID == basics.AssetIndex(ref) { - return assetID, nil - } + + if cx.version < sharedResourcesVersion { + // Allow use of indexes, but this comes last so that clear advice can be + // given to anyone who cares about semantics in the first few rounds of + // a new network - don't use indexes for references, use the asa ID. + if ref < uint64(len(cx.txn.Txn.ForeignAssets)) { + return basics.AssetIndex(cx.txn.Txn.ForeignAssets[ref]), nil } } - // Allow use of indexes, but this comes last so that clear advice can be - // given to anyone who cares about semantics in the first few rounds of - // a new network - don't use indexes for references, use the asa ID. - if ref < uint64(len(cx.txn.Txn.ForeignAssets)) { - return basics.AssetIndex(cx.txn.Txn.ForeignAssets[ref]), nil - } } else { // Old rules if foreign { @@ -4473,10 +4557,53 @@ func asaReference(cx *EvalContext, ref uint64, foreign bool) (basics.AssetIndex, return basics.AssetIndex(ref), nil } } - return basics.AssetIndex(0), fmt.Errorf("invalid Asset reference %d", ref) + return 0, fmt.Errorf("invalid Asset reference %d", ref) } +func (cx *EvalContext) holdingReference(account stackValue, ref uint64) (basics.Address, basics.AssetIndex, error) { + if cx.version >= sharedResourcesVersion { + addr, err := account.address() + if err != nil { + return basics.Address{}, 0, err + } + aid := basics.AssetIndex(ref) + if cx.allowsHolding(addr, aid) { + return addr, aid, nil + } + + // Do some extra lookups to give a more concise err. Whenever a holding + // is available, its account and asset must be as well (but not vice + // versa, anymore). So, if (only) one of them is not available, yell + // about it, specifically. + + _, _, acctErr := cx.accountReference(account) + _, assetErr := cx.assetReference(ref, false) + switch { + case acctErr != nil && assetErr == nil: + err = acctErr + case acctErr == nil && assetErr != nil: + err = assetErr + default: + err = fmt.Errorf("invalid Holding access %s x %d", addr, aid) + } + + return basics.Address{}, 0, err + } + + // Pre group resource sharing, the rule is just that account and asset are + // each available. + addr, _, err := cx.accountReference(account) + if err != nil { + return basics.Address{}, 0, err + } + asset, err := cx.assetReference(ref, false) + if err != nil { + return basics.Address{}, 0, err + } + return addr, asset, nil +} + func opAssetHoldingGet(cx *EvalContext) error { last := len(cx.stack) - 1 // asset prev := last - 1 // account @@ -4487,12 +4614,7 @@ func opAssetHoldingGet(cx *EvalContext) error { return fmt.Errorf("invalid asset_holding_get field %d", holdingField) } - addr, _, err := cx.accountReference(cx.stack[prev]) - if err != nil { - return err - } - - asset, err := asaReference(cx, cx.stack[last].Uint, false) + addr, asset, err := cx.holdingReference(cx.stack[prev], cx.stack[last].Uint) if err != nil { return err } @@ -4500,7 +4622,7 @@ func opAssetHoldingGet(cx *EvalContext) error { var exist uint64 = 0 var value stackValue if holding, err := cx.Ledger.AssetHolding(addr, asset); err == nil { - // the holding exist, read the value + // the holding exists, read the value exist = 1 value, err = cx.assetHoldingToValue(&holding, fs) if err != nil { @@ -4522,7 +4644,7 @@ func opAssetParamsGet(cx *EvalContext) error { return fmt.Errorf("invalid asset_params_get field %d", paramField) } - asset, err := asaReference(cx, cx.stack[last].Uint, true) + asset, err := cx.assetReference(cx.stack[last].Uint, true) if err != nil { return err } @@ -4552,7 +4674,7 @@ func opAppParamsGet(cx *EvalContext) error { return fmt.Errorf("invalid app_params_get field %d", paramField) } - app, err := appReference(cx, cx.stack[last].Uint, true) + app, err := cx.appReference(cx.stack[last].Uint, true) if err != nil { return err } @@ -4726,76 +4848,99 @@ func opItxnNext(cx *EvalContext) error { return addInnerTxn(cx) } -// availableAccount is used instead of accountReference for more recent opcodes -// that don't need (or want!) to allow low numbers to represent the account at -// that index in Accounts array. -func (cx *EvalContext) availableAccount(sv stackValue) (basics.Address, error) { - if sv.argType() != StackBytes || len(sv.Bytes) != crypto.DigestSize { - return basics.Address{}, fmt.Errorf("not an address") - } - - addr, _, err := cx.accountReference(sv) - return addr, err -} - -// availableAsset is used instead of asaReference for more recent opcodes that -// don't need (or want!) to allow low numbers to represent the asset at that -// index in ForeignAssets array. -func (cx *EvalContext) availableAsset(sv stackValue) (basics.AssetIndex, error) { +// assignAsset is used to convert a stackValue to a uint64 assetIndex, reporting +// any errors due to availability rules or type checking. +func (cx *EvalContext) assignAsset(sv stackValue) (basics.AssetIndex, error) { uint, err := sv.uint() if err != nil { - return basics.AssetIndex(0), err + return 0, err } aid := basics.AssetIndex(uint) + if cx.availableAsset(aid) { + return aid, nil + } + + return 0, fmt.Errorf("invalid Asset reference %d", aid) +} + +// availableAsset determines whether an asset is "available". Before +// sharedResourcesVersion, an asset had to be available for asset param +// lookups, asset holding lookups, and asset id assignments to inner +// transactions. After sharedResourcesVersion, the distinction must be more fine +// grained. It must be available for asset param lookups, or use in an asset +// transaction (axfer,acfg,afrz), but not for holding lookups or assignments to +// an inner static array. +func (cx *EvalContext) availableAsset(aid basics.AssetIndex) bool { // Ensure that aid is in Foreign Assets for _, assetID := range cx.txn.Txn.ForeignAssets { if assetID == aid { - return aid, nil + return true } } // or was created in group if cx.version >= createdResourcesVersion { - for _, assetID := range cx.available.asas { + for _, assetID := range cx.available.createdAsas { if assetID == aid { - return aid, nil + return true } } } - return basics.AssetIndex(0), fmt.Errorf("invalid Asset reference %d", aid) + // or some other txn mentioned it + if cx.version >= sharedResourcesVersion { + if _, ok := cx.available.sharedAsas[aid]; ok { + return true + } + } + + return false } -// availableApp is used instead of appReference for more recent (stateful) -// opcodes that don't need (or want!) to allow low numbers to represent the app -// at that index in ForeignApps array. -func (cx *EvalContext) availableApp(sv stackValue) (basics.AppIndex, error) { +// assignApp is used to convert a stackValue to a uint64 appIndex, reporting +// any errors due to availability rules or type checking. +func (cx *EvalContext) assignApp(sv stackValue) (basics.AppIndex, error) { uint, err := sv.uint() if err != nil { - return basics.AppIndex(0), err + return 0, err } aid := basics.AppIndex(uint) + if cx.availableApp(aid) { + return aid, nil + } + + return 0, fmt.Errorf("invalid App reference %d", aid) +} + +func (cx *EvalContext) availableApp(aid basics.AppIndex) bool { // Ensure that aid is in Foreign Apps for _, appID := range cx.txn.Txn.ForeignApps { if appID == aid { - return aid, nil + return true } } // or was created in group if cx.version >= createdResourcesVersion { - for _, appID := range cx.available.apps { + for _, appID := range cx.available.createdApps { if appID == aid { - return aid, nil + return true } } } // Or, it can be the current app if cx.appID == aid { - return aid, nil + return true } - return 0, fmt.Errorf("invalid App reference %d", aid) + // or some other txn mentioned it + if cx.version >= sharedResourcesVersion { + if _, ok := cx.available.sharedApps[aid]; ok { + return true + } + } + + return false } func (cx *EvalContext) stackIntoTxnField(sv stackValue, fs *txnFieldSpec, txn *transactions.Transaction) (err error) { @@ -4829,7 +4974,7 @@ func (cx *EvalContext) stackIntoTxnField(sv stackValue, fs *txnFieldSpec, txn *t return fmt.Errorf("%d is not a valid TypeEnum", i) } case Sender: - txn.Sender, err = cx.availableAccount(sv) + txn.Sender, err = cx.assignAccount(sv) case Fee: txn.Fee.Raw, err = sv.uint() // FirstValid, LastValid unsettable: little motivation (maybe a app call @@ -4879,25 +5024,25 @@ func (cx *EvalContext) stackIntoTxnField(sv stackValue, fs *txnFieldSpec, txn *t // Payment case Receiver: - txn.Receiver, err = cx.availableAccount(sv) + txn.Receiver, err = cx.assignAccount(sv) case Amount: txn.Amount.Raw, err = sv.uint() case CloseRemainderTo: - txn.CloseRemainderTo, err = cx.availableAccount(sv) + txn.CloseRemainderTo, err = cx.assignAccount(sv) // AssetTransfer case XferAsset: - txn.XferAsset, err = cx.availableAsset(sv) + txn.XferAsset, err = cx.assignAsset(sv) case AssetAmount: txn.AssetAmount, err = sv.uint() case AssetSender: - txn.AssetSender, err = cx.availableAccount(sv) + txn.AssetSender, err = cx.assignAccount(sv) case AssetReceiver: - txn.AssetReceiver, err = cx.availableAccount(sv) + txn.AssetReceiver, err = cx.assignAccount(sv) case AssetCloseTo: - txn.AssetCloseTo, err = cx.availableAccount(sv) + txn.AssetCloseTo, err = cx.assignAccount(sv) // AssetConfig case ConfigAsset: - txn.ConfigAsset, err = cx.availableAsset(sv) + txn.ConfigAsset, err = cx.assignAsset(sv) case ConfigAssetTotal: txn.AssetParams.Total, err = sv.uint() case ConfigAssetDecimals: @@ -4933,15 +5078,15 @@ func (cx *EvalContext) stackIntoTxnField(sv stackValue, fs *txnFieldSpec, txn *t txn.AssetParams.Clawback, err = sv.address() // Freeze case FreezeAsset: - txn.FreezeAsset, err = cx.availableAsset(sv) + txn.FreezeAsset, err = cx.assignAsset(sv) case FreezeAssetAccount: - txn.FreezeAccount, err = cx.availableAccount(sv) + txn.FreezeAccount, err = cx.assignAccount(sv) case FreezeAssetFrozen: txn.AssetFrozen, err = sv.bool() // ApplicationCall case ApplicationID: - txn.ApplicationID, err = cx.availableApp(sv) + txn.ApplicationID, err = cx.assignApp(sv) case OnCompletion: var onc uint64 onc, err = sv.uintMaxed(uint64(transactions.DeleteApplicationOC)) @@ -4965,7 +5110,7 @@ func (cx *EvalContext) stackIntoTxnField(sv stackValue, fs *txnFieldSpec, txn *t txn.ApplicationArgs = append(txn.ApplicationArgs, new) case Accounts: var new basics.Address - new, err = cx.availableAccount(sv) + new, err = cx.assignAccount(sv) if err != nil { return err } @@ -5001,7 +5146,7 @@ func (cx *EvalContext) stackIntoTxnField(sv stackValue, fs *txnFieldSpec, txn *t } case Assets: var new basics.AssetIndex - new, err = cx.availableAsset(sv) + new, err = cx.assignAsset(sv) if err != nil { return err } @@ -5011,7 +5156,7 @@ func (cx *EvalContext) stackIntoTxnField(sv stackValue, fs *txnFieldSpec, txn *t txn.ForeignAssets = append(txn.ForeignAssets, new) case Applications: var new basics.AppIndex - new, err = cx.availableApp(sv) + new, err = cx.assignApp(sv) if err != nil { return err } @@ -5120,6 +5265,8 @@ func opItxnSubmit(cx *EvalContext) (err error) { return err } + var calledVersion uint64 + // Disallow reentrancy, limit inner app call depth, and do version checks if cx.subtxns[itx].Txn.Type == protocol.ApplicationCallTx { if cx.appID == cx.subtxns[itx].Txn.ApplicationID { @@ -5150,13 +5297,13 @@ func opItxnSubmit(cx *EvalContext) (err error) { } // Can't call old versions in inner apps. - v, _, err := transactions.ProgramVersion(program) + calledVersion, _, err = transactions.ProgramVersion(program) if err != nil { return err } - if v < cx.Proto.MinInnerApplVersion { + if calledVersion < cx.Proto.MinInnerApplVersion { return fmt.Errorf("inner app call with version v%d < v%d", - v, cx.Proto.MinInnerApplVersion) + calledVersion, cx.Proto.MinInnerApplVersion) } // Don't allow opt-in if the CSP is not runnable as an inner. @@ -5180,7 +5327,18 @@ func opItxnSubmit(cx *EvalContext) (err error) { csv, cx.Proto.MinInnerApplVersion) } } + } + // Starting in v9, it's possible for apps to create transactions that + // should not be allowed to run, because they require access to + // resources that the caller does not have. This can only happen for + // Holdings and Local States. The caller might have access to the + // account and the asa or app, but not the holding or locals, because + // the caller gained access to the two top resources by group sharing + // from two different transactions. + err = cx.allows(&cx.subtxns[itx].Txn, calledVersion) + if err != nil { + return err } if isGroup { diff --git a/data/transactions/logic/evalAppTxn_test.go b/data/transactions/logic/evalAppTxn_test.go index 2886b57a52..84040dddf1 100644 --- a/data/transactions/logic/evalAppTxn_test.go +++ b/data/transactions/logic/evalAppTxn_test.go @@ -25,6 +25,7 @@ import ( "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/data/transactions/logic" . "github.com/algorand/go-algorand/data/transactions/logic" "github.com/algorand/go-algorand/data/txntest" "github.com/algorand/go-algorand/protocol" @@ -109,7 +110,7 @@ func TestFieldTypes(t *testing.T) { TestApp(t, "itxn_begin; byte \"\"; itxn_field CloseRemainderTo;", ep, "not an address") TestApp(t, "itxn_begin; byte \"\"; itxn_field AssetSender;", ep, "not an address") // can't really tell if it's an addres, so 32 bytes gets further - TestApp(t, "itxn_begin; byte \"01234567890123456789012345678901\"; itxn_field AssetReceiver;", + TestApp(t, "itxn_begin; byte \"01234567890123456789012345678901\"; itxn_field AssetReceiver; int 1", ep, "invalid Account reference") // but a b32 string rep is not an account TestApp(t, "itxn_begin; byte \"GAYTEMZUGU3DOOBZGAYTEMZUGU3DOOBZGAYTEMZUGU3DOOBZGAYZIZD42E\"; itxn_field AssetCloseTo;", @@ -145,49 +146,52 @@ func TestAppPay(t *testing.T) { int 1 ` - ep, tx, ledger := MakeSampleEnv() - ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) - TestApp(t, "txn Sender; balance; int 0; ==;", ep) - TestApp(t, "txn Sender; txn Accounts 1; int 100"+pay, ep, "unauthorized") - TestApp(t, "global CurrentApplicationAddress; txn Accounts 1; int 100"+pay, ep, - "insufficient balance") - ledger.NewAccount(appAddr(888), 1000000) - - // You might NewExpect this to fail because of min balance issue - // (receiving account only gets 100 microalgos). It does not fail at - // this level, instead, we must be certain that the existing min - // balance check in eval.transaction() properly notices and fails - // the transaction later. This fits with the model that we check - // min balances once at the end of each "top-level" transaction. - TestApp(t, "global CurrentApplicationAddress; txn Accounts 1; int 100"+pay, ep) - - // 100 of 1000000 spent, plus MinTxnFee in our fake protocol is 1001 - TestApp(t, "global CurrentApplicationAddress; balance; int 998899; ==", ep) - TestApp(t, "txn Receiver; balance; int 100; ==", ep) - - close := ` + // v5 added inners + logic.TestLogicRange(t, 5, 0, func(t *testing.T, ep *logic.EvalParams, tx *transactions.Transaction, ledger *logic.Ledger) { + ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) + TestApp(t, "txn Sender; balance; int 0; ==;", ep) + TestApp(t, "txn Sender; txn Accounts 1; int 100"+pay, ep, "unauthorized") + TestApp(t, "global CurrentApplicationAddress; txn Accounts 1; int 100"+pay, ep, + "insufficient balance") + ledger.NewAccount(appAddr(888), 1000000) + + // You might NewExpect this to fail because of min balance issue + // (receiving account only gets 100 microalgos). It does not fail at + // this level, instead, we must be certain that the existing min + // balance check in eval.transaction() properly notices and fails + // the transaction later. This fits with the model that we check + // min balances once at the end of each "top-level" transaction. + TestApp(t, "global CurrentApplicationAddress; txn Accounts 1; int 100"+pay, ep) + + // 100 of 1000000 spent, plus MinTxnFee in our fake protocol is 1001 + TestApp(t, "global CurrentApplicationAddress; balance; int 998899; ==", ep) + TestApp(t, "txn Receiver; balance; int 100; ==", ep) + + close := ` itxn_begin int pay; itxn_field TypeEnum txn Receiver; itxn_field CloseRemainderTo itxn_submit int 1 ` - TestApp(t, close, ep) - TestApp(t, "global CurrentApplicationAddress; balance; !", ep) - // Receiver got most of the algos (except 1001 for fee) - TestApp(t, "txn Receiver; balance; int 997998; ==", ep) + TestApp(t, close, ep) + TestApp(t, "global CurrentApplicationAddress; balance; !", ep) + // Receiver got most of the algos (except 1001 for fee) + TestApp(t, "txn Receiver; balance; int 997998; ==", ep) + }) } func TestAppAssetOptIn(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - ep, tx, ledger := MakeSampleEnv() - // Establish 888 as the app id, and fund it. - ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) - ledger.NewAccount(basics.AppIndex(888).Address(), 200000) + // v5 added inners + logic.TestLogicRange(t, 5, 0, func(t *testing.T, ep *logic.EvalParams, tx *transactions.Transaction, ledger *logic.Ledger) { + // Establish 888 as the app id, and fund it. + ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) + ledger.NewAccount(basics.AppIndex(888).Address(), 200000) - axfer := ` + axfer := ` itxn_begin int axfer; itxn_field TypeEnum; int 25; itxn_field XferAsset; @@ -196,10 +200,10 @@ txn Sender; itxn_field AssetReceiver; itxn_submit int 1 ` - TestApp(t, axfer, ep, "invalid Asset reference") - tx.ForeignAssets = append(tx.ForeignAssets, 25) - TestApp(t, axfer, ep, "not opted in") // app account not opted in - optin := ` + TestApp(t, axfer, ep, "invalid Asset reference") + tx.ForeignAssets = append(tx.ForeignAssets, 25) + TestApp(t, axfer, ep, "not opted in") // app account not opted in + optin := ` itxn_begin int axfer; itxn_field TypeEnum; int 25; itxn_field XferAsset; @@ -208,25 +212,25 @@ global CurrentApplicationAddress; itxn_field AssetReceiver; itxn_submit int 1 ` - TestApp(t, optin, ep, "does not exist") - // Asset 25 - ledger.NewAsset(tx.Sender, 25, basics.AssetParams{ - Total: 10, - UnitName: "x", - AssetName: "Cross", - }) - TestApp(t, optin, ep) + TestApp(t, optin, ep, "does not exist") + // Asset 25 + ledger.NewAsset(tx.Sender, 25, basics.AssetParams{ + Total: 10, + UnitName: "x", + AssetName: "Cross", + }) + TestApp(t, optin, ep) - TestApp(t, axfer, ep, "insufficient balance") // opted in, but balance=0 + TestApp(t, axfer, ep, "insufficient balance") // opted in, but balance=0 - // Fund the app account with the asset - ledger.NewHolding(basics.AppIndex(888).Address(), 25, 5, false) - TestApp(t, axfer, ep) - TestApp(t, axfer, ep) - TestApp(t, axfer, ep, "insufficient balance") // balance = 1, tried to move 2) - TestApp(t, "global CurrentApplicationAddress; int 25; asset_holding_get AssetBalance; assert; int 1; ==", ep) + // Fund the app account with the asset + ledger.NewHolding(basics.AppIndex(888).Address(), 25, 5, false) + TestApp(t, axfer, ep) + TestApp(t, axfer, ep) + TestApp(t, axfer, ep, "insufficient balance") // balance = 1, tried to move 2) + TestApp(t, "global CurrentApplicationAddress; int 25; asset_holding_get AssetBalance; assert; int 1; ==", ep) - close := ` + close := ` itxn_begin int axfer; itxn_field TypeEnum; int 25; itxn_field XferAsset; @@ -236,8 +240,9 @@ txn Sender; itxn_field AssetCloseTo; itxn_submit int 1 ` - TestApp(t, close, ep) - TestApp(t, "global CurrentApplicationAddress; int 25; asset_holding_get AssetBalance; !; assert; !", ep) + TestApp(t, close, ep) + TestApp(t, "global CurrentApplicationAddress; int 25; asset_holding_get AssetBalance; !; assert; !", ep) + }) } func TestRekeyPay(t *testing.T) { @@ -254,16 +259,18 @@ func TestRekeyPay(t *testing.T) { itxn_submit ` - ep, tx, ledger := MakeSampleEnv() - ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) - TestApp(t, "txn Sender; balance; int 0; ==;", ep) - TestApp(t, "txn Sender; txn Accounts 1; int 100"+pay, ep, "unauthorized") - ledger.NewAccount(tx.Sender, 120+ep.Proto.MinTxnFee) - ledger.Rekey(tx.Sender, basics.AppIndex(888).Address()) - TestApp(t, "txn Sender; txn Accounts 1; int 100"+pay+"; int 1", ep) - // Note that the Sender would fail min balance check if we did it here. - // It seems proper to wait until end of txn though. - // See explanation in cowRoundState's Perform() + // v5 added inners + logic.TestLogicRange(t, 5, 0, func(t *testing.T, ep *logic.EvalParams, tx *transactions.Transaction, ledger *logic.Ledger) { + ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) + TestApp(t, "txn Sender; balance; int 0; ==;", ep) + TestApp(t, "txn Sender; txn Accounts 1; int 100"+pay, ep, "unauthorized") + ledger.NewAccount(tx.Sender, 120+ep.Proto.MinTxnFee) + ledger.Rekey(tx.Sender, basics.AppIndex(888).Address()) + TestApp(t, "txn Sender; txn Accounts 1; int 100"+pay+"; int 1", ep) + // Note that the Sender would fail min balance check if we did it here. + // It seems proper to wait until end of txn though. + // See explanation in cowRoundState's Perform() + }) } func TestRekeyBack(t *testing.T) { @@ -282,15 +289,17 @@ func TestRekeyBack(t *testing.T) { itxn_submit ` - ep, tx, ledger := MakeSampleEnv() - ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) - TestApp(t, "txn Sender; balance; int 0; ==;", ep) - TestApp(t, "txn Sender; txn Accounts 1; int 100"+payAndUnkey, ep, "unauthorized") - ledger.NewAccount(tx.Sender, 120+3*ep.Proto.MinTxnFee) - ledger.Rekey(tx.Sender, basics.AppIndex(888).Address()) - TestApp(t, "txn Sender; txn Accounts 1; int 100"+payAndUnkey+"; int 1", ep) - // now rekeyed back to original - TestApp(t, "txn Sender; txn Accounts 1; int 100"+payAndUnkey, ep, "unauthorized") + // v6 added inner rekey + logic.TestLogicRange(t, 6, 0, func(t *testing.T, ep *logic.EvalParams, tx *transactions.Transaction, ledger *logic.Ledger) { + ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) + TestApp(t, "txn Sender; balance; int 0; ==;", ep) + TestApp(t, "txn Sender; txn Accounts 1; int 100"+payAndUnkey, ep, "unauthorized") + ledger.NewAccount(tx.Sender, 120+3*ep.Proto.MinTxnFee) + ledger.Rekey(tx.Sender, basics.AppIndex(888).Address()) + TestApp(t, "txn Sender; txn Accounts 1; int 100"+payAndUnkey+"; int 1", ep) + // now rekeyed back to original + TestApp(t, "txn Sender; txn Accounts 1; int 100"+payAndUnkey, ep, "unauthorized") + }) } func TestDefaultSender(t *testing.T) { @@ -306,13 +315,15 @@ func TestDefaultSender(t *testing.T) { itxn_submit ` - ep, tx, ledger := MakeSampleEnv() - ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) - tx.Accounts = append(tx.Accounts, appAddr(888)) - TestApp(t, "txn Accounts 1; int 100"+pay, ep, "insufficient balance") - ledger.NewAccount(appAddr(888), 1000000) - TestApp(t, "txn Accounts 1; int 100"+pay+"int 1", ep) - TestApp(t, "global CurrentApplicationAddress; balance; int 998899; ==", ep) + // v5 added inners + logic.TestLogicRange(t, 5, 0, func(t *testing.T, ep *logic.EvalParams, tx *transactions.Transaction, ledger *logic.Ledger) { + ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) + tx.Accounts = append(tx.Accounts, appAddr(888)) + TestApp(t, "txn Accounts 1; int 100"+pay, ep, "insufficient balance") + ledger.NewAccount(appAddr(888), 1000000) + TestApp(t, "txn Accounts 1; int 100"+pay+"int 1", ep) + TestApp(t, "global CurrentApplicationAddress; balance; int 998899; ==", ep) + }) } func TestAppAxfer(t *testing.T) { @@ -331,36 +342,37 @@ func TestAppAxfer(t *testing.T) { itxn_submit ` - ep, tx, ledger := MakeSampleEnv() - ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) - ledger.NewAsset(tx.Receiver, 777, basics.AssetParams{}) // not in foreign-assets of sample - ledger.NewAsset(tx.Receiver, 77, basics.AssetParams{}) // in foreign-assets of sample - TestApp(t, "txn Sender; int 777; asset_holding_get AssetBalance; assert; int 0; ==;", ep, - "invalid Asset reference") // 777 not in foreign-assets - TestApp(t, "txn Sender; int 77; asset_holding_get AssetBalance; assert; int 0; ==;", ep, - "assert failed") // because Sender not opted-in - TestApp(t, "global CurrentApplicationAddress; int 77; asset_holding_get AssetBalance; assert; int 0; ==;", ep, - "assert failed") // app account not opted in - - ledger.NewAccount(appAddr(888), 10000) // plenty for fees - ledger.NewHolding(appAddr(888), 77, 3000, false) - TestApp(t, "global CurrentApplicationAddress; int 77; asset_holding_get AssetBalance; assert; int 3000; ==;", ep) - - TestApp(t, "txn Sender; txn Accounts 1; int 100"+axfer, ep, "unauthorized") - TestApp(t, "global CurrentApplicationAddress; txn Accounts 0; int 100"+axfer, ep, - fmt.Sprintf("Receiver (%s) not opted in", tx.Sender)) // txn.Sender (receiver of the axfer) isn't opted in - TestApp(t, "global CurrentApplicationAddress; txn Accounts 1; int 100000"+axfer, ep, - "insufficient balance") - - // Temporarily remove from ForeignAssets to ensure App Account - // doesn't get some sort of free pass to send arbitrary assets. - save := tx.ForeignAssets - tx.ForeignAssets = []basics.AssetIndex{6, 10} - TestApp(t, "global CurrentApplicationAddress; txn Accounts 1; int 100000"+axfer, ep, - "invalid Asset reference 77") - tx.ForeignAssets = save - - noid := ` + // v5 added inners + logic.TestLogicRange(t, 5, 0, func(t *testing.T, ep *logic.EvalParams, tx *transactions.Transaction, ledger *logic.Ledger) { + ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) + ledger.NewAsset(tx.Receiver, 777, basics.AssetParams{}) // not in foreign-assets of sample + ledger.NewAsset(tx.Receiver, 77, basics.AssetParams{}) // in foreign-assets of sample + TestApp(t, "txn Sender; int 777; asset_holding_get AssetBalance; assert; int 0; ==;", ep, + "invalid Asset reference") // 777 not in foreign-assets + TestApp(t, "txn Sender; int 77; asset_holding_get AssetBalance; assert; int 0; ==;", ep, + "assert failed") // because Sender not opted-in + TestApp(t, "global CurrentApplicationAddress; int 77; asset_holding_get AssetBalance; assert; int 0; ==;", ep, + "assert failed") // app account not opted in + + ledger.NewAccount(appAddr(888), 10000) // plenty for fees + ledger.NewHolding(appAddr(888), 77, 3000, false) + TestApp(t, "global CurrentApplicationAddress; int 77; asset_holding_get AssetBalance; assert; int 3000; ==;", ep) + + TestApp(t, "txn Sender; txn Accounts 1; int 100"+axfer, ep, "unauthorized") + TestApp(t, "global CurrentApplicationAddress; txn Accounts 0; int 100"+axfer, ep, + fmt.Sprintf("Receiver (%s) not opted in", tx.Sender)) // txn.Sender (receiver of the axfer) isn't opted in + TestApp(t, "global CurrentApplicationAddress; txn Accounts 1; int 100000"+axfer, ep, + "insufficient balance") + + // Temporarily remove from ForeignAssets to ensure App Account + // doesn't get some sort of free pass to send arbitrary assets. + save := tx.ForeignAssets + tx.ForeignAssets = []basics.AssetIndex{6, 10} + TestApp(t, "global CurrentApplicationAddress; txn Accounts 1; int 100000"+axfer, ep, + "invalid Asset reference 77") + tx.ForeignAssets = save + + noid := ` itxn_begin itxn_field AssetAmount itxn_field AssetReceiver @@ -369,14 +381,46 @@ func TestAppAxfer(t *testing.T) { itxn_field TypeEnum itxn_submit ` - TestApp(t, "global CurrentApplicationAddress; txn Accounts 1; int 100"+noid+"int 1", ep, - fmt.Sprintf("Sender (%s) not opted in to 0", appAddr(888))) - TestApp(t, "global CurrentApplicationAddress; txn Accounts 1; int 100"+axfer+"int 1", ep) + // Here, the XferAsset is never set, so it is defaulted to 0. Therefore + // v8 and below had no opportunity to complain about the inavailability + // of the implied holding. Of course, there is no 0 asset, so the axfer + // is going to fail anyway, but to keep the behavior consistent, v9 + // allows the zero asset (and zero account) in `requireHolding`. + TestApp(t, "global CurrentApplicationAddress; txn Accounts 1; int 100"+noid+"int 1", ep, + fmt.Sprintf("Sender (%s) not opted in to 0", appAddr(888))) + + TestApp(t, "global CurrentApplicationAddress; txn Accounts 1; int 100"+axfer+"int 1", ep) - // 100 of 3000 spent - TestApp(t, "global CurrentApplicationAddress; int 77; asset_holding_get AssetBalance; assert; int 2900; ==", ep) - TestApp(t, "txn Accounts 1; int 77; asset_holding_get AssetBalance; assert; int 100; ==", ep) + // 100 of 3000 spent + TestApp(t, "global CurrentApplicationAddress; int 77; asset_holding_get AssetBalance; assert; int 2900; ==", ep) + TestApp(t, "txn Accounts 1; int 77; asset_holding_get AssetBalance; assert; int 100; ==", ep) + }) +} + +func TestInnerAppl(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + appl := ` + itxn_begin + int appl; itxn_field TypeEnum + int 56 // present in ForeignApps of sample txn + itxn_field ApplicationID + itxn_submit + int 1 +` + + // v6 added inner appls + TestLogicRange(t, 6, 0, func(t *testing.T, ep *logic.EvalParams, tx *transactions.Transaction, ledger *logic.Ledger) { + // Establish 888 as the app id, and fund it. + ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) + ledger.NewAccount(basics.AppIndex(888).Address(), 200000) + + ops := TestProg(t, "int 1", 5) + ledger.NewApp(basics.Address{0x01}, 56, basics.AppParams{ApprovalProgram: ops.Program}) + TestApp(t, appl, ep) + }) } func TestExtraFields(t *testing.T) { @@ -556,12 +600,14 @@ func TestAssetCreate(t *testing.T) { itxn_submit int 1 ` - ep, tx, ledger := MakeSampleEnv() - ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) - TestApp(t, create, ep, "insufficient balance") - // Give it enough for fee. Recall that we don't check min balance at this level. - ledger.NewAccount(appAddr(888), MakeTestProto().MinTxnFee) - TestApp(t, create, ep) + // v5 added inners + logic.TestLogicRange(t, 5, 0, func(t *testing.T, ep *logic.EvalParams, tx *transactions.Transaction, ledger *logic.Ledger) { + ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) + TestApp(t, create, ep, "insufficient balance") + // Give it enough for fee. Recall that we don't check min balance at this level. + ledger.NewAccount(appAddr(888), MakeTestProto().MinTxnFee) + TestApp(t, create, ep) + }) } func TestAssetFreeze(t *testing.T) { @@ -582,13 +628,14 @@ func TestAssetFreeze(t *testing.T) { int 5000 == ` - ep, tx, ledger := MakeSampleEnv() - ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) - // Give it enough for fees. Recall that we don't check min balance at this level. - ledger.NewAccount(appAddr(888), 12*MakeTestProto().MinTxnFee) - TestApp(t, create, ep) + // v5 added inners + logic.TestLogicRange(t, 5, 0, func(t *testing.T, ep *logic.EvalParams, tx *transactions.Transaction, ledger *logic.Ledger) { + ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) + // Give it enough for fees. Recall that we don't check min balance at this level. + ledger.NewAccount(appAddr(888), 12*MakeTestProto().MinTxnFee) + TestApp(t, create, ep) - freeze := ` + freeze := ` itxn_begin int afrz ; itxn_field TypeEnum int 5000 ; itxn_field FreezeAsset @@ -597,20 +644,21 @@ func TestAssetFreeze(t *testing.T) { itxn_submit int 1 ` - TestApp(t, freeze, ep, "invalid Asset reference") - tx.ForeignAssets = []basics.AssetIndex{basics.AssetIndex(5000)} - tx.ApplicationArgs = [][]byte{{0x01}} - TestApp(t, freeze, ep, "does not hold Asset") - ledger.NewHolding(tx.Receiver, 5000, 55, false) - TestApp(t, freeze, ep) - holding, err := ledger.AssetHolding(tx.Receiver, 5000) - require.NoError(t, err) - require.Equal(t, true, holding.Frozen) - tx.ApplicationArgs = [][]byte{{0x00}} - TestApp(t, freeze, ep) - holding, err = ledger.AssetHolding(tx.Receiver, 5000) - require.NoError(t, err) - require.Equal(t, false, holding.Frozen) + TestApp(t, freeze, ep, "invalid Asset reference") + tx.ForeignAssets = []basics.AssetIndex{basics.AssetIndex(5000)} + tx.ApplicationArgs = [][]byte{{0x01}} + TestApp(t, freeze, ep, "does not hold Asset") + ledger.NewHolding(tx.Receiver, 5000, 55, false) + TestApp(t, freeze, ep) + holding, err := ledger.AssetHolding(tx.Receiver, 5000) + require.NoError(t, err) + require.Equal(t, true, holding.Frozen) + tx.ApplicationArgs = [][]byte{{0x00}} + TestApp(t, freeze, ep) + holding, err = ledger.AssetHolding(tx.Receiver, 5000) + require.NoError(t, err) + require.Equal(t, false, holding.Frozen) + }) } func TestKeyReg(t *testing.T) { @@ -1153,7 +1201,7 @@ int 5000; app_params_get AppGlobalNumByteSlice; !; assert; !; assert; int 1 `, ep) // Can't call it either - TestApp(t, call, ep, "no such app 5000") + TestApp(t, call, ep, "no app 5000") } diff --git a/data/transactions/logic/evalStateful_test.go b/data/transactions/logic/evalStateful_test.go index f6ab14a078..ae573724bf 100644 --- a/data/transactions/logic/evalStateful_test.go +++ b/data/transactions/logic/evalStateful_test.go @@ -70,6 +70,50 @@ func makeOldAndNewEnv(version uint64) (*EvalParams, *EvalParams, *Ledger) { return old, new, sharedLedger } +func (r *resources) String() string { + sb := strings.Builder{} + if len(r.createdAsas) > 0 { + fmt.Fprintf(&sb, "createdAsas: %v\n", r.createdAsas) + } + if len(r.createdApps) > 0 { + fmt.Fprintf(&sb, "createdApps: %v\n", r.createdApps) + } + + if len(r.sharedAccounts) > 0 { + fmt.Fprintf(&sb, "sharedAccts:\n") + for addr := range r.sharedAccounts { + fmt.Fprintf(&sb, " %s\n", addr) + } + } + if len(r.sharedAsas) > 0 { + fmt.Fprintf(&sb, "sharedAsas:\n") + for id := range r.sharedAsas { + fmt.Fprintf(&sb, " %d\n", id) + } + } + if len(r.sharedApps) > 0 { + fmt.Fprintf(&sb, "sharedApps:\n") + for id := range r.sharedApps { + fmt.Fprintf(&sb, " %d\n", id) + } + } + + if len(r.sharedHoldings) > 0 { + fmt.Fprintf(&sb, "sharedHoldings:\n") + for hl := range r.sharedHoldings { + fmt.Fprintf(&sb, " %s x %d\n", hl.Address, hl.Asset) + } + } + if len(r.sharedLocals) > 0 { + fmt.Fprintf(&sb, "sharedLocals:\n") + for hl := range r.sharedLocals { + fmt.Fprintf(&sb, " %s x %d\n", hl.Address, hl.App) + } + } + + return sb.String() +} + func TestEvalModes(t *testing.T) { partitiontest.PartitionTest(t) @@ -79,7 +123,7 @@ func TestEvalModes(t *testing.T) { // check modeAny (v1 + txna/gtxna) are available in RunModeSignature // check all opcodes available in runModeApplication opcodesRunModeAny := `intcblock 0 1 1 1 1 5 100 - bytecblock 0x414c474f 0x1337 0x2001 0xdeadbeef 0x70077007 + bytecblock "ALGO" 0x1337 0x2001 0xdeadbeef 0x70077007 bytec 0 sha256 keccak256 @@ -151,24 +195,24 @@ arg 4 && ` - opcodesRunModeApplication := `int 0 + opcodesRunModeApplication := `txn Sender balance && -int 0 +txn Sender min_balance && -intc_0 +txn Sender intc 6 // 100 app_opted_in && -intc_0 +txn Sender bytec_0 // ALGO intc_1 app_local_put bytec_0 intc_1 app_global_put -intc_0 +txn Sender intc 6 bytec_0 app_local_get_ex @@ -179,17 +223,17 @@ bytec_0 app_global_get_ex pop && -intc_0 +txn Sender bytec_0 app_local_del bytec_0 app_global_del -intc_0 +txn Sender intc 5 // 5 asset_holding_get AssetBalance pop && -intc_0 +intc 5 // 5 asset_params_get AssetTotal pop && @@ -281,20 +325,20 @@ log // check stateful opcodes are not allowed in stateless mode statefulOpcodeCalls := []string{ - "int 0\nbalance", - "int 0\nmin_balance", - "int 0\nint 0\napp_opted_in", - "int 0\nint 0\nbyte 0x01\napp_local_get_ex", - "byte 0x01\napp_global_get", - "int 0\nbyte 0x01\napp_global_get_ex", - "int 1\nbyte 0x01\nbyte 0x01\napp_local_put", - "byte 0x01\nint 0\napp_global_put", - "int 0\nbyte 0x01\napp_local_del", - "byte 0x01\napp_global_del", - "int 0\nint 0\nasset_holding_get AssetFrozen", - "int 0\nint 0\nasset_params_get AssetManager", - "int 0\nint 0\napp_params_get AppApprovalProgram", - "byte 0x01\nlog", + "txn Sender; balance", + "txn Sender; min_balance", + "txn Sender; int 0; app_opted_in", + "txn Sender; int 0; byte 0x01; app_local_get_ex", + "byte 0x01; app_global_get", + "int 0; byte 0x01; app_global_get_ex", + "txn Sender; byte 0x01; byte 0x01; app_local_put", + "byte 0x01; int 0; app_global_put", + "txn Sender; byte 0x01; app_local_del", + "byte 0x01; app_global_del", + "txn Sender; int 0; asset_holding_get AssetFrozen", + "int 0; int 0; asset_params_get AssetManager", + "int 0; int 0; app_params_get AppApprovalProgram", + "byte 0x01; log", } for _, source := range statefulOpcodeCalls { @@ -311,35 +355,36 @@ log func TestBalance(t *testing.T) { partitiontest.PartitionTest(t) - t.Parallel() - ep, tx, ledger := makeSampleEnv() - text := "int 2; balance; int 177; ==" - ledger.NewAccount(tx.Receiver, 177) - testApp(t, text, ep, "invalid Account reference") - - text = `int 1; balance; int 177; ==` - testApp(t, text, ep) - - text = `txn Accounts 1; balance; int 177; ==;` - // won't assemble in old version teal - testProg(t, text, directRefEnabledVersion-1, Expect{1, "balance arg 0 wanted type uint64..."}) - // but legal after that - testApp(t, text, ep) - - text = "int 0; balance; int 13; ==; assert; int 1" - var addr basics.Address - copy(addr[:], []byte("aoeuiaoeuiaoeuiaoeuiaoeuiaoeui02")) - ledger.NewAccount(addr, 13) - testApp(t, text, ep, "assert failed") - - ledger.NewAccount(tx.Sender, 13) - testApp(t, text, ep) + testLogicRange(t, 2, 0, func(t *testing.T, ep *EvalParams, tx *transactions.Transaction, ledger *Ledger) { + ledger.NewAccount(tx.Receiver, 177) + if ep.Proto.LogicSigVersion < sharedResourcesVersion { + testApp(t, "int 2; balance; int 177; ==", ep, "invalid Account reference") + testApp(t, `int 1; balance; int 177; ==`, ep) + } + + source := `txn Accounts 1; balance; int 177; ==;` + // won't assemble in old version teal + if ep.Proto.LogicSigVersion < directRefEnabledVersion { + testProg(t, source, ep.Proto.LogicSigVersion, + Expect{1, "balance arg 0 wanted type uint64..."}) + return + } + + // but legal after that + testApp(t, source, ep) + + source = "txn Sender; balance; int 13; ==; assert; int 1" + testApp(t, source, ep, "assert failed") + + ledger.NewAccount(tx.Sender, 13) + testApp(t, source, ep) + }) } func testApps(t *testing.T, programs []string, txgroup []transactions.SignedTxn, version uint64, ledger *Ledger, - expected ...Expect) { + expected ...Expect) *EvalParams { t.Helper() codes := make([][]byte, len(programs)) for i, program := range programs { @@ -364,22 +409,27 @@ func testApps(t *testing.T, programs []string, txgroup []transactions.SignedTxn, ep.Ledger = ledger ep.SigLedger = ledger testAppsBytes(t, codes, ep, expected...) + return ep } func testAppsBytes(t *testing.T, programs [][]byte, ep *EvalParams, expected ...Expect) { t.Helper() - require.Equal(t, len(programs), len(ep.TxnGroup)) + require.LessOrEqual(t, len(programs), len(ep.TxnGroup)) for i := range ep.TxnGroup { - if programs[i] != nil { + program := ep.TxnGroup[i].Txn.ApprovalProgram + if len(programs) > i && programs[i] != nil { + program = programs[i] + } + if program != nil { appID := ep.TxnGroup[i].Txn.ApplicationID if appID == 0 { appID = basics.AppIndex(888) } if len(expected) > 0 && expected[0].l == i { - testAppFull(t, programs[i], i, appID, ep, expected[0].s) + testAppFull(t, program, i, appID, ep, expected[0].s) break // Stop after first failure } else { - testAppFull(t, programs[i], i, appID, ep) + testAppFull(t, program, i, appID, ep) } } } @@ -461,45 +511,71 @@ func testAppFull(t *testing.T, program []byte, gi int, aid basics.AppIndex, ep * return delta } +// testLogicRange allows for running tests against a range of avm +// versions. Generally `start` will be the version that introduced the feature, +// and `stop` will be 0 to indicate it should work right on up through the +// current version. `stop` will be an actual version number if we're confirming +// that something STOPS working as of a particular version. Note that this does +// *not* use different consensus versions. It is tempting to make it find the +// lowest possible consensus version in the loop in order to support the `v` it +// it working on. For super confidence, one might argue this should be a nested +// loop over all of the consensus versions that work with the `v`, from the +// first possible, to vFuture. +func testLogicRange(t *testing.T, start, stop int, test func(t *testing.T, ep *EvalParams, tx *transactions.Transaction, ledger *Ledger)) { + t.Helper() + if stop == 0 { // Treat 0 as current max + stop = LogicVersion + } + + for v := uint64(start); v <= uint64(stop); v++ { + t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) { + ep, tx, ledger := makeSampleEnvWithVersion(v) + test(t, ep, tx, ledger) + }) + } +} + func TestMinBalance(t *testing.T) { partitiontest.PartitionTest(t) - t.Parallel() - ep, tx, ledger := makeSampleEnv() - - ledger.NewAccount(tx.Sender, 234) - ledger.NewAccount(tx.Receiver, 123) + // since v3 is before directRefEnabledVersion, do a quick test on it separately + ep, tx, ledger := makeSampleEnvWithVersion(3) + ledger.NewAccount(tx.Sender, 100) testApp(t, "int 0; min_balance; int 1001; ==", ep) // Sender makes an asset, min balance goes up ledger.NewAsset(tx.Sender, 7, basics.AssetParams{Total: 1000}) testApp(t, "int 0; min_balance; int 2002; ==", ep) - schemas := makeApp(1, 2, 3, 4) - ledger.NewApp(tx.Sender, 77, schemas) - ledger.NewLocals(tx.Sender, 77) - // create + optin + 10 schema base + 4 ints + 6 bytes (local - // and global count b/c NewLocals opts the creator in) - minb := 1002 + 1006 + 10*1003 + 4*1004 + 6*1005 - testApp(t, fmt.Sprintf("int 0; min_balance; int %d; ==", 2002+minb), ep) - // request extra program pages, min balance increase - withepp := makeApp(1, 2, 3, 4) - withepp.ExtraProgramPages = 2 - ledger.NewApp(tx.Sender, 77, withepp) - minb += 2 * 1002 - testApp(t, fmt.Sprintf("int 0; min_balance; int %d; ==", 2002+minb), ep) - - testApp(t, "int 1; min_balance; int 1001; ==", ep) // 1 == Accounts[0] - testProg(t, "txn Accounts 1; min_balance; int 1001; ==", directRefEnabledVersion-1, - Expect{1, "min_balance arg 0 wanted type uint64..."}) - testProg(t, "txn Accounts 1; min_balance; int 1001; ==", directRefEnabledVersion) - testApp(t, "txn Accounts 1; min_balance; int 1001; ==", ep) // 1 == Accounts[0] - // Receiver opts in - ledger.NewHolding(tx.Receiver, 7, 1, true) - testApp(t, "int 1; min_balance; int 2002; ==", ep) // 1 == Accounts[0] - - testApp(t, "int 2; min_balance; int 1001; ==", ep, "invalid Account reference 2") + // now test in more detail v4 and on + testLogicRange(t, 4, 0, func(t *testing.T, ep *EvalParams, tx *transactions.Transaction, ledger *Ledger) { + ledger.NewAccount(tx.Sender, 234) + ledger.NewAccount(tx.Receiver, 123) + + testApp(t, "txn Sender; min_balance; int 1001; ==", ep) + // Sender makes an asset, min balance goes up + ledger.NewAsset(tx.Sender, 7, basics.AssetParams{Total: 1000}) + testApp(t, "txn Sender; min_balance; int 2002; ==", ep) + schemas := makeApp(1, 2, 3, 4) + ledger.NewApp(tx.Sender, 77, schemas) + ledger.NewLocals(tx.Sender, 77) + // create + optin + 10 schema base + 4 ints + 6 bytes (local + // and global count b/c NewLocals opts the creator in) + minb := 1002 + 1006 + 10*1003 + 4*1004 + 6*1005 + testApp(t, fmt.Sprintf("txn Sender; min_balance; int %d; ==", 2002+minb), ep) + // request extra program pages, min balance increase + withepp := makeApp(1, 2, 3, 4) + withepp.ExtraProgramPages = 2 + ledger.NewApp(tx.Sender, 77, withepp) + minb += 2 * 1002 + testApp(t, fmt.Sprintf("txn Sender; min_balance; int %d; ==", 2002+minb), ep) + + testApp(t, "txn Accounts 1; min_balance; int 1001; ==", ep) + // Receiver opts in + ledger.NewHolding(tx.Receiver, 7, 1, true) + testApp(t, "txn Receiver; min_balance; int 2002; ==", ep) // 1 == Accounts[0] + }) } func TestAppCheckOptedIn(t *testing.T) { @@ -601,7 +677,7 @@ app_local_get_ex bnz exist err exist: -byte 0x414c474f +byte "ALGO" ==` ledger.NewLocal(now.TxnGroup[0].Txn.Receiver, 100, string(protocol.PaymentTx), basics.TealValue{Type: basics.TealBytesType, Bytes: "ALGO"}) @@ -618,7 +694,7 @@ byte 0x414c474f testApp(t, strings.Replace(text, "int 100 // app id", "int 2", -1), pre, "is not opted into") testApp(t, strings.Replace(text, "int 100 // app id", "int 9", -1), now, "invalid App reference 9") testApp(t, strings.Replace(text, "int 1 // account idx", "byte \"aoeuiaoeuiaoeuiaoeuiaoeuiaoeui00\"", -1), now, - "no such address") + "no account") // opt into 123, and try again ledger.NewApp(now.TxnGroup[0].Txn.Receiver, 123, basics.AppParams{}) @@ -643,7 +719,7 @@ app_local_get_ex bnz exist err exist: -byte 0x414c474f +byte "ALGO" ==` ledger.NewLocal(now.TxnGroup[0].Txn.Sender, 100, string(protocol.PaymentTx), basics.TealValue{Type: basics.TealBytesType, Bytes: "ALGO"}) @@ -662,7 +738,7 @@ app_local_get_ex bnz exist err exist: -byte 0x414c474f +byte "ALGO" ==` ledger.NewLocals(now.TxnGroup[0].Txn.Sender, 56) @@ -673,7 +749,7 @@ byte 0x414c474f text = `int 0 // account idx txn ApplicationArgs 0 app_local_get -byte 0x414c474f +byte "ALGO" ==` ledger.NewLocal(now.TxnGroup[0].Txn.Sender, 100, string(protocol.PaymentTx), basics.TealValue{Type: basics.TealBytesType, Bytes: "ALGO"}) @@ -688,7 +764,7 @@ byte 0x414c474f // check app_local_get default value text = `int 0 // account idx -byte 0x414c474f +byte "ALGO" app_local_get int 0 ==` @@ -707,7 +783,7 @@ app_global_get_ex bnz exist err exist: -byte 0x414c474f +byte "ALGO" == int 1 // ForeignApps index txn ApplicationArgs 0 @@ -715,12 +791,12 @@ app_global_get_ex bnz exist1 err exist1: -byte 0x414c474f +byte "ALGO" == && txn ApplicationArgs 0 app_global_get -byte 0x414c474f +byte "ALGO" == && ` @@ -729,7 +805,7 @@ byte 0x414c474f now.TxnGroup[0].Txn.ApplicationID = 100 now.TxnGroup[0].Txn.ForeignApps = []basics.AppIndex{now.TxnGroup[0].Txn.ApplicationID} - testApp(t, text, now, "no such app") + testApp(t, text, now, "no app 100") // create the app and check the value from ApplicationArgs[0] (protocol.PaymentTx) does not exist ledger.NewApp(now.TxnGroup[0].Txn.Sender, 100, basics.AppParams{}) @@ -744,7 +820,7 @@ byte 0x414c474f text = "int 2; txn ApplicationArgs 0; app_global_get_ex" testApp(t, text, now, "invalid App reference 2") // check that actual app id ok instead of indirect reference - text = "int 100; txn ApplicationArgs 0; app_global_get_ex; int 1; ==; assert; byte 0x414c474f; ==" + text = `int 100; txn ApplicationArgs 0; app_global_get_ex; int 1; ==; assert; byte "ALGO"; ==` testApp(t, text, now) testApp(t, text, pre, "invalid App reference 100") // but not in old teal @@ -824,7 +900,7 @@ int 0//params asset_params_get AssetUnitName ! bnz error -byte 0x414c474f +byte "ALGO" == && int 0//params @@ -1100,19 +1176,98 @@ intc_1 testApp(t, notrack(source), now, "cannot compare ([]byte to uint64)") } -func TestAppParams(t *testing.T) { +// TestAssetDisambiguation ensures we have a consistent interpretation of low +// numbers when used as an argument to asset_*_get. A low number is an asset ID +// if that asset ID is available, or a slot number in txn.Assets if not. +func TestAssetDisambiguation(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - ep, tx, ledger := makeSampleEnv() - ledger.NewAccount(tx.Sender, 1) - ledger.NewApp(tx.Sender, 100, basics.AppParams{}) - /* app id is in ForeignApps, but does not exist */ - source := "int 56; app_params_get AppExtraProgramPages; int 0; ==; assert; int 0; ==" - testApp(t, source, ep) - /* app id is in ForeignApps, but has zero ExtraProgramPages */ - source = "int 100; app_params_get AppExtraProgramPages; int 1; ==; assert; int 0; ==" - testApp(t, source, ep) + // start at 4 when the two meanings were added, stop at 8 because 9 removed the dual meaning + testLogicRange(t, 4, 8, func(t *testing.T, ep *EvalParams, tx *transactions.Transaction, ledger *Ledger) { + ledger.NewAsset(tx.Sender, 1, basics.AssetParams{AssetName: "one", Total: 1}) + ledger.NewAsset(tx.Sender, 20, basics.AssetParams{AssetName: "twenty", Total: 20}) + ledger.NewAsset(tx.Sender, 30, basics.AssetParams{AssetName: "thirty", Total: 30}) + tx.ForeignAssets = []basics.AssetIndex{20, 30} + // Since 1 is not available, 1 must mean the 1th asset slot = 30 + testApp(t, `int 1; asset_params_get AssetName; assert; byte "thirty"; ==`, ep) + testApp(t, `int 0; int 1; asset_holding_get AssetBalance; assert; int 30; ==`, ep) + + tx.ForeignAssets = []basics.AssetIndex{1, 30} + // Since 1 IS available, 1 means the assetid=1, not the 1th slot + testApp(t, `int 1; asset_params_get AssetName; assert; byte "one"; ==`, ep) + testApp(t, `int 0; int 1; asset_holding_get AssetBalance; assert; int 1; ==`, ep) + }) +} + +// TestAppDisambiguation ensures we have a consistent interpretation of low +// numbers when used as an argument to app_(global,local)_get. A low number is +// an app ID if that app ID is available, or a slot number in +// txn.ForeignApplications if not. +func TestAppDisambiguation(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + // start at 4 when the two meanings were added, stop at 8 because 9 removed the dual meaning + testLogicRange(t, 4, 8, func(t *testing.T, ep *EvalParams, tx *transactions.Transaction, ledger *Ledger) { + ledger.NewApp(tx.Sender, 1, basics.AppParams{ + GlobalState: map[string]basics.TealValue{"a": { + Type: basics.TealUintType, + Uint: 1, + }}, + ExtraProgramPages: 1, + }) + ledger.NewLocals(tx.Sender, 1) + ledger.NewLocal(tx.Sender, 1, "x", basics.TealValue{Type: basics.TealUintType, Uint: 100}) + ledger.NewApp(tx.Sender, 20, basics.AppParams{ + GlobalState: map[string]basics.TealValue{"a": { + Type: basics.TealUintType, + Uint: 20, + }}, + ExtraProgramPages: 20, + }) + ledger.NewLocals(tx.Sender, 20) + ledger.NewLocal(tx.Sender, 20, "x", basics.TealValue{Type: basics.TealUintType, Uint: 200}) + ledger.NewApp(tx.Sender, 30, basics.AppParams{ + GlobalState: map[string]basics.TealValue{"a": { + Type: basics.TealUintType, + Uint: 30, + }}, + ExtraProgramPages: 30, + }) + tx.ForeignApps = []basics.AppIndex{20, 30} + // Since 1 is not available, 1 must mean the first app slot = 20 (recall, 0 mean "this app") + if ep.Proto.LogicSigVersion >= 5 { + testApp(t, `int 1; app_params_get AppExtraProgramPages; assert; int 20; ==`, ep) + } + testApp(t, `int 1; byte "a"; app_global_get_ex; assert; int 20; ==`, ep) + testApp(t, `int 0; int 1; byte "x"; app_local_get_ex; assert; int 200; ==`, ep) + + tx.ForeignApps = []basics.AppIndex{1, 30} + // Since 1 IS available, 1 means the assetid=1, not the 1th slot + if ep.Proto.LogicSigVersion >= 5 { + testApp(t, `int 1; app_params_get AppExtraProgramPages; assert; int 1; ==`, ep) + } + testApp(t, `int 1; byte "a"; app_global_get_ex; assert; int 1; ==`, ep) + testApp(t, `int 0; int 1; byte "x"; app_local_get_ex; assert; int 100; ==`, ep) + }) +} + +func TestAppParams(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + // start at 5 for app_params_get + testLogicRange(t, 5, 0, func(t *testing.T, ep *EvalParams, tx *transactions.Transaction, ledger *Ledger) { + ledger.NewAccount(tx.Sender, 1) + ledger.NewApp(tx.Sender, 100, basics.AppParams{}) + + /* app id is in ForeignApps, but does not exist */ + source := "int 56; app_params_get AppExtraProgramPages; int 0; ==; assert; int 0; ==" + testApp(t, source, ep) + /* app id is in ForeignApps, but has zero ExtraProgramPages */ + source = "int 100; app_params_get AppExtraProgramPages; int 1; ==; assert; int 0; ==" + testApp(t, source, ep) + }) } func TestAcctParams(t *testing.T) { @@ -1120,31 +1275,31 @@ func TestAcctParams(t *testing.T) { t.Parallel() ep, tx, ledger := makeSampleEnv() - source := "int 0; acct_params_get AcctBalance; !; assert; int 0; ==" + source := "txn Sender; acct_params_get AcctBalance; !; assert; int 0; ==" testApp(t, source, ep) - source = "int 0; acct_params_get AcctMinBalance; !; assert; int 1001; ==" + source = "txn Sender; acct_params_get AcctMinBalance; !; assert; int 1001; ==" testApp(t, source, ep) ledger.NewAccount(tx.Sender, 42) - source = "int 0; acct_params_get AcctBalance; assert; int 42; ==" + source = "txn Sender; acct_params_get AcctBalance; assert; int 42; ==" testApp(t, source, ep) - source = "int 0; acct_params_get AcctMinBalance; assert; int 1001; ==" + source = "txn Sender; acct_params_get AcctMinBalance; assert; int 1001; ==" testApp(t, source, ep) - source = "int 0; acct_params_get AcctAuthAddr; assert; global ZeroAddress; ==" + source = "txn Sender; acct_params_get AcctAuthAddr; assert; global ZeroAddress; ==" testApp(t, source, ep) // No apps or schema at first, then 1 created and the global schema noted - source = "int 0; acct_params_get AcctTotalAppsCreated; assert; !" + source = "txn Sender; acct_params_get AcctTotalAppsCreated; assert; !" testApp(t, source, ep) - source = "int 0; acct_params_get AcctTotalNumUint; assert; !" + source = "txn Sender; acct_params_get AcctTotalNumUint; assert; !" testApp(t, source, ep) - source = "int 0; acct_params_get AcctTotalNumByteSlice; assert; !" + source = "txn Sender; acct_params_get AcctTotalNumByteSlice; assert; !" testApp(t, source, ep) - source = "int 0; acct_params_get AcctTotalExtraAppPages; assert; !" + source = "txn Sender; acct_params_get AcctTotalExtraAppPages; assert; !" testApp(t, source, ep) ledger.NewApp(tx.Sender, 2000, basics.AppParams{ StateSchemas: basics.StateSchemas{ @@ -1159,38 +1314,39 @@ func TestAcctParams(t *testing.T) { }, ExtraProgramPages: 2, }) - source = "int 0; acct_params_get AcctTotalAppsCreated; assert; int 1; ==" + source = "txn Sender; acct_params_get AcctTotalAppsCreated; assert; int 1; ==" testApp(t, source, ep) - source = "int 0; acct_params_get AcctTotalNumUint; assert; int 8; ==" + source = "txn Sender; acct_params_get AcctTotalNumUint; assert; int 8; ==" testApp(t, source, ep) - source = "int 0; acct_params_get AcctTotalNumByteSlice; assert; int 9; ==" + source = "txn Sender; acct_params_get AcctTotalNumByteSlice; assert; int 9; ==" testApp(t, source, ep) - source = "int 0; acct_params_get AcctTotalExtraAppPages; assert; int 2; ==" + source = "txn Sender; acct_params_get AcctTotalExtraAppPages; assert; int 2; ==" testApp(t, source, ep) // Not opted in at first, then opted into 1, schema added - source = "int 0; acct_params_get AcctTotalAppsOptedIn; assert; !" + source = "txn Sender; acct_params_get AcctTotalAppsOptedIn; assert; !" testApp(t, source, ep) ledger.NewLocals(tx.Sender, 2000) - source = "int 0; acct_params_get AcctTotalAppsOptedIn; assert; int 1; ==" + source = "txn Sender; acct_params_get AcctTotalAppsOptedIn; assert; int 1; ==" testApp(t, source, ep) - source = "int 0; acct_params_get AcctTotalNumUint; assert; int 8; int 6; +; ==" + source = "txn Sender; acct_params_get AcctTotalNumUint; assert; int 8; int 6; +; ==" testApp(t, source, ep) - source = "int 0; acct_params_get AcctTotalNumByteSlice; assert; int 9; int 7; +; ==" + source = "txn Sender; acct_params_get AcctTotalNumByteSlice; assert; int 9; int 7; +; ==" testApp(t, source, ep) // No ASAs at first, then 1 created AND in total - source = "int 0; acct_params_get AcctTotalAssetsCreated; assert; !" + source = "txn Sender; acct_params_get AcctTotalAssetsCreated; assert; !" testApp(t, source, ep) - source = "int 0; acct_params_get AcctTotalAssets; assert; !" + source = "txn Sender; acct_params_get AcctTotalAssets; assert; !" testApp(t, source, ep) ledger.NewAsset(tx.Sender, 3000, basics.AssetParams{}) - source = "int 0; acct_params_get AcctTotalAssetsCreated; assert; int 1; ==" + source = "txn Sender; acct_params_get AcctTotalAssetsCreated; assert; int 1; ==" testApp(t, source, ep) - source = "int 0; acct_params_get AcctTotalAssets; assert; int 1; ==" + source = "txn Sender; acct_params_get AcctTotalAssets; assert; int 1; ==" testApp(t, source, ep) } +// TestGlobalNonDelete ensures that a deletion is not inserted in the delta if the global didn't exist func TestGlobalNonDelete(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() @@ -1207,13 +1363,14 @@ int 1 require.Empty(t, delta.LocalDeltas) } +// TestLocalNonDelete ensures that a deletion is not inserted in the delta if the local didn't exist func TestLocalNonDelete(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() ep, txn, ledger := makeSampleEnv() source := ` -int 0 +txn Sender byte "none" app_local_del int 1 @@ -1231,8 +1388,8 @@ func TestAppLocalReadWriteDeleteErrors(t *testing.T) { t.Parallel() sourceRead := `intcblock 0 100 0x77 1 -bytecblock 0x414c474f 0x414c474f41 -intc_0 // 0, account idx (txn.Sender) +bytecblock "ALGO" "ALGOA" +txn Sender intc_1 // 100, app id bytec_0 // key "ALGO" app_local_get_ex @@ -1240,7 +1397,7 @@ app_local_get_ex bnz error intc_2 // 0x77 == -intc_0 // 0 +txn Sender intc_1 // 100 bytec_1 // ALGOA app_local_get_ex @@ -1256,36 +1413,34 @@ ok: intc_3 // 1 ` sourceWrite := `intcblock 0 100 1 -bytecblock 0x414c474f -intc_0 // 0, account idx (txn.Sender) +bytecblock "ALGO" +txn Sender bytec_0 // key "ALGO" intc_1 // 100 app_local_put intc_2 // 1 ` sourceDelete := `intcblock 0 100 -bytecblock 0x414c474f -intc_0 // account idx +bytecblock "ALGO" +txn Sender bytec_0 // key "ALGO" app_local_del intc_1 ` type cmdtest struct { - source string - accNumOffset int + source string } tests := map[string]cmdtest{ - "read": {sourceRead, 20}, - "write": {sourceWrite, 13}, - "delete": {sourceDelete, 12}, + "read": {sourceRead}, + "write": {sourceWrite}, + "delete": {sourceDelete}, } for name, cmdtest := range tests { name, cmdtest := name, cmdtest t.Run(fmt.Sprintf("test=%s", name), func(t *testing.T) { t.Parallel() source := cmdtest.source - firstCmdOffset := cmdtest.accNumOffset ops := testProg(t, source, AssemblerMaxVersion) @@ -1304,14 +1459,6 @@ intc_1 ep.Ledger = ledger ep.SigLedger = ledger - saved := ops.Program[firstCmdOffset] - require.Equal(t, OpsByName[0]["intc_0"].Opcode, saved) - ops.Program[firstCmdOffset] = OpsByName[0]["intc_1"].Opcode - _, err = EvalApp(ops.Program, 0, 100, ep) - require.Error(t, err) - require.Contains(t, err.Error(), "invalid Account reference 100") - - ops.Program[firstCmdOffset] = saved _, err = EvalApp(ops.Program, 0, 100, ep) require.Error(t, err) require.Contains(t, err.Error(), "is not opted into") @@ -1347,39 +1494,34 @@ func TestAppLocalStateReadWrite(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - txn := makeSampleAppl(100) - ep := defaultEvalParams(txn) - ledger := NewLedger( - map[basics.Address]uint64{ - txn.Txn.Sender: 1, - }, - ) - ep.Ledger = ledger - ep.SigLedger = ledger - ledger.NewApp(txn.Txn.Sender, 100, basics.AppParams{}) - ledger.NewLocals(txn.Txn.Sender, 100) + testLogicRange(t, 2, 0, func(t *testing.T, ep *EvalParams, txn *transactions.Transaction, ledger *Ledger) { + + txn.ApplicationID = 100 + ledger.NewAccount(txn.Sender, 1) + ledger.NewApp(txn.Sender, 100, basics.AppParams{}) + ledger.NewLocals(txn.Sender, 100) - // write int and bytes values - source := `int 0 // account -byte 0x414c474f // key "ALGO" + // write int and bytes values + source := `txn Sender +byte "ALGO" // key int 0x77 // value app_local_put -int 0 // account -byte 0x414c474f41 // key "ALGOA" -byte 0x414c474f // value +txn Sender +byte "ALGOA" // key +byte "ALGO" // value app_local_put -int 0 // account +txn Sender int 100 // app id -byte 0x414c474f41 // key "ALGOA" +byte "ALGOA" // key app_local_get_ex bnz exist err exist: -byte 0x414c474f +byte "ALGO" == -int 0 // account +txn Sender int 100 // app id -byte 0x414c474f // key "ALGO" +byte "ALGO" // key app_local_get_ex bnz exist2 err @@ -1388,27 +1530,30 @@ int 0x77 == && ` - delta := testApp(t, source, ep) - require.Empty(t, delta.GlobalDelta) - require.Len(t, delta.LocalDeltas, 1) - - require.Len(t, delta.LocalDeltas[0], 2) - vd := delta.LocalDeltas[0]["ALGO"] - require.Equal(t, basics.SetUintAction, vd.Action) - require.Equal(t, uint64(0x77), vd.Uint) - - vd = delta.LocalDeltas[0]["ALGOA"] - require.Equal(t, basics.SetBytesAction, vd.Action) - require.Equal(t, "ALGO", vd.Bytes) - - // write same value without writing, expect no local delta - source = `int 0 // account -byte 0x414c474f // key + if ep.Proto.LogicSigVersion < directRefEnabledVersion { + source = strings.ReplaceAll(source, "txn Sender", "int 0") + } + delta := testApp(t, source, ep) + require.Empty(t, delta.GlobalDelta) + require.Len(t, delta.LocalDeltas, 1) + + require.Len(t, delta.LocalDeltas[0], 2) + vd := delta.LocalDeltas[0]["ALGO"] + require.Equal(t, basics.SetUintAction, vd.Action) + require.Equal(t, uint64(0x77), vd.Uint) + + vd = delta.LocalDeltas[0]["ALGOA"] + require.Equal(t, basics.SetBytesAction, vd.Action) + require.Equal(t, "ALGO", vd.Bytes) + + // write same value without writing, expect no local delta + source = `txn Sender +byte "ALGO" // key int 0x77 // value app_local_put -int 0 // account +txn Sender int 100 // app id -byte 0x414c474f // key +byte "ALGO" // key app_local_get_ex bnz exist err @@ -1416,73 +1561,82 @@ exist: int 0x77 == ` - ledger.Reset() - ledger.NoLocal(txn.Txn.Sender, 100, "ALGOA") - ledger.NoLocal(txn.Txn.Sender, 100, "ALGO") + if ep.Proto.LogicSigVersion < directRefEnabledVersion { + source = strings.ReplaceAll(source, "txn Sender", "int 0") + } + ledger.Reset() + ledger.NoLocal(txn.Sender, 100, "ALGOA") + ledger.NoLocal(txn.Sender, 100, "ALGO") - algoValue := basics.TealValue{Type: basics.TealUintType, Uint: 0x77} - ledger.NewLocal(txn.Txn.Sender, 100, "ALGO", algoValue) + algoValue := basics.TealValue{Type: basics.TealUintType, Uint: 0x77} + ledger.NewLocal(txn.Sender, 100, "ALGO", algoValue) - delta = testApp(t, source, ep) - require.Empty(t, delta.GlobalDelta) - require.Empty(t, delta.LocalDeltas) + delta = testApp(t, source, ep) + require.Empty(t, delta.GlobalDelta) + require.Empty(t, delta.LocalDeltas) - // write same value after reading, expect no local delta - source = `int 0 // account + // write same value after reading, expect no local delta + source = `txn Sender int 100 // app id -byte 0x414c474f // key +byte "ALGO" // key app_local_get_ex bnz exist err exist: -int 0 // account -byte 0x414c474f // key +txn Sender +byte "ALGO" // key int 0x77 // value app_local_put -int 0 // account +txn Sender int 100 // app id -byte 0x414c474f // key +byte "ALGO" // key app_local_get_ex bnz exist2 err exist2: == ` - ledger.Reset() - ledger.NewLocal(txn.Txn.Sender, 100, "ALGO", algoValue) - ledger.NoLocal(txn.Txn.Sender, 100, "ALGOA") + ledger.Reset() + ledger.NewLocal(txn.Sender, 100, "ALGO", algoValue) + ledger.NoLocal(txn.Sender, 100, "ALGOA") - delta = testApp(t, source, ep) - require.Empty(t, delta.GlobalDelta) - require.Empty(t, delta.LocalDeltas) - - // write a value and expect local delta change - source = `int 0 // account -byte 0x414c474f41 // key "ALGOA" -int 0x78 // value + if ep.Proto.LogicSigVersion < directRefEnabledVersion { + source = strings.ReplaceAll(source, "txn Sender", "int 0") + } + delta = testApp(t, source, ep) + require.Empty(t, delta.GlobalDelta) + require.Empty(t, delta.LocalDeltas) + + // write a value and expect local delta change + source = `txn Sender +byte "ALGOA" // key +int 0x78 // value app_local_put int 1 ` - ledger.Reset() - ledger.NewLocal(txn.Txn.Sender, 100, "ALGO", algoValue) - ledger.NoLocal(txn.Txn.Sender, 100, "ALGOA") + ledger.Reset() + ledger.NewLocal(txn.Sender, 100, "ALGO", algoValue) + ledger.NoLocal(txn.Sender, 100, "ALGOA") - delta = testApp(t, source, ep) - require.Empty(t, delta.GlobalDelta) - require.Len(t, delta.LocalDeltas, 1) - require.Len(t, delta.LocalDeltas[0], 1) - vd = delta.LocalDeltas[0]["ALGOA"] - require.Equal(t, basics.SetUintAction, vd.Action) - require.Equal(t, uint64(0x78), vd.Uint) - - // write a value to existing key and expect delta change and reading the new value - source = `int 0 // account -byte 0x414c474f // key "ALGO" + if ep.Proto.LogicSigVersion < directRefEnabledVersion { + source = strings.ReplaceAll(source, "txn Sender", "int 0") + } + delta = testApp(t, source, ep) + require.Empty(t, delta.GlobalDelta) + require.Len(t, delta.LocalDeltas, 1) + require.Len(t, delta.LocalDeltas[0], 1) + vd = delta.LocalDeltas[0]["ALGOA"] + require.Equal(t, basics.SetUintAction, vd.Action) + require.Equal(t, uint64(0x78), vd.Uint) + + // write a value to existing key and expect delta change and reading the new value + source = `txn Sender +byte "ALGO" // key int 0x78 // value app_local_put -int 0 // account +txn Sender int 100 // app id -byte 0x414c474f // key "ALGO" +byte "ALGO" // key app_local_get_ex bnz exist err @@ -1490,85 +1644,96 @@ exist: int 0x78 == ` - ledger.Reset() - ledger.NewLocal(txn.Txn.Sender, 100, "ALGO", algoValue) - ledger.NoLocal(txn.Txn.Sender, 100, "ALGOA") + ledger.Reset() + ledger.NewLocal(txn.Sender, 100, "ALGO", algoValue) + ledger.NoLocal(txn.Sender, 100, "ALGOA") - delta = testApp(t, source, ep) - require.Empty(t, delta.GlobalDelta) - require.Len(t, delta.LocalDeltas, 1) - require.Len(t, delta.LocalDeltas[0], 1) - vd = delta.LocalDeltas[0]["ALGO"] - require.Equal(t, basics.SetUintAction, vd.Action) - require.Equal(t, uint64(0x78), vd.Uint) - - // write a value after read and expect delta change - source = `int 0 // account + if ep.Proto.LogicSigVersion < directRefEnabledVersion { + source = strings.ReplaceAll(source, "txn Sender", "int 0") + } + delta = testApp(t, source, ep) + require.Empty(t, delta.GlobalDelta) + require.Len(t, delta.LocalDeltas, 1) + require.Len(t, delta.LocalDeltas[0], 1) + vd = delta.LocalDeltas[0]["ALGO"] + require.Equal(t, basics.SetUintAction, vd.Action) + require.Equal(t, uint64(0x78), vd.Uint) + + // write a value after read and expect delta change + source = `txn Sender int 100 // app id -byte 0x414c474f // key "ALGO" +byte "ALGO" // key app_local_get_ex bnz exist err exist: -int 0 // account -byte 0x414c474f // key "ALGO" +txn Sender +byte "ALGO" // key int 0x78 // value app_local_put ` - ledger.Reset() - ledger.NewLocal(txn.Txn.Sender, 100, "ALGO", algoValue) - ledger.NoLocal(txn.Txn.Sender, 100, "ALGOA") + ledger.Reset() + ledger.NewLocal(txn.Sender, 100, "ALGO", algoValue) + ledger.NoLocal(txn.Sender, 100, "ALGOA") - delta = testApp(t, source, ep) - require.Empty(t, delta.GlobalDelta) - require.Len(t, delta.LocalDeltas, 1) - require.Len(t, delta.LocalDeltas[0], 1) - vd = delta.LocalDeltas[0]["ALGO"] - require.Equal(t, basics.SetUintAction, vd.Action) - require.Equal(t, uint64(0x78), vd.Uint) - - // write a few values and expect delta change only for unique changed - source = `int 0 // account -byte 0x414c474f // key "ALGO" + if ep.Proto.LogicSigVersion < directRefEnabledVersion { + source = strings.ReplaceAll(source, "txn Sender", "int 0") + } + delta = testApp(t, source, ep) + require.Empty(t, delta.GlobalDelta) + require.Len(t, delta.LocalDeltas, 1) + require.Len(t, delta.LocalDeltas[0], 1) + vd = delta.LocalDeltas[0]["ALGO"] + require.Equal(t, basics.SetUintAction, vd.Action) + require.Equal(t, uint64(0x78), vd.Uint) + + // write a few values and expect delta change only for unique changed + source = `txn Sender +byte "ALGO" // key int 0x77 // value app_local_put -int 0 // account -byte 0x414c474f // key "ALGO" +txn Sender +byte "ALGO" // key int 0x78 // value app_local_put -int 0 // account -byte 0x414c474f41 // key "ALGOA" +txn Sender +byte "ALGOA" // key int 0x78 // value app_local_put -int 1 // account -byte 0x414c474f // key "ALGO" +txn Accounts 1 +byte "ALGO" // key int 0x79 // value app_local_put int 1 ` - ledger.Reset() - ledger.NewLocal(txn.Txn.Sender, 100, "ALGO", algoValue) - ledger.NoLocal(txn.Txn.Sender, 100, "ALGOA") + ledger.Reset() + ledger.NewLocal(txn.Sender, 100, "ALGO", algoValue) + ledger.NoLocal(txn.Sender, 100, "ALGOA") - ledger.NewAccount(txn.Txn.Receiver, 500) - ledger.NewLocals(txn.Txn.Receiver, 100) + ledger.NewAccount(txn.Receiver, 500) + ledger.NewLocals(txn.Receiver, 100) - delta = testApp(t, source, ep) - require.Empty(t, delta.GlobalDelta) - require.Len(t, delta.LocalDeltas, 2) - require.Len(t, delta.LocalDeltas[0], 2) - require.Len(t, delta.LocalDeltas[1], 1) - vd = delta.LocalDeltas[0]["ALGO"] - require.Equal(t, basics.SetUintAction, vd.Action) - require.Equal(t, uint64(0x78), vd.Uint) - - vd = delta.LocalDeltas[0]["ALGOA"] - require.Equal(t, basics.SetUintAction, vd.Action) - require.Equal(t, uint64(0x78), vd.Uint) - - vd = delta.LocalDeltas[1]["ALGO"] - require.Equal(t, basics.SetUintAction, vd.Action) - require.Equal(t, uint64(0x79), vd.Uint) + if ep.Proto.LogicSigVersion < directRefEnabledVersion { + source = strings.ReplaceAll(source, "txn Sender", "int 0") + source = strings.ReplaceAll(source, "txn Accounts 1", "int 1") + } + delta = testApp(t, source, ep) + require.Empty(t, delta.GlobalDelta) + require.Len(t, delta.LocalDeltas, 2) + require.Len(t, delta.LocalDeltas[0], 2) + require.Len(t, delta.LocalDeltas[1], 1) + vd = delta.LocalDeltas[0]["ALGO"] + require.Equal(t, basics.SetUintAction, vd.Action) + require.Equal(t, uint64(0x78), vd.Uint) + + vd = delta.LocalDeltas[0]["ALGOA"] + require.Equal(t, basics.SetUintAction, vd.Action) + require.Equal(t, uint64(0x78), vd.Uint) + + vd = delta.LocalDeltas[1]["ALGO"] + require.Equal(t, basics.SetUintAction, vd.Action) + require.Equal(t, uint64(0x79), vd.Uint) + }) } func TestAppLocalGlobalErrorCases(t *testing.T) { @@ -1583,22 +1748,22 @@ func TestAppLocalGlobalErrorCases(t *testing.T) { testApp(t, fmt.Sprintf(`byte "%v"; int 1; app_global_put; int 1`, strings.Repeat("v", ep.Proto.MaxAppKeyLen)), ep) ledger.NewLocals(tx.Sender, 888) - testApp(t, fmt.Sprintf(`int 0; byte "%v"; int 1; app_local_put; int 1`, strings.Repeat("v", ep.Proto.MaxAppKeyLen+1)), ep, "key too long") + testApp(t, fmt.Sprintf(`txn Sender; byte "%v"; int 1; app_local_put; int 1`, strings.Repeat("v", ep.Proto.MaxAppKeyLen+1)), ep, "key too long") - testApp(t, fmt.Sprintf(`int 0; byte "%v"; int 1; app_local_put; int 1`, strings.Repeat("v", ep.Proto.MaxAppKeyLen)), ep) + testApp(t, fmt.Sprintf(`txn Sender; byte "%v"; int 1; app_local_put; int 1`, strings.Repeat("v", ep.Proto.MaxAppKeyLen)), ep) testApp(t, fmt.Sprintf(`byte "foo"; byte "%v"; app_global_put; int 1`, strings.Repeat("v", ep.Proto.MaxAppBytesValueLen+1)), ep, "value too long for key") testApp(t, fmt.Sprintf(`byte "foo"; byte "%v"; app_global_put; int 1`, strings.Repeat("v", ep.Proto.MaxAppBytesValueLen)), ep) - testApp(t, fmt.Sprintf(`int 0; byte "foo"; byte "%v"; app_local_put; int 1`, strings.Repeat("v", ep.Proto.MaxAppBytesValueLen+1)), ep, "value too long for key") + testApp(t, fmt.Sprintf(`txn Sender; byte "foo"; byte "%v"; app_local_put; int 1`, strings.Repeat("v", ep.Proto.MaxAppBytesValueLen+1)), ep, "value too long for key") - testApp(t, fmt.Sprintf(`int 0; byte "foo"; byte "%v"; app_local_put; int 1`, strings.Repeat("v", ep.Proto.MaxAppBytesValueLen)), ep) + testApp(t, fmt.Sprintf(`txn Sender; byte "foo"; byte "%v"; app_local_put; int 1`, strings.Repeat("v", ep.Proto.MaxAppBytesValueLen)), ep) ep.Proto.MaxAppSumKeyValueLens = 2 // Override to generate error. testApp(t, `byte "foo"; byte "foo"; app_global_put; int 1`, ep, "key/value total too long for key") - testApp(t, `int 0; byte "foo"; byte "foo"; app_local_put; int 1`, ep, "key/value total too long for key") + testApp(t, `txn Sender; byte "foo"; byte "foo"; app_local_put; int 1`, ep, "key/value total too long for key") } func TestAppGlobalReadWriteDeleteErrors(t *testing.T) { @@ -1607,7 +1772,7 @@ func TestAppGlobalReadWriteDeleteErrors(t *testing.T) { t.Parallel() sourceRead := `int 0 -byte 0x414c474f // key "ALGO" +byte "ALGO" // key app_global_get_ex bnz ok err @@ -1615,18 +1780,18 @@ ok: int 0x77 == ` - sourceReadSimple := `byte 0x414c474f // key "ALGO" + sourceReadSimple := `byte "ALGO" // key app_global_get int 0x77 == ` - sourceWrite := `byte 0x414c474f // key "ALGO" + sourceWrite := `byte "ALGO" // key int 100 app_global_put int 1 ` - sourceDelete := `byte 0x414c474f // key "ALGO" + sourceDelete := `byte "ALGO" // key app_global_del int 1 ` @@ -1645,7 +1810,7 @@ int 1 ep, txn, ledger := makeSampleEnv() txn.ApplicationID = basics.AppIndex(100) - testAppBytes(t, ops.Program, ep, "no such app") + testAppBytes(t, ops.Program, ep, "no app 100") ledger.NewApp(txn.Sender, 100, makeApp(0, 0, 1, 0)) @@ -1665,50 +1830,51 @@ int 1 func TestAppGlobalReadWrite(t *testing.T) { partitiontest.PartitionTest(t) - t.Parallel() - // check writing ints and bytes - source := `byte 0x414c474f // key "ALGO" + testLogicRange(t, 2, 0, func(t *testing.T, ep *EvalParams, txn *transactions.Transaction, ledger *Ledger) { + + // check writing ints and bytes + source := `byte "ALGO" // key int 0x77 // value app_global_put -byte 0x414c474f41 // key "ALGOA" -byte 0x414c474f // value +byte "ALGOA" // key "ALGOA" +byte "ALGO" // value app_global_put // check simple -byte 0x414c474f41 // key "ALGOA" +byte "ALGOA" // key "ALGOA" app_global_get -byte 0x414c474f +byte "ALGO" == // check generic with alias int 0 // current app id alias -byte 0x414c474f41 // key "ALGOA" +byte "ALGOA" // key "ALGOA" app_global_get_ex bnz ok err ok: -byte 0x414c474f +byte "ALGO" == && // check generic with exact app id -int 1 // ForeignApps index - current app -byte 0x414c474f41 // key "ALGOA" +THISAPP +byte "ALGOA" // key "ALGOA" app_global_get_ex bnz ok1 err ok1: -byte 0x414c474f +byte "ALGO" == && // check simple -byte 0x414c474f +byte "ALGO" app_global_get int 0x77 == && // check generic with alias int 0 // ForeignApps index - current app -byte 0x414c474f +byte "ALGO" app_global_get_ex bnz ok2 err @@ -1717,8 +1883,8 @@ int 0x77 == && // check generic with exact app id -int 1 // ForeignApps index - current app -byte 0x414c474f +THISAPP +byte "ALGO" app_global_get_ex bnz ok3 err @@ -1727,137 +1893,143 @@ int 0x77 == && ` - txn := makeSampleAppl(100) - txn.Txn.ForeignApps = []basics.AppIndex{txn.Txn.ApplicationID} - ep := defaultEvalParams(txn) - ledger := NewLedger( - map[basics.Address]uint64{ - txn.Txn.Sender: 1, - }, - ) - ep.Ledger = ledger - ep.SigLedger = ledger - ledger.NewApp(txn.Txn.Sender, 100, basics.AppParams{}) - delta := testApp(t, source, ep) + txn.Type = protocol.ApplicationCallTx + txn.ApplicationID = 100 + txn.ForeignApps = []basics.AppIndex{txn.ApplicationID} + ledger.NewAccount(txn.Sender, 1) + ledger.NewApp(txn.Sender, 100, basics.AppParams{}) + + if ep.Proto.LogicSigVersion < sharedResourcesVersion { + // 100 is in the ForeignApps array, name it by slot + source = strings.ReplaceAll(source, "THISAPP", "int 1") + } else { + // use the actual app number, slots no longer allowed + source = strings.ReplaceAll(source, "THISAPP", "int 100") + } + delta := testApp(t, source, ep) - require.Len(t, delta.GlobalDelta, 2) - require.Empty(t, delta.LocalDeltas) + require.Len(t, delta.GlobalDelta, 2) + require.Empty(t, delta.LocalDeltas) - vd := delta.GlobalDelta["ALGO"] - require.Equal(t, basics.SetUintAction, vd.Action) - require.Equal(t, uint64(0x77), vd.Uint) + vd := delta.GlobalDelta["ALGO"] + require.Equal(t, basics.SetUintAction, vd.Action) + require.Equal(t, uint64(0x77), vd.Uint) - vd = delta.GlobalDelta["ALGOA"] - require.Equal(t, basics.SetBytesAction, vd.Action) - require.Equal(t, "ALGO", vd.Bytes) + vd = delta.GlobalDelta["ALGOA"] + require.Equal(t, basics.SetBytesAction, vd.Action) + require.Equal(t, "ALGO", vd.Bytes) - // write existing value before read - source = `byte 0x414c474f // key "ALGO" + // write existing value before read + source = `byte "ALGO" // key int 0x77 // value app_global_put -byte 0x414c474f +byte "ALGO" app_global_get int 0x77 == ` - ledger.Reset() - ledger.NoGlobal(100, "ALGOA") - ledger.NoGlobal(100, "ALGO") + ledger.Reset() + ledger.NoGlobal(100, "ALGOA") + ledger.NoGlobal(100, "ALGO") - algoValue := basics.TealValue{Type: basics.TealUintType, Uint: 0x77} - ledger.NewGlobal(100, "ALGO", algoValue) + algoValue := basics.TealValue{Type: basics.TealUintType, Uint: 0x77} + ledger.NewGlobal(100, "ALGO", algoValue) - delta = testApp(t, source, ep) - require.Empty(t, delta.GlobalDelta) - require.Empty(t, delta.LocalDeltas) + delta = testApp(t, source, ep) + require.Empty(t, delta.GlobalDelta) + require.Empty(t, delta.LocalDeltas) - // write existing value after read - source = `int 0 -byte 0x414c474f + // write existing value after read + source = `int 0 +byte "ALGO" app_global_get_ex bnz ok err ok: pop -byte 0x414c474f +byte "ALGO" int 0x77 app_global_put -byte 0x414c474f +byte "ALGO" app_global_get int 0x77 == ` - ledger.Reset() - ledger.NoGlobal(100, "ALGOA") - ledger.NewGlobal(100, "ALGO", algoValue) + ledger.Reset() + ledger.NoGlobal(100, "ALGOA") + ledger.NewGlobal(100, "ALGO", algoValue) - delta = testApp(t, source, ep) - require.Empty(t, delta.GlobalDelta) - require.Empty(t, delta.LocalDeltas) + delta = testApp(t, source, ep) + require.Empty(t, delta.GlobalDelta) + require.Empty(t, delta.LocalDeltas) - // write new values after and before read - source = `int 0 -byte 0x414c474f + // write new values after and before read + source = `int 0 +byte "ALGO" app_global_get_ex bnz ok err ok: pop -byte 0x414c474f +byte "ALGO" int 0x78 app_global_put int 0 -byte 0x414c474f +byte "ALGO" app_global_get_ex bnz ok2 err ok2: int 0x78 == -byte 0x414c474f41 -byte 0x414c474f +byte "ALGOA" +byte "ALGO" app_global_put int 0 -byte 0x414c474f41 +byte "ALGOA" app_global_get_ex bnz ok3 err ok3: -byte 0x414c474f +byte "ALGO" == && ` - ledger.Reset() - ledger.NoGlobal(100, "ALGOA") - ledger.NewGlobal(100, "ALGO", algoValue) + ledger.Reset() + ledger.NoGlobal(100, "ALGOA") + ledger.NewGlobal(100, "ALGO", algoValue) - delta = testApp(t, source, ep) + delta = testApp(t, source, ep) - require.Len(t, delta.GlobalDelta, 2) - require.Empty(t, delta.LocalDeltas) + require.Len(t, delta.GlobalDelta, 2) + require.Empty(t, delta.LocalDeltas) - vd = delta.GlobalDelta["ALGO"] - require.Equal(t, basics.SetUintAction, vd.Action) - require.Equal(t, uint64(0x78), vd.Uint) + vd = delta.GlobalDelta["ALGO"] + require.Equal(t, basics.SetUintAction, vd.Action) + require.Equal(t, uint64(0x78), vd.Uint) - vd = delta.GlobalDelta["ALGOA"] - require.Equal(t, basics.SetBytesAction, vd.Action) - require.Equal(t, "ALGO", vd.Bytes) + vd = delta.GlobalDelta["ALGOA"] + require.Equal(t, basics.SetBytesAction, vd.Action) + require.Equal(t, "ALGO", vd.Bytes) + }) } func TestAppGlobalReadOtherApp(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - source := `int 2 // ForeignApps index + // app_global_get_ex starts in v2 + testLogicRange(t, 2, 0, func(t *testing.T, ep *EvalParams, txn *transactions.Transaction, ledger *Ledger) { + source := ` +OTHERAPP byte "mykey1" app_global_get_ex bz ok1 err ok1: pop -int 2 // ForeignApps index +OTHERAPP byte "mykey" app_global_get_ex bnz ok2 @@ -1867,24 +2039,32 @@ byte "myval" == ` - ep, txn, ledger := makeSampleEnv() - txn.ApplicationID = 100 - txn.ForeignApps = []basics.AppIndex{txn.ApplicationID, 101} - ledger.NewAccount(txn.Sender, 1) - ledger.NewApp(txn.Sender, 100, basics.AppParams{}) + if ep.Proto.LogicSigVersion < sharedResourcesVersion { + // 101 is in the ForeignApps array, name it by slot + source = strings.ReplaceAll(source, "OTHERAPP", "int 2") + } else { + // use the actual app number, slots no longer allowed + source = strings.ReplaceAll(source, "OTHERAPP", "int 101") + } - delta := testApp(t, source, ep, "no such app") - require.Empty(t, delta.GlobalDelta) - require.Empty(t, delta.LocalDeltas) + txn.ApplicationID = 100 + txn.ForeignApps = []basics.AppIndex{txn.ApplicationID, 101} + ledger.NewAccount(txn.Sender, 1) + ledger.NewApp(txn.Sender, 100, basics.AppParams{}) - ledger.NewApp(txn.Receiver, 101, basics.AppParams{}) - ledger.NewApp(txn.Receiver, 100, basics.AppParams{}) // this keeps current app id = 100 - algoValue := basics.TealValue{Type: basics.TealBytesType, Bytes: "myval"} - ledger.NewGlobal(101, "mykey", algoValue) + delta := testApp(t, source, ep, "no app 101") + require.Empty(t, delta.GlobalDelta) + require.Empty(t, delta.LocalDeltas) - delta = testApp(t, source, ep) - require.Empty(t, delta.GlobalDelta) - require.Empty(t, delta.LocalDeltas) + ledger.NewApp(txn.Receiver, 101, basics.AppParams{}) + ledger.NewApp(txn.Receiver, 100, basics.AppParams{}) // this keeps current app id = 100 + algoValue := basics.TealValue{Type: basics.TealBytesType, Bytes: "myval"} + ledger.NewGlobal(101, "mykey", algoValue) + + delta = testApp(t, source, ep) + require.Empty(t, delta.GlobalDelta) + require.Empty(t, delta.LocalDeltas) + }) } func TestBlankKey(t *testing.T) { @@ -1923,23 +2103,24 @@ func TestAppGlobalDelete(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - // check write/delete/read - source := `byte 0x414c474f // key "ALGO" + testLogicRange(t, 2, 0, func(t *testing.T, ep *EvalParams, txn *transactions.Transaction, ledger *Ledger) { + // check write/delete/read + source := `byte "ALGO" int 0x77 // value app_global_put -byte 0x414c474f41 // key "ALGOA" -byte 0x414c474f +byte "ALGOA" +byte "ALGO" app_global_put -byte 0x414c474f +byte "ALGO" app_global_del -byte 0x414c474f41 +byte "ALGOA" app_global_del int 0 -byte 0x414c474f +byte "ALGO" app_global_get_ex bnz error int 0 -byte 0x414c474f41 +byte "ALGOA" app_global_get_ex bnz error == @@ -1949,156 +2130,166 @@ err ok: int 1 ` - ep, txn, ledger := makeSampleEnv() - ledger.NewAccount(txn.Sender, 1) - txn.ApplicationID = 100 - ledger.NewApp(txn.Sender, 100, basics.AppParams{}) - delta := testApp(t, source, ep) - require.Len(t, delta.GlobalDelta, 2) - require.Empty(t, delta.LocalDeltas) + ledger.NewAccount(txn.Sender, 1) + txn.ApplicationID = 100 + ledger.NewApp(txn.Sender, 100, basics.AppParams{}) - ledger.Reset() - ledger.NoGlobal(100, "ALGOA") - ledger.NoGlobal(100, "ALGO") + delta := testApp(t, source, ep) + require.Len(t, delta.GlobalDelta, 2) + require.Empty(t, delta.LocalDeltas) - algoValue := basics.TealValue{Type: basics.TealUintType, Uint: 0x77} - ledger.NewGlobal(100, "ALGO", algoValue) + ledger.Reset() + ledger.NoGlobal(100, "ALGOA") + ledger.NoGlobal(100, "ALGO") - // check delete existing - source = `byte 0x414c474f // key "ALGO" + algoValue := basics.TealValue{Type: basics.TealUintType, Uint: 0x77} + ledger.NewGlobal(100, "ALGO", algoValue) + + // check delete existing + source = `byte "ALGO" app_global_del -int 1 -byte 0x414c474f +THISAPP +byte "ALGO" app_global_get_ex == // two zeros ` - txn.ForeignApps = []basics.AppIndex{txn.ApplicationID} - delta = testApp(t, source, ep) - require.Len(t, delta.GlobalDelta, 1) - vd := delta.GlobalDelta["ALGO"] - require.Equal(t, basics.DeleteAction, vd.Action) - require.Equal(t, uint64(0), vd.Uint) - require.Equal(t, "", vd.Bytes) - require.Equal(t, 0, len(delta.LocalDeltas)) - - ledger.Reset() - ledger.NoGlobal(100, "ALGOA") - ledger.NoGlobal(100, "ALGO") - - ledger.NewGlobal(100, "ALGO", algoValue) - // check delete and write non-existing - source = `byte 0x414c474f41 // key "ALGOA" + if ep.Proto.LogicSigVersion < sharedResourcesVersion { + // 100 is in the ForeignApps array, name it by slot + source = strings.ReplaceAll(source, "THISAPP", "int 1") + } else { + // use the actual app number, slots no longer allowed + source = strings.ReplaceAll(source, "THISAPP", "int 100") + } + txn.ForeignApps = []basics.AppIndex{txn.ApplicationID} + delta = testApp(t, source, ep) + require.Len(t, delta.GlobalDelta, 1) + vd := delta.GlobalDelta["ALGO"] + require.Equal(t, basics.DeleteAction, vd.Action) + require.Equal(t, uint64(0), vd.Uint) + require.Equal(t, "", vd.Bytes) + require.Equal(t, 0, len(delta.LocalDeltas)) + + ledger.Reset() + ledger.NoGlobal(100, "ALGOA") + ledger.NoGlobal(100, "ALGO") + + ledger.NewGlobal(100, "ALGO", algoValue) + + // check delete and write non-existing + source = `byte "ALGOA" app_global_del int 0 -byte 0x414c474f41 +byte "ALGOA" app_global_get_ex == // two zeros -byte 0x414c474f41 +byte "ALGOA" int 0x78 app_global_put ` - delta = testApp(t, source, ep) - require.Len(t, delta.GlobalDelta, 1) - vd = delta.GlobalDelta["ALGOA"] - require.Equal(t, basics.SetUintAction, vd.Action) - require.Equal(t, uint64(0x78), vd.Uint) - require.Equal(t, "", vd.Bytes) - require.Empty(t, delta.LocalDeltas) - - ledger.Reset() - ledger.NoGlobal(100, "ALGOA") - ledger.NoGlobal(100, "ALGO") - - ledger.NewGlobal(100, "ALGO", algoValue) - - // check delete and write existing - source = `byte 0x414c474f // key "ALGO" + delta = testApp(t, source, ep) + require.Len(t, delta.GlobalDelta, 1) + vd = delta.GlobalDelta["ALGOA"] + require.Equal(t, basics.SetUintAction, vd.Action) + require.Equal(t, uint64(0x78), vd.Uint) + require.Equal(t, "", vd.Bytes) + require.Empty(t, delta.LocalDeltas) + + ledger.Reset() + ledger.NoGlobal(100, "ALGOA") + ledger.NoGlobal(100, "ALGO") + + ledger.NewGlobal(100, "ALGO", algoValue) + + // check delete and write existing + source = `byte "ALGO" app_global_del -byte 0x414c474f +byte "ALGO" int 0x78 app_global_put int 1 ` - delta = testApp(t, source, ep) - require.Len(t, delta.GlobalDelta, 1) - vd = delta.GlobalDelta["ALGO"] - require.Equal(t, basics.SetUintAction, vd.Action) - require.Empty(t, delta.LocalDeltas) + delta = testApp(t, source, ep) + require.Len(t, delta.GlobalDelta, 1) + vd = delta.GlobalDelta["ALGO"] + require.Equal(t, basics.SetUintAction, vd.Action) + require.Empty(t, delta.LocalDeltas) - ledger.Reset() - ledger.Reset() - ledger.NoGlobal(100, "ALGOA") - ledger.NoGlobal(100, "ALGO") + ledger.Reset() + ledger.Reset() + ledger.NoGlobal(100, "ALGOA") + ledger.NoGlobal(100, "ALGO") - ledger.NewGlobal(100, "ALGO", algoValue) + ledger.NewGlobal(100, "ALGO", algoValue) - // check delete,write,delete existing - source = `byte 0x414c474f // key "ALGO" + // check delete,write,delete existing + source = `byte "ALGO" app_global_del -byte 0x414c474f +byte "ALGO" int 0x78 app_global_put -byte 0x414c474f +byte "ALGO" app_global_del int 1 ` - delta = testApp(t, source, ep) - require.Len(t, delta.GlobalDelta, 1) - vd = delta.GlobalDelta["ALGO"] - require.Equal(t, basics.DeleteAction, vd.Action) - require.Empty(t, delta.LocalDeltas) + delta = testApp(t, source, ep) + require.Len(t, delta.GlobalDelta, 1) + vd = delta.GlobalDelta["ALGO"] + require.Equal(t, basics.DeleteAction, vd.Action) + require.Empty(t, delta.LocalDeltas) - ledger.Reset() - ledger.Reset() - ledger.NoGlobal(100, "ALGOA") - ledger.NoGlobal(100, "ALGO") + ledger.Reset() + ledger.Reset() + ledger.NoGlobal(100, "ALGOA") + ledger.NoGlobal(100, "ALGO") - ledger.NewGlobal(100, "ALGO", algoValue) + ledger.NewGlobal(100, "ALGO", algoValue) - // check delete, write, delete non-existing - source = `byte 0x414c474f41 // key "ALGOA" + // check delete, write, delete non-existing + source = `byte "ALGOA" // key "ALGOA" app_global_del -byte 0x414c474f41 +byte "ALGOA" int 0x78 app_global_put -byte 0x414c474f41 +byte "ALGOA" app_global_del int 1 ` - delta = testApp(t, source, ep) - require.Len(t, delta.GlobalDelta, 1) - require.Len(t, delta.LocalDeltas, 0) + delta = testApp(t, source, ep) + require.Len(t, delta.GlobalDelta, 1) + require.Len(t, delta.LocalDeltas, 0) + }) } func TestAppLocalDelete(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - // check write/delete/read - source := `int 0 // sender -byte 0x414c474f // key "ALGO" + testLogicRange(t, 2, 0, func(t *testing.T, ep *EvalParams, txn *transactions.Transaction, ledger *Ledger) { + // check write/delete/read + source := `int 0 // sender +byte "ALGO" int 0x77 // value app_local_put -int 1 -byte 0x414c474f41 // key "ALGOA" -byte 0x414c474f +int 1 // other +byte "ALGOA" // key "ALGOA" +byte "ALGO" app_local_put int 0 // sender -byte 0x414c474f +byte "ALGO" app_local_del -int 1 -byte 0x414c474f41 +int 1 // other +byte "ALGOA" app_local_del int 0 // sender int 0 // app -byte 0x414c474f +byte "ALGO" app_local_get_ex bnz error -int 1 +int 1 // other int 100 -byte 0x414c474f41 +byte "ALGOA" app_local_get_ex bnz error == @@ -2108,168 +2299,183 @@ err ok: int 1 ` - txn := makeSampleAppl(100) - ep := defaultEvalParams(txn) - ledger := NewLedger( - map[basics.Address]uint64{ - txn.Txn.Sender: 1, - }, - ) - ep.Ledger = ledger - ep.SigLedger = ledger - ledger.NewApp(txn.Txn.Sender, 100, basics.AppParams{}) - ledger.NewLocals(txn.Txn.Sender, 100) - ledger.NewAccount(txn.Txn.Receiver, 1) - ledger.NewLocals(txn.Txn.Receiver, 100) - - ep.Trace = &strings.Builder{} - - delta := testApp(t, source, ep) - require.Equal(t, 0, len(delta.GlobalDelta)) - require.Equal(t, 2, len(delta.LocalDeltas)) + txn.ApplicationID = 100 + ledger.NewAccount(txn.Sender, 1) + ledger.NewApp(txn.Sender, 100, basics.AppParams{}) + ledger.NewLocals(txn.Sender, 100) + ledger.NewAccount(txn.Receiver, 1) + ledger.NewLocals(txn.Receiver, 100) + + ep.Trace = &strings.Builder{} + + if ep.Proto.LogicSigVersion < sharedResourcesVersion { + delta := testApp(t, source, ep) + require.Equal(t, 0, len(delta.GlobalDelta)) + require.Equal(t, 2, len(delta.LocalDeltas)) + ledger.Reset() + } - ledger.Reset() - // test that app_local_put and _app_local_del can use byte addresses - delta = testApp(t, strings.Replace(source, "int 0 // sender", "byte \"aoeuiaoeuiaoeuiaoeuiaoeuiaoeui00\"", -1), ep) - // But won't even compile in old teal - testProg(t, strings.Replace(source, "int 0 // sender", "byte \"aoeuiaoeuiaoeuiaoeuiaoeuiaoeui00\"", -1), directRefEnabledVersion-1, - Expect{4, "app_local_put arg 0 wanted..."}, Expect{11, "app_local_del arg 0 wanted..."}) - require.Equal(t, 0, len(delta.GlobalDelta)) - require.Equal(t, 2, len(delta.LocalDeltas)) + if ep.Proto.LogicSigVersion >= directRefEnabledVersion { + // test that app_local_put and _app_local_del can use byte addresses + withBytes := strings.ReplaceAll(source, "int 0 // sender", "txn Sender") + withBytes = strings.ReplaceAll(withBytes, "int 1 // other", "txn Accounts 1") + delta := testApp(t, withBytes, ep) + // But won't even compile in old teal + testProg(t, withBytes, directRefEnabledVersion-1, + Expect{4, "app_local_put arg 0 wanted..."}, Expect{11, "app_local_del arg 0 wanted..."}) + require.Equal(t, 0, len(delta.GlobalDelta)) + require.Equal(t, 2, len(delta.LocalDeltas)) + ledger.Reset() + } - ledger.Reset() - ledger.NoLocal(txn.Txn.Sender, 100, "ALGOA") - ledger.NoLocal(txn.Txn.Sender, 100, "ALGO") - ledger.NoLocal(txn.Txn.Receiver, 100, "ALGOA") - ledger.NoLocal(txn.Txn.Receiver, 100, "ALGO") + ledger.NoLocal(txn.Sender, 100, "ALGOA") + ledger.NoLocal(txn.Sender, 100, "ALGO") + ledger.NoLocal(txn.Receiver, 100, "ALGOA") + ledger.NoLocal(txn.Receiver, 100, "ALGO") - algoValue := basics.TealValue{Type: basics.TealUintType, Uint: 0x77} - ledger.NewLocal(txn.Txn.Sender, 100, "ALGO", algoValue) + algoValue := basics.TealValue{Type: basics.TealUintType, Uint: 0x77} + ledger.NewLocal(txn.Sender, 100, "ALGO", algoValue) - // check delete existing - source = `int 0 // account -byte 0x414c474f // key "ALGO" + // check delete existing + source = `txn Sender +byte "ALGO" app_local_del -int 0 +txn Sender int 100 -byte 0x414c474f +byte "ALGO" app_local_get_ex == // two zeros ` - delta = testApp(t, source, ep) - require.Equal(t, 0, len(delta.GlobalDelta)) - require.Equal(t, 1, len(delta.LocalDeltas)) - vd := delta.LocalDeltas[0]["ALGO"] - require.Equal(t, basics.DeleteAction, vd.Action) - require.Equal(t, uint64(0), vd.Uint) - require.Equal(t, "", vd.Bytes) - - ledger.Reset() - ledger.NoLocal(txn.Txn.Sender, 100, "ALGOA") - ledger.NoLocal(txn.Txn.Sender, 100, "ALGO") - - ledger.NewLocal(txn.Txn.Sender, 100, "ALGO", algoValue) - - // check delete and write non-existing - source = `int 0 // account -byte 0x414c474f41 // key "ALGOA" + if ep.Proto.LogicSigVersion < directRefEnabledVersion { + source = strings.ReplaceAll(source, "txn Sender", "int 0") + } + delta := testApp(t, source, ep) + require.Equal(t, 0, len(delta.GlobalDelta)) + require.Equal(t, 1, len(delta.LocalDeltas)) + vd := delta.LocalDeltas[0]["ALGO"] + require.Equal(t, basics.DeleteAction, vd.Action) + require.Equal(t, uint64(0), vd.Uint) + require.Equal(t, "", vd.Bytes) + + ledger.Reset() + ledger.NoLocal(txn.Sender, 100, "ALGOA") + ledger.NoLocal(txn.Sender, 100, "ALGO") + + ledger.NewLocal(txn.Sender, 100, "ALGO", algoValue) + + // check delete and write non-existing + source = `txn Sender +byte "ALGOA" app_local_del +txn Sender int 0 -int 0 -byte 0x414c474f41 +byte "ALGOA" app_local_get_ex == // two zeros -int 0 -byte 0x414c474f41 +txn Sender +byte "ALGOA" int 0x78 app_local_put ` - delta = testApp(t, source, ep) - require.Equal(t, 0, len(delta.GlobalDelta)) - require.Equal(t, 1, len(delta.LocalDeltas)) - vd = delta.LocalDeltas[0]["ALGOA"] - require.Equal(t, basics.SetUintAction, vd.Action) - require.Equal(t, uint64(0x78), vd.Uint) - require.Equal(t, "", vd.Bytes) - - ledger.Reset() - ledger.NoLocal(txn.Txn.Sender, 100, "ALGOA") - ledger.NoLocal(txn.Txn.Sender, 100, "ALGO") - - ledger.NewLocal(txn.Txn.Sender, 100, "ALGO", algoValue) - - // check delete and write existing - source = `int 0 // account -byte 0x414c474f // key "ALGO" + if ep.Proto.LogicSigVersion < directRefEnabledVersion { + source = strings.ReplaceAll(source, "txn Sender", "int 0") + } + delta = testApp(t, source, ep) + require.Equal(t, 0, len(delta.GlobalDelta)) + require.Equal(t, 1, len(delta.LocalDeltas)) + vd = delta.LocalDeltas[0]["ALGOA"] + require.Equal(t, basics.SetUintAction, vd.Action) + require.Equal(t, uint64(0x78), vd.Uint) + require.Equal(t, "", vd.Bytes) + + ledger.Reset() + ledger.NoLocal(txn.Sender, 100, "ALGOA") + ledger.NoLocal(txn.Sender, 100, "ALGO") + + ledger.NewLocal(txn.Sender, 100, "ALGO", algoValue) + + // check delete and write existing + source = `txn Sender +byte "ALGO" app_local_del -int 0 -byte 0x414c474f +txn Sender +byte "ALGO" int 0x78 app_local_put int 1 ` - delta = testApp(t, source, ep) - require.Equal(t, 0, len(delta.GlobalDelta)) - require.Equal(t, 1, len(delta.LocalDeltas)) - vd = delta.LocalDeltas[0]["ALGO"] - require.Equal(t, basics.SetUintAction, vd.Action) - require.Equal(t, uint64(0x78), vd.Uint) - require.Equal(t, "", vd.Bytes) - - ledger.Reset() - ledger.NoLocal(txn.Txn.Sender, 100, "ALGOA") - ledger.NoLocal(txn.Txn.Sender, 100, "ALGO") - - ledger.NewLocal(txn.Txn.Sender, 100, "ALGO", algoValue) - - // check delete,write,delete existing - source = `int 0 // account -byte 0x414c474f // key "ALGO" + if ep.Proto.LogicSigVersion < directRefEnabledVersion { + source = strings.ReplaceAll(source, "txn Sender", "int 0") + } + delta = testApp(t, source, ep) + require.Equal(t, 0, len(delta.GlobalDelta)) + require.Equal(t, 1, len(delta.LocalDeltas)) + vd = delta.LocalDeltas[0]["ALGO"] + require.Equal(t, basics.SetUintAction, vd.Action) + require.Equal(t, uint64(0x78), vd.Uint) + require.Equal(t, "", vd.Bytes) + + ledger.Reset() + ledger.NoLocal(txn.Sender, 100, "ALGOA") + ledger.NoLocal(txn.Sender, 100, "ALGO") + + ledger.NewLocal(txn.Sender, 100, "ALGO", algoValue) + + // check delete,write,delete existing + source = `txn Sender +byte "ALGO" app_local_del -int 0 -byte 0x414c474f +txn Sender +byte "ALGO" int 0x78 app_local_put -int 0 -byte 0x414c474f +txn Sender +byte "ALGO" app_local_del int 1 ` - delta = testApp(t, source, ep) - require.Equal(t, 0, len(delta.GlobalDelta)) - require.Equal(t, 1, len(delta.LocalDeltas)) - vd = delta.LocalDeltas[0]["ALGO"] - require.Equal(t, basics.DeleteAction, vd.Action) - require.Equal(t, uint64(0), vd.Uint) - require.Equal(t, "", vd.Bytes) - - ledger.Reset() - ledger.NoLocal(txn.Txn.Sender, 100, "ALGOA") - ledger.NoLocal(txn.Txn.Sender, 100, "ALGO") - - ledger.NewLocal(txn.Txn.Sender, 100, "ALGO", algoValue) - - // check delete, write, delete non-existing - source = `int 0 // account -byte 0x414c474f41 // key "ALGOA" + if ep.Proto.LogicSigVersion < directRefEnabledVersion { + source = strings.ReplaceAll(source, "txn Sender", "int 0") + } + delta = testApp(t, source, ep) + require.Equal(t, 0, len(delta.GlobalDelta)) + require.Equal(t, 1, len(delta.LocalDeltas)) + vd = delta.LocalDeltas[0]["ALGO"] + require.Equal(t, basics.DeleteAction, vd.Action) + require.Equal(t, uint64(0), vd.Uint) + require.Equal(t, "", vd.Bytes) + + ledger.Reset() + ledger.NoLocal(txn.Sender, 100, "ALGOA") + ledger.NoLocal(txn.Sender, 100, "ALGO") + + ledger.NewLocal(txn.Sender, 100, "ALGO", algoValue) + + // check delete, write, delete non-existing + source = `txn Sender +byte "ALGOA" app_local_del -int 0 -byte 0x414c474f41 +txn Sender +byte "ALGOA" int 0x78 app_local_put -int 0 -byte 0x414c474f41 +txn Sender +byte "ALGOA" app_local_del int 1 ` - delta = testApp(t, source, ep) - require.Equal(t, 0, len(delta.GlobalDelta)) - require.Equal(t, 1, len(delta.LocalDeltas)) - require.Equal(t, 1, len(delta.LocalDeltas[0])) + if ep.Proto.LogicSigVersion < directRefEnabledVersion { + source = strings.ReplaceAll(source, "txn Sender", "int 0") + } + delta = testApp(t, source, ep) + require.Equal(t, 0, len(delta.GlobalDelta)) + require.Equal(t, 1, len(delta.LocalDeltas)) + require.Equal(t, 1, len(delta.LocalDeltas[0])) + }) } -func TestEnumFieldErrors(t *testing.T) { // nolint:paralleltest // manipulates globalFieldSpecs +func TestEnumFieldErrors(t *testing.T) { // nolint:paralleltest // manipulates txnFieldSpecs partitiontest.PartitionTest(t) source := `txn Amount` @@ -2313,7 +2519,7 @@ func TestEnumFieldErrors(t *testing.T) { // nolint:paralleltest // manipulates g } ledger.NewAsset(tx.Sender, 55, params) - source = `int 0 + source = `txn Sender int 55 asset_holding_get AssetBalance assert @@ -2328,7 +2534,7 @@ assert testApp(t, source, ep, "AssetBalance expected field type is []byte but got uint64") - source = `int 0 + source = `int 55 asset_params_get AssetTotal assert ` @@ -2362,40 +2568,50 @@ func TestReturnTypes(t *testing.T) { // opcodes that need to set up their own stack inputs, a ": at the front of // the string means "start with an empty stack". specialCmd := map[string]string{ - "txn": "txn Sender", - "txna": "txna ApplicationArgs 0", - "gtxn": "gtxn 0 Sender", - "gtxna": "gtxna 0 ApplicationArgs 0", - "global": "global MinTxnFee", - "gaids": ": int 0; gaids", - "gloads": ": int 0; gloads 0", // Needs txn index = 0 to work - "gloadss": ": int 0; int 1; gloadss", // Needs txn index = 0 to work - "intc": "intcblock 0; intc 0", - "intc_0": "intcblock 0; intc_0", - "intc_1": "intcblock 0 0; intc_1", - "intc_2": "intcblock 0 0 0; intc_2", - "intc_3": "intcblock 0 0 0 0; intc_3", - "bytec": "bytecblock 0x32; bytec 0", - "bytec_0": "bytecblock 0x32; bytec_0", - "bytec_1": "bytecblock 0x32 0x33; bytec_1", - "bytec_2": "bytecblock 0x32 0x33 0x34; bytec_2", - "bytec_3": "bytecblock 0x32 0x33 0x34 0x35; bytec_3", - "substring": "substring 0 2", - "extract_uint32": ": byte 0x0102030405; int 1; extract_uint32", - "extract_uint64": ": byte 0x010203040506070809; int 1; extract_uint64", - "replace2": ": byte 0x0102030405; byte 0x0809; replace2 2", - "replace3": ": byte 0x0102030405; int 2; byte 0x0809; replace3", - "asset_params_get": "asset_params_get AssetUnitName", - "asset_holding_get": "asset_holding_get AssetBalance", - "gtxns": "gtxns Sender", - "gtxnsa": ": int 0; gtxnsa ApplicationArgs 0", - "app_params_get": "app_params_get AppGlobalNumUint", - "acct_params_get": "acct_params_get AcctMinBalance", - "extract": "extract 0 2", - "txnas": "txnas ApplicationArgs", - "gtxnas": "gtxnas 0 ApplicationArgs", - "gtxnsas": ": int 0; int 0; gtxnsas ApplicationArgs", - "divw": ": int 1; int 2; int 3; divw", + "txn": "txn Sender", + "txna": "txna ApplicationArgs 0", + "gtxn": "gtxn 0 Sender", + "gtxna": "gtxna 0 ApplicationArgs 0", + "global": "global MinTxnFee", + "gaids": ": int 0; gaids", + "gloads": ": int 0; gloads 0", // Needs txn index = 0 to work + "gloadss": ": int 0; int 1; gloadss", // Needs txn index = 0 to work + "intc": "intcblock 0; intc 0", + "intc_0": "intcblock 0; intc_0", + "intc_1": "intcblock 0 0; intc_1", + "intc_2": "intcblock 0 0 0; intc_2", + "intc_3": "intcblock 0 0 0 0; intc_3", + "bytec": "bytecblock 0x32; bytec 0", + "bytec_0": "bytecblock 0x32; bytec_0", + "bytec_1": "bytecblock 0x32 0x33; bytec_1", + "bytec_2": "bytecblock 0x32 0x33 0x34; bytec_2", + "bytec_3": "bytecblock 0x32 0x33 0x34 0x35; bytec_3", + "substring": "substring 0 2", + "extract_uint32": ": byte 0x0102030405; int 1; extract_uint32", + "extract_uint64": ": byte 0x010203040506070809; int 1; extract_uint64", + "replace2": ": byte 0x0102030405; byte 0x0809; replace2 2", + "replace3": ": byte 0x0102030405; int 2; byte 0x0809; replace3", + "asset_params_get": "asset_params_get AssetUnitName", + "gtxns": "gtxns Sender", + "gtxnsa": ": int 0; gtxnsa ApplicationArgs 0", + "app_params_get": "app_params_get AppGlobalNumUint", + "extract": "extract 0 2", + "txnas": "txnas ApplicationArgs", + "gtxnas": "gtxnas 0 ApplicationArgs", + "gtxnsas": ": int 0; int 0; gtxnsas ApplicationArgs", + "divw": ": int 1; int 2; int 3; divw", + + // opcodes that require addresses, not just bytes + "balance": ": txn Sender; balance", + "min_balance": ": txn Sender; min_balance", + "acct_params_get": ": txn Sender; acct_params_get AcctMinBalance", + // Use "bury" here to take advantage of args pushed on stack by test + "app_local_get": "txn Accounts 1; bury 2; app_local_get", + "app_local_get_ex": "txn Accounts 1; bury 3; app_local_get_ex", + "app_local_del": "txn Accounts 1; bury 2; app_local_del", + "app_local_put": "txn Accounts 1; bury 3; app_local_put", + "app_opted_in": "txn Sender; bury 2; app_opted_in", + "asset_holding_get": "txn Sender; bury 2; asset_holding_get AssetBalance", "itxn_field": "itxn_begin; itxn_field TypeEnum", "itxn_next": "itxn_begin; int pay; itxn_field TypeEnum; itxn_next", @@ -2762,11 +2978,11 @@ int 695 testApp(t, source, defaultEvalParams()) } -func TestSelfMutate(t *testing.T) { +func TestSelfMutateV8(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - ep, _, ledger := makeSampleEnv() + ep, _, ledger := makeSampleEnvWithVersion(8) /* In order to test the added protection of mutableAccountReference, we're going to set up a ledger in which an app account is opted into @@ -2791,7 +3007,7 @@ app_local_del ` testApp(t, source, ep, "invalid Account reference for mutation") - /* But let's just check normal access is working properly. */ + /* But let's just check read access is working properly. */ source = ` global CurrentApplicationAddress byte "hey" @@ -2801,3 +3017,138 @@ int 77 ` testApp(t, source, ep) } + +// TestSelfMutateV9AndUp tests that apps can mutate their own app's local state +// starting with v9. Includes tests to the EvalDelta created. +func TestSelfMutateV9AndUp(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + // start at 9, when such mutation became legal + testLogicRange(t, 9, 0, func(t *testing.T, ep *EvalParams, tx *transactions.Transaction, ledger *Ledger) { + /* In order to test that apps can now mutate their own app's local state, + we're going to set up a ledger in which an app account is opted into + itself. */ + ledger.NewLocals(basics.AppIndex(888).Address(), 888) + ledger.NewLocal(basics.AppIndex(888).Address(), 888, "hey", + basics.TealValue{Type: basics.TealUintType, Uint: 77}) + + // and we'll modify the passed account's locals, to better check the ED + ledger.NewLocals(tx.Accounts[0], 888) + + source := ` +global CurrentApplicationAddress +byte "hey" +int 42 +app_local_put +txn Accounts 1 +byte "acct" +int 43 +app_local_put +int 1 +` + ed := testApp(t, source, ep) + require.Len(t, tx.Accounts, 1) // Sender + 1 tx.Accounts means LocalDelta index should be 2 + require.Equal(t, map[uint64]basics.StateDelta{ + 1: { + "acct": { + Action: basics.SetUintAction, + Uint: 43, + }, + }, + 2: { + "hey": { + Action: basics.SetUintAction, + Uint: 42, + }, + }, + }, ed.LocalDeltas) + require.Equal(t, []basics.Address{tx.ApplicationID.Address()}, ed.SharedAccts) + + /* Confirm it worked. */ + source = ` +global CurrentApplicationAddress +byte "hey" +app_local_get +int 42 +== +` + testApp(t, source, ep) + + source = ` +global CurrentApplicationAddress +byte "hey" +int 10 +app_local_put // this will get wiped out by del +global CurrentApplicationAddress +byte "hey" +app_local_del +txn Accounts 1 +byte "acct" +int 7 +app_local_put +int 1 +` + ed = testApp(t, source, ep) + require.Len(t, tx.Accounts, 1) // Sender + 1 tx.Accounts means LocalDelta index should be 2 + require.Equal(t, map[uint64]basics.StateDelta{ + 1: { + "acct": { + Action: basics.SetUintAction, + Uint: 7, + }, + }, + 2: { + "hey": { + Action: basics.DeleteAction, + }, + }, + }, ed.LocalDeltas) + require.Equal(t, []basics.Address{tx.ApplicationID.Address()}, ed.SharedAccts) + + // Now, repeat the "put" test with multiple keys, to ensure only one + // address is added to SharedAccts and we'll modify the Sender too, to + // better check the ED + ledger.NewLocals(tx.Sender, 888) + + source = ` +txn Sender +byte "hey" +int 40 +app_local_put + +global CurrentApplicationAddress +byte "hey" +int 42 +app_local_put + +global CurrentApplicationAddress +byte "joe" +int 21 +app_local_put +int 1 +` + ed = testApp(t, source, ep) + require.Len(t, tx.Accounts, 1) // Sender + 1 tx.Accounts means LocalDelta index should be 2 + require.Equal(t, map[uint64]basics.StateDelta{ + 0: { + "hey": { + Action: basics.SetUintAction, + Uint: 40, + }, + }, + 2: { + "hey": { + Action: basics.SetUintAction, + Uint: 42, + }, + "joe": { + Action: basics.SetUintAction, + Uint: 21, + }, + }, + }, ed.LocalDeltas) + + require.Equal(t, []basics.Address{tx.ApplicationID.Address()}, ed.SharedAccts) + }) +} diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index 5d5f321280..29231d3448 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -168,12 +168,9 @@ func (ep *EvalParams) reset() { ep.TxnGroup[i].ApplyData = transactions.ApplyData{} } if ep.available != nil { - ep.available.apps = nil - ep.available.asas = nil - // reinitialize boxes because evaluation can add box refs for app creates. available := NewEvalParams(ep.TxnGroup, ep.Proto, ep.Specials).available if available != nil { - ep.available.boxes = available.boxes + ep.available = available } ep.available.dirtyBytes = 0 } @@ -4312,7 +4309,7 @@ func TestAllowedOpcodesV2(t *testing.T) { "gtxn": true, } - ep := defaultEvalParams() + ep := defaultEvalParamsWithVersion(2) cnt := 0 for _, spec := range OpSpecs { @@ -4320,7 +4317,7 @@ func TestAllowedOpcodesV2(t *testing.T) { source, ok := tests[spec.Name] require.True(t, ok, "Missed opcode in the test: %s", spec.Name) require.Contains(t, source, spec.Name) - ops := testProg(t, source, AssemblerMaxVersion) + ops := testProg(t, source, 2) // all opcodes allowed in stateful mode so use CheckStateful/EvalContract err := CheckContract(ops.Program, ep) require.NoError(t, err, source) @@ -4365,7 +4362,7 @@ func TestAllowedOpcodesV3(t *testing.T) { "pushbytes": `pushbytes "stringsfail?"`, } - ep := defaultEvalParams() + ep := defaultEvalParamsWithVersion(3) cnt := 0 for _, spec := range OpSpecs { @@ -4373,7 +4370,7 @@ func TestAllowedOpcodesV3(t *testing.T) { source, ok := tests[spec.Name] require.True(t, ok, "Missed opcode in the test: %s", spec.Name) require.Contains(t, source, spec.Name) - ops := testProg(t, source, AssemblerMaxVersion) + ops := testProg(t, source, 3) // all opcodes allowed in stateful mode so use CheckStateful/EvalContract testAppBytes(t, ops.Program, ep, "REJECT") diff --git a/data/transactions/logic/export_test.go b/data/transactions/logic/export_test.go index 6ee072aff5..44f90f0400 100644 --- a/data/transactions/logic/export_test.go +++ b/data/transactions/logic/export_test.go @@ -56,6 +56,7 @@ var TestLogic = testLogic var TestApp = testApp var TestAppBytes = testAppBytes var TestApps = testApps +var TestLogicRange = testLogicRange var TestProg = testProg var WithPanicOpcode = withPanicOpcode diff --git a/data/transactions/logic/fields_test.go b/data/transactions/logic/fields_test.go index cbe425d5f4..2d58854d5e 100644 --- a/data/transactions/logic/fields_test.go +++ b/data/transactions/logic/fields_test.go @@ -219,7 +219,7 @@ func TestAssetParamsFieldsVersions(t *testing.T) { for _, field := range fields { // Need to use intc so we can "backversion" the // program and not have it fail because of pushint. - text := fmt.Sprintf("intcblock 0 1; intc_0; asset_params_get %s; bnz ok; err; ok: ", field.field.String()) + text := fmt.Sprintf("intcblock 55 1; intc_0; asset_params_get %s; bnz ok; err; ok: ", field.field.String()) switch field.ftype { case StackUint64: // ensure the return type is uint64 by adding text += " intc_1; +" @@ -229,7 +229,6 @@ func TestAssetParamsFieldsVersions(t *testing.T) { // check assembler fails if version before introduction for v := uint64(2); v <= AssemblerMaxVersion; v++ { ep, txn, ledger := makeSampleEnv() - // Create app 55, since txn.ForeignApps[0] == 55 ledger.NewAsset(txn.Sender, 55, basics.AssetParams{}) ep.Proto.LogicSigVersion = v if field.version > v { @@ -270,19 +269,13 @@ func TestAcctParamsFieldsVersions(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - var fields []acctParamsFieldSpec - for _, fs := range acctParamsFieldSpecs { - if fs.version > 6 { - fields = append(fields, fs) + for _, field := range acctParamsFieldSpecs { + text := fmt.Sprintf("txn Sender; acct_params_get %s; assert;", field.field.String()) + if field.ftype == StackBytes { + text += "global ZeroAddress; concat; len" // use concat to prove we have bytes + } else { + text += "global ZeroAddress; len; +" // use + to prove we have an int } - } - require.Greater(t, len(fields), 0) - - for _, field := range fields { - // Need to use intc so we can "backversion" the program and not have it - // fail because of pushint. - // Use of '+' confirms the type, which is uint64 for all fields - text := fmt.Sprintf("intcblock 0 1; intc_0; acct_params_get %s; assert; intc_1; +", field.field.String()) // check assembler fails if version before introduction for v := uint64(2); v <= AssemblerMaxVersion; v++ { ep, txn, ledger := makeSampleEnv() diff --git a/data/transactions/logic/langspec.json b/data/transactions/logic/langspec.json index cbfb4b0d95..a5c42c7b15 100644 --- a/data/transactions/logic/langspec.json +++ b/data/transactions/logic/langspec.json @@ -1462,7 +1462,7 @@ "Returns": "U", "Size": 1, "Doc": "balance for account A, in microalgos. The balance is observed after the effects of previous transactions in the group, and after the fee for the current transaction is deducted. Changes caused by inner transactions are observable immediately following `itxn_submit`", - "DocExtra": "params: Txn.Accounts offset (or, since v4, an _available_ account address), _available_ application id (or, since v4, a Txn.ForeignApps offset). Return: value.", + "DocExtra": "params: Txn.Accounts offset (or, since v4, an _available_ account address). Return: value.", "IntroducedVersion": 2, "Groups": [ "State Access" @@ -1689,7 +1689,7 @@ "Returns": "U", "Size": 1, "Doc": "minimum required balance for account A, in microalgos. Required balance is affected by ASA, App, and Box usage. When creating or opting into an app, the minimum balance grows before the app code runs, therefore the increase is visible there. When deleting or closing out, the minimum balance decreases after the app executes. Changes caused by inner transactions or box usage are observable immediately following the opcode effecting the change.", - "DocExtra": "params: Txn.Accounts offset (or, since v4, an _available_ account address), _available_ application id (or, since v4, a Txn.ForeignApps offset). Return: value.", + "DocExtra": "params: Txn.Accounts offset (or, since v4, an _available_ account address). Return: value.", "IntroducedVersion": 3, "Groups": [ "State Access" diff --git a/data/transactions/logic/ledger_test.go b/data/transactions/logic/ledger_test.go index ecda755627..9d6106e57b 100644 --- a/data/transactions/logic/ledger_test.go +++ b/data/transactions/logic/ledger_test.go @@ -328,7 +328,7 @@ func (l *Ledger) Authorizer(addr basics.Address) (basics.Address, error) { func (l *Ledger) GetGlobal(appIdx basics.AppIndex, key string) (basics.TealValue, bool, error) { params, ok := l.applications[appIdx] if !ok { - return basics.TealValue{}, false, fmt.Errorf("no such app %d", appIdx) + return basics.TealValue{}, false, fmt.Errorf("no app %d", appIdx) } // return most recent value if available @@ -351,7 +351,7 @@ func (l *Ledger) GetGlobal(appIdx basics.AppIndex, key string) (basics.TealValue func (l *Ledger) SetGlobal(appIdx basics.AppIndex, key string, value basics.TealValue) error { params, ok := l.applications[appIdx] if !ok { - return fmt.Errorf("no such app %d", appIdx) + return fmt.Errorf("no app %d", appIdx) } // if writing the same value, return @@ -375,7 +375,7 @@ func (l *Ledger) SetGlobal(appIdx basics.AppIndex, key string, value basics.Teal func (l *Ledger) DelGlobal(appIdx basics.AppIndex, key string) error { params, ok := l.applications[appIdx] if !ok { - return fmt.Errorf("no such app %d", appIdx) + return fmt.Errorf("no app %d", appIdx) } exist := false @@ -405,7 +405,7 @@ func (l *Ledger) NewBox(appIdx basics.AppIndex, key string, value []byte, appAdd } params, ok := l.applications[appIdx] if !ok { - return fmt.Errorf("no such app %d", appIdx) + return fmt.Errorf("no app %d", appIdx) } if params.boxMods == nil { params.boxMods = make(map[string][]byte) @@ -449,7 +449,7 @@ func (l *Ledger) SetBox(appIdx basics.AppIndex, key string, value []byte) error return err } if !ok { - return fmt.Errorf("no such box %d", appIdx) + return fmt.Errorf("no box %d", appIdx) } params := l.applications[appIdx] // assured, based on above if params.boxMods == nil { @@ -487,7 +487,7 @@ func (l *Ledger) DelBox(appIdx basics.AppIndex, key string, appAddr basics.Addre func (l *Ledger) GetLocal(addr basics.Address, appIdx basics.AppIndex, key string, accountIdx uint64) (basics.TealValue, bool, error) { br, ok := l.balances[addr] if !ok { - return basics.TealValue{}, false, fmt.Errorf("no such address") + return basics.TealValue{}, false, fmt.Errorf("no account: %s", addr) } tkvd, ok := br.locals[appIdx] if !ok { @@ -513,7 +513,7 @@ func (l *Ledger) GetLocal(addr basics.Address, appIdx basics.AppIndex, key strin func (l *Ledger) SetLocal(addr basics.Address, appIdx basics.AppIndex, key string, value basics.TealValue, accountIdx uint64) error { br, ok := l.balances[addr] if !ok { - return fmt.Errorf("no such address") + return fmt.Errorf("no account: %s", addr) } tkv, ok := br.locals[appIdx] if !ok { @@ -541,7 +541,7 @@ func (l *Ledger) SetLocal(addr basics.Address, appIdx basics.AppIndex, key strin func (l *Ledger) DelLocal(addr basics.Address, appIdx basics.AppIndex, key string, accountIdx uint64) error { br, ok := l.balances[addr] if !ok { - return fmt.Errorf("no such address") + return fmt.Errorf("no account: %s", addr) } tkv, ok := br.locals[appIdx] if !ok { @@ -573,7 +573,7 @@ func (l *Ledger) DelLocal(addr basics.Address, appIdx basics.AppIndex, key strin func (l *Ledger) OptedIn(addr basics.Address, appIdx basics.AppIndex) (bool, error) { br, ok := l.balances[addr] if !ok { - return false, fmt.Errorf("no such address") + return false, fmt.Errorf("no account: %s", addr) } _, ok = br.locals[appIdx] return ok, nil @@ -586,9 +586,9 @@ func (l *Ledger) AssetHolding(addr basics.Address, assetID basics.AssetIndex) (b if asset, ok := br.holdings[assetID]; ok { return asset, nil } - return basics.AssetHolding{}, fmt.Errorf("No asset for account") + return basics.AssetHolding{}, fmt.Errorf("no asset %d for account %s", assetID, addr) } - return basics.AssetHolding{}, fmt.Errorf("no such address") + return basics.AssetHolding{}, fmt.Errorf("no account: %s", addr) } // AssetParams gives the parameters of an ASA if it exists @@ -596,7 +596,7 @@ func (l *Ledger) AssetParams(assetID basics.AssetIndex) (basics.AssetParams, bas if asset, ok := l.assets[assetID]; ok { return asset.AssetParams, asset.Creator, nil } - return basics.AssetParams{}, basics.Address{}, fmt.Errorf("no such asset") + return basics.AssetParams{}, basics.Address{}, fmt.Errorf("no asset %d", assetID) } // AppParams gives the parameters of an App if it exists @@ -604,7 +604,7 @@ func (l *Ledger) AppParams(appID basics.AppIndex) (basics.AppParams, basics.Addr if app, ok := l.applications[appID]; ok { return app.AppParams, app.Creator, nil } - return basics.AppParams{}, basics.Address{}, fmt.Errorf("no such app %d", appID) + return basics.AppParams{}, basics.Address{}, fmt.Errorf("no app %d", appID) } func (l *Ledger) move(from basics.Address, to basics.Address, amount uint64) error { @@ -905,7 +905,7 @@ func (l *Ledger) Perform(gi int, ep *EvalParams) error { func (l *Ledger) Get(addr basics.Address, withPendingRewards bool) (basics.AccountData, error) { br, ok := l.balances[addr] if !ok { - return basics.AccountData{}, fmt.Errorf("addr %s not in test.Ledger", addr.String()) + return basics.AccountData{}, fmt.Errorf("no account %s", addr) } return basics.AccountData{ MicroAlgos: basics.MicroAlgos{Raw: br.balance}, @@ -926,7 +926,7 @@ func (l *Ledger) GetCreator(cidx basics.CreatableIndex, ctype basics.CreatableTy params, found := l.applications[basics.AppIndex(cidx)] return params.Creator, found, nil } - return basics.Address{}, false, fmt.Errorf("%v %d is not in test.Ledger", ctype, cidx) + return basics.Address{}, false, fmt.Errorf("no creatable %v %d", ctype, cidx) } // SetKey creates a new key-value in {addr, aidx, global} storage diff --git a/data/transactions/logic/opcodes.go b/data/transactions/logic/opcodes.go index 5dd6f20d12..f7575f774c 100644 --- a/data/transactions/logic/opcodes.go +++ b/data/transactions/logic/opcodes.go @@ -66,10 +66,12 @@ const fidoVersion = 7 // base64, json, secp256r1 const randomnessVersion = 7 // vrf_verify, block const fpVersion = 8 // changes for frame pointers and simpler function discipline +const sharedResourcesVersion = 9 // apps can access resources from other transactions. + // EXPERIMENTAL. These should be revisited whenever a new LogicSigVersion is // moved from vFuture to a new consensus version. If they remain unready, bump // their version, and fixup TestAssemble() in assembler_test.go. -const pairingVersion = 9 // bn256 opcodes. will add bls12-381, and unify the available opcodes. +const pairingVersion = 10 // bn256 opcodes. will add bls12-381, and unify the available opcodes. // Unlimited Global Storage opcodes const boxVersion = 8 // box_* @@ -528,29 +530,38 @@ var OpSpecs = []OpSpec{ {0x60, "balance", opBalance, proto("i:i"), 2, only(ModeApp)}, {0x60, "balance", opBalance, proto("a:i"), directRefEnabledVersion, only(ModeApp)}, + {0x60, "balance", opBalance, proto("b:i"), sharedResourcesVersion, only(ModeApp)}, {0x61, "app_opted_in", opAppOptedIn, proto("ii:i"), 2, only(ModeApp)}, {0x61, "app_opted_in", opAppOptedIn, proto("ai:i"), directRefEnabledVersion, only(ModeApp)}, + {0x61, "app_opted_in", opAppOptedIn, proto("bi:i"), sharedResourcesVersion, only(ModeApp)}, {0x62, "app_local_get", opAppLocalGet, proto("ib:a"), 2, only(ModeApp)}, {0x62, "app_local_get", opAppLocalGet, proto("ab:a"), directRefEnabledVersion, only(ModeApp)}, + {0x62, "app_local_get", opAppLocalGet, proto("bb:a"), sharedResourcesVersion, only(ModeApp)}, {0x63, "app_local_get_ex", opAppLocalGetEx, proto("iib:ai"), 2, only(ModeApp)}, {0x63, "app_local_get_ex", opAppLocalGetEx, proto("aib:ai"), directRefEnabledVersion, only(ModeApp)}, + {0x63, "app_local_get_ex", opAppLocalGetEx, proto("bib:ai"), sharedResourcesVersion, only(ModeApp)}, {0x64, "app_global_get", opAppGlobalGet, proto("b:a"), 2, only(ModeApp)}, {0x65, "app_global_get_ex", opAppGlobalGetEx, proto("ib:ai"), 2, only(ModeApp)}, {0x66, "app_local_put", opAppLocalPut, proto("iba:"), 2, only(ModeApp)}, {0x66, "app_local_put", opAppLocalPut, proto("aba:"), directRefEnabledVersion, only(ModeApp)}, + {0x66, "app_local_put", opAppLocalPut, proto("bba:"), sharedResourcesVersion, only(ModeApp)}, {0x67, "app_global_put", opAppGlobalPut, proto("ba:"), 2, only(ModeApp)}, {0x68, "app_local_del", opAppLocalDel, proto("ib:"), 2, only(ModeApp)}, {0x68, "app_local_del", opAppLocalDel, proto("ab:"), directRefEnabledVersion, only(ModeApp)}, + {0x68, "app_local_del", opAppLocalDel, proto("bb:"), sharedResourcesVersion, only(ModeApp)}, {0x69, "app_global_del", opAppGlobalDel, proto("b:"), 2, only(ModeApp)}, {0x70, "asset_holding_get", opAssetHoldingGet, proto("ii:ai"), 2, field("f", &AssetHoldingFields).only(ModeApp)}, {0x70, "asset_holding_get", opAssetHoldingGet, proto("ai:ai"), directRefEnabledVersion, field("f", &AssetHoldingFields).only(ModeApp)}, + {0x70, "asset_holding_get", opAssetHoldingGet, proto("bi:ai"), sharedResourcesVersion, field("f", &AssetHoldingFields).only(ModeApp)}, {0x71, "asset_params_get", opAssetParamsGet, proto("i:ai"), 2, field("f", &AssetParamsFields).only(ModeApp)}, {0x72, "app_params_get", opAppParamsGet, proto("i:ai"), 5, field("f", &AppParamsFields).only(ModeApp)}, {0x73, "acct_params_get", opAcctParamsGet, proto("a:ai"), 6, field("f", &AcctParamsFields).only(ModeApp)}, + {0x73, "acct_params_get", opAcctParamsGet, proto("b:ai"), sharedResourcesVersion, field("f", &AcctParamsFields).only(ModeApp)}, {0x78, "min_balance", opMinBalance, proto("i:i"), 3, only(ModeApp)}, {0x78, "min_balance", opMinBalance, proto("a:i"), directRefEnabledVersion, only(ModeApp)}, + {0x78, "min_balance", opMinBalance, proto("b:i"), sharedResourcesVersion, only(ModeApp)}, // Immediate bytes and ints. Smaller code size for single use of constant. {0x80, "pushbytes", opPushBytes, proto(":b"), 3, constants(asmPushBytes, opPushBytes, "bytes", immBytes)}, diff --git a/data/transactions/logic/resources.go b/data/transactions/logic/resources.go new file mode 100644 index 0000000000..849440a26c --- /dev/null +++ b/data/transactions/logic/resources.go @@ -0,0 +1,359 @@ +// Copyright (C) 2019-2023 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package logic + +import ( + "fmt" + + "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/protocol" +) + +// resources contains a catalog of available resources. It's used to track the +// apps, assets, and boxes that are available to a transaction, outside the +// direct foreign array mechanism. +type resources struct { + // These resources were created previously in the group, so they can be used + // by later transactions. + createdAsas []basics.AssetIndex + createdApps []basics.AppIndex + + // These resources have been used by some txn in the group, so they are + // available. These maps track the availability of the basic objects (often + // called "params"), not the "cross-product" objects (which are tracked + // below) + sharedAccounts map[basics.Address]struct{} + sharedAsas map[basics.AssetIndex]struct{} + sharedApps map[basics.AppIndex]struct{} + // We need to carefully track the "cross-product" availability, because if + // tx0 mentions an account A, and tx1 mentions an ASA X, that does _not_ + // make the holding AX available + sharedHoldings map[ledgercore.AccountAsset]struct{} + sharedLocals map[ledgercore.AccountApp]struct{} + + // boxes are all of the top-level box refs from the txgroup. Most are added + // during NewEvalParams(). refs using 0 on an appl create are resolved and + // added when the appl executes. The boolean value indicates the "dirtiness" + // of the box - has it been modified in this txngroup? If yes, the size of + // the box counts against the group writeBudget. So delete is NOT a dirtying + // operation. + boxes map[boxRef]bool + + // dirtyBytes maintains a running count of the number of dirty bytes in `boxes` + dirtyBytes uint64 +} + +func (r *resources) shareHolding(addr basics.Address, id basics.AssetIndex) { + r.sharedHoldings[ledgercore.AccountAsset{Address: addr, Asset: id}] = struct{}{} +} + +func (r *resources) shareAccountAndHolding(addr basics.Address, id basics.AssetIndex) { + r.sharedAccounts[addr] = struct{}{} + if id != 0 { + r.sharedHoldings[ledgercore.AccountAsset{Address: addr, Asset: id}] = struct{}{} + } +} + +func (r *resources) shareLocal(addr basics.Address, id basics.AppIndex) { + r.sharedLocals[ledgercore.AccountApp{Address: addr, App: id}] = struct{}{} +} + +// In the fill* and allows* routines, we pass the header and the fields in +// separately, even though they are pointers into the same structure. That +// prevents dumb attempts to use other fields from the transaction. + +func (r *resources) fill(tx *transactions.Transaction, ep *EvalParams) { + switch tx.Type { + case protocol.PaymentTx: + r.fillPayment(&tx.Header, &tx.PaymentTxnFields) + case protocol.KeyRegistrationTx: + r.fillKeyRegistration(&tx.Header) + case protocol.AssetConfigTx: + r.fillAssetConfig(&tx.Header, &tx.AssetConfigTxnFields) + case protocol.AssetTransferTx: + r.fillAssetTransfer(&tx.Header, &tx.AssetTransferTxnFields) + case protocol.AssetFreezeTx: + r.fillAssetFreeze(&tx.Header, &tx.AssetFreezeTxnFields) + case protocol.ApplicationCallTx: + r.fillApplicationCall(ep, &tx.Header, &tx.ApplicationCallTxnFields) + case protocol.StateProofTx: + // state proof txns add nothing to availability (they can't even appear + // in a group with an appl. but still.) + default: + panic(tx.Type) + } +} + +func (cx *EvalContext) allows(tx *transactions.Transaction, calleeVer uint64) error { + switch tx.Type { + case protocol.PaymentTx, protocol.KeyRegistrationTx, protocol.AssetConfigTx: + // these transactions don't touch cross-product resources, so no error is possible + return nil + case protocol.AssetTransferTx: + return cx.allowsAssetTransfer(&tx.Header, &tx.AssetTransferTxnFields) + case protocol.AssetFreezeTx: + return cx.allowsAssetFreeze(&tx.Header, &tx.AssetFreezeTxnFields) + case protocol.ApplicationCallTx: + return cx.allowsApplicationCall(&tx.Header, &tx.ApplicationCallTxnFields, cx.version, calleeVer) + default: + return fmt.Errorf("unknown inner transaction type %s", tx.Type) + } +} + +func (r *resources) fillKeyRegistration(hdr *transactions.Header) { + r.sharedAccounts[hdr.Sender] = struct{}{} +} + +func (r *resources) fillPayment(hdr *transactions.Header, tx *transactions.PaymentTxnFields) { + r.sharedAccounts[hdr.Sender] = struct{}{} + r.sharedAccounts[tx.Receiver] = struct{}{} + if !tx.CloseRemainderTo.IsZero() { + r.sharedAccounts[tx.CloseRemainderTo] = struct{}{} + } +} + +func (r *resources) fillAssetConfig(hdr *transactions.Header, tx *transactions.AssetConfigTxnFields) { + r.sharedAccounts[hdr.Sender] = struct{}{} + if id := tx.ConfigAsset; id != 0 { + r.sharedAsas[id] = struct{}{} + } + // We don't need to read the special addresses, so they don't go in. +} + +func (r *resources) fillAssetTransfer(hdr *transactions.Header, tx *transactions.AssetTransferTxnFields) { + id := tx.XferAsset + r.sharedAsas[id] = struct{}{} + r.shareAccountAndHolding(hdr.Sender, id) + r.shareAccountAndHolding(tx.AssetReceiver, id) + + if !tx.AssetSender.IsZero() { + r.shareAccountAndHolding(tx.AssetSender, id) + } + + if !tx.AssetCloseTo.IsZero() { + r.shareAccountAndHolding(tx.AssetCloseTo, id) + } +} + +// allowsHolding checks if a holding is available under the txgroup sharing rules +func (cx *EvalContext) allowsHolding(addr basics.Address, ai basics.AssetIndex) bool { + r := cx.available + if _, ok := r.sharedHoldings[ledgercore.AccountAsset{Address: addr, Asset: ai}]; ok { + return true + } + // If an ASA was created in this group, then allow holding access for any allowed account. + for _, created := range r.createdAsas { + if created == ai { + return cx.availableAccount(addr) + } + } + // If the address was "created" by making its app in this group, then allow for available assets. + for _, created := range r.createdApps { + if cx.getApplicationAddress(created) == addr { + return cx.availableAsset(ai) + } + } + // If the current txn is a creation, the new appID won't be in r.createdApps + // yet, but it should get the same special treatment. + if cx.txn.Txn.ApplicationID == 0 && cx.getApplicationAddress(cx.appID) == addr { + return cx.availableAsset(ai) + } + return false +} + +// allowsLocals checks if a local state is available under the txgroup sharing rules +func (cx *EvalContext) allowsLocals(addr basics.Address, ai basics.AppIndex) bool { + r := cx.available + if _, ok := r.sharedLocals[ledgercore.AccountApp{Address: addr, App: ai}]; ok { + return true + } + // All locals of created apps are available + for _, created := range r.createdApps { + if created == ai { + return cx.availableAccount(addr) + } + } + if cx.txn.Txn.ApplicationID == 0 && cx.appID == ai { + return cx.availableAccount(addr) + } + + // All locals of created app accounts are available + for _, created := range r.createdApps { + if cx.getApplicationAddress(created) == addr { + return cx.availableApp(ai) + } + } + if cx.txn.Txn.ApplicationID == 0 && cx.getApplicationAddress(cx.appID) == addr { + return cx.availableApp(ai) + } + return false +} + +func (cx *EvalContext) requireHolding(acct basics.Address, id basics.AssetIndex) error { + /* Previous versions allowed inner appls with zeros in "required" places, + even if that 0 resource should have be inaccessible, because the check + was done at itxn_field time, and maybe the app simply didn't set the + field. */ + if id == 0 || acct.IsZero() { + return nil + } + if !cx.allowsHolding(acct, id) { + return fmt.Errorf("invalid Holding access %s x %d would be possible", acct, id) + } + return nil +} + +func (cx *EvalContext) requireLocals(acct basics.Address, id basics.AppIndex) error { + if !cx.allowsLocals(acct, id) { + return fmt.Errorf("invalid Local State access %s x %d would be possible", acct, id) + } + return nil +} + +func (cx *EvalContext) allowsAssetTransfer(hdr *transactions.Header, tx *transactions.AssetTransferTxnFields) error { + err := cx.requireHolding(hdr.Sender, tx.XferAsset) + if err != nil { + return fmt.Errorf("axfer Sender: %w", err) + } + err = cx.requireHolding(tx.AssetReceiver, tx.XferAsset) + if err != nil { + return fmt.Errorf("axfer AssetReceiver: %w", err) + } + err = cx.requireHolding(tx.AssetSender, tx.XferAsset) + if err != nil { + return fmt.Errorf("axfer AssetSender: %w", err) + } + err = cx.requireHolding(tx.AssetCloseTo, tx.XferAsset) + if err != nil { + return fmt.Errorf("axfer AssetCloseTo: %w", err) + } + return nil +} + +func (r *resources) fillAssetFreeze(hdr *transactions.Header, tx *transactions.AssetFreezeTxnFields) { + r.sharedAccounts[hdr.Sender] = struct{}{} + id := tx.FreezeAsset + r.sharedAsas[id] = struct{}{} + r.shareAccountAndHolding(tx.FreezeAccount, id) +} + +func (cx *EvalContext) allowsAssetFreeze(hdr *transactions.Header, tx *transactions.AssetFreezeTxnFields) error { + err := cx.requireHolding(tx.FreezeAccount, tx.FreezeAsset) + if err != nil { + return fmt.Errorf("afrz FreezeAccount: %w", err) + } + return nil +} + +func (r *resources) fillApplicationCall(ep *EvalParams, hdr *transactions.Header, tx *transactions.ApplicationCallTxnFields) { + txAccounts := make([]basics.Address, 0, 2+len(tx.Accounts)+len(tx.ForeignApps)) + txAccounts = append(txAccounts, hdr.Sender) + txAccounts = append(txAccounts, tx.Accounts...) + for _, id := range tx.ForeignAssets { + r.sharedAsas[id] = struct{}{} + } + // Make the app account associated with app calls available. We + // don't have to add code to make the accounts of freshly created + // apps available, because that is already handled by looking at + // `createdApps`. + if id := tx.ApplicationID; id != 0 { + txAccounts = append(txAccounts, ep.getApplicationAddress(id)) + r.sharedApps[id] = struct{}{} + } + for _, id := range tx.ForeignApps { + txAccounts = append(txAccounts, ep.getApplicationAddress(id)) + r.sharedApps[id] = struct{}{} + } + for _, address := range txAccounts { + r.sharedAccounts[address] = struct{}{} + + for _, id := range tx.ForeignAssets { + r.shareHolding(address, id) + } + // Similar to note about app accounts, availableLocals allows + // all createdApps holdings, so we don't care if id == 0 here. + if id := tx.ApplicationID; id != 0 { + r.shareLocal(address, id) + } + for _, id := range tx.ForeignApps { + r.shareLocal(address, id) + } + } + + for _, br := range tx.Boxes { + var app basics.AppIndex + if br.Index == 0 { + // "current app": Ignore if this is a create, else use ApplicationID + if tx.ApplicationID == 0 { + // When the create actually happens, and we learn the appID, we'll add it. + continue + } + app = tx.ApplicationID + } else { + // Bounds check will already have been done by + // WellFormed. For testing purposes, it's better to panic + // now than after returning a nil. + app = tx.ForeignApps[br.Index-1] // shift for the 0=this convention + } + r.boxes[boxRef{app, string(br.Name)}] = false + } +} + +func (cx *EvalContext) allowsApplicationCall(hdr *transactions.Header, tx *transactions.ApplicationCallTxnFields, callerVer, calleeVer uint64) error { + // If an old (pre resource sharing) app is being called from an app that has + // resource sharing enabled, we need to confirm that no new "cross-product" + // resources have become available. + if callerVer < sharedResourcesVersion || calleeVer >= sharedResourcesVersion { + return nil + } + + // This should closely match the `fillApplicationCall` routine, as the idea + // is to find all of the cross product resources this attempted call will + // have access to, and check that they are already available. + txAccounts := make([]basics.Address, 0, 2+len(tx.Accounts)+len(tx.ForeignApps)) + txAccounts = append(txAccounts, hdr.Sender) + txAccounts = append(txAccounts, tx.Accounts...) + if id := tx.ApplicationID; id != 0 { + txAccounts = append(txAccounts, cx.getApplicationAddress(id)) + } + for _, id := range tx.ForeignApps { + txAccounts = append(txAccounts, cx.getApplicationAddress(id)) + } + for _, address := range txAccounts { + for _, id := range tx.ForeignAssets { + err := cx.requireHolding(address, id) + if err != nil { + return fmt.Errorf("appl ForeignAssets: %w", err) + } + } + if id := tx.ApplicationID; id != 0 { + err := cx.requireLocals(address, id) + if err != nil { + return fmt.Errorf("appl ApplicationID: %w", err) + } + } + for _, id := range tx.ForeignApps { + err := cx.requireLocals(address, id) + if err != nil { + return fmt.Errorf("appl ForeignApps: %w", err) + } + } + } + return nil +} diff --git a/data/transactions/logic/resources_test.go b/data/transactions/logic/resources_test.go new file mode 100644 index 0000000000..01f784c12b --- /dev/null +++ b/data/transactions/logic/resources_test.go @@ -0,0 +1,909 @@ +// Copyright (C) 2019-2023 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package logic_test + +import ( + "encoding/binary" + "fmt" + "strings" + "testing" + + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/data/transactions" + "github.com/algorand/go-algorand/data/transactions/logic" + "github.com/algorand/go-algorand/data/txntest" + "github.com/algorand/go-algorand/protocol" + "github.com/algorand/go-algorand/test/partitiontest" + "github.com/stretchr/testify/require" +) + +// TestAppSharing confirms that as of v9, apps can be accessed across groups, +// but that before then, they could not. +func TestAppSharing(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + // Create some sample transactions. The main reason this a blackbox test + // (_test package) is to have access to txntest. + appl0 := txntest.Txn{ + Type: protocol.ApplicationCallTx, + ApplicationID: 900, + Sender: basics.Address{1, 2, 3, 4}, + ForeignApps: []basics.AppIndex{500}, + } + + appl1 := txntest.Txn{ + Type: protocol.ApplicationCallTx, + ApplicationID: 901, + Sender: basics.Address{4, 3, 2, 1}, + } + + appl2 := txntest.Txn{ + Type: protocol.ApplicationCallTx, + ApplicationID: 902, + Sender: basics.Address{1, 2, 3, 4}, + } + + getSchema := ` +int 500 +app_params_get AppGlobalNumByteSlice +!; assert; pop; int 1 +` + sources := []string{getSchema, getSchema} + // 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, sources, txntest.Group(&appl0, &appl1), 8, nil, + logic.NewExpect(1, "invalid App reference 500")) + // In v9, the second can, because the first can. + logic.TestApps(t, sources, txntest.Group(&appl0, &appl1), 9, nil) + + getLocalEx := ` +txn Sender +int 500 +byte "some-key" +app_local_get_ex +pop; pop; int 1 +` + + sources = []string{getLocalEx, getLocalEx} + // In contrast, here there's no help from v9, because the second tx is + // reading the locals for a different account. + + // app_local_get* requires the address and the app exist, else the program fails + logic.TestApps(t, sources, txntest.Group(&appl0, &appl1), 8, nil, + logic.NewExpect(0, "no account")) + + _, _, ledger := logic.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, sources, txntest.Group(&appl0, &appl1), 9, ledger, + logic.NewExpect(1, "invalid Local State access")) + + // 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, sources, txntest.Group(&appl0, &appl2), 9, ledger) + logic.TestApps(t, sources, txntest.Group(&appl0, &appl2), 8, ledger, // version 8 does not get sharing + logic.NewExpect(1, "invalid App reference 500")) + + // Checking if an account is opted in has pretty much the same rules + optInCheck500 := ` +txn Sender +int 500 +app_opted_in +` + + sources = []string{optInCheck500, optInCheck500} + // app_opted_in requires the address and the app exist, else the program fails + logic.TestApps(t, sources, txntest.Group(&appl0, &appl1), 9, nil, // nil ledger, no account + logic.NewExpect(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, sources, txntest.Group(&appl0, &appl1), 9, ledger, + logic.NewExpect(1, "invalid Local State access "+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, sources, txntest.Group(&appl0, &appl2), 9, ledger) + logic.TestApps(t, sources, txntest.Group(&appl0, &appl2), 8, ledger, // version 8 does not get sharing + logic.NewExpect(1, "invalid App reference 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 +` + sources = []string{optInCheck900, optInCheck900} + // as above, appl1 can't see the local state, but appl2 can b/c sender is same as appl0 + logic.TestApps(t, sources, txntest.Group(&appl0, &appl1), 9, ledger, + logic.NewExpect(1, "invalid Local State access "+appl1.Sender.String())) + logic.TestApps(t, sources, txntest.Group(&appl0, &appl2), 9, ledger) + logic.TestApps(t, sources, txntest.Group(&appl0, &appl2), 8, ledger, // version 8 does not get sharing + logic.NewExpect(1, "invalid App reference 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 + // properly in EvalDelta. + putLocal := ` +txn ApplicationArgs 0 +byte "X" +int 74 +app_local_put +int 1 +` + noop := `int 1` + sources = []string{noop, putLocal} + appl1.ApplicationArgs = [][]byte{appl0.Sender[:]} // tx1 will try to modify local state exposed in tx0 + logic.TestApps(t, sources, txntest.Group(&appl0, &appl1), 9, ledger, + logic.NewExpect(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) + require.Len(t, ep.TxnGroup, 2) + ed := ep.TxnGroup[1].ApplyData.EvalDelta + require.Equal(t, map[uint64]basics.StateDelta{ + 1: { // no tx.Accounts, 1 indicates first in SharedAccts + "X": { + Action: basics.SetUintAction, + Uint: 74, + }, + }, + }, ed.LocalDeltas) + require.Len(t, ed.SharedAccts, 1) + require.Equal(t, ep.TxnGroup[0].Txn.Sender, ed.SharedAccts[0]) + + // 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.NewExpect(2, "invalid Local State access")) // 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.NewExpect(2, "invalid Local State access")) // note that the error message is for Locals, not specialized +} + +// TestBetterLocalErrors confirms that we get specific errors about the missing +// address or app when accessing a Local State with only one available. +func TestBetterLocalErrors(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + joe := basics.Address{9, 9, 9} + + ep, tx, ledger := logic.MakeSampleEnv() + ledger.NewAccount(joe, 5000000) + ledger.NewApp(joe, 500, basics.AppParams{}) + ledger.NewLocals(joe, 500) + + getLocalEx := ` +txn ApplicationArgs 0 +txn ApplicationArgs 1; btoi +byte "some-key" +app_local_get_ex +pop; pop; int 1 +` + app := make([]byte, 8) + binary.BigEndian.PutUint64(app, 500) + + tx.ApplicationArgs = [][]byte{joe[:], app} + logic.TestApp(t, getLocalEx, ep, "invalid Local State") + tx.Accounts = []basics.Address{joe} + logic.TestApp(t, getLocalEx, ep, "invalid App reference 500") + tx.ForeignApps = []basics.AppIndex{500} + logic.TestApp(t, getLocalEx, ep) + binary.BigEndian.PutUint64(tx.ApplicationArgs[1], 500) + logic.TestApp(t, getLocalEx, ep) + binary.BigEndian.PutUint64(tx.ApplicationArgs[1], 501) + logic.TestApp(t, getLocalEx, ep, "invalid App reference 501") + + binary.BigEndian.PutUint64(tx.ApplicationArgs[1], 500) + tx.Accounts = []basics.Address{} + logic.TestApp(t, getLocalEx, ep, "invalid Account reference "+joe.String()) +} + +// TestAssetSharing confirms that as of v9, assets can be accessed across +// groups, but that before then, they could not. +func TestAssetSharing(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + // Create some sample transactions. The main reason this a blackbox test + // (_test package) is to have access to txntest. + appl0 := txntest.Txn{ + Type: protocol.ApplicationCallTx, + Sender: basics.Address{1, 2, 3, 4}, + ForeignAssets: []basics.AssetIndex{400}, + } + + appl1 := txntest.Txn{ + Type: protocol.ApplicationCallTx, + Sender: basics.Address{4, 3, 2, 1}, + } + + appl2 := txntest.Txn{ + Type: protocol.ApplicationCallTx, + Sender: basics.Address{1, 2, 3, 4}, + } + + getTotal := ` +int 400 +asset_params_get AssetTotal +pop; pop; int 1 +` + sources := []string{getTotal, getTotal} + // In v8, the first tx can read asset 400, because it's in its foreign array, + // but the second can't + logic.TestApps(t, sources, txntest.Group(&appl0, &appl1), 8, nil, + logic.NewExpect(1, "invalid Asset reference 400")) + // In v9, the second can, because the first can. + logic.TestApps(t, sources, txntest.Group(&appl0, &appl1), 9, nil) + + getBalance := ` +txn Sender +int 400 +asset_holding_get AssetBalance +pop; pop; int 1 +` + + sources = []string{getBalance, getBalance} + // In contrast, here there's no help from v9, because the second tx is + // reading a holding for a different account. + logic.TestApps(t, sources, txntest.Group(&appl0, &appl1), 8, nil, + logic.NewExpect(1, "invalid Asset reference 400")) + logic.TestApps(t, sources, txntest.Group(&appl0, &appl1), 9, nil, + logic.NewExpect(1, "invalid Holding access")) + // 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, sources, 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.NewExpect(2, "invalid Holding access")) // 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.NewExpect(2, "invalid Holding access")) // note that the error message is for Holding, not specialized +} + +// TestBetterHoldingErrors confirms that we get specific errors about the missing +// address or asa when accessesing a holding with only one available. +func TestBetterHoldingErrors(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + joe := basics.Address{9, 9, 9} + + ep, tx, ledger := logic.MakeSampleEnv() + ledger.NewAccount(joe, 5000000) + ledger.NewAsset(joe, 200, basics.AssetParams{}) + // as creator, joe will also be opted in + + getHoldingBalance := ` +txn ApplicationArgs 0 +txn ApplicationArgs 1; btoi +asset_holding_get AssetBalance +pop; pop; int 1 +` + asa := make([]byte, 8) + binary.BigEndian.PutUint64(asa, 200) + + tx.ApplicationArgs = [][]byte{joe[:], asa} + logic.TestApp(t, getHoldingBalance, ep, "invalid Holding access "+joe.String()) + tx.Accounts = []basics.Address{joe} + logic.TestApp(t, getHoldingBalance, ep, "invalid Asset reference 200") + tx.ForeignAssets = []basics.AssetIndex{200} + logic.TestApp(t, getHoldingBalance, ep) + binary.BigEndian.PutUint64(tx.ApplicationArgs[1], 0) + logic.TestApp(t, getHoldingBalance, ep, "invalid Asset reference 0") // slots not allowed + + binary.BigEndian.PutUint64(tx.ApplicationArgs[1], 200) + tx.Accounts = []basics.Address{} + logic.TestApp(t, getHoldingBalance, ep, "invalid Account reference "+joe.String()) +} + +// TestAccountPassing checks that the current app account and foreign app's +// accounts can be passed in txn.Accounts for a called app. +func TestAccountPassing(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + // appAddressVersion=7 + logic.TestLogicRange(t, 7, 0, func(t *testing.T, ep *logic.EvalParams, tx *transactions.Transaction, ledger *logic.Ledger) { + t.Parallel() + accept := logic.TestProg(t, "int 1", 6) + alice := basics.Address{1, 1, 1, 1, 1} + ledger.NewApp(alice, 4, basics.AppParams{ + ApprovalProgram: accept.Program, + }) + callWithAccount := ` +itxn_begin + int appl; itxn_field TypeEnum + int 4; itxn_field ApplicationID + %s; itxn_field Accounts +itxn_submit +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, + "invalid Account reference AAAAA") + // Now show we can pass our own address + logic.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) + }) +} + +// TestOtherTxSharing tests resource sharing across other kinds of transactions besides appl. +func TestOtherTxSharing(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + _, _, ledger := logic.MakeSampleEnv() + + senderAcct := basics.Address{1, 2, 3, 4, 5, 6, 1} + ledger.NewAccount(senderAcct, 2001) + senderBalance := "txn ApplicationArgs 0; balance; int 2001; ==" + + receiverAcct := basics.Address{1, 2, 3, 4, 5, 6, 2} + ledger.NewAccount(receiverAcct, 2002) + receiverBalance := "txn ApplicationArgs 0; balance; int 2002; ==" + + otherAcct := basics.Address{1, 2, 3, 4, 5, 6, 3} + ledger.NewAccount(otherAcct, 2003) + otherBalance := "txn ApplicationArgs 0; balance; int 2003; ==" + + other2Acct := basics.Address{1, 2, 3, 4, 5, 6, 4} + ledger.NewAccount(other2Acct, 2004) + other2Balance := "txn ApplicationArgs 0; balance; int 2004; ==" + + appl := txntest.Txn{ + Type: protocol.ApplicationCallTx, + Sender: basics.Address{5, 5, 5, 5}, // different from all other accounts used + ApplicationArgs: [][]byte{senderAcct[:]}, + } + + keyreg := txntest.Txn{ + Type: protocol.KeyRegistrationTx, + Sender: senderAcct, + } + pay := txntest.Txn{ + Type: protocol.PaymentTx, + Sender: senderAcct, + Receiver: receiverAcct, + } + acfg := txntest.Txn{ + Type: protocol.AssetConfigTx, + Sender: senderAcct, + AssetParams: basics.AssetParams{ + Manager: otherAcct, // other is here to show they _don't_ become available + Reserve: otherAcct, + Freeze: otherAcct, + Clawback: otherAcct, + }, + } + axfer := txntest.Txn{ + Type: protocol.AssetTransferTx, + XferAsset: 100, // must be < 256, later code assumes it fits in a byte + Sender: senderAcct, + AssetReceiver: receiverAcct, + AssetSender: otherAcct, + } + afrz := txntest.Txn{ + Type: protocol.AssetFreezeTx, + FreezeAsset: 200, // must be < 256, later code assumes it fits in a byte + Sender: senderAcct, + FreezeAccount: otherAcct, + } + + sources := []string{"", senderBalance} + rsources := []string{senderBalance, ""} + for _, send := range []txntest.Txn{keyreg, pay, acfg, axfer, afrz} { + logic.TestApps(t, sources, txntest.Group(&send, &appl), 9, ledger) + logic.TestApps(t, rsources, txntest.Group(&appl, &send), 9, ledger) + + logic.TestApps(t, sources, txntest.Group(&send, &appl), 8, ledger, + logic.NewExpect(1, "invalid Account reference")) + logic.TestApps(t, rsources, txntest.Group(&appl, &send), 8, ledger, + logic.NewExpect(0, "invalid Account reference")) + } + + holdingAccess := ` + txn ApplicationArgs 0 + txn ApplicationArgs 1; btoi + asset_holding_get AssetBalance + pop; pop; int 1 +` + sources = []string{"", holdingAccess} + rsources = []string{holdingAccess, ""} + + t.Run("keyreg", func(t *testing.T) { + appl.ApplicationArgs = [][]byte{senderAcct[:], {200}} + logic.TestApps(t, sources, txntest.Group(&keyreg, &appl), 9, ledger, + logic.NewExpect(1, "invalid Asset reference 200")) + withRef := appl + withRef.ForeignAssets = []basics.AssetIndex{200} + logic.TestApps(t, sources, txntest.Group(&keyreg, &withRef), 9, ledger, + logic.NewExpect(1, "invalid Holding access "+senderAcct.String())) + }) + t.Run("pay", func(t *testing.T) { + // The receiver is available for algo balance reading + appl.ApplicationArgs = [][]byte{receiverAcct[:]} + logic.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.NewExpect(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) + }) + + t.Run("acfg", func(t *testing.T) { + // 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.NewExpect(1, "invalid Account reference "+otherAcct.String())) + }) + + t.Run("axfer", func(t *testing.T) { + // The receiver is also available for algo balance reading + appl.ApplicationArgs = [][]byte{receiverAcct[:]} + logic.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) + + // sender holding is available + appl.ApplicationArgs = [][]byte{senderAcct[:], {byte(axfer.XferAsset)}} + logic.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) + + // asset sender (other) account is available + appl.ApplicationArgs = [][]byte{otherAcct[:], {byte(axfer.XferAsset)}} + logic.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.NewExpect(1, "invalid Account reference "+other2Acct.String())) + logic.TestApps(t, []string{"", holdingAccess}, txntest.Group(&axfer, &appl), 9, ledger, + logic.NewExpect(1, "invalid Account reference "+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) + }) + + t.Run("afrz", func(t *testing.T) { + // 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) + + // 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.NewExpect(1, "invalid Holding access "+senderAcct.String())) + }) +} + +// TestSharedInnerTxns checks how inner txns access resources. +func TestSharedInnerTxns(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + _, _, ledger := logic.MakeSampleEnv() + + const asa1 = 201 + const asa2 = 202 + + senderAcct := basics.Address{1, 2, 3, 4, 5, 6, 1} + ledger.NewAccount(senderAcct, 2001) + ledger.NewHolding(senderAcct, asa1, 1, false) + + receiverAcct := basics.Address{1, 2, 3, 4, 5, 6, 2} + ledger.NewAccount(receiverAcct, 2002) + ledger.NewHolding(receiverAcct, asa1, 1, false) + + otherAcct := basics.Address{1, 2, 3, 4, 5, 6, 3} + ledger.NewAccount(otherAcct, 2003) + ledger.NewHolding(otherAcct, asa1, 1, false) + + unusedAcct := basics.Address{1, 2, 3, 4, 5, 6, 4} + + payToArg := ` +itxn_begin + int pay; itxn_field TypeEnum + int 100; itxn_field Amount + txn ApplicationArgs 0; itxn_field Receiver +itxn_submit +int 1 +` + axferToArgs := ` +itxn_begin + int axfer; itxn_field TypeEnum + int 2; itxn_field AssetAmount + txn ApplicationArgs 0; itxn_field AssetReceiver + txn ApplicationArgs 1; btoi; itxn_field XferAsset +itxn_submit +int 1 +` + + acfgArg := ` +itxn_begin + int acfg; itxn_field TypeEnum + txn ApplicationArgs 0; btoi; itxn_field ConfigAsset +itxn_submit +int 1 +` + + appl := txntest.Txn{ + Type: protocol.ApplicationCallTx, + ApplicationID: 1234, + Sender: basics.Address{5, 5, 5, 5}, // different from all other accounts used + } + appAcct := appl.ApplicationID.Address() + // App will do a lot of txns. Start well funded. + ledger.NewAccount(appAcct, 1_000_000) + // And needs some ASAs for inner axfer testing + ledger.NewHolding(appAcct, asa1, 1_000_000, false) + + t.Run("keyreg", func(t *testing.T) { + keyreg := txntest.Txn{ + Type: protocol.KeyRegistrationTx, + Sender: senderAcct, + } + + // 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.NewExpect(1, "invalid Account reference "+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.NewExpect(1, "invalid Account reference "+receiverAcct.String())) + }) + + t.Run("pay", func(t *testing.T) { + pay := txntest.Txn{ + Type: protocol.PaymentTx, + Sender: senderAcct, + Receiver: receiverAcct, + } + + // 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.NewExpect(1, "invalid Account reference "+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.NewExpect(1, "invalid Account reference "+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.NewExpect(1, "invalid Account reference "+otherAcct.String())) + }) + + t.Run("axfer", func(t *testing.T) { + axfer := txntest.Txn{ + Type: protocol.AssetTransferTx, + XferAsset: asa1, + Sender: senderAcct, + AssetReceiver: receiverAcct, + AssetSender: otherAcct, + } + + // 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.NewExpect(1, "invalid Account reference "+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.NewExpect(1, "invalid Holding access")) + // 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.NewExpect(0, "invalid Holding access")) + // 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.NewExpect(1, "invalid Holding access")) + + // 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) + appl.ApplicationArgs = [][]byte{receiverAcct[:], {asa1}} + logic.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) + + // but can't axfer a different asset + appl.ApplicationArgs = [][]byte{senderAcct[:], {asa2}} + logic.TestApps(t, []string{"", axferToArgs}, txntest.Group(&axfer, &appl), 9, ledger, + logic.NewExpect(1, fmt.Sprintf("invalid Asset reference %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.NewExpect(1, "invalid Account reference")) + + // 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) + appl.ApplicationArgs = [][]byte{{asa2}} // but not asa2 + logic.TestApps(t, []string{"", acfgArg}, txntest.Group(&axfer, &appl), 9, ledger, + logic.NewExpect(1, fmt.Sprintf("invalid Asset reference %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 + // access to that payer's holding.) + payAcct := basics.Address{3, 2, 3, 2, 3, 2} + pay := txntest.Txn{ + Type: protocol.PaymentTx, + Sender: payAcct, + Receiver: payAcct, + } + // 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) + // payAcct (the pay sender) is payable + appl.ApplicationArgs = [][]byte{payAcct[:]} + logic.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.NewExpect(2, "invalid Holding access "+payAcct.String())) + }) + + t.Run("afrz", func(t *testing.T) { + appl.ForeignAssets = []basics.AssetIndex{} // reset after previous tests + afrz := txntest.Txn{ + Type: protocol.AssetFreezeTx, + FreezeAsset: asa1, + Sender: senderAcct, + FreezeAccount: otherAcct, + } + + // appl can pay to the sender & freeze account + appl.ApplicationArgs = [][]byte{senderAcct[:], {asa1}} + logic.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) + + // 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.NewExpect(1, "invalid Holding access "+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.NewExpect(1, "invalid Holding access "+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.NewExpect(0, "invalid Account reference "+receiverAcct.String())) + logic.TestApps(t, []string{axferToArgs}, txntest.Group(&appl, &afrz), 9, ledger, + logic.NewExpect(0, "invalid Account reference "+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) + + // 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.NewExpect(1, fmt.Sprintf("invalid Asset reference %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.NewExpect(1, "invalid Holding access "+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) + appl.ApplicationArgs = [][]byte{{asa2}} // but not asa2 + logic.TestApps(t, []string{"", acfgArg}, txntest.Group(&afrz, &appl), 9, ledger, + logic.NewExpect(1, fmt.Sprintf("invalid Asset reference %d", asa2))) + + }) + + t.Run("appl", func(t *testing.T) { + appl.ForeignAssets = []basics.AssetIndex{} // reset after previous test + appl.Accounts = []basics.Address{} // reset after previous tests + appl0 := txntest.Txn{ + Type: protocol.ApplicationCallTx, + Sender: senderAcct, + Accounts: []basics.Address{otherAcct}, + ForeignAssets: []basics.AssetIndex{asa1}, + } + + // 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.NewExpect(1, "invalid Account reference "+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.NewExpect(1, "axfer Sender: invalid 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) + + // 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.NewExpect(1, "invalid Asset reference 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.NewExpect(1, "axfer AssetReceiver: invalid Holding access "+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) + ledger.NewApp(senderAcct, 88, basics.AppParams{ApprovalProgram: payToArgV8.Program}) + ledger.NewAccount(appAddr(88), 1_000_000) + payToArgV9 := logic.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) + ledger.NewApp(senderAcct, 11, basics.AppParams{ApprovalProgram: approvalV8.Program}) + + innerCallTemplate := ` +itxn_begin +int appl; itxn_field TypeEnum; +txn ApplicationArgs 0; btoi; itxn_field ApplicationID +txn ApplicationArgs 1; itxn_field ApplicationArgs +txn ApplicationArgs 2; itxn_field ApplicationArgs +%s +itxn_submit +int 1 +` + innerCall := fmt.Sprintf(innerCallTemplate, "") + + appl.ForeignApps = []basics.AppIndex{11, 88, 99} + + appl.ApplicationArgs = [][]byte{{99}, otherAcct[:], {asa1}} + logic.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.NewExpect(1, "invalid Account reference "+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.NewExpect(1, "appl ApplicationID: invalid Local State access "+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.NewExpect(1, "appl ApplicationID: invalid Local State access "+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) + + // 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 + // to that local state. (this is confirming we check the cross product + // 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.NewExpect(1, "appl ForeignApps: invalid Local State access "+otherAcct.String())) + + }) + +} + +// TestAccessMyLocals confirms that apps can access their OWN locals if they opt +// in at creation time. +func TestAccessMyLocals(t *testing.T) { + partitiontest.PartitionTest(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) { + 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 + // OptInOC below, we must manually opt the sender into the app that + // will get created for this test. + ledger.NewLocals(sender, 888) + + *tx = txntest.Txn{ + Type: protocol.ApplicationCallTx, + Sender: sender, + ApplicationID: 0, + OnCompletion: transactions.OptInOC, + LocalStateSchema: basics.StateSchema{ + NumUint: 1, + }, + }.Txn() + source := ` + int 0 + byte "X" + app_local_get + ! + assert + int 0 + byte "X" + int 7 + app_local_put + int 0 + byte "X" + app_local_get + int 7 + == +` + if ep.Proto.LogicSigVersion >= 9 { + source = strings.ReplaceAll(source, "int 0\n", "txn Sender\n") + } + logic.TestApp(t, source, ep) + + // They can also see that they are opted in, though it's a weird question to ask. + if ep.Proto.LogicSigVersion >= 9 { + logic.TestApp(t, "txn Sender; int 0; app_opted_in", ep) + } else { + logic.TestApp(t, "int 0; int 0; app_opted_in", ep) + } + }) +} diff --git a/data/transactions/msgp_gen.go b/data/transactions/msgp_gen.go index 641e541c7b..053330615f 100644 --- a/data/transactions/msgp_gen.go +++ b/data/transactions/msgp_gen.go @@ -1875,33 +1875,37 @@ func (z *BoxRef) MsgIsZero() bool { func (z *EvalDelta) MarshalMsg(b []byte) (o []byte) { o = msgp.Require(b, z.Msgsize()) // omitempty: check for empty values - zb0005Len := uint32(4) - var zb0005Mask uint8 /* 5 bits */ + zb0006Len := uint32(5) + var zb0006Mask uint8 /* 6 bits */ if (*z).GlobalDelta.MsgIsZero() { - zb0005Len-- - zb0005Mask |= 0x2 + zb0006Len-- + zb0006Mask |= 0x2 } if len((*z).InnerTxns) == 0 { - zb0005Len-- - zb0005Mask |= 0x4 + zb0006Len-- + zb0006Mask |= 0x4 } if len((*z).LocalDeltas) == 0 { - zb0005Len-- - zb0005Mask |= 0x8 + zb0006Len-- + zb0006Mask |= 0x8 } if len((*z).Logs) == 0 { - zb0005Len-- - zb0005Mask |= 0x10 + zb0006Len-- + zb0006Mask |= 0x10 + } + if len((*z).SharedAccts) == 0 { + zb0006Len-- + zb0006Mask |= 0x20 } - // variable map header, size zb0005Len - o = append(o, 0x80|uint8(zb0005Len)) - if zb0005Len != 0 { - if (zb0005Mask & 0x2) == 0 { // if not empty + // variable map header, size zb0006Len + o = append(o, 0x80|uint8(zb0006Len)) + if zb0006Len != 0 { + if (zb0006Mask & 0x2) == 0 { // if not empty // string "gd" o = append(o, 0xa2, 0x67, 0x64) o = (*z).GlobalDelta.MarshalMsg(o) } - if (zb0005Mask & 0x4) == 0 { // if not empty + if (zb0006Mask & 0x4) == 0 { // if not empty // string "itx" o = append(o, 0xa3, 0x69, 0x74, 0x78) if (*z).InnerTxns == nil { @@ -1909,11 +1913,11 @@ func (z *EvalDelta) MarshalMsg(b []byte) (o []byte) { } else { o = msgp.AppendArrayHeader(o, uint32(len((*z).InnerTxns))) } - for zb0004 := range (*z).InnerTxns { - o = (*z).InnerTxns[zb0004].MarshalMsg(o) + for zb0005 := range (*z).InnerTxns { + o = (*z).InnerTxns[zb0005].MarshalMsg(o) } } - if (zb0005Mask & 0x8) == 0 { // if not empty + if (zb0006Mask & 0x8) == 0 { // if not empty // string "ld" o = append(o, 0xa2, 0x6c, 0x64) if (*z).LocalDeltas == nil { @@ -1933,7 +1937,7 @@ func (z *EvalDelta) MarshalMsg(b []byte) (o []byte) { o = zb0002.MarshalMsg(o) } } - if (zb0005Mask & 0x10) == 0 { // if not empty + if (zb0006Mask & 0x10) == 0 { // if not empty // string "lg" o = append(o, 0xa2, 0x6c, 0x67) if (*z).Logs == nil { @@ -1941,8 +1945,20 @@ func (z *EvalDelta) MarshalMsg(b []byte) (o []byte) { } else { o = msgp.AppendArrayHeader(o, uint32(len((*z).Logs))) } - for zb0003 := range (*z).Logs { - o = msgp.AppendString(o, (*z).Logs[zb0003]) + for zb0004 := range (*z).Logs { + o = msgp.AppendString(o, (*z).Logs[zb0004]) + } + } + if (zb0006Mask & 0x20) == 0 { // if not empty + // string "sa" + o = append(o, 0xa2, 0x73, 0x61) + if (*z).SharedAccts == nil { + o = msgp.AppendNil(o) + } else { + o = msgp.AppendArrayHeader(o, uint32(len((*z).SharedAccts))) + } + for zb0003 := range (*z).SharedAccts { + o = (*z).SharedAccts[zb0003].MarshalMsg(o) } } } @@ -1958,46 +1974,46 @@ func (_ *EvalDelta) CanMarshalMsg(z interface{}) bool { func (z *EvalDelta) UnmarshalMsg(bts []byte) (o []byte, err error) { var field []byte _ = field - var zb0005 int - var zb0006 bool - zb0005, zb0006, bts, err = msgp.ReadMapHeaderBytes(bts) + var zb0006 int + var zb0007 bool + zb0006, zb0007, bts, err = msgp.ReadMapHeaderBytes(bts) if _, ok := err.(msgp.TypeError); ok { - zb0005, zb0006, bts, err = msgp.ReadArrayHeaderBytes(bts) + zb0006, zb0007, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err) return } - if zb0005 > 0 { - zb0005-- + if zb0006 > 0 { + zb0006-- bts, err = (*z).GlobalDelta.UnmarshalMsg(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "GlobalDelta") return } } - if zb0005 > 0 { - zb0005-- - var zb0007 int - var zb0008 bool - zb0007, zb0008, bts, err = msgp.ReadMapHeaderBytes(bts) + if zb0006 > 0 { + zb0006-- + var zb0008 int + var zb0009 bool + zb0008, zb0009, bts, err = msgp.ReadMapHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "LocalDeltas") return } - if zb0007 > config.MaxEvalDeltaAccounts { - err = msgp.ErrOverflow(uint64(zb0007), uint64(config.MaxEvalDeltaAccounts)) + if zb0008 > config.MaxEvalDeltaAccounts { + err = msgp.ErrOverflow(uint64(zb0008), uint64(config.MaxEvalDeltaAccounts)) err = msgp.WrapError(err, "struct-from-array", "LocalDeltas") return } - if zb0008 { + if zb0009 { (*z).LocalDeltas = nil } else if (*z).LocalDeltas == nil { - (*z).LocalDeltas = make(map[uint64]basics.StateDelta, zb0007) + (*z).LocalDeltas = make(map[uint64]basics.StateDelta, zb0008) } - for zb0007 > 0 { + for zb0008 > 0 { var zb0001 uint64 var zb0002 basics.StateDelta - zb0007-- + zb0008-- zb0001, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "LocalDeltas") @@ -2011,66 +2027,95 @@ func (z *EvalDelta) UnmarshalMsg(bts []byte) (o []byte, err error) { (*z).LocalDeltas[zb0001] = zb0002 } } - if zb0005 > 0 { - zb0005-- - var zb0009 int - var zb0010 bool - zb0009, zb0010, bts, err = msgp.ReadArrayHeaderBytes(bts) + if zb0006 > 0 { + zb0006-- + var zb0010 int + var zb0011 bool + zb0010, zb0011, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "SharedAccts") + return + } + if zb0010 > config.MaxEvalDeltaAccounts { + err = msgp.ErrOverflow(uint64(zb0010), uint64(config.MaxEvalDeltaAccounts)) + err = msgp.WrapError(err, "struct-from-array", "SharedAccts") + return + } + if zb0011 { + (*z).SharedAccts = nil + } else if (*z).SharedAccts != nil && cap((*z).SharedAccts) >= zb0010 { + (*z).SharedAccts = ((*z).SharedAccts)[:zb0010] + } else { + (*z).SharedAccts = make([]basics.Address, zb0010) + } + for zb0003 := range (*z).SharedAccts { + bts, err = (*z).SharedAccts[zb0003].UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "SharedAccts", zb0003) + return + } + } + } + if zb0006 > 0 { + zb0006-- + var zb0012 int + var zb0013 bool + zb0012, zb0013, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Logs") return } - if zb0009 > config.MaxLogCalls { - err = msgp.ErrOverflow(uint64(zb0009), uint64(config.MaxLogCalls)) + if zb0012 > config.MaxLogCalls { + err = msgp.ErrOverflow(uint64(zb0012), uint64(config.MaxLogCalls)) err = msgp.WrapError(err, "struct-from-array", "Logs") return } - if zb0010 { + if zb0013 { (*z).Logs = nil - } else if (*z).Logs != nil && cap((*z).Logs) >= zb0009 { - (*z).Logs = ((*z).Logs)[:zb0009] + } else if (*z).Logs != nil && cap((*z).Logs) >= zb0012 { + (*z).Logs = ((*z).Logs)[:zb0012] } else { - (*z).Logs = make([]string, zb0009) + (*z).Logs = make([]string, zb0012) } - for zb0003 := range (*z).Logs { - (*z).Logs[zb0003], bts, err = msgp.ReadStringBytes(bts) + for zb0004 := range (*z).Logs { + (*z).Logs[zb0004], bts, err = msgp.ReadStringBytes(bts) if err != nil { - err = msgp.WrapError(err, "struct-from-array", "Logs", zb0003) + err = msgp.WrapError(err, "struct-from-array", "Logs", zb0004) return } } } - if zb0005 > 0 { - zb0005-- - var zb0011 int - var zb0012 bool - zb0011, zb0012, bts, err = msgp.ReadArrayHeaderBytes(bts) + if zb0006 > 0 { + zb0006-- + var zb0014 int + var zb0015 bool + zb0014, zb0015, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "InnerTxns") return } - if zb0011 > config.MaxInnerTransactionsPerDelta { - err = msgp.ErrOverflow(uint64(zb0011), uint64(config.MaxInnerTransactionsPerDelta)) + if zb0014 > config.MaxInnerTransactionsPerDelta { + err = msgp.ErrOverflow(uint64(zb0014), uint64(config.MaxInnerTransactionsPerDelta)) err = msgp.WrapError(err, "struct-from-array", "InnerTxns") return } - if zb0012 { + if zb0015 { (*z).InnerTxns = nil - } else if (*z).InnerTxns != nil && cap((*z).InnerTxns) >= zb0011 { - (*z).InnerTxns = ((*z).InnerTxns)[:zb0011] + } else if (*z).InnerTxns != nil && cap((*z).InnerTxns) >= zb0014 { + (*z).InnerTxns = ((*z).InnerTxns)[:zb0014] } else { - (*z).InnerTxns = make([]SignedTxnWithAD, zb0011) + (*z).InnerTxns = make([]SignedTxnWithAD, zb0014) } - for zb0004 := range (*z).InnerTxns { - bts, err = (*z).InnerTxns[zb0004].UnmarshalMsg(bts) + for zb0005 := range (*z).InnerTxns { + bts, err = (*z).InnerTxns[zb0005].UnmarshalMsg(bts) if err != nil { - err = msgp.WrapError(err, "struct-from-array", "InnerTxns", zb0004) + err = msgp.WrapError(err, "struct-from-array", "InnerTxns", zb0005) return } } } - if zb0005 > 0 { - err = msgp.ErrTooManyArrayFields(zb0005) + if zb0006 > 0 { + err = msgp.ErrTooManyArrayFields(zb0006) if err != nil { err = msgp.WrapError(err, "struct-from-array") return @@ -2081,11 +2126,11 @@ func (z *EvalDelta) UnmarshalMsg(bts []byte) (o []byte, err error) { err = msgp.WrapError(err) return } - if zb0006 { + if zb0007 { (*z) = EvalDelta{} } - for zb0005 > 0 { - zb0005-- + for zb0006 > 0 { + zb0006-- field, bts, err = msgp.ReadMapKeyZC(bts) if err != nil { err = msgp.WrapError(err) @@ -2099,27 +2144,27 @@ func (z *EvalDelta) UnmarshalMsg(bts []byte) (o []byte, err error) { return } case "ld": - var zb0013 int - var zb0014 bool - zb0013, zb0014, bts, err = msgp.ReadMapHeaderBytes(bts) + var zb0016 int + var zb0017 bool + zb0016, zb0017, bts, err = msgp.ReadMapHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "LocalDeltas") return } - if zb0013 > config.MaxEvalDeltaAccounts { - err = msgp.ErrOverflow(uint64(zb0013), uint64(config.MaxEvalDeltaAccounts)) + if zb0016 > config.MaxEvalDeltaAccounts { + err = msgp.ErrOverflow(uint64(zb0016), uint64(config.MaxEvalDeltaAccounts)) err = msgp.WrapError(err, "LocalDeltas") return } - if zb0014 { + if zb0017 { (*z).LocalDeltas = nil } else if (*z).LocalDeltas == nil { - (*z).LocalDeltas = make(map[uint64]basics.StateDelta, zb0013) + (*z).LocalDeltas = make(map[uint64]basics.StateDelta, zb0016) } - for zb0013 > 0 { + for zb0016 > 0 { var zb0001 uint64 var zb0002 basics.StateDelta - zb0013-- + zb0016-- zb0001, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "LocalDeltas") @@ -2132,57 +2177,84 @@ func (z *EvalDelta) UnmarshalMsg(bts []byte) (o []byte, err error) { } (*z).LocalDeltas[zb0001] = zb0002 } + case "sa": + var zb0018 int + var zb0019 bool + zb0018, zb0019, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "SharedAccts") + return + } + if zb0018 > config.MaxEvalDeltaAccounts { + err = msgp.ErrOverflow(uint64(zb0018), uint64(config.MaxEvalDeltaAccounts)) + err = msgp.WrapError(err, "SharedAccts") + return + } + if zb0019 { + (*z).SharedAccts = nil + } else if (*z).SharedAccts != nil && cap((*z).SharedAccts) >= zb0018 { + (*z).SharedAccts = ((*z).SharedAccts)[:zb0018] + } else { + (*z).SharedAccts = make([]basics.Address, zb0018) + } + for zb0003 := range (*z).SharedAccts { + bts, err = (*z).SharedAccts[zb0003].UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "SharedAccts", zb0003) + return + } + } case "lg": - var zb0015 int - var zb0016 bool - zb0015, zb0016, bts, err = msgp.ReadArrayHeaderBytes(bts) + var zb0020 int + var zb0021 bool + zb0020, zb0021, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "Logs") return } - if zb0015 > config.MaxLogCalls { - err = msgp.ErrOverflow(uint64(zb0015), uint64(config.MaxLogCalls)) + if zb0020 > config.MaxLogCalls { + err = msgp.ErrOverflow(uint64(zb0020), uint64(config.MaxLogCalls)) err = msgp.WrapError(err, "Logs") return } - if zb0016 { + if zb0021 { (*z).Logs = nil - } else if (*z).Logs != nil && cap((*z).Logs) >= zb0015 { - (*z).Logs = ((*z).Logs)[:zb0015] + } else if (*z).Logs != nil && cap((*z).Logs) >= zb0020 { + (*z).Logs = ((*z).Logs)[:zb0020] } else { - (*z).Logs = make([]string, zb0015) + (*z).Logs = make([]string, zb0020) } - for zb0003 := range (*z).Logs { - (*z).Logs[zb0003], bts, err = msgp.ReadStringBytes(bts) + for zb0004 := range (*z).Logs { + (*z).Logs[zb0004], bts, err = msgp.ReadStringBytes(bts) if err != nil { - err = msgp.WrapError(err, "Logs", zb0003) + err = msgp.WrapError(err, "Logs", zb0004) return } } case "itx": - var zb0017 int - var zb0018 bool - zb0017, zb0018, bts, err = msgp.ReadArrayHeaderBytes(bts) + var zb0022 int + var zb0023 bool + zb0022, zb0023, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "InnerTxns") return } - if zb0017 > config.MaxInnerTransactionsPerDelta { - err = msgp.ErrOverflow(uint64(zb0017), uint64(config.MaxInnerTransactionsPerDelta)) + if zb0022 > config.MaxInnerTransactionsPerDelta { + err = msgp.ErrOverflow(uint64(zb0022), uint64(config.MaxInnerTransactionsPerDelta)) err = msgp.WrapError(err, "InnerTxns") return } - if zb0018 { + if zb0023 { (*z).InnerTxns = nil - } else if (*z).InnerTxns != nil && cap((*z).InnerTxns) >= zb0017 { - (*z).InnerTxns = ((*z).InnerTxns)[:zb0017] + } else if (*z).InnerTxns != nil && cap((*z).InnerTxns) >= zb0022 { + (*z).InnerTxns = ((*z).InnerTxns)[:zb0022] } else { - (*z).InnerTxns = make([]SignedTxnWithAD, zb0017) + (*z).InnerTxns = make([]SignedTxnWithAD, zb0022) } - for zb0004 := range (*z).InnerTxns { - bts, err = (*z).InnerTxns[zb0004].UnmarshalMsg(bts) + for zb0005 := range (*z).InnerTxns { + bts, err = (*z).InnerTxns[zb0005].UnmarshalMsg(bts) if err != nil { - err = msgp.WrapError(err, "InnerTxns", zb0004) + err = msgp.WrapError(err, "InnerTxns", zb0005) return } } @@ -2215,19 +2287,23 @@ func (z *EvalDelta) Msgsize() (s int) { } } s += 3 + msgp.ArrayHeaderSize - for zb0003 := range (*z).Logs { - s += msgp.StringPrefixSize + len((*z).Logs[zb0003]) + for zb0003 := range (*z).SharedAccts { + s += (*z).SharedAccts[zb0003].Msgsize() + } + s += 3 + msgp.ArrayHeaderSize + for zb0004 := range (*z).Logs { + s += msgp.StringPrefixSize + len((*z).Logs[zb0004]) } s += 4 + msgp.ArrayHeaderSize - for zb0004 := range (*z).InnerTxns { - s += (*z).InnerTxns[zb0004].Msgsize() + for zb0005 := range (*z).InnerTxns { + s += (*z).InnerTxns[zb0005].Msgsize() } return } // MsgIsZero returns whether this is a zero value func (z *EvalDelta) MsgIsZero() bool { - return ((*z).GlobalDelta.MsgIsZero()) && (len((*z).LocalDeltas) == 0) && (len((*z).Logs) == 0) && (len((*z).InnerTxns) == 0) + return ((*z).GlobalDelta.MsgIsZero()) && (len((*z).LocalDeltas) == 0) && (len((*z).SharedAccts) == 0) && (len((*z).Logs) == 0) && (len((*z).InnerTxns) == 0) } // MarshalMsg implements msgp.Marshaler diff --git a/data/transactions/teal.go b/data/transactions/teal.go index 9472fd3b92..37388a0d9f 100644 --- a/data/transactions/teal.go +++ b/data/transactions/teal.go @@ -32,9 +32,14 @@ type EvalDelta struct { GlobalDelta basics.StateDelta `codec:"gd"` // When decoding EvalDeltas, the integer key represents an offset into - // [txn.Sender, txn.Accounts[0], txn.Accounts[1], ...] + // [txn.Sender, txn.Accounts[0], txn.Accounts[1], ..., SharedAccts[0], SharedAccts[1], ...] LocalDeltas map[uint64]basics.StateDelta `codec:"ld,allocbound=config.MaxEvalDeltaAccounts"` + // If a program modifies the local of an account that is not the Sender, or + // in txn.Accounts, it must be recorded here, so that the key in LocalDeltas + // can refer to it. + SharedAccts []basics.Address `codec:"sa,allocbound=config.MaxEvalDeltaAccounts"` + Logs []string `codec:"lg,allocbound=config.MaxLogCalls"` InnerTxns []SignedTxnWithAD `codec:"itx,allocbound=config.MaxInnerTransactionsPerDelta"` diff --git a/ledger/apptxn_test.go b/ledger/apptxn_test.go index fa840b40a4..1d356b2cfd 100644 --- a/ledger/apptxn_test.go +++ b/ledger/apptxn_test.go @@ -1833,6 +1833,110 @@ assert endBlock(t, l, eval) } +// TestSelfCheckHoldingNewApp checks whether a newly created app can check its +// own holdings. There can't really be any value in it from before this group, +// since it could not have opted in. But it should be legal to look. +func TestSelfCheckHoldingNewApp(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + genBalances, addrs, _ := ledgertesting.NewTestGenesis() + + // 31 allowed inner appls. + ledgertesting.TestConsensusRange(t, 31, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { + dl := NewDoubleLedger(t, genBalances, cv, cfg) + defer dl.Close() + + asset := txntest.Txn{ + Type: "acfg", + Sender: addrs[0], + ConfigAsset: 0, + AssetParams: basics.AssetParams{ + Total: 10, + Decimals: 1, + UnitName: "X", + AssetName: "TEN", + }, + } + vb := dl.fullBlock(&asset) + assetID := vb.Block().Payset[0].ApplyData.ConfigAsset + + selfcheck := txntest.Txn{ + Type: "appl", + Sender: addrs[0], + ApprovalProgram: ` + global CurrentApplicationAddress + txn Assets 0 + asset_holding_get AssetBalance + !; assert // is not opted in, so exists=0 + ! // value is also 0 +`, + ForeignAssets: []basics.AssetIndex{assetID}, + } + vb = dl.fullBlock(&selfcheck) + selfcheck.ApplicationID = vb.Block().Payset[0].ApplicationID + + dl.txn(&selfcheck) + + }) +} + +// TestCheckHoldingNewApp checks whether a newly created app (account) can have +// its holding value checked in a later txn. There can't really be any value in +// it from before this group, since it could not have opted in. But it should be +// legal to look. +func TestCheckHoldingNewApp(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + genBalances, addrs, _ := ledgertesting.NewTestGenesis() + + // 31 allowed inner appls. + ledgertesting.TestConsensusRange(t, 31, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { + dl := NewDoubleLedger(t, genBalances, cv, cfg) + defer dl.Close() + + asset := txntest.Txn{ + Type: "acfg", + Sender: addrs[0], + ConfigAsset: 0, + AssetParams: basics.AssetParams{ + Total: 10, + Decimals: 1, + UnitName: "X", + AssetName: "TEN", + }, + } + vb := dl.fullBlock(&asset) + assetID := vb.Block().Payset[0].ApplyData.ConfigAsset + + check := txntest.Txn{ + Type: "appl", + Sender: addrs[0], + ApprovalProgram: main(` + gaid 0 + app_params_get AppAddress + assert + txn Assets 0 + asset_holding_get AssetBalance + !; assert // is not opted in, so exists=0 + !; assert // value is also 0 +`), + ForeignAssets: []basics.AssetIndex{assetID}, + } + vb = dl.fullBlock(&check) + check.ApplicationID = vb.Block().Payset[0].ApplicationID + + create := txntest.Txn{ + Type: "appl", + Sender: addrs[1], + ApplicationID: 0, + } + dl.txgroup("", &create, &check) + + }) +} + // TestInnerAppVersionCalling ensure that inner app calls must be the >=v6 apps func TestInnerAppVersionCalling(t *testing.T) { partitiontest.PartitionTest(t) @@ -3132,19 +3236,29 @@ func TestForeignAppAccountsImmutable(t *testing.T) { appA := txntest.Txn{ Type: "appl", Sender: addrs[0], + ApprovalProgram: main(` +itxn_begin +int appl; itxn_field TypeEnum +txn Applications 1; itxn_field ApplicationID +int OptIn; itxn_field OnCompletion +itxn_submit +`), } appB := txntest.Txn{ Type: "appl", Sender: addrs[0], ApprovalProgram: main(` +txn NumApplications // allow "bare" optin +bz end txn Applications 1 app_params_get AppAddress +assert byte "X" byte "ABC" app_local_put -int 1 `), + LocalStateSchema: basics.StateSchema{NumByteSlice: 1}, } vb := dl.fullBlock(&appA, &appB) @@ -3160,6 +3274,13 @@ int 1 fund0 := fund1 fund0.Receiver = index0.Address() + optin := txntest.Txn{ + Type: "appl", + Sender: addrs[2], + ApplicationID: index0, + ForeignApps: []basics.AppIndex{index1}, + } + callTx := txntest.Txn{ Type: "appl", Sender: addrs[2], @@ -3167,9 +3288,14 @@ int 1 ForeignApps: []basics.AppIndex{index0}, } - dl.beginBlock() - dl.txgroup("invalid Account reference", &fund0, &fund1, &callTx) - dl.endBlock() + var problem string + switch { + case ver < 34: // before v7, app accounts not available at all + problem = "invalid Account reference " + index0.Address().String() + case ver < 37: // as of v7, it's the mutation that's the problem + problem = "invalid Account reference for mutation" + } + dl.txgroup(problem, &fund0, &fund1, &optin, &callTx) }) } @@ -3407,14 +3533,14 @@ func TestRewardsInAD(t *testing.T) { }) } -// TestDeleteNonExistantKeys checks if the EvalDeltas from deleting missing keys are correct -func TestDeleteNonExistantKeys(t *testing.T) { +// TestDeleteNonExistentKeys checks if the EvalDeltas from deleting missing keys are correct +func TestDeleteNonExistentKeys(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() genBalances, addrs, _ := ledgertesting.NewTestGenesis() - // AVM v2 (apps) - ledgertesting.TestConsensusRange(t, 24, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { + // AVM v4 start, so we can use `txn Sender` + ledgertesting.TestConsensusRange(t, 28, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { dl := NewDoubleLedger(t, genBalances, cv, cfg) defer dl.Close() @@ -3426,7 +3552,7 @@ func TestDeleteNonExistantKeys(t *testing.T) { ApprovalProgram: main(` byte "missing_global" app_global_del -int 0 +txn Sender byte "missing_local" app_local_del `), diff --git a/ledger/boxtxn_test.go b/ledger/boxtxn_test.go index 47291575bf..ca36770fc7 100644 --- a/ledger/boxtxn_test.go +++ b/ledger/boxtxn_test.go @@ -277,7 +277,8 @@ func TestBoxCreateAvailability(t *testing.T) { `, } - // We know box_create worked because we finished and checked MBR + // We know box_create worked because this failure (checking the MBR) + // happens at the end of the group evaluation. dl.txn(&accessInCreate, "balance 0 below min") // But let's fund it and be sure. This is "psychic". We're going to fund diff --git a/ledger/eval/cow.go b/ledger/eval/cow.go index 66824ce978..0b5909d7b0 100644 --- a/ledger/eval/cow.go +++ b/ledger/eval/cow.go @@ -84,10 +84,10 @@ type roundCowState struct { // must be incorporated into mods.accts before passing deltas forward sdeltas map[basics.Address]map[storagePtr]*storageDelta - // either or not maintain compatibility with original app refactoring behavior + // whether or not to maintain compatibility with original app refactoring behavior // this is needed for generating old eval delta in new code compatibilityMode bool - // cache mainaining accountIdx used in getKey for local keys access + // cache maintaining accountIdx used in getKey for local keys access compatibilityGetKeyCache map[basics.Address]map[storagePtr]uint64 // prevTotals contains the accounts totals for the previous round. It's being used to calculate the totals for the new round diff --git a/ledger/eval/cow_creatables.go b/ledger/eval/cow_creatables.go index 1c5651a18a..3159d230b5 100644 --- a/ledger/eval/cow_creatables.go +++ b/ledger/eval/cow_creatables.go @@ -73,8 +73,8 @@ func (cs *roundCowState) GetAssetHolding(addr basics.Address, aidx basics.AssetI return } if d.Holding == nil { - // found and not deleled => must exist. Err if not - err = fmt.Errorf("GetAppLocalState got a nil entry for (%s, %d): %p, %v", addr.String(), aidx, d.Holding, d.Deleted) + // found and not deleted => must exist. Err if not + err = fmt.Errorf("GetAssetHolding got a nil entry for (%s, %d): %p, %v", addr, aidx, d.Holding, d.Deleted) } ret = *d.Holding return diff --git a/ledger/eval_simple_test.go b/ledger/eval_simple_test.go index c5c9354520..c817e0c143 100644 --- a/ledger/eval_simple_test.go +++ b/ledger/eval_simple_test.go @@ -210,18 +210,192 @@ func TestBlockEvaluator(t *testing.T) { require.Equal(t, bal2new.MicroAlgos.Raw, bal2.MicroAlgos.Raw-minFee.Raw) } +// TestHoldingGet tests some of the corner cases for the asset_holding_get +// opcode: the asset doesn't exist, the account doesn't exist, account not opted +// in, vs it has none of the asset. This is tested here, even though it should +// be well tested in 'logic' package, because we want to make sure that errors +// come out of the real ledger in the way that the logic package expects (it +// uses a mock ledger for testing). +func TestHoldingGet(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + genBalances, addrs, _ := ledgertesting.NewTestGenesis() + // 24 is first version with apps + ledgertesting.TestConsensusRange(t, 24, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { + dl := NewDoubleLedger(t, genBalances, cv, cfg) + defer dl.Close() + + makegold := txntest.Txn{ + Type: protocol.AssetConfigTx, + Sender: addrs[0], + AssetParams: basics.AssetParams{ + Total: 10, + UnitName: "gold", + AssetName: "oz", + }, + } + + // written without assert or swap, so we can use teal v2 and test back to consensus v24 + source := ` +#pragma version 2 +txn ApplicationID +bnz main +int 1; return +main: + txn NumAccounts // Sender, or Accounts[n] + txn ApplicationArgs 0; btoi + asset_holding_get AssetBalance + txn ApplicationArgs 1; btoi; ==; bz bad + txn ApplicationArgs 2; btoi; ==; return +bad: err +` + + // Advance the ledger so that there's ambiguity of asset index or foreign array index + for i := 0; i < 10; i++ { + dl.fullBlock(&txntest.Txn{Type: "pay", Sender: addrs[2], Receiver: addrs[2]}) + } + + checker := basics.AppIndex(11) + gold := basics.AssetIndex(13) + create := txntest.Txn{ + Type: protocol.ApplicationCallTx, + Sender: addrs[0], + ApprovalProgram: source, + } + + dl.fullBlock(&create) // create the app + + check := txntest.Txn{ + Type: protocol.ApplicationCallTx, + Sender: addrs[0], + ApplicationID: checker, + ApplicationArgs: [][]byte{{byte(gold)}, {0}, {0}}, // exist=0 value=0 + } + + dl.fullBlock(&check) + + dl.fullBlock(&makegold) + + // confirm hardcoded "gold" is correct + b, ok := holding(t, dl.generator, addrs[0], gold) + require.True(t, ok) + require.EqualValues(t, 10, b) + + // The asset exists now. asset_holding_get gives 1,10 for the creator + // (who is auto-opted in) + check.ApplicationArgs = [][]byte{{byte(gold)}, {1}, {10}} // exist=1 value=10 + dl.fullBlock(&check) + + // but still gives 0,0 for un opted-in addrs[1], because it means + // "exists" in the given account, i.e. opted in + check.Sender = addrs[1] + check.ApplicationArgs = [][]byte{{byte(gold)}, {0}, {0}} + dl.fullBlock(&check) + + // opt-in addr[1] + dl.fullBlock(&txntest.Txn{Type: "axfer", XferAsset: gold, Sender: addrs[1], AssetReceiver: addrs[1]}) + check.ApplicationArgs = [][]byte{{byte(gold)}, {1}, {0}} + dl.fullBlock(&check) + + // non-existent account, with existing asset, cleanly reports exists=0, value=0 + check.Accounts = []basics.Address{{0x01, 0x02}} + check.ApplicationArgs = [][]byte{{byte(gold)}, {0}, {0}} + dl.fullBlock(&check) + }) +} + +// TestLocalGetEx tests some of the corner cases for the app_local_get_ex +// opcode: the app doesn't exist, the account doesn't exist, account not opted +// in, local key doesn't exists. This is tested here, even though it should be +// well tested in 'logic' package, because we want to make sure that errors come +// out of the real ledger in the way that the logic package expects (it uses a +// mock ledger for testing). +func TestLocalGetEx(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + genBalances, addrs, _ := ledgertesting.NewTestGenesis() + // 24 is first version with apps + ledgertesting.TestConsensusRange(t, 24, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { + dl := NewDoubleLedger(t, genBalances, cv, cfg) + defer dl.Close() + + makeapp := txntest.Txn{ + Type: "appl", + Sender: addrs[0], + LocalStateSchema: basics.StateSchema{ + NumUint: 1, + }, + GlobalStateSchema: basics.StateSchema{ + NumByteSlice: 3, + }, + } + + // written without assert or swap, so we can use teal v2 and test back to consensus v24 + source := ` +#pragma version 2 +txn ApplicationID +bnz main +int 1; return +main: + txn NumAccounts // Sender, or Accounts[n] + txn ApplicationArgs 0; btoi + byte "KEY" + app_local_get_ex + txn ApplicationArgs 1; btoi; ==; bz bad + txn ApplicationArgs 2; btoi; ==; return +bad: err +` + + // Advance the ledger so that there's ambiguity of app index or foreign array index + for i := 0; i < 10; i++ { + dl.fullBlock(&txntest.Txn{Type: "pay", Sender: addrs[2], Receiver: addrs[2]}) + } + + checker := basics.AppIndex(11) // the app that checks state + state := basics.AppIndex(12) // the app with state + create := txntest.Txn{ + Type: protocol.ApplicationCallTx, + Sender: addrs[0], + ApprovalProgram: source, + } + + dl.fullBlock(&create) // create the checker app + + check := txntest.Txn{ + Type: protocol.ApplicationCallTx, + Sender: addrs[0], + ApplicationID: checker, + ApplicationArgs: [][]byte{{byte(state)}, {0}, {0}}, // exist=0 value=0 + } + + // unlike assets, you can't even as `app_local_get_ex` for address that + // have not been opted in. For local state, the existence bit is only + // used to distinguish "key existence". The local state bundle MUST + // exist of program fails. + dl.txn(&check, "cannot fetch key") + + // so we make the app and try again + dl.fullBlock(&makeapp) + // confirm hardcoded "state" index is correct + g, ok := globals(t, dl.generator, addrs[0], state) + require.True(t, ok) + require.EqualValues(t, 3, g.GlobalStateSchema.NumByteSlice) + + // still no good, because creating an app does not opt in the creator + dl.txn(&check, "cannot fetch key") + + // opt-in addr[0] + dl.fullBlock(&txntest.Txn{Type: "appl", ApplicationID: state, Sender: addrs[0], OnCompletion: transactions.OptInOC}) + check.ApplicationArgs = [][]byte{{byte(state)}, {0}, {0}} + dl.fullBlock(&check) + }) +} + func TestRekeying(t *testing.T) { partitiontest.PartitionTest(t) - // t.Parallel() NO! This test manipulates []protocol.Consensus - - // Pretend rekeying is supported - actual := config.Consensus[protocol.ConsensusCurrentVersion] - pretend := actual - pretend.SupportRekeying = true - config.Consensus[protocol.ConsensusCurrentVersion] = pretend - defer func() { - config.Consensus[protocol.ConsensusCurrentVersion] = actual - }() + t.Parallel() // Bring up a ledger genesisInitState, addrs, keys := ledgertesting.Genesis(10) diff --git a/ledger/simple_test.go b/ledger/simple_test.go index 8a41cb4a6d..31601bd25c 100644 --- a/ledger/simple_test.go +++ b/ledger/simple_test.go @@ -180,3 +180,19 @@ func asaParams(t testing.TB, ledger *Ledger, asset basics.AssetIndex) (basics.As } return basics.AssetParams{}, fmt.Errorf("bad lookup (%d)", asset) } + +// globals gets the AppParams for an address, app index pair (only works if addr is the creator) +func globals(t testing.TB, ledger *Ledger, addr basics.Address, app basics.AppIndex) (basics.AppParams, bool) { + if globals, ok := lookup(t, ledger, addr).AppParams[app]; ok { + return globals, true + } + return basics.AppParams{}, false +} + +// locals gets the AppLocalState for an address, app index pair +func locals(t testing.TB, ledger *Ledger, addr basics.Address, app basics.AppIndex) (basics.AppLocalState, bool) { + if locals, ok := lookup(t, ledger, addr).AppLocalStates[app]; ok { + return locals, true + } + return basics.AppLocalState{}, false +} diff --git a/ledger/testing/consensusRange.go b/ledger/testing/consensusRange.go index 02ae83fce9..5408195f71 100644 --- a/ledger/testing/consensusRange.go +++ b/ledger/testing/consensusRange.go @@ -47,9 +47,9 @@ var consensusByNumber = []protocol.ConsensusVersion{ protocol.ConsensusV23, protocol.ConsensusV24, // AVM v2 (apps) protocol.ConsensusV25, - protocol.ConsensusV26, + protocol.ConsensusV26, // AVM v3 protocol.ConsensusV27, - protocol.ConsensusV28, + protocol.ConsensusV28, // AVM v4 (direct refs) protocol.ConsensusV29, protocol.ConsensusV30, // AVM v5 (inner txs) protocol.ConsensusV31, // AVM v6 (inner txs with appls) diff --git a/test/scripts/e2e_subs/shared-resources.py b/test/scripts/e2e_subs/shared-resources.py new file mode 100755 index 0000000000..d4c1c95945 --- /dev/null +++ b/test/scripts/e2e_subs/shared-resources.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python + +import os +import sys +import algosdk.encoding as enc +import algosdk.future.transaction as txn +from goal import Goal + +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) + +joe = goal.new_account() + +txinfo, err = goal.pay(goal.account, joe, amt=500_000) +assert not err, err + +putTeal = """ +#pragma version 8 +txn ApplicationID +bz end + +txn ApplicationArgs 0 +byte 0x1032 +txn ApplicationArgs 1 +btoi +app_local_put + +end: int 1 +""" + +txinfo, err = goal.app_create(joe, goal.assemble(putTeal), + local_schema=(2, 0)) +assert not err, err +app_id = txinfo['application-index'] +assert app_id + +print("goal.account: ", goal.account) +print("joe: ", joe) + +goal.autosend = False +grp1 = goal.app_call(goal.account, app_id, + on_complete=txn.OnComplete.OptInOC, + app_args=[enc.decode_address(goal.account), 10]) +grp2 = goal.app_call(joe, app_id, + app_args=[enc.decode_address(goal.account), 20]) +[grp1_info, grp2_info], err = goal.send_group([grp1, grp2]) + +# Won't work, because v8 can't modify an account (goal.account) that +# isn't in the `grp2` txn +assert err +assert "invalid Account reference "+goal.account in str(err) + +# Now, upgrade program to same thing, but v9 + +optin = goal.app_call(joe, app_id, + on_complete=txn.OnComplete.OptInOC, + app_args=[enc.decode_address(joe), 40]) +optin_info, err = goal.send(optin) +assert not err, err + +putTealV9 = putTeal.replace("#pragma version 8", "#pragma version 9") + +update = goal.app_call(joe, app_id, + on_complete=txn.OnComplete.UpdateApplicationOC, + approval_program=goal.assemble(putTealV9), + clear_program=goal.assemble(putTealV9), + app_args=[enc.decode_address(joe), 50]) +update_info, err = goal.send(update) +assert not err, err + +# Works now, because a v9 program is allowed to modify a "non-local" +# account. Under the covers, the txn gets a "SharedAccts" array, and +# the index points there. But the REST API hides that. +grp1 = goal.app_call(goal.account, app_id, + on_complete=txn.OnComplete.OptInOC, + app_args=[enc.decode_address(goal.account), 60]) +grp2 = goal.app_call(joe, app_id, + app_args=[enc.decode_address(goal.account), 70]) +[grp1_info, grp2_info], err = goal.send_group([grp1, grp2]) +assert not err, err + +# Both txns should have a local-state-delta that modified +# goal.account, even though that would have been impossible in v8 +# because goal.account does not appear in the `grp2` transaction. +assert len(grp1_info["local-state-delta"]) == 1 +assert grp1_info["local-state-delta"][0]["address"] == goal.account +assert grp1_info["local-state-delta"][0]["delta"][0]["value"]["uint"] == 60 + +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 + +print(f"{os.path.basename(sys.argv[0])} OK {stamp}") From 142bbed35b5e0e3255d77291e3791e02a4934024 Mon Sep 17 00:00:00 2001 From: Ikko Eltociear Ashimine Date: Tue, 11 Apr 2023 00:01:06 +0900 Subject: [PATCH 038/125] Typo Fix: fix typo in simulate.go (#5282) --- agreement/agreementtest/simulate.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agreement/agreementtest/simulate.go b/agreement/agreementtest/simulate.go index 225d0632c8..8d8b713c68 100644 --- a/agreement/agreementtest/simulate.go +++ b/agreement/agreementtest/simulate.go @@ -129,7 +129,7 @@ func (b *blackhole) Address() (string, bool) { // CryptoRandomSource is a random source that is based off our crypto library. type CryptoRandomSource struct{} -// Uint64 implements the randomness by calling hte crypto library. +// Uint64 implements the randomness by calling the crypto library. func (c *CryptoRandomSource) Uint64() uint64 { return crypto.RandUint64() } From e5ad8eebc962a0411995aea057490e93135540d9 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Mon, 10 Apr 2023 11:22:19 -0400 Subject: [PATCH 039/125] AVM: Avoid panics while type checking bad immediates (#5271) --- data/transactions/logic/assembler.go | 38 ++++++++++++++--------- data/transactions/logic/assembler_test.go | 28 +++++++++++++++++ 2 files changed, 52 insertions(+), 14 deletions(-) diff --git a/data/transactions/logic/assembler.go b/data/transactions/logic/assembler.go index 9e726a977e..392c510f25 100644 --- a/data/transactions/logic/assembler.go +++ b/data/transactions/logic/assembler.go @@ -1158,8 +1158,9 @@ func asmDefault(ops *OpStream, spec *OpSpec, args []string) error { return nil } -// getImm interprets the arg at index argIndex as an immediate -func getImm(args []string, argIndex int) (int, bool) { +// getImm interprets the arg at index argIndex as an immediate that must be +// between -128 and 127 (if signed=true) or between 0 and 255 (if signed=false) +func getImm(args []string, argIndex int, signed bool) (int, bool) { if len(args) <= argIndex { return 0, false } @@ -1169,6 +1170,15 @@ func getImm(args []string, argIndex int) (int, bool) { if err != nil { return 0, false } + if signed { + if n < -128 || n > 127 { + return 0, false + } + } else { + if n < 0 || n > 255 { + return 0, false + } + } return int(n), true } @@ -1193,7 +1203,7 @@ func typeSwap(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, err } func typeDig(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, error) { - n, ok := getImm(args, 0) + n, ok := getImm(args, 0, false) if !ok { return nil, nil, nil } @@ -1210,7 +1220,7 @@ func typeDig(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, erro } func typeBury(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, error) { - n, ok := getImm(args, 0) + n, ok := getImm(args, 0, false) if !ok { return nil, nil, nil } @@ -1242,7 +1252,7 @@ func typeBury(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, err } func typeFrameDig(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, error) { - n, ok := getImm(args, 0) + n, ok := getImm(args, 0, true) if !ok { return nil, nil, nil } @@ -1263,7 +1273,7 @@ func typeFrameDig(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, } func typeFrameBury(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, error) { - n, ok := getImm(args, 0) + n, ok := getImm(args, 0, true) if !ok { return nil, nil, nil } @@ -1351,7 +1361,7 @@ func typeSetBit(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, e } func typeCover(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, error) { - n, ok := getImm(args, 0) + n, ok := getImm(args, 0, false) if !ok { return nil, nil, nil } @@ -1373,7 +1383,7 @@ func typeCover(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, er } func typeUncover(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, error) { - n, ok := getImm(args, 0) + n, ok := getImm(args, 0, false) if !ok { return nil, nil, nil } @@ -1402,7 +1412,7 @@ func typeTxField(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, } func typeStore(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, error) { - scratchIndex, ok := getImm(args, 0) + scratchIndex, ok := getImm(args, 0, false) if !ok { return nil, nil, nil } @@ -1428,7 +1438,7 @@ func typeStores(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, e } func typeLoad(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, error) { - scratchIndex, ok := getImm(args, 0) + scratchIndex, ok := getImm(args, 0, false) if !ok { return nil, nil, nil } @@ -1436,8 +1446,8 @@ func typeLoad(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, err } func typeProto(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, error) { - a, aok := getImm(args, 0) - _, rok := getImm(args, 1) + a, aok := getImm(args, 0, false) + _, rok := getImm(args, 1, false) if !aok || !rok { return nil, nil, nil } @@ -1462,7 +1472,7 @@ func typeLoads(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, er } func typePopN(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, error) { - n, ok := getImm(args, 0) + n, ok := getImm(args, 0, false) if !ok { return nil, nil, nil } @@ -1470,7 +1480,7 @@ func typePopN(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, err } func typeDupN(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, error) { - n, ok := getImm(args, 0) + n, ok := getImm(args, 0, false) if !ok { return nil, nil, nil } diff --git a/data/transactions/logic/assembler_test.go b/data/transactions/logic/assembler_test.go index 78bc420334..7c515c6931 100644 --- a/data/transactions/logic/assembler_test.go +++ b/data/transactions/logic/assembler_test.go @@ -3284,6 +3284,34 @@ add: require.EqualError(t, err, "0: invalid syntax: not#pragma") } +func TestAssembleImmediateRanges(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + /* Perhaps all of these "unable to parse" errors could be improved to + discuss limits rather than bailout when the immediate is, in fact, an + integer. */ + + testProg(t, "int 1; store 0;", AssemblerMaxVersion) + testProg(t, "load 255;", AssemblerMaxVersion) + + testProg(t, "int 1; store -1000;", AssemblerMaxVersion, + Expect{1, "store unable to parse..."}) + testProg(t, "load -100;", AssemblerMaxVersion, + Expect{1, "load unable to parse..."}) + testProg(t, "int 1; store 256;", AssemblerMaxVersion, + Expect{1, "store i beyond 255: 256"}) + + testProg(t, "frame_dig -1;", AssemblerMaxVersion) + testProg(t, "frame_dig 127;", AssemblerMaxVersion) + testProg(t, "int 1; frame_bury -128;", AssemblerMaxVersion) + + testProg(t, "frame_dig 128;", AssemblerMaxVersion, + Expect{1, "frame_dig unable to parse..."}) + testProg(t, "int 1; frame_bury -129;", AssemblerMaxVersion, + Expect{1, "frame_bury unable to parse..."}) +} + func TestAssembleMatch(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() From 9875686a8ae2da17ad308dd2b52dc11cfcc7c10b Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Tue, 11 Apr 2023 07:31:36 -0500 Subject: [PATCH 040/125] follower: add round information for missing deltas request (#5277) --- daemon/algod/api/server/v2/errors.go | 5 +---- daemon/algod/api/server/v2/handlers.go | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/daemon/algod/api/server/v2/errors.go b/daemon/algod/api/server/v2/errors.go index 947a38fa97..f72fe212ef 100644 --- a/daemon/algod/api/server/v2/errors.go +++ b/daemon/algod/api/server/v2/errors.go @@ -24,7 +24,7 @@ var ( errBoxDoesNotExist = "box not found" errFailedLookingUpLedger = "failed to retrieve information from the ledger" errFailedLookingUpTransactionPool = "failed to retrieve information from the transaction pool" - errFailedRetrievingStateDelta = "failed retrieving State Delta" + errFailedRetrievingStateDelta = "failed retrieving State Delta: %v" errFailedRetrievingNodeStatus = "failed retrieving node status" errFailedRetrievingLatestBlockHeaderStatus = "failed retrieving latests block header" errFailedRetrievingSyncRound = "failed retrieving sync round from ledger" @@ -32,9 +32,6 @@ var ( errFailedParsingFormatOption = "failed to parse the format option" errFailedToParseAddress = "failed to parse the address" errFailedToParseExclude = "failed to parse exclude" - errFailedToParseTransaction = "failed to parse transaction" - errFailedToParseBlock = "failed to parse block" - errFailedToParseCert = "failed to parse cert" errFailedToEncodeResponse = "failed to encode response" errInternalFailure = "internal failure" errNoValidTxnSpecified = "no valid transaction ID was specified" diff --git a/daemon/algod/api/server/v2/handlers.go b/daemon/algod/api/server/v2/handlers.go index e04217e779..1b2e917e11 100644 --- a/daemon/algod/api/server/v2/handlers.go +++ b/daemon/algod/api/server/v2/handlers.go @@ -1102,7 +1102,7 @@ func (v2 *Handlers) GetLedgerStateDelta(ctx echo.Context, round uint64, params m } sDelta, err := v2.Node.LedgerForAPI().GetStateDeltaForRound(basics.Round(round)) if err != nil { - return notFound(ctx, err, errFailedRetrievingStateDelta, v2.Log) + return notFound(ctx, err, fmt.Sprintf(errFailedRetrievingStateDelta, err), v2.Log) } data, err := encode(handle, sDelta) if err != nil { From f8a974662a9d284a8503b945cc58bbd4dceefd11 Mon Sep 17 00:00:00 2001 From: Joe Polny <50534337+joe-p@users.noreply.github.com> Date: Tue, 11 Apr 2023 12:33:31 -0400 Subject: [PATCH 041/125] docker: disable rewards in devmode template (#5270) --- docker/files/run/devmode_template.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docker/files/run/devmode_template.json b/docker/files/run/devmode_template.json index 8e756502b5..c48a6bc1c0 100644 --- a/docker/files/run/devmode_template.json +++ b/docker/files/run/devmode_template.json @@ -21,7 +21,8 @@ "Online": true } ], - "DevMode": true + "DevMode": true, + "RewardsPoolBalance": 0 }, "Nodes": [ { From 335e0add94e575764d95642e3a4c764d77e355f3 Mon Sep 17 00:00:00 2001 From: Will Winder Date: Tue, 11 Apr 2023 19:56:37 -0400 Subject: [PATCH 042/125] chore: Focus CONTRIBUTING.md on code contributions. (#5294) Co-authored-by: algoanne <90275860+algoanne@users.noreply.github.com> --- CONTRIBUTING.md | 74 ++++++++++++++++++++++++++----------------------- README.md | 2 +- 2 files changed, 40 insertions(+), 36 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 04f8c9508c..599d4a3b4b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,58 +2,62 @@ If you are interested in contributing to the project, we welcome and thank you. We want to make the best decentralized and effective blockchain platform available and we appreciate your willingness to help us. -The [Algorand GitHub Organization](https://github.com/algorand) has all of our open source projects, and dependencies which we fork and use in those projects. This contribution guide applies to all of these. +The [Algorand GitHub Organization](https://github.com/algorand) has all of our open source projects, and dependencies which we fork and use in those projects. While technical details in this document are specific to `go-algorand`, the general ideas are applicable to all of our projects. -Some of our most active projects include: -* [go-algorand](https://github.com/algorand/go-algorand) - Algorand node software (this repository) -* [go-algorand-sdk](https://github.com/algorand/go-algorand-sdk) - Golang SDK -* [java-algorand-sdk](https://github.com/algorand/java-algorand-sdk) - Java SDK -* [js-algorand-sdk](https://github.com/algorand/js-algorand-sdk) - JavaScript SDK -* [indexer](https://github.com/algorand/indexer) - Blockchain analytics database -* [ledger-app-algorand](https://github.com/algorand/ledger-app-algorand) - Ledger hardware wallet application -* [mule](https://github.com/algorand/mule) - Continuous Integration automation tool -* [py-algorand-sdk](https://github.com/algorand/py-algorand-sdk) - Python SDK -* [sandbox](https://github.com/algorand/sandbox) - Algorand node quickstart tool +## Non-code Contributions -## Filing Issues +While contributions come in many forms, this document is focused on code. For other types of involvement, see the following: +* [Reporting issues and features requests.](go-algorand-issues) +* [Security vulnerability disclosures.](security-disclosure) +* [Documentation improvements.](algorand-docs) -Did you discover a bug? Do you have a feature request? Filing issues is an easy way anyone can contribute and helps us improve Algorand. We use GitHub Issues to track all known bugs and feature requests. +## Contribution Model -Before logging an issue be sure to check current issues, verify that your [node is synced](https://developer.algorand.org/docs/introduction-installing-node#sync-node), check the [Developer Frequently Asked Questions](https://developer.algorand.org/docs/developer-faq) and [GitHub issues][issues_url] to see if your issue is described there. +All changes to `go-algorand` are made through the same process: a pull request targeting the `master` branch. This goes for internal and external contributions. To familiarize yourself with the process we recommend that you review the current open pull requests, and the GitHub documentation for [creating a pull request from a fork](gh-pr-process). -If you’d like to contribute to any of the repositories, please file a [GitHub issue][issues_url] using the issues menu item. Make sure to specify whether you are describing a bug or a new enhancement using the **Bug report** or **Feature request** button. +Note: some of our other projects are using gitflow, for these the process is the same but you will target pull requests against the `develop` branch. -See the GitHub help guide for more information on [filing an issue](https://help.github.com/en/articles/creating-an-issue). +## Communication Channels -## Security / Vulnerabilities +The core development team monitors the Algorand [discord community](https://discord.gg/algorand) and regularly responds to questions and suggestions. For very technical questions and implementation discussions GitHub Issues and Pull Requests are a good way to reach maintainers. -Please refer to our [SECURITY](SECURITY.md) document. +## Pull Requests -If you have any questions, don't hesitate to contact us at security@algorand.com. +All changes are are made via pull requests. -## Contribution Model +Small changes are easier to review and merge than large ones, so the more focused a PR the better. If a feature requires refactoring, the refactoring should be a separate PR. If refactoring uncovers a bug, the fix should be a separate PR. These are not strict rules, but generally speaking, they make things easier to review which speeds up the PR process. -For each of our repositories we use the same model for contributing code. Developers wanting to contribute must create pull requests. This process is described in the GitHub [Creating a pull request from a fork](https://help.github.com/en/articles/creating-a-pull-request-from-a-fork) documentation. Each pull request should be initiated against the `master` branch in the Algorand repository. After a pull request is submitted the core development team will review the submission and communicate with the developer using the comments sections of the PR. After the submission is reviewed and approved, it will be merged into the `master` branch of the source. These changes will be merged to our release branch on the next viable release date. For the SDKs, this may be immediate. Changes to the node software may take more time as we must ensure and verify the security, as well as apply protocol upgrades in an orderly way. +### General Guidelines -Note: some of our projects are using gitflow, for these you will open pull requests against the `develop` branch. +* Have a clear well-formatted description in the pull request. This helps reviewers and later serves as documentation in the release notes. +* Code must adhere to the [Go formatting guidelines](https://golang.org/doc/effective_go.html). +* All tests must be passing. +* New unit and integration tests should be added to ensure correctness and prevent regressions where appropriate. +* Run linting and code formatting tools, see [the README](README.md) for details. +* All CI checks should pass. +* Use draft mode for PRs that are still in progress. -Again, if you have a patch for a critical security vulnerability, please use our [vulnerability disclosure form][vuln_url] instead of creating a PR. We'll follow up with you on distributing the patch before we merge it. +### Peer Review -## Code Guidelines +This is the single most important part of introducing new code to `go-algorand`. -For Go code we use the [Golang guidelines defined here](https://golang.org/doc/effective_go.html). -* Code must adhere to the official Go formatting guidelines (i.e. uses gofmt). -* We use **gofmt** and **golangci-lint**. Also make sure to run `make sanity` and `make generate` before opening a pull request. -* Code must be documented adhering to the official Go commentary guidelines. +#### Concept Review -For JavaScript code we use the [MDN formatting rules](https://developer.mozilla.org/en-US/docs/MDN/Contribute/Guidelines/Code_guidelines/JavaScript). +Because code reviews are a considerable time commitment, the first step for peer review is convincing reviewers that it is worth their time. Typically this is done by keeping changes small, writing a thorough description to clearly explain the need for a given improvement, or discussing larger changes ahead of time through one of the communication channels. -For Java code we use [Oracle’s standard formatting rules for Java](https://www.oracle.com/technetwork/java/codeconventions-150003.pdf). +If reviewers are not convinced about the merits of a change, they may reject a PR instead of reviewing it. All rejections should include the rationale for how that decision was reached. It is not uncommon for this to occur. Some users opt to maintain long running forks to add features which are not suitable for the upstream repo at this time. -## Communication Channels +#### Code Review + +Reviewers will leave feedback directly on the pull request, typically inline with the code. This is an opportunity to discuss the changes. If a PR is left open with unresolved feedback it may eventually be closed. + +The project maintainers are responsible for the code in `go-algorand`, so ultimately whether or not a pull request is merged depends on their involvement. + +#### Merge -The core development team monitors the Algorand community forums and regularly responds to questions and suggestions. Issues and Pull Requests are handled on GitHub. +All changes are subject to a minimum of two reviews from subject matter experts prior to merge. Once this approval is reached a small number of committers are responsible for merging the changes. The list of committers is limited for practical and security reasons. -[issues_url]: https://github.com/algorand/go-algorand/issues -[vuln_url]: https://www.algorand.com/resources/blog/security -[bug_bounty_url]: https://bugcrowd.com/algorand +[gh-pr-process]: https://help.github.com/en/articles/creating-a-pull-request-from-a-fork +[go-algorand-issues]: https://github.com/algorand/go-algorand/issues/new/choose +[security-disclosure]: https://github.com/algorand/go-algorand/security/policy +[algorand-docs]: https://github.com/algorand/docs/blob/staging/CONTRIBUTING.md diff --git a/README.md b/README.md index 9e787d6d4b..c938d2b3a2 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ ${GOPATH}/bin/goal node start -d ~/testnet_data Genesis files for mainnet, testnet, and betanet can be found in `installer/genesis/`. -## Contributing (Code, Documentation, Bugs, Etc) ## +## Contributing Please refer to our [CONTRIBUTING](CONTRIBUTING.md) document. From b75a1df0237cbc35e3721268e9135500281caf5e Mon Sep 17 00:00:00 2001 From: shiqizng <80276844+shiqizng@users.noreply.github.com> Date: Wed, 12 Apr 2023 17:12:15 -0400 Subject: [PATCH 043/125] tools: new endpoints for block generator (#5257) --- tools/block-generator/generator/generate.go | 70 ++++++++++++++++--- .../generator/generate_test.go | 47 +++++++++++-- tools/block-generator/generator/server.go | 68 ++++++++---------- .../block-generator/generator/server_test.go | 63 +++++++++++------ tools/block-generator/generator/utils.go | 36 ++++++++++ tools/block-generator/generator/utils_test.go | 21 ++++++ tools/block-generator/runner/run.go | 2 + 7 files changed, 230 insertions(+), 77 deletions(-) diff --git a/tools/block-generator/generator/generate.go b/tools/block-generator/generator/generate.go index 41fad8736e..f1961587bb 100644 --- a/tools/block-generator/generator/generate.go +++ b/tools/block-generator/generator/generate.go @@ -17,7 +17,6 @@ package generator import ( - "encoding/binary" "encoding/json" "fmt" "io" @@ -26,6 +25,9 @@ import ( "time" cconfig "github.com/algorand/go-algorand/config" + "github.com/algorand/go-algorand/ledger" + "github.com/algorand/go-algorand/ledger/ledgercore" + "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/agreement" @@ -133,6 +135,7 @@ func MakeGenerator(config GenerationConfig) (Generator, error) { gen.genesisHash[31] = 3 gen.initializeAccounting() + gen.initializeLedger() for _, val := range getTransactionOptions() { switch val { @@ -177,7 +180,9 @@ type Generator interface { WriteBlock(output io.Writer, round uint64) error WriteAccount(output io.Writer, accountString string) error WriteStatus(output io.Writer) error + WriteDeltas(output io.Writer, round uint64) error Accounts() <-chan basics.Address + Stop() } type generator struct { @@ -224,6 +229,9 @@ type generator struct { // Reporting information from transaction type to data reportData Report + + // ledger + ledger *ledger.Ledger } type assetData struct { @@ -410,20 +418,31 @@ func (g *generator) WriteBlock(output io.Writer, round uint64) error { if err != nil { return err } - + err = g.ledger.AddBlock(cert.Block, agreement.Certificate{}) + if err != nil { + return err + } + g.ledger.WaitForCommit(basics.Round(g.round)) g.finishRound(numTxnForBlock) return nil } -func indexToAccount(i uint64) (addr basics.Address) { - // Make sure we don't generate a zero address by adding 1 to i - binary.LittleEndian.PutUint64(addr[:], i+1) - return -} - -func accountToIndex(a basics.Address) (addr uint64) { - // Make sure we don't generate a zero address by adding 1 to i - return binary.LittleEndian.Uint64(a[:]) - 1 +// WriteDeltas generates returns the deltas for payset. +func (g *generator) WriteDeltas(output io.Writer, round uint64) error { + delta, err := g.ledger.GetStateDeltaForRound(basics.Round(round)) + if err != nil { + return fmt.Errorf("err getting state delta for round %d, %v", round, err) + } + // msgp encode deltas + data, err := encode(protocol.CodecHandle, delta) + if err != nil { + return err + } + _, err = output.Write(data) + if err != nil { + return err + } + return nil } // initializeAccounting creates the genesis accounts. @@ -686,6 +705,35 @@ func (g *generator) generateAssetTxn(round uint64, intra uint64) (transactions.S return signTxn(txn), transactions.ApplyData{}, nil } +func (g *generator) initializeLedger() { + genBal := convertToGenesisBalances(g.balances) + // add rewards pool with min balance + genBal[g.rewardsPool] = basics.AccountData{ + MicroAlgos: basics.MicroAlgos{Raw: g.params.MinBalance}, + } + bal := bookkeeping.MakeGenesisBalances(genBal, g.feeSink, g.rewardsPool) + block, err := bookkeeping.MakeGenesisBlock(g.protocol, bal, g.genesisID, g.genesisHash) + if err != nil { + fmt.Printf("error making genesis: %v\n.", err) + os.Exit(1) + } + l, err := ledger.OpenLedger(logging.Base(), "block-generator", true, ledgercore.InitState{ + Block: block, + Accounts: bal.Balances, + GenesisHash: g.genesisHash, + }, cconfig.GetDefaultLocal()) + if err != nil { + fmt.Printf("error initializing ledger: %v\n.", err) + os.Exit(1) + } + g.ledger = l +} + +// Stop cleans up allocated resources. +func (g *generator) Stop() { + g.ledger.Close() +} + func (g *generator) WriteAccount(output io.Writer, accountString string) error { addr, err := basics.UnmarshalChecksumAddress(accountString) if err != nil { diff --git a/tools/block-generator/generator/generate_test.go b/tools/block-generator/generator/generate_test.go index 4f25b4d0fb..78ca9e0323 100644 --- a/tools/block-generator/generator/generate_test.go +++ b/tools/block-generator/generator/generate_test.go @@ -18,8 +18,11 @@ package generator import ( "bytes" + "net/http" + "net/http/httptest" "testing" + "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/rpcs" @@ -31,7 +34,7 @@ func makePrivateGenerator(t *testing.T) *generator { partitiontest.PartitionTest(t) publicGenerator, err := MakeGenerator(GenerationConfig{ NumGenesisAccounts: 10, - GenesisAccountInitialBalance: 10000000000000000000, + GenesisAccountInitialBalance: 1000000000000, PaymentTransactionFraction: 1.0, PaymentNewAccountFraction: 1.0, AssetCreateFraction: 1.0, @@ -204,13 +207,45 @@ func TestWriteRound(t *testing.T) { var block rpcs.EncodedBlockCert protocol.Decode(data, &block) require.Len(t, block.Block.Payset, int(g.config.TxnPerBlock)) + require.NotNil(t, g.ledger) + require.Equal(t, basics.Round(1), g.ledger.Latest()) + _, err := g.ledger.GetStateDeltaForRound(1) + require.NoError(t, err) } -func TestIndexToAccountAndAccountToIndex(t *testing.T) { +func TestHandlers(t *testing.T) { partitiontest.PartitionTest(t) - for i := uint64(0); i < uint64(100000); i++ { - acct := indexToAccount(i) - result := accountToIndex(acct) - require.Equal(t, i, result) + g := makePrivateGenerator(t) + handler := getBlockHandler(g) + var testcases = []struct { + name string + url string + err string + }{ + { + name: "no block", + url: "/v2/blocks/?nothing", + err: "invalid request path, /", + }, + { + name: "blocks: round must be numeric", + url: "/v2/blocks/round", + err: `strconv.ParseUint: parsing "round": invalid syntax`, + }, + { + name: "deltas: round must be numeric", + url: "/v2/deltas/round", + err: `strconv.ParseUint: parsing "round": invalid syntax`, + }, + } + + for _, testcase := range testcases { + t.Run(testcase.name, func(t *testing.T) { + req := httptest.NewRequest("GET", testcase.url, nil) + w := httptest.NewRecorder() + handler(w, req) + require.Equal(t, http.StatusBadRequest, w.Code) + require.Contains(t, w.Body.String(), testcase.err) + }) } } diff --git a/tools/block-generator/generator/server.go b/tools/block-generator/generator/server.go index 10adc04728..e0c3314082 100644 --- a/tools/block-generator/generator/server.go +++ b/tools/block-generator/generator/server.go @@ -20,6 +20,7 @@ import ( "fmt" "net/http" "os" + "strconv" "strings" "time" @@ -67,6 +68,8 @@ func MakeServerWithMiddleware(configFile string, addr string, blocksMiddleware B mux.HandleFunc("/genesis", getGenesisHandler(gen)) mux.HandleFunc("/report", getReportHandler(gen)) mux.HandleFunc("/v2/status/wait-for-block-after/", getStatusWaitHandler(gen)) + mux.HandleFunc("/v2/ledger/sync/", func(w http.ResponseWriter, r *http.Request) {}) + mux.HandleFunc("/v2/deltas/", getDeltasHandler(gen)) return &http.Server{ Addr: addr, @@ -107,12 +110,16 @@ func getGenesisHandler(gen Generator) func(w http.ResponseWriter, r *http.Reques func getBlockHandler(gen Generator) func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) { // The generator doesn't actually care about the block... - round, err := parseRound(r.URL.Path) + s, err := parseURL(r.URL.Path) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + round, err := strconv.ParseUint(s, 0, 64) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } - maybeWriteError(w, gen.WriteBlock(w, round)) } } @@ -120,55 +127,38 @@ func getBlockHandler(gen Generator) func(w http.ResponseWriter, r *http.Request) func getAccountHandler(gen Generator) func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) { // The generator doesn't actually care about the block... - account, err := parseAccount(r.URL.Path) + account, err := parseURL(r.URL.Path) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } - maybeWriteError(w, gen.WriteAccount(w, account)) } } -const blockQueryPrefix = "/v2/blocks/" -const blockQueryBlockIdx = len(blockQueryPrefix) -const accountsQueryPrefix = "/v2/accounts/" -const accountsQueryAccountIdx = len(accountsQueryPrefix) - -func parseRound(path string) (uint64, error) { - if !strings.HasPrefix(path, blockQueryPrefix) { - return 0, fmt.Errorf("not a blocks query: %s", path) - } - - result := uint64(0) - pathlen := len(path) - - if pathlen == blockQueryBlockIdx { - return 0, fmt.Errorf("no block in path") - } - - for i := blockQueryBlockIdx; i < pathlen; i++ { - if path[i] < '0' || path[i] > '9' { - if i == blockQueryBlockIdx { - return 0, fmt.Errorf("no block in path") - } - break +func getDeltasHandler(gen Generator) func(w http.ResponseWriter, r *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + s, err := parseURL(r.URL.Path) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return } - result = (uint64(10) * result) + uint64(int(path[i])-'0') + round, err := strconv.ParseUint(s, 0, 64) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + maybeWriteError(w, gen.WriteDeltas(w, round)) } - return result, nil } -func parseAccount(path string) (string, error) { - if !strings.HasPrefix(path, accountsQueryPrefix) { - return "", fmt.Errorf("not a accounts query: %s", path) +func parseURL(path string) (string, error) { + i := strings.LastIndex(path, "/") + if i == len(path)-1 { + return "", fmt.Errorf("invalid request path, %s", path) } - - pathlen := len(path) - - if pathlen == accountsQueryAccountIdx { - return "", fmt.Errorf("no address in path") + if strings.Contains(path[i+1:], "?") { + return strings.Split(path[i+1:], "?")[0], nil } - - return path[accountsQueryAccountIdx:], nil + return path[i+1:], nil } diff --git a/tools/block-generator/generator/server_test.go b/tools/block-generator/generator/server_test.go index 4d2863aeff..6e21d77586 100644 --- a/tools/block-generator/generator/server_test.go +++ b/tools/block-generator/generator/server_test.go @@ -45,57 +45,78 @@ func TestInitConfigFileNotExist(t *testing.T) { } } -func TestParseRound(t *testing.T) { +func TestParseURL(t *testing.T) { partitiontest.PartitionTest(t) + const blockQueryPrefix = "http://v2/blocks/" + const accountQueryPrefix = "http://v2/accounts/" + const deltaQueryPrefix = "http://v2/deltas/" var testcases = []struct { name string url string - expectedRound uint64 + expectedRound string err string }{ { name: "no block", url: "/v2/blocks/", - expectedRound: 0, - err: "no block in path", - }, - { - name: "no block 2", - url: "/v2/blocks/?nothing", - expectedRound: 0, - err: "no block in path", - }, - { - name: "invalid prefix", - url: "/v2/wrong/prefix/1", - expectedRound: 0, - err: "not a blocks query", + expectedRound: "", + err: "invalid request path, /v2/blocks/", }, { name: "normal one digit", url: fmt.Sprintf("%s1", blockQueryPrefix), - expectedRound: 1, + expectedRound: "1", err: "", }, { name: "normal long number", url: fmt.Sprintf("%s12345678", blockQueryPrefix), - expectedRound: 12345678, + expectedRound: "12345678", + err: "", + }, + { + name: "with query parameters", + url: fmt.Sprintf("%s1234?pretty", blockQueryPrefix), + expectedRound: "1234", err: "", }, { name: "with query parameters", url: fmt.Sprintf("%s1234?pretty", blockQueryPrefix), - expectedRound: 1234, + expectedRound: "1234", + err: "", + }, + { + name: "no deltas", + url: "/v2/deltas/", + expectedRound: "", + err: "invalid request path, /v2/deltas/", + }, + { + name: "deltas", + url: fmt.Sprintf("%s123?Format=msgp", deltaQueryPrefix), + expectedRound: "123", + err: "", + }, + { + name: "no account", + url: "/v2/accounts/", + expectedRound: "", + err: "invalid request path, /v2/accounts/", + }, + { + name: "accounts", + url: fmt.Sprintf("%sAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGFFWAF4", accountQueryPrefix), + expectedRound: "AIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGFFWAF4", err: "", }, } for _, testcase := range testcases { t.Run(testcase.name, func(t *testing.T) { - round, err := parseRound(testcase.url) + round, err := parseURL(testcase.url) if len(testcase.err) == 0 { - msg := fmt.Sprintf("Unexpected error parsing '%s', expected round '%d' received error: %v", + msg := fmt.Sprintf("Unexpected error parsing '%s', expected round '%s' received error: %v", testcase.url, testcase.expectedRound, err) require.NoError(t, err, msg) assert.Equal(t, testcase.expectedRound, round) diff --git a/tools/block-generator/generator/utils.go b/tools/block-generator/generator/utils.go index 7c7a3980ce..59ff7f2e06 100644 --- a/tools/block-generator/generator/utils.go +++ b/tools/block-generator/generator/utils.go @@ -17,8 +17,12 @@ package generator import ( + "encoding/binary" "fmt" "math/rand" + + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-codec/codec" ) func weightedSelection(weights []float32, options []interface{}, defaultOption interface{}) (selection interface{}, err error) { @@ -43,3 +47,35 @@ func weightedSelectionInternal(selectionNumber float32, weights []float32, optio selection = defaultOption return } + +func indexToAccount(i uint64) (addr basics.Address) { + // Make sure we don't generate a zero address by adding 1 to i + binary.LittleEndian.PutUint64(addr[:], i+1) + return +} + +func accountToIndex(a basics.Address) (addr uint64) { + // Make sure we don't generate a zero address by adding 1 to i + return binary.LittleEndian.Uint64(a[:]) - 1 +} + +func convertToGenesisBalances(balances []uint64) map[basics.Address]basics.AccountData { + genesisBalances := make(map[basics.Address]basics.AccountData) + for i, balance := range balances { + genesisBalances[indexToAccount(uint64(i))] = basics.AccountData{ + MicroAlgos: basics.MicroAlgos{Raw: balance}, + } + } + return genesisBalances +} + +func encode(handle codec.Handle, obj interface{}) ([]byte, error) { + var output []byte + enc := codec.NewEncoderBytes(&output, handle) + + err := enc.Encode(obj) + if err != nil { + return nil, fmt.Errorf("failed to encode object: %v", err) + } + return output, nil +} diff --git a/tools/block-generator/generator/utils_test.go b/tools/block-generator/generator/utils_test.go index 7fc289d695..fb69a43fee 100644 --- a/tools/block-generator/generator/utils_test.go +++ b/tools/block-generator/generator/utils_test.go @@ -109,3 +109,24 @@ func TestWeightedSelectionOutOfRange(t *testing.T) { } assert.Fail(t, "Expected an out of range error by this point.") } + +func TestConvertToGenesisBalance(t *testing.T) { + partitiontest.PartitionTest(t) + balance := []uint64{100, 200, 300} + genesisBalances := convertToGenesisBalances(balance) + require.Equal(t, 3, len(genesisBalances)) + for i, bal := range balance { + require.Equal(t, bal, genesisBalances[indexToAccount(uint64(i))].MicroAlgos.Raw) + } +} + +func TestIndexToAccountAndAccountToIndex(t *testing.T) { + partitiontest.PartitionTest(t) + account := indexToAccount(1) + fmt.Printf("account: %v\n", account) + for i := uint64(0); i < uint64(100000); i++ { + acct := indexToAccount(i) + result := accountToIndex(acct) + require.Equal(t, i, result) + } +} diff --git a/tools/block-generator/runner/run.go b/tools/block-generator/runner/run.go index 7d933c557b..9604212da1 100644 --- a/tools/block-generator/runner/run.go +++ b/tools/block-generator/runner/run.go @@ -341,6 +341,8 @@ func startGenerator(configFile string, addr string, blockMiddleware func(http.Ha }() return func() error { + // stop generator + defer generator.Stop() // Shutdown blocks until the server has stopped. if err := server.Shutdown(context.Background()); err != nil { return fmt.Errorf("failed during generator graceful shutdown: %w", err) From c252aba8607cd532508c76b89eca2e100209e864 Mon Sep 17 00:00:00 2001 From: abebeos <129396476+abebeos@users.noreply.github.com> Date: Thu, 13 Apr 2023 18:12:32 +0300 Subject: [PATCH 044/125] Fix: broken links (correct md link syntax) in contributing.md (#5301) --- CONTRIBUTING.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 599d4a3b4b..461f9eebe3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,13 +7,13 @@ The [Algorand GitHub Organization](https://github.com/algorand) has all of our o ## Non-code Contributions While contributions come in many forms, this document is focused on code. For other types of involvement, see the following: -* [Reporting issues and features requests.](go-algorand-issues) -* [Security vulnerability disclosures.](security-disclosure) -* [Documentation improvements.](algorand-docs) +* [Reporting issues and features requests.][go-algorand-issues] +* [Security vulnerability disclosures.][security-disclosure] +* [Documentation improvements.][algorand-docs] ## Contribution Model -All changes to `go-algorand` are made through the same process: a pull request targeting the `master` branch. This goes for internal and external contributions. To familiarize yourself with the process we recommend that you review the current open pull requests, and the GitHub documentation for [creating a pull request from a fork](gh-pr-process). +All changes to `go-algorand` are made through the same process: a pull request targeting the `master` branch. This goes for internal and external contributions. To familiarize yourself with the process we recommend that you review the current open pull requests, and the GitHub documentation for [creating a pull request from a fork][gh-pr-process]. Note: some of our other projects are using gitflow, for these the process is the same but you will target pull requests against the `develop` branch. From afc7387edd789b11a87fa9cda83d0d337059995e Mon Sep 17 00:00:00 2001 From: Will Winder Date: Thu, 13 Apr 2023 11:20:22 -0400 Subject: [PATCH 045/125] ledger: Check MaxAcctLookback in tracker. (#5300) --- ledger/tracker.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ledger/tracker.go b/ledger/tracker.go index 375b63c98c..6eab2e1d02 100644 --- a/ledger/tracker.go +++ b/ledger/tracker.go @@ -727,8 +727,8 @@ func (tr *trackerRegistry) replay(l ledgerForTracker) (err error) { roundsBehind = blk.Round() - tr.dbRound tr.mu.RUnlock() - // are we too far behind ? ( taking into consideration the catchpoint writing, which can stall the writing for quite a bit ) - if roundsBehind > initializeCachesRoundFlushInterval+basics.Round(catchpointInterval) { + // are we farther behind than we need to be? Consider: catchpoint interval, flush interval and max acct lookback. + if roundsBehind > basics.Round(maxAcctLookback) && roundsBehind > initializeCachesRoundFlushInterval+basics.Round(catchpointInterval) { // we're unable to persist changes. This is unexpected, but there is no point in keep trying batching additional changes since any further changes // would just accumulate in memory. close(blockEvalFailed) From 1d80ccb1e213268cc6ce20e93e4876a5d97d9762 Mon Sep 17 00:00:00 2001 From: Will Winder Date: Fri, 14 Apr 2023 15:53:15 -0400 Subject: [PATCH 046/125] chore: Rename ModStateProofNextRound to StateProofNext. (#5265) --- ledger/apply/stateproof_test.go | 2 +- ledger/eval/cow.go | 10 +++++----- ledger/ledger_test.go | 2 +- ledger/ledgercore/statedelta.go | 8 ++++---- ledger/ledgercore/statedelta_test.go | 22 +++++++++++----------- ledger/spverificationtracker.go | 4 ++-- ledger/spverificationtracker_test.go | 2 +- 7 files changed, 25 insertions(+), 25 deletions(-) diff --git a/ledger/apply/stateproof_test.go b/ledger/apply/stateproof_test.go index 3160b9d057..72c898475d 100644 --- a/ledger/apply/stateproof_test.go +++ b/ledger/apply/stateproof_test.go @@ -270,6 +270,6 @@ func TestApplyStateProof(t *testing.T) { // transaction should be applied without stateproof validation (no context, blockheader or valid stateproof needed as it represents a node catching up) err = StateProof(stateProofTx, atRound, applier, false) a.NoError(err) - // make sure that the ModStateProofNextRound was updated correctly after applying + // make sure that the StateProofNext was updated correctly after applying a.Equal(basics.Round(512+config.Consensus[protocol.ConsensusFuture].StateProofInterval), applier.GetStateProofNextRound()) } diff --git a/ledger/eval/cow.go b/ledger/eval/cow.go index 0b5909d7b0..4a04eb83eb 100644 --- a/ledger/eval/cow.go +++ b/ledger/eval/cow.go @@ -235,8 +235,8 @@ func (cb *roundCowState) Counter() uint64 { } func (cb *roundCowState) GetStateProofNextRound() basics.Round { - if cb.mods.ModStateProofNextRound != 0 { - return cb.mods.ModStateProofNextRound + if cb.mods.StateProofNext != 0 { + return cb.mods.StateProofNext } return cb.lookupParent.GetStateProofNextRound() } @@ -266,7 +266,7 @@ func (cb *roundCowState) addTx(txn transactions.Transaction, txid transactions.T } func (cb *roundCowState) SetStateProofNextRound(rnd basics.Round) { - cb.mods.ModStateProofNextRound = rnd + cb.mods.StateProofNext = rnd } func (cb *roundCowState) child(hint int) *roundCowState { @@ -274,7 +274,7 @@ func (cb *roundCowState) child(hint int) *roundCowState { ch.lookupParent = cb ch.commitParent = cb ch.proto = cb.proto - ch.mods.PopulateStateDelta(cb.mods.Hdr, cb.mods.PrevTimestamp, hint, cb.mods.ModStateProofNextRound) + ch.mods.PopulateStateDelta(cb.mods.Hdr, cb.mods.PrevTimestamp, hint, cb.mods.StateProofNext) if ch.sdeltas == nil { ch.sdeltas = make(map[basics.Address]map[storagePtr]*storageDelta) @@ -318,7 +318,7 @@ func (cb *roundCowState) commitToParent() { } } } - cb.commitParent.mods.ModStateProofNextRound = cb.mods.ModStateProofNextRound + cb.commitParent.mods.StateProofNext = cb.mods.StateProofNext for key, value := range cb.mods.KvMods { cb.commitParent.mods.AddKvMod(key, value) diff --git a/ledger/ledger_test.go b/ledger/ledger_test.go index 9dab45207f..fb2531041e 100644 --- a/ledger/ledger_test.go +++ b/ledger/ledger_test.go @@ -3189,7 +3189,7 @@ func TestLedgerSPVerificationTracker(t *testing.T) { // to the ledger. delta, err := eval.Eval(context.Background(), l, blk, false, l.verifiedTxnCache, nil) require.NoError(t, err) - delta.ModStateProofNextRound = stateProofReceived.StateProofNextRound + delta.StateProofNext = stateProofReceived.StateProofNextRound vb := ledgercore.MakeValidatedBlock(blk, delta) err = l.AddValidatedBlock(vb, agreement.Certificate{}) require.NoError(t, err) diff --git a/ledger/ledgercore/statedelta.go b/ledger/ledgercore/statedelta.go index 0c940e32cf..1a0f824de1 100644 --- a/ledger/ledgercore/statedelta.go +++ b/ledger/ledgercore/statedelta.go @@ -110,10 +110,10 @@ type StateDelta struct { // new block header; read-only Hdr *bookkeeping.BlockHeader - // ModStateProofNextRound represents modification on StateProofNextRound field in the block header. If the block contains + // StateProofNext represents modification on StateProofNextRound field in the block header. If the block contains // a valid state proof transaction, this field will contain the next round for state proof. // otherwise it will be set to 0. - ModStateProofNextRound basics.Round + StateProofNext basics.Round // previous block timestamp PrevTimestamp int64 @@ -215,7 +215,7 @@ func (sd *StateDelta) PopulateStateDelta(hdr *bookkeeping.BlockHeader, prevTimes sd.initialHint = hint } sd.Hdr = hdr - sd.ModStateProofNextRound = stateProofNext + sd.StateProofNext = stateProofNext sd.PrevTimestamp = prevTimestamp } @@ -248,7 +248,7 @@ func (sd *StateDelta) Reset() { // these fields are going to be populated on next use but resetting them anyway for safety. // we are not resetting sd.initialHint since it should only be reset if reallocating AccountDeltas sd.Hdr = nil - sd.ModStateProofNextRound = basics.Round(0) + sd.StateProofNext = basics.Round(0) sd.PrevTimestamp = 0 } diff --git a/ledger/ledgercore/statedelta_test.go b/ledger/ledgercore/statedelta_test.go index 95f3bcb325..dd0f3e201d 100644 --- a/ledger/ledgercore/statedelta_test.go +++ b/ledger/ledgercore/statedelta_test.go @@ -150,7 +150,7 @@ func TestStateDeltaReset(t *testing.T) { // StateDeltas simple fields require.Zero(t, sd.Hdr) - require.Zero(t, sd.ModStateProofNextRound) + require.Zero(t, sd.StateProofNext) require.Zero(t, sd.PrevTimestamp) require.Zero(t, sd.Totals) @@ -184,16 +184,16 @@ func TestStateDeltaReflect(t *testing.T) { partitiontest.PartitionTest(t) stateDeltaFieldNames := map[string]struct{}{ - "Accts": {}, - "KvMods": {}, - "Txids": {}, - "Txleases": {}, - "Creatables": {}, - "Hdr": {}, - "ModStateProofNextRound": {}, - "PrevTimestamp": {}, - "initialHint": {}, - "Totals": {}, + "Accts": {}, + "KvMods": {}, + "Txids": {}, + "Txleases": {}, + "Creatables": {}, + "Hdr": {}, + "StateProofNext": {}, + "PrevTimestamp": {}, + "initialHint": {}, + "Totals": {}, } sd := StateDelta{} diff --git a/ledger/spverificationtracker.go b/ledger/spverificationtracker.go index 4dda2b7057..0d4597633e 100644 --- a/ledger/spverificationtracker.go +++ b/ledger/spverificationtracker.go @@ -96,7 +96,7 @@ func (spt *spVerificationTracker) newBlock(blk bookkeeping.Block, delta ledgerco spt.appendCommitContext(&blk) } - if delta.ModStateProofNextRound != 0 { + if delta.StateProofNext != 0 { spt.appendDeleteContext(&blk, &delta) } } @@ -306,7 +306,7 @@ func (spt *spVerificationTracker) appendDeleteContext(blk *bookkeeping.Block, de deletionContext := verificationDeleteContext{ confirmedRound: blk.Round(), - stateProofNextRound: delta.ModStateProofNextRound, + stateProofNextRound: delta.StateProofNext, } spt.pendingDeleteContexts = append(spt.pendingDeleteContexts, deletionContext) diff --git a/ledger/spverificationtracker_test.go b/ledger/spverificationtracker_test.go index 53e532a067..e0f073fe4d 100644 --- a/ledger/spverificationtracker_test.go +++ b/ledger/spverificationtracker_test.go @@ -141,7 +141,7 @@ func feedBlocksUpToRound(spt *spVerificationTracker, prevBlock *blockEntry, targ stateProofDelta = currentStateProofNextRound } - spt.newBlock(block.block, ledgercore.StateDelta{ModStateProofNextRound: stateProofDelta}) + spt.newBlock(block.block, ledgercore.StateDelta{StateProofNext: stateProofDelta}) prevBlock = &block } From 15bc838c6387010b3ac1aa54987cad2b98f72568 Mon Sep 17 00:00:00 2001 From: Ian Suvak Date: Tue, 18 Apr 2023 14:32:20 -0500 Subject: [PATCH 047/125] network: enforce maximum header size on outgoing ws conns (#5268) --- go.mod | 4 +- go.sum | 4 +- network/wsNetwork.go | 10 ++ network/wsNetwork_test.go | 198 +++++++++++++++++++++++++++++++++++++- 4 files changed, 207 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index a63e13e5c2..47e6bcd8f2 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/algorand/graphtrace v0.1.0 github.com/algorand/msgp v1.1.53 github.com/algorand/oapi-codegen v1.12.0-algorand.0 - github.com/algorand/websocket v1.4.5 + 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 @@ -36,6 +36,7 @@ require ( golang.org/x/sys v0.1.0 golang.org/x/text v0.4.0 gopkg.in/sohlich/elogrus.v3 v3.0.0-20180410122755-1fa29e2f2009 + gopkg.in/yaml.v3 v3.0.1 ) require ( @@ -71,5 +72,4 @@ require ( golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 // 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 ) diff --git a/go.sum b/go.sum index 0bf38663bd..21406525ea 100644 --- a/go.sum +++ b/go.sum @@ -18,8 +18,8 @@ github.com/algorand/msgp v1.1.53 h1:D6HKLyvLE6ltfsf8Apsrc+kqYb/CcOZEAfh1DpkPrNg= github.com/algorand/msgp v1.1.53/go.mod h1:5K3d58/poT5fPmtiwuQft6GjgSrVEM46KoXdLrID8ZU= github.com/algorand/oapi-codegen v1.12.0-algorand.0 h1:W9PvED+wAJc+9EeXPONnA+0zE9UhynEqoDs4OgAxKhk= github.com/algorand/oapi-codegen v1.12.0-algorand.0/go.mod h1:tIWJ9K/qrLDVDt5A1p82UmxZIEGxv2X+uoujdhEAL48= -github.com/algorand/websocket v1.4.5 h1:Cs6UTaCReAl02evYxmN8k57cNHmBILRcspfSxYg4AJE= -github.com/algorand/websocket v1.4.5/go.mod h1:79n6FSZY08yQagHzE/YWZqTPBYfY5wc3IS+UTZe1W5c= +github.com/algorand/websocket v1.4.6 h1:I0kV4EYwatuUrKtNiwzYYgojgwh6pksDmlqntKG2Woc= +github.com/algorand/websocket v1.4.6/go.mod h1:HJmdGzFtnlUQ4nTzZP6WrT29oGYf1t6Ybi64vROcT+M= 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/aws/aws-sdk-go v1.33.0 h1:Bq5Y6VTLbfnJp1IV8EL/qUU5qO1DYHda/zis/sqevkY= diff --git a/network/wsNetwork.go b/network/wsNetwork.go index 7339bbde61..9196553524 100644 --- a/network/wsNetwork.go +++ b/network/wsNetwork.go @@ -105,6 +105,9 @@ const unprintableCharacterGlyph = "▯" // PublicAddress (which will match HTTP Listener's Address) in tests only. const testingPublicAddress = "testing" +// Maximum number of bytes to read from a header when trying to establish a websocket connection. +const wsMaxHeaderBytes = 4096 + var networkIncomingConnections = metrics.MakeGauge(metrics.NetworkIncomingConnections) var networkOutgoingConnections = metrics.MakeGauge(metrics.NetworkOutgoingConnections) @@ -396,6 +399,9 @@ type WebsocketNetwork struct { // outgoingMessagesBufferSize is the size used for outgoing messages. outgoingMessagesBufferSize int + // 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 @@ -758,6 +764,8 @@ func (wn *WebsocketNetwork) setup() { config.Consensus[protocol.ConsensusCurrentVersion].DownCommitteeSize), ) + wn.wsMaxHeaderBytes = wsMaxHeaderBytes + wn.identityTracker = NewIdentityTracker() wn.broadcastQueueHighPrio = make(chan broadcastRequest, wn.outgoingMessagesBufferSize) @@ -2193,9 +2201,11 @@ func (wn *WebsocketNetwork) tryConnect(addr, gossipAddr string) { EnableCompression: false, NetDialContext: wn.dialer.DialContext, NetDial: wn.dialer.Dial, + MaxHeaderSize: wn.wsMaxHeaderBytes, } conn, response, err := websocketDialer.DialContext(wn.ctx, gossipAddr, requestHeader) + if err != nil { if err == websocket.ErrBadHandshake { // reading here from ioutil is safe only because it came from DialContext above, which already finished reading all the data from the network diff --git a/network/wsNetwork_test.go b/network/wsNetwork_test.go index 4f7d01c95b..3229e2ae20 100644 --- a/network/wsNetwork_test.go +++ b/network/wsNetwork_test.go @@ -27,6 +27,7 @@ import ( "math/rand" "net" "net/http" + "net/http/httptest" "net/url" "os" "runtime" @@ -41,6 +42,7 @@ import ( "github.com/stretchr/testify/require" "github.com/algorand/go-deadlock" + "github.com/algorand/websocket" "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" @@ -54,6 +56,8 @@ import ( const sendBufferLength = 1000 +const genesisID = "go-test-network-genesis" + func init() { // this allows test code to use out-of-protocol message tags and have them go through allowCustomTags = true @@ -127,7 +131,7 @@ func makeTestWebsocketNodeWithConfig(t testing.TB, conf config.Local, opts ...te log: log, config: conf, phonebook: MakePhonebook(1, 1*time.Millisecond), - GenesisID: "go-test-network-genesis", + GenesisID: genesisID, NetworkID: config.Devtestnet, } // apply options to newly-created WebsocketNetwork, if provided @@ -990,7 +994,7 @@ func makeTestFilterWebsocketNode(t *testing.T, nodename string) *WebsocketNetwor log: logging.TestingLog(t).With("node", nodename), config: dc, phonebook: MakePhonebook(1, 1*time.Millisecond), - GenesisID: "go-test-network-genesis", + GenesisID: genesisID, NetworkID: config.Devtestnet, } require.True(t, wn.config.EnableIncomingMessageFilter) @@ -2462,7 +2466,7 @@ func TestSlowPeerDisconnection(t *testing.T) { log: log, config: defaultConfig, phonebook: MakePhonebook(1, 1*time.Millisecond), - GenesisID: "go-test-network-genesis", + GenesisID: genesisID, NetworkID: config.Devtestnet, slowWritingPeerMonitorInterval: time.Millisecond * 50, } @@ -2537,7 +2541,7 @@ func TestForceMessageRelaying(t *testing.T) { log: log, config: defaultConfig, phonebook: MakePhonebook(1, 1*time.Millisecond), - GenesisID: "go-test-network-genesis", + GenesisID: genesisID, NetworkID: config.Devtestnet, } wn.setup() @@ -2631,7 +2635,7 @@ func TestCheckProtocolVersionMatch(t *testing.T) { log: log, config: defaultConfig, phonebook: MakePhonebook(1, 1*time.Millisecond), - GenesisID: "go-test-network-genesis", + GenesisID: genesisID, NetworkID: config.Devtestnet, } wn.setup() @@ -3757,3 +3761,187 @@ func TestWebsocketNetworkTelemetryTCP(t *testing.T) { t.Log("closed detailsA", string(pcdA)) t.Log("closed detailsB", string(pcdB)) } + +type mockServer struct { + *httptest.Server + URL string + t *testing.T + + waitForClientClose bool +} + +type mockHandler struct { + *testing.T + s *mockServer +} + +var mockUpgrader = websocket.Upgrader{ + ReadBufferSize: 1024, + WriteBufferSize: 1024, + EnableCompression: true, + Error: func(w http.ResponseWriter, r *http.Request, status int, reason error) { + http.Error(w, reason.Error(), status) + }, +} + +func buildWsResponseHeader() http.Header { + h := http.Header{} + h.Add(ProtocolVersionHeader, ProtocolVersion) + h.Add(GenesisHeader, genesisID) + h.Add(NodeRandomHeader, "randomHeader") + return h +} + +func (t mockHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + // Set the required headers to successfully establish a connection + ws, err := mockUpgrader.Upgrade(w, r, buildWsResponseHeader()) + if err != nil { + t.Logf("Upgrade: %v", err) + return + } + defer ws.Close() + // Send a message of interest immediately after the connection is established + wr, err := ws.NextWriter(websocket.BinaryMessage) + if err != nil { + t.Logf("NextWriter: %v", err) + return + } + + bytes := MarshallMessageOfInterest([]protocol.Tag{protocol.AgreementVoteTag}) + msgBytes := append([]byte(protocol.MsgOfInterestTag), bytes...) + _, err = wr.Write(msgBytes) + if err != nil { + t.Logf("Error writing MessageOfInterest: %v", err) + return + } + wr.Close() + + for true { + // echo a message back to the client + _, _, err := ws.NextReader() + if err != nil { + if _, ok := err.(*websocket.CloseError); ok && t.s.waitForClientClose { + t.Log("got client close") + return + } + return + } + } +} + +func makeWsProto(s string) string { + return "ws" + strings.TrimPrefix(s, "http") +} + +func newServer(t *testing.T) *mockServer { + var s mockServer + s.Server = httptest.NewServer(mockHandler{t, &s}) + s.Server.URL += "" + s.URL = makeWsProto(s.Server.URL) + return &s +} + +func TestMaxHeaderSize(t *testing.T) { + partitiontest.PartitionTest(t) + + netA := makeTestWebsocketNode(t, testWebsocketLogNameOption{"netA"}) + netA.config.GossipFanout = 1 + + netB := makeTestWebsocketNode(t, testWebsocketLogNameOption{"netB"}) + netB.config.GossipFanout = 1 + + netA.Start() + defer netA.Stop() + netB.Start() + defer netB.Stop() + + addrB, ok := netB.Address() + require.True(t, ok) + gossipB, err := netB.addrToGossipAddr(addrB) + require.NoError(t, err) + + // First make sure that the regular connection with default max header size works + netA.wsMaxHeaderBytes = wsMaxHeaderBytes + netA.wg.Add(1) + netA.tryConnect(addrB, gossipB) + time.Sleep(250 * time.Millisecond) + assert.Equal(t, 1, len(netA.peers)) + + netA.removePeer(netA.peers[0], disconnectReasonNone) + assert.Zero(t, len(netA.peers)) + + // Now try to connect with a max header size that is too small + logBuffer := bytes.NewBuffer(nil) + netA.log.SetOutput(logBuffer) + + netA.wsMaxHeaderBytes = 128 + netA.wg.Add(1) + netA.tryConnect(addrB, gossipB) + lg := logBuffer.String() + logBuffer.Reset() + time.Sleep(250 * time.Millisecond) + assert.Contains(t, lg, fmt.Sprintf("ws connect(%s) fail:", gossipB)) + assert.Zero(t, len(netA.peers)) + + // Test that setting 0 disables the max header size check + netA.wsMaxHeaderBytes = 0 + netA.wg.Add(1) + netA.tryConnect(addrB, gossipB) + time.Sleep(250 * time.Millisecond) + assert.Equal(t, 1, len(netA.peers)) +} + +func TestTryConnectEarlyWrite(t *testing.T) { + partitiontest.PartitionTest(t) + + netA := makeTestWebsocketNode(t, testWebsocketLogNameOption{"netA"}) + netA.config.GossipFanout = 1 + + s := newServer(t) + s.waitForClientClose = true + defer s.Close() + + netA.Start() + defer netA.Stop() + + dialer := websocket.Dialer{} + mconn, resp, _ := dialer.Dial(s.URL, nil) + expectedHeader := buildWsResponseHeader() + for k, v := range expectedHeader { + assert.Equal(t, v[0], resp.Header.Get(k)) + } + + // Fixed overhead of the full status line "HTTP/1.1 101 Switching Protocols" (32) + 4 bytes for two instance of CRLF + // one after the status line and one to separate headers from the body + minValidHeaderSize := 36 + for k, v := range resp.Header { + minValidHeaderSize += len(k) + len(v[0]) + 4 // + 4 is for the ": " and CRLF + } + mconn.Close() + + // Setting the max header size to 1 byte less than the minimum header size should fail + netA.wsMaxHeaderBytes = int64(minValidHeaderSize) - 1 + netA.wg.Add(1) + netA.tryConnect(s.URL, s.URL) + time.Sleep(250 * time.Millisecond) + assert.Len(t, netA.peers, 0) + + // Now set the max header size to the minimum header size and it should succeed + netA.wsMaxHeaderBytes = int64(minValidHeaderSize) + netA.wg.Add(1) + netA.tryConnect(s.URL, s.URL) + p := netA.peers[0] + var messageCount uint64 + for x := 0; x < 1000; x++ { + messageCount = atomic.LoadUint64(&p.miMessageCount) + if messageCount == 1 { + break + } + time.Sleep(2 * time.Millisecond) + } + + // Confirm that we successfuly received a message of interest + assert.Len(t, netA.peers, 1) + fmt.Printf("MI Message Count: %v\n", netA.peers[0].miMessageCount) + assert.Equal(t, uint64(1), netA.peers[0].miMessageCount) +} From 9901aa810b6bc144c01990f184f65616a53b4d86 Mon Sep 17 00:00:00 2001 From: AlgoAxel <113933518+AlgoAxel@users.noreply.github.com> Date: Tue, 18 Apr 2023 15:55:34 -0400 Subject: [PATCH 048/125] scripts: enhance upload_config.sh (#5260) --- scripts/upload_config.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/upload_config.sh b/scripts/upload_config.sh index 7ffaaca201..6ada497d2b 100755 --- a/scripts/upload_config.sh +++ b/scripts/upload_config.sh @@ -1,5 +1,6 @@ #!/usr/bin/env bash set -e +trap 'echo "ERROR: ${BASH_SOURCE}:${LINENO} ${BASH_COMMAND}"' ERR # upload_config.sh - Archives and uploads a netgoal configuration package from a specified directory # NOTE: Will only work if you have the required AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY vars set @@ -33,11 +34,11 @@ SRCPATH=${SCRIPTPATH}/.. export CHANNEL=$2 export FULLVERSION=$($SRCPATH/scripts/compute_build_number.sh -f) -TEMPDIR=$(mktemp -d 2>/dev/null || mktemp -d -t "tmp") +TEMPDIR=$(mktemp -d -t "upload_config.tmp.XXXXXX") TARFILE=${TEMPDIR}/config_${CHANNEL}_${FULLVERSION}.tar.gz cd $1 -tar -zcf ${TARFILE} * >/dev/null 2>&1 +tar -zcf ${TARFILE} * >/dev/null ${GOPATH}/bin/updater send -s ${TEMPDIR} -c ${CHANNEL} -b "${S3_RELEASE_BUCKET}" rm ${TARFILE} From 885191ade662296d5f4392a243c25820ec06dad9 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Tue, 18 Apr 2023 16:31:30 -0400 Subject: [PATCH 049/125] ledger: fix error shadowing in accountsNewRound (#5266) --- ledger/acctdeltas.go | 213 +++++++++++++++++++------------------- ledger/acctdeltas_test.go | 211 +++++++++++++++++++++++++++++++++++++ 2 files changed, 320 insertions(+), 104 deletions(-) diff --git a/ledger/acctdeltas.go b/ledger/acctdeltas.go index fcc937e01f..e587a8fcfa 100644 --- a/ledger/acctdeltas.go +++ b/ledger/acctdeltas.go @@ -759,13 +759,15 @@ func accountsNewRoundImpl( // if we didn't had it before, and we don't have anything now, just skip it. } else { // create a new entry. + var ref trackerdb.AccountRef normBalance := data.newAcct.NormalizedOnlineBalance(proto) - ref, err := writer.InsertAccount(data.address, normBalance, data.newAcct) - if err == nil { - updatedAccounts[updatedAccountIdx].Ref = ref - updatedAccounts[updatedAccountIdx].AccountData = data.newAcct - newAddressesRowIDs[data.address] = ref + ref, err = writer.InsertAccount(data.address, normBalance, data.newAcct) + if err != nil { + return nil, nil, nil, err } + updatedAccounts[updatedAccountIdx].Ref = ref + updatedAccounts[updatedAccountIdx].AccountData = data.newAcct + newAddressesRowIDs[data.address] = ref } } else { // non-zero rowid means we had a previous value. @@ -773,33 +775,33 @@ func accountsNewRoundImpl( // new value is zero, which means we need to delete the current value. var rowsAffected int64 rowsAffected, err = writer.DeleteAccount(data.oldAcct.Ref) - if err == nil { - // we deleted the entry successfully. - updatedAccounts[updatedAccountIdx].Ref = nil - updatedAccounts[updatedAccountIdx].AccountData = trackerdb.BaseAccountData{} - if rowsAffected != 1 { - err = fmt.Errorf("failed to delete accountbase row for account %v, rowid %d", data.address, data.oldAcct.Ref) - } + if err != nil { + return nil, nil, nil, err + } + // we deleted the entry successfully. + updatedAccounts[updatedAccountIdx].Ref = nil + updatedAccounts[updatedAccountIdx].AccountData = trackerdb.BaseAccountData{} + if rowsAffected != 1 { + err = fmt.Errorf("failed to delete accountbase row for account %v, rowid %d", data.address, data.oldAcct.Ref) + return nil, nil, nil, err } } else { var rowsAffected int64 normBalance := data.newAcct.NormalizedOnlineBalance(proto) rowsAffected, err = writer.UpdateAccount(data.oldAcct.Ref, normBalance, data.newAcct) - if err == nil { - // rowid doesn't change on update. - updatedAccounts[updatedAccountIdx].Ref = data.oldAcct.Ref - updatedAccounts[updatedAccountIdx].AccountData = data.newAcct - if rowsAffected != 1 { - err = fmt.Errorf("failed to update accountbase row for account %v, rowid %d", data.address, data.oldAcct.Ref) - } + if err != nil { + return nil, nil, nil, err + } + // rowid doesn't change on update. + updatedAccounts[updatedAccountIdx].Ref = data.oldAcct.Ref + updatedAccounts[updatedAccountIdx].AccountData = data.newAcct + if rowsAffected != 1 { + err = fmt.Errorf("failed to update accountbase row for account %v, rowid %d", data.address, data.oldAcct.Ref) + return nil, nil, nil, err } } } - if err != nil { - return - } - // set the returned persisted account states so that we could store that as the baseAccounts in commitRound updatedAccounts[updatedAccountIdx].Round = lastUpdateRound updatedAccounts[updatedAccountIdx].Addr = data.address @@ -850,7 +852,7 @@ func accountsNewRoundImpl( acctRef = newAddressesRowIDs[addr] if acctRef == nil && !inMemEntry { err = fmt.Errorf("cannot resolve address %s (%d), aidx %d, data %v", addr.String(), acctRef, aidx, data.newResource) - return + return nil, nil, nil, err } } var entry trackerdb.PersistedResourcesData @@ -867,7 +869,7 @@ func accountsNewRoundImpl( // create a new entry. if !data.newResource.IsApp() && !data.newResource.IsAsset() { err = fmt.Errorf("unknown creatable for addr %v (%d), aidx %d, data %v", addr, acctRef, aidx, data.newResource) - return + return nil, nil, nil, err } // check if we need to "upgrade" this insert operation into an update operation due to a scheduled // delete operation of the same resource. @@ -877,19 +879,22 @@ func accountsNewRoundImpl( delete(pendingResourcesDeletion, resourceKey{acctRef: acctRef, aidx: aidx}) var rowsAffected int64 rowsAffected, err = writer.UpdateResource(acctRef, aidx, data.newResource) - if err == nil { - // rowid doesn't change on update. - entry = trackerdb.PersistedResourcesData{AcctRef: acctRef, Aidx: aidx, Data: data.newResource, Round: lastUpdateRound} - if rowsAffected != 1 { - err = fmt.Errorf("failed to update resources row for addr %s (%d), aidx %d", addr, acctRef, aidx) - } + if err != nil { + return nil, nil, nil, err + } + // rowid doesn't change on update. + entry = trackerdb.PersistedResourcesData{AcctRef: acctRef, Aidx: aidx, Data: data.newResource, Round: lastUpdateRound} + if rowsAffected != 1 { + err = fmt.Errorf("failed to update resources row for addr %s (%d), aidx %d", addr, acctRef, aidx) + return nil, nil, nil, err } } else { _, err = writer.InsertResource(acctRef, aidx, data.newResource) - if err == nil { - // set the returned persisted account states so that we could store that as the baseResources in commitRound - entry = trackerdb.PersistedResourcesData{AcctRef: acctRef, Aidx: aidx, Data: data.newResource, Round: lastUpdateRound} + if err != nil { + return nil, nil, nil, err } + // set the returned persisted account states so that we could store that as the baseResources in commitRound + entry = trackerdb.PersistedResourcesData{AcctRef: acctRef, Aidx: aidx, Data: data.newResource, Round: lastUpdateRound} } } } else { @@ -901,24 +906,22 @@ func accountsNewRoundImpl( } else { if !data.newResource.IsApp() && !data.newResource.IsAsset() { err = fmt.Errorf("unknown creatable for addr %v (%d), aidx %d, data %v", addr, acctRef, aidx, data.newResource) - return + return nil, nil, nil, err } var rowsAffected int64 rowsAffected, err = writer.UpdateResource(acctRef, aidx, data.newResource) - if err == nil { - // rowid doesn't change on update. - entry = trackerdb.PersistedResourcesData{AcctRef: acctRef, Aidx: aidx, Data: data.newResource, Round: lastUpdateRound} - if rowsAffected != 1 { - err = fmt.Errorf("failed to update resources row for addr %s (%d), aidx %d", addr, acctRef, aidx) - } + if err != nil { + return nil, nil, nil, err + } + // rowid doesn't change on update. + entry = trackerdb.PersistedResourcesData{AcctRef: acctRef, Aidx: aidx, Data: data.newResource, Round: lastUpdateRound} + if rowsAffected != 1 { + err = fmt.Errorf("failed to update resources row for addr %s (%d), aidx %d", addr, acctRef, aidx) + return nil, nil, nil, err } } } - if err != nil { - return - } - deltas := updatedResources[addr] deltas = append(deltas, entry) updatedResources[addr] = deltas @@ -929,16 +932,15 @@ func accountsNewRoundImpl( // new value is zero, which means we need to delete the current value. var rowsAffected int64 rowsAffected, err = writer.DeleteResource(delRes.acctRef, delRes.aidx) - if err == nil { - // we deleted the entry successfully. - // set zero addrid to mark this entry invalid for subsequent addr to addrid resolution - // because the base account might gone. - if rowsAffected != 1 { - err = fmt.Errorf("failed to delete resources row (%d), aidx %d", delRes.acctRef, delRes.aidx) - } - } if err != nil { - return + return nil, nil, nil, err + } + // we deleted the entry successfully. + // set zero addrid to mark this entry invalid for subsequent addr to addrid resolution + // because the base account might gone. + if rowsAffected != 1 { + err = fmt.Errorf("failed to delete resources row (%d), aidx %d", delRes.acctRef, delRes.aidx) + return nil, nil, nil, err } } @@ -950,17 +952,20 @@ func accountsNewRoundImpl( continue // changed back within the delta span } err = writer.UpsertKvPair(key, mv.data) + if err != nil { + return nil, nil, nil, err + } updatedKVs[key] = trackerdb.PersistedKVData{Value: mv.data, Round: lastUpdateRound} } else { if mv.oldData == nil { // Came and went within the delta span continue } err = writer.DeleteKvPair(key) + if err != nil { + return nil, nil, nil, err + } updatedKVs[key] = trackerdb.PersistedKVData{Value: nil, Round: lastUpdateRound} } - if err != nil { - return - } } for cidx, cdelta := range creatables { @@ -970,7 +975,7 @@ func accountsNewRoundImpl( _, err = writer.DeleteCreatable(cidx, cdelta.Ctype) } if err != nil { - return + return nil, nil, nil, err } } @@ -998,25 +1003,27 @@ func onlineAccountsNewRoundImpl( if newStatus == basics.Online { if newAcct.IsVotingEmpty() { err = fmt.Errorf("empty voting data for online account %s: %v", data.address.String(), newAcct) - } else { - // create a new entry. - var ref trackerdb.OnlineAccountRef - normBalance := newAcct.NormalizedOnlineBalance(proto) - ref, err = writer.InsertOnlineAccount(data.address, normBalance, newAcct, updRound, uint64(newAcct.VoteLastValid)) - if err == nil { - updated := trackerdb.PersistedOnlineAccountData{ - Addr: data.address, - AccountData: newAcct, - Round: lastUpdateRound, - Ref: ref, - UpdRound: basics.Round(updRound), - } - updatedAccounts = append(updatedAccounts, updated) - prevAcct = updated - } + return nil, err } + // create a new entry. + var ref trackerdb.OnlineAccountRef + normBalance := newAcct.NormalizedOnlineBalance(proto) + ref, err = writer.InsertOnlineAccount(data.address, normBalance, newAcct, updRound, uint64(newAcct.VoteLastValid)) + if err != nil { + return nil, err + } + updated := trackerdb.PersistedOnlineAccountData{ + Addr: data.address, + AccountData: newAcct, + Round: lastUpdateRound, + Ref: ref, + UpdRound: basics.Round(updRound), + } + updatedAccounts = append(updatedAccounts, updated) + prevAcct = updated } else if !newAcct.IsVotingEmpty() { err = fmt.Errorf("non-empty voting data for non-online account %s: %v", data.address.String(), newAcct) + return nil, err } } } else { @@ -1025,46 +1032,44 @@ func onlineAccountsNewRoundImpl( // new value is zero then go offline if newStatus == basics.Online { err = fmt.Errorf("empty voting data but online account %s: %v", data.address.String(), newAcct) - } else { - var ref trackerdb.OnlineAccountRef - ref, err = writer.InsertOnlineAccount(data.address, 0, trackerdb.BaseOnlineAccountData{}, updRound, 0) - if err == nil { - updated := trackerdb.PersistedOnlineAccountData{ - Addr: data.address, - AccountData: trackerdb.BaseOnlineAccountData{}, - Round: lastUpdateRound, - Ref: ref, - UpdRound: basics.Round(updRound), - } - - updatedAccounts = append(updatedAccounts, updated) - prevAcct = updated - } + return nil, err + } + var ref trackerdb.OnlineAccountRef + ref, err = writer.InsertOnlineAccount(data.address, 0, trackerdb.BaseOnlineAccountData{}, updRound, 0) + if err != nil { + return nil, err + } + updated := trackerdb.PersistedOnlineAccountData{ + Addr: data.address, + AccountData: trackerdb.BaseOnlineAccountData{}, + Round: lastUpdateRound, + Ref: ref, + UpdRound: basics.Round(updRound), } + + updatedAccounts = append(updatedAccounts, updated) + prevAcct = updated } else { if prevAcct.AccountData != newAcct { var ref trackerdb.OnlineAccountRef normBalance := newAcct.NormalizedOnlineBalance(proto) ref, err = writer.InsertOnlineAccount(data.address, normBalance, newAcct, updRound, uint64(newAcct.VoteLastValid)) - if err == nil { - updated := trackerdb.PersistedOnlineAccountData{ - Addr: data.address, - AccountData: newAcct, - Round: lastUpdateRound, - Ref: ref, - UpdRound: basics.Round(updRound), - } - - updatedAccounts = append(updatedAccounts, updated) - prevAcct = updated + if err != nil { + return nil, err + } + updated := trackerdb.PersistedOnlineAccountData{ + Addr: data.address, + AccountData: newAcct, + Round: lastUpdateRound, + Ref: ref, + UpdRound: basics.Round(updRound), } + + updatedAccounts = append(updatedAccounts, updated) + prevAcct = updated } } } - - if err != nil { - return - } } } diff --git a/ledger/acctdeltas_test.go b/ledger/acctdeltas_test.go index e272983847..c5f74aed72 100644 --- a/ledger/acctdeltas_test.go +++ b/ledger/acctdeltas_test.go @@ -3036,6 +3036,217 @@ func TestOnlineAccountsNewRoundError(t *testing.T) { require.Empty(t, updated) } +type mockAccountsErrorWriter struct { +} + +var errMockAccountsErrorWriterIns = errors.New("synthetic ins err") +var errMockAccountsErrorWriterUpd = errors.New("synthetic upd err") +var errMockAccountsErrorWriterDel = errors.New("synthetic del err") + +func (w *mockAccountsErrorWriter) InsertAccount(addr basics.Address, normBalance uint64, data trackerdb.BaseAccountData) (ref trackerdb.AccountRef, err error) { + return nil, errMockAccountsErrorWriterIns +} +func (w *mockAccountsErrorWriter) DeleteAccount(ref trackerdb.AccountRef) (rowsAffected int64, err error) { + return 0, errMockAccountsErrorWriterDel +} +func (w *mockAccountsErrorWriter) UpdateAccount(ref trackerdb.AccountRef, normBalance uint64, data trackerdb.BaseAccountData) (rowsAffected int64, err error) { + return 0, errMockAccountsErrorWriterUpd +} +func (w *mockAccountsErrorWriter) InsertResource(accountRef trackerdb.AccountRef, aidx basics.CreatableIndex, data trackerdb.ResourcesData) (ref trackerdb.ResourceRef, err error) { + return nil, errMockAccountsErrorWriterIns +} +func (w *mockAccountsErrorWriter) DeleteResource(accountRef trackerdb.AccountRef, aidx basics.CreatableIndex) (rowsAffected int64, err error) { + return 0, errMockAccountsErrorWriterDel +} +func (w *mockAccountsErrorWriter) UpdateResource(accountRef trackerdb.AccountRef, aidx basics.CreatableIndex, data trackerdb.ResourcesData) (rowsAffected int64, err error) { + return 0, errMockAccountsErrorWriterUpd +} +func (w *mockAccountsErrorWriter) UpsertKvPair(key string, value []byte) error { + return errMockAccountsErrorWriterUpd +} +func (w *mockAccountsErrorWriter) DeleteKvPair(key string) error { + return errMockAccountsErrorWriterDel +} +func (w *mockAccountsErrorWriter) InsertCreatable(cidx basics.CreatableIndex, ctype basics.CreatableType, creator []byte) (ref trackerdb.CreatableRef, err error) { + return nil, errMockAccountsErrorWriterIns +} +func (w *mockAccountsErrorWriter) DeleteCreatable(cidx basics.CreatableIndex, ctype basics.CreatableType) (rowsAffected int64, err error) { + return 0, errMockAccountsErrorWriterDel +} +func (w *mockAccountsErrorWriter) Close() { +} + +// TestAccountsNewRoundError checks accountsNewRound propagates errors to the caller +func TestAccountsNewRoundError(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + writer := &mockAccountsErrorWriter{} + proto := config.Consensus[protocol.ConsensusCurrentVersion] + + addrA := ledgertesting.RandomAddress() + + type testcase struct { + ad accountDelta + rd resourceDelta + kd map[string]modifiedKvValue + cd map[basics.CreatableIndex]ledgercore.ModifiedCreatable + expErr error + } + + tests := []testcase{ + { + ad: accountDelta{ // acct A is new + address: addrA, + newAcct: trackerdb.BaseAccountData{ + MicroAlgos: basics.MicroAlgos{Raw: 100_000_000}, + UpdateRound: 1, + }, + }, + expErr: errMockAccountsErrorWriterIns, + }, + { + ad: accountDelta{ // acct A is old, update + address: addrA, + oldAcct: trackerdb.PersistedAccountData{ + Addr: addrA, + Ref: &mockEntryRef{1}, + AccountData: trackerdb.BaseAccountData{ + MicroAlgos: basics.MicroAlgos{Raw: 100_000_000}, + UpdateRound: 0, + }, + Round: 0, + }, + newAcct: trackerdb.BaseAccountData{ + MicroAlgos: basics.MicroAlgos{Raw: 100_000_000}, + UpdateRound: 1, + }, + }, + expErr: errMockAccountsErrorWriterUpd, + }, + { + ad: accountDelta{ // acct A is old, delete + address: addrA, + oldAcct: trackerdb.PersistedAccountData{ + Addr: addrA, + Ref: &mockEntryRef{1}, + AccountData: trackerdb.BaseAccountData{ + MicroAlgos: basics.MicroAlgos{Raw: 100_000_000}, + UpdateRound: 0, + }, + Round: 0, + }, + newAcct: trackerdb.BaseAccountData{}, + }, + expErr: errMockAccountsErrorWriterDel, + }, + { + rd: resourceDelta{ // new entry + oldResource: trackerdb.PersistedResourcesData{AcctRef: &mockEntryRef{1}}, + newResource: trackerdb.ResourcesData{ + Total: 1, + SchemaNumUint: 1, + }, + nAcctDeltas: 1, + address: addrA, + }, + expErr: errMockAccountsErrorWriterIns, + }, + { + rd: resourceDelta{ // existing entry + oldResource: trackerdb.PersistedResourcesData{ + AcctRef: &mockEntryRef{1}, + Data: trackerdb.ResourcesData{ + Total: 1, + SchemaNumUint: 1, + }, + }, + newResource: trackerdb.ResourcesData{ + Total: 2, + SchemaNumUint: 2, + }, + nAcctDeltas: 1, + address: addrA, + }, + expErr: errMockAccountsErrorWriterUpd, + }, + { + rd: resourceDelta{ // deleting entry + oldResource: trackerdb.PersistedResourcesData{ + AcctRef: &mockEntryRef{1}, + Data: trackerdb.ResourcesData{ + Total: 2, + SchemaNumUint: 2, + }, + }, + nAcctDeltas: 1, + address: addrA, + }, + expErr: errMockAccountsErrorWriterDel, + }, + { + kd: map[string]modifiedKvValue{ + "key1": { + data: []byte("value1"), + }, + }, + expErr: errMockAccountsErrorWriterUpd, + }, + { + kd: map[string]modifiedKvValue{ + "key1": { + oldData: []byte("value1"), + }, + }, + expErr: errMockAccountsErrorWriterDel, + }, + { + cd: map[basics.CreatableIndex]ledgercore.ModifiedCreatable{ + 1: { + Created: true, + }, + }, + expErr: errMockAccountsErrorWriterIns, + }, + { + cd: map[basics.CreatableIndex]ledgercore.ModifiedCreatable{ + 2: { + Created: false, + }, + }, + expErr: errMockAccountsErrorWriterDel, + }, + } + + for i, test := range tests { + t.Run(fmt.Sprintf("test-%d", i), func(t *testing.T) { + updates := compactAccountDeltas{} + resources := compactResourcesDeltas{} + if test.ad != (accountDelta{}) { + updates.deltas = append(updates.deltas, test.ad) + } + if test.rd.nAcctDeltas != 0 { + resources.deltas = append(resources.deltas, test.rd) + } + var kvs map[string]modifiedKvValue + if len(test.kd) != 0 { + kvs = test.kd + } + var creatables map[basics.CreatableIndex]ledgercore.ModifiedCreatable + if len(test.cd) != 0 { + creatables = test.cd + } + lastUpdateRound := basics.Round(i + 1) + updatedAcct, updatedResources, updatedKvs, err := accountsNewRoundImpl(writer, updates, resources, kvs, creatables, proto, lastUpdateRound) + require.Error(t, err) + require.Equal(t, test.expErr, err) + require.Empty(t, updatedAcct) + require.Empty(t, updatedResources) + require.Empty(t, updatedKvs) + }) + } +} + func randomBaseAccountData() trackerdb.BaseAccountData { vd := trackerdb.BaseVotingData{ VoteFirstValid: basics.Round(crypto.RandUint64()), From 30acd707819fc9bc2b59036902d8b453ffbd4e10 Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Tue, 18 Apr 2023 14:20:27 -0700 Subject: [PATCH 050/125] algod: Modify simulate endpoint request type (#5292) --- cmd/goal/clerk.go | 181 +++++---- daemon/algod/api/algod.oas2.json | 47 ++- daemon/algod/api/algod.oas3.yml | 51 ++- daemon/algod/api/client/restClient.go | 2 +- .../api/server/v2/generated/data/routes.go | 338 ++++++++--------- .../v2/generated/experimental/routes.go | 322 ++++++++-------- .../api/server/v2/generated/model/types.go | 15 + .../nonparticipating/private/routes.go | 335 ++++++++-------- .../nonparticipating/public/routes.go | 189 +++++----- .../generated/participating/private/routes.go | 339 ++++++++--------- .../generated/participating/public/routes.go | 357 +++++++++--------- daemon/algod/api/server/v2/handlers.go | 45 ++- daemon/algod/api/server/v2/handlers_test.go | 29 ++ .../algod/api/server/v2/test/handlers_test.go | 189 +++++++--- libgoal/libgoal.go | 16 +- test/e2e-go/restAPI/restClient_test.go | 9 +- test/scripts/e2e_subs/e2e-app-simulate.sh | 28 +- 17 files changed, 1394 insertions(+), 1098 deletions(-) diff --git a/cmd/goal/clerk.go b/cmd/goal/clerk.go index 94d12c345f..2d652d7f95 100644 --- a/cmd/goal/clerk.go +++ b/cmd/goal/clerk.go @@ -30,6 +30,7 @@ import ( "github.com/algorand/go-algorand/cmd/util/datadir" "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" + v2 "github.com/algorand/go-algorand/daemon/algod/api/server/v2" "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" @@ -43,27 +44,29 @@ import ( ) var ( - toAddress string - account string - amount uint64 - txFilename string - rejectsFilename string - closeToAddress string - noProgramOutput bool - writeSourceMap bool - signProgram bool - programSource string - argB64Strings []string - disassemble bool - verbose bool - progByteFile string - msigParams string - logicSigFile string - timeStamp int64 - protoVersion string - rekeyToAddress string - signerAddress string - rawOutput bool + toAddress string + account string + amount uint64 + txFilename string + rejectsFilename string + closeToAddress string + noProgramOutput bool + writeSourceMap bool + signProgram bool + programSource string + argB64Strings []string + disassemble bool + verbose bool + progByteFile string + msigParams string + logicSigFile string + timeStamp int64 + protoVersion string + rekeyToAddress string + signerAddress string + rawOutput bool + requestFilename string + requestOutFilename string ) func init() { @@ -144,9 +147,10 @@ func init() { dryrunRemoteCmd.Flags().BoolVarP(&rawOutput, "raw", "r", false, "Output raw response from algod") dryrunRemoteCmd.MarkFlagRequired("dryrun-state") - simulateCmd.Flags().StringVarP(&txFilename, "txfile", "t", "", "Transaction or transaction-group to test") - simulateCmd.Flags().StringVarP(&outFilename, "outfile", "o", "", "Filename for writing simulation result") - panicIfErr(simulateCmd.MarkFlagRequired("txfile")) + simulateCmd.Flags().StringVarP(&txFilename, "txfile", "t", "", "Transaction or transaction-group to test. Mutually exclusive with --request") + simulateCmd.Flags().StringVar(&requestFilename, "request", "", "Simulate request object to run. Mutually exclusive with --txfile") + simulateCmd.Flags().StringVar(&requestOutFilename, "request-only-out", "", "Filename for writing simulate request object. If provided, the command will only write the request object and exit. No simulation will happen") + simulateCmd.Flags().StringVarP(&outFilename, "result-out", "o", "", "Filename for writing simulation result") } var clerkCmd = &cobra.Command{ @@ -918,32 +922,12 @@ var splitCmd = &cobra.Command{ Long: `Split a file containing many transactions. The input file must contain one or more transactions. These transactions will be written to individual files.`, Args: validateNoPosArgsFn, Run: func(cmd *cobra.Command, args []string) { - data, err := readFile(txFilename) - if err != nil { - reportErrorf(fileReadError, txFilename, err) - } - - dec := protocol.NewMsgpDecoderBytes(data) - - var txns []transactions.SignedTxn - for { - var txn transactions.SignedTxn - err = dec.Decode(&txn) - if err == io.EOF { - break - } - if err != nil { - reportErrorf(txDecodeError, txFilename, err) - } - - txns = append(txns, txn) - } - + txns := decodeTxnsFromFile(txFilename) outExt := filepath.Ext(outFilename) outBase := outFilename[:len(outFilename)-len(outExt)] - for idx, txn := range txns { + for idx := range txns { fn := fmt.Sprintf("%s-%d%s", outBase, idx, outExt) - err = writeFile(fn, protocol.Encode(&txn), 0600) + err := writeFile(fn, protocol.Encode(&txns[idx]), 0600) if err != nil { reportErrorf(fileWriteError, outFilename, err) } @@ -1119,23 +1103,7 @@ var dryrunCmd = &cobra.Command{ Short: "Test a program offline", Long: "Test a TEAL program offline under various conditions and verbosity.", Run: func(cmd *cobra.Command, args []string) { - data, err := readFile(txFilename) - if err != nil { - reportErrorf(fileReadError, txFilename, err) - } - dec := protocol.NewMsgpDecoderBytes(data) - stxns := make([]transactions.SignedTxn, 0, 10) - for { - var txn transactions.SignedTxn - err = dec.Decode(&txn) - if err == io.EOF { - break - } - if err != nil { - reportErrorf(txDecodeError, txFilename, err) - } - stxns = append(stxns, txn) - } + stxns := decodeTxnsFromFile(txFilename) txgroup := transactions.WrapSignedTxnsWithAD(stxns) proto, params := getProto(protoVersion) if dumpForDryrun { @@ -1262,25 +1230,73 @@ var simulateCmd = &cobra.Command{ Short: "Simulate a transaction or transaction group with algod's simulate REST endpoint", Long: `Simulate a transaction or transaction group with algod's simulate REST endpoint under various configurations.`, Run: func(cmd *cobra.Command, args []string) { - data, err := readFile(txFilename) - if err != nil { - reportErrorf(fileReadError, txFilename, err) + txProvided := cmd.Flags().Changed("txfile") + requestProvided := cmd.Flags().Changed("request") + if txProvided == requestProvided { + reportErrorf("exactly one of --txfile or --request must be provided") + } + + requestOutProvided := cmd.Flags().Changed("request-only-out") + resultOutProvided := cmd.Flags().Changed("result-out") + if requestOutProvided && resultOutProvided { + reportErrorf("--request-only-out and --result-out are mutually exclusive") + } + + if requestOutProvided { + // If request-only-out is provided, only create a request and write it. Do not actually + // simulate. + if requestProvided { + reportErrorf("--request-only-out and --request are mutually exclusive") + } + txgroup := decodeTxnsFromFile(txFilename) + simulateRequest := v2.PreEncodedSimulateRequest{ + TxnGroups: []v2.PreEncodedSimulateRequestTransactionGroup{ + { + Txns: txgroup, + }, + }, + } + err := writeFile(requestOutFilename, protocol.EncodeJSON(simulateRequest), 0600) + if err != nil { + reportErrorf("write file error: %s", err.Error()) + } + return } dataDir := datadir.EnsureSingleDataDir() client := ensureFullClient(dataDir) - resp, err := client.SimulateRawTransaction(data) - if err != nil { - reportErrorf("simulation error: %s", err.Error()) + var simulateResponse v2.PreEncodedSimulateResponse + var responseErr error + if txProvided { + txgroup := decodeTxnsFromFile(txFilename) + simulateRequest := v2.PreEncodedSimulateRequest{ + TxnGroups: []v2.PreEncodedSimulateRequestTransactionGroup{ + { + Txns: txgroup, + }, + }, + } + simulateResponse, responseErr = client.SimulateTransactions(simulateRequest) + } else { + data, err := readFile(requestFilename) + if err != nil { + reportErrorf(fileReadError, requestFilename, err) + } + simulateResponse, responseErr = client.SimulateTransactionsRaw(data) + } + + if responseErr != nil { + reportErrorf("simulation error: %s", responseErr.Error()) } + encodedResponse := protocol.EncodeJSON(&simulateResponse) if outFilename != "" { - err = writeFile(outFilename, protocol.EncodeJSON(&resp), 0600) + err := writeFile(outFilename, encodedResponse, 0600) if err != nil { reportErrorf("write file error: %s", err.Error()) } } else { - fmt.Println(string(protocol.EncodeJSON(&resp))) + fmt.Println(string(encodedResponse)) } }, } @@ -1297,3 +1313,24 @@ func unmarshalSlice(accts []string) ([]basics.Address, error) { } return result, nil } + +func decodeTxnsFromFile(file string) []transactions.SignedTxn { + data, err := readFile(file) + if err != nil { + reportErrorf(fileReadError, txFilename, err) + } + var txgroup []transactions.SignedTxn + dec := protocol.NewMsgpDecoderBytes(data) + for { + var txn transactions.SignedTxn + err = dec.Decode(&txn) + if err == io.EOF { + break + } + if err != nil { + reportErrorf(txDecodeError, txFilename, err) + } + txgroup = append(txgroup, txn) + } + return txgroup +} diff --git a/daemon/algod/api/algod.oas2.json b/daemon/algod/api/algod.oas2.json index 3f63053140..1cbf67ae31 100644 --- a/daemon/algod/api/algod.oas2.json +++ b/daemon/algod/api/algod.oas2.json @@ -1242,7 +1242,8 @@ "nonparticipating" ], "consumes": [ - "application/x-binary" + "application/json", + "application/msgpack" ], "produces": [ "application/json", @@ -1255,13 +1256,12 @@ "operationId": "SimulateTransaction", "parameters": [ { - "description": "The byte encoded transaction to simulate", - "name": "rawtxn", + "description": "The transactions to simulate, along with any other inputs.", + "name": "request", "in": "body", "required": true, "schema": { - "type": "string", - "format": "binary" + "$ref": "#/definitions/SimulateRequest" } }, { @@ -1273,7 +1273,7 @@ "$ref": "#/responses/SimulateResponse" }, "400": { - "description": "Bad Request - Malformed Algorand transaction", + "description": "Bad Request", "schema": { "$ref": "#/definitions/ErrorResponse" } @@ -3189,6 +3189,41 @@ } } }, + "SimulateRequest": { + "description": "Request type for simulation endpoint.", + "type": "object", + "required": [ + "txn-groups" + ], + "properties": { + "txn-groups": { + "description": "The transaction groups to simulate.", + "type": "array", + "items": { + "$ref": "#/definitions/SimulateRequestTransactionGroup" + } + } + } + }, + "SimulateRequestTransactionGroup": { + "description": "A transaction group to simulate.", + "type": "object", + "required": [ + "txns" + ], + "properties": { + "txns": { + "description": "An atomic transaction group.", + "type": "array", + "items": { + "description": "SignedTxn object. Must be canonically encoded.", + "type": "string", + "format": "json", + "x-algorand-format": "SignedTransaction" + } + } + } + }, "Box": { "description": "Box name and its content.", "type": "object", diff --git a/daemon/algod/api/algod.oas3.yml b/daemon/algod/api/algod.oas3.yml index 09ed0b08cf..bf1cbf743a 100644 --- a/daemon/algod/api/algod.oas3.yml +++ b/daemon/algod/api/algod.oas3.yml @@ -1835,6 +1835,41 @@ ], "type": "object" }, + "SimulateRequest": { + "description": "Request type for simulation endpoint.", + "properties": { + "txn-groups": { + "description": "The transaction groups to simulate.", + "items": { + "$ref": "#/components/schemas/SimulateRequestTransactionGroup" + }, + "type": "array" + } + }, + "required": [ + "txn-groups" + ], + "type": "object" + }, + "SimulateRequestTransactionGroup": { + "description": "A transaction group to simulate.", + "properties": { + "txns": { + "description": "An atomic transaction group.", + "items": { + "description": "SignedTxn object. Must be canonically encoded.", + "format": "json", + "type": "string", + "x-algorand-format": "SignedTransaction" + }, + "type": "array" + } + }, + "required": [ + "txns" + ], + "type": "object" + }, "SimulateTransactionGroupResult": { "description": "Simulation result for an atomic transaction group", "properties": { @@ -5737,14 +5772,18 @@ ], "requestBody": { "content": { - "application/x-binary": { + "application/json": { "schema": { - "format": "binary", - "type": "string" + "$ref": "#/components/schemas/SimulateRequest" + } + }, + "application/msgpack": { + "schema": { + "$ref": "#/components/schemas/SimulateRequest" } } }, - "description": "The byte encoded transaction to simulate", + "description": "The transactions to simulate, along with any other inputs.", "required": true }, "responses": { @@ -5830,7 +5869,7 @@ } } }, - "description": "Bad Request - Malformed Algorand transaction" + "description": "Bad Request" }, "401": { "content": { @@ -5887,7 +5926,7 @@ "public", "nonparticipating" ], - "x-codegen-request-body-name": "rawtxn" + "x-codegen-request-body-name": "request" } }, "/versions": { diff --git a/daemon/algod/api/client/restClient.go b/daemon/algod/api/client/restClient.go index e3a8ea6ea1..2a7e539050 100644 --- a/daemon/algod/api/client/restClient.go +++ b/daemon/algod/api/client/restClient.go @@ -677,7 +677,7 @@ func (client RestClient) RawDryrun(data []byte) (response []byte, err error) { return } -// RawSimulateRawTransaction simulates the raw transaction or raw transaction group and returns relevant simulation results as raw bytes. +// RawSimulateRawTransaction simulates transactions by taking raw request bytes and returns relevant simulation results as raw bytes. func (client RestClient) RawSimulateRawTransaction(data []byte) (response []byte, err error) { var blob Blob err = client.submitForm(&blob, "/v2/transactions/simulate", rawFormat{Format: "msgpack"}, data, "POST", false /* encodeJSON */, false /* decodeJSON */, false) diff --git a/daemon/algod/api/server/v2/generated/data/routes.go b/daemon/algod/api/server/v2/generated/data/routes.go index 4ad51c96da..b62c0fa89c 100644 --- a/daemon/algod/api/server/v2/generated/data/routes.go +++ b/daemon/algod/api/server/v2/generated/data/routes.go @@ -146,174 +146,176 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL var swaggerSpec = []string{ "H4sIAAAAAAAC/+x9a3PctpLoX0HNbpUfdyjJr5xYVam9ip3kaOM4LkvJubu2b4Ihe2ZwRAIMAGpm4qv/", - "fgsNgARJkEM9Yidb55OtIdBoNBqNRr/wcZaKohQcuFaz44+zkkpagAaJf9E0FRXXCcvMXxmoVLJSM8Fn", - "x/4bUVoyvprNZ8z8WlK9ns1nnBbQtDH95zMJv1VMQjY71rKC+UylayioAax3pWldQ9omK5E4ECcWxOnL", - "2dXIB5plEpTqY/kjz3eE8TSvMiBaUq5oaj4psmF6TfSaKeI6E8aJ4EDEkuh1qzFZMsgzdeAn+VsFchfM", - "0g0+PKWrBsVEihz6eL4QxYJx8FhBjVS9IEQLksESG62pJmYEg6tvqAVRQGW6Jksh96BqkQjxBV4Vs+N3", - "MwU8A4mrlQK7xP8uJcDvkGgqV6BnH+axyS01yESzIjK1U0d9CarKtSLYFue4YpfAiel1QH6olCYLIJST", - "t9++IE+ePHluJlJQrSFzTDY4q2b0cE62++x4llEN/nOf12i+EpLyLKnbv/32BY5/5iY4tRVVCuKb5cR8", - "IacvhybgO0ZYiHENK1yHFvebHpFN0fy8gKWQMHFNbOM7XZRw/M+6KinV6boUjOvIuhD8SuznqAwLuo/J", - "sBqBVvvSUEoaoO+OkucfPj6aPzq6+rd3J8l/uz+fPbmaOP0XNdw9FIg2TCspgae7ZCWB4m5ZU96nx1vH", - "D2otqjwja3qJi08LFPWuLzF9rei8pHll+ISlUpzkK6EIdWyUwZJWuSZ+YFLx3IgpA81xO2GKlFJcsgyy", - "uZG+mzVL1ySlyoLAdmTD8tzwYKUgG+K1+OxGNtNVSBKD143ogRP68xKjmdceSsAWpUGS5kJBosWe48mf", - "OJRnJDxQmrNKXe+wIudrIDi4+WAPW6QdNzyd5zuicV0zQhWhxB9Nc8KWZCcqssHFydkF9nezMVQriCEa", - "Lk7rHDWbd4h8PWJEiLcQIgfKkXh+3/VJxpdsVUlQZLMGvXZnngRVCq6AiMU/IdVm2f/z7MfXREjyAyhF", - "V/CGphcEeCoyyA7I6ZJwoQPWcLyENDQ9h+bh8Iod8v9UwvBEoVYlTS/iJ3rOChaZ1Q90y4qqILwqFiDN", - "kvojRAsiQVeSDyFkIe5hxYJu+4Oey4qnuP7NsC1dznAbU2VOd0iwgm6/Opo7dBSheU5K4BnjK6K3fFCP", - "M2PvRy+RouLZBDVHmzUNDlZVQsqWDDJSQxnBxA2zDx/Gr4dPo3wF6Hggg+jUo+xBh8M2wjNmd5svpKQr", - "CFjmgPzkhBt+1eICeM3oZLHDT6WESyYqVXcawBGHHtfAudCQlBKWLMJjZ44cRsDYNk4CF04HSgXXlHHI", - "jHBGpIUGK6wGcQoGHL/v9E/xBVXwxdOhM775OnH1l6K76qMrPmm1sVFit2Tk6DRf3YaNa1at/hPuh+HY", - "iq0S+3NvIdnq3Jw2S5bjSfRPs36eDJVCIdAihD+bFFtxqisJx+/5Q/MXSciZpjyjMjO/FPanH6pcszO2", - "Mj/l9qdXYsXSM7YaIGaNa/TChd0K+4+BFxfHehu9V7wS4qIqwwmlrYvrYkdOXw4tsoV5XcY8qW+74cXj", - "fOsvI9ftobf1Qg4gOUi7kpqGF7CTYLCl6RL/2S6Rn+hS/m7+Kcvc9NblMkZaw8fuSEbzgTMrnJRlzlJq", - "iPjWfTZfjRAAe5GgTYtDPFCPPwYollKUIDWzQGlZJrlIaZ4oTTVC+ncJy9nx7N8OG/vLoe2uDoPBX5le", - "Z9jJqKxWDUpoWV4Dxhuj+qgRYWEENH5CMWHFHipNjNtFNKzEjAjO4ZJyfdBcWVryoN7A79xIDb2ttmPp", - "3bmCDRKc2IYLUFYDtg3vKRKQniBZCZIVFdJVLhb1D/dPyrKhIH4/KUtLD9QegaFiBlumtHqA06fNTgrH", - "OX15QL4LYaMqLni+M4eDVTXM2bB0p5Y7xWrbkptDA/GeIricQh6YpfFkMGr+XXAcXivWIjdaz15eMY3/", - "7tqGbGZ+n9T5r8FiIW2HmQsvWo5y9o6DvwSXm/sdzukzjjP3HJCTbt+bsY2BEmeYG/HK6HpauCN0rEm4", - "kbS0CLov9ixlHC9ptpHF9ZbSdKKgi+Ic7OGA1xCrG++1vfshigmyQgeHr3ORXvydqvUd7PmFh9XffjgM", - "WQPNQJI1VeuDWUzLCLdXA23KFjMN8YJPFsFQB/UU72p6e6aWUU2DqTl842qJJT32Q6EHMnJ3+RH/Q3Ni", - "Ppu9bUS/BXtAzlGAKbudnZMhM7d9e0GwI5kGaIUQpLAXfGJu3dfC8kUzeHydJq3RN9am4FbITQJXSGzv", - "fBt8LbYxHL4W294WEFtQd8EfBg6qkRoKNQG/lw4zgevvyEelpLs+kRH2FCKbCRrVVeFu4OGJb0ZpjLMn", - "CyFvJn06YoWTxuRMqIEaCN95h0jYtCoTx4oRs5Vt0AHUePnGhUYXfIxiLSqcafoHUEEZqHdBhTagu6aC", - "KEqWwx2w/joq9BdUwZPH5OzvJ88ePf7l8bMvDEuWUqwkLchip0GR++5uRpTe5fCgPzO8HVW5jkP/4qk3", - "VLbhxuAoUckUClr2QVkDqFWBbDNi2vWp1iYzzrpGcMrmPAcjyS3ZibXtG9ReMmU0rGJxJ4sxRLCsGSUj", - "DpMM9jLTdafXDLMLpyh3srqLqyxIKWTEvoZbTItU5MklSMVExJvyxrUgroVXb8vu7xZbsqGKmLHR9Ftx", - "VCginKW3fLrct6DPt7yhzajkt/ONzM6NO2Vd2sT3lkRFSpCJ3nKSwaJatW5CSykKQkmGHfGM/g702Y6n", - "aFW7CyYdvqYVjKOJX+14GtzZzELlkK1ai3D7u1mXKt4+Z4e6pyLoGHK8ws94rX8JuaZ3rr90B4jh/sIv", - "pEWWZKYh3oJfsdVaBwrmGynE8u5xjI0SQxQ/WPU8N336SvprkYGZbKXu4DBugDW8btY05HC6EJUmlHCR", - "AVpUKhU/pgc89+gyRE+nDk9+vbYa9wIMI6W0MrOtSoJ+vJ7kaDomNLXcmyBp1IAXo3Y/2VZ2OOsVziXQ", - "zNzqgROxcK4C58TASVJ0Qmp/0DklIbKXWniVUqSgFGSJM1HsRc23s0JEj9AJEUeE61GIEmRJ5a2Rvbjc", - "i+cF7BJ0mSty//uf1YPPgK8WmuZ7CIttYuStL3zOH9THetrwYwzXHTxkOyqBeJlrbpdGQOSgYYiE16LJ", - "4Pp1Meqt4u3JcgkSPTN/KMf7QW7HQDWqfzC/3xbbqhwIBHMXnXNWoN2OUy4UpIJnKgosp0on+8SyadS6", - "jZkZBJIwJokR8IBS8ooqbb2JjGdoBLHHCY5jFRQzxDDCgwqpgfyz10X7sFNzDnJVqVoxVVVZCqkhi82B", - "w3ZkrNewrccSywB2rf1qQSoF+yAPUSmA74hlZ2IJRHVtdHfu9v7k0DRtzvldlJQtJBpCjCFy5lsF1A2D", - "YQYQYaohtGUcpjqcU0fgzGdKi7I00kInFa/7DZHpzLY+0T81bfvMRXVzbmcCFMbguPYO842lrA2DWlNz", - "hUbIpKAXRvfAC7F1e/ZxNpsxUYynkIxxvtmWZ6ZVuAX2btKqXEmaQZJBTnd9oD/Zz8R+HgOAK95cfISG", - "xMazxBe94WQfPjACWiA8FVMeCX4hqdmC5ubRMIjrvQdyBgg7JpwcH92rQeFY0SXy8HDadqkjEPE0vBTa", - "rLjjB0TZSfQpCA/QoQZ9c1Jg56S5l3WH+C9QboBaj7j+IDtQQ1No4F9rAgPWNBcqHOyXjnjvSOCo2BwU", - "Y3vkyNCWHTDtvaFSs5SVeNf5HnZ3fvXrDhB1OJEMNGU5ZCT4YK+BZdif2EiMLsybXQUnWWH66PfMMJHp", - "5EyhytNG/gJ2eOd+Y0P8zoPAwDu4y0agmvOJcoKI+sAho4KHTWBLU53vjKKm17AjG5BAVLUomNY2dLd9", - "1dWiTEIAUQv3yIjOnWPD4/wKTPEvnSGoYHr9pZjP7J1gHL/zzsWgRQ53FyiFyCdYj3rEiGIwyfNPSmFW", - "nbkoYh9H6jmphaQT2ujLq4//e6pFZpwB+S9RkZRyvHJVGmqdRkhUFFCBNCMYFawe0/n4GwpBDgXYmyR+", - "efiwO/GHD92aM0WWsPGh96ZhlxwPH6Id541QurW57sBWaLbbaeT4QNO/OfjcLaQrU/b7mB3kKSv5pgO8", - "9heYPaWUY1wz/VsLgM7O3E6Ze8gj0/zrCHeSVT8AHZs3rvsZK6qc6rvwX4wqpPWFghUFZIxqyHeklJCC", - "Da82GpayuBjUiA28SteUr1CxlqJaucgfCwcFY6WsCUNWvAciqnzoLU9WUlRlTFC6aE8fYW/UDqDm6hMQ", - "EjtbRX9D6/FcUsWUE8wTPFid7wzMIbfCfDZ4MzREvWxuhpY47TSBOBUw7yFRVZoCRGOAY3eueqqddMgm", - "wcUBNGpDJW0QFKGprmgech05XRLKd+08ScpyZaQgUwTbmc5NYO3czs0nsSxpbp2zkayKcKe0NL5g5RuS", - "dkkx0fGATGK0oT5nhAxotpdh4z/GiN+AjmHZHziIumo+DgVemQt4vrsDNcgCIhJKCQoPrdBwpexXsQyT", - "n9yppnZKQ9G37duuvwwImreDN0jBc8YhKQSHXTTfl3H4AT9GBQcenAOdUYUZ6tu9lbTw76DVHmcKN96W", - "vrjagSx6U0cc3sHid+F23Dph2heaLSEvCSVpztCoKbjSskr1e07RbBJstkhkhr8fDhvSXvgmcctdxLDm", - "QL3nFKNyamNK1Ju8hIjl4FsAb09T1WoFqiM/yRLgPXetGCcVZxrHKsx6JXbBSpAYHnFgWxZ0Z0Qg2v1+", - "BynIotJtmYypJ0obcWl9TGYYIpbvOdUkB3On/oHx8y2C8z5azzMc9EbIi5oK8SNkBRwUU0k8guQ7+xWD", - "+9z01y7QD1OF7WfrlTDwm/yUHVpVmvTX/3v/P47fnST/TZPfj5Ln/+vww8enVw8e9n58fPXVV/+v/dOT", - "q68e/Me/x1bK4x5LjHCYn750l7XTl6iRN26JHu6fzCRdMJ5EmSx0vnd4i9zHJEDHQA/a9hq9hvdcb7lh", - "pEuas8yoXDdhh66I6+1Fuzs6XNNaiI59xs/1mnruLaQMiQiZjmi88THeD7qKpyChn8xlFeF+WVbcLqVX", - "dG2EvQ9+Ect5nWZmK1AcE8xBWlMfueX+fPzsi9m8yR2qv8/mM/f1Q4STWbaNaoewjV1f3AbBjXFPkZLu", - "FAwooIh7NM7HhhuEYAsw9161ZuWnlxRKs0Vcwvm4ZWcG2fJTbgOKzf5Br9vOGfPF8tPjraXRw0u9jmWm", - "tzQFbNWsJkAnEqKU4hL4nLADOOiaITJzNXMRRznQJWZI40VPTMnDqPeBZTTPFQHVw4lMuuvH+AeVWyet", - "r+Yzd/irO9fHHeAYXt0xaxeb/1sLcu+7b87JoROY6p5NVrSgg/SyyK3VZVC0YmSMNLP1OGy25nv+nr+E", - "JePMfD9+zzOq6eGCKpaqw0qB/JrmlKdwsBLk2CdlvKSavuc9TWuwZE6QDkPKapGzlFyEGnHDnrYMQh/C", - "+/fvaL4S799/6IUL9PVXN1RUvtgBkg3Ta1HpxCVxJxI2VMbcMapO4kXItkrD2Khz4mBbUeySxB38uMyj", - "Zam6yXz96ZdlbqYfsKFyqWpmyYjSQnpdxCgoFhtc39fCHQySbrwJo1KgyK8FLd8xrj+Q5H11dPQESCu7", - "7Vd35Bue3JUw2ZAxmGzYtV/gxO29BrZa0qSkq5jX5/37dxpoiauP+nKBl+w8J9itlVXno4YRVDMBT4/h", - "BbB4XDtDCCd3Znv5gj3xKeAnXEJsY9SNxhd90/UK8uxuvFydXL3eKlV6nZi9HZ2VMizuV6au47EySpYP", - "EFBshUGYruTJAki6hvTC1aKAotS7eau7j0FxiqYXHUzZKiU2Swbz5NFmvgBSlRl1qnjXgrTYEQVa+yjQ", - "t3ABu3PRpNlfJ0O5nTCrhjYqcmqgXRpmDbetg9FdfBfohCausvR5p5iA5NniuOYL32d4I1uV9w42cYwp", - "WgmdQ4SgMkIIy/wDJLjBRA28W7F+bHrmlrGwJ1+kYomX/cQ1aS5PLiYpnA0auO33ArDkkdgosqBGbxeu", - "Wo9NCg2kWKXoCgY05NBtMTH1suXqQCD7zr3oSSeW3QOtd95EUbaNEzPnKKeA+WJYBS8znUg0P5L1jDkn", - "ABbhcwRb5Kgm1SF7VuhQ2XIf2apiQ6jFGRgkbxQOj0abIqFms6bKFxLCekt+L0/SAf7AJOex0hahQT8o", - "qlTb173M7e7T3u3SFbjwVS18KYvwajmhLIXR8DFuO7YcgqMClEEOKztx29gzSpNw3SyQwePH5TJnHEgS", - "i8eiSomU2UpQzTHjxgCjHz8kxJqAyWQIMTYO0EaPLwImr0W4N/nqOkhylzBOPWz0FQd/Qzy3xUYoG5VH", - "lEaEswEHUuolAHVBfPX51QklRTCE8TkxYu6S5kbMuRtfA6RXYQHV1k49BRdz8GBInR2xwNuD5VpzskfR", - "TWYT6kwe6bhCN4LxQmwTm9wW1XgX24Xh92jQNqbaxTamrWVxT5GF2GIcCx4tNkh4Dy7DeHg0ghv+link", - "V+w3dJpbZMaGHdemYlyokGWcOa9mlyF1YsrQAxrMELvcD8pT3AiBjrGjqfXqLr97L6lt9aR/mDen2rwp", - "u+TzYWLbf2gLRVdpgH59K0xdUOJNV2OJ2ina4RjtWhqBChljeiMm+k6avitIQQ54KUhaSlRyEXPdmbsN", - "4Ilz5rsFxgus2EH57kEQ4yNhxZSGxojuQxI+h3mSYqEwIZbDs9OlXJr5vRWiPqZsJRrs2JrmJ58BBsku", - "mVQ6QQ9EdAqm0bcKL9XfmqZxXakdRWTLarIsLhtw2AvYJRnLqzi/unG/f2mGfV2LRFUtUN4ybmNDFlgG", - "NhpbODK0DT8dnfArO+FX9M7mO203mKZmYGnYpT3GX2RfdCTvmDiIMGCMOfqrNkjSEQEZ5IT2pWOgN9nN", - "iTmhB2PW195myjzsvWEjPjN16IyykKJzCQwGo7Ng6CYyagnTQRXVfrLmwB6gZcmybccWaqEO3pjptQwe", - "vvZUhwq4ug7YHgoEds9YvogE1S4z1ij4th5uq8rHwSTKnLeLgYUCIRyKKV/NvU+oOp9sH63Ogebfw+5n", - "0xanM7uaz25nOo3R2kHcQ+s39fJG6YyueWtKa3lCrklyWpZSXNI8cQbmIdaU4tKxJjb39uhPLOriZszz", - "b05evXHoX81naQ5UJrWqMDgrbFf+ZWZlK5oNbBBfLdrc+bzOblXJYPHrMkyhUXqzBld2N9BGe/UBG4dD", - "sBWdkXoZjxDaa3J2vhE7xREfCZS1i6Qx31kPSdsrQi8py73dzGM7EM2Dk5tWZDIqFUIAt/auBE6y5E7F", - "TW93x3dHw117ZFI41khh4MLWvlZE8K4LHcOLd6XzuhcUq/tZq0hfOPGqQEtConKWxm2sfKEMc3DrOzON", - "CTYeUEYNxIoNuGJ5xQJYppmacNHtIBmMESWmrxQ5RLuFcO+aVJz9VgFhGXBtPknclZ2NiuUUnbW9f5wa", - "3aE/lgNsLfQN+NvoGGFly+6Jh0iMKxihp66H7sv6yuwnWlukMNy6cUlcw+Efjtg7Ekec9Y4/HDfb4MV1", - "2+MWPkPSl3+GMWw96v1voPjLqyuxOTBG9E0TppKlFL9D/J6H1+NIKo6v5ckwyuV34BNizhvrTvM0SzP6", - "4HIPaTehFaodpDDA9bjygVsOiwp6CzXldqntEwOtWLc4w4RRpYcWfsMwDudeJG5ONwsaq7holAyD00nj", - "AG7Z0rUgvrOnvaoTG+zoJPAl122ZTbMuQTZZcv2SLTdUGOywk1WFRjNArg11grn1/+VKRMBUfEO5fanC", - "9LNbyfVWYI1fptdGSCySoOJm/wxSVtA8rjlkad/Em7EVs48wVAqCKv8OkH3gxnKReymhTtdxpDldkqN5", - "8NSIW42MXTLFFjlgi0e2xYIqlOS1IaruYqYHXK8VNn88ofm64pmETK+VJawSpFbq8HpTO68WoDcAnBxh", - "u0fPyX102yl2CQ8MFd35PDt+9ByNrvaPo9gB4B7RGJMmGYqTfzhxEudj9FtaGEZwO6gH0Xxy+4rWsOAa", - "2U2265S9hC2drNu/lwrK6QrikSLFHpxsX1xNNKR16MIz+wSM0lLsCNPx8UFTI58Gos+N+LNokFQUBdOF", - "c+4oURh+akr420E9OPuejKu+6vHyH9FHWnoXUecS+WmNpvZ8i80aPdmvaQFtss4JtZUxctZEL/ia0OTU", - "F97BcrR1FVpLGzOWmTqqORjMsCSlZFzjxaLSy+RLkq6ppKkRfwdD6CaLL55GSvC2S0Hy6yH+yekuQYG8", - "jJNeDrC91yFcX3KfC54URqJkD5psj2BXDjpz4267Id/hOOipSpmBkgyyW9ViNxpI6lsxHh8BeEtWrOdz", - "LX689sw+OWdWMs4etDIr9NPbV07LKISMVdNrtrvTOCRoyeASY/fii2Rg3nItZD5pFW6D/ef1PHiVM1DL", - "/F6OXQS+FpHbqS8LXVvSXax6xDowtE3NB8MGCwdqTtoleD+9088bn/vOJ/PF44p/dJH9zEuKRPYzGFjE", - "oDx4dDmz+nvg/6bka7GduqidHeIX9k9AmihJKpZnPzdZmZ3q65LydB31Zy1Mx1+ad6LqydnzKVq0bk05", - "hzwKzuqCv3idMaLV/lNMHadgfGLbbkF4O93O5BrE22h6pPyAhrxM52aAkKrthLc6oDpfiYzgOE2FtEZ6", - "9h8SCMo9/1aB0rHkIfxgg7rQbmnuu7baMAGe4W3xgHxnn4JdA2mVv8FbWl1FwNW+tQb1qswFzeZYyOH8", - "m5NXxI5q+9jXTmy14xVeUtqz6NirguKP08KD/cMl8dSF6XDGY6nNrJXGalRK06KMJYeaFue+AWaghjZ8", - "vL6E1DkgL4NHHW0eqQFh+GHJZGFuXDU0q7sgT5j/aE3TNV7JWiJ1mOWnl+n2XKmCp/HqJ27qioi47wze", - "rlK3LdQ9J8LcmzdM2RdA4RLa+ah1crYzCfj81Pb0ZMW55ZSo7jFWPOAmZPfI2UANb+aPYtYh/DUVclvl", - "/rpVy8+wV7RAU7cEeu9NPJvdWD9d4l92TikXnKVYHil2NLunQqf4wCZUkuoaWf0Wdzs0srmihdfrMDlH", - "xcFS7F4QOsL1jfDBV7OoljvsnxrfpFxTTVaglZNskM39+wHODsi4AlfhEh+WDeSkkC2/IkrIqKs6qV0a", - "12QjTIsZuNh9a769dtd+jBe/YBwVfEc2F5puLXX4kqE2twKmyUqAcvNp5ward6bPAabJZrD9cOBfPrTV", - "YNAtZ6ZtfdB9UCfeI+08wKbtC9PW1Qmqf25FINtBT8rSDTr8ukRUH9BbPkjgiGcx8a6dgLg1/BDaCLuN", - "hpLgeWoYDS7REQ0lnsM9xqhfWui84mOUVstR2ILYEK5oBQPGI2i8YhyadzkjB0QaPRJwYXC/DvRTqaTa", - "qoCTZNo50By9zzGBprRzPdwWVLeWkCEJztGPMbyMzSMRA4KjbtAobpTv6udADXcHysQLfIfYEbL/5ANq", - "VU6JyjCjoPMIRExwGMHtn5lpHwD9bdDXiWx3LandOdc5iYaSRBdVtgKd0CyLVaT6Gr8S/OqLS8EW0qou", - "TFmWJMWaKO0iMX1ucwOlgquqGBnLN7jlcMGrKhFuCF928SuMSSiLHf4bq8o4vDIuCOPaYYA+4sI9Q3FN", - "vbkNqaf1Gp5OFFsl0ymBZ8rtydEMfTNGb/rfKafnYtVG5BOXhhiTcuEaxeTbN+bgCCsn9EqN2qOlLmyA", - "QXfCv4WH18Y6JbctlfAo69UeRWdP/dbWuAFi+NWsOR5+A6G3QUEMas9X6z0cCsBNB+PFqXaZa5qSURE0", - "mA1ko3ds3g9iEbecDkXs2IAd87nXe5pm2NOzEfYoQX0oWB+h732cKSkpc67xRlj0Kesi0ofNhWObrlng", - "7iRcnPegxe77y6GYbKIYX+VA8Hv3naELcOns9UPzdq4+KslfCe2v7p1XC6+Oio/Ovx+dgEN9XjPooNH2", - "3NW0t9N0d/Lvf7YxbAS4lrs/gQm3t+i9V5r62q41TzVNSF0OeVJ55NapGH9wabj+UVPzCPmpFIo1Jbhj", - "LzFNjHU7x8eUgvpNfVg+0OQSUo111xsHugS4TjUnM1jwyt+/6iAN3B3rkEBX/mis5lG/2PqeA62XlhSk", - "1tlC1QfTK/yc1GFSKJSwAu4KuHtor51wMDnsebmEVLPLPWlg/1gDD1KM5t4IYR/MDbLCWB1Gi1VErm9i", - "axAay9IaxSeo5ndrdIaSQC5gd0+RFjdEK2fP/blykwISSAGUDolhEaFiYQjWauo8w0zVnIFU8GE/tjs0", - "pbgGH90JkhpvOJZnSXPiNomOI0PGX/2YNJbpeq30X4wIHcoU6z8aMKxsv8Q3GlT9IJ4vQBFeSclpv0zf", - "xhWwwKS92lHgS1mA8r/5DF07Ss4uIHwWCN0yGyoz3yJqZ/AmjGTkPOqld/mC912kl/XIrAnS7Cf0RAo/", - "YShumgujfyVD8cztuMjw9XyM/rAlvzHi0+C1BOmeT0NlLxcKEi18UOcYHmOkcC+934QIarDYokVusATK", - "26bGCxadpVjyhLrIlnCCREJBDXYyqMQyPOYYsV/Y7z6DxRcd3WtOqfl1f6F5H57LVI+IIdcviTst92fG", - "3MSywji3j7WqWFkWbkgZmv5LKbIqtQd0uDFq69PkokcjoiRqlEj7s+woxEF64QXsDq3G7yv0+xUMkbaa", - "k0U9SOfvLPKd2ppUDO/VnaD3Oc0081kpRJ4MWPZP+7Vkuhx/wdILyIg5KXwY28AjJeQ+GpRr1+1mvfO1", - "U8oSOGQPDgg54TZw2Htx28WMO4Pze3ps/C2OmlW2vJOzIB285/EITCy8JG8pzTyYcRmmwIi6Ww5lgeyp", - "VLIdqGMj6SbyZM/B1Cto36/afUalYSqLRUwn2fNgRcRn7F9A8O9p+PwULQqW9t9MiDooxv0B9s2gxVSv", - "QF29szf2YKXIZK+foIXDJG/BddFY4htcCY0Q+bQ+tuatJxJZ57kSX1nJPk6RUqu2misTZXklweVL2MeC", - "Oo8IlFSvPRub5n3l0igqoDCZwRaip8pehfyVzL1U1JUPokxyuISW+8QlcVRpCkqxSwhfObKdSQZQooGi", - "e2zG/ALh/urIUjf3JLAsT6FuVLhawtqVInsk58Ab9IndJmrqVjIYXbKsoi36qVs8QDPxSfsQ14mS4tpC", - "Ij65MRGx15OHPB/dlzzuyAtziOpbEY6W1dYTy4TNzlYl3fBhLSJy8ay9S7efB0FgRHVy+gYiF/F5naQu", - "/hczfLv0I8/8ZsTmKaCOQh082VPDHHi8tOaf2yivg0wZ58mbFV2ZtJP6PpCIsAme6Rm31IU1mZpgb2ld", - "aXiz9/Kquxl+aOTYtAeDfIc96IUG3ODJIK9LO3Q+c0T2DzVRgqkMckJr+vtswm6CjeAPlsieemaatkKe", - "jeZrr0tg8Fcvajv60DteXXM7FmASHIvS9c30Cl2rWNs+ZByz0eUlzT+9qR0rc50gPdzDyPGJhrbakMiW", - "lOpmYZGv6KSxA7vs3Q3N36Br4B9g1ijqE3eg3IlSa1nek4gik+ZGiNcvIiJIskGY1on+6AuycCldpYSU", - "qe5JtfFlt2vTJL5C0byXPW4L3TfPn4W+BRsvveJHXjclfLXAE6PBsNmin1moDOzcKJfHuK/HFhH6xWRU", - "WFtlz3Fx0fKu25LonbBRIeGOvexBvNw1vez9qjFTp2c9yebQqRT05zn5tG7RNnJQN3ObGiLSJ+5Yndcp", - "kR3x8s2mO4aWWIJg7XOCqJJfH/1KJCzxcSNBHj7EAR4+nLumvz5ufzbb+eHD+LPcnyqoxNLIwXDjxjjm", - "56E0AxtKP5DR0lmPiuXZPsZo5Sc1z4NhBs4vLkPxszxQ9ov1/fW3qnsk5jrhbN1FQMJE5toaPBgqyDya", - "kHTkukVSjNCullaS6R0WTvLXOfZLNPzlu9q77KIT6lIb7uzT4gLq0luNL7pS/nT9TtAczyOjU2Mwocan", - "mL/Z0qLMwW2Ur+4t/gZPvnyaHT159LfFl0fPjlJ4+uz50RF9/pQ+ev7kETz+8tnTI3i0/OL54nH2+Onj", - "xdPHT7949jx98vTR4ukXz/92z8ghg7JFdObT9Gf/B1/xS07enCbnBtmGJrRk9Qvsho39U0Q0xZ0IBWX5", - "7Nj/9L/9DjtIRdGA97/OXBbwbK11qY4PDzebzUHY5XCFzqdEiypdH/px+i9fvzmtM7nspRxX1CbpeGOL", - "Z4UT/Pb2m7NzcvLm9CB4WfV4dnRwdPAIH94sgdOSzY5nT/An3D1rXPdDx2yz449X89nhGmiOsRrmjwK0", - "ZKn/JIFmO/d/taGrFcgD9z6T+eny8aFXKw4/Oifc1di3w7DU+eHHlq8y29MTSyEffvQVfsZbt0roOB9t", - "0GEiFmPNDheYODy1Kaig8fBU8LKhDj+iujz4+6HLpox/xGuL3Q+H3qEfb9mi0ke9Nbh2eqRUp+uqPPyI", - "/0H+DNCyscsBurNVrLDYd6B94qHt4SLz6pC8ms9PM9u8FynmanfZYqbH76a95QB+OHNrzEAxV+ANJYbZ", - "Ds2G9klKjbjWsoKw8OZYiZqreeTZ0yVbVbLznHPnoWjCFPnPsx9fEyGJux+/oelFHbNHTpe2MIwUlwzT", - "mbIgB870rKfzWwVy18zHHZ3hBPxjES5JrFCrsp1RUavlH7DqBiKKAuPx0dGdPdjWW9kra/qvwXm8bgOx", - "J19f+FOzxYZGTj49enRnc2sHat96Yl1wvVmdcoyTMqcAsaccTujpX3ZCL/A2bTh+yXhmX9vQFPe03aA4", - "vy//svPTrPD+WI4PF4FCZeDZHW6wT8+ERienOcGWdjZP/rKzOQN5yVIg51CUQlLJ8h35ideZyUE1sv4x", - "9xO/4GLDPSGM0l0VBZU7dwRS0hVV/hiwx2HwdqTR9uhKobsY67/P5jOXdgjbEiQrgGNFkyt3Erd+rc9n", - "K+0O7YPk/Z933CUP5hALxfuJK7AmWF81YMfToUMbG5/tePq2Pkl7Jwju3j+O0/uLWeOLMgVjtf5gkT9N", - "Rj/7lFT4tBv0k+2ot1CIS1D1G9wNcxoty9xi7HPcUhQBDx8M7qwPaAKIa6/OM9EfyXtlGuA9VXbPnrjp", - "e9YjkXiT8NwTOmvBT3n+t35et5OqY4e6F1ug2b8Ewb8EwR0KAl1JPrhFg/MLw8mhtLFzJKXpGsbkQf+0", - "DG+6pYjV6jkbERauQsmQrDhry4o/9X33w5/ifH9Bud/PrRW3EY1U5gxkzQWU94vG/EsK/I+RArb6lbMp", - "zYmGPFfh3tcC974NK3BZQtyGe0yUA93HYWM/H35sP07UMu6pdaUzsQn6onPYRjb0bX71c52tvw83lOlk", - "KaTLEMLi0f3OGmh+6GrfdH5t0s17XzCHPvgxDHSL/npY1+aPfuwaXmNfneFxoJGvXOY/N06Y0KmBErJ2", - "Z7z7YOQTVn51wrOx0R8fHmLU/VoofTi7mn/s2O/Djx9qlvAlAWelZJdYYeDD1f8PAAD//2GweNWMxwAA", + "fgsNgARJkEM9Yidb55OtIR6NRqPR6OfHWSqKUnDgWs2OP85KKmkBGiT+RdNUVFwnLDN/ZaBSyUrNBJ8d", + "+29Eacn4ajafMfNrSfV6Np9xWkDTxvSfzyT8VjEJ2exYywrmM5WuoaBmYL0rTet6pG2yEokb4sQOcfpy", + "djXygWaZBKX6UP7I8x1hPM2rDIiWlCuamk+KbJheE71mirjOhHEiOBCxJHrdakyWDPJMHfhF/laB3AWr", + "dJMPL+mqATGRIoc+nC9EsWAcPFRQA1VvCNGCZLDERmuqiZnBwOobakEUUJmuyVLIPaBaIEJ4gVfF7Pjd", + "TAHPQOJupcAu8b9LCfA7JJrKFejZh3lscUsNMtGsiCzt1GFfgqpyrQi2xTWu2CVwYnodkB8qpckCCOXk", + "7bcvyJMnT56bhRRUa8gckQ2uqpk9XJPtPjueZVSD/9ynNZqvhKQ8S+r2b799gfOfuQVObUWVgvhhOTFf", + "yOnLoQX4jhESYlzDCvehRf2mR+RQND8vYCkkTNwT2/hONyWc/7PuSkp1ui4F4zqyLwS/Evs5ysOC7mM8", + "rAag1b40mJJm0HdHyfMPHx/NHx1d/du7k+S/3Z/PnlxNXP6Letw9GIg2TCspgae7ZCWB4mlZU97Hx1tH", + "D2otqjwja3qJm08LZPWuLzF9Leu8pHll6ISlUpzkK6EIdWSUwZJWuSZ+YlLx3LApM5qjdsIUKaW4ZBlk", + "c8N9N2uWrklKlR0C25ENy3NDg5WCbIjW4qsbOUxXIUoMXDfCBy7oz4uMZl17MAFb5AZJmgsFiRZ7rid/", + "41CekfBCae4qdb3LipyvgeDk5oO9bBF33NB0nu+Ixn3NCFWEEn81zQlbkp2oyAY3J2cX2N+txmCtIAZp", + "uDmte9Qc3iH09ZARQd5CiBwoR+T5c9dHGV+yVSVBkc0a9NrdeRJUKbgCIhb/hFSbbf/Psx9fEyHJD6AU", + "XcEbml4Q4KnIIDsgp0vChQ5Iw9ES4tD0HFqHgyt2yf9TCUMThVqVNL2I3+g5K1hkVT/QLSuqgvCqWIA0", + "W+qvEC2IBF1JPgSQHXEPKRZ025/0XFY8xf1vpm3JcobamCpzukOEFXT71dHcgaMIzXNSAs8YXxG95YNy", + "nJl7P3iJFBXPJog52uxpcLGqElK2ZJCRepQRSNw0++Bh/HrwNMJXAI4fZBCcepY94HDYRmjGnG7zhZR0", + "BQHJHJCfHHPDr1pcAK8JnSx2+KmUcMlEpepOAzDi1OMSOBcaklLCkkVo7MyhwzAY28Zx4MLJQKngmjIO", + "mWHOCLTQYJnVIEzBhOPvnf4tvqAKvng6dMc3Xyfu/lJ0d310xyftNjZK7JGMXJ3mqzuwccmq1X/C+zCc", + "W7FVYn/ubSRbnZvbZslyvIn+afbPo6FSyARaiPB3k2IrTnUl4fg9f2j+Igk505RnVGbml8L+9EOVa3bG", + "Vuan3P70SqxYesZWA8isYY0+uLBbYf8x48XZsd5G3xWvhLioynBBaevhutiR05dDm2zHvC5hntSv3fDh", + "cb71j5Hr9tDbeiMHgBzEXUlNwwvYSTDQ0nSJ/2yXSE90KX83/5RlbnrrchlDraFjdyWj+sCpFU7KMmcp", + "NUh86z6br4YJgH1I0KbFIV6oxx8DEEspSpCa2UFpWSa5SGmeKE01jvTvEpaz49m/HTb6l0PbXR0Gk78y", + "vc6wkxFZrRiU0LK8xhhvjOijRpiFYdD4CdmEZXsoNDFuN9GQEjMsOIdLyvVB82Rp8YP6AL9zMzX4ttKO", + "xXfnCTaIcGIbLkBZCdg2vKdIgHqCaCWIVhRIV7lY1D/cPynLBoP4/aQsLT5QegSGghlsmdLqAS6fNicp", + "nOf05QH5LhwbRXHB8525HKyoYe6Gpbu13C1W65bcGpoR7ymC2ynkgdkajwYj5t8FxeGzYi1yI/XspRXT", + "+O+ubUhm5vdJnf8aJBbidpi48KHlMGffOPhL8Li536GcPuE4dc8BOen2vRnZmFHiBHMjWhndTzvuCB5r", + "FG4kLS2A7ou9SxnHR5ptZGG9JTedyOiiMAdnOKA1hOrGZ23veYhCgqTQgeHrXKQXf6dqfQdnfuHH6h8/", + "nIasgWYgyZqq9cEsJmWEx6sZbcoRMw3xgU8WwVQH9RLvanl7lpZRTYOlOXjjYolFPfZDpgcy8nb5Ef9D", + "c2I+m7NtWL8d9oCcIwNT9jg7I0NmXvv2gWBnMg1QCyFIYR/4xLy6rwXli2by+D5N2qNvrE7B7ZBbBO6Q", + "2N75MfhabGMwfC22vSMgtqDugj7MOChGaijUBPheOsgE7r9DH5WS7vpIxrGnINks0IiuCk8DD298M0uj", + "nD1ZCHkz7tNhK5w0KmdCzagB8513kIRNqzJxpBhRW9kGnYEaK9840+gOH8NYCwtnmv4BWFBm1LvAQnug", + "u8aCKEqWwx2Q/jrK9BdUwZPH5OzvJ88ePf7l8bMvDEmWUqwkLchip0GR++5tRpTe5fCgvzJ8HVW5jo/+", + "xVOvqGyPGxtHiUqmUNCyP5RVgFoRyDYjpl0fa20046prAKccznMwnNyinVjdvgHtJVNGwioWd7IZQwjL", + "mlky4iDJYC8xXXd5zTS7cIlyJ6u7eMqClEJG9Gt4xLRIRZ5cglRMRKwpb1wL4lp48bbs/m6hJRuqiJkb", + "Vb8VR4EiQll6y6fzfTv0+ZY3uBnl/Ha9kdW5eafsSxv5XpOoSAky0VtOMlhUq9ZLaClFQSjJsCPe0d+B", + "PtvxFLVqd0Gkw8+0gnFU8asdT4M3m9moHLJVaxNu/zbrYsXr5+xU91QEHIOOV/gZn/UvIdf0zuWX7gQx", + "2F/4jbTAksw0xFfwK7Za60DAfCOFWN49jLFZYoDiByue56ZPX0h/LTIwi63UHVzGzWANrZs9DSmcLkSl", + "CSVcZIAalUrFr+kByz2aDNHSqcObX6+txL0AQ0gprcxqq5KgHa/HOZqOCU0t9SaIGjVgxajNT7aVnc5a", + "hXMJNDOveuBELJypwBkxcJEUjZDaX3ROSIicpRZcpRQpKAVZ4lQUe0Hz7SwT0SN4QsAR4HoWogRZUnlr", + "YC8u98J5AbsETeaK3P/+Z/XgM8Crhab5HsRimxh66wefswf1oZ42/RjBdScPyY5KIJ7nmtelYRA5aBhC", + "4bVwMrh/XYh6u3h7tFyCRMvMH0rxfpLbEVAN6h9M77eFtioHHMHcQ+ecFai345QLBangmYoOllOlk31s", + "2TRqvcbMCgJOGOPEOPCAUPKKKm2tiYxnqASx1wnOYwUUM8UwwIMCqRn5Zy+L9sdOzT3IVaVqwVRVZSmk", + "hiy2Bg7bkblew7aeSyyDsWvpVwtSKdg38hCWgvEdsuxKLIKorpXuztzeXxyqps09v4uisgVEg4gxQM58", + "qwC7oTPMACBMNYi2hMNUh3JqD5z5TGlRloZb6KTidb8hNJ3Z1if6p6Ztn7iobu7tTIBCHxzX3kG+sZi1", + "blBrap7QODIp6IWRPfBBbM2efZjNYUwU4ykkY5RvjuWZaRUegb2HtCpXkmaQZJDTXX/Qn+xnYj+PDYA7", + "3jx8hIbE+rPEN72hZO8+MDK0wPFUTHgk+IWk5gial0dDIK73npEzwLFjzMnR0b16KJwrukV+PFy23erI", + "iHgbXgptdtzRA4LsOPoUgAfwUA99c1Rg56R5l3Wn+C9QboJajrj+JDtQQ0toxr/WAga0ac5VODgvHfbe", + "4cBRtjnIxvbwkaEjO6Dae0OlZikr8a3zPezu/OnXnSBqcCIZaMpyyEjwwT4Dy7A/sZ4Y3TFv9hScpIXp", + "g99Tw0SWkzOFIk8b+AvY4Zv7jXXxOw8cA+/gLRsZ1dxPlBME1DsOGRE8bAJbmup8ZwQ1vYYd2YAEoqpF", + "wbS2rrvtp64WZRIOENVwj8zozDnWPc7vwBT70hkOFSyvvxXzmX0TjMN33nkYtNDh3gKlEPkE7VEPGVEI", + "Jln+SSnMrjPnRez9SD0ltYB0TBttefX1f0+10IwrIP8lKpJSjk+uSkMt0wiJggIKkGYGI4LVczobf4Mh", + "yKEA+5LELw8fdhf+8KHbc6bIEjbe9d407KLj4UPU47wRSrcO1x3oCs1xO41cH6j6Nxefe4V0ecp+G7Mb", + "ecpOvukMXtsLzJlSyhGuWf6tGUDnZG6nrD2kkWn2dRx3klY/GDq2btz3M1ZUOdV3Yb8YFUjrBwUrCsgY", + "1ZDvSCkhBetebSQsZWExoBHreJWuKV+hYC1FtXKeP3YcZIyVsioMWfHeEFHhQ295spKiKmOM0nl7eg97", + "I3YANU+fAJHY2Qr6G1rP54IqptxgHuHB7nxnxhwyK8xngy9Dg9TL5mVokdMOE4hjAeMeElWlKUDUBzj2", + "5qqX2gmHbAJc3IBGbKikdYIiNNUVzUOqI6dLQvmuHSdJWa4MF2SKYDvTuXGsndu1+SCWJc2tcTYSVRGe", + "lJbEF+x8g9IuKiYaHpBIjDTUp4yQAM3xMmT8xyjxm6FjUPYnDryumo9DjlfmAZ7v7kAMsgMRCaUEhZdW", + "qLhS9qtYhsFP7lZTO6Wh6Ov2bddfBhjN28EXpOA545AUgsMuGu/LOPyAH6OMAy/Ogc4owgz17b5KWvB3", + "wGrPM4Uab4tf3O2AF72pPQ7vYPO743bMOmHYF6otIS8JJWnOUKkpuNKySvV7TlFtEhy2iGeGfx8OK9Je", + "+CZxzV1EseaGes8peuXUypSoNXkJEc3BtwBen6aq1QpUh3+SJcB77loxTirONM5VmP1K7IaVINE94sC2", + "LOjOsEDU+/0OUpBFpds8GUNPlDbs0tqYzDRELN9zqkkO5k39A+PnWxzO22g9zXDQGyEvaizEr5AVcFBM", + "JXEPku/sV3Tuc8tfO0c/DBW2n61VwozfxKfsUKvShL/+3/v/cfzuJPlvmvx+lDz/X4cfPj69evCw9+Pj", + "q6+++n/tn55cffXgP/49tlMe9lhghIP89KV7rJ2+RIm8MUv0YP9kKumC8SRKZKHxvUNb5D4GAToCetDW", + "1+g1vOd6yw0hXdKcZUbkugk5dFlc7yza09GhmtZGdPQzfq3XlHNvwWVIhMl0WOONr/G+01U8BAntZC6q", + "CM/LsuJ2K72gaz3svfOLWM7rMDObgeKYYAzSmnrPLffn42dfzOZN7FD9fTafua8fIpTMsm1UOoRt7Pni", + "DggejHuKlHSnYEAARdijfj7W3SActgDz7lVrVn56TqE0W8Q5nPdbdmqQLT/l1qHYnB+0uu2cMl8sPz3c", + "Who5vNTrWGR6S1LAVs1uAnQ8IUopLoHPCTuAg64aIjNPM+dxlANdYoQ0PvTElDiM+hxYQvNUEWA9XMik", + "t36MflC4ddz6aj5zl7+6c3ncDRyDqztnbWLzf2tB7n33zTk5dAxT3bPBinboILws8mp1ERQtHxnDzWw+", + "Dhut+Z6/5y9hyTgz34/f84xqerigiqXqsFIgv6Y55SkcrAQ59kEZL6mm73lP0hpMmROEw5CyWuQsJReh", + "RNyQp02D0B/h/ft3NF+J9+8/9NwF+vKrmyrKX+wEyYbptah04oK4EwkbKmPmGFUH8eLINkvD2Kxz4sa2", + "rNgFibvx4zyPlqXqBvP1l1+WuVl+QIbKhaqZLSNKC+llESOgWGhwf18LdzFIuvEqjEqBIr8WtHzHuP5A", + "kvfV0dETIK3otl/dlW9oclfCZEXGYLBhV3+BC7fvGthqSZOSrmJWn/fv32mgJe4+yssFPrLznGC3VlSd", + "9xrGoZoFeHwMb4CF49oRQri4M9vLJ+yJLwE/4RZiGyNuNLbom+5XEGd34+3qxOr1dqnS68Sc7eiqlCFx", + "vzN1Ho+VEbK8g4BiK3TCdClPFkDSNaQXLhcFFKXezVvdvQ+KEzQ962DKZimxUTIYJ4868wWQqsyoE8W7", + "GqTFjijQ2nuBvoUL2J2LJsz+OhHK7YBZNXRQkVID6dIQa3hs3RjdzXeOTqjiKksfd4oBSJ4sjmu68H2G", + "D7IVee/gEMeIohXQOYQIKiOIsMQ/gIIbLNSMdyvSjy3PvDIW9uaLZCzxvJ+4Js3jyfkkhatBBbf9XgCm", + "PBIbRRbUyO3CZeuxQaEBF6sUXcGAhByaLSaGXrZMHTjIvnsvetOJZfdC6903UZBt48SsOUopYL4YUsHH", + "TMcTzc9kLWPOCIBJ+BzCFjmKSbXLnmU6VLbMRzar2BBocQIGyRuBw4PRxkgo2ayp8omEMN+SP8uTZIA/", + "MMh5LLVFqNAPkirV+nXPc7vntPe6dAkufFYLn8oifFpOSEthJHz0245th+AoAGWQw8ou3Db2hNIEXDcb", + "ZOD4cbnMGQeSxPyxqFIiZTYTVHPNuDnAyMcPCbEqYDJ5hBgZB2CjxRcHJq9FeDb56jpAchcwTv3YaCsO", + "/oZ4bIv1UDYijygNC2cDBqTUcwDqnPjq+6vjSorDEMbnxLC5S5obNudefM0gvQwLKLZ28ik4n4MHQ+Ls", + "iAbeXizXWpO9im6ymlBm8kDHBboRiBdim9jgtqjEu9guDL1HnbYx1C52MG0ui3uKLMQW/VjwarFOwntg", + "GYbDgxG88LdMIb1iv6Hb3AIzNu24NBWjQoUk49R5NbkMiRNTph6QYIbI5X6QnuJGAHSUHU2uV/f43ftI", + "bYsn/cu8udXmTdolHw8TO/5DRyi6SwP462th6oQSb7oSS1RP0XbHaOfSCETIGNEbNtE30vRNQQpywEdB", + "0hKikouY6c68bQBvnDPfLVBeYMYOyncPAh8fCSumNDRKdO+S8DnUkxQThQmxHF6dLuXSrO+tEPU1ZTPR", + "YMfWMj/5CtBJdsmk0glaIKJLMI2+Vfio/tY0jctKbS8im1aTZXHegNNewC7JWF7F6dXN+/1LM+3rmiWq", + "aoH8lnHrG7LANLBR38KRqa376eiCX9kFv6J3tt5pp8E0NRNLQy7tOf4i56LDecfYQYQAY8TR37VBlI4w", + "yCAmtM8dA7nJHk6MCT0Y0772DlPmx97rNuIjU4fuKDtSdC2BwmB0FQzNREYsYTrIotoP1hw4A7QsWbbt", + "6ELtqIMvZnothYfPPdXBAu6uG2wPBgK9ZyxeRIJqpxlrBHybD7eV5eNgEmbO28nAQoYQTsWUz+beR1Qd", + "T7YPV+dA8+9h97Npi8uZXc1nt1OdxnDtRtyD6zf19kbxjKZ5q0prWUKuiXJallJc0jxxCuYh0pTi0pEm", + "Nvf66E/M6uJqzPNvTl69ceBfzWdpDlQmtagwuCpsV/5lVmUzmg0cEJ8t2rz5vMxuRclg8+s0TKFSerMG", + "l3Y3kEZ7+QEbg0NwFJ2Sehn3ENqrcna2EbvEERsJlLWJpFHfWQtJ2ypCLynLvd7MQzvgzYOLm5ZkMsoV", + "wgFubV0JjGTJnbKb3umOn46GuvbwpHCukcTAhc19rYjgXRM6uhfvSmd1Lyhm97NakT5z4lWBmoRE5SyN", + "61j5Qhni4NZ2ZhoTbDwgjJoRKzZgiuUVC8YyzdSEh24HyGCOKDJ9psgh3C2Eq2tScfZbBYRlwLX5JPFU", + "dg4qplN02vb+dWpkh/5cbmCroW+Gv42MEWa27N54CMS4gBFa6nrgvqyfzH6htUYK3a0bk8Q1DP7hjL0r", + "ccRY7+jDUbN1Xly3LW5hGZI+/zOEYfNR76+B4h+vLsXmwBzRmiZMJUspfof4Ow+fx5FQHJ/Lk6GXy+/A", + "J/icN9qdpjRLM/vgdg9JN6EWqu2kMED1uPOBWQ6TCnoNNeV2q22JgZavW5xgQq/SQzt+QzAO5p4nbk43", + "CxrLuGiEDAPTSWMAbunStSC+s8e9qgMb7OwksCXXbZkNsy5BNlFy/ZQtNxQY7LSTRYVGMkCqDWWCubX/", + "5UpEhqn4hnJbqcL0s0fJ9VZglV+m10ZITJKg4mr/DFJW0DwuOWRpX8WbsRWzRRgqBUGWfzeQLXBjqchV", + "SqjDdRxqTpfkaB6UGnG7kbFLptgiB2zxyLZYUIWcvFZE1V3M8oDrtcLmjyc0X1c8k5DptbKIVYLUQh0+", + "b2rj1QL0BoCTI2z36Dm5j2Y7xS7hgcGiu59nx4+eo9LV/nEUuwBcEY0xbpIhO/mHYydxOka7pR3DMG43", + "6kE0ntxW0RpmXCOnyXadcpawpeN1+89SQTldQdxTpNgDk+2Lu4mKtA5eeGZLwCgtxY4wHZ8fNDX8acD7", + "3LA/CwZJRVEwXTjjjhKFoacmhb+d1A9n68m47KseLv8RbaSlNxF1HpGfVmlq77fYqtGS/ZoW0EbrnFCb", + "GSNnjfeCzwlNTn3iHUxHW2ehtbgxc5mlo5iDzgxLUkrGNT4sKr1MviTpmkqaGvZ3MARusvjiaSQFbzsV", + "JL8e4J8c7xIUyMs46uUA2XsZwvUl97ngSWE4SvagifYITuWgMTduthuyHY4PPVUoM6Mkg+RWtciNBpz6", + "VoTHRwa8JSnW67kWPV57ZZ+cMisZJw9amR366e0rJ2UUQsay6TXH3UkcErRkcIm+e/FNMmPeci9kPmkX", + "bgP957U8eJEzEMv8WY49BL4WkdepTwtda9Kdr3pEOzB0TM0HQwYLN9SctFPwfnqjn1c+941P5ouHFf/o", + "AvuZtxSR7FcwsIlBevDodmb198D+TcnXYjt1UzsnxG/snwA1UZRULM9+bqIyO9nXJeXpOmrPWpiOvzR1", + "ourF2fspmrRuTTmHPDqclQV/8TJjRKr9p5g6T8H4xLbdhPB2uZ3FNYC3wfRA+QkNepnOzQQhVtsBb7VD", + "db4SGcF5mgxpDffsFxII0j3/VoHSseAh/GCdulBvad67NtswAZ7ha/GAfGdLwa6BtNLf4CutziLgct9a", + "hXpV5oJmc0zkcP7NyStiZ7V9bLUTm+14hY+U9io6+qog+eM092BfuCQeujB9nHFfarNqpTEbldK0KGPB", + "oabFuW+AEaihDh+fLyF2DsjLoKijjSM1Qxh6WDJZmBdXPZqVXZAmzH+0pukan2QtljpM8tPTdHuqVEFp", + "vLrETZ0REc+dgdtl6raJuudEmHfzhilbARQuoR2PWgdnO5WAj09tL09WnFtKicoeY8kDboJ2D5x11PBq", + "/ihkHcRfUyC3We6vm7X8DHtFEzR1U6D3auLZ6Ma6dImv7JxSLjhLMT1S7Gp2pUKn2MAmZJLqKln9EXcn", + "NHK4oonXazc5h8XBVOyeETrE9ZXwwVezqZY67J8aa1KuqSYr0MpxNsjmvn6A0wMyrsBluMTCsgGfFLJl", + "V0QOGTVVJ7VJ45pkhGExAw+7b8231+7Zj/7iF4yjgO/Q5lzTraYOKxlq8ypgmqwEKLeedmywemf6HGCY", + "bAbbDwe+8qHNBoNmObNsa4PuD3XiLdLOAmzavjBtXZ6g+ueWB7Kd9KQs3aTD1SWi8oDe8kEERyyLiTft", + "BMitxw9HGyG3UVcSvE8NocElGqKhxHu4Rxh1pYVOFR8jtFqKwhbEunBFMxgwHgHjFePQ1OWMXBBp9ErA", + "jcHzOtBPpZJqKwJO4mnnQHO0PscYmtLO9HDbobq5hAxKcI1+juFtbIpEDDCOukEjuFG+q8uBGuoOhIkX", + "WIfYIbJf8gGlKidEZRhR0CkCEWMchnH7MjPtC6B/DPoyke2uJbUn5zo30VCQ6KLKVqATmmWxjFRf41eC", + "X31yKdhCWtWJKcuSpJgTpZ0kpk9tbqJUcFUVI3P5BrecLqiqEqGGsLKL32EMQlns8N9YVsbhnXFOGNd2", + "A/QeF64MxTXl5vZIPanX0HSi2CqZjgm8U26PjmbqmxF60/9OKT0XqzYgnzg1xBiXC/coxt++MRdHmDmh", + "l2rUXi11YgN0uhO+Fh4+G+uQ3DZXwqusl3sUjT11ra1xBcRw1aw5Xn4DrrdBQgxq71drPRxywE0H/cWp", + "dpFrmpJRFjQYDWS9d2zcD0IR15wOeexYhx3zudd7mmTYk7Nx7FGEelewPkDfez9TUlLmTOMNs+hj1nmk", + "D6sLxw5ds8HdRTg/70GN3feXQz7ZRDG+yoHg926doQtw4ex1oXm7Vu+V5J+E9ldX59WOV3vFR9ff907A", + "qT6vGnRQaXvuctrbZbo3+fc/Wx82AlzL3Z9Ahdvb9F6Vpr60a9VTTRNSp0OelB65dSvGCy4N5z9qch4h", + "PZVCsSYFd6wS00Rft3MsphTkb+qP5R1NLiHVmHe9MaBLgOtkczKTBVX+/pUHaeDtWLsEuvRHYzmP+snW", + "91xovbCkILTOJqo+mJ7h56R2k0KmhBlwV8Bdob12wMFkt+flElLNLveEgf1jDTwIMZp7JYQtmBtEhbHa", + "jRaziFxfxdYANBalNQpPkM3v1uAMBYFcwO6eIi1qiGbOnvt75SYJJBADyB0SQyJCxdwQrNbUWYaZqikD", + "seDdfmx3aFJxDRbdCYIabziXJ0lz4zaBjiNTxqt+TJrLdL1W+C96hA5FivWLBgwL2y+xRoOqC+L5BBTh", + "k5Sc9tP0bVwCCwzaqw0FPpUFKP+bj9C1s+TsAsKyQGiW2VCZ+RZRPYNXYSQj91EvvMsnvO8CvaxnZo2T", + "Zj+gJ5L4CV1x01wY+SsZ8mdu+0WG1fPR+8Om/EaPTwPXEqQrn4bCXi4UJFp4p84xOMZQ4Sq93wQJajDZ", + "ogVuMAXK2ybHCyadpZjyhDrPlnCBREJBDXQyyMQyPOcYsl/Y7z6CxScd3atOqel1f6J5757LVA+JIdUv", + "ibst90fG3ESzwji3xVpVLC0LN6gMVf+lFFmV2gs6PBi19mly0qMRVhJVSqT9VXYE4iC88AJ2h1bi9xn6", + "/Q6GQFvJyYIehPN3NvlOdU0qBvfqTsD7nGqa+awUIk8GNPun/VwyXYq/YOkFZMTcFN6NbaBICbmPCuXa", + "dLtZ73zulLIEDtmDA0JOuHUc9lbcdjLjzuT8nh6bf4uzZpVN7+Q0SAfvedwDExMvyVtyMz/MOA9TYFjd", + "Laeyg+zJVLIdyGMj6SZSsudg6hO0b1ftllFpiMpCEZNJmgohe5xCan+QpgZC4xPSL1w0UonjvENEth3a", + "4h0w1y634YDsVt3YqxENwJyAnN7wEaVSpJpIe109PA0UddKiYGl/uBZq/hLm/EEj/J5aKZH11YTnSrn4", + "0KgBXEVtY+OmKFuuajHVIFUnjo3uUzRJabLXRNWCYZKh6rpgLLH8W0IjSD6tJaZ5qzon61TK8Um9LI2n", + "1L6YzGudsryS4EJ1bJ2qTv2Kkuq156Cmef9dY2RkUBhHY2sgUGVf4V4b4Ipkda8mUSY5XELLcufih6o0", + "BaXYJYQFtmxnkgGUqBvrSmwxk1TI2jvXuFt7Ehg1pmA3eq9bxNqdInsu7aiIseWJPSZq6lEyEF2yrKIt", + "/Klb1D4aKnsUYcMe1omc4tpMIr64MRax14iMNB89lzxuQw7D1+oHOc6W1Yo7S4TNyVYl3fBhATai86gN", + "m7dfB8HBiOqEkw44zWJlp6TOOxm7Hl3kmyd+M2NTharzlguqRdVjDtTNrennNu+mQaKM0+TN8v1MOkl9", + "81uE2QQVosaVxGE6sCbOQForLiqVPL/qHoYfGj42rVaV77AHvNB2EFSr8s84B85nDgb4oUZKsJRBSmgt", + "f585wi2wYfzBFtlbzyzTJme0jqTtfQlsTepFbcIZKiHXtfRg7i/BMR9i30Kk0KqPZRVCwjEHXV7S/NNb", + "eTAp3Aniw9Xkji80NBOESLaoVDfzyH1FJ80dmATubmr+Bq1S/wCzR9FHghvK3Si1lOWN2MgyaW6YeF2M", + "E4ckGxzT+m88+oIsXDRhKSFlqntTbXzG91orjgVQmlLt42r4fev8WehbkPHSC37kdZM9Gp9cK95A2BzR", + "z8xUBk5ulMpj1Ncjiwj+YjwqTOuz57q4aDl22Gz8HY9lIeGOHTwCV81rOnj0ExZNXZ51YjCXTqWgv87J", + "t3ULt5GLulnbVO+kPnLHUgxPcSqKZw433dGrySIE0+4TBJX8+uhXImGJdbUEefgQJ3j4cO6a/vq4/dkc", + "54cP4xXhP5U/k8WRG8PNG6OYn4ciXGwUx0AwVWc/KpZn+wijFRrXVKbD4K9fXHDsZ6mN94s1O/ePqqtP", + "dB1Pyu4mIGIia21NHkwVBL1NiHdz3SLRbajSTSvJ9A5zdvnnHPsl6nn1Xe3Y4Bxj6iwv7u7T4gLqrG+N", + "G0Sl/O36naA53kdGpkY/Vo1VwL/Z0qLMwR2Ur+4t/gZPvnyaHT159LfFl0fPjlJ4+uz50RF9/pQ+ev7k", + "ETz+8tnTI3i0/OL54nH2+OnjxdPHT7949jx98vTR4ukXz/92z/AhA7IFdOYzRMz+DxaQTE7enCbnBtgG", + "J7RkdfF/Q8a+ChZN8SRCQVk+O/Y//W9/wg5SUTTD+19nLgB9tta6VMeHh5vN5iDscrhCu2eiRZWuD/08", + "/aLrb05rhbF9lOOO2vgwr2zxpHCC395+c3ZOTt6cHgRFfY9nRwdHB4+w5msJnJZsdjx7gj/h6Vnjvh86", + "Ypsdf7yazw7XQHN0EzJ/FKAlS/0nCTTbuf+rDV2tQB640mDmp8vHh16sOPzo7L9XY98Owyz7hx9bZvJs", + "T0/Mwn340SeXGm/dyt7k3AOCDhOhGGt2uMCY9alNQQWNh5eCjw11+BHF5cHfD10gb/wjPlvseTj0viTx", + "li0sfdRbA2unR0p1uq7Kw4/4H6TPACzrNh+AO1vFctp9B9rHvNoezim09gat6fw0s817TooubZzNo3v8", + "bloZEfDTmVdjBoq53ILIMcxxaA60j49r2LWWFYQ5X8eyI13NIxV3l2xVyU4l8U6NcsIU+c+zH18TIYl7", + "H7+h6UVthyCnS5uTSIpLhpF0WRB+aXrWy/mtArlr1uOuznABvk6JM2gUalW2g3lqsfwDJnxBQJFhPD46", + "urNagb2dvbKq/3o4D9dtRuzx1xf+1myRoeGTT48e3dna2jECt15Yd7jeqk45uuiZW4DYWw4X9PQvu6AX", + "+Jo2FL9kPLOFXjTFM20PKK7vy7/s+jQrvCsAx5pZoFAYeHaHB+zTE6GRyWlOsKVdzZO/7GrOQF6yFMg5", + "FKWQVLJ8R37idVB8kAivf839xC+42HCPCCN0V0VB5c5dgZR0WZW/Bux1GJQtNdIeXSn0VMDSA7P5zEW8", + "wrYEyQrgmEznyt3ErV/r+9lyu0NbC7//8467uNUcYl6gP3EFVgXrE1bseDp0aWPjsx1P39Y3ae8GwdP7", + "x1F6fzNreJGnoJvgH8zyp/HoZ58SC5/2gH6yE/UWCnEJqi7/3hCnkbLMK8ZWgpeiCGj4YPBkfUAVQFx6", + "dZaJ/kzeKtMM3hNl95yJm5ZSH3ECnQTnHq9tO/yUytN1ZedOlJid6l5sg2b/YgT/YgR3yAh0JfngEQ3u", + "L4xkgNK6bZKUpmsY4wf92zJ86ZYi5hF4NsIsXHKcIV5x1uYVf+r37oc/xf3+gnJ/nls7bp1pqcwZyJoK", + "KO/nK/oXF/gfwwVs4jWnU5oTDXmuwrOvBZ5961bgAtS4dfeYyAe6dYljPx9+bNfFain31LrSmdgEfdE4", + "bD0b+jq/ulJs6+/DDWU6WQrpgtMwb3m/swaaH7q0S51fm0wHvS+YviH4MXR0i/56WJeFiH7sKl5jX53i", + "caCR9w72nxsjTGjUQA5ZmzPefTD8CZMOO+bZ6OiPDw8x4GMtlD6cXc0/dvT34ccPNUn4bJSzUrJLTG7x", + "4er/BwAA//8yQRH0B8oAAA==", } // 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 c61cd7d877..76c05508ac 100644 --- a/daemon/algod/api/server/v2/generated/experimental/routes.go +++ b/daemon/algod/api/server/v2/generated/experimental/routes.go @@ -75,167 +75,169 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", + "T/8vAAD//yOoPrWwuAAA", } // 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 eb61d6ad66..804e649c55 100644 --- a/daemon/algod/api/server/v2/generated/model/types.go +++ b/daemon/algod/api/server/v2/generated/model/types.go @@ -608,6 +608,18 @@ type PendingTransactionResponse struct { Txn map[string]interface{} `json:"txn"` } +// SimulateRequest Request type for simulation endpoint. +type SimulateRequest struct { + // TxnGroups The transaction groups to simulate. + TxnGroups []SimulateRequestTransactionGroup `json:"txn-groups"` +} + +// SimulateRequestTransactionGroup A transaction group to simulate. +type SimulateRequestTransactionGroup struct { + // Txns An atomic transaction group. + Txns []json.RawMessage `json:"txns"` +} + // SimulateTransactionGroupResult Simulation result for an atomic transaction group type SimulateTransactionGroupResult struct { // AppBudgetAdded Total budget added during execution of app calls in the transaction group. @@ -1220,3 +1232,6 @@ type TealCompileTextRequestBody = TealCompileTextBody // TealDryrunJSONRequestBody defines body for TealDryrun for application/json ContentType. type TealDryrunJSONRequestBody = DryrunRequest + +// SimulateTransactionJSONRequestBody defines body for SimulateTransaction for application/json ContentType. +type SimulateTransactionJSONRequestBody = SimulateRequest 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 f4994e1eb0..0fcdcb6950 100644 --- a/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go +++ b/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go @@ -130,173 +130,174 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "EwAA//8f+8KPlsAAAA==", + "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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 5f575ff99b..76104325fa 100644 --- a/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go +++ b/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go @@ -708,100 +708,101 @@ var swaggerSpec = []string{ "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", - "3///AQAA//8WqsRKx0sBAA==", + "zsMUGFZ3zansIDsylawH8thIepko2XMw9gnat6t2y6g0RGWhSMkkTYWQHU4hwR+kqYHQ+IT0CxdtqcRx", + "1iEi2w5t8Q6YvcttOCC7VTd2akQjMEcgpzd8QqmUqCbSXlcPTwNFnbRYsbw/XAs1n4U5f9AIv6NWSmJ9", + "gfBcKRcfGjWAq6RtbLspyparmo01SIXEscl9SiYpzXaaqFowjDJU7QvGHMu/ZTSB5JMgMU1b1TlZp1KO", + "T+plaTyn9sVkXuuUlbUEF6pj61R16ldUVC89BzXN++8aIyODwjgaWwOBKvsK99oAVySrezWJKivhAlqW", + "Oxc/VOc5KMUuIC6wZTuTAqBC3VhXYkuZpGLW3rnG3dqzyKgxBrvJe90i1u4U2XFpJ0WMNc/sMVFjj5KB", + "6IIVNW3hT12j9tFQ2aMEG/awjuQUezOJ9OK2sYidRmSk+eS55Gkbchy+Fh7kOFsRFHeWCJuTrSp6yYcF", + "2ITOIxg2r78OgoMR1QknHXCaxcpOWcg7mboeXeSbJ34zY1OFqvOWi6pFhTEH6uYG+rnOu2mQKNM0ebV8", + "P6NOUt/8lmA2UYWo7UriOB1YE2cgrRUXlUqeX3UPww8NHxtXq8p32AFebDuIqlX5Z5wD5xMHA/wQkBIt", + "ZZASWsvfZY5wC2wYf7RF9tYzy7TJGa0jaXtfIluTehZMOEMl5LqWHsz9JTjmQ+xbiBRa9bGsQkw45qDL", + "C1p+fCsPJoU7Rny4mtzphcZmghjJFpXqah65L+iouSOTwM1NzV+hVep/wOxR8pHghnI3SpCyvBEbWSYt", + "DRMPxThxSHKJY1r/jYdfkJmLJqwk5Ex1b6pLn/E9aMWxAEpTqn27Gn7XOn8W+hpkPPeCH3nZZI/GJ9eC", + "NxA2R/QTM5WBk5uk8hT19cgigb8Uj4rT+uy4Ls5bjh02G3/HY1lIuGEHj8hVc08Hj37CorHLs04M5tKp", + "FfTXOfq2buE2cVE3axvrndRH7rYUw2OcitKZw0139GqyCMG0+wRBJb89/I1ImGNdLUHu38cJ7t+fuqa/", + "PWp/Nsf5/v10RfiP5c9kceTGcPOmKObnoQgXG8UxEEzV2Y+alcUuwmiFxjWV6TD461cXHPtJauP9as3O", + "/aPq6hPt40nZ3QRETGKtrcmjqaKgtxHxbq5bIroNVbp5LZneYM4u/5xjvyY9r74Ljg3OMSZkeXF3nxbn", + "ELK+NW4QtfK363eClngfGZka/Vg1VgH/Zk1XVQnuoHx1Z/af8PhvT4oHjx/+5+xvD54+yOHJ0y8fPKBf", + "PqEPv3z8EB797emTB/Bw/sWXs0fFoyePZk8ePfni6Zf54ycPZ0+++PI/7xg+ZEC2gE58hojJ/2IByez4", + "1Ul2ZoBtcEIrFor/GzL2VbBojicRVpSVkyP/0//jT9hBLlbN8P7XiQtAnyy1rtTR4eHl5eVB3OVwgXbP", + "TIs6Xx76efpF11+dBIWxfZTjjtr4MK9s8aRwjN9ef3N6Ro5fnRxERX2PJg8OHhw8xJqvFXBascnR5DH+", + "hKdnift+6IhtcvTu/XRyuARaopuQ+WMFWrLcf5JAi437v7qkiwXIA1cazPx08ejQixWH75z9972ZYZHK", + "bGZDI6N4uH7FLOdLgroBG/rYqkChXEGEaahL4nQ/vMCINWtSNWwuIO6kaBJwnzRMy6chs3lZj35JFEyd", + "s4V5U7cKQXdKTJtX+H+f/viSCEnc8+YVzc+DGpmczG1KGSkuGAZCFVH0nOl54On3XzXITUNfjvPFOUd9", + "mQmnj16pRdWOxWikqpTKPFWdDGc2ZBERdvDWaBiXljXEkDRs2LDWB9mXb989/dv7yQhA0HVIAWak+Y2W", + "5W+2sDWsUd3jc7q5nD3TREkFlKanjfUfOzQ7OcVgkvA1rpoV2rRDGH/jgsNvQ9vgAEvuAy1L01BwSO3B", + "W8yZgsSCZ+7Rgwc3Vm4vRO1apXkYxZPEFQbqMyT7KVFB21fdGyif/eQGF9r2ub/2crvD9Rb9NS2wkhEo", + "bZfy8LNdyglH7z1zQRB7Ab6fTp5+xntzwg3PoSXBllFCsv5F8xM/5+KS+5ZG+KlXKyo3KNpE5dY6GQHo", + "QqFpGFmkPdutAkuTt+8Hb73DuH7M4buWA1hxrTuxVzrr5PmOa/KOGuKc/XS+nfI05nuoPoIuQq4GD9ZD", + "UfcOyHdxb+TemB3H5p6pJYfC+2/5Wy+k+/NJBBvY7qg4cVDy0o7Uxbf396e+v4/byo5WytgUMK1TsBWm", + "nhPpdS/QvuWqU4D0SgU+o0IxV0i3/0GroHXemnamt6mn4E5GfYu7AdwNiUkRvEFiahf4+fCs2QdahZuk", + "dWV8QMb9mQt9P9DS0Em03E4SCptH+VYY/MsIgyGuwNYR96UDriceYhGxw3c+N/YNiIQuN/gIYTB+Vkd9", + "o9zNdzvs5N6BTXQdt7kaz3CBBDvFPMxYfivg/QEEvH41gBQYTY73TyfUIQzLplzAPkXAW9X99ipr8JlK", + "cX9hZA2KbQbS3QLbFdhnTxhzzPqDsdU/pRDmkHYrfv2lxa8Q3nctAaxVz8MFjEZmrGtp77raOaaDJNYO", + "8Yw4W6ie7o7wtKk9ZlgMpoPzmYDU1L8M0ZxqH412s6a9d2NfxPoO4gfq15uT57ukq89IzzM6LWniFkjv", + "zYfmpUmzw+uPY3YYx5uePHjy8SCId+Gl0ORbvMU/MIf8oCwtTVb7srBtHOlwZhOmb+NKvMOWkFE0idAj", + "HoX1ZuJk69Y7466r8hsnsLl3QHxadhWKzLj4moWgZePaT+XCdjI8ziCB3PF/HuH4dw7ItxhXodUUncy0", + "q0BC7jCujx4+evzENZH00vpwddvNvnhydPzVV65Zk4Tfvm96zZWWR0soS+E6uLuhP675cPS///i/g4OD", + "OzvZqVh/vXlpM17+UXjqNBVbEDZ+aLc+801KvdJdJtKdqPsoZvuvxTrJ/cX69vb5ZLePwf6f4taZtcnI", + "PUCDBrOVJuQGbyF7TPa5h6Y+qb3hO+EyOSAvhcvYVJdUEiELkK4q16KmknINUBx4SsXYP2Uz1OQlA67N", + "gxHrDMlMsQJsootFLaEgJVthIW4JF+gbj9PjW74FwW5Gjx60f1gm/wNdR1lcZuGa1sItGdWdK7r2lc6w", + "lo+Q+NNXX5EH0+bVUpZmgCwgJsVcV3Q9+YjavkBso/zO28VIdjrm4thjNEeN9BNCGePKB39tzv3ZSuyW", + "3N3G3hDn3Nvg0xh0Yv2By4u0VXNgBTtbBw0Lc22aoGgj5XkRKs3izAxjlQJ/YNvATpV08vHZRe/tIb59", + "/F+LlXQJak+2gdGm6vAd2jJintE7txgt99cyk0Y2IylW3mgkyBx0vnSBuh3UJ9iTL4UyzJu2FcK9aakG", + "d7GffCBOS4sFWkdmPopiKNFwBzJBxD/6rOTmM5vbPB8+L46v94wmKeZLIIbqh65GLFPej9/H85pd3AvK", + "Z83kfYEM0XITds9bBO+H4B5z/MaX2kOMuUX8GTz9/VMyIy9FEy7uqrz8GU2OH/Jm/9ALeik4WNu6kXwt", + "Ld6aUYPYgSp8RIrPE2LfLyHf/pVFkENfNHKrHPJ3W7Jxqywy5vY2k32WV/jfk+XYW7eMWdvBziQIzWhj", + "mLNpaJMRtZPif8JXzCfhp3/Ap82n4Fgfh8XgIfV8xokF/GaZDqbescR8GPKhD3GgdImJ0dxIi+B+lqwK", + "MYNS8IX6Y7KibdSRxkuCSkLxjXSFjb/e2X2GWX3Mk9d6Pro8T4rxHGxRVKzn1CRhsxD+7eNBqNnKJxXm", + "cczqJ+YuTx88/njTn4K8YDmQM1hVQlLJyg35iYcCttfhdlg/JORd89rgZMkYtDa184HlcfKiqzPBlsva", + "O71mxfvdzDDK3rcnH2Q84oNxrkFaVUDl1RngbtNVN+vxyfPYK7hV1iJk0kqAYlC0p2P8f0xG6p0w3F3M", + "3eVXcwuoz/rl2IRz2RXzaXCOMVKAmB+RN/w+UUv69OGjXx89/cL/+ejpFwOaMzOPS9bT1501A5nPdpgx", + "CrTPWh14s1J7wO/Rx97t/TZxOmHFOpn4vill1cs97MSyO4pUdDNYHaPaUYorHrYpy/XxkxwqzWbL5PvK", + "P39Cdu8T/nV4BdtMfK6C1W0JroGgiYjPGEJranEFrG8vy7VFmuyQZah/9LEfp01wgb3oPPJk5875pIKu", + "/lSP1AzfqMC9YNNGy6eTKbE4wzQyd/uq9tZ3pa4qIXU43epglLgHQ2a7lrQ3RLh7CXM51fmyrg7f4X8w", + "s9f7JuDAFhyO7Hzud1hXIJkRHbHemPu1xIqMh9a2v036O7UtrnlTdsRs61HQyVPvU885fwMxJz+wXIpj", + "rATiLiG1URpWvfyAruuvA5FhPpln/8ISvGQcspXgqax1P+LXH/BjstaI0LQc6ox1BYb6dlhmG/4OWO15", + "xvDL6+L3D/JAv5ZiqbNaCeZwN8UaLf3veQD9odnwvH+SNjzvH75WxciBnw/ftf50nj2upVrWuhCXUV98", + "FloONcaoH2XTHq9NDy+lTlZqRQpQhmg/P9VVhIfUiQlfE/nJopzpgynK/qLKrDnjRYdIUM7MxQVIFdQc", + "0jvh3Gq0/jwardH7vhePtfk4d3G0Wt2sRPJSFGDHbafDTQWRclGASxvaF0SCZJbWAvhbqWnXeZfltF4s", + "NcFCVqkXYNMxo7llsramrNpVhNO28pVsLoDQEpOxkhkAJ2JmFt0uZkyoQgd6/4x08me6lmQDVyVFDkpB", + "kfmg2V2ghWSs+OjUW/CEgCPAYRaiBJlTeW1gzy92whmSmSty9/uf1b1PAK8VBbcj1rrtJtAbXIOctNeH", + "etz02wiuO3lMdlQC8aIBar3EqirB6b0SKNwLJ4P714Wot4vXRwsqhtgHpng/yfUIKID6gen9utDWVWbu", + "70S1W/v1jK1QEuOUCwW54IUarkm9iy1jQZFoLcqsIOKEKU6MAw88OF9QpV87E0hcMS0qXGKm2FJEeyhp", + "vhn555Ayvzd2bu5DrmoV8uo7tUa6ahmH9Za5XsI6zIU2KD920JtoQWoFu0YewlI0vkOWiqti68h4hGVF", + "+ovDTCfUKSj6qGwB0SBiGyCnvlWrHF9j2BgAhKkG0aF2WJtyogJaSouqwoqAWc1DvyE0ndrWx/qnpm2f", + "uFxpI7y3CwEq1mk5yC8tZhWGciypIg4OsqLnTu21cJmgEkW/2AoyNFdn2yjfHMtT0yo+AjsPaV0tJC2w", + "WjJNqFJ+sp+J/bxtANxxT55Yij6bwTxZpsRsekPJclBFFIYWOJ5KCY9YuV6R3BzBOdbN8QTieu8YuYCB", + "svlnUXFf1xznSm6RHw+Xbbd6qNzlhUBFp6MHBNlx9DEAD+AhDH11VGDnrFEfdKf4Byg3QZAj9p9kA2po", + "Cc34ey2gq86LL7DWTdFh7x0OnGSbg2xsBx8ZOrIpBeJnGevXteZ+QFe1tgI1egAeXOVxe3hJmc7mQlpB", + "OqNzDTKhy+tUOqBM+1BCa1fRwjlSEBzB3ZtuHGTycT4Ox0UsCMSXQGWrRHofM9W3Qo6KB2p7vVGmSc01", + "K6OY6PBU/uMpDG+VALdKgFslwK0S4FYJcKsEuFUC3CoBbpUAt0qAWyXArRLgr6sE+FQRfpmXOLzfMxc8", + "47Cgml1ACP27TUr0p4qICXeVV0qgGuOSMu1SfBLq5QD8cr2AQA20RBywEplsJdRg7iQsDq1ELXMguYGQ", + "cVKV1LwNYK1Dwrl2KlOfXNmVh8bsqFTB40fk9O/H3nF/6RzM223vHrsk5UpvSrjnUjqE+q0+twNwg3SX", + "2oH6O8EnpnNp+lgJRBn0foOtn8MFlKICaX2CiZZ1QuVzBrR85nCzQ+PTKrlpRvtt2lI0ObStaBWVwce1", + "UkWojeZoV8yc01INl8y0461olcoNF24+qwtCbvK1wBKz8Qkxu3aIG9g+G437PuNUbhKhO70T0SMNLQy/", + "coTVV2a9v/Egkz7R9slsF4WlxHUJKnmOt1F5MroibFhvKBsENO/QSbK8dDekYBIAHOMCa+jZ7wl5bft9", + "2hB2hMgdsYaZ/2E8B9stA9PAtuYV4VjP5xpv7hGfPL149qeGsIs6B8K0Ij5OZff1Mp2sMzPSAnjmGFA2", + "E8Uma7GvSesWKpiiSsFqtvsmivmny4bsLh/zZfs99WmukefR4rbx5Jho1pljwAPceaNhNG8O2MIRHXuO", + "MP6hWfQQG41BII4/pbRK3Ro0ezK9ZprNLeO7ZXzRaexIBIy7uL4uEzn4gIxPbmTNh3neN2vIawNcfJLv", + "onoebXKw1i3DZgGzerHArM49I51ZGuB4TPBPxArtcsdywf0oyA4eMn1eN7lUd7g+d4li2O4KSRZS1NU9", + "W76Kb9Casaoo33ibL2SKrerS4tAmxLtZRmtD7/qeAGiPdcq/IbX2K6/zi5S37qpt/27RQi6pInZ/oSA1", + "L1zsUC9Ad83HZ5S2Q5+tecOmt+aUtutNrM7NO+aK8LvsglyCnbsCmek1tweqnfbdBgLbk3twm832r3Ft", + "vLKZGAYYbD+otWEIN3R7yIiv4fURpS5pguHaNbhshcCh0JE4j4lteaPeI73h204kUX0+aySFsiLUlxrI", + "BVda1rl+wykaaaKFHfQdTLw2epi/PfNN0nbChBnPDfWGU8xEH0w3ST43h4Sd4lsAz0ZVvViAMrwyJpI5", + "wBvuWjFOam5eWmJOViyXIrOBqOYMGfnkwLZc0Q2Z0xKtjL+DFGRmbvZo163CWGlWls6jxUxDxPwNp5qU", + "QJUmPzDDZc1wPsdYcOUCfSnkecBCOq3FAjgoprK08uU7+xUzR7jleyUfKizt5ybi++OmjPCws2IQ8pPn", + "Bm6KSXJKpnTjBNGD/aMZwFeMZ0kiO1sCcT5hXdoidzGHjCOge23rkF7CG25uOC0IcnWqr0YOXTNP7yza", + "09GhmtZGdKxBfq2jnng3wmVIgsncmlb+RKGZER148yVuPNao6e79nmaUrWUvU19dprGBRu6R0FKEtS/u", + "U9firAXynzdL/dsP8170aLyxF2N/wD67aueSQrz5DZ8SWgq+IJdML/EFKXCfGK9qjY7VH1JJt9V9pUlg", + "t1pBwaiGckMqCTlg2hj062netgc2QQDJl5Qv8IqTol642tN2nEuQEHJ9medkd4h0epE1z1CmTvgoHBOr", + "F/SUbE45VleM70TsbC8C8371iLfZG8a8UBMn7zsz5tCDdToZFEgNUi8aPzKLnPZxTGPhUtRlkak6zyHl", + "DHKS9NAKS21TH45lxUQ3IBSkqKUtw01ormsseDPDJJzC1rcypNmSNLBQmJBRsk5CiWILTnUtYWrXhl5p", + "MyBo2zxIuOp05IiWRBDtfIPSLipuopbB7TG4PQZ/vmPQu4ZeOyKZd7QWljJiAvzzVKG4rXH/hy/O8CGf", + "Gx96NR/q9eJ5rSIUKwDHx7WTX9GeXqoI046lzYDABS1rZHkug7x70x+Qs4YhBrfxWrnEpvmSMu7y2YQA", + "B4RDu+TL2md7/CAKTsvcULNp0AF5LZne4MuGVuzXczD/f2ueBrYkqX301LKcHE2WWldHh4dYxX8plD6c", + "vJ/G31Tn49sA/zv/Xqkku8CiPm/f//8BAAD//8KiPvCNTgEA", } // 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 d3ef2011e1..18916f47f7 100644 --- a/daemon/algod/api/server/v2/generated/participating/private/routes.go +++ b/daemon/algod/api/server/v2/generated/participating/private/routes.go @@ -158,175 +158,176 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "axL4VN8VjhSuP17/TwAAAP//fbJmDq7KAAA=", + "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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 1bb7c09d39..a7111270cd 100644 --- a/daemon/algod/api/server/v2/generated/participating/public/routes.go +++ b/daemon/algod/api/server/v2/generated/participating/public/routes.go @@ -177,184 +177,185 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "AAA=", + "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", + "Ukh2jdlI33/8fwEAAP//LOKPVrjbAAA=", } // 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 1b2e917e11..f7bd4799d1 100644 --- a/daemon/algod/api/server/v2/handlers.go +++ b/daemon/algod/api/server/v2/handlers.go @@ -948,6 +948,16 @@ type PreEncodedSimulateResponse struct { WouldSucceed bool `codec:"would-succeed"` } +// PreEncodedSimulateRequestTransactionGroup mirrors model.SimulateRequestTransactionGroup +type PreEncodedSimulateRequestTransactionGroup struct { + Txns []transactions.SignedTxn `codec:"txns"` +} + +// PreEncodedSimulateRequest mirrors model.SimulateRequest +type PreEncodedSimulateRequest struct { + TxnGroups []PreEncodedSimulateRequestTransactionGroup `codec:"txn-groups"` +} + // SimulateTransaction simulates broadcasting a raw transaction to the network, returning relevant simulation results. // (POST /v2/transactions/simulate) func (v2 *Handlers) SimulateTransaction(ctx echo.Context, params model.SimulateTransactionParams) error { @@ -961,10 +971,39 @@ func (v2 *Handlers) SimulateTransaction(ctx echo.Context, params model.SimulateT } proto := config.Consensus[stat.LastVersion] - txgroup, err := decodeTxGroup(ctx.Request().Body, proto.MaxTxGroupSize) + requestBuffer := new(bytes.Buffer) + requestBodyReader := http.MaxBytesReader(nil, ctx.Request().Body, MaxTealDryrunBytes) + _, err = requestBuffer.ReadFrom(requestBodyReader) if err != nil { return badRequest(ctx, err, err.Error(), v2.Log) } + requestData := requestBuffer.Bytes() + + var simulateRequest PreEncodedSimulateRequest + err = decode(protocol.CodecHandle, requestData, &simulateRequest) + if err != nil { + err = decode(protocol.JSONStrictHandle, requestData, &simulateRequest) + if err != nil { + return badRequest(ctx, err, err.Error(), v2.Log) + } + } + + for _, txgroup := range simulateRequest.TxnGroups { + if len(txgroup.Txns) == 0 { + err = errors.New("empty txgroup") + return badRequest(ctx, err, err.Error(), v2.Log) + } + if len(txgroup.Txns) > proto.MaxTxGroupSize { + err = fmt.Errorf("transaction group size %d exceeds protocol max %d", len(txgroup.Txns), proto.MaxTxGroupSize) + return badRequest(ctx, err, err.Error(), v2.Log) + } + } + + if len(simulateRequest.TxnGroups) != 1 { + err := fmt.Errorf("expected 1 transaction group, got %d", len(simulateRequest.TxnGroups)) + return badRequest(ctx, err, err.Error(), v2.Log) + } + txgroup := simulateRequest.TxnGroups[0].Txns // Simulate transaction simulationResult, err := v2.Node.Simulate(txgroup) @@ -984,12 +1023,12 @@ func (v2 *Handlers) SimulateTransaction(ctx echo.Context, params model.SimulateT if err != nil { return badRequest(ctx, err, errFailedParsingFormatOption, v2.Log) } - data, err := encode(handle, &response) + responseData, err := encode(handle, &response) if err != nil { return internalError(ctx, err, errFailedToEncodeResponse, v2.Log) } - return ctx.Blob(http.StatusOK, contentType, data) + return ctx.Blob(http.StatusOK, contentType, responseData) } // TealDryrun takes transactions and additional simulated ledger state and returns debugging information. diff --git a/daemon/algod/api/server/v2/handlers_test.go b/daemon/algod/api/server/v2/handlers_test.go index 4dc20abe4c..42de7293fd 100644 --- a/daemon/algod/api/server/v2/handlers_test.go +++ b/daemon/algod/api/server/v2/handlers_test.go @@ -207,3 +207,32 @@ func TestSimulateResponseStruct(t *testing.T) { generatedResponseGraph.AssertEquals(t, customResponseGraph) } + +// TestSimulateRequestStruct ensures that the hand-written PreEncodedSimulateRequest has the same +// encoding structure as the generated model.SimulateRequest +func TestSimulateRequestStruct(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + generatedResponseType := reflect.TypeOf(model.SimulateRequest{}) + generatedResponseGraph := makeTagGraph(generatedResponseType, make(map[reflect.Type]*tagNode)) + + customResponseType := reflect.TypeOf(PreEncodedSimulateRequest{}) + customResponseGraph := makeTagGraph(customResponseType, make(map[reflect.Type]*tagNode)) + + expectedGeneratedTxnGraph := map[string]*tagNode{ + "": {children: make(map[string]*tagNode)}, + } + preEncodedTxPath := func(graph *tagNode) *tagNode { + // Resolve the field model.SimulateRequest{}.TxnGroups[0].Txns[0] + return graph.children["txn-groups"].children[""].children["txns"].children[""] + } + if assert.Equal(t, expectedGeneratedTxnGraph, preEncodedTxPath(generatedResponseGraph).children) { + // The generated response type uses json.RawMessage to represent a transaction, while + // the custom response type uses transactions.SignedTxn. Let's copy that into the generated + // type. + preEncodedTxPath(generatedResponseGraph).children = preEncodedTxPath(customResponseGraph).children + } + + generatedResponseGraph.AssertEquals(t, customResponseGraph) +} diff --git a/daemon/algod/api/server/v2/test/handlers_test.go b/daemon/algod/api/server/v2/test/handlers_test.go index b5aeb8e6ef..b12af6f1c1 100644 --- a/daemon/algod/api/server/v2/test/handlers_test.go +++ b/daemon/algod/api/server/v2/test/handlers_test.go @@ -716,7 +716,7 @@ func TestPendingTransactionsByAddress(t *testing.T) { pendingTransactionsByAddressTest(t, -1, "json", 400) } -func prepareTransactionTest(t *testing.T, txnToUse, expectedCode int) (handler v2.Handlers, c echo.Context, rec *httptest.ResponseRecorder, releasefunc func()) { +func prepareTransactionTest(t *testing.T, txnToUse int, txnPrep func(transactions.SignedTxn) []byte) (handler v2.Handlers, c echo.Context, rec *httptest.ResponseRecorder, releasefunc func()) { numAccounts := 5 numTransactions := 5 offlineAccounts := true @@ -733,7 +733,7 @@ func prepareTransactionTest(t *testing.T, txnToUse, expectedCode int) (handler v var body io.Reader if txnToUse >= 0 { stxn := stxns[txnToUse] - bodyBytes := protocol.Encode(&stxn) + bodyBytes := txnPrep(stxn) body = bytes.NewReader(bodyBytes) } req := httptest.NewRequest(http.MethodPost, "/", body) @@ -743,7 +743,10 @@ func prepareTransactionTest(t *testing.T, txnToUse, expectedCode int) (handler v } func postTransactionTest(t *testing.T, txnToUse, expectedCode int) { - handler, c, rec, releasefunc := prepareTransactionTest(t, txnToUse, expectedCode) + txnPrep := func(stxn transactions.SignedTxn) []byte { + return protocol.Encode(&stxn) + } + handler, c, rec, releasefunc := prepareTransactionTest(t, txnToUse, txnPrep) defer releasefunc() err := handler.RawTransaction(c) require.NoError(t, err) @@ -759,7 +762,17 @@ func TestPostTransaction(t *testing.T) { } func simulateTransactionTest(t *testing.T, txnToUse int, format string, expectedCode int) { - handler, c, rec, releasefunc := prepareTransactionTest(t, txnToUse, expectedCode) + txnPrep := func(stxn transactions.SignedTxn) []byte { + request := v2.PreEncodedSimulateRequest{ + TxnGroups: []v2.PreEncodedSimulateRequestTransactionGroup{ + { + Txns: []transactions.SignedTxn{stxn}, + }, + }, + } + return protocol.EncodeReflect(&request) + } + handler, c, rec, releasefunc := prepareTransactionTest(t, txnToUse, txnPrep) defer releasefunc() err := handler.SimulateTransaction(c, model.SimulateTransactionParams{Format: (*model.SimulateTransactionParamsFormat)(&format)}) require.NoError(t, err) @@ -806,43 +819,30 @@ func TestPostSimulateTransaction(t *testing.T) { } } -func copyInnerTxnGroupIDs(t *testing.T, dst, src *model.PendingTransactionResponse) { +func copyInnerTxnGroupIDs(t *testing.T, dst, src *v2.PreEncodedTxInfo) { t.Helper() - // msgpack decodes to map[interface{}]interface{} while JSON decodes to map[string]interface{} - txn := dst.Txn["txn"] - switch dstTxnMap := txn.(type) { - case map[string]interface{}: - srcTxnMap := src.Txn["txn"].(map[string]interface{}) - groupID, hasGroupID := srcTxnMap["grp"] - if hasGroupID { - dstTxnMap["grp"] = groupID - } - case map[interface{}]interface{}: - srcTxnMap := src.Txn["txn"].(map[interface{}]interface{}) - groupID, hasGroupID := srcTxnMap["grp"] - if hasGroupID { - dstTxnMap["grp"] = groupID - } + if !src.Txn.Txn.Group.IsZero() { + dst.Txn.Txn.Group = src.Txn.Txn.Group } - if dst.InnerTxns == nil || src.InnerTxns == nil { + if dst.Inners == nil || src.Inners == nil { return } - assert.Equal(t, len(*dst.InnerTxns), len(*src.InnerTxns)) + assert.Equal(t, len(*dst.Inners), len(*src.Inners)) - for innerIndex := range *dst.InnerTxns { - if innerIndex == len(*src.InnerTxns) { + for innerIndex := range *dst.Inners { + if innerIndex == len(*src.Inners) { break } - dstInner := &(*dst.InnerTxns)[innerIndex] - srcInner := &(*src.InnerTxns)[innerIndex] + dstInner := &(*dst.Inners)[innerIndex] + srcInner := &(*src.Inners)[innerIndex] copyInnerTxnGroupIDs(t, dstInner, srcInner) } } -func assertSimulationResultsEqual(t *testing.T, expectedError string, expected, actual model.SimulateResponse) { +func assertSimulationResultsEqual(t *testing.T, expectedError string, expected, actual v2.PreEncodedSimulateResponse) { t.Helper() if len(expectedError) != 0 { @@ -861,23 +861,23 @@ func assertSimulationResultsEqual(t *testing.T, expectedError string, expected, } expectedGroup := &expected.TxnGroups[groupIndex] actualGroup := &actual.TxnGroups[groupIndex] - assert.Equal(t, len(expectedGroup.TxnResults), len(actualGroup.TxnResults)) - for txnIndex := range expectedGroup.TxnResults { - if txnIndex == len(actualGroup.TxnResults) { + assert.Equal(t, len(expectedGroup.Txns), len(actualGroup.Txns)) + for txnIndex := range expectedGroup.Txns { + if txnIndex == len(actualGroup.Txns) { break } - expectedTxn := &expectedGroup.TxnResults[txnIndex] - actualTxn := &actualGroup.TxnResults[txnIndex] - if expectedTxn.TxnResult.InnerTxns == nil || actualTxn.TxnResult.InnerTxns == nil { + expectedTxn := &expectedGroup.Txns[txnIndex] + actualTxn := &actualGroup.Txns[txnIndex] + if expectedTxn.Txn.Inners == nil || actualTxn.Txn.Inners == nil { continue } - assert.Equal(t, len(*expectedTxn.TxnResult.InnerTxns), len(*actualTxn.TxnResult.InnerTxns)) - for innerIndex := range *expectedTxn.TxnResult.InnerTxns { - if innerIndex == len(*actualTxn.TxnResult.InnerTxns) { + assert.Equal(t, len(*expectedTxn.Txn.Inners), len(*actualTxn.Txn.Inners)) + for innerIndex := range *expectedTxn.Txn.Inners { + if innerIndex == len(*actualTxn.Txn.Inners) { break } - expectedInner := &(*expectedTxn.TxnResult.InnerTxns)[innerIndex] - actualInner := &(*actualTxn.TxnResult.InnerTxns)[innerIndex] + expectedInner := &(*expectedTxn.Txn.Inners)[innerIndex] + actualInner := &(*actualTxn.Txn.Inners)[innerIndex] copyInnerTxnGroupIDs(t, expectedInner, actualInner) } } @@ -886,20 +886,20 @@ func assertSimulationResultsEqual(t *testing.T, expectedError string, expected, require.Equal(t, expected, actual) } -func makePendingTxnResponse(t *testing.T, txn transactions.SignedTxnWithAD, handle codec.Handle) model.PendingTransactionResponse { +func makePendingTxnResponse(t *testing.T, txn transactions.SignedTxnWithAD) v2.PreEncodedTxInfo { t.Helper() preEncoded := v2.ConvertInnerTxn(&txn) - // encode to bytes - var encodedBytes []byte - encoder := codec.NewEncoderBytes(&encodedBytes, handle) - err := encoder.Encode(&preEncoded) - require.NoError(t, err) + // In theory we could return preEncoded directly, but there appears to be some subtle differences + // once you encode and decode the object, such as *uint64 fields turning from 0 to nil. So to be + // safe, let's encode and decode the object. + + // Encode to bytes + encodedBytes := protocol.EncodeReflect(&preEncoded) - // decode to model.PendingTransactionResponse - var response model.PendingTransactionResponse - decoder := codec.NewDecoderBytes(encodedBytes, handle) - err = decoder.Decode(&response) + // Decode to v2.PreEncodedTxInfo + var response v2.PreEncodedTxInfo + err := protocol.DecodeReflect(encodedBytes, &response) require.NoError(t, err) return response @@ -961,10 +961,14 @@ int 1`, // build request body var body io.Reader - var bodyBytes []byte - for _, stxn := range stxns { - bodyBytes = append(bodyBytes, protocol.Encode(&stxn)...) + request := v2.PreEncodedSimulateRequest{ + TxnGroups: []v2.PreEncodedSimulateRequestTransactionGroup{ + { + Txns: stxns, + }, + }, } + bodyBytes := protocol.EncodeReflect(&request) msgpackFormat := model.SimulateTransactionParamsFormatMsgpack jsonFormat := model.SimulateTransactionParamsFormatJson @@ -1011,7 +1015,7 @@ int 1`, require.Equal(t, 200, rec.Code, rec.Body.String()) // decode actual response - var actualBody model.SimulateResponse + var actualBody v2.PreEncodedSimulateResponse decoder := codec.NewDecoderBytes(rec.Body.Bytes(), responseFormat.handle) err = decoder.Decode(&actualBody) require.NoError(t, err) @@ -1030,26 +1034,26 @@ int 1`, for i := range scenario.TxnAppBudgetConsumed { txnAppBudgetUsed = append(txnAppBudgetUsed, numOrNil(scenario.TxnAppBudgetConsumed[i])) } - expectedBody := model.SimulateResponse{ + expectedBody := v2.PreEncodedSimulateResponse{ Version: 1, - TxnGroups: []model.SimulateTransactionGroupResult{ + TxnGroups: []v2.PreEncodedSimulateTxnGroupResult{ { AppBudgetAdded: appBudgetAdded, AppBudgetConsumed: appBudgetConsumed, FailedAt: expectedFailedAt, - TxnResults: []model.SimulateTransactionResult{ + Txns: []v2.PreEncodedSimulateTxnResult{ { - TxnResult: makePendingTxnResponse(t, transactions.SignedTxnWithAD{ + Txn: makePendingTxnResponse(t, transactions.SignedTxnWithAD{ SignedTxn: stxns[0], // expect no ApplyData info - }, responseFormat.handle), + }), AppBudgetConsumed: txnAppBudgetUsed[0], }, { - TxnResult: makePendingTxnResponse(t, transactions.SignedTxnWithAD{ + Txn: makePendingTxnResponse(t, transactions.SignedTxnWithAD{ SignedTxn: stxns[1], ApplyData: scenario.ExpectedSimulationAD, - }, responseFormat.handle), + }), AppBudgetConsumed: txnAppBudgetUsed[1], }, }, @@ -1114,6 +1118,73 @@ func TestSimulateTransactionVerificationFailure(t *testing.T) { require.Equal(t, 400, rec.Code, rec.Body.String()) } +func TestSimulateTransactionMultipleGroups(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + // prepare node and handler + numAccounts := 5 + offlineAccounts := true + mockLedger, roots, _, _, releasefunc := testingenv(t, numAccounts, 1, offlineAccounts) + defer releasefunc() + dummyShutdownChan := make(chan struct{}) + mockNode := makeMockNode(mockLedger, t.Name(), nil, cannedStatusReportGolden) + handler := v2.Handlers{ + Node: mockNode, + Log: logging.Base(), + Shutdown: dummyShutdownChan, + } + + hdr, err := mockLedger.BlockHdr(mockLedger.Latest()) + require.NoError(t, err) + txnInfo := simulationtesting.TxnInfo{LatestHeader: hdr} + + sender := roots[0] + receiver := roots[1] + + txn1 := txnInfo.NewTxn(txntest.Txn{ + Type: protocol.PaymentTx, + Sender: sender.Address(), + Receiver: receiver.Address(), + Amount: 1, + }) + txn2 := txnInfo.NewTxn(txntest.Txn{ + Type: protocol.PaymentTx, + Sender: sender.Address(), + Receiver: receiver.Address(), + Amount: 2, + }) + + stxn1 := txn1.Txn().Sign(sender.Secrets()) + stxn2 := txn2.Txn().Sign(sender.Secrets()) + + // build request body + request := v2.PreEncodedSimulateRequest{ + TxnGroups: []v2.PreEncodedSimulateRequestTransactionGroup{ + { + Txns: []transactions.SignedTxn{stxn1}, + }, + { + Txns: []transactions.SignedTxn{stxn2}, + }, + }, + } + bodyBytes := protocol.EncodeReflect(&request) + body := bytes.NewReader(bodyBytes) + req := httptest.NewRequest(http.MethodPost, "/", body) + rec := httptest.NewRecorder() + + e := echo.New() + c := e.NewContext(req, rec) + + // simulate transaction + err = handler.SimulateTransaction(c, model.SimulateTransactionParams{}) + require.NoError(t, err) + bodyString := rec.Body.String() + require.Equal(t, 400, rec.Code, bodyString) + require.Contains(t, bodyString, "expected 1 transaction group, got 2") +} + func startCatchupTest(t *testing.T, catchpoint string, nodeError error, expectedCode int) { numAccounts := 1 numTransactions := 1 diff --git a/libgoal/libgoal.go b/libgoal/libgoal.go index 143cfde028..0e45c6f6f8 100644 --- a/libgoal/libgoal.go +++ b/libgoal/libgoal.go @@ -1269,14 +1269,14 @@ func (c *Client) Dryrun(data []byte) (resp model.DryrunResponse, err error) { return } -// SimulateRawTransaction simulates a raw transaction or raw transaction group and returns relevant simulation results. -func (c *Client) SimulateRawTransaction(data []byte) (result v2.PreEncodedSimulateResponse, err error) { +// SimulateTransactionsRaw simulates a transaction group by taking raw request bytes and returns relevant simulation results. +func (c *Client) SimulateTransactionsRaw(encodedRequest []byte) (result v2.PreEncodedSimulateResponse, err error) { algod, err := c.ensureAlgodClient() if err != nil { return } var resp []byte - resp, err = algod.RawSimulateRawTransaction(data) + resp, err = algod.RawSimulateRawTransaction(encodedRequest) if err != nil { return } @@ -1284,13 +1284,9 @@ func (c *Client) SimulateRawTransaction(data []byte) (result v2.PreEncodedSimula return } -// SimulateTransactionGroup simulates a transaction group and returns relevant simulation results. -func (c *Client) SimulateTransactionGroup(txgroup []transactions.SignedTxn) (result v2.PreEncodedSimulateResponse, err error) { - var enc []byte - for i := range txgroup { - enc = append(enc, protocol.Encode(&txgroup[i])...) - } - return c.SimulateRawTransaction(enc) +// SimulateTransactions simulates transactions and returns relevant simulation results. +func (c *Client) SimulateTransactions(request v2.PreEncodedSimulateRequest) (result v2.PreEncodedSimulateResponse, err error) { + return c.SimulateTransactionsRaw(protocol.EncodeReflect(&request)) } // TransactionProof returns a Merkle proof for a transaction in a block. diff --git a/test/e2e-go/restAPI/restClient_test.go b/test/e2e-go/restAPI/restClient_test.go index f8d712f773..4b2024ca70 100644 --- a/test/e2e-go/restAPI/restClient_test.go +++ b/test/e2e-go/restAPI/restClient_test.go @@ -1601,7 +1601,14 @@ func TestSimulateTransaction(t *testing.T) { currentRoundBeforeSimulate, err := testClient.CurrentRound() a.NoError(err) - result, err := testClient.SimulateTransactionGroup([]transactions.SignedTxn{stxn}) + simulateRequest := v2.PreEncodedSimulateRequest{ + TxnGroups: []v2.PreEncodedSimulateRequestTransactionGroup{ + { + Txns: []transactions.SignedTxn{stxn}, + }, + }, + } + result, err := testClient.SimulateTransactions(simulateRequest) a.NoError(err) currentAfterAfterSimulate, err := testClient.CurrentRound() diff --git a/test/scripts/e2e_subs/e2e-app-simulate.sh b/test/scripts/e2e_subs/e2e-app-simulate.sh index c68ab9ee98..ebfb63d9c1 100755 --- a/test/scripts/e2e_subs/e2e-app-simulate.sh +++ b/test/scripts/e2e_subs/e2e-app-simulate.sh @@ -20,13 +20,13 @@ ACCOUNT=$(${gcmd} account list|awk '{ print $3 }') CONST_TRUE="true" CONST_FALSE="false" -# First, try to send an extremely large "transaction" in the request body. +# First, try to send an extremely large "request" in the request body. # This should fail with a 413 error. # Some of our MacOS nightly tests fail for specifying the bs (block size) # value in capital letters (i.e. 11M), so just specify it as 1024 bytes and # allocate 11K blocks so we get a 11MB sized file. -dd if=/dev/zero of="${TEMPDIR}/toolarge.tx" bs=1024 count=11000 -RES=$(${gcmd} clerk simulate -t "${TEMPDIR}/toolarge.tx" 2>&1 || true) +dd if=/dev/zero of="${TEMPDIR}/tooLargeRequest.json" bs=1024 count=11000 +RES=$(${gcmd} clerk simulate --request "${TEMPDIR}/tooLargeRequest.json" 2>&1 || true) EXPERROR="simulation error: HTTP 413 Request Entity Too Large:" if [[ $RES != *"${EXPERROR}"* ]]; then date '+app-simulate-test FAIL the simulate API should fail for request bodies exceeding 10MB %Y%m%d_%H%M%S' @@ -71,12 +71,32 @@ ${gcmd} clerk sign -i "${TEMPDIR}/grouped-1.tx" -o "${TEMPDIR}/grouped-1.stx" cat "${TEMPDIR}/grouped-0.stx" "${TEMPDIR}/grouped-1.stx" > "${TEMPDIR}/grouped.stx" RES=$(${gcmd} clerk simulate -t "${TEMPDIR}/grouped.stx" | jq '."would-succeed"') - if [[ $RES != $CONST_TRUE ]]; then date '+app-simulate-test FAIL should pass to simulate self pay transaction group %Y%m%d_%H%M%S' false fi +# Test creating and using a simulate request object +${gcmd} clerk simulate -t "${TEMPDIR}/grouped.stx" --request-only-out "${TEMPDIR}/simulateRequest.json" + +NUM_GROUPS=$(jq '."txn-groups" | length' < "${TEMPDIR}/simulateRequest.json") +if [ $NUM_GROUPS -ne 1 ]; then + date '+app-simulate-test FAIL should have 1 transaction group in simulate request %Y%m%d_%H%M%S' + false +fi + +NUM_TXNS=$(jq '."txn-groups"[0]."txns" | length' < "${TEMPDIR}/simulateRequest.json") +if [ $NUM_TXNS -ne 2 ]; then + date '+app-simulate-test FAIL should have 2 transactions in simulate request %Y%m%d_%H%M%S' + false +fi + +RES=$(${gcmd} clerk simulate --request "${TEMPDIR}/simulateRequest.json" | jq '."would-succeed"') +if [[ $RES != $CONST_TRUE ]]; then + date '+app-simulate-test FAIL should pass with raw simulate request %Y%m%d_%H%M%S' + false +fi + ############################################### # WE ALSO TEST OVERSPEND IN TRANSACTION GROUP # ############################################### From 7cc6a6cc96f5958ed23f19f9aa99b353586498de Mon Sep 17 00:00:00 2001 From: Will Winder Date: Wed, 19 Apr 2023 10:00:31 -0400 Subject: [PATCH 051/125] docker: Updated user and data dir handling. (#5276) --- Dockerfile | 6 ++---- docker/README.md | 20 +++----------------- docker/files/run/run.sh | 10 +++++++++- 3 files changed, 14 insertions(+), 22 deletions(-) diff --git a/Dockerfile b/Dockerfile index aaa7b6c3bd..859e602d49 100644 --- a/Dockerfile +++ b/Dockerfile @@ -49,12 +49,10 @@ ENV PATH="/node/bin:${PATH}" ALGOD_PORT="8080" KMD_PORT="7833" ALGORAND_DATA="/a RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates curl && \ rm -rf /var/lib/apt/lists/* && \ mkdir -p "$ALGORAND_DATA" && \ - groupadd --system algorand && \ - useradd --no-log-init --create-home --system --gid algorand algorand && \ + groupadd --gid=999 --system algorand && \ + useradd --uid=999 --no-log-init --create-home --system --gid algorand algorand && \ chown -R algorand:algorand /algod -USER algorand - COPY --chown=algorand:algorand --from=builder "/dist/bin/" "/node/bin/" COPY --chown=algorand:algorand --from=builder "/dist/files/run/" "/node/run/" diff --git a/docker/README.md b/docker/README.md index 0ea18b0c55..0df4408a3e 100644 --- a/docker/README.md +++ b/docker/README.md @@ -80,7 +80,7 @@ The data directory located at `/algod/data`. Mounting a volume at that location ### Volume Permissions -The container executes in the context of the `algorand` user with it's own UID and GID which is handled differently depending on your operating system. Here are a few options for how to work with this environment: +The container executes in the context of the `algorand` user with UID=999 and GID=999 which is handled differently depending on your operating system or deployment platform. During startup the container temporarily runs as `root` in order to modify the permissions of `/algod/data`. It then changes to the `algorand` user. This can sometimes cause problems, for example if your deployment platform doesn't allow containers to run as the root user. #### Named Volume @@ -91,23 +91,9 @@ docker volume create algod-data docker run -it --rm -d -v algod-data:/algod/data algorand/algod ``` -#### Local Directory without SELinux +#### Use specific UID and GID -Explicitly set the UID and GID of the container: - -```bash -docker run -it --rm -d -v /srv/data:/algod/data -u $UID:$GID algorand/algod -``` - -#### Local Directory with SELinux - -Set the UID and GID of the container while add the `Z` option to the volume definition: - -```bash -docker run -it --rm -d -v /srv/data:/algod/data:Z -u $UID:$GID algorand/algod -``` - -> See the documentation on [configuring the selinux label](https://docs.docker.com/storage/bind-mounts/#configure-the-selinux-label). +On the host system, ensure the directory being mounted uses UID=999 and GID=999. If the directory already has these permissions you may override the default user with `-u 999:999`. ### Private Network diff --git a/docker/files/run/run.sh b/docker/files/run/run.sh index f9caa98818..9ea28be4bc 100755 --- a/docker/files/run/run.sh +++ b/docker/files/run/run.sh @@ -6,9 +6,17 @@ if [ "$DEBUG" = "1" ]; then set -x fi +# To allow mounting the data directory we need to change permissions +# to our algorand user. The script is initially run as the root user +# in order to change permissions, afterwards the script is re-launched +# as the algorand user. +if [ "$(id -u)" = '0' ]; then + chown -R algorand:algorand $ALGORAND_DATA + exec runuser -u algorand "$BASH_SOURCE" +fi + # Script to configure or resume a network. Based on environment settings the # node will be setup with a private network or connect to a public network. - #################### # Helper functions # #################### From 4e06e73ced7373f03c8c6c0acf3358a51b41ba17 Mon Sep 17 00:00:00 2001 From: abebeos <129396476+abebeos@users.noreply.github.com> Date: Wed, 19 Apr 2023 17:03:03 +0300 Subject: [PATCH 052/125] docs: remove outdated personal statements (#5307) --- docs/agreement_service.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/agreement_service.md b/docs/agreement_service.md index 28d760054c..7d343d8df4 100644 --- a/docs/agreement_service.md +++ b/docs/agreement_service.md @@ -4,10 +4,6 @@ The Algorand Agreement Service manages the consensus protocol and is composed of The agreement service learns what blocks the community has reached consensus on and writes them to the ledger. When your account is called upon to propose a block or serve on a committee, the agreement service handles that too. Other parts of the `agreement` package deal with related issues -- asking the network for blocks we're missing (and helping peers who are missing blocks), generally figuring out how to handle incoming messages from the network, etc. Most of the complicated logic lives in this package (along with most of the subtle concurrency issues). Reading the Algorand SOSP paper before diving into this package will be super helpful for understanding what's going on. -A few subpackages are maybe not used anymore but I'm not confident enough to delete them myself (Adam, 2018-02-28): - - `debug` contains a couple of python scripts for visualizing / extracting useful info from the logfiles produced by the agreement service. These might still be useful once we have an end-to-end test running again. (It also contains obsolete logging functions that have been replaced by `logging`) - - `util` as a top-level package contained a few utility functions that I'm pretty sure have migrated into the relevant subpackages - ## Dispatcher The dispatcher provides an interface between the Agreement Service components and the Algorand Network. Incoming messages are distributed between the Vote Manager and Proposal Manager. Outgoing Votes and Proposals generated by the Consensus State Machine are passed through the Dispatcher. From 66911af62f878b4dc0cfe5dc871a05646fdc7818 Mon Sep 17 00:00:00 2001 From: Eric Warehime Date: Wed, 19 Apr 2023 07:12:25 -0700 Subject: [PATCH 053/125] eval: Add block hooks to eval tracer (#5303) --- data/pools/transactionPool.go | 4 +-- data/pools/transactionPool_test.go | 8 ++--- data/transactions/logic/mocktracer/tracer.go | 29 +++++++++++++++ data/transactions/logic/tracer.go | 36 ++++++++++++++++++- ledger/eval/eval.go | 10 ++++++ ledger/eval/eval_test.go | 37 +++++++++++--------- ledger/eval_simple_test.go | 4 +-- ledger/evalbench_test.go | 6 ++-- ledger/fullblock_perf_test.go | 4 +-- ledger/ledger.go | 6 +++- ledger/ledger_perf_test.go | 2 +- ledger/simulation/simulator.go | 3 +- ledger/simulation/simulator_test.go | 3 ++ 13 files changed, 118 insertions(+), 34 deletions(-) diff --git a/data/pools/transactionPool.go b/data/pools/transactionPool.go index 1c567f2bae..a03baea4f0 100644 --- a/data/pools/transactionPool.go +++ b/data/pools/transactionPool.go @@ -700,7 +700,7 @@ func (pool *TransactionPool) recomputeBlockEvaluator(committedTxIds map[transact if hint < 0 || int(knownCommitted) < 0 { hint = 0 } - pool.pendingBlockEvaluator, err = pool.ledger.StartEvaluator(next.BlockHeader, hint, 0) + pool.pendingBlockEvaluator, err = pool.ledger.StartEvaluator(next.BlockHeader, hint, 0, nil) if err != nil { // The pendingBlockEvaluator is an interface, and in case of an evaluator error // we want to remove the interface itself rather then keeping an interface @@ -968,7 +968,7 @@ func (pool *TransactionPool) assembleEmptyBlock(round basics.Round) (assembled * return nil, err } next := bookkeeping.MakeBlock(prev) - blockEval, err := pool.ledger.StartEvaluator(next.BlockHeader, 0, 0) + blockEval, err := pool.ledger.StartEvaluator(next.BlockHeader, 0, 0, nil) if err != nil { var nonSeqBlockEval ledgercore.ErrNonSequentialBlockEval if errors.As(err, &nonSeqBlockEval) { diff --git a/data/pools/transactionPool_test.go b/data/pools/transactionPool_test.go index 2952fb6f01..b8adcff0a8 100644 --- a/data/pools/transactionPool_test.go +++ b/data/pools/transactionPool_test.go @@ -119,7 +119,7 @@ func newBlockEvaluator(t TestingT, l *ledger.Ledger) BlockEvaluator { require.NoError(t, err) next := bookkeeping.MakeBlock(prev) - eval, err := l.StartEvaluator(next.BlockHeader, 0, 0) + eval, err := l.StartEvaluator(next.BlockHeader, 0, 0, nil) require.NoError(t, err) return eval @@ -1453,7 +1453,7 @@ func TestStateProofLogging(t *testing.T) { require.NoError(t, err) b.BlockHeader.Branch = phdr.Hash() - _, err = mockLedger.StartEvaluator(b.BlockHeader, 0, 10000) + _, err = mockLedger.StartEvaluator(b.BlockHeader, 0, 10000, nil) require.NoError(t, err) // Simulate the blocks up to round 512 without any transactions @@ -1477,7 +1477,7 @@ func TestStateProofLogging(t *testing.T) { break } - _, err = mockLedger.StartEvaluator(b.BlockHeader, 0, 10000) + _, err = mockLedger.StartEvaluator(b.BlockHeader, 0, 10000, nil) require.NoError(t, err) } @@ -1520,7 +1520,7 @@ func TestStateProofLogging(t *testing.T) { require.NoError(t, err) // Add it to the transaction pool and assemble the block - eval, err := mockLedger.StartEvaluator(b.BlockHeader, 0, 1000000) + eval, err := mockLedger.StartEvaluator(b.BlockHeader, 0, 1000000, nil) require.NoError(t, err) err = eval.Transaction(stxn, transactions.ApplyData{}) diff --git a/data/transactions/logic/mocktracer/tracer.go b/data/transactions/logic/mocktracer/tracer.go index 2428d022e9..f73a379aea 100644 --- a/data/transactions/logic/mocktracer/tracer.go +++ b/data/transactions/logic/mocktracer/tracer.go @@ -17,6 +17,8 @@ package mocktracer 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/data/transactions/logic" "github.com/algorand/go-algorand/protocol" @@ -26,6 +28,8 @@ import ( type EventType string const ( + // BeforeBlockEvent represents the logic.EvalTracer.BeforeBlock event + BeforeBlockEvent EventType = "BeforeBlock" // BeforeTxnGroupEvent represents the logic.EvalTracer.BeforeTxnGroup event BeforeTxnGroupEvent EventType = "BeforeTxnGroup" // AfterTxnGroupEvent represents the logic.EvalTracer.AfterTxnGroup event @@ -42,6 +46,8 @@ const ( BeforeOpcodeEvent EventType = "BeforeOpcode" // AfterOpcodeEvent represents the logic.EvalTracer.AfterOpcode event AfterOpcodeEvent EventType = "AfterOpcode" + // AfterBlockEvent represents the logic.EvalTracer.AfterBlock event + AfterBlockEvent EventType = "AfterBlock" ) // Event represents a logic.EvalTracer event @@ -62,6 +68,14 @@ type Event struct { // only for AfterOpcode, AfterProgram, AfterTxn, and AfterTxnGroup HasError bool + + // only for BeforeBlock, AfterBlock + Round basics.Round +} + +// BeforeBlock creates a new Event with the type BeforeBlockEvent for a particular round +func BeforeBlock(round basics.Round) Event { + return Event{Type: BeforeBlockEvent, Round: round} } // BeforeTxnGroup creates a new Event with the type BeforeTxnGroupEvent @@ -104,6 +118,11 @@ func AfterOpcode(hasError bool) Event { return Event{Type: AfterOpcodeEvent, HasError: hasError} } +// AfterBlock creates a new Event with the type AfterBlockEvent +func AfterBlock(round basics.Round) Event { + return Event{Type: AfterBlockEvent, Round: round} +} + // OpcodeEvents returns a slice of events that represent calling `count` opcodes func OpcodeEvents(count int, endsWithError bool) []Event { events := make([]Event, 0, count*2) @@ -131,6 +150,11 @@ type Tracer struct { Events []Event } +// BeforeBlock mocks the logic.EvalTracer.BeforeBlock method +func (d *Tracer) BeforeBlock(hdr *bookkeeping.BlockHeader) { + d.Events = append(d.Events, BeforeBlock(hdr.Round)) +} + // BeforeTxnGroup mocks the logic.EvalTracer.BeforeTxnGroup method func (d *Tracer) BeforeTxnGroup(ep *logic.EvalParams) { d.Events = append(d.Events, BeforeTxnGroup(len(ep.TxnGroup))) @@ -170,3 +194,8 @@ func (d *Tracer) BeforeOpcode(cx *logic.EvalContext) { func (d *Tracer) AfterOpcode(cx *logic.EvalContext, evalError error) { d.Events = append(d.Events, AfterOpcode(evalError != nil)) } + +// AfterBlock mocks the logic.EvalTracer.BeforeBlock method +func (d *Tracer) AfterBlock(hdr *bookkeeping.BlockHeader) { + d.Events = append(d.Events, AfterBlock(hdr.Round)) +} diff --git a/data/transactions/logic/tracer.go b/data/transactions/logic/tracer.go index 89802b1c0e..5f2ab332fd 100644 --- a/data/transactions/logic/tracer.go +++ b/data/transactions/logic/tracer.go @@ -16,7 +16,10 @@ package logic -import "github.com/algorand/go-algorand/data/transactions" +import ( + "github.com/algorand/go-algorand/data/bookkeeping" + "github.com/algorand/go-algorand/data/transactions" +) // EvalTracer functions are called by eval function during AVM program execution, if a tracer // is provided. @@ -93,7 +96,28 @@ import "github.com/algorand/go-algorand/data/transactions" // │ │ // │ > AfterTxnGroup │ // └──────────────────────────────────────────────────────┘ +// +// Block Lifecycle Graph +// ┌──────────────────────────────────────────────────────┐ +// │ Block Evaluation │ +// │ ┌────────────────────────────────────────────────┐ │ +// │ │ > BeforeBlock │ │ +// │ │ │ │ +// │ │ ┌──────────────────────────────────────────┐ │ │ +// │ │ │ > Transaction/LogicSig Lifecycle │ │ │ +// │ │ ├──────────────────────────────────────────┤ │ │ +// │ │ │ ┌────────────────────────────────────┐ │ │ │ +// │ │ │ │ ... │ │ │ │ +// │ │ │ └────────────────────────────────────┘ │ │ │ +// │ │ └──────────────────────────────────────────┘ │ │ +// │ ├────────────────────────────────────────────────│ │ +// │ │ > AfterBlock │ │ +// │ └────────────────────────────────────────────────┘ │ +// └──────────────────────────────────────────────────────┘ type EvalTracer interface { + // BeforeBlock is called once at the beginning of block evaluation. It is passed the block header. + BeforeBlock(hdr *bookkeeping.BlockHeader) + // BeforeTxnGroup is called before a transaction group is executed. This includes both top-level // and inner transaction groups. The argument ep is the EvalParams object for the group; if the // group is an inner group, this is the EvalParams object for the inner group. @@ -130,11 +154,18 @@ type EvalTracer interface { // AfterOpcode is called after the op has been evaluated AfterOpcode(cx *EvalContext, evalError error) + + // AfterBlock is called after the block has finished evaluation. It will not be called in the event that an evalError + // stops evaluation of the block. + AfterBlock(hdr *bookkeeping.BlockHeader) } // NullEvalTracer implements EvalTracer, but all of its hook methods do nothing type NullEvalTracer struct{} +// BeforeBlock does nothing +func (n NullEvalTracer) BeforeBlock(hdr *bookkeeping.BlockHeader) {} + // BeforeTxnGroup does nothing func (n NullEvalTracer) BeforeTxnGroup(ep *EvalParams) {} @@ -159,3 +190,6 @@ func (n NullEvalTracer) BeforeOpcode(cx *EvalContext) {} // AfterOpcode does nothing func (n NullEvalTracer) AfterOpcode(cx *EvalContext, evalError error) {} + +// AfterBlock does nothing +func (n NullEvalTracer) AfterBlock(hdr *bookkeeping.BlockHeader) {} diff --git a/ledger/eval/eval.go b/ledger/eval/eval.go index 62533fe14f..a424df0a95 100644 --- a/ledger/eval/eval.go +++ b/ledger/eval/eval.go @@ -620,6 +620,7 @@ type EvaluatorOptions struct { Generate bool MaxTxnBytesPerBlock int ProtoParams *config.ConsensusParams + Tracer logic.EvalTracer } // StartEvaluator creates a BlockEvaluator, given a ledger and a block header @@ -678,6 +679,7 @@ func StartEvaluator(l LedgerForEvaluator, hdr bookkeeping.BlockHeader, evalOpts genesisHash: l.GenesisHash(), l: l, maxTxnBytesPerBlock: evalOpts.MaxTxnBytesPerBlock, + Tracer: evalOpts.Tracer, } // Preallocate space for the payset so that we don't have to @@ -786,6 +788,10 @@ func StartEvaluator(l LedgerForEvaluator, hdr bookkeeping.BlockHeader, evalOpts return nil, fmt.Errorf("overflowed subtracting rewards for block %v", hdr.Round) } + if eval.Tracer != nil { + eval.Tracer.BeforeBlock(&eval.block.BlockHeader) + } + return eval, nil } @@ -1334,6 +1340,10 @@ func (eval *BlockEvaluator) endOfBlock() error { return err } + if eval.Tracer != nil { + eval.Tracer.AfterBlock(&eval.block.BlockHeader) + } + return nil } diff --git a/ledger/eval/eval_test.go b/ledger/eval/eval_test.go index 57860c1e92..ab0e7fdcd8 100644 --- a/ledger/eval/eval_test.go +++ b/ledger/eval/eval_test.go @@ -71,7 +71,7 @@ func TestBlockEvaluatorFeeSink(t *testing.T) { genesisBlockHeader, err := l.BlockHdr(basics.Round(0)) require.NoError(t, err) newBlock := bookkeeping.MakeBlock(genesisBlockHeader) - eval, err := l.StartEvaluator(newBlock.BlockHeader, 0, 0) + eval, err := l.StartEvaluator(newBlock.BlockHeader, 0, 0, nil) require.NoError(t, err) require.Equal(t, eval.specials.FeeSink, testSinkAddr) } @@ -90,7 +90,7 @@ func testEvalAppGroup(t *testing.T, schema basics.StateSchema) (*BlockEvaluator, blkHeader, err := l.BlockHdr(basics.Round(0)) require.NoError(t, err) newBlock := bookkeeping.MakeBlock(blkHeader) - eval, err := l.StartEvaluator(newBlock.BlockHeader, 0, 0) + eval, err := l.StartEvaluator(newBlock.BlockHeader, 0, 0, nil) require.NoError(t, err) eval.validate = true eval.generate = false @@ -291,7 +291,8 @@ func TestTransactionGroupWithTracer(t *testing.T) { blkHeader, err := l.BlockHdr(basics.Round(0)) require.NoError(t, err) newBlock := bookkeeping.MakeBlock(blkHeader) - eval, err := l.StartEvaluator(newBlock.BlockHeader, 0, 0) + tracer := &mocktracer.Tracer{} + eval, err := l.StartEvaluator(newBlock.BlockHeader, 0, 0, tracer) require.NoError(t, err) eval.validate = true eval.generate = true @@ -368,8 +369,6 @@ int 1`, require.Len(t, eval.block.Payset, 0) - tracer := &mocktracer.Tracer{} - eval.Tracer = tracer err = eval.TransactionGroup(txgroup) switch testCase.firstTxnBehavior { case "approve": @@ -403,9 +402,11 @@ int 1`, }, } - var expectedEvents []mocktracer.Event + var expectedEvents = []mocktracer.Event{mocktracer.BeforeBlock(eval.block.Round())} if testCase.firstTxnBehavior == "approve" { - expectedEvents = mocktracer.FlattenEvents([][]mocktracer.Event{ + err = eval.endOfBlock() + require.NoError(t, err) + expectedEvents = append(expectedEvents, mocktracer.FlattenEvents([][]mocktracer.Event{ { mocktracer.BeforeTxnGroup(3), mocktracer.BeforeTxn(protocol.ApplicationCallTx), // start basicAppCallTxn @@ -422,12 +423,15 @@ int 1`, { mocktracer.AfterTxnGroup(3, scenario.Outcome != mocktracer.ApprovalOutcome), }, - }) + { + mocktracer.AfterBlock(eval.block.Round()), + }, + })...) } else { hasError := testCase.firstTxnBehavior == "error" // EvalDeltas are removed from failed app call transactions expectedBasicAppCallAD.EvalDelta = transactions.EvalDelta{} - expectedEvents = mocktracer.FlattenEvents([][]mocktracer.Event{ + expectedEvents = append(expectedEvents, mocktracer.FlattenEvents([][]mocktracer.Event{ { mocktracer.BeforeTxnGroup(3), mocktracer.BeforeTxn(protocol.ApplicationCallTx), // start basicAppCallTxn @@ -439,7 +443,7 @@ int 1`, mocktracer.AfterTxn(protocol.ApplicationCallTx, expectedBasicAppCallAD, true), // end basicAppCallTxn mocktracer.AfterTxnGroup(3, true), }, - }) + })...) } require.Equal(t, expectedEvents, mocktracer.StripInnerTxnGroupIDsFromEvents(tracer.Events)) }) @@ -501,7 +505,7 @@ func testnetFixupExecution(t *testing.T, headerRound basics.Round, poolBonus uin l.genesisHash = genesisInitState.GenesisHash newBlock := bookkeeping.MakeBlock(genesisInitState.Block.BlockHeader) - eval, err := l.StartEvaluator(newBlock.BlockHeader, 0, 0) + eval, err := l.StartEvaluator(newBlock.BlockHeader, 0, 0, nil) require.NoError(t, err) // won't work before funding bank @@ -648,13 +652,14 @@ func (ledger *evalTestLedger) Validate(ctx context.Context, blk bookkeeping.Bloc // of the block that the caller is planning to evaluate. If the length of the // payset being evaluated is known in advance, a paysetHint >= 0 can be // passed, avoiding unnecessary payset slice growth. -func (ledger *evalTestLedger) StartEvaluator(hdr bookkeeping.BlockHeader, paysetHint, maxTxnBytesPerBlock int) (*BlockEvaluator, error) { +func (ledger *evalTestLedger) StartEvaluator(hdr bookkeeping.BlockHeader, paysetHint, maxTxnBytesPerBlock int, tracer logic.EvalTracer) (*BlockEvaluator, error) { return StartEvaluator(ledger, hdr, EvaluatorOptions{ PaysetHint: paysetHint, Validate: true, Generate: true, MaxTxnBytesPerBlock: maxTxnBytesPerBlock, + Tracer: tracer, }) } @@ -834,7 +839,7 @@ func (ledger *evalTestLedger) nextBlock(t testing.TB) *BlockEvaluator { require.NoError(t, err) nextHdr := bookkeeping.MakeBlock(hdr).BlockHeader - eval, err := ledger.StartEvaluator(nextHdr, 0, 0) + eval, err := ledger.StartEvaluator(nextHdr, 0, 0, nil) require.NoError(t, err) return eval } @@ -1017,7 +1022,7 @@ func TestEvalFunctionForExpiredAccounts(t *testing.T) { newBlock := bookkeeping.MakeBlock(l.blocks[0].BlockHeader) - blkEval, err := l.StartEvaluator(newBlock.BlockHeader, 0, 0) + blkEval, err := l.StartEvaluator(newBlock.BlockHeader, 0, 0, nil) require.NoError(t, err) // Advance the evaluator a couple rounds... @@ -1158,7 +1163,7 @@ func TestExpiredAccountGenerationWithDiskFailure(t *testing.T) { newBlock := bookkeeping.MakeBlock(l.blocks[0].BlockHeader) - eval, err := l.StartEvaluator(newBlock.BlockHeader, 0, 0) + eval, err := l.StartEvaluator(newBlock.BlockHeader, 0, 0, nil) require.NoError(t, err) // Advance the evaluator a couple rounds... @@ -1256,7 +1261,7 @@ func TestExpiredAccountGeneration(t *testing.T) { newBlock := bookkeeping.MakeBlock(l.blocks[0].BlockHeader) - eval, err := l.StartEvaluator(newBlock.BlockHeader, 0, 0) + eval, err := l.StartEvaluator(newBlock.BlockHeader, 0, 0, nil) require.NoError(t, err) // Advance the evaluator a couple rounds... diff --git a/ledger/eval_simple_test.go b/ledger/eval_simple_test.go index c817e0c143..243e92912d 100644 --- a/ledger/eval_simple_test.go +++ b/ledger/eval_simple_test.go @@ -52,7 +52,7 @@ func TestBlockEvaluator(t *testing.T) { genesisBlockHeader, err := l.BlockHdr(basics.Round(0)) require.NoError(t, err) newBlock := bookkeeping.MakeBlock(genesisBlockHeader) - eval, err := l.StartEvaluator(newBlock.BlockHeader, 0, 0) + eval, err := l.StartEvaluator(newBlock.BlockHeader, 0, 0, nil) require.NoError(t, err) genHash := l.GenesisHash() @@ -437,7 +437,7 @@ func TestRekeying(t *testing.T) { genesisHdr, err := l.BlockHdr(basics.Round(0)) require.NoError(t, err) newBlock := bookkeeping.MakeBlock(genesisHdr) - eval, err := l.StartEvaluator(newBlock.BlockHeader, 0, 0) + eval, err := l.StartEvaluator(newBlock.BlockHeader, 0, 0, nil) require.NoError(t, err) for _, stxn := range stxns { diff --git a/ledger/evalbench_test.go b/ledger/evalbench_test.go index 407c6f67f1..355e7253bd 100644 --- a/ledger/evalbench_test.go +++ b/ledger/evalbench_test.go @@ -505,7 +505,7 @@ func benchmarkBlockEvaluator(b *testing.B, inMem bool, withCrypto bool, proto pr func benchmarkPreparePaymentTransactionsTesting(b *testing.B, numTxns int, txnSource BenchTxnGenerator, genesisInitState ledgercore.InitState, addrs []basics.Address, keys []*crypto.SignatureSecrets, l, l2 *Ledger) *ledgercore.ValidatedBlock { newBlock := bookkeeping.MakeBlock(genesisInitState.Block.BlockHeader) - bev, err := l.StartEvaluator(newBlock.BlockHeader, 0, 0) + bev, err := l.StartEvaluator(newBlock.BlockHeader, 0, 0, nil) require.NoError(b, err) genHash := l.GenesisHash() @@ -529,7 +529,7 @@ func benchmarkPreparePaymentTransactionsTesting(b *testing.B, numTxns int, txnSo require.NoError(b, err) } newBlock = bookkeeping.MakeBlock(validatedBlock.Block().BlockHeader) - bev, err = l.StartEvaluator(newBlock.BlockHeader, 0, 0) + bev, err = l.StartEvaluator(newBlock.BlockHeader, 0, 0, nil) require.NoError(b, err) numBlocks++ } @@ -550,7 +550,7 @@ func benchmarkPreparePaymentTransactionsTesting(b *testing.B, numTxns int, txnSo wg.Wait() newBlock = bookkeeping.MakeBlock(validatedBlock.Block().BlockHeader) - bev, err = l.StartEvaluator(newBlock.BlockHeader, 0, 0) + bev, err = l.StartEvaluator(newBlock.BlockHeader, 0, 0, nil) require.NoError(b, err) } diff --git a/ledger/fullblock_perf_test.go b/ledger/fullblock_perf_test.go index 0de160970a..080cccb687 100644 --- a/ledger/fullblock_perf_test.go +++ b/ledger/fullblock_perf_test.go @@ -119,7 +119,7 @@ func setupEnv(b *testing.B, numAccts int) (bc *benchConfig) { require.NoError(b, err) newBlk := bookkeeping.MakeBlock(blk.BlockHeader) - blockEvaluator, err := l0.StartEvaluator(newBlk.BlockHeader, 5000, 0) + blockEvaluator, err := l0.StartEvaluator(newBlk.BlockHeader, 5000, 0, nil) require.NoError(b, err) bc = &benchConfig{ @@ -326,7 +326,7 @@ func addBlock(bc *benchConfig) { prev, err := bc.l0.BlockHdr(basics.Round(last)) require.NoError(bc.b, err) newBlk := bookkeeping.MakeBlock(prev) - bc.eval, err = bc.l0.StartEvaluator(newBlk.BlockHeader, 5000, 0) + bc.eval, err = bc.l0.StartEvaluator(newBlk.BlockHeader, 5000, 0, nil) bc.round++ require.NoError(bc.b, err) } diff --git a/ledger/ledger.go b/ledger/ledger.go index 769edbe864..35d8b2ee34 100644 --- a/ledger/ledger.go +++ b/ledger/ledger.go @@ -31,6 +31,7 @@ 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/data/transactions/logic" "github.com/algorand/go-algorand/data/transactions/verify" "github.com/algorand/go-algorand/ledger/apply" "github.com/algorand/go-algorand/ledger/eval" @@ -833,13 +834,16 @@ func (l *Ledger) VerifiedTransactionCache() verify.VerifiedTransactionCache { // provides a cap on the size of a single generated block size, when a non-zero value is passed. // If a value of zero or less is passed to maxTxnBytesPerBlock, the consensus MaxTxnBytesPerBlock would // be used instead. -func (l *Ledger) StartEvaluator(hdr bookkeeping.BlockHeader, paysetHint, maxTxnBytesPerBlock int) (*eval.BlockEvaluator, error) { +// The tracer argument is a logic.EvalTracer which will be attached to the evaluator and have its hooked invoked during +// the eval process for each block. A nil tracer will skip tracer invocation entirely. +func (l *Ledger) StartEvaluator(hdr bookkeeping.BlockHeader, paysetHint, maxTxnBytesPerBlock int, tracer logic.EvalTracer) (*eval.BlockEvaluator, error) { return eval.StartEvaluator(l, hdr, eval.EvaluatorOptions{ PaysetHint: paysetHint, Generate: true, Validate: true, MaxTxnBytesPerBlock: maxTxnBytesPerBlock, + Tracer: tracer, }) } diff --git a/ledger/ledger_perf_test.go b/ledger/ledger_perf_test.go index b2f3b2437f..123994afcc 100644 --- a/ledger/ledger_perf_test.go +++ b/ledger/ledger_perf_test.go @@ -205,7 +205,7 @@ func benchmarkFullBlocks(params testParams, b *testing.B) { prev, err := l0.BlockHdr(basics.Round(i)) require.NoError(b, err) newBlk := bookkeeping.MakeBlock(prev) - eval, err := l0.StartEvaluator(newBlk.BlockHeader, 5000, 0) + eval, err := l0.StartEvaluator(newBlk.BlockHeader, 5000, 0, nil) require.NoError(b, err) // build a payset diff --git a/ledger/simulation/simulator.go b/ledger/simulation/simulator.go index e2b8ffd115..585847c374 100644 --- a/ledger/simulation/simulator.go +++ b/ledger/simulation/simulator.go @@ -149,11 +149,10 @@ func (s Simulator) check(hdr bookkeeping.BlockHeader, txgroup []transactions.Sig func (s Simulator) evaluate(hdr bookkeeping.BlockHeader, stxns []transactions.SignedTxn, tracer logic.EvalTracer) (*ledgercore.ValidatedBlock, error) { // s.ledger has 'StartEvaluator' because *data.Ledger is embedded in the simulatorLedger // and data.Ledger embeds *ledger.Ledger - eval, err := s.ledger.StartEvaluator(hdr, len(stxns), 0) + eval, err := s.ledger.StartEvaluator(hdr, len(stxns), 0, tracer) if err != nil { return nil, err } - eval.Tracer = tracer group := transactions.WrapSignedTxnsWithAD(stxns) diff --git a/ledger/simulation/simulator_test.go b/ledger/simulation/simulator_test.go index 76ae0ef1b2..25d7e18d12 100644 --- a/ledger/simulation/simulator_test.go +++ b/ledger/simulation/simulator_test.go @@ -152,6 +152,7 @@ int 1`, mocktracer.AfterOpcode(false), mocktracer.AfterProgram(logic.ModeSig, false), // Txn evaluation + mocktracer.BeforeBlock(block.Block().Round()), mocktracer.BeforeTxnGroup(2), mocktracer.BeforeTxn(protocol.PaymentTx), mocktracer.AfterTxn(protocol.PaymentTx, payset[0].ApplyData, false), @@ -162,6 +163,8 @@ int 1`, mocktracer.AfterProgram(logic.ModeApp, false), mocktracer.AfterTxn(protocol.ApplicationCallTx, payset[1].ApplyData, false), mocktracer.AfterTxnGroup(2, false), + //Block evaluation + mocktracer.AfterBlock(block.Block().Round()), } require.Equal(t, expectedEvents, mockTracer.Events) } From cae1b5b4ab58a0463b642cd63b844458de01697c Mon Sep 17 00:00:00 2001 From: abebeos <129396476+abebeos@users.noreply.github.com> Date: Thu, 20 Apr 2023 15:16:10 +0300 Subject: [PATCH 054/125] docs: update agreement README with committableEvent (#5317) --- agreement/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agreement/README.md b/agreement/README.md index 6c627a6f9b..04ca71451e 100644 --- a/agreement/README.md +++ b/agreement/README.md @@ -356,5 +356,5 @@ a given period. The staging slot for a given period is important because its state is the precursor to cert and next votes. Once both a soft threshold for a value and the `Block` corresponding to this value has been observed by -the node, a `proposalCommittableEvent` is emitted, which indicates +the node, a proposal `committableEvent` is emitted, which indicates that the node may cert or next-vote for the proposal. From bfc98637024e0422a122031d41a908f929af6e3c Mon Sep 17 00:00:00 2001 From: algochoi <86622919+algochoi@users.noreply.github.com> Date: Thu, 20 Apr 2023 08:20:32 -0400 Subject: [PATCH 055/125] algod: Allow timestamp offsets in dev mode (#5296) Co-authored-by: shiqizng <80276844+shiqizng@users.noreply.github.com> Co-authored-by: Eric Warehime --- daemon/algod/api/algod.oas2.json | 98 ++++ daemon/algod/api/algod.oas3.yml | 128 ++++++ daemon/algod/api/server/v2/errors.go | 2 + .../api/server/v2/generated/data/routes.go | 333 +++++++------- .../v2/generated/experimental/routes.go | 259 +++++------ .../api/server/v2/generated/model/types.go | 6 + .../nonparticipating/private/routes.go | 337 +++++++------- .../nonparticipating/public/routes.go | 434 ++++++++++-------- .../generated/participating/private/routes.go | 341 +++++++------- .../generated/participating/public/routes.go | 359 +++++++-------- daemon/algod/api/server/v2/handlers.go | 32 ++ .../server/v2/test/handlers_resources_test.go | 2 +- .../algod/api/server/v2/test/handlers_test.go | 90 +++- daemon/algod/api/server/v2/test/helpers.go | 38 +- node/follower_node.go | 12 + node/node.go | 47 +- 16 files changed, 1481 insertions(+), 1037 deletions(-) diff --git a/daemon/algod/api/algod.oas2.json b/daemon/algod/api/algod.oas2.json index 1cbf67ae31..cab2591d32 100644 --- a/daemon/algod/api/algod.oas2.json +++ b/daemon/algod/api/algod.oas2.json @@ -2429,6 +2429,89 @@ } } } + }, + "/v2/devmode/blocks/offset": { + "get": { + "description": "Gets the current timestamp offset.", + "tags": [ + "public", + "nonparticipating" + ], + "produces": [ + "application/json" + ], + "schemes": [ + "http" + ], + "summary": "Returns the timestamp offset. Timestamp offsets can only be set in dev mode.", + "operationId": "GetBlockTimeStampOffset", + "responses": { + "200": { + "description": "OK", + "$ref": "#/responses/GetBlockTimeStampOffsetResponse" + }, + "400": { + "description": "TimeStamp offset not set.", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + }, + "default": { + "description": "Unknown Error" + } + } + } + }, + "/v2/devmode/blocks/offset/{offset}": { + "post": { + "description": "Sets the timestamp offset (seconds) for blocks in dev mode. Providing an offset of 0 will unset this value and try to use the real clock for the timestamp.", + "tags": [ + "public", + "nonparticipating" + ], + "schemes": [ + "http" + ], + "summary": "Given a timestamp offset in seconds, adds the offset to every subsequent block header's timestamp.", + "operationId": "SetBlockTimeStampOffset", + "parameters": [ + { + "type": "integer", + "description": "The timestamp offset for blocks in dev mode.", + "name": "offset", + "in": "path", + "required": true, + "minimum": 0 + } + ], + "responses": { + "200": { + "description": "OK", + "type": "object" + }, + "400": { + "description": "Cannot set timestamp offset to a negative integer.", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + }, + "401": { + "description": "Invalid API Token", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + }, + "500": { + "description": "Internal Error", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + }, + "default": { + "description": "Unknown Error" + } + } + } } }, "definitions": { @@ -3730,6 +3813,21 @@ } }, "responses": { + "GetBlockTimeStampOffsetResponse": { + "description": "Response containing the timestamp offset in seconds", + "schema": { + "type": "object", + "required": [ + "offset" + ], + "properties": { + "offset": { + "description": "Timestamp offset in seconds.", + "type": "integer" + } + } + } + }, "GetSyncRoundResponse": { "description": "Response containing the ledger's minimum sync round", "schema": { diff --git a/daemon/algod/api/algod.oas3.yml b/daemon/algod/api/algod.oas3.yml index bf1cbf743a..7243dbedab 100644 --- a/daemon/algod/api/algod.oas3.yml +++ b/daemon/algod/api/algod.oas3.yml @@ -497,6 +497,25 @@ }, "description": "DryrunResponse contains per-txn debug information from a dryrun." }, + "GetBlockTimeStampOffsetResponse": { + "content": { + "application/json": { + "schema": { + "properties": { + "offset": { + "description": "Timestamp offset in seconds.", + "type": "integer" + } + }, + "required": [ + "offset" + ], + "type": "object" + } + } + }, + "description": "Response containing the timestamp offset in seconds" + }, "GetSyncRoundResponse": { "content": { "application/json": { @@ -3886,6 +3905,115 @@ ] } }, + "/v2/devmode/blocks/offset": { + "get": { + "description": "Gets the current timestamp offset.", + "operationId": "GetBlockTimeStampOffset", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "offset": { + "description": "Timestamp offset in seconds.", + "type": "integer" + } + }, + "required": [ + "offset" + ], + "type": "object" + } + } + }, + "description": "Response containing the timestamp offset in seconds" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "TimeStamp offset not set." + }, + "default": { + "content": {}, + "description": "Unknown Error" + } + }, + "summary": "Returns the timestamp offset. Timestamp offsets can only be set in dev mode.", + "tags": [ + "public", + "nonparticipating" + ] + } + }, + "/v2/devmode/blocks/offset/{offset}": { + "post": { + "description": "Sets the timestamp offset (seconds) for blocks in dev mode. Providing an offset of 0 will unset this value and try to use the real clock for the timestamp.", + "operationId": "SetBlockTimeStampOffset", + "parameters": [ + { + "description": "The timestamp offset for blocks in dev mode.", + "in": "path", + "name": "offset", + "required": true, + "schema": { + "minimum": 0, + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": {}, + "description": "OK" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Cannot set timestamp offset to a negative integer." + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Invalid API Token" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Internal Error" + }, + "default": { + "content": {}, + "description": "Unknown Error" + } + }, + "summary": "Given a timestamp offset in seconds, adds the offset to every subsequent block header's timestamp.", + "tags": [ + "public", + "nonparticipating" + ] + } + }, "/v2/experimental": { "get": { "operationId": "ExperimentalCheck", diff --git a/daemon/algod/api/server/v2/errors.go b/daemon/algod/api/server/v2/errors.go index f72fe212ef..e076d6c8a5 100644 --- a/daemon/algod/api/server/v2/errors.go +++ b/daemon/algod/api/server/v2/errors.go @@ -27,6 +27,8 @@ var ( errFailedRetrievingStateDelta = "failed retrieving State Delta: %v" errFailedRetrievingNodeStatus = "failed retrieving node status" errFailedRetrievingLatestBlockHeaderStatus = "failed retrieving latests block header" + errFailedRetrievingTimeStampOffset = "failed retrieving timestamp offset from node: %v" + errFailedSettingTimeStampOffset = "failed to set timestamp offset on the node: %v" errFailedRetrievingSyncRound = "failed retrieving sync round from ledger" errFailedSettingSyncRound = "failed to set sync round on the ledger" errFailedParsingFormatOption = "failed to parse the format option" diff --git a/daemon/algod/api/server/v2/generated/data/routes.go b/daemon/algod/api/server/v2/generated/data/routes.go index b62c0fa89c..d10be59b64 100644 --- a/daemon/algod/api/server/v2/generated/data/routes.go +++ b/daemon/algod/api/server/v2/generated/data/routes.go @@ -145,177 +145,178 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "4er/BwAA//8yQRH0B8oAAA==", + "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", + "ZBvMkfHh6v8HAAD//8J2O3RgywAA", } // 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 76c05508ac..3183c03502 100644 --- a/daemon/algod/api/server/v2/generated/experimental/routes.go +++ b/daemon/algod/api/server/v2/generated/experimental/routes.go @@ -109,135 +109,136 @@ var swaggerSpec = []string{ "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", - "T/8vAAD//yOoPrWwuAAA", + "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", + "ZDeYzPHh0/8LAAD//+8NMlAJugAA", } // 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 804e649c55..beee2e5a04 100644 --- a/daemon/algod/api/server/v2/generated/model/types.go +++ b/daemon/algod/api/server/v2/generated/model/types.go @@ -886,6 +886,12 @@ type DryrunResponse struct { Txns []DryrunTxnResult `json:"txns"` } +// GetBlockTimeStampOffsetResponse defines model for GetBlockTimeStampOffsetResponse. +type GetBlockTimeStampOffsetResponse struct { + // Offset Timestamp offset in seconds. + Offset uint64 `json:"offset"` +} + // GetSyncRoundResponse defines model for GetSyncRoundResponse. type GetSyncRoundResponse struct { // Round The minimum sync round for the ledger. 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 0fcdcb6950..8d0203f0f8 100644 --- a/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go +++ b/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go @@ -130,174 +130,175 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go b/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go index 76104325fa..b15beff954 100644 --- a/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go +++ b/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go @@ -54,6 +54,12 @@ type ServerInterface interface { // Get a proof for a transaction in a block. // (GET /v2/blocks/{round}/transactions/{txid}/proof) GetTransactionProof(ctx echo.Context, round uint64, txid string, params GetTransactionProofParams) error + // Returns the timestamp offset. Timestamp offsets can only be set in dev mode. + // (GET /v2/devmode/blocks/offset) + GetBlockTimeStampOffset(ctx echo.Context) error + // Given a timestamp offset in seconds, adds the offset to every subsequent block header's timestamp. + // (POST /v2/devmode/blocks/offset/{offset}) + SetBlockTimeStampOffset(ctx echo.Context, offset uint64) error // Get the current supply reported by the ledger. // (GET /v2/ledger/supply) GetSupply(ctx echo.Context) error @@ -387,6 +393,35 @@ func (w *ServerInterfaceWrapper) GetTransactionProof(ctx echo.Context) error { return err } +// GetBlockTimeStampOffset converts echo context to params. +func (w *ServerInterfaceWrapper) GetBlockTimeStampOffset(ctx echo.Context) error { + var err error + + ctx.Set(Api_keyScopes, []string{""}) + + // Invoke the callback with all the unmarshalled arguments + err = w.Handler.GetBlockTimeStampOffset(ctx) + return err +} + +// SetBlockTimeStampOffset converts echo context to params. +func (w *ServerInterfaceWrapper) SetBlockTimeStampOffset(ctx echo.Context) error { + var err error + // ------------- Path parameter "offset" ------------- + var offset uint64 + + err = runtime.BindStyledParameterWithLocation("simple", false, "offset", runtime.ParamLocationPath, ctx.Param("offset"), &offset) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter offset: %s", err)) + } + + ctx.Set(Api_keyScopes, []string{""}) + + // Invoke the callback with all the unmarshalled arguments + err = w.Handler.SetBlockTimeStampOffset(ctx, offset) + return err +} + // GetSupply converts echo context to params. func (w *ServerInterfaceWrapper) GetSupply(ctx echo.Context) error { var err error @@ -557,6 +592,8 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL router.GET(baseURL+"/v2/blocks/:round/hash", wrapper.GetBlockHash, m...) router.GET(baseURL+"/v2/blocks/:round/lightheader/proof", wrapper.GetLightBlockHeaderProof, m...) router.GET(baseURL+"/v2/blocks/:round/transactions/:txid/proof", wrapper.GetTransactionProof, m...) + router.GET(baseURL+"/v2/devmode/blocks/offset", wrapper.GetBlockTimeStampOffset, m...) + router.POST(baseURL+"/v2/devmode/blocks/offset/:offset", wrapper.SetBlockTimeStampOffset, m...) router.GET(baseURL+"/v2/ledger/supply", wrapper.GetSupply, m...) router.GET(baseURL+"/v2/stateproofs/:round", wrapper.GetStateProof, m...) router.GET(baseURL+"/v2/status", wrapper.GetStatus, m...) @@ -607,202 +644,207 @@ var swaggerSpec = []string{ "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/generated/participating/private/routes.go b/daemon/algod/api/server/v2/generated/participating/private/routes.go index 18916f47f7..d9527d3e5b 100644 --- a/daemon/algod/api/server/v2/generated/participating/private/routes.go +++ b/daemon/algod/api/server/v2/generated/participating/private/routes.go @@ -158,176 +158,177 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "H4sIAAAAAAAC/+y9e3PcNrIo/lVQc06VH7+hJL+ya1Wlzk+2k6xuHK/LUrL3HNs3wZA9M1iRABcApZn4", - "6rvfQgMgQRLkcCTF3lT5L1tDPBqNRqPf+DRLRVEKDlyr2fGnWUklLUCDxL9omoqK64Rl5q8MVCpZqZng", - "s2P/jSgtGV/N5jNmfi2pXs/mM04LaNqY/vOZhH9VTEI2O9aygvlMpWsoqBlYb0vTuh5pk6xE4oY4sUOc", - "vppdj3ygWSZBqT6Uf+f5ljCe5lUGREvKFU3NJ0WumF4TvWaKuM6EcSI4ELEket1qTJYM8kwd+EX+qwK5", - "DVbpJh9e0nUDYiJFDn04X4piwTh4qKAGqt4QogXJYImN1lQTM4OB1TfUgiigMl2TpZA7QLVAhPACr4rZ", - "8fuZAp6BxN1KgV3if5cS4HdINJUr0LOP89jilhpkolkRWdqpw74EVeVaEWyLa1yxS+DE9DogP1VKkwUQ", - "ysm771+SJ0+ePDcLKajWkDkiG1xVM3u4Jtt9djzLqAb/uU9rNF8JSXmW1O3fff8S5z9zC5zaiioF8cNy", - "Yr6Q01dDC/AdIyTEuIYV7kOL+k2PyKFofl7AUkiYuCe28Z1uSjj/F92VlOp0XQrGdWRfCH4l9nOUhwXd", - "x3hYDUCrfWkwJc2g74+S5x8/PZo/Orr+j/cnyf+4P589uZ64/Jf1uDswEG2YVlICT7fJSgLF07KmvI+P", - "d44e1FpUeUbW9BI3nxbI6l1fYvpa1nlJ88rQCUulOMlXQhHqyCiDJa1yTfzEpOK5YVNmNEfthClSSnHJ", - "MsjmhvterVm6JilVdghsR65YnhsarBRkQ7QWX93IYboOUWLguhE+cEH/vsho1rUDE7BBbpCkuVCQaLHj", - "evI3DuUZCS+U5q5S+11W5HwNBCc3H+xli7jjhqbzfEs07mtGqCKU+KtpTtiSbEVFrnBzcnaB/d1qDNYK", - "YpCGm9O6R83hHUJfDxkR5C2EyIFyRJ4/d32U8SVbVRIUuVqDXrs7T4IqBVdAxOKfkGqz7f/r7O9viJDk", - "J1CKruAtTS8I8FRkkB2Q0yXhQgek4WgJcWh6Dq3DwRW75P+phKGJQq1Kml7Eb/ScFSyyqp/ohhVVQXhV", - "LECaLfVXiBZEgq4kHwLIjriDFAu66U96Liue4v4307ZkOUNtTJU53SLCCrr59mjuwFGE5jkpgWeMr4je", - "8EE5zsy9G7xEiopnE8QcbfY0uFhVCSlbMshIPcoIJG6aXfAwvh88jfAVgOMHGQSnnmUHOBw2EZoxp9t8", - "ISVdQUAyB+Rnx9zwqxYXwGtCJ4stfiolXDJRqbrTAIw49bgEzoWGpJSwZBEaO3PoMAzGtnEcuHAyUCq4", - "poxDZpgzAi00WGY1CFMw4bi+07/FF1TBN0+H7vjm68TdX4ruro/u+KTdxkaJPZKRq9N8dQc2Llm1+k/Q", - "D8O5FVsl9ufeRrLVubltlizHm+ifZv88GiqFTKCFCH83KbbiVFcSjj/wh+YvkpAzTXlGZWZ+KexPP1W5", - "ZmdsZX7K7U+vxYqlZ2w1gMwa1qjChd0K+48ZL86O9SaqV7wW4qIqwwWlLcV1sSWnr4Y22Y65L2Ge1Npu", - "qHicb7wysm8Pvak3cgDIQdyV1DS8gK0EAy1Nl/jPZon0RJfyd/NPWeamty6XMdQaOnZXMpoPnFnhpCxz", - "llKDxHfus/lqmABYRYI2LQ7xQj3+FIBYSlGC1MwOSssyyUVK80RpqnGk/5SwnB3P/uOwsb8c2u7qMJj8", - "tel1hp2MyGrFoISW5R5jvDWijxphFoZB4ydkE5btodDEuN1EQ0rMsOAcLinXB43K0uIH9QF+72Zq8G2l", - "HYvvjgo2iHBiGy5AWQnYNrynSIB6gmgliFYUSFe5WNQ/3D8pywaD+P2kLC0+UHoEhoIZbJjS6gEunzYn", - "KZzn9NUB+SEcG0VxwfOtuRysqGHuhqW7tdwtVtuW3BqaEe8pgtsp5IHZGo8GI+bfBcWhWrEWuZF6dtKK", - "afw31zYkM/P7pM5/DhILcTtMXKhoOcxZHQd/CZSb+x3K6ROOM/cckJNu35uRjRklTjA3opXR/bTjjuCx", - "RuGVpKUF0H2xdynjqKTZRhbWW3LTiYwuCnNwhgNaQ6hufNZ2nocoJEgKHRhe5CK9+BtV6zs48ws/Vv/4", - "4TRkDTQDSdZUrQ9mMSkjPF7NaFOOmGmICj5ZBFMd1Eu8q+XtWFpGNQ2W5uCNiyUW9dgPmR7IiO7yd/wP", - "zYn5bM62Yf122ANyjgxM2ePsnAyZ0fatgmBnMg3QCiFIYRV8YrTuvaB82Uwe36dJe/SdtSm4HXKLwB0S", - "mzs/Bi/EJgbDC7HpHQGxAXUX9GHGQTFSQ6EmwPfKQSZw/x36qJR020cyjj0FyWaBRnRVeBp4eOObWRrj", - "7MlCyJtxnw5b4aQxORNqRg2Y77yDJGxalYkjxYjZyjboDNR4+caZRnf4GMZaWDjT9A/AgjKj3gUW2gPd", - "NRZEUbIc7oD011Gmv6AKnjwmZ387efbo8a+Pn31jSLKUYiVpQRZbDYrcd7oZUXqbw4P+ylA7qnIdH/2b", - "p95Q2R43No4SlUyhoGV/KGsAtSKQbUZMuz7W2mjGVdcATjmc52A4uUU7sbZ9A9orpoyEVSzuZDOGEJY1", - "s2TEQZLBTmLad3nNNNtwiXIrq7tQZUFKISP2NTxiWqQiTy5BKiYi3pS3rgVxLbx4W3Z/t9CSK6qImRtN", - "vxVHgSJCWXrDp/N9O/T5hje4GeX8dr2R1bl5p+xLG/nekqhICTLRG04yWFSrlia0lKIglGTYEe/oH0Cf", - "bXmKVrW7INJhNa1gHE38asvTQGczG5VDtmptwu11sy5WvH3OTnVPRcAx6HiNn1GtfwW5pncuv3QniMH+", - "0m+kBZZkpiFqwa/Zaq0DAfOtFGJ59zDGZokBih+seJ6bPn0h/Y3IwCy2UndwGTeDNbRu9jSkcLoQlSaU", - "cJEBWlQqFb+mBzz36DJET6cOb369thL3AgwhpbQyq61Kgn68HudoOiY0tdSbIGrUgBejdj/ZVnY66xXO", - "JdDMaPXAiVg4V4FzYuAiKTohtb/onJAQOUstuEopUlAKssSZKHaC5ttZJqJH8ISAI8D1LEQJsqTy1sBe", - "XO6E8wK2CbrMFbn/4y/qwReAVwtN8x2IxTYx9NYKn/MH9aGeNv0YwXUnD8mOSiCe5xrt0jCIHDQMoXAv", - "nAzuXxei3i7eHi2XINEz84dSvJ/kdgRUg/oH0/ttoa3KgUAwp+icswLtdpxyoSAVPFPRwXKqdLKLLZtG", - "LW3MrCDghDFOjAMPCCWvqdLWm8h4hkYQe53gPFZAMVMMAzwokJqRf/GyaH/s1NyDXFWqFkxVVZZCashi", - "a+CwGZnrDWzqucQyGLuWfrUglYJdIw9hKRjfIcuuxCKI6tro7tzt/cWhadrc89soKltANIgYA+TMtwqw", - "GwbDDADCVINoSzhMdSinjsCZz5QWZWm4hU4qXvcbQtOZbX2if27a9omL6ubezgQojMFx7R3kVxazNgxq", - "TY0KjSOTgl4Y2QMVYuv27MNsDmOiGE8hGaN8cyzPTKvwCOw8pFW5kjSDJIOcbvuD/mw/E/t5bADc8Ubx", - "ERoSG88S3/SGkn34wMjQAsdTMeGR4BeSmiNoNI+GQFzvHSNngGPHmJOjo3v1UDhXdIv8eLhsu9WREfE2", - "vBTa7LijBwTZcfQpAA/goR765qjAzkmjl3Wn+G9QboJajth/ki2ooSU04++1gAFrmgsVDs5Lh713OHCU", - "bQ6ysR18ZOjIDpj23lKpWcpK1HV+hO2dq37dCaIOJ5KBpiyHjAQfrBpYhv2JjcTojnkzVXCSFaYPfs8M", - "E1lOzhSKPG3gL2CLOvdbG+J3HgQG3oEuGxnV3E+UEwTUBw4ZETxsAhua6nxrBDW9hi25AglEVYuCaW1D", - "d9uqrhZlEg4QtXCPzOjcOTY8zu/AFP/SGQ4VLK+/FfOZ1QnG4TvvKAYtdDhdoBQin2A96iEjCsEkzz8p", - "hdl15qKIfRypp6QWkI5poy+vvv7vqRaacQXkv0VFUspR5ao01DKNkCgooABpZjAiWD2n8/E3GIIcCrCa", - "JH55+LC78IcP3Z4zRZZw5UPvTcMuOh4+RDvOW6F063Ddga3QHLfTyPWBpn9z8TktpMtTdvuY3chTdvJt", - "Z/DaX2DOlFKOcM3yb80AOidzM2XtIY1M86/juJOs+sHQsXXjvp+xosqpvgv/xahAWisUrCggY1RDviWl", - "hBRseLWRsJSFxYBGbOBVuqZ8hYK1FNXKRf7YcZAxVsqaMGTFe0NEhQ+94clKiqqMMUoX7ekj7I3YAdSo", - "PgEisbMV9K9oPZ9Lqphyg3mEB7vzgxlzyK0wnw1qhgapl41maJHTThOIYwHzHhJVpSlANAY4pnPVS+2k", - "QzYJLm5AIzZU0gZBEZrqiuYh1ZHTJaF8286TpCxXhgsyRbCd6dwE1s7t2nwSy5Lm1jkbyaoIT0pL4gt2", - "vkFpFxUTHQ9IJEYa6lNGSIDmeBky/mOM+M3QMSj7EwdRV83HocAro4Dn2zsQg+xAREIpQeGlFRqulP0q", - "lmHyk7vV1FZpKPq2fdv11wFG825QgxQ8ZxySQnDYRvN9GYef8GOUceDFOdAZRZihvl2tpAV/B6z2PFOo", - "8bb4xd0OeNHbOuLwDja/O27HrROmfaHZEvKSUJLmDI2agistq1R/4BTNJsFhi0RmeP1w2JD20jeJW+4i", - "hjU31AdOMSqnNqZEvclLiFgOvgfw9jRVrVagOvyTLAE+cNeKcVJxpnGuwuxXYjesBInhEQe2ZUG3hgWi", - "3e93kIIsKt3myZh6orRhl9bHZKYhYvmBU01yMDr1T4yfb3A476P1NMNBXwl5UWMhfoWsgINiKolHkPxg", - "v2Jwn1v+2gX6Yaqw/Wy9Emb8Jj9li1aVJv31/9z/r+P3J8n/0OT3o+T5/3f48dPT6wcPez8+vv722//b", - "/unJ9bcP/us/YzvlYY8lRjjIT185Ze30FUrkjVuiB/tnM0kXjCdRIgud7x3aIvcxCdAR0IO2vUav4QPX", - "G24I6ZLmLDMi103IocviemfRno4O1bQ2omOf8WvdU869BZchESbTYY03vsb7QVfxFCT0k7msIjwvy4rb", - "rfSCro2w98EvYjmv08xsBYpjgjlIa+ojt9yfj599M5s3uUP199l85r5+jFAyyzZR6RA2MfXFHRA8GPcU", - "KelWwYAAirBH43xsuEE4bAFG71VrVn5+TqE0W8Q5nI9bdmaQDT/lNqDYnB/0um2dMV8sPz/cWho5vNTr", - "WGZ6S1LAVs1uAnQiIUopLoHPCTuAg64ZIjOqmYs4yoEuMUMaFT0xJQ+jPgeW0DxVBFgPFzJJ14/RDwq3", - "jltfz2fu8ld3Lo+7gWNwdeesXWz+by3IvR++OyeHjmGqezZZ0Q4dpJdFtFaXQdGKkTHczNbjsNmaH/gH", - "/gqWjDPz/fgDz6imhwuqWKoOKwXyBc0pT+FgJcixT8p4RTX9wHuS1mDJnCAdhpTVImcpuQgl4oY8bRmE", - "/ggfPryn+Up8+PCxFy7Ql1/dVFH+YidIrphei0onLok7kXBFZcwdo+okXhzZVmkYm3VO3NiWFbskcTd+", - "nOfRslTdZL7+8ssyN8sPyFC5VDWzZURpIb0sYgQUCw3u7xvhLgZJr7wJo1KgyG8FLd8zrj+S5EN1dPQE", - "SCu77Td35Rua3JYw2ZAxmGzYtV/gwq1eAxstaVLSVczr8+HDew20xN1HeblAJTvPCXZrZdX5qGEcqlmA", - "x8fwBlg49s4QwsWd2V6+YE98CfgJtxDbGHGj8UXfdL+CPLsbb1cnV6+3S5VeJ+ZsR1elDIn7nanreKyM", - "kOUDBBRbYRCmK3myAJKuIb1wtSigKPV23uruY1CcoOlZB1O2SonNksE8ebSZL4BUZUadKN61IC22RIHW", - "Pgr0HVzA9lw0afb7ZCi3E2bV0EFFSg2kS0Os4bF1Y3Q33wU6oYmrLH3eKSYgebI4runC9xk+yFbkvYND", - "HCOKVkLnECKojCDCEv8ACm6wUDPerUg/tjyjZSzszRepWOJ5P3FNGuXJxSSFq0EDt/1eAJY8EleKLKiR", - "24Wr1mOTQgMuVim6ggEJOXRbTEy9bLk6cJBd9170phPL7oXWu2+iINvGiVlzlFLAfDGkgspMJxLNz2Q9", - "Y84JgEX4HMIWOYpJdcieZTpUttxHtqrYEGhxAgbJG4HDg9HGSCjZrKnyhYSw3pI/y5NkgD8wyXmstEVo", - "0A+KKtX2dc9zu+e0p126Ahe+qoUvZRGqlhPKUhgJH+O2Y9shOApAGeSwsgu3jT2hNAnXzQYZOP6+XOaM", - "A0li8VhUKZEyWwmquWbcHGDk44eEWBMwmTxCjIwDsNHjiwOTNyI8m3y1D5DcJYxTPzb6ioO/IZ7bYiOU", - "jcgjSsPC2YADKfUcgLogvvr+6oSS4jCE8TkxbO6S5obNOY2vGaRXYQHF1k49BRdz8GBInB2xwNuLZa81", - "2avoJqsJZSYPdFygG4F4ITaJTW6LSryLzcLQezRoG1PtYgfT1rK4p8hCbDCOBa8WGyS8A5ZhODwYgYa/", - "YQrpFfsN3eYWmLFpx6WpGBUqJBlnzqvJZUicmDL1gAQzRC73g/IUNwKgY+xoar065XenktoWT/qXeXOr", - "zZuySz4fJnb8h45QdJcG8Ne3wtQFJd52JZaonaIdjtGupRGIkDGiN2yi76Tpu4IU5IBKQdISopKLmOvO", - "6DaAN86Z7xYYL7BiB+XbB0GMj4QVUxoaI7oPSfgS5kmKhcKEWA6vTpdyadb3Toj6mrKVaLBja5mffQUY", - "JLtkUukEPRDRJZhG3ytUqr83TeOyUjuKyJbVZFmcN+C0F7BNMpZXcXp18/74ykz7pmaJqlogv2XcxoYs", - "sAxsNLZwZGobfjq64Nd2wa/pna132mkwTc3E0pBLe44/ybnocN4xdhAhwBhx9HdtEKUjDDLICe1zx0Bu", - "socTc0IPxqyvvcOU+bF3ho34zNShO8qOFF1LYDAYXQVDN5ERS5gOqqj2kzUHzgAtS5ZtOrZQO+qgxkz3", - "Mnj42lMdLODuusF2YCCwe8byRSSodpmxRsC39XBbVT4OJmHmvF0MLGQI4VRM+WrufUTV+WS7cHUONP8R", - "tr+Ytric2fV8djvTaQzXbsQduH5bb28Uz+iat6a0lidkT5TTspTikuaJMzAPkaYUl440sbm3R39mVhc3", - "Y55/d/L6rQP/ej5Lc6AyqUWFwVVhu/JPsypb0WzggPhq0Ubn8zK7FSWDza/LMIVG6as1uLK7gTTaqw/Y", - "OByCo+iM1Mt4hNBOk7PzjdgljvhIoKxdJI35znpI2l4ReklZ7u1mHtqBaB5c3LQik1GuEA5wa+9K4CRL", - "7pTd9E53/HQ01LWDJ4VzjRQGLmzta0UE77rQMbx4Wzqve0Gxup+1ivSZE68KtCQkKmdp3MbKF8oQB7e+", - "M9OYYOMBYdSMWLEBVyyvWDCWaaYmKLodIIM5osj0lSKHcLcQ7l2TirN/VUBYBlybTxJPZeegYjlFZ23v", - "X6dGdujP5Qa2Fvpm+NvIGGFly+6Nh0CMCxihp64H7qtaZfYLrS1SGG7duCT2cPiHM/auxBFnvaMPR802", - "eHHd9riFz5D0+Z8hDFuPevcbKF55dSU2B+aIvmnCVLKU4neI63moHkdScXwtT4ZRLr8DnxBz3lh3mqdZ", - "mtkHt3tIugmtUO0ghQGqx50P3HJYVNBbqCm3W22fGGjFusUJJowqPbTjNwTjYO5F4ub0akFjFReNkGFg", - "OmkcwC1buhbEd/a4V3Vig52dBL7kui2zadYlyCZLrl+y5YYCg512sqjQSAZItaFMMLf+v1yJyDAVv6Lc", - "vlRh+tmj5HorsMYv0+tKSCySoOJm/wxSVtA8Ljlkad/Em7EVs48wVAqCKv9uIPvAjaUi91JCna7jUHO6", - "JEfz4KkRtxsZu2SKLXLAFo9siwVVyMlrQ1TdxSwPuF4rbP54QvN1xTMJmV4ri1glSC3UoXpTO68WoK8A", - "ODnCdo+ek/votlPsEh4YLLr7eXb86DkaXe0fR7ELwD2iMcZNMmQn/3DsJE7H6Le0YxjG7UY9iOaT21e0", - "hhnXyGmyXaecJWzpeN3us1RQTlcQjxQpdsBk++JuoiGtgxee2SdglJZiS5iOzw+aGv40EH1u2J8Fg6Si", - "KJgunHNHicLQU1PC307qh7Pvybjqqx4u/xF9pKV3EXWUyM9rNLX3W2zV6Ml+Qwtoo3VOqK2MkbMmesHX", - "hCanvvAOlqOtq9Ba3Ji5zNJRzMFghiUpJeMaFYtKL5O/knRNJU0N+zsYAjdZfPM0UoK3XQqS7wf4Z8e7", - "BAXyMo56OUD2XoZwfcl9LnhSGI6SPWiyPYJTOejMjbvthnyH40NPFcrMKMkguVUtcqMBp74V4fGRAW9J", - "ivV69qLHvVf22SmzknHyoJXZoZ/fvXZSRiFkrJpec9ydxCFBSwaXGLsX3yQz5i33QuaTduE20H9Zz4MX", - "OQOxzJ/lmCLwQkS0U18Wuraku1j1iHVg6JiaD4YMFm6oOWmX4P38Tj9vfO47n8wXDyv+0QX2C28pItmv", - "YGATg/Lg0e3M6u+B/5uSF2IzdVM7J8Rv7L8BaqIoqVie/dJkZXaqr0vK03XUn7UwHX9t3omqF2fvp2jR", - "ujXlHPLocFYW/NXLjBGp9p9i6jwF4xPbdgvC2+V2FtcA3gbTA+UnNOhlOjcThFhtJ7zVAdX5SmQE52kq", - "pDXcs/+QQFDu+V8VKB1LHsIPNqgL7ZZG37XVhgnwDLXFA/KDfQp2DaRV/ga1tLqKgKt9aw3qVZkLms2x", - "kMP5dyeviZ3V9rGvndhqxytUUtqr6NirguKP08KD/cMl8dSF6eOMx1KbVSuN1aiUpkUZSw41Lc59A8xA", - "DW34qL6E2Dkgr4JHHW0eqRnC0MOSycJoXPVoVnZBmjD/0Zqma1TJWix1mOSnl+n2VKmCp/HqJ27qioh4", - "7gzcrlK3LdQ9J8LozVdM2RdA4RLa+ah1crYzCfj81PbyZMW5pZSo7DFWPOAmaPfA2UANb+aPQtZB/J4C", - "ua1yv2/V8jPsFS3Q1C2B3nsTz2Y31k+X+JedU8oFZymWR4pdze6p0Ck+sAmVpLpGVn/E3QmNHK5o4fU6", - "TM5hcbAUu2eEDnF9I3zw1WyqpQ77p8Y3KddUkxVo5TgbZHP/foCzAzKuwFW4xIdlAz4pZMuviBwy6qpO", - "apfGnmSEaTEDit335tsbp/ZjvPgF4yjgO7S50HRrqcOXDLXRCpgmKwHKraedG6zemz4HmCabwebjgX/5", - "0FaDQbecWbb1QfeHOvEeaecBNm1fmrauTlD9cysC2U56UpZu0uHXJaLygN7wQQRHPIuJd+0EyK3HD0cb", - "IbfRUBK8Tw2hwSU6oqHEe7hHGPVLC51XfIzQaikKWxAbwhWtYMB4BIzXjEPzLmfkgkijVwJuDJ7XgX4q", - "lVRbEXASTzsHmqP3OcbQlHauh9sO1a0lZFCCa/RzDG9j80jEAOOoGzSCG+Xb+jlQQ92BMPES3yF2iOw/", - "+YBSlROiMswo6DwCEWMchnH7Z2baF0D/GPRlIttdS2pPzj430VCS6KLKVqATmmWxilQv8CvBr764FGwg", - "rerClGVJUqyJ0i4S06c2N1EquKqKkbl8g1tOF7yqEqGG8GUXv8OYhLLY4r+xqozDO+OCMPYOA/QRF+4Z", - "ij3l5vZIPanX0HSi2CqZjgm8U26PjmbqmxF60/9OKT0XqzYgn7k0xBiXC/coxt++MxdHWDmhV2rUXi11", - "YQMMuhP+LTxUG+uU3DZXwqusV3sUnT31W1vjBojhV7PmePkNhN4GBTGovV+t93AoADcdjBen2mWuaUpG", - "WdBgNpCN3rF5PwhF3HI6FLFjA3bM517vaZJhT87GsUcR6kPB+gD96ONMSUmZc403zKKPWReRPmwuHDt0", - "zQZ3F+HivActdj9eDsVkE8X4KgeC37vvDF2AS2evH5q3a/VRSV4ltL+6d17teHVUfHT9/egEnOrLmkEH", - "jbbnrqa9XabTyX/8xcawEeBabv8NTLi9Te+90tSXdq15qmlC6nLIk8ojt27F+INLw/WPmppHSE+lUKwp", - "wR17iWlirNs5PqYU1G/qj+UDTS4h1Vh3vXGgS4B9qjmZyYJX/r7WQRrQHeuQQFf+aKzmUb/Y+o4LrZeW", - "FKTW2ULVB9Mr/JzUYVLIlLAC7gq4e2ivnXAwOex5uYRUs8sdaWD/WAMPUozm3ghhH8wNssJYHUaLVUT2", - "N7E1AI1laY3CE1TzuzU4Q0kgF7C9p0iLGqKVs+f+XrlJAQnEAHKHxJCIULEwBGs1dZ5hpmrKQCz4sB/b", - "HZpSXIOP7gRJjTecy5OkuXGbRMeRKeOvfkyay3TdK/0XI0KHMsX6jwYMC9uv8I0GVT+I5wtQhCopOe2X", - "6btyBSwwaa92FPhSFqD8bz5D186SswsInwVCt8wVlZlvEbUzeBNGMnIf9dK7fMH7LtDLembWBGn2E3oi", - "hZ8wFDfNhZG/kqF45nZcZPh6PkZ/2JLfGPFp4FqCdM+nobCXCwWJFj6ocwyOMVS4l95vggQ1WGzRAjdY", - "AuVdU+MFi85SLHlCXWRLuEAioaAGOhlUYhmecwzZL+13n8Hii47uNKfU9Lq70LwPz2Wqh8SQ6pfE3Za7", - "M2NuYllhnNvHWlWsLAs3qAxN/6UUWZXaCzo8GLX1aXLRoxFWEjVKpP1VdgTiIL3wAraHVuL3Ffr9DoZA", - "W8nJgh6k83c2+U5tTSoG9+pOwPuSZpr5rBQiTwYs+6f9WjJdir9g6QVkxNwUPoxt4JESch8NyrXr9mq9", - "9bVTyhI4ZA8OCDnhNnDYe3HbxYw7k/N7emz+Dc6aVba8k7MgHXzg8QhMLLwkb8nN/DDjPEyBYXW3nMoO", - "sqNSyWagjo2kV5Enew6mqqB9v2r3GZWGqCwUMZmkeSFkR1BIHQ/SvIHQxIT0Hy4aeYnjvENEth364h0w", - "ez+34YDsvrqx0yIagDkBOb3hI0alyGsi7XX18DTwqJMWBUv7w7VQ86dw5w864Xe8lRJZX0147ikXnxo1", - "gKuob2zcFWWfq1pMdUjVhWOj+xQtUprsdFG1YJjkqNoXjCU+/5bQCJJPa4lp3nqdk3VeyvFFvSyNp9Rq", - "TEZbpyyvJLhUHftOVef9ipLqteegpnlfrzEyMijMo7FvIFBltXBvDXCPZHWvJlEmOVxCy3Pn8oeqNAWl", - "2CWED2zZziQDKNE21pXYYi6pkLV3rnG39iRwakzBbvRet4i1O0V2XNpREWPDE3tM1NSjZCC6ZFlFW/hT", - "t3j7aOjZowgb9rBO5BR7M4n44sZYxE4nMtJ89FzyuA85TF+rFXKcLasNd5YIm5OtSnrFhwXYiM2jdmze", - "fh0EByOqk046EDSLLzsldd3J2PXoMt888ZsZm1eoOrpc8FpUPebAu7k1/dxGbxokyjhN3qzez6ST1He/", - "RZhN8ELUuJE4LAfW5BlI68VFo5LnV93D8FPDx6a9VeU77AAv9B0Er1V5Nc6B84WTAX6qkRIsZZASWsvf", - "5Y5wC2wYf7BF9tYzy7TFGW0gaXtfAl+Telm7cIaekOt6erD2l+BYD7HvIVLo1cdnFULCMQddXtL883t5", - "sCjcCeLDvckdX2joJgiRbFGpbhaR+5pOmjtwCdzd1PwteqX+AWaPokqCG8rdKLWU5Z3YyDJpbph4/Rgn", - "DkmucEwbv/HoG7Jw2YSlhJSp7k115Su+11ZxfACleap93Ay/a52/CH0LMl56wY+8aapHo8q14g2EzRH9", - "wkxl4ORGqTxGfT2yiOAvxqPCsj47rouLVmCHrcbfiVgWEu44wCMI1dwzwKNfsGjq8mwQg7l0KgX9dU6+", - "rVu4jVzUzdqmRif1kTtWYnhKUFG8crjpjlFNFiFYdp8gqOS3R78RCUt8V0uQhw9xgocP567pb4/bn81x", - "fvgw/iL854pnsjhyY7h5YxTzy1CGi83iGEim6uxHxfJsF2G0UuOal+kw+etXlxz7Rd7G+9W6nftH1b1P", - "tE8kZXcTEDGRtbYmD6YKkt4m5Lu5bpHsNjTpppVkeos1u7w6x36NRl79UAc2uMCYusqLu/u0uIC66lsT", - "BlEpf7v+IGiO95GRqTGOVeMr4N9taFHm4A7Kt/cWf4Enf32aHT159JfFX4+eHaXw9NnzoyP6/Cl99PzJ", - "I3j812dPj+DR8pvni8fZ46ePF08fP/3m2fP0ydNHi6ffPP/LPcOHDMgW0JmvEDH73/iAZHLy9jQ5N8A2", - "OKElqx//N2TsX8GiKZ5EKCjLZ8f+p//fn7CDVBTN8P7XmUtAn621LtXx4eHV1dVB2OVwhX7PRIsqXR/6", - "efqPrr89rQ3GVinHHbX5Yd7Y4knhBL+9++7snJy8PT0IHvU9nh0dHB08wjdfS+C0ZLPj2RP8CU/PGvf9", - "0BHb7PjT9Xx2uAaaY5iQ+aMALVnqP0mg2db9X13R1QrkgXsazPx0+fjQixWHn5z/93rs22FYZf/wU8tN", - "nu3oiVW4Dz/54lLjrVvVm1x4QNBhIhRjzQ4XmLM+tSmooPHwUlDZUIefUFwe/P3QJfLGP6LaYs/DoY8l", - "ibdsYemT3hhYOz1SqtN1VR5+wv8gfQZg2bD5PriwKUEyI3Bh1Iz71aa9HtqXefs/b3ka/bE/fO+FmBVE", - "nTOYO0vxFdN4neoZnhN7xE4z5Hy6G2Vmy81bOwgen8dHR3u9nDfNZ92NbevfJX2mMbay6/ns6Z6Ajlpd", - "WuHvEWBe0Ix4PxnO/ejzzX3KMVTNcENiuT1C8PTzQdCu7f8jbMkbocn3qJZcz2fPPudOnHIjJNGcYMug", - "dFf/iPzML7i44r6lEROqoqByO/n4aLpS6E+V7JI6IS147mX2ER341nfaPmonWdYjeisugdIvBN47Qxgr", - "1Kp0yW4N0hppkXGzhL662X8/dw2RMFEbzOSdMFxkMAvlOC0ruL4lT2gLzAaE04j1BM2A+ADL0hfbC0CN", - "xjx23dx25EkviHYGrx9MqBZoaRb8K0/5ylNqnvLs6Mnnm/4M5CVLgZxDUQpJJcu35Gdelyq4MY87ybJo", - "oHj76O/kcUYTT0UGK+CJY2DJQmRbX461NcEFWMWwJ8gcfmq/qWAFw1kGOehoEKz5vX7ttr+IxZacvupJ", - "OLZbl/O+2GLT4K2C4/efrGZl1IZG8emC2OOMYZn8Lm/6GOeaY2RvFrISmlgsZG5RXxnRV0Z0K+Fm8uGZ", - "It9EtQ9bCIj27uy5r+kTq+ZGdR+UKTrKFz2+d7Lxff0npu/YgHvISPDB+uK7aP7KIr6yiNuxiB8gchjx", - "1DqmESG6/fShqQwDA5my7stl6FzwzaucSqJgqpnjBEd0xo3PwTU+t1IXxZXV6ShvHneMbODd6nlfWd5X", - "lvfnYXknuxlNWzC5tWZ0AduClrU+pNaVzsRV4F9AWGzsT98OXL+l3Pr78IoynSyFdOmbWNm/31kDzQ9d", - "YbLOr00tkN4XLHAS/BiGgkZ/PawfTol+7LomYl+daX6gkY+f958bN2Xo9kPWXjv83n80bBnLcjuu33ix", - "jg8PMSVqLZQ+nF3PP3U8XOHHjzUJfKrvCkcK1x+v/18AAAD//3SkX3IpzQAA", + "H4sIAAAAAAAC/+x9a3PcNrLoX0HNOVV+3KEkv7JrVaXOle0kqxvHcVlK9p61fRMM2TODFQlwAVCaia/+", + "+yk0ABIkQQ5HUuxNVT7ZGuLRaDQa/canWSqKUnDgWs2OP81KKmkBGiT+RdNUVFwnLDN/ZaBSyUrNBJ8d", + "+29Eacn4ajafMfNrSfV6Np9xWkDTxvSfzyT8q2ISstmxlhXMZypdQ0HNwHpbmtb1SJtkJRI3xIkd4vTV", + "7HrkA80yCUr1ofyR51vCeJpXGRAtKVc0NZ8UuWJ6TfSaKeI6E8aJ4EDEkuh1qzFZMsgzdeAX+a8K5DZY", + "pZt8eEnXDYiJFDn04XwpigXj4KGCGqh6Q4gWJIMlNlpTTcwMBlbfUAuigMp0TZZC7gDVAhHCC7wqZsfv", + "Zwp4BhJ3KwV2if9dSoDfINFUrkDPPs5ji1tqkIlmRWRppw77ElSVa0WwLa5xxS6BE9PrgPxQKU0WQCgn", + "7759SZ48efLcLKSgWkPmiGxwVc3s4Zps99nxLKMa/Oc+rdF8JSTlWVK3f/ftS5z/zC1waiuqFMQPy4n5", + "Qk5fDS3Ad4yQEOMaVrgPLeo3PSKHovl5AUshYeKe2MZ3uinh/F90V1Kq03UpGNeRfSH4ldjPUR4WdB/j", + "YTUArfalwZQ0g74/Sp5//PRo/ujo+j/enyT/cH8+e3I9cfkv63F3YCDaMK2kBJ5uk5UEiqdlTXkfH+8c", + "Pai1qPKMrOklbj4tkNW7vsT0tazzkuaVoROWSnGSr4Qi1JFRBkta5Zr4iUnFc8OmzGiO2glTpJTikmWQ", + "zQ33vVqzdE1SquwQ2I5csTw3NFgpyIZoLb66kcN0HaLEwHUjfOCC/n2R0axrByZgg9wgSXOhINFix/Xk", + "bxzKMxJeKM1dpfa7rMj5GghObj7YyxZxxw1N5/mWaNzXjFBFKPFX05ywJdmKilzh5uTsAvu71RisFcQg", + "DTendY+awzuEvh4yIshbCJED5Yg8f+76KONLtqokKHK1Br12d54EVQqugIjFPyHVZtv/z9mPb4iQ5AdQ", + "iq7gLU0vCPBUZJAdkNMl4UIHpOFoCXFoeg6tw8EVu+T/qYShiUKtSppexG/0nBUssqof6IYVVUF4VSxA", + "mi31V4gWRIKuJB8CyI64gxQLuulPei4rnuL+N9O2ZDlDbUyVOd0iwgq6+fpo7sBRhOY5KYFnjK+I3vBB", + "Oc7MvRu8RIqKZxPEHG32NLhYVQkpWzLISD3KCCRuml3wML4fPI3wFYDjBxkEp55lBzgcNhGaMafbfCEl", + "XUFAMgfkJ8fc8KsWF8BrQieLLX4qJVwyUam60wCMOPW4BM6FhqSUsGQRGjtz6DAMxrZxHLhwMlAquKaM", + "Q2aYMwItNFhmNQhTMOG4vtO/xRdUwVdPh+745uvE3V+K7q6P7vik3cZGiT2SkavTfHUHNi5ZtfpP0A/D", + "uRVbJfbn3kay1bm5bZYsx5von2b/PBoqhUyghQh/Nym24lRXEo4/8IfmL5KQM015RmVmfinsTz9UuWZn", + "bGV+yu1Pr8WKpWdsNYDMGtaowoXdCvuPGS/OjvUmqle8FuKiKsMFpS3FdbElp6+GNtmOuS9hntTabqh4", + "nG+8MrJvD72pN3IAyEHcldQ0vICtBAMtTZf4z2aJ9ESX8jfzT1nmprculzHUGjp2VzKaD5xZ4aQsc5ZS", + "g8R37rP5apgAWEWCNi0O8UI9/hSAWEpRgtTMDkrLMslFSvNEaapxpP+UsJwdz/7jsLG/HNru6jCY/LXp", + "dYadjMhqxaCEluUeY7w1oo8aYRaGQeMnZBOW7aHQxLjdRENKzLDgHC4p1weNytLiB/UBfu9mavBtpR2L", + "744KNohwYhsuQFkJ2Da8p0iAeoJoJYhWFEhXuVjUP9w/KcsGg/j9pCwtPlB6BIaCGWyY0uoBLp82Jymc", + "5/TVAfkuHBtFccHzrbkcrKhh7oalu7XcLVbbltwamhHvKYLbKeSB2RqPBiPm3wXFoVqxFrmRenbSimn8", + "N9c2JDPz+6TOfwwSC3E7TFyoaDnMWR0HfwmUm/sdyukTjjP3HJCTbt+bkY0ZJU4wN6KV0f20447gsUbh", + "laSlBdB9sXcp46ik2UYW1lty04mMLgpzcIYDWkOobnzWdp6HKCRICh0YXuQivfgbVes7OPMLP1b/+OE0", + "ZA00A0nWVK0PZjEpIzxezWhTjphpiAo+WQRTHdRLvKvl7VhaRjUNlubgjYslFvXYD5keyIju8iP+h+bE", + "fDZn27B+O+wBOUcGpuxxdk6GzGj7VkGwM5kGaIUQpLAKPjFa915Qvmwmj+/TpD36xtoU3A65ReAOic2d", + "H4MXYhOD4YXY9I6A2IC6C/ow46AYqaFQE+B75SATuP8OfVRKuu0jGceegmSzQCO6KjwNPLzxzSyNcfZk", + "IeTNuE+HrXDSmJwJNaMGzHfeQRI2rcrEkWLEbGUbdAZqvHzjTKM7fAxjLSycafo7YEGZUe8CC+2B7hoL", + "oihZDndA+uso019QBU8ek7O/nTx79PiXx8++MiRZSrGStCCLrQZF7jvdjCi9zeFBf2WoHVW5jo/+1VNv", + "qGyPGxtHiUqmUNCyP5Q1gFoRyDYjpl0fa20046prAKccznMwnNyinVjbvgHtFVNGwioWd7IZQwjLmlky", + "4iDJYCcx7bu8ZpptuES5ldVdqLIgpZAR+xoeMS1SkSeXIBUTEW/KW9eCuBZevC27v1toyRVVxMyNpt+K", + "o0ARoSy94dP5vh36fMMb3IxyfrveyOrcvFP2pY18b0lUpASZ6A0nGSyqVUsTWkpREEoy7Ih39HegURQ4", + "ZwWcaVqUPy6Xd6MqChwoorKxApSZidgWRq5XkApuIyF2aGdu1Cno6SLGm+j0MAAOI2dbnqKd8S6O7bDi", + "WjCOTg+15WmgxRoYc8hWLbK8vbY6hA471T0VAceg4zV+RkPHK8g1vXOJrjtBDPaXnrQtsCQzDXG3XrPV", + "Wgci91spxPLuYYzNEgMUP1iFJTd9+mrLG5GZg6YrdQfiSTNYc/rNnoZnni5EpQklXGSANqZKxQWXgVgG", + "dKKi71eHspBeWx1kAYaQUlqZ1VYlQc9mj5c2HROaWupNEDVqwK9TO+RsKzud9ZPnEmi2JQsATsTCOU+c", + "WwcXSdEtq/3V78SmyFlqwVVKkYJSkCXOaLMTNN/OslU9gicEHAGuZyFKkCWVtwb24nInnBewTTCIQJH7", + "3/+sHnwBeLXQNN+BWGwTQ2+tAjsPWR/qadOPEVx38pDsqATiea7Rtw2DyEHDEAr3wsng/nUh6u3i7dFy", + "CRJ9Vb8rxftJbkdANai/M73fFtqqHAiNc6qfkX7MhnHKhRc6YoPlVOlkF1s2jVr6qVlBwAljnBgHHhBK", + "XlOlrX+V8QzNQvY6wXmsgGKmGAZ4UEQ3I//spfP+2Km5B7mqVC2qq6oshdSQxdbAYTMy1xvY1HOJZTB2", + "rQ9oQSoFu0YewlIwvkOWXYlFENW1G8IFIPQXh8Z6c89vo6hsAdEgYgyQM98qwG4YHjQACFMNoi3hMNWh", + "nDomaT5TWpSl4RY6qXjdbwhNZ7b1if6padsnLqqbezsToDAqybV3kF9ZzNrAsDVVxMFBCnphZA80EVhH", + "cB9mcxgTxXgKyRjlo/pjWoVHYOchrcqVpBkkGeR02x/0J/uZ2M9jA+CON6qg0JDYCJ/4pjeU7AMqRoYW", + "OJ6KCY8Ev5DUHEGjeTQE4nrvGDkDHDvGnBwd3auHwrmiW+THw2XbrY6MiLfhpdBmxx09IMiOo08BeAAP", + "9dA3RwV2Thq9rDvFf4NyE9RyxP6TbEENLaEZf68FDNgXXfB0cF467L3DgaNsc5CN7eAjQ0d2wNj5lkrN", + "UlairvM9bO9c9etOEHXBkQw0ZTlkJPhg1cAy7E9sbEp3zJupgpPsUn3we4apyHJyplDkaQN/AVvUud/a", + "oMfzIFTyDnTZyKjmfqKcIKA+lMqI4GET2NBU51sjqOk1bMkVSCCqWhRMaxvM3FZ1tSiTcICozX9kRufg", + "sgGDfgemeNzOcKhgef2tmM+sTjAO33lHMWihw+kCpRD5BOtRDxlRCCbFQpBSmF1nLq7aR9Z6SmoB6Zg2", + "ejfr6/+eaqEZV0D+W1QkpRxVrkpDLdMIiYICCpBmBiOC1XO6qIcGQ5BDAVaTxC8PH3YX/vCh23OmyBKu", + "fDKCadhFx8OHaMd5K5RuHa47sBWa43YauT7QGWIuPqeFdHnKbq+7G3nKTr7tDF57UMyZUsoRrln+rRlA", + "52Rupqw9pJFpEQc47iQ/RzB0bN2472esqHKq78KjMyqQ1goFKwrIGNWQb0kpIQUbcG4kLGVhMaARG4qW", + "rilfoWAtRbVysVB2HGSMlbImDFnx3hBR4UNveLKSoipjjNLFv/qcAyN2ADWqT4BI7GwF/Staz+fSTKbc", + "YB7hwe58Z8YccrTMZ4OaoUHqZaMZWuS0EyfiWMBMkERVaQoQjYqO6Vz1UjsJok3KjxvQiA2VtGFhhKa6", + "onlIdeR0SSjftjNHKcuV4YJMEWxnOjehxnO7Np/Ws6S5dVdH8kzCk9KS+IKdb1DaRcVExwMSiZGG+pQR", + "EqA5XoaMfx8jfjN0DMr+xEEcWvNxKBTNKOD59g7EIDsQkVBKUHhphYYrZb+KZZgO5m41tVUair5t33b9", + "ZYDRvBvUIAXPGYekEBy20QxoxuEH/BhlHHhxDnRGEWaob1cracHfAas9zxRqvC1+cbcDXvS2jsG8g83v", + "jttx64SJcGi2hLwklKQ5Q6Om4ErLKtUfOEWzSXDYIrEqXj8cNqS99E3ilruIYc0N9YFTjFOqjSlR//oS", + "IpaDbwG8PU1VqxWoDv8kS4AP3LVinFScaZyrMPuV2A0rQWLAyIFtWdCtYYFo9/sNpCCLSrd5MibjKG3Y", + "pfUxmWmIWH7gVJMcjE79A+PnGxzO+2g9zXDQV0Je1FiIXyEr4KCYSuIxNd/Zrxju6Ja/dqGPmDxtP1uv", + "hBm/ydjZolWlSQj+f/f/6/j9SfIPmvx2lDz/X4cfPz29fvCw9+Pj66+//v/tn55cf/3gv/4ztlMe9liq", + "iIP89JVT1k5foUTeuCV6sH82k3TBeBIlstD53qEtch/TIh0BPWjba/QaPnC94YaQLmnOMiNy3YQcuiyu", + "dxbt6ehQTWsjOvYZv9Y95dxbcBkSYTId1njja7wfhhZPykI/mcuzwvOyrLjdSi/o2pwDHw4klvM68c7W", + "5DgmmJW1pj6Wzf35+NlXs3mTTVV/n81n7uvHCCWzbBOVDmETU1/cAcGDcU+Rkm4VDAigCHs08smGG4TD", + "FmD0XrVm5efnFEqzRZzD+UhuZwbZ8FNuQ6zN+UGv29YZ88Xy88OtpZHDS72O5eq3JAVs1ewmQCcSopTi", + "EvicsAM46JohMqOauRisHOgSc8ZR0RNTMlPqc2AJzVNFgPVwIZN0/Rj9oHDruPX1fOYuf3Xn8rgbOAZX", + "d87axeb/1oLc++6bc3LoGKa6Z9M37dBBwl1Ea3U5Ja0YGcPNbIUSm7/6gX/gr2DJODPfjz/wjGp6uKCK", + "peqwUiBf0JzyFA5Wghz7NJVXVNMPvCdpDRYRChKESFktcpaSi1AibsjTFoboj/Dhw3uar8SHDx974QJ9", + "+dVNFeUvdoLkium1qHTi0toTCVdUxtwxqk5rxpFt3YqxWefEjW1ZsUubd+PHeR4tS9VNb+wvvyxzs/yA", + "DJVL3jNbRpQW0ssiRkCx0OD+vhHuYpD0ypswKgWK/FrQ8j3j+iNJPlRHR0+AtPL9fnVXvqHJbQmTDRmD", + "6Zdd+wUu3Oo1sNGSJiVdxbw+Hz6810BL3H2UlwtUsvOcYLdWnqGPo8ahmgV4fAxvgIVj75wpXNyZ7eVL", + "GMWXgJ9wC7GNETcaX/RN9yvIPLzxdnWyF3u7VOl1Ys52dFXKkLjfmbqyycoIWT5AQLEVBmG6IjALIOka", + "0gtXnQOKUm/nre4+BsUJmp51MGXrtti8IawcgDbzBZCqzKgTxbsWpMWWKNDaR4G+gwvYnoum8MA+Odvt", + "FGI1dFCRUgPp0hBreGzdGN3Nd4FOaOIqS5+JiylZniyOa7rwfYYPshV57+AQx4iileI6hAgqI4iwxD+A", + "ghss1Ix3K9KPLc9oGQt780VquHjeT1yTRnlyMUnhatDAbb8XgEWgxJUiC2rkduHqF9k02YCLVYquYEBC", + "Dt0WE5NRW64OHGTXvRe96cSye6H17psoyLZxYtYcpRQwXwypoDLTiUTzM1nPmHMCYFlCh7BFjmJSHbJn", + "mQ6VLfeRrbM2BFqcgEHyRuDwYLQxEko2a6p8aSWsQOXP8iQZ4HdM+x4r9hEa9IMyU7V93fPc7jntaZeu", + "5Iev8+GLe4Sq5YRCHUbCx7jt2HYIjgJQBjms7MJtY08oTQp6s0EGjh+Xy5xxIEksHosqJVJma2M114yb", + "A4x8/JAQawImk0eIkXEANnp8cWDyRoRnk6/2AZK7FHrqx0ZfcfA3xLN9bISyEXlEaVg4G3AgpZ4DUBfE", + "V99fnVBSHIYwPieGzV3S3LA5p/E1g/RqTqDY2qkw4WIOHgyJsyMWeHux7LUmexXdZDWhzOSBjgt0IxAv", + "xCax6X5RiXexWRh6jwZtY/Jh7GDa6h73FFmIDcax4NVig4R3wDIMhwcj0PA3TCG9Yr+h29wCMzbtuDQV", + "o0KFJOPMeTW5DIkTU6YekGCGyOV+ULDjRgB0jB1N9Vun/O5UUtviSf8yb261eVOIyufDxI7/0BGK7tIA", + "/vpWmLrExtuuxBK1U7TDMdrVRQIRMkb0hk30nTR9V5CCHFApSFpCVHIRc90Z3Qbwxjnz3QLjBdYwoXz7", + "IIjxkbBiSkNjRPchCV/CPEmxdJoQy+HV6VIuzfreCVFfU7Y2D3ZsLfOzrwCDZJdMKp2gByK6BNPoW4VK", + "9bemaVxWakcR2UKjLIvzBpz2ArZJxvIqTq9u3u9fmWnf1CxRVQvkt4zb2JAFFsaNxhaOTG3DT0cX/Nou", + "+DW9s/VOOw2mqZlYGnJpz/EHORcdzjvGDiIEGCOO/q4NonSEQQY5oX3uGMhN9nBiTujBmPW1d5gyP/bO", + "sBGfmTp0R9mRomsJDAajq2DoJjJiCdNBXdl+subAGaBlybJNxxZqRx3UmOleBg9fjauDBdxdN9gODAR2", + "z1i+iATVLrzWCPi2QnCr7snBJMyct8ujhQwhnIopX9++j6g6n2wXrs6B5t/D9mfTFpczu57Pbmc6jeHa", + "jbgD12/r7Y3iGV3z1pTW8oTsiXJallJc0jxxBuYh0pTi0pEmNvf26M/M6uJmzPNvTl6/deBfz2dpDlQm", + "tagwuCpsV/5hVmVrvA0cEF8/2+h8Xma3omSw+XVhqtAofbUGV4g4kEZ7FRMbh0NwFJ2RehmPENppcna+", + "EbvEER8JlLWLpDHfWQ9J2ytCLynLvd3MQzsQzYOLm1Z2M8oVwgFu7V0JnGTJnbKb3umOn46GunbwpHCu", + "kVLJha0GrojgXRc6hhdvS+d1LyjWO7RWkT5z4lWBloRE5SyN21j5Qhni4NZ3ZhoTbDwgjJoRKzbgiuUV", + "C8YyzaZUNOkAGcwRRaaKFlVpcLcQ7qWXirN/VUBYBlybTxJPZeegYoFJZ23vX6dGdujP5Qa2Fvpm+NvI", + "GGGtz+6Nh0CMCxihp64H7qtaZfYLrS1SGG7duCT2cPiHM/auxBFnvaMPR802eHHd9riFD7P0+Z8hDFuh", + "e/erMF55dUVHB+aIvvLCVLKU4jeI63moHkdScXx1U4ZRLr8BnxBz3lh3msdqmtkHt3tIugmtUO0ghQGq", + "x50P3HJYZtFbqCm3W20fXWjFusUJJowqPbTjNwTjYO5F4ub0akFjNSiNkGFgOmkcwC1buhbEd/a4V3Vi", + "g52dBL7kui2zadYlyCZLrl+y5YYCg512sqjQSAZItaFMMLf+v1yJyDAVv6Lcvt1h+tmj5HorsMYv0+tK", + "SCySoOJm/wxSVtA8Ljlkad/Em7EVs89SVAqCdw/cQPbJH0tF7u2IOl3HoeZ0SY7mweMrbjcydskUW+SA", + "LR7ZFguqkJPXhqi6i1kecL1W2PzxhObrimcSMr1WFrFKkFqoQ/Wmdl4tQF8BcHKE7R49J/fRbafYJTww", + "WHT38+z40XM0uto/jmIXgHtWZIybZMhO/u7YSZyO0W9pxzCM2416EM0nt++KDTOukdNku045S9jS8brd", + "Z6mgnK4gHilS7IDJ9sXdRENaBy88s4/iKC3FljAdnx80NfxpIPrcsD8LBklFUTBdOOeOEoWhp+ZRAzup", + "H86+sOPq0Xq4/Ef0kZbeRdRRIj+v0dTeb7FVoyf7DS2gjdY5obYyRs6a6AVfJZuc+sI7WKC3rstrcWPm", + "MktHMQeDGZaklIxrVCwqvUz+StI1lTQ17O9gCNxk8dXTSFHidnFMvh/gnx3vEhTIyzjq5QDZexnC9SX3", + "ueBJYThK9qDJ9ghO5aAzN+62G/Idjg89VSgzoySD5Fa1yI0GnPpWhMdHBrwlKdbr2Yse917ZZ6fMSsbJ", + "g1Zmh35699pJGYWQsWp6zXF3EocELRlcYuxefJPMmLfcC5lP2oXbQP9lPQ9e5AzEMn+WY4rACxHRTn2h", + "7NqS7mLVI9aBoWNqPhgyWLih5qRdlPjzO/288bnvfDJfPKz4RxfYL7yliGS/goFNDAqmR7czq78H/m9K", + "XojN1E3tnBC/sf8GqImipGJ59nOTldmpRy8pT9dRf9bCdPyleTmrXpy9n6JF69aUc8ijw1lZ8BcvM0ak", + "2n+KqfMUjE9s2y2Rb5fbWVwDeBtMD5Sf0KCX6dxMEGK1nfBWB1TnK5ERnKepkNZwz/7TCkEB7H9VoHQs", + "eQg/2KAutFsafdfWXybAM9QWD8h39nHcNZBW+RvU0uoqAq72rTWoV2UuaDbHQg7n35y8JnZW28e+/2Lr", + "P69QSWmvomOvCoo/TgsP9k+5xFMXpo8zHkttVq10UpdrjiWHmhZNQWnWseGj+hJi54C8Cp65tHmkZghD", + "D0smC6Nx1aNZ2QVpwvxHa5quUSVrsdRhkp9euNxTpQoeC6wf/akrIuK5M3C72uW2dPmcCKM3XzFl30SF", + "S2jno9bJ2c4k4PNT28uTFeeWUqKyx1jxgJug3QNnAzW8mT8KWQfxewrktu7/vnXcz7BXtEBTtyh875VA", + "m91YP+bi37pOKRecpVgeKXY1u8dTp/jAJlSS6hpZ/RF3JzRyuKKl6OswOYfFweL0nhE6xPWN8MFXs6mW", + "OuyfGl/pXFNNVqCV42yQzf2LCs4OyLgCV+ESn9oN+KSQLb8icsioqzqpXRp7khGmxQwodt+ab2+c2o/x", + "4heMo4Dv0OZC062lDt921EYrYJqsBCi3nnZusHpv+hxgmmwGm48H/i1IWw0G3XJm2dYH3R/qxHuknQfY", + "tH1p2ro6QfXPrQhkO+lJWbpJh9/biMoDesMHERzxLCbetRMgtx4/HG2E3EZDSfA+NYQGl+iIhhLv4R5h", + "1G9PdN41MkKrpShsQWwIV7SCAeMRMF4zDs1LpZELIo1eCbgxeF4H+qlUUm1FwEk87Rxojt7nGENT2rke", + "bjtUt5aQQQmu0c8xvI3NsxkDjKNu0AhulG/rB1INdQfCxEt8mdkhsv8IBkpVTojKMKOg8yxGjHEYxu0f", + "3mlfAP1j0JeJbHctqT05+9xEQ0miiypbgU5olsUqUr3ArwS/+uJSsIG0qgtTliVJsSZKu0hMn9rcRKng", + "qipG5vINbjld8M5MhBrCt278DmMSymKL/8aqMg7vjAvC2DsM0EdcuGco9pSb2yP1pF5D04liq2Q6JvBO", + "uT06mqlvRuhN/zul9Fys2oB85tIQY1wu3KMYf/vGXBxh5YReqVF7tdSFDTDoTvjXAVFtrFNy21wJr7Je", + "7VF09tSvj40bIIbfEZvj5TcQehsUxKD2frXew6EA3HQwXpxql7mmKRllQYPZQDZ6x+b9IBRxy+lQxI4N", + "2DGfe72nSYY9ORvHHkWoDwXrA/S9jzMlJWXONd4wiz5mXUT6sLlw7NA1G9xdhIvzHrTYfX85FJNNFOOr", + "HAh+774zdAEunb1+et+u1UcleZXQ/upevrXj1VHx0fX3oxNwqi9rBh002p67mvZ2mU4n//5nG8NGgGu5", + "/Tcw4fY2vfdKU1/ateappgmpyyFPKo/cuhXjDy4N1z9qah4hPZVCsaYEd+wlpomxbuf4mFJQv6k/lg80", + "uYRUY931xoEuAfap5mQmC949/LMO0oDuWIcEuvJHYzWP+sXWd1xovbSkILXOFqo+mF7h56QOk0KmhBVw", + "V8Dd04PthIPJYc/LJaSaXe5IA/v7GniQYjT3Rgj7hHCQFcbqMFqsIrK/ia0BaCxLaxSeoJrfrcEZSgK5", + "gO09RVrUEK2cPff3yk0KSCAGkDskhkSEioUhWKup8wwzVVMGYsGH/dju0JTiGnx0J0hqvOFcniTNjdsk", + "Oo5MGX/1Y9Jcpute6b8YETqUKdZ/NGBY2H6FbzSo+kE8X4AiVEnJab9M35UrYIFJe7WjwJeyAOV/8xm6", + "dpacXUD4LBC6Za6ozHyLqJ3BmzCSkfuol97lC953gV7WM7MmSLOf0BMp/IShuGkujPyVDMUzt+Mi66CC", + "e8pGf9iS3xjxaeBagnTPp6GwlwsFiRY+qHMMjjFUuLfvb4IENVhs0QI3WALlXVPjBYvOUix5Ql1kS7hA", + "IqGgBjoZVGIZnnMM2S/td5/B4ouO7jSn1PS6u9C8D89lqofEkOqXxN2WuzNjbmJZYZzb52tVrCwLN6gM", + "Tf+lFFmV2gs6PBi19Wly0aMRVhI1SqT9VXYE4iC98AK2h1bi9xX6/Q6GQFvJyYIepPN3NvlObU0qBvfq", + "TsD7kmaa+awUIk8GLPun/VoyXYq/YOkFZMTcFD6MbeCREnIfDcq16/ZqvfW1U8oSOGQPDgg54TZw2Htx", + "28WMO5Pze3ps/g3OmlW2vJOzIB184PEITCy8JG/Jzfww4zxMgWF1t5zKDrKjUslmoI6NpFeRJ3sOpqqg", + "fb9q9xmVhqgsFDGZpHkhZEdQSB0P0ryB0MSE9B8uGnmJ47xDRLYd+uIdMHs/t+GA7L66sdMiGoA5ATm9", + "4SNGpchrIu119fA08KiTFgVL+8O1UPOHcOcPOuF3vJUSWV9NeO4pF58aNYCrqG9s3BVln6taTHVI1YVj", + "o/sULVKa7HRRtWCY5KjaF4wlPv+W0AiST2uJad56nZN1XsrxRb0sjafUakxGW6csryS4VB37TlXn/YqS", + "6rXnoKZ5X68xMjIozKOxbyBQZbVwbw1wj2R1ryZRJjlcQstz5/KHqjQFpdglhA9s2c4kAyjRNtaV2GIu", + "qZC1d65xt/YkcGpMwW70XreItTtFdlzaURFjwxN7TNTUo2QgumRZRVv4U7d4+2jo2aMIG/awTuQUezOJ", + "+OLGWMROJzLSfPRc8rgPOUxfqxVynC2rDXeWCJuTrUp6xYcF2IjNo3Zs3n4dBAcjqpNOOhA0iy87JXXd", + "ydj16DLfPPGbGZtXqDq6XPBaVD3mwLu5Nf3cRm8aJMo4Td6s3s+kk9R3v0WYTfBC1LiROCwH1uQZSOvF", + "RaOS51fdw/BDw8emvVXlO+wAL/QdBK9VeTXOgfOFkwF+qJESLGWQElrL3+WOcAtsGH+wRfbWM8u0xRlt", + "IGl7XwJfk3pZu3CGnpDrenqw9pfgWA+x7yFS6NXHZxVCwjEHXV7S/PN7ebAo3Aniw73JHV9o6CYIkWxR", + "qW4WkfuaTpo7cAnc3dT8LXql/g5mj6JKghvK3Si1lOWd2MgyaW6YeP0YJw5JrnBMG7/x6CuycNmEpYSU", + "qe5NdeUrvtdWcXwApXmqfdwMv2udPwt9CzJeesGPvGmqR6PKteINhM0R/cJMZeDkRqk8Rn09sojgL8aj", + "wrI+O66Li1Zgh63G34lYFhLuOMAjCNXcM8CjX7Bo6vJsEIO5dCoF/XVOvq1buI1c1M3apkYn9ZE7VmJ4", + "SlBRvHK46Y5RTRYhWHafIKjk10e/EglLfFdLkIcPcYKHD+eu6a+P25/NcX74MP4i/OeKZ7I4cmO4eWMU", + "8/NQhovN4hhIpursR8XybBdhtFLjmpfpMPnrF5cc+0XexvvFup37R9W9T7RPJGV3ExAxkbW2Jg+mCpLe", + "JuS7uW6R7DY06aaVZHqLNbu8Osd+iUZefVcHNrjAmLrKi7v7tLiAuupbEwZRKX+7fidojveRkakxjlXj", + "K+DfbGhR5uAOytf3Fn+BJ399mh09efSXxV+Pnh2l8PTZ86Mj+vwpffT8ySN4/NdnT4/g0fKr54vH2eOn", + "jxdPHz/96tnz9MnTR4unXz3/yz3DhwzIFtCZrxAx+7/4gGRy8vY0OTfANjihJasf/zdk7F/BoimeRCgo", + "y2fH/qf/7U/YQSqKZnj/68wloM/WWpfq+PDw6urqIOxyuEK/Z6JFla4P/Tz9R9ffntYGY6uU447a/DBv", + "bPGkcILf3n1zdk5O3p4eBI/6Hs+ODo4OHuGbryVwWrLZ8ewJ/oSnZ437fuiIbXb86Xo+O1wDzTFMyPxR", + "gJYs9Z8k0Gzr/q+u6GoF8sA9DWZ+unx86MWKw0/O/3s99u0wrLJ/+KnlJs929MQq3IeffHGp8dat6k0u", + "PCDoMBGKsWaHC8xZn9oUVNB4eCmobKjDTyguD/5+6BJ54x9RbbHn4dDHksRbtrD0SW8MrJ0eKdXpuioP", + "P+F/kD4DsGzYfB/cDC4LkYGfTyyXttbd2OfDT/bfYBjYlCCZkdsw+Mb9arNnD+0Dv/2ftzyN/tiHsvfQ", + "zAqiPh5MwaX4GGq83PUMj5s9qacZMlDdDVazVeutOQVP4eOjo70e4Jvm+u6GyPWvpD7vGVvZ9Xz2dE9A", + "R403rSj6CDAvaEa8uw3nfvT55j7lGPFmmCqxlwZC8PTzQdB+IuB72JI3QpNvUbu5ns+efc6dOOVG1qI5", + "wZZBBbD+EfmJX3BxxX1LI21URUHldvLx0XSl0C0r2SV1sl7waszsI8YBWBds+6idZFmP6K3UBUq/EHh9", + "DWGsUKvS5cw1SGuETsbNEvpaa/8Z3jVEok1tTJT35XCRwSwUB7Ws4PqWPKEtdxsQTiNGGLQm4jsuS1+z", + "LwA1GjrZ9ZbbkSc9RNoZvH53oVqgwVrwP3nKnzyl5inPjp58vunPQF6yFMg5FKWQVLJ8S37idcWDG/O4", + "kyyLxpu3j/5OHmcU+lRksAKeOAaWLES29VVdWxNcgNUve4LM4af20wxWvpxlkIOOxtKa3+tHc/uLWGzJ", + "6auehGO7dTnviy02DZ48OH7/ySpoRvto9KcuiD3OGFbb7/Kmj3GuOUb2ZiEroYnFQuYW9Scj+pMR3Uq4", + "mXx4psg3Ue3D1hOivTt77ksDxYrCUd0HZYqO8kWP751sfF//iek7Nm4fMhJ8sC79Lpr/ZBF/sojbsYjv", + "IHIY8dQ6phEhuv30oakMA+Ohsu4DaOij8M2rnEqiYKqZ4wRHdMaNz8E1PrdSF8WV1ekob96IjGzg3ep5", + "f7K8P1neH4flnexmNG3B5Naa0QVsC1rW+pBaVzoTV4GbAmGxIUR9O3D9JHPr78MrynSyFNJlgeIDAf3O", + "Gmh+6OqbdX5tSor0vmCdlODHMKI0+uth/f5K9GPXwxH76iz8A418GL7/3Hg7Q+8hsvbab/j+o2HLWN3b", + "cf3GGXZ8eIiZVWuh9OHsev6p4ygLP36sSeBTfVc4Urj+eP0/AQAA//9/miZMgs4AAA==", } // 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 a7111270cd..a6522fea42 100644 --- a/daemon/algod/api/server/v2/generated/participating/public/routes.go +++ b/daemon/algod/api/server/v2/generated/participating/public/routes.go @@ -177,185 +177,186 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "Ukh2jdlI33/8fwEAAP//LOKPVrjbAAA=", + "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", + "fQ2zr0IyKSW7xqSm7z/+vwAAAP//kA6nBhHdAAA=", } // 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 f7bd4799d1..3e774a8309 100644 --- a/daemon/algod/api/server/v2/handlers.go +++ b/daemon/algod/api/server/v2/handlers.go @@ -115,6 +115,8 @@ type NodeInterface interface { SetSyncRound(rnd uint64) error GetSyncRound() uint64 UnsetSyncRound() + GetBlockTimeStampOffset() (*int64, error) + SetBlockTimeStampOffset(int64) error } func roundToPtrOrNil(value basics.Round) *uint64 { @@ -1709,3 +1711,33 @@ func (v2 *Handlers) TealDisassemble(ctx echo.Context) error { func (v2 *Handlers) ExperimentalCheck(ctx echo.Context) error { return ctx.JSON(http.StatusOK, true) } + +// GetBlockTimeStampOffset gets the timestamp offset. +// This is only available in dev mode. +// (GET /v2/devmode/blocks/offset) +func (v2 *Handlers) GetBlockTimeStampOffset(ctx echo.Context) error { + offset, err := v2.Node.GetBlockTimeStampOffset() + if err != nil { + err = fmt.Errorf("cannot get block timestamp offset because we are not in dev mode") + return badRequest(ctx, err, fmt.Sprintf(errFailedRetrievingTimeStampOffset, err), v2.Log) + } else if offset == nil { + err = fmt.Errorf("block timestamp offset was never set, using real clock for timestamps") + return notFound(ctx, err, fmt.Sprintf(errFailedRetrievingTimeStampOffset, err), v2.Log) + } + return ctx.JSON(http.StatusOK, model.GetBlockTimeStampOffsetResponse{Offset: uint64(*offset)}) +} + +// SetBlockTimeStampOffset sets the timestamp offset. +// This is only available in dev mode. +// (POST /v2/devmode/blocks/offset/{offset}) +func (v2 *Handlers) SetBlockTimeStampOffset(ctx echo.Context, offset uint64) error { + if offset > math.MaxInt64 { + err := fmt.Errorf("block timestamp offset cannot be larger than max int64 value") + return badRequest(ctx, err, fmt.Sprintf(errFailedSettingTimeStampOffset, err), v2.Log) + } + err := v2.Node.SetBlockTimeStampOffset(int64(offset)) + if err != nil { + return badRequest(ctx, err, fmt.Sprintf(errFailedSettingTimeStampOffset, err), v2.Log) + } + return ctx.NoContent(http.StatusOK) +} diff --git a/daemon/algod/api/server/v2/test/handlers_resources_test.go b/daemon/algod/api/server/v2/test/handlers_resources_test.go index f57121473d..2b013f69b2 100644 --- a/daemon/algod/api/server/v2/test/handlers_resources_test.go +++ b/daemon/algod/api/server/v2/test/handlers_resources_test.go @@ -229,7 +229,7 @@ func setupTestForLargeResources(t *testing.T, acctSize, maxResults int, accountM acctData = accountMaker(acctSize) ml.accounts[fakeAddr] = acctData - mockNode := makeMockNode(&ml, t.Name(), nil, cannedStatusReportGolden) + mockNode := makeMockNode(&ml, t.Name(), nil, cannedStatusReportGolden, false) mockNode.config.MaxAPIResourcesPerAccount = uint64(maxResults) dummyShutdownChan := make(chan struct{}) handlers = v2.Handlers{ diff --git a/daemon/algod/api/server/v2/test/handlers_test.go b/daemon/algod/api/server/v2/test/handlers_test.go index b12af6f1c1..0687279a3a 100644 --- a/daemon/algod/api/server/v2/test/handlers_test.go +++ b/daemon/algod/api/server/v2/test/handlers_test.go @@ -23,6 +23,7 @@ import ( "errors" "fmt" "io" + "math" "net/http" "net/http/httptest" "strings" @@ -64,12 +65,12 @@ import ( const stateProofInterval = uint64(256) -func setupTestForMethodGet(t *testing.T, status node.StatusReport) (v2.Handlers, echo.Context, *httptest.ResponseRecorder, []account.Root, []transactions.SignedTxn, func()) { +func setupMockNodeForMethodGet(t *testing.T, status node.StatusReport, devmode bool) (v2.Handlers, echo.Context, *httptest.ResponseRecorder, []account.Root, []transactions.SignedTxn, func()) { numAccounts := 1 numTransactions := 1 offlineAccounts := true mockLedger, rootkeys, _, stxns, releasefunc := testingenv(t, numAccounts, numTransactions, offlineAccounts) - mockNode := makeMockNode(mockLedger, t.Name(), nil, status) + mockNode := makeMockNode(mockLedger, t.Name(), nil, status, devmode) dummyShutdownChan := make(chan struct{}) handler := v2.Handlers{ Node: mockNode, @@ -83,6 +84,10 @@ func setupTestForMethodGet(t *testing.T, status node.StatusReport) (v2.Handlers, return handler, c, rec, rootkeys, stxns, releasefunc } +func setupTestForMethodGet(t *testing.T, status node.StatusReport) (v2.Handlers, echo.Context, *httptest.ResponseRecorder, []account.Root, []transactions.SignedTxn, func()) { + return setupMockNodeForMethodGet(t, status, false) +} + func numOrNil(n uint64) *uint64 { if n == 0 { return nil @@ -187,7 +192,7 @@ func TestSyncRound(t *testing.T) { numTransactions := 1 offlineAccounts := true mockLedger, _, _, _, releasefunc := testingenv(t, numAccounts, numTransactions, offlineAccounts) - mockNode := makeMockNode(mockLedger, t.Name(), nil, cannedStatusReportGolden) + mockNode := makeMockNode(mockLedger, t.Name(), nil, cannedStatusReportGolden, false) dummyShutdownChan := make(chan struct{}) handler := v2.Handlers{ Node: mockNode, @@ -242,7 +247,6 @@ func TestSyncRound(t *testing.T) { require.NoError(t, err) require.Equal(t, 200, rec.Code) mockCall.Unset() - c, rec = newReq(t) mock.AssertExpectationsForObjects(t, mockNode) } @@ -722,7 +726,7 @@ func prepareTransactionTest(t *testing.T, txnToUse int, txnPrep func(transaction offlineAccounts := true mockLedger, _, _, stxns, releasefunc := testingenv(t, numAccounts, numTransactions, offlineAccounts) dummyShutdownChan := make(chan struct{}) - mockNode := makeMockNode(mockLedger, t.Name(), nil, cannedStatusReportGolden) + mockNode := makeMockNode(mockLedger, t.Name(), nil, cannedStatusReportGolden, false) handler = v2.Handlers{ Node: mockNode, @@ -915,7 +919,7 @@ func TestSimulateTransaction(t *testing.T) { mockLedger, roots, _, _, releasefunc := testingenvWithBalances(t, 999_998, 999_999, numAccounts, 1, offlineAccounts) defer releasefunc() dummyShutdownChan := make(chan struct{}) - mockNode := makeMockNode(mockLedger, t.Name(), nil, cannedStatusReportGolden) + mockNode := makeMockNode(mockLedger, t.Name(), nil, cannedStatusReportGolden, false) handler := v2.Handlers{ Node: mockNode, Log: logging.Base(), @@ -1078,7 +1082,7 @@ func TestSimulateTransactionVerificationFailure(t *testing.T) { mockLedger, roots, _, _, releasefunc := testingenv(t, numAccounts, 1, offlineAccounts) defer releasefunc() dummyShutdownChan := make(chan struct{}) - mockNode := makeMockNode(mockLedger, t.Name(), nil, cannedStatusReportGolden) + mockNode := makeMockNode(mockLedger, t.Name(), nil, cannedStatusReportGolden, false) handler := v2.Handlers{ Node: mockNode, Log: logging.Base(), @@ -1128,7 +1132,7 @@ func TestSimulateTransactionMultipleGroups(t *testing.T) { mockLedger, roots, _, _, releasefunc := testingenv(t, numAccounts, 1, offlineAccounts) defer releasefunc() dummyShutdownChan := make(chan struct{}) - mockNode := makeMockNode(mockLedger, t.Name(), nil, cannedStatusReportGolden) + mockNode := makeMockNode(mockLedger, t.Name(), nil, cannedStatusReportGolden, false) handler := v2.Handlers{ Node: mockNode, Log: logging.Base(), @@ -1192,7 +1196,7 @@ func startCatchupTest(t *testing.T, catchpoint string, nodeError error, expected mockLedger, _, _, _, releasefunc := testingenv(t, numAccounts, numTransactions, offlineAccounts) defer releasefunc() dummyShutdownChan := make(chan struct{}) - mockNode := makeMockNode(mockLedger, t.Name(), nodeError, cannedStatusReportGolden) + mockNode := makeMockNode(mockLedger, t.Name(), nodeError, cannedStatusReportGolden, false) handler := v2.Handlers{Node: mockNode, Log: logging.Base(), Shutdown: dummyShutdownChan} e := echo.New() req := httptest.NewRequest(http.MethodPost, "/", nil) @@ -1233,7 +1237,7 @@ func abortCatchupTest(t *testing.T, catchpoint string, expectedCode int) { mockLedger, _, _, _, releasefunc := testingenv(t, numAccounts, numTransactions, offlineAccounts) defer releasefunc() dummyShutdownChan := make(chan struct{}) - mockNode := makeMockNode(mockLedger, t.Name(), nil, cannedStatusReportGolden) + mockNode := makeMockNode(mockLedger, t.Name(), nil, cannedStatusReportGolden, false) handler := v2.Handlers{ Node: mockNode, Log: logging.Base(), @@ -1268,7 +1272,7 @@ func tealCompileTest(t *testing.T, bytesToUse []byte, expectedCode int, mockLedger, _, _, _, releasefunc := testingenv(t, numAccounts, numTransactions, offlineAccounts) defer releasefunc() dummyShutdownChan := make(chan struct{}) - mockNode := makeMockNode(mockLedger, t.Name(), nil, cannedStatusReportGolden) + mockNode := makeMockNode(mockLedger, t.Name(), nil, cannedStatusReportGolden, false) mockNode.config.EnableDeveloperAPI = enableDeveloperAPI handler := v2.Handlers{ Node: mockNode, @@ -1339,7 +1343,7 @@ func tealDisassembleTest(t *testing.T, program []byte, expectedCode int, mockLedger, _, _, _, releasefunc := testingenv(t, numAccounts, numTransactions, offlineAccounts) defer releasefunc() dummyShutdownChan := make(chan struct{}) - mockNode := makeMockNode(mockLedger, t.Name(), nil, cannedStatusReportGolden) + mockNode := makeMockNode(mockLedger, t.Name(), nil, cannedStatusReportGolden, false) mockNode.config.EnableDeveloperAPI = enableDeveloperAPI handler := v2.Handlers{ Node: mockNode, @@ -1407,7 +1411,7 @@ func tealDryrunTest( mockLedger, _, _, _, releasefunc := testingenv(t, numAccounts, numTransactions, offlineAccounts) defer releasefunc() dummyShutdownChan := make(chan struct{}) - mockNode := makeMockNode(mockLedger, t.Name(), nil, cannedStatusReportGolden) + mockNode := makeMockNode(mockLedger, t.Name(), nil, cannedStatusReportGolden, false) mockNode.config.EnableDeveloperAPI = enableDeveloperAPI handler := v2.Handlers{ Node: mockNode, @@ -1536,7 +1540,7 @@ func TestAppendParticipationKeys(t *testing.T) { mockLedger, _, _, _, releasefunc := testingenv(t, 1, 1, true) defer releasefunc() - mockNode := makeMockNode(mockLedger, t.Name(), nil, cannedStatusReportGolden) + mockNode := makeMockNode(mockLedger, t.Name(), nil, cannedStatusReportGolden, false) handler := v2.Handlers{ Node: mockNode, Log: logging.Base(), @@ -1620,7 +1624,7 @@ func TestAppendParticipationKeys(t *testing.T) { t.Run("Internal error", func(t *testing.T) { // Create mock node with an error. expectedErr := errors.New("expected error") - mockNode := makeMockNode(mockLedger, t.Name(), expectedErr, cannedStatusReportGolden) + mockNode := makeMockNode(mockLedger, t.Name(), expectedErr, cannedStatusReportGolden, false) handler := v2.Handlers{ Node: mockNode, Log: logging.Base(), @@ -2012,3 +2016,59 @@ func TestExperimentalCheck(t *testing.T) { require.Equal(t, 200, rec.Code) require.Equal(t, "true\n", string(rec.Body.Bytes())) } + +func TestTimestampOffsetNotInDevMode(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + handler, c, rec, _, _, releasefunc := setupTestForMethodGet(t, cannedStatusReportGolden) + defer releasefunc() + + // TestGetBlockTimeStampOffset 400 - offset is not set and mock node is + // not in dev mode + err := handler.GetBlockTimeStampOffset(c) + require.NoError(t, err) + require.Equal(t, 400, rec.Code) + require.Equal(t, "{\"message\":\"failed retrieving timestamp offset from node: cannot get block timestamp offset because we are not in dev mode\"}\n", rec.Body.String()) + c, rec = newReq(t) + + // TestSetBlockTimeStampOffset 400 - cannot set timestamp offset when not + // in dev mode + err = handler.SetBlockTimeStampOffset(c, 1) + require.NoError(t, err) + require.Equal(t, 400, rec.Code) + require.Equal(t, "{\"message\":\"failed to set timestamp offset on the node: cannot set block timestamp when not in dev mode\"}\n", rec.Body.String()) +} + +func TestTimestampOffsetInDevMode(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + handler, c, rec, _, _, releasefunc := setupMockNodeForMethodGet(t, cannedStatusReportGolden, true) + defer releasefunc() + + // TestGetBlockTimeStampOffset 404 + err := handler.GetBlockTimeStampOffset(c) + require.NoError(t, err) + require.Equal(t, 404, rec.Code) + require.Equal(t, "{\"message\":\"failed retrieving timestamp offset from node: block timestamp offset was never set, using real clock for timestamps\"}\n", rec.Body.String()) + c, rec = newReq(t) + + // TestSetBlockTimeStampOffset 200 + err = handler.SetBlockTimeStampOffset(c, 1) + require.NoError(t, err) + require.Equal(t, 200, rec.Code) + c, rec = newReq(t) + + // TestGetBlockTimeStampOffset 200 + err = handler.GetBlockTimeStampOffset(c) + require.NoError(t, err) + require.Equal(t, 200, rec.Code) + c, rec = newReq(t) + + // TestSetBlockTimeStampOffset 400 + err = handler.SetBlockTimeStampOffset(c, math.MaxUint64) + require.NoError(t, err) + require.Equal(t, 400, rec.Code) + require.Equal(t, "{\"message\":\"failed to set timestamp offset on the node: block timestamp offset cannot be larger than max int64 value\"}\n", rec.Body.String()) +} diff --git a/daemon/algod/api/server/v2/test/helpers.go b/daemon/algod/api/server/v2/test/helpers.go index d0409eadac..7f9877ad89 100644 --- a/daemon/algod/api/server/v2/test/helpers.go +++ b/daemon/algod/api/server/v2/test/helpers.go @@ -90,14 +90,16 @@ var txnPoolGolden = make([]transactions.SignedTxn, 2) // package `data` and package `node`, which themselves import `mocks` type mockNode struct { mock.Mock - ledger v2.LedgerForAPI - genesisID string - config config.Local - err error - id account.ParticipationID - keys account.StateProofKeys - usertxns map[basics.Address][]node.TxnWithStatus - status node.StatusReport + ledger v2.LedgerForAPI + genesisID string + config config.Local + err error + id account.ParticipationID + keys account.StateProofKeys + usertxns map[basics.Address][]node.TxnWithStatus + status node.StatusReport + devmode bool + timestampOffset *int64 } func (m *mockNode) InstallParticipationKey(partKeyBinary []byte) (account.ParticipationID, error) { @@ -135,7 +137,7 @@ func (m *mockNode) AppendParticipationKeys(id account.ParticipationID, keys acco return m.err } -func makeMockNode(ledger v2.LedgerForAPI, genesisID string, nodeError error, status node.StatusReport) *mockNode { +func makeMockNode(ledger v2.LedgerForAPI, genesisID string, nodeError error, status node.StatusReport, devMode bool) *mockNode { return &mockNode{ ledger: ledger, genesisID: genesisID, @@ -143,6 +145,7 @@ func makeMockNode(ledger v2.LedgerForAPI, genesisID string, nodeError error, sta err: nodeError, usertxns: map[basics.Address][]node.TxnWithStatus{}, status: status, + devmode: devMode, } } @@ -238,6 +241,23 @@ func (m *mockNode) AbortCatchup(catchpoint string) error { return m.err } +func (m *mockNode) SetBlockTimeStampOffset(offset int64) error { + if !m.devmode { + return fmt.Errorf("cannot set block timestamp when not in dev mode") + } + m.timestampOffset = &offset + return nil +} + +func (m *mockNode) GetBlockTimeStampOffset() (*int64, error) { + if !m.devmode { + return nil, fmt.Errorf("cannot get block timestamp when not in dev mode") + } else if m.timestampOffset == nil { + return nil, nil + } + return m.timestampOffset, nil +} + ////// mock ledger testing environment follows var sinkAddr = basics.Address{0x7, 0xda, 0xcb, 0x4b, 0x6d, 0x9e, 0xd1, 0x41, 0xb1, 0x75, 0x76, 0xbd, 0x45, 0x9a, 0xe6, 0x42, 0x1d, 0x48, 0x6d, 0xa3, 0xd4, 0xef, 0x22, 0x47, 0xc4, 0x9, 0xa3, 0x96, 0xb8, 0x2e, 0xa2, 0x21} diff --git a/node/follower_node.go b/node/follower_node.go index b2e6b58295..e04cbe2eb8 100644 --- a/node/follower_node.go +++ b/node/follower_node.go @@ -447,3 +447,15 @@ func (node *AlgorandFollowerNode) GetSyncRound() uint64 { func (node *AlgorandFollowerNode) UnsetSyncRound() { node.catchupService.UnsetDisableSyncRound() } + +// SetBlockTimeStampOffset sets a timestamp offset in the block header. +// This is only available in dev mode. +func (node *AlgorandFollowerNode) SetBlockTimeStampOffset(offset int64) error { + return fmt.Errorf("cannot set block timestamp offset in follower mode") +} + +// GetBlockTimeStampOffset gets a timestamp offset. +// This is only available in dev mode. +func (node *AlgorandFollowerNode) GetBlockTimeStampOffset() (*int64, error) { + return nil, fmt.Errorf("cannot get block timestamp offset in follower mode") +} diff --git a/node/node.go b/node/node.go index 0009e02128..d99d455774 100644 --- a/node/node.go +++ b/node/node.go @@ -21,6 +21,7 @@ import ( "context" "errors" "fmt" + "math" "os" "path/filepath" "strings" @@ -129,10 +130,11 @@ type AlgorandFullNode struct { indexer *indexer.Indexer - rootDir string - genesisID string - genesisHash crypto.Digest - devMode bool // is this node operates in a developer mode ? ( benign agreement, broadcasting transaction generates a new block ) + rootDir string + genesisID string + genesisHash crypto.Digest + devMode bool // is this node operating in a developer mode ? ( benign agreement, broadcasting transaction generates a new block ) + timestampOffset *int64 log logging.Logger @@ -469,6 +471,24 @@ func (node *AlgorandFullNode) writeDevmodeBlock() (err error) { return } + // Make a new validated block. + prevRound := vb.Block().Round() - 1 + prev, err := node.ledger.BlockHdr(prevRound) + if err != nil { + return err + } + + blk := vb.Block() + + // Set block timestamp based on offset, if set. + // Make sure block timestamp is not greater than MaxInt64. + if node.timestampOffset != nil && *node.timestampOffset < math.MaxInt64-prev.TimeStamp { + blk.TimeStamp = prev.TimeStamp + *node.timestampOffset + } + blk.BlockHeader.Seed = committee.Seed(prev.Hash()) + vb2 := ledgercore.MakeValidatedBlock(blk, vb.Delta()) + vb = &vb2 + // add the newly generated block to the ledger err = node.ledger.AddValidatedBlock(*vb, agreement.Certificate{Round: vb.Block().Round()}) return err @@ -1418,3 +1438,22 @@ func (node *AlgorandFullNode) GetSyncRound() uint64 { // UnsetSyncRound no-ops func (node *AlgorandFullNode) UnsetSyncRound() { } + +// SetBlockTimeStampOffset sets a timestamp offset in the block header. +// This is only available in dev mode. +func (node *AlgorandFullNode) SetBlockTimeStampOffset(offset int64) error { + if node.devMode { + node.timestampOffset = &offset + return nil + } + return fmt.Errorf("cannot set block timestamp offset when not in dev mode") +} + +// GetBlockTimeStampOffset gets a timestamp offset. +// This is only available in dev mode. +func (node *AlgorandFullNode) GetBlockTimeStampOffset() (*int64, error) { + if node.devMode { + return node.timestampOffset, nil + } + return nil, fmt.Errorf("cannot get block timestamp offset when not in dev mode") +} From c798ffd87626d903ba6aac37e2d33a4a5f059130 Mon Sep 17 00:00:00 2001 From: shiqizng <80276844+shiqizng@users.noreply.github.com> Date: Thu, 20 Apr 2023 15:56:25 -0400 Subject: [PATCH 056/125] tools: update block-generator to use conduit binary (#5306) --- tools/block-generator/generator/generate.go | 34 ++++- .../generator/generate_test.go | 1 + .../block-generator/generator/server_test.go | 24 +-- tools/block-generator/metrics/metrics.go | 42 ----- tools/block-generator/run_generator.sh | 56 ------- tools/block-generator/run_postgres.sh | 2 +- tools/block-generator/run_runner.sh | 13 +- tools/block-generator/run_tests.sh | 19 ++- .../runner/metrics_collector.go | 25 +++ tools/block-generator/runner/run.go | 144 ++++++++++++++---- tools/block-generator/runner/runner.go | 8 +- .../runner/template/conduit.yml.tmpl | 61 ++++++++ 12 files changed, 265 insertions(+), 164 deletions(-) delete mode 100644 tools/block-generator/metrics/metrics.go delete mode 100755 tools/block-generator/run_generator.sh create mode 100644 tools/block-generator/runner/template/conduit.yml.tmpl diff --git a/tools/block-generator/generator/generate.go b/tools/block-generator/generator/generate.go index f1961587bb..a8c245a488 100644 --- a/tools/block-generator/generator/generate.go +++ b/tools/block-generator/generator/generate.go @@ -72,7 +72,7 @@ type GenerationConfig struct { GenesisAccountInitialBalance uint64 `yaml:"genesis_account_balance"` // Block generation - TxnPerBlock uint64 `mapstructure:"tx_per_block"` + TxnPerBlock uint64 `yaml:"tx_per_block"` // TX Distribution PaymentTransactionFraction float32 `yaml:"tx_pay_fraction"` @@ -120,7 +120,7 @@ func MakeGenerator(config GenerationConfig) (Generator, error) { genesisHash: [32]byte{}, genesisID: "blockgen-test", prevBlockHash: "", - round: 1, + round: 0, txnCounter: 0, timestamp: 0, rewardsLevel: 0, @@ -312,7 +312,9 @@ func (g *generator) WriteGenesis(output io.Writer) error { FeeSink: g.feeSink.String(), Timestamp: g.timestamp, } - return json.NewEncoder(output).Encode(gen) + + _, err := output.Write(protocol.EncodeJSON(gen)) + return err } func getTransactionOptions() []interface{} { @@ -357,12 +359,28 @@ func (g *generator) finishRound(txnCount uint64) { // WriteBlock generates a block full of new transactions and writes it to the writer. func (g *generator) WriteBlock(output io.Writer, round uint64) error { + if round != g.round { fmt.Printf("Generator only supports sequential block access. Expected %d but received request for %d.\n", g.round, round) } numTxnForBlock := g.txnForRound(round) + // return genesis block + if round == 0 { + // write the msgpack bytes for a block + block, err := rpcs.RawBlockBytes(g.ledger, basics.Round(round)) + if err != nil { + return err + } + _, err = output.Write(block) + if err != nil { + return err + } + g.finishRound(numTxnForBlock) + return nil + } + header := bookkeeping.BlockHeader{ Round: basics.Round(g.round), Branch: bookkeeping.BlockHash{}, @@ -414,15 +432,19 @@ func (g *generator) WriteBlock(output io.Writer, round uint64) error { Certificate: agreement.Certificate{}, } - err := json.NewEncoder(output).Encode(cert) + err := g.ledger.AddBlock(cert.Block, cert.Certificate) + if err != nil { + return err + } + // write the msgpack bytes for a block + block, err := rpcs.RawBlockBytes(g.ledger, basics.Round(round)) if err != nil { return err } - err = g.ledger.AddBlock(cert.Block, agreement.Certificate{}) + _, err = output.Write(block) if err != nil { return err } - g.ledger.WaitForCommit(basics.Round(g.round)) g.finishRound(numTxnForBlock) return nil } diff --git a/tools/block-generator/generator/generate_test.go b/tools/block-generator/generator/generate_test.go index 78ca9e0323..e57d3f8006 100644 --- a/tools/block-generator/generator/generate_test.go +++ b/tools/block-generator/generator/generate_test.go @@ -203,6 +203,7 @@ func TestWriteRound(t *testing.T) { g := makePrivateGenerator(t) var data []byte writer := bytes.NewBuffer(data) + g.WriteBlock(writer, 0) g.WriteBlock(writer, 1) var block rpcs.EncodedBlockCert protocol.Decode(data, &block) diff --git a/tools/block-generator/generator/server_test.go b/tools/block-generator/generator/server_test.go index 6e21d77586..7007db00f0 100644 --- a/tools/block-generator/generator/server_test.go +++ b/tools/block-generator/generator/server_test.go @@ -53,61 +53,61 @@ func TestParseURL(t *testing.T) { var testcases = []struct { name string url string - expectedRound string + expectedParam string err string }{ { name: "no block", url: "/v2/blocks/", - expectedRound: "", + expectedParam: "", err: "invalid request path, /v2/blocks/", }, { name: "normal one digit", url: fmt.Sprintf("%s1", blockQueryPrefix), - expectedRound: "1", + expectedParam: "1", err: "", }, { name: "normal long number", url: fmt.Sprintf("%s12345678", blockQueryPrefix), - expectedRound: "12345678", + expectedParam: "12345678", err: "", }, { name: "with query parameters", url: fmt.Sprintf("%s1234?pretty", blockQueryPrefix), - expectedRound: "1234", + expectedParam: "1234", err: "", }, { name: "with query parameters", url: fmt.Sprintf("%s1234?pretty", blockQueryPrefix), - expectedRound: "1234", + expectedParam: "1234", err: "", }, { name: "no deltas", url: "/v2/deltas/", - expectedRound: "", + expectedParam: "", err: "invalid request path, /v2/deltas/", }, { name: "deltas", url: fmt.Sprintf("%s123?Format=msgp", deltaQueryPrefix), - expectedRound: "123", + expectedParam: "123", err: "", }, { name: "no account", url: "/v2/accounts/", - expectedRound: "", + expectedParam: "", err: "invalid request path, /v2/accounts/", }, { name: "accounts", url: fmt.Sprintf("%sAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGFFWAF4", accountQueryPrefix), - expectedRound: "AIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGFFWAF4", + expectedParam: "AIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGFFWAF4", err: "", }, } @@ -117,9 +117,9 @@ func TestParseURL(t *testing.T) { round, err := parseURL(testcase.url) if len(testcase.err) == 0 { msg := fmt.Sprintf("Unexpected error parsing '%s', expected round '%s' received error: %v", - testcase.url, testcase.expectedRound, err) + testcase.url, testcase.expectedParam, err) require.NoError(t, err, msg) - assert.Equal(t, testcase.expectedRound, round) + assert.Equal(t, testcase.expectedParam, round) } else { require.Error(t, err, fmt.Sprintf("Expected an error containing: %s", testcase.err)) require.True(t, strings.Contains(err.Error(), testcase.err)) diff --git a/tools/block-generator/metrics/metrics.go b/tools/block-generator/metrics/metrics.go deleted file mode 100644 index 6c60fbabc2..0000000000 --- a/tools/block-generator/metrics/metrics.go +++ /dev/null @@ -1,42 +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 . - -package metrics - -// Prometheus metrics collected in Conduit. -const ( - BlockImportTimeName = "import_time_sec" - ImportedTxnsPerBlockName = "imported_tx_per_block" - ImportedRoundGaugeName = "imported_round" - GetAlgodRawBlockTimeName = "get_algod_raw_block_time_sec" - ImportedTxnsName = "imported_txns" - ImporterTimeName = "importer_time_sec" - ProcessorTimeName = "processor_time_sec" - ExporterTimeName = "exporter_time_sec" - PipelineRetryCountName = "pipeline_retry_count" -) - -// AllMetricNames is a reference for all the custom metric names. -var AllMetricNames = []string{ - BlockImportTimeName, - ImportedTxnsPerBlockName, - ImportedRoundGaugeName, - GetAlgodRawBlockTimeName, - ImporterTimeName, - ProcessorTimeName, - ExporterTimeName, - PipelineRetryCountName, -} diff --git a/tools/block-generator/run_generator.sh b/tools/block-generator/run_generator.sh deleted file mode 100755 index 47e87b2e0d..0000000000 --- a/tools/block-generator/run_generator.sh +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/env bash - -# Demonstrate how to run the generator and connect it to indexer. - -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 - - # Start postgres container... - docker run \ - -d \ - --name $POSTGRES_CONTAINER \ - -e POSTGRES_USER=algorand \ - -e POSTGRES_PASSWORD=algorand \ - -e PGPASSWORD=algorand \ - -p $POSTGRES_PORT:5432 \ - postgres - - sleep 5 - - docker exec -it $POSTGRES_CONTAINER psql -Ualgorand -c "create database $POSTGRES_DATABASE" -} - -function shutdown() { - docker rm -f $POSTGRES_CONTAINER > /dev/null 2>&1 || true - kill -9 $GENERATOR_PID -} - -trap shutdown EXIT - -echo "Building generator." -pushd $(dirname "$0") > /dev/null -go build -cd ../.. > /dev/null -echo "Building indexer." -make -popd -echo "Starting postgres container." -start_postgres -echo "Starting block generator (see generator.log)" -$(dirname "$0")/block-generator daemon --port 11111 --config "${CONFIG}" & -GENERATOR_PID=$! -echo "Starting indexer" -$(dirname "$0")/../../cmd/algorand-indexer/algorand-indexer daemon \ - -S localhost:8980 \ - --algod-net localhost:11111 \ - --algod-token security-is-our-number-one-priority \ - --metrics-mode VERBOSE \ - -P "host=localhost user=algorand password=algorand dbname=generator_db port=15432 sslmode=disable" diff --git a/tools/block-generator/run_postgres.sh b/tools/block-generator/run_postgres.sh index c6a967132b..4817961220 100755 --- a/tools/block-generator/run_postgres.sh +++ b/tools/block-generator/run_postgres.sh @@ -4,7 +4,7 @@ # in a debugger. Simply start this script and run with: # ./block-generator runner \ # -d 5s \ -# -i ./../algorand-indexer/algorand-indexer \ +# -i \ # -c "host=localhost user=algorand password=algorand dbname=algorand port=15432 sslmode=disable" \ # -r results \ # -s scenarios/config.payment.small.yml diff --git a/tools/block-generator/run_runner.sh b/tools/block-generator/run_runner.sh index d90749b96b..7f1205cae5 100755 --- a/tools/block-generator/run_runner.sh +++ b/tools/block-generator/run_runner.sh @@ -4,10 +4,16 @@ set -e +CONDUIT_BINARY=$1 +if [ -z "$CONDUIT_BINARY" ]; then + echo "path to conduit binary is required" + exit 1 +fi + POSTGRES_CONTAINER=generator-test-container POSTGRES_PORT=15432 POSTGRES_DATABASE=generator_db -CONFIG=${1:-"$(dirname $0)/test_config.yml"} +CONFIG=${2:-"$(dirname $0)/test_config.yml"} echo "Using config file: $CONFIG" function start_postgres() { @@ -38,15 +44,12 @@ rm -rf OUTPUT_RUN_RUNNER_TEST > /dev/null 2>&1 echo "Building generator." pushd $(dirname "$0") > /dev/null go build -cd ../.. > /dev/null -echo "Building indexer." -make popd echo "Starting postgres container." start_postgres echo "Starting test runner" $(dirname "$0")/block-generator runner \ - --indexer-binary ../algorand-indexer/algorand-indexer \ + --conduit-binary "$CONDUIT_BINARY" \ --report-directory OUTPUT_RUN_RUNNER_TEST \ --test-duration 30s \ --log-level trace \ diff --git a/tools/block-generator/run_tests.sh b/tools/block-generator/run_tests.sh index 0a1c6df49e..5d25d50af4 100755 --- a/tools/block-generator/run_tests.sh +++ b/tools/block-generator/run_tests.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash CONNECTION_STRING="" -INDEXER_BINARY="" +CONDUIT_BINARY="" REPORT_DIR="" DURATION="1h" LOG_LEVEL="error" @@ -17,7 +17,7 @@ help() { echo " -r|--report-dir directory where the report should be written." echo " -d|--duration test duration." echo " -l|--level log level to pass to Indexer." - echo " -g|--generator use a different indexer binary to run the generator." + echo " -g|--generator block-generator binary to run the generator." exit } @@ -34,7 +34,7 @@ while :; do shift ;; -i | --indexer) - INDEXER_BINARY="${2-}" + CONDUIT_BINARY="${2-}" shift ;; -r | --report-dir) @@ -66,7 +66,7 @@ if [ -z "$CONNECTION_STRING" ]; then exit 1 fi -if [ -z "$INDEXER_BINARY" ]; then +if [ -z "$CONDUIT_BINARY" ]; then echo "Missing required indexer binary parameter (-i / --indexer)." exit 1 fi @@ -77,18 +77,17 @@ if [ -z "$SCENARIOS" ]; then fi if [ -z "$GENERATOR_BINARY" ]; then - echo "Using indexer binary for generator, override with (-g / --generator)." - GENERATOR_BINARY="$INDEXER_BINARY" + echo "path to block-generator binary is required" + exit 1 fi -echo "Running with binary: $INDEXER_BINARY" +echo "Running with binary: $CONDUIT_BINARY" echo "Report directory: $REPORT_DIR" echo "Duration: $DURATION" echo "Log Level: $LOG_LEVEL" -"$GENERATOR_BINARY" \ - util block-generator runner \ - -i "$INDEXER_BINARY" \ +"$GENERATOR_BINARY" runner \ + -i "$CONDUIT_BINARY" \ -s "$SCENARIOS" \ -d "$DURATION" \ -c "$CONNECTION_STRING" \ diff --git a/tools/block-generator/runner/metrics_collector.go b/tools/block-generator/runner/metrics_collector.go index 327ca9e33b..394bf32cba 100644 --- a/tools/block-generator/runner/metrics_collector.go +++ b/tools/block-generator/runner/metrics_collector.go @@ -24,6 +24,31 @@ import ( "time" ) +// Prometheus metrics collected in Conduit. +const ( + BlockImportTimeName = "import_time_sec" + ImportedTxnsPerBlockName = "imported_tx_per_block" + ImportedRoundGaugeName = "imported_round" + GetAlgodRawBlockTimeName = "get_algod_raw_block_time_sec" + ImportedTxnsName = "imported_txns" + ImporterTimeName = "importer_time_sec" + ProcessorTimeName = "processor_time_sec" + ExporterTimeName = "exporter_time_sec" + PipelineRetryCountName = "pipeline_retry_count" +) + +// AllMetricNames is a reference for all the custom metric names. +var AllMetricNames = []string{ + BlockImportTimeName, + ImportedTxnsPerBlockName, + ImportedRoundGaugeName, + GetAlgodRawBlockTimeName, + ImporterTimeName, + ProcessorTimeName, + ExporterTimeName, + PipelineRetryCountName, +} + // MetricsCollector queries a /metrics endpoint for prometheus style metrics and saves metrics matching a pattern. type MetricsCollector struct { // MetricsURL where metrics can be queried. diff --git a/tools/block-generator/runner/run.go b/tools/block-generator/runner/run.go index 9604212da1..5e68cd49ae 100644 --- a/tools/block-generator/runner/run.go +++ b/tools/block-generator/runner/run.go @@ -17,29 +17,36 @@ package runner import ( + "bytes" "context" + // embed conduit template config file + _ "embed" "encoding/json" "fmt" "net/http" "os" + "os/exec" "path" "path/filepath" "strconv" "strings" + "text/template" "time" "github.com/algorand/go-algorand/tools/block-generator/generator" - "github.com/algorand/go-algorand/tools/block-generator/metrics" "github.com/algorand/go-algorand/tools/block-generator/util" "github.com/algorand/go-deadlock" ) +//go:embed template/conduit.yml.tmpl +var conduitConfigTmpl string + // 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. Path string - IndexerBinary string - IndexerPort uint64 + ConduitBinary string + MetricsPort uint64 PostgresConnectionString string CPUProfilePath string RunDuration time.Duration @@ -50,7 +57,15 @@ type Args struct { KeepDataDir bool } -// Run is a publi8c helper to run the tests. +type config struct { + LogLevel string + LogFile string + MetricsPort string + AlgodNet string + PostgresConnectionString string +} + +// Run is a public helper to run the tests. // 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 { @@ -86,8 +101,12 @@ func (r *Args) run() 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.indexer-log", baseNameNoExt)) + logfile := path.Join(r.ReportDirectory, fmt.Sprintf("%s.indexer-log", baseNameNoExt)) dataDir := path.Join(r.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) + } if !r.KeepDataDir { defer os.RemoveAll(dataDir) } @@ -103,7 +122,7 @@ func (r *Args) run() error { } // Start services algodNet := fmt.Sprintf("localhost:%d", 11112) - indexerNet := fmt.Sprintf("localhost:%d", r.IndexerPort) + metricsNet := fmt.Sprintf("localhost:%d", r.MetricsPort) generatorShutdownFunc, _ := startGenerator(r.Path, algodNet, blockMiddleware) defer func() { // Shutdown generator. @@ -112,16 +131,40 @@ func (r *Args) run() error { } }() - //indexerShutdownFunc, err := startIndexer(dataDir, logfile, r.LogLevel, r.IndexerBinary, algodNet, indexerNet, r.PostgresConnectionString, r.CPUProfilePath) - //if err != nil { - // return fmt.Errorf("failed to start indexer: %w", err) - //} - //defer func() { - // // Shutdown indexer - // if err := indexerShutdownFunc(); err != nil { - // fmt.Printf("Failed to shutdown indexer: %s\n", err) - // } - //}() + // get conduit config 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("creating conduit.yml: %v", err) + } + defer f.Close() + + conduitConfig := config{r.LogLevel, logfile, + fmt.Sprintf(":%d", r.MetricsPort), + algodNet, r.PostgresConnectionString, + } + + err = t.Execute(f, conduitConfig) + if err != nil { + return fmt.Errorf("execute template file: %v", err) + } + + // Start conduit + conduitShutdownFunc, err := startConduit(dataDir, r.ConduitBinary) + if err != nil { + return fmt.Errorf("failed to start Conduit: %w", err) + } + defer func() { + // Shutdown conduit + if err := conduitShutdownFunc(); err != nil { + fmt.Printf("Failed to shutdown Conduit: %s\n", err) + } + }() // Create the report file report, err := os.Create(reportfile) @@ -131,7 +174,20 @@ func (r *Args) run() error { defer report.Close() // Run the test, collecting results. - if err := r.runTest(report, indexerNet, algodNet); err != nil { + // check /metrics endpoint is available before running the test + var resp *http.Response + for retry := 0; retry < 10; retry++ { + resp, err = http.Get(fmt.Sprintf("http://%s/metrics", metricsNet)) + if err == nil { + resp.Body.Close() + break + } + time.Sleep(3 * time.Second) + } + if err != nil { + return fmt.Errorf("failed to query metrics endpoint: %w", err) + } + if err = r.runTest(report, metricsNet, algodNet); err != nil { return err } @@ -158,23 +214,23 @@ func recordDataToFile(start time.Time, entry Entry, prefix string, out *os.File) } } - record("_average", metrics.BlockImportTimeName, rate) - record("_cumulative", metrics.BlockImportTimeName, floatTotal) - record("_average", metrics.ImportedTxnsPerBlockName, rate) - record("_cumulative", metrics.ImportedTxnsPerBlockName, intTotal) - record("", metrics.ImportedRoundGaugeName, intTotal) + record("_average", BlockImportTimeName, rate) + record("_cumulative", BlockImportTimeName, floatTotal) + record("_average", ImportedTxnsPerBlockName, rate) + record("_cumulative", ImportedTxnsPerBlockName, intTotal) + record("", ImportedRoundGaugeName, intTotal) if len(writeErrors) > 0 { return fmt.Errorf("error writing metrics (%s): %w", strings.Join(writeErrors, ", "), writeErr) } // Calculate import transactions per second. - totalTxn, err := getMetric(entry, metrics.ImportedTxnsPerBlockName, false) + totalTxn, err := getMetric(entry, ImportedTxnsPerBlockName, false) if err != nil { return err } - importTimeS, err := getMetric(entry, metrics.BlockImportTimeName, false) + importTimeS, err := getMetric(entry, BlockImportTimeName, false) if err != nil { return err } @@ -266,8 +322,8 @@ func getMetric(entry Entry, suffix string, rateMetric bool) (float64, error) { } // Run the test for 'RunDuration', collect metrics and write them to the 'ReportDirectory' -func (r *Args) runTest(report *os.File, indexerURL string, generatorURL string) error { - collector := &MetricsCollector{MetricsURL: fmt.Sprintf("http://%s/metrics", indexerURL)} +func (r *Args) runTest(report *os.File, metricsURL string, generatorURL string) error { + collector := &MetricsCollector{MetricsURL: fmt.Sprintf("http://%s/metrics", metricsURL)} // Run for r.RunDuration start := time.Now() @@ -275,12 +331,12 @@ func (r *Args) runTest(report *os.File, indexerURL string, generatorURL string) for time.Since(start) < r.RunDuration { time.Sleep(r.RunDuration / 10) - if err := collector.Collect(metrics.AllMetricNames...); err != nil { + if err := collector.Collect(AllMetricNames...); err != nil { return fmt.Errorf("problem collecting metrics (%d / %s): %w", count, time.Since(start), err) } count++ } - if err := collector.Collect(metrics.AllMetricNames...); err != nil { + if err := collector.Collect(AllMetricNames...); err != nil { return fmt.Errorf("problem collecting final metrics (%d / %s): %w", count, time.Since(start), err) } @@ -335,6 +391,7 @@ func startGenerator(configFile string, addr string, blockMiddleware func(http.Ha // Start the server go func() { // always returns error. ErrServerClosed on graceful close + fmt.Printf("generator serving on %s\n", server.Addr) if err := server.ListenAndServe(); err != http.ErrServerClosed { util.MaybeFail(err, "ListenAndServe() failure to start with config file '%s'", configFile) } @@ -350,3 +407,34 @@ func startGenerator(configFile string, addr string, blockMiddleware func(http.Ha return nil }, generator } + +// startConduit starts the indexer. +func startConduit(dataDir string, conduitBinary string) (func() error, error) { + cmd := exec.Command( + conduitBinary, + "-d", dataDir, + ) + + var stdout bytes.Buffer + var stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + + if err := cmd.Start(); err != nil { + return nil, fmt.Errorf("failure calling Start(): %w", err) + } + // conduit doesn't have health check endpoint. so, no health check for now + + return func() error { + if err := cmd.Process.Signal(os.Interrupt); err != nil { + fmt.Printf("failed to kill indexer process: %s\n", err) + if err := cmd.Process.Kill(); err != nil { + return fmt.Errorf("failed to kill indexer process: %w", err) + } + } + if err := cmd.Wait(); err != nil { + fmt.Printf("ignoring error while waiting for process to stop: %s\n", err) + } + return nil + }, nil +} diff --git a/tools/block-generator/runner/runner.go b/tools/block-generator/runner/runner.go index 94df877660..6c653efaec 100644 --- a/tools/block-generator/runner/runner.go +++ b/tools/block-generator/runner/runner.go @@ -34,7 +34,7 @@ func init() { RunnerCmd = &cobra.Command{ Use: "runner", Short: "Run test suite and collect results.", - Long: "Run an automated test suite using the block-generator daemon and a provided algorand-indexer binary. Results are captured to a specified output directory.", + Long: "Run an automated test suite using the block-generator daemon and a provided conduit binary. Results are captured to a specified output directory.", Run: func(cmd *cobra.Command, args []string) { if err := Run(runnerArgs); err != nil { fmt.Println(err) @@ -43,12 +43,12 @@ func init() { } RunnerCmd.Flags().StringVarP(&runnerArgs.Path, "scenario", "s", "", "Directory containing scenarios, or specific scenario file.") - RunnerCmd.Flags().StringVarP(&runnerArgs.IndexerBinary, "indexer-binary", "i", "", "Path to indexer binary.") - RunnerCmd.Flags().Uint64VarP(&runnerArgs.IndexerPort, "indexer-port", "p", 4010, "Port to start the server at. This is useful if you have a prometheus server for collecting additional data.") + RunnerCmd.Flags().StringVarP(&runnerArgs.ConduitBinary, "conduit-binary", "i", "", "Path to conduit binary.") + 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.LogLevel, "log-level", "l", "error", "LogLevel to use when starting Indexer. [error, warn, info, debug, trace]") + RunnerCmd.Flags().StringVarP(&runnerArgs.LogLevel, "log-level", "l", "error", "LogLevel to use when starting Indexer. [panic, fatal, error, warn, info, debug, trace]") RunnerCmd.Flags().StringVarP(&runnerArgs.CPUProfilePath, "cpuprofile", "", "", "Path where Indexer writes its CPU profile.") RunnerCmd.Flags().BoolVarP(&runnerArgs.ResetReportDir, "reset", "", false, "If set any existing report directory will be deleted before running tests.") RunnerCmd.Flags().BoolVarP(&runnerArgs.RunValidation, "validate", "", false, "If set the validator will run after test-duration has elapsed to verify data is correct. An extra line in each report indicates validator success or failure.") diff --git a/tools/block-generator/runner/template/conduit.yml.tmpl b/tools/block-generator/runner/template/conduit.yml.tmpl new file mode 100644 index 0000000000..c361426ee6 --- /dev/null +++ b/tools/block-generator/runner/template/conduit.yml.tmpl @@ -0,0 +1,61 @@ +# Log verbosity: PANIC, FATAL, ERROR, WARN, INFO, DEBUG, TRACE +log-level: {{.LogLevel}} + +# If no log file is provided logs are written to stdout. +log-file: {{.LogFile}} + +# Number of retries to perform after a pipeline plugin error. +retry-count: 10 + +# Time duration to wait between retry attempts. +retry-delay: "1s" + +# Optional filepath to use for pidfile. +#pid-filepath: /path/to/pidfile + +# Whether or not to print the conduit banner on startup. +hide-banner: false + +# When enabled prometheus metrics are available on '/metrics' +metrics: + mode: ON + addr: "{{.MetricsPort}}" + prefix: "conduit" + +# The importer is typically an algod follower node. +importer: + name: algod + config: + # The mode of operation, either "archival" or "follower". + # * archival mode allows you to start processing on any round but does not + # contain the ledger state delta objects required for the postgres writer. + # * follower mode allows you to use a lightweight non-archival node as the + # data source. In addition, it will provide ledger state delta objects to + # the processors and exporter. + mode: "follower" + + # Algod API address. + netaddr: "{{.AlgodNet}}" + + # Algod API token. + token: "" + + +# Zero or more processors may be defined to manipulate what data +# reaches the exporter. +processors: + +# An exporter is defined to do something with the data. +exporter: + name: postgresql + config: + # Pgsql connection string + # See https://github.com/jackc/pgconn for more details + connection-string: "{{ .PostgresConnectionString }}" + + # Maximum connection number for connection pool + # This means the total number of active queries that can be running + # concurrently can never be more than this + max-conn: 20 + + From a79532be0568d335e994a7f8b7a3bc08b45f630d Mon Sep 17 00:00:00 2001 From: abebeos <129396476+abebeos@users.noreply.github.com> Date: Fri, 21 Apr 2023 17:44:32 +0300 Subject: [PATCH 057/125] docs: increase heading levels, thus only one h1 is used (#5291) --- agreement/README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/agreement/README.md b/agreement/README.md index 04ca71451e..a1b80e862f 100644 --- a/agreement/README.md +++ b/agreement/README.md @@ -40,7 +40,7 @@ parameters: If `Ledger` and `db.Accessor` provide crash-safe storage, `agreement` will also recover safely after crashes. -# Specification +## Specification The specification for the protocol implemented by this package is located [here](https://github.com/algorandfoundation/specs). @@ -48,7 +48,7 @@ located [here](https://github.com/algorandfoundation/specs). Optimizations from and other deviations from the spec will be noted throughout this file. -## Terminology +### Terminology Certain terms in this implementation are used as shorthands for specific concepts: @@ -68,7 +68,7 @@ specific concepts: a quorum of soft votes (i.e., sigma(S, r, p)). - Additional terminology is described in the [agreement service doc](../docs/agreement_service.md). -# Design +## Design At the top level, an `agreement.Service` encapsulates the parameters and the goroutines which execute the protocol. @@ -97,7 +97,7 @@ data structures, such as `vote`, `bundle`, and `proposal`. These data types have distinct unauthenticated versions, which allows routines to specify that they accept untrusted input. -# Concurrent Component +## Concurrent Component The `demux` object demultiplexes over a variety of channels which all represent inputs to the system. Inputs include: @@ -121,7 +121,7 @@ machine validates these messages in the same way it validates real network messages and relays them back into the network as appropriate. -## Spec Notes: Additional Events +### Spec Notes: Additional Events Because signature verification is expected to be a computational bottleneck in the agreement code, it executes concurrently with @@ -154,7 +154,7 @@ verification concurrently. Moreover, the implementation abstracts over multiple keys by generating synthetic network events from the `pseudonode`. -# Serialized State Machine +## Serialized State Machine The logic of the agreement protocol is implemented as a state machine. This state machine is composed of many smaller state machines, which From 036c4c939b50237241a752e9775c6f31b466b5a0 Mon Sep 17 00:00:00 2001 From: Will Winder Date: Fri, 21 Apr 2023 13:34:22 -0400 Subject: [PATCH 058/125] docker: add profile support and improved endpoint access. (#5323) --- docker/README.md | 17 +++++++++-------- docker/files/run/run.sh | 9 ++++++++- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/docker/README.md b/docker/README.md index 0df4408a3e..db0aac7980 100644 --- a/docker/README.md +++ b/docker/README.md @@ -10,11 +10,11 @@ There are a number of special files and environment variables used to control ho ### Default Configuration -By default the following config.json overrides are applied: +The following config.json overrides are applied: -| Setting | Value | -| ------- | ----- | -| EndpointAddress | 0.0.0.0:8080 | +| Setting | Value | Description | +| ------- | ----- | ----------- | +| EndpointAddress | 0.0.0.0:8080 | Ensure the API is accessible from outside of the container. | ### Environment Variables @@ -23,14 +23,15 @@ The following environment variables can be supplied. Except when noted, it is po | Variable | Description | | -------- | ----------- | | NETWORK | Leave blank for a private network, otherwise specify one of mainnet, betanet, testnet, or devnet. Only used during a data directory initialization. | -| FAST_CATCHUP | If set to 1 on a public network, attempt to start fast-catchup during initial config. | -| TELEMETRY_NAME | If set on a public network, telemetry is reported with this name. | +| PROFILE | If set, initializes the config.json file according to the given profile. | | DEV_MODE | If set to 1 on a private network, enable dev mode. Only used during data directory initialization. | -| NUM_ROUNDS | If set on a private network, override default of 30000 participation keys. | +| START_KMD | When set to 1, start kmd service with no timeout. THIS SHOULD NOT BE USED IN PRODUCTION. | +| FAST_CATCHUP | If set to 1 on a public network, attempt to start fast-catchup during initial config. | | TOKEN | If set, overrides the REST API token. | | ADMIN_TOKEN | If set, overrides the REST API admin token. | | KMD_TOKEN | If set along with `START_KMD`, override the KMD REST API token. | -| START_KMD | When set to 1, start kmd service with no timeout. THIS SHOULD NOT BE USED IN PRODUCTION. | +| TELEMETRY_NAME | If set on a public network, telemetry is reported with this name. | +| NUM_ROUNDS | If set on a private network, override default of 30000 participation keys. | | PEER_ADDRESS | If set, override phonebook with peer ip:port (or semicolon separated list: ip:port;ip:port;ip:port...) | ### Special Files diff --git a/docker/files/run/run.sh b/docker/files/run/run.sh index 9ea28be4bc..0244f0ff23 100755 --- a/docker/files/run/run.sh +++ b/docker/files/run/run.sh @@ -54,7 +54,6 @@ function start_public_network() { function configure_data_dir() { cd "$ALGORAND_DATA" - algocfg -d . set -p EndpointAddress -v "0.0.0.0:${ALGOD_PORT}" # check for config file overrides. if [ -f "/etc/algorand/config.json" ]; then @@ -70,6 +69,14 @@ function configure_data_dir() { cp /etc/algorand/logging.config logging.config fi + # initialize config with profile. + if [ "$PROFILE" != "" ]; then + algocfg profile set --yes -d "$ALGORAND_DATA" "$PROFILE" + fi + + # call after copying config.json to make sure the port is exposed. + algocfg -d . set -p EndpointAddress -v "0.0.0.0:${ALGOD_PORT}" + # check for token overrides if [ "$TOKEN" != "" ]; then for dir in ${ALGORAND_DATA}/../*/; do From c7ee8b2f044de45a7dc0ee6acebe126de6e7943d Mon Sep 17 00:00:00 2001 From: Hang Su <87964331+ahangsu@users.noreply.github.com> Date: Fri, 21 Apr 2023 16:25:10 -0400 Subject: [PATCH 059/125] Algod: Better Box Reference Error Message (#5325) --- data/transactions/logic/box.go | 6 +++--- data/transactions/logic/box_test.go | 2 +- data/transactions/logic/ledger_test.go | 4 ++-- ledger/boxtxn_test.go | 11 ++++++----- ledger/eval/applications.go | 6 +++--- 5 files changed, 15 insertions(+), 14 deletions(-) diff --git a/data/transactions/logic/box.go b/data/transactions/logic/box.go index ebc1c25f2e..388ae77fed 100644 --- a/data/transactions/logic/box.go +++ b/data/transactions/logic/box.go @@ -37,7 +37,7 @@ func (cx *EvalContext) availableBox(name string, operation int, createSize uint6 dirty, ok := cx.available.boxes[boxRef{cx.appID, name}] if !ok { - return nil, false, fmt.Errorf("invalid Box reference %v", name) + return nil, false, fmt.Errorf("invalid Box reference %#x", name) } // Since the box is in cx.available, we know this GetBox call is cheap. It @@ -150,7 +150,7 @@ func opBoxExtract(cx *EvalContext) error { return err } if !exists { - return fmt.Errorf("no such box %#v", name) + return fmt.Errorf("no such box %#x", name) } bytes, err := extractCarefully(contents, start, length) @@ -178,7 +178,7 @@ func opBoxReplace(cx *EvalContext) error { return err } if !exists { - return fmt.Errorf("no such box %#v", name) + return fmt.Errorf("no such box %#x", name) } bytes, err := replaceCarefully(contents, replacement, start) diff --git a/data/transactions/logic/box_test.go b/data/transactions/logic/box_test.go index f73cdf52a9..578dbcc770 100644 --- a/data/transactions/logic/box_test.go +++ b/data/transactions/logic/box_test.go @@ -242,7 +242,7 @@ func TestBoxAvailability(t *testing.T) { logic.TestApps(t, []string{ `byte "self"; int 64; box_create`, `byte "B"; int 10; int 4; box_extract; byte 0x00000000; ==`, - }, nil, 8, ledger, logic.NewExpect(1, "invalid Box reference B")) + }, nil, 8, ledger, logic.NewExpect(1, fmt.Sprintf("invalid Box reference %#x", 'B'))) // B is available if indexed by 0 in tx[1].Boxes group := logic.MakeSampleTxnGroup(logic.MakeSampleTxn(), txntest.Txn{ diff --git a/data/transactions/logic/ledger_test.go b/data/transactions/logic/ledger_test.go index 9d6106e57b..3016ae5298 100644 --- a/data/transactions/logic/ledger_test.go +++ b/data/transactions/logic/ledger_test.go @@ -412,10 +412,10 @@ func (l *Ledger) NewBox(appIdx basics.AppIndex, key string, value []byte, appAdd } if current, ok := params.boxMods[key]; ok { if current != nil { - return fmt.Errorf("attempt to recreate %s", key) + return fmt.Errorf("attempt to recreate box %#v", key) } } else if _, ok := params.boxes[key]; ok { - return fmt.Errorf("attempt to recreate %s", key) + return fmt.Errorf("attempt to recreate box %#x", key) } params.boxMods[key] = value l.applications[appIdx] = params diff --git a/ledger/boxtxn_test.go b/ledger/boxtxn_test.go index ca36770fc7..fc6ec0cc31 100644 --- a/ledger/boxtxn_test.go +++ b/ledger/boxtxn_test.go @@ -19,6 +19,7 @@ package ledger import ( "bytes" "encoding/binary" + "fmt" "strings" "testing" "time" @@ -155,7 +156,7 @@ func TestBoxCreate(t *testing.T) { } adam := call.Args("create", "adam") - dl.txn(adam, "invalid Box reference adam") + dl.txn(adam, fmt.Sprintf("invalid Box reference %#x", "adam")) adam.Boxes = []transactions.BoxRef{{Index: 0, Name: []byte("adam")}} dl.beginBlock() @@ -173,7 +174,7 @@ func TestBoxCreate(t *testing.T) { dl.txgroup("box_create\nassert", adam.Noted("one"), adam.Noted("two")) bobo := call.Args("create", "bobo") - dl.txn(bobo, "invalid Box reference bobo") + dl.txn(bobo, fmt.Sprintf("invalid Box reference %#x", "bobo")) bobo.Boxes = []transactions.BoxRef{{Index: 0, Name: []byte("bobo")}} dl.txn(bobo) dl.txgroup("box_create\nassert", bobo.Noted("one"), bobo.Noted("two")) @@ -421,7 +422,7 @@ func TestBoxRW(t *testing.T) { time.Sleep(100 * time.Millisecond) // give commit time to run, and prune au caches dl.fullBlock(call.Args("check", "x", "ABCDEFGH")) - dl.txn(call.Args("create", "yy"), "invalid Box reference yy") + dl.txn(call.Args("create", "yy"), fmt.Sprintf("invalid Box reference %#x", "yy")) withBr := call.Args("create", "yy") withBr.Boxes = append(withBr.Boxes, transactions.BoxRef{Index: 1, Name: []byte("yy")}) require.Error(dl.t, withBr.Txn().WellFormed(transactions.SpecialAddresses{}, dl.generator.GenesisProto())) @@ -616,7 +617,7 @@ func TestBoxInners(t *testing.T) { } // The current Boxes gives top-level access to "x", not the inner app dl.txn(call.Args("create", "x", "\x10"), // 8 - "invalid Box reference x") + fmt.Sprintf("invalid Box reference %#x", 'x')) // This isn't right: Index should be index into ForeignApps call.Boxes = []transactions.BoxRef{{Index: uint64(boxIndex), Name: []byte("x")}} @@ -658,7 +659,7 @@ func TestBoxInners(t *testing.T) { dl.txgroup("", checkX, checkY) require.Len(t, setY.Boxes, 2) // recall that setY has ("y", "nope") right now. no "x" - dl.txgroup("invalid Box reference x", checkX, setY) + dl.txgroup(fmt.Sprintf("invalid Box reference %#x", 'x'), checkX, setY) setY.Boxes = append(setY.Boxes, transactions.BoxRef{Index: 1, Name: []byte("x")}) dl.txgroup("", checkX, setY) diff --git a/ledger/eval/applications.go b/ledger/eval/applications.go index 7263425e33..e126cfd309 100644 --- a/ledger/eval/applications.go +++ b/ledger/eval/applications.go @@ -229,7 +229,7 @@ func (cs *roundCowState) NewBox(appIdx basics.AppIndex, key string, value []byte return err } if exists { - return fmt.Errorf("attempt to recreate %s", key) + return fmt.Errorf("attempt to recreate box %#x", key) } record, err := cs.Get(appAddr, false) @@ -258,10 +258,10 @@ func (cs *roundCowState) SetBox(appIdx basics.AppIndex, key string, value []byte return err } if !ok { - return fmt.Errorf("box %s does not exist for %d", key, appIdx) + return fmt.Errorf("box %#x does not exist for %d", key, appIdx) } if len(old) != len(value) { - return fmt.Errorf("box %s is wrong size old:%d != new:%d", + return fmt.Errorf("box %#x is wrong size old:%d != new:%d", key, len(old), len(value)) } return cs.kvPut(fullKey, value) From b3f5ba13d70d21866f9cab2dbab2655cd86b68c0 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Mon, 24 Apr 2023 11:17:32 -0500 Subject: [PATCH 060/125] tools: Cross Repo Types Comparison Tool (#5304) --- .gitignore | 3 + go.mod | 8 +- go.sum | 16 +- tools/README.md | 12 +- tools/x-repo-types/Makefile | 58 ++ tools/x-repo-types/README.md | 58 ++ tools/x-repo-types/typeAnalyzer/main.go | 85 +++ tools/x-repo-types/typeAnalyzer/main.tmpl | 79 +++ .../x-repo-types/typeAnalyzer/typeAnalyzer.go | 580 ++++++++++++++++++ .../typeAnalyzer/typeAnalyzer_test.go | 449 ++++++++++++++ tools/x-repo-types/xrt.go | 198 ++++++ 11 files changed, 1530 insertions(+), 16 deletions(-) create mode 100644 tools/x-repo-types/Makefile create mode 100644 tools/x-repo-types/README.md create mode 100644 tools/x-repo-types/typeAnalyzer/main.go create mode 100644 tools/x-repo-types/typeAnalyzer/main.tmpl create mode 100644 tools/x-repo-types/typeAnalyzer/typeAnalyzer.go create mode 100644 tools/x-repo-types/typeAnalyzer/typeAnalyzer_test.go create mode 100644 tools/x-repo-types/xrt.go diff --git a/.gitignore b/.gitignore index 39abf02ee5..00df89eb2c 100644 --- a/.gitignore +++ b/.gitignore @@ -71,3 +71,6 @@ testresults.json # block generator binary tools/block-generator/block-generator + +# cross repo types tool binary +tools/x-repo-types/xrt diff --git a/go.mod b/go.mod index 47e6bcd8f2..e1ac463199 100644 --- a/go.mod +++ b/go.mod @@ -33,8 +33,8 @@ require ( github.com/spf13/cobra v0.0.3 github.com/stretchr/testify v1.8.1 golang.org/x/crypto v0.1.0 - golang.org/x/sys v0.1.0 - golang.org/x/text v0.4.0 + golang.org/x/sys v0.7.0 + golang.org/x/text v0.9.0 gopkg.in/sohlich/elogrus.v3 v3.0.0-20180410122755-1fa29e2f2009 gopkg.in/yaml.v3 v3.0.1 ) @@ -67,8 +67,8 @@ require ( 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.1.0 // indirect - golang.org/x/term v0.1.0 // indirect + golang.org/x/net v0.9.0 // indirect + golang.org/x/term v0.7.0 // indirect golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 // indirect gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index 21406525ea..85b7f0af3d 100644 --- a/go.sum +++ b/go.sum @@ -154,8 +154,8 @@ golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLL 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.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0= -golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +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/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -170,17 +170,17 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= -golang.org/x/sys v0.1.0/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/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw= -golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ= +golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= 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.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +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/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= diff --git a/tools/README.md b/tools/README.md index b8394ac775..f7ecf655a1 100644 --- a/tools/README.md +++ b/tools/README.md @@ -2,19 +2,23 @@ Various tools and utilities that don't have a better place to go. -### debug +## debug Tools for debugging algod. These were really useful before launch when we spent a lot of time analyzing node behavior, but aren't needed as much recently. -### misc +## misc Small tools that are useful in niche situations. -### network +## network This is a go package used by some of the CLI commands. -### TEAL +## TEAL Some of the earliest tools we built for working with TEAL programs. A lot of these are workarounds for things that no longer need to be worked around, but these are still nice examples of what TEAL can do. +## x-repo-types + +A tool for checking that `go-algorand` types get serialized in the same way as +purportedly clone types in other Algorand repositories. diff --git a/tools/x-repo-types/Makefile b/tools/x-repo-types/Makefile new file mode 100644 index 0000000000..7d21516da1 --- /dev/null +++ b/tools/x-repo-types/Makefile @@ -0,0 +1,58 @@ +all: goal-v-sdk goal-v-spv + +# 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: + ./xrt --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: + ./xrt --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: + ./xrt --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: + ./xrt --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: + ./xrt --x-package "github.com/algorand/go-algorand/crypto/stateproof" \ + --x-type "StateProof" \ + --y-branch "develop" \ + --y-package "github.com/algorand/go-algorand-sdk/v2/types" \ + --y-type "StateProof" + +# go-algorand vs go-stateproof-verification: + +goal-v-spv: goal-v-spv-stateproof + +goal-v-spv-stateproof: + ./xrt --x-package "github.com/algorand/go-algorand/crypto/stateproof" \ + --x-type "StateProof" \ + --y-package "github.com/algorand/go-stateproof-verification/stateproof" \ + --y-type "StateProof" + +# reset typeAnalyzer/main.go for passing checks: + +reset-dummy-main: + ./xrt --x-package "github.com/algorand/go-algorand/ledger/ledgercore" \ + --x-type "StateDelta" \ + --y-package "github.com/algorand/go-algorand/data/bookkeeping" \ + --y-type "Genesis" diff --git a/tools/x-repo-types/README.md b/tools/x-repo-types/README.md new file mode 100644 index 0000000000..03755da256 --- /dev/null +++ b/tools/x-repo-types/README.md @@ -0,0 +1,58 @@ +# Cross Repo Type Comparisons + +Given two types **X** and **Y** from separate repositories, compare the types and generate a report of any differences to the serialized shape of the types. In particular it ignores different embedding of structs, different field names if `codec` tags are used, and different types if they map to the same primitives. +This tool is designed to be used in CI systems to alert us if a change is made to one repo without a corresponding change to another. For example the `Genesis` type in `go-algorand` and `go-algorand-sdk`. See the [Makefile](./Makefile) for additional examples. + +## Build the `xrt` binary + +```sh +go build -o xrt +``` + +## Example run + +```sh +goal-v-sdk-state-delta-xrt: + ./xrt --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" +``` + +## Pseudocode + +### Cross Type Comparison Process + +1. Inside of `tools/x-repo-types` run the command `./xrt --x-package X_PACKAGE_NAME ...` +2. `xrt` then does the following: + 1. `go get`'s the package + 2. Populates the template `typeAnalyzer/main.tmpl` with comparison types + 3. Saves it in `typeAnalyzer/main.go` + 4. Executes it +3. `typeAnalyzer/main.go` runs the logic defined in `typeAnalyzer/typeAnalyzer.go`: + 1. using reflection, build up each type's "Type Tree" + 2. compare the trees using the rules outlined below +4. If the template reports back a non-empty diff, exit with an error + +### Type Tree Comparison + +`func StructDiff(x, y interface{}, exclusions map[string]bool) (TypeNode, TypeNode, *Diff, error)` in `typeAnalyzer/typeAnalyzer.go` implements the following recursive notion of _identical_ types: + +* if **X** and **Y** are native simple types (`int`, `uint64`, `string`, ...), they are _identical_ IFF they are the same type +* if both **X** and **Y** are compound types (`struct`, slice, `map`, ...) with each of their child types being _identical_ and with _equivalent serialization metadata_, then they are _identical_ + * _equivalent serialization metadata_ definition: + * for non-structs: there is no metadata so the metadata are _trivially_ identical + * for structs: + * the keys will encode to the same name + * omission of values based on zeroness, etc. will happen in the same way for both structs + * embedded structs will be flattened + +* ELSE: they are **not** _identical_ + +### Exceptional cases + +There are some cases that break the definition above. For example, `basics.MicroAlgos` is a struct in +`go-algorand` but is an alias for `uint64` in `go-algorand-sdk`. Our serializers know to produce the same +output, but this violates the previous notion of _identical_. Such exceptions are handled by providing the string produced by the type's `TypeNode.String()` method +as an element in the set `diffExclusions` of `typeAnalyzer/typeAnalyzer.go`. diff --git a/tools/x-repo-types/typeAnalyzer/main.go b/tools/x-repo-types/typeAnalyzer/main.go new file mode 100644 index 0000000000..3de25af203 --- /dev/null +++ b/tools/x-repo-types/typeAnalyzer/main.go @@ -0,0 +1,85 @@ +// Copyright (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 . + +/* + WARNING + THIS FILE ONLY EXISTS FOR DEBUGGING AND TO MAKE THE BUILD HAPPY + !!!!! IT IS OVERWRITTEN BY AT RUNTIME !!!!! +*/ + +package main + +import ( + "fmt" + "os" + + ypkg "github.com/algorand/go-algorand/data/bookkeeping" + xpkg "github.com/algorand/go-algorand/ledger/ledgercore" +) + +func main() { + xRoot := MakeType(xpkg.StateDelta{}) + yRoot := MakeType(ypkg.Genesis{}) + + // ---- BUILD ---- // + x, y := xRoot.Type, yRoot.Type + + fmt.Printf("Build the Type Tree for %s\n\n", &xRoot) + xCycle := xRoot.Build() + xTgt := Target{ChildName{Name: fmt.Sprintf("%q", x)}, xRoot} + + fmt.Printf("Build the Type Tree for %s\n\n", &yRoot) + yCycle := yRoot.Build() + yTgt := Target{ChildName{Name: fmt.Sprintf("%q", y)}, yRoot} + + fmt.Printf("Potential CYCLE in %s:\n%s\n\n", &xRoot, xCycle) + fmt.Printf("Potential CYCLE in %s:\n%s\n\n", &yRoot, yCycle) + + // ---- DEBUG ---- // + + /* + xRoot.Print() + fmt.Printf("\n\nSerialization Tree of %q\n\n", x) + xTgt.PrintSerializable() + + yRoot.Print() + fmt.Printf("\n\nSerialization Tree of %q\n\n", y) + yTgt.PrintSerializable() + */ + + // ---- STATS ---- // + + LeafStatsReport(xTgt) + LeafStatsReport(yTgt) + + MaxDepthReport(xTgt) + MaxDepthReport(yTgt) + + // ---- DIFF ---- // + + fmt.Printf("\n\nCompare the Type Trees %q v %q\n", x, y) + xType, yType, diff, err := StructDiff(xpkg.StateDelta{}, ypkg.Genesis{}, diffExclusions) + if err != nil { + fmt.Printf("Error: %s\n", err) + os.Exit(1) + } + fmt.Println(Report(xType, yType, diff)) + + if !diff.Empty() { + // signal that this "test" has failed + os.Exit(1) + } +} diff --git a/tools/x-repo-types/typeAnalyzer/main.tmpl b/tools/x-repo-types/typeAnalyzer/main.tmpl new file mode 100644 index 0000000000..37ec0699ba --- /dev/null +++ b/tools/x-repo-types/typeAnalyzer/main.tmpl @@ -0,0 +1,79 @@ +// Copyright (C) 2019-2023 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package main + +import ( + "fmt" + "os" + + xpkg "{{.XModulePath}}/{{.XPackagePath}}" + ypkg "{{.YModulePath}}/{{.YPackagePath}}" +) + +func main() { + xRoot := MakeType(xpkg.{{.XTypeInstance}}{}) + yRoot := MakeType(ypkg.{{.YTypeInstance}}{}) + + // ---- BUILD ---- // + x, y := xRoot.Type, yRoot.Type + + fmt.Printf("Build the Type Tree for %s\n\n", &xRoot) + xCycle := xRoot.Build() + xTgt := Target{ChildName{Name: fmt.Sprintf("%q", x)}, xRoot} + + fmt.Printf("Build the Type Tree for %s\n\n", &yRoot) + yCycle := yRoot.Build() + yTgt := Target{ChildName{Name: fmt.Sprintf("%q", y)}, yRoot} + + fmt.Printf("Potential CYCLE in %s:\n%s\n\n", &xRoot, xCycle) + fmt.Printf("Potential CYCLE in %s:\n%s\n\n", &yRoot, yCycle) + + // ---- DEBUG ---- // + + /* + xRoot.Print() + fmt.Printf("\n\nSerialization Tree of %q\n\n", x) + xTgt.PrintSerializable() + + yRoot.Print() + fmt.Printf("\n\nSerialization Tree of %q\n\n", y) + yTgt.PrintSerializable() + */ + + // ---- STATS ---- // + + LeafStatsReport(xTgt) + LeafStatsReport(yTgt) + + MaxDepthReport(xTgt) + MaxDepthReport(yTgt) + + // ---- DIFF ---- // + + fmt.Printf("\n\nCompare the Type Trees %q v %q\n", x, y) + xType, yType, diff, err := StructDiff(xpkg.{{.XTypeInstance}}{}, ypkg.{{.YTypeInstance}}{}, diffExclusions) + if err != nil { + fmt.Printf("Error: %s\n", err) + os.Exit(1) + } + fmt.Println(Report(xType, yType, diff)) + + if !diff.Empty() { + // signal that this "test" has failed + os.Exit(1) + } +} diff --git a/tools/x-repo-types/typeAnalyzer/typeAnalyzer.go b/tools/x-repo-types/typeAnalyzer/typeAnalyzer.go new file mode 100644 index 0000000000..a0e3b0fb40 --- /dev/null +++ b/tools/x-repo-types/typeAnalyzer/typeAnalyzer.go @@ -0,0 +1,580 @@ +// Copyright (C) 2019-2023 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package main + +import ( + "fmt" + "reflect" + "regexp" + "sort" + "strings" + "unicode" +) + +// diffExclusions is a set of types to exclude. The string keys are assumed to be generated by +// the `TypeNode.String()` method, and these values are ignored when building up the diff. +// These exclusions represent types that are known to serialize in the same way, but +// would be evaluated as different by the diff algorithm if not for this exclusion. +var diffExclusions = map[string]bool{ + // MicroAlgos is a struct with custom marshal override in go-algorand. In other repos it is a uint64. + `github.com/algorand/go-algorand/data/basics :: "basics.MicroAlgos" (struct)`: true, +} + +// --------------- TYPE TREE DATA STRUCTURES --------------- // + +// TypeNode wraps reflect.Type and reflect.Kind to make it easier to +// build a tree of types. +type TypeNode struct { + Depth int + Type reflect.Type + Kind reflect.Kind + ChildNames []ChildName + children *ChildTypes +} + +// String returns fully qualified information about the TypeNode. +func (t *TypeNode) String() string { + return fmt.Sprintf("%s :: %q (%s)", t.Type.PkgPath(), t.Type, t.Kind) +} + +// IsStruct returns true if the TypeNode is a struct. +func (t *TypeNode) IsStruct() bool { + return t.Kind == reflect.Struct +} + +// MakeType uses reflection to build a TypeNode from a concrete value. +func MakeType(v interface{}) TypeNode { + t := reflect.TypeOf(v) + return TypeNode{Type: t, Kind: t.Kind()} +} + +// ChildTypes represent a TypeNode's child Types. +type ChildTypes map[string]TypeNode + +// ChildName represents the name a type is referred to together with +// the Tag metadata (for the case of a struct field) +type ChildName struct { + Name, Tag string +} + +// String representing the ChildName in an invertible manner. +func (e *ChildName) String() string { + return fmt.Sprintf("[%s](%s)", e.Name, e.Tag) +} + +// ChildNameFromLabel de-"serialize"s a ChildName that has been rendered via ChildName.String() +func ChildNameFromLabel(s string) (ChildName, error) { + re := regexp.MustCompile(`^\[(.*)\]\((.*)\)$`) + matches := re.FindStringSubmatch(s) + if len(matches) == 3 { + return ChildName{Name: matches[1], Tag: matches[2]}, nil + } + return ChildName{}, fmt.Errorf("invalid label: %s", s) +} + +// Target represents a Type that is a child of another Type by +// providing the ChildName that "points" to it. +// In the case of a "root" TypeNode, it's up to the caller to provide +// the ChildName. +type Target struct { + ChildName + TypeNode TypeNode +} + +// String is a convenience method for printing a Target. +func (tgt *Target) String() string { + return fmt.Sprintf("%s|-->%s", &tgt.ChildName, &tgt.TypeNode) +} + +// SerializationInfo provides the essential data +// for go to serialize the field or other type. +func (e *ChildName) SerializationInfo() string { + // Probably more subtlety is called for. + re := regexp.MustCompile(`^codec:"([^,"]+)`) + + if e.Tag == "" { + return e.Name + } + + matches := re.FindStringSubmatch(e.Tag) + if len(matches) > 1 { + return matches[1] + } + return e.Tag +} + +// TargetPair represents a pair of Targets. +type TargetPair struct { + X, Y Target +} + +// Diff represents a difference (if any) between two Types. +// CommonPath is the path in the trees of the types from their roots +// that arrives at the difference. +// Xdiff and Ydiff are the differences at the end of the common path +// between children. Set theoretically: +// * Xdiff = X - Y +// * Ydiff = Y - X. +type Diff struct { + CommonPath []TargetPair + Xdiff, Ydiff []Target +} + +// Empty reports whether there was any difference at all. +func (d *Diff) Empty() bool { + return d == nil || (len(d.Xdiff) == 0 && len(d.Ydiff) == 0) +} + +// --------------- BUILD THE TYPE TREE --------------- // + +// Targets returns a slice of Targets, one for each child of the TypeNode. +// In the case of structs, the order is the same as the order of the fields in the struct. +// In the case of maps, the key Target precedes the value Target. +func (t *TypeNode) Targets() []Target { + targets := make([]Target, 0, len(t.ChildNames)) + for _, edge := range t.ChildNames { + targets = append(targets, Target{edge, (*t.children)[edge.String()]}) + } + return targets +} + +// TypePath encapsulates a path in a TypeNode tree. +type TypePath []TypeNode + +// String is a convenience method for printing a TypePath +// in a go-literal-friendly format. +func (t TypePath) String() string { + parts := make([]string, len(t)) + for i, node := range t { + parts[i] = fmt.Sprintf("%q", node.String()) + } + return fmt.Sprintf("[]string{%s}", strings.Join(parts, ", ")) +} + +// IsLeaf returns true if the TypeNode has no children. +func (t *TypeNode) IsLeaf() bool { + return t.children == nil || len(*t.children) == 0 +} + +// Build the TypeNode tree by finding all child types that belong to a kind and recursively +// building their TypeNode trees. Stop traversing when a cycle is detected. +func (t *TypeNode) Build() TypePath { + return t.build(TypePath{}) +} + +func (t *TypeNode) appendChild(typeName, typeTag string, child TypeNode) { + cname := ChildName{typeName, typeTag} + t.ChildNames = append(t.ChildNames, cname) + if t.children == nil { + children := make(ChildTypes) + t.children = &children + } + (*t.children)[cname.String()] = child +} + +// build the TypeNode tree by finding all child types that belong to a kind and recursively +// building their TypeNode trees. +// Return a path that will be non-trivial only in a case that a cycle is detected. +func (t *TypeNode) build(path TypePath) TypePath { + if t.IsStruct() { + me := t.String() + foundCycle := false + for _, node := range path { + if node.String() == me { + foundCycle = true + break + } + } + path = append(path, *t) + if foundCycle { + return path + } + } + + var cyclePath TypePath + switch t.Kind { + case reflect.Struct: + cyclePath = t.buildStructChildren(path) + case reflect.Slice, reflect.Array: + cyclePath = t.buildListChild(path) + case reflect.Map: + cyclePath = t.buildMapChildren(path) + case reflect.Ptr: + cyclePath = t.buildPtrChild(path) + } + + return cyclePath +} + +// buildStructChildren builds the children of a struct type. +func (t *TypeNode) buildStructChildren(path TypePath) TypePath { + var cyclePath TypePath + for i := 0; i < t.Type.NumField(); i++ { + typeField := t.Type.Field(i) + typeName := typeField.Name + + // probably we need to skip typeField.Tag == `codec:"-"` as well + if typeName == "" || (!unicode.IsUpper(rune(typeName[0])) && typeName != "_struct") { + continue + } + + if typeField.Anonymous { + // embedded struct case + actualKind := typeField.Type.Kind() + if actualKind != reflect.Struct { + panic(fmt.Sprintf("expected [%s] but got unexpected embedded type: %s", reflect.Struct, typeField.Type)) + } + + embedded := TypeNode{t.Depth, typeField.Type, reflect.Struct, nil, nil} + embeddedCyclePath := embedded.build(path) + if len(embeddedCyclePath) > 0 { + cyclePath = embeddedCyclePath + } + for _, edge := range embedded.ChildNames { + child := (*embedded.children)[edge.String()] + t.appendChild(edge.Name, edge.Tag, child) + } + continue + } + + typeTag := string(typeField.Tag) + child := TypeNode{t.Depth + 1, typeField.Type, typeField.Type.Kind(), nil, nil} + childCyclePath := child.build(path) + if len(childCyclePath) > 0 { + cyclePath = childCyclePath + } + t.appendChild(typeName, typeTag, child) + } + return cyclePath +} + +func (t *TypeNode) buildListChild(path TypePath) TypePath { + tt := t.Type.Elem() + child := TypeNode{t.Depth + 1, tt, tt.Kind(), nil, nil} + path = child.build(path) + t.appendChild("", "", child) + return path +} + +// buildMapChildren builds the children of a map type. +// To distinguish between the key and value children as well as children of lists and structs, +// the key child is given the name "" while the value child is given the name "". +func (t *TypeNode) buildMapChildren(path TypePath) TypePath { + keyType, valueType := t.Type.Key(), t.Type.Elem() + + keyChild := TypeNode{t.Depth + 1, keyType, keyType.Kind(), nil, nil} + // don't worry about path because struct keys must be hashable: + keyChild.build(path) + t.appendChild("", "", keyChild) + + valChild := TypeNode{t.Depth + 1, valueType, valueType.Kind(), nil, nil} + path = valChild.build(path) + t.appendChild("", "", valChild) + return path +} + +// buildPtrChild builds the child of a pointer type. To distinguish between a child +// that is a pointer and other children, the child is given the name "". +func (t *TypeNode) buildPtrChild(path TypePath) TypePath { + tt := t.Type.Elem() + child := TypeNode{t.Depth + 1, tt, tt.Kind(), nil, nil} + path = child.build(path) + t.appendChild("", "", child) + return path +} + +// Visit traverses the Target tree and applies any actions provided at each node. +func (tgt *Target) Visit(actions ...func(Target)) { + if len(actions) > 0 { + for _, action := range actions { + action(*tgt) + } + for _, target := range tgt.TypeNode.Targets() { + target.Visit(actions...) + } + } +} + +// StructDiff compares two structs by building their type tree and then +// calling targetTreeDiff on the trees. +func StructDiff(x, y interface{}, exclusions map[string]bool) (TypeNode, TypeNode, *Diff, error) { + xRoot, yRoot := MakeType(x), MakeType(y) + xRoot.Build() + yRoot.Build() + + diff, err := targetTreeDiff(Target{TypeNode: xRoot}, Target{TypeNode: yRoot}, exclusions) + return xRoot, yRoot, diff, err +} + +// targetTreeDiff recursively computes a diff between two Target's x and y considering only data +// that impacts serialization, but ignoring any assumption on key ordering. +func targetTreeDiff(x, y Target, exclusions map[string]bool) (*Diff, error) { + xtype, ytype := x.TypeNode, y.TypeNode + if xtype.Depth != ytype.Depth { + return nil, fmt.Errorf("cannot compare types at different depth") + } + // if we got here it must be the case that either depth == 0 or + // the edges of x and y serialize the same way. + + // First check that the native type for x and y are the same. + if xtype.Kind != ytype.Kind { + return &Diff{ + Xdiff: []Target{x}, + Ydiff: []Target{y}, + }, nil + } + + // So look at the children. + // If any children differ report back the diff. + xTgts, yTgts := xtype.Targets(), ytype.Targets() + xSerials, ySerials := make(map[string]Target), make(map[string]Target) + for _, tgt := range xTgts { + xSerials[tgt.ChildName.SerializationInfo()] = tgt + } + for _, tgt := range yTgts { + ySerials[tgt.ChildName.SerializationInfo()] = tgt + } + xDiff, yDiff := []Target{}, []Target{} + for k, v := range xSerials { + if _, ok := ySerials[k]; !ok { + xDiff = append(xDiff, v) + } + } + for k, v := range ySerials { + if _, ok := xSerials[k]; !ok { + yDiff = append(yDiff, v) + } + } + if len(xDiff) != 0 || len(yDiff) != 0 { + return &Diff{ + Xdiff: xDiff, + Ydiff: yDiff, + }, nil + } + + // Otherwise, call the children recursively. If any of them report + // a diff, modify the diff's CommonPath to include the current edge and return it. + for k, xChild := range xSerials { + if exclusions[xChild.TypeNode.String()] { + continue + } + yChild := ySerials[k] + diff, err := targetTreeDiff(xChild, yChild, exclusions) + if err != nil { + return nil, err + + } + if diff != nil { + diff.CommonPath = append([]TargetPair{ + { + X: xChild, + Y: yChild, + }, + }, diff.CommonPath...) + return diff, nil + } + } + // No diffs detected up the tree: + return nil, nil +} + +// --------------- DIFF REPORT ----------------- // + +// Report returns a human-readable listing of the differences as a string. +func Report(x, y TypeNode, d *Diff) string { + var sb strings.Builder + + sb.WriteString(` +======================================================== + REPORT +comparing + <<<<<`) + sb.WriteString(x.String()) + sb.WriteString(`>>>>> +VS + <<<<<`) + sb.WriteString(y.String()) + sb.WriteString(`>>>>> +========================================================`) + + if d == nil { + sb.WriteString("\nNo differences found.") + } else { + if len(d.Xdiff) == 0 && len(d.Ydiff) == 0 { + if len(d.CommonPath) != 0 { + panic("A common paths was found with no diffs. This should NEVER happen.") + } + sb.WriteString("\nNo differences found.") + } else { + sb.WriteString(` +-------------------------------------------------------- + DIFFERENCES FOUND (partial) +--------------------------------------------------------`) + sb.WriteString(fmt.Sprintf("\nCommon path of length %d:\n", len(d.CommonPath))) + for depth, tgts := range d.CommonPath { + indent := strings.Repeat(" ", depth) + sb.WriteString(fmt.Sprintf("%s_____LEVEL %d_____\n", indent, depth+1)) + sb.WriteString(fmt.Sprintf("%sX-FIELD: %s\n%s\tX-TYPE: %s\n", indent, &tgts.X.ChildName, indent, &tgts.X.TypeNode)) + sb.WriteString(fmt.Sprintf("%sY-FIELD: %s\n%s\tY-TYPE: %s\n", indent, &tgts.Y.ChildName, indent, &tgts.Y.TypeNode)) + } + sb.WriteString(` +X-DIFF +------ +EXISTS IN: `) + sb.WriteString(fmt.Sprintf("%q", &x)) + sb.WriteString(` +MISSING FROM: `) + sb.WriteString(fmt.Sprintf("%q", &y)) + sb.WriteString(fmt.Sprintf("\n%d TYPES TOTAL:\n", len(d.Xdiff))) + for i, tgt := range d.Xdiff { + sb.WriteString(fmt.Sprintf(` +(%d) +[FIELD](+codec): %s +SOURCE: %s`, i+1, &tgt.ChildName, &tgt.TypeNode)) + } + + sb.WriteString(` + + + +Y-DIFF +------ +EXISTS IN: `) + sb.WriteString(fmt.Sprintf("%q", &y)) + sb.WriteString(` +MISSING FROM: `) + sb.WriteString(fmt.Sprintf("%q", &x)) + sb.WriteString(fmt.Sprintf("\n%d TYPES TOTAL:\n", len(d.Ydiff))) + for i, tgt := range d.Ydiff { + sb.WriteString(fmt.Sprintf(`(%d) +[FIELD](+codec): %s +SOURCE: %s +`, i+1, &tgt.ChildName, &tgt.TypeNode)) + } + } + } + sb.WriteString(` +======================================================== +=============== REPORT END =============== +========================================================`) + + return sb.String() +} + +// ------- STATISTICS AND DEBUGGING ------------- // + +// Print prints out information about the TypeNode's structure using `Visit()`. +func (t *TypeNode) Print() { + action := func(tgt Target) { + tabs := strings.Repeat("\t", tgt.TypeNode.Depth) + fmt.Printf("%s[depth=%d]. Value is type %q (%s)\n", tabs, tgt.TypeNode.Depth, tgt.TypeNode.Type, tgt.TypeNode.Kind) + + if tgt.TypeNode.IsLeaf() { + x := fmt.Sprintf("%q", tgt.TypeNode.Type) + _ = x + fmt.Printf("%s-------B I N G O: A LEAF---------->%q (%s)\n", tabs, tgt.TypeNode.Type, tgt.TypeNode.Kind) + return + } + fmt.Printf("%s=====EDGE: %s=====>\n", tabs, tgt.ChildName) + } + (&Target{ChildName{}, *t}).Visit(action) +} + +// PrintSerializable prints the information that determines go-codec serialization. +// cf: https://github.com/algorand/go-codec/blob/master/codec/encode.go#L1416-L1436 +func (tgt Target) PrintSerializable() { + action := func(tgt Target) { + ttype := tgt.TypeNode + tkind := ttype.Kind + depth := ttype.Depth + edge := tgt.ChildName + if depth == 0 { + fmt.Printf("Serialization info for type %q (%s):\n", ttype.Type, tkind) + return + } + fmt.Printf("%s%s", strings.Repeat(" ", depth-1), edge.SerializationInfo()) + suffix := "" + if ttype.IsLeaf() { + x := ttype.String() + _ = x + suffix = fmt.Sprintf(":%s", tkind) + } + fmt.Printf("%s\n", suffix) + } + tgt.Visit(action) +} + +// LeafStatsReport prints out a report for the leafs type count. +func LeafStatsReport(xTgt Target) { + fmt.Printf("\n\nLeaf-type stats for type %s:\n\n", &xTgt.TypeNode) + leaves := []TypeNode{} + leafCollector := func(tgt Target) { + if tgt.TypeNode.IsLeaf() { + leaves = append(leaves, tgt.TypeNode) + } + } + + xTgt.Visit(leafCollector) + fmt.Printf("Found %d leaves\n\n", len(leaves)) + + stats := make(map[string]int) + for _, leaf := range leaves { + key := fmt.Sprintf("%s/%s", leaf.Type, leaf.Kind) + if _, ok := stats[key]; !ok { + stats[key] = 0 + } + stats[key]++ + } + printSortedStats(stats) +} + +// MaxDepthReport prints out a report for the max depth of the type tree. +func MaxDepthReport(xTgt Target) int { + fmt.Printf("\n\nMax depth stats for type %s:\n\n", &xTgt.TypeNode) + maxDepth := 0 + maxDepthCollector := func(tgt Target) { + if tgt.TypeNode.Depth > maxDepth { + maxDepth = tgt.TypeNode.Depth + } + } + xTgt.Visit(maxDepthCollector) + fmt.Printf("Max depth is %d\n", maxDepth) + return maxDepth +} + +type keyValue struct { + Key string + Value int +} + +func printSortedStats(stats map[string]int) { + // Create a slice of key-value pairs + var kvSlice []keyValue + for k, v := range stats { + kvSlice = append(kvSlice, keyValue{k, v}) + } + + // Sort the slice by the count in descending order + sort.Slice(kvSlice, func(i, j int) bool { + return kvSlice[i].Value > kvSlice[j].Value + }) + + // Print the sorted slice + for _, kv := range kvSlice { + fmt.Printf("%s: %d\n", kv.Key, kv.Value) + } +} diff --git a/tools/x-repo-types/typeAnalyzer/typeAnalyzer_test.go b/tools/x-repo-types/typeAnalyzer/typeAnalyzer_test.go new file mode 100644 index 0000000000..632a38b520 --- /dev/null +++ b/tools/x-repo-types/typeAnalyzer/typeAnalyzer_test.go @@ -0,0 +1,449 @@ +// Copyright (C) 2019-2023 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package main // cannot use main_type for main package? + +import ( + "fmt" + "testing" + + "github.com/algorand/go-algorand/test/partitiontest" + "github.com/stretchr/testify/require" +) + +func TestEdgeFromLabel(t *testing.T) { + partitiontest.PartitionTest(t) + + testCases := []struct { + label string + expected ChildName + expectError bool + }{ + { + label: "[foo](bar)", + expected: ChildName{Name: "foo", Tag: "bar"}, + expectError: false, + }, + { + label: "[foo]()", + expected: ChildName{Name: "foo", Tag: ""}, + expectError: false, + }, + { + label: "[](bar)", + expected: ChildName{Name: "", Tag: "bar"}, + expectError: false, + }, + { + label: "[]()", + expected: ChildName{Name: "", Tag: ""}, + expectError: false, + }, + { + label: "[f[]()oo](()(()", + expected: ChildName{Name: "f[]()oo", Tag: "()(("}, + expectError: false, + }, + { + label: "foo:bar", + expected: ChildName{}, + expectError: true, + }, + { + label: "[f[]()oo](()((", + expected: ChildName{}, + expectError: true, + }, + } + + for _, tc := range testCases { + tc := tc // capture range variable + t.Run(tc.label, func(t *testing.T) { + t.Parallel() + edge, err := ChildNameFromLabel(tc.label) + if tc.expectError { + require.Error(t, err) + require.Equal(t, ChildName{}, edge) + } else { + require.NoError(t, err) + require.Equal(t, tc.expected, edge) + require.Equal(t, tc.label, edge.String()) + } + }) + } +} + +type Node struct { + Name string + Next *Node +} + +type Senior struct { + Children []Parent +} + +type Parent struct { + Granddaughter *Child +} + +type Child struct { + Grandpa Senior +} + +type Family struct { + Brother, Sister Kid +} + +type Kid struct { + Age int + Name string +} + +func TestBuild(t *testing.T) { + partitiontest.PartitionTest(t) + + testcases := []struct { + name string + x interface{} + depth int + }{ + { + name: "recursive 0", + x: Node{}, + depth: 2, + }, + { + name: "recursive 1", + x: Senior{}, + depth: 5, + }, + { + name: "recursive 2", + x: Child{}, + depth: 5, + }, + { + name: "basic struct", + x: struct{ A int }{}, + depth: 1, + }, + { + name: "basic codec", + x: struct { + B int `codec:"A"` + }{}, + depth: 1, + }, + { + name: "deeper unexported", + x: struct { + a []string + B string + }{}, + depth: 1, + }, + { + name: "deeper exported", + x: struct { + A []string + b int + }{}, + depth: 2, + }, + { + name: "embed flattened", + x: func() interface{} { + type Embedded struct{ A int } + return struct{ Embedded }{} + }(), + depth: 1, + }, + { + name: "primitive alias", + x: func() interface{} { + type MYINT int + var i MYINT + return i + }(), + depth: 0, + }, + { + name: "primitive type", + x: 5, + depth: 0, + }, + { + name: "nested embeds 1", + x: func() interface{} { + type Embedded struct{ A int } + type Embedded2 struct{ Embedded } + return struct{ Embedded2 }{} + }(), + depth: 1, + }, + } + + for _, tc := range testcases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + xRoot := MakeType(tc.x) + xRoot.Build() + tgt := Target{TypeNode: xRoot} + tgt.PrintSerializable() + require.Equal(t, tc.depth, MaxDepthReport(tgt), "test case: %s", tc.name) + }) + } +} + +func TestDiffErrors(t *testing.T) { + partitiontest.PartitionTest(t) + + testcases := []struct { + name string + x interface{} + y interface{} + equal bool + }{ + { + name: "basic equal", + x: struct{ A int }{}, + y: struct{ A int }{}, + equal: true, + }, + { + name: "basic codec equal", + x: struct{ A int }{}, + y: struct { + B int `codec:"A"` + }{}, + equal: true, + }, + { + name: "equal because only care about exported", + x: struct { + a int + B string + }{}, + y: struct{ c, B string }{}, + equal: true, + }, + { + name: "basic codec not equal", + x: struct{ A int }{}, + y: struct { + A int `codec:"B"` + }{}, + equal: false, + }, + { + name: "basic field not equal", + x: struct{ A int }{}, + y: struct{ B int }{}, + equal: false, + }, + { + name: "embed flattened", + x: struct{ A int }{}, + y: func() interface{} { + type Embedded struct{ A int } + return struct{ Embedded }{} + }(), + equal: true, + }, + { + name: "embed flattened not equal", + x: struct{ A int }{}, + y: func() interface{} { + type Embedded struct{ B int } + return struct{ Embedded }{} + }(), + equal: false, + }, + { + name: "primitive types equal", + x: func() interface{} { + type MYINT int + var i MYINT + return i + }(), + y: func() interface{} { + type MYOTHERINT int + var i MYOTHERINT + return i + }(), + equal: true, + }, + { + name: "primitive type and primitive equal", + x: func() interface{} { + type MYINT int + var i MYINT + return i + }(), + y: 5, + equal: true, + }, + { + name: "primitives not equal", + x: func() interface{} { + type MYINT int + var i MYINT + return i + }(), + y: uint(5), + equal: false, + }, + { + name: "nested embeds 2", + x: func() interface{} { + type Embedded struct{ A int } + type Embedded2 struct{ Embedded } + return struct{ Embedded2 }{} + }(), + y: struct{ A int }{}, + equal: true, + }, + { + name: "field order", + x: struct{ A, B int }{}, + y: struct{ B, A int }{}, + equal: true, + }, + } + + for _, tc := range testcases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + xRoot, yRoot, diff, err := StructDiff(tc.x, tc.y, nil) + require.NoError(t, err) + require.Equal(t, tc.equal, diff.Empty(), "test case: %s, report: %s", tc.name, Report(xRoot, yRoot, diff)) + }) + } +} + +func TestBuildWithCyclicCheck(t *testing.T) { + partitiontest.PartitionTest(t) + + testcases := []struct { + name string + x interface{} + path []string + }{ + { + name: "recursive 0", + x: Node{}, + path: []string{ + "github.com/algorand/go-algorand/tools/x-repo-types/typeAnalyzer :: \"main.Node\" (struct)", + "github.com/algorand/go-algorand/tools/x-repo-types/typeAnalyzer :: \"main.Node\" (struct)", + }, + }, + { + name: "recursive 1", + x: Senior{}, + path: []string{ + "github.com/algorand/go-algorand/tools/x-repo-types/typeAnalyzer :: \"main.Senior\" (struct)", + "github.com/algorand/go-algorand/tools/x-repo-types/typeAnalyzer :: \"main.Parent\" (struct)", + "github.com/algorand/go-algorand/tools/x-repo-types/typeAnalyzer :: \"main.Child\" (struct)", + "github.com/algorand/go-algorand/tools/x-repo-types/typeAnalyzer :: \"main.Senior\" (struct)", + }, + }, + { + name: "recursive 2", + x: Child{}, + path: []string{ + "github.com/algorand/go-algorand/tools/x-repo-types/typeAnalyzer :: \"main.Child\" (struct)", + "github.com/algorand/go-algorand/tools/x-repo-types/typeAnalyzer :: \"main.Senior\" (struct)", + "github.com/algorand/go-algorand/tools/x-repo-types/typeAnalyzer :: \"main.Parent\" (struct)", + "github.com/algorand/go-algorand/tools/x-repo-types/typeAnalyzer :: \"main.Child\" (struct)", + }, + }, + { + name: "basic struct", + x: struct{ A int }{}, + path: []string{}, + }, + { + name: "basic codec", + x: struct { + B int `codec:"A"` + }{}, + path: []string{}, + }, + { + name: "deeper unexported", + x: struct { + a []string + B string + }{}, + path: []string{}, + }, + { + name: "deeper exported", + x: struct { + A []string + b int + }{}, + path: []string{}, + }, + { + name: "embed flattened", + x: func() interface{} { + type Embedded struct{ A int } + return struct{ Embedded }{} + }(), + path: []string{}, + }, + { + name: "primitive alias", + x: func() interface{} { + type MYINT int + var i MYINT + return i + }(), + path: []string{}, + }, + { + name: "primitive type", + x: 5, + path: []string{}, + }, + { + name: "types may reappear with no cycles", + x: Family{}, + path: []string{}, + }, + } + + for _, tc := range testcases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + xRoot := MakeType(tc.x) + cycle := xRoot.Build() + hasCycle := len(cycle) > 0 + expectedHasCycle := len(tc.path) > 0 + require.Equal(t, expectedHasCycle, hasCycle, `test case: %s + cycle: %s + expected: %#v`, tc.name, cycle, tc.path) + require.Equal(t, fmt.Sprintf("%#v", tc.path), cycle.String(), `test case: %s + cycle: %s + expected: %#v`, tc.name, cycle, tc.path, + ) + }) + } +} diff --git a/tools/x-repo-types/xrt.go b/tools/x-repo-types/xrt.go new file mode 100644 index 0000000000..84d72d6f85 --- /dev/null +++ b/tools/x-repo-types/xrt.go @@ -0,0 +1,198 @@ +// Copyright (C) 2019-2023 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package main + +import ( + "bytes" + "fmt" + "log" + "os" + "os/exec" + "path/filepath" + "strings" + "text/template" + + "github.com/spf13/cobra" + + _ "embed" +) + +//go:embed typeAnalyzer/main.tmpl +var differTmpl string + +func main() { + var xPkg, xBranch, xType, yPkg, yBranch, yType string + + rootCmd := &cobra.Command{ + Use: "xrt", + Short: "Compare types across repos", + Run: func(cmd *cobra.Command, args []string) { + if err := runApp(xPkg, xBranch, xType, yPkg, yBranch, yType); err != nil { + log.Fatal(err) + } + }, + } + + rootCmd.Flags().StringVar(&xPkg, "x-package", "", "Go repo and package for type x") + rootCmd.Flags().StringVar(&xBranch, "x-branch", "", "repository branch for type x") + rootCmd.Flags().StringVar(&xType, "x-type", "", "Exported type in the package for type x") + rootCmd.Flags().StringVar(&yPkg, "y-package", "", "Go repo and package for type for type y") + rootCmd.Flags().StringVar(&yBranch, "y-branch", "", "repository branch for type y") + rootCmd.Flags().StringVar(&yType, "y-type", "", "Exported type in the package for type y") + + if err := rootCmd.Execute(); err != nil { + log.Fatal(err) + } +} + +func runApp(xPkg, xBranch, xType, yPkg, yBranch, yType string) error { + if xPkg == "" || xType == "" { + return fmt.Errorf("package:%s, and type:%s flags are required", xPkg, xType) + } + if yPkg == "" || yType == "" { + return fmt.Errorf("package:%s, and type:%s flags are required", yPkg, yType) + } + + xPkgBranch := xPkg + if xBranch != "" { + xPkgBranch += "@" + xBranch + } + yPkgBranch := yPkg + if yBranch != "" { + yPkgBranch += "@" + yBranch + } + + err := goGet(xPkgBranch) + if err != nil { + return err + } + err = goGet(yPkgBranch) + if err != nil { + return err + } + + xParts := strings.Split(xPkg, "/") + yParts := strings.Split(yPkg, "/") + + xRepo := strings.Join(xParts[:3], "/") + yRepo := strings.Join(yParts[:3], "/") + + xPkgSuffix := strings.Join(xParts[3:], "/") + yPkgSuffix := strings.Join(yParts[3:], "/") + + // Instantiate the type in a separate process as a "Smoke Test" + err = instantiate(xRepo, xPkgSuffix, xType) + if err != nil { + return err + } + err = instantiate(yRepo, yPkgSuffix, yType) + if err != nil { + return err + } + + // Compare the types by running the template typeAnalyzer/main.tmpl in a separate process + // typeAnalyzer/main will return an error if the types are not the same + // here we propagate the error to the caller, so as to fail the test. + err = serializationDiff(xRepo, xPkgSuffix, xType, yRepo, yPkgSuffix, yType) + if err != nil { + return err + } + return nil +} + +func goGet(repo string) error { + fmt.Println("Downloading repo:", repo) + cmd := exec.Command("go", "get", repo) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + return cmd.Run() +} + +func instantiate(repo, pkgPath, typeName string) error { + fmt.Println("Instantiating type for:", typeName) + + pkgParts := strings.Split(pkgPath, "/") + pkgOnly := pkgParts[len(pkgParts)-1] + + code := fmt.Sprintf(`package main + +import ( + "fmt" + "%s/%s" +) + +func main() { + var item %s.%s + fmt.Printf("Instantiated: %%#v\n\n", item) +} +`, repo, pkgPath, pkgOnly, typeName) + + tmpDir, err := os.MkdirTemp(".", "instantiate-*") + if err != nil { + return err + } + defer os.RemoveAll(tmpDir) + + tmpFile := filepath.Join(tmpDir, "main.go") + err = os.WriteFile(tmpFile, []byte(code), 0644) + if err != nil { + return err + } + + //nolint:gosec // tmpFile is defined above so no security concerns here + cmd := exec.Command("go", "run", tmpFile) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + return cmd.Run() +} + +func serializationDiff(xRepo, xPkgPath, xType, yRepo, yPkgPath, yType string) error { + fmt.Printf("Diffing %s from package %s VS %s from package %s...\n", xType, xPkgPath, yType, yPkgPath) + + tmpl, err := template.New("code").Parse(differTmpl) + if err != nil { + fmt.Println("Error parsing template:", err) + os.Exit(1) + } + + var buf bytes.Buffer + err = tmpl.Execute(&buf, map[string]string{ + "XModulePath": xRepo, + "XPackagePath": xPkgPath, + "XTypeInstance": xType, + "YModulePath": yRepo, + "YPackagePath": yPkgPath, + "YTypeInstance": yType, + }) + if err != nil { + fmt.Println("Error executing template:", err) + os.Exit(1) + } + + main := filepath.Join("typeAnalyzer", "main.go") + typeAnalyzer := filepath.Join("typeAnalyzer", "typeAnalyzer.go") + err = os.WriteFile(main, buf.Bytes(), 0644) + if err != nil { + return err + } + + //nolint:gosec // main and typeAnalyzer are hard-coded above so no security concerns here + cmd := exec.Command("go", "run", main, typeAnalyzer) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + return cmd.Run() +} From 904a2a91ff767440869eb35a77c6e15eb31c5da0 Mon Sep 17 00:00:00 2001 From: Will Winder Date: Mon, 24 Apr 2023 12:23:51 -0400 Subject: [PATCH 061/125] algocfg(profile): improve invalid profile error message. (#5324) --- cmd/algocfg/profileCommand.go | 7 +++- cmd/algocfg/profileCommand_test.go | 51 ++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 cmd/algocfg/profileCommand_test.go diff --git a/cmd/algocfg/profileCommand.go b/cmd/algocfg/profileCommand.go index ea907afb20..6a80e7d646 100644 --- a/cmd/algocfg/profileCommand.go +++ b/cmd/algocfg/profileCommand.go @@ -169,5 +169,10 @@ func getConfigForArg(configType string) (config.Local, error) { if updater, ok := profileNames[configType]; ok { return updater.updateFunc(cfg), nil } - return config.Local{}, fmt.Errorf("invalid profile type %v", configType) + + var names []string + for name := range profileNames { + names = append(names, name) + } + return config.Local{}, fmt.Errorf("unknown profile provided: '%s' is not in list of valid profiles: %s", configType, strings.Join(names, ", ")) } diff --git a/cmd/algocfg/profileCommand_test.go b/cmd/algocfg/profileCommand_test.go new file mode 100644 index 0000000000..e461c4e7c3 --- /dev/null +++ b/cmd/algocfg/profileCommand_test.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 . + +package main + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" +) + +func Test_getConfigForArg(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + t.Run("invalid config test", func(t *testing.T) { + t.Parallel() + _, err := getConfigForArg("invalid") + + var names []string + for name := range profileNames { + names = append(names, name) + } + require.ErrorContains(t, err, strings.Join(names, ", ")) + + }) + + t.Run("valid config test", func(t *testing.T) { + t.Parallel() + cfg, err := getConfigForArg("conduit") + require.NoError(t, err) + require.True(t, cfg.EnableFollowMode) + }) + +} From a9f11cd4b6eb55beb7993e74e19aa82ac20fe590 Mon Sep 17 00:00:00 2001 From: Hang Su <87964331+ahangsu@users.noreply.github.com> Date: Mon, 24 Apr 2023 13:45:10 -0400 Subject: [PATCH 062/125] Algod: Simulation run with increased limits on logs (#5247) Significantly higher per-transaction logging limits when enabled with a Simulate request object. --- cmd/goal/clerk.go | 4 + daemon/algod/api/algod.oas2.json | 23 + daemon/algod/api/algod.oas3.yml | 29 ++ .../api/server/v2/generated/data/routes.go | 241 ++++----- .../v2/generated/experimental/routes.go | 331 ++++++------ .../api/server/v2/generated/model/types.go | 15 + .../nonparticipating/private/routes.go | 235 ++++----- .../nonparticipating/public/routes.go | 476 +++++++++--------- .../generated/participating/private/routes.go | 346 ++++++------- .../generated/participating/public/routes.go | 353 ++++++------- daemon/algod/api/server/v2/handlers.go | 28 +- daemon/algod/api/server/v2/test/helpers.go | 4 +- daemon/algod/api/server/v2/utils.go | 17 +- data/transactions/logic/eval.go | 29 +- ledger/simulation/simulation_eval_test.go | 271 +++++++++- ledger/simulation/simulator.go | 22 +- ledger/simulation/trace.go | 68 ++- ledger/simulation/tracer.go | 7 +- node/follower_node.go | 2 +- node/follower_node_test.go | 3 +- node/node.go | 4 +- test/e2e-go/restAPI/restClient_test.go | 147 ++++++ test/scripts/e2e_subs/e2e-app-simulate.sh | 104 +++- .../e2e_subs/tealprogs/logs-a-lot.teal | 141 ++++++ 24 files changed, 1866 insertions(+), 1034 deletions(-) create mode 100644 test/scripts/e2e_subs/tealprogs/logs-a-lot.teal diff --git a/cmd/goal/clerk.go b/cmd/goal/clerk.go index 2d652d7f95..54584613b5 100644 --- a/cmd/goal/clerk.go +++ b/cmd/goal/clerk.go @@ -67,6 +67,7 @@ var ( rawOutput bool requestFilename string requestOutFilename string + liftLogLimits bool ) func init() { @@ -151,6 +152,7 @@ func init() { simulateCmd.Flags().StringVar(&requestFilename, "request", "", "Simulate request object to run. Mutually exclusive with --txfile") simulateCmd.Flags().StringVar(&requestOutFilename, "request-only-out", "", "Filename for writing simulate request object. If provided, the command will only write the request object and exit. No simulation will happen") simulateCmd.Flags().StringVarP(&outFilename, "result-out", "o", "", "Filename for writing simulation result") + simulateCmd.Flags().BoolVar(&liftLogLimits, "lift-log-limits", false, "Lift the limits on log opcode during simulation") } var clerkCmd = &cobra.Command{ @@ -1255,6 +1257,7 @@ var simulateCmd = &cobra.Command{ Txns: txgroup, }, }, + LiftLogLimits: liftLogLimits, } err := writeFile(requestOutFilename, protocol.EncodeJSON(simulateRequest), 0600) if err != nil { @@ -1275,6 +1278,7 @@ var simulateCmd = &cobra.Command{ Txns: txgroup, }, }, + LiftLogLimits: liftLogLimits, } simulateResponse, responseErr = client.SimulateTransactions(simulateRequest) } else { diff --git a/daemon/algod/api/algod.oas2.json b/daemon/algod/api/algod.oas2.json index cab2591d32..8f4584ea60 100644 --- a/daemon/algod/api/algod.oas2.json +++ b/daemon/algod/api/algod.oas2.json @@ -3285,6 +3285,10 @@ "items": { "$ref": "#/definitions/SimulateRequestTransactionGroup" } + }, + "lift-log-limits": { + "description": "The boolean flag that lifts the limit on log opcode during simulation.", + "type": "boolean" } } }, @@ -3639,6 +3643,22 @@ "x-algorand-format": "uint64" } } + }, + "SimulationEvalOverrides": { + "description": "The set of parameters and limits override during simulation. If this set of parameters is present, then evaluation parameters may differ from standard evaluation in certain ways.", + "type": "object", + "properties": { + "max-log-calls": { + "description": "The maximum log calls one can make during simulation", + "type": "integer", + "x-algorand-format": "uint64" + }, + "max-log-size": { + "description": "The maximum byte number to log during simulation", + "type": "integer", + "x-algorand-format": "uint64" + } + } } }, "parameters": { @@ -4244,6 +4264,9 @@ "$ref": "#/definitions/SimulateTransactionGroupResult" } }, + "eval-overrides": { + "$ref": "#/definitions/SimulationEvalOverrides" + }, "would-succeed": { "description": "Indicates whether the simulated transactions would have succeeded during an actual submission. If any transaction fails or is missing a signature, this will be false.", "type": "boolean" diff --git a/daemon/algod/api/algod.oas3.yml b/daemon/algod/api/algod.oas3.yml index 7243dbedab..51650a769a 100644 --- a/daemon/algod/api/algod.oas3.yml +++ b/daemon/algod/api/algod.oas3.yml @@ -777,6 +777,9 @@ "application/json": { "schema": { "properties": { + "eval-overrides": { + "$ref": "#/components/schemas/SimulationEvalOverrides" + }, "last-round": { "description": "The round immediately preceding this simulation. State changes through this round were used to run this simulation.", "type": "integer" @@ -1857,6 +1860,10 @@ "SimulateRequest": { "description": "Request type for simulation endpoint.", "properties": { + "lift-log-limits": { + "description": "The boolean flag that lifts the limit on log opcode during simulation.", + "type": "boolean" + }, "txn-groups": { "description": "The transaction groups to simulate.", "items": { @@ -1948,6 +1955,22 @@ ], "type": "object" }, + "SimulationEvalOverrides": { + "description": "The set of parameters and limits override during simulation. If this set of parameters is present, then evaluation parameters may differ from standard evaluation in certain ways.", + "properties": { + "max-log-calls": { + "description": "The maximum log calls one can make during simulation", + "type": "integer", + "x-algorand-format": "uint64" + }, + "max-log-size": { + "description": "The maximum byte number to log during simulation", + "type": "integer", + "x-algorand-format": "uint64" + } + }, + "type": "object" + }, "StateDelta": { "description": "Application state delta.", "items": { @@ -5920,6 +5943,9 @@ "application/json": { "schema": { "properties": { + "eval-overrides": { + "$ref": "#/components/schemas/SimulationEvalOverrides" + }, "last-round": { "description": "The round immediately preceding this simulation. State changes through this round were used to run this simulation.", "type": "integer" @@ -5952,6 +5978,9 @@ "application/msgpack": { "schema": { "properties": { + "eval-overrides": { + "$ref": "#/components/schemas/SimulationEvalOverrides" + }, "last-round": { "description": "The round immediately preceding this simulation. State changes through this round were used to run this simulation.", "type": "integer" diff --git a/daemon/algod/api/server/v2/generated/data/routes.go b/daemon/algod/api/server/v2/generated/data/routes.go index d10be59b64..5a9aff2756 100644 --- a/daemon/algod/api/server/v2/generated/data/routes.go +++ b/daemon/algod/api/server/v2/generated/data/routes.go @@ -198,125 +198,128 @@ var swaggerSpec = []string{ "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", - "ZBvMkfHh6v8HAAD//8J2O3RgywAA", + "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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 3183c03502..e254b9a4a5 100644 --- a/daemon/algod/api/server/v2/generated/experimental/routes.go +++ b/daemon/algod/api/server/v2/generated/experimental/routes.go @@ -75,170 +75,173 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "H4sIAAAAAAAC/+x9/XPcNrLgv4Ka96r8cUNJ/kjeWlWpd4qdZHVxHJelZO89y5dgyJ4ZrEiAC4Camfj8", - "v1+hAZAgCXI4kmLvVt1PtoZAo9FoNBr9hY+zVBSl4MC1mp1+nJVU0gI0SPyLpqmouE5YZv7KQKWSlZoJ", - "Pjv134jSkvHVbD5j5teS6vVsPuO0gKaN6T+fSfhHxSRks1MtK5jPVLqGghrAelea1jWkbbISiQNxZkGc", - "v5p9GvlAs0yCUn0sf+b5jjCe5lUGREvKFU3NJ0U2TK+JXjNFXGfCOBEciFgSvW41JksGeaaO/CT/UYHc", - "BbN0gw9P6VODYiJFDn08X4piwTh4rKBGql4QogXJYImN1lQTM4LB1TfUgiigMl2TpZB7ULVIhPgCr4rZ", - "6fuZAp6BxNVKgd3gf5cS4A9INJUr0LMP89jklhpkolkRmdq5o74EVeVaEWyLc1yxG+DE9DoiP1VKkwUQ", - "ysm771+SZ8+evTATKajWkDkmG5xVM3o4J9t9djrLqAb/uc9rNF8JSXmW1O3fff8Sx79wE5zaiioF8c1y", - "Zr6Q81dDE/AdIyzEuIYVrkOL+02PyKZofl7AUkiYuCa28b0uSjj+F12VlOp0XQrGdWRdCH4l9nNUhgXd", - "x2RYjUCrfWkoJQ3Q9yfJiw8fn8yfnHz6t/dnyX+7P7969mni9F/WcPdQINowraQEnu6SlQSKu2VNeZ8e", - "7xw/qLWo8oys6Q0uPi1Q1Lu+xPS1ovOG5pXhE5ZKcZavhCLUsVEGS1rlmviBScVzI6YMNMfthClSSnHD", - "MsjmRvpu1ixdk5QqCwLbkQ3Lc8ODlYJsiNfisxvZTJ9Ckhi8bkUPnNA/LzGaee2hBGxRGiRpLhQkWuw5", - "nvyJQ3lGwgOlOavUYYcVuVwDwcHNB3vYIu244ek83xGN65oRqggl/miaE7YkO1GRDS5Ozq6xv5uNoVpB", - "DNFwcVrnqNm8Q+TrESNCvIUQOVCOxPP7rk8yvmSrSoIimzXotTvzJKhScAVELP4OqTbL/r8ufn5DhCQ/", - "gVJ0BW9pek2ApyKD7IicLwkXOmANx0tIQ9NzaB4Or9gh/3clDE8UalXS9Dp+ouesYJFZ/US3rKgKwqti", - "AdIsqT9CtCASdCX5EEIW4h5WLOi2P+ilrHiK698M29LlDLcxVeZ0hwQr6Pabk7lDRxGa56QEnjG+InrL", - "B/U4M/Z+9BIpKp5NUHO0WdPgYFUlpGzJICM1lBFM3DD78GH8MHwa5StAxwMZRKceZQ86HLYRnjG723wh", - "JV1BwDJH5Bcn3PCrFtfAa0Ynix1+KiXcMFGputMAjjj0uAbOhYaklLBkER67cOQwAsa2cRK4cDpQKrim", - "jENmhDMiLTRYYTWIUzDg+H2nf4ovqIKvnw+d8c3Xiau/FN1VH13xSauNjRK7JSNHp/nqNmxcs2r1n3A/", - "DMdWbJXYn3sLyVaX5rRZshxPor+b9fNkqBQKgRYh/Nmk2IpTXUk4veKPzV8kIRea8ozKzPxS2J9+qnLN", - "LtjK/JTbn16LFUsv2GqAmDWu0QsXdivsPwZeXBzrbfRe8VqI66oMJ5S2Lq6LHTl/NbTIFuahjHlW33bD", - "i8fl1l9GDu2ht/VCDiA5SLuSmobXsJNgsKXpEv/ZLpGf6FL+Yf4py9z01uUyRlrDx+5IRvOBMyuclWXO", - "UmqI+M59Nl+NEAB7kaBNi2M8UE8/BiiWUpQgNbNAaVkmuUhpnihNNUL6dwnL2ens344b+8ux7a6Og8Ff", - "m14X2MmorFYNSmhZHgDjrVF91IiwMAIaP6GYsGIPlSbG7SIaVmJGBOdwQ7k+aq4sLXlQb+D3bqSG3lbb", - "sfTuXMEGCU5swwUoqwHbhg8UCUhPkKwEyYoK6SoXi/qHh2dl2VAQv5+VpaUHao/AUDGDLVNaPcLp02Yn", - "heOcvzoiP4SwURUXPN+Zw8GqGuZsWLpTy51itW3JzaGB+EARXE4hj8zSeDIYNf8+OA6vFWuRG61nL6+Y", - "xn91bUM2M79P6vyvwWIhbYeZCy9ajnL2joO/BJebhx3O6TOOM/cckbNu39uxjYESZ5hb8croelq4I3Ss", - "SbiRtLQIui/2LGUcL2m2kcX1jtJ0oqCL4hzs4YDXEKtb77W9+yGKCbJCB4dvc5Fe/5Wq9T3s+YWH1d9+", - "OAxZA81AkjVV66NZTMsIt1cDbcoWMw3xgk8WwVBH9RTva3p7ppZRTYOpOXzjaoklPfZDoQcycnf5Gf9D", - "c2I+m71tRL8Fe0QuUYApu52dkyEzt317QbAjmQZohRCksBd8Ym7dB2H5shk8vk6T1ug7a1NwK+QmgSsk", - "tve+Db4V2xgO34ptbwuILaj74A8DB9VIDYWagN8rh5nA9Xfko1LSXZ/ICHsKkc0EjeqqcDfw8MQ3ozTG", - "2bOFkLeTPh2xwkljcibUQA2E77xDJGxalYljxYjZyjboAGq8fONCows+RrEWFS40/ROooAzU+6BCG9B9", - "U0EUJcvhHlh/HRX6C6rg2VNy8dezr548/e3pV18bliylWElakMVOgyIP3d2MKL3L4VF/Zng7qnIdh/71", - "c2+obMONwVGikikUtOyDsgZQqwLZZsS061OtTWacdY3glM15CUaSW7ITa9s3qL1iymhYxeJeFmOIYFkz", - "SkYcJhnsZaZDp9cMswunKHeyuo+rLEgpZMS+hltMi1TkyQ1IxUTEm/LWtSCuhVdvy+7vFluyoYqYsdH0", - "W3FUKCKcpbd8uty3oC+3vKHNqOS3843Mzo07ZV3axPeWREVKkInecpLBolq1bkJLKQpCSYYd8Yz+ATSq", - "ApesgAtNi/Ln5fJ+rooCAUWubKwAZUYitoXR6xWkgttIiD23Mwd1Cnm6hPEmOj2MgKPIxY6naGe8j207", - "fHEtGEenh9rxNLjFGhxzyFYttrz7bXWIHHaoByqCjiHHa/yMho5XkGt67xpdd4AY7i89a1tkSWYa4mq9", - "Zqu1DlTut1KI5f3jGBslhih+sBeW3PTpX1veiMxsNF2pe1BPGmDN7jdrGu55uhCVJpRwkQHamCoVV1wG", - "YhnQiYq+Xx3qQnpt7yALMIyU0srMtioJejZ7srTpmNDUcm+CpFEDfp3aIWdb2eGsnzyXQLMdWQBwIhbO", - "eeLcOjhJim5Z7Y9+pzZF9lILr1KKFJSCLHFGm72o+XZWrOoROiHiiHA9ClGCLKm8M7LXN3vxvIZdgkEE", - "ijz88Vf16Avgq4Wm+R7CYpsYeesrsPOQ9bGeNvwYw3UHD9mOSiBe5pr7thEQOWgYIuFBNBlcvy5GvVW8", - "O1luQKKv6k/leD/I3RioRvVP5ve7YluVA6Fx7upntB+zYJxy4ZWOGLCcKp3sE8umUet+amYQSMKYJEbA", - "A0rJa6q09a8ynqFZyB4nOI5VUMwQwwgPqugG8q9eO+/DTs05yFWlalVdVWUppIYsNgcO25Gx3sC2Hkss", - "A9j1fUALUinYB3mISgF8Ryw7E0sgqms3hAtA6E8OjfXmnN9FSdlCoiHEGCIXvlVA3TA8aAARphpCW8Zh", - "qsM5dUzSfKa0KEsjLXRS8brfEJkubOsz/UvTts9cVDfndiZAYVSSa+8w31jK2sCwNVXE4UEKem10DzQR", - "WEdwH2ezGRPFeArJGOfj9ce0CrfA3k1alStJM0gyyOmuD/QX+5nYz2MAcMWbq6DQkNgIn/iiN5zsAypG", - "QAuEp2LKI8EvJDVb0Nw8GgZxvfdAzgBhx4ST46MHNSgcK7pEHh5O2y51BCKehjdCmxV3/IAoO4k+BeEB", - "OtSgb08K7Jw097LuEP8Fyg1Q6xGHD7IDNTSFBv5BExiwL7rg6WC/dMR7RwJHxeagGNsjR4a27ICx8y2V", - "mqWsxLvOj7C796tfd4CoC45koCnLISPBB3sNLMP+xMamdGHe7io4yS7VR79nmIpMJ2cKVZ428tewwzv3", - "Wxv0eBmESt7DXTYC1ZxPlBNE1IdSGRU8bAJbmup8ZxQ1vYYd2YAEoqpFwbS2wcztq64WZRICiNr8R0Z0", - "Di4bMOhXYIrH7QJBBdPrL8V8Zu8E4/hddi4GLXK4u0ApRD7BetQjRhSDSbEQpBRm1ZmLq/aRtZ6TWkg6", - "oY3ezfr4f6BaZMYZkP8SFUkpxytXpaHWaYRERQEVSDOCUcHqMV3UQ0MhyKEAe5PEL48fdyf++LFbc6bI", - "EjY+GcE07JLj8WO047wVSrc21z3YCs12O48cH+gMMQefu4V0Zcp+r7uDPGUl33aA1x4Us6eUcoxrpn9n", - "AdDZmdspcw95ZFrEAcKd5OcIQMfmjet+wYoqp/o+PDqjCml9oWBFARmjGvIdKSWkYAPOjYalLC4GNWJD", - "0dI15StUrKWoVi4WysJBwVgpa8KQFe+BiCofesuTlRRVGROULv7V5xwYtQOoufoEhMTOVtHf0Ho8l2Yy", - "5QTzBA9W5wcDc8jRMp8N3gwNUW+am6ElTjtxIk4FzARJVJWmANGo6Nidq55qJ0G0SflxAI3aUEkbFkZo", - "qiuah1xHzpeE8l07c5SyXBkpyBTBdqZzE2o8t3PzaT1Lmlt3dSTPJNwpLY0vWPmGpF1STHQ8IJMYbajP", - "GSEDmu1l2PjPMeI3oGNY9gcO4tCaj0OhaOYCnu/uQQ2ygIiEUoLCQys0XCn7VSzDdDB3qqmd0lD0bfu2", - "628Dgubd4A1S8JxxSArBYRfNgGYcfsKPUcGBB+dAZ1Rhhvp2byUt/DtotceZwo13pS+udiCL3tYxmPew", - "+F24HbdOmAiHZkvIS0JJmjM0agqutKxSfcUpmk2CzRaJVfH3w2FD2kvfJG65ixjWHKgrTjFOqTamRP3r", - "S4hYDr4H8PY0Va1WoDrykywBrrhrxTipONM4VmHWK7ELVoLEgJEj27KgOyMC0e73B0hBFpVuy2RMxlHa", - "iEvrYzLDELG84lSTHMyd+ifGL7cIzvtoPc9w0Bshr2sqxI+QFXBQTCXxmJof7FcMd3TTX7vQR0yetp+t", - "V8LAbzJ2dmhVaRKC/8/D/zx9f5b8N03+OEle/I/jDx+ff3r0uPfj00/ffPN/2z89+/TNo//899hKedxj", - "qSIO8/NX7rJ2/go18sYt0cP9s5mkC8aTKJOFzvcOb5GHmBbpGOhR216j13DF9ZYbRrqhOcuMynUbduiK", - "uN5etLujwzWthejYZ/xcD9Rz7yBlSETIdETjrY/xfhhaPCkL/WQuzwr3y7Lidim9omtzDnw4kFjO68Q7", - "W5PjlGBW1pr6WDb359Ovvp7Nm2yq+vtsPnNfP0Q4mWXbqHYI29j1xW0Q3BgPFCnpTsGAAoq4RyOfbLhB", - "CLYAc+9Va1Z+fkmhNFvEJZyP5HZmkC0/5zbE2uwf9LrtnDFfLD8/3loaPbzU61iufktTwFbNagJ0IiFK", - "KW6Azwk7gqOuGSIzVzMXg5UDXWLOOF70xJTMlHofWEbzXBFQPZzIpLt+jH9QuXXS+tN85g5/de/6uAMc", - "w6s7Zu1i839rQR788N0lOXYCUz2w6ZsWdJBwF7m1upySVoyMkWa2QonNX73iV/wVLBln5vvpFc+opscL", - "qliqjisF8luaU57C0UqQU5+m8opqesV7mtZgEaEgQYiU1SJnKbkONeKGPW1hiD6Eq6v3NF+Jq6sPvXCB", - "vv7qhorKFztAsmF6LSqduLT2RMKGypg7RtVpzQjZ1q0YG3VOHGwril3avIMfl3m0LFU3vbE//bLMzfQD", - "NlQuec8sGVFaSK+LGAXFYoPr+0a4g0HSjTdhVAoU+b2g5XvG9QeSXFUnJ8+AtPL9fndHvuHJXQmTDRmD", - "6Zdd+wVO3N5rYKslTUq6inl9rq7ea6Alrj7qywVesvOcYLdWnqGPo0ZQzQQ8PYYXwOJxcM4UTu7C9vIl", - "jOJTwE+4hNjGqBuNL/q26xVkHt56uTrZi71VqvQ6MXs7OitlWNyvTF3ZZGWULB8goNgKgzBdEZgFkHQN", - "6bWrzgFFqXfzVncfg+IUTS86mLJ1W2zeEFYOQJv5AkhVZtSp4l0L0mJHFGjto0DfwTXsLkVTeOCQnO12", - "CrEa2qjIqYF2aZg13LYORnfxXaATmrjK0mfiYkqWZ4vTmi98n+GNbFXee9jEMaZopbgOEYLKCCEs8w+Q", - "4BYTNfDuxPqx6ZlbxsKefJEaLl72E9ekuTy5mKRwNmjgtt8LwCJQYqPIghq9Xbj6RTZNNpBilaIrGNCQ", - "Q7fFxGTUlqsDgew796InnVh2D7TeeRNF2TZOzJyjnALmi2EVvMx0ItH8SNYz5pwAWJbQEWyRo5pUh+xZ", - "oUNly31k66wNoRZnYJC8UTg8Gm2KhJrNmipfWgkrUPm9PEkH+BPTvseKfYQG/aDMVG1f9zK3u097t0tX", - "8sPX+fDFPcKr5YRCHUbDx7jt2HIIjgpQBjms7MRtY88oTQp6s0AGj5+Xy5xxIEksHosqJVJma2M1x4wb", - "A4x+/JgQawImkyHE2DhAGz2+CJi8EeHe5KtDkOQuhZ562OgrDv6GeLaPjVA2Ko8ojQhnAw6k1EsA6oL4", - "6vOrE0qKYAjjc2LE3A3NjZhzN74GSK/mBKqtnQoTLubg0ZA6O2KBtwfLQXOyR9FtZhPqTB7puEI3gvFC", - "bBOb7hfVeBfbheH3aNA2Jh/GNqat7vFAkYXYYhwLHi02SHgPLsN4eDSCG/6WKeRX7Dd0mltkxoYd16Zi", - "XKiQZZw5r2aXIXViytADGswQuzwMCnbcCoGOsaOpfusuv3svqW31pH+YN6favClE5fNhYtt/aAtFV2mA", - "fn0rTF1i421XY4naKdrhGO3qIoEKGWN6Iyb6Tpq+K0hBDngpSFpKVHIdc92Zuw3giXPhuwXGC6xhQvnu", - "URDjI2HFlIbGiO5DEr6EeZJi6TQhlsOz06Vcmvm9E6I+pmxtHuzYmuZnnwEGyS6ZVDpBD0R0CqbR9wov", - "1d+bpnFdqR1FZAuNsiwuG3DYa9glGcurOL+6cX98ZYZ9U4tEVS1Q3jJuY0MWWBg3Gls4MrQNPx2d8Gs7", - "4df03uY7bTeYpmZgadilPca/yL7oSN4xcRBhwBhz9FdtkKQjAjLICe1Lx0BvspsTc0KPxqyvvc2Uedh7", - "w0Z8ZurQGWUhRecSGAxGZ8HQTWTUEqaDurL9ZM2BPUDLkmXbji3UQh28MdODDB6+GleHCri6DtgeCgR2", - "z1i+iATVLrzWKPi2QnCr7snRJMpctsujhQIhHIopX9++T6g6n2wfrS6B5j/C7lfTFqcz+zSf3c10GqO1", - "g7iH1m/r5Y3SGV3z1pTW8oQcSHJallLc0DxxBuYh1pTixrEmNvf26M8s6uJmzMvvzl6/deh/ms/SHKhM", - "alVhcFbYrvyXmZWt8TawQXz9bHPn8zq7VSWDxa8LU4VG6c0aXCHiQBvtVUxsHA7BVnRG6mU8Qmivydn5", - "RuwUR3wkUNYuksZ8Zz0kba8IvaEs93Yzj+1ANA9OblrZzahUCAHc2bsSOMmSexU3vd0d3x0Nd+2RSeFY", - "I6WSC1sNXBHBuy50DC/elc7rXlCsd2itIn3hxKsCLQmJylkat7HyhTLMwa3vzDQm2HhAGTUQKzbgiuUV", - "C2CZZlMqmnSQDMaIElNFi6o0tFsI99JLxdk/KiAsA67NJ4m7srNRscCks7b3j1OjO/THcoCthb4Bfxcd", - "I6z12T3xEIlxBSP01PXQfVVfmf1Ea4sUhls3LokDHP7hiL0jccRZ7/jDcbMNXly3PW7hwyx9+WcYw1bo", - "3v8qjL+8uqKjA2NEX3lhKllK8QfE73l4PY6k4vjqpgyjXP4APiHmvLHuNI/VNKMPLveQdhNaodpBCgNc", - "jysfuOWwzKK3UFNul9o+utCKdYszTBhVemzhNwzjcO5F4uZ0s6CxGpRGyTA4nTUO4JYtXQviO3vaqzqx", - "wY5OAl9y3ZbZNOsSZJMl1y/ZckuFwQ47WVVoNAPk2lAnmFv/X65EBEzFN5TbtztMP7uVXG8F1vhlem2E", - "xCIJKm72zyBlBc3jmkOW9k28GVsx+yxFpSB498ABsk/+WC5yb0fU6TqONOdLcjIPHl9xq5GxG6bYIgds", - "8cS2WFCFkrw2RNVdzPSA67XC5k8nNF9XPJOQ6bWyhFWC1EodXm9q59UC9AaAkxNs9+QFeYhuO8Vu4JGh", - "ojufZ6dPXqDR1f5xEjsA3LMiY9IkQ3HyNydO4nyMfksLwwhuB/Uomk9u3xUbFlwju8l2nbKXsKWTdfv3", - "UkE5XUE8UqTYg5Pti6uJhrQOXXhmH8VRWoodYTo+Pmhq5NNA9LkRfxYNkoqiYLpwzh0lCsNPzaMGdlAP", - "zr6w4+rRerz8R/SRlt5F1LlEfl6jqT3fYrNGT/YbWkCbrHNCbWWMnDXRC75KNjn3hXewQG9dl9fSxoxl", - "po5qDgYzLEkpGdd4saj0MvkLSddU0tSIv6MhdJPF188jRYnbxTH5YYh/drpLUCBv4qSXA2zvdQjXlzzk", - "gieFkSjZoybbI9iVg87cuNtuyHc4DnqqUmagJIPsVrXYjQaS+k6Mx0cA3pEV6/kcxI8Hz+yzc2Yl4+xB", - "K7NCv7x77bSMQshYNb1muzuNQ4KWDG4wdi++SAbmHddC5pNW4S7Yf1nPg1c5A7XM7+XYReBbEbmd+kLZ", - "tSXdxapHrAND29R8MGywcKDmpF2U+PM7/bzxue98Ml88rvhHF9kvvKRIZD+DgUUMCqZHlzOrvwf+b0q+", - "Fdupi9rZIX5h/wlIEyVJxfLs1yYrs1OPXlKerqP+rIXp+FvzclY9OXs+RYvWrSnnkEfBWV3wN68zRrTa", - "v4up4xSMT2zbLZFvp9uZXIN4G02PlB/QkJfp3AwQUrWd8FYHVOcrkREcp6mQ1kjP/tMKQQHsf1SgdCx5", - "CD/YoC60W5r7rq2/TIBneFs8Ij/Yx3HXQFrlb/CWVlcRcLVvrUG9KnNBszkWcrj87uw1saPaPvb9F1v/", - "eYWXlPYsOvaqoPjjtPBg/5RLPHVhOpzxWGoza6WTulxzLDnUtGgKSrOODR+vLyF1jsir4JlLm0dqQBh+", - "WDJZmBtXDc3qLsgT5j9a03SNV7KWSB1m+emFyz1XquCxwPrRn7oiIu47g7erXW5Ll8+JMPfmDVP2TVS4", - "gXY+ap2c7UwCPj+1PT1ZcW45Jap7jBUPuA3ZPXI2UMOb+aOYdQh/oEJu6/4fWsf9AntFCzR1i8L3Xgm0", - "2Y31Yy7+reuUcsFZiuWRYkezezx1ig9sQiWprpHVb3G3QyObK1qKvg6Tc1QcLE7vBaEjXN8IH3w1i2q5", - "w/6p8ZXONdVkBVo5yQbZ3L+o4OyAjCtwFS7xqd1ATgrZ8iuihIy6qpPapXEgG2FazMDF7nvz7Y279mO8", - "+DXjqOA7srnQdGupw7cdtbkVME1WApSbTzs3WL03fY4wTTaD7Ycj/xakrQaDbjkzbeuD7oM68x5p5wE2", - "bV+atq5OUP1zKwLZDnpWlm7Q4fc2ovqA3vJBAkc8i4l37QTEreGH0EbYbTSUBM9Tw2hwg45oKPEc7jFG", - "/fZE510jo7RajsIWxIZwRSsYMB5B4zXj0LxUGjkg0uiRgAuD+3Wgn0ol1VYFnCTTLoHm6H2OCTSlnevh", - "rqC6tYQMSXCOfozhZWyezRgQHHWDRnGjfFc/kGq4O1AmXuLLzI6Q/UcwUKtySlSGGQWdZzFigsMIbv/w", - "TvsA6G+Dvk5ku2tJ7c455CQaShJdVNkKdEKzLFaR6lv8SvCrLy4FW0irujBlWZIUa6K0i8T0uc0NlAqu", - "qmJkLN/gjsMF78xEuCF868avMCahLHb4b6wq4/DKuCCMg8MAfcSFe4biQL25Damn9RqeThRbJdMpgWfK", - "3cnRDH07Rm/63yun52LVRuQzl4YYk3LhGsXk23fm4AgrJ/RKjdqjpS5sgEF3wr8OiNfGOiW3LZXwKOvV", - "HkVnT/362LgBYvgdsTkefgOht0FBDGrPV+s9HArATQfjxal2mWuaklERNJgNZKN3bN4PYhG3nA5F7NiA", - "HfO513uaZtjTsxH2KEF9KFgfoR99nCkpKXOu8UZY9CnrItKHzYVjm65Z4O4kXJz3oMXux5uhmGyiGF/l", - "QPB7952ha3Dp7PXT+3auPirJXwntr+7lWwuvjoqPzr8fnYBDfVkz6KDR9tLVtLfTdHfyH3+1MWwEuJa7", - "fwITbm/Re6809bVda55qmpC6HPKk8sitUzH+4NJw/aOm5hHyUykUa0pwx15imhjrdomPKQX1m/qwfKDJ", - "DaQa6643DnQJcEg1JzNY8O7h/6+DNHB3rEMCXfmjsZpH/WLrew60XlpSkFpnC1UfTa/wc1aHSaFQwgq4", - "K+Du6cF2wsHksOflElLNbvakgf1tDTxIMZp7I4R9QjjICmN1GC1WETncxNYgNJalNYpPUM3vzugMJYFc", - "w+6BIi1uiFbOnvtz5TYFJJACKB0SwyJCxcIQrNXUeYaZqjkDqeDDfmx3aEpxDT66EyQ13nIsz5LmxG0S", - "HUeGjL/6MWks0/Wg9F+MCB3KFOs/GjCsbL/CNxpU/SCeL0ARXknJeb9M38YVsMCkvdpR4EtZgPK/+Qxd", - "O0rOriF8FgjdMhsqM98iamfwJoxk5DzqpXf5gvddpJf1yKwJ0uwn9EQKP2EobpoLo38lQ/HM7bjIOqjg", - "gbLRH7bkN0Z8GryWIN3zaajs5UJBooUP6hzDY4wU7u372xBBDRZbtMgNlkB519R4waKzFEueUBfZEk6Q", - "SCiowU4GlViGxxwj9kv73Wew+KKje80pNb/uLzTvw3OZ6hEx5Polcafl/syY21hWGOf2+VoVK8vCDSlD", - "038pRVal9oAON0ZtfZpc9GhElESNEml/lh2FOEgvvIbdsdX4fYV+v4Ih0lZzsqgH6fydRb5XW5OK4b26", - "F/S+pJlmPiuFyJMBy/55v5ZMl+OvWXoNGTEnhQ9jG3ikhDxEg3Ltut2sd752SlkCh+zRESFn3AYOey9u", - "u5hxZ3D+QI+Nv8VRs8qWd3IWpKMrHo/AxMJL8o7SzIMZl2EKjKi741AWyJ5KJduBOjaSbiJP9hxNvYL2", - "/ardZ1QaprJYxHSS5oWQPUEhdTxI8wZCExPSf7ho5CWOyw4T2Xboi3fIHPzchkOy++rGXotogOYE4vTA", - "R4xKkddE2vPq0WngUSctCpb2wbVI8y/hzh90wu95KyUyv5rx3FMuPjVqgFZR39i4K8o+V7WY6pCqC8dG", - "1ylapDTZ66Jq4TDJUXUoGkt8/i2hESKf1xrTvPU6J+u8lOOLelkeT6m9MZnbOmV5JcGl6th3qjrvV5RU", - "r70ENc379xqjI4PCPBr7BgJV9hburQHukazu0STKJIcbaHnuXP5QlaagFLuB8IEt25lkACXaxroaW8wl", - "FYr2zjHu5p4ETo0p1I2e65awdqXInkM7qmJseWK3iZq6lQxGNyyraIt+6g5vHw09exQRwx7XiZLiYCER", - "n9yYiNjrREaej+5LHvchh+lr9YUcR8tqw51lwmZnq5Ju+LACG7F51I7Nu8+DIDCiOumkA0Gz+LJTUted", - "jB2PLvPNM78ZsXmFqnOXC16LqmEOvJtb889d7k2DTBnnydvV+5m0k/rut4iwCV6IGjcSh+XAmjwDab24", - "aFTy8qq7GX5q5Ni0t6p8hz3ohb6D4LUqf41z6HzhZICfaqIEUxnkhNb097kj3AQbwR8skT31zDRtcUYb", - "SNpel8DXpF7WLpyhJ+S6nh6s/SU41kPse4gUevXxWYWQccxGlzc0//xeHiwKd4b0cG9yxycauglCIltS", - "qttF5L6mk8YOXAL3NzR/i16pv4FZo+glwYFyJ0qtZXknNopMmhshXj/GiSDJBmHa+I0nX5OFyyYsJaRM", - "dU+qja/4XlvF8QGU5qn2cTP8vnn+KvQd2HjpFT/ypqkejVeuFW8wbLboFxYqAzs3yuUx7uuxRYR+MRkV", - "lvXZc1xctwI7bDX+TsSykHDPAR5BqOaBAR79gkVTp2eDGMyhUynoz3Pyad2ibeSgbuY2NTqpT9yxEsNT", - "gorilcNNd4xqsgTBsvsEUSW/P/mdSFjiu1qCPH6MAzx+PHdNf3/a/my28+PH8RfhP1c8k6WRg+HGjXHM", - "r0MZLjaLYyCZqrMeFcuzfYzRSo1rXqbD5K/fXHLsF3kb7zfrdu5vVfc+0SGRlN1FQMJE5toaPBgqSHqb", - "kO/mukWy29Ckm1aS6R3W7PLXOfZbNPLqhzqwwQXG1FVe3NmnxTXUVd+aMIhK+dP1B0FzPI+MTo1xrBpf", - "Af9uS4syB7dRvnmw+A949pfn2cmzJ/+x+MvJVycpPP/qxckJffGcPnnx7Ak8/ctXz0/gyfLrF4un2dPn", - "TxfPnz7/+qsX6bPnTxbPv37xHw+MHDIoW0RnvkLE7H/jA5LJ2dvz5NIg29CElqx+/N+wsX8Fi6a4E6Gg", - "LJ+d+p/+p99hR6koGvD+15lLQJ+ttS7V6fHxZrM5Crscr9DvmWhRpetjP07/0fW357XB2F7KcUVtfpg3", - "tnhWOMNv7767uCRnb8+Pgkd9T2cnRydHT/DN1xI4LdnsdPYMf8Lds8Z1P3bMNjv9+Gk+O14DzTFMyPxR", - "gJYs9Z8k0Gzn/q82dLUCeeSeBjM/3Tw99mrF8Ufn//009u04rLJ//LHlJs/29MQq3McffXGp8dat6k0u", - "PCDoMBGLsWbHC8xZn9oUVNB4eCp42VDHH1FdHvz92CXyxj/itcXuh2MfSxJv2aLSR701uHZ6pFSn66o8", - "/oj/Qf4M0LJh8310M7gpRAZ+PLFc2lp3Y5+PP9p/AzCwLUEyo7fZ4JuVLZhX74rzbHY6+y5o9HIN6TWW", - "h7d2C2T3pycnkZSgoBexu48ucsjM1nl+8nxCBy502MkVMup3/IVfc7HhBAPIrSiuioLKHao4upJckZ9/", - "JGxJoDsEU34E3P50pdB1hbWoZ/NZizwfPjmi2eTiY/v+cUNL//OOp9Ef+4vYfYcn9vPxx3Yd6BYzq3Wl", - "M7EJ+uJlyN7k++PVL6O0/j7eUKaNeuOCsbBOV7+zBpofuzTDzq9NZH/vC6YrBD+Ght3or8d1GcTox66g", - "iX11G22gkfeG+c+N0hEe4rPT98Hx/f7Dpw/mmzSt8VNzJp0eH2OAw1oofTz7NP/YOa/Cjx9qHvPVF2al", - "ZDeYzPHh0/8LAAD//+8NMlAJugAA", + "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", } // 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 beee2e5a04..67309448fb 100644 --- a/daemon/algod/api/server/v2/generated/model/types.go +++ b/daemon/algod/api/server/v2/generated/model/types.go @@ -610,6 +610,9 @@ type PendingTransactionResponse struct { // SimulateRequest Request type for simulation endpoint. type SimulateRequest struct { + // LiftLogLimits The boolean flag that lifts the limit on log opcode during simulation. + LiftLogLimits *bool `json:"lift-log-limits,omitempty"` + // TxnGroups The transaction groups to simulate. TxnGroups []SimulateRequestTransactionGroup `json:"txn-groups"` } @@ -653,6 +656,15 @@ type SimulateTransactionResult struct { TxnResult PendingTransactionResponse `json:"txn-result"` } +// SimulationEvalOverrides The set of parameters and limits override during simulation. If this set of parameters is present, then evaluation parameters may differ from standard evaluation in certain ways. +type SimulationEvalOverrides struct { + // MaxLogCalls The maximum log calls one can make during simulation + MaxLogCalls *uint64 `json:"max-log-calls,omitempty"` + + // MaxLogSize The maximum byte number to log during simulation + MaxLogSize *uint64 `json:"max-log-size,omitempty"` +} + // StateDelta Application state delta. type StateDelta = []EvalDeltaKeyValue @@ -1014,6 +1026,9 @@ type PostTransactionsResponse struct { // SimulateResponse defines model for SimulateResponse. type SimulateResponse struct { + // EvalOverrides The set of parameters and limits override during simulation. If this set of parameters is present, then evaluation parameters may differ from standard evaluation in certain ways. + EvalOverrides *SimulationEvalOverrides `json:"eval-overrides,omitempty"` + // LastRound The round immediately preceding this simulation. State changes through this round were used to run this simulation. LastRound uint64 `json:"last-round"` 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 8d0203f0f8..1adb0f8493 100644 --- a/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go +++ b/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go @@ -183,122 +183,125 @@ var swaggerSpec = []string{ "/5YRnYOLAgb9CozxuJ0hqGB6/aWYTuhOsB2/887FoEUOdxcolMpHWI96xIhiMCoWghXKrrpwcdU+stZz", "UgtJJ7TRu1kf//d0i8w4A/ZfqmIpl3jlqgzUOo0qUVFABdKOYFWwekwX9dBQCHJYAd0k8cvDh92JP3zo", "1lxoNocrn4xgG3bJ8fAh2nHeKm1am+sObIV2u51Gjg90htiDz91CujJlt9fdQR6zkm87wGsPit1TWjvG", - "tdO/tQDo7Mz1mLmHPDIu4gDhjvJzBKBj88Z1PxOrKufmLjw6WxXS+kIhVivIBDeQb1hRQgoUcG41LE24", - "WNQYhaKlSy4XqFiXqlq4WCiCg4Kx0mTCKCvZAxFVPsxaJotSVUVMULr4V59zYNUO4PbqExASO5Oif8Xr", - "8VyayZgTzBM8WJ3vLMwhR8t0MngztES9bG6GRJx24kScCpgJkugqTQGiUdGxO1c91U6CaJPy4wBataEq", - "KSyM8dRUPA+5jp3OGZebduYoF7m2UlBohu1s5ybUeEpz82k9c56TuzqSZxLulJbGF6x8Q9IuKUY6HpBJ", - "rDbU54yQAe32smz8xxjxG9AxLPsDB3FozcehUDR7Ac83d6AGESBWQlGCxkMrNFxp+qrmYTqYO9X0RhtY", - "9W371PWXAUHzbvAGqWQuJCQrJWETzYAWEn7Aj1HBgQfnQGdUYYb6dm8lLfw7aLXHGcONt6UvrnYgi97W", - "MZh3sPhduB23TpgIh2ZLyAvGWZoLNGoqqU1ZpeaD5Gg2CTZbJFbF3w+HDWkvfZO45S5iWHOgPkiOcUq1", - "MSXqX59DxHLwLYC3p+lqsQDdkZ9sDvBBulZCskoKg2Ot7HoltGAFlBgwckAtV3xjRSDa/X6HUrFZZdoy", - "GZNxtLHiknxMdhim5h8kNywHe6f+QcjzNYLzPlrPMxLMlSovairEj5AFSNBCJ/GYmu/oK4Y7uukvXegj", - "Jk/TZ/JKWPhNxs4GrSpNQvD/u/+fx+9Pkv/mye9HyYv/dfjx09PrBw97Pz6+/uqr/9/+6cn1Vw/+8z9i", - "K+Vxj6WKOMxPX7nL2ukr1Mgbt0QP989mkl4JmUSZLHS+d3iL3ce0SMdAD9r2GrOED9KspWWkS56LzKpc", - "N2GHrojr7UXaHR2uaS1Exz7j57qnnnsLKcMiQqYjGm98jPfD0OJJWegnc3lWuF/mlaSl9Iou5Rz4cCA1", - "n9aJd1ST45hhVtaS+1g29+fjZ88n0yabqv4+mU7c148RThbZOqodwjp2fXEbBDfGPc0KvtEwoIAi7tHI", - "Jwo3CMGuwN579VIUn19SaCNmcQnnI7mdGWQtTyWFWNv9g163jTPmq/nnx9uUVg8vzDKWq9/SFLBVs5oA", - "nUiIolSXIKdMHMBB1wyR2auZi8HKgc8xZxwvempMZkq9D4jRPFcEVA8nMuquH+MfVG6dtL6eTtzhr+9c", - "H3eAY3h1x6xdbP5vo9i97745Z4dOYOp7lL5JoIOEu8it1eWUtGJkrDSjCiWUv/pBfpCvYC6ksN+PP8iM", - "G34441qk+rDSUH7Ncy5TOFgoduzTVF5xwz/InqY1WEQoSBBiRTXLRcouQo24YU8qDNGH8OHDe54v1IcP", - "H3vhAn391Q0VlS80QHIlzFJVJnFp7UkJV7yMuWN0ndaMkKluxbZRp8zBJlHs0uYd/LjM40Whu+mN/ekX", - "RW6nH7Chdsl7dsmYNqr0uohVUAgbXN83yh0MJb/yJoxKg2a/rnjxXkjzkSUfqqOjJ8Ba+X6/uiPf8uSm", - "gNGGjMH0y679AidO9xpYm5InBV/EvD4fPrw3wAtcfdSXV3jJznOG3Vp5hj6OGkE1E/D0GF4AwmPvnCmc", - "3Bn18iWM4lPAT7iE2MaqG40v+qbrFWQe3ni5OtmLvVWqzDKxezs6K21Z3K9MXdlkYZUsHyCgxQKDMF0R", - "mBmwdAnphavOAavCbKat7j4GxSmaXnQITXVbKG8IKwegzXwGrCoy7lTxrgVptmEajPFRoO/gAjbnqik8", - "sE/OdjuFWA9tVOTUQLu0zBpuWweju/gu0AlNXEXhM3ExJcuzxXHNF77P8EYmlfcONnGMKVoprkOE4GWE", - "EMT8AyS4wUQtvFuxfmx69pYxo5MvUsPFy37mmjSXJxeTFM4GDdz0fQVYBEpdaTbjVm9Xrn4RpckGUqzS", - "fAEDGnLothiZjNpydSCQXede9KRT8+6B1jtvoihT48TOOcopYL9YVsHLTCcSzY9EnjHnBMCyhI5gsxzV", - "pDpkj4QOL1vuI6qzNoRanIGhlI3C4dFoUyTUbJZc+9JKWIHK7+VROsAfmPa9rdhHaNAPykzV9nUvc7v7", - "tHe7dCU/fJ0PX9wjvFqOKNRhNXyM244th5KoAGWQw4ImTo09ozQp6M0CWTx+nM9zIYElsXgsrrVKBdXG", - "ao4ZNwZY/fghY2QCZqMhxNg4QBs9vgiYvVHh3pSLfZCULoWee9joKw7+hni2D0UoW5VHFVaEiwEHUuol", - "AHdBfPX51QklRTBMyCmzYu6S51bMuRtfA6RXcwLV1k6FCRdz8GBInd1igaeDZa850VF0k9mEOpNHOq7Q", - "bcF4ptYJpftFNd7Zemb5PRq0jcmHsY1J1T3uaTZTa4xjwaOFgoR34DKMh0cjuOGvhUZ+xX5Dpzkhs23Y", - "7dpUjAs1sowz59XsMqROjBl6QIMZYpf7QcGOGyHQMXY01W/d5XfnJbWtnvQP8+ZUmzaFqHw+TGz7D22h", - "6CoN0K9vhalLbLztaixRO0U7HKNdXSRQIWNMb8VE30nTdwVpyAEvBUlLiUouYq47e7cBPHHOfLfAeIE1", - "TLjcPAhifEpYCG2gMaL7kIQvYZ7kWDpNqfnw7ExRzu383ilVH1NUmwc7tqb52WeAQbJzUWqToAciOgXb", - "6FuNl+pvbdO4rtSOIqJCoyKLywYc9gI2SSbyKs6vbtzvX9lh39QiUVczlLdCUmzIDAvjRmMLtwxN4adb", - "J/yaJvya39l8x+0G29QOXFp2aY/xJ9kXHcm7TRxEGDDGHP1VGyTpFgEZ5IT2pWOgN9HmxJzQg23W195m", - "yjzsnWEjPjN16IwiSNG5BAaDrbMQ6CayaokwQV3ZfrLmwB7gRSGydccWSlAHb8x8L4OHr8bVoQKurgO2", - "gwKB3TOWL1KCbhdeaxR8qhDcqntyMIoy5+3yaKFACIcS2te37xOqzifbRatz4Pn3sPnZtsXpTK6nk9uZ", - "TmO0dhB30PptvbxROqNrnkxpLU/IniTnRVGqS54nzsA8xJqlunSsic29Pfozi7q4GfP8m5PXbx3619NJ", - "mgMvk1pVGJwVtiv+NLOiGm8DG8TXz7Z3Pq+zkyoZLH5dmCo0Sl8twRUiDrTRXsXExuEQbEVnpJ7HI4R2", - "mpydb4SmuMVHAkXtImnMd+QhaXtF+CUXubebeWwHonlwcuPKbkalQgjg1t6VwEmW3Km46e3u+O5ouGuH", - "TArH2lIqeUXVwDVTsutCx/DiTeG87iuO9Q7JKtIXTrJaoSUh0blI4zZWOdOWOST5zmxjho0HlFELsRID", - "rlhZiQCWbTamokkHyWCMKDF1tKhKQ7uZci+9VFL8VgETGUhjP5W4KzsbFQtMOmt7/zi1ukN/LAeYLPQN", - "+NvoGGGtz+6Jh0hsVzBCT10P3Vf1ldlPtLZIYbh145LYw+Efjtg7Erc46x1/OG6m4MVl2+MWPszSl3+W", - "MahC9+5XYfzl1RUdHRgj+sqL0Mm8VL9D/J6H1+NIKo6vbiowyuV3kCNizhvrTvNYTTP64HIPaTehFaod", - "pDDA9bjygVsOyyx6CzWXtNT06EIr1i3OMGFU6SHBbxjG4dyLxM351YzHalBaJcPidNI4gFu2dKOY7+xp", - "r+vEBhqdBb7kuq2gNOsCyiZLrl+y5YYKAw07WlVoNAPk2lAnmJL/L9cqAqaSV1zS2x22H20l11sDGb9s", - "rytVYpEEHTf7Z5CKFc/jmkOW9k28mVgIepai0hC8e+AA0ZM/xEXu7Yg6XceR5nTOjqbB4ytuNTJxKbSY", - "5YAtHlGLGdcoyWtDVN3FTg+kWWps/nhE82UlsxIys9REWK1YrdTh9aZ2Xs3AXAFIdoTtHr1g99Ftp8Ul", - "PLBUdOfz5PjRCzS60h9HsQPAPSuyTZpkKE7+4cRJnI/Rb0kwrOB2UA+i+eT0rtiw4Nqym6jrmL2ELZ2s", - "272XVlzyBcQjRVY7cKK+uJpoSOvQRWb0KI42pdowYeLjg+FWPg1En1vxR2iwVK1Wwqycc0erleWn5lED", - "GtSDoxd2XD1aj5f/iD7SwruIOpfIz2s0pfMtNmv0ZL/hK2iTdco4VcbIRRO94Ktks1NfeAcL9NZ1eYk2", - "diw7dVRzMJhhzopSSIMXi8rMk7+xdMlLnlrxdzCEbjJ7/jRSlLhdHFPuh/hnp3sJGsrLOOnLAbb3OoTr", - "y+5LJZOVlSjZgybbI9iVg87cuNtuyHe4HfRYpcxCSQbZrWqxGw8k9a0YT24BeEtWrOezFz/uPbPPzplV", - "GWcPXtkV+unda6dlrFQZq6bXbHencZRgSgGXGLsXXyQL85ZrUeajVuE22H9Zz4NXOQO1zO/l2EXgaxW5", - "nfpC2bUl3cWqR6wDQ9vUfrBsMHOgpqxdlPjzO/288bnvfLJfPK74RxfZL7ykSGQ/g4FFDAqmR5czq78H", - "/m/OvlbrsYva2SF+Yf8FSBMlSSXy7OcmK7NTj77kMl1G/Vkz2/GX5uWsenJ0PkWL1i25lJBHwZEu+IvX", - "GSNa7T/V2HFWQo5s2y2RT9PtTK5BvI2mR8oPaMkrTG4HCKnaTnirA6rzhcoYjtNUSGukZ/9phaAA9m8V", - "aBNLHsIPFNSFdkt736X6ywxkhrfFA/YdPY67BNYqf4O3tLqKgKt9Swb1qsgVz6ZYyOH8m5PXjEalPvT+", - "C9V/XuAlpT2Ljr0qKP44LjzYP+UST10YD2d7LLWdtTZJXa45lhxqWzQFpUXHho/Xl5A6B+xV8Mwl5ZFa", - "EJYf5qJc2RtXDY10F+QJ+x9jeLrEK1lLpA6z/PjC5Z4rdfBYYP3oT10REfedxdvVLqfS5VOm7L35Smh6", - "ExUuoZ2PWidnO5OAz09tT6+spCROieoe24oH3ITsHjkK1PBm/ihmHcLvqZBT3f9967ifYa9ogaZuUfje", - "K4GU3Vg/5uLfuk65VFKkWB4pdjS7x1PH+MBGVJLqGln9Fnc7NLK5oqXo6zA5R8XB4vReEDrC9Y3wwVe7", - "qMQd9KfBVzqX3LAFGO0kG2RT/6KCswMKqcFVuMSndgM5qcqWXxElZNRVndQujT3ZCNNiBi5239pvb9y1", - "H+PFL4REBd+RzYWmk6UO33Y09lYgDFso0G4+7dxg/d72OcA02QzWHw/8W5BUDQbdcnba5IPugzrxHmnn", - "AbZtX9q2rk5Q/XMrApkGPSkKN+jwextRfcCs5SCBI57FxLt2AuLW8ENoW9htaygJnqeW0eASHdFQ4Dnc", - "Y4z67YnOu0ZWaSWOwhaMQriiFQyEjKDxWkhoXiqNHBBp9EjAhcH9OtBPpyU3pAKOkmnnwHP0PscEmjbO", - "9XBbUN1aQpYkOEc/xvAyNs9mDAiOukGjuHG5qR9ItdwdKBMv8WVmR8j+IxioVTklKsOMgs6zGDHBYQW3", - "f3infQD0t0FfJ6LupuS0c/Y5iYaSRGdVtgCT8CyLVaT6Gr8y/OqLS8Ea0qouTFkULMWaKO0iMX1ucwOl", - "SupqtWUs3+CWwwXvzES4IXzrxq8wJqHMNvhvrCrj8Mq4IIy9wwB9xIV7hmJPvbkNqaf1Wp5OtFgk4ymB", - "Z8rtydEMfTNGb/rfKafnatFG5DOXhtgm5cI1ism3b+zBEVZO6JUapaOlLmyAQXfKvw6I18Y6JbctlfAo", - "69UeRWdP/frYdgPE8DtiUzz8BkJvg4IYnM5X8h4OBeCmg/Hi3LjMNcPZVhE0mA1E0TuU94NYxC2nQxE7", - "FLBjP/d6j9MMe3o2wt5KUB8K1kfoex9nygounGu8ERZ9yrqI9GFz4bZN1yxwdxIuznvQYvf95VBMNtNC", - "LnJg+L37ztAFuHT2+ul9mquPSvJXQvrVvXxL8Oqo+Oj8+9EJONSXNYMOGm3PXU17mqa7k3//M8WwMZCm", - "3PwLmHB7i957pamv7ZJ5qmnC6nLIo8ojt07F+INLw/WPmppHyE+F0qIpwR17iWlkrNs5PqYU1G/qw/KB", - "JpeQGqy73jjQS4B9qjnZwYJ3D/9dB2ng7liHBLryR9tqHvWLre840HppSUFqHRWqPhhf4eekDpNCoYQV", - "cBcg3dOD7YSD0WHP8zmkRlzuSAP7xxJkkGI09UYIekI4yAoTdRgtVhHZ38TWILQtS2srPkE1v1ujM5QE", - "cgGbe5q1uCFaOXvqz5WbFJBACqB0SCyLKB0LQyCrqfMMC11zBlLBh/1Qd2hKcQ0+uhMkNd5wLM+S9sRt", - "Eh23DBl/9WPUWLbrXum/GBE6lCnWfzRgWNl+hW806PpBPF+AIrySstN+mb4rV8ACk/ZqR4EvZQHa/+Yz", - "dGmUXFxA+CwQumWueJn5FlE7gzdhJFvOo156ly9430V6Xo8smiDNfkJPpPAThuKmubL6VzIUz9yOi6yD", - "Cu5piv6gkt8Y8WnxmkPpnk9DZS9XGhKjfFDnNjy2kcK9fX8TIujBYouE3GAJlHdNjRcsOsux5Al3kS3h", - "BFkJK26xK4NKLMNjbiP2S/ruM1h80dGd5pSaX3cXmvfhuUL3iBhy/Zy503J3ZsxNLCtCSnq+VsfKskhL", - "ytD0X5Qqq1I6oMONUVufRhc92iJKokaJtD/LjkIcpBdewOaQNH5fod+vYIg0aU6EepDO31nkO7U16Rje", - "iztB70uaaaaTQqk8GbDsn/ZryXQ5/kKkF5Axe1L4MLaBR0rYfTQo167bq+XG104pCpCQPThg7ERS4LD3", - "4raLGXcGl/fMtvHXOGpWUXknZ0E6+CDjEZhYeKm8pTTzYLbLMA1W1N1yKAKyo1LJeqCOTcmvIk/2HIy9", - "gvb9qt1nVBqmIixiOknzQsiOoJA6HqR5A6GJCek/XLTlJY7zDhNRO/TFO2T2fm7DIdl9dWOnRTRAcwRx", - "euAjRqXIayLtefXoNPCok1ErkfbBtUjzp3DnDzrhd7yVEplfzXjuKRefGjVAq6hvbLsrip6rmo11SNWF", - "Y6PrFC1Smux0UbVwGOWo2heNOT7/lvAIkU9rjWnaep1TdF7K8UW9iMdTTjcme1vnIq9KcKk69E5V5/2K", - "gpull6C2ef9eY3Vk0JhHQ28gcE23cG8NcI9kdY8mVSQ5XELLc+fyh6o0Ba3FJYQPbFFnlgEUaBvramwx", - "l1Qo2jvHuJt7Ejg1xlA3eq4TYWml2I5DO6pirGVC20SP3UoWo0uRVbxFP32Lt4+Gnj2KiGGP60hJsbeQ", - "iE9um4jY6URGno/uSxn3IYfpa/WFHEfLasMdMWGzs3XBr+SwAhuxedSOzdvPgyEwpjvppANBs/iyU1LX", - "nYwdjy7zzTO/HbF5hapzlwtei6phDrybW/PPbe5Ng0wZ58mb1fsZtZP67reIsAleiNpuJA7LgTV5BiV5", - "cdGo5OVVdzP80MixcW9V+Q470At9B8FrVf4a59D5wskAP9RECaYyyAmt6e9yR7gJNoI/WCI69ew0qTgj", - "BZK21yXwNemXtQtn6Am5rqcHa38pifUQ+x4ijV59fFYhZBy70ctLnn9+Lw8WhTtBerg3ueMTDd0EIZGJ", - "lPpmEbmv+aixA5fA3Q0t36JX6h9g1yh6SXCg3IlSa1neiY0ik+dWiNePcSJIdoUwKX7j0XM2c9mERQmp", - "0N2T6spXfK+t4vgASvNU+3Yz/K55/qzMLdh47hU/9qapHo1XroVsMGy26BcWKgM7N8rlMe7rsUWEfjEZ", - "FZb12XFcXLQCO6gafydiWZVwxwEeQajmngEe/YJFY6dHQQz20Kk09Oc5+rRu0TZyUDdzGxud1CfuthLD", - "Y4KK4pXDbXeMaiKCYNl9hqiyXx/9ykqY47taij18iAM8fDh1TX993P5st/PDh/EX4T9XPBPRyMFw48Y4", - "5uehDBfK4hhIpuqsRyXybBdjtFLjmpfpMPnrF5cc+0XexvuF3M79rereJ9onkrK7CEiYyFxbgwdDBUlv", - "I/LdXLdIdhuadNOqFGaDNbv8dU78Eo28+q4ObHCBMXWVF3f2GXUBddW3Jgyi0v50/U7xHM8jq1NjHKvB", - "V8C/WfNVkYPbKF/dm/0VnvztaXb05NFfZ387enaUwtNnL46O+Iun/NGLJ4/g8d+ePT2CR/PnL2aPs8dP", - "H8+ePn76/NmL9MnTR7Onz1/89Z6VQxZlQnTiK0RM/i8+IJmcvD1Nzi2yDU14IerH/y0b+1eweIo7EVZc", - "5JNj/9P/9jvsIFWrBrz/deIS0CdLYwp9fHh4dXV1EHY5XKDfMzGqSpeHfpz+o+tvT2uDMV3KcUUpP8wb", - "WzwrnOC3d9+cnbOTt6cHwaO+x5Ojg6ODR/jmawGSF2JyPHmCP+HuWeK6Hzpmmxx/up5ODpfAcwwTsn+s", - "wJQi9Z9K4NnG/V9f8cUCygP3NJj96fLxoVcrDj85/+/1tm+HYZX9w08tN3m2oydW4T785ItLbW/dqt7k", - "wgOCDiOx2NbscIY562Obgg4aD08FLxv68BOqy4O/H7pE3vhHvLbQfjj0sSTxli0qfTJri2unR8pNuqyK", - "w0/4H+TPaxIYOcQiRyj/lbOm+ZQJw/hMlVjVyaRLKyN8ORmhg5YT5Fpi+NPMMrrt9ZIw8IXjqJLu8fu+", - "+QQBMQ8JpYJl+WbTtkZq5LIpKwiLu9anTqt9c/a8P0pefPz0aPro6Pov9mxxfz57cj3STfCyhsvO6oNj", - "ZMOPWIsFjTK4lx8fHd3ileITGZCfFil4DLtX3oxWYtiC65aqA4jVxNhRM6IDPvbs4fV08nTPGW+1JbWC", - "+iPPF37NM+a9fzj2o8839qnEADwr4xmdYdfTybPPOftTaVme5wxbBkXA+kv/k7yQ6kr6llbhqFYrXm78", - "NtYtocDcYuOxxhcaXbKluOSo50klWy8bTT5iGEDMAzsgb7ThN5A3Z7bXv+XN55I3uEh3IW/agO5Y3jze", - "c8//+Wf8bwn7Z5OwZyTubiVhncJHmZB9DTSDy5XKwKuQaj6n8sXbPh9+on8DMLAuoBQrkFTWzf1KBVEO", - "sajYpv/zRqbRH/tYdt8OjP18+Kn9dkVLAdfLymTqiqr1RE8cLNDMc1fNEa2t9c3NKOYBNEkG7EeXBJhv", - "0MQsMmAcq5OoyjRXa9u5drrXzg8LoXlXdCEkDoBWbByFypbyIHxXQ6okvcLXOd0cZm9UBv3TDc+v3yoo", - "N80B5nCcTFvizfFnpEjorU+LvjS63o970dpOrqI+c9RP77X+Prziwtgz0EX7I0X7nQ3w/NDVsej82qSO", - "9r5gPmzwYxg5EP31sK6zHf3YvcnGvrqb3EAjH27lPzdWrdBKhCxR24fef7Qri1UcHbc0Ro/jw0OMoF0q", - "bQ4n19NPHYNI+PFjvZi+vFe9qNcfr/8nAAD//zho4oJqxAAA", + "tdO/tQDo7Mz1mLmHPDIu4gDhjvJzBKBj88Z1PxOrKufmLjw6cMnzRF1CWYoMdkpyN7BQ8ptLnv9Yd9tx", + "p2sipMRqBZngBvINK0pIgSLXraqma9gHjGLa0iWXC9TQS1UtXFAVwUEJW2myhZSV7IGIajFmLZNFqaoi", + "JnFdIK1PXrD6C3B7hwpWBDvTjeGK1+O5fJUxR6FfuWCZv7Mwhzw208ngFdMS9bK5YhJx2hkYcSpgSkmi", + "qzQFiIZXxy5v9VQ7maZN7pADaPWPqqT4MsZTU/E8ZF92OmdcbtopqFzk2opToRm2s52bmOUpzc3nB815", + "Tn7vSMJKuOVaqmOw8g1Ju6QY6cFAJrFqVZ8zQga0+9Sy8R/jDWhAx7DsDxwEtDUfh2La7E0+39yBPkWA", + "WAlFCRpPv9ACpumrmod5Ze541BttYNV3ElDXXwYEzbvBq6iSuZCQrJSETTSVWkj4AT9GBQeewAOdURca", + "6tu93rTw76DVHmcMN96WvrjagSx6Wwdz3sHid+F2/ENhRh3aPyEvGGdpLtA6qqQ2ZZWaD5Kj/SXYbJGg", + "F3/RHLbIvfRN4ibAiIXOgfogOQY81VaZqKN+DhETxLcA3jCnq8UCdEd+sjnAB+laCckqKQyOtbLrldCC", + "FVBi5MkBtVzxjRWBaED8HUrFZpVpy2TM6tHGiktyVtlhmJp/kNywHOzl/Achz9cIzjt7Pc9IMFeqvKip", + "ED9CFiBBC53Eg3O+o68YN+mmv3QxlJiFTZ/JvWHhN6k/GzTPNJnF/+/+fx6/P0n+mye/HyUv/tfhx09P", + "rx887P34+Pqrr/5/+6cn1189+M//iK2Uxz2Wc+IwP33lbn2nr1C1b/wbPdw/m217JWQSZbLQi9/hLXYf", + "8ysdAz1oG37MEj5Is5aWkS55LjKrct2EHboirrcXaXd0uKa1EB1Dj5/rngrzLaQMiwiZjmi88THej2eL", + "Z3ehw80lbOF+mVeSltIrupS84OOK1HxaZ/BRcY9jhuldS+6D4tyfj589n0ybtKz6+2Q6cV8/RjhZZOuo", + "dgjr2D3IbRDcGPc0K/hGw4ACirhHQ6gobiEEuwJ7gdZLUXx+SaGNmMUlnA8Jd/aUtTyVFKtt9w+67zbO", + "K6Dmnx9vU1o9vDDLWNJ/S1PAVs1qAnRCKopSXYKcMnEAB117RmavZi6YKwc+x+RzvOipMSku9T4gRvNc", + "EVA9nMgoo0GMf1C5ddL6ejpxh7++c33cAY7h1R2z9tX5v41i97775pwdOoGp71EeKIEOMvcit1aXnNIK", + "trHSjEqdUCLsB/lBvoK5kMJ+P/4gM2744YxrkerDSkP5Nc+5TOFgodixz3d5xQ3/IHua1mA1oiDTiBXV", + "LBcpuwg14oY9qcJEH8KHD+95vlAfPnzsxR309Vc3VFS+0ADJlTBLVZnE5ccnJVzxMubX0XV+NEKmAhjb", + "Rp0yB5tEscu/d/DjMo8Xhe7mSfanXxS5nX7AhtplAdolY9qo0usiVkEhbHB93yh3MJT8ypswKg2a/bri", + "xXshzUeWfKiOjp4AayUO/uqOfMuTmwJGGzIG8zi79gucON1rYG1KnhR8EXMfffjw3gAvcPVRX17hJTvP", + "GXZrJSz6gGwE1UzA02N4AQiPvZOvcHJn1MvXQopPAT/hEmIbq240Tu2brleQwnjj5eqkQfZWqTLLxO7t", + "6Ky0ZXG/MnWJlIVVsnykgRYLjOZ01WRmwNIlpBeuzAesCrOZtrr7YBanaHrRITQVgKEEJCxBgMb3GbCq", + "yLhTxbsWpNmGaTDGh5O+gwvYnKumgsE+yd/tXGQ9tFGRUwPt0jJruG0djO7iu4gpNHEVhU/pxdwuzxbH", + "NV/4PsMbmVTeO9jEMaZo5coOEYKXEUIQ8w+Q4AYTtfBuxfqx6dlbxoxOvkgxGC/7mWvSXJ5ccFM4GzRw", + "0/cVYDUpdaXZjFu9XblCSJRvG0ixSvMFDGjIof9jZFZry2eCQHade9GTTs27B1rvvImiTI0TO+cop4D9", + "YlkFLzOdkDY/ErnYnBMA6xs6gs1yVJPq2D8SOrxs+aGoYNsQanEGhlI2CodHo02RULNZcu1rNGEpK7+X", + "R+kAf2D++LaqIaFBP6hXVdvXvczt7tPe7dLVDvEFQ3yVkPBqOaLih9XwMQA8thxKogKUQQ4Lmjg19ozS", + "5LI3C2Tx+HE+z4UElsQCu7jWKhVUZKs5ZtwYYPXjh4yRCZiNhhBj4wBtdB0jYPZGhXtTLvZBUrpcfO5h", + "o9M5+BviaUMU6mxVHlVYES4GHEiplwDcRQPW51cnJhXBMCGnzIq5S55bMedufA2QXvEKVFs7pSpc8MKD", + "IXV2iwWeDpa95kRH0U1mE+pMHum4QrcF45laJ5Q3GNV4Z+uZ5fdo9DdmMcY2JpUJuafZTK0xIAaPFoo2", + "3oHLMB4ejeCGvxYa+RX7DZ3mhMy2YbdrUzEu1MgyzpxXs8uQOjFm6AENZohd7geVP26EQMfY0ZTRdZff", + "nZfUtnrSP8ybU23aVLTyiTWx7T+0haKrNEC/vhWmrtXxtquxRO0U7biOdpmSQIWMMb0VE30nTd8VpCEH", + "vBQkLSUquYi57uzdBvDEOfPdAuMFFkPhcvMgCBYqYSG0gcaI7kMSvoR5kmMNNqXmw7MzRTm383unVH1M", + "UZEf7Nia5mefAUbbzkWpTYIeiOgUbKNvNV6qv7VN47pSOxyJKpaKLC4bcNgL2CSZyKs4v7pxv39lh31T", + "i0RdzVDeCkmxITOssBsNUtwyNMWxbp3wa5rwa35n8x23G2xTO3Bp2aU9xp9kX3Qk7zZxEGHAGHP0V22Q", + "pFsEZJBc2peOgd5EmxOTSw+2WV97mynzsHeGjfgU16EziiBF5xIYDLbOQqCbyKolwgQFavtZnwN7gBeF", + "yNYdWyhBHbwx870MHr6sV4cKuLoO2A4KBHbPWOJJCbpdwa1R8KnUcKuAysEoypy366yFAiEcSmhfKL9P", + "qDoxbRetzoHn38PmZ9sWpzO5nk5uZzqN0dpB3EHrt/XyRumMrnkypbU8IXuSnBdFqS55njgD8xBrlurS", + "sSY29/bozyzq4mbM829OXr916F9PJ2kOvExqVWFwVtiu+NPMiorFDWwQX4jb3vm8zk6qZLD4dYWr0Ch9", + "tQRX0TjQRnulFxuHQ7AVnZF6Ho8Q2mlydr4RmuIWHwkUtYukMd+Rh6TtFeGXXOTebuaxHYjmwcmNq98Z", + "lQohgFt7VwInWXKn4qa3u+O7o+GuHTIpHGtLzeUVlRXXTMmuCx3DizeF87qvOBZOJKtIXzjJaoWWhETn", + "Io3bWOVMW+aQ5DuzjRk2HlBGLcRKDLhiZSUCWLbZmNIoHSSDMaLE1NHqLA3tZso9GVNJ8VsFTGQgjf1U", + "4q7sbFSsVOms7f3j1OoO/bEcYLLQN+Bvo2OERUO7Jx4isV3BCD11PXRf1VdmP9HaIoXh1o1LYg+Hfzhi", + "70jc4qx3/OG4mYIXl22PW/jCS1/+WcagUt+7n5fxl1dXvXRgjOhzMUIn81L9DvF7Hl6PIzk9vkyqwCiX", + "30GOiDlvrDvNqzfN6IPLPaTdhFaodpDCANfjygduOazX6C3UXNJS0+sNrVi3OMOEUaWHBL9hGIdzLxI3", + "51czHitmaZUMi9NJ4wBu2dKNYr6zp72uExtodBb4kuu2gvK1CyibdLt+7ZcbKgw07GhVodEMkGtDnWBK", + "/r9cqwiYSl5xSY+A2H60lVxvDWT8sr2uVInVFnTc7J9BKlY8j2sOWdo38WZiIeh9i0pD8ICCA0RvBxEX", + "uUco6nQdR5rTOTuaBq+4uNXIxKXQYpYDtnhELWZcoySvDVF1Fzs9kGapsfnjEc2XlcxKyMxSE2G1YrVS", + "h9eb2nk1A3MFINkRtnv0gt1Ht50Wl/DAUtGdz5PjRy/Q6Ep/HMUOAPc+yTZpkqE4+YcTJ3E+Rr8lwbCC", + "20E9iCam0wNlw4Jry26irmP2ErZ0sm73XlpxyRcQjxRZ7cCJ+uJqoiGtQxeZ0es62pRqw4SJjw+GW/k0", + "EH1uxR+hwVK1Wgmzcs4drVaWn5rXEWhQD46e6nGFbT1e/iP6SAvvIupcIj+v0ZTOt9is0ZP9hq+gTdYp", + "41RiIxdN9IIvt81OfQUfrPRbF/gl2tix7NRRzcFghjkrSiENXiwqM0/+xtIlL3lqxd/BELrJ7PnTSHXj", + "dpVNuR/in53uJWgoL+OkLwfY3usQri+7L5VMVlaiZA+abI9gVw46c+NuuyHf4XbQY5UyCyUZZLeqxW48", + "kNS3Yjy5BeAtWbGez178uPfMPjtnVmWcPXhlV+ind6+dlrFSZawsX7PdncZRgikFXGLsXnyRLMxbrkWZ", + "j1qF22D/ZT0PXuUM1DK/l2MXga9V5HbqK27XlnQXqx6xDgxtU/vBssHMgZqydnXjz+/088bnvvPJfvG4", + "4h9dZL/wkiKR/QwGFjGovB5dzqz+Hvi/OftarccuameH+IX9FyBNlCSVyLOfm6zMTmH7kst0GfVnzWzH", + "X5onuOrJ0fkUrX635FJCHgVHuuAvXmeMaLX/VGPHWQk5sm231j5NtzO5BvE2mh4pP6AlrzC5HSCkajvh", + "rQ6ozhcqYzhOU2qtkZ79NxqCStq/VaBNLHkIP1BQF9ot7X2XCjkzkBneFg/Yd/TK7hJYq44O3tLqKgKu", + "iC4Z1KsiVzybYiGH829OXjMalfrQQzJUSHqBl5T2LDr2qqCK5LjwYP8mTDx1YTyc7bHUdtbaJHXd51hy", + "qG3RVKYWHRs+Xl9C6hywV8F7mZRHakFYfpiLcmVvXDU00l2QJ+x/jOHpEq9kLZE6zPLjK6B7rtTBq4P1", + "60F1aUXcdxZvVwSdaqBPmbL35iuh6XFVuIR2PmqdnO1MAj4/tT29spKSOCWqe2wrHnATsnvkKFDDm/mj", + "mHUIv6dCTg8I7FsQ/gx7RSs9davL954bpOzG+lUY/2h2yqWSIsU6S7Gj2b3COsYHNqIkVdfI6re426GR", + "zRWtaV+HyTkqDla594LQEa5vhA++2kUl7qA/DT73ueSGLcBoJ9kgm/qnGZwdUEgNrlQmvtkbyElVtvyK", + "KCGjruqkdmnsyUaYFjNwsfvWfnvjrv0YL34hJCr4jmwuNJ0sdfhIpLG3AmHYQoF282nnBuv3ts8Bpslm", + "sP544B+VpGow6Jaz0yYfdB/UifdIOw+wbfvStnV1guqfWxHINOhJUbhBhx/uiOoDZi0HCRzxLCbetRMQ", + "t4YfQtvCbltDSfA8tYwGl+iIhgLP4R5j1I9YdB5IskorcRS2YBTCFa1gIGQEjddCQvPkaeSASKNHAi4M", + "7teBfjotuSEVcJRMOweeo/c5JtC0ca6H24Lq1hKyJME5+jGGl7F5f2NAcNQNGsWNy0390qrl7kCZeIlP", + "PDtC9l/TQK3KKVEZZhR03teICQ4ruP0LPu0DoL8N+joRdTclp52zz0k0lCQ6q7IFmIRnWawi1df4leFX", + "X1wK1pBWdYXLomAp1kRpF4npc5sbKFVSV6stY/kGtxwueLAmwg3hozl+hTEJZbbBf2PlHYdXxgVh7B0G", + "6CMu3HsWe+rNbUg9rdfydKLFIhlPCTxTbk+OZuibMXrT/045PVeLNiKfuTTENikXrlFMvn1jD46wckKv", + "ZikdLXVhAwy6U/6ZQbw21im5bamER1mviCk6e+pnzLYbIIYfJJvi4TcQehsUxOB0vpL3cCgANx2MF+fG", + "Za4ZzraKoMFsIIreobwfxCJuOR2K2KGAHfu513ucZtjTsxH2VoL6ULA+Qt/7OFNWcOFc442w6FPWRaQP", + "mwu3bbpmgbuTcHHegxa77y+HYrKZFnKRA8Pv3QeLLsCls9dv+NNcfVSSvxLSr+4JXYJXR8VH59+PTsCh", + "vqwZdNBoe+6K49M03Z38+58pho2BNOXmX8CE21v03nNPfW2XzFNNE1bXVR5VZ7l1KsZfbhquf9TUPEJ+", + "KpQWTS3v2JNOI2PdzvFVpqB+Ux+WDzS5hNRgAffGgV4C7FPNyQ4WPKD47zpIA3fHOiTQlT/aVvOoX7V9", + "x4HWS0sKUuuo4vXB+Ao/J3WYFAolrIC7AOneMGwnHIwOe57PITXickca2D+WIIMUo6k3QtBbxEFWmKjD", + "aLGKyP4mtgahbVlaW/EJqvndGp2hJJAL2NzTrMUN0RLcU3+u3KSABFIApUNiWUTpWBgCWU2dZ1jomjOQ", + "Cj7sh7pDU4pr8PWeIKnxhmN5lrQnbpPouGXI+PMho8ayXfdK/8WI0KFMsf7rA8PK9it87EHXL+v5AhTh", + "lZSd9sv0XbkCFpi0VzsKfCkL0P43n6FLo+TiAsL3hdAtc8XLzLeI2hm8CSPZch710rt85fwu0vN6ZNEE", + "afYTeiKFnzAUN82V1b+SoXjmdlxkHVRwT1P0B5X8xohPi9ccSvcOGyp7udKQGOWDOrfhsY0U7hH9mxBB", + "DxZbJOQGS6C8a2q8YNFZjiVPuItsCSfISlhxi10ZVGIZHnMbsV/Sd5/B4ouO7jSn1Py6u9C8D88VukfE", + "kOvnzJ2WuzNjbmJZEVLSO7g6VpZFWlKGpv+iVFmV0gEdboza+jS66NEWURI1SqT9WXYU4iC98AI2h6Tx", + "+wr9fgVDpElzItSDdP7OIt+prUnH8F7cCXpf0kwznRRK5cmAZf+0X0umy/EXIr2AjNmTwoexDbx2wu6j", + "Qbl23V4tN752SlGAhOzBAWMnkgKHvRe3Xcy4M7i8Z7aNv8ZRs4rKOzkL0sEHGY/AxMJL5S2lmQezXYZp", + "sKLulkMRkB2VStYDdWxKfhV5++dg7BW071ftvsfSMBVhEdNJmqdGdgSF1PEgzRsITUxITzvIxdwkuVok", + "uViJocosLgyezXO+IC603ZySbfsxrDO0YKpI8e0+stVHXwEJn+Pb8grIeYeBqR3GAThC7P3UhyNQ98WP", + "ndbYAM0RC9MDHzFoRV4yac+r+xbO0MtURq1E2gfXIs2fIpRgMABgxzstkfnVTO+ekfFpWQO0ivrltrvB", + "6M2t2VhnWF20NrpO0QKpyU73WAuHUU6yfdGY4xt2CY8Q+bTW1qatJ0ZF55UeX1CMeDzldFtbAj40U5Xg", + "0oTosa3O2xkFN0svvW3z/p3K6uegMYeH3l/gmiwA3hLhXvrqHouqSHK4hJbX0OUuVWkKWotLCF8Jo84s", + "AyjQLtfVFmPusPBY6agQbu5J4FAZQ92oTkGEpZViOxSGqHqzlgltEz12K1mMLkVW8Rb99C3eXRp6ciki", + "hj2uIyXF3kIiPrltImKnAxt5ProvZdx/HabO1cYAHC2rjYbEhM3O1gW/ksPKc8TeUjtVbz8PhsCY7qSy", + "DgTs4qtSSV3zMnY8enXDMb8dsXkBq3OPDF6qqmHqQW2jrBnipne2QabcxpO9Z9qi6o4G/8xmWPCC9DLm", + "X4aL6FZkXRI6AkDoRpRgdBg00UdBsxXfsEzM51CS/VwbLjNeZmFzIVkKpeHCXiY2ETvTiq9RkUSOHHgd", + "hTI8UVUkxlUSFQ+24heRqe1vpvVIaPE7bMcBizI4Q5NRiNKtx4+5uG5YbmqUMO17fyPnTfBA2XYfRViN", + "rklzKSmIAG2a/sjqLv0PzVE27qk032EHeqHrKngszVsRHDpfOBflh5oowVQ+DnFCa/q7vGFugs3ZHywR", + "KT52mlQblOKY2+sSuDr1y9qDOPSCYdfRiKXnlMRynH0HpcagEnzVI2Qcu1HKS55/ficj1iQ8QXq4t+Xj", + "Ew29VCGRiZT6ZgHhr/mosQOP1N0NLd+iU/QfYNcoek90oJxSUSvaPoYCT02e0w1+7l81ugTJrhAmhQ89", + "es5mLpm1KCEVuqusXPkHB2qnDL6/44Lw12aHF2jXPH9W5hZsPPe6P3vTFC/HW/dCNhg2W/QLC5WBnRvl", + "8hj39dgiQr+YjAqrSu04Li5acUX0GEQnYF6VcMfxRUGk8J7xRf16WWOnRzE09tCpNPTnOfq0btE2clA3", + "cxsbHNcn7rYK12Ni2uKF6213DKojguCrDwxRZb8++pWVMMdn3RR7+BAHePhw6pr++rj92W7nhw+jV4PP", + "Fk5HNHIw3Lgxjvl5KMGKkogGcvk661GJPNvFGK3MzOZhRMw9/MXlZn+Rpxl/oaiH/lZ1z2PtE8jbXQQk", + "TGSurcGDoYKcyxHplq5bJLkSPQppVQqzwZJx/kYvfokG/n1Xx9W4uKz62uTOPqMuoC462EThVNqfrt8p", + "nuN5RLc5aU8hlR+wb9Z8VeTgNspX92Z/hSd/e5odPXn019nfjp4dpfD02YujI/7iKX/04skjePy3Z0+P", + "4NH8+YvZ4+zx08ezp4+fPn/2In3y9NHs6fMXf71n5ZBFmRCd+AIlk/+L75cmJ29Pk3OLbEMTXojvYUNP", + "pVk29o+w8RR3Iqy4yCfH/qf/7XfYQapWDXj/68TVP5gsjSn08eHh1dXVQdjlcIFu98SoKl0e+nF6r7Sd", + "vD2t/RVkl8EVpfREb2/zrHCC3959c3bOTt6eHgRvSh9Pjg6ODh7hk8MFSF6IyfHkCf6Eu2eJ637omG1y", + "/Ol6OjlcAs8xSs3+sQJTitR/KoFnG/d/fcUXCygP3Mt09qfLx4derTj85MIPrrd9OwwfeTj81IrSyHb0", + "xCLwh598bbPtrVvFw1x0StBhJBbbmh3OsGTC2Kagg8bDU8HLhj78hOry4O+HLo88/hGvLbQfDn0oU7xl", + "i0qfzNri2umRcpMuq+LwE/4H+fOaBEYOscAlSr/mrGk+ZcIwPlMlFhUz6dLKCF/NSOig5QS5lhj+NLOM", + "bnu9JAx83UIq5Hz8vm9BQ0DMQ0KpYFm+2bStkRq5bMoKwtrC9anTat+cPe+PkhcfPz2aPjq6/os9W9yf", + "z55cj/QUvazhsrP64BjZ8COWAkK7HO7lx0dHt3gk+0QG5KdFCt5i71XXo5UYNuK7peoAYjUxdpQs6YCP", + "vbp5PZ083XPGW21JrZySyOuZX/OMeeczjv3o8419KjH+08p4RmfY9XTy7HPO/lRaluc5w5ZBDbr+0v8k", + "L6S6kr6lVTiq1YqXG7+NdUsoMLfYeKzxhcaIgFJcctTzpJKth7UmHzEKJRYAMCBvtOE3kDdntte/5c3n", + "kje4SHchb9qA7ljePN5zz//5Z/xvCftnk7BnJO5uJWGdwkeJuH0NNIPLlcrAq5BqPqfq2ds+H36ifwMw", + "sC6gFCuQVFXQ/Ur1eA6xpt2m//NGptEf+1h2n66M/Xz4qf10SksB18vKZOqKikVFTxysD85zV0wUra31", + "zc0o5gE0OS7sR5eDmm/QxCwyYByL46jKNFdr27mOu6idHxZC86ztQkgcAK3YOApVzeVB9LiGVEl6BLJz", + "ujnM3qgM+qcbnl+/VVBumgPM4TiZtsSb489IjdpbnxZ9aXS9H/eitZ1cRX3mqF9+bP19eMWFsWegSzZB", + "ivY7G+D5oSuj0vm1yVzufcF07ODHMHgk+uthXeY9+rF7k419dTe5gUY+4s5/bqxaoZUIWaK2D73/aFcW", + "i4g6bmmMHseHhxjAvVTaHE6up586BpHw48d6MX11uXpRrz9e/08AAAD///OIKHQyxwAA", } // 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 b15beff954..58a67b3219 100644 --- a/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go +++ b/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go @@ -609,242 +609,246 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", + "VUi2wnJ6Hz79/wAAAP//VlRZNeFZAQA=", } // 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 d9527d3e5b..aca762aa83 100644 --- a/daemon/algod/api/server/v2/generated/participating/private/routes.go +++ b/daemon/algod/api/server/v2/generated/participating/private/routes.go @@ -158,177 +158,181 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "H4sIAAAAAAAC/+x9a3PcNrLoX0HNOVV+3KEkv7JrVaXOle0kqxvHcVlK9p61fRMM2TODFQlwAVCaia/+", - "+yk0ABIkQQ5HUuxNVT7ZGuLRaDQa/canWSqKUnDgWs2OP81KKmkBGiT+RdNUVFwnLDN/ZaBSyUrNBJ8d", - "+29Eacn4ajafMfNrSfV6Np9xWkDTxvSfzyT8q2ISstmxlhXMZypdQ0HNwHpbmtb1SJtkJRI3xIkd4vTV", - "7HrkA80yCUr1ofyR51vCeJpXGRAtKVc0NZ8UuWJ6TfSaKeI6E8aJ4EDEkuh1qzFZMsgzdeAX+a8K5DZY", - "pZt8eEnXDYiJFDn04XwpigXj4KGCGqh6Q4gWJIMlNlpTTcwMBlbfUAuigMp0TZZC7gDVAhHCC7wqZsfv", - "Zwp4BhJ3KwV2if9dSoDfINFUrkDPPs5ji1tqkIlmRWRppw77ElSVa0WwLa5xxS6BE9PrgPxQKU0WQCgn", - "7759SZ48efLcLKSgWkPmiGxwVc3s4Zps99nxLKMa/Oc+rdF8JSTlWVK3f/ftS5z/zC1waiuqFMQPy4n5", - "Qk5fDS3Ad4yQEOMaVrgPLeo3PSKHovl5AUshYeKe2MZ3uinh/F90V1Kq03UpGNeRfSH4ldjPUR4WdB/j", - "YTUArfalwZQ0g74/Sp5//PRo/ujo+j/enyT/cH8+e3I9cfkv63F3YCDaMK2kBJ5uk5UEiqdlTXkfH+8c", - "Pai1qPKMrOklbj4tkNW7vsT0tazzkuaVoROWSnGSr4Qi1JFRBkta5Zr4iUnFc8OmzGiO2glTpJTikmWQ", - "zQ33vVqzdE1SquwQ2I5csTw3NFgpyIZoLb66kcN0HaLEwHUjfOCC/n2R0axrByZgg9wgSXOhINFix/Xk", - "bxzKMxJeKM1dpfa7rMj5GghObj7YyxZxxw1N5/mWaNzXjFBFKPFX05ywJdmKilzh5uTsAvu71RisFcQg", - "DTendY+awzuEvh4yIshbCJED5Yg8f+76KONLtqokKHK1Br12d54EVQqugIjFPyHVZtv/z9mPb4iQ5AdQ", - "iq7gLU0vCPBUZJAdkNMl4UIHpOFoCXFoeg6tw8EVu+T/qYShiUKtSppexG/0nBUssqof6IYVVUF4VSxA", - "mi31V4gWRIKuJB8CyI64gxQLuulPei4rnuL+N9O2ZDlDbUyVOd0iwgq6+fpo7sBRhOY5KYFnjK+I3vBB", - "Oc7MvRu8RIqKZxPEHG32NLhYVQkpWzLISD3KCCRuml3wML4fPI3wFYDjBxkEp55lBzgcNhGaMafbfCEl", - "XUFAMgfkJ8fc8KsWF8BrQieLLX4qJVwyUam60wCMOPW4BM6FhqSUsGQRGjtz6DAMxrZxHLhwMlAquKaM", - "Q2aYMwItNFhmNQhTMOG4vtO/xRdUwVdPh+745uvE3V+K7q6P7vik3cZGiT2SkavTfHUHNi5ZtfpP0A/D", - "uRVbJfbn3kay1bm5bZYsx5von2b/PBoqhUyghQh/Nym24lRXEo4/8IfmL5KQM015RmVmfinsTz9UuWZn", - "bGV+yu1Pr8WKpWdsNYDMGtaowoXdCvuPGS/OjvUmqle8FuKiKsMFpS3FdbElp6+GNtmOuS9hntTabqh4", - "nG+8MrJvD72pN3IAyEHcldQ0vICtBAMtTZf4z2aJ9ESX8jfzT1nmprculzHUGjp2VzKaD5xZ4aQsc5ZS", - "g8R37rP5apgAWEWCNi0O8UI9/hSAWEpRgtTMDkrLMslFSvNEaapxpP+UsJwdz/7jsLG/HNru6jCY/LXp", - "dYadjMhqxaCEluUeY7w1oo8aYRaGQeMnZBOW7aHQxLjdRENKzLDgHC4p1weNytLiB/UBfu9mavBtpR2L", - "744KNohwYhsuQFkJ2Da8p0iAeoJoJYhWFEhXuVjUP9w/KcsGg/j9pCwtPlB6BIaCGWyY0uoBLp82Jymc", - "5/TVAfkuHBtFccHzrbkcrKhh7oalu7XcLVbbltwamhHvKYLbKeSB2RqPBiPm3wXFoVqxFrmRenbSimn8", - "N9c2JDPz+6TOfwwSC3E7TFyoaDnMWR0HfwmUm/sdyukTjjP3HJCTbt+bkY0ZJU4wN6KV0f20447gsUbh", - "laSlBdB9sXcp46ik2UYW1lty04mMLgpzcIYDWkOobnzWdp6HKCRICh0YXuQivfgbVes7OPMLP1b/+OE0", - "ZA00A0nWVK0PZjEpIzxezWhTjphpiAo+WQRTHdRLvKvl7VhaRjUNlubgjYslFvXYD5keyIju8iP+h+bE", - "fDZn27B+O+wBOUcGpuxxdk6GzGj7VkGwM5kGaIUQpLAKPjFa915Qvmwmj+/TpD36xtoU3A65ReAOic2d", - "H4MXYhOD4YXY9I6A2IC6C/ow46AYqaFQE+B75SATuP8OfVRKuu0jGceegmSzQCO6KjwNPLzxzSyNcfZk", - "IeTNuE+HrXDSmJwJNaMGzHfeQRI2rcrEkWLEbGUbdAZqvHzjTKM7fAxjLSycafo7YEGZUe8CC+2B7hoL", - "oihZDndA+uso019QBU8ek7O/nTx79PiXx8++MiRZSrGStCCLrQZF7jvdjCi9zeFBf2WoHVW5jo/+1VNv", - "qGyPGxtHiUqmUNCyP5Q1gFoRyDYjpl0fa20046prAKccznMwnNyinVjbvgHtFVNGwioWd7IZQwjLmlky", - "4iDJYCcx7bu8ZpptuES5ldVdqLIgpZAR+xoeMS1SkSeXIBUTEW/KW9eCuBZevC27v1toyRVVxMyNpt+K", - "o0ARoSy94dP5vh36fMMb3IxyfrveyOrcvFP2pY18b0lUpASZ6A0nGSyqVUsTWkpREEoy7Ih39HegURQ4", - "ZwWcaVqUPy6Xd6MqChwoorKxApSZidgWRq5XkApuIyF2aGdu1Cno6SLGm+j0MAAOI2dbnqKd8S6O7bDi", - "WjCOTg+15WmgxRoYc8hWLbK8vbY6hA471T0VAceg4zV+RkPHK8g1vXOJrjtBDPaXnrQtsCQzDXG3XrPV", - "Wgci91spxPLuYYzNEgMUP1iFJTd9+mrLG5GZg6YrdQfiSTNYc/rNnoZnni5EpQklXGSANqZKxQWXgVgG", - "dKKi71eHspBeWx1kAYaQUlqZ1VYlQc9mj5c2HROaWupNEDVqwK9TO+RsKzud9ZPnEmi2JQsATsTCOU+c", - "WwcXSdEtq/3V78SmyFlqwVVKkYJSkCXOaLMTNN/OslU9gicEHAGuZyFKkCWVtwb24nInnBewTTCIQJH7", - "3/+sHnwBeLXQNN+BWGwTQ2+tAjsPWR/qadOPEVx38pDsqATiea7Rtw2DyEHDEAr3wsng/nUh6u3i7dFy", - "CRJ9Vb8rxftJbkdANai/M73fFtqqHAiNc6qfkX7MhnHKhRc6YoPlVOlkF1s2jVr6qVlBwAljnBgHHhBK", - "XlOlrX+V8QzNQvY6wXmsgGKmGAZ4UEQ3I//spfP+2Km5B7mqVC2qq6oshdSQxdbAYTMy1xvY1HOJZTB2", - "rQ9oQSoFu0YewlIwvkOWXYlFENW1G8IFIPQXh8Z6c89vo6hsAdEgYgyQM98qwG4YHjQACFMNoi3hMNWh", - "nDomaT5TWpSl4RY6qXjdbwhNZ7b1if6padsnLqqbezsToDAqybV3kF9ZzNrAsDVVxMFBCnphZA80EVhH", - "cB9mcxgTxXgKyRjlo/pjWoVHYOchrcqVpBkkGeR02x/0J/uZ2M9jA+CON6qg0JDYCJ/4pjeU7AMqRoYW", - "OJ6KCY8Ev5DUHEGjeTQE4nrvGDkDHDvGnBwd3auHwrmiW+THw2XbrY6MiLfhpdBmxx09IMiOo08BeAAP", - "9dA3RwV2Thq9rDvFf4NyE9RyxP6TbEENLaEZf68FDNgXXfB0cF467L3DgaNsc5CN7eAjQ0d2wNj5lkrN", - "UlairvM9bO9c9etOEHXBkQw0ZTlkJPhg1cAy7E9sbEp3zJupgpPsUn3we4apyHJyplDkaQN/AVvUud/a", - "oMfzIFTyDnTZyKjmfqKcIKA+lMqI4GET2NBU51sjqOk1bMkVSCCqWhRMaxvM3FZ1tSiTcICozX9kRufg", - "sgGDfgemeNzOcKhgef2tmM+sTjAO33lHMWihw+kCpRD5BOtRDxlRCCbFQpBSmF1nLq7aR9Z6SmoB6Zg2", - "ejfr6/+eaqEZV0D+W1QkpRxVrkpDLdMIiYICCpBmBiOC1XO6qIcGQ5BDAVaTxC8PH3YX/vCh23OmyBKu", - "fDKCadhFx8OHaMd5K5RuHa47sBWa43YauT7QGWIuPqeFdHnKbq+7G3nKTr7tDF57UMyZUsoRrln+rRlA", - "52Rupqw9pJFpEQc47iQ/RzB0bN2472esqHKq78KjMyqQ1goFKwrIGNWQb0kpIQUbcG4kLGVhMaARG4qW", - "rilfoWAtRbVysVB2HGSMlbImDFnx3hBR4UNveLKSoipjjNLFv/qcAyN2ADWqT4BI7GwF/Staz+fSTKbc", - "YB7hwe58Z8YccrTMZ4OaoUHqZaMZWuS0EyfiWMBMkERVaQoQjYqO6Vz1UjsJok3KjxvQiA2VtGFhhKa6", - "onlIdeR0SSjftjNHKcuV4YJMEWxnOjehxnO7Np/Ws6S5dVdH8kzCk9KS+IKdb1DaRcVExwMSiZGG+pQR", - "EqA5XoaMfx8jfjN0DMr+xEEcWvNxKBTNKOD59g7EIDsQkVBKUHhphYYrZb+KZZgO5m41tVUair5t33b9", - "ZYDRvBvUIAXPGYekEBy20QxoxuEH/BhlHHhxDnRGEWaob1cracHfAas9zxRqvC1+cbcDXvS2jsG8g83v", - "jttx64SJcGi2hLwklKQ5Q6Om4ErLKtUfOEWzSXDYIrEqXj8cNqS99E3ilruIYc0N9YFTjFOqjSlR//oS", - "IpaDbwG8PU1VqxWoDv8kS4AP3LVinFScaZyrMPuV2A0rQWLAyIFtWdCtYYFo9/sNpCCLSrd5MibjKG3Y", - "pfUxmWmIWH7gVJMcjE79A+PnGxzO+2g9zXDQV0Je1FiIXyEr4KCYSuIxNd/Zrxju6Ja/dqGPmDxtP1uv", - "hBm/ydjZolWlSQj+f/f/6/j9SfIPmvx2lDz/X4cfPz29fvCw9+Pj66+//v/tn55cf/3gv/4ztlMe9liq", - "iIP89JVT1k5foUTeuCV6sH82k3TBeBIlstD53qEtch/TIh0BPWjba/QaPnC94YaQLmnOMiNy3YQcuiyu", - "dxbt6ehQTWsjOvYZv9Y95dxbcBkSYTId1njja7wfhhZPykI/mcuzwvOyrLjdSi/o2pwDHw4klvM68c7W", - "5DgmmJW1pj6Wzf35+NlXs3mTTVV/n81n7uvHCCWzbBOVDmETU1/cAcGDcU+Rkm4VDAigCHs08smGG4TD", - "FmD0XrVm5efnFEqzRZzD+UhuZwbZ8FNuQ6zN+UGv29YZ88Xy88OtpZHDS72O5eq3JAVs1ewmQCcSopTi", - "EvicsAM46JohMqOauRisHOgSc8ZR0RNTMlPqc2AJzVNFgPVwIZN0/Rj9oHDruPX1fOYuf3Xn8rgbOAZX", - "d87axeb/1oLc++6bc3LoGKa6Z9M37dBBwl1Ea3U5Ja0YGcPNbIUSm7/6gX/gr2DJODPfjz/wjGp6uKCK", - "peqwUiBf0JzyFA5Wghz7NJVXVNMPvCdpDRYRChKESFktcpaSi1AibsjTFoboj/Dhw3uar8SHDx974QJ9", - "+dVNFeUvdoLkium1qHTi0toTCVdUxtwxqk5rxpFt3YqxWefEjW1ZsUubd+PHeR4tS9VNb+wvvyxzs/yA", - "DJVL3jNbRpQW0ssiRkCx0OD+vhHuYpD0ypswKgWK/FrQ8j3j+iNJPlRHR0+AtPL9fnVXvqHJbQmTDRmD", - "6Zdd+wUu3Oo1sNGSJiVdxbw+Hz6810BL3H2UlwtUsvOcYLdWnqGPo8ahmgV4fAxvgIVj75wpXNyZ7eVL", - "GMWXgJ9wC7GNETcaX/RN9yvIPLzxdnWyF3u7VOl1Ys52dFXKkLjfmbqyycoIWT5AQLEVBmG6IjALIOka", - "0gtXnQOKUm/nre4+BsUJmp51MGXrtti8IawcgDbzBZCqzKgTxbsWpMWWKNDaR4G+gwvYnoum8MA+Odvt", - "FGI1dFCRUgPp0hBreGzdGN3Nd4FOaOIqS5+JiylZniyOa7rwfYYPshV57+AQx4iileI6hAgqI4iwxD+A", - "ghss1Ix3K9KPLc9oGQt780VquHjeT1yTRnlyMUnhatDAbb8XgEWgxJUiC2rkduHqF9k02YCLVYquYEBC", - "Dt0WE5NRW64OHGTXvRe96cSye6H17psoyLZxYtYcpRQwXwypoDLTiUTzM1nPmHMCYFlCh7BFjmJSHbJn", - "mQ6VLfeRrbM2BFqcgEHyRuDwYLQxEko2a6p8aSWsQOXP8iQZ4HdM+x4r9hEa9IMyU7V93fPc7jntaZeu", - "5Iev8+GLe4Sq5YRCHUbCx7jt2HYIjgJQBjms7MJtY08oTQp6s0EGjh+Xy5xxIEksHosqJVJma2M114yb", - "A4x8/JAQawImk0eIkXEANnp8cWDyRoRnk6/2AZK7FHrqx0ZfcfA3xLN9bISyEXlEaVg4G3AgpZ4DUBfE", - "V99fnVBSHIYwPieGzV3S3LA5p/E1g/RqTqDY2qkw4WIOHgyJsyMWeHux7LUmexXdZDWhzOSBjgt0IxAv", - "xCax6X5RiXexWRh6jwZtY/Jh7GDa6h73FFmIDcax4NVig4R3wDIMhwcj0PA3TCG9Yr+h29wCMzbtuDQV", - "o0KFJOPMeTW5DIkTU6YekGCGyOV+ULDjRgB0jB1N9Vun/O5UUtviSf8yb261eVOIyufDxI7/0BGK7tIA", - "/vpWmLrExtuuxBK1U7TDMdrVRQIRMkb0hk30nTR9V5CCHFApSFpCVHIRc90Z3Qbwxjnz3QLjBdYwoXz7", - "IIjxkbBiSkNjRPchCV/CPEmxdJoQy+HV6VIuzfreCVFfU7Y2D3ZsLfOzrwCDZJdMKp2gByK6BNPoW4VK", - "9bemaVxWakcR2UKjLIvzBpz2ArZJxvIqTq9u3u9fmWnf1CxRVQvkt4zb2JAFFsaNxhaOTG3DT0cX/Nou", - "+DW9s/VOOw2mqZlYGnJpz/EHORcdzjvGDiIEGCOO/q4NonSEQQY5oX3uGMhN9nBiTujBmPW1d5gyP/bO", - "sBGfmTp0R9mRomsJDAajq2DoJjJiCdNBXdl+subAGaBlybJNxxZqRx3UmOleBg9fjauDBdxdN9gODAR2", - "z1i+iATVLrzWCPi2QnCr7snBJMyct8ujhQwhnIopX9++j6g6n2wXrs6B5t/D9mfTFpczu57Pbmc6jeHa", - "jbgD12/r7Y3iGV3z1pTW8oTsiXJallJc0jxxBuYh0pTi0pEmNvf26M/M6uJmzPNvTl6/deBfz2dpDlQm", - "tagwuCpsV/5hVmVrvA0cEF8/2+h8Xma3omSw+XVhqtAofbUGV4g4kEZ7FRMbh0NwFJ2RehmPENppcna+", - "EbvEER8JlLWLpDHfWQ9J2ytCLynLvd3MQzsQzYOLm1Z2M8oVwgFu7V0JnGTJnbKb3umOn46GunbwpHCu", - "kVLJha0GrojgXRc6hhdvS+d1LyjWO7RWkT5z4lWBloRE5SyN21j5Qhni4NZ3ZhoTbDwgjJoRKzbgiuUV", - "C8YyzaZUNOkAGcwRRaaKFlVpcLcQ7qWXirN/VUBYBlybTxJPZeegYoFJZ23vX6dGdujP5Qa2Fvpm+NvI", - "GGGtz+6Nh0CMCxihp64H7qtaZfYLrS1SGG7duCT2cPiHM/auxBFnvaMPR802eHHd9riFD7P0+Z8hDFuh", - "e/erMF55dUVHB+aIvvLCVLKU4jeI63moHkdScXx1U4ZRLr8BnxBz3lh3msdqmtkHt3tIugmtUO0ghQGq", - "x50P3HJYZtFbqCm3W20fXWjFusUJJowqPbTjNwTjYO5F4ub0akFjNSiNkGFgOmkcwC1buhbEd/a4V3Vi", - "g52dBL7kui2zadYlyCZLrl+y5YYCg512sqjQSAZItaFMMLf+v1yJyDAVv6Lcvt1h+tmj5HorsMYv0+tK", - "SCySoOJm/wxSVtA8Ljlkad/Em7EVs89SVAqCdw/cQPbJH0tF7u2IOl3HoeZ0SY7mweMrbjcydskUW+SA", - "LR7ZFguqkJPXhqi6i1kecL1W2PzxhObrimcSMr1WFrFKkFqoQ/Wmdl4tQF8BcHKE7R49J/fRbafYJTww", - "WHT38+z40XM0uto/jmIXgHtWZIybZMhO/u7YSZyO0W9pxzCM2416EM0nt++KDTOukdNku045S9jS8brd", - "Z6mgnK4gHilS7IDJ9sXdRENaBy88s4/iKC3FljAdnx80NfxpIPrcsD8LBklFUTBdOOeOEoWhp+ZRAzup", - "H86+sOPq0Xq4/Ef0kZbeRdRRIj+v0dTeb7FVoyf7DS2gjdY5obYyRs6a6AVfJZuc+sI7WKC3rstrcWPm", - "MktHMQeDGZaklIxrVCwqvUz+StI1lTQ17O9gCNxk8dXTSFHidnFMvh/gnx3vEhTIyzjq5QDZexnC9SX3", - "ueBJYThK9qDJ9ghO5aAzN+62G/Idjg89VSgzoySD5Fa1yI0GnPpWhMdHBrwlKdbr2Yse917ZZ6fMSsbJ", - "g1Zmh35699pJGYWQsWp6zXF3EocELRlcYuxefJPMmLfcC5lP2oXbQP9lPQ9e5AzEMn+WY4rACxHRTn2h", - "7NqS7mLVI9aBoWNqPhgyWLih5qRdlPjzO/288bnvfDJfPKz4RxfYL7yliGS/goFNDAqmR7czq78H/m9K", - "XojN1E3tnBC/sf8GqImipGJ59nOTldmpRy8pT9dRf9bCdPyleTmrXpy9n6JF69aUc8ijw1lZ8BcvM0ak", - "2n+KqfMUjE9s2y2Rb5fbWVwDeBtMD5Sf0KCX6dxMEGK1nfBWB1TnK5ERnKepkNZwz/7TCkEB7H9VoHQs", - "eQg/2KAutFsafdfWXybAM9QWD8h39nHcNZBW+RvU0uoqAq72rTWoV2UuaDbHQg7n35y8JnZW28e+/2Lr", - "P69QSWmvomOvCoo/TgsP9k+5xFMXpo8zHkttVq10UpdrjiWHmhZNQWnWseGj+hJi54C8Cp65tHmkZghD", - "D0smC6Nx1aNZ2QVpwvxHa5quUSVrsdRhkp9euNxTpQoeC6wf/akrIuK5M3C72uW2dPmcCKM3XzFl30SF", - "S2jno9bJ2c4k4PNT28uTFeeWUqKyx1jxgJug3QNnAzW8mT8KWQfxewrktu7/vnXcz7BXtEBTtyh875VA", - "m91YP+bi37pOKRecpVgeKXY1u8dTp/jAJlSS6hpZ/RF3JzRyuKKl6OswOYfFweL0nhE6xPWN8MFXs6mW", - "OuyfGl/pXFNNVqCV42yQzf2LCs4OyLgCV+ESn9oN+KSQLb8icsioqzqpXRp7khGmxQwodt+ab2+c2o/x", - "4heMo4Dv0OZC062lDt921EYrYJqsBCi3nnZusHpv+hxgmmwGm48H/i1IWw0G3XJm2dYH3R/qxHuknQfY", - "tH1p2ro6QfXPrQhkO+lJWbpJh9/biMoDesMHERzxLCbetRMgtx4/HG2E3EZDSfA+NYQGl+iIhhLv4R5h", - "1G9PdN41MkKrpShsQWwIV7SCAeMRMF4zDs1LpZELIo1eCbgxeF4H+qlUUm1FwEk87Rxojt7nGENT2rke", - "bjtUt5aQQQmu0c8xvI3NsxkDjKNu0AhulG/rB1INdQfCxEt8mdkhsv8IBkpVTojKMKOg8yxGjHEYxu0f", - "3mlfAP1j0JeJbHctqT05+9xEQ0miiypbgU5olsUqUr3ArwS/+uJSsIG0qgtTliVJsSZKu0hMn9rcRKng", - "qipG5vINbjld8M5MhBrCt278DmMSymKL/8aqMg7vjAvC2DsM0EdcuGco9pSb2yP1pF5D04liq2Q6JvBO", - "uT06mqlvRuhN/zul9Fys2oB85tIQY1wu3KMYf/vGXBxh5YReqVF7tdSFDTDoTvjXAVFtrFNy21wJr7Je", - "7VF09tSvj40bIIbfEZvj5TcQehsUxKD2frXew6EA3HQwXpxql7mmKRllQYPZQDZ6x+b9IBRxy+lQxI4N", - "2DGfe72nSYY9ORvHHkWoDwXrA/S9jzMlJWXONd4wiz5mXUT6sLlw7NA1G9xdhIvzHrTYfX85FJNNFOOr", - "HAh+774zdAEunb1+et+u1UcleZXQ/upevrXj1VHx0fX3oxNwqi9rBh002p67mvZ2mU4n//5nG8NGgGu5", - "/Tcw4fY2vfdKU1/ateappgmpyyFPKo/cuhXjDy4N1z9qah4hPZVCsaYEd+wlpomxbuf4mFJQv6k/lg80", - "uYRUY931xoEuAfap5mQmC949/LMO0oDuWIcEuvJHYzWP+sXWd1xovbSkILXOFqo+mF7h56QOk0KmhBVw", - "V8Dd04PthIPJYc/LJaSaXe5IA/v7GniQYjT3Rgj7hHCQFcbqMFqsIrK/ia0BaCxLaxSeoJrfrcEZSgK5", - "gO09RVrUEK2cPff3yk0KSCAGkDskhkSEioUhWKup8wwzVVMGYsGH/dju0JTiGnx0J0hqvOFcniTNjdsk", - "Oo5MGX/1Y9Jcpute6b8YETqUKdZ/NGBY2H6FbzSo+kE8X4AiVEnJab9M35UrYIFJe7WjwJeyAOV/8xm6", - "dpacXUD4LBC6Za6ozHyLqJ3BmzCSkfuol97lC953gV7WM7MmSLOf0BMp/IShuGkujPyVDMUzt+Mi66CC", - "e8pGf9iS3xjxaeBagnTPp6GwlwsFiRY+qHMMjjFUuLfvb4IENVhs0QI3WALlXVPjBYvOUix5Ql1kS7hA", - "IqGgBjoZVGIZnnMM2S/td5/B4ouO7jSn1PS6u9C8D89lqofEkOqXxN2WuzNjbmJZYZzb52tVrCwLN6gM", - "Tf+lFFmV2gs6PBi19Wly0aMRVhI1SqT9VXYE4iC98AK2h1bi9xX6/Q6GQFvJyYIepPN3NvlObU0qBvfq", - "TsD7kmaa+awUIk8GLPun/VoyXYq/YOkFZMTcFD6MbeCREnIfDcq16/ZqvfW1U8oSOGQPDgg54TZw2Htx", - "28WMO5Pze3ps/g3OmlW2vJOzIB184PEITCy8JG/Jzfww4zxMgWF1t5zKDrKjUslmoI6NpFeRJ3sOpqqg", - "fb9q9xmVhqgsFDGZpHkhZEdQSB0P0ryB0MSE9B8uGnmJ47xDRLYd+uIdMHs/t+GA7L66sdMiGoA5ATm9", - "4SNGpchrIu119fA08KiTFgVL+8O1UPOHcOcPOuF3vJUSWV9NeO4pF58aNYCrqG9s3BVln6taTHVI1YVj", - "o/sULVKa7HRRtWCY5KjaF4wlPv+W0AiST2uJad56nZN1XsrxRb0sjafUakxGW6csryS4VB37TlXn/YqS", - "6rXnoKZ5X68xMjIozKOxbyBQZbVwbw1wj2R1ryZRJjlcQstz5/KHqjQFpdglhA9s2c4kAyjRNtaV2GIu", - "qZC1d65xt/YkcGpMwW70XreItTtFdlzaURFjwxN7TNTUo2QgumRZRVv4U7d4+2jo2aMIG/awTuQUezOJ", - "+OLGWMROJzLSfPRc8rgPOUxfqxVynC2rDXeWCJuTrUp6xYcF2IjNo3Zs3n4dBAcjqpNOOhA0iy87JXXd", - "ydj16DLfPPGbGZtXqDq6XPBaVD3mwLu5Nf3cRm8aJMo4Td6s3s+kk9R3v0WYTfBC1LiROCwH1uQZSOvF", - "RaOS51fdw/BDw8emvVXlO+wAL/QdBK9VeTXOgfOFkwF+qJESLGWQElrL3+WOcAtsGH+wRfbWM8u0xRlt", - "IGl7XwJfk3pZu3CGnpDrenqw9pfgWA+x7yFS6NXHZxVCwjEHXV7S/PN7ebAo3Aniw73JHV9o6CYIkWxR", - "qW4WkfuaTpo7cAnc3dT8LXql/g5mj6JKghvK3Si1lOWd2MgyaW6YeP0YJw5JrnBMG7/x6CuycNmEpYSU", - "qe5NdeUrvtdWcXwApXmqfdwMv2udPwt9CzJeesGPvGmqR6PKteINhM0R/cJMZeDkRqk8Rn09sojgL8aj", - "wrI+O66Li1Zgh63G34lYFhLuOMAjCNXcM8CjX7Bo6vJsEIO5dCoF/XVOvq1buI1c1M3apkYn9ZE7VmJ4", - "SlBRvHK46Y5RTRYhWHafIKjk10e/EglLfFdLkIcPcYKHD+eu6a+P25/NcX74MP4i/OeKZ7I4cmO4eWMU", - "8/NQhovN4hhIpursR8XybBdhtFLjmpfpMPnrF5cc+0XexvvFup37R9W9T7RPJGV3ExAxkbW2Jg+mCpLe", - "JuS7uW6R7DY06aaVZHqLNbu8Osd+iUZefVcHNrjAmLrKi7v7tLiAuupbEwZRKX+7fidojveRkakxjlXj", - "K+DfbGhR5uAOytf3Fn+BJ399mh09efSXxV+Pnh2l8PTZ86Mj+vwpffT8ySN4/NdnT4/g0fKr54vH2eOn", - "jxdPHz/96tnz9MnTR4unXz3/yz3DhwzIFtCZrxAx+7/4gGRy8vY0OTfANjihJasf/zdk7F/BoimeRCgo", - "y2fH/qf/7U/YQSqKZnj/68wloM/WWpfq+PDw6urqIOxyuEK/Z6JFla4P/Tz9R9ffntYGY6uU447a/DBv", - "bPGkcILf3n1zdk5O3p4eBI/6Hs+ODo4OHuGbryVwWrLZ8ewJ/oSnZ437fuiIbXb86Xo+O1wDzTFMyPxR", - "gJYs9Z8k0Gzr/q+u6GoF8sA9DWZ+unx86MWKw0/O/3s99u0wrLJ/+KnlJs929MQq3IeffHGp8dat6k0u", - "PCDoMBGKsWaHC8xZn9oUVNB4eCmobKjDTyguD/5+6BJ54x9RbbHn4dDHksRbtrD0SW8MrJ0eKdXpuioP", - "P+F/kD4DsGzYfB/cDC4LkYGfTyyXttbd2OfDT/bfYBjYlCCZkdsw+Mb9arNnD+0Dv/2ftzyN/tiHsvfQ", - "zAqiPh5MwaX4GGq83PUMj5s9qacZMlDdDVazVeutOQVP4eOjo70e4Jvm+u6GyPWvpD7vGVvZ9Xz2dE9A", - "R403rSj6CDAvaEa8uw3nfvT55j7lGPFmmCqxlwZC8PTzQdB+IuB72JI3QpNvUbu5ns+efc6dOOVG1qI5", - "wZZBBbD+EfmJX3BxxX1LI21URUHldvLx0XSl0C0r2SV1sl7waszsI8YBWBds+6idZFmP6K3UBUq/EHh9", - "DWGsUKvS5cw1SGuETsbNEvpaa/8Z3jVEok1tTJT35XCRwSwUB7Ws4PqWPKEtdxsQTiNGGLQm4jsuS1+z", - "LwA1GjrZ9ZbbkSc9RNoZvH53oVqgwVrwP3nKnzyl5inPjp58vunPQF6yFMg5FKWQVLJ8S37idcWDG/O4", - "kyyLxpu3j/5OHmcU+lRksAKeOAaWLES29VVdWxNcgNUve4LM4af20wxWvpxlkIOOxtKa3+tHc/uLWGzJ", - "6auehGO7dTnviy02DZ48OH7/ySpoRvto9KcuiD3OGFbb7/Kmj3GuOUb2ZiEroYnFQuYW9Scj+pMR3Uq4", - "mXx4psg3Ue3D1hOivTt77ksDxYrCUd0HZYqO8kWP751sfF//iek7Nm4fMhJ8sC79Lpr/ZBF/sojbsYjv", - "IHIY8dQ6phEhuv30oakMA+Ohsu4DaOij8M2rnEqiYKqZ4wRHdMaNz8E1PrdSF8WV1ekob96IjGzg3ep5", - "f7K8P1neH4flnexmNG3B5Naa0QVsC1rW+pBaVzoTV4GbAmGxIUR9O3D9JHPr78MrynSyFNJlgeIDAf3O", - "Gmh+6OqbdX5tSor0vmCdlODHMKI0+uth/f5K9GPXwxH76iz8A418GL7/3Hg7Q+8hsvbab/j+o2HLWN3b", - "cf3GGXZ8eIiZVWuh9OHsev6p4ygLP36sSeBTfVc4Urj+eP0/AQAA//9/miZMgs4AAA==", + "H4sIAAAAAAAC/+y9e3PcNrIo/lVQc06VH7+hJL+ya1VtnZ9sJ1ndOI7LUrL3HNs3iyF7ZrAiAS4Ajmbi", + "6+9+Cw2ABEmQw5G09qYqf9ka4tFoNBr9QvenWSqKUnDgWs1OP81KKmkBGiT+RdNUVFwnLDN/ZaBSyUrN", + "BJ+d+m9Eacn4ajafMfNrSfV6Np9xWkDTxvSfzyT8s2ISstmplhXMZypdQ0HNwHpXmtb1SNtkJRI3xJkd", + "4vzV7PPIB5plEpTqQ/kTz3eE8TSvMiBaUq5oaj4pcs30mug1U8R1JowTwYGIJdHrVmOyZJBn6sgv8p8V", + "yF2wSjf58JI+NyAmUuTQh/OlKBaMg4cKaqDqDSFakAyW2GhNNTEzGFh9Qy2IAirTNVkKuQdUC0QIL/Cq", + "mJ2+nyngGUjcrRTYBv+7lAC/QaKpXIGefZzHFrfUIBPNisjSzh32Jagq14pgW1zjim2AE9PriPxYKU0W", + "QCgn7757SZ48efLcLKSgWkPmiGxwVc3s4Zps99npLKMa/Oc+rdF8JSTlWVK3f/fdS5z/wi1waiuqFMQP", + "y5n5Qs5fDS3Ad4yQEOMaVrgPLeo3PSKHovl5AUshYeKe2MZ3uinh/F91V1Kq03UpGNeRfSH4ldjPUR4W", + "dB/jYTUArfalwZQ0g74/SZ5//PRo/ujk83+8P0v+x/357Mnnict/WY+7BwPRhmklJfB0l6wkUDwta8r7", + "+Hjn6EGtRZVnZE03uPm0QFbv+hLT17LODc0rQycsleIsXwlFqCOjDJa0yjXxE5OK54ZNmdEctROmSCnF", + "hmWQzQ33vV6zdE1SquwQ2I5cszw3NFgpyIZoLb66kcP0OUSJgetG+MAF/fsio1nXHkzAFrlBkuZCQaLF", + "nuvJ3ziUZyS8UJq7Sh12WZHLNRCc3Hywly3ijhuazvMd0bivGaGKUOKvpjlhS7ITFbnGzcnZFfZ3qzFY", + "K4hBGm5O6x41h3cIfT1kRJC3ECIHyhF5/tz1UcaXbFVJUOR6DXrt7jwJqhRcARGLf0Cqzbb/r4uf3hAh", + "yY+gFF3BW5peEeCpyCA7IudLwoUOSMPREuLQ9Bxah4Mrdsn/QwlDE4ValTS9it/oOStYZFU/0i0rqoLw", + "qliANFvqrxAtiARdST4EkB1xDykWdNuf9FJWPMX9b6ZtyXKG2pgqc7pDhBV0+5eTuQNHEZrnpASeMb4i", + "essH5Tgz937wEikqnk0Qc7TZ0+BiVSWkbMkgI/UoI5C4afbBw/hh8DTCVwCOH2QQnHqWPeBw2EZoxpxu", + "84WUdAUByRyRnx1zw69aXAGvCZ0sdviplLBholJ1pwEYcepxCZwLDUkpYckiNHbh0GEYjG3jOHDhZKBU", + "cE0Zh8wwZwRaaLDMahCmYMJxfad/iy+ogm+eDt3xzdeJu78U3V0f3fFJu42NEnskI1en+eoObFyyavWf", + "oB+Gcyu2SuzPvY1kq0tz2yxZjjfRP8z+eTRUCplACxH+blJsxamuJJx+4A/NXyQhF5ryjMrM/FLYn36s", + "cs0u2Mr8lNufXosVSy/YagCZNaxRhQu7FfYfM16cHettVK94LcRVVYYLSluK62JHzl8NbbId81DCPKu1", + "3VDxuNx6ZeTQHnpbb+QAkIO4K6lpeAU7CQZami7xn+0S6Yku5W/mn7LMTW9dLmOoNXTsrmQ0HzizwllZ", + "5iylBonv3Gfz1TABsIoEbVoc44V6+ikAsZSiBKmZHZSWZZKLlOaJ0lTjSP8pYTk7nf3HcWN/Obbd1XEw", + "+WvT6wI7GZHVikEJLcsDxnhrRB81wiwMg8ZPyCYs20OhiXG7iYaUmGHBOWwo10eNytLiB/UBfu9mavBt", + "pR2L744KNohwYhsuQFkJ2Da8p0iAeoJoJYhWFEhXuVjUP9w/K8sGg/j9rCwtPlB6BIaCGWyZ0uoBLp82", + "Jymc5/zVEfk+HBtFccHznbkcrKhh7oalu7XcLVbbltwamhHvKYLbKeSR2RqPBiPm3wXFoVqxFrmRevbS", + "imn8V9c2JDPz+6TOvw8SC3E7TFyoaDnMWR0HfwmUm/sdyukTjjP3HJGzbt+bkY0ZJU4wN6KV0f20447g", + "sUbhtaSlBdB9sXcp46ik2UYW1lty04mMLgpzcIYDWkOobnzW9p6HKCRICh0YXuQivforVes7OPMLP1b/", + "+OE0ZA00A0nWVK2PZjEpIzxezWhTjphpiAo+WQRTHdVLvKvl7VlaRjUNlubgjYslFvXYD5keyIju8hP+", + "h+bEfDZn27B+O+wRuUQGpuxxdk6GzGj7VkGwM5kGaIUQpLAKPjFa90FQvmwmj+/TpD361toU3A65ReAO", + "ie2dH4MXYhuD4YXY9o6A2IK6C/ow46AYqaFQE+B75SATuP8OfVRKuusjGceegmSzQCO6KjwNPLzxzSyN", + "cfZsIeTNuE+HrXDSmJwJNaMGzHfeQRI2rcrEkWLEbGUbdAZqvHzjTKM7fAxjLSxcaPovwIIyo94FFtoD", + "3TUWRFGyHO6A9NdRpr+gCp48Jhd/PXv26PGvj599Y0iylGIlaUEWOw2K3He6GVF6l8OD/spQO6pyHR/9", + "m6feUNkeNzaOEpVMoaBlfyhrALUikG1GTLs+1tpoxlXXAE45nJdgOLlFO7G2fQPaK6aMhFUs7mQzhhCW", + "NbNkxEGSwV5iOnR5zTS7cIlyJ6u7UGVBSiEj9jU8YlqkIk82IBUTEW/KW9eCuBZevC27v1toyTVVxMyN", + "pt+Ko0ARoSy95dP5vh36cssb3IxyfrveyOrcvFP2pY18b0lUpASZ6C0nGSyqVUsTWkpREEoy7Ih39Peg", + "URS4ZAVcaFqUPy2Xd6MqChwoorKxApSZidgWRq5XkApuIyH2aGdu1Cno6SLGm+j0MAAOIxc7nqKd8S6O", + "7bDiWjCOTg+142mgxRoYc8hWLbK8vbY6hA471T0VAceg4zV+RkPHK8g1vXOJrjtBDPaXnrQtsCQzDXG3", + "XrPVWgci91spxPLuYYzNEgMUP1iFJTd9+mrLG5GZg6YrdQfiSTNYc/rNnoZnni5EpQklXGSANqZKxQWX", + "gVgGdKKi71eHspBeWx1kAYaQUlqZ1VYlQc9mj5c2HROaWupNEDVqwK9TO+RsKzud9ZPnEmi2IwsATsTC", + "OU+cWwcXSdEtq/3V78SmyFlqwVVKkYJSkCXOaLMXNN/OslU9gicEHAGuZyFKkCWVtwb2arMXzivYJRhE", + "oMj9H35RD74CvFpomu9BLLaJobdWgZ2HrA/1tOnHCK47eUh2VALxPNfo24ZB5KBhCIUH4WRw/7oQ9Xbx", + "9mjZgERf1b+U4v0ktyOgGtR/Mb3fFtqqHAiNc6qfkX7MhnHKhRc6YoPlVOlkH1s2jVr6qVlBwAljnBgH", + "HhBKXlOlrX+V8QzNQvY6wXmsgGKmGAZ4UEQ3I//ipfP+2Km5B7mqVC2qq6oshdSQxdbAYTsy1xvY1nOJ", + "ZTB2rQ9oQSoF+0YewlIwvkOWXYlFENW1G8IFIPQXh8Z6c8/voqhsAdEgYgyQC98qwG4YHjQACFMNoi3h", + "MNWhnDomaT5TWpSl4RY6qXjdbwhNF7b1mf65adsnLqqbezsToDAqybV3kF9bzNrAsDVVxMFBCnplZA80", + "EVhHcB9mcxgTxXgKyRjlo/pjWoVHYO8hrcqVpBkkGeR01x/0Z/uZ2M9jA+CON6qg0JDYCJ/4pjeU7AMq", + "RoYWOJ6KCY8Ev5DUHEGjeTQE4nrvGTkDHDvGnBwd3auHwrmiW+THw2XbrY6MiLfhRmiz444eEGTH0acA", + "PICHeuibowI7J41e1p3iv0G5CWo54vBJdqCGltCMf9ACBuyLLng6OC8d9t7hwFG2OcjG9vCRoSM7YOx8", + "S6VmKStR1/kBdneu+nUniLrgSAaashwyEnywamAZ9ic2NqU75s1UwUl2qT74PcNUZDk5UyjytIG/gh3q", + "3G9t0ONlECp5B7psZFRzP1FOEFAfSmVE8LAJbGmq850R1PQaduQaJBBVLQqmtQ1mbqu6WpRJOEDU5j8y", + "o3Nw2YBBvwNTPG4XOFSwvP5WzGdWJxiH77KjGLTQ4XSBUoh8gvWoh4woBJNiIUgpzK4zF1ftI2s9JbWA", + "dEwbvZv19X9PtdCMKyD/LSqSUo4qV6WhlmmEREEBBUgzgxHB6jld1EODIcihAKtJ4peHD7sLf/jQ7TlT", + "ZAnX/jGCadhFx8OHaMd5K5RuHa47sBWa43YeuT7QGWIuPqeFdHnKfq+7G3nKTr7tDF57UMyZUsoRrln+", + "rRlA52Rup6w9pJFpEQc47iQ/RzB0bN247xesqHKq78KjAxuaJ2IDUrIM9nJyNzET/NsNzX+qu+3R6ZoI", + "KVYUkDGqId+RUkIKNnLdiGqqHvuI2Ji2dE35CiV0KaqVC6qy4yCHrZS1hciK94aISjF6y5OVFFUZ47gu", + "kNY/XjDyC1CjQwU7gp2txnBN6/nce5UpV6HfuWCbvzdjDnls5rNBFdMgddOomBY57RcYcSzgk5JEVWkK", + "EA2vjilv9VI7L02bt0NuQCN/VNLGlxGa6ormIfmS8yWhfNd+gkpZrgw7ZYpgO9O5iVme27X590FLmlu/", + "d+TBSnjkWqJjsPMNSruomOjBQCIxYlWfMkICNOfUkPG/xhvQDB2Dsj9xENDWfByKaTOafL67A3nKDkQk", + "lBIU3n6hBUzZr2IZvitz16PaKQ1F30lgu/46wGjeDaqigueMQ1IIDrvoU2rG4Uf8GGUceAMPdEZZaKhv", + "V71pwd8Bqz3PFGq8LX5xtwNe9LYO5ryDze+O2/EPhS/q0P4JeUkoSXOG1lHBlZZVqj9wivaX4LBFgl68", + "ojlskXvpm8RNgBELnRvqA6cY8FRbZaKO+iVETBDfAXjDnKpWK1Ad/kmWAB+4a8U4qTjTOFdh9iuxG1aC", + "xMiTI9uyoDvDAtGA+BtIQRaVbvNkfNWjtGGX1lllpiFi+YFTTXIwyvmPjF9ucTjv7PU0w0FfC3lVYyF+", + "hayAg2IqiQfnfG+/YtykW/7axVDiK2z72bo3zPjN058dmmeal8X/5/5/nb4/S/6HJr+dJM//v+OPn55+", + "fvCw9+Pjz3/5y/9t//Tk818e/Nd/xnbKwx57c+IgP3/ltL7zVyjaN/6NHuxfzLZdMJ5EiSz04ndoi9zH", + "95WOgB60DT96DR+43nJDSBuas8yIXDchhy6L651Fezo6VNPaiI6hx6/1QIH5FlyGRJhMhzXe+Brvx7PF", + "X3ehw8092MLzsqy43Uov6NrHCz6uSCzn9Qs+m9zjlODzrjX1QXHuz8fPvpnNm2dZ9ffZfOa+foxQMsu2", + "UekQtjE9yB0QPBj3FCnpTsGAAIqwR0OobNxCOGwBRoFWa1Z+eU6hNFvEOZwPCXf2lC0/5zZW25wfdN/t", + "nFdALL883FoaObzU69ij/5akgK2a3QTohFSUUmyAzwk7gqOuPSMzqpkL5sqBLvHxOSp6YsoTl/ocWELz", + "VBFgPVzIJKNBjH5QuHXc+vN85i5/defyuBs4Bld3ztpX5//Wgtz7/ttLcuwYprpn34HaoYOXexGt1T1O", + "aQXbGG5mU53Yh7Af+Af+CpaMM/P99APPqKbHC6pYqo4rBfIFzSlP4WglyKl/7/KKavqB9yStwWxEwUsj", + "UlaLnKXkKpSIG/K0GSb6I3z48J7mK/Hhw8de3EFffnVTRfmLnSC5ZnotKp249/GJhGsqY34dVb+PxpFt", + "AoyxWefEjW1ZsXt/78aP8zxalqr7TrK//LLMzfIDMlTuFaDZMqK0kF4WMQKKhQb3941wF4Ok196EUSlQ", + "5O8FLd8zrj+S5EN1cvIESOvh4N/dlW9oclfCZEPG4DvOrv0CF271GthqSZOSrmLuow8f3mugJe4+yssF", + "Ktl5TrBb68GiD8jGoZoFeHwMb4CF4+DHV7i4C9vL50KKLwE/4RZiGyNuNE7tm+5X8ITxxtvVeQbZ26VK", + "rxNztqOrUobE/c7UKVJWRsjykQaKrTCa02WTWQBJ15BeuTQfUJR6N29198EsTtD0rIMpmwDGPkDCFARo", + "fF8AqcqMOlG8a0Fa7IgCrX046Tu4gt2laDIYHPL4u/0WWQ0dVKTUQLo0xBoeWzdGd/NdxBSauMrSP+nF", + "t12eLE5ruvB9hg+yFXnv4BDHiKL1VnYIEVRGEGGJfwAFN1ioGe9WpB9bntEyFvbmiySD8byfuCaN8uSC", + "m8LVoIHbfi8As0mJa0UW1MjtwiVCsu9tAy5WKbqCAQk59H9MfNXa8pngIPvuvehNJ5bdC61330RBto0T", + "s+YopYD5YkgFlZlOSJufybrYnBMA8xs6hC1yFJPq2D/LdKhs+aFswrYh0OIEDJI3AocHo42RULJZU+Vz", + "NGEqK3+WJ8kA/8L342NZQ0KDfpCvqrave57bPac97dLlDvEJQ3yWkFC1nJDxw0j4GAAe2w7BUQDKIIeV", + "Xbht7AmlecvebJCB46flMmccSBIL7KJKiZTZJFvNNePmACMfPyTEmoDJ5BFiZByAja5jHJi8EeHZ5KtD", + "gOTuLT71Y6PTOfgb4s+GbKizEXlEaVg4G3AgpZ4DUBcNWN9fnZhUHIYwPieGzW1obtic0/iaQXrJK1Bs", + "7aSqcMELD4bE2RELvL1YDlqTvYpusppQZvJAxwW6EYgXYpvYd4NRiXexXRh6j0Z/4yvG2MG0aULuKbIQ", + "WwyIwavFRhvvgWUYDg9GoOFvmUJ6xX5Dt7kFZmzacWkqRoUKScaZ82pyGRInpkw9IMEMkcv9IPPHjQDo", + "GDuaNLpO+d2rpLbFk/5l3txq8yajlX9YEzv+Q0couksD+OtbYepcHW+7EkvUTtGO62inKQlEyBjRGzbR", + "d9L0XUEKckClIGkJUclVzHVndBvAG+fCdwuMF5gMhfLdgyBYSMKKKQ2NEd2HJHwN8yTFHGxCLIdXp0u5", + "NOt7J0R9TdkkP9ixtcwvvgKMtl0yqXSCHojoEkyj7xQq1d+ZpnFZqR2OZDOWsizOG3DaK9glGcurOL26", + "eX94ZaZ9U7NEVS2Q3zJuY0MWmGE3GqQ4MrWNYx1d8Gu74Nf0ztY77TSYpmZiacilPcfv5Fx0OO8YO4gQ", + "YIw4+rs2iNIRBhk8Lu1zx0BusocTH5cejVlfe4cp82PvDRvxT1yH7ig7UnQtgcFgdBUM3URGLGE6SFDb", + "f/U5cAZoWbJs27GF2lEHNWZ6kMHDp/XqYAF31w22BwOB3TP28ESCamdwawR8m2q4lUDlaBJmLtt51kKG", + "EE7FlE+U30dU/TBtH64ugeY/wO4X0xaXM/s8n93OdBrDtRtxD67f1tsbxTO65q0preUJORDltCyl2NA8", + "cQbmIdKUYuNIE5t7e/QXZnVxM+blt2ev3zrwP89naQ5UJrWoMLgqbFf+blZlk8UNHBCfiNvofF5mt6Jk", + "sPl1hqvQKH29BpfROJBGe6kXG4dDcBSdkXoZjxDaa3J2vhG7xBEfCZS1i6Qx31kPSdsrQjeU5d5u5qEd", + "iObBxU3L3xnlCuEAt/auBE6y5E7ZTe90x09HQ117eFI410jO5cKmFVdE8K4LHcOLd6XzuhcUEydaq0if", + "OfGqQEtConKWxm2sfKEMcXDrOzONCTYeEEbNiBUbcMXyigVjmWZTUqN0gAzmiCJTRbOzNLhbCFcypuLs", + "nxUQlgHX5pPEU9k5qJip0lnb+9epkR36c7mBrYW+Gf42MkaYNLR74yEQ4wJG6KnrgfuqVpn9QmuLFIZb", + "Ny6JAxz+4Yy9K3HEWe/ow1GzDV5ctz1uYYWXPv8zhGFTfe8vL+OVV5e9dGCOaLkYppKlFL9BXM9D9Tjy", + "psenSWUY5fIb8Akx5411p6l608w+uN1D0k1ohWoHKQxQPe584JbDfI3eQk253WpbvaEV6xYnmDCq9NiO", + "3xCMg7kXiZvT6wWNJbM0QoaB6axxALds6VoQ39njXtUPG+zsJPAl122Zfa9dgmye2/Vzv9xQYLDTThYV", + "GskAqTaUCebW/5crERmm4teU2yIgpp89Sq63Amv8Mr2uhcRsCypu9s8gZQXN45JDlvZNvBlbMVvfolIQ", + "FFBwA9naQZaKXBGK+rmOQ835kpzMgyoubjcytmGKLXLAFo9siwVVyMlrQ1TdxSwPuF4rbP54QvN1xTMJ", + "mV4ri1glSC3UoXpTO68WoK8BODnBdo+ek/votlNsAw8MFt39PDt99ByNrvaPk9gF4OqTjHGTDNnJ3xw7", + "idMx+i3tGIZxu1GPog/TbYGyYcY1cpps1ylnCVs6Xrf/LBWU0xXEI0WKPTDZvribaEjr4IVntrqO0lLs", + "CNPx+UFTw58Gos8N+7NgkFQUBdOFc+4oURh6aqoj2En9cLZUj0ts6+HyH9FHWnoXUUeJ/LJGU3u/xVaN", + "nuw3tIA2WueE2hQbOWuiF3y6bXLuM/hgpt86wa/FjZnLLB3FHAxmWJJSMq5Rsaj0MvkzSddU0tSwv6Mh", + "cJPFN08j2Y3bWTb5YYB/cbxLUCA3cdTLAbL3MoTrS+5zwZPCcJTsQfPaIziVg87cuNtuyHc4PvRUocyM", + "kgySW9UiNxpw6lsRHh8Z8JakWK/nIHo8eGVfnDIrGScPWpkd+vndaydlFELG0vI1x91JHBK0ZLDB2L34", + "Jpkxb7kXMp+0C7eB/ut6HrzIGYhl/izHFIEXIqKd+ozbtSXdxapHrANDx9R8MGSwcEPNSTu78Zd3+nnj", + "c9/5ZL54WPGPLrBfeUsRyX4FA5sYZF6PbmdWfw/835S8ENupm9o5IX5j/w1QE0VJxfLsl+ZVZiexvaQ8", + "XUf9WQvT8demBFe9OHs/RbPfrSnnkEeHs7Lgr15mjEi1/xBT5ykYn9i2m2vfLrezuAbwNpgeKD+hQS/T", + "uZkgxGr7wVsdUJ2vREZwnibVWsM9+zUagkza/6xA6djjIfxgg7rQbmn0XZvImQDPUFs8It/bKrtrIK08", + "Oqil1VkEXBJda1CvylzQbI6JHC6/PXtN7Ky2jy0kYxNJr1BJaa+iY68KskhOCw/2NWHiTxemjzMeS21W", + "rXRS532OPQ41LZrM1Kxjw0f1JcTOEXkV1Mu070jNEIYelkwWRuOqR7OyC9KE+Y/WNF2jStZiqcMkPz0D", + "uqdKFVQdrKsH1akV8dwZuF0SdJsDfU6E0ZuvmbLFVWED7feo9eNsZxLw71Pby5MV55ZSorLHWPKAm6Dd", + "A2cDNbyZPwpZB/EHCuS2gMChCeEvsFc001M3u3yv3KB93VhXhfFFs1PKBWcp5lmKXc2uCusUH9iElFRd", + "I6s/4u6ERg5XNKd9HSbnsDiY5d4zQoe4vhE++Go21VKH/VNjuc811WQFWjnOBtncl2ZwdkDGFbhUmViz", + "N+CTQrb8isgho67qpHZpHEhG+CxmQLH7znx749R+jBe/YhwFfIc2F5puLXVYJFIbrYBpshKg3Hrab4PV", + "e9PnCJ/JZrD9eOSLStpsMOiWM8u2Puj+UGfeI+08wKbtS9PW5Qmqf25FINtJz8rSTTpcuCMqD+gtH0Rw", + "xLOYeNdOgNx6/HC0EXIbDSXB+9QQGmzQEQ0l3sM9wqiLWHQKJBmh1VIUtiA2hCuawYDxCBivGYem5Gnk", + "gkijVwJuDJ7XgX4qlVRbEXAST7sEmqP3OcbQlHauh9sO1c0lZFCCa/RzDG9jU39jgHHUDRrBjfJdXWnV", + "UHcgTLzEEs8Okf1qGihVOSEqwxcFnfoaMcZhGLev4NO+APrHoC8T2e5aUntyDrmJhh6JLqpsBTqhWRbL", + "SPUCvxL86pNLwRbSqs5wWZYkxZwo7SQxfWpzE6WCq6oYmcs3uOV0QcGaCDWERXP8DuMjlMUO/42ldxze", + "GReEcXAYoI+4cPUsDpSb2yP1pF5D04liq2Q6JvBOuT06mqlvRuhN/zul9Fys2oB84dQQY1wu3KMYf/vW", + "XBxh5oRezlJ7tdSJDTDoTvgyg6g21k9y21wJr7JeElN09tRlzMYNEMMFyeZ4+Q2E3gYJMai9X633cCgA", + "Nx2MF6favVzTlIyyoMHXQDZ6x777QSjiltOhiB0bsGM+93pPkwx7cjaOPYpQHwrWB+gHH2dKSsqca7xh", + "Fn3Muoj0YXPh2KFrNri7CBfnPWix+2EzFJNNFOOrHAh+7xYsugL3nL2u4W/X6qOSvEpof3UldO14dVR8", + "dP396ASc6uuaQQeNtpcuOb5dptPJf/jFxrAR4Fru/g1MuL1N75V76ku71jzVNCF1XuVJeZZbt2K8ctNw", + "/qMm5xHSUykUa3J5x0o6TYx1u8SqTEH+pv5YPtBkA6nGBO6NA10CHJLNyUwWFFD8Iw/SgO5YhwS69Edj", + "OY/6Wdv3XGi9Z0nB0zqb8fpoeoafszpMCpkSZsBdAXc1DNsPDiaHPS+XkGq22fMM7G9r4METo7k3Qtha", + "xMGrMFaH0WIWkcNNbA1AY6+0RuEJsvndGpyhRyBXsLunSIsaoim45/5euUkCCcQAcofEkIhQsTAEazV1", + "nmGmaspALPiwH9sdmlRcg9V7gkeNN5zLk6S5cZuHjiNTxsuHTJrLdD3o+S9GhA69FOtXHxgWtl9hsQdV", + "V9bzCShClZSc99P0XbsEFvhor3YU+FQWoPxv/oWunSVnVxDWF0K3zDWVmW8RtTN4E0Yych/1nnf5zPld", + "oJf1zKwJ0uw/6IkkfsJQ3DQXRv5KhuKZ23GRdVDBPWWjP2zKb4z4NHAtQbo6bCjs5UJBooUP6hyDYwwV", + "roj+TZCgBpMtWuAGU6C8a3K8YNJZiilPqItsCRdIJBTUQCeDTCzDc44h+6X97l+w+KSje80pNb3uTzTv", + "w3OZ6iExpPolcbfl/pcxN7GsMM5tHVwVS8vCDSpD038pRVal9oIOD0ZtfZqc9GiElUSNEml/lR2BOHhe", + "eAW7Yyvx+wz9fgdDoK3kZEEPnvN3NvlObU0qBvfqTsD7mmaa+awUIk8GLPvn/VwyXYq/YukVZMTcFD6M", + "baDaCbmPBuXadXu93vncKWUJHLIHR4SccRs47L247WTGncn5PT02/xZnzSqb3slZkI4+8HgEJiZekrfk", + "Zn6YcR6mwLC6W05lB9mTqWQ7kMdG0utI7Z+jqSpo36/arcfSEJWFIiaTNKVG9gSF1PEgTQ2EJiakJx3k", + "bKmTXKySnBVsKDOLC4Mny5yuLBWabk7INv0I5hlaEVGmWLvP2uqjVUDCcnwjVUAuOwRs22EcgEPEwaU+", + "HIK6FT/2WmMDMCdsTG/4iEErUsmkva5uLZyhylRaFCztD9dCze8ilGAwAGBPnZbI+mqid2Vk/LOsAVxF", + "/XLjbjBbc2sx1RlWJ62N7lM0QWqy1z3WgmGSk+xQMJZYwy6hESSf19LavFVilHWq9PiEYpbGU2q1tTVg", + "oZlKgnsmZIttdWpnlFSvPfc2zfs6lZHPQeEbHlt/gSprAfCWCFfpq3stijLJYQMtr6F7u1SlKSjFNhBW", + "CbOdSQZQol2uKy3G3GHhtdIRIdzak8ChMgW7UZnCItbuFNkjMETFmy1P7DFRU4+SgWjDsoq28KduUXdp", + "qORShA17WCdyioOZRHxxYyxirwMbaT56Lnncfx0+nauNAThbVhsNLRE2J1uV9JoPC88Re0vtVL39OggO", + "RlTnKetAwC5WlUrqnJex69GLG474zYxNBayOHhlUqqrHVIPShqwJ4qY62yBRjtFkr0xbVNxR4Mtshgkv", + "rFxGfGW4iGxlrUtMRQZgqmElGB0GTfRR0KygO5Kx5RKktZ8rTXlGZRY2Z5ykIDVlRpnYRexMBd2iIIkU", + "OVAdxb7wRFHREq7gKHiQgl5Flna4mdYDodhvMA4DJmVwhiYtEKRbzx9zcd0w3dQkZtr3/kbum6BA2biP", + "IsxG1zxzkTaIAG2a/srqbv2PzVU2rVSa77AHvNB1FRRL81YEB85XfovyY42UYCkfhyihtfx93jC3wObu", + "D7bICj5mmTY3qI1jbu9L4OpUL2sP4lAFw66jEVPPCY7pOPsOSoVBJVjVIyQcc1DkhuZf3smIOQnPEB+u", + "tnx8oaGXKkSyRaW6WUD4azpp7sAjdXdT87foFP0bmD2K6oluKCdU1IK2j6HAW5PmVoNf+qpGG+DkGse0", + "4UOPviEL95i1lJAy1RVWrn3Bgdopg/V3XBD+Vu/xAu1b5y9C34KMl172J2+a5OWoda94A2FzRL8yUxk4", + "uVEqj1Ffjywi+IvxqDCr1J7r4qoVV2SLQXQC5oWEO44vCiKFD4wv6ufLmro8G0NjLp1KQX+dk2/rFm4j", + "F3WztqnBcX3kjmW4nhLTFk9cb7pjUJ1FCFZ9IAgq+fujvxMJSyzrJsjDhzjBw4dz1/Tvj9ufzXF++DCq", + "GnyxcDqLIzeGmzdGMb8MPbCyj4gG3vJ19qNiebaPMFovM5vCiPj28Ff3NvurlGb81UY99I+qK491SCBv", + "dxMQMZG1tiYPpgreXE54bum6RR5XokchrSTTO0wZ5zV69ms08O/7Oq7GxWXVapO7+7S4gjrpYBOFUyl/", + "u34vaI73kdXmuLmFRH5Evt3SoszBHZS/3Fv8CZ78+Wl28uTRnxZ/Pnl2ksLTZ89PTujzp/TR8yeP4PGf", + "nz09gUfLb54vHmePnz5ePH389Jtnz9MnTx8tnn7z/E/3DB8yIFtAZz5Byex/Y/3S5OzteXJpgG1wQkv2", + "A+xsqTRDxr4IG03xJEJBWT479T/9//6EHaWiaIb3v85c/oPZWutSnR4fX19fH4Vdjlfodk+0qNL1sZ+n", + "V6Xt7O157a+wdhncUfs80dvbPCmc4bd3315ckrO350dBTenT2cnRydEjLDlcAqclm53OnuBPeHrWuO/H", + "jthmp58+z2fHa6A5RqmZPwrQkqX+kwSa7dz/1TVdrUAeucp05qfN42MvVhx/cuEHn8e+HYdFHo4/taI0", + "sj09MQn88Sef22y8dSt5mItOCTpMhGKs2fECUyZMbQoqaDy8FFQ21PEnFJcHfz9278jjH1Ftsefh2Icy", + "xVu2sPRJbw2snR4p1em6Ko8/4X+QPgOw7KuNPrgZbAqRgZ9PLJc21eLY5+NP9t9gGNiWIJmR2zD2y/1q", + "H28f2/rS/Z93PI3+2IeyV+doBVEXI74Ap1iLN55tfYbHzZ7U8wwZqO7GStqiCdaihqfw8cnJQfUfp0Ve", + "dCM0+1dSn/eMrezzfPb0QEBHjTetRxwRYF7QjHhvL8796MvNfc4x4NIwVWIvDYTg6ZeDoF2h4gfYkTdC", + "k+9Qu/k8nz37kjtxzo2sRXOCLYMEdP0j8jO/4uKa+5ZG2qiKgsrd5OOj6UphVIBkG+pkvaBo0ewjhqHY", + "CID2UTvLsh7RW6kLlH4h8PoawlihVqV7stkgrRE6GTdL6Gut/SrQa4gEO9uQPO/O4yKDWSgOalnB51vy", + "hLbcbUA4jxhh0JqIZYSWPmVkAGo0crcbrGFHnlQHtzN4XfajWqDPQvA/eMofPKXmKc9Onny56S9AblgK", + "5BKKUkgqWb4jP/M64caNedxZlkWfO7SP/l4eZxT6VGSwAp44BpYsRLbzSYVbE1yB1S97gszxp3ZlECtf", + "zjLIQUdDuc3vdc3m/iIWO3L+qifh2G5dzvtih02Dihun7z9ZBc1oH43+1AWxxxnDYg9d3vQxzjXHyN4s", + "ZCU0sVjI3KL+YER/MKJbCTeTD88U+Saqfdh0VrR3Z899ZqpYTkKq+6BM0VG+6vG9k43v6z8xfcc+G4GM", + "BB9sVEcXzX+wiD9YxO1YxPcQOYx4ah3TiBDdYfrQVIaBIXFZt/4e+ih88yqnMoio2WfmOMMRnXHjS3CN", + "L63URXFldTrKmxKlkQ28Wz3vD5b3B8v7/bC8s/2Mpi2Y3FozuoJdQctaH1LrSmfiOnBTICw2hKhvB64r", + "grf+Pr6mTCdLId0jZKxP0e+sgebHLr1e59cmo03vC6bpCX4Mg4qjvx7X5X+iH7sejthXZ+EfaORfYvjP", + "jbcz9B4ia6/9hu8/GraMyeUd12+cYafHx/iwby2UPp59nn/qOMrCjx9rEvhU3xWOFD5//Pz/AgAA///R", + "h9sPStEAAA==", } // 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 a6522fea42..82f705c7ad 100644 --- a/daemon/algod/api/server/v2/generated/participating/public/routes.go +++ b/daemon/algod/api/server/v2/generated/participating/public/routes.go @@ -177,186 +177,189 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "fQ2zr0IyKSW7xqSm7z/+vwAAAP//kA6nBhHdAAA=", + "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", } // 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 3e774a8309..4eba078ace 100644 --- a/daemon/algod/api/server/v2/handlers.go +++ b/daemon/algod/api/server/v2/handlers.go @@ -100,7 +100,7 @@ type NodeInterface interface { GenesisID() string GenesisHash() crypto.Digest BroadcastSignedTxGroup(txgroup []transactions.SignedTxn) error - Simulate(txgroup []transactions.SignedTxn) (result simulation.Result, err error) + Simulate(request simulation.Request) (result simulation.Result, err error) GetPendingTransaction(txID transactions.Txid) (res node.TxnWithStatus, found bool) GetPendingTxnsFromPool() ([]transactions.SignedTxn, error) SuggestedFee() basics.MicroAlgos @@ -944,10 +944,11 @@ type PreEncodedSimulateTxnGroupResult struct { // PreEncodedSimulateResponse mirrors model.SimulateResponse type PreEncodedSimulateResponse struct { - Version uint64 `codec:"version"` - LastRound uint64 `codec:"last-round"` - TxnGroups []PreEncodedSimulateTxnGroupResult `codec:"txn-groups"` - WouldSucceed bool `codec:"would-succeed"` + Version uint64 `codec:"version"` + LastRound uint64 `codec:"last-round"` + TxnGroups []PreEncodedSimulateTxnGroupResult `codec:"txn-groups"` + WouldSucceed bool `codec:"would-succeed"` + EvalOverrides *model.SimulationEvalOverrides `codec:"eval-overrides,omitempty"` } // PreEncodedSimulateRequestTransactionGroup mirrors model.SimulateRequestTransactionGroup @@ -957,7 +958,8 @@ type PreEncodedSimulateRequestTransactionGroup struct { // PreEncodedSimulateRequest mirrors model.SimulateRequest type PreEncodedSimulateRequest struct { - TxnGroups []PreEncodedSimulateRequestTransactionGroup `codec:"txn-groups"` + TxnGroups []PreEncodedSimulateRequestTransactionGroup `codec:"txn-groups"` + LiftLogLimits bool `codec:"lift-log-limits,omitempty"` } // SimulateTransaction simulates broadcasting a raw transaction to the network, returning relevant simulation results. @@ -1001,14 +1003,18 @@ func (v2 *Handlers) SimulateTransaction(ctx echo.Context, params model.SimulateT } } - if len(simulateRequest.TxnGroups) != 1 { - err := fmt.Errorf("expected 1 transaction group, got %d", len(simulateRequest.TxnGroups)) - return badRequest(ctx, err, err.Error(), v2.Log) + txnGroups := make([][]transactions.SignedTxn, len(simulateRequest.TxnGroups)) + for i := 0; i < len(simulateRequest.TxnGroups); i++ { + txnGroups[i] = simulateRequest.TxnGroups[i].Txns } - txgroup := simulateRequest.TxnGroups[0].Txns // Simulate transaction - simulationResult, err := v2.Node.Simulate(txgroup) + simulationResult, err := v2.Node.Simulate( + simulation.Request{ + TxnGroups: txnGroups, + LiftLogLimits: simulateRequest.LiftLogLimits, + }, + ) if err != nil { var invalidTxErr simulation.InvalidTxGroupError switch { diff --git a/daemon/algod/api/server/v2/test/helpers.go b/daemon/algod/api/server/v2/test/helpers.go index 7f9877ad89..d9f368c2ad 100644 --- a/daemon/algod/api/server/v2/test/helpers.go +++ b/daemon/algod/api/server/v2/test/helpers.go @@ -167,9 +167,9 @@ func (m *mockNode) BroadcastSignedTxGroup(txgroup []transactions.SignedTxn) erro return m.err } -func (m *mockNode) Simulate(txgroup []transactions.SignedTxn) (simulation.Result, error) { +func (m *mockNode) Simulate(request simulation.Request) (simulation.Result, error) { simulator := simulation.MakeSimulator(m.ledger.(*data.Ledger)) - return simulator.Simulate(txgroup) + return simulator.Simulate(request) } func (m *mockNode) GetPendingTransaction(txID transactions.Txid) (res node.TxnWithStatus, found bool) { diff --git a/daemon/algod/api/server/v2/utils.go b/daemon/algod/api/server/v2/utils.go index bce9371e2b..55d1c49c39 100644 --- a/daemon/algod/api/server/v2/utils.go +++ b/daemon/algod/api/server/v2/utils.go @@ -388,11 +388,20 @@ func convertTxnGroupResult(txnGroupResult simulation.TxnGroupResult) PreEncodedS } func convertSimulationResult(result simulation.Result) PreEncodedSimulateResponse { + var evalOverrides *model.SimulationEvalOverrides + if result.EvalOverrides != (simulation.ResultEvalOverrides{}) { + evalOverrides = &model.SimulationEvalOverrides{ + MaxLogSize: result.EvalOverrides.MaxLogSize, + MaxLogCalls: result.EvalOverrides.MaxLogCalls, + } + } + encodedSimulationResult := PreEncodedSimulateResponse{ - Version: result.Version, - LastRound: uint64(result.LastRound), - WouldSucceed: result.WouldSucceed, - TxnGroups: make([]PreEncodedSimulateTxnGroupResult, len(result.TxnGroups)), + Version: result.Version, + LastRound: uint64(result.LastRound), + WouldSucceed: result.WouldSucceed, + TxnGroups: make([]PreEncodedSimulateTxnGroupResult, len(result.TxnGroups)), + EvalOverrides: evalOverrides, } for i, txnGroup := range result.TxnGroups { diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index 14ab082c50..f40f60fce5 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -248,6 +248,23 @@ type boxRef struct { name string } +// EvalConstants contains constant parameters that are used by opcodes during evaluation (including both real-execution and simulation). +type EvalConstants struct { + // MaxLogSize is the limit of total log size from n log calls in a program + MaxLogSize uint64 + + // MaxLogCalls is the limit of total log calls during a program execution + MaxLogCalls uint64 +} + +// RuntimeEvalConstants gives a set of const params used in normal runtime of opcodes +func RuntimeEvalConstants() EvalConstants { + return EvalConstants{ + MaxLogSize: uint64(maxLogSize), + MaxLogCalls: uint64(maxLogCalls), + } +} + // EvalParams contains data that comes into condition evaluation. type EvalParams struct { Proto *config.ConsensusParams @@ -299,6 +316,8 @@ type EvalParams struct { // readBudgetChecked allows us to only check the read budget once readBudgetChecked bool + EvalConstants + // Caching these here means the hashes can be shared across the TxnGroup // (and inners, because the cache is shared with the inner EvalParams) appAddrCache map[basics.AppIndex]basics.Address @@ -372,6 +391,7 @@ func NewEvalParams(txgroup []transactions.SignedTxnWithAD, proto *config.Consens 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 @@ -451,6 +471,7 @@ func NewInnerEvalParams(txg []transactions.SignedTxnWithAD, caller *EvalContext) ioBudget: caller.ioBudget, readBudgetChecked: true, // don't check for inners appAddrCache: caller.appAddrCache, + EvalConstants: caller.EvalConstants, // read comment in EvalParams declaration about txid caches caller: caller, } @@ -4761,13 +4782,13 @@ func opAcctParamsGet(cx *EvalContext) error { func opLog(cx *EvalContext) error { last := len(cx.stack) - 1 - if len(cx.txn.EvalDelta.Logs) >= maxLogCalls { - return fmt.Errorf("too many log calls in program. up to %d is allowed", maxLogCalls) + if uint64(len(cx.txn.EvalDelta.Logs)) >= cx.MaxLogCalls { + return fmt.Errorf("too many log calls in program. up to %d is allowed", cx.MaxLogCalls) } log := cx.stack[last] cx.logSize += len(log.Bytes) - if cx.logSize > maxLogSize { - return fmt.Errorf("program logs too large. %d bytes > %d bytes limit", cx.logSize, maxLogSize) + if uint64(cx.logSize) > cx.MaxLogSize { + return fmt.Errorf("program logs too large. %d bytes > %d bytes limit", cx.logSize, cx.MaxLogSize) } cx.txn.EvalDelta.Logs = append(cx.txn.EvalDelta.Logs, string(log.Bytes)) cx.stack = cx.stack[:last] diff --git a/ledger/simulation/simulation_eval_test.go b/ledger/simulation/simulation_eval_test.go index 134fdd2339..977f4a21e6 100644 --- a/ledger/simulation/simulation_eval_test.go +++ b/ledger/simulation/simulation_eval_test.go @@ -151,7 +151,7 @@ func simulationTest(t *testing.T, f func(accounts []simulationtesting.Account, t testcase := f(accounts, txnInfo) - actual, err := s.Simulate(testcase.input) + actual, err := s.Simulate(simulation.Request{TxnGroups: [][]transactions.SignedTxn{testcase.input}}) require.NoError(t, err) validateSimulationResult(t, actual) @@ -457,7 +457,7 @@ func TestStateProofTxn(t *testing.T) { }).SignedTxn(), } - _, err := s.Simulate(txgroup) + _, err := s.Simulate(simulation.Request{TxnGroups: [][]transactions.SignedTxn{txgroup}}) require.ErrorContains(t, err, "cannot simulate StateProof transactions") } @@ -490,7 +490,7 @@ func TestSimpleGroupTxn(t *testing.T) { } // Should fail if there is no group parameter - result, err := s.Simulate(txgroup) + result, err := s.Simulate(simulation.Request{TxnGroups: [][]transactions.SignedTxn{txgroup}}) require.NoError(t, err) require.False(t, result.WouldSucceed) require.Len(t, result.TxnGroups, 1) @@ -510,7 +510,7 @@ func TestSimpleGroupTxn(t *testing.T) { require.Equal(t, sender2Balance, sender2Data.MicroAlgos) // Should now pass - result, err = s.Simulate(txgroup) + result, err = s.Simulate(simulation.Request{TxnGroups: [][]transactions.SignedTxn{txgroup}}) require.NoError(t, err) require.False(t, result.WouldSucceed) require.Len(t, result.TxnGroups, 1) @@ -1121,7 +1121,7 @@ func TestSignatureCheck(t *testing.T) { } // should catch missing signature - result, err := s.Simulate(txgroup) + result, err := s.Simulate(simulation.Request{TxnGroups: [][]transactions.SignedTxn{txgroup}}) require.NoError(t, err) require.False(t, result.WouldSucceed) require.Len(t, result.TxnGroups, 1) @@ -1134,7 +1134,7 @@ func TestSignatureCheck(t *testing.T) { txgroup[0] = txgroup[0].Txn.Sign(signatureSecrets) // should not error now that we have a signature - result, err = s.Simulate(txgroup) + result, err = s.Simulate(simulation.Request{TxnGroups: [][]transactions.SignedTxn{txgroup}}) require.NoError(t, err) require.True(t, result.WouldSucceed) require.Len(t, result.TxnGroups, 1) @@ -1144,7 +1144,7 @@ func TestSignatureCheck(t *testing.T) { // should error with invalid signature txgroup[0].Sig[0] += byte(1) // will wrap if > 255 - result, err = s.Simulate(txgroup) + result, err = s.Simulate(simulation.Request{TxnGroups: [][]transactions.SignedTxn{txgroup}}) require.ErrorAs(t, err, &simulation.InvalidTxGroupError{}) require.ErrorContains(t, err, "one signature didn't pass") } @@ -1170,11 +1170,266 @@ func TestInvalidTxGroup(t *testing.T) { } // should error with invalid transaction group error - _, err := s.Simulate(txgroup) + _, err := s.Simulate(simulation.Request{TxnGroups: [][]transactions.SignedTxn{txgroup}}) require.ErrorAs(t, err, &simulation.InvalidTxGroupError{}) require.ErrorContains(t, err, "transaction from incentive pool is invalid") } +// TestLogLimitLiftingInSimulation tests that an app with log calls that exceed limits during normal runtime +// can get through during simulation with `lift-log-limits` activated +func TestLogLimitLiftingInSimulation(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + LogTimes := 40 + LogLongLine := strings.Repeat("a", 1050) + + appSourceThatLogsALot := `#pragma version 8 +txn NumAppArgs +int 0 +== +bnz final +` + strings.Repeat(fmt.Sprintf(`byte "%s" +log +`, LogLongLine), LogTimes) + `final: +int 1` + + l, accounts, txnInfo := simulationtesting.PrepareSimulatorTest(t) + defer l.Close() + s := simulation.MakeSimulator(l) + + sender := accounts[0] + receiver := accounts[1] + + futureAppID := basics.AppIndex(1) + + createTxn := txnInfo.NewTxn(txntest.Txn{ + Type: protocol.ApplicationCallTx, + Sender: sender.Addr, + ApplicationID: 0, + ApprovalProgram: appSourceThatLogsALot, + ClearStateProgram: "#pragma version 8\nint 1", + }) + + callsABunchLogs := txnInfo.NewTxn(txntest.Txn{ + Type: protocol.ApplicationCallTx, + Sender: sender.Addr, + ApplicationID: futureAppID, + Accounts: []basics.Address{receiver.Addr}, + ApplicationArgs: [][]byte{[]byte("first-arg")}, + }) + + txntest.Group(&createTxn, &callsABunchLogs) + + signedCreateTxn := createTxn.Txn().Sign(sender.Sk) + signedCallsABunchLogs := callsABunchLogs.Txn().Sign(sender.Sk) + + actual, err := s.Simulate( + simulation.Request{ + TxnGroups: [][]transactions.SignedTxn{{signedCreateTxn, signedCallsABunchLogs}}, + LiftLogLimits: true, + }, + ) + require.NoError(t, err) + + validateSimulationResult(t, actual) + + expectedMaxLogCalls, expectedMaxLogSize := uint64(2048), uint64(65536) + expectedLog := make([]string, LogTimes) + for i := 0; i < LogTimes; i++ { + expectedLog[i] = LogLongLine + } + testcase := simulationTestCase{ + input: []transactions.SignedTxn{signedCreateTxn, signedCallsABunchLogs}, + expected: simulation.Result{ + Version: 1, + LastRound: txnInfo.LatestRound(), + TxnGroups: []simulation.TxnGroupResult{ + { + Txns: []simulation.TxnResult{ + { + Txn: transactions.SignedTxnWithAD{ + ApplyData: transactions.ApplyData{ + ApplicationID: futureAppID, + }, + }, + AppBudgetConsumed: 6, + }, + { + Txn: transactions.SignedTxnWithAD{ + ApplyData: transactions.ApplyData{ + EvalDelta: transactions.EvalDelta{ + Logs: expectedLog, + }, + }, + }, + AppBudgetConsumed: 86, + }, + }, + AppBudgetAdded: 1400, + AppBudgetConsumed: 92, + }, + }, + EvalOverrides: simulation.ResultEvalOverrides{ + MaxLogCalls: &expectedMaxLogCalls, + MaxLogSize: &expectedMaxLogSize, + }, + WouldSucceed: true, + }, + } + + require.Len(t, testcase.expected.TxnGroups, 1, "Test case must expect a single txn group") + require.Len(t, testcase.expected.TxnGroups[0].Txns, len(testcase.input), "Test case expected a different number of transactions than its input") + + for i, inputTxn := range testcase.input { + if testcase.expected.TxnGroups[0].Txns[i].Txn.Txn.Type == "" { + // Use Type as a marker for whether the transaction was specified or not. If not + // specified, replace it with the input txn + testcase.expected.TxnGroups[0].Txns[i].Txn.SignedTxn = inputTxn + } + normalizeEvalDeltas(t, &actual.TxnGroups[0].Txns[i].Txn.EvalDelta, &testcase.expected.TxnGroups[0].Txns[i].Txn.EvalDelta) + } + + if len(testcase.expectedError) != 0 { + require.Contains(t, actual.TxnGroups[0].FailureMessage, testcase.expectedError) + require.False(t, testcase.expected.WouldSucceed, "Test case WouldSucceed value is not consistent with expected failure") + // if it matched the expected error, copy the actual one so it will pass the equality check below + testcase.expected.TxnGroups[0].FailureMessage = actual.TxnGroups[0].FailureMessage + } + + // Do not attempt to compare blocks + actual.Block = nil + require.Equal(t, testcase.expected, actual) +} + +func TestLogSizeExceedWithLiftInSimulation(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + LogTimes := 65 + LogLongLine := strings.Repeat("a", 1050) + + appSourceThatLogsALot := `#pragma version 8 +txn NumAppArgs +int 0 +== +bnz final +` + strings.Repeat(fmt.Sprintf(`byte "%s" +log +`, LogLongLine), LogTimes) + `final: +int 1` + + l, accounts, txnInfo := simulationtesting.PrepareSimulatorTest(t) + defer l.Close() + s := simulation.MakeSimulator(l) + + sender := accounts[0] + receiver := accounts[1] + + futureAppID := basics.AppIndex(1) + + createTxn := txnInfo.NewTxn(txntest.Txn{ + Type: protocol.ApplicationCallTx, + Sender: sender.Addr, + ApplicationID: 0, + ApprovalProgram: appSourceThatLogsALot, + ClearStateProgram: "#pragma version 8\nint 1", + }) + + callsABunchLogs := txnInfo.NewTxn(txntest.Txn{ + Type: protocol.ApplicationCallTx, + Sender: sender.Addr, + ApplicationID: futureAppID, + Accounts: []basics.Address{receiver.Addr}, + ApplicationArgs: [][]byte{[]byte("first-arg")}, + }) + + txntest.Group(&createTxn, &callsABunchLogs) + + signedCreateTxn := createTxn.Txn().Sign(sender.Sk) + signedCallsABunchLogs := callsABunchLogs.Txn().Sign(sender.Sk) + + actual, err := s.Simulate( + simulation.Request{ + TxnGroups: [][]transactions.SignedTxn{{signedCreateTxn, signedCallsABunchLogs}}, + LiftLogLimits: true, + }, + ) + require.NoError(t, err) + + validateSimulationResult(t, actual) + + expectedMaxLogCalls, expectedMaxLogSize := uint64(2048), uint64(65536) + actualLogTimes := 65536 / len(LogLongLine) + expectedLog := make([]string, actualLogTimes) + for i := 0; i < actualLogTimes; i++ { + expectedLog[i] = LogLongLine + } + testcase := simulationTestCase{ + input: []transactions.SignedTxn{signedCreateTxn, signedCallsABunchLogs}, + expected: simulation.Result{ + Version: 1, + LastRound: txnInfo.LatestRound(), + TxnGroups: []simulation.TxnGroupResult{ + { + FailedAt: simulation.TxnPath{1}, + Txns: []simulation.TxnResult{ + { + Txn: transactions.SignedTxnWithAD{ + ApplyData: transactions.ApplyData{ + ApplicationID: futureAppID, + }, + }, + AppBudgetConsumed: 6, + }, + { + Txn: transactions.SignedTxnWithAD{ + ApplyData: transactions.ApplyData{ + EvalDelta: transactions.EvalDelta{ + Logs: expectedLog, + }, + }, + }, + AppBudgetConsumed: 131, + }, + }, + AppBudgetAdded: 1400, + AppBudgetConsumed: 137, + }, + }, + EvalOverrides: simulation.ResultEvalOverrides{ + MaxLogCalls: &expectedMaxLogCalls, + MaxLogSize: &expectedMaxLogSize, + }, + WouldSucceed: false, + }, + expectedError: "logic eval error: program logs too large. 66150 bytes > 65536 bytes limit.", + } + + require.Len(t, testcase.expected.TxnGroups, 1, "Test case must expect a single txn group") + require.Len(t, testcase.expected.TxnGroups[0].Txns, len(testcase.input), "Test case expected a different number of transactions than its input") + + for i, inputTxn := range testcase.input { + if testcase.expected.TxnGroups[0].Txns[i].Txn.Txn.Type == "" { + // Use Type as a marker for whether the transaction was specified or not. If not + // specified, replace it with the input txn + testcase.expected.TxnGroups[0].Txns[i].Txn.SignedTxn = inputTxn + } + normalizeEvalDeltas(t, &actual.TxnGroups[0].Txns[i].Txn.EvalDelta, &testcase.expected.TxnGroups[0].Txns[i].Txn.EvalDelta) + } + + if len(testcase.expectedError) != 0 { + require.Contains(t, actual.TxnGroups[0].FailureMessage, testcase.expectedError) + require.False(t, testcase.expected.WouldSucceed, "Test case WouldSucceed value is not consistent with expected failure") + // if it matched the expected error, copy the actual one so it will pass the equality check below + testcase.expected.TxnGroups[0].FailureMessage = actual.TxnGroups[0].FailureMessage + } + + // Do not attempt to compare blocks + actual.Block = nil + require.Equal(t, testcase.expected, actual) +} + // TestBalanceChangesWithApp sends a payment transaction to a new account and confirms its balance // within a subsequent app call func TestBalanceChangesWithApp(t *testing.T) { diff --git a/ledger/simulation/simulator.go b/ledger/simulation/simulator.go index 585847c374..cb91c61656 100644 --- a/ledger/simulation/simulator.go +++ b/ledger/simulation/simulator.go @@ -18,6 +18,7 @@ package simulation import ( "errors" + "fmt" "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data" @@ -36,6 +37,12 @@ type simulatorLedger struct { start basics.Round } +// Request packs simulation related txn-group(s), and configurations that are overlapping the ones in real transactions. +type Request struct { + TxnGroups [][]transactions.SignedTxn + LiftLogLimits bool +} + // Latest is part of the LedgerForSimulator interface. // We override this to use the set latest to prevent racing with the network func (l simulatorLedger) Latest() basics.Round { @@ -189,9 +196,18 @@ func (s Simulator) simulateWithTracer(txgroup []transactions.SignedTxn, tracer l } // Simulate simulates a transaction group using the simulator. Will error if the transaction group is not well-formed. -func (s Simulator) Simulate(txgroup []transactions.SignedTxn) (Result, error) { - simulatorTracer := makeEvalTracer(s.ledger.start, txgroup) - block, missingSigIndexes, err := s.simulateWithTracer(txgroup, simulatorTracer) +func (s Simulator) Simulate(simulateRequest Request) (Result, error) { + simulatorTracer := makeEvalTracer(s.ledger.start, simulateRequest) + + if len(simulateRequest.TxnGroups) != 1 { + return Result{}, InvalidTxGroupError{ + SimulatorError{ + err: fmt.Errorf("expected 1 transaction group, got %d", len(simulateRequest.TxnGroups)), + }, + } + } + + block, missingSigIndexes, err := s.simulateWithTracer(simulateRequest.TxnGroups[0], simulatorTracer) if err != nil { simulatorTracer.result.WouldSucceed = false diff --git a/ledger/simulation/trace.go b/ledger/simulation/trace.go index 6313e0539c..3def4f85a4 100644 --- a/ledger/simulation/trace.go +++ b/ledger/simulation/trace.go @@ -19,8 +19,10 @@ package simulation import ( "fmt" + "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/data/transactions/logic" "github.com/algorand/go-algorand/ledger/ledgercore" ) @@ -67,36 +69,74 @@ func makeTxnGroupResult(txgroup []transactions.SignedTxn) TxnGroupResult { // ResultLatestVersion is the latest version of the Result struct const ResultLatestVersion = uint64(1) +// ResultEvalOverrides contains the limits and parameters during a call to Simulator.Simulate +type ResultEvalOverrides struct { + MaxLogCalls *uint64 + MaxLogSize *uint64 +} + +// SimulateLogBytesLimit hardcode limit of how much bytes one can log per transaction during simulation (with lift-log-limits) +const SimulateLogBytesLimit = uint64(65536) + +// LiftLogLimits method modify the log limits from lift option: +// - if lift log limits, then overload result from local Config +// - otherwise, set `LogLimits` field to be nil +func (eo ResultEvalOverrides) LiftLogLimits(lift bool) ResultEvalOverrides { + if lift { + maxLogCalls, maxLogSize := uint64(config.MaxLogCalls), SimulateLogBytesLimit + eo.MaxLogCalls = &maxLogCalls + eo.MaxLogSize = &maxLogSize + } + return eo +} + +// LogicEvalConstants method infers the logic.EvalConstants from Result.EvalOverrides (*ResultEvalOverrides) +// and generate appropriate parameters to override during simulation runtime. +func (eo ResultEvalOverrides) LogicEvalConstants() logic.EvalConstants { + logicEvalConstants := logic.RuntimeEvalConstants() + if eo.MaxLogSize != nil { + logicEvalConstants.MaxLogSize = *eo.MaxLogSize + } + if eo.MaxLogCalls != nil { + logicEvalConstants.MaxLogCalls = *eo.MaxLogCalls + } + return logicEvalConstants +} + // Result contains the result from a call to Simulator.Simulate type Result struct { - Version uint64 - LastRound basics.Round - TxnGroups []TxnGroupResult // this is a list so that supporting multiple in the future is not breaking - WouldSucceed bool // true iff no failure message, no missing signatures, and the budget was not exceeded - Block *ledgercore.ValidatedBlock + Version uint64 + LastRound basics.Round + TxnGroups []TxnGroupResult // this is a list so that supporting multiple in the future is not breaking + WouldSucceed bool // true iff no failure message, no missing signatures, and the budget was not exceeded + EvalOverrides ResultEvalOverrides + Block *ledgercore.ValidatedBlock } -func makeSimulationResultWithVersion(lastRound basics.Round, txgroups [][]transactions.SignedTxn, version uint64) (Result, error) { +func makeSimulationResultWithVersion(lastRound basics.Round, request Request, version uint64) (Result, error) { if version != ResultLatestVersion { return Result{}, fmt.Errorf("invalid SimulationResult version: %d", version) } - groups := make([]TxnGroupResult, len(txgroups)) + groups := make([]TxnGroupResult, len(request.TxnGroups)) - for i, txgroup := range txgroups { + for i, txgroup := range request.TxnGroups { groups[i] = makeTxnGroupResult(txgroup) } + resultEvalConstants := ResultEvalOverrides{}.LiftLogLimits(request.LiftLogLimits) + return Result{ - Version: version, - LastRound: lastRound, - TxnGroups: groups, - WouldSucceed: true, + Version: version, + LastRound: lastRound, + TxnGroups: groups, + EvalOverrides: resultEvalConstants, + WouldSucceed: true, }, nil } -func makeSimulationResult(lastRound basics.Round, txgroups [][]transactions.SignedTxn) Result { - result, err := makeSimulationResultWithVersion(lastRound, txgroups, ResultLatestVersion) +func makeSimulationResult(lastRound basics.Round, request Request) Result { + result, err := makeSimulationResultWithVersion(lastRound, request, ResultLatestVersion) if err != nil { // this should never happen, since we pass in ResultLatestVersion panic(err) diff --git a/ledger/simulation/tracer.go b/ledger/simulation/tracer.go index 9a42e617c2..1a80d2654a 100644 --- a/ledger/simulation/tracer.go +++ b/ledger/simulation/tracer.go @@ -83,8 +83,8 @@ type evalTracer struct { failedAt TxnPath } -func makeEvalTracer(lastRound basics.Round, txgroup []transactions.SignedTxn) *evalTracer { - result := makeSimulationResult(lastRound, [][]transactions.SignedTxn{txgroup}) +func makeEvalTracer(lastRound basics.Round, request Request) *evalTracer { + result := makeSimulationResult(lastRound, request) return &evalTracer{result: &result} } @@ -141,6 +141,9 @@ func (tracer *evalTracer) BeforeTxnGroup(ep *logic.EvalParams) { if ep.FeeCredit != nil { tracer.result.TxnGroups[0].FeeCredit = *ep.FeeCredit } + + // Override runtime related constraints against ep, before entering txn group + ep.EvalConstants = tracer.result.EvalOverrides.LogicEvalConstants() } func (tracer *evalTracer) AfterTxnGroup(ep *logic.EvalParams, evalError error) { diff --git a/node/follower_node.go b/node/follower_node.go index e04cbe2eb8..633515d1c1 100644 --- a/node/follower_node.go +++ b/node/follower_node.go @@ -240,7 +240,7 @@ func (node *AlgorandFollowerNode) BroadcastInternalSignedTxGroup(_ []transaction // Simulate speculatively runs a transaction group against the current // blockchain state and returns the effects and/or errors that would result. -func (node *AlgorandFollowerNode) Simulate(_ []transactions.SignedTxn) (result simulation.Result, err error) { +func (node *AlgorandFollowerNode) Simulate(_ simulation.Request) (result simulation.Result, err error) { err = fmt.Errorf("cannot simulate in data mode") return } diff --git a/node/follower_node_test.go b/node/follower_node_test.go index 33a004acd2..cbc77b3eb6 100644 --- a/node/follower_node_test.go +++ b/node/follower_node_test.go @@ -29,6 +29,7 @@ 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/ledger/simulation" "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" @@ -112,7 +113,7 @@ func TestErrors(t *testing.T) { node := setupFollowNode(t) require.Error(t, node.BroadcastSignedTxGroup([]transactions.SignedTxn{})) require.Error(t, node.BroadcastInternalSignedTxGroup([]transactions.SignedTxn{})) - _, err := node.Simulate([]transactions.SignedTxn{}) + _, err := node.Simulate(simulation.Request{}) require.Error(t, err) _, err = node.GetParticipationKey(account.ParticipationID{}) require.Error(t, err) diff --git a/node/node.go b/node/node.go index d99d455774..00aeffe75e 100644 --- a/node/node.go +++ b/node/node.go @@ -565,9 +565,9 @@ func (node *AlgorandFullNode) broadcastSignedTxGroup(txgroup []transactions.Sign // Simulate speculatively runs a transaction group against the current // blockchain state and returns the effects and/or errors that would result. -func (node *AlgorandFullNode) Simulate(txgroup []transactions.SignedTxn) (result simulation.Result, err error) { +func (node *AlgorandFullNode) Simulate(request simulation.Request) (result simulation.Result, err error) { simulator := simulation.MakeSimulator(node.ledger) - return simulator.Simulate(txgroup) + return simulator.Simulate(request) } // ListTxns returns SignedTxns associated with a specific account in a range of Rounds (inclusive). diff --git a/test/e2e-go/restAPI/restClient_test.go b/test/e2e-go/restAPI/restClient_test.go index 4b2024ca70..d3ee6de4ff 100644 --- a/test/e2e-go/restAPI/restClient_test.go +++ b/test/e2e-go/restAPI/restClient_test.go @@ -1646,3 +1646,150 @@ func TestSimulateTransaction(t *testing.T) { a.NoError(err) a.Zero(closeToBalance) } + +func TestSimulateWithUnlimitedLog(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + a := require.New(fixtures.SynchronizedTest(t)) + var localFixture fixtures.RestClientFixture + localFixture.Setup(t, filepath.Join("nettemplates", "TwoNodes50EachFuture.json")) + defer localFixture.Shutdown() + + testClient := localFixture.LibGoalClient + + _, 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) + if senderAddress == "" { + t.Error("no addr with funds") + } + a.NoError(err) + + toAddress := getDestAddr(t, testClient, nil, senderAddress, wh) + closeToAddress := getDestAddr(t, testClient, nil, senderAddress, wh) + + // Ensure these accounts don't exist + receiverBalance, err := testClient.GetBalance(toAddress) + a.NoError(err) + a.Zero(receiverBalance) + closeToBalance, err := testClient.GetBalance(closeToAddress) + a.NoError(err) + a.Zero(closeToBalance) + + // construct program that uses a lot of log + prog := `#pragma version 8 +txn NumAppArgs +int 0 +== +bnz final +` + for i := 0; i < 17; i++ { + prog += `byte "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +log +` + } + prog += `final: +int 1` + ops, err := logic.AssembleString(prog) + 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{} + + // create app + appCreateTxn, err := testClient.MakeUnsignedApplicationCallTx( + 0, nil, nil, nil, + nil, nil, transactions.NoOpOC, + approval, clearState, gl, lc, 0, + ) + a.NoError(err) + appCreateTxn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, 0, appCreateTxn) + a.NoError(err) + // sign and broadcast + appCreateTxID, err := testClient.SignAndBroadcastTransaction(wh, nil, appCreateTxn) + a.NoError(err) + _, err = waitForTransaction(t, testClient, senderAddress, appCreateTxID, 30*time.Second) + a.NoError(err) + + // get app ID + submittedAppCreateTxn, err := testClient.PendingTransactionInformation(appCreateTxID) + a.NoError(err) + a.NotNil(submittedAppCreateTxn.ApplicationIndex) + createdAppID := basics.AppIndex(*submittedAppCreateTxn.ApplicationIndex) + a.Greater(uint64(createdAppID), uint64(0)) + + // fund app account + appFundTxn, err := testClient.SendPaymentFromWallet( + wh, nil, senderAddress, createdAppID.Address().String(), + 0, 10_000_000, nil, "", 0, 0, + ) + a.NoError(err) + appFundTxID := appFundTxn.ID() + _, err = waitForTransaction(t, testClient, senderAddress, appFundTxID.String(), 30*time.Second) + a.NoError(err) + + // construct app call + appCallTxn, err := testClient.MakeUnsignedAppNoOpTx( + uint64(createdAppID), [][]byte{[]byte("first-arg")}, + nil, nil, nil, nil, + ) + a.NoError(err) + appCallTxn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, 0, appCallTxn) + a.NoError(err) + appCallTxnSigned, err := testClient.SignTransactionWithWallet(wh, nil, appCallTxn) + a.NoError(err) + + resp, err := testClient.SimulateTransactions(v2.PreEncodedSimulateRequest{ + TxnGroups: []v2.PreEncodedSimulateRequestTransactionGroup{ + { + Txns: []transactions.SignedTxn{appCallTxnSigned}, + }, + }, + LiftLogLimits: true, + }) + a.NoError(err) + + var logs [][]byte + for i := 0; i < 17; i++ { + logs = append(logs, []byte("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")) + } + + budgetAdded, budgetUsed := uint64(700), uint64(40) + maxLogSize, maxLogCalls := uint64(65536), uint64(2048) + + expectedResult := v2.PreEncodedSimulateResponse{ + Version: 1, + LastRound: resp.LastRound, + WouldSucceed: true, + EvalOverrides: &model.SimulationEvalOverrides{ + MaxLogSize: &maxLogSize, + MaxLogCalls: &maxLogCalls, + }, + TxnGroups: []v2.PreEncodedSimulateTxnGroupResult{ + { + Txns: []v2.PreEncodedSimulateTxnResult{ + { + Txn: v2.PreEncodedTxInfo{ + Txn: appCallTxnSigned, + Logs: &logs, + }, + AppBudgetConsumed: &budgetUsed, + }, + }, + AppBudgetAdded: &budgetAdded, + AppBudgetConsumed: &budgetUsed, + }, + }, + } + a.Equal(expectedResult, resp) +} diff --git a/test/scripts/e2e_subs/e2e-app-simulate.sh b/test/scripts/e2e_subs/e2e-app-simulate.sh index ebfb63d9c1..6fcd9bc08f 100755 --- a/test/scripts/e2e_subs/e2e-app-simulate.sh +++ b/test/scripts/e2e_subs/e2e-app-simulate.sh @@ -24,7 +24,7 @@ CONST_FALSE="false" # This should fail with a 413 error. # Some of our MacOS nightly tests fail for specifying the bs (block size) # value in capital letters (i.e. 11M), so just specify it as 1024 bytes and -# allocate 11K blocks so we get a 11MB sized file. +# allocate 11K blocks so we get a 11MB sized file. dd if=/dev/zero of="${TEMPDIR}/tooLargeRequest.json" bs=1024 count=11000 RES=$(${gcmd} clerk simulate --request "${TEMPDIR}/tooLargeRequest.json" 2>&1 || true) EXPERROR="simulation error: HTTP 413 Request Entity Too Large:" @@ -170,3 +170,105 @@ if [[ $RES != $CONST_TRUE ]]; then date '+app-simulate-test FAIL the simulation call to empty()void should succeed %Y%m%d_%H%M%S' false fi + +########################################################### +# WE WANT TO FURTHER TEST UNLIMIT LOG IN SIMULATION WORKS # +########################################################### + +TEAL=test/scripts/e2e_subs/tealprogs + +printf '#pragma version 6\nint 1' > "${TEMPDIR}/simple-v6.teal" + +# NOTE: logs-a-lot.teal contains a method that logs 1.4kb info, which is well over 1kb limit in binary +# we test it here to see if the simulate unlimit log works under goal clerk simulate + +RES=$(${gcmd} app create --creator ${ACCOUNT} --approval-prog "${TEAL}/logs-a-lot.teal" --clear-prog "${TEMPDIR}/simple-v6.teal" --global-byteslices 0 --global-ints 0 --local-byteslices 0 --local-ints 0 2>&1 || true) +EXPSUCCESS='Created app with app index' +if [[ $RES != *"${EXPSUCCESS}"* ]]; then + date '+app-simulate-test FAIL the app creation for logs-a-lot.teal should succeed %Y%m%d_%H%M%S' + false +fi + +APPID=$(echo "$RES" | grep Created | awk '{ print $6 }') + +# SIMULATION! without unlimiting log should call `small_log()void` +${gcmd} app method --method "small_log()void" --app-id $APPID --from $ACCOUNT 2>&1 -o "${TEMPDIR}/small_log.tx" +${gcmd} clerk sign -i "${TEMPDIR}/small_log.tx" -o "${TEMPDIR}/small_log.stx" +RES=$(${gcmd} clerk simulate -t "${TEMPDIR}/small_log.stx") + +if [[ $(echo "$RES" | jq '."would-succeed"') != $CONST_TRUE ]]; then + date '+app-simulate-test FAIL the app call to logs-a-lot.teal for small_log()void would-succeed should be true %Y%m%d_%H%M%S' + false +fi + +EXPECTED_SMALL_LOG='yet another ephemeral log' + +if [[ $(echo "$RES" | jq '."txn-groups"[0]."txn-results"[0]."txn-result"."logs"[0] | @base64d') != *"${EXPECTED_SMALL_LOG}"* ]]; then + date '+app-simulate-test FAIL the app call to logs-a-lot.teal for small_log()void should succeed %Y%m%d_%H%M%S' + false +fi + +if [[ $(echo "$RES" | jq '."eval-overrides"') != null ]]; then + date '+app-simulate-test FAIL the app call to logs-a-lot.teal without lift-log-limits should not return with eval-overrides field %Y%m%d_%H%M%S' + false +fi + +${gcmd} app method --method "unlimited_log_test()void" --app-id $APPID --from $ACCOUNT 2>&1 -o "${TEMPDIR}/big_log.tx" +${gcmd} clerk sign -i "${TEMPDIR}/big_log.tx" -o "${TEMPDIR}/big_log.stx" +RES=$(${gcmd} clerk simulate -t "${TEMPDIR}/big_log.stx") + +if [[ $(echo "$RES" | jq '."would-succeed"') != $CONST_FALSE ]]; then + date '+app-simulate-test FAIL the app call to logs-a-lot.teal for unlimited_log_test()void would-succeed should be false without unlimiting log %Y%m%d_%H%M%S' + false +fi + +EXPECTED_FAILURE='logic eval error: too many log calls in program. up to 32 is allowed.' + +if [[ $(echo "$RES" | jq '."txn-groups"[0]."failure-message"') != *"${EXPECTED_FAILURE}"* ]]; then + date '+app-simulate-test FAIL the app call to logs-a-lot.teal for unlimited_log_test()void should fail without unlmited log option %Y%m%d_%H%M%S' + false +fi + +if [[ $(echo "$RES" | jq '."eval-overrides"') != null ]]; then + date '+app-simulate-test FAIL the app call to logs-a-lot.teal without lift-log-limits should not return with eval-overrides field %Y%m%d_%H%M%S' + false +fi + +# SIMULATION! with unlimiting log should call `unlimited_log_test()void` +${gcmd} app method --method "unlimited_log_test()void" --app-id $APPID --from $ACCOUNT 2>&1 -o "${TEMPDIR}/big_log.tx" +${gcmd} clerk sign -i "${TEMPDIR}/big_log.tx" -o "${TEMPDIR}/big_log.stx" +RES=$(${gcmd} clerk simulate --lift-log-limits -t "${TEMPDIR}/big_log.stx") + +if [[ $(echo "$RES" | jq '."would-succeed"') != $CONST_TRUE ]]; then + date '+app-simulate-test FAIL the app call to logs-a-lot.teal for unlimited_log_test()void would-succeed should be true with unlimiting log %Y%m%d_%H%M%S' + false +fi + +if [[ $(echo "$RES" | jq '."txn-groups"[0]."failed-at"') != null ]]; then + date '+app-simulate-test FAIL the app call to logs-a-lot.teal for unlimited_log_test()void should succeed with unlmited log option %Y%m%d_%H%M%S' + false +fi + +if [[ $(echo "$RES" | jq '."eval-overrides"."max-log-size"') -ne 65536 ]]; then + date '+app-simulate-test FAIL the app call to logs-a-lot.teal with unlimited log should return max log size 65536 %Y%m%d_%H%M%S' + false +fi + +if [[ $(echo "$RES" | jq '."eval-overrides"."max-log-calls"') -ne 2048 ]]; then + date '+app-simulate-test FAIL the app call to logs-a-lot.teal with unlimited log should return max log calls 2048 %Y%m%d_%H%M%S' + false +fi + +EXPECTED_FIRST_LINE_BIG_LOG='The time has come,' + +if [[ $(echo "$RES" | jq '."txn-groups"[0]."txn-results"[0]."txn-result"."logs"[0] | @base64d') != *"${EXPECTED_FIRST_LINE_BIG_LOG}"* ]]; then + date '+app-simulate-test FAIL the app call to logs-a-lot.teal for unlimited_log_test()void should succeed %Y%m%d_%H%M%S' + false +fi + +EXPECTED_LAST_LINE_BIG_LOG='Those of the largest size,' + +if [[ $(echo "$RES" | jq '."txn-groups"[0]."txn-results"[0]."txn-result"."logs"[-1] | @base64d') != *"${EXPECTED_LAST_LINE_BIG_LOG}"* ]]; then + date '+app-simulate-test FAIL the app call to logs-a-lot.teal for unlimited_log_test()void should succeed %Y%m%d_%H%M%S' + false +fi diff --git a/test/scripts/e2e_subs/tealprogs/logs-a-lot.teal b/test/scripts/e2e_subs/tealprogs/logs-a-lot.teal new file mode 100644 index 0000000000..5618275d6f --- /dev/null +++ b/test/scripts/e2e_subs/tealprogs/logs-a-lot.teal @@ -0,0 +1,141 @@ +#pragma version 6 +txn NumAppArgs +int 0 +== +bnz main_l6 +txna ApplicationArgs 0 +method "small_log()void" +== +bnz main_l5 +txna ApplicationArgs 0 +method "unlimited_log_test()void" +== +bnz main_l4 +err +main_l4: +txn OnCompletion +int NoOp +== +txn ApplicationID +int 0 +!= +&& +assert +callsub unlimitedlogtest_1 +int 1 +return +main_l5: +txn OnCompletion +int NoOp +== +txn ApplicationID +int 0 +!= +&& +assert +callsub smalllog_0 +int 1 +return +main_l6: +txn OnCompletion +int NoOp +== +bnz main_l8 +err +main_l8: +txn ApplicationID +int 0 +== +assert +int 1 +return + +// small_log +smalllog_0: +byte "yet another ephemeral log" +log +retsub + +// unlimited_log_test +unlimitedlogtest_1: +byte "The time has come," +log +byte "To talk of many things" +log +byte "Of shoes--and ships--and sealing-wax--" +log +byte "Of cabbages--and kings--" +log +byte "And why the sea is boiling hot--" +log +byte "And whether pigs have wings." +log +byte "But wait a bit, the Oysters cried," +log +byte "Before we have our chat" +log +byte "For some of us are out of breath," +log +byte "And all of us are fat!" +log +byte "No hurry! said the Carpenter." +log +byte "They thanked him much for that." +log +byte "A loaf of bread, the Walrus said," +log +byte "Is what we chiefly need" +log +byte "Pepper and vinegar besides" +log +byte "Are very good indeed--" +log +byte "Now if youre ready, Oysters dear," +log +byte "We can begin to feed." +log +byte "But not on us! the Oysters cried," +log +byte "Turning a little blue." +log +byte "After such kindness, that would be" +log +byte "A dismal thing to do!" +log +byte "The night is fine, the Walrus said." +log +byte "Do you admire the view?" +log +byte "It was so kind of you to come!" +log +byte "And you are very nice!" +log +byte "The Carpenter said nothing but" +log +byte "Cut us another slice" +log +byte "I wish you were not quite so deaf--" +log +byte "Ive had to ask you twice!" +log +byte "It seems a shame, the Walrus said," +log +byte "To play them such a trick," +log +byte "After weve brought them out so far," +log +byte "And made them trot so quick!" +log +byte "The Carpenter said nothing but" +log +byte "The butters spread too thick!" +log +byte "I weep for you, the Walrus said" +log +byte "I deeply sympathize." +log +byte "With sobs and tears he sorted out" +log +byte "Those of the largest size," +log +retsub \ No newline at end of file From 1d155e5e94bbea66e24a79038c8bdd9fa1072f3d Mon Sep 17 00:00:00 2001 From: abebeos <129396476+abebeos@users.noreply.github.com> Date: Mon, 24 Apr 2023 23:19:26 +0300 Subject: [PATCH 063/125] docs: add titles of subsystems, minor md fixes (#5279) --- README.md | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index c938d2b3a2..b8e3f82070 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,26 @@ | rel/stable
[![CircleCI](https://circleci.com/gh/algorand/go-algorand/tree/rel%2Fstable.svg?style=svg)](https://circleci.com/gh/algorand/go-algorand/tree/rel%2Fstable) | rel/beta
[![CircleCI](https://circleci.com/gh/algorand/go-algorand/tree/rel%2Fbeta.svg?style=svg)](https://circleci.com/gh/algorand/go-algorand/tree/rel%2Fbeta) | rel/nightly
[![CircleCI](https://circleci.com/gh/algorand/go-algorand/tree/rel%2Fnightly.svg?style=svg)](https://circleci.com/gh/algorand/go-algorand/tree/rel%2Fnightly) | | --- | --- | --- | -go-algorand -==================== +# go-algorand + Algorand's official implementation in Go. Algorand is a permissionless, pure proof-of-stake blockchain that delivers decentralization, scalability, security, and transaction finality. -## Getting Started ## +## Getting Started Our [developer website][developer site url] has the most up to date information about using and installing the Algorand platform. -## Building from source ## +## Building from source Development is done using the [Go Programming Language](https://golang.org/). The version of go is specified in the project's [go.mod](go.mod) file. This document assumes that you have a functioning environment setup. If you need assistance setting up an environment please visit the [official Go documentation website](https://golang.org/doc/). -### Linux / OSX ### +### Linux / OSX We currently strive to support Debian-based distributions with Ubuntu 18.04 being our official release target. @@ -94,12 +94,13 @@ Genesis files for mainnet, testnet, and betanet can be found in Please refer to our [CONTRIBUTING](CONTRIBUTING.md) document. -## Project Layout ## +## Project Layout + +`go-algorand` is split into various subsystems containing varius packages. -`go-algorand` is split into various subpackages. +### Core -The following packages provide core functionality to the `algod` and `kmd` -daemons, as well as other tools and commands: +Provides core functionality to the `algod` and `kmd` daemons, as well as other tools and commands: - `crypto` contains the cryptographic constructions we're using for hashing, signatures, and VRFs. There are also some Algorand-specific details here @@ -147,7 +148,9 @@ daemons, as well as other tools and commands: - `node` integrates the components above and handles initialization and shutdown. It provides queries into these components. -`daemon` defines the two daemons which provide Algorand clients with services: +### Daemon + +Contains the two daemons which provide Algorand clients with services: - `daemon/algod` holds the `algod` daemon, which implements a participating node. `algod` allows a node to participate in the agreement protocol, @@ -158,7 +161,9 @@ daemons, as well as other tools and commands: daemon allows a node to sign transactions. Because `kmd` is separate from `algod`, `kmd` allows a user to sign transactions on an air-gapped computer. -The following packages allow developers to interface with the Algorand system: +### Interfacing + +Allows developers to interface with the Algorand system: - `cmd` holds the primary commands defining entry points into the system. - `cmd/catchupsrv` ([README](cmd/catchupsrv/README.md)) is a tool to @@ -168,8 +173,8 @@ The following packages allow developers to interface with the Algorand system: - `tools/debug` holds secondary commands which assist developers during debugging. - `tools/misc` ([README](tools/misc/README.md)) small tools that are sometimes handy in a pinch. -The following packages contain tools to help Algorand developers deploy networks -of their own: +### Deployment +Help Algorand developers deploy networks of their own: - `nodecontrol` - `docker` @@ -178,12 +183,14 @@ of their own: - `components` - `netdeploy` -A number of packages provide utilities for the various components: +### Utilities +Provides utilities for the various components: - `logging` is a wrapper around `logrus`. - `util` contains a variety of utilities, including a codec, a SQLite wrapper, a goroutine pool, a timer interface, node metrics, and more. +### Test `test` ([README](test/README.md)) contains end-to-end tests and utilities for the above components. From ebf65a66940efcbc17e2453ba39ba797674800bc Mon Sep 17 00:00:00 2001 From: Hang Su <87964331+ahangsu@users.noreply.github.com> Date: Mon, 24 Apr 2023 16:42:47 -0400 Subject: [PATCH 064/125] Test: Improve flaky test from non-deterministic order string (#5333) --- cmd/algocfg/profileCommand_test.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/cmd/algocfg/profileCommand_test.go b/cmd/algocfg/profileCommand_test.go index e461c4e7c3..a99b65f0b0 100644 --- a/cmd/algocfg/profileCommand_test.go +++ b/cmd/algocfg/profileCommand_test.go @@ -17,7 +17,6 @@ package main import ( - "strings" "testing" "github.com/stretchr/testify/require" @@ -33,11 +32,9 @@ func Test_getConfigForArg(t *testing.T) { t.Parallel() _, err := getConfigForArg("invalid") - var names []string for name := range profileNames { - names = append(names, name) + require.ErrorContains(t, err, name) } - require.ErrorContains(t, err, strings.Join(names, ", ")) }) From 13e9c29d1fedf5670078c996a7d4091e2ab8f19e Mon Sep 17 00:00:00 2001 From: abebeos <129396476+abebeos@users.noreply.github.com> Date: Tue, 25 Apr 2023 19:36:15 +0300 Subject: [PATCH 065/125] docs: Fix bug label (#5322) --- .github/ISSUE_TEMPLATE/bug_report.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 880031609e..edf673f01d 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -2,7 +2,7 @@ name: "\U0001F41C Bug report" about: Report a reproducible bug. title: '' -labels: new-bug +labels: bug assignees: '' --- From 5e27371aad6ebab41a715dbabe1d724a8dd11e94 Mon Sep 17 00:00:00 2001 From: Eric Warehime Date: Wed, 26 Apr 2023 10:04:00 -0700 Subject: [PATCH 066/125] tests: Fix devmode test (#5334) --- daemon/algod/api/client/restClient.go | 12 ++++++++ test/e2e-go/features/devmode/devmode_test.go | 32 +++++++++++++------- 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/daemon/algod/api/client/restClient.go b/daemon/algod/api/client/restClient.go index 2a7e539050..829e7c68ec 100644 --- a/daemon/algod/api/client/restClient.go +++ b/daemon/algod/api/client/restClient.go @@ -753,3 +753,15 @@ func (client RestClient) GetLedgerStateDelta(round uint64) (response model.Ledge err = client.get(&response, fmt.Sprintf("/v2/deltas/%d", round), nil) return } + +// SetBlockTimestampOffset sets the offset in seconds to add to the block timestamp when in devmode +func (client RestClient) SetBlockTimestampOffset(offset uint64) (err error) { + err = client.post(nil, fmt.Sprintf("/v2/devmode/blocks/offset/%d", offset), nil, nil, true) + return +} + +// GetBlockTimestampOffset gets the offset in seconds which is being added to devmode blocks +func (client RestClient) GetBlockTimestampOffset() (response model.GetBlockTimeStampOffsetResponse, err error) { + err = client.get(&response, "/v2/devmode/blocks/offset", nil) + return +} diff --git a/test/e2e-go/features/devmode/devmode_test.go b/test/e2e-go/features/devmode/devmode_test.go index ec1fa692bc..728155ad3a 100644 --- a/test/e2e-go/features/devmode/devmode_test.go +++ b/test/e2e-go/features/devmode/devmode_test.go @@ -32,7 +32,6 @@ import ( func TestDevMode(t *testing.T) { partitiontest.PartitionTest(t) - t.Skipf("Skipping flaky test. Re-enable with #3267") if testing.Short() { t.Skip() @@ -50,18 +49,29 @@ func TestDevMode(t *testing.T) { txn := fixture.SendMoneyAndWait(0, 100000, 1000, sender.Address, receiver.String(), "") require.NotNil(t, txn.ConfirmedRound) firstRound := *txn.ConfirmedRound + 1 - start := time.Now() + blk, err := fixture.AlgodClient.Block(*txn.ConfirmedRound) + require.NoError(t, err) + seconds := int64(blk.Block["ts"].(float64)) + prevTime := time.Unix(seconds, 0) + // Set Block timestamp offset to test that consecutive txns properly get their block time set + const blkOffset = uint64(1_000_000) + err = fixture.AlgodClient.SetBlockTimestampOffset(blkOffset) + require.NoError(t, err) + resp, err := fixture.AlgodClient.GetBlockTimestampOffset() + require.NoError(t, err) + require.Equal(t, blkOffset, resp.Offset) // 2 transactions should be sent within one normal confirmation time. for i := uint64(0); i < 2; i++ { - txn = fixture.SendMoneyAndWait(firstRound+i, 100000, 1000, sender.Address, receiver.String(), "") - require.Equal(t, firstRound+i, txn.Txn.Txn.FirstValid) + round := firstRound + i + txn = fixture.SendMoneyAndWait(round, 100001, 1000, sender.Address, receiver.String(), "") + // SendMoneyAndWait subtracts 1 from firstValid + require.Equal(t, round-1, uint64(txn.Txn.Txn.FirstValid)) + newBlk, err := fixture.AlgodClient.Block(round) + require.NoError(t, err) + newBlkSeconds := int64(newBlk.Block["ts"].(float64)) + currTime := time.Unix(newBlkSeconds, 0) + require.Equal(t, currTime, prevTime.Add(1_000_000*time.Second)) + prevTime = currTime } - require.True(t, time.Since(start) < 8*time.Second, "Transactions should be quickly confirmed faster than usual.") - - // Without transactions there should be no rounds even after a normal confirmation time. - time.Sleep(10 * time.Second) - status, err := fixture.LibGoalClient.Status() - require.NoError(t, err) - require.Equal(t, txn.ConfirmedRound, status.LastRound, "There should be no rounds without a transaction.") } From 7866c324ba45273b9f0cc9e7040792f09bbdf24a Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Wed, 26 Apr 2023 14:08:39 -0400 Subject: [PATCH 067/125] AVM: Show opcode context for logicsigs (not just apps) (#5336) --- config/consensus.go | 4 + .../transactions/logic/backwardCompat_test.go | 4 +- data/transactions/logic/eval.go | 77 +++++++++++++------ data/transactions/logic/eval_test.go | 45 ++++------- data/transactions/verify/txn.go | 2 +- ledger/apply/application.go | 4 +- ledger/apply/application_test.go | 2 +- ledger/boxtxn_test.go | 12 +-- ledger/eval/appcow.go | 14 +--- ledger/eval/cow.go | 2 +- ledger/ledgercore/error.go | 15 ---- 11 files changed, 88 insertions(+), 93 deletions(-) diff --git a/config/consensus.go b/config/consensus.go index c3739a5903..e3bf4d527f 100644 --- a/config/consensus.go +++ b/config/consensus.go @@ -488,6 +488,9 @@ type ConsensusParams struct { // returning false, if pubkey is not on the curve. EnablePrecheckECDSACurve bool + // EnableBareBudgetError specifies that I/O budget overruns should not be considered EvalError + EnableBareBudgetError bool + // StateProofUseTrackerVerification specifies whether the node will use data from state proof verification tracker // in order to verify state proofs. StateProofUseTrackerVerification bool @@ -1262,6 +1265,7 @@ func initConsensusProtocols() { vFuture.LogicSigVersion = 9 // When moving this to a release, put a new higher LogicSigVersion here vFuture.EnablePrecheckECDSACurve = true + vFuture.EnableBareBudgetError = true vFuture.StateProofUseTrackerVerification = true vFuture.EnableCatchpointsWithSPContexts = true diff --git a/data/transactions/logic/backwardCompat_test.go b/data/transactions/logic/backwardCompat_test.go index df6bd68210..e8353e5d27 100644 --- a/data/transactions/logic/backwardCompat_test.go +++ b/data/transactions/logic/backwardCompat_test.go @@ -284,7 +284,7 @@ func TestBackwardCompatTEALv1(t *testing.T) { ep.TxnGroup[0].Lsig.Logic = program ep.TxnGroup[0].Lsig.Args = [][]byte{data[:], sig[:], pk[:], tx.Sender[:], tx.Note} - // ensure v1 program runs well on latest TEAL evaluator + // ensure v1 program runs well on latest evaluator require.Equal(t, uint8(1), program[0]) // Cost should stay exactly 2140 @@ -315,7 +315,7 @@ func TestBackwardCompatTEALv1(t *testing.T) { ep2.Proto.LogicSigMaxCost = 2308 testLogicBytes(t, opsV2.Program, ep2) - // ensure v0 program runs well on latest TEAL evaluator + // ensure v0 program runs well on latest evaluator ep, tx, _ = makeSampleEnv() program[0] = 0 sig = c.Sign(Msg{ diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index f40f60fce5..f05fa2cc76 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -678,33 +678,46 @@ func (st StackType) Typed() bool { return false } -// PanicError wraps a recover() catching a panic() -type PanicError struct { +// panicError wraps a recover() catching a panic() +type panicError struct { PanicValue interface{} StackTrace string } -func (pe PanicError) Error() string { +func (pe panicError) Error() string { return fmt.Sprintf("panic in TEAL Eval: %v\n%s", pe.PanicValue, pe.StackTrace) } var errLogicSigNotSupported = errors.New("LogicSig not supported") var errTooManyArgs = errors.New("LogicSig has too many arguments") -// ClearStateBudgetError allows evaluation to signal that the caller should -// reject the transaction. Normally, an error in evaluation would not cause a -// ClearState txn to fail. However, callers fail a txn for ClearStateBudgetError -// because the transaction has not provided enough budget to let ClearState do -// its job. -type ClearStateBudgetError struct { - offered int +// EvalError indicates AVM evaluation failure +type EvalError struct { + Err error + details string + groupIndex int + logicsig bool } -func (e ClearStateBudgetError) Error() string { - return fmt.Sprintf("Attempted ClearState execution with low OpcodeBudget %d", e.offered) +// Error satisfies builtin interface `error` +func (err EvalError) Error() string { + var msg string + if err.logicsig { + msg = fmt.Sprintf("rejected by logic err=%v", err.Err) + } else { + msg = fmt.Sprintf("logic eval error: %v", err.Err) + } + if err.details == "" { + return msg + } + return msg + ". Details: " + err.details } -// EvalContract executes stateful TEAL program as the gi'th transaction in params +func (err EvalError) Unwrap() error { + return err.Err +} + +// EvalContract executes stateful program as the gi'th transaction in params func EvalContract(program []byte, gi int, aid basics.AppIndex, params *EvalParams) (bool, *EvalContext, error) { if params.Ledger == nil { return false, nil, errors.New("no ledger in contract eval") @@ -725,7 +738,7 @@ func EvalContract(program []byte, gi int, aid basics.AppIndex, params *EvalParam if cx.Proto.IsolateClearState && cx.txn.Txn.OnCompletion == transactions.ClearStateOC { if cx.PooledApplicationBudget != nil && *cx.PooledApplicationBudget < cx.Proto.MaxAppProgramCost { - return false, nil, ClearStateBudgetError{*cx.PooledApplicationBudget} + return false, nil, fmt.Errorf("Attempted ClearState execution with low OpcodeBudget %d", *cx.PooledApplicationBudget) } } @@ -766,7 +779,11 @@ func EvalContract(program []byte, gi int, aid basics.AppIndex, params *EvalParam used = basics.AddSaturate(used, size) if used > cx.ioBudget { - return false, nil, fmt.Errorf("box read budget (%d) exceeded", cx.ioBudget) + err = fmt.Errorf("box read budget (%d) exceeded", cx.ioBudget) + if !cx.Proto.EnableBareBudgetError { + err = EvalError{err, "", gi, false} + } + return false, nil, err } } cx.readBudgetChecked = true @@ -776,6 +793,11 @@ func EvalContract(program []byte, gi int, aid basics.AppIndex, params *EvalParam fmt.Fprintf(cx.Trace, "--- enter %d %s %v\n", aid, cx.txn.Txn.OnCompletion, cx.txn.Txn.ApplicationArgs) } pass, err := eval(program, &cx) + if err != nil { + pc, det := cx.pcDetails() + details := fmt.Sprintf("pc=%d, opcodes=%s", pc, det) + err = EvalError{err, details, gi, false} + } if cx.Trace != nil && cx.caller != nil { fmt.Fprintf(cx.Trace, "--- exit %d accept=%t\n", aid, pass) @@ -798,7 +820,7 @@ func EvalApp(program []byte, gi int, aid basics.AppIndex, params *EvalParams) (b // EvalSignatureFull evaluates the logicsig of the ith transaction in params. // A program passes successfully if it finishes with one int element on the stack that is non-zero. // It returns EvalContext suitable for obtaining additional info about the execution. -func EvalSignatureFull(gi int, params *EvalParams) (pass bool, pcx *EvalContext, err error) { +func EvalSignatureFull(gi int, params *EvalParams) (bool, *EvalContext, error) { if params.SigLedger == nil { return false, nil, errors.New("no sig ledger in signature eval") } @@ -808,14 +830,21 @@ func EvalSignatureFull(gi int, params *EvalParams) (pass bool, pcx *EvalContext, groupIndex: gi, txn: ¶ms.TxnGroup[gi], } - pass, err = eval(cx.txn.Lsig.Logic, &cx) + pass, err := eval(cx.txn.Lsig.Logic, &cx) + + if err != nil { + pc, det := cx.pcDetails() + details := fmt.Sprintf("pc=%d, opcodes=%s", pc, det) + err = EvalError{err, details, gi, true} + } + return pass, &cx, err } // EvalSignature evaluates the logicsig of the ith transaction in params. // A program passes successfully if it finishes with one int element on the stack that is non-zero. -func EvalSignature(gi int, params *EvalParams) (pass bool, err error) { - pass, _, err = EvalSignatureFull(gi, params) +func EvalSignature(gi int, params *EvalParams) (bool, error) { + pass, _, err := EvalSignatureFull(gi, params) return pass, err } @@ -831,7 +860,7 @@ func eval(program []byte, cx *EvalContext) (pass bool, err error) { if cx.Trace != nil { errstr += cx.Trace.String() } - err = PanicError{x, errstr} + err = panicError{x, errstr} cx.EvalParams.log().Errorf("recovered panic in Eval: %v", err) } }() @@ -941,7 +970,7 @@ func check(program []byte, params *EvalParams, mode RunMode) (err error) { if params.Trace != nil { errstr += params.Trace.String() } - err = PanicError{x, errstr} + err = panicError{x, errstr} params.log().Errorf("recovered panic in Check: %s", err) } }() @@ -5522,8 +5551,8 @@ func opBlock(cx *EvalContext) error { } } -// PcDetails return PC and disassembled instructions at PC up to 2 opcodes back -func (cx *EvalContext) PcDetails() (pc int, dis string) { +// pcDetails return PC and disassembled instructions at PC up to 2 opcodes back +func (cx *EvalContext) pcDetails() (pc int, dis string) { const maxNumAdditionalOpcodes = 2 text, ds, err := disassembleInstrumented(cx.program, nil) if err != nil { @@ -5547,7 +5576,7 @@ func (cx *EvalContext) PcDetails() (pc int, dis string) { break } } - return cx.pc, dis + return cx.pc, strings.ReplaceAll(strings.TrimSuffix(dis, "\n"), "\n", "; ") } func base64Decode(encoded []byte, encoding *base64.Encoding) ([]byte, error) { diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index 29231d3448..42bd615444 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -2961,7 +2961,7 @@ func isNotPanic(t *testing.T, err error) { if err == nil { return } - if pe, ok := err.(PanicError); ok { + if pe, ok := err.(panicError); ok { t.Error(pe) } } @@ -3188,14 +3188,10 @@ func TestPanic(t *testing.T) { //nolint:paralleltest // Uses withPanicOpcode params := defaultEvalParams() params.TxnGroup[0].Lsig.Logic = ops.Program err := CheckSignature(0, params) - require.Error(t, err) - if pe, ok := err.(PanicError); ok { - require.Equal(t, panicString, pe.PanicValue) - pes := pe.Error() - require.True(t, strings.Contains(pes, "panic")) - } else { - t.Errorf("expected PanicError object but got %T %#v", err, err) - } + var pe panicError + require.ErrorAs(t, err, &pe) + require.Equal(t, panicString, pe.PanicValue) + require.ErrorContains(t, pe, "panic") var txn transactions.SignedTxn txn.Lsig.Logic = ops.Program @@ -3206,13 +3202,9 @@ func TestPanic(t *testing.T) { //nolint:paralleltest // Uses withPanicOpcode t.Log(params.Trace.String()) } require.False(t, pass) - if pe, ok := err.(PanicError); ok { - require.Equal(t, panicString, pe.PanicValue) - pes := pe.Error() - require.True(t, strings.Contains(pes, "panic")) - } else { - t.Errorf("expected PanicError object but got %T %#v", err, err) - } + require.ErrorAs(t, err, &pe) + require.Equal(t, panicString, pe.PanicValue) + require.ErrorContains(t, pe, "panic") if v >= appsEnabledVersion { txn = transactions.SignedTxn{ @@ -3224,13 +3216,9 @@ func TestPanic(t *testing.T) { //nolint:paralleltest // Uses withPanicOpcode params.Ledger = NewLedger(nil) pass, err = EvalApp(ops.Program, 0, 1, params) require.False(t, pass) - if pe, ok := err.(PanicError); ok { - require.Equal(t, panicString, pe.PanicValue) - pes := pe.Error() - require.True(t, strings.Contains(pes, "panic")) - } else { - t.Errorf("expected PanicError object but got %T %#v", err, err) - } + require.ErrorAs(t, err, &pe) + require.Equal(t, panicString, pe.PanicValue) + require.ErrorContains(t, pe, "panic") } }) }) @@ -5150,9 +5138,9 @@ func TestPcDetails(t *testing.T) { pc int det string }{ - {"int 1; int 2; -", 5, "pushint 1\npushint 2\n-\n"}, - {"int 1; err", 3, "pushint 1\nerr\n"}, - {"int 1; dup; int 2; -; +", 6, "dup\npushint 2\n-\n"}, + {"int 1; int 2; -", 5, "pushint 1; pushint 2; -"}, + {"int 1; err", 3, "pushint 1; err"}, + {"int 1; dup; int 2; -; +", 6, "dup; pushint 2; -"}, {"b end; end:", 4, ""}, } for i, test := range tests { @@ -5170,7 +5158,7 @@ func TestPcDetails(t *testing.T) { assert.Equal(t, test.pc, cx.pc, ep.Trace.String()) - pc, det := cx.PcDetails() + pc, det := cx.pcDetails() assert.Equal(t, test.pc, pc) assert.Equal(t, test.det, det) }) @@ -5789,8 +5777,7 @@ func TestOpJSONRef(t *testing.T) { pass, _, err := EvalContract(ops.Program, 0, 888, ep) require.False(t, pass) - require.Error(t, err) - require.EqualError(t, err, s.error) + require.ErrorContains(t, err, s.error) // reset pooled budget for new "app call" *ep.PooledApplicationBudget = ep.Proto.MaxAppProgramCost diff --git a/data/transactions/verify/txn.go b/data/transactions/verify/txn.go index 1d36055210..7c75ffd9ec 100644 --- a/data/transactions/verify/txn.go +++ b/data/transactions/verify/txn.go @@ -450,7 +450,7 @@ func logicSigVerify(txn *transactions.SignedTxn, groupIndex int, groupCtx *Group pass, cx, err := logic.EvalSignatureFull(groupIndex, &ep) if err != nil { logicErrTotal.Inc(nil) - return LogicSigError{groupIndex, fmt.Errorf("transaction %v: rejected by logic err=%w", txn.ID(), err)} + return LogicSigError{groupIndex, fmt.Errorf("transaction %v: %w", txn.ID(), err)} } if !pass { logicRejTotal.Inc(nil) diff --git a/ledger/apply/application.go b/ledger/apply/application.go index 3d03349ebe..a887309a45 100644 --- a/ledger/apply/application.go +++ b/ledger/apply/application.go @@ -413,8 +413,8 @@ func ApplicationCall(ac transactions.ApplicationCallTxnFields, header transactio if exists { pass, evalDelta, err := balances.StatefulEval(gi, evalParams, appIdx, params.ClearStateProgram) if err != nil { - // Fail on non-logic eval errors and ignore LogicEvalError errors - if _, ok := err.(ledgercore.LogicEvalError); !ok { + // ClearStateProgram evaluation can't make the txn fail. + if _, ok := err.(logic.EvalError); !ok { return err } } diff --git a/ledger/apply/application_test.go b/ledger/apply/application_test.go index bc8eb74ae9..54f7941081 100644 --- a/ledger/apply/application_test.go +++ b/ledger/apply/application_test.go @@ -895,7 +895,7 @@ func TestAppCallClearState(t *testing.T) { // one to opt out, one deallocate, no error from ApplicationCall b.pass = true b.delta = transactions.EvalDelta{GlobalDelta: nil} - b.err = ledgercore.LogicEvalError{Err: fmt.Errorf("test error")} + b.err = logic.EvalError{Err: fmt.Errorf("test error")} err = ApplicationCall(ac, h, b, ad, 0, &ep, txnCounter) a.NoError(err) a.Equal(1, b.put) diff --git a/ledger/boxtxn_test.go b/ledger/boxtxn_test.go index fc6ec0cc31..b892a21f08 100644 --- a/ledger/boxtxn_test.go +++ b/ledger/boxtxn_test.go @@ -171,28 +171,28 @@ func TestBoxCreate(t *testing.T) { } dl.txn(adam.Args("check", "adam", "\x00\x00")) - dl.txgroup("box_create\nassert", adam.Noted("one"), adam.Noted("two")) + dl.txgroup("box_create; assert", adam.Noted("one"), adam.Noted("two")) bobo := call.Args("create", "bobo") dl.txn(bobo, fmt.Sprintf("invalid Box reference %#x", "bobo")) bobo.Boxes = []transactions.BoxRef{{Index: 0, Name: []byte("bobo")}} dl.txn(bobo) - dl.txgroup("box_create\nassert", bobo.Noted("one"), bobo.Noted("two")) + dl.txgroup("box_create; assert", bobo.Noted("one"), bobo.Noted("two")) dl.beginBlock() chaz := call.Args("create", "chaz") chaz.Boxes = []transactions.BoxRef{{Index: 0, Name: []byte("chaz")}} dl.txn(chaz) - dl.txn(chaz.Noted("again"), "box_create\nassert") + dl.txn(chaz.Noted("again"), "box_create; assert") dl.endBlock() // new block - dl.txn(chaz.Noted("again"), "box_create\nassert") + dl.txn(chaz.Noted("again"), "box_create; assert") dogg := call.Args("create", "dogg") dogg.Boxes = []transactions.BoxRef{{Index: 0, Name: []byte("dogg")}} dl.txn(dogg, "below min") dl.txn(chaz.Args("delete", "chaz")) - dl.txn(chaz.Args("delete", "chaz").Noted("again"), "box_del\nassert") + dl.txn(chaz.Args("delete", "chaz").Noted("again"), "box_del; assert") dl.txn(dogg) dl.txn(bobo.Args("delete", "bobo")) @@ -231,7 +231,7 @@ func TestBoxRecreate(t *testing.T) { create := call.Args("create", "adam", "\x04") // box value size is 4 bytes recreate := call.Args("recreate", "adam", "\x04") - dl.txn(recreate, "box_create\n!\nassert") + dl.txn(recreate, "box_create; !; assert") dl.txn(create) dl.txn(recreate) dl.txn(call.Args("set", "adam", "\x01\x02\x03\x04")) diff --git a/ledger/eval/appcow.go b/ledger/eval/appcow.go index 0e22704521..14815e8e2c 100644 --- a/ledger/eval/appcow.go +++ b/ledger/eval/appcow.go @@ -467,19 +467,9 @@ func (cb *roundCowState) StatefulEval(gi int, params *logic.EvalParams, aidx bas params.Ledger = calf params.SigLedger = calf - // Eval the program - pass, cx, err := logic.EvalContract(program, gi, aidx, params) + pass, err = logic.EvalApp(program, gi, aidx, params) if err != nil { - var details string - if cx != nil { - pc, det := cx.PcDetails() - details = fmt.Sprintf("pc=%d, opcodes=%s", pc, det) - } - // Don't wrap ClearStateBudgetError, so it will be taken seriously - if _, ok := err.(logic.ClearStateBudgetError); ok { - return false, transactions.EvalDelta{}, err - } - return false, transactions.EvalDelta{}, ledgercore.LogicEvalError{Err: err, Details: details} + return false, transactions.EvalDelta{}, err } // If program passed, build our eval delta, and commit to state changes diff --git a/ledger/eval/cow.go b/ledger/eval/cow.go index 4a04eb83eb..c58f65fc51 100644 --- a/ledger/eval/cow.go +++ b/ledger/eval/cow.go @@ -80,7 +80,7 @@ type roundCowState struct { // storage deltas populated as side effects of AppCall transaction // 1. Opt-in/Close actions (see Allocate/Deallocate) - // 2. Stateful TEAL evaluation (see setKey/delKey) + // 2. Application evaluation (see setKey/delKey) // must be incorporated into mods.accts before passing deltas forward sdeltas map[basics.Address]map[storagePtr]*storageDelta diff --git a/ledger/ledgercore/error.go b/ledger/ledgercore/error.go index b1dff71a4a..c98312a9a1 100644 --- a/ledger/ledgercore/error.go +++ b/ledger/ledgercore/error.go @@ -93,21 +93,6 @@ func (err ErrNoEntry) Error() string { return fmt.Sprintf("ledger does not have entry %d (latest %d, committed %d)", err.Round, err.Latest, err.Committed) } -// LogicEvalError indicates TEAL evaluation failure -type LogicEvalError struct { - Err error - Details string -} - -// Error satisfies builtin interface `error` -func (err LogicEvalError) Error() string { - msg := fmt.Sprintf("logic eval error: %v", err.Err) - if len(err.Details) > 0 { - msg = fmt.Sprintf("%s. Details: %s", msg, err.Details) - } - return msg -} - // ErrNonSequentialBlockEval provides feedback when the evaluator cannot be created for // stale/future rounds. type ErrNonSequentialBlockEval struct { From 1ff6da89165ea59516ec914af1553ff3ab52b5ba Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Wed, 26 Apr 2023 13:18:00 -0700 Subject: [PATCH 068/125] Simulate: Make optional signatures an opt-in feature (#5335) --- cmd/goal/clerk.go | 58 +- daemon/algod/api/algod.oas2.json | 23 +- daemon/algod/api/algod.oas3.yml | 37 +- .../api/server/v2/generated/data/routes.go | 348 ++-- .../v2/generated/experimental/routes.go | 333 ++-- .../api/server/v2/generated/model/types.go | 16 +- .../nonparticipating/private/routes.go | 344 ++-- .../nonparticipating/public/routes.go | 478 +++--- .../generated/participating/private/routes.go | 349 ++-- .../generated/participating/public/routes.go | 364 ++-- daemon/algod/api/server/v2/handlers.go | 19 +- .../algod/api/server/v2/test/handlers_test.go | 4 +- daemon/algod/api/server/v2/utils.go | 19 +- data/transactions/verify/txn.go | 56 +- data/transactions/verify/txnBatch.go | 6 +- ledger/simulation/simulation_eval_test.go | 1457 ++++++++--------- ledger/simulation/simulator.go | 58 +- ledger/simulation/simulator_test.go | 2 +- ledger/simulation/trace.go | 25 +- ledger/simulation/tracer.go | 3 - test/e2e-go/restAPI/restClient_test.go | 72 +- test/scripts/e2e_subs/e2e-app-simulate.sh | 84 +- 22 files changed, 2062 insertions(+), 2093 deletions(-) diff --git a/cmd/goal/clerk.go b/cmd/goal/clerk.go index 54584613b5..715468514e 100644 --- a/cmd/goal/clerk.go +++ b/cmd/goal/clerk.go @@ -44,30 +44,31 @@ import ( ) var ( - toAddress string - account string - amount uint64 - txFilename string - rejectsFilename string - closeToAddress string - noProgramOutput bool - writeSourceMap bool - signProgram bool - programSource string - argB64Strings []string - disassemble bool - verbose bool - progByteFile string - msigParams string - logicSigFile string - timeStamp int64 - protoVersion string - rekeyToAddress string - signerAddress string - rawOutput bool - requestFilename string - requestOutFilename string - liftLogLimits bool + toAddress string + account string + amount uint64 + txFilename string + rejectsFilename string + closeToAddress string + noProgramOutput bool + writeSourceMap bool + signProgram bool + programSource string + argB64Strings []string + disassemble bool + verbose bool + progByteFile string + msigParams string + logicSigFile string + timeStamp int64 + protoVersion string + rekeyToAddress string + signerAddress string + rawOutput bool + requestFilename string + requestOutFilename string + simulateAllowEmptySignatures bool + simulateAllowMoreLogging bool ) func init() { @@ -152,7 +153,8 @@ func init() { simulateCmd.Flags().StringVar(&requestFilename, "request", "", "Simulate request object to run. Mutually exclusive with --txfile") simulateCmd.Flags().StringVar(&requestOutFilename, "request-only-out", "", "Filename for writing simulate request object. If provided, the command will only write the request object and exit. No simulation will happen") simulateCmd.Flags().StringVarP(&outFilename, "result-out", "o", "", "Filename for writing simulation result") - simulateCmd.Flags().BoolVar(&liftLogLimits, "lift-log-limits", false, "Lift the limits on log opcode during simulation") + simulateCmd.Flags().BoolVar(&simulateAllowEmptySignatures, "allow-empty-signatures", false, "Allow transactions without signatures to be simulated as if they had correct signatures") + simulateCmd.Flags().BoolVar(&simulateAllowMoreLogging, "allow-more-logging", false, "Lift the limits on log opcode during simulation") } var clerkCmd = &cobra.Command{ @@ -1257,7 +1259,8 @@ var simulateCmd = &cobra.Command{ Txns: txgroup, }, }, - LiftLogLimits: liftLogLimits, + AllowEmptySignatures: simulateAllowEmptySignatures, + AllowMoreLogging: simulateAllowMoreLogging, } err := writeFile(requestOutFilename, protocol.EncodeJSON(simulateRequest), 0600) if err != nil { @@ -1278,7 +1281,8 @@ var simulateCmd = &cobra.Command{ Txns: txgroup, }, }, - LiftLogLimits: liftLogLimits, + AllowEmptySignatures: simulateAllowEmptySignatures, + AllowMoreLogging: simulateAllowMoreLogging, } simulateResponse, responseErr = client.SimulateTransactions(simulateRequest) } else { diff --git a/daemon/algod/api/algod.oas2.json b/daemon/algod/api/algod.oas2.json index 8f4584ea60..d83a18a990 100644 --- a/daemon/algod/api/algod.oas2.json +++ b/daemon/algod/api/algod.oas2.json @@ -3286,8 +3286,12 @@ "$ref": "#/definitions/SimulateRequestTransactionGroup" } }, - "lift-log-limits": { - "description": "The boolean flag that lifts the limit on log opcode during simulation.", + "allow-empty-signatures": { + "description": "Allow transactions without signatures to be simulated as if they had correct signatures.", + "type": "boolean" + }, + "allow-more-logging": { + "description": "Lifts limits on log opcode usage during simulation.", "type": "boolean" } } @@ -3550,10 +3554,6 @@ "txn-result": { "$ref": "#/definitions/PendingTransactionResponse" }, - "missing-signature": { - "description": "A boolean indicating whether this transaction is missing signatures", - "type": "boolean" - }, "app-budget-consumed": { "description": "Budget used during execution of an app call transaction. This value includes budged used by inner app calls spawned by this transaction.", "type": "integer" @@ -3648,6 +3648,10 @@ "description": "The set of parameters and limits override during simulation. If this set of parameters is present, then evaluation parameters may differ from standard evaluation in certain ways.", "type": "object", "properties": { + "allow-empty-signatures": { + "description": "If true, transactions without signatures are allowed and simulated as if they were properly signed.", + "type": "boolean" + }, "max-log-calls": { "description": "The maximum log calls one can make during simulation", "type": "integer", @@ -4245,8 +4249,7 @@ "required": [ "version", "last-round", - "txn-groups", - "would-succeed" + "txn-groups" ], "properties": { "version": { @@ -4266,10 +4269,6 @@ }, "eval-overrides": { "$ref": "#/definitions/SimulationEvalOverrides" - }, - "would-succeed": { - "description": "Indicates whether the simulated transactions would have succeeded during an actual submission. If any transaction fails or is missing a signature, this will be false.", - "type": "boolean" } } } diff --git a/daemon/algod/api/algod.oas3.yml b/daemon/algod/api/algod.oas3.yml index 51650a769a..85899765ba 100644 --- a/daemon/algod/api/algod.oas3.yml +++ b/daemon/algod/api/algod.oas3.yml @@ -794,17 +794,12 @@ "version": { "description": "The version of this response object.", "type": "integer" - }, - "would-succeed": { - "description": "Indicates whether the simulated transactions would have succeeded during an actual submission. If any transaction fails or is missing a signature, this will be false.", - "type": "boolean" } }, "required": [ "last-round", "txn-groups", - "version", - "would-succeed" + "version" ], "type": "object" } @@ -1860,8 +1855,12 @@ "SimulateRequest": { "description": "Request type for simulation endpoint.", "properties": { - "lift-log-limits": { - "description": "The boolean flag that lifts the limit on log opcode during simulation.", + "allow-empty-signatures": { + "description": "Allow transactions without signatures to be simulated as if they had correct signatures.", + "type": "boolean" + }, + "allow-more-logging": { + "description": "Lifts limits on log opcode usage during simulation.", "type": "boolean" }, "txn-groups": { @@ -1942,10 +1941,6 @@ "description": "Budget used during execution of a logic sig transaction.", "type": "integer" }, - "missing-signature": { - "description": "A boolean indicating whether this transaction is missing signatures", - "type": "boolean" - }, "txn-result": { "$ref": "#/components/schemas/PendingTransactionResponse" } @@ -1958,6 +1953,10 @@ "SimulationEvalOverrides": { "description": "The set of parameters and limits override during simulation. If this set of parameters is present, then evaluation parameters may differ from standard evaluation in certain ways.", "properties": { + "allow-empty-signatures": { + "description": "If true, transactions without signatures are allowed and simulated as if they were properly signed.", + "type": "boolean" + }, "max-log-calls": { "description": "The maximum log calls one can make during simulation", "type": "integer", @@ -5960,17 +5959,12 @@ "version": { "description": "The version of this response object.", "type": "integer" - }, - "would-succeed": { - "description": "Indicates whether the simulated transactions would have succeeded during an actual submission. If any transaction fails or is missing a signature, this will be false.", - "type": "boolean" } }, "required": [ "last-round", "txn-groups", - "version", - "would-succeed" + "version" ], "type": "object" } @@ -5995,17 +5989,12 @@ "version": { "description": "The version of this response object.", "type": "integer" - }, - "would-succeed": { - "description": "Indicates whether the simulated transactions would have succeeded during an actual submission. If any transaction fails or is missing a signature, this will be false.", - "type": "boolean" } }, "required": [ "last-round", "txn-groups", - "version", - "would-succeed" + "version" ], "type": "object" } diff --git a/daemon/algod/api/server/v2/generated/data/routes.go b/daemon/algod/api/server/v2/generated/data/routes.go index 5a9aff2756..500333a963 100644 --- a/daemon/algod/api/server/v2/generated/data/routes.go +++ b/daemon/algod/api/server/v2/generated/data/routes.go @@ -146,180 +146,180 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL var swaggerSpec = []string{ "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", + "zQAA", } // 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 e254b9a4a5..48553e35da 100644 --- a/daemon/algod/api/server/v2/generated/experimental/routes.go +++ b/daemon/algod/api/server/v2/generated/experimental/routes.go @@ -75,173 +75,172 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/generated/model/types.go b/daemon/algod/api/server/v2/generated/model/types.go index 67309448fb..e59a4680c4 100644 --- a/daemon/algod/api/server/v2/generated/model/types.go +++ b/daemon/algod/api/server/v2/generated/model/types.go @@ -610,8 +610,11 @@ type PendingTransactionResponse struct { // SimulateRequest Request type for simulation endpoint. type SimulateRequest struct { - // LiftLogLimits The boolean flag that lifts the limit on log opcode during simulation. - LiftLogLimits *bool `json:"lift-log-limits,omitempty"` + // AllowEmptySignatures Allow transactions without signatures to be simulated as if they had correct signatures. + AllowEmptySignatures *bool `json:"allow-empty-signatures,omitempty"` + + // AllowMoreLogging Lifts limits on log opcode usage during simulation. + AllowMoreLogging *bool `json:"allow-more-logging,omitempty"` // TxnGroups The transaction groups to simulate. TxnGroups []SimulateRequestTransactionGroup `json:"txn-groups"` @@ -649,15 +652,15 @@ type SimulateTransactionResult struct { // LogicSigBudgetConsumed Budget used during execution of a logic sig transaction. LogicSigBudgetConsumed *uint64 `json:"logic-sig-budget-consumed,omitempty"` - // MissingSignature A boolean indicating whether this transaction is missing signatures - MissingSignature *bool `json:"missing-signature,omitempty"` - // TxnResult Details about a pending transaction. If the transaction was recently confirmed, includes confirmation details like the round and reward details. TxnResult PendingTransactionResponse `json:"txn-result"` } // SimulationEvalOverrides The set of parameters and limits override during simulation. If this set of parameters is present, then evaluation parameters may differ from standard evaluation in certain ways. type SimulationEvalOverrides struct { + // AllowEmptySignatures If true, transactions without signatures are allowed and simulated as if they were properly signed. + AllowEmptySignatures *bool `json:"allow-empty-signatures,omitempty"` + // MaxLogCalls The maximum log calls one can make during simulation MaxLogCalls *uint64 `json:"max-log-calls,omitempty"` @@ -1037,9 +1040,6 @@ type SimulateResponse struct { // Version The version of this response object. Version uint64 `json:"version"` - - // WouldSucceed Indicates whether the simulated transactions would have succeeded during an actual submission. If any transaction fails or is missing a signature, this will be false. - WouldSucceed bool `json:"would-succeed"` } // StateProofResponse Represents a state proof and its corresponding message 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 1adb0f8493..651284ea4e 100644 --- a/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go +++ b/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go @@ -130,178 +130,178 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", + "TwAAAP//xilWefLGAAA=", } // 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 58a67b3219..b351aa2e2f 100644 --- a/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go +++ b/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go @@ -609,246 +609,244 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "VUi2wnJ6Hz79/wAAAP//VlRZNeFZAQA=", + "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", } // 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 aca762aa83..c772f7ce3b 100644 --- a/daemon/algod/api/server/v2/generated/participating/private/routes.go +++ b/daemon/algod/api/server/v2/generated/participating/private/routes.go @@ -158,181 +158,180 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "h9sPStEAAA==", + "H4sIAAAAAAAC/+y9e3PcNrIo/lVQc06VH7+h5Feya1VtnZ9sJ1ndOI7LUrL3HNs3wZA9M1iRABcARzPx", + "1Xe/hQZAgiTI4UiKvanKX7aGeDQajUa/0P1ploqiFBy4VrOTT7OSSlqABol/0TQVFdcJy8xfGahUslIz", + "wWcn/htRWjK+ms1nzPxaUr2ezWecFtC0Mf3nMwn/qpiEbHaiZQXzmUrXUFAzsN6VpnU90jZZicQNcWqH", + "OHs1ux75QLNMglJ9KH/k+Y4wnuZVBkRLyhVNzSdFrpheE71mirjOhHEiOBCxJHrdakyWDPJMHflF/qsC", + "uQtW6SYfXtJ1A2IiRQ59OF+KYsE4eKigBqreEKIFyWCJjdZUEzODgdU31IIooDJdk6WQe0C1QITwAq+K", + "2cn7mQKegcTdSoFt8L9LCfAbJJrKFejZx3lscUsNMtGsiCztzGFfgqpyrQi2xTWu2AY4Mb2OyA+V0mQB", + "hHLy7tuX5OnTp8/NQgqqNWSOyAZX1cwersl2n53MMqrBf+7TGs1XQlKeJXX7d9++xPnP3QKntqJKQfyw", + "nJov5OzV0AJ8xwgJMa5hhfvQon7TI3Iomp8XsBQSJu6JbXynmxLO/0V3JaU6XZeCcR3ZF4Jfif0c5WFB", + "9zEeVgPQal8aTEkz6PtHyfOPnx7PHz+6/o/3p8n/uD+/eno9cfkv63H3YCDaMK2kBJ7ukpUEiqdlTXkf", + "H+8cPai1qPKMrOkGN58WyOpdX2L6Wta5oXll6ISlUpzmK6EIdWSUwZJWuSZ+YlLx3LApM5qjdsIUKaXY", + "sAyyueG+V2uWrklKlR0C25ErlueGBisF2RCtxVc3cpiuQ5QYuG6ED1zQvy8ymnXtwQRskRskaS4UJFrs", + "uZ78jUN5RsILpbmr1GGXFblYA8HJzQd72SLuuKHpPN8RjfuaEaoIJf5qmhO2JDtRkSvcnJxdYn+3GoO1", + "ghik4ea07lFzeIfQ10NGBHkLIXKgHJHnz10fZXzJVpUERa7WoNfuzpOgSsEVELH4J6TabPv/Ov/xDRGS", + "/ABK0RW8peklAZ6KDLIjcrYkXOiANBwtIQ5Nz6F1OLhil/w/lTA0UahVSdPL+I2es4JFVvUD3bKiKgiv", + "igVIs6X+CtGCSNCV5EMA2RH3kGJBt/1JL2TFU9z/ZtqWLGeojakypztEWEG3f3s0d+AoQvOclMAzxldE", + "b/mgHGfm3g9eIkXFswlijjZ7GlysqoSULRlkpB5lBBI3zT54GD8Mnkb4CsDxgwyCU8+yBxwO2wjNmNNt", + "vpCSriAgmSPyk2Nu+FWLS+A1oZPFDj+VEjZMVKruNAAjTj0ugXOhISklLFmExs4dOgyDsW0cBy6cDJQK", + "rinjkBnmjEALDZZZDcIUTDiu7/Rv8QVV8PWzoTu++Tpx95eiu+ujOz5pt7FRYo9k5Oo0X92BjUtWrf4T", + "9MNwbsVWif25t5FsdWFumyXL8Sb6p9k/j4ZKIRNoIcLfTYqtONWVhJMP/KH5iyTkXFOeUZmZXwr70w9V", + "rtk5W5mfcvvTa7Fi6TlbDSCzhjWqcGG3wv5jxouzY72N6hWvhbisynBBaUtxXezI2auhTbZjHkqYp7W2", + "GyoeF1uvjBzaQ2/rjRwAchB3JTUNL2EnwUBL0yX+s10iPdGl/M38U5a56a3LZQy1ho7dlYzmA2dWOC3L", + "nKXUIPGd+2y+GiYAVpGgTYtjvFBPPgUgllKUIDWzg9KyTHKR0jxRmmoc6T8lLGcns/84buwvx7a7Og4m", + "f216nWMnI7JaMSihZXnAGG+N6KNGmIVh0PgJ2YRleyg0MW430ZASMyw4hw3l+qhRWVr8oD7A791MDb6t", + "tGPx3VHBBhFObMMFKCsB24b3FAlQTxCtBNGKAukqF4v6h/unZdlgEL+flqXFB0qPwFAwgy1TWj3A5dPm", + "JIXznL06It+FY6MoLni+M5eDFTXM3bB0t5a7xWrbkltDM+I9RXA7hTwyW+PRYMT8u6A4VCvWIjdSz15a", + "MY3/7tqGZGZ+n9T5j0FiIW6HiQsVLYc5q+PgL4Fyc79DOX3CceaeI3La7XszsjGjxAnmRrQyup923BE8", + "1ii8krS0ALov9i5lHJU028jCektuOpHRRWEOznBAawjVjc/a3vMQhQRJoQPDi1ykl3+nan0HZ37hx+of", + "P5yGrIFmIMmaqvXRLCZlhMerGW3KETMNUcEni2Cqo3qJd7W8PUvLqKbB0hy8cbHEoh77IdMDGdFdfsT/", + "0JyYz+ZsG9Zvhz0iF8jAlD3OzsmQGW3fKgh2JtMArRCCFFbBJ0brPgjKl83k8X2atEffWJuC2yG3CNwh", + "sb3zY/BCbGMwvBDb3hEQW1B3QR9mHBQjNRRqAnyvHGQC99+hj0pJd30k49hTkGwWaERXhaeBhze+maUx", + "zp4uhLwZ9+mwFU4akzOhZtSA+c47SMKmVZk4UoyYrWyDzkCNl2+caXSHj2GshYVzTX8HLCgz6l1goT3Q", + "XWNBFCXL4Q5Ifx1l+guq4OkTcv73068eP/nlyVdfG5IspVhJWpDFToMi951uRpTe5fCgvzLUjqpcx0f/", + "+pk3VLbHjY2jRCVTKGjZH8oaQK0IZJsR066PtTaacdU1gFMO5wUYTm7RTqxt34D2iikjYRWLO9mMIYRl", + "zSwZcZBksJeYDl1eM80uXKLcyeouVFmQUsiIfQ2PmBapyJMNSMVExJvy1rUgroUXb8vu7xZackUVMXOj", + "6bfiKFBEKEtv+XS+b4e+2PIGN6Oc3643sjo375R9aSPfWxIVKUEmestJBotq1dKEllIUhJIMO+Id/R1o", + "FAUuWAHnmhblj8vl3aiKAgeKqGysAGVmIraFkesVpILbSIg92pkbdQp6uojxJjo9DIDDyPmOp2hnvItj", + "O6y4Foyj00PteBposQbGHLJViyxvr60OocNOdU9FwDHoeI2f0dDxCnJN71yi604Qg/2lJ20LLMlMQ9yt", + "12y11oHI/VYKsbx7GGOzxADFD1ZhyU2fvtryRmTmoOlK3YF40gzWnH6zp+GZpwtRaUIJFxmgjalSccFl", + "IJYBnajo+9WhLKTXVgdZgCGklFZmtVVJ0LPZ46VNx4SmlnoTRI0a8OvUDjnbyk5n/eS5BJrtyAKAE7Fw", + "zhPn1sFFUnTLan/1O7EpcpZacJVSpKAUZIkz2uwFzbezbFWP4AkBR4DrWYgSZEnlrYG93OyF8xJ2CQYR", + "KHL/+5/Vgy8Arxaa5nsQi21i6K1VYOch60M9bfoxgutOHpIdlUA8zzX6tmEQOWgYQuFBOBncvy5EvV28", + "PVo2INFX9btSvJ/kdgRUg/o70/ttoa3KgdA4p/oZ6cdsGKdceKEjNlhOlU72sWXTqKWfmhUEnDDGiXHg", + "AaHkNVXa+lcZz9AsZK8TnMcKKGaKYYAHRXQz8s9eOu+PnZp7kKtK1aK6qspSSA1ZbA0ctiNzvYFtPZdY", + "BmPX+oAWpFKwb+QhLAXjO2TZlVgEUV27IVwAQn9xaKw39/wuisoWEA0ixgA5960C7IbhQQOAMNUg2hIO", + "Ux3KqWOS5jOlRVkabqGTitf9htB0bluf6p+atn3iorq5tzMBCqOSXHsH+ZXFrA0MW1NFHBykoJdG9kAT", + "gXUE92E2hzFRjKeQjFE+qj+mVXgE9h7SqlxJmkGSQU53/UF/sp+J/Tw2AO54owoKDYmN8IlvekPJPqBi", + "ZGiB46mY8EjwC0nNETSaR0MgrveekTPAsWPMydHRvXoonCu6RX48XLbd6siIeBtuhDY77ugBQXYcfQrA", + "A3ioh745KrBz0uhl3Sn+G5SboJYjDp9kB2poCc34By1gwL7ogqeD89Jh7x0OHGWbg2xsDx8ZOrIDxs63", + "VGqWshJ1ne9hd+eqX3eCqAuOZKApyyEjwQerBpZhf2JjU7pj3kwVnGSX6oPfM0xFlpMzhSJPG/hL2KHO", + "/dYGPV4EoZJ3oMtGRjX3E+UEAfWhVEYED5vAlqY63xlBTa9hR65AAlHVomBa22DmtqqrRZmEA0Rt/iMz", + "OgeXDRj0OzDF43aOQwXL62/FfGZ1gnH4LjqKQQsdThcohcgnWI96yIhCMCkWgpTC7DpzcdU+stZTUgtI", + "x7TRu1lf//dUC824AvLfoiIp5ahyVRpqmUZIFBRQgDQzGBGsntNFPTQYghwKsJokfnn4sLvwhw/dnjNF", + "lnDlHyOYhl10PHyIdpy3QunW4boDW6E5bmeR6wOdIebic1pIl6fs97q7kafs5NvO4LUHxZwppRzhmuXf", + "mgF0TuZ2ytpDGpkWcYDjTvJzBEPH1o37fs6KKqf6Ljw6sKF5IjYgJctgLyd3EzPBv9nQ/Me62x6dromQ", + "YkUBGaMa8h0pJaRgI9eNqKbqsY+IjWlL15SvUEKXolq5oCo7DnLYSllbiKx4b4ioFKO3PFlJUZUxjusC", + "af3jBSO/ADU6VLAj2NlqDFe0ns+9V5lyFfqdC7b5OzPmkMdmPhtUMQ1SN42KaZHTfoExgfu2BKwAP83E", + "E+36iDojbPTxFW6LoV6zub+PjbwZOgZlf+IgzKv5OBTpZfTbfHcHUoYdiEgoJSi8E0K7kLJfxTJ8beUu", + "DbVTGoq+6dx2/WXg+L0bVNAEzxmHpBAcdtEHxozDD/gxepzwXhrojBLCUN+u0N+CvwNWe54p1Hhb/OJu", + "Byf0bR3ieAeb3x234zUJ35mhVRDyklCS5gxthoIrLatUf+AUrRLBYYuEgnj1a9hO9dI3iRvGInYrN9QH", + "TjEMqLZVRN3XS4go5t8CeHOVqlYrULoj3S4BPnDXinFScaZxrsLsV2I3rASJ8RhHtmVBd2RJczSr/QZS", + "kEWl2/IevnVRmuW5c+GYaYhYfuBUkxyMyvoD4xdbHM67QD3NcNBXQl7WWIhfLyvgoJhK4iEr39mvGE3o", + "lr92kYX4Ntl+tkZ/M37zIGaHRovmve3/uf9fJ+9Pk/+hyW+Pkuf/3/HHT8+uHzzs/fjk+m9/+7/tn55e", + "/+3Bf/1nbKc87LGXGA7ys1dOFzp7hQJvY/Xvwf7ZLL4F40mUyELfdoe2yH18degI6EHbHKLX8IHrLTeE", + "tKE5y4wgchNy6LK43lm0p6NDNa2N6Jg//FoPFCNvwWVIhMl0WOONr/F+lFf8zRO6odwzJjwvy4rbrfTi", + "nw3p99E2Yjmv37XZlBcnBB89rakPFXN/Pvnq69m8eaxUf5/NZ+7rxwgls2wbe5KWwTamHbgDggfjniIl", + "3SnQce6BsEcDi6w3Pxy2AKNWqjUrPz+nUJot4hzOB0o7K8OWn3EbwWzODzq1ds5WLpafH24tATIo9Tr2", + "FL4lKWCrZjcBOoEGpRQb4HPCjuCoq+VnRmFxIU450CU+yUb1R0wRx+tzYAnNU0WA9XAhk1TpGP2gcOu4", + "9fV85i5/defyuBs4Bld3ztqD5f/Wgtz77psLcuwYprpnX0faoYP3bBFdzj3ZaIWgGG5mE4DY56Ef+Af+", + "CpaMM/P95APPqKbHC6pYqo4rBfIFzSlP4WglyIl/BfKKavqB9yStwRw9wfsbUlaLnKXkMpSIG/K0eRf6", + "I3z48J7mK/Hhw8eeN74vv7qpovzFTpBcMb0WlU7cq/FEwhWVMW+Hql8N48g2LcTYrHPixras2L1Kd+PH", + "eR4tS9V9PdhfflnmZvkBGSr3Ns5sGVFaSC+LGAHFQoP7+0a4i0HSK6/YVwoU+bWg5XvG9UeSfKgePXoK", + "pPWc7ld35Rua3JUwWb0ffN3Y1epx4Vavga2WNCnpKuZU+fDhvQZa4u6jvFygkp3nBLu1nvH5MGUcqlmA", + "x8fwBlg4Dn6ShIs7t718hqD4EvATbiG2MeJG4+q96X4FD/tuvF2dx4G9Xar0OjFnO7oqZUjc70ydOGRl", + "hCzvf1dshTGOLsfKAki6hvTSJb+AotS7eau7D/FwgqZnHUzZtCj2WQ4+zEeT9AJIVWbUieKU77ovpBVo", + "7YMs38El7C5E867/kCfR7Re6auigIqUG0qUh1vDYujG6m+/iiPBVYln6h6744smTxUlNF77P8EG2Iu8d", + "HOIYUbRekA4hgsoIIizxD6DgBgs1492K9GPLM1rGwt58kRQpnvcT16RRnlzIT7gaNPva7wVgjiVxpciC", + "GrlduPRA9hVqwMUqRVcwICGHXoGJbz1bngQcZN+9F73pxLJ7ofXumyjItnFi1hylFDBfDKmgMtMJ9PIz", + "WceTM41j1j+HsEWOYlIdEWeZDpUt74xNYzYEWpyAQfJG4PBgtDESSjZrqnzmIkzw5M/yJBngd3xVPZZL", + "4yyIUQqyONWZMjzP7Z7TnnbpMmr4NBo+d0aoWk7Ig2EkfAyLjm2H4CgAZZDDyi7cNvaE0rzwbjbIwPHj", + "cpkzDiSJhTtRpUTKbOqp5ppxc4CRjx8SYk3AZPIIMTIOwEaHKg5M3ojwbPLVIUBy90Kd+rHRFRv8DfHH", + "NDYA2Ig8ojQsnA24VVLPAaiLkavvr06kJg5DGJ8Tw+Y2NDdszml8zSC9lA4otnYSODiX/oMhcXbEAm8v", + "loPWZK+im6wmlJk80HGBbgTihdgm9jVdVOJdbBeG3qMx0fi2L3YwbfKMe4osxBbDRPBqsTG4e2AZhsOD", + "EWj4W6aQXrHf0G1ugRmbdlyailGhQpJx5ryaXIbEiSlTD0gwQ+RyP8iHcSMAOsaOJrmsU373Kqlt8aR/", + "mTe32rzJ8+Sfm8SO/9ARiu7SAP76Vpg6g8XbrsQStVO0ox3ayTsCETJG9IZN9J00fVeQghxQKUhaQlRy", + "GXPdGd0G8MY5990C4wWmCKF89yAIoZGwYkpDY0T3jvovYZ6kmJlMiOXw6nQpl2Z974Sorymb+gY7tpb5", + "2VeAMahLJpVO0AMRXYJp9K1Cpfpb0zQuK7WDdGweT5bFeQNOewm7JGN5FadXN+/3r8y0b2qWqKoF8lvG", + "bcTEAvPORkP3Rqa20Z2jC35tF/ya3tl6p50G09RMLA25tOf4g5yLDucdYwcRAowRR3/XBlE6wiCDJ5d9", + "7hjITfZw4pPLozHra+8wZX7svWEj/uHn0B1lR4quJTAYjK6CoZvIiCVMB2lb+28hB84ALUuWbTu2UDvq", + "oMZMDzJ4+GRXHSzg7rrB9mAgsHvGnmNIUO28Zo2AbxPwttKKHE3CzEU7+1jIEMKpmPLp4/uIqp9r7cPV", + "BdD8e9j9bNricmbX89ntTKcxXLsR9+D6bb29UTyja96a0lqekANRTstSig3NE2dgHiJNKTaONLG5t0d/", + "ZlYXN2NefHP6+q0D/3o+S3OgMqlFhcFVYbvyD7Mqm0Jt4ID49NRG5/MyuxUlg82v8z6FRumrNbg8v4E0", + "2ktI2DgcgqPojNTLeITQXpOz843YJY74SKCsXSSN+c56SNpeEbqhLPd2Mw/tQDQPLm5aVssoVwgHuLV3", + "JXCSJXfKbnqnO346Guraw5PCuUYyERc22bYigndd6Bh0uyud172gmE7QWkX6zIlXBVoSEpWzNG5j5Qtl", + "iINb35lpTLDxgDBqRqzYgCuWVywYyzSbkjCkA2QwRxSZKpqzpMHdQrhCKhVn/6qAsAy4Np8knsrOQcX8", + "jc7a3r9OjezQn8sNbC30zfC3kTHCVJrdGw+BGBcwQk9dD9xXtcrsF1pbpMwPgUviAId/OGPvShxx1jv6", + "cNRsgxfXbY9bWPekz/8MYdgE2PuLrnjl1eX0HJgjWkSFqWQpxW8Q1/NQPY68dPHJQxlGufwGYaR9WDqg", + "xWJq605TC6aZfXC7h6Sb0ArVDlIYoHrc+cAth1kMvYWacrvVtqZBK9YtTjBhVOmxHb8hGAdzLxI3p1cL", + "GkvxaIQMA9Np4wBu2dK1IL6zx72qw/3t7CTwJddtmX3FXIJsHqH1M6LcUGCw004WFRrJAKk2lAnm1v+X", + "KxEZpuJXlNvSGKafPUqutwJr/DK9roTEHAQqbvbPIGUFzeOSQ5b2TbwZWzFb9aFSEJQVcAPZijqWilxp", + "hvoRi0PN2ZI8mge1TdxuZGzDFFvkgC0e2xYLqpCT14aouotZHnC9Vtj8yYTm64pnEjK9VhaxSpBaqEP1", + "pnZeLUBfAXDyCNs9fk7uo9tOsQ08MFh09/Ps5PFzNLraPx7FLgBXtWOMm2TITv7h2EmcjtFvaccwjNuN", + "ehR9rm3Ldg0zrpHTZLtOOUvY0vG6/WepoJyuIB4pUuyByfbF3URDWgcvPLM1Z5SWYkeYjs8Pmhr+NBB9", + "btifBYOkoiiYLpxzR4nC0FNTM8BO6oezBWxculcPl/+IPtLSu4g6SuTnNZra+y22avRkv6EFtNE6J9Qm", + "nshZE73gk1CTM5/XBvPf1mlvLW7MXGbpKOZgMMOSlJJxjYpFpZfJX0m6ppKmhv0dDYGbLL5+Fsn52849", + "yQ8D/LPjXYICuYmjXg6QvZchXF9ynwueFIajZA+a1x7BqRx05sbddkO+w/GhpwplZpRkkNyqFrnRgFPf", + "ivD4yIC3JMV6PQfR48Er++yUWck4edDK7NBP7147KaMQMpasrjnuTuKQoCWDDcbuxTfJjHnLvZD5pF24", + "DfRf1vPgRc5ALPNnOaYIvBAR7dTnoa4t6S5WPWIdGDqm5oMhg4Ubak7aOX8/v9PPG5/7zifzxcOKf3SB", + "/cJbikj2KxjYxCAfeXQ7s/p74P+m5IXYTt3UzgnxG/tvgJooSiqWZz83rzI76d4l5ek66s9amI6/NIWp", + "6sXZ+ymaE25NOYc8OpyVBX/xMmNEqv2nmDpPwfjEtt0M9Ha5ncU1gLfB9ED5CQ16mc7NBCFW2w/e6oDq", + "fCUygvM0Ccga7tmvXBDkl/5XBUrHHg/hBxvUhXZLo+/a9MYEeIba4hH5ztaeXQNpZZdBLc2nEfCpZa1B", + "vSpzQbM5pje4+Ob0NbGz2j62vIpNr7xCJaW9io69KsitOC082FdKiT9dmD7OeCy1WbXSSZ0NOfY41LRo", + "8jWzjg0f1ZcQO0fkVVBF0r4jNUMYelgyWRiNqx7Nyi5IE+Y/WtN0jSpZi6UOk/z0vOCeKlVQi6+uqVMn", + "HMRzZ+B2qcFtZvA5EUZvvmLKlhyFDbTfo9aPs51JwL9PbS9PVpxbSonKHmPJA26Cdg+cDdTwZv4oZB3E", + "HyiQ27T6h6ZJP8de0fxH3ZzrvSJ89nVjXSvFl5JOKRecpZh9KHY1u9qkU3xgExI1dY2s/oi7Exo5XNFM", + "73WYnMPiYO53zwgd4vpG+OCr2VRLHfZPjUUw11STFWjlOBtkc1+wwNkBGVfgEkhiJduATwrZ8isih4y6", + "qpPapXEgGeGzmAHF7lvz7Y1T+zFe/JJxFPAd2lxourXUYelEbbQCpslKgHLrab8NVu9NnyN8JpvB9uOR", + "L7WIY1i3nFm29UH3hzr1HmnnATZtX5q2LntO/XMrAtlOelqWbtLhchZReUBv+SCCI57FxLt2AuTW44ej", + "jZDbaCgJ3qeG0GCDjmgo8R7uEUZd2qFTNsgIrZaisAWxIVzRDAaMR8B4zTg0hUAjF0QavRJwY/C8DvRT", + "qaTaioCTeNoF0By9zzGGprRzPdx2qG7uIIMSXKOfY3gbm6oUA4yjbtAIbpTv6vqjhroDYeIlFj52iOzX", + "mECpyglRGb4o6FSdiDEOw7h9XZv2BdA/Bn2ZyHbXktqTc8hNNPRIdFFlK9AJzbJYPs8X+JXgV5JVKDnA", + "FtKqzvtYliTFnCjtJDF9anMTpYKrqhiZyze45XRBGZcINYSlZPwO4yOUxQ7/jSU9HN4ZF4RxcBigj7hw", + "VR4OlJvbI/WkXkPTiWKrZDom8E65PTqaqW9G6E3/O6X0XKzagHzm1BBjXC7coxh/+8ZcHGHmhF4mT3u1", + "1IkNMOhO+OJ7qDbWT3LbXAmvsl5qT3T21MW9xg0Qw2W65nj5DYTeBgkxqL1frfdwKAA3HYwXp9q9XNOU", + "jLKgwddANnrHvvtBKOKW06GIHRuwYz73ek+TDHtyto5nxQsQ6kPB+gB97+NMSUmZc403zKKPWReRPmwu", + "HDt0zQZ3F+HivActdt9vhmKyiWJ8lQPB790yPpfgnrPXle3tWn1UklcJ7a+usKwdr46Kj66/H52AU31Z", + "M+ig0fbCpYy3y3Q6+fc/2xg2AlzL3b+BCbe36b0iSH1p15qnmiakzjY8Kftw61aM1zMazn/U5DxCeiqF", + "Yk2G61iho4mxbhdYqyjI39QfyweabCDVmNa8caBLgEOyOZnJgrKCf+ZBGtAd65BAl/5oLOdRP5f5ngut", + "9ywpeFpn80AfTc/wc1qHSSFTwrywK+Cusl/7wcHksOflElLNNnuegf1jDTx4YjT3RghboTd4FcbqMFrM", + "InK4ia0BaOyV1ig8QTa/W4Mz9AjkEnb3FGlRQzQx9dzfKzdJIIEYQO6QGBIRKhaGYK2mzjPMVE0ZiAUf", + "9mO7Q5OKa7CmTfCo8YZzeZI0N27z0HFkynhRjUlzma4HPf/FiNChl2L9nPzDwvYrLIGg6npzPgFFqJKS", + "s36aviuXwAIf7dWOAp/KApT/zb/QtbPk7BLCqjvolrmiMvMtonYGb8JIRu6j3vMun0++C/Synpk1QZr9", + "Bz2RxE8YipvmwshfyVA8czsuMizXj9EfNhE2RnwauJYgXXUyFPZyoSDRwgd1jsExhgpXWv4mSFCDyRYt", + "cIMpUN41OV4w6SzFlCfURbaECyQSCmqgk0EmluE5x5D90n73L1h80tG95pSaXvenX/fhuUz1kBhS/ZK4", + "23L/y5ibWFYY57Y6rIqlZeEGlaHpv5Qiq1J7QYcHo7Y+TU56NMJKokaJtL/KjkAcPC+8hN2xlfh93nq/", + "gyHQVnKyoAfP+TubfKe2JhWDe3Un4H1JM818VgqRJwOW/bN+LpkuxV+y9BIyYm4KH8Y2UAOE3EeDcu26", + "vVrvfO6UsgQO2YMjQk65DRz2Xtx2MuPO5PyeHpt/i7NmlU3v5CxIRx94PAITEy/JW3IzP8w4D1NgWN0t", + "p7KD7MlUsh3IYyPpVaQiztFUFbTvV+1WKWmIykIRk0maAhx7gkLqeJCmBkITE9KXDvJcXCVIRUmdiCqm", + "c5h2bSbpU2823Qy2FxAEl1DlLtAdWdOMpEJKSMMe8Vh7C1QhJCS5wFiTmBtsqY08VGCALSe5WBFRGjXX", + "5nPzDoNogY6wUt5IgY6Lzimy7TAYwS3x4Cocbpe6xTj2moQDMCdQR2/4iFUtUmSkva5umZqholFaFCzt", + "D9dCzR8inmEwCmFPCZXI+uqT5yq8+LdhA7iKOgfHfXG2HNZiqkeuzpwb3adoltZkr4+uBcMkT92hYCyx", + "vFxCI0g+q0XGeav6J+sU0PFZzSyNp9SqjGsgZuxKgnurZOtgdQp4lFSv/RVimvcVO6MkgMKHRLYIBFXW", + "DOHNIa4IV/duFmWSwwZarkv3gKpKU1CKbSAs4GU7kwygRONgV2SN+eTCu60jx7i1J4FXZwp2o4KNRazd", + "KbJHaonKWFue2GOiph4lA9GGZRVt4U/doiTSUDWkCBv2sE7kFAczifjixljEXi860nz0XPK4Ez18v1db", + "JHC2rLZcWiJsTrYq6RUfluAjRp/as3v7dRAcjKjOe9rBmlyy3pWbam+DlDFGGL0yZlGZQ4EvQxmmvvCy", + "jusbEXCsnYmpyABMNecZ48SgiUMKmhV0RzK2XIK0lnSlKc+ozMLmjJMUpKbMqBU7dXOZ0kArK5jvFSsN", + "d8VBPYOJCZhoFLKA5Dsnr8dFvoJujVyJ4TMDO+DeoqJUaalbcJROSEEvI6g/3KDsgVDsNxiHAdNHOJOY", + "FgjSreePOeNumBhrEsft+6kjl1JQSm3cmxLmzWse5Egb7oDWV3+vdUnzh+a+m1bUzXfYA17oZAvKunl7", + "hwPnC7+a+aFGSrCUj0OU0Fr+Pr+dW2AjIARb5M6v1mCzmNqI6/a+BE5Z9bL2dQ5VIOy6RDFJnuCYOLTv", + "SrUsBeuPhIRjDorc0Pzzu0Mxe+Ip4sPVho8vNPSnhUi2qFQ3C11/TSfNHfjO7m5q/hbdt/8As0dRZdIN", + "5SSPWhr30R54IdDcKvtLX39pA5xc4Zg20Onx12Thnt2WElKmuhLNlS+NULuPsFKQey6w1Xv8VfvW+bPQ", + "tyDjpVcQyJsmzTqq5iveQNgc0S/MVAZObpTKY9TXI4sI/mI8Ksx/tee6uGxFQNmyFZ3QfiHhjiOhgpjm", + "AyOh+pm9pi7PRvuYS6dS0F/n5Nu6hdvIRd2sbWoYXx+5Y7m4p0TfxVPsm+4Y/mcRgvUpCIJKfn38K5Gw", + "xAJ0gjx8iBM8fDh3TX990v5sjvPDh1H94bMF/lkcuTHcvDGK+XnoKZh97jTw6rCzHxXLs32E0XpD2pRw", + "xFeSv7hX5F+kiOQvNj6jf1RdIa9DQo67m4CIiay1NXkwVfA6dMLDUNct8gwUfR9pJZneYXI7r/azX6Ih", + "it/VEUAugqxW69zdp8Ul1OkRm3ihSvnb9TtBc7yPrLbJzS0k8iPyzZYWZQ7uoPzt3uIv8PSvz7JHTx//", + "ZfHXR189SuHZV88fPaLPn9HHz58+hid//erZI3i8/Pr54kn25NmTxbMnz77+6nn69NnjxbOvn//lnuFD", + "BmQL6MynUpn9b6y0mpy+PUsuDLANTmjJvoedLepmyNiXi6MpnkQoKMtnJ/6n/9+fsKNUFM3w/teZy9Qw", + "W2tdqpPj46urq6Owy/EKAwQSLap0fezn6Rf/f3tWe1as8QZ31D6k9EY5Twqn+O3dN+cX5PTt2VFQ7fpk", + "9ujo0dFjLI5cAqclm53MnuJPeHrWuO/HjthmJ5+u57PjNdAc4+nMHwVoyVL/SQLNdu7/6oquViCPXA09", + "89PmybEXK44/uUCJ67Fvx2E5iuNPrXiSbE9PTFd//MlnYRtv3Upz5uJogg4ToRhrdrzA5A5Tm4IKGg8v", + "BZUNdfwJxeXB34/di/f4R1Rb7Hk49kFX8ZYtLH3SWwNrp0dKdbquyuNP+B+kzwAs+76kD24Gm0Jk4OcT", + "y6VNCjn2+fiT/TcYBrYlSGbkNoxSc7/aZ+bHthJ2/+cdT6M/9qHsVWRaQdQZim/VKVYNjueFn+Fxsyf1", + "LEMGqrtRnba8g7X44Sl88ujRQZUqp8WIdGNJ+1dSn/eMrex6Pnt2IKCjxpvWc5MIMC9oRrxfGud+/Pnm", + "PuMYGmqYKrGXBkLw7PNB0K6l8T3syBuhybeo3VzPZ199zp0440bWojnBlkGqvP4R+YlfcnHFfUsjbVRF", + "QeVu8vHRdKUwfkGyDXWyXlBeafYRA2ZsrEL7qJ1mWY/ordQFSr8QeH0NYaxQq9I9Lm2Q1gidjJsl9LXW", + "fr3qNUTCsm3woPf5cZHBLBQHtazg+pY8oS13GxDOIkYYtCZiwaOlT24ZgBqNMe6GldiRJ1Xs7QxeFyip", + "FgVTXtr/k6f8yVOknf7p55v+HOSGpUAuoCiFpJLlO/ITr1OD3JjHnWZZ9GFG++jv5XFGoU9FBivgiWNg", + "yUJkO5/+uDXBJVj9sifIHH9q1zCx8uUsgxx0NOjc/F5Xl+4vYrEjZ696Eo7t1uW8L3bYNKgNcvL+k1XQ", + "jPbR6E9dEHucMSxL0eVNH+Ncc4zszUJWQhOLhcwt6k9G9CcjupVwM/nwTJFvotqHTbxFe3f23OfQimVP", + "pLoPyhQd5Yse3zvZ+L7+E9N37AMXyEjwwYZ+dNH8J4v4k0XcjkV8B5HDiKfWMY0I0R2mD01lGBg3l3Ur", + "BaKPwjevciqDiJ99Zo5THNEZNz4H1/jcSl0UV1ano7wpphrZwLvV8/5keX+yvD8Oyzvdz2jagsmtNaNL", + "2BW0rPUhta50Jq4CNwXCYkOI+nbgunZ56+/jK8p0shTSPZfGShr9zhpofuwSAXZ+bXLv9L5gQqHgxzDy", + "OPrrcV2oKPqx6+GIfXUW/oFGPhDSf268naH3EFl77Td8/9GwZUyD77h+4ww7OT7GJ4hrofTx7Hr+qeMo", + "Cz9+rEngU31XOFK4/nj9/wIAAP//QAj1qArRAAA=", } // 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 82f705c7ad..095790c281 100644 --- a/daemon/algod/api/server/v2/generated/participating/public/routes.go +++ b/daemon/algod/api/server/v2/generated/participating/public/routes.go @@ -178,188 +178,188 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL var swaggerSpec = []string{ "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", + "//+yRy9Gmd8AAA==", } // 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 4eba078ace..254f81acea 100644 --- a/daemon/algod/api/server/v2/handlers.go +++ b/daemon/algod/api/server/v2/handlers.go @@ -928,7 +928,6 @@ func (v2 *Handlers) RawTransaction(ctx echo.Context) error { // PreEncodedSimulateTxnResult mirrors model.SimulateTransactionResult type PreEncodedSimulateTxnResult struct { Txn PreEncodedTxInfo `codec:"txn-result"` - MissingSignature *bool `codec:"missing-signature,omitempty"` AppBudgetConsumed *uint64 `codec:"app-budget-consumed,omitempty"` LogicSigBudgetConsumed *uint64 `codec:"logic-sig-budget-consumed,omitempty"` } @@ -947,7 +946,6 @@ type PreEncodedSimulateResponse struct { Version uint64 `codec:"version"` LastRound uint64 `codec:"last-round"` TxnGroups []PreEncodedSimulateTxnGroupResult `codec:"txn-groups"` - WouldSucceed bool `codec:"would-succeed"` EvalOverrides *model.SimulationEvalOverrides `codec:"eval-overrides,omitempty"` } @@ -958,8 +956,9 @@ type PreEncodedSimulateRequestTransactionGroup struct { // PreEncodedSimulateRequest mirrors model.SimulateRequest type PreEncodedSimulateRequest struct { - TxnGroups []PreEncodedSimulateRequestTransactionGroup `codec:"txn-groups"` - LiftLogLimits bool `codec:"lift-log-limits,omitempty"` + TxnGroups []PreEncodedSimulateRequestTransactionGroup `codec:"txn-groups"` + AllowEmptySignatures bool `codec:"allow-empty-signatures,omitempty"` + AllowMoreLogging bool `codec:"allow-more-logging,omitempty"` } // SimulateTransaction simulates broadcasting a raw transaction to the network, returning relevant simulation results. @@ -1003,18 +1002,8 @@ func (v2 *Handlers) SimulateTransaction(ctx echo.Context, params model.SimulateT } } - txnGroups := make([][]transactions.SignedTxn, len(simulateRequest.TxnGroups)) - for i := 0; i < len(simulateRequest.TxnGroups); i++ { - txnGroups[i] = simulateRequest.TxnGroups[i].Txns - } - // Simulate transaction - simulationResult, err := v2.Node.Simulate( - simulation.Request{ - TxnGroups: txnGroups, - LiftLogLimits: simulateRequest.LiftLogLimits, - }, - ) + simulationResult, err := v2.Node.Simulate(convertSimulationRequest(simulateRequest)) if err != nil { var invalidTxErr simulation.InvalidTxGroupError switch { diff --git a/daemon/algod/api/server/v2/test/handlers_test.go b/daemon/algod/api/server/v2/test/handlers_test.go index 0687279a3a..0c34aacd5d 100644 --- a/daemon/algod/api/server/v2/test/handlers_test.go +++ b/daemon/algod/api/server/v2/test/handlers_test.go @@ -852,7 +852,6 @@ func assertSimulationResultsEqual(t *testing.T, expectedError string, expected, if len(expectedError) != 0 { require.NotNil(t, actual.TxnGroups[0].FailureMessage) require.Contains(t, *actual.TxnGroups[0].FailureMessage, expectedError) - require.False(t, expected.WouldSucceed, "Test case WouldSucceed value is not consistent with expected failure") // if it matched the expected error, copy the actual one so it will pass the equality check below expected.TxnGroups[0].FailureMessage = actual.TxnGroups[0].FailureMessage } @@ -1039,7 +1038,7 @@ int 1`, txnAppBudgetUsed = append(txnAppBudgetUsed, numOrNil(scenario.TxnAppBudgetConsumed[i])) } expectedBody := v2.PreEncodedSimulateResponse{ - Version: 1, + Version: 2, TxnGroups: []v2.PreEncodedSimulateTxnGroupResult{ { AppBudgetAdded: appBudgetAdded, @@ -1063,7 +1062,6 @@ int 1`, }, }, }, - WouldSucceed: scenario.Outcome == mocktracer.ApprovalOutcome, } assertSimulationResultsEqual(t, scenario.ExpectedError, expectedBody, actualBody) }) diff --git a/daemon/algod/api/server/v2/utils.go b/daemon/algod/api/server/v2/utils.go index 55d1c49c39..ce1e6340bd 100644 --- a/daemon/algod/api/server/v2/utils.go +++ b/daemon/algod/api/server/v2/utils.go @@ -359,7 +359,6 @@ func ConvertInnerTxn(txn *transactions.SignedTxnWithAD) PreEncodedTxInfo { func convertTxnResult(txnResult simulation.TxnResult) PreEncodedSimulateTxnResult { return PreEncodedSimulateTxnResult{ Txn: ConvertInnerTxn(&txnResult.Txn), - MissingSignature: trueOrNil(txnResult.MissingSignature), AppBudgetConsumed: numOrNil(txnResult.AppBudgetConsumed), LogicSigBudgetConsumed: numOrNil(txnResult.LogicSigBudgetConsumed), } @@ -391,15 +390,15 @@ func convertSimulationResult(result simulation.Result) PreEncodedSimulateRespons var evalOverrides *model.SimulationEvalOverrides if result.EvalOverrides != (simulation.ResultEvalOverrides{}) { evalOverrides = &model.SimulationEvalOverrides{ - MaxLogSize: result.EvalOverrides.MaxLogSize, - MaxLogCalls: result.EvalOverrides.MaxLogCalls, + AllowEmptySignatures: trueOrNil(result.EvalOverrides.AllowEmptySignatures), + MaxLogSize: result.EvalOverrides.MaxLogSize, + MaxLogCalls: result.EvalOverrides.MaxLogCalls, } } encodedSimulationResult := PreEncodedSimulateResponse{ Version: result.Version, LastRound: uint64(result.LastRound), - WouldSucceed: result.WouldSucceed, TxnGroups: make([]PreEncodedSimulateTxnGroupResult, len(result.TxnGroups)), EvalOverrides: evalOverrides, } @@ -411,6 +410,18 @@ func convertSimulationResult(result simulation.Result) PreEncodedSimulateRespons return encodedSimulationResult } +func convertSimulationRequest(request PreEncodedSimulateRequest) simulation.Request { + txnGroups := make([][]transactions.SignedTxn, len(request.TxnGroups)) + for i, txnGroup := range request.TxnGroups { + txnGroups[i] = txnGroup.Txns + } + return simulation.Request{ + TxnGroups: txnGroups, + AllowEmptySignatures: request.AllowEmptySignatures, + AllowMoreLogging: request.AllowMoreLogging, + } +} + // printableUTF8OrEmpty checks to see if the entire string is a UTF8 printable string. // If this is the case, the string is returned as is. Otherwise, the empty string is returned. func printableUTF8OrEmpty(in string) string { diff --git a/data/transactions/verify/txn.go b/data/transactions/verify/txn.go index 7c75ffd9ec..528d5ea5e8 100644 --- a/data/transactions/verify/txn.go +++ b/data/transactions/verify/txn.go @@ -108,8 +108,11 @@ const ( // TxGroupError is an error from txn pre-validation (well form-ness, signature format, etc). // It can be unwrapped into underlying error, as well as has a specific failure reason code. type TxGroupError struct { - err error - Reason TxGroupErrorReason + err error + // GroupIndex is the index of the transaction in the group that failed. NOTE: this will be -1 if + // the error is not specific to a single transaction. + GroupIndex int + Reason TxGroupErrorReason } // Error returns an error message from the underlying error @@ -155,16 +158,16 @@ func (g *GroupContext) Equal(other *GroupContext) bool { // txnBatchPrep verifies a SignedTxn having no obviously inconsistent data. // Block-assembly time checks of LogicSig and accounting rules may still block the txn. // It is the caller responsibility to call batchVerifier.Verify(). -func txnBatchPrep(s *transactions.SignedTxn, txnIdx int, groupCtx *GroupContext, verifier *crypto.BatchVerifier, evalTracer logic.EvalTracer) *TxGroupError { +func txnBatchPrep(s *transactions.SignedTxn, groupIndex int, groupCtx *GroupContext, verifier *crypto.BatchVerifier, evalTracer logic.EvalTracer) *TxGroupError { if !groupCtx.consensusParams.SupportRekeying && (s.AuthAddr != basics.Address{}) { - return &TxGroupError{err: errRekeyingNotSupported, Reason: TxGroupErrorReasonGeneric} + return &TxGroupError{err: errRekeyingNotSupported, GroupIndex: groupIndex, Reason: TxGroupErrorReasonGeneric} } if err := s.Txn.WellFormed(groupCtx.specAddrs, groupCtx.consensusParams); err != nil { - return &TxGroupError{err: err, Reason: TxGroupErrorReasonNotWellFormed} + return &TxGroupError{err: err, GroupIndex: groupIndex, Reason: TxGroupErrorReasonNotWellFormed} } - return stxnCoreChecks(s, txnIdx, groupCtx, verifier, evalTracer) + return stxnCoreChecks(s, groupIndex, groupCtx, verifier, evalTracer) } // TxnGroup verifies a []SignedTxn as being signed and having no obviously inconsistent data. @@ -219,7 +222,7 @@ func txnGroupBatchPrep(stxs []transactions.SignedTxn, contextHdr *bookkeeping.Bl } feeNeeded, overflow := basics.OMul(groupCtx.consensusParams.MinTxnFee, minFeeCount) if overflow { - err = &TxGroupError{err: errTxGroupInvalidFee, Reason: TxGroupErrorReasonInvalidFee} + err = &TxGroupError{err: errTxGroupInvalidFee, GroupIndex: -1, Reason: TxGroupErrorReasonInvalidFee} return nil, err } // feesPaid may have saturated. That's ok. Since we know @@ -230,7 +233,8 @@ func txnGroupBatchPrep(stxs []transactions.SignedTxn, contextHdr *bookkeeping.Bl err: fmt.Errorf( "txgroup had %d in fees, which is less than the minimum %d * %d", feesPaid, minFeeCount, groupCtx.consensusParams.MinTxnFee), - Reason: TxGroupErrorReasonInvalidFee, + GroupIndex: -1, + Reason: TxGroupErrorReasonInvalidFee, } return nil, err } @@ -246,7 +250,7 @@ const logicSig sigOrTxnType = 3 const stateProofTxn sigOrTxnType = 4 // checkTxnSigTypeCounts checks the number of signature types and reports an error in case of a violation -func checkTxnSigTypeCounts(s *transactions.SignedTxn) (sigType sigOrTxnType, err *TxGroupError) { +func checkTxnSigTypeCounts(s *transactions.SignedTxn, groupIndex int) (sigType sigOrTxnType, err *TxGroupError) { numSigCategories := 0 if s.Sig != (crypto.Signature{}) { numSigCategories++ @@ -268,17 +272,17 @@ func checkTxnSigTypeCounts(s *transactions.SignedTxn) (sigType sigOrTxnType, err if s.Txn.Sender == transactions.StateProofSender && s.Txn.Type == protocol.StateProofTx { return stateProofTxn, nil } - return 0, &TxGroupError{err: errTxnSigHasNoSig, Reason: TxGroupErrorReasonHasNoSig} + return 0, &TxGroupError{err: errTxnSigHasNoSig, GroupIndex: groupIndex, Reason: TxGroupErrorReasonHasNoSig} } if numSigCategories > 1 { - return 0, &TxGroupError{err: errTxnSigNotWellFormed, Reason: TxGroupErrorReasonSigNotWellFormed} + return 0, &TxGroupError{err: errTxnSigNotWellFormed, GroupIndex: groupIndex, Reason: TxGroupErrorReasonSigNotWellFormed} } return sigType, nil } // stxnCoreChecks runs signatures validity checks and enqueues signature into batchVerifier for verification. -func stxnCoreChecks(s *transactions.SignedTxn, txnIdx int, groupCtx *GroupContext, batchVerifier *crypto.BatchVerifier, evalTracer logic.EvalTracer) *TxGroupError { - sigType, err := checkTxnSigTypeCounts(s) +func stxnCoreChecks(s *transactions.SignedTxn, groupIndex int, groupCtx *GroupContext, batchVerifier *crypto.BatchVerifier, evalTracer logic.EvalTracer) *TxGroupError { + sigType, err := checkTxnSigTypeCounts(s, groupIndex) if err != nil { return err } @@ -289,7 +293,7 @@ func stxnCoreChecks(s *transactions.SignedTxn, txnIdx int, groupCtx *GroupContex return nil case multiSig: if err := crypto.MultisigBatchPrep(s.Txn, crypto.Digest(s.Authorizer()), s.Msig, batchVerifier); err != nil { - return &TxGroupError{err: fmt.Errorf("multisig validation failed: %w", err), Reason: TxGroupErrorReasonMsigNotWellFormed} + return &TxGroupError{err: fmt.Errorf("multisig validation failed: %w", err), GroupIndex: groupIndex, Reason: TxGroupErrorReasonMsigNotWellFormed} } counter := 0 for _, subsigi := range s.Msig.Subsigs { @@ -307,8 +311,8 @@ func stxnCoreChecks(s *transactions.SignedTxn, txnIdx int, groupCtx *GroupContex return nil case logicSig: - if err := logicSigVerify(s, txnIdx, groupCtx, evalTracer); err != nil { - return &TxGroupError{err: err, Reason: TxGroupErrorReasonLogicSigFailed} + if err := logicSigVerify(s, groupIndex, groupCtx, evalTracer); err != nil { + return &TxGroupError{err: err, GroupIndex: groupIndex, Reason: TxGroupErrorReasonLogicSigFailed} } return nil @@ -316,7 +320,7 @@ func stxnCoreChecks(s *transactions.SignedTxn, txnIdx int, groupCtx *GroupContex return nil default: - return &TxGroupError{err: errUnknownSignature, Reason: TxGroupErrorReasonGeneric} + return &TxGroupError{err: errUnknownSignature, GroupIndex: groupIndex, Reason: TxGroupErrorReasonGeneric} } } @@ -416,20 +420,6 @@ func logicSigSanityCheckBatchPrep(txn *transactions.SignedTxn, groupIndex int, g return nil } -// LogicSigError represents a LogicSig evaluation which rejected or errored -type LogicSigError struct { - GroupIndex int - err error -} - -func (e LogicSigError) Error() string { - return e.err.Error() -} - -func (e LogicSigError) Unwrap() error { - return e.err -} - // logicSigVerify checks that the signature is valid, executing the program. func logicSigVerify(txn *transactions.SignedTxn, groupIndex int, groupCtx *GroupContext, evalTracer logic.EvalTracer) error { err := LogicSigSanityCheck(txn, groupIndex, groupCtx) @@ -450,11 +440,11 @@ func logicSigVerify(txn *transactions.SignedTxn, groupIndex int, groupCtx *Group pass, cx, err := logic.EvalSignatureFull(groupIndex, &ep) if err != nil { logicErrTotal.Inc(nil) - return LogicSigError{groupIndex, fmt.Errorf("transaction %v: %w", txn.ID(), err)} + return fmt.Errorf("transaction %v: %w", txn.ID(), err) } if !pass { logicRejTotal.Inc(nil) - return LogicSigError{groupIndex, fmt.Errorf("transaction %v: rejected by logic", txn.ID())} + return fmt.Errorf("transaction %v: rejected by logic", txn.ID()) } logicGoodTotal.Inc(nil) logicCostTotal.AddUint64(uint64(cx.Cost()), nil) diff --git a/data/transactions/verify/txnBatch.go b/data/transactions/verify/txnBatch.go index 40ffb4d35a..40bd2dfffa 100644 --- a/data/transactions/verify/txnBatch.go +++ b/data/transactions/verify/txnBatch.go @@ -194,7 +194,7 @@ func (tbp *txnSigBatchProcessor) preProcessUnverifiedTxns(uTxns []execpool.Input func (ue UnverifiedTxnSigJob) GetNumberOfBatchableItems() (batchSigs uint64, err error) { batchSigs = 0 for i := range ue.TxnGroup { - count, err := getNumberOfBatchableSigsInTxn(&ue.TxnGroup[i]) + count, err := getNumberOfBatchableSigsInTxn(&ue.TxnGroup[i], i) if err != nil { return 0, err } @@ -203,8 +203,8 @@ func (ue UnverifiedTxnSigJob) GetNumberOfBatchableItems() (batchSigs uint64, err return } -func getNumberOfBatchableSigsInTxn(stx *transactions.SignedTxn) (uint64, error) { - sigType, err := checkTxnSigTypeCounts(stx) +func getNumberOfBatchableSigsInTxn(stx *transactions.SignedTxn, groupIndex int) (uint64, error) { + sigType, err := checkTxnSigTypeCounts(stx, groupIndex) if err != nil { return 0, err } diff --git a/ledger/simulation/simulation_eval_test.go b/ledger/simulation/simulation_eval_test.go index 977f4a21e6..e4cd00454e 100644 --- a/ledger/simulation/simulation_eval_test.go +++ b/ledger/simulation/simulation_eval_test.go @@ -39,23 +39,6 @@ import ( "github.com/stretchr/testify/require" ) -// attachGroupID calculates and assigns the ID for a transaction group. -// Mutates the group directly. -func attachGroupID(txns []transactions.SignedTxn) { - txgroup := transactions.TxGroup{ - TxGroupHashes: make([]crypto.Digest, len(txns)), - } - for i, txn := range txns { - txn.Txn.Group = crypto.Digest{} - txgroup.TxGroupHashes[i] = crypto.Digest(txn.ID()) - } - group := crypto.HashObj(txgroup) - - for i := range txns { - txns[i].Txn.Header.Group = group - } -} - func uint64ToBytes(num uint64) []byte { ibytes := make([]byte, 8) binary.BigEndian.PutUint64(ibytes, num) @@ -63,7 +46,7 @@ func uint64ToBytes(num uint64) []byte { } type simulationTestCase struct { - input []transactions.SignedTxn + input simulation.Request expected simulation.Result expectedError string } @@ -102,21 +85,13 @@ func normalizeEvalDeltas(t *testing.T, actual, expected *transactions.EvalDelta) func validateSimulationResult(t *testing.T, result simulation.Result) { t.Helper() - shouldHaveBlock := true - if !result.WouldSucceed { - // WouldSucceed might be false because of missing signatures, in which case a block would - // still be generated. The only reason for no block would be an eval error. - for _, groupResult := range result.TxnGroups { - if len(groupResult.FailureMessage) != 0 { - shouldHaveBlock = false - break - } + for _, groupResult := range result.TxnGroups { + if len(groupResult.FailureMessage) != 0 { + // The only reason for no block is an eval error. + assert.Nil(t, result.Block) + return } } - if !shouldHaveBlock { - assert.Nil(t, result.Block) - return - } require.NotNil(t, result.Block) blockGroups, err := result.Block.Block().DecodePaysetGroups() @@ -151,26 +126,26 @@ func simulationTest(t *testing.T, f func(accounts []simulationtesting.Account, t testcase := f(accounts, txnInfo) - actual, err := s.Simulate(simulation.Request{TxnGroups: [][]transactions.SignedTxn{testcase.input}}) + actual, err := s.Simulate(testcase.input) require.NoError(t, err) validateSimulationResult(t, actual) - require.Len(t, testcase.expected.TxnGroups, 1, "Test case must expect a single txn group") - require.Len(t, testcase.expected.TxnGroups[0].Txns, len(testcase.input), "Test case expected a different number of transactions than its input") + require.Len(t, testcase.expected.TxnGroups, len(testcase.input.TxnGroups), "Test case must expect the same number of transaction groups as its input") - for i, inputTxn := range testcase.input { - if testcase.expected.TxnGroups[0].Txns[i].Txn.Txn.Type == "" { - // Use Type as a marker for whether the transaction was specified or not. If not - // specified, replace it with the input txn - testcase.expected.TxnGroups[0].Txns[i].Txn.SignedTxn = inputTxn + for i := range testcase.input.TxnGroups { + for j := range testcase.input.TxnGroups[i] { + if testcase.expected.TxnGroups[i].Txns[j].Txn.Txn.Type == "" { + // Use Type as a marker for whether the transaction was specified or not. If not + // specified, replace it with the input txn + testcase.expected.TxnGroups[i].Txns[j].Txn.SignedTxn = testcase.input.TxnGroups[i][j] + } + normalizeEvalDeltas(t, &actual.TxnGroups[i].Txns[j].Txn.EvalDelta, &testcase.expected.TxnGroups[i].Txns[j].Txn.EvalDelta) } - normalizeEvalDeltas(t, &actual.TxnGroups[0].Txns[i].Txn.EvalDelta, &testcase.expected.TxnGroups[0].Txns[i].Txn.EvalDelta) } if len(testcase.expectedError) != 0 { require.Contains(t, actual.TxnGroups[0].FailureMessage, testcase.expectedError) - require.False(t, testcase.expected.WouldSucceed, "Test case WouldSucceed value is not consistent with expected failure") // if it matched the expected error, copy the actual one so it will pass the equality check below testcase.expected.TxnGroups[0].FailureMessage = actual.TxnGroups[0].FailureMessage } @@ -186,155 +161,118 @@ func TestPayTxn(t *testing.T) { t.Run("simple", func(t *testing.T) { t.Parallel() - for _, signed := range []bool{true, false} { - signed := signed - t.Run(fmt.Sprintf("signed=%t", signed), func(t *testing.T) { - t.Parallel() - simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { - sender := accounts[0] - receiver := accounts[1] - - txn := txnInfo.NewTxn(txntest.Txn{ - Type: protocol.PaymentTx, - Sender: sender.Addr, - Receiver: receiver.Addr, - Amount: 1_000_000, - }).SignedTxn() - - if signed { - txn = txn.Txn.Sign(sender.Sk) - } - - return simulationTestCase{ - input: []transactions.SignedTxn{txn}, - expected: simulation.Result{ - Version: 1, - LastRound: txnInfo.LatestRound(), - TxnGroups: []simulation.TxnGroupResult{ - { - Txns: []simulation.TxnResult{ - { - MissingSignature: !signed, - }, - }, - }, - }, - WouldSucceed: signed, + simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { + sender := accounts[0] + receiver := accounts[1] + + txn := txnInfo.NewTxn(txntest.Txn{ + Type: protocol.PaymentTx, + Sender: sender.Addr, + Receiver: receiver.Addr, + Amount: 1_000_000, + }).Txn().Sign(sender.Sk) + + return simulationTestCase{ + input: simulation.Request{ + TxnGroups: [][]transactions.SignedTxn{{txn}}, + }, + expected: simulation.Result{ + Version: simulation.ResultLatestVersion, + LastRound: txnInfo.LatestRound(), + TxnGroups: []simulation.TxnGroupResult{ + { + Txns: []simulation.TxnResult{{}}, }, - } - }) - }) - } + }, + }, + } + }) }) t.Run("close to", func(t *testing.T) { t.Parallel() - for _, signed := range []bool{true, false} { - signed := signed - t.Run(fmt.Sprintf("signed=%t", signed), func(t *testing.T) { - t.Parallel() - simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { - sender := accounts[0] - receiver := accounts[1] - closeTo := accounts[2] - amount := uint64(1_000_000) - - txn := txnInfo.NewTxn(txntest.Txn{ - Type: protocol.PaymentTx, - Sender: sender.Addr, - Receiver: receiver.Addr, - Amount: amount, - CloseRemainderTo: closeTo.Addr, - }).SignedTxn() - - if signed { - txn = txn.Txn.Sign(sender.Sk) - } - - expectedClosingAmount := sender.AcctData.MicroAlgos.Raw - expectedClosingAmount -= amount + txn.Txn.Fee.Raw - - return simulationTestCase{ - input: []transactions.SignedTxn{txn}, - expected: simulation.Result{ - Version: 1, - LastRound: txnInfo.LatestRound(), - TxnGroups: []simulation.TxnGroupResult{ + simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { + sender := accounts[0] + receiver := accounts[1] + closeTo := accounts[2] + amount := uint64(1_000_000) + + txn := txnInfo.NewTxn(txntest.Txn{ + Type: protocol.PaymentTx, + Sender: sender.Addr, + Receiver: receiver.Addr, + Amount: amount, + CloseRemainderTo: closeTo.Addr, + }).Txn().Sign(sender.Sk) + + expectedClosingAmount := sender.AcctData.MicroAlgos.Raw + expectedClosingAmount -= amount + txn.Txn.Fee.Raw + + return simulationTestCase{ + input: simulation.Request{ + TxnGroups: [][]transactions.SignedTxn{{txn}}, + }, + expected: simulation.Result{ + Version: simulation.ResultLatestVersion, + LastRound: txnInfo.LatestRound(), + TxnGroups: []simulation.TxnGroupResult{ + { + Txns: []simulation.TxnResult{ { - Txns: []simulation.TxnResult{ - { - Txn: transactions.SignedTxnWithAD{ - ApplyData: transactions.ApplyData{ - ClosingAmount: basics.MicroAlgos{Raw: expectedClosingAmount}, - }, - }, - MissingSignature: !signed, + Txn: transactions.SignedTxnWithAD{ + ApplyData: transactions.ApplyData{ + ClosingAmount: basics.MicroAlgos{Raw: expectedClosingAmount}, }, }, }, }, - WouldSucceed: signed, }, - } - }) - }) - } + }, + }, + } + }) }) t.Run("overspend", func(t *testing.T) { t.Parallel() - for _, signed := range []bool{true, false} { - signed := signed - t.Run(fmt.Sprintf("signed=%t", signed), func(t *testing.T) { - t.Parallel() - simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { - sender := accounts[0] - receiver := accounts[1] - amount := sender.AcctData.MicroAlgos.Raw + 100 - - txn := txnInfo.NewTxn(txntest.Txn{ - Type: protocol.PaymentTx, - Sender: sender.Addr, - Receiver: receiver.Addr, - Amount: amount, - }).SignedTxn() - - if signed { - txn = txn.Txn.Sign(sender.Sk) - } - - return simulationTestCase{ - input: []transactions.SignedTxn{txn}, - expectedError: fmt.Sprintf("tried to spend {%d}", amount), - expected: simulation.Result{ - Version: 1, - LastRound: txnInfo.LatestRound(), - TxnGroups: []simulation.TxnGroupResult{ - { - Txns: []simulation.TxnResult{ - { - MissingSignature: !signed, - }, - }, - FailedAt: simulation.TxnPath{0}, - }, - }, - WouldSucceed: false, + simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { + sender := accounts[0] + receiver := accounts[1] + amount := sender.AcctData.MicroAlgos.Raw + 100 + + txn := txnInfo.NewTxn(txntest.Txn{ + Type: protocol.PaymentTx, + Sender: sender.Addr, + Receiver: receiver.Addr, + Amount: amount, + }).Txn().Sign(sender.Sk) + + return simulationTestCase{ + input: simulation.Request{ + TxnGroups: [][]transactions.SignedTxn{{txn}}, + }, + expectedError: fmt.Sprintf("tried to spend {%d}", amount), + expected: simulation.Result{ + Version: simulation.ResultLatestVersion, + LastRound: txnInfo.LatestRound(), + TxnGroups: []simulation.TxnGroupResult{ + { + Txns: []simulation.TxnResult{{}}, + FailedAt: simulation.TxnPath{0}, }, - } - }) - }) - } + }, + }, + } + }) }) } func TestWrongAuthorizerTxn(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - - for _, signed := range []bool{true, false} { - signed := signed - t.Run(fmt.Sprintf("signed=%t", signed), func(t *testing.T) { + for _, optionalSigs := range []bool{false, true} { + optionalSigs := optionalSigs + t.Run(fmt.Sprintf("optionalSigs=%t", optionalSigs), func(t *testing.T) { t.Parallel() simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { sender := accounts[0] @@ -345,33 +283,31 @@ func TestWrongAuthorizerTxn(t *testing.T) { Sender: sender.Addr, Receiver: sender.Addr, Amount: 0, - }) + }).Txn().Sign(authority.Sk) - var stxn transactions.SignedTxn - if signed { - stxn = txn.Txn().Sign(authority.Sk) - } else { - stxn = txn.SignedTxn() - stxn.AuthAddr = authority.Addr + if optionalSigs { + // erase signature + txn.Sig = crypto.Signature{} } return simulationTestCase{ - input: []transactions.SignedTxn{stxn}, + input: simulation.Request{ + TxnGroups: [][]transactions.SignedTxn{{txn}}, + AllowEmptySignatures: optionalSigs, + }, expectedError: fmt.Sprintf("should have been authorized by %s but was actually authorized by %s", sender.Addr, authority.Addr), expected: simulation.Result{ - Version: 1, + Version: simulation.ResultLatestVersion, LastRound: txnInfo.LatestRound(), TxnGroups: []simulation.TxnGroupResult{ { - Txns: []simulation.TxnResult{ - { - MissingSignature: !signed, - }, - }, + Txns: []simulation.TxnResult{{}}, FailedAt: simulation.TxnPath{0}, }, }, - WouldSucceed: false, + EvalOverrides: simulation.ResultEvalOverrides{ + AllowEmptySignatures: optionalSigs, + }, }, } }) @@ -382,64 +318,49 @@ func TestWrongAuthorizerTxn(t *testing.T) { func TestRekey(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() + simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { + sender := accounts[0] + authority := accounts[1] - for _, signed := range []bool{true, false} { - signed := signed - t.Run(fmt.Sprintf("signed=%t", signed), func(t *testing.T) { - t.Parallel() - simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { - sender := accounts[0] - authority := accounts[1] + txn1 := txnInfo.NewTxn(txntest.Txn{ + Type: protocol.PaymentTx, + Sender: sender.Addr, + Receiver: sender.Addr, + Amount: 1, + RekeyTo: authority.Addr, + }) + txn2 := txnInfo.NewTxn(txntest.Txn{ + Type: protocol.PaymentTx, + Sender: sender.Addr, + Receiver: sender.Addr, + Amount: 2, + }) - txn1 := txnInfo.NewTxn(txntest.Txn{ - Type: protocol.PaymentTx, - Sender: sender.Addr, - Receiver: sender.Addr, - Amount: 1, - RekeyTo: authority.Addr, - }) - txn2 := txnInfo.NewTxn(txntest.Txn{ - Type: protocol.PaymentTx, - Sender: sender.Addr, - Receiver: sender.Addr, - Amount: 2, - }) + txntest.Group(&txn1, &txn2) - txntest.Group(&txn1, &txn2) + stxn1 := txn1.Txn().Sign(sender.Sk) + stxn2 := txn2.Txn().Sign(authority.Sk) - var stxn1 transactions.SignedTxn - var stxn2 transactions.SignedTxn - if signed { - stxn1 = txn1.Txn().Sign(sender.Sk) - stxn2 = txn2.Txn().Sign(authority.Sk) - } else { - stxn1 = txn1.SignedTxn() - stxn2 = txn2.SignedTxn() - stxn2.AuthAddr = authority.Addr - } - return simulationTestCase{ - input: []transactions.SignedTxn{stxn1, stxn2}, - expected: simulation.Result{ - Version: 1, - LastRound: txnInfo.LatestRound(), - TxnGroups: []simulation.TxnGroupResult{ - { - Txns: []simulation.TxnResult{ - { - MissingSignature: !signed, - }, - { - MissingSignature: !signed, - }, - }, - }, + return simulationTestCase{ + input: simulation.Request{ + TxnGroups: [][]transactions.SignedTxn{ + {stxn1, stxn2}, + }, + }, + expected: simulation.Result{ + Version: simulation.ResultLatestVersion, + LastRound: txnInfo.LatestRound(), + TxnGroups: []simulation.TxnGroupResult{ + { + Txns: []simulation.TxnResult{ + {}, + {}, }, - WouldSucceed: signed, }, - } - }) - }) - } + }, + }, + } + }) } func TestStateProofTxn(t *testing.T) { @@ -468,61 +389,72 @@ func TestSimpleGroupTxn(t *testing.T) { l, accounts, txnInfo := simulationtesting.PrepareSimulatorTest(t) defer l.Close() s := simulation.MakeSimulator(l) - sender1 := accounts[0].Addr + sender1 := accounts[0] sender1Balance := accounts[0].AcctData.MicroAlgos - sender2 := accounts[1].Addr + sender2 := accounts[1] sender2Balance := accounts[1].AcctData.MicroAlgos // Send money back and forth - txgroup := []transactions.SignedTxn{ - txnInfo.NewTxn(txntest.Txn{ - Type: protocol.PaymentTx, - Sender: sender1, - Receiver: sender2, - Amount: 1_000_000, - }).SignedTxn(), - txnInfo.NewTxn(txntest.Txn{ - Type: protocol.PaymentTx, - Sender: sender2, - Receiver: sender1, - Amount: 0, - }).SignedTxn(), + txn1 := txnInfo.NewTxn(txntest.Txn{ + Type: protocol.PaymentTx, + Sender: sender1.Addr, + Receiver: sender2.Addr, + Amount: 1_000_000, + }) + txn2 := txnInfo.NewTxn(txntest.Txn{ + Type: protocol.PaymentTx, + Sender: sender2.Addr, + Receiver: sender1.Addr, + Amount: 0, + }) + + request := simulation.Request{ + TxnGroups: [][]transactions.SignedTxn{ + { + txn1.Txn().Sign(sender1.Sk), + txn2.Txn().Sign(sender2.Sk), + }, + }, } // Should fail if there is no group parameter - result, err := s.Simulate(simulation.Request{TxnGroups: [][]transactions.SignedTxn{txgroup}}) + result, err := s.Simulate(request) require.NoError(t, err) - require.False(t, result.WouldSucceed) require.Len(t, result.TxnGroups, 1) require.Len(t, result.TxnGroups[0].Txns, 2) require.Contains(t, result.TxnGroups[0].FailureMessage, "had zero Group but was submitted in a group of 2") - // Add group parameter - attachGroupID(txgroup) + // Add group parameter and sign again + txntest.Group(&txn1, &txn2) + request.TxnGroups = [][]transactions.SignedTxn{ + { + txn1.Txn().Sign(sender1.Sk), + txn2.Txn().Sign(sender2.Sk), + }, + } // Check balances before transaction - sender1Data, _, err := l.LookupWithoutRewards(l.Latest(), sender1) + sender1Data, _, err := l.LookupWithoutRewards(l.Latest(), sender1.Addr) require.NoError(t, err) require.Equal(t, sender1Balance, sender1Data.MicroAlgos) - sender2Data, _, err := l.LookupWithoutRewards(l.Latest(), sender2) + sender2Data, _, err := l.LookupWithoutRewards(l.Latest(), sender2.Addr) require.NoError(t, err) require.Equal(t, sender2Balance, sender2Data.MicroAlgos) // Should now pass - result, err = s.Simulate(simulation.Request{TxnGroups: [][]transactions.SignedTxn{txgroup}}) + result, err = s.Simulate(request) require.NoError(t, err) - require.False(t, result.WouldSucceed) require.Len(t, result.TxnGroups, 1) require.Len(t, result.TxnGroups[0].Txns, 2) require.Zero(t, result.TxnGroups[0].FailureMessage) // Confirm balances have not changed - sender1Data, _, err = l.LookupWithoutRewards(l.Latest(), sender1) + sender1Data, _, err = l.LookupWithoutRewards(l.Latest(), sender1.Addr) require.NoError(t, err) require.Equal(t, sender1Balance, sender1Data.MicroAlgos) - sender2Data, _, err = l.LookupWithoutRewards(l.Latest(), sender2) + sender2Data, _, err = l.LookupWithoutRewards(l.Latest(), sender2.Addr) require.NoError(t, err) require.Equal(t, sender2Balance, sender2Data.MicroAlgos) } @@ -614,10 +546,14 @@ int 1`, } return simulationTestCase{ - input: []transactions.SignedTxn{signedPayTxn, signedAppCallTxn}, + input: simulation.Request{ + TxnGroups: [][]transactions.SignedTxn{ + {signedPayTxn, signedAppCallTxn}, + }, + }, expectedError: testCase.expectedError, expected: simulation.Result{ - Version: 1, + Version: simulation.ResultLatestVersion, LastRound: txnInfo.LatestRound(), TxnGroups: []simulation.TxnGroupResult{ { @@ -636,7 +572,6 @@ int 1`, AppBudgetConsumed: AppBudgetConsumed, }, }, - WouldSucceed: expectedSuccess, }, } }) @@ -647,21 +582,16 @@ int 1`, func TestSimpleAppCall(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() + simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { + sender := accounts[0] - for _, signed := range []bool{true, false} { - signed := signed - t.Run(fmt.Sprintf("signed=%t", signed), func(t *testing.T) { - t.Parallel() - simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { - sender := accounts[0] - - // Create program and call it - futureAppID := basics.AppIndex(1) - createTxn := txnInfo.NewTxn(txntest.Txn{ - Type: protocol.ApplicationCallTx, - Sender: sender.Addr, - ApplicationID: 0, - ApprovalProgram: `#pragma version 6 + // Create program and call it + futureAppID := basics.AppIndex(1) + createTxn := txnInfo.NewTxn(txntest.Txn{ + Type: protocol.ApplicationCallTx, + Sender: sender.Addr, + ApplicationID: 0, + ApprovalProgram: `#pragma version 6 txn ApplicationID bz create byte "app call" @@ -673,202 +603,172 @@ log end: int 1 `, - ClearStateProgram: `#pragma version 6 + ClearStateProgram: `#pragma version 6 int 0 `, - }) - callTxn := txnInfo.NewTxn(txntest.Txn{ - Type: protocol.ApplicationCallTx, - Sender: sender.Addr, - ApplicationID: futureAppID, - }) - - txntest.Group(&createTxn, &callTxn) + }) + callTxn := txnInfo.NewTxn(txntest.Txn{ + Type: protocol.ApplicationCallTx, + Sender: sender.Addr, + ApplicationID: futureAppID, + }) - signedCreateTxn := createTxn.SignedTxn() - signedCallTxn := callTxn.SignedTxn() + txntest.Group(&createTxn, &callTxn) - if signed { - signedCreateTxn = signedCreateTxn.Txn.Sign(sender.Sk) - signedCallTxn = signedCallTxn.Txn.Sign(sender.Sk) - } + signedCreateTxn := createTxn.Txn().Sign(sender.Sk) + signedCallTxn := callTxn.Txn().Sign(sender.Sk) - return simulationTestCase{ - input: []transactions.SignedTxn{signedCreateTxn, signedCallTxn}, - expected: simulation.Result{ - Version: 1, - LastRound: txnInfo.LatestRound(), - TxnGroups: []simulation.TxnGroupResult{ + return simulationTestCase{ + input: simulation.Request{ + TxnGroups: [][]transactions.SignedTxn{ + {signedCreateTxn, signedCallTxn}, + }, + }, + expected: simulation.Result{ + Version: simulation.ResultLatestVersion, + LastRound: txnInfo.LatestRound(), + TxnGroups: []simulation.TxnGroupResult{ + { + Txns: []simulation.TxnResult{ { - Txns: []simulation.TxnResult{ - { - Txn: transactions.SignedTxnWithAD{ - ApplyData: transactions.ApplyData{ - ApplicationID: futureAppID, - EvalDelta: transactions.EvalDelta{ - Logs: []string{"app creation"}, - }, - }, + Txn: transactions.SignedTxnWithAD{ + ApplyData: transactions.ApplyData{ + ApplicationID: futureAppID, + EvalDelta: transactions.EvalDelta{ + Logs: []string{"app creation"}, }, - MissingSignature: !signed, - AppBudgetConsumed: 5, }, - { - Txn: transactions.SignedTxnWithAD{ - ApplyData: transactions.ApplyData{ - EvalDelta: transactions.EvalDelta{ - Logs: []string{"app call"}, - }, - }, + }, + AppBudgetConsumed: 5, + }, + { + Txn: transactions.SignedTxnWithAD{ + ApplyData: transactions.ApplyData{ + EvalDelta: transactions.EvalDelta{ + Logs: []string{"app call"}, }, - MissingSignature: !signed, - AppBudgetConsumed: 6, }, }, - AppBudgetAdded: 1400, - AppBudgetConsumed: 11, + AppBudgetConsumed: 6, }, }, - WouldSucceed: signed, + AppBudgetAdded: 1400, + AppBudgetConsumed: 11, }, - } - }) - }) - } + }, + }, + } + }) } func TestRejectAppCall(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() + simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { + sender := accounts[0] - for _, signed := range []bool{true, false} { - signed := signed - t.Run(fmt.Sprintf("signed=%t", signed), func(t *testing.T) { - t.Parallel() - simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { - sender := accounts[0] - - futureAppID := basics.AppIndex(1) - createTxn := txnInfo.NewTxn(txntest.Txn{ - Type: protocol.ApplicationCallTx, - Sender: sender.Addr, - ApplicationID: 0, - ApprovalProgram: `#pragma version 6 + futureAppID := basics.AppIndex(1) + createTxn := txnInfo.NewTxn(txntest.Txn{ + Type: protocol.ApplicationCallTx, + Sender: sender.Addr, + ApplicationID: 0, + ApprovalProgram: `#pragma version 6 byte "app creation" log int 0 `, - ClearStateProgram: `#pragma version 6 + ClearStateProgram: `#pragma version 6 int 0 `, - }) - - signedCreateTxn := createTxn.SignedTxn() - - if signed { - signedCreateTxn = createTxn.Txn().Sign(sender.Sk) - } + }) + signedCreateTxn := createTxn.Txn().Sign(sender.Sk) - return simulationTestCase{ - input: []transactions.SignedTxn{signedCreateTxn}, - expectedError: "transaction rejected by ApprovalProgram", - expected: simulation.Result{ - Version: 1, - LastRound: txnInfo.LatestRound(), - TxnGroups: []simulation.TxnGroupResult{ + return simulationTestCase{ + input: simulation.Request{ + TxnGroups: [][]transactions.SignedTxn{{signedCreateTxn}}, + }, + expectedError: "transaction rejected by ApprovalProgram", + expected: simulation.Result{ + Version: simulation.ResultLatestVersion, + LastRound: txnInfo.LatestRound(), + TxnGroups: []simulation.TxnGroupResult{ + { + Txns: []simulation.TxnResult{ { - Txns: []simulation.TxnResult{ - { - Txn: transactions.SignedTxnWithAD{ - ApplyData: transactions.ApplyData{ - ApplicationID: futureAppID, - EvalDelta: transactions.EvalDelta{ - Logs: []string{"app creation"}, - }, - }, + Txn: transactions.SignedTxnWithAD{ + ApplyData: transactions.ApplyData{ + ApplicationID: futureAppID, + EvalDelta: transactions.EvalDelta{ + Logs: []string{"app creation"}, }, - MissingSignature: !signed, - AppBudgetConsumed: 3, }, }, - FailedAt: simulation.TxnPath{0}, - AppBudgetAdded: 700, AppBudgetConsumed: 3, }, }, - WouldSucceed: false, + FailedAt: simulation.TxnPath{0}, + AppBudgetAdded: 700, + AppBudgetConsumed: 3, }, - } - }) - }) - } + }, + }, + } + }) } func TestErrorAppCall(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() + simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { + sender := accounts[0] - for _, signed := range []bool{true, false} { - signed := signed - t.Run(fmt.Sprintf("signed=%t", signed), func(t *testing.T) { - t.Parallel() - simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { - sender := accounts[0] - - futureAppID := basics.AppIndex(1) - createTxn := txnInfo.NewTxn(txntest.Txn{ - Type: protocol.ApplicationCallTx, - Sender: sender.Addr, - ApplicationID: 0, - ApprovalProgram: `#pragma version 6 + futureAppID := basics.AppIndex(1) + createTxn := txnInfo.NewTxn(txntest.Txn{ + Type: protocol.ApplicationCallTx, + Sender: sender.Addr, + ApplicationID: 0, + ApprovalProgram: `#pragma version 6 byte "app creation" log err `, - ClearStateProgram: `#pragma version 6 + ClearStateProgram: `#pragma version 6 int 0 `, - }) - - signedCreateTxn := createTxn.SignedTxn() - - if signed { - signedCreateTxn = createTxn.Txn().Sign(sender.Sk) - } + }) + signedCreateTxn := createTxn.Txn().Sign(sender.Sk) - return simulationTestCase{ - input: []transactions.SignedTxn{signedCreateTxn}, - expectedError: "err opcode executed", - expected: simulation.Result{ - Version: 1, - LastRound: txnInfo.LatestRound(), - TxnGroups: []simulation.TxnGroupResult{ + return simulationTestCase{ + input: simulation.Request{ + TxnGroups: [][]transactions.SignedTxn{{signedCreateTxn}}, + }, + expectedError: "err opcode executed", + expected: simulation.Result{ + Version: simulation.ResultLatestVersion, + LastRound: txnInfo.LatestRound(), + TxnGroups: []simulation.TxnGroupResult{ + { + Txns: []simulation.TxnResult{ { - Txns: []simulation.TxnResult{ - { - Txn: transactions.SignedTxnWithAD{ - ApplyData: transactions.ApplyData{ - ApplicationID: futureAppID, - EvalDelta: transactions.EvalDelta{ - Logs: []string{"app creation"}, - }, - }, + Txn: transactions.SignedTxnWithAD{ + ApplyData: transactions.ApplyData{ + ApplicationID: futureAppID, + EvalDelta: transactions.EvalDelta{ + Logs: []string{"app creation"}, }, - MissingSignature: !signed, - AppBudgetConsumed: 3, }, }, - FailedAt: simulation.TxnPath{0}, - AppBudgetAdded: 700, AppBudgetConsumed: 3, }, }, - WouldSucceed: false, + FailedAt: simulation.TxnPath{0}, + AppBudgetAdded: 700, + AppBudgetConsumed: 3, }, - } - }) - }) - } + }, + }, + } + }) } func TestAppCallOverBudget(t *testing.T) { @@ -914,10 +814,14 @@ int 0 signedExpensiveTxn := expensiveTxn.Txn().Sign(sender.Sk) return simulationTestCase{ - input: []transactions.SignedTxn{signedCreateTxn, signedExpensiveTxn}, + input: simulation.Request{ + TxnGroups: [][]transactions.SignedTxn{ + {signedCreateTxn, signedExpensiveTxn}, + }, + }, expectedError: "dynamic cost budget exceeded", expected: simulation.Result{ - Version: 1, + Version: simulation.ResultLatestVersion, LastRound: txnInfo.LatestRound(), TxnGroups: []simulation.TxnGroupResult{ { @@ -939,7 +843,6 @@ int 0 AppBudgetConsumed: 1400, }, }, - WouldSucceed: false, }, } }) @@ -991,10 +894,14 @@ int 1`, // Opcode cost exceeded, but report current cost of LogicSig before it went over the limit. return simulationTestCase{ - input: []transactions.SignedTxn{signedPayTxn, signedAppCallTxn}, + input: simulation.Request{ + TxnGroups: [][]transactions.SignedTxn{ + {signedPayTxn, signedAppCallTxn}, + }, + }, expectedError: "dynamic cost budget exceeded", expected: simulation.Result{ - Version: 1, + Version: simulation.ResultLatestVersion, LastRound: txnInfo.LatestRound(), TxnGroups: []simulation.TxnGroupResult{ { @@ -1013,7 +920,6 @@ int 1`, AppBudgetConsumed: 0, }, }, - WouldSucceed: false, }, } }) @@ -1066,9 +972,13 @@ int 1`, signedAppCall := appCall.Txn().Sign(sender.Sk) return simulationTestCase{ - input: []transactions.SignedTxn{signedFundTxn, signedAppCall}, + input: simulation.Request{ + TxnGroups: [][]transactions.SignedTxn{ + {signedFundTxn, signedAppCall}, + }, + }, expected: simulation.Result{ - Version: 1, + Version: simulation.ResultLatestVersion, LastRound: txnInfo.LatestRound(), TxnGroups: []simulation.TxnGroupResult{ { @@ -1096,55 +1006,49 @@ int 1`, AppBudgetConsumed: 701, }, }, - WouldSucceed: true, }, } }) } -func TestSignatureCheck(t *testing.T) { +// TestDefaultSignatureCheck tests signature checking when SignaturesOption is NOT enabled. +func TestDefaultSignatureCheck(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() l, accounts, txnInfo := simulationtesting.PrepareSimulatorTest(t) defer l.Close() s := simulation.MakeSimulator(l) - sender := accounts[0].Addr + sender := accounts[0] - txgroup := []transactions.SignedTxn{ - txnInfo.NewTxn(txntest.Txn{ - Type: protocol.PaymentTx, - Sender: sender, - Receiver: sender, - Amount: 0, - }).SignedTxn(), - } + stxn := txnInfo.NewTxn(txntest.Txn{ + Type: protocol.PaymentTx, + Sender: sender.Addr, + Receiver: sender.Addr, + Amount: 0, + }).SignedTxn() - // should catch missing signature - result, err := s.Simulate(simulation.Request{TxnGroups: [][]transactions.SignedTxn{txgroup}}) + // should error on missing signature + result, err := s.Simulate(simulation.Request{TxnGroups: [][]transactions.SignedTxn{{stxn}}}) require.NoError(t, err) - require.False(t, result.WouldSucceed) require.Len(t, result.TxnGroups, 1) require.Len(t, result.TxnGroups[0].Txns, 1) - require.True(t, result.TxnGroups[0].Txns[0].MissingSignature) - require.Zero(t, result.TxnGroups[0].FailureMessage) + require.Contains(t, result.TxnGroups[0].FailureMessage, "signedtxn has no sig") + require.Equal(t, result.TxnGroups[0].FailedAt, simulation.TxnPath{0}) // add signature - signatureSecrets := accounts[0].Sk - txgroup[0] = txgroup[0].Txn.Sign(signatureSecrets) + stxn = stxn.Txn.Sign(sender.Sk) // should not error now that we have a signature - result, err = s.Simulate(simulation.Request{TxnGroups: [][]transactions.SignedTxn{txgroup}}) + result, err = s.Simulate(simulation.Request{TxnGroups: [][]transactions.SignedTxn{{stxn}}}) require.NoError(t, err) - require.True(t, result.WouldSucceed) require.Len(t, result.TxnGroups, 1) require.Len(t, result.TxnGroups[0].Txns, 1) - require.False(t, result.TxnGroups[0].Txns[0].MissingSignature) require.Zero(t, result.TxnGroups[0].FailureMessage) // should error with invalid signature - txgroup[0].Sig[0] += byte(1) // will wrap if > 255 - result, err = s.Simulate(simulation.Request{TxnGroups: [][]transactions.SignedTxn{txgroup}}) + stxn.Sig[0] += byte(1) // will wrap if > 255 + result, err = s.Simulate(simulation.Request{TxnGroups: [][]transactions.SignedTxn{{stxn}}}) require.ErrorAs(t, err, &simulation.InvalidTxGroupError{}) require.ErrorContains(t, err, "one signature didn't pass") } @@ -1154,29 +1058,38 @@ func TestSignatureCheck(t *testing.T) { func TestInvalidTxGroup(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() + simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { + receiver := accounts[0].Addr - l, accounts, txnInfo := simulationtesting.PrepareSimulatorTest(t) - defer l.Close() - s := simulation.MakeSimulator(l) - receiver := accounts[0].Addr - - txgroup := []transactions.SignedTxn{ - txnInfo.NewTxn(txntest.Txn{ - Type: protocol.PaymentTx, + txn := txnInfo.NewTxn(txntest.Txn{ + Type: protocol.PaymentTx, + // should error with invalid transaction group error Sender: ledgertesting.PoolAddr(), Receiver: receiver, Amount: 0, - }).SignedTxn(), - } + }).SignedTxn() - // should error with invalid transaction group error - _, err := s.Simulate(simulation.Request{TxnGroups: [][]transactions.SignedTxn{txgroup}}) - require.ErrorAs(t, err, &simulation.InvalidTxGroupError{}) - require.ErrorContains(t, err, "transaction from incentive pool is invalid") + return simulationTestCase{ + input: simulation.Request{ + TxnGroups: [][]transactions.SignedTxn{{txn}}, + }, + expectedError: "transaction from incentive pool is invalid", + expected: simulation.Result{ + Version: simulation.ResultLatestVersion, + LastRound: txnInfo.LatestRound(), + TxnGroups: []simulation.TxnGroupResult{ + { + FailedAt: simulation.TxnPath{0}, + Txns: []simulation.TxnResult{{}}, + }, + }, + }, + } + }) } // TestLogLimitLiftingInSimulation tests that an app with log calls that exceed limits during normal runtime -// can get through during simulation with `lift-log-limits` activated +// can get through during simulation with AllowMoreLogging activated func TestLogLimitLiftingInSimulation(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() @@ -1194,112 +1107,81 @@ log `, LogLongLine), LogTimes) + `final: int 1` - l, accounts, txnInfo := simulationtesting.PrepareSimulatorTest(t) - defer l.Close() - s := simulation.MakeSimulator(l) - - sender := accounts[0] - receiver := accounts[1] - - futureAppID := basics.AppIndex(1) - - createTxn := txnInfo.NewTxn(txntest.Txn{ - Type: protocol.ApplicationCallTx, - Sender: sender.Addr, - ApplicationID: 0, - ApprovalProgram: appSourceThatLogsALot, - ClearStateProgram: "#pragma version 8\nint 1", - }) + simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { + sender := accounts[0] + receiver := accounts[1] - callsABunchLogs := txnInfo.NewTxn(txntest.Txn{ - Type: protocol.ApplicationCallTx, - Sender: sender.Addr, - ApplicationID: futureAppID, - Accounts: []basics.Address{receiver.Addr}, - ApplicationArgs: [][]byte{[]byte("first-arg")}, - }) + futureAppID := basics.AppIndex(1) - txntest.Group(&createTxn, &callsABunchLogs) + createTxn := txnInfo.NewTxn(txntest.Txn{ + Type: protocol.ApplicationCallTx, + Sender: sender.Addr, + ApplicationID: 0, + ApprovalProgram: appSourceThatLogsALot, + ClearStateProgram: "#pragma version 8\nint 1", + }) - signedCreateTxn := createTxn.Txn().Sign(sender.Sk) - signedCallsABunchLogs := callsABunchLogs.Txn().Sign(sender.Sk) + callsABunchLogs := txnInfo.NewTxn(txntest.Txn{ + Type: protocol.ApplicationCallTx, + Sender: sender.Addr, + ApplicationID: futureAppID, + Accounts: []basics.Address{receiver.Addr}, + ApplicationArgs: [][]byte{[]byte("first-arg")}, + }) - actual, err := s.Simulate( - simulation.Request{ - TxnGroups: [][]transactions.SignedTxn{{signedCreateTxn, signedCallsABunchLogs}}, - LiftLogLimits: true, - }, - ) - require.NoError(t, err) + txntest.Group(&createTxn, &callsABunchLogs) - validateSimulationResult(t, actual) + signedCreateTxn := createTxn.Txn().Sign(sender.Sk) + signedCallsABunchLogs := callsABunchLogs.Txn().Sign(sender.Sk) - expectedMaxLogCalls, expectedMaxLogSize := uint64(2048), uint64(65536) - expectedLog := make([]string, LogTimes) - for i := 0; i < LogTimes; i++ { - expectedLog[i] = LogLongLine - } - testcase := simulationTestCase{ - input: []transactions.SignedTxn{signedCreateTxn, signedCallsABunchLogs}, - expected: simulation.Result{ - Version: 1, - LastRound: txnInfo.LatestRound(), - TxnGroups: []simulation.TxnGroupResult{ - { - Txns: []simulation.TxnResult{ - { - Txn: transactions.SignedTxnWithAD{ - ApplyData: transactions.ApplyData{ - ApplicationID: futureAppID, + expectedMaxLogCalls, expectedMaxLogSize := uint64(2048), uint64(65536) + expectedLog := make([]string, LogTimes) + for i := 0; i < LogTimes; i++ { + expectedLog[i] = LogLongLine + } + return simulationTestCase{ + input: simulation.Request{ + TxnGroups: [][]transactions.SignedTxn{ + {signedCreateTxn, signedCallsABunchLogs}, + }, + AllowMoreLogging: true, + }, + expected: simulation.Result{ + Version: simulation.ResultLatestVersion, + LastRound: txnInfo.LatestRound(), + TxnGroups: []simulation.TxnGroupResult{ + { + Txns: []simulation.TxnResult{ + { + Txn: transactions.SignedTxnWithAD{ + ApplyData: transactions.ApplyData{ + ApplicationID: futureAppID, + }, }, + AppBudgetConsumed: 6, }, - AppBudgetConsumed: 6, - }, - { - Txn: transactions.SignedTxnWithAD{ - ApplyData: transactions.ApplyData{ - EvalDelta: transactions.EvalDelta{ - Logs: expectedLog, + { + Txn: transactions.SignedTxnWithAD{ + ApplyData: transactions.ApplyData{ + EvalDelta: transactions.EvalDelta{ + Logs: expectedLog, + }, }, }, + AppBudgetConsumed: 86, }, - AppBudgetConsumed: 86, }, + AppBudgetAdded: 1400, + AppBudgetConsumed: 92, }, - AppBudgetAdded: 1400, - AppBudgetConsumed: 92, + }, + EvalOverrides: simulation.ResultEvalOverrides{ + MaxLogCalls: &expectedMaxLogCalls, + MaxLogSize: &expectedMaxLogSize, }, }, - EvalOverrides: simulation.ResultEvalOverrides{ - MaxLogCalls: &expectedMaxLogCalls, - MaxLogSize: &expectedMaxLogSize, - }, - WouldSucceed: true, - }, - } - - require.Len(t, testcase.expected.TxnGroups, 1, "Test case must expect a single txn group") - require.Len(t, testcase.expected.TxnGroups[0].Txns, len(testcase.input), "Test case expected a different number of transactions than its input") - - for i, inputTxn := range testcase.input { - if testcase.expected.TxnGroups[0].Txns[i].Txn.Txn.Type == "" { - // Use Type as a marker for whether the transaction was specified or not. If not - // specified, replace it with the input txn - testcase.expected.TxnGroups[0].Txns[i].Txn.SignedTxn = inputTxn } - normalizeEvalDeltas(t, &actual.TxnGroups[0].Txns[i].Txn.EvalDelta, &testcase.expected.TxnGroups[0].Txns[i].Txn.EvalDelta) - } - - if len(testcase.expectedError) != 0 { - require.Contains(t, actual.TxnGroups[0].FailureMessage, testcase.expectedError) - require.False(t, testcase.expected.WouldSucceed, "Test case WouldSucceed value is not consistent with expected failure") - // if it matched the expected error, copy the actual one so it will pass the equality check below - testcase.expected.TxnGroups[0].FailureMessage = actual.TxnGroups[0].FailureMessage - } - - // Do not attempt to compare blocks - actual.Block = nil - require.Equal(t, testcase.expected, actual) + }) } func TestLogSizeExceedWithLiftInSimulation(t *testing.T) { @@ -1319,115 +1201,84 @@ log `, LogLongLine), LogTimes) + `final: int 1` - l, accounts, txnInfo := simulationtesting.PrepareSimulatorTest(t) - defer l.Close() - s := simulation.MakeSimulator(l) - - sender := accounts[0] - receiver := accounts[1] - - futureAppID := basics.AppIndex(1) - - createTxn := txnInfo.NewTxn(txntest.Txn{ - Type: protocol.ApplicationCallTx, - Sender: sender.Addr, - ApplicationID: 0, - ApprovalProgram: appSourceThatLogsALot, - ClearStateProgram: "#pragma version 8\nint 1", - }) + simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { + sender := accounts[0] + receiver := accounts[1] - callsABunchLogs := txnInfo.NewTxn(txntest.Txn{ - Type: protocol.ApplicationCallTx, - Sender: sender.Addr, - ApplicationID: futureAppID, - Accounts: []basics.Address{receiver.Addr}, - ApplicationArgs: [][]byte{[]byte("first-arg")}, - }) + futureAppID := basics.AppIndex(1) - txntest.Group(&createTxn, &callsABunchLogs) + createTxn := txnInfo.NewTxn(txntest.Txn{ + Type: protocol.ApplicationCallTx, + Sender: sender.Addr, + ApplicationID: 0, + ApprovalProgram: appSourceThatLogsALot, + ClearStateProgram: "#pragma version 8\nint 1", + }) - signedCreateTxn := createTxn.Txn().Sign(sender.Sk) - signedCallsABunchLogs := callsABunchLogs.Txn().Sign(sender.Sk) + callsABunchLogs := txnInfo.NewTxn(txntest.Txn{ + Type: protocol.ApplicationCallTx, + Sender: sender.Addr, + ApplicationID: futureAppID, + Accounts: []basics.Address{receiver.Addr}, + ApplicationArgs: [][]byte{[]byte("first-arg")}, + }) - actual, err := s.Simulate( - simulation.Request{ - TxnGroups: [][]transactions.SignedTxn{{signedCreateTxn, signedCallsABunchLogs}}, - LiftLogLimits: true, - }, - ) - require.NoError(t, err) + txntest.Group(&createTxn, &callsABunchLogs) - validateSimulationResult(t, actual) + signedCreateTxn := createTxn.Txn().Sign(sender.Sk) + signedCallsABunchLogs := callsABunchLogs.Txn().Sign(sender.Sk) - expectedMaxLogCalls, expectedMaxLogSize := uint64(2048), uint64(65536) - actualLogTimes := 65536 / len(LogLongLine) - expectedLog := make([]string, actualLogTimes) - for i := 0; i < actualLogTimes; i++ { - expectedLog[i] = LogLongLine - } - testcase := simulationTestCase{ - input: []transactions.SignedTxn{signedCreateTxn, signedCallsABunchLogs}, - expected: simulation.Result{ - Version: 1, - LastRound: txnInfo.LatestRound(), - TxnGroups: []simulation.TxnGroupResult{ - { - FailedAt: simulation.TxnPath{1}, - Txns: []simulation.TxnResult{ - { - Txn: transactions.SignedTxnWithAD{ - ApplyData: transactions.ApplyData{ - ApplicationID: futureAppID, + expectedMaxLogCalls, expectedMaxLogSize := uint64(2048), uint64(65536) + actualLogTimes := 65536 / len(LogLongLine) + expectedLog := make([]string, actualLogTimes) + for i := 0; i < actualLogTimes; i++ { + expectedLog[i] = LogLongLine + } + return simulationTestCase{ + input: simulation.Request{ + TxnGroups: [][]transactions.SignedTxn{ + {signedCreateTxn, signedCallsABunchLogs}, + }, + AllowMoreLogging: true, + }, + expected: simulation.Result{ + Version: simulation.ResultLatestVersion, + LastRound: txnInfo.LatestRound(), + TxnGroups: []simulation.TxnGroupResult{ + { + FailedAt: simulation.TxnPath{1}, + Txns: []simulation.TxnResult{ + { + Txn: transactions.SignedTxnWithAD{ + ApplyData: transactions.ApplyData{ + ApplicationID: futureAppID, + }, }, + AppBudgetConsumed: 6, }, - AppBudgetConsumed: 6, - }, - { - Txn: transactions.SignedTxnWithAD{ - ApplyData: transactions.ApplyData{ - EvalDelta: transactions.EvalDelta{ - Logs: expectedLog, + { + Txn: transactions.SignedTxnWithAD{ + ApplyData: transactions.ApplyData{ + EvalDelta: transactions.EvalDelta{ + Logs: expectedLog, + }, }, }, + AppBudgetConsumed: 131, }, - AppBudgetConsumed: 131, }, + AppBudgetAdded: 1400, + AppBudgetConsumed: 137, }, - AppBudgetAdded: 1400, - AppBudgetConsumed: 137, + }, + EvalOverrides: simulation.ResultEvalOverrides{ + MaxLogCalls: &expectedMaxLogCalls, + MaxLogSize: &expectedMaxLogSize, }, }, - EvalOverrides: simulation.ResultEvalOverrides{ - MaxLogCalls: &expectedMaxLogCalls, - MaxLogSize: &expectedMaxLogSize, - }, - WouldSucceed: false, - }, - expectedError: "logic eval error: program logs too large. 66150 bytes > 65536 bytes limit.", - } - - require.Len(t, testcase.expected.TxnGroups, 1, "Test case must expect a single txn group") - require.Len(t, testcase.expected.TxnGroups[0].Txns, len(testcase.input), "Test case expected a different number of transactions than its input") - - for i, inputTxn := range testcase.input { - if testcase.expected.TxnGroups[0].Txns[i].Txn.Txn.Type == "" { - // Use Type as a marker for whether the transaction was specified or not. If not - // specified, replace it with the input txn - testcase.expected.TxnGroups[0].Txns[i].Txn.SignedTxn = inputTxn + expectedError: "logic eval error: program logs too large. 66150 bytes > 65536 bytes limit.", } - normalizeEvalDeltas(t, &actual.TxnGroups[0].Txns[i].Txn.EvalDelta, &testcase.expected.TxnGroups[0].Txns[i].Txn.EvalDelta) - } - - if len(testcase.expectedError) != 0 { - require.Contains(t, actual.TxnGroups[0].FailureMessage, testcase.expectedError) - require.False(t, testcase.expected.WouldSucceed, "Test case WouldSucceed value is not consistent with expected failure") - // if it matched the expected error, copy the actual one so it will pass the equality check below - testcase.expected.TxnGroups[0].FailureMessage = actual.TxnGroups[0].FailureMessage - } - - // Do not attempt to compare blocks - actual.Block = nil - require.Equal(t, testcase.expected, actual) + }) } // TestBalanceChangesWithApp sends a payment transaction to a new account and confirms its balance @@ -1435,23 +1286,18 @@ int 1` func TestBalanceChangesWithApp(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() + simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { + sender := accounts[0] + senderBalance := sender.AcctData.MicroAlgos.Raw + sendAmount := senderBalance - 500_000 // Leave 0.5 Algos in the sender account + receiver := accounts[1] + receiverBalance := receiver.AcctData.MicroAlgos.Raw - for _, signed := range []bool{true, false} { - signed := signed - t.Run(fmt.Sprintf("signed=%t", signed), func(t *testing.T) { - t.Parallel() - simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { - sender := accounts[0] - senderBalance := sender.AcctData.MicroAlgos.Raw - sendAmount := senderBalance - 500_000 // Leave 0.5 Algos in the sender account - receiver := accounts[1] - receiverBalance := receiver.AcctData.MicroAlgos.Raw - - futureAppID := basics.AppIndex(1) - createTxn := txnInfo.NewTxn(txntest.Txn{ - Type: protocol.ApplicationCallTx, - Sender: sender.Addr, - ApprovalProgram: `#pragma version 6 + futureAppID := basics.AppIndex(1) + createTxn := txnInfo.NewTxn(txntest.Txn{ + Type: protocol.ApplicationCallTx, + Sender: sender.Addr, + ApprovalProgram: `#pragma version 6 txn ApplicationID // [appId] bz end // [] int 1 // [1] @@ -1463,84 +1309,122 @@ assert end: int 1 // [1] `, - ClearStateProgram: `#pragma version 6 + ClearStateProgram: `#pragma version 6 int 1`, - }) - checkStartingBalanceTxn := txnInfo.NewTxn(txntest.Txn{ - Type: protocol.ApplicationCallTx, - Sender: sender.Addr, - ApplicationID: futureAppID, - Accounts: []basics.Address{receiver.Addr}, - ApplicationArgs: [][]byte{uint64ToBytes(receiverBalance)}, - }) - paymentTxn := txnInfo.NewTxn(txntest.Txn{ + }) + checkStartingBalanceTxn := txnInfo.NewTxn(txntest.Txn{ + Type: protocol.ApplicationCallTx, + Sender: sender.Addr, + ApplicationID: futureAppID, + Accounts: []basics.Address{receiver.Addr}, + ApplicationArgs: [][]byte{uint64ToBytes(receiverBalance)}, + }) + paymentTxn := txnInfo.NewTxn(txntest.Txn{ + Type: protocol.PaymentTx, + Sender: sender.Addr, + Receiver: receiver.Addr, + Amount: sendAmount, + }) + checkEndingBalanceTxn := txnInfo.NewTxn(txntest.Txn{ + Type: protocol.ApplicationCallTx, + Sender: sender.Addr, + ApplicationID: futureAppID, + Accounts: []basics.Address{receiver.Addr}, + // Receiver's balance should have increased by sendAmount + ApplicationArgs: [][]byte{uint64ToBytes(receiverBalance + sendAmount)}, + }) + + txntest.Group(&createTxn, &checkStartingBalanceTxn, &paymentTxn, &checkEndingBalanceTxn) + + signedCreateTxn := createTxn.Txn().Sign(sender.Sk) + signedCheckStartingBalanceTxn := checkStartingBalanceTxn.Txn().Sign(sender.Sk) + signedPaymentTxn := paymentTxn.Txn().Sign(sender.Sk) + signedCheckEndingBalanceTxn := checkEndingBalanceTxn.Txn().Sign(sender.Sk) + + return simulationTestCase{ + input: simulation.Request{ + TxnGroups: [][]transactions.SignedTxn{ + { + signedCreateTxn, + signedCheckStartingBalanceTxn, + signedPaymentTxn, + signedCheckEndingBalanceTxn, + }, + }, + }, + expected: simulation.Result{ + Version: simulation.ResultLatestVersion, + LastRound: txnInfo.LatestRound(), + TxnGroups: []simulation.TxnGroupResult{ + { + Txns: []simulation.TxnResult{ + { + Txn: transactions.SignedTxnWithAD{ + ApplyData: transactions.ApplyData{ + ApplicationID: futureAppID, + }, + }, + AppBudgetConsumed: 4, + }, + { + AppBudgetConsumed: 10, + }, + {}, + { + AppBudgetConsumed: 10, + }, + }, + AppBudgetAdded: 2100, + AppBudgetConsumed: 24, + }, + }, + }, + } + }) +} + +// TestOptionalSignatures tests that transactions with signatures and without signatures are both +// properly handled when AllowEmptySignatures is enabled. +func TestOptionalSignatures(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + for _, signed := range []bool{true, false} { + signed := signed + t.Run(fmt.Sprintf("signed=%t", signed), func(t *testing.T) { + simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { + sender := accounts[0] + + txn := txnInfo.NewTxn(txntest.Txn{ Type: protocol.PaymentTx, Sender: sender.Addr, - Receiver: receiver.Addr, - Amount: sendAmount, - }) - checkEndingBalanceTxn := txnInfo.NewTxn(txntest.Txn{ - Type: protocol.ApplicationCallTx, - Sender: sender.Addr, - ApplicationID: futureAppID, - Accounts: []basics.Address{receiver.Addr}, - // Receiver's balance should have increased by sendAmount - ApplicationArgs: [][]byte{uint64ToBytes(receiverBalance + sendAmount)}, + Receiver: sender.Addr, + Amount: 1, }) - txntest.Group(&createTxn, &checkStartingBalanceTxn, &paymentTxn, &checkEndingBalanceTxn) - - signedCreateTxn := createTxn.SignedTxn() - signedCheckStartingBalanceTxn := checkStartingBalanceTxn.SignedTxn() - signedPaymentTxn := paymentTxn.SignedTxn() - signedCheckEndingBalanceTxn := checkEndingBalanceTxn.SignedTxn() - + var stxn transactions.SignedTxn if signed { - signedCreateTxn = createTxn.Txn().Sign(sender.Sk) - signedCheckStartingBalanceTxn = checkStartingBalanceTxn.Txn().Sign(sender.Sk) - signedPaymentTxn = paymentTxn.Txn().Sign(sender.Sk) - signedCheckEndingBalanceTxn = checkEndingBalanceTxn.Txn().Sign(sender.Sk) + stxn = txn.Txn().Sign(sender.Sk) + } else { + // no signature is included + stxn = txn.SignedTxn() } return simulationTestCase{ - input: []transactions.SignedTxn{ - signedCreateTxn, - signedCheckStartingBalanceTxn, - signedPaymentTxn, - signedCheckEndingBalanceTxn, + input: simulation.Request{ + TxnGroups: [][]transactions.SignedTxn{{stxn}}, + AllowEmptySignatures: true, }, expected: simulation.Result{ - Version: 1, + Version: simulation.ResultLatestVersion, LastRound: txnInfo.LatestRound(), TxnGroups: []simulation.TxnGroupResult{ { - Txns: []simulation.TxnResult{ - { - Txn: transactions.SignedTxnWithAD{ - ApplyData: transactions.ApplyData{ - ApplicationID: futureAppID, - }, - }, - MissingSignature: !signed, - AppBudgetConsumed: 4, - }, - { - MissingSignature: !signed, - AppBudgetConsumed: 10, - }, - { - MissingSignature: !signed, - }, - { - MissingSignature: !signed, - AppBudgetConsumed: 10, - }, - }, - AppBudgetAdded: 2100, - AppBudgetConsumed: 24, + Txns: []simulation.TxnResult{{}}, }, }, - WouldSucceed: signed, + EvalOverrides: simulation.ResultEvalOverrides{ + AllowEmptySignatures: true, + }, }, } }) @@ -1548,10 +1432,36 @@ int 1`, } } -func TestPartialMissingSignatures(t *testing.T) { +// TestOptionalSignaturesIncorrect tests that an incorrect signature still fails when +// AllowEmptySignatures is enabled. +func TestOptionalSignaturesIncorrect(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() + l, accounts, txnInfo := simulationtesting.PrepareSimulatorTest(t) + defer l.Close() + s := simulation.MakeSimulator(l) + sender := accounts[0] + + stxn := txnInfo.NewTxn(txntest.Txn{ + Type: protocol.PaymentTx, + Sender: sender.Addr, + Receiver: sender.Addr, + Amount: 0, + }).Txn().Sign(sender.Sk) + + // should error with invalid signature + stxn.Sig[0] += byte(1) // will wrap if > 255 + _, err := s.Simulate(simulation.Request{TxnGroups: [][]transactions.SignedTxn{{stxn}}}) + require.ErrorAs(t, err, &simulation.InvalidTxGroupError{}) + require.ErrorContains(t, err, "one signature didn't pass") +} + +// TestPartialMissingSignatures tests that a group of transactions with some signatures missing is +// handled properly when AllowEmptySignatures is enabled. +func TestPartialMissingSignatures(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { sender := accounts[0] @@ -1583,15 +1493,19 @@ func TestPartialMissingSignatures(t *testing.T) { signedTxn2 := txn2.Txn().Sign(sender.Sk) return simulationTestCase{ - input: []transactions.SignedTxn{signedTxn1, signedTxn2}, + input: simulation.Request{ + TxnGroups: [][]transactions.SignedTxn{ + {signedTxn1, signedTxn2}, + }, + AllowEmptySignatures: true, + }, expected: simulation.Result{ - Version: 1, + Version: simulation.ResultLatestVersion, LastRound: txnInfo.LatestRound(), TxnGroups: []simulation.TxnGroupResult{ { Txns: []simulation.TxnResult{ { - MissingSignature: true, Txn: transactions.SignedTxnWithAD{ ApplyData: transactions.ApplyData{ ConfigAsset: 1, @@ -1607,20 +1521,22 @@ func TestPartialMissingSignatures(t *testing.T) { }, }, }, - WouldSucceed: false, + EvalOverrides: simulation.ResultEvalOverrides{ + AllowEmptySignatures: true, + }, }, } }) } // TestPooledFeesAcrossSignedAndUnsigned tests that the simulator's transaction group checks -// allow for pooled fees across a mix of signed and unsigned transactions. -// Transaction 1 is a signed transaction with not enough fees paid on its own. -// Transaction 2 is an unsigned transaction with enough fees paid to cover transaction 1. +// allow for pooled fees across a mix of signed and unsigned transactions when AllowEmptySignatures is +// enabled. +// Transaction 1 is a signed transaction with not enough fees paid on its own. +// Transaction 2 is an unsigned transaction with enough fees paid to cover transaction 1. func TestPooledFeesAcrossSignedAndUnsigned(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { sender1 := accounts[0] sender2 := accounts[1] @@ -1647,20 +1563,25 @@ func TestPooledFeesAcrossSignedAndUnsigned(t *testing.T) { signedPay2 := pay2.SignedTxn() return simulationTestCase{ - input: []transactions.SignedTxn{signedPay1, signedPay2}, + input: simulation.Request{ + TxnGroups: [][]transactions.SignedTxn{ + {signedPay1, signedPay2}, + }, + AllowEmptySignatures: true, + }, expected: simulation.Result{ - Version: 1, + Version: simulation.ResultLatestVersion, LastRound: txnInfo.LatestRound(), TxnGroups: []simulation.TxnGroupResult{ { Txns: []simulation.TxnResult{ - {}, { - MissingSignature: true, - }, + {}, {}, }, }, }, - WouldSucceed: false, + EvalOverrides: simulation.ResultEvalOverrides{ + AllowEmptySignatures: true, + }, }, } }) @@ -1746,20 +1667,24 @@ int 1`, txgroup := txntest.Group(&pay1, &pay2, &appCall) + for i := range txgroup { + txgroup[i] = txgroup[i].Txn.Sign(sender.Sk) + } + return simulationTestCase{ - input: txgroup, + input: simulation.Request{ + TxnGroups: [][]transactions.SignedTxn{txgroup}, + }, expectedError: "rejected by ApprovalProgram", expected: simulation.Result{ - Version: 1, + Version: simulation.ResultLatestVersion, LastRound: txnInfo.LatestRound(), TxnGroups: []simulation.TxnGroupResult{ { Txns: []simulation.TxnResult{ + {}, + {}, { - MissingSignature: true, - }, { - MissingSignature: true, - }, { Txn: transactions.SignedTxnWithAD{ ApplyData: transactions.ApplyData{ ApplicationID: 3, @@ -1789,7 +1714,6 @@ int 1`, }, }, AppBudgetConsumed: 27, - MissingSignature: true, }, }, AppBudgetAdded: 2100, @@ -1797,7 +1721,6 @@ int 1`, FailedAt: simulation.TxnPath{2, 0, 0}, }, }, - WouldSucceed: false, }, } }) @@ -1844,18 +1767,23 @@ int 1`, txgroup := txntest.Group(&pay1, &appCall) + for i := range txgroup { + txgroup[i] = txgroup[i].Txn.Sign(sender.Sk) + } + return simulationTestCase{ - input: txgroup, + input: simulation.Request{ + TxnGroups: [][]transactions.SignedTxn{txgroup}, + }, expectedError: "rejected by ApprovalProgram", expected: simulation.Result{ - Version: 1, + Version: simulation.ResultLatestVersion, LastRound: txnInfo.LatestRound(), TxnGroups: []simulation.TxnGroupResult{ { Txns: []simulation.TxnResult{ + {}, { - MissingSignature: true, - }, { Txn: transactions.SignedTxnWithAD{ ApplyData: transactions.ApplyData{ ApplicationID: 2, @@ -1880,7 +1808,6 @@ int 1`, }, }, AppBudgetConsumed: 23, - MissingSignature: true, }, }, AppBudgetAdded: 2100, @@ -1888,7 +1815,6 @@ int 1`, FailedAt: simulation.TxnPath{1, 1}, }, }, - WouldSucceed: false, }, } }) @@ -1911,7 +1837,6 @@ log func TestInnerTxnNonAppCallFailure(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { sender := accounts[0] @@ -1937,18 +1862,23 @@ int 1`, txgroup := txntest.Group(&pay1, &appCall) + for i := range txgroup { + txgroup[i] = txgroup[i].Txn.Sign(sender.Sk) + } + return simulationTestCase{ - input: txgroup, + input: simulation.Request{ + TxnGroups: [][]transactions.SignedTxn{txgroup}, + }, expectedError: "logic eval error: this transaction should be issued by the manager", expected: simulation.Result{ - Version: 1, + Version: simulation.ResultLatestVersion, LastRound: txnInfo.LatestRound(), TxnGroups: []simulation.TxnGroupResult{ { Txns: []simulation.TxnResult{ + {}, { - MissingSignature: true, - }, { Txn: transactions.SignedTxnWithAD{ ApplyData: transactions.ApplyData{ ApplicationID: 2, @@ -1966,7 +1896,6 @@ int 1`, }, }, AppBudgetConsumed: 17, - MissingSignature: true, }, }, AppBudgetAdded: 2100, @@ -1974,7 +1903,6 @@ int 1`, FailedAt: simulation.TxnPath{1, 1}, }, }, - WouldSucceed: false, }, } }) @@ -2024,7 +1952,7 @@ func TestMockTracerScenarios(t *testing.T) { expectedFailedAt[0]++ } expected := simulation.Result{ - Version: 1, + Version: simulation.ResultLatestVersion, LastRound: txnInfo.LatestRound(), TxnGroups: []simulation.TxnGroupResult{ { @@ -2044,11 +1972,14 @@ func TestMockTracerScenarios(t *testing.T) { }, }, }, - WouldSucceed: scenario.Outcome == mocktracer.ApprovalOutcome, } return simulationTestCase{ - input: []transactions.SignedTxn{signedPayTxn, signedAppCallTxn}, + input: simulation.Request{ + TxnGroups: [][]transactions.SignedTxn{ + {signedPayTxn, signedAppCallTxn}, + }, + }, expectedError: scenario.ExpectedError, expected: expected, } diff --git a/ledger/simulation/simulator.go b/ledger/simulation/simulator.go index cb91c61656..3794b2597f 100644 --- a/ledger/simulation/simulator.go +++ b/ledger/simulation/simulator.go @@ -39,8 +39,9 @@ type simulatorLedger struct { // Request packs simulation related txn-group(s), and configurations that are overlapping the ones in real transactions. type Request struct { - TxnGroups [][]transactions.SignedTxn - LiftLogLimits bool + TxnGroups [][]transactions.SignedTxn + AllowEmptySignatures bool + AllowMoreLogging bool } // Latest is part of the LedgerForSimulator interface. @@ -111,30 +112,27 @@ var proxySigner = crypto.PrivateKey{ // check verifies that the transaction is well-formed and has valid or missing signatures. // An invalid transaction group error is returned if the transaction is not well-formed or there are invalid signatures. // To make things easier, we support submitting unsigned transactions and will respond whether signatures are missing. -func (s Simulator) check(hdr bookkeeping.BlockHeader, txgroup []transactions.SignedTxn, debugger logic.EvalTracer) ([]int, error) { +func (s Simulator) check(hdr bookkeeping.BlockHeader, txgroup []transactions.SignedTxn, tracer logic.EvalTracer, overrides ResultEvalOverrides) error { proxySignerSecrets, err := crypto.SecretKeyToSignatureSecrets(proxySigner) if err != nil { - return nil, err + return err } - // Find and prep any transactions that are missing signatures. We will modify a copy of these - // transactions to pass signature verification. The modifications will not affect the input - // txgroup slice. + // If signaturesOptional is enabled, find and prep any transactions that are missing signatures. + // We will modify a copy of these transactions to pass signature verification. The modifications + // will not affect the input txgroup slice. // // Note: currently we only support missing transaction signatures, but it should be possible to // support unsigned delegated LogicSigs as well. A single-signature unsigned delegated LogicSig // is indistinguishable from an escrow LogicSig, so we would need to decide on another way of // denoting that a LogicSig's delegation signature is omitted, e.g. by setting all the bits of // the signature. - missingSigs := make([]int, 0, len(txgroup)) txnsToVerify := make([]transactions.SignedTxn, len(txgroup)) for i, stxn := range txgroup { if stxn.Txn.Type == protocol.StateProofTx { - return nil, errors.New("cannot simulate StateProof transactions") + return errors.New("cannot simulate StateProof transactions") } - if txnHasNoSignature(stxn) { - missingSigs = append(missingSigs, i) - + if overrides.AllowEmptySignatures && txnHasNoSignature(stxn) { // Replace the signed txn with one signed by the proxySigner. At evaluation this would // raise an error, since the proxySigner's public key likely does not have authority // over the sender's account. However, this will pass validation, since the signature @@ -146,11 +144,11 @@ func (s Simulator) check(hdr bookkeeping.BlockHeader, txgroup []transactions.Sig } // Verify the signed transactions are well-formed and have valid signatures - _, err = verify.TxnGroupWithTracer(txnsToVerify, &hdr, nil, s.ledger, debugger) + _, err = verify.TxnGroupWithTracer(txnsToVerify, &hdr, nil, s.ledger, tracer) if err != nil { err = InvalidTxGroupError{SimulatorError{err}} } - return missingSigs, err + return err } func (s Simulator) evaluate(hdr bookkeeping.BlockHeader, stxns []transactions.SignedTxn, tracer logic.EvalTracer) (*ledgercore.ValidatedBlock, error) { @@ -177,22 +175,22 @@ func (s Simulator) evaluate(hdr bookkeeping.BlockHeader, stxns []transactions.Si return vb, nil } -func (s Simulator) simulateWithTracer(txgroup []transactions.SignedTxn, tracer logic.EvalTracer) (*ledgercore.ValidatedBlock, []int, error) { +func (s Simulator) simulateWithTracer(txgroup []transactions.SignedTxn, tracer logic.EvalTracer, overrides ResultEvalOverrides) (*ledgercore.ValidatedBlock, error) { prevBlockHdr, err := s.ledger.BlockHdr(s.ledger.start) if err != nil { - return nil, nil, err + return nil, err } nextBlock := bookkeeping.MakeBlock(prevBlockHdr) hdr := nextBlock.BlockHeader // check that the transaction is well-formed and mark whether signatures are missing - missingSignatures, err := s.check(hdr, txgroup, tracer) + err = s.check(hdr, txgroup, tracer, overrides) if err != nil { - return nil, missingSignatures, err + return nil, err } vb, err := s.evaluate(hdr, txgroup, tracer) - return vb, missingSignatures, err + return vb, err } // Simulate simulates a transaction group using the simulator. Will error if the transaction group is not well-formed. @@ -207,15 +205,17 @@ func (s Simulator) Simulate(simulateRequest Request) (Result, error) { } } - block, missingSigIndexes, err := s.simulateWithTracer(simulateRequest.TxnGroups[0], simulatorTracer) + block, err := s.simulateWithTracer(simulateRequest.TxnGroups[0], simulatorTracer, simulatorTracer.result.EvalOverrides) if err != nil { - simulatorTracer.result.WouldSucceed = false - - var lsigError verify.LogicSigError + var verifyError *verify.TxGroupError switch { - case errors.As(err, &lsigError): - simulatorTracer.result.TxnGroups[0].FailureMessage = lsigError.Error() - simulatorTracer.result.TxnGroups[0].FailedAt = TxnPath{uint64(lsigError.GroupIndex)} + case errors.As(err, &verifyError): + if verifyError.GroupIndex < 0 { + // This group failed verification, but the problem can't be blamed on a single transaction. + return Result{}, InvalidTxGroupError{SimulatorError{err}} + } + simulatorTracer.result.TxnGroups[0].FailureMessage = verifyError.Error() + simulatorTracer.result.TxnGroups[0].FailedAt = TxnPath{uint64(verifyError.GroupIndex)} case errors.As(err, &EvalFailureError{}): simulatorTracer.result.TxnGroups[0].FailureMessage = err.Error() simulatorTracer.result.TxnGroups[0].FailedAt = simulatorTracer.failedAt @@ -227,12 +227,6 @@ func (s Simulator) Simulate(simulateRequest Request) (Result, error) { simulatorTracer.result.Block = block - // mark whether signatures are missing - for _, index := range missingSigIndexes { - simulatorTracer.result.TxnGroups[0].Txns[index].MissingSignature = true - simulatorTracer.result.WouldSucceed = false - } - // Update total cost by aggregating individual txn costs totalCost := uint64(0) for _, txn := range simulatorTracer.result.TxnGroups[0].Txns { diff --git a/ledger/simulation/simulator_test.go b/ledger/simulation/simulator_test.go index 25d7e18d12..fea209906a 100644 --- a/ledger/simulation/simulator_test.go +++ b/ledger/simulation/simulator_test.go @@ -139,7 +139,7 @@ int 1`, txgroup := []transactions.SignedTxn{signedPayTxn, signedAppCallTxn} mockTracer := &mocktracer.Tracer{} - block, _, err := s.simulateWithTracer(txgroup, mockTracer) + block, err := s.simulateWithTracer(txgroup, mockTracer, ResultEvalOverrides{}) require.NoError(t, err) payset := block.Block().Payset diff --git a/ledger/simulation/trace.go b/ledger/simulation/trace.go index 3def4f85a4..8e24b9c70f 100644 --- a/ledger/simulation/trace.go +++ b/ledger/simulation/trace.go @@ -33,7 +33,6 @@ type TxnPath []uint64 // TxnResult contains the simulation result for a single transaction type TxnResult struct { Txn transactions.SignedTxnWithAD - MissingSignature bool AppBudgetConsumed uint64 LogicSigBudgetConsumed uint64 } @@ -51,9 +50,6 @@ type TxnGroupResult struct { // AppBudgetConsumed is the total opcode cost used for this group AppBudgetConsumed uint64 - - // FeeCredit is the fees left over after covering fees for this group - FeeCredit uint64 } func makeTxnGroupResult(txgroup []transactions.SignedTxn) TxnGroupResult { @@ -67,22 +63,23 @@ func makeTxnGroupResult(txgroup []transactions.SignedTxn) TxnGroupResult { } // ResultLatestVersion is the latest version of the Result struct -const ResultLatestVersion = uint64(1) +const ResultLatestVersion = uint64(2) // ResultEvalOverrides contains the limits and parameters during a call to Simulator.Simulate type ResultEvalOverrides struct { - MaxLogCalls *uint64 - MaxLogSize *uint64 + AllowEmptySignatures bool + MaxLogCalls *uint64 + MaxLogSize *uint64 } -// SimulateLogBytesLimit hardcode limit of how much bytes one can log per transaction during simulation (with lift-log-limits) +// SimulateLogBytesLimit hardcode limit of how much bytes one can log per transaction during simulation (with AllowMoreLogging) const SimulateLogBytesLimit = uint64(65536) -// LiftLogLimits method modify the log limits from lift option: +// AllowMoreLogging method modify the log limits from lift option: // - if lift log limits, then overload result from local Config // - otherwise, set `LogLimits` field to be nil -func (eo ResultEvalOverrides) LiftLogLimits(lift bool) ResultEvalOverrides { - if lift { +func (eo ResultEvalOverrides) AllowMoreLogging(allow bool) ResultEvalOverrides { + if allow { maxLogCalls, maxLogSize := uint64(config.MaxLogCalls), SimulateLogBytesLimit eo.MaxLogCalls = &maxLogCalls eo.MaxLogSize = &maxLogSize @@ -108,7 +105,6 @@ type Result struct { Version uint64 LastRound basics.Round TxnGroups []TxnGroupResult // this is a list so that supporting multiple in the future is not breaking - WouldSucceed bool // true iff no failure message, no missing signatures, and the budget was not exceeded EvalOverrides ResultEvalOverrides Block *ledgercore.ValidatedBlock } @@ -124,14 +120,15 @@ func makeSimulationResultWithVersion(lastRound basics.Round, request Request, ve groups[i] = makeTxnGroupResult(txgroup) } - resultEvalConstants := ResultEvalOverrides{}.LiftLogLimits(request.LiftLogLimits) + resultEvalConstants := ResultEvalOverrides{ + AllowEmptySignatures: request.AllowEmptySignatures, + }.AllowMoreLogging(request.AllowMoreLogging) return Result{ Version: version, LastRound: lastRound, TxnGroups: groups, EvalOverrides: resultEvalConstants, - WouldSucceed: true, }, nil } diff --git a/ledger/simulation/tracer.go b/ledger/simulation/tracer.go index 1a80d2654a..5c2160f43c 100644 --- a/ledger/simulation/tracer.go +++ b/ledger/simulation/tracer.go @@ -138,9 +138,6 @@ func (tracer *evalTracer) BeforeTxnGroup(ep *logic.EvalParams) { if ep.PooledApplicationBudget != nil && tracer.result.TxnGroups[0].AppBudgetAdded == 0 { tracer.result.TxnGroups[0].AppBudgetAdded = uint64(*ep.PooledApplicationBudget) } - if ep.FeeCredit != nil { - tracer.result.TxnGroups[0].FeeCredit = *ep.FeeCredit - } // Override runtime related constraints against ep, before entering txn group ep.EvalConstants = tracer.result.EvalOverrides.LogicEvalConstants() diff --git a/test/e2e-go/restAPI/restClient_test.go b/test/e2e-go/restAPI/restClient_test.go index d3ee6de4ff..9abbf19eab 100644 --- a/test/e2e-go/restAPI/restClient_test.go +++ b/test/e2e-go/restAPI/restClient_test.go @@ -1620,9 +1620,8 @@ func TestSimulateTransaction(t *testing.T) { closingAmount := senderBalance - txn.Fee.Raw - txn.Amount.Raw expectedResult := v2.PreEncodedSimulateResponse{ - Version: 1, - LastRound: result.LastRound, // checked above - WouldSucceed: true, + Version: 2, + LastRound: result.LastRound, // checked above TxnGroups: []v2.PreEncodedSimulateTxnGroupResult{ { Txns: []v2.PreEncodedSimulateTxnResult{ @@ -1647,6 +1646,66 @@ func TestSimulateTransaction(t *testing.T) { a.Zero(closeToBalance) } +func TestSimulateWithOptionalSignatures(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + a := require.New(fixtures.SynchronizedTest(t)) + var localFixture fixtures.RestClientFixture + localFixture.Setup(t, filepath.Join("nettemplates", "TwoNodes50EachFuture.json")) + defer localFixture.Shutdown() + + testClient := localFixture.LibGoalClient + + _, 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) + if senderAddress == "" { + t.Error("no addr with funds") + } + a.NoError(err) + + txn, err := testClient.ConstructPayment(senderAddress, senderAddress, 0, 1, nil, "", [32]byte{}, 0, 0) + a.NoError(err) + + simulateRequest := v2.PreEncodedSimulateRequest{ + TxnGroups: []v2.PreEncodedSimulateRequestTransactionGroup{ + { + Txns: []transactions.SignedTxn{{Txn: txn}}, // no signature + }, + }, + AllowEmptySignatures: true, + } + result, err := testClient.SimulateTransactions(simulateRequest) + a.NoError(err) + + allowEmptySignatures := true + expectedResult := v2.PreEncodedSimulateResponse{ + Version: 2, + LastRound: result.LastRound, + TxnGroups: []v2.PreEncodedSimulateTxnGroupResult{ + { + Txns: []v2.PreEncodedSimulateTxnResult{ + { + Txn: v2.PreEncodedTxInfo{ + Txn: transactions.SignedTxn{Txn: txn}, + }, + }, + }, + }, + }, + EvalOverrides: &model.SimulationEvalOverrides{ + AllowEmptySignatures: &allowEmptySignatures, + }, + } + a.Equal(expectedResult, result) +} + func TestSimulateWithUnlimitedLog(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() @@ -1755,7 +1814,7 @@ int 1` Txns: []transactions.SignedTxn{appCallTxnSigned}, }, }, - LiftLogLimits: true, + AllowMoreLogging: true, }) a.NoError(err) @@ -1768,9 +1827,8 @@ int 1` maxLogSize, maxLogCalls := uint64(65536), uint64(2048) expectedResult := v2.PreEncodedSimulateResponse{ - Version: 1, - LastRound: resp.LastRound, - WouldSucceed: true, + Version: 2, + LastRound: resp.LastRound, EvalOverrides: &model.SimulationEvalOverrides{ MaxLogSize: &maxLogSize, MaxLogCalls: &maxLogCalls, diff --git a/test/scripts/e2e_subs/e2e-app-simulate.sh b/test/scripts/e2e_subs/e2e-app-simulate.sh index 6fcd9bc08f..9e07074e2b 100755 --- a/test/scripts/e2e_subs/e2e-app-simulate.sh +++ b/test/scripts/e2e_subs/e2e-app-simulate.sh @@ -42,23 +42,23 @@ ${gcmd} clerk send -a 10000 -f ${ACCOUNT} -t ${ACCOUNT} -o "${TEMPDIR}/pay2.tx" cat "${TEMPDIR}/pay1.tx" "${TEMPDIR}/pay2.tx" | ${gcmd} clerk group -i - -o "${TEMPDIR}/grouped.tx" -# We test transaction group simulation WITHOUT signatures +# We test transaction group simulation WITHOUT signatures with default arguments RES=$(${gcmd} clerk simulate -t "${TEMPDIR}/grouped.tx") - -if [[ $(echo "$RES" | jq '."would-succeed"') != $CONST_FALSE ]]; then - date '+app-simulate-test FAIL the simulation transaction group without signatures should not succeed %Y%m%d_%H%M%S' +if [[ $(echo "$RES" | jq '."txn-groups" | any(has("failure-message"))') != $CONST_TRUE ]]; then + date '+app-simulate-test FAIL the simulation transaction group without signatures not fail %Y%m%d_%H%M%S' false fi -# check the simulation failing reason, first transaction has no signature -if [[ $(echo "$RES" | jq '."txn-groups"[0]."txn-results"[0]."missing-signature"') != $CONST_TRUE ]]; then - date '+app-simulate-test FAIL the simulation transaction group FAIL for first transaction has NO signature %Y%m%d_%H%M%S' +# We test transaction group simulation WITHOUT signatures, but with allow-empty-signatures enabled +RES=$(${gcmd} clerk simulate --allow-empty-signatures -t "${TEMPDIR}/grouped.tx") +if [[ $(echo "$RES" | jq '."txn-groups" | any(has("failure-message"))') != $CONST_FALSE ]]; then + date '+app-simulate-test FAIL the simulation transaction group without signatures should not fail when allow-empty-signatures is true %Y%m%d_%H%M%S' false fi -# check the simulation failing reason, second transaction has no signature -if [[ $(echo "$RES" | jq '."txn-groups"[0]."txn-results"[1]."missing-signature"') != $CONST_TRUE ]]; then - date '+app-simulate-test FAIL the simulation transaction group FAIL for second transaction has NO signature %Y%m%d_%H%M%S' +# check the simulation eval overrides reports the right value +if [[ $(echo "$RES" | jq '."eval-overrides"."allow-empty-signatures"') != $CONST_TRUE ]]; then + date '+app-simulate-test FAIL the simulation response should report eval overrides %Y%m%d_%H%M%S' false fi @@ -70,12 +70,17 @@ ${gcmd} clerk sign -i "${TEMPDIR}/grouped-1.tx" -o "${TEMPDIR}/grouped-1.stx" cat "${TEMPDIR}/grouped-0.stx" "${TEMPDIR}/grouped-1.stx" > "${TEMPDIR}/grouped.stx" -RES=$(${gcmd} clerk simulate -t "${TEMPDIR}/grouped.stx" | jq '."would-succeed"') -if [[ $RES != $CONST_TRUE ]]; then +RES=$(${gcmd} clerk simulate -t "${TEMPDIR}/grouped.stx") +if [[ $(echo "$RES" | jq '."txn-groups" | any(has("failure-message"))') != $CONST_FALSE ]]; then date '+app-simulate-test FAIL should pass to simulate self pay transaction group %Y%m%d_%H%M%S' false fi +if [[ $(echo "$RES" | jq 'has("eval-overrides")') != $CONST_FALSE ]]; then + date '+app-simulate-test FAIL the simulation response should not report eval overrides %Y%m%d_%H%M%S' + false +fi + # Test creating and using a simulate request object ${gcmd} clerk simulate -t "${TEMPDIR}/grouped.stx" --request-only-out "${TEMPDIR}/simulateRequest.json" @@ -91,8 +96,8 @@ if [ $NUM_TXNS -ne 2 ]; then false fi -RES=$(${gcmd} clerk simulate --request "${TEMPDIR}/simulateRequest.json" | jq '."would-succeed"') -if [[ $RES != $CONST_TRUE ]]; then +RES=$(${gcmd} clerk simulate --request "${TEMPDIR}/simulateRequest.json" | jq '."txn-groups" | any(has("failure-message"))') +if [[ $RES != $CONST_FALSE ]]; then date '+app-simulate-test FAIL should pass with raw simulate request %Y%m%d_%H%M%S' false fi @@ -115,7 +120,7 @@ cat "${TEMPDIR}/grouped-0.stx" "${TEMPDIR}/grouped-1.stx" > "${TEMPDIR}/grouped. RES=$(${gcmd} clerk simulate -t "${TEMPDIR}/grouped.stx") -if [[ $(echo "$RES" | jq '."would-succeed"') != $CONST_FALSE ]]; then +if [[ $(echo "$RES" | jq '."txn-groups" | any(has("failure-message"))') != $CONST_TRUE ]]; then data '+app-simulate-test FAIL should FAIL for overspending in simulate self pay transaction group %Y%m%d_%H%M%S' false fi @@ -148,29 +153,40 @@ ${gcmd} app method --method "empty()void" --app-id $APPID --from $ACCOUNT 2>&1 - # SIMULATE without a signature first RES=$(${gcmd} clerk simulate -t "${TEMPDIR}/empty.tx") - -# confirm that without signature, the simulation should fail -if [[ $(echo "$RES" | jq '."would-succeed"') != $CONST_FALSE ]]; then +# confirm that without signature, the simulation should fail with default args +if [[ $(echo "$RES" | jq '."txn-groups" | any(has("failure-message"))') != $CONST_TRUE ]]; then date '+app-simulate-test FAIL the simulation call to empty()void without signature should not succeed %Y%m%d_%H%M%S' false fi -# check again the simulation failing reason -if [[ $(echo "$RES" | jq '."txn-groups"[0]."txn-results"[0]."missing-signature"') != $CONST_TRUE ]]; then - date '+app-simulate-test FAIL the simulation call to empty()void without signature should fail with missing-signature %Y%m%d_%H%M%S' +RES=$(${gcmd} clerk simulate --allow-empty-signatures -t "${TEMPDIR}/empty.tx") +# confirm that without signature, the simulation should pass with allow-empty-signatures +if [[ $(echo "$RES" | jq '."txn-groups" | any(has("failure-message"))') != $CONST_FALSE ]]; then + date '+app-simulate-test FAIL the simulation call to empty()void without signature should succeed with allow-empty-signatures %Y%m%d_%H%M%S' + false +fi + +# check the simulation eval overrides reports the right value +if [[ $(echo "$RES" | jq '."eval-overrides"."allow-empty-signatures"') != $CONST_TRUE ]]; then + date '+app-simulate-test FAIL the simulation call to empty()void without signature should report eval overrides %Y%m%d_%H%M%S' false fi # SIMULATE with a signature ${gcmd} clerk sign -i "${TEMPDIR}/empty.tx" -o "${TEMPDIR}/empty.stx" -RES=$(${gcmd} clerk simulate -t "${TEMPDIR}/empty.stx" | jq '."would-succeed"') +RES=$(${gcmd} clerk simulate -t "${TEMPDIR}/empty.stx") # with signature, simulation app-call should succeed -if [[ $RES != $CONST_TRUE ]]; then +if [[ $(echo "$RES" | jq '."txn-groups" | any(has("failure-message"))') != $CONST_FALSE ]]; then date '+app-simulate-test FAIL the simulation call to empty()void should succeed %Y%m%d_%H%M%S' false fi +if [[ $(echo "$RES" | jq 'has("eval-overrides")') != $CONST_FALSE ]]; then + date '+app-simulate-test FAIL the simulation call to empty()void should not report eval overrides %Y%m%d_%H%M%S' + false +fi + ########################################################### # WE WANT TO FURTHER TEST UNLIMIT LOG IN SIMULATION WORKS # ########################################################### @@ -196,20 +212,20 @@ ${gcmd} app method --method "small_log()void" --app-id $APPID --from $ACCOUNT 2> ${gcmd} clerk sign -i "${TEMPDIR}/small_log.tx" -o "${TEMPDIR}/small_log.stx" RES=$(${gcmd} clerk simulate -t "${TEMPDIR}/small_log.stx") -if [[ $(echo "$RES" | jq '."would-succeed"') != $CONST_TRUE ]]; then - date '+app-simulate-test FAIL the app call to logs-a-lot.teal for small_log()void would-succeed should be true %Y%m%d_%H%M%S' +if [[ $(echo "$RES" | jq '."txn-groups" | any(has("failure-message"))') != $CONST_FALSE ]]; then + date '+app-simulate-test FAIL the app call to logs-a-lot.teal for small_log()void should not fail %Y%m%d_%H%M%S' false fi EXPECTED_SMALL_LOG='yet another ephemeral log' if [[ $(echo "$RES" | jq '."txn-groups"[0]."txn-results"[0]."txn-result"."logs"[0] | @base64d') != *"${EXPECTED_SMALL_LOG}"* ]]; then - date '+app-simulate-test FAIL the app call to logs-a-lot.teal for small_log()void should succeed %Y%m%d_%H%M%S' + date '+app-simulate-test FAIL the app call to logs-a-lot.teal for small_log()void should have expected logs %Y%m%d_%H%M%S' false fi -if [[ $(echo "$RES" | jq '."eval-overrides"') != null ]]; then - date '+app-simulate-test FAIL the app call to logs-a-lot.teal without lift-log-limits should not return with eval-overrides field %Y%m%d_%H%M%S' +if [[ $(echo "$RES" | jq 'has("eval-overrides")') != $CONST_FALSE ]]; then + date '+app-simulate-test FAIL the app call to logs-a-lot.teal without allow-more-logging should not return with eval-overrides field %Y%m%d_%H%M%S' false fi @@ -217,7 +233,7 @@ ${gcmd} app method --method "unlimited_log_test()void" --app-id $APPID --from $A ${gcmd} clerk sign -i "${TEMPDIR}/big_log.tx" -o "${TEMPDIR}/big_log.stx" RES=$(${gcmd} clerk simulate -t "${TEMPDIR}/big_log.stx") -if [[ $(echo "$RES" | jq '."would-succeed"') != $CONST_FALSE ]]; then +if [[ $(echo "$RES" | jq '."txn-groups" | any(has("failure-message"))') != $CONST_TRUE ]]; then date '+app-simulate-test FAIL the app call to logs-a-lot.teal for unlimited_log_test()void would-succeed should be false without unlimiting log %Y%m%d_%H%M%S' false fi @@ -229,18 +245,18 @@ if [[ $(echo "$RES" | jq '."txn-groups"[0]."failure-message"') != *"${EXPECTED_F false fi -if [[ $(echo "$RES" | jq '."eval-overrides"') != null ]]; then - date '+app-simulate-test FAIL the app call to logs-a-lot.teal without lift-log-limits should not return with eval-overrides field %Y%m%d_%H%M%S' +if [[ $(echo "$RES" | jq 'has("eval-overrides")') != $CONST_FALSE ]]; then + date '+app-simulate-test FAIL the app call to logs-a-lot.teal without allow-more-logging should not return with eval-overrides field %Y%m%d_%H%M%S' false fi # SIMULATION! with unlimiting log should call `unlimited_log_test()void` ${gcmd} app method --method "unlimited_log_test()void" --app-id $APPID --from $ACCOUNT 2>&1 -o "${TEMPDIR}/big_log.tx" ${gcmd} clerk sign -i "${TEMPDIR}/big_log.tx" -o "${TEMPDIR}/big_log.stx" -RES=$(${gcmd} clerk simulate --lift-log-limits -t "${TEMPDIR}/big_log.stx") +RES=$(${gcmd} clerk simulate --allow-more-logging -t "${TEMPDIR}/big_log.stx") -if [[ $(echo "$RES" | jq '."would-succeed"') != $CONST_TRUE ]]; then - date '+app-simulate-test FAIL the app call to logs-a-lot.teal for unlimited_log_test()void would-succeed should be true with unlimiting log %Y%m%d_%H%M%S' +if [[ $(echo "$RES" | jq '."txn-groups" | any(has("failure-message"))') != $CONST_FALSE ]]; then + date '+app-simulate-test FAIL the app call to logs-a-lot.teal for unlimited_log_test()void should not fail with unlimiting log %Y%m%d_%H%M%S' false fi From ebcd07b302d0f26d85ae538fbf1c2d620e7bed2e Mon Sep 17 00:00:00 2001 From: Eric Warehime Date: Thu, 27 Apr 2023 13:14:31 -0700 Subject: [PATCH 069/125] EvalTracer: Txn group deltas Tracer (#5297) --- config/localTemplate.go | 4 + config/local_defaults.go | 1 + data/transactions/logic/debugger.go | 3 +- data/transactions/logic/eval.go | 2 +- data/transactions/logic/mocktracer/tracer.go | 3 +- data/transactions/logic/tracer.go | 9 +- installer/config.json.example | 1 + ledger/eval/eval.go | 6 +- ledger/eval/eval_test.go | 16 +- ledger/eval/txntracer.go | 100 ++++++++ ledger/eval/txntracer_test.go | 253 +++++++++++++++++++ ledger/evalbench_test.go | 2 +- ledger/fullblock_perf_test.go | 4 +- ledger/ledger.go | 13 +- ledger/ledger_perf_test.go | 2 +- ledger/ledger_test.go | 2 +- ledger/simulation/tracer.go | 7 +- ledger/simulation/tracer_test.go | 2 +- 18 files changed, 404 insertions(+), 26 deletions(-) create mode 100644 ledger/eval/txntracer.go create mode 100644 ledger/eval/txntracer_test.go diff --git a/config/localTemplate.go b/config/localTemplate.go index 95a8a493b1..a9dd313bb8 100644 --- a/config/localTemplate.go +++ b/config/localTemplate.go @@ -506,6 +506,10 @@ type Local struct { // and APIs related to broadcasting transactions, and enables APIs which can retrieve detailed information // from ledger caches and can control the ledger round. EnableFollowMode bool `version[27]:"false"` + + // EnableTxnEvalTracer turns on features in the BlockEvaluator which collect data on transactions, exposing them via algod APIs. + // It will store txn deltas created during block evaluation, potentially consuming much larger amounts of memory, + EnableTxnEvalTracer bool `version[27]:"false"` } // DNSBootstrapArray returns an array of one or more DNS Bootstrap identifiers diff --git a/config/local_defaults.go b/config/local_defaults.go index 48ddae4be9..81e4a25870 100644 --- a/config/local_defaults.go +++ b/config/local_defaults.go @@ -75,6 +75,7 @@ var defaultLocal = Local{ EnableRuntimeMetrics: false, EnableTopAccountsReporting: false, EnableTxBacklogRateLimiting: false, + EnableTxnEvalTracer: false, EnableUsageLog: false, EnableVerbosedTransactionSyncLogging: false, EndpointAddress: "127.0.0.1:0", diff --git a/data/transactions/logic/debugger.go b/data/transactions/logic/debugger.go index ac2bcab7d7..0d62b8dc84 100644 --- a/data/transactions/logic/debugger.go +++ b/data/transactions/logic/debugger.go @@ -29,6 +29,7 @@ import ( "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/ledger/ledgercore" "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/protocol" ) @@ -73,7 +74,7 @@ func (a *debuggerEvalTracerAdaptor) BeforeTxnGroup(ep *EvalParams) { } // AfterTxnGroup updates inner txn depth -func (a *debuggerEvalTracerAdaptor) AfterTxnGroup(ep *EvalParams, evalError error) { +func (a *debuggerEvalTracerAdaptor) AfterTxnGroup(ep *EvalParams, deltas *ledgercore.StateDelta, evalError error) { a.txnDepth-- } diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index f05fa2cc76..dfc2639195 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -5420,7 +5420,7 @@ func opItxnSubmit(cx *EvalContext) (err error) { ep.Tracer.BeforeTxnGroup(ep) // Ensure we update the tracer before exiting defer func() { - ep.Tracer.AfterTxnGroup(ep, err) + ep.Tracer.AfterTxnGroup(ep, nil, err) }() } diff --git a/data/transactions/logic/mocktracer/tracer.go b/data/transactions/logic/mocktracer/tracer.go index f73a379aea..30c4bff70f 100644 --- a/data/transactions/logic/mocktracer/tracer.go +++ b/data/transactions/logic/mocktracer/tracer.go @@ -21,6 +21,7 @@ import ( "github.com/algorand/go-algorand/data/bookkeeping" "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/data/transactions/logic" + "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/protocol" ) @@ -161,7 +162,7 @@ func (d *Tracer) BeforeTxnGroup(ep *logic.EvalParams) { } // AfterTxnGroup mocks the logic.EvalTracer.AfterTxnGroup method -func (d *Tracer) AfterTxnGroup(ep *logic.EvalParams, evalError error) { +func (d *Tracer) AfterTxnGroup(ep *logic.EvalParams, deltas *ledgercore.StateDelta, evalError error) { d.Events = append(d.Events, AfterTxnGroup(len(ep.TxnGroup), evalError != nil)) } diff --git a/data/transactions/logic/tracer.go b/data/transactions/logic/tracer.go index 5f2ab332fd..4b4c5f5807 100644 --- a/data/transactions/logic/tracer.go +++ b/data/transactions/logic/tracer.go @@ -19,6 +19,7 @@ package logic import ( "github.com/algorand/go-algorand/data/bookkeeping" "github.com/algorand/go-algorand/data/transactions" + "github.com/algorand/go-algorand/ledger/ledgercore" ) // EvalTracer functions are called by eval function during AVM program execution, if a tracer @@ -129,7 +130,10 @@ type EvalTracer interface { // AfterTxnGroup is called after a transaction group has been executed. This includes both // top-level and inner transaction groups. The argument ep is the EvalParams object for the // group; if the group is an inner group, this is the EvalParams object for the inner group. - AfterTxnGroup(ep *EvalParams, evalError error) + // For top-level transaction groups, the deltas argument is the ledgercore.StateDelta changes + // that occurred because of this transaction group. For inner transaction groups, this argument + // is nil. + AfterTxnGroup(ep *EvalParams, deltas *ledgercore.StateDelta, evalError error) // BeforeTxn is called before a transaction is executed. // @@ -170,7 +174,8 @@ func (n NullEvalTracer) BeforeBlock(hdr *bookkeeping.BlockHeader) {} func (n NullEvalTracer) BeforeTxnGroup(ep *EvalParams) {} // AfterTxnGroup does nothing -func (n NullEvalTracer) AfterTxnGroup(ep *EvalParams, evalError error) {} +func (n NullEvalTracer) AfterTxnGroup(ep *EvalParams, deltas *ledgercore.StateDelta, evalError error) { +} // BeforeTxn does nothing func (n NullEvalTracer) BeforeTxn(ep *EvalParams, groupIndex int) {} diff --git a/installer/config.json.example b/installer/config.json.example index 3eefcf69ef..4ae34a5716 100644 --- a/installer/config.json.example +++ b/installer/config.json.example @@ -54,6 +54,7 @@ "EnableRuntimeMetrics": false, "EnableTopAccountsReporting": false, "EnableTxBacklogRateLimiting": false, + "EnableTxnEvalTracer": false, "EnableUsageLog": false, "EnableVerbosedTransactionSyncLogging": false, "EndpointAddress": "127.0.0.1:0", diff --git a/ledger/eval/eval.go b/ledger/eval/eval.go index a424df0a95..914d7cdca2 100644 --- a/ledger/eval/eval.go +++ b/ledger/eval/eval.go @@ -965,7 +965,8 @@ func (eval *BlockEvaluator) TransactionGroup(txgroup []transactions.SignedTxnWit eval.Tracer.BeforeTxnGroup(evalParams) // Ensure we update the tracer before exiting defer func() { - eval.Tracer.AfterTxnGroup(evalParams, err) + deltas := cow.deltas() + eval.Tracer.AfterTxnGroup(evalParams, &deltas, err) }() } @@ -1554,7 +1555,7 @@ func (validator *evalTxValidator) run() { // Validate: Eval(ctx, l, blk, true, txcache, executionPool) // AddBlock: Eval(context.Background(), l, blk, false, txcache, nil) // tracker: Eval(context.Background(), l, blk, false, txcache, nil) -func Eval(ctx context.Context, l LedgerForEvaluator, blk bookkeeping.Block, validate bool, txcache verify.VerifiedTransactionCache, executionPool execpool.BacklogPool) (ledgercore.StateDelta, error) { +func Eval(ctx context.Context, l LedgerForEvaluator, blk bookkeeping.Block, validate bool, txcache verify.VerifiedTransactionCache, executionPool execpool.BacklogPool, tracer logic.EvalTracer) (ledgercore.StateDelta, error) { // flush the pending writes in the cache to make everything read so far available during eval l.FlushCaches() @@ -1563,6 +1564,7 @@ func Eval(ctx context.Context, l LedgerForEvaluator, blk bookkeeping.Block, vali PaysetHint: len(blk.Payset), Validate: validate, Generate: false, + Tracer: tracer, }) if err != nil { return ledgercore.StateDelta{}, err diff --git a/ledger/eval/eval_test.go b/ledger/eval/eval_test.go index ab0e7fdcd8..bbc218d9a0 100644 --- a/ledger/eval/eval_test.go +++ b/ledger/eval/eval_test.go @@ -599,6 +599,7 @@ type evalTestLedger struct { feeSink basics.Address rewardsPool basics.Address latestTotals ledgercore.AccountTotals + tracer logic.EvalTracer } // newTestLedger creates a in memory Ledger that is as realistic as @@ -609,6 +610,7 @@ func newTestLedger(t testing.TB, balances bookkeeping.GenesisBalances) *evalTest roundBalances: make(map[basics.Round]map[basics.Address]basics.AccountData), feeSink: balances.FeeSink, rewardsPool: balances.RewardsPool, + tracer: nil, } crypto.RandBytes(l.genesisHash[:]) @@ -639,7 +641,7 @@ func newTestLedger(t testing.TB, balances bookkeeping.GenesisBalances) *evalTest func (ledger *evalTestLedger) Validate(ctx context.Context, blk bookkeeping.Block, executionPool execpool.BacklogPool) (*ledgercore.ValidatedBlock, error) { verifiedTxnCache := verify.MakeVerifiedTransactionCache(config.GetDefaultLocal().VerifiedTranscationsCacheSize) - delta, err := Eval(ctx, ledger, blk, true, verifiedTxnCache, executionPool) + delta, err := Eval(ctx, ledger, blk, true, verifiedTxnCache, executionPool, ledger.tracer) if err != nil { return nil, err } @@ -1059,7 +1061,7 @@ func TestEvalFunctionForExpiredAccounts(t *testing.T) { validatedBlock, err := blkEval.GenerateBlock() require.NoError(t, err) - _, err = Eval(context.Background(), l, validatedBlock.Block(), false, nil, nil) + _, err = Eval(context.Background(), l, validatedBlock.Block(), false, nil, nil, l.tracer) require.NoError(t, err) acctData, _ := blkEval.state.lookup(recvAddr) @@ -1070,7 +1072,7 @@ func TestEvalFunctionForExpiredAccounts(t *testing.T) { badBlock := *validatedBlock // First validate that bad block is fine if we dont touch it... - _, err = Eval(context.Background(), l, badBlock.Block(), true, verify.GetMockedCache(true), nil) + _, err = Eval(context.Background(), l, badBlock.Block(), true, verify.GetMockedCache(true), nil, l.tracer) require.NoError(t, err) badBlock = *validatedBlock @@ -1080,7 +1082,7 @@ func TestEvalFunctionForExpiredAccounts(t *testing.T) { badBlockObj.ExpiredParticipationAccounts = append(badBlockObj.ExpiredParticipationAccounts, basics.Address{1}) badBlock = ledgercore.MakeValidatedBlock(badBlockObj, badBlock.Delta()) - _, err = Eval(context.Background(), l, badBlock.Block(), true, verify.GetMockedCache(true), nil) + _, err = Eval(context.Background(), l, badBlock.Block(), true, verify.GetMockedCache(true), nil, l.tracer) require.Error(t, err) badBlock = *validatedBlock @@ -1094,7 +1096,7 @@ func TestEvalFunctionForExpiredAccounts(t *testing.T) { } badBlock = ledgercore.MakeValidatedBlock(badBlockObj, badBlock.Delta()) - _, err = Eval(context.Background(), l, badBlock.Block(), true, verify.GetMockedCache(true), nil) + _, err = Eval(context.Background(), l, badBlock.Block(), true, verify.GetMockedCache(true), nil, l.tracer) require.Error(t, err) badBlock = *validatedBlock @@ -1104,12 +1106,12 @@ func TestEvalFunctionForExpiredAccounts(t *testing.T) { badBlockObj.ExpiredParticipationAccounts = append(badBlockObj.ExpiredParticipationAccounts, badBlockObj.ExpiredParticipationAccounts[0]) badBlock = ledgercore.MakeValidatedBlock(badBlockObj, badBlock.Delta()) - _, err = Eval(context.Background(), l, badBlock.Block(), true, verify.GetMockedCache(true), nil) + _, err = Eval(context.Background(), l, badBlock.Block(), true, verify.GetMockedCache(true), nil, l.tracer) require.Error(t, err) badBlock = *validatedBlock // sanity check that bad block is being actually copied and not just the pointer - _, err = Eval(context.Background(), l, badBlock.Block(), true, verify.GetMockedCache(true), nil) + _, err = Eval(context.Background(), l, badBlock.Block(), true, verify.GetMockedCache(true), nil, l.tracer) require.NoError(t, err) } diff --git a/ledger/eval/txntracer.go b/ledger/eval/txntracer.go new file mode 100644 index 0000000000..a8f7baa37f --- /dev/null +++ b/ledger/eval/txntracer.go @@ -0,0 +1,100 @@ +// Copyright (C) 2019-2023 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package eval + +import ( + "fmt" + + "github.com/algorand/go-algorand/crypto" + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/data/bookkeeping" + "github.com/algorand/go-algorand/data/transactions/logic" + "github.com/algorand/go-algorand/ledger/ledgercore" +) + +// TxnGroupDeltaTracer collects groups of StateDelta objects covering groups of txns +type TxnGroupDeltaTracer struct { + // lookback is the number of rounds stored at any given time + lookback uint64 + // no-op methods we don't care about + logic.NullEvalTracer + // txnGroupDeltas stores the StateDelta objects for each round, indexed by all the IDs within the group + txnGroupDeltas map[basics.Round]map[crypto.Digest]*ledgercore.StateDelta + // latestRound is the most recent round seen via the BeforeBlock hdr + latestRound basics.Round +} + +// MakeTxnGroupDeltaTracer creates a TxnGroupDeltaTracer +func MakeTxnGroupDeltaTracer(lookback uint64) *TxnGroupDeltaTracer { + return &TxnGroupDeltaTracer{ + lookback: lookback, + txnGroupDeltas: make(map[basics.Round]map[crypto.Digest]*ledgercore.StateDelta), + } +} + +// BeforeBlock implements the EvalTracer interface for pre-block evaluation +func (tracer *TxnGroupDeltaTracer) BeforeBlock(hdr *bookkeeping.BlockHeader) { + // Drop older rounds based on the lookback parameter + delete(tracer.txnGroupDeltas, hdr.Round-basics.Round(tracer.lookback)) + tracer.latestRound = hdr.Round + // Initialize the delta map for the round + tracer.txnGroupDeltas[tracer.latestRound] = make(map[crypto.Digest]*ledgercore.StateDelta) +} + +// AfterTxnGroup implements the EvalTracer interface for txn group boundaries +func (tracer *TxnGroupDeltaTracer) AfterTxnGroup(ep *logic.EvalParams, deltas *ledgercore.StateDelta, evalError error) { + if deltas == nil { + return + } + txnDeltaMap := tracer.txnGroupDeltas[tracer.latestRound] + for _, txn := range ep.TxnGroup { + // Add Group ID + if !txn.Txn.Group.IsZero() { + txnDeltaMap[txn.Txn.Group] = deltas + } + // Add Txn ID + txnDeltaMap[crypto.Digest(txn.ID())] = deltas + } +} + +// GetDeltasForRound supplies all StateDelta objects for txn groups in a given rnd +func (tracer *TxnGroupDeltaTracer) GetDeltasForRound(rnd basics.Round) ([]ledgercore.StateDelta, error) { + rndEntries, exists := tracer.txnGroupDeltas[rnd] + if !exists { + return nil, fmt.Errorf("round %d not found in txnGroupDeltaTracer", rnd) + } + // Dedupe entries in our map + var deltas = map[*ledgercore.StateDelta]bool{} + var entries []ledgercore.StateDelta + for _, delta := range rndEntries { + if _, present := deltas[delta]; !present { + deltas[delta] = true + entries = append(entries, *delta) + } + } + return entries, nil +} + +// GetDeltaForID retruns the StateDelta associated with the group of transaction executed for the supplied ID (txn or group) +func (tracer *TxnGroupDeltaTracer) GetDeltaForID(id crypto.Digest) (ledgercore.StateDelta, error) { + for _, deltasForRound := range tracer.txnGroupDeltas { + if delta, exists := deltasForRound[id]; exists { + return *delta, nil + } + } + return ledgercore.StateDelta{}, fmt.Errorf("unable to find delta for id: %s", id) +} diff --git a/ledger/eval/txntracer_test.go b/ledger/eval/txntracer_test.go new file mode 100644 index 0000000000..1efed23d6d --- /dev/null +++ b/ledger/eval/txntracer_test.go @@ -0,0 +1,253 @@ +// Copyright (C) 2019-2023 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package eval + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/crypto" + "github.com/algorand/go-algorand/data/basics" + basics_testing "github.com/algorand/go-algorand/data/basics/testing" + "github.com/algorand/go-algorand/data/bookkeeping" + "github.com/algorand/go-algorand/data/transactions" + "github.com/algorand/go-algorand/data/transactions/logic/mocktracer" + "github.com/algorand/go-algorand/data/txntest" + "github.com/algorand/go-algorand/ledger/ledgercore" + ledgertesting "github.com/algorand/go-algorand/ledger/testing" + "github.com/algorand/go-algorand/protocol" + "github.com/algorand/go-algorand/test/partitiontest" +) + +func TestTransactionGroupWithDeltaTracer(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + // In all cases, a group of three transactions is tested. They are: + // 1. A basic app call transaction + // 2. A payment transaction + // 3. An app call transaction that spawns inners. This is from the mocktracer scenarios. + + // We don't care about testing error scenarios here--just exercising different successful txn group evals + scenario := mocktracer.GetTestScenarios()["none"] + type tracerTestCase struct { + name string + innerAppCallScenario mocktracer.TestScenarioGenerator + } + var testCases = []tracerTestCase{ + { + name: "noError", + innerAppCallScenario: scenario, + }, + } + + for _, testCase := range testCases { + testCase := testCase + t.Run(testCase.name, func(t *testing.T) { + t.Parallel() + // SETUP THE BLOCK + genesisInitState, addrs, keys := ledgertesting.Genesis(4) + + innerAppID := basics.AppIndex(3) + innerAppAddress := innerAppID.Address() + balances := genesisInitState.Accounts + balances[innerAppAddress] = basics_testing.MakeAccountData(basics.Offline, basics.MicroAlgos{Raw: 1_000_000}) + + genesisBalances := bookkeeping.GenesisBalances{ + Balances: genesisInitState.Accounts, + FeeSink: testSinkAddr, + RewardsPool: testPoolAddr, + Timestamp: 0, + } + l := newTestLedger(t, genesisBalances) + blkHeader, err := l.BlockHdr(basics.Round(0)) + require.NoError(t, err) + newBlock := bookkeeping.MakeBlock(blkHeader) + tracer := MakeTxnGroupDeltaTracer(4) + eval, err := l.StartEvaluator(newBlock.BlockHeader, 0, 0, tracer) + require.NoError(t, err) + eval.validate = true + eval.generate = true + genHash := l.GenesisHash() + + // a basic app call + basicAppCallTxn := txntest.Txn{ + Type: protocol.ApplicationCallTx, + Sender: addrs[0], + ApprovalProgram: `#pragma version 6 +byte "hello" +log +int 1`, + ClearStateProgram: `#pragma version 6 +int 1`, + FirstValid: newBlock.Round(), + LastValid: newBlock.Round() + 1000, + Fee: minFee, + GenesisHash: genHash, + Note: []byte("one"), + } + + // a non-app call txn + payTxn := txntest.Txn{ + Type: protocol.PaymentTx, + Sender: addrs[1], + Receiver: addrs[2], + CloseRemainderTo: addrs[3], + Amount: 1_000_000, + FirstValid: newBlock.Round(), + LastValid: newBlock.Round() + 1000, + Fee: minFee, + GenesisHash: genHash, + Note: []byte("two"), + } + // an app call with inner txn + innerAppCallTxn := txntest.Txn{ + Type: protocol.ApplicationCallTx, + Sender: addrs[0], + ClearStateProgram: `#pragma version 6 +int 1`, + FirstValid: newBlock.Round(), + LastValid: newBlock.Round() + 1000, + Fee: minFee, + GenesisHash: genHash, + Note: []byte("three"), + } + scenario := testCase.innerAppCallScenario(mocktracer.TestScenarioInfo{ + CallingTxn: innerAppCallTxn.Txn(), + MinFee: minFee, + CreatedAppID: innerAppID, + }) + innerAppCallTxn.ApprovalProgram = scenario.Program + + txntest.Group(&basicAppCallTxn, &payTxn, &innerAppCallTxn) + + txgroup := transactions.WrapSignedTxnsWithAD([]transactions.SignedTxn{ + basicAppCallTxn.Txn().Sign(keys[0]), + payTxn.Txn().Sign(keys[1]), + innerAppCallTxn.Txn().Sign(keys[0]), + }) + + require.Len(t, eval.block.Payset, 0) + + err = eval.TransactionGroup(txgroup) + require.NoError(t, err) + require.Len(t, eval.block.Payset, 3) + + expectedAccts := ledgercore.AccountDeltas{ + Accts: []ledgercore.BalanceRecord{ + { + Addr: addrs[0], + AccountData: ledgercore.AccountData{ + AccountBaseData: ledgercore.AccountBaseData{ + MicroAlgos: basics.MicroAlgos{Raw: 1666666666664666}, + TotalAppParams: 2, + }, + }, + }, + { + Addr: testSinkAddr, + AccountData: ledgercore.AccountData{ + AccountBaseData: ledgercore.AccountBaseData{ + Status: basics.Status(2), + MicroAlgos: basics.MicroAlgos{Raw: 1666666666672666}, + }, + }, + }, + { + Addr: addrs[1], + }, + { + Addr: addrs[2], + AccountData: ledgercore.AccountData{ + AccountBaseData: ledgercore.AccountBaseData{ + MicroAlgos: basics.MicroAlgos{Raw: 1666666667666666}, + }, + }, + }, + { + Addr: addrs[3], + AccountData: ledgercore.AccountData{ + AccountBaseData: ledgercore.AccountBaseData{ + MicroAlgos: basics.MicroAlgos{Raw: 3333333332332332}, + }, + }, + }, + { + Addr: innerAppAddress, + AccountData: ledgercore.AccountData{ + AccountBaseData: ledgercore.AccountBaseData{ + MicroAlgos: basics.MicroAlgos{Raw: 997000}, + TotalAppParams: 1, + }, + }, + }, + }, + AppResources: []ledgercore.AppResourceRecord{ + { + Aidx: 1, + Addr: addrs[0], + Params: ledgercore.AppParamsDelta{ + Params: &basics.AppParams{ + ApprovalProgram: []byte{0x06, 0x80, 0x05, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0xb0, 0x81, 0x01}, + ClearStateProgram: []byte{0x06, 0x81, 0x01}, + }, + }, + }, + { + Aidx: 3, + Addr: addrs[0], + Params: ledgercore.AppParamsDelta{ + Params: &basics.AppParams{ + ApprovalProgram: []byte{0x06, 0x80, 0x01, 0x61, 0xb0, 0xb1, 0x81, 0x06, 0xb2, 0x10, 0x81, 0x00, 0xb2, 0x19, 0x80, 0x07, + 0x06, 0x80, 0x01, 0x78, 0xb0, 0x81, 0x01, 0xb2, 0x1e, 0x80, 0x03, 0x06, 0x81, 0x01, 0xb2, 0x1f, + 0xb3, 0x80, 0x01, 0x62, 0xb0, 0xb1, 0x81, 0x01, 0xb2, 0x10, 0x81, 0x01, 0xb2, 0x08, 0x32, 0x0a, + 0xb2, 0x07, 0xb6, 0x81, 0x01, 0xb2, 0x10, 0x81, 0x02, 0xb2, 0x08, 0x32, 0x0a, 0xb2, 0x07, 0xb3, + 0x80, 0x01, 0x63, 0xb0, 0x81, 0x01}, + ClearStateProgram: []byte{0x06, 0x81, 0x01}, + }, + }, + }, + { + Aidx: 4, + Addr: innerAppAddress, + Params: ledgercore.AppParamsDelta{ + Params: &basics.AppParams{ + ApprovalProgram: []byte{0x06, 0x80, 0x01, 0x78, 0xb0, 0x81, 0x01}, + ClearStateProgram: []byte{0x06, 0x81, 0x01}, + }, + }, + }, + }, + } + + actualDelta, err := tracer.GetDeltaForID(crypto.Digest(txgroup[0].ID())) + require.NoError(t, err) + _, err = tracer.GetDeltaForID(crypto.Digest(txgroup[1].ID())) + require.NoError(t, err) + _, err = tracer.GetDeltaForID(crypto.Digest(txgroup[2].ID())) + require.NoError(t, err) + allDeltas, err := tracer.GetDeltasForRound(basics.Round(1)) + require.NoError(t, err) + require.Len(t, allDeltas, 1) + + require.Equal(t, expectedAccts.Accts, actualDelta.Accts.Accts) + require.Equal(t, expectedAccts.AppResources, actualDelta.Accts.AppResources) + require.Equal(t, expectedAccts.AssetResources, actualDelta.Accts.AssetResources) + }) + } +} diff --git a/ledger/evalbench_test.go b/ledger/evalbench_test.go index 355e7253bd..5134497279 100644 --- a/ledger/evalbench_test.go +++ b/ledger/evalbench_test.go @@ -491,7 +491,7 @@ func benchmarkBlockEvaluator(b *testing.B, inMem bool, withCrypto bool, proto pr if withCrypto { _, err = l2.Validate(context.Background(), validatedBlock.Block(), backlogPool) } else { - _, err = eval.Eval(context.Background(), l2, validatedBlock.Block(), false, nil, nil) + _, err = eval.Eval(context.Background(), l2, validatedBlock.Block(), false, nil, nil, l2.tracer) } require.NoError(b, err) } diff --git a/ledger/fullblock_perf_test.go b/ledger/fullblock_perf_test.go index 080cccb687..cad634a5a7 100644 --- a/ledger/fullblock_perf_test.go +++ b/ledger/fullblock_perf_test.go @@ -144,7 +144,7 @@ func setupEnv(b *testing.B, numAccts int) (bc *benchConfig) { addBlock(bc) vc := verify.GetMockedCache(true) for _, blk := range bc.blocks { - _, err := eval.Eval(context.Background(), bc.l1, blk, true, vc, nil) + _, err := eval.Eval(context.Background(), bc.l1, blk, true, vc, nil, bc.l1.tracer) require.NoError(b, err) err = bc.l1.AddBlock(blk, cert) require.NoError(b, err) @@ -424,7 +424,7 @@ func benchmarkBlockValidationMix(b *testing.B, newAcctProb, payProb, astProb flo tt := time.Now() b.ResetTimer() for _, blk := range bc.blocks { - _, err := eval.Eval(context.Background(), bc.l1, blk, true, vc, nil) + _, err := eval.Eval(context.Background(), bc.l1, blk, true, vc, nil, bc.l1.tracer) require.NoError(b, err) err = bc.l1.AddBlock(blk, cert) require.NoError(b, err) diff --git a/ledger/ledger.go b/ledger/ledger.go index 35d8b2ee34..7f2d6e69bb 100644 --- a/ledger/ledger.go +++ b/ledger/ledger.go @@ -99,6 +99,8 @@ type Ledger struct { cfg config.Local dbPathPrefix string + + tracer logic.EvalTracer } // OpenLedger creates a Ledger object, using SQLite database filenames @@ -114,6 +116,10 @@ func OpenLedger( verifiedCacheSize = cfg.TxPoolSize log.Warnf("The VerifiedTranscationsCacheSize in the config file was misconfigured to have smaller size then the TxPoolSize; The verified cache size was adjusted from %d to %d.", cfg.VerifiedTranscationsCacheSize, cfg.TxPoolSize) } + var tracer logic.EvalTracer + if cfg.EnableTxnEvalTracer { + tracer = eval.MakeTxnGroupDeltaTracer(cfg.MaxAcctLookback) + } l := &Ledger{ log: log, @@ -127,6 +133,7 @@ func OpenLedger( verifiedTxnCache: verify.MakeVerifiedTransactionCache(verifiedCacheSize), cfg: cfg, dbPathPrefix: dbPathPrefix, + tracer: tracer, } l.headerCache.initialize() @@ -681,7 +688,7 @@ func (l *Ledger) BlockCert(rnd basics.Round) (blk bookkeeping.Block, cert agreem func (l *Ledger) AddBlock(blk bookkeeping.Block, cert agreement.Certificate) error { // passing nil as the executionPool is ok since we've asking the evaluator to skip verification. - updates, err := eval.Eval(context.Background(), l, blk, false, l.verifiedTxnCache, nil) + updates, err := eval.Eval(context.Background(), l, blk, false, l.verifiedTxnCache, nil, l.tracer) if err != nil { if errNSBE, ok := err.(ledgercore.ErrNonSequentialBlockEval); ok && errNSBE.EvaluatorRound <= errNSBE.LatestRound { return ledgercore.BlockInLedgerError{ @@ -810,7 +817,7 @@ func (l *Ledger) trackerLog() logging.Logger { // evaluator to shortcut the "main" ledger ( i.e. this struct ) and avoid taking the trackers lock a second time. func (l *Ledger) trackerEvalVerified(blk bookkeeping.Block, accUpdatesLedger eval.LedgerForEvaluator) (ledgercore.StateDelta, error) { // passing nil as the executionPool is ok since we've asking the evaluator to skip verification. - return eval.Eval(context.Background(), accUpdatesLedger, blk, false, l.verifiedTxnCache, nil) + return eval.Eval(context.Background(), accUpdatesLedger, blk, false, l.verifiedTxnCache, nil, l.tracer) } // IsWritingCatchpointDataFile returns true when a catchpoint file is being generated. @@ -857,7 +864,7 @@ func (l *Ledger) FlushCaches() { // not a valid block (e.g., it has duplicate transactions, overspends some // account, etc). func (l *Ledger) Validate(ctx context.Context, blk bookkeeping.Block, executionPool execpool.BacklogPool) (*ledgercore.ValidatedBlock, error) { - delta, err := eval.Eval(ctx, l, blk, true, l.verifiedTxnCache, executionPool) + delta, err := eval.Eval(ctx, l, blk, true, l.verifiedTxnCache, executionPool, l.tracer) if err != nil { return nil, err } diff --git a/ledger/ledger_perf_test.go b/ledger/ledger_perf_test.go index 123994afcc..ba76df5e78 100644 --- a/ledger/ledger_perf_test.go +++ b/ledger/ledger_perf_test.go @@ -319,7 +319,7 @@ func benchmarkFullBlocks(params testParams, b *testing.B) { vc := verify.GetMockedCache(true) b.ResetTimer() for _, blk := range blocks { - _, err = eval.Eval(context.Background(), l1, blk, true, vc, nil) + _, err = eval.Eval(context.Background(), l1, blk, true, vc, nil, l1.tracer) require.NoError(b, err) err = l1.AddBlock(blk, cert) require.NoError(b, err) diff --git a/ledger/ledger_test.go b/ledger/ledger_test.go index fb2531041e..ebe4b26a18 100644 --- a/ledger/ledger_test.go +++ b/ledger/ledger_test.go @@ -3187,7 +3187,7 @@ func TestLedgerSPVerificationTracker(t *testing.T) { // This implementation is an easy way to feed the delta, which the state proof verification tracker relies on, // to the ledger. - delta, err := eval.Eval(context.Background(), l, blk, false, l.verifiedTxnCache, nil) + delta, err := eval.Eval(context.Background(), l, blk, false, l.verifiedTxnCache, nil, l.tracer) require.NoError(t, err) delta.StateProofNext = stateProofReceived.StateProofNextRound vb := ledgercore.MakeValidatedBlock(blk, delta) diff --git a/ledger/simulation/tracer.go b/ledger/simulation/tracer.go index 5c2160f43c..74ab23c92c 100644 --- a/ledger/simulation/tracer.go +++ b/ledger/simulation/tracer.go @@ -22,6 +22,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/ledger/ledgercore" ) // cursorEvalTracer is responsible for maintaining a TxnPath that points to the currently executing @@ -47,7 +48,7 @@ func (tracer *cursorEvalTracer) AfterTxn(ep *logic.EvalParams, groupIndex int, a tracer.previousInnerTxns = tracer.previousInnerTxns[:len(tracer.previousInnerTxns)-1] } -func (tracer *cursorEvalTracer) AfterTxnGroup(ep *logic.EvalParams, evalError error) { +func (tracer *cursorEvalTracer) AfterTxnGroup(ep *logic.EvalParams, deltas *ledgercore.StateDelta, evalError error) { top := len(tracer.relativeCursor) - 1 if len(tracer.previousInnerTxns) != 0 { tracer.previousInnerTxns[len(tracer.previousInnerTxns)-1] += tracer.relativeCursor[top] + 1 @@ -143,9 +144,9 @@ func (tracer *evalTracer) BeforeTxnGroup(ep *logic.EvalParams) { ep.EvalConstants = tracer.result.EvalOverrides.LogicEvalConstants() } -func (tracer *evalTracer) AfterTxnGroup(ep *logic.EvalParams, evalError error) { +func (tracer *evalTracer) AfterTxnGroup(ep *logic.EvalParams, deltas *ledgercore.StateDelta, evalError error) { tracer.handleError(evalError) - tracer.cursorEvalTracer.AfterTxnGroup(ep, evalError) + tracer.cursorEvalTracer.AfterTxnGroup(ep, deltas, evalError) } func (tracer *evalTracer) saveApplyData(applyData transactions.ApplyData) { diff --git a/ledger/simulation/tracer_test.go b/ledger/simulation/tracer_test.go index b4429be414..06a31fc94b 100644 --- a/ledger/simulation/tracer_test.go +++ b/ledger/simulation/tracer_test.go @@ -236,7 +236,7 @@ func TestCursorEvalTracer(t *testing.T) { case mocktracer.BeforeTxnGroupEvent: tracer.BeforeTxnGroup(&ep) case mocktracer.AfterTxnGroupEvent: - tracer.AfterTxnGroup(&ep, nil) + tracer.AfterTxnGroup(&ep, nil, nil) default: t.Fatalf("unexpected timeline hook: %v", step.action) } From 6d13c3142a78e51575844eac8c6cd43ee1cd2aab Mon Sep 17 00:00:00 2001 From: Bob Broderick <118225939+bbroder-algo@users.noreply.github.com> Date: Thu, 27 Apr 2023 21:31:36 -0400 Subject: [PATCH 070/125] REST: add the round number to algod box endpoint repsonse (#5340) --- daemon/algod/api/algod.oas2.json | 7 +- daemon/algod/api/algod.oas3.yml | 7 +- .../api/server/v2/generated/data/routes.go | 348 +++++++++--------- .../v2/generated/experimental/routes.go | 333 ++++++++--------- .../api/server/v2/generated/model/types.go | 3 + .../nonparticipating/private/routes.go | 312 ++++++++-------- .../nonparticipating/public/routes.go | 264 ++++++------- .../generated/participating/private/routes.go | 348 +++++++++--------- .../generated/participating/public/routes.go | 154 ++++---- daemon/algod/api/server/v2/handlers.go | 1 + test/e2e-go/restAPI/restClient_test.go | 8 + 11 files changed, 904 insertions(+), 881 deletions(-) diff --git a/daemon/algod/api/algod.oas2.json b/daemon/algod/api/algod.oas2.json index d83a18a990..7c930ead30 100644 --- a/daemon/algod/api/algod.oas2.json +++ b/daemon/algod/api/algod.oas2.json @@ -1814,7 +1814,7 @@ }, "/v2/applications/{application-id}/box": { "get": { - "description": "Given an application ID and box name, it returns the box name and value (each base64 encoded). Box names must be in the goal app call arg encoding form 'encoding:value'. For ints, use the form 'int:1234'. For raw bytes, use the form 'b64:A=='. For printable strings, use the form 'str:hello'. For addresses, use the form 'addr:XYZ...'.", + "description": "Given an application ID and box name, it returns the round, box name, and value (each base64 encoded). Box names must be in the goal app call arg encoding form 'encoding:value'. For ints, use the form 'int:1234'. For raw bytes, use the form 'b64:A=='. For printable strings, use the form 'str:hello'. For addresses, use the form 'addr:XYZ...'.", "tags": [ "public", "nonparticipating" @@ -3319,10 +3319,15 @@ "description": "Box name and its content.", "type": "object", "required": [ + "round", "name", "value" ], "properties": { + "round": { + "description": "The round for which this information is relevant", + "type": "integer" + }, "name": { "description": "\\[name\\] box name, base64 encoded", "type": "string", diff --git a/daemon/algod/api/algod.oas3.yml b/daemon/algod/api/algod.oas3.yml index 85899765ba..9ef5c66cd8 100644 --- a/daemon/algod/api/algod.oas3.yml +++ b/daemon/algod/api/algod.oas3.yml @@ -1375,6 +1375,10 @@ "pattern": "^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$", "type": "string" }, + "round": { + "description": "The round for which this information is relevant", + "type": "integer" + }, "value": { "description": "\\[value\\] box value, base64 encoded.", "format": "byte", @@ -1384,6 +1388,7 @@ }, "required": [ "name", + "round", "value" ], "type": "object" @@ -2894,7 +2899,7 @@ }, "/v2/applications/{application-id}/box": { "get": { - "description": "Given an application ID and box name, it returns the box name and value (each base64 encoded). Box names must be in the goal app call arg encoding form 'encoding:value'. For ints, use the form 'int:1234'. For raw bytes, use the form 'b64:A=='. For printable strings, use the form 'str:hello'. For addresses, use the form 'addr:XYZ...'.", + "description": "Given an application ID and box name, it returns the round, box name, and value (each base64 encoded). Box names must be in the goal app call arg encoding form 'encoding:value'. For ints, use the form 'int:1234'. For raw bytes, use the form 'b64:A=='. For printable strings, use the form 'str:hello'. For addresses, use the form 'addr:XYZ...'.", "operationId": "GetApplicationBoxByName", "parameters": [ { diff --git a/daemon/algod/api/server/v2/generated/data/routes.go b/daemon/algod/api/server/v2/generated/data/routes.go index 500333a963..084735657c 100644 --- a/daemon/algod/api/server/v2/generated/data/routes.go +++ b/daemon/algod/api/server/v2/generated/data/routes.go @@ -145,181 +145,181 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "zQAA", + "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", + "XP//AAAA//8bsE0vTM4AAA==", } // 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 48553e35da..6ca5bbde6c 100644 --- a/daemon/algod/api/server/v2/generated/experimental/routes.go +++ b/daemon/algod/api/server/v2/generated/experimental/routes.go @@ -75,172 +75,173 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", + "/8cfsTf1vAAA", } // 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 e59a4680c4..438d260875 100644 --- a/daemon/algod/api/server/v2/generated/model/types.go +++ b/daemon/algod/api/server/v2/generated/model/types.go @@ -400,6 +400,9 @@ type Box struct { // Name \[name\] box name, base64 encoded Name []byte `json:"name"` + // Round The round for which this information is relevant + Round uint64 `json:"round"` + // Value \[value\] box value, base64 encoded. Value []byte `json:"value"` } 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 651284ea4e..5f16e2b311 100644 --- a/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go +++ b/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go @@ -131,7 +131,7 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL var swaggerSpec = []string{ "H4sIAAAAAAAC/+x9a3PcNrLoX0HNnio/7lCSn7tWVepcxU6yunEcl6Vk7zm2b4Ihe2awIgEGAOcRX/33", - "U2gAJEiCMxxJsTdV+8nWEGg0Go1Gv9D4NElFUQoOXKvJ6adJSSUtQIPEv2iaiorrhGXmrwxUKlmpmeCT", + "U2gAJEiCMxxJsTdV+8nWEGg0Go1Go1/4NElFUQoOXKvJ6adJSSUtQIPEv2iaiorrhGXmrwxUKlmpmeCT", "U/+NKC0ZX0ymE2Z+LaleTqYTTgto2pj+04mE3yomIZucalnBdKLSJRTUANbb0rSuIW2ShUgciDML4vzV", "5HrHB5plEpTqY/kjz7eE8TSvMiBaUq5oaj4psmZ6SfSSKeI6E8aJ4EDEnOhlqzGZM8gzdeQn+VsFchvM", "0g0+PKXrBsVEihz6eL4UxYxx8FhBjVS9IEQLksEcGy2pJmYEg6tvqAVRQGW6JHMh96BqkQjxBV4Vk9P3", @@ -147,161 +147,161 @@ var swaggerSpec = []string{ "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", - "TwAAAP//xilWefLGAAA=", + "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", + "P9aL6evg1Yt6/fH6fwIAAP//+2oQp1bHAAA=", } // 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 b351aa2e2f..3ffa72282a 100644 --- a/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go +++ b/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go @@ -715,138 +715,138 @@ var swaggerSpec = []string{ "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/generated/participating/private/routes.go b/daemon/algod/api/server/v2/generated/participating/private/routes.go index c772f7ce3b..a4b540a2ca 100644 --- a/daemon/algod/api/server/v2/generated/participating/private/routes.go +++ b/daemon/algod/api/server/v2/generated/participating/private/routes.go @@ -158,180 +158,180 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "Cz9+rEngU31XOFK4/nj9/wIAAP//QAj1qArRAAA=", + "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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 095790c281..5c417aca3e 100644 --- a/daemon/algod/api/server/v2/generated/participating/public/routes.go +++ b/daemon/algod/api/server/v2/generated/participating/public/routes.go @@ -283,83 +283,83 @@ var swaggerSpec = []string{ "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", - "//+yRy9Gmd8AAA==", + "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", + "Ukq2wuq/7z/+vwAAAP//l0i9xf3fAAA=", } // 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 254f81acea..a26d2752de 100644 --- a/daemon/algod/api/server/v2/handlers.go +++ b/daemon/algod/api/server/v2/handlers.go @@ -1496,6 +1496,7 @@ func (v2 *Handlers) GetApplicationBoxByName(ctx echo.Context, applicationID uint } response := model.BoxResponse{ + Round: uint64(lastRound), Name: boxName, Value: value, } diff --git a/test/e2e-go/restAPI/restClient_test.go b/test/e2e-go/restAPI/restClient_test.go index 9abbf19eab..fabb32c35f 100644 --- a/test/e2e-go/restAPI/restClient_test.go +++ b/test/e2e-go/restAPI/restClient_test.go @@ -1522,15 +1522,23 @@ end: {encodeInt(12321), "int:12321", []byte{0, 1, 254, 3, 2}}, {[]byte{0, 248, 255, 32}, "b64:APj/IA==", []byte("lux56")}, } + for _, boxTest := range boxTests { // Box values are 5 bytes, as defined by the test TEAL program. operateBoxAndSendTxn("create", []string{string(boxTest.name)}, []string{""}) operateBoxAndSendTxn("set", []string{string(boxTest.name)}, []string{string(boxTest.value)}) + currentRoundBeforeBoxes, err := testClient.CurrentRound() + a.NoError(err) boxResponse, err := testClient.GetApplicationBoxByName(uint64(createdAppID), boxTest.encodedName) a.NoError(err) + currentRoundAfterBoxes, err := testClient.CurrentRound() + a.NoError(err) a.Equal(boxTest.name, boxResponse.Name) a.Equal(boxTest.value, boxResponse.Value) + // To reduce flakiness, only check the round from boxes is within a range. + a.GreaterOrEqual(boxResponse.Round, currentRoundBeforeBoxes) + a.LessOrEqual(boxResponse.Round, currentRoundAfterBoxes) } const numberOfBoxesRemaining = uint64(3) From f03090f40909ea93b14a41003e38fda0a3297741 Mon Sep 17 00:00:00 2001 From: Ben Guidarelli Date: Fri, 28 Apr 2023 07:57:58 -0400 Subject: [PATCH 071/125] Enhancement: Only look back to FirstValid for pending transactions that were found in the pool (#5295) --- node/node.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/node/node.go b/node/node.go index 00aeffe75e..3ff616da67 100644 --- a/node/node.go +++ b/node/node.go @@ -669,6 +669,18 @@ func (node *AlgorandFullNode) GetPendingTransaction(txID transactions.Txid) (res minRound++ } + // If we did find the transaction, we know there is no point + // checking rounds earlier or later than validity rounds + if found { + if tx.Txn.FirstValid > minRound { + minRound = tx.Txn.FirstValid + } + + if tx.Txn.LastValid < maxRound { + maxRound = tx.Txn.LastValid + } + } + for r := maxRound; r >= minRound; r-- { tx, found, err := node.ledger.LookupTxid(txID, r) if err != nil || !found { From 9fc615830193f3fb4f0e527ca94871048021104d Mon Sep 17 00:00:00 2001 From: AlgoAxel <113933518+AlgoAxel@users.noreply.github.com> Date: Fri, 28 Apr 2023 09:44:05 -0400 Subject: [PATCH 072/125] Fix: Correct GeneratedAccountsMnemonics Check (#5274) --- cmd/pingpong/runCmd.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cmd/pingpong/runCmd.go b/cmd/pingpong/runCmd.go index 7813d4f313..fe545f1749 100644 --- a/cmd/pingpong/runCmd.go +++ b/cmd/pingpong/runCmd.go @@ -438,7 +438,9 @@ var runCmd = &cobra.Command{ cfg.GeneratedAccountSampleMethod = generatedAccountSampleMethod } // check if numAccounts is greater than the length of the mnemonic list, if provided - if cfg.DeterministicKeys && cfg.NumPartAccounts > uint32(len(cfg.GeneratedAccountsMnemonics)) { + if cfg.DeterministicKeys && + len(cfg.GeneratedAccountsMnemonics) > 0 && + cfg.NumPartAccounts > uint32(len(cfg.GeneratedAccountsMnemonics)) { reportErrorf("numAccounts is greater than number of account mnemonics provided") } From 59ea5e9c14e13d1e5f22e8c3949e36d07e7eaf67 Mon Sep 17 00:00:00 2001 From: cce <51567+cce@users.noreply.github.com> Date: Fri, 28 Apr 2023 11:45:59 -0400 Subject: [PATCH 073/125] testing: fix consensus version inconsistency in newTestLedger (#5342) --- ledger/eval/eval_test.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/ledger/eval/eval_test.go b/ledger/eval/eval_test.go index bbc218d9a0..2e61c92801 100644 --- a/ledger/eval/eval_test.go +++ b/ledger/eval/eval_test.go @@ -613,8 +613,11 @@ func newTestLedger(t testing.TB, balances bookkeeping.GenesisBalances) *evalTest tracer: nil, } + protoVersion := protocol.ConsensusFuture + proto := config.Consensus[protoVersion] + crypto.RandBytes(l.genesisHash[:]) - genBlock, err := bookkeeping.MakeGenesisBlock(protocol.ConsensusFuture, + genBlock, err := bookkeeping.MakeGenesisBlock(protoVersion, balances, "test", l.genesisHash) require.NoError(t, err) l.roundBalances[0] = balances.Balances @@ -622,12 +625,11 @@ func newTestLedger(t testing.TB, balances bookkeeping.GenesisBalances) *evalTest // calculate the accounts totals. var ot basics.OverflowTracker - proto := config.Consensus[protocol.ConsensusCurrentVersion] for _, acctData := range balances.Balances { l.latestTotals.AddAccount(proto, ledgercore.ToAccountData(acctData), &ot) } l.genesisProto = proto - l.genesisProtoVersion = protocol.ConsensusCurrentVersion + l.genesisProtoVersion = protoVersion require.False(t, genBlock.FeeSink.IsZero()) require.False(t, genBlock.RewardsPool.IsZero()) From c90fe8542539d4869ba1fb97608d151882afa721 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Fri, 28 Apr 2023 10:56:43 -0500 Subject: [PATCH 074/125] tools: search/replace indexer/conduit in block-generator (#5321) --- tools/block-generator/README.md | 67 ++++++++++++++++----- tools/block-generator/generator/generate.go | 2 +- tools/block-generator/generator/utils.go | 2 +- tools/block-generator/run_tests.sh | 12 ++-- tools/block-generator/runner/run.go | 17 +++--- tools/block-generator/runner/runner.go | 6 +- 6 files changed, 72 insertions(+), 34 deletions(-) diff --git a/tools/block-generator/README.md b/tools/block-generator/README.md index 5caff65225..4b2fd9b5a6 100644 --- a/tools/block-generator/README.md +++ b/tools/block-generator/README.md @@ -1,6 +1,6 @@ # Block Generator -This tool is used for testing Indexer import performance. It does this by generating synthetic blocks which are sent by mocking the Algod REST API endpoints that Indexer uses. +This tool is used for testing Conduit import performance. It does this by generating synthetic blocks which are sent by mocking the Algod REST API endpoints that Conduit uses. ## Scenario Configuration @@ -9,11 +9,11 @@ Block generator uses a YAML config file to describe the composition of each rand 2. Transaction type distribution 3. Transaction type specific configuration -At the time of writing, the block generator supports **payment** and **asset** transactions. The settings are hopefully, more or less, obvious. Distributions are specified as fractions of one, and the sum of all options must add up to one. +At the time of writing, the block generator supports **payment** and **asset** transactions. The settings are hopefully, more or less, obvious. Distributions are specified as fractions of 1.0, and the sum of all options must add up to 1.0. -Here is an example which uses all of the current options. Notice that the synthetic blocks are not required to follow algod limits, in this case the block size is specified as 19999, or four times larger than the current block size limit: +Here is an example which uses all of the current options. Notice that the synthetic blocks are not required to follow algod limits, in this case the block size is specified as 19999: ``` -name: "Mixed (jumbo)" +name: "Mixed (19,999)" genesis_accounts: 10000 genesis_account_balance: 1000000000000 tx_per_block: 19999 @@ -36,13 +36,15 @@ asset_delete_fraction: 0 ## Modes -The block generator can run in one of two modes, a standalone **daemon**, or a test suite **runner** +The block generator can run in one of two _modes_: +1. standalone **daemon** +2. test suite **runner** ### daemon -In standalone mode, a block-generator process starts and exposes the mock algod endpoints for **/genesis** and **/v2/blocks/{block}**. If you choose to query them manually, it only supports fetching blocks sequentially. This is due to the fact that it generates a pseudorandom stream of transactions and after each random transaction the state increments to the next. +In standalone daemon mode, a block-generator process starts and exposes the mock algod endpoints for **/genesis** and **/v2/blocks/{block}**. If you choose to query them manually, it only supports fetching blocks sequentially. This is due to the fact that it generates a pseudorandom stream of transactions and after each random transaction the state increments to the next. -Here is the help output: +Here is the help output for **daemon**: ```bash ~$ ./block-generator daemon -h Start the generator daemon in standalone mode. @@ -58,9 +60,9 @@ Flags: ### runner -For our usage, we want to run the same set of tests consistently across many scenarios and with many different releases. The runner mode automates this process by starting the **daemon** with many different configurations, managing a postgres database, and running a separate indexer process configured to use them. +The runner mode is well suited for runing the same set of tests consistently across many scenarios and for different releases. The runner mode automates this process by starting the **daemon** with many different configurations, managing a postgres database, and running a separate Conduit process configured to use them. -The results of the testing are written to the directory specified by the **--report-directory** option, and include many different metrics. In addition to the report, the indexer log is written to this directory. The files are named according to the scenario file, and end in "report" or "log". +The results of the testing are written to the directory specified by the **--report-directory** option, and include many different metrics. In addition to the report, the Conduit log is written to this directory. The files are named according to the scenario file, and end in "report" or "log". Here is an example report from running with a test duration of "1h": ``` @@ -92,20 +94,21 @@ final_overall_transactions_per_second:8493.40 final_uptime_seconds:3600.06 ``` -Here is the help output: +Here is the help output for **runner**: ```bash ~$ ./block-generator runner -h -Run test suite and collect results. +Run an automated test suite using the block-generator daemon and a provided conduit binary. Results are captured to a specified output directory. Usage: block-generator runner [flags] Flags: - --cpuprofile string Path where Indexer writes its CPU profile. + -i, --conduit-binary string Path to conduit binary. + --cpuprofile string Path where conduit writes its CPU profile. -h, --help help for runner - -i, --indexer-binary string Path to indexer binary. - -p, --indexer-port uint Port to start the server at. This is useful if you have a prometheus server for collecting additional data. (default 4010) - -l, --log-level string LogLevel to use when starting Indexer. [error, warn, info, debug, trace] (default "error") + -k, --keep-data-dir If set the validator will not delete the data directory after tests complete. + -l, --log-level string LogLevel to use when starting conduit. [panic, fatal, error, warn, info, debug, trace] (default "error") + -p, --metrics-port uint Port to start the metrics server at. (default 9999) -c, --postgres-connection-string string Postgres connection string. -r, --report-directory string Location to place test reports. --reset If set any existing report directory will be deleted before running tests. @@ -113,3 +116,37 @@ Flags: -d, --test-duration duration Duration to use for each scenario. (default 5m0s) --validate If set the validator will run after test-duration has elapsed to verify data is correct. An extra line in each report indicates validator success or failure. ``` + +## Example Scenario Run using Conduit and Postgres - `run_runner.sh` + +A typical **runner** scenario involves: +* a [scenario configuration](#scenario-configuration) file, e.g. [test_config.yml](./test_config.yml) +* access to a `conduit` binary to query the block generator's mock Algod endpoint and ingest the synthetic blocks +* 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) + +### 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: +* saved the conduit binary to `tools/block-generator/conduit` +* created a block generator scenario config at `tools/block-generator/scenario.yml` + +Then you can execute the following command to run the scenario: +```sh +./run_runner.sh ./conduit scenario.yml +``` + +### Scenario Report + +If all goes well, the run will generate a directory `tools/block-generator/OUTPUT_RUN_RUNNER_TEST` and in that directory you can see the statistics +of the run in `scenario.report`. + diff --git a/tools/block-generator/generator/generate.go b/tools/block-generator/generator/generate.go index a8c245a488..d1e5b2bda6 100644 --- a/tools/block-generator/generator/generate.go +++ b/tools/block-generator/generator/generate.go @@ -453,7 +453,7 @@ func (g *generator) WriteBlock(output io.Writer, round uint64) error { func (g *generator) WriteDeltas(output io.Writer, round uint64) error { delta, err := g.ledger.GetStateDeltaForRound(basics.Round(round)) if err != nil { - return fmt.Errorf("err getting state delta for round %d, %v", round, err) + return fmt.Errorf("err getting state delta for round %d: %w", round, err) } // msgp encode deltas data, err := encode(protocol.CodecHandle, delta) diff --git a/tools/block-generator/generator/utils.go b/tools/block-generator/generator/utils.go index 59ff7f2e06..5f048a0253 100644 --- a/tools/block-generator/generator/utils.go +++ b/tools/block-generator/generator/utils.go @@ -75,7 +75,7 @@ func encode(handle codec.Handle, obj interface{}) ([]byte, error) { err := enc.Encode(obj) if err != nil { - return nil, fmt.Errorf("failed to encode object: %v", err) + return nil, fmt.Errorf("failed to encode object: %w", err) } return output, nil } diff --git a/tools/block-generator/run_tests.sh b/tools/block-generator/run_tests.sh index 5d25d50af4..7916f45267 100755 --- a/tools/block-generator/run_tests.sh +++ b/tools/block-generator/run_tests.sh @@ -12,11 +12,11 @@ help() { echo " -v|--verbose enable verbose script output." echo " -c|--connection-string" echo " PostgreSQL connection string." - echo " -i|--indexer path to indexer binary." - echo " -s|--scenarios path to indexer test scenarios." + 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 Indexer." + echo " -l|--level log level to pass to conduit." echo " -g|--generator block-generator binary to run the generator." exit } @@ -33,7 +33,7 @@ while :; do GENERATOR_BINARY="${2-}" shift ;; - -i | --indexer) + -i | --conduit) CONDUIT_BINARY="${2-}" shift ;; @@ -67,12 +67,12 @@ if [ -z "$CONNECTION_STRING" ]; then fi if [ -z "$CONDUIT_BINARY" ]; then - echo "Missing required indexer binary parameter (-i / --indexer)." + echo "Missing required conduit binary parameter (-i / --conduit)." exit 1 fi if [ -z "$SCENARIOS" ]; then - echo "Missing required indexer test scenario parameter (-s / --scenarios)." + echo "Missing required conduit test scenario parameter (-s / --scenarios)." exit 1 fi diff --git a/tools/block-generator/runner/run.go b/tools/block-generator/runner/run.go index 5e68cd49ae..2792c94be7 100644 --- a/tools/block-generator/runner/run.go +++ b/tools/block-generator/runner/run.go @@ -19,6 +19,7 @@ package runner import ( "bytes" "context" + // embed conduit template config file _ "embed" "encoding/json" @@ -101,7 +102,7 @@ func (r *Args) run() 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.indexer-log", baseNameNoExt)) + logfile := path.Join(r.ReportDirectory, fmt.Sprintf("%s.conduit-log", baseNameNoExt)) dataDir := path.Join(r.ReportDirectory, fmt.Sprintf("%s_data", baseNameNoExt)) // create the data directory. if err := os.Mkdir(dataDir, os.ModeDir|os.ModePerm); err != nil { @@ -127,7 +128,7 @@ func (r *Args) run() error { defer func() { // Shutdown generator. if err := generatorShutdownFunc(); err != nil { - fmt.Printf("Failed to shutdown generator: %s\n", err) + fmt.Printf("failed to shutdown generator: %s\n", err) } }() @@ -140,7 +141,7 @@ func (r *Args) run() error { // create config file in the right data directory f, err := os.Create(path.Join(dataDir, "conduit.yml")) if err != nil { - return fmt.Errorf("creating conduit.yml: %v", err) + return fmt.Errorf("problem creating conduit.yml: %w", err) } defer f.Close() @@ -151,7 +152,7 @@ func (r *Args) run() error { err = t.Execute(f, conduitConfig) if err != nil { - return fmt.Errorf("execute template file: %v", err) + return fmt.Errorf("problem executing template file: %w", err) } // Start conduit @@ -162,7 +163,7 @@ func (r *Args) run() error { defer func() { // Shutdown conduit if err := conduitShutdownFunc(); err != nil { - fmt.Printf("Failed to shutdown Conduit: %s\n", err) + fmt.Printf("failed to shutdown Conduit: %s\n", err) } }() @@ -408,7 +409,7 @@ func startGenerator(configFile string, addr string, blockMiddleware func(http.Ha }, generator } -// startConduit starts the indexer. +// startConduit starts the conduit binary. func startConduit(dataDir string, conduitBinary string) (func() error, error) { cmd := exec.Command( conduitBinary, @@ -427,9 +428,9 @@ func startConduit(dataDir string, conduitBinary string) (func() error, error) { return func() error { if err := cmd.Process.Signal(os.Interrupt); err != nil { - fmt.Printf("failed to kill indexer process: %s\n", err) + fmt.Printf("failed to kill conduit process: %s\n", err) if err := cmd.Process.Kill(); err != nil { - return fmt.Errorf("failed to kill indexer process: %w", err) + return fmt.Errorf("failed to kill conduit process: %w", err) } } if err := cmd.Wait(); err != nil { diff --git a/tools/block-generator/runner/runner.go b/tools/block-generator/runner/runner.go index 6c653efaec..89e4ac88c4 100644 --- a/tools/block-generator/runner/runner.go +++ b/tools/block-generator/runner/runner.go @@ -48,14 +48,14 @@ func init() { 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.LogLevel, "log-level", "l", "error", "LogLevel to use when starting Indexer. [panic, fatal, error, warn, info, debug, trace]") - RunnerCmd.Flags().StringVarP(&runnerArgs.CPUProfilePath, "cpuprofile", "", "", "Path where Indexer writes its CPU profile.") + RunnerCmd.Flags().StringVarP(&runnerArgs.LogLevel, "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.") RunnerCmd.Flags().BoolVarP(&runnerArgs.ResetReportDir, "reset", "", false, "If set any existing report directory will be deleted before running tests.") RunnerCmd.Flags().BoolVarP(&runnerArgs.RunValidation, "validate", "", false, "If set the validator will run after test-duration has elapsed to verify data is correct. An extra line in each report indicates validator success or failure.") RunnerCmd.Flags().BoolVarP(&runnerArgs.KeepDataDir, "keep-data-dir", "k", false, "If set the validator will not delete the data directory after tests complete.") RunnerCmd.MarkFlagRequired("scenario") - RunnerCmd.MarkFlagRequired("indexer-binary") + RunnerCmd.MarkFlagRequired("conduit-binary") RunnerCmd.MarkFlagRequired("postgres-connection-string") RunnerCmd.MarkFlagRequired("report-directory") } From e24a35add0bbae0767c002f9f63cdeb8fed5326b Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Tue, 2 May 2023 09:06:08 -0500 Subject: [PATCH 075/125] tools: add block-generator into published build (#5351) --- scripts/build_package.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/build_package.sh b/scripts/build_package.sh index d8f169201d..7b538329ff 100755 --- a/scripts/build_package.sh +++ b/scripts/build_package.sh @@ -106,7 +106,7 @@ TOOLS_ROOT=${PKG_ROOT}/tools echo "Staging tools package files" -bin_files=("algons" "coroner" "dispenser" "netgoal" "nodecfg" "pingpong" "cc_service" "cc_agent" "cc_client" "loadgenerator" "COPYING" "dsign" "catchpointdump") +bin_files=("algons" "coroner" "dispenser" "netgoal" "nodecfg" "pingpong" "cc_service" "cc_agent" "cc_client" "loadgenerator" "COPYING" "dsign" "catchpointdump" "block-generator") mkdir -p ${TOOLS_ROOT} for bin in "${bin_files[@]}"; do cp ${GOPATHBIN}/${bin} ${TOOLS_ROOT} From d17c1b5b3472b9b17dd424577dfa023a8969d7af Mon Sep 17 00:00:00 2001 From: shiqizng <80276844+shiqizng@users.noreply.github.com> Date: Wed, 3 May 2023 09:37:23 -0400 Subject: [PATCH 076/125] tools: block generator data reporting (#5339) --- tools/block-generator/requirements.txt | 1 + tools/block-generator/runner/run.go | 5 +++ tools/block-generator/upload_metrics.py | 53 +++++++++++++++++++++++++ 3 files changed, 59 insertions(+) create mode 100644 tools/block-generator/requirements.txt create mode 100644 tools/block-generator/upload_metrics.py diff --git a/tools/block-generator/requirements.txt b/tools/block-generator/requirements.txt new file mode 100644 index 0000000000..829c2ea815 --- /dev/null +++ b/tools/block-generator/requirements.txt @@ -0,0 +1 @@ +datadog==0.45.0 \ No newline at end of file diff --git a/tools/block-generator/runner/run.go b/tools/block-generator/runner/run.go index 2792c94be7..2113b36a45 100644 --- a/tools/block-generator/runner/run.go +++ b/tools/block-generator/runner/run.go @@ -341,6 +341,11 @@ func (r *Args) runTest(report *os.File, metricsURL string, generatorURL string) return fmt.Errorf("problem collecting final metrics (%d / %s): %w", count, time.Since(start), err) } + // write scenario to report + scenario := path.Base(r.Path) + if _, err := report.WriteString(fmt.Sprintf("scenario:%s\n", scenario)); err != nil { + return fmt.Errorf("unable to write scenario to report: %w", err) + } // Collect results. durationStr := fmt.Sprintf("test_duration_seconds:%d\ntest_duration_actual_seconds:%f\n", uint64(r.RunDuration.Seconds()), diff --git a/tools/block-generator/upload_metrics.py b/tools/block-generator/upload_metrics.py new file mode 100644 index 0000000000..f623a0c55a --- /dev/null +++ b/tools/block-generator/upload_metrics.py @@ -0,0 +1,53 @@ +from datadog import initialize +from datadog import api +import os +import argparse + +parser = argparse.ArgumentParser(description="Upload performance metrics to Datadog") +parser.add_argument( + "-f", + "--perf-reports", + required=True, + action="store", + dest="files", + type=str, + nargs="*", + help="list of reports created by the block generator", +) +parser.add_argument( + "-c", + "--binary-version", + required=True, + help="Release version or the commit hash of the Conduit binary used during the performance test", +) +args = parser.parse_args() + + +def parse_report(report): + data = dict() + with open(report) as f: + for line in f: + tag, value = line.split(":") + data[tag] = value if tag == "scenario" else float(value) + return data + + +if __name__ == "__main__": + print("initializing datadog") + options = { + "api_key": os.getenv("DATADOG_API_KEY"), + "app_key": os.getenv("DATADOG_APP_KEY"), + } + initialize(**options) + for fp in args.files: + print(f"uploading metrics for {fp}") + data = parse_report(fp) + tags = [ + f"conduit_version:{args.binary_version}", + f'duration:{data["test_duration_seconds"]}s', + f'scenario:{data["scenario"]}', + ] + transactionsPerBlockAvgMetricName = "conduit.perf.transactions_per_second" + tps = data["final_overall_transactions_per_second"] + api.Metric.send(metric=transactionsPerBlockAvgMetricName, points=tps, tags=tags) + print("uploaded metrics") From 12bf7ffc2bba08eb2c25e73cc9ac2e424f84d248 Mon Sep 17 00:00:00 2001 From: John Lee Date: Wed, 3 May 2023 11:23:33 -0400 Subject: [PATCH 077/125] DevOps: Add CODEOWNERS to restrict workflow edits (#5353) --- .github/workflows/pr-type-category.yml | 4 +++- CODEOWNERS | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 CODEOWNERS diff --git a/.github/workflows/pr-type-category.yml b/.github/workflows/pr-type-category.yml index 8dd4cfcd25..478b0b90e4 100644 --- a/.github/workflows/pr-type-category.yml +++ b/.github/workflows/pr-type-category.yml @@ -17,8 +17,10 @@ jobs: labels: "New Feature, Enhancement, Bug-Fix, Not-Yet-Enabled, Skip-Release-Notes" - name: "Checking for PR Category in PR title. Should be like ': '." + env: + PR_TITLE: ${{ github.event.pull_request.title }} run: | - if [[ ! "${{ github.event.pull_request.title }}" =~ ^.{2,}\:.{2,} ]]; then + if [[ ! "$PR_TITLE" =~ ^.{2,}\:.{2,} ]]; then echo "## PR Category is missing from PR title. Please add it like ': '." >> GITHUB_STEP_SUMMARY exit 1 fi diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 0000000000..aa26c82a49 --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,2 @@ +.github/ @algorand/dev +.circleci/ @algorand/dev From 1634285a7cbbb5860cb7d5b1b0f6d754b83b2fbb Mon Sep 17 00:00:00 2001 From: John Lee Date: Wed, 3 May 2023 16:27:18 -0400 Subject: [PATCH 078/125] DevOps: fix codecov uploading (#5345) --- .circleci/config.yml | 9 +++++++++ scripts/travis/codecov | 23 ++++++++++++++--------- scripts/travis/upload_coverage.sh | 14 ++++---------- 3 files changed, 27 insertions(+), 19 deletions(-) mode change 100755 => 100644 scripts/travis/codecov diff --git a/.circleci/config.yml b/.circleci/config.yml index d98d70c1de..34e111cada 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -22,6 +22,11 @@ parameters: valid_nightly_branch: type: string default: /hotfix\/.*/ + # The following is intentional - hardcoding a token for public repos + # is recommended here to allow fork access + codecov: + type: string + default: "8b4a1f91-f154-4c26-b84c-c9aaa90159c6" executors: amd64_medium: @@ -239,6 +244,8 @@ jobs: executor: << parameters.platform >>_medium working_directory: << pipeline.parameters.build_dir >>/project parallelism: 32 + environment: + CODECOV_TOKEN: << pipeline.parameters.codecov >> steps: - generic_build - generic_test: @@ -254,6 +261,8 @@ jobs: executor: << parameters.platform >>_large working_directory: << pipeline.parameters.build_dir >>/project parallelism: 4 + environment: + CODECOV_TOKEN: << pipeline.parameters.codecov >> steps: - generic_build - generic_test: diff --git a/scripts/travis/codecov b/scripts/travis/codecov old mode 100755 new mode 100644 index d443a8979c..36513ce06d --- a/scripts/travis/codecov +++ b/scripts/travis/codecov @@ -5,7 +5,7 @@ set -e +o pipefail -VERSION="1.0.3" +VERSION="1.0.6" codecov_flags=( ) url="https://codecov.io" @@ -865,14 +865,17 @@ then if [ "$GITHUB_HEAD_REF" != "" ]; then # PR refs are in the format: refs/pull/7/merge - pr="${GITHUB_REF#refs/pull/}" - pr="${pr%/merge}" + if [[ "$GITHUB_REF" =~ ^refs\/pull\/[0-9]+\/merge$ ]]; + then + pr="${GITHUB_REF#refs/pull/}" + pr="${pr%/merge}" + fi branch="${GITHUB_HEAD_REF}" fi commit="${GITHUB_SHA}" slug="${GITHUB_REPOSITORY}" build="${GITHUB_RUN_ID}" - build_url=$(urlencode "http://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}") + build_url=$(urlencode "${GITHUB_SERVER_URL:-https://github.com}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}") job="$(urlencode "${GITHUB_WORKFLOW}")" # actions/checkout runs in detached HEAD @@ -987,6 +990,7 @@ else fi +say " ${e}current dir: ${x} $PWD" say " ${e}project root:${x} $git_root" # find branch, commit, repo from git command @@ -1618,7 +1622,7 @@ then # [ or ] syntax_list='^[[:space:]]*[][][[:space:]]*(//.*)?$' # func ... { - syntax_go_func='^[[:space:]]*[func].*[\{][[:space:]]*$' + syntax_go_func='^[[:space:]]*func[[:space:]]*[\{][[:space:]]*$' # shellcheck disable=SC2089 skip_dirs="-not -path '*/$bower_components/*' \ @@ -1783,7 +1787,7 @@ if [ "$dump" != "0" ]; then # trim whitespace from query say " ${e}->${x} Dumping upload file (no upload)" - echo "$url/upload/v4?$(echo "package=$package-$VERSION&token=$token&$query" | tr -d ' ')" + echo "$url/upload/v4?$(echo "package=$package-$VERSION&$query" | tr -d ' ')" cat "$upload_file" else if [ "$save_to" != "" ]; @@ -1802,10 +1806,9 @@ else say " ${e}url:${x} $url" say " ${e}query:${x} $query" - # Full query without token (to display on terminal output) - queryNoToken=$(echo "package=$package-$VERSION&token=secret&$query" | tr -d ' ') - # now add token to query + # Full query (to display on terminal output) query=$(echo "package=$package-$VERSION&token=$token&$query" | tr -d ' ') + queryNoToken=$(echo "package=$package-$VERSION&token=&$query" | tr -d ' ') if [ "$ft_s3" = "1" ]; then @@ -1817,6 +1820,7 @@ else -H 'X-Reduced-Redundancy: false' \ -H 'X-Content-Type: application/x-gzip' \ -H 'Content-Length: 0' \ + -H "X-Upload-Token: ${token}" \ --write-out "\n%{response_code}\n" \ $curlargs \ "$url/upload/v4?$query" || true) @@ -1863,6 +1867,7 @@ else -H 'Content-Type: text/plain' \ -H 'Content-Encoding: gzip' \ -H 'X-Content-Encoding: gzip' \ + -H "X-Upload-Token: ${token}" \ -H 'Accept: text/plain' \ $curlargs \ "$url/upload/v2?$query&attempt=$i" || echo 'HTTP 500') diff --git a/scripts/travis/upload_coverage.sh b/scripts/travis/upload_coverage.sh index e861b7ef98..8ab3b56ea6 100755 --- a/scripts/travis/upload_coverage.sh +++ b/scripts/travis/upload_coverage.sh @@ -1,15 +1,9 @@ #!/usr/bin/env bash -# Print a warning when there is a new version notification before uploading the -# coverage report to codecov. set -eo pipefail -# Check if there is a new version. -curl -fLso codecov https://codecov.io/bash -UPSTREAM_VERSION=$(grep -o 'VERSION=\"[0-9\.]*\"' codecov | cut -d'"' -f2) -LOCAL_VERSION=$(grep -o 'VERSION=\"[0-9\.]*\"' scripts/travis/codecov | cut -d'"' -f2) -if [[ "${UPSTREAM_VERSION}" != "${LOCAL_VERSION}" ]]; then - echo "WARN: version ${UPSTREAM_VERSION} of the codecov upload script is available." +if [[ -z "$CODECOV_TOKEN" ]]; then + /usr/bin/env bash scripts/travis/codecov +else + /usr/bin/env bash scripts/travis/codecov -t $CODECOV_TOKEN fi - -/usr/bin/env bash scripts/travis/codecov From 78181832aaa80e8368ba8053a8f76569a29badb3 Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Thu, 4 May 2023 08:01:30 -0700 Subject: [PATCH 079/125] goal: Make goal state schema optional (#5356) --- cmd/goal/README.md | 2 +- cmd/goal/application.go | 4 -- config/consensus.go | 4 ++ data/transactions/transaction.go | 3 + data/transactions/transaction_test.go | 27 ++++++++ .../e2e_subs/access-previous-scratch.sh | 2 +- test/scripts/e2e_subs/box-search.sh | 12 +++- test/scripts/e2e_subs/e2e-app-abi-arg.sh | 2 +- test/scripts/e2e_subs/e2e-app-abi-method.sh | 4 +- test/scripts/e2e_subs/e2e-app-bootloader.sh | 2 +- test/scripts/e2e_subs/e2e-app-delete-clear.sh | 4 +- test/scripts/e2e_subs/e2e-app-extra-pages.sh | 10 +-- .../goal-app-create-state-defaults.sh | 64 +++++++++++++++++++ test/scripts/e2e_subs/teal-creatable-id.sh | 4 +- 14 files changed, 124 insertions(+), 20 deletions(-) create mode 100755 test/scripts/e2e_subs/goal-app-create-state-defaults.sh diff --git a/cmd/goal/README.md b/cmd/goal/README.md index 2ecebf65bf..f33e1ce41b 100644 --- a/cmd/goal/README.md +++ b/cmd/goal/README.md @@ -68,7 +68,7 @@ TEALDIR=cmd/goal/examples echo $TEALDIR # create the app and TAKE NOTE of its "app index" -goal app create --creator ${ACCOUNT} --approval-prog ${TEALDIR}/boxes.teal --clear-prog ${TEALDIR}/clear.teal --global-byteslices 0 --global-ints 0 --local-byteslices 0 --local-ints 0 +goal app create --creator ${ACCOUNT} --approval-prog ${TEALDIR}/boxes.teal --clear-prog ${TEALDIR}/clear.teal ``` For the following questions, you'll need to use the app index. That will be shown in the last line printed. EG: diff --git a/cmd/goal/application.go b/cmd/goal/application.go index 0c5e9e0cbf..cb048ce50d 100644 --- a/cmd/goal/application.go +++ b/cmd/goal/application.go @@ -163,10 +163,6 @@ func init() { readStateAppCmd.Flags().BoolVar(&guessFormat, "guess-format", false, "Format application state using heuristics to guess data encoding.") createAppCmd.MarkFlagRequired("creator") - createAppCmd.MarkFlagRequired("global-ints") - createAppCmd.MarkFlagRequired("global-byteslices") - createAppCmd.MarkFlagRequired("local-ints") - createAppCmd.MarkFlagRequired("local-byteslices") optInAppCmd.MarkFlagRequired("app-id") optInAppCmd.MarkFlagRequired("from") diff --git a/config/consensus.go b/config/consensus.go index e3bf4d527f..59f9721a4e 100644 --- a/config/consensus.go +++ b/config/consensus.go @@ -498,6 +498,9 @@ type ConsensusParams struct { // EnableCatchpointsWithSPContexts specifies when to re-enable version 7 catchpoints. // Version 7 includes state proof verification contexts EnableCatchpointsWithSPContexts bool + + // EnableBoxRefNameError specifies that box ref names should be validated early + EnableBoxRefNameError bool } // PaysetCommitType enumerates possible ways for the block header to commit to @@ -1266,6 +1269,7 @@ func initConsensusProtocols() { vFuture.LogicSigVersion = 9 // When moving this to a release, put a new higher LogicSigVersion here vFuture.EnablePrecheckECDSACurve = true vFuture.EnableBareBudgetError = true + vFuture.EnableBoxRefNameError = true vFuture.StateProofUseTrackerVerification = true vFuture.EnableCatchpointsWithSPContexts = true diff --git a/data/transactions/transaction.go b/data/transactions/transaction.go index 6c1b56fd29..5877e99c7f 100644 --- a/data/transactions/transaction.go +++ b/data/transactions/transaction.go @@ -477,6 +477,9 @@ func (tx Transaction) WellFormed(spec SpecialAddresses, proto config.ConsensusPa if br.Index > uint64(len(tx.ForeignApps)) { return fmt.Errorf("tx.Boxes[%d].Index is %d. Exceeds len(tx.ForeignApps)", i, br.Index) } + if proto.EnableBoxRefNameError && len(br.Name) > proto.MaxAppKeyLen { + return fmt.Errorf("tx.Boxes[%d].Name too long, max len %d bytes", i, proto.MaxAppKeyLen) + } } if tx.LocalStateSchema.NumEntries() > proto.MaxLocalSchemaEntries { diff --git a/data/transactions/transaction_test.go b/data/transactions/transaction_test.go index 51f69ce714..0f3cf18258 100644 --- a/data/transactions/transaction_test.go +++ b/data/transactions/transaction_test.go @@ -274,6 +274,7 @@ func TestWellFormedErrors(t *testing.T) { protoV27 := config.Consensus[protocol.ConsensusV27] protoV28 := config.Consensus[protocol.ConsensusV28] protoV32 := config.Consensus[protocol.ConsensusV32] + protoV36 := config.Consensus[protocol.ConsensusV36] addr1, err := basics.UnmarshalChecksumAddress("NDQCJNNY5WWWFLP4GFZ7MEF2QJSMZYK6OWIV2AQ7OMAVLEFCGGRHFPKJJA") require.NoError(t, err) v5 := []byte{0x05} @@ -566,6 +567,32 @@ func TestWellFormedErrors(t *testing.T) { proto: protoV32, expectedError: fmt.Errorf("tx.Boxes too long, max number of box references is 0"), }, + { + tx: Transaction{ + Type: protocol.ApplicationCallTx, + Header: okHeader, + ApplicationCallTxnFields: ApplicationCallTxnFields{ + ApplicationID: 1, + Boxes: []BoxRef{{Index: 1, Name: make([]byte, 65)}}, + ForeignApps: []basics.AppIndex{1}, + }, + }, + proto: futureProto, + expectedError: fmt.Errorf("tx.Boxes[0].Name too long, max len 64 bytes"), + }, + { + tx: Transaction{ + Type: protocol.ApplicationCallTx, + Header: okHeader, + ApplicationCallTxnFields: ApplicationCallTxnFields{ + ApplicationID: 1, + Boxes: []BoxRef{{Index: 1, Name: make([]byte, 65)}}, + ForeignApps: []basics.AppIndex{1}, + }, + }, + proto: protoV36, + expectedError: nil, + }, } for _, usecase := range usecases { err := usecase.tx.WellFormed(specialAddr, usecase.proto) diff --git a/test/scripts/e2e_subs/access-previous-scratch.sh b/test/scripts/e2e_subs/access-previous-scratch.sh index 7c519ec43d..f8dd54d966 100755 --- a/test/scripts/e2e_subs/access-previous-scratch.sh +++ b/test/scripts/e2e_subs/access-previous-scratch.sh @@ -17,7 +17,7 @@ gcmd="goal -w ${WALLET}" ACCOUNT=$(${gcmd} account list|awk '{ print $3 }') -APPID=$(${gcmd} app create --creator "${ACCOUNT}" --approval-prog=${TEAL}/scratch-rw.teal --global-byteslices 0 --global-ints 0 --local-byteslices 0 --local-ints 0 --clear-prog=${TEAL}/approve-all.teal | grep Created | awk '{ print $6 }') +APPID=$(${gcmd} app create --creator "${ACCOUNT}" --approval-prog=${TEAL}/scratch-rw.teal --clear-prog=${TEAL}/approve-all.teal | grep Created | awk '{ print $6 }') # Create app calls function create_app_call { diff --git a/test/scripts/e2e_subs/box-search.sh b/test/scripts/e2e_subs/box-search.sh index 8803e87851..29ed02757b 100755 --- a/test/scripts/e2e_subs/box-search.sh +++ b/test/scripts/e2e_subs/box-search.sh @@ -20,7 +20,7 @@ ACCOUNT=$(${gcmd} account list|awk '{ print $3 }') # Version 8 clear program printf '#pragma version 8\nint 1' > "${TEMPDIR}/clear.teal" -APPID=$(${gcmd} app create --creator "$ACCOUNT" --approval-prog=${TEAL}/boxes.teal --clear-prog "$TEMPDIR/clear.teal" --global-byteslices 0 --global-ints 0 --local-byteslices 0 --local-ints 0 | grep Created | awk '{ print $6 }') +APPID=$(${gcmd} app create --creator "$ACCOUNT" --approval-prog=${TEAL}/boxes.teal --clear-prog "$TEMPDIR/clear.teal" | grep Created | awk '{ print $6 }') # Fund the app account 10 algos APP_ACCOUNT=$(${gcmd} app info --app-id "$APPID" | grep "Application account" | awk '{print $3}') @@ -38,6 +38,16 @@ EXPECTED="No box found for appid $APPID with name str:not_found" [ "$BOX_INFO" = "$EXPECTED" ] +# Confirm that we error for an invalid box name +BOX_NAME="str:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +RES=$(${gcmd} app call --from "$ACCOUNT" --app-id "$APPID" --box "$BOX_NAME" --app-arg "str:create" --app-arg "$BOX_NAME" 2>&1 || true) +EXPECTED="invalid : tx.Boxes[0].Name too long, max len 64 bytes" + +if [[ "$RES" != *"$EXPECTED" ]]; then + date "+${scriptname} unexpected response from goal app call with invalid box name %Y%m%d_%H%M%S" + false +fi + # Create several boxes BOX_NAMES=("str:box1" "str:with spaces" "b64:YmFzZTY0" "b64:AQIDBA==") # b64:YmFzZTY0 == str:base64, b64:AQIDBA== is not unicode BOX_VALUE="box value" diff --git a/test/scripts/e2e_subs/e2e-app-abi-arg.sh b/test/scripts/e2e_subs/e2e-app-abi-arg.sh index c6f719a479..336e892c2f 100755 --- a/test/scripts/e2e_subs/e2e-app-abi-arg.sh +++ b/test/scripts/e2e_subs/e2e-app-abi-arg.sh @@ -19,7 +19,7 @@ ACCOUNT=$(${gcmd} account list|awk '{ print $3 }') printf '#pragma version 2\nint 1' > "${TEMPDIR}/simple.teal" PROGRAM=($(${gcmd} clerk compile "${TEMPDIR}/simple.teal")) -APPID=$(${gcmd} app create --creator ${ACCOUNT} --approval-prog ${DIR}/tealprogs/app-abi-arg.teal --clear-prog ${TEMPDIR}/simple.teal --global-byteslices 0 --global-ints ${GLOBAL_INTS} --local-byteslices 0 --local-ints 0 | grep Created | awk '{ print $6 }') +APPID=$(${gcmd} app create --creator ${ACCOUNT} --approval-prog ${DIR}/tealprogs/app-abi-arg.teal --clear-prog ${TEMPDIR}/simple.teal --global-ints ${GLOBAL_INTS} | grep Created | awk '{ print $6 }') # Should succeed to opt in with string "optin" ${gcmd} app optin --app-id $APPID --from $ACCOUNT --app-arg 'abi:string:"optin"' diff --git a/test/scripts/e2e_subs/e2e-app-abi-method.sh b/test/scripts/e2e_subs/e2e-app-abi-method.sh index ac5d4f5925..b41d87b93e 100755 --- a/test/scripts/e2e_subs/e2e-app-abi-method.sh +++ b/test/scripts/e2e_subs/e2e-app-abi-method.sh @@ -20,7 +20,7 @@ printf '#pragma version 2\nint 1' > "${TEMPDIR}/simple-v2.teal" printf '#pragma version 3\nint 1' > "${TEMPDIR}/simple-v3.teal" # Create -RES=$(${gcmd} app method --method "create(uint64)uint64" --arg "1234" --create --approval-prog ${DIR}/tealprogs/app-abi-method-example.teal --clear-prog ${TEMPDIR}/simple-v2.teal --global-byteslices 0 --global-ints 0 --local-byteslices 1 --local-ints 0 --extra-pages 0 --from $ACCOUNT 2>&1 || true) +RES=$(${gcmd} app method --method "create(uint64)uint64" --arg "1234" --create --approval-prog ${DIR}/tealprogs/app-abi-method-example.teal --clear-prog ${TEMPDIR}/simple-v2.teal --local-byteslices 1 --from $ACCOUNT 2>&1 || true) EXPECTED="method create(uint64)uint64 succeeded with output: 2468" if [[ $RES != *"${EXPECTED}"* ]]; then date '+app-abi-method-test FAIL the method call to create(uint64)uint64 should not fail %Y%m%d_%H%M%S' @@ -112,7 +112,7 @@ if [[ $RES != *"${EXPECTED}"* ]]; then fi # Foreign reference test during creation -RES=$(${gcmd} app method --create --approval-prog ${DIR}/tealprogs/app-abi-method-example.teal --clear-prog ${TEMPDIR}/simple-v2.teal --global-byteslices 0 --global-ints 0 --local-byteslices 1 --local-ints 0 --extra-pages 0 --on-completion deleteapplication --method "referenceTest(account,application,account,asset,account,asset,asset,application,application)uint8[9]" --arg KGTOR3F3Q74JP4LB5M3SOCSJ4BOPOKZ2GPSLMLLGCWYWRXZJNN4LYQJXXU --arg 0 --arg $ACCOUNT --arg 10 --arg KGTOR3F3Q74JP4LB5M3SOCSJ4BOPOKZ2GPSLMLLGCWYWRXZJNN4LYQJXXU --arg 11 --arg 10 --arg 20 --arg 21 --app-account 2R5LMPTYLVMWYEG4RPI26PJAM7ARTGUB7LZSONQPGLUWTPOP6LQCJTQZVE --foreign-app 21 --foreign-asset 10 --from $ACCOUNT 2>&1 || true) +RES=$(${gcmd} app method --create --approval-prog ${DIR}/tealprogs/app-abi-method-example.teal --clear-prog ${TEMPDIR}/simple-v2.teal --local-byteslices 1 --on-completion deleteapplication --method "referenceTest(account,application,account,asset,account,asset,asset,application,application)uint8[9]" --arg KGTOR3F3Q74JP4LB5M3SOCSJ4BOPOKZ2GPSLMLLGCWYWRXZJNN4LYQJXXU --arg 0 --arg $ACCOUNT --arg 10 --arg KGTOR3F3Q74JP4LB5M3SOCSJ4BOPOKZ2GPSLMLLGCWYWRXZJNN4LYQJXXU --arg 11 --arg 10 --arg 20 --arg 21 --app-account 2R5LMPTYLVMWYEG4RPI26PJAM7ARTGUB7LZSONQPGLUWTPOP6LQCJTQZVE --foreign-app 21 --foreign-asset 10 --from $ACCOUNT 2>&1 || true) EXPECTED="method referenceTest(account,application,account,asset,account,asset,asset,application,application)uint8[9] succeeded with output: [2,0,2,0,2,1,0,1,0]" if [[ $RES != *"${EXPECTED}"* ]]; then date '+app-abi-method-test FAIL the creation method call to referenceTest(account,application,account,asset,account,asset,asset,application,application)uint8[9] should not fail %Y%m%d_%H%M%S' diff --git a/test/scripts/e2e_subs/e2e-app-bootloader.sh b/test/scripts/e2e_subs/e2e-app-bootloader.sh index 170a96f09e..e9c22a65fb 100755 --- a/test/scripts/e2e_subs/e2e-app-bootloader.sh +++ b/test/scripts/e2e_subs/e2e-app-bootloader.sh @@ -33,7 +33,7 @@ sed -i"" -e "s/TMPL_CLEARSTATE_HASH/${TARGET_HASH}/g" ${TEMPDIR}/bootloader.teal # Create an app using filled-in bootloader template printf '#pragma version 2\nint 1' > "${TEMPDIR}/int1.teal" -APPID=$(${gcmd} app create --creator ${ACCOUNT} --approval-prog ${TEMPDIR}/bootloader.teal --global-byteslices 1 --global-ints 0 --local-byteslices 0 --local-ints 0 --clear-prog "${TEMPDIR}/int1.teal" | grep Created | awk '{ print $6 }') +APPID=$(${gcmd} app create --creator ${ACCOUNT} --approval-prog ${TEMPDIR}/bootloader.teal --global-byteslices 1 --clear-prog "${TEMPDIR}/int1.teal" | grep Created | awk '{ print $6 }') # Calling app without args and wrong OnCompletion should fail EXPERROR='rejected by ApprovalProgram' diff --git a/test/scripts/e2e_subs/e2e-app-delete-clear.sh b/test/scripts/e2e_subs/e2e-app-delete-clear.sh index 9373c1ba84..2795f869fc 100755 --- a/test/scripts/e2e_subs/e2e-app-delete-clear.sh +++ b/test/scripts/e2e_subs/e2e-app-delete-clear.sh @@ -20,7 +20,7 @@ PROGRAM_FILE="${TEMPDIR}/simple.teal" GLOBAL_INTS=2 # Succeed in creating app with on-completion delete -APPID=$(${gcmd} app create --creator ${ACCOUNT} --on-completion "DeleteApplication" --approval-prog "${PROGRAM_FILE}" --clear-prog "${PROGRAM_FILE}" --global-byteslices 0 --global-ints ${GLOBAL_INTS} --local-byteslices 0 --local-ints 0 | grep Created | awk '{ print $6 }') +APPID=$(${gcmd} app create --creator ${ACCOUNT} --on-completion "DeleteApplication" --approval-prog "${PROGRAM_FILE}" --clear-prog "${PROGRAM_FILE}" --global-ints ${GLOBAL_INTS} | grep Created | awk '{ print $6 }') # Check that the app is not created APPID_CHECK=$(${gcmd} app info --app-id $APPID 2>&1 || true) EXPERROR="application does not exist" @@ -30,7 +30,7 @@ if [[ $APPID_CHECK != *"${EXPERROR}"* ]]; then fi # Fail if creating app with on-completion clear -RES=$(${gcmd} app create --creator ${ACCOUNT} --on-completion "ClearState" --approval-prog "${PROGRAM_FILE}" --clear-prog "${PROGRAM_FILE}" --global-byteslices 0 --global-ints ${GLOBAL_INTS} --local-byteslices 0 --local-ints 0 2>&1 || true ) +RES=$(${gcmd} app create --creator ${ACCOUNT} --on-completion "ClearState" --approval-prog "${PROGRAM_FILE}" --clear-prog "${PROGRAM_FILE}" --global-ints ${GLOBAL_INTS} 2>&1 || true ) EXPERROR1='cannot clear state' EXPERROR2='is not currently opted in' if [[ $RES != *"${EXPERROR1}"*"${EXPERROR2}"* ]]; then diff --git a/test/scripts/e2e_subs/e2e-app-extra-pages.sh b/test/scripts/e2e_subs/e2e-app-extra-pages.sh index 1b9dd779c0..b2d3cf8709 100755 --- a/test/scripts/e2e_subs/e2e-app-extra-pages.sh +++ b/test/scripts/e2e_subs/e2e-app-extra-pages.sh @@ -52,7 +52,7 @@ generate_teal "$APPR_PROG" 4 3072 1 "int 0\nbalance\npop\n" generate_teal "$BIG_APPR_PROG" 4 4098 1 "int 0\nbalance\npop\n" # App create fails. Approval program too long -RES=$(${gcmd} app create --creator ${ACCOUNT} --approval-prog "${BIG_TEAL_FILE}" --clear-prog "${BIG_TEAL_FILE}" --global-byteslices 1 --global-ints 0 --local-byteslices 0 --local-ints 0 2>&1 || true) +RES=$(${gcmd} app create --creator ${ACCOUNT} --approval-prog "${BIG_TEAL_FILE}" --clear-prog "${BIG_TEAL_FILE}" --global-byteslices 1 2>&1 || true) EXPERROR="approval program too long. max len 2048 bytes" if [[ $RES != *"${EXPERROR}"* ]]; then date '+app-extra-pages-test FAIL the application creation should fail %Y%m%d_%H%M%S' @@ -60,7 +60,7 @@ if [[ $RES != *"${EXPERROR}"* ]]; then fi # App create fails. Clear state program too long -RES=$(${gcmd} app create --creator ${ACCOUNT} --approval-prog "${SMALL_TEAL_FILE}" --clear-prog "${BIG_TEAL_FILE}" --global-byteslices 1 --global-ints 0 --local-byteslices 0 --local-ints 0 2>&1 || true) +RES=$(${gcmd} app create --creator ${ACCOUNT} --approval-prog "${SMALL_TEAL_FILE}" --clear-prog "${BIG_TEAL_FILE}" --global-byteslices 1 2>&1 || true) EXPERROR="clear state program too long. max len 2048 bytes" if [[ $RES != *"${EXPERROR}"* ]]; then date '+app-extra-pages-test FAIL the application creation should fail %Y%m%d_%H%M%S' @@ -68,7 +68,7 @@ if [[ $RES != *"${EXPERROR}"* ]]; then fi # App create with extra pages, v3 teal -RES=$(${gcmd} app create --creator ${ACCOUNT} --approval-prog "${BIG_TEAL_FILE}" --clear-prog "${BIG_TEAL_FILE}" --extra-pages 3 --global-byteslices 1 --global-ints 0 --local-byteslices 0 --local-ints 0 2>&1 || true) +RES=$(${gcmd} app create --creator ${ACCOUNT} --approval-prog "${BIG_TEAL_FILE}" --clear-prog "${BIG_TEAL_FILE}" --extra-pages 3 --global-byteslices 1 2>&1 || true) EXPERROR="pc=705 static cost budget of 700 exceeded" if [[ $RES != *"${EXPERROR}"* ]]; then date '+app-extra-pages-test FAIL the application creation should fail %Y%m%d_%H%M%S' @@ -76,7 +76,7 @@ if [[ $RES != *"${EXPERROR}"* ]]; then fi # App create with extra pages, v4 teal -RES=$(${gcmd} app create --creator ${ACCOUNT} --approval-prog "${BIG_TEAL_V4_FILE}" --clear-prog "${BIG_TEAL_V4_FILE}" --extra-pages 3 --global-byteslices 1 --global-ints 0 --local-byteslices 0 --local-ints 0 2>&1 || true) +RES=$(${gcmd} app create --creator ${ACCOUNT} --approval-prog "${BIG_TEAL_V4_FILE}" --clear-prog "${BIG_TEAL_V4_FILE}" --extra-pages 3 --global-byteslices 1 2>&1 || true) EXPERROR="pc=704 dynamic cost budget exceeded, executing intc_0: local program cost was 700" if [[ $RES != *"${EXPERROR}"* ]]; then date '+app-extra-pages-test FAIL the application creation should fail %Y%m%d_%H%M%S' @@ -84,7 +84,7 @@ if [[ $RES != *"${EXPERROR}"* ]]; then fi # App create with extra pages, succeeded -RES=$(${gcmd} app create --creator ${ACCOUNT} --approval-prog "${SMALL_TEAL_FILE}" --clear-prog "${SMALL_TEAL_FILE}" --extra-pages 1 --global-byteslices 1 --global-ints 0 --local-byteslices 0 --local-ints 0 2>&1 || true) +RES=$(${gcmd} app create --creator ${ACCOUNT} --approval-prog "${SMALL_TEAL_FILE}" --clear-prog "${SMALL_TEAL_FILE}" --extra-pages 1 --global-byteslices 1 2>&1 || true) EXP="Created app" APPID=$(echo $RES | awk '{print $NF}') if [[ $RES != *"${EXP}"* ]]; then diff --git a/test/scripts/e2e_subs/goal-app-create-state-defaults.sh b/test/scripts/e2e_subs/goal-app-create-state-defaults.sh new file mode 100755 index 0000000000..c77faa64db --- /dev/null +++ b/test/scripts/e2e_subs/goal-app-create-state-defaults.sh @@ -0,0 +1,64 @@ +#!/bin/bash + +scriptname="goal-app-create-state-defaults" +date "+${scriptname} start %Y%m%d_%H%M%S" + +set -e +set -x +set -o pipefail +export SHELLOPTS + +WALLET=$1 + +# Directory of this bash program +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" + +gcmd="goal -w ${WALLET}" + +ACCOUNT=$(${gcmd} account list|awk '{ print $3 }') + +printf '#pragma version 2\nint 1' > "${TEMPDIR}/simple.teal" + +# Check goal flags --global-byteslices, --global-ints, --local-byteslices, --local-ints. We want to +# ensure that omitting these flags has the same effect as setting them to 0. + +APP_CREATE_TXN_NO_STATE_FILE="${TEMPDIR}/create_no_state.txn" +APP_CREATE_TXN_NO_FULLY_SPECIFIED_FILE="${TEMPDIR}/create_fully_specified.txn" + +# Checks for 'goal app create' + +# Passing a note is needed because goal will sometimes try to customize a note +# to avoid duplicate txns + +${gcmd} app create --note "hello" --creator "${ACCOUNT}" --approval-prog "${TEMPDIR}/simple.teal" --clear-prog "${TEMPDIR}/simple.teal" --out "${APP_CREATE_TXN_NO_STATE_FILE}" +APP_CREATE_TXN_NO_STATE=$(msgpacktool -d < "${APP_CREATE_TXN_NO_STATE_FILE}") + +FIRSTVALID=$(echo $APP_CREATE_TXN_NO_STATE | jq ".txn.fv") + +# Passing --firstvalid is used for subsequent transactions to ensure they have +# the same valid range as the first txn + +${gcmd} app create --note "hello" --creator "${ACCOUNT}" --approval-prog "${TEMPDIR}/simple.teal" --clear-prog "${TEMPDIR}/simple.teal" --global-byteslices 0 --global-ints 0 --local-byteslices 0 --local-ints 0 --firstvalid $FIRSTVALID --out "${APP_CREATE_TXN_NO_FULLY_SPECIFIED_FILE}" +APP_CREATE_TXN_NO_FULLY_SPECIFIED=$(msgpacktool -d < "${APP_CREATE_TXN_NO_FULLY_SPECIFIED_FILE}") + +if [ "$APP_CREATE_TXN_NO_FULLY_SPECIFIED" != "$APP_CREATE_TXN_NO_STATE" ]; then + date "+${scriptname} transactions made with 'goal app create' are not equal %Y%m%d_%H%M%S" + false +fi + +# Checks for 'goal method --create' + +${gcmd} app method --create --note "hello" --from "${ACCOUNT}" --method "create(uint64)uint64" --arg "1234" --create --approval-prog "${TEMPDIR}/simple.teal" --clear-prog "${TEMPDIR}/simple.teal" --out "${APP_CREATE_TXN_NO_STATE_FILE}" +APP_CREATE_TXN_NO_STATE=$(msgpacktool -d < "${APP_CREATE_TXN_NO_STATE_FILE}") + +FIRSTVALID=$(echo $APP_CREATE_TXN_NO_STATE | jq ".txn.fv") + +${gcmd} app method --create --note "hello" --from "${ACCOUNT}" --method "create(uint64)uint64" --arg "1234" --create --approval-prog "${TEMPDIR}/simple.teal" --clear-prog "${TEMPDIR}/simple.teal" --global-byteslices 0 --global-ints 0 --local-byteslices 0 --local-ints 0 --firstvalid $FIRSTVALID --out "${APP_CREATE_TXN_NO_FULLY_SPECIFIED_FILE}" +APP_CREATE_TXN_NO_FULLY_SPECIFIED=$(msgpacktool -d < "${APP_CREATE_TXN_NO_FULLY_SPECIFIED_FILE}") + +if [ "$APP_CREATE_TXN_NO_FULLY_SPECIFIED" != "$APP_CREATE_TXN_NO_STATE" ]; then + date "+${scriptname} transactions made with 'goal method --create' are not equal %Y%m%d_%H%M%S" + false +fi + +date "+${scriptname} OK %Y%m%d_%H%M%S" diff --git a/test/scripts/e2e_subs/teal-creatable-id.sh b/test/scripts/e2e_subs/teal-creatable-id.sh index 50dff53b3b..b461e11ee1 100755 --- a/test/scripts/e2e_subs/teal-creatable-id.sh +++ b/test/scripts/e2e_subs/teal-creatable-id.sh @@ -17,7 +17,7 @@ gcmd="goal -w ${WALLET}" ACCOUNT=$(${gcmd} account list|awk '{ print $3 }') -APPID=$(${gcmd} app create --creator "${ACCOUNT}" --approval-prog=${TEAL}/check_creatable_id.teal --global-byteslices 0 --global-ints 0 --local-byteslices 0 --local-ints 0 --clear-prog=${TEAL}/approve-all.teal --app-arg=str:skipcreation | grep Created | awk '{ print $6 }') +APPID=$(${gcmd} app create --creator "${ACCOUNT}" --approval-prog=${TEAL}/check_creatable_id.teal --clear-prog=${TEAL}/approve-all.teal --app-arg=str:skipcreation | grep Created | awk '{ print $6 }') # ============================== # > Asset and application test @@ -30,7 +30,7 @@ ${gcmd} asset create --creator "${ACCOUNT}" --total 1000 --unitname "" --assetur ${gcmd} app call --app-id="$APPID" --from="$ACCOUNT" --app-arg=str:skipcreation --app-arg=int:0 --out "$TEMPDIR/unsigned_asset_check_app_call.txn" # Create app transaction -${gcmd} app create --creator "${ACCOUNT}" --approval-prog=${TEAL}/approve-all.teal --global-byteslices 0 --global-ints 0 --local-byteslices 0 --local-ints 0 --clear-prog=${TEAL}/approve-all.teal --out "$TEMPDIR/unsigned_app_create.txn" +${gcmd} app create --creator "${ACCOUNT}" --approval-prog=${TEAL}/approve-all.teal --clear-prog=${TEAL}/approve-all.teal --out "$TEMPDIR/unsigned_app_create.txn" # App call transaction to check app creatable ID ${gcmd} app call --app-id="$APPID" --from="$ACCOUNT" --app-arg=str:skipcreation --app-arg=int:2 --out "$TEMPDIR/unsigned_app_check_app_call.txn" From 91771c09c2d425097d23f1179986fcb91d8c4302 Mon Sep 17 00:00:00 2001 From: Will Winder Date: Thu, 4 May 2023 13:42:09 -0400 Subject: [PATCH 080/125] follow mode: set sync round after fast catchup. (#5349) --- catchup/service.go | 18 ++++++++++------ daemon/algod/api/server/v2/handlers.go | 2 -- .../algod/api/server/v2/test/handlers_test.go | 4 ---- node/error.go | 21 ------------------- node/follower_node.go | 17 ++++++++------- node/follower_node_test.go | 21 +++++++++++++++++++ 6 files changed, 42 insertions(+), 41 deletions(-) diff --git a/catchup/service.go b/catchup/service.go index 2178f92eec..ac5fa730d4 100644 --- a/catchup/service.go +++ b/catchup/service.go @@ -157,6 +157,16 @@ func (s *Service) IsSynchronizing() (synchronizing bool, initialSync bool) { return } +// triggerSync attempts to wake up the sync loop. +func (s *Service) triggerSync() { + if syncing, initial := s.IsSynchronizing(); !syncing && !initial { + select { + case s.syncNow <- struct{}{}: + default: + } + } +} + // SetDisableSyncRound attempts to set the first round we _do_not_ want to fetch from the network // Blocks from disableSyncRound or any round after disableSyncRound will not be fetched while this is set func (s *Service) SetDisableSyncRound(rnd uint64) error { @@ -164,18 +174,14 @@ func (s *Service) SetDisableSyncRound(rnd uint64) error { return ErrSyncRoundInvalid } atomic.StoreUint64(&s.disableSyncRound, rnd) - if syncing, initial := s.IsSynchronizing(); !syncing && !initial { - s.syncNow <- struct{}{} - } + s.triggerSync() return nil } // UnsetDisableSyncRound removes any previously set disabled sync round func (s *Service) UnsetDisableSyncRound() { atomic.StoreUint64(&s.disableSyncRound, 0) - if syncing, initial := s.IsSynchronizing(); !syncing && !initial { - s.syncNow <- struct{}{} - } + s.triggerSync() } // GetDisableSyncRound returns the disabled sync round diff --git a/daemon/algod/api/server/v2/handlers.go b/daemon/algod/api/server/v2/handlers.go index a26d2752de..045b69553d 100644 --- a/daemon/algod/api/server/v2/handlers.go +++ b/daemon/algod/api/server/v2/handlers.go @@ -1348,8 +1348,6 @@ func (v2 *Handlers) startCatchup(ctx echo.Context, catchpoint string) error { code = http.StatusOK case *node.CatchpointUnableToStartError: return badRequest(ctx, err, err.Error(), v2.Log) - case *node.CatchpointSyncRoundFailure: - return badRequest(ctx, err, fmt.Sprintf(errFailedToStartCatchup, err), v2.Log) default: return internalError(ctx, err, fmt.Sprintf(errFailedToStartCatchup, err), v2.Log) } diff --git a/daemon/algod/api/server/v2/test/handlers_test.go b/daemon/algod/api/server/v2/test/handlers_test.go index 0c34aacd5d..fd31a1bef5 100644 --- a/daemon/algod/api/server/v2/test/handlers_test.go +++ b/daemon/algod/api/server/v2/test/handlers_test.go @@ -1222,10 +1222,6 @@ func TestStartCatchup(t *testing.T) { badCatchPoint := "bad catchpoint" startCatchupTest(t, badCatchPoint, nil, 400) - - // Test that a catchup fails w/ 400 when the catchpoint round is > syncRound (while syncRound is set) - syncRoundError := node.MakeCatchpointSyncRoundFailure(goodCatchPoint, 1) - startCatchupTest(t, goodCatchPoint, syncRoundError, 400) } func abortCatchupTest(t *testing.T, catchpoint string, expectedCode int) { diff --git a/node/error.go b/node/error.go index dbe036b605..d177f0c870 100644 --- a/node/error.go +++ b/node/error.go @@ -62,24 +62,3 @@ func (e *CatchpointUnableToStartError) Error() string { e.catchpointRequested, e.catchpointRunning) } - -// CatchpointSyncRoundFailure indicates that the requested catchpoint is beyond the currently set sync round -type CatchpointSyncRoundFailure struct { - catchpoint string - syncRound uint64 -} - -// MakeCatchpointSyncRoundFailure creates the error type -func MakeCatchpointSyncRoundFailure(catchpoint string, syncRound uint64) *CatchpointSyncRoundFailure { - return &CatchpointSyncRoundFailure{ - catchpoint: catchpoint, - syncRound: syncRound, - } -} - -// Error satisfies the builtin `error` interface -func (e *CatchpointSyncRoundFailure) Error() string { - return fmt.Sprintf( - "unable to start catchpoint catchup for '%s' - resulting round is beyond current sync round '%v'", - e.catchpoint, e.syncRound) -} diff --git a/node/follower_node.go b/node/follower_node.go index 633515d1c1..ae770c7112 100644 --- a/node/follower_node.go +++ b/node/follower_node.go @@ -342,14 +342,7 @@ func (node *AlgorandFollowerNode) StartCatchup(catchpoint string) error { } return MakeCatchpointUnableToStartError(stats.CatchpointLabel, catchpoint) } - cpRound, _, err := ledgercore.ParseCatchpointLabel(catchpoint) - if err != nil { - return err - } - sRound := node.GetSyncRound() - if sRound > 0 && uint64(cpRound) > sRound { - return MakeCatchpointSyncRoundFailure(catchpoint, sRound) - } + var err error accessor := ledger.MakeCatchpointCatchupAccessor(node.ledger.Ledger, node.log) node.catchpointCatchupService, err = catchup.MakeNewCatchpointCatchupService(catchpoint, node, node.log, node.net, accessor, node.config) if err != nil { @@ -414,7 +407,15 @@ func (node *AlgorandFollowerNode) SetCatchpointCatchupMode(catchpointCatchupMode prevNodeCancelFunc() return } + + // Catchup finished, resume. defer node.mu.Unlock() + + // update sync round before starting services + if err := node.SetSyncRound(uint64(node.ledger.LastRound())); err != nil { + node.log.Warnf("unable to set sync round while resuming fast catchup: %v", err) + } + // start node.catchupService.Start() node.blockService.Start() diff --git a/node/follower_node_test.go b/node/follower_node_test.go index cbc77b3eb6..c29ee4f0f9 100644 --- a/node/follower_node_test.go +++ b/node/follower_node_test.go @@ -17,10 +17,12 @@ package node import ( + "context" "testing" "github.com/sirupsen/logrus" "github.com/sirupsen/logrus/hooks/test" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/algorand/go-algorand/agreement" @@ -213,3 +215,22 @@ func TestSyncRoundWithRemake(t *testing.T) { syncRound := followNode.GetSyncRound() require.Equal(t, uint64(maxAcctLookback+1), syncRound) } + +func TestFastCatchupResume(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + node := setupFollowNode(t) + node.ctx = context.Background() + + // Initialize sync round to a future round. + syncRound := uint64(10000) + node.SetSyncRound(syncRound) + require.Equal(t, syncRound, node.GetSyncRound()) + + // Force catchpoint catchup mode to end, this should set the sync round to the current ledger round (0). + out := node.SetCatchpointCatchupMode(false) + <-out + + // Verify the sync was reset. + assert.Equal(t, uint64(0), node.GetSyncRound()) +} From c5e57c3b4958db4618089c0b45526403b1cb782c Mon Sep 17 00:00:00 2001 From: Will Winder Date: Thu, 4 May 2023 17:12:06 -0400 Subject: [PATCH 081/125] logging: fix logging to line. (#5359) --- daemon/algod/server.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/daemon/algod/server.go b/daemon/algod/server.go index 2283ef8e0d..010d3aec57 100644 --- a/daemon/algod/server.go +++ b/daemon/algod/server.go @@ -83,7 +83,6 @@ func (s *Server) Initialize(cfg config.Local, phonebookAddresses []string, genes liveLog := filepath.Join(s.RootPath, "node.log") archive := filepath.Join(s.RootPath, cfg.LogArchiveName) - fmt.Println("Logging to: ", liveLog) var maxLogAge time.Duration var err error if cfg.LogArchiveMaxAge != "" { @@ -96,8 +95,10 @@ func (s *Server) Initialize(cfg config.Local, phonebookAddresses []string, genes var logWriter io.Writer if cfg.LogSizeLimit > 0 { + fmt.Println("Logging to: ", liveLog) logWriter = logging.MakeCyclicFileWriter(liveLog, archive, cfg.LogSizeLimit, maxLogAge) } else { + fmt.Println("Logging to: stdout") logWriter = os.Stdout } s.log.SetOutput(logWriter) From d939705c2d833722cff925c55116df453d7c3d66 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Fri, 5 May 2023 10:18:56 -0500 Subject: [PATCH 082/125] bugfix: reduce flakiness in follower node test (#5355) --- node/follower_node_test.go | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/node/follower_node_test.go b/node/follower_node_test.go index c29ee4f0f9..c15fb3317d 100644 --- a/node/follower_node_test.go +++ b/node/follower_node_test.go @@ -19,6 +19,7 @@ package node import ( "context" "testing" + "time" "github.com/sirupsen/logrus" "github.com/sirupsen/logrus/hooks/test" @@ -206,8 +207,21 @@ func TestSyncRoundWithRemake(t *testing.T) { } followNode, _ = remakeableFollowNode(t, tempDir, maxAcctLookback) - status, err := followNode.Status() - require.NoError(t, err) + + // Wait for follower to catch up. This rarely is needed, but can happen + // and cause flakey test failures. Timing out can still occur, but is less + // likely than the being caught behind a few rounds. + var status *StatusReport + require.Eventually(t, func() bool { + st, err := followNode.Status() + require.NoError(t, err) + if st.LastRound >= newRound { + status = &st + return true + } + return false + }, 10*time.Second, 500*time.Millisecond, "failed to reach newRound within the allowed time") + require.Equal(t, newRound, status.LastRound) // syncRound should be at From ac9cdad492c043d4d0a218cb9ef5c37d83957fa5 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Fri, 5 May 2023 16:56:36 -0400 Subject: [PATCH 083/125] AVM: No low resources (#5328) New approach that allows resource opcodes to maintain compatibility in v9, which allows group resource sharing. Obsoletes a prior approach that deprecated a slew of opcodes and made new ones that could not access by static array slot. --- cmd/opdoc/opdoc.go | 2 +- config/consensus.go | 7 + daemon/algod/api/server/v2/dryrun_test.go | 12 +- data/bookkeeping/genesis.go | 6 + data/transactions/logic/README.md | 12 + data/transactions/logic/README_in.md | 12 + data/transactions/logic/assembler.go | 32 +- data/transactions/logic/assembler_test.go | 322 ++- data/transactions/logic/box_test.go | 256 +-- data/transactions/logic/doc_test.go | 4 +- data/transactions/logic/eval.go | 277 +-- data/transactions/logic/evalAppTxn_test.go | 376 ++-- data/transactions/logic/evalStateful_test.go | 1019 +++++----- data/transactions/logic/fields_test.go | 12 +- data/transactions/logic/langspec.json | 2 +- data/transactions/logic/opcodes.go | 10 - data/transactions/logic/resources.go | 4 +- data/transactions/logic/resources_test.go | 219 +- data/transactions/transaction.go | 2 +- data/txntest/txn.go | 4 +- ledger/apply/application.go | 2 +- ledger/apply/application_test.go | 2 +- ledger/apptxn_test.go | 1754 +++++++---------- ledger/boxtxn_test.go | 67 +- ledger/double_test.go | 20 +- ledger/eval/eval_test.go | 14 +- ledger/eval/txntracer_test.go | 18 +- ledger/eval_simple_test.go | 51 +- .../cli/goal/expect/goalAccountTest.exp | 10 +- test/scripts/e2e_subs/assets-app-b.sh | 2 +- test/scripts/e2e_subs/assets-app.sh | 2 +- .../e2e_subs/e2e-app-stateful-global.sh | 2 +- .../e2e_subs/e2e-app-stateful-local.sh | 2 +- test/scripts/e2e_subs/e2e-app-x-app-reads.sh | 2 +- test/scripts/e2e_subs/sectok-app.sh | 2 +- 35 files changed, 2228 insertions(+), 2312 deletions(-) mode change 100644 => 100755 test/e2e-go/cli/goal/expect/goalAccountTest.exp diff --git a/cmd/opdoc/opdoc.go b/cmd/opdoc/opdoc.go index a7f1894018..a79496824a 100644 --- a/cmd/opdoc/opdoc.go +++ b/cmd/opdoc/opdoc.go @@ -28,7 +28,7 @@ import ( "github.com/algorand/go-algorand/protocol" ) -var docVersion = 8 +var docVersion = 9 func opGroupMarkdownTable(names []string, out io.Writer) { fmt.Fprint(out, `| Opcode | Description | diff --git a/config/consensus.go b/config/consensus.go index 59f9721a4e..6a131c8e38 100644 --- a/config/consensus.go +++ b/config/consensus.go @@ -499,6 +499,12 @@ type ConsensusParams struct { // Version 7 includes state proof verification contexts EnableCatchpointsWithSPContexts bool + // AppForbidLowResources enforces a rule that prevents apps from accessing + // asas and apps below 256, in an effort to decrease the ambiguity of + // opcodes that accept IDs or slot indexes. Simultaneously, the first ID + // allocated in new chains is raised to 1001. + AppForbidLowResources bool + // EnableBoxRefNameError specifies that box ref names should be validated early EnableBoxRefNameError bool } @@ -1268,6 +1274,7 @@ func initConsensusProtocols() { vFuture.LogicSigVersion = 9 // When moving this to a release, put a new higher LogicSigVersion here vFuture.EnablePrecheckECDSACurve = true + vFuture.AppForbidLowResources = true vFuture.EnableBareBudgetError = true vFuture.EnableBoxRefNameError = true diff --git a/daemon/algod/api/server/v2/dryrun_test.go b/daemon/algod/api/server/v2/dryrun_test.go index fd74e90108..2f43b12a65 100644 --- a/daemon/algod/api/server/v2/dryrun_test.go +++ b/daemon/algod/api/server/v2/dryrun_test.go @@ -486,7 +486,7 @@ func TestDryrunGlobal2(t *testing.T) { Txn: transactions.Transaction{ Type: protocol.ApplicationCallTx, ApplicationCallTxnFields: transactions.ApplicationCallTxnFields{ - ApplicationID: 1, + ApplicationID: 1234, ApplicationArgs: [][]byte{ []byte("check"), []byte("bar"), @@ -503,7 +503,7 @@ func TestDryrunGlobal2(t *testing.T) { } dr.Apps = []model.Application{ { - Id: 1, + Id: 1234, Params: model.ApplicationParams{ ApprovalProgram: globalTestProgram, GlobalState: &gkv, @@ -691,7 +691,7 @@ func TestDryrunLocalCheck(t *testing.T) { Txn: transactions.Transaction{ Type: protocol.ApplicationCallTx, ApplicationCallTxnFields: transactions.ApplicationCallTxnFields{ - ApplicationID: 1, + ApplicationID: 1234, ApplicationArgs: [][]byte{ []byte("check"), []byte("bar"), @@ -702,13 +702,13 @@ func TestDryrunLocalCheck(t *testing.T) { } dr.Apps = []model.Application{ { - Id: 1, + Id: 1234, Params: model.ApplicationParams{ ApprovalProgram: localStateCheckProg, }, }, } - localv := make(model.TealKeyValueStore, 1) + localv := make(model.TealKeyValueStore, 1234) localv[0] = model.TealKeyValue{ Key: b64("foo"), Value: model.TealValue{ @@ -722,7 +722,7 @@ func TestDryrunLocalCheck(t *testing.T) { Status: "Online", Address: basics.Address{}.String(), AppsLocalState: &[]model.ApplicationLocalState{{ - Id: 1, + Id: 1234, KeyValue: &localv, }}, }, diff --git a/data/bookkeeping/genesis.go b/data/bookkeeping/genesis.go index a81103f281..d750b0eaf2 100644 --- a/data/bookkeeping/genesis.go +++ b/data/bookkeeping/genesis.go @@ -223,6 +223,12 @@ func MakeGenesisBlock(proto protocol.ConsensusVersion, genesisBal GenesisBalance }, } + // If a new network is being created in which AVM can't access low numbered + // resources, bump the TxnCounter so there won't be any such resources. + if params.AppForbidLowResources { + blk.TxnCounter = 1000 + } + if params.SupportGenesisHash { blk.BlockHeader.GenesisHash = genesisHash } diff --git a/data/transactions/logic/README.md b/data/transactions/logic/README.md index 0e5dc8ff27..1c8e0bcb04 100644 --- a/data/transactions/logic/README.md +++ b/data/transactions/logic/README.md @@ -237,6 +237,18 @@ _available_. indicates the application ID of the app called by that transaction. No box is ever _available_ to a ClearStateProgram. +Regardless of _availability_, any attempt to access an Asset or +Application with an ID less than 256 from within a Contract will fail +immediately. This avoids any ambiguity in opcodes that interpret their +integer arguments as resource IDs _or_ indexes into the +`txn.ForeignAssets` or `txn.ForeignApplications` arrays. + +It is recommended that contract authors avoid supplying array indexes +to these opcodes, and always use explicit resource IDs. By using +explicit IDs, contracts will better take advantage of group resource +sharing. The array indexing interpretation may be deprecated in a +future version. + ## Constants Constants can be pushed onto the stack in two different ways: diff --git a/data/transactions/logic/README_in.md b/data/transactions/logic/README_in.md index ff5b524c80..de8f437f7c 100644 --- a/data/transactions/logic/README_in.md +++ b/data/transactions/logic/README_in.md @@ -237,6 +237,18 @@ _available_. indicates the application ID of the app called by that transaction. No box is ever _available_ to a ClearStateProgram. +Regardless of _availability_, any attempt to access an Asset or +Application with an ID less than 256 from within a Contract will fail +immediately. This avoids any ambiguity in opcodes that interpret their +integer arguments as resource IDs _or_ indexes into the +`txn.ForeignAssets` or `txn.ForeignApplications` arrays. + +It is recommended that contract authors avoid supplying array indexes +to these opcodes, and always use explicit resource IDs. By using +explicit IDs, contracts will better take advantage of group resource +sharing. The array indexing interpretation may be deprecated in a +future version. + ## Constants Constants can be pushed onto the stack in two different ways: diff --git a/data/transactions/logic/assembler.go b/data/transactions/logic/assembler.go index 392c510f25..a5631d43b0 100644 --- a/data/transactions/logic/assembler.go +++ b/data/transactions/logic/assembler.go @@ -1580,14 +1580,36 @@ func getSpec(ops *OpStream, name string, args []string) (OpSpec, string, bool) { } spec, ok := OpsByName[ops.Version][name] if !ok { - spec, ok = OpsByName[AssemblerMaxVersion][name] + var err error + spec, err = unknownOpcodeComplaint(name, ops.Version) + // unknownOpcodeComplaint's job is to return a nice error, so err != nil + ops.error(err) + } + return spec, spec.Name, ok +} + +// unknownOpcodeComplaint returns the best error it can for a missing opcode, +// plus a "standin" OpSpec, if possible. +func unknownOpcodeComplaint(name string, v uint64) (OpSpec, error) { + first, last := -1, -1 + var standin OpSpec + for i := 1; i < len(OpsByName); i++ { + spec, ok := OpsByName[i][name] if ok { - ops.errorf("%s opcode was introduced in v%d", name, spec.Version) - } else { - ops.errorf("unknown opcode: %s", name) + standin = spec + if first == -1 { + first = i + } + last = i } } - return spec, spec.Name, ok + if first > int(v) { + return standin, fmt.Errorf("%s opcode was introduced in v%d", name, first) + } + if last != -1 && last < int(v) { + return standin, fmt.Errorf("%s opcode was removed in v%d", name, last+1) + } + return OpSpec{}, fmt.Errorf("unknown opcode: %s", name) } // pseudoOps allows us to provide convenient ops that mirror existing ops without taking up another opcode. Using "txn" in version 2 and on, for example, determines whether to actually assemble txn or to use txna instead based on the number of immediates. diff --git a/data/transactions/logic/assembler_test.go b/data/transactions/logic/assembler_test.go index 7c515c6931..8f6b86998f 100644 --- a/data/transactions/logic/assembler_test.go +++ b/data/transactions/logic/assembler_test.go @@ -22,6 +22,7 @@ import ( "errors" "fmt" "regexp" + "strconv" "strings" "testing" @@ -1622,15 +1623,11 @@ func TestAssembleDisassembleCycle(t *testing.T) { t.Parallel() // Test that disassembly re-assembles to the same program bytes. - // Disassembly won't necessarily perfectly recreate the source text, but assembling the result of Disassemble() should be the same program bytes. - // This confirms that each program compiles to the same bytes - // (except the leading version indicator), when compiled under - // original version, unspecified version (so it should pick up - // the pragma) and current version with pragma removed. That - // doesn't *have* to be true, as we can introduce - // optimizations in later versions that change the bytecode - // emitted. But currently it is, so we test it for now to - // catch any suprises. + // Disassembly won't necessarily perfectly recreate the source text, but + // assembling the result of Disassemble() should be the same program bytes. + // To confirm that, the disassembly output must be re-assembled. Since it + // has a #pragma version, we re-assemble with assemblerNoVersion to let the + // assembler pick it up. require.LessOrEqual(t, LogicVersion, len(nonsense)) // Allow nonsense for future versions for v, source := range nonsense { v, source := v, source @@ -1642,11 +1639,9 @@ func TestAssembleDisassembleCycle(t *testing.T) { ops := testProg(t, source, v) t2, err := Disassemble(ops.Program) require.NoError(t, err) - none := testProg(t, notrack(t2), assemblerNoVersion) - require.Equal(t, ops.Program[1:], none.Program[1:]) - t3 := "// " + t2 // This comments out the #pragma version - current := testProg(t, notrack(t3), AssemblerMaxVersion) - require.Equal(t, ops.Program[1:], current.Program[1:]) + // we use pragma notrack in nonsense to avoid tracking types, that is lost in disassembly + reassembled := testProg(t, notrack(t2), assemblerNoVersion) + require.Equal(t, ops.Program, reassembled.Program) }) } } @@ -1727,117 +1722,101 @@ func TestAssembleDisassembleErrors(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - source := `txn Sender` - ops, err := AssembleStringWithVersion(source, AssemblerMaxVersion) - require.NoError(t, err) - ops.Program[2] = 0x50 // txn field - _, err = Disassemble(ops.Program) - require.Error(t, err) - require.Contains(t, err.Error(), "invalid immediate f for txn") - - source = `txna Accounts 0` - ops, err = AssembleStringWithVersion(source, AssemblerMaxVersion) - require.NoError(t, err) - ops.Program[2] = 0x50 // txn field - _, err = Disassemble(ops.Program) - require.Error(t, err) - require.Contains(t, err.Error(), "invalid immediate f for txna") - - source = `gtxn 0 Sender` - ops, err = AssembleStringWithVersion(source, AssemblerMaxVersion) - require.NoError(t, err) - ops.Program[3] = 0x50 // txn field - _, err = Disassemble(ops.Program) - require.Error(t, err) - require.Contains(t, err.Error(), "invalid immediate f for gtxn") - - source = `gtxna 0 Accounts 0` - ops, err = AssembleStringWithVersion(source, AssemblerMaxVersion) - require.NoError(t, err) - ops.Program[3] = 0x50 // txn field - _, err = Disassemble(ops.Program) - require.Error(t, err) - require.Contains(t, err.Error(), "invalid immediate f for gtxna") - - source = `global MinTxnFee` - ops, err = AssembleStringWithVersion(source, AssemblerMaxVersion) - require.NoError(t, err) - ops.Program[2] = 0x50 // txn field - _, err = Disassemble(ops.Program) - require.Error(t, err) - require.Contains(t, err.Error(), "invalid immediate f for global") - - ops.Program[0] = 0x11 // version - out, err := Disassemble(ops.Program) - require.NoError(t, err) - require.Contains(t, out, "unsupported version") - - ops.Program[0] = 0x01 // version - ops.Program[1] = 0xFF // first opcode - _, err = Disassemble(ops.Program) - require.Error(t, err) - require.Contains(t, err.Error(), "invalid opcode") - - source = "int 0\nint 0\nasset_holding_get AssetFrozen" - ops, err = AssembleStringWithVersion(source, sharedResourcesVersion-1) - require.NoError(t, err) - ops.Program[7] = 0x50 // holding field - _, err = Disassemble(ops.Program) - require.Error(t, err) - require.Contains(t, err.Error(), "invalid immediate f for asset_holding_get") - - source = "int 0\nasset_params_get AssetTotal" - ops, err = AssembleStringWithVersion(source, AssemblerMaxVersion) - require.NoError(t, err) - ops.Program[4] = 0x50 // params field - _, err = Disassemble(ops.Program) - require.Error(t, err) - require.Contains(t, err.Error(), "invalid immediate f for asset_params_get") - - source = "int 0\nasset_params_get AssetTotal" - ops, err = AssembleStringWithVersion(source, AssemblerMaxVersion) - require.NoError(t, err) - _, err = Disassemble(ops.Program) - require.NoError(t, err) - ops.Program = ops.Program[0 : len(ops.Program)-1] - _, err = Disassemble(ops.Program) - require.Error(t, err) - require.Contains(t, err.Error(), "program end while reading immediate f for asset_params_get") - - source = "gtxna 0 Accounts 0" - ops, err = AssembleStringWithVersion(source, AssemblerMaxVersion) - require.NoError(t, err) - _, err = Disassemble(ops.Program) - require.NoError(t, err) - _, err = Disassemble(ops.Program[0 : len(ops.Program)-1]) - require.Error(t, err) - require.Contains(t, err.Error(), "program end while reading immediate i for gtxna") - _, err = Disassemble(ops.Program[0 : len(ops.Program)-2]) - require.Error(t, err) - require.Contains(t, err.Error(), "program end while reading immediate f for gtxna") - _, err = Disassemble(ops.Program[0 : len(ops.Program)-3]) - require.Error(t, err) - require.Contains(t, err.Error(), "program end while reading immediate t for gtxna") - - source = "txna Accounts 0" - ops, err = AssembleStringWithVersion(source, AssemblerMaxVersion) - require.NoError(t, err) - _, err = Disassemble(ops.Program) - require.NoError(t, err) - ops.Program = ops.Program[0 : len(ops.Program)-1] - _, err = Disassemble(ops.Program) - require.Error(t, err) - require.Contains(t, err.Error(), "program end while reading immediate i for txna") - - source = "byte 0x4141\nsubstring 0 1" - ops, err = AssembleStringWithVersion(source, AssemblerMaxVersion) - require.NoError(t, err) - _, err = Disassemble(ops.Program) - require.NoError(t, err) - ops.Program = ops.Program[0 : len(ops.Program)-1] - _, err = Disassemble(ops.Program) - require.Error(t, err) - require.Contains(t, err.Error(), "program end while reading immediate e for substring") + for v := uint64(2); v <= AssemblerMaxVersion; v++ { + t.Run(fmt.Sprintf("v%d", v), func(t *testing.T) { + source := `txn Sender` + ops := testProg(t, source, v) + ops.Program[len(ops.Program)-1] = 0x50 // txn field + dis, err := Disassemble(ops.Program) + require.Error(t, err, dis) + require.Contains(t, err.Error(), "invalid immediate f for txn") + + source = `txna Accounts 0` + ops = testProg(t, source, v) + ops.Program[len(ops.Program)-2] = 0x50 // txn field + dis, err = Disassemble(ops.Program) + require.Error(t, err, dis) + require.Contains(t, err.Error(), "invalid immediate f for txna") + + source = `gtxn 0 Sender` + ops = testProg(t, source, v) + ops.Program[len(ops.Program)-1] = 0x50 // txn field + dis, err = Disassemble(ops.Program) + require.Error(t, err, dis) + require.Contains(t, err.Error(), "invalid immediate f for gtxn") + + source = `gtxna 0 Accounts 0` + ops = testProg(t, source, v) + ops.Program[len(ops.Program)-2] = 0x50 // txn field + dis, err = Disassemble(ops.Program) + require.Error(t, err, dis) + require.Contains(t, err.Error(), "invalid immediate f for gtxna") + + source = `global MinTxnFee` + ops = testProg(t, source, v) + ops.Program[len(ops.Program)-1] = 0x50 // txn field + _, err = Disassemble(ops.Program) + require.Error(t, err) + require.Contains(t, err.Error(), "invalid immediate f for global") + + ops.Program[0] = 0x11 // version + out, err := Disassemble(ops.Program) + require.NoError(t, err) + require.Contains(t, out, "unsupported version") + + ops.Program[0] = 0x01 // version + ops.Program[1] = 0xFF // first opcode + dis, err = Disassemble(ops.Program) + require.Error(t, err, dis) + require.Contains(t, err.Error(), "invalid opcode") + + source = "int 0; int 0\nasset_holding_get AssetFrozen" + ops = testProg(t, source, v) + ops.Program[len(ops.Program)-1] = 0x50 // holding field + dis, err = Disassemble(ops.Program) + require.Error(t, err, dis) + require.Contains(t, err.Error(), "invalid immediate f for") + + source = "int 0\nasset_params_get AssetTotal" + ops = testProg(t, source, v) + ops.Program[len(ops.Program)-1] = 0x50 // params field + dis, err = Disassemble(ops.Program) + require.Error(t, err, dis) + require.Contains(t, err.Error(), "invalid immediate f for") + + source = "int 0\nasset_params_get AssetTotal" + ops = testProg(t, source, v) + ops.Program = ops.Program[0 : len(ops.Program)-1] + dis, err = Disassemble(ops.Program) + require.Error(t, err, dis) + require.Contains(t, err.Error(), "program end while reading immediate f for") + + source = "gtxna 0 Accounts 0" + ops = testProg(t, source, v) + dis, err = Disassemble(ops.Program[0 : len(ops.Program)-1]) + require.Error(t, err, dis) + require.Contains(t, err.Error(), "program end while reading immediate i for gtxna") + dis, err = Disassemble(ops.Program[0 : len(ops.Program)-2]) + require.Error(t, err, dis) + require.Contains(t, err.Error(), "program end while reading immediate f for gtxna") + dis, err = Disassemble(ops.Program[0 : len(ops.Program)-3]) + require.Error(t, err, dis) + require.Contains(t, err.Error(), "program end while reading immediate t for gtxna") + + source = "txna Accounts 0" + ops = testProg(t, source, v) + ops.Program = ops.Program[0 : len(ops.Program)-1] + dis, err = Disassemble(ops.Program) + require.Error(t, err, dis) + require.Contains(t, err.Error(), "program end while reading immediate i for txna") + + source = "byte 0x4141\nsubstring 0 1" + ops = testProg(t, source, v) + dis, err = Disassemble(ops.Program[0 : len(ops.Program)-1]) + require.Error(t, err, dis) + require.Contains(t, err.Error(), "program end while reading immediate e for substring") + }) + } } func TestAssembleVersions(t *testing.T) { @@ -1886,38 +1865,31 @@ func TestAssembleAsset(t *testing.T) { t.Parallel() for v := uint64(2); v <= AssemblerMaxVersion; v++ { - testProg(t, "asset_holding_get ABC 1", v, - Expect{1, "asset_holding_get ABC 1 expects 2 stack arguments..."}) - testProg(t, "int 1; asset_holding_get ABC 1", v, - Expect{1, "asset_holding_get ABC 1 expects 2 stack arguments..."}) + t.Run(strconv.Itoa(int(v)), func(t *testing.T) { + testProg(t, "asset_holding_get ABC 1", v, + Expect{1, "asset_holding_get ABC 1 expects 2 stack arguments..."}) + testProg(t, "int 1; asset_holding_get ABC 1", v, + Expect{1, "asset_holding_get ABC 1 expects 2 stack arguments..."}) - if v < sharedResourcesVersion { testProg(t, "int 1; int 1; asset_holding_get ABC 1", v, Expect{1, "asset_holding_get expects 1 immediate argument"}) testProg(t, "int 1; int 1; asset_holding_get ABC", v, Expect{1, "asset_holding_get unknown field: \"ABC\""}) - } else { - testProg(t, "int 1; int 1; asset_holding_get ABC 1", v, - Expect{1, "asset_holding_get ABC 1 arg 0 wanted type..."}) - testProg(t, "txn Sender; int 1; asset_holding_get ABC 1", v, - Expect{1, "asset_holding_get expects 1 immediate argument"}) - testProg(t, "txn Sender; int 1; asset_holding_get ABC", v, - Expect{1, "asset_holding_get unknown field: \"ABC\""}) - } - testProg(t, "byte 0x1234; asset_params_get ABC 1", v, - Expect{1, "asset_params_get ABC 1 arg 0 wanted type uint64..."}) + testProg(t, "byte 0x1234; asset_params_get ABC 1", v, + Expect{1, "asset_params_get ABC 1 arg 0 wanted type uint64..."}) - // Test that AssetUnitName is known to return bytes - testProg(t, "int 1; asset_params_get AssetUnitName; pop; int 1; +", v, - Expect{1, "+ arg 0 wanted type uint64..."}) + // Test that AssetUnitName is known to return bytes + testProg(t, "int 1; asset_params_get AssetUnitName; pop; int 1; +", v, + Expect{1, "+ arg 0 wanted type uint64..."}) - // Test that AssetTotal is known to return uint64 - testProg(t, "int 1; asset_params_get AssetTotal; pop; byte 0x12; concat", v, - Expect{1, "concat arg 0 wanted type []byte..."}) + // Test that AssetTotal is known to return uint64 + testProg(t, "int 1; asset_params_get AssetTotal; pop; byte 0x12; concat", v, + Expect{1, "concat arg 0 wanted type []byte..."}) - testLine(t, "asset_params_get ABC 1", v, "asset_params_get expects 1 immediate argument") - testLine(t, "asset_params_get ABC", v, "asset_params_get unknown field: \"ABC\"") + testLine(t, "asset_params_get ABC 1", v, "asset_params_get expects 1 immediate argument") + testLine(t, "asset_params_get ABC", v, "asset_params_get unknown field: \"ABC\"") + }) } } @@ -2168,23 +2140,27 @@ func TestHasStatefulOps(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - source := "int 1" - ops := testProg(t, source, AssemblerMaxVersion) - has, err := HasStatefulOps(ops.Program) - require.NoError(t, err) - require.False(t, has) + for v := uint64(2); v <= AssemblerMaxVersion; v++ { + t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) { + source := "int 1" + ops := testProg(t, source, v) + has, err := HasStatefulOps(ops.Program) + require.NoError(t, err) + require.False(t, has) - source = `txn Sender; int 1; app_opted_in; err` - ops = testProg(t, source, AssemblerMaxVersion) - has, err = HasStatefulOps(ops.Program) - require.NoError(t, err) - require.True(t, has) + source = `int 0; int 1; app_opted_in; err` + ops = testProg(t, source, v) + has, err = HasStatefulOps(ops.Program) + require.NoError(t, err) + require.True(t, has) - source = `int 1; int 1; app_opted_in; err` - ops = testProg(t, source, sharedResourcesVersion-1) - has, err = HasStatefulOps(ops.Program) - require.NoError(t, err) - require.True(t, has) + source = `int 1; asset_params_get AssetURL; err` + ops = testProg(t, source, v) + has, err = HasStatefulOps(ops.Program) + require.NoError(t, err) + require.True(t, has) + }) + } } func TestStringLiteralParsing(t *testing.T) { @@ -2477,7 +2453,9 @@ func TestBranchAssemblyTypeCheck(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - text := ` + for v := uint64(2); v <= AssemblerMaxVersion; v++ { + t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) { + text := ` int 0 // current app id [0] int 1 // key [1, 0] itob // ["\x01", 0] @@ -2485,13 +2463,9 @@ func TestBranchAssemblyTypeCheck(t *testing.T) { pop // [x] btoi // [n] ` + testProg(t, text, v) - ops := newOpStream(AssemblerMaxVersion) - err := ops.assemble(text) - require.NoError(t, err) - require.Empty(t, ops.Warnings) - - text = ` + text = ` int 0 // current app id [0] int 1 // key [1, 0] itob // ["\x01", 0] @@ -2500,11 +2474,9 @@ func TestBranchAssemblyTypeCheck(t *testing.T) { flip: // [x] btoi // [n] ` - - ops = newOpStream(AssemblerMaxVersion) - err = ops.assemble(text) - require.NoError(t, err) - require.Empty(t, ops.Warnings) + testProg(t, text, v) + }) + } } func TestSwapTypeCheck(t *testing.T) { diff --git a/data/transactions/logic/box_test.go b/data/transactions/logic/box_test.go index 578dbcc770..9a98a6455d 100644 --- a/data/transactions/logic/box_test.go +++ b/data/transactions/logic/box_test.go @@ -23,7 +23,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" @@ -38,29 +38,29 @@ func TestBoxNewDel(t *testing.T) { t.Run(fmt.Sprintf("box size=%d", size), func(t *testing.T) { t.Parallel() - ep, txn, ledger := logic.MakeSampleEnv() + ep, txn, ledger := MakeSampleEnv() createSelf := fmt.Sprintf(`byte "self"; int %d; box_create;`, size) createOther := fmt.Sprintf(`byte "other"; int %d; box_create;`, size) ledger.NewApp(txn.Sender, 888, basics.AppParams{}) - logic.TestApp(t, createSelf, ep) + TestApp(t, createSelf, ep) ledger.DelBoxes(888, "self") - logic.TestApp(t, createSelf+`assert;`+createSelf+`!`, ep) + TestApp(t, createSelf+`assert;`+createSelf+`!`, ep) ledger.DelBoxes(888, "self") - logic.TestApp(t, createSelf+`assert;`+createOther, ep) + TestApp(t, createSelf+`assert;`+createOther, ep) ledger.DelBoxes(888, "self") - logic.TestApp(t, createSelf+`assert; byte "self"; box_del`, ep) - logic.TestApp(t, `byte "self"; box_del; !`, ep) - logic.TestApp(t, createSelf+`assert + TestApp(t, createSelf+`assert; byte "self"; box_del`, ep) + TestApp(t, `byte "self"; box_del; !`, ep) + TestApp(t, createSelf+`assert byte "self"; box_del; assert byte "self"; box_del; !`, ep) ledger.DelBoxes(888, "self") - logic.TestApp(t, fmt.Sprintf( + TestApp(t, fmt.Sprintf( `byte "self"; box_get; !; assert; pop byte "self"; int %d; bzero; box_put; int 1`, size), ep) }) @@ -72,40 +72,40 @@ func TestBoxNewBad(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - ep, txn, ledger := logic.MakeSampleEnv() + ep, txn, ledger := MakeSampleEnv() ledger.NewApp(txn.Sender, 888, basics.AppParams{}) - logic.TestApp(t, `byte "self"; int 999; box_create`, ep, "write budget") + TestApp(t, `byte "self"; int 999; box_create`, ep, "write budget") // In test proto, you get 100 I/O budget per boxref ten := [10]transactions.BoxRef{} txn.Boxes = append(txn.Boxes, ten[:]...) // write budget is now 11*100 = 1100 - logic.TestApp(t, `byte "self"; int 999; box_create`, ep) + TestApp(t, `byte "self"; int 999; box_create`, ep) ledger.DelBoxes(888, "self") - logic.TestApp(t, `byte "self"; int 1000; box_create`, ep) + TestApp(t, `byte "self"; int 1000; box_create`, ep) ledger.DelBoxes(888, "self") - logic.TestApp(t, `byte "self"; int 1001; box_create`, ep, "box size too large") + TestApp(t, `byte "self"; int 1001; box_create`, ep, "box size too large") - logic.TestApp(t, `byte "unknown"; int 1000; box_create`, ep, "invalid Box reference") + TestApp(t, `byte "unknown"; int 1000; box_create`, ep, "invalid Box reference") long := strings.Repeat("x", 65) txn.Boxes = []transactions.BoxRef{{Name: []byte(long)}} - logic.TestApp(t, fmt.Sprintf(`byte "%s"; int 1000; box_create`, long), ep, "name too long") + TestApp(t, fmt.Sprintf(`byte "%s"; int 1000; box_create`, long), ep, "name too long") txn.Boxes = []transactions.BoxRef{{Name: []byte("")}} // irrelevant, zero check comes first anyway - logic.TestApp(t, `byte ""; int 1000; box_create`, ep, "zero length") + TestApp(t, `byte ""; int 1000; box_create`, ep, "zero length") } func TestBoxReadWrite(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - ep, txn, ledger := logic.MakeSampleEnv() + ep, txn, ledger := MakeSampleEnv() ledger.NewApp(txn.Sender, 888, basics.AppParams{}) // extract some bytes until past the end, confirm the begin as zeros, and // when it fails. - logic.TestApp(t, `byte "self"; int 4; box_create; assert + TestApp(t, `byte "self"; int 4; box_create; assert byte "self"; int 1; int 2; box_extract; byte 0x0000; ==; assert; byte "self"; int 1; int 3; box_extract; @@ -114,30 +114,30 @@ func TestBoxReadWrite(t *testing.T) { byte 0x00000000; ==; assert; int 1`, ep) - logic.TestApp(t, `byte "self"; int 1; int 4; box_extract; + TestApp(t, `byte "self"; int 1; int 4; box_extract; byte 0x00000000; ==`, ep, "extraction end 5") // Replace some bytes until past the end, confirm when it fails. - logic.TestApp(t, `byte "self"; int 1; byte 0x3031; box_replace; + TestApp(t, `byte "self"; int 1; byte 0x3031; box_replace; byte "self"; int 0; int 4; box_extract; byte 0x00303100; ==`, ep) - logic.TestApp(t, `byte "self"; int 1; byte 0x303132; box_replace; + TestApp(t, `byte "self"; int 1; byte 0x303132; box_replace; byte "self"; int 0; int 4; box_extract; byte 0x00303132; ==`, ep) - logic.TestApp(t, `byte "self"; int 1; byte 0x30313233; box_replace; + TestApp(t, `byte "self"; int 1; byte 0x30313233; box_replace; byte "self"; int 0; int 4; box_extract; byte 0x0030313233; ==`, ep, "replacement end 5") // Replace with different byte in different place. - logic.TestApp(t, `byte "self"; int 0; byte 0x4444; box_replace; + TestApp(t, `byte "self"; int 0; byte 0x4444; box_replace; byte "self"; int 0; int 4; box_extract; byte 0x44443132; ==`, ep) // All bow down to the God of code coverage! ledger.DelBoxes(888, "self") - logic.TestApp(t, `byte "self"; int 1; byte 0x3031; box_replace`, ep, + TestApp(t, `byte "self"; int 1; byte 0x3031; box_replace`, ep, "no such box") - logic.TestApp(t, `byte "junk"; int 1; byte 0x3031; box_replace`, ep, + TestApp(t, `byte "junk"; int 1; byte 0x3031; box_replace`, ep, "invalid Box reference") } @@ -145,15 +145,15 @@ func TestBoxAcrossTxns(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - ledger := logic.NewLedger(nil) + ledger := NewLedger(nil) ledger.NewApp(basics.Address{}, 888, basics.AppParams{}) // After creation in first txn, second one can read it (though it's empty) - logic.TestApps(t, []string{ + TestApps(t, []string{ `byte "self"; int 64; box_create`, `byte "self"; int 10; int 4; box_extract; byte 0x00000000; ==`, }, nil, 8, ledger) // after creation, modification, the third can read it - logic.TestApps(t, []string{ + TestApps(t, []string{ `byte "self"; int 64; box_create`, `byte "self"; int 2; byte "hi"; box_replace; int 1`, `byte "self"; int 1; int 4; box_extract; byte 0x00686900; ==`, // "\0hi\0" @@ -167,24 +167,24 @@ func TestDirtyTracking(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - ep, txn, ledger := logic.MakeSampleEnv() + ep, txn, ledger := MakeSampleEnv() ledger.NewApp(txn.Sender, 888, basics.AppParams{}) - logic.TestApp(t, `byte "self"; int 200; box_create`, ep) - logic.TestApp(t, `byte "other"; int 201; box_create`, ep, "write budget") + TestApp(t, `byte "self"; int 200; box_create`, ep) + TestApp(t, `byte "other"; int 201; box_create`, ep, "write budget") // deleting "self" doesn't give extra write budget to create big "other" - logic.TestApp(t, `byte "self"; box_del; !; byte "other"; int 201; box_create`, ep, + TestApp(t, `byte "self"; box_del; !; byte "other"; int 201; box_create`, ep, "write budget") // though it cancels out a creation that happened here - logic.TestApp(t, `byte "self"; int 200; box_create; assert + TestApp(t, `byte "self"; int 200; box_create; assert byte "self"; box_del; assert byte "self"; int 200; box_create; `, ep) ledger.DelBoxes(888, "self", "other") // same, but create a different box than deleted - logic.TestApp(t, `byte "self"; int 200; box_create; assert + TestApp(t, `byte "self"; int 200; box_create; assert byte "self"; box_del; assert byte "other"; int 200; box_create; `, ep) @@ -192,16 +192,16 @@ func TestDirtyTracking(t *testing.T) { // no funny business by trying to del twice! this case is also interested // because the read budget is spent on "other", which is 200, while the // write budget is spent on "self" - logic.TestApp(t, `byte "other"; box_len; assert`, ep) // reminder, "other" exists! - logic.TestApp(t, `byte "self"; int 200; box_create; assert + TestApp(t, `byte "other"; box_len; assert`, ep) // reminder, "other" exists! + TestApp(t, `byte "self"; int 200; box_create; assert byte "self"; box_del; assert byte "self"; box_del; !; assert byte "self"; int 201; box_create; `, ep, "write budget") - logic.TestApp(t, `byte "self"; box_len; !; assert; !`, ep) // "self" was not made - logic.TestApp(t, `byte "self"; int 200; box_create`, ep) // make it + TestApp(t, `byte "self"; box_len; !; assert; !`, ep) // "self" was not made + TestApp(t, `byte "self"; int 200; box_create`, ep) // make it // Now that both exist with size 200, naming both in Boxes causes failure - logic.TestApp(t, `int 1`, ep, "read budget") + TestApp(t, `int 1`, ep, "read budget") } @@ -223,10 +223,10 @@ func TestBoxUnavailableWithClearState(t *testing.T) { name, program := name, program t.Run(name, func(t *testing.T) { t.Parallel() - ep, _, l := logic.MakeSampleEnv() + ep, _, l := MakeSampleEnv() l.NewApp(basics.Address{}, 888, basics.AppParams{}) ep.TxnGroup[0].Txn.OnCompletion = transactions.ClearStateOC - logic.TestApp(t, program, ep, "boxes may not be accessed from ClearState program") + TestApp(t, program, ep, "boxes may not be accessed from ClearState program") }) } } @@ -235,39 +235,39 @@ func TestBoxAvailability(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - ledger := logic.NewLedger(nil) + ledger := NewLedger(nil) ledger.NewApp(basics.Address{}, 888, basics.AppParams{}) // B is not available (recall that "self" is set up by MakeSampleEnv, in TestApps) - logic.TestApps(t, []string{ + TestApps(t, []string{ `byte "self"; int 64; box_create`, `byte "B"; int 10; int 4; box_extract; byte 0x00000000; ==`, - }, nil, 8, ledger, logic.NewExpect(1, fmt.Sprintf("invalid Box reference %#x", 'B'))) + }, nil, 8, ledger, NewExpect(1, fmt.Sprintf("invalid Box reference %#x", 'B'))) // B is available if indexed by 0 in tx[1].Boxes - group := logic.MakeSampleTxnGroup(logic.MakeSampleTxn(), txntest.Txn{ + group := MakeSampleTxnGroup(MakeSampleTxn(), txntest.Txn{ Type: "appl", ApplicationID: 10000, Boxes: []transactions.BoxRef{{Index: 0, Name: []byte("B")}}, }.SignedTxn()) group[0].Txn.Type = protocol.ApplicationCallTx - logic.TestApps(t, []string{ + TestApps(t, []string{ `byte "self"; int 64; box_create`, `byte "B"; int 10; int 4; box_extract; byte 0x00000000; ==`, - }, group, 8, ledger, logic.NewExpect(1, "no such box")) + }, group, 8, ledger, NewExpect(1, "no such box")) // B is available if listed by appId in tx[1].Boxes - group = logic.MakeSampleTxnGroup(logic.MakeSampleTxn(), txntest.Txn{ + group = MakeSampleTxnGroup(MakeSampleTxn(), txntest.Txn{ Type: "appl", ApplicationID: 10000, ForeignApps: []basics.AppIndex{10000}, Boxes: []transactions.BoxRef{{Index: 1, Name: []byte("B")}}, }.SignedTxn()) group[0].Txn.Type = protocol.ApplicationCallTx - logic.TestApps(t, []string{ + TestApps(t, []string{ `byte "self"; int 64; box_create`, `byte "B"; int 10; int 4; box_extract; byte 0x00000000; ==`, - }, group, 8, ledger, logic.NewExpect(1, "no such box")) + }, group, 8, ledger, NewExpect(1, "no such box")) } func TestBoxReadBudget(t *testing.T) { @@ -277,7 +277,7 @@ func TestBoxReadBudget(t *testing.T) { appID := basics.AppIndex(888) appAddr := appID.Address() - ep, txn, ledger := logic.MakeSampleEnv() + ep, txn, ledger := MakeSampleEnv() ledger.NewApp(basics.Address{}, appID, basics.AppParams{}) // Sample txn has two box refs, so read budget is 2*100 @@ -287,97 +287,97 @@ func TestBoxReadBudget(t *testing.T) { ledger.NewBox(appID, "third", make([]byte, 100), appAddr) // Right at budget - logic.TestApp(t, `byte "self"; box_len; assert; byte "other"; box_len; assert; ==`, ep) + TestApp(t, `byte "self"; box_len; assert; byte "other"; box_len; assert; ==`, ep) // With three box refs, read budget is now 3*100 txn.Boxes = append(txn.Boxes, transactions.BoxRef{Name: []byte("third")}) - logic.TestApp(t, `byte "self"; box_len; assert; byte "third"; box_len; assert; ==`, ep) + TestApp(t, `byte "self"; box_len; assert; byte "third"; box_len; assert; ==`, ep) // Increase "third" box size to 101 ledger.DelBox(appID, "third", appAddr) ledger.NewBox(appID, "third", make([]byte, 101), appAddr) // Budget exceeded - logic.TestApp(t, `byte "self"; box_len; assert; byte "third"; box_len; assert; ==`, ep, "box read budget (300) exceeded") + TestApp(t, `byte "self"; box_len; assert; byte "third"; box_len; assert; ==`, ep, "box read budget (300) exceeded") // Still exceeded if we don't touch the boxes - logic.TestApp(t, `int 1`, ep, "box read budget (300) exceeded") + TestApp(t, `int 1`, ep, "box read budget (300) exceeded") // Still exceeded with one box ref txn.Boxes = txn.Boxes[2:] - logic.TestApp(t, `byte "third"; box_len; assert; int 101; ==`, ep, "box read budget (100) exceeded") + TestApp(t, `byte "third"; box_len; assert; int 101; ==`, ep, "box read budget (100) exceeded") // But not with two txn.Boxes = append(txn.Boxes, transactions.BoxRef{}) - logic.TestApp(t, `byte "third"; box_len; assert; int 101; ==`, ep) + TestApp(t, `byte "third"; box_len; assert; int 101; ==`, ep) } func TestBoxWriteBudget(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - ep, _, ledger := logic.MakeSampleEnv() + ep, _, ledger := MakeSampleEnv() ledger.NewApp(basics.Address{}, 888, basics.AppParams{}) // Sample tx[0] has two box refs, so write budget is 2*100 // Test simple use of one box, less than, equal, or over budget - logic.TestApp(t, `byte "self"; int 4; box_create`, ep) - logic.TestApp(t, `byte "self"; box_del; assert + TestApp(t, `byte "self"; int 4; box_create`, ep) + TestApp(t, `byte "self"; box_del; assert byte "self"; int 199; box_create`, ep) - logic.TestApp(t, `byte "self"; box_del; assert + TestApp(t, `byte "self"; box_del; assert byte "self"; int 200; box_create`, ep) - logic.TestApp(t, `byte "self"; box_del; assert + TestApp(t, `byte "self"; box_del; assert byte "self"; int 201; box_create`, ep, "write budget (200) exceeded") // Test interplay of two different boxes being created - logic.TestApp(t, `byte "self"; int 4; box_create; assert + TestApp(t, `byte "self"; int 4; box_create; assert byte "other"; int 4; box_create`, ep) - logic.TestApp(t, `byte "self"; box_del; assert; byte "other"; box_del; assert + TestApp(t, `byte "self"; box_del; assert; byte "other"; box_del; assert byte "self"; int 4; box_create; assert; byte "other"; int 196; box_create`, ep) - logic.TestApp(t, `byte "self"; box_del; assert; byte "other"; box_del; assert + TestApp(t, `byte "self"; box_del; assert; byte "other"; box_del; assert byte "self"; int 6; box_create; assert byte "other"; int 196; box_create`, ep, "write budget (200) exceeded") ledger.DelBoxes(888, "other") - logic.TestApp(t, `byte "self"; box_del; assert + TestApp(t, `byte "self"; box_del; assert byte "self"; int 6; box_create; assert byte "other"; int 196; box_create; assert // fails to create byte "self"; box_del;`, ep, "write budget (200) exceeded") - logic.TestApp(t, `byte "other"; int 196; box_create`, ep) - logic.TestApp(t, `byte "self"; box_del`, ep, "read budget") // 6 + 196 > 200 - logic.TestApp(t, `byte "junk"; box_del`, ep, "read budget") // fails before invalid "junk" is noticed + TestApp(t, `byte "other"; int 196; box_create`, ep) + TestApp(t, `byte "self"; box_del`, ep, "read budget") // 6 + 196 > 200 + TestApp(t, `byte "junk"; box_del`, ep, "read budget") // fails before invalid "junk" is noticed ledger.DelBoxes(888, "self", "other") - logic.TestApp(t, `byte "junk"; box_del`, ep, "invalid Box reference") + TestApp(t, `byte "junk"; box_del`, ep, "invalid Box reference") // Create two boxes, that sum to over budget, then test trying to use them together - logic.TestApp(t, `byte "self"; int 101; box_create`, ep) - logic.TestApp(t, `byte "self"; int 1; byte 0x3333; box_replace; + TestApp(t, `byte "self"; int 101; box_create`, ep) + TestApp(t, `byte "self"; int 1; byte 0x3333; box_replace; byte "other"; int 101; box_create`, ep, "write budget (200) exceeded") - logic.TestApp(t, `byte "other"; int 101; box_create`, ep) - logic.TestApp(t, `byte "self"; int 1; byte 0x3333; box_replace; + TestApp(t, `byte "other"; int 101; box_create`, ep) + TestApp(t, `byte "self"; int 1; byte 0x3333; box_replace; byte "other"; int 1; byte 0x3333; box_replace; int 1`, ep, "read budget (200) exceeded") ledger.DelBoxes(888, "other") - logic.TestApp(t, `byte "self"; int 1; byte 0x3333; box_replace; + TestApp(t, `byte "self"; int 1; byte 0x3333; box_replace; byte "other"; int 10; box_create`, ep) // They're now small enough to read and write - logic.TestApp(t, `byte "self"; int 1; byte 0x3333; box_replace; + TestApp(t, `byte "self"; int 1; byte 0x3333; box_replace; byte "other"; int 1; byte 0x3333; box_replace; int 1`, ep) // writing twice is no problem (even though it's the big one) - logic.TestApp(t, `byte "self"; int 1; byte 0x3333; box_replace; + TestApp(t, `byte "self"; int 1; byte 0x3333; box_replace; byte "self"; int 50; byte 0x3333; box_replace; byte "other"; int 1; byte 0x3333; box_replace; int 1`, ep) - logic.TestApp(t, `byte "self"; box_del; assert; byte "other"; box_del`, ep) // cleanup + TestApp(t, `byte "self"; box_del; assert; byte "other"; box_del`, ep) // cleanup } @@ -386,33 +386,33 @@ func TestWriteBudgetPut(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - ep, _, ledger := logic.MakeSampleEnv() + ep, _, ledger := MakeSampleEnv() ledger.NewApp(basics.Address{}, 888, basics.AppParams{}) // Sample tx[0] has two box refs, so write budget is 2*100 // Test simple use of one box - logic.TestApp(t, `byte "self"; int 200; box_create`, ep) // equal to budget - logic.TestApp(t, `byte "self"; box_del`, ep) - logic.TestApp(t, `byte "self"; int 201; box_create`, ep, // 1 over budget + TestApp(t, `byte "self"; int 200; box_create`, ep) // equal to budget + TestApp(t, `byte "self"; box_del`, ep) + TestApp(t, `byte "self"; int 201; box_create`, ep, // 1 over budget "write budget") // More complicated versions that use 1 or more 150 byte boxes, so one is ok, two is over - logic.TestApp(t, `byte "self"; int 150; box_create`, ep) - logic.TestApp(t, `byte "self"; int 150; bzero; box_put; int 1`, ep) - logic.TestApp(t, `byte "self"; int 149; bzero; byte "x"; concat; box_put; int 1`, ep) + TestApp(t, `byte "self"; int 150; box_create`, ep) + TestApp(t, `byte "self"; int 150; bzero; box_put; int 1`, ep) + TestApp(t, `byte "self"; int 149; bzero; byte "x"; concat; box_put; int 1`, ep) // puts to same name, doesn't go over budget (although we don't optimize // away puts with the same content, this test uses different contents just // to be sure). - logic.TestApp(t, `byte "self"; int 150; bzero; box_put; + TestApp(t, `byte "self"; int 150; bzero; box_put; byte "self"; int 149; bzero; byte "x"; concat; box_put; int 1`, ep) // puts to different names do - logic.TestApp(t, `byte "self"; int 150; bzero; box_put; + TestApp(t, `byte "self"; int 150; bzero; box_put; byte "other"; int 149; bzero; byte "x"; concat; box_put; int 1`, ep, "write budget") // testing a regression: ensure box_put does not double debit when creating - logic.TestApp(t, `byte "self"; int 150; bzero; box_put; int 1`, ep) + TestApp(t, `byte "self"; int 150; bzero; box_put; int 1`, ep) } // TestBoxRepeatedCreate ensures that app is not charged write budget for @@ -421,19 +421,19 @@ func TestBoxRepeatedCreate(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - ep, _, ledger := logic.MakeSampleEnv() + ep, _, ledger := MakeSampleEnv() ledger.NewApp(basics.Address{}, 888, basics.AppParams{}) // Sample tx[0] has two box refs, so write budget is 2*100 - logic.TestApp(t, `byte "self"; int 201; box_create`, ep, + TestApp(t, `byte "self"; int 201; box_create`, ep, "write budget") - logic.TestApp(t, `byte "self"; int 200; box_create`, ep) - logic.TestApp(t, `byte "self"; int 200; box_create; !; assert // does not actually create + TestApp(t, `byte "self"; int 200; box_create`, ep) + TestApp(t, `byte "self"; int 200; box_create; !; assert // does not actually create byte "other"; int 200; box_create; assert // does create, and budget should be enough int 1`, ep) ledger.DelBoxes(888, "self", "other") - logic.TestApp(t, `byte "other"; int 200; box_create; assert + TestApp(t, `byte "other"; int 200; box_create; assert byte "other"; box_del; assert byte "other"; int 200; box_create`, ep) @@ -443,29 +443,29 @@ func TestIOBudgetGrow(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - ep, txn, ledger := logic.MakeSampleEnv() + ep, txn, ledger := MakeSampleEnv() ledger.NewApp(basics.Address{}, 888, basics.AppParams{}) ledger.CreateBox(888, "self", 101) ledger.CreateBox(888, "other", 101) - logic.TestApp(t, `byte "self"; int 1; byte 0x3333; box_replace; + TestApp(t, `byte "self"; int 1; byte 0x3333; box_replace; byte "other"; int 1; byte 0x3333; box_replace; int 1`, ep, "read budget (200) exceeded") txn.Boxes = append(txn.Boxes, transactions.BoxRef{}) // Since we added an empty BoxRef, we can read > 200. - logic.TestApp(t, `byte "self"; int 1; int 7; box_extract; pop; + TestApp(t, `byte "self"; int 1; int 7; box_extract; pop; byte "other"; int 1; int 7; box_extract; pop; int 1`, ep) // Add write, for that matter - logic.TestApp(t, `byte "self"; int 1; byte 0x3333; box_replace; + TestApp(t, `byte "self"; int 1; byte 0x3333; box_replace; byte "other"; int 1; byte 0x3333; box_replace; int 1`, ep) txn.Boxes = append(txn.Boxes, transactions.BoxRef{Name: []byte("another")}) // Here we read 202, and write a very different 350 (since we now have 4 brs) - logic.TestApp(t, `byte "self"; int 1; int 7; box_extract; pop; + TestApp(t, `byte "self"; int 1; int 7; box_extract; pop; byte "other"; int 1; int 7; box_extract; pop; byte "another"; int 350; box_create`, ep) } @@ -474,37 +474,37 @@ func TestConveniences(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - ep, _, ledger := logic.MakeSampleEnv() + ep, _, ledger := MakeSampleEnv() ledger.NewApp(basics.Address{}, 888, basics.AppParams{}) // box_get of a new name reports !exists, and returns 0 length bytes. - logic.TestApp(t, `byte "self"; box_get; !; assert; len; !`, ep) + TestApp(t, `byte "self"; box_get; !; assert; len; !`, ep) // box_len of a new name reports !exists, and returns 0 as the length - logic.TestApp(t, `byte "self"; box_len; !; assert; !`, ep) + TestApp(t, `byte "self"; box_len; !; assert; !`, ep) // box_put creates the box with contents provided - logic.TestApp(t, `byte "self"; byte 0x3132; box_put; + TestApp(t, `byte "self"; byte 0x3132; box_put; byte "self"; box_len; assert; int 2; ==; assert byte "self"; box_get; assert; byte 0x3132; ==`, ep) // box_put fails if box exists and is wrong size (self exists from last test) - logic.TestApp(t, `byte "self"; byte 0x313233; box_put; int 1`, ep, + TestApp(t, `byte "self"; byte 0x313233; box_put; int 1`, ep, "box_put wrong size") ledger.DelBoxes(888, "self") // put and get can interact with created boxes - logic.TestApp(t, `byte "self"; int 3; box_create`, ep) - logic.TestApp(t, `byte "self"; box_get; assert; byte 0x000000; ==`, ep) - logic.TestApp(t, `byte "self"; byte 0xAABBCC; box_put; int 1`, ep) - logic.TestApp(t, `byte "self"; int 1; byte 0xDDEE; box_replace; int 1`, ep) - logic.TestApp(t, `byte "self"; box_get; assert; byte 0xAADDEE; ==`, ep) + TestApp(t, `byte "self"; int 3; box_create`, ep) + TestApp(t, `byte "self"; box_get; assert; byte 0x000000; ==`, ep) + TestApp(t, `byte "self"; byte 0xAABBCC; box_put; int 1`, ep) + TestApp(t, `byte "self"; int 1; byte 0xDDEE; box_replace; int 1`, ep) + TestApp(t, `byte "self"; box_get; assert; byte 0xAADDEE; ==`, ep) ledger.DelBoxes(888, "self") // box_get panics if the box is too big (for TEAL, or for proto) ep.Proto.MaxBoxSize = 5000 ep.Proto.BytesPerBoxReference = 5000 // avoid write budget error - logic.TestApp(t, `byte "self"; int 4098; box_create; assert; // bigger than maxStringSize + TestApp(t, `byte "self"; int 4098; box_create; assert; // bigger than maxStringSize byte "self"; box_get; assert; len`, ep, "box_get produced a too big") } @@ -529,9 +529,9 @@ func TestEarlyPanics(t *testing.T) { name, program := name, program t.Run(name+"/zero", func(t *testing.T) { t.Parallel() - ep, _, l := logic.MakeSampleEnv() + ep, _, l := MakeSampleEnv() l.NewApp(basics.Address{}, 888, basics.AppParams{}) - logic.TestApp(t, fmt.Sprintf(program, ""), ep, "zero length") + TestApp(t, fmt.Sprintf(program, ""), ep, "zero length") }) } @@ -540,9 +540,9 @@ func TestEarlyPanics(t *testing.T) { name, program := name, program t.Run(name+"/long", func(t *testing.T) { t.Parallel() - ep, _, l := logic.MakeSampleEnv() + ep, _, l := MakeSampleEnv() l.NewApp(basics.Address{}, 888, basics.AppParams{}) - logic.TestApp(t, fmt.Sprintf(program, big), ep, "name too long") + TestApp(t, fmt.Sprintf(program, big), ep, "name too long") }) } @@ -552,18 +552,18 @@ func TestBoxTotals(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - ep, txn, ledger := logic.MakeSampleEnv() - - ledger.NewApp(txn.Sender, 888, basics.AppParams{}) - // The SENDER certainly has no boxes (but does exist) - logic.TestApp(t, `txn Sender; acct_params_get AcctTotalBoxes; pop; !`, ep) - // Nor does the app account, to start - logic.TestApp(t, `int 888; app_params_get AppAddress; assert; - acct_params_get AcctTotalBoxes; pop; !; `, ep) - // Create a 31 byte box with a 4 byte name - logic.TestApp(t, `byte "self"; int 31; box_create`, ep) - logic.TestApp(t, `int 888; app_params_get AppAddress; assert; - acct_params_get AcctTotalBoxes; pop; int 1; ==`, ep) - logic.TestApp(t, `int 888; app_params_get AppAddress; assert; - acct_params_get AcctTotalBoxBytes; pop; int 35; ==`, ep) + TestLogicRange(t, 8, 0, func(t *testing.T, ep *EvalParams, tx *transactions.Transaction, ledger *Ledger) { + ledger.NewApp(tx.Sender, 888, basics.AppParams{}) + // The SENDER certainly has no boxes (but does exist) + TestApp(t, `txn Sender; acct_params_get AcctTotalBoxes; pop; !`, ep) + // Nor does the app account, to start + TestApp(t, `int 888; app_params_get AppAddress; assert; + acct_params_get AcctTotalBoxes; pop; !; `, ep) + // Create a 31 byte box with a 4 byte name + TestApp(t, `byte "self"; int 31; box_create`, ep) + TestApp(t, `int 888; app_params_get AppAddress; assert; + acct_params_get AcctTotalBoxes; pop; int 1; ==`, ep) + TestApp(t, `int 888; app_params_get AppAddress; assert; + acct_params_get AcctTotalBoxBytes; pop; int 35; ==`, ep) + }) } diff --git a/data/transactions/logic/doc_test.go b/data/transactions/logic/doc_test.go index 8afca4520f..404a934d99 100644 --- a/data/transactions/logic/doc_test.go +++ b/data/transactions/logic/doc_test.go @@ -55,11 +55,11 @@ func TestDocStragglers(t *testing.T) { for op := range opDocExtras { _, ok := opDocByName[op] - require.True(t, ok, "%s is in opDocExtra, but not opDocByName", op) + assert.True(t, ok, "%s is in opDocExtra, but not opDocByName", op) } for op := range opcodeImmediateNotes { _, ok := opDocByName[op] - require.True(t, ok, "%s is in opcodeImmediateNotes, but not opDocByName", op) + assert.True(t, ok, "%s is in opcodeImmediateNotes, but not opDocByName", op) } } diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index dfc2639195..cbf93a9a9f 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -69,7 +69,7 @@ const maxLogCalls = 32 // To be clear, 0 would prevent inner appls, 1 would mean inner app calls cannot // make inner appls. So the total app depth can be 1 higher than this number, if // you count the top-level app call. -const maxAppCallDepth = 8 +var maxAppCallDepth = 8 // maxStackDepth should not change unless controlled by an AVM version change const maxStackDepth = 1000 @@ -1306,10 +1306,6 @@ func (cx *EvalContext) ensureStackCap(targetCap int) { } } -func opDeprecated(cx *EvalContext) error { - return fmt.Errorf("deprecated opcode %d executed", cx.program[cx.pc]) -} - func opErr(cx *EvalContext) error { return errors.New("err opcode executed") } @@ -4065,34 +4061,48 @@ func (cx *EvalContext) assignAccount(sv stackValue) (basics.Address, error) { // not, based on version. func (cx *EvalContext) accountReference(account stackValue) (basics.Address, uint64, error) { - if cx.version < sharedResourcesVersion { - if account.argType() == StackUint64 { - addr, err := cx.txn.Txn.AddressByIndex(account.Uint, cx.txn.Txn.Sender) - return addr, account.Uint, err - } - } - addr, err := account.address() + addr, idx, err := cx.resolveAccount(account) if err != nil { return addr, 0, err } - idx, err := cx.txn.Txn.IndexByAddress(addr, cx.txn.Txn.Sender) - if err == nil { - return addr, idx, nil + if idx >= 0 { + return addr, uint64(idx), err } - - // IndexByAddress's `err` tells us we can't return the idx into + // negative idx tells us we can't return the idx into // txn.Accounts, but the account might still be available (because it was - // created in earlier in the group, or because of group sharing) + // created earlier in the group, or because of group sharing) ok := cx.availableAccount(addr) if !ok { - // nope, it's not available at all. So return the `err` we have from - // above, indicating that it was not found. - return addr, 0, err + return addr, 0, fmt.Errorf("invalid Account reference %s", addr) } + // available, but not in txn.Accounts. Return 1 higher to signal. return addr, uint64(len(cx.txn.Txn.Accounts) + 1), nil } +// resolveAccount determines the Address and slot indicated by a stackValue, so +// it is either confirming that the bytes is indeed 32 bytes (and trying to find +// it in txn.Accounts or returning -1), or it is performing the lookup of the +// integer arg in txn.Accounts. +func (cx *EvalContext) resolveAccount(account stackValue) (basics.Address, int, error) { + if account.argType() == StackUint64 { + addr, err := cx.txn.Txn.AddressByIndex(account.Uint, cx.txn.Txn.Sender) + return addr, int(account.Uint), err + } + addr, err := account.address() + if err != nil { + return addr, -1, err + } + + idx, err := cx.txn.Txn.IndexByAddress(addr, cx.txn.Txn.Sender) + if err != nil { + // we don't want to convery `err`, because the supplied `account` does + // seem to be an address, but we can't give a valid index. + return addr, -1, nil + } + return addr, int(idx), nil +} + func (cx *EvalContext) availableAccount(addr basics.Address) bool { _, err := cx.txn.Txn.IndexByAddress(addr, cx.txn.Txn.Sender) if err == nil { @@ -4492,40 +4502,63 @@ func opAppGlobalDel(cx *EvalContext) error { // more than 2 or so, and was often called an "index". But it was not a // basics.AssetIndex or basics.ApplicationIndex. -func (cx *EvalContext) appReference(ref uint64, foreign bool) (basics.AppIndex, error) { +func (cx *EvalContext) appReference(ref uint64, foreign bool) (aid basics.AppIndex, err error) { if cx.version >= directRefEnabledVersion { - if ref == 0 || ref == uint64(cx.appID) { - return cx.appID, nil - } - aid := basics.AppIndex(ref) - if cx.availableApp(aid) { - return aid, nil - } + return cx.resolveApp(ref) + } - if cx.version < sharedResourcesVersion { - // Allow use of indexes, but this comes last so that clear advice can be - // given to anyone who cares about semantics in the first few rounds of - // a new network - don't use indexes for references, use the App ID - if ref <= uint64(len(cx.txn.Txn.ForeignApps)) { - return basics.AppIndex(cx.txn.Txn.ForeignApps[ref-1]), nil + // resolveApp is already similarly protected (and must be, since it is + // called independently) + if cx.Proto.AppForbidLowResources { + defer func() { + if aid <= lastForbiddenResource && err == nil { + err = fmt.Errorf("low App lookup %d", aid) } + }() + } + // Old rules, pre directRefEnabledVersion, when a ref has to be a slot for + // some opcodes, and had to be an ID for others. + if ref == 0 { // Even back when expected to be a real ID, ref = 0 was current app + return cx.appID, nil + } + if foreign { + // In old versions, a foreign reference must be an index in ForeignApps or 0 + if ref <= uint64(len(cx.txn.Txn.ForeignApps)) { + return basics.AppIndex(cx.txn.Txn.ForeignApps[ref-1]), nil } - } else { - // Old rules - if ref == 0 { // Even back when expected to be a real ID, ref = 0 was current app - return cx.appID, nil - } - if foreign { - // In old versions, a foreign reference must be an index in ForeignAssets or 0 - if ref <= uint64(len(cx.txn.Txn.ForeignApps)) { - return basics.AppIndex(cx.txn.Txn.ForeignApps[ref-1]), nil + return 0, fmt.Errorf("App index %d beyond txn.ForeignApps", ref) + } + // Otherwise it's direct + return basics.AppIndex(ref), nil +} + +// resolveApp figures out what App an integer is referring to, considering 0 as +// current app first, then uses the integer as is if it is an availableApp, then +// tries to perform a slot lookup. +func (cx *EvalContext) resolveApp(ref uint64) (aid basics.AppIndex, err error) { + if cx.Proto.AppForbidLowResources { + defer func() { + if aid <= lastForbiddenResource && err == nil { + err = fmt.Errorf("low App lookup %d", aid) } - } else { - // Otherwise it's direct - return basics.AppIndex(ref), nil - } + }() } - return 0, fmt.Errorf("invalid App reference %d", ref) + + if ref == 0 || ref == uint64(cx.appID) { + return cx.appID, nil + } + aid = basics.AppIndex(ref) + if cx.availableApp(aid) { + return aid, nil + } + + // Allow use of indexes, but this comes last so that clear advice can be + // given to anyone who cares about semantics in the first few rounds of + // a new network - don't use indexes for references, use the App ID + if ref <= uint64(len(cx.txn.Txn.ForeignApps)) { + return basics.AppIndex(cx.txn.Txn.ForeignApps[ref-1]), nil + } + return 0, fmt.Errorf("unavailable App %d", ref) } // localsReference has the main job of resolving the account (as bytes or u64) @@ -4535,33 +4568,32 @@ func (cx *EvalContext) appReference(ref uint64, foreign bool) (basics.AppIndex, // job in certainly old versions that need the slot index while doing a lookup. func (cx *EvalContext) localsReference(account stackValue, ref uint64) (basics.Address, basics.AppIndex, uint64, error) { if cx.version >= sharedResourcesVersion { - unused := uint64(0) // see function comment - addr, err := account.address() + addr, _, err := cx.resolveAccount(account) if err != nil { return basics.Address{}, 0, 0, err } - aid := basics.AppIndex(ref) - if ref == 0 { - aid = cx.appID - } - if cx.allowsLocals(addr, aid) { - return addr, aid, unused, nil + aid, err := cx.resolveApp(ref) + if err == nil { + if cx.allowsLocals(addr, aid) { + return addr, aid, 0, nil // >v9 caller doesn't care about slot + } } - // Do some extra lookups to give a more concise err. Whenever a locals - // is available, its account and app must be as well (but not vice - // versa, anymore). So, if (only) one of them is not available, yell - // about it, specifically. + // Do an extra check to give a better error. The app is definitely + // available. If the addr is too, then the trouble is they must have + // come from different transactions, and the HOLDING is the problem. - _, _, acctErr := cx.accountReference(account) - _, appErr := cx.appReference(ref, false) + acctOK := cx.availableAccount(addr) switch { - case acctErr != nil && appErr == nil: - err = acctErr - case acctErr == nil && appErr != nil: - err = appErr - default: - err = fmt.Errorf("invalid Local State access %s x %d", addr, aid) + case err != nil && acctOK: + // do nothing, err contains the an Asset specific problem + case err == nil && acctOK: + // although both are available, the LOCALS are not + err = fmt.Errorf("unavailable Local State %s x %d", addr, aid) + case err != nil && !acctOK: + err = fmt.Errorf("unavailable Account %s, %w", addr, err) + case err == nil && !acctOK: + err = fmt.Errorf("unavailable Account %s", addr) } return basics.Address{}, 0, 0, err @@ -4580,64 +4612,89 @@ func (cx *EvalContext) localsReference(account stackValue, ref uint64) (basics.A return addr, app, addrIdx, nil } -func (cx *EvalContext) assetReference(ref uint64, foreign bool) (basics.AssetIndex, error) { +func (cx *EvalContext) assetReference(ref uint64, foreign bool) (aid basics.AssetIndex, err error) { if cx.version >= directRefEnabledVersion { - aid := basics.AssetIndex(ref) - if cx.availableAsset(aid) { - return aid, nil - } + return cx.resolveAsset(ref) + } - if cx.version < sharedResourcesVersion { - // Allow use of indexes, but this comes last so that clear advice can be - // given to anyone who cares about semantics in the first few rounds of - // a new network - don't use indexes for references, use the asa ID. - if ref < uint64(len(cx.txn.Txn.ForeignAssets)) { - return basics.AssetIndex(cx.txn.Txn.ForeignAssets[ref]), nil + // resolveAsset is already similarly protected (and must be, since it is + // called independently) + if cx.Proto.AppForbidLowResources { + defer func() { + if aid <= lastForbiddenResource && err == nil { + err = fmt.Errorf("low Asset lookup %d", aid) } + }() + } + // Old rules, pre directRefEnabledVersion, when a ref has to be a slot for + // some opcodes, and had to be an ID for others. + if foreign { + // In old versions, a foreign reference must be an index in ForeignAssets + if ref < uint64(len(cx.txn.Txn.ForeignAssets)) { + return basics.AssetIndex(cx.txn.Txn.ForeignAssets[ref]), nil } - } else { - // Old rules - if foreign { - // In old versions, a foreign reference must be an index in ForeignAssets - if ref < uint64(len(cx.txn.Txn.ForeignAssets)) { - return basics.AssetIndex(cx.txn.Txn.ForeignAssets[ref]), nil + return 0, fmt.Errorf("Asset index %d beyond txn.ForeignAssets", ref) + } + // Otherwise it's direct + return basics.AssetIndex(ref), nil +} + +const lastForbiddenResource = 255 + +// resolveAsset figures out what Asset an integer is referring to, considering 0 as +// current app first, then uses the integer as is if it is an availableAsset, then +// tries to perform a slot lookup. +func (cx *EvalContext) resolveAsset(ref uint64) (aid basics.AssetIndex, err error) { + if cx.Proto.AppForbidLowResources { + defer func() { + if aid <= lastForbiddenResource && err == nil { + err = fmt.Errorf("low Asset lookup %d", aid) } - } else { - // Otherwise it's direct - return basics.AssetIndex(ref), nil - } + }() + } + aid = basics.AssetIndex(ref) + if cx.availableAsset(aid) { + return aid, nil } - return 0, fmt.Errorf("invalid Asset reference %d", ref) + // Allow use of indexes, but this comes last so that clear advice can be + // given to anyone who cares about semantics in the first few rounds of + // a new network - don't use indexes for references, use the Asset ID + if ref < uint64(len(cx.txn.Txn.ForeignAssets)) { + return basics.AssetIndex(cx.txn.Txn.ForeignAssets[ref]), nil + } + return 0, fmt.Errorf("unavailable Asset %d", ref) } func (cx *EvalContext) holdingReference(account stackValue, ref uint64) (basics.Address, basics.AssetIndex, error) { if cx.version >= sharedResourcesVersion { - addr, err := account.address() + addr, _, err := cx.resolveAccount(account) if err != nil { return basics.Address{}, 0, err } - aid := basics.AssetIndex(ref) - if cx.allowsHolding(addr, aid) { - return addr, aid, nil + aid, err := cx.resolveAsset(ref) + if err == nil { + if cx.allowsHolding(addr, aid) { + return addr, aid, nil + } } - // Do some extra lookups to give a more concise err. Whenever a holding - // is available, its account and asset must be as well (but not vice - // versa, anymore). So, if (only) one of them is not available, yell - // about it, specifically. + // Do an extra check to give a better error. The asset is definitely + // available. If the addr is too, then the trouble is they must have + // come from different transactions, and the HOLDING is the problem. - _, _, acctErr := cx.accountReference(account) - _, assetErr := cx.assetReference(ref, false) + acctOK := cx.availableAccount(addr) switch { - case acctErr != nil && assetErr == nil: - err = acctErr - case acctErr == nil && assetErr != nil: - err = assetErr - default: - err = fmt.Errorf("invalid Holding access %s x %d", addr, aid) + case err != nil && acctOK: + // do nothing, err contains the an Asset specific problem + case err == nil && acctOK: + // although both are available, the HOLDING is not + err = fmt.Errorf("unavailable Holding %s x %d", addr, aid) + case err != nil && !acctOK: + err = fmt.Errorf("unavailable Account %s, %w", addr, err) + case err == nil && !acctOK: + err = fmt.Errorf("unavailable Account %s", addr) } - return basics.Address{}, 0, err } @@ -4911,7 +4968,7 @@ func (cx *EvalContext) assignAsset(sv stackValue) (basics.AssetIndex, error) { return aid, nil } - return 0, fmt.Errorf("invalid Asset reference %d", aid) + return 0, fmt.Errorf("unavailable Asset %d", aid) } // availableAsset determines whether an asset is "available". Before @@ -4960,7 +5017,7 @@ func (cx *EvalContext) assignApp(sv stackValue) (basics.AppIndex, error) { return aid, nil } - return 0, fmt.Errorf("invalid App reference %d", aid) + return 0, fmt.Errorf("unavailable App %d", aid) } func (cx *EvalContext) availableApp(aid basics.AppIndex) bool { diff --git a/data/transactions/logic/evalAppTxn_test.go b/data/transactions/logic/evalAppTxn_test.go index 84040dddf1..0204d04e98 100644 --- a/data/transactions/logic/evalAppTxn_test.go +++ b/data/transactions/logic/evalAppTxn_test.go @@ -19,13 +19,13 @@ package logic_test import ( "encoding/hex" "fmt" + "strconv" "strings" "testing" "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/data/transactions/logic" . "github.com/algorand/go-algorand/data/transactions/logic" "github.com/algorand/go-algorand/data/txntest" "github.com/algorand/go-algorand/protocol" @@ -147,11 +147,14 @@ func TestAppPay(t *testing.T) { ` // v5 added inners - logic.TestLogicRange(t, 5, 0, func(t *testing.T, ep *logic.EvalParams, tx *transactions.Transaction, ledger *logic.Ledger) { + TestLogicRange(t, 5, 0, func(t *testing.T, ep *EvalParams, tx *transactions.Transaction, ledger *Ledger) { + test := func(source string, problem ...string) { + TestApp(t, source, ep, problem...) + } ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) - TestApp(t, "txn Sender; balance; int 0; ==;", ep) - TestApp(t, "txn Sender; txn Accounts 1; int 100"+pay, ep, "unauthorized") - TestApp(t, "global CurrentApplicationAddress; txn Accounts 1; int 100"+pay, ep, + test("txn Sender; balance; int 0; ==;") + test("txn Sender; txn Accounts 1; int 100"+pay, "unauthorized") + test("global CurrentApplicationAddress; txn Accounts 1; int 100"+pay, "insufficient balance") ledger.NewAccount(appAddr(888), 1000000) @@ -161,11 +164,11 @@ func TestAppPay(t *testing.T) { // balance check in eval.transaction() properly notices and fails // the transaction later. This fits with the model that we check // min balances once at the end of each "top-level" transaction. - TestApp(t, "global CurrentApplicationAddress; txn Accounts 1; int 100"+pay, ep) + test("global CurrentApplicationAddress; txn Accounts 1; int 100" + pay) // 100 of 1000000 spent, plus MinTxnFee in our fake protocol is 1001 - TestApp(t, "global CurrentApplicationAddress; balance; int 998899; ==", ep) - TestApp(t, "txn Receiver; balance; int 100; ==", ep) + test("global CurrentApplicationAddress; balance; int 998899; ==") + test("txn Receiver; balance; int 100; ==") close := ` itxn_begin @@ -174,10 +177,10 @@ func TestAppPay(t *testing.T) { itxn_submit int 1 ` - TestApp(t, close, ep) - TestApp(t, "global CurrentApplicationAddress; balance; !", ep) + test(close) + test("global CurrentApplicationAddress; balance; !") // Receiver got most of the algos (except 1001 for fee) - TestApp(t, "txn Receiver; balance; int 997998; ==", ep) + test("txn Receiver; balance; int 997998; ==") }) } @@ -186,7 +189,12 @@ func TestAppAssetOptIn(t *testing.T) { t.Parallel() // v5 added inners - logic.TestLogicRange(t, 5, 0, func(t *testing.T, ep *logic.EvalParams, tx *transactions.Transaction, ledger *logic.Ledger) { + TestLogicRange(t, 5, 0, func(t *testing.T, ep *EvalParams, tx *transactions.Transaction, ledger *Ledger) { + test := func(source string, problem ...string) { + t.Helper() + TestApp(t, source, ep, problem...) + } + // Establish 888 as the app id, and fund it. ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) ledger.NewAccount(basics.AppIndex(888).Address(), 200000) @@ -200,9 +208,9 @@ txn Sender; itxn_field AssetReceiver; itxn_submit int 1 ` - TestApp(t, axfer, ep, "invalid Asset reference") + test(axfer, "unavailable Asset 25") tx.ForeignAssets = append(tx.ForeignAssets, 25) - TestApp(t, axfer, ep, "not opted in") // app account not opted in + test(axfer, "not opted in") // app account not opted in optin := ` itxn_begin int axfer; itxn_field TypeEnum; @@ -212,23 +220,23 @@ global CurrentApplicationAddress; itxn_field AssetReceiver; itxn_submit int 1 ` - TestApp(t, optin, ep, "does not exist") + test(optin, "does not exist") // Asset 25 ledger.NewAsset(tx.Sender, 25, basics.AssetParams{ Total: 10, UnitName: "x", AssetName: "Cross", }) - TestApp(t, optin, ep) + test(optin) - TestApp(t, axfer, ep, "insufficient balance") // opted in, but balance=0 + test(axfer, "insufficient balance") // opted in, but balance=0 // Fund the app account with the asset ledger.NewHolding(basics.AppIndex(888).Address(), 25, 5, false) - TestApp(t, axfer, ep) - TestApp(t, axfer, ep) - TestApp(t, axfer, ep, "insufficient balance") // balance = 1, tried to move 2) - TestApp(t, "global CurrentApplicationAddress; int 25; asset_holding_get AssetBalance; assert; int 1; ==", ep) + test(axfer) + test(axfer) + test(axfer, "insufficient balance") // balance = 1, tried to move 2) + test("global CurrentApplicationAddress; int 25; asset_holding_get AssetBalance; assert; int 1; ==") close := ` itxn_begin @@ -240,8 +248,8 @@ txn Sender; itxn_field AssetCloseTo; itxn_submit int 1 ` - TestApp(t, close, ep) - TestApp(t, "global CurrentApplicationAddress; int 25; asset_holding_get AssetBalance; !; assert; !", ep) + test(close) + test("global CurrentApplicationAddress; int 25; asset_holding_get AssetBalance; !; assert; !") }) } @@ -260,9 +268,8 @@ func TestRekeyPay(t *testing.T) { ` // v5 added inners - logic.TestLogicRange(t, 5, 0, func(t *testing.T, ep *logic.EvalParams, tx *transactions.Transaction, ledger *logic.Ledger) { + TestLogicRange(t, 5, 0, func(t *testing.T, ep *EvalParams, tx *transactions.Transaction, ledger *Ledger) { ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) - TestApp(t, "txn Sender; balance; int 0; ==;", ep) TestApp(t, "txn Sender; txn Accounts 1; int 100"+pay, ep, "unauthorized") ledger.NewAccount(tx.Sender, 120+ep.Proto.MinTxnFee) ledger.Rekey(tx.Sender, basics.AppIndex(888).Address()) @@ -290,7 +297,7 @@ func TestRekeyBack(t *testing.T) { ` // v6 added inner rekey - logic.TestLogicRange(t, 6, 0, func(t *testing.T, ep *logic.EvalParams, tx *transactions.Transaction, ledger *logic.Ledger) { + TestLogicRange(t, 6, 0, func(t *testing.T, ep *EvalParams, tx *transactions.Transaction, ledger *Ledger) { ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) TestApp(t, "txn Sender; balance; int 0; ==;", ep) TestApp(t, "txn Sender; txn Accounts 1; int 100"+payAndUnkey, ep, "unauthorized") @@ -316,7 +323,7 @@ func TestDefaultSender(t *testing.T) { ` // v5 added inners - logic.TestLogicRange(t, 5, 0, func(t *testing.T, ep *logic.EvalParams, tx *transactions.Transaction, ledger *logic.Ledger) { + TestLogicRange(t, 5, 0, func(t *testing.T, ep *EvalParams, tx *transactions.Transaction, ledger *Ledger) { ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) tx.Accounts = append(tx.Accounts, appAddr(888)) TestApp(t, "txn Accounts 1; int 100"+pay, ep, "insufficient balance") @@ -343,33 +350,38 @@ func TestAppAxfer(t *testing.T) { ` // v5 added inners - logic.TestLogicRange(t, 5, 0, func(t *testing.T, ep *logic.EvalParams, tx *transactions.Transaction, ledger *logic.Ledger) { + TestLogicRange(t, 5, 0, func(t *testing.T, ep *EvalParams, tx *transactions.Transaction, ledger *Ledger) { + test := func(source string, problem ...string) { + t.Helper() + TestApp(t, source, ep, problem...) + } + ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) ledger.NewAsset(tx.Receiver, 777, basics.AssetParams{}) // not in foreign-assets of sample ledger.NewAsset(tx.Receiver, 77, basics.AssetParams{}) // in foreign-assets of sample - TestApp(t, "txn Sender; int 777; asset_holding_get AssetBalance; assert; int 0; ==;", ep, - "invalid Asset reference") // 777 not in foreign-assets - TestApp(t, "txn Sender; int 77; asset_holding_get AssetBalance; assert; int 0; ==;", ep, + test("txn Sender; int 777; asset_holding_get AssetBalance; assert; int 0; ==;", + "unavailable Asset 777") // 777 not in foreign-assets + test("txn Sender; int 77; asset_holding_get AssetBalance; assert; int 0; ==;", "assert failed") // because Sender not opted-in - TestApp(t, "global CurrentApplicationAddress; int 77; asset_holding_get AssetBalance; assert; int 0; ==;", ep, + test("global CurrentApplicationAddress; int 77; asset_holding_get AssetBalance; assert; int 0; ==;", "assert failed") // app account not opted in ledger.NewAccount(appAddr(888), 10000) // plenty for fees ledger.NewHolding(appAddr(888), 77, 3000, false) - TestApp(t, "global CurrentApplicationAddress; int 77; asset_holding_get AssetBalance; assert; int 3000; ==;", ep) + test("global CurrentApplicationAddress; int 77; asset_holding_get AssetBalance; assert; int 3000; ==;") - TestApp(t, "txn Sender; txn Accounts 1; int 100"+axfer, ep, "unauthorized") - TestApp(t, "global CurrentApplicationAddress; txn Accounts 0; int 100"+axfer, ep, + test("txn Sender; txn Accounts 1; int 100"+axfer, "unauthorized") + test("global CurrentApplicationAddress; txn Accounts 0; int 100"+axfer, fmt.Sprintf("Receiver (%s) not opted in", tx.Sender)) // txn.Sender (receiver of the axfer) isn't opted in - TestApp(t, "global CurrentApplicationAddress; txn Accounts 1; int 100000"+axfer, ep, + test("global CurrentApplicationAddress; txn Accounts 1; int 100000"+axfer, "insufficient balance") // Temporarily remove from ForeignAssets to ensure App Account // doesn't get some sort of free pass to send arbitrary assets. save := tx.ForeignAssets tx.ForeignAssets = []basics.AssetIndex{6, 10} - TestApp(t, "global CurrentApplicationAddress; txn Accounts 1; int 100000"+axfer, ep, - "invalid Asset reference 77") + test("global CurrentApplicationAddress; txn Accounts 1; int 100000"+axfer, + "unavailable Asset 77") tx.ForeignAssets = save noid := ` @@ -387,14 +399,14 @@ func TestAppAxfer(t *testing.T) { // of the implied holding. Of course, there is no 0 asset, so the axfer // is going to fail anyway, but to keep the behavior consistent, v9 // allows the zero asset (and zero account) in `requireHolding`. - TestApp(t, "global CurrentApplicationAddress; txn Accounts 1; int 100"+noid+"int 1", ep, + test("global CurrentApplicationAddress; txn Accounts 1; int 100"+noid+"int 1", fmt.Sprintf("Sender (%s) not opted in to 0", appAddr(888))) - TestApp(t, "global CurrentApplicationAddress; txn Accounts 1; int 100"+axfer+"int 1", ep) + test("global CurrentApplicationAddress; txn Accounts 1; int 100" + axfer + "int 1") // 100 of 3000 spent - TestApp(t, "global CurrentApplicationAddress; int 77; asset_holding_get AssetBalance; assert; int 2900; ==", ep) - TestApp(t, "txn Accounts 1; int 77; asset_holding_get AssetBalance; assert; int 100; ==", ep) + test("global CurrentApplicationAddress; int 77; asset_holding_get AssetBalance; assert; int 2900; ==") + test("txn Accounts 1; int 77; asset_holding_get AssetBalance; assert; int 100; ==") }) } @@ -412,7 +424,7 @@ func TestInnerAppl(t *testing.T) { ` // v6 added inner appls - TestLogicRange(t, 6, 0, func(t *testing.T, ep *logic.EvalParams, tx *transactions.Transaction, ledger *logic.Ledger) { + TestLogicRange(t, 6, 0, func(t *testing.T, ep *EvalParams, tx *transactions.Transaction, ledger *Ledger) { // Establish 888 as the app id, and fund it. ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) ledger.NewAccount(basics.AppIndex(888).Address(), 200000) @@ -423,6 +435,8 @@ func TestInnerAppl(t *testing.T) { }) } +// TestExtraFields tests that the inner txn fields are not allowed to be set for +// different transaction type than the one submitted. func TestExtraFields(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() @@ -440,8 +454,6 @@ func TestExtraFields(t *testing.T) { ep, tx, ledger := MakeSampleEnv() ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) - TestApp(t, "txn Sender; balance; int 0; ==;", ep) - TestApp(t, "txn Sender; txn Accounts 1; int 100"+pay, ep, "unauthorized") TestApp(t, "global CurrentApplicationAddress; txn Accounts 1; int 100"+pay, ep, "non-zero fields for type axfer") } @@ -601,7 +613,7 @@ func TestAssetCreate(t *testing.T) { int 1 ` // v5 added inners - logic.TestLogicRange(t, 5, 0, func(t *testing.T, ep *logic.EvalParams, tx *transactions.Transaction, ledger *logic.Ledger) { + TestLogicRange(t, 5, 0, func(t *testing.T, ep *EvalParams, tx *transactions.Transaction, ledger *Ledger) { ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) TestApp(t, create, ep, "insufficient balance") // Give it enough for fee. Recall that we don't check min balance at this level. @@ -629,7 +641,7 @@ func TestAssetFreeze(t *testing.T) { == ` // v5 added inners - logic.TestLogicRange(t, 5, 0, func(t *testing.T, ep *logic.EvalParams, tx *transactions.Transaction, ledger *logic.Ledger) { + TestLogicRange(t, 5, 0, func(t *testing.T, ep *EvalParams, tx *transactions.Transaction, ledger *Ledger) { ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) // Give it enough for fees. Recall that we don't check min balance at this level. ledger.NewAccount(appAddr(888), 12*MakeTestProto().MinTxnFee) @@ -644,7 +656,7 @@ func TestAssetFreeze(t *testing.T) { itxn_submit int 1 ` - TestApp(t, freeze, ep, "invalid Asset reference") + TestApp(t, freeze, ep, "unavailable Asset 5000") tx.ForeignAssets = []basics.AssetIndex{basics.AssetIndex(5000)} tx.ApplicationArgs = [][]byte{{0x01}} TestApp(t, freeze, ep, "does not hold Asset") @@ -958,8 +970,7 @@ func TestApplCreation(t *testing.T) { p := "itxn_begin;" s := "; int 1" - TestApp(t, p+"int 31; itxn_field ApplicationID"+s, ep, - "invalid App reference") + TestApp(t, p+"int 31; itxn_field ApplicationID"+s, ep, "unavailable App 31") tx.ForeignApps = append(tx.ForeignApps, 31) TestApp(t, p+"int 31; itxn_field ApplicationID"+s, ep) @@ -997,14 +1008,14 @@ func TestApplCreation(t *testing.T) { "too many foreign accounts") TestApp(t, p+strings.Repeat("int 621; itxn_field Applications;", 5)+s, ep, - "invalid App reference") + "unavailable App 621") tx.ForeignApps = append(tx.ForeignApps, basics.AppIndex(621)) TestApp(t, p+strings.Repeat("int 621; itxn_field Applications;", 5)+s, ep) TestApp(t, p+strings.Repeat("int 621; itxn_field Applications;", 6)+s, ep, "too many foreign apps") TestApp(t, p+strings.Repeat("int 621; itxn_field Assets;", 6)+s, ep, - "invalid Asset reference") + "unavailable Asset 621") tx.ForeignAssets = append(tx.ForeignAssets, basics.AssetIndex(621)) TestApp(t, p+strings.Repeat("int 621; itxn_field Assets;", 6)+s, ep) TestApp(t, p+strings.Repeat("int 621; itxn_field Assets;", 7)+s, ep, @@ -1141,68 +1152,72 @@ func TestInnerApplCreate(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - ep, tx, ledger := MakeSampleEnv() - ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) - ledger.NewAccount(appAddr(888), 50_000) + TestLogicRange(t, 6, 0, func(t *testing.T, ep *EvalParams, tx *transactions.Transaction, ledger *Ledger) { + v := ep.Proto.LogicSigVersion + ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) + ledger.NewAccount(appAddr(888), 50_000) - ops := TestProg(t, "int 50", AssemblerMaxVersion) - approve := "byte 0x" + hex.EncodeToString(ops.Program) + ops := TestProg(t, "int 50", v) + approve := "byte 0x" + hex.EncodeToString(ops.Program) - TestApp(t, ` + test := func(source string, problems ...string) { + t.Helper() + TestApp(t, source, ep, problems...) + } + + test(` itxn_begin int appl; itxn_field TypeEnum -`+approve+`; itxn_field ApprovalProgram -`+approve+`; itxn_field ClearStateProgram +` + approve + `; itxn_field ApprovalProgram +` + approve + `; itxn_field ClearStateProgram int 1; itxn_field GlobalNumUint int 2; itxn_field LocalNumByteSlice int 3; itxn_field LocalNumUint itxn_submit int 1 -`, ep) +`) - TestApp(t, ` -int 5000; app_params_get AppGlobalNumByteSlice; assert; int 0; ==; assert -`, ep, "invalid App reference") + test("int 5000; app_params_get AppGlobalNumByteSlice; assert; int 0; ==; assert", + "unavailable App 5000") - call := ` + call := ` itxn_begin int appl; itxn_field TypeEnum int 5000; itxn_field ApplicationID itxn_submit int 1 ` - // Can't call it either - TestApp(t, call, ep, "invalid App reference") + // Can't call it either + test(call, "unavailable App 5000") - tx.ForeignApps = []basics.AppIndex{basics.AppIndex(5000)} - TestApp(t, ` + tx.ForeignApps = []basics.AppIndex{basics.AppIndex(5000)} + test(` int 5000; app_params_get AppGlobalNumByteSlice; assert; int 0; ==; assert int 5000; app_params_get AppGlobalNumUint; assert; int 1; ==; assert int 5000; app_params_get AppLocalNumByteSlice; assert; int 2; ==; assert int 5000; app_params_get AppLocalNumUint; assert; int 3; ==; assert int 1 -`, ep) +`) - // Call it (default OnComplete is NoOp) - TestApp(t, call, ep) + // Call it (default OnComplete is NoOp) + test(call) - TestApp(t, ` + test(` itxn_begin int appl; itxn_field TypeEnum int DeleteApplication; itxn_field OnCompletion txn Applications 1; itxn_field ApplicationID itxn_submit int 1 -`, ep) +`) - // App is gone - TestApp(t, ` -int 5000; app_params_get AppGlobalNumByteSlice; !; assert; !; assert; int 1 -`, ep) + // App is gone + test("int 5000; app_params_get AppGlobalNumByteSlice; !; assert; !; assert; int 1") - // Can't call it either - TestApp(t, call, ep, "no app 5000") + // Can't call it either + test(call, "no app 5000") + }) } func TestCreateOldAppFails(t *testing.T) { @@ -2709,20 +2724,29 @@ func TestCreateAndUse(t *testing.T) { itxn_begin int axfer; itxn_field TypeEnum itxn CreatedAssetID; itxn_field XferAsset - txn Accounts 0; itxn_field AssetReceiver + txn Sender; itxn_field AssetReceiver itxn_submit int 1 ` - // First testing use in axfer - ep, tx, ledger := MakeSampleEnv() - ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) - ledger.NewAccount(appAddr(888), 4*MakeTestProto().MinTxnFee) - TestApp(t, axfer, ep) + // First testing use in axfer, start at v5 so that the failure is tested + TestLogicRange(t, 5, 0, func(t *testing.T, ep *EvalParams, tx *transactions.Transaction, ledger *Ledger) { + v := ep.Proto.LogicSigVersion + test := func(source string, problems ...string) { + t.Helper() + TestApp(t, source, ep, problems...) + } + + ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) + ledger.NewAccount(appAddr(888), 4*MakeTestProto().MinTxnFee) - ep.Proto = MakeTestProtoV(CreatedResourcesVersion - 1) - TestApp(t, axfer, ep, "invalid Asset reference") + if v < CreatedResourcesVersion { + test(axfer, "unavailable Asset") + } else { + test(axfer) + } + }) balance := ` itxn_begin @@ -2755,14 +2779,23 @@ func TestCreateAndUse(t *testing.T) { int 1 ` - // Now test use in asset balance opcode - ep, tx, ledger = MakeSampleEnv() - ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) - ledger.NewAccount(appAddr(888), 4*MakeTestProto().MinTxnFee) - TestApp(t, balance, ep) + // Now test use in asset balance opcode, over the same range + TestLogicRange(t, 5, 0, func(t *testing.T, ep *EvalParams, tx *transactions.Transaction, ledger *Ledger) { + v := ep.Proto.LogicSigVersion + test := func(source string, problems ...string) { + t.Helper() + TestApp(t, source, ep, problems...) + } - ep.Proto = MakeTestProtoV(CreatedResourcesVersion - 1) - TestApp(t, balance, ep, "invalid Asset reference") + ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) + ledger.NewAccount(appAddr(888), 4*MakeTestProto().MinTxnFee) + + if v < CreatedResourcesVersion { + test(balance, "unavailable Asset "+strconv.Itoa(FirstTestID)) + } else { + test(balance) + } + }) appcall := ` itxn_begin @@ -2780,18 +2813,20 @@ func TestCreateAndUse(t *testing.T) { int 1 ` - // Now as ForeignAsset - ep, tx, ledger = MakeSampleEnv() - ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) - ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) - ledger.NewAccount(appAddr(888), 4*MakeTestProto().MinTxnFee) - // It gets passed the Assets setting - TestApp(t, appcall, ep, "attempt to self-call") - - // Appcall is isn't allowed pre-CreatedResourcesVersion, because same - // version allowed inner app calls - // ep.Proto = MakeTestProtoV(CreatedResourcesVersion - 1) - // TestApp(t, appcall, ep, "invalid Asset reference") + // Now as ForeignAsset (starts in v6, when inner app calls allowed) + TestLogicRange(t, 6, 0, func(t *testing.T, ep *EvalParams, tx *transactions.Transaction, ledger *Ledger) { + test := func(source string, problems ...string) { + TestApp(t, source, ep, problems...) + } + + ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) + ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) + ledger.NewAccount(appAddr(888), 4*MakeTestProto().MinTxnFee) + // It gets passed the Assets setting + test(appcall, "attempt to self-call") + // Appcall is isn't allowed pre-6, so there's no point in this loop + // checking v5. + }) } // main wraps up some TEAL source in a header and footer so that it is @@ -2805,42 +2840,33 @@ func main(source string) string { end: int 1`, source) } -func hexProgram(t *testing.T, source string) string { - return "0x" + hex.EncodeToString(TestProg(t, source, AssemblerMaxVersion).Program) +func hexProgram(t *testing.T, source string, v uint64) string { + return "0x" + hex.EncodeToString(TestProg(t, source, v).Program) } -// TestCreateAndUseApp checks that an app can be created in an inner txn, and then +// TestCreateAndSeeApp checks that an app can be created in an inner txn, and then // the address for it can be looked up. -func TestCreateUseApp(t *testing.T) { +func TestCreateSeeApp(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - pay5back := main(` -itxn_begin -int pay; itxn_field TypeEnum -txn Sender; itxn_field Receiver -int 5; itxn_field Amount -itxn_submit -int 1 -`) - - createAndUse := ` + TestLogicRange(t, CreatedResourcesVersion, 0, func(t *testing.T, ep *EvalParams, tx *transactions.Transaction, ledger *Ledger) { + ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) + ledger.NewAccount(appAddr(888), 1*MakeTestProto().MinTxnFee) + createAndUse := ` itxn_begin int appl; itxn_field TypeEnum - byte ` + hexProgram(t, pay5back) + `; dup; itxn_field ApprovalProgram; itxn_field ClearStateProgram; + byte ` + hexProgram(t, main(""), 5) + `; dup; itxn_field ApprovalProgram; itxn_field ClearStateProgram; itxn_submit itxn CreatedApplicationID; app_params_get AppAddress; assert addr ` + appAddr(5000).String() + ` == ` - - ep, tx, ledger := MakeSampleEnv() - ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) - ledger.NewAccount(appAddr(888), 1*MakeTestProto().MinTxnFee) - TestApp(t, createAndUse, ep) - // Again, can't test if this (properly) fails in previous version, because - // we can't even create apps this way in previous version. + TestApp(t, createAndUse, ep) + // Again, can't test if this (properly) fails in previous version, because + // we can't even create apps this way in previous version. + }) } // TestCreateAndPay checks that an app can be created in an inner app, and then @@ -2850,7 +2876,9 @@ func TestCreateAndPay(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - pay5back := main(` + TestLogicRange(t, CreatedResourcesVersion, 0, func(t *testing.T, ep *EvalParams, tx *transactions.Transaction, ledger *Ledger) { + v := ep.Proto.LogicSigVersion + pay5back := main(` itxn_begin int pay; itxn_field TypeEnum txn Sender; itxn_field Receiver @@ -2859,10 +2887,10 @@ itxn_submit int 1 `) - createAndPay := ` + createAndPay := ` itxn_begin int appl; itxn_field TypeEnum - ` + fmt.Sprintf("byte %s", hexProgram(t, pay5back)) + ` + ` + fmt.Sprintf("byte %s", hexProgram(t, pay5back, v)) + ` dup itxn_field ApprovalProgram; itxn_field ClearStateProgram; @@ -2877,15 +2905,15 @@ int 1 int 1 ` - ep, tx, ledger := MakeSampleEnv() - ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) - ledger.NewAccount(appAddr(888), 10*MakeTestProto().MinTxnFee) - TestApp(t, createAndPay, ep) + ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) + ledger.NewAccount(appAddr(888), 10*MakeTestProto().MinTxnFee) + TestApp(t, createAndPay, ep) - // This test is impossible because CreatedResourcesVersion is also when - // inner txns could make apps. - // ep.Proto = MakeTestProtoV(CreatedResourcesVersion - 1) - // TestApp(t, createAndPay, ep, "invalid Address reference") + // This test is impossible because CreatedResourcesVersion is also when + // inner txns could make apps. + // ep.Proto = MakeTestProtoV(CreatedResourcesVersion - 1) + // TestApp(t, createAndPay, ep, "invalid Address reference") + }) } // TestInnerGaid ensures there's no confusion over the tracking of ids @@ -3048,64 +3076,38 @@ done: TestAppBytes(t, app.ApprovalProgram, ep, "appl depth") } -func TestInfiniteRecursion(t *testing.T) { - partitiontest.PartitionTest(t) - t.Parallel() - - ep, tx, ledger := MakeSampleEnv() - source := ` -itxn_begin -int appl; itxn_field TypeEnum -int 0; app_params_get AppApprovalProgram -assert -itxn_field ApprovalProgram - -int 0; app_params_get AppClearStateProgram -assert -itxn_field ClearStateProgram - -itxn_submit -` - // This app looks itself up in the ledger, so we need to put it in there. - ledger.NewApp(tx.Sender, 888, basics.AppParams{ - ApprovalProgram: TestProg(t, source, AssemblerMaxVersion).Program, - ClearStateProgram: TestProg(t, "int 1", AssemblerMaxVersion).Program, - }) - // We're testing if this can recur forever. It's hard to fund all these - // apps, but we can put a huge credit in the ep. - *ep.FeeCredit = 1_000_000_000 - - // This has been tested by hand, by setting maxAppCallDepth to 10_000_000 - // but without that, the depth limiter stops it first. - // TestApp(t, source, ep, "too many inner transactions 1 with 0 left") - - TestApp(t, source, ep, "appl depth (8) exceeded") -} - +// TestForeignAppAccountAccess ensures that an app can access the account +// associated withe an app mentioned in its ForeignApps. func TestForeignAppAccountAccess(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - ep, tx, ledger := MakeSampleEnv() - ledger.NewAccount(appAddr(888), 50_000) - tx.ForeignApps = []basics.AppIndex{basics.AppIndex(111)} + TestLogicRange(t, 5, 0, func(t *testing.T, ep *EvalParams, tx *transactions.Transaction, ledger *Ledger) { + v := ep.Proto.LogicSigVersion + ledger.NewAccount(appAddr(888), 50_000) + tx.ForeignApps = []basics.AppIndex{basics.AppIndex(111)} - ledger.NewApp(tx.Sender, 111, basics.AppParams{ - ApprovalProgram: TestProg(t, "int 1", AssemblerMaxVersion).Program, - ClearStateProgram: TestProg(t, "int 1", AssemblerMaxVersion).Program, - }) + ledger.NewApp(tx.Sender, 111, basics.AppParams{ + ApprovalProgram: TestProg(t, "int 1", AssemblerMaxVersion).Program, + ClearStateProgram: TestProg(t, "int 1", AssemblerMaxVersion).Program, + }) - TestApp(t, ` + // app address available starting with 7 + var problem []string + if v < 7 { + problem = []string{"invalid Account reference " + appAddr(111).String()} + } + + TestApp(t, ` itxn_begin -int pay -itxn_field TypeEnum -int 100 -itxn_field Amount +int pay; itxn_field TypeEnum +int 100; itxn_field Amount txn Applications 1 app_params_get AppAddress assert itxn_field Receiver itxn_submit int 1 -`, ep) +`, ep, problem...) + }) } diff --git a/data/transactions/logic/evalStateful_test.go b/data/transactions/logic/evalStateful_test.go index ae573724bf..ae34c1303e 100644 --- a/data/transactions/logic/evalStateful_test.go +++ b/data/transactions/logic/evalStateful_test.go @@ -122,7 +122,7 @@ func TestEvalModes(t *testing.T) { // check modeAny (v1 + txna/gtxna) are available in RunModeSignature // check all opcodes available in runModeApplication - opcodesRunModeAny := `intcblock 0 1 1 1 1 5 100 + opcodesRunModeAny := `intcblock 0 1 1 1 1 500 100 bytecblock "ALGO" 0x1337 0x2001 0xdeadbeef 0x70077007 bytec 0 sha256 @@ -229,11 +229,11 @@ app_local_del bytec_0 app_global_del txn Sender -intc 5 // 5 +intc 5 // 500 asset_holding_get AssetBalance pop && -intc 5 // 5 +intc 5 // 500 asset_params_get AssetTotal pop && @@ -261,7 +261,7 @@ log tx.Note, } ep.TxnGroup[0].Txn.ApplicationID = 100 - ep.TxnGroup[0].Txn.ForeignAssets = []basics.AssetIndex{5} // needed since v4 + ep.TxnGroup[0].Txn.ForeignAssets = []basics.AssetIndex{500} // needed since v4 params := basics.AssetParams{ Total: 1000, Decimals: 2, @@ -324,27 +324,43 @@ log } // check stateful opcodes are not allowed in stateless mode - statefulOpcodeCalls := []string{ - "txn Sender; balance", - "txn Sender; min_balance", - "txn Sender; int 0; app_opted_in", - "txn Sender; int 0; byte 0x01; app_local_get_ex", - "byte 0x01; app_global_get", - "int 0; byte 0x01; app_global_get_ex", - "txn Sender; byte 0x01; byte 0x01; app_local_put", - "byte 0x01; int 0; app_global_put", - "txn Sender; byte 0x01; app_local_del", - "byte 0x01; app_global_del", - "txn Sender; int 0; asset_holding_get AssetFrozen", - "int 0; int 0; asset_params_get AssetManager", - "int 0; int 0; app_params_get AppApprovalProgram", - "byte 0x01; log", - } - - for _, source := range statefulOpcodeCalls { - source := source - testLogic(t, source, AssemblerMaxVersion, defaultEvalParams(), - "not allowed in current mode", "not allowed in current mode") + for v := uint64(2); v <= AssemblerMaxVersion; v++ { + sender := "txn Sender;" + if v < directRefEnabledVersion { + sender = "int 0;" + } + statefulOpcodeCalls := map[string]uint64{ + sender + "balance": 2, + sender + "min_balance": 3, + sender + "int 0; app_opted_in": 2, + sender + "int 0; byte 0x01; app_local_get_ex": 2, + "byte 0x01; app_global_get": 2, + "int 0; byte 0x01; app_global_get_ex": 2, + sender + "byte 0x01; byte 0x01; app_local_put": 2, + "byte 0x01; int 0; app_global_put": 2, + sender + "byte 0x01; app_local_del": 2, + "byte 0x01; app_global_del": 2, + sender + "int 0; asset_holding_get AssetFrozen": 2, + "int 0; int 0; asset_params_get AssetManager": 2, + "int 0; int 0; app_params_get AppApprovalProgram": 5, + "byte 0x01; log": 5, + sender + "acct_params_get AcctBalance": 7, + + "byte 0x1234; int 12; box_create": 8, + "byte 0x1234; int 12; int 4; box_extract": 8, + "byte 0x1234; int 12; byte 0x24; box_replace": 8, + "byte 0x1234; box_del": 8, + "byte 0x1234; box_len": 8, + "byte 0x1234; box_get": 8, + "byte 0x1234; byte 0x12; box_put": 8, + } + for source, introduced := range statefulOpcodeCalls { + if v < introduced { + continue + } + testLogic(t, source, v, defaultEvalParamsWithVersion(v), + "not allowed in current mode", "not allowed in current mode") + } } require.Equal(t, RunMode(1), ModeSig) @@ -358,15 +374,14 @@ func TestBalance(t *testing.T) { t.Parallel() testLogicRange(t, 2, 0, func(t *testing.T, ep *EvalParams, tx *transactions.Transaction, ledger *Ledger) { + v := ep.Proto.LogicSigVersion ledger.NewAccount(tx.Receiver, 177) - if ep.Proto.LogicSigVersion < sharedResourcesVersion { - testApp(t, "int 2; balance; int 177; ==", ep, "invalid Account reference") - testApp(t, `int 1; balance; int 177; ==`, ep) - } + testApp(t, "int 2; balance; int 177; ==", ep, "invalid Account reference") + testApp(t, `int 1; balance; int 177; ==`, ep) source := `txn Accounts 1; balance; int 177; ==;` // won't assemble in old version teal - if ep.Proto.LogicSigVersion < directRefEnabledVersion { + if v < directRefEnabledVersion { testProg(t, source, ep.Proto.LogicSigVersion, Expect{1, "balance arg 0 wanted type uint64..."}) return @@ -552,7 +567,6 @@ func TestMinBalance(t *testing.T) { testLogicRange(t, 4, 0, func(t *testing.T, ep *EvalParams, tx *transactions.Transaction, ledger *Ledger) { ledger.NewAccount(tx.Sender, 234) ledger.NewAccount(tx.Receiver, 123) - testApp(t, "txn Sender; min_balance; int 1001; ==", ep) // Sender makes an asset, min balance goes up ledger.NewAsset(tx.Sender, 7, basics.AssetParams{Total: 1000}) @@ -574,7 +588,7 @@ func TestMinBalance(t *testing.T) { testApp(t, "txn Accounts 1; min_balance; int 1001; ==", ep) // Receiver opts in ledger.NewHolding(tx.Receiver, 7, 1, true) - testApp(t, "txn Receiver; min_balance; int 2002; ==", ep) // 1 == Accounts[0] + testApp(t, "txn Receiver; min_balance; int 2002; ==", ep) }) } @@ -692,7 +706,7 @@ byte "ALGO" now.TxnGroup[0].Txn.ApplicationID = 123 testApp(t, strings.Replace(text, "int 100 // app id", "int 123", -1), now, "is not opted into") testApp(t, strings.Replace(text, "int 100 // app id", "int 2", -1), pre, "is not opted into") - testApp(t, strings.Replace(text, "int 100 // app id", "int 9", -1), now, "invalid App reference 9") + testApp(t, strings.Replace(text, "int 100 // app id", "int 9", -1), now, "unavailable App 9") testApp(t, strings.Replace(text, "int 1 // account idx", "byte \"aoeuiaoeuiaoeuiaoeuiaoeuiaoeui00\"", -1), now, "no account") @@ -818,11 +832,11 @@ byte "ALGO" // check error on invalid app index for app_global_get_ex text = "int 2; txn ApplicationArgs 0; app_global_get_ex" - testApp(t, text, now, "invalid App reference 2") + testApp(t, text, now, "unavailable App 2") // check that actual app id ok instead of indirect reference text = `int 100; txn ApplicationArgs 0; app_global_get_ex; int 1; ==; assert; byte "ALGO"; ==` testApp(t, text, now) - testApp(t, text, pre, "invalid App reference 100") // but not in old teal + testApp(t, text, pre, "App index 100 beyond") // but not in old teal // check app_global_get default value text = "byte 0x414c474f55; app_global_get; int 0; ==" @@ -1015,7 +1029,7 @@ func testAssetsByVersion(t *testing.T, assetsTestProgram string, version uint64) // was legal to get balance on a non-ForeignAsset testApp(t, "int 0; int 54; asset_holding_get AssetBalance; ==", pre) // but not since directRefEnabledVersion - testApp(t, "int 0; int 54; asset_holding_get AssetBalance", now, "invalid Asset reference 54") + testApp(t, "int 0; int 54; asset_holding_get AssetBalance", now, "unavailable Asset 54") // it wasn't legal to use a direct ref for account testProg(t, `byte "aoeuiaoeuiaoeuiaoeuiaoeuiaoeui00"; int 54; asset_holding_get AssetBalance`, @@ -1028,8 +1042,8 @@ func testAssetsByVersion(t *testing.T, assetsTestProgram string, version uint64) testApp(t, `byte "aoeuiaoeuiaoeuiaoeuiaoeuiaoeui02"; int 55; asset_holding_get AssetBalance; ==`, now, "invalid") // for params get, presence in ForeignAssets has always be required - testApp(t, "int 5; asset_params_get AssetTotal", pre, "invalid Asset reference 5") - testApp(t, "int 5; asset_params_get AssetTotal", now, "invalid Asset reference 5") + testApp(t, "int 5; asset_params_get AssetTotal", pre, "Asset index 5 beyond") + testApp(t, "int 5; asset_params_get AssetTotal", now, "unavailable Asset 5") params := basics.AssetParams{ Total: 1000, @@ -1066,7 +1080,7 @@ func testAssetsByVersion(t *testing.T, assetsTestProgram string, version uint64) if version < 5 { // Can't run these with AppCreator anyway - testApp(t, strings.Replace(assetsTestProgram, "int 0//params", "int 55", -1), pre, "invalid Asset ref") + testApp(t, strings.Replace(assetsTestProgram, "int 0//params", "int 55", -1), pre, "Asset index 55 beyond") testApp(t, strings.Replace(assetsTestProgram, "int 55", "int 0", -1), pre, "err opcode") } @@ -1183,20 +1197,71 @@ func TestAssetDisambiguation(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - // start at 4 when the two meanings were added, stop at 8 because 9 removed the dual meaning - testLogicRange(t, 4, 8, func(t *testing.T, ep *EvalParams, tx *transactions.Transaction, ledger *Ledger) { + // It would be nice to start at 2, when apps were added, but `assert` is + // very convenient for testing, and nothing important changed from 2 to + // 3. (Between directRefEnabledVersion=4, so that change is a big deal.) + testLogicRange(t, 3, 0, func(t *testing.T, ep *EvalParams, tx *transactions.Transaction, ledger *Ledger) { ledger.NewAsset(tx.Sender, 1, basics.AssetParams{AssetName: "one", Total: 1}) - ledger.NewAsset(tx.Sender, 20, basics.AssetParams{AssetName: "twenty", Total: 20}) - ledger.NewAsset(tx.Sender, 30, basics.AssetParams{AssetName: "thirty", Total: 30}) - tx.ForeignAssets = []basics.AssetIndex{20, 30} - // Since 1 is not available, 1 must mean the 1th asset slot = 30 + ledger.NewAsset(tx.Sender, 255, basics.AssetParams{AssetName: "twenty", Total: 255}) + ledger.NewAsset(tx.Sender, 256, basics.AssetParams{AssetName: "thirty", Total: 256}) + tx.ForeignAssets = []basics.AssetIndex{255, 256} + // Since 1 is not available, 1 must mean the 1th asset slot = 256 testApp(t, `int 1; asset_params_get AssetName; assert; byte "thirty"; ==`, ep) - testApp(t, `int 0; int 1; asset_holding_get AssetBalance; assert; int 30; ==`, ep) - tx.ForeignAssets = []basics.AssetIndex{1, 30} - // Since 1 IS available, 1 means the assetid=1, not the 1th slot - testApp(t, `int 1; asset_params_get AssetName; assert; byte "one"; ==`, ep) + if ep.Proto.LogicSigVersion < directRefEnabledVersion { + // in v3, the asset argument is always treated as an ID, so this is asset 1 + testApp(t, `int 0; int 1; asset_holding_get AssetBalance; assert; int 1; ==`, ep) + } else { + testApp(t, `int 0; int 1; asset_holding_get AssetBalance; assert; int 256; ==`, ep) + } + + tx.ForeignAssets = []basics.AssetIndex{1, 256} + if ep.Proto.LogicSigVersion < directRefEnabledVersion { + // There's no direct use of assets IDs, so 1 is still the 1th slot (256) + testApp(t, `int 1; asset_params_get AssetName; assert; byte "thirty"; ==`, ep) + } else { + // Since 1 IS available, 1 means the assetid=1, not the 1th slot + testApp(t, `int 1; asset_params_get AssetName; assert; byte "one"; ==`, ep) + } testApp(t, `int 0; int 1; asset_holding_get AssetBalance; assert; int 1; ==`, ep) + + ep.Proto.AppForbidLowResources = true + tx.ForeignAssets = []basics.AssetIndex{255, 256} + // Since 1 is not available, 1 must mean the 1th asset slot = 256 + testApp(t, `int 1; asset_params_get AssetName; assert; byte "thirty"; ==`, ep) + if ep.Proto.LogicSigVersion < directRefEnabledVersion { + // in v3, the asset argument is always treated as an ID, so this is asset 1 + testApp(t, `int 0; int 1; asset_holding_get AssetBalance; assert; int 256; ==`, ep, + "low Asset lookup 1") + } else { + testApp(t, `int 0; int 1; asset_holding_get AssetBalance; assert; int 256; ==`, ep) + } + + // but now if that resolution led to a number below 255, boom + tx.ForeignAssets = []basics.AssetIndex{256, 255} + testApp(t, `int 1; asset_params_get AssetName; assert; byte "thirty"; ==`, ep, + "low Asset lookup 255") + if ep.Proto.LogicSigVersion < directRefEnabledVersion { + // in v3, the asset argument is always treated as an ID, so this is asset 1 + testApp(t, `int 0; int 1; asset_holding_get AssetBalance; assert; int 30; ==`, ep, + "low Asset lookup 1") + } else { + testApp(t, `int 0; int 1; asset_holding_get AssetBalance; assert; int 30; ==`, ep, + "low Asset lookup 255") + } + + tx.ForeignAssets = []basics.AssetIndex{1, 256} + if ep.Proto.LogicSigVersion < directRefEnabledVersion { + // in v3, the asset argument is always a slot, so this is asset 256 + testApp(t, `int 1; asset_params_get AssetName; assert; byte "thirty"; ==`, ep) + } else { + // Since 1 IS available, 1 means the assetid=1, not the 1th slot + testApp(t, `int 1; asset_params_get AssetName; assert; byte "one"; ==`, ep, + "low Asset lookup 1") + } + // pre v4 and the availability rule come to the same conclusion: treat the 1 as an ID + testApp(t, `int 0; int 1; asset_holding_get AssetBalance; assert; int 1; ==`, ep, + "low Asset lookup 1") }) } @@ -1208,48 +1273,82 @@ func TestAppDisambiguation(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - // start at 4 when the two meanings were added, stop at 8 because 9 removed the dual meaning - testLogicRange(t, 4, 8, func(t *testing.T, ep *EvalParams, tx *transactions.Transaction, ledger *Ledger) { - ledger.NewApp(tx.Sender, 1, basics.AppParams{ - GlobalState: map[string]basics.TealValue{"a": { - Type: basics.TealUintType, - Uint: 1, - }}, - ExtraProgramPages: 1, - }) - ledger.NewLocals(tx.Sender, 1) - ledger.NewLocal(tx.Sender, 1, "x", basics.TealValue{Type: basics.TealUintType, Uint: 100}) - ledger.NewApp(tx.Sender, 20, basics.AppParams{ - GlobalState: map[string]basics.TealValue{"a": { - Type: basics.TealUintType, - Uint: 20, - }}, - ExtraProgramPages: 20, - }) - ledger.NewLocals(tx.Sender, 20) - ledger.NewLocal(tx.Sender, 20, "x", basics.TealValue{Type: basics.TealUintType, Uint: 200}) - ledger.NewApp(tx.Sender, 30, basics.AppParams{ - GlobalState: map[string]basics.TealValue{"a": { - Type: basics.TealUintType, - Uint: 30, - }}, - ExtraProgramPages: 30, - }) - tx.ForeignApps = []basics.AppIndex{20, 30} + // It would be nice to start at 2, when apps were added, but `assert` is + // very convenient for testing, and nothing important changed from 2 to + // 3. (But directRefEnabledVersion=4, so that change is a big deal.) + testLogicRange(t, 3, 0, func(t *testing.T, ep *EvalParams, tx *transactions.Transaction, ledger *Ledger) { + // make apps with identifiable properties, so we can tell what we get + makeIdentifiableApp := func(appID uint64) { + ledger.NewApp(tx.Sender, basics.AppIndex(appID), basics.AppParams{ + GlobalState: map[string]basics.TealValue{"a": { + Type: basics.TealUintType, + Uint: appID, + }}, + ExtraProgramPages: uint32(appID), + }) + ledger.NewLocals(tx.Sender, appID) + ledger.NewLocal(tx.Sender, appID, "x", basics.TealValue{Type: basics.TealUintType, Uint: appID * 10}) + } + makeIdentifiableApp(1) + makeIdentifiableApp(20) + makeIdentifiableApp(256) + + tx.ForeignApps = []basics.AppIndex{20, 256} // Since 1 is not available, 1 must mean the first app slot = 20 (recall, 0 mean "this app") - if ep.Proto.LogicSigVersion >= 5 { + if ep.Proto.LogicSigVersion >= 5 { // to get AppExtraProgramPages testApp(t, `int 1; app_params_get AppExtraProgramPages; assert; int 20; ==`, ep) } testApp(t, `int 1; byte "a"; app_global_get_ex; assert; int 20; ==`, ep) - testApp(t, `int 0; int 1; byte "x"; app_local_get_ex; assert; int 200; ==`, ep) + if ep.Proto.LogicSigVersion < directRefEnabledVersion { + // in v3, the app argument is always treated as an ID. + testApp(t, `int 0; int 1; byte "x"; app_local_get_ex; assert; int 10; ==`, ep) + } else { + testApp(t, `int 0; int 1; byte "x"; app_local_get_ex; assert; int 200; ==`, ep) + } - tx.ForeignApps = []basics.AppIndex{1, 30} - // Since 1 IS available, 1 means the assetid=1, not the 1th slot - if ep.Proto.LogicSigVersion >= 5 { + // Make 1 available, so now 1 means the appid=1, not the 1th slot + tx.ForeignApps = []basics.AppIndex{1, 256} + if ep.Proto.LogicSigVersion >= 5 { // to get AppExtraProgramPages testApp(t, `int 1; app_params_get AppExtraProgramPages; assert; int 1; ==`, ep) } testApp(t, `int 1; byte "a"; app_global_get_ex; assert; int 1; ==`, ep) - testApp(t, `int 0; int 1; byte "x"; app_local_get_ex; assert; int 100; ==`, ep) + testApp(t, `int 0; int 1; byte "x"; app_local_get_ex; assert; int 10; ==`, ep) + + // same tests, but as of AppForbidLowResources, using 1 is forbidden + ep.Proto.AppForbidLowResources = true + + // repeat the first tests, they are using 20 and 256 directly, which are too low + tx.ForeignApps = []basics.AppIndex{20, 256} + if ep.Proto.LogicSigVersion >= 5 { // to get AppExtraProgramPages + testApp(t, `int 1; app_params_get AppExtraProgramPages; assert; int 20; ==`, ep, + "low App lookup 20") + testApp(t, `int 2; app_params_get AppExtraProgramPages; assert; int 256; ==`, ep) + } + testApp(t, `int 1; byte "a"; app_global_get_ex; assert; int 20; ==`, ep, + "low App lookup 20") + testApp(t, `int 2; byte "a"; app_global_get_ex; assert; int 256; ==`, ep) + if ep.Proto.LogicSigVersion < directRefEnabledVersion { + // in v3, the app argument is always treated as an ID. + testApp(t, `int 0; int 1; byte "x"; app_local_get_ex; assert; int 200; ==`, ep, + "low App lookup 1") + testApp(t, `int 0; int 2; byte "x"; app_local_get_ex; assert; int 2560; ==`, ep, + "low App lookup 2") + } else { + testApp(t, `int 0; int 1; byte "x"; app_local_get_ex; assert; int 200; ==`, ep, + "low App lookup 20") + testApp(t, `int 0; int 2; byte "x"; app_local_get_ex; assert; int 2560; ==`, ep) + } + + // repeat the second tests, which are using 1, which is too low + tx.ForeignApps = []basics.AppIndex{1, 256} + if ep.Proto.LogicSigVersion >= 5 { // to get AppExtraProgramPages + testApp(t, `int 1; app_params_get AppExtraProgramPages; assert; int 1; ==`, ep, + "low App lookup 1") + } + testApp(t, `int 1; byte "a"; app_global_get_ex; assert; int 1; ==`, ep, + "low App lookup 1") + testApp(t, `int 0; int 1; byte "x"; app_local_get_ex; assert; int 10; ==`, ep, + "low App lookup 1") }) } @@ -1273,77 +1372,63 @@ func TestAppParams(t *testing.T) { func TestAcctParams(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - ep, tx, ledger := makeSampleEnv() - source := "txn Sender; acct_params_get AcctBalance; !; assert; int 0; ==" - testApp(t, source, ep) + // start at 6 for acct_params_get + testLogicRange(t, 6, 0, func(t *testing.T, ep *EvalParams, tx *transactions.Transaction, ledger *Ledger) { + test := func(source string) { + t.Helper() + testApp(t, source, ep) + } - source = "txn Sender; acct_params_get AcctMinBalance; !; assert; int 1001; ==" - testApp(t, source, ep) + test("txn Sender; acct_params_get AcctBalance; !; assert; int 0; ==") + test("txn Sender; acct_params_get AcctMinBalance; !; assert; int 1001; ==") - ledger.NewAccount(tx.Sender, 42) + ledger.NewAccount(tx.Sender, 42) - source = "txn Sender; acct_params_get AcctBalance; assert; int 42; ==" - testApp(t, source, ep) + test("txn Sender; acct_params_get AcctBalance; assert; int 42; ==") + test("txn Sender; acct_params_get AcctMinBalance; assert; int 1001; ==") + test("txn Sender; acct_params_get AcctAuthAddr; assert; global ZeroAddress; ==") - source = "txn Sender; acct_params_get AcctMinBalance; assert; int 1001; ==" - testApp(t, source, ep) - - source = "txn Sender; acct_params_get AcctAuthAddr; assert; global ZeroAddress; ==" - testApp(t, source, ep) - - // No apps or schema at first, then 1 created and the global schema noted - source = "txn Sender; acct_params_get AcctTotalAppsCreated; assert; !" - testApp(t, source, ep) - source = "txn Sender; acct_params_get AcctTotalNumUint; assert; !" - testApp(t, source, ep) - source = "txn Sender; acct_params_get AcctTotalNumByteSlice; assert; !" - testApp(t, source, ep) - source = "txn Sender; acct_params_get AcctTotalExtraAppPages; assert; !" - testApp(t, source, ep) - ledger.NewApp(tx.Sender, 2000, basics.AppParams{ - StateSchemas: basics.StateSchemas{ - LocalStateSchema: basics.StateSchema{ - NumUint: 6, - NumByteSlice: 7, - }, - GlobalStateSchema: basics.StateSchema{ - NumUint: 8, - NumByteSlice: 9, + if ep.Proto.LogicSigVersion < 8 { + return // the rest uses fields that came at 8 + } + // No apps or schema at first, then 1 created and the global schema noted + test("txn Sender; acct_params_get AcctTotalAppsCreated; assert; !") + test("txn Sender; acct_params_get AcctTotalNumUint; assert; !") + test("txn Sender; acct_params_get AcctTotalNumByteSlice; assert; !") + test("txn Sender; acct_params_get AcctTotalExtraAppPages; assert; !") + ledger.NewApp(tx.Sender, 2000, basics.AppParams{ + StateSchemas: basics.StateSchemas{ + LocalStateSchema: basics.StateSchema{ + NumUint: 6, + NumByteSlice: 7, + }, + GlobalStateSchema: basics.StateSchema{ + NumUint: 8, + NumByteSlice: 9, + }, }, - }, - ExtraProgramPages: 2, + ExtraProgramPages: 2, + }) + test("txn Sender; acct_params_get AcctTotalAppsCreated; assert; int 1; ==") + test("txn Sender; acct_params_get AcctTotalNumUint; assert; int 8; ==") + test("txn Sender; acct_params_get AcctTotalNumByteSlice; assert; int 9; ==") + test("txn Sender; acct_params_get AcctTotalExtraAppPages; assert; int 2; ==") + + // Not opted in at first, then opted into 1, schema added + test("txn Sender; acct_params_get AcctTotalAppsOptedIn; assert; !") + ledger.NewLocals(tx.Sender, 2000) + test("txn Sender; acct_params_get AcctTotalAppsOptedIn; assert; int 1; ==") + test("txn Sender; acct_params_get AcctTotalNumUint; assert; int 8; int 6; +; ==") + test("txn Sender; acct_params_get AcctTotalNumByteSlice; assert; int 9; int 7; +; ==") + + // No ASAs at first, then 1 created AND in total + test("txn Sender; acct_params_get AcctTotalAssetsCreated; assert; !") + test("txn Sender; acct_params_get AcctTotalAssets; assert; !") + ledger.NewAsset(tx.Sender, 3000, basics.AssetParams{}) + test("txn Sender; acct_params_get AcctTotalAssetsCreated; assert; int 1; ==") + test("txn Sender; acct_params_get AcctTotalAssets; assert; int 1; ==") }) - source = "txn Sender; acct_params_get AcctTotalAppsCreated; assert; int 1; ==" - testApp(t, source, ep) - source = "txn Sender; acct_params_get AcctTotalNumUint; assert; int 8; ==" - testApp(t, source, ep) - source = "txn Sender; acct_params_get AcctTotalNumByteSlice; assert; int 9; ==" - testApp(t, source, ep) - source = "txn Sender; acct_params_get AcctTotalExtraAppPages; assert; int 2; ==" - testApp(t, source, ep) - - // Not opted in at first, then opted into 1, schema added - source = "txn Sender; acct_params_get AcctTotalAppsOptedIn; assert; !" - testApp(t, source, ep) - ledger.NewLocals(tx.Sender, 2000) - source = "txn Sender; acct_params_get AcctTotalAppsOptedIn; assert; int 1; ==" - testApp(t, source, ep) - source = "txn Sender; acct_params_get AcctTotalNumUint; assert; int 8; int 6; +; ==" - testApp(t, source, ep) - source = "txn Sender; acct_params_get AcctTotalNumByteSlice; assert; int 9; int 7; +; ==" - testApp(t, source, ep) - - // No ASAs at first, then 1 created AND in total - source = "txn Sender; acct_params_get AcctTotalAssetsCreated; assert; !" - testApp(t, source, ep) - source = "txn Sender; acct_params_get AcctTotalAssets; assert; !" - testApp(t, source, ep) - ledger.NewAsset(tx.Sender, 3000, basics.AssetParams{}) - source = "txn Sender; acct_params_get AcctTotalAssetsCreated; assert; int 1; ==" - testApp(t, source, ep) - source = "txn Sender; acct_params_get AcctTotalAssets; assert; int 1; ==" - testApp(t, source, ep) } // TestGlobalNonDelete ensures that a deletion is not inserted in the delta if the global didn't exist @@ -1351,16 +1436,13 @@ func TestGlobalNonDelete(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - ep, txn, ledger := makeSampleEnv() - source := ` -byte "none" -app_global_del -int 1 -` - ledger.NewApp(txn.Sender, 888, makeApp(0, 0, 1, 0)) - delta := testApp(t, source, ep) - require.Empty(t, delta.GlobalDelta) - require.Empty(t, delta.LocalDeltas) + testLogicRange(t, 2, 0, func(t *testing.T, ep *EvalParams, txn *transactions.Transaction, ledger *Ledger) { + source := `byte "none"; app_global_del; int 1` + ledger.NewApp(txn.Sender, 888, makeApp(0, 0, 1, 0)) + delta := testApp(t, source, ep) + require.Empty(t, delta.GlobalDelta) + require.Empty(t, delta.LocalDeltas) + }) } // TestLocalNonDelete ensures that a deletion is not inserted in the delta if the local didn't exist @@ -1368,19 +1450,15 @@ func TestLocalNonDelete(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - ep, txn, ledger := makeSampleEnv() - source := ` -txn Sender -byte "none" -app_local_del -int 1 -` - ledger.NewAccount(txn.Sender, 100000) - ledger.NewApp(txn.Sender, 888, makeApp(0, 0, 1, 0)) - ledger.NewLocals(txn.Sender, 888) - delta := testApp(t, source, ep) - require.Empty(t, delta.GlobalDelta) - require.Empty(t, delta.LocalDeltas) + testLogicRange(t, 2, 0, func(t *testing.T, ep *EvalParams, txn *transactions.Transaction, ledger *Ledger) { + source := `int 0; byte "none"; app_local_del; int 1` + ledger.NewAccount(txn.Sender, 100000) + ledger.NewApp(txn.Sender, 888, makeApp(0, 0, 1, 0)) + ledger.NewLocals(txn.Sender, 888) + delta := testApp(t, source, ep) + require.Empty(t, delta.GlobalDelta) + require.Empty(t, delta.LocalDeltas) + }) } func TestAppLocalReadWriteDeleteErrors(t *testing.T) { @@ -1427,20 +1505,15 @@ bytec_0 // key "ALGO" app_local_del intc_1 ` - type cmdtest struct { - source string - } - - tests := map[string]cmdtest{ - "read": {sourceRead}, - "write": {sourceWrite}, - "delete": {sourceDelete}, + tests := map[string]string{ + "read": sourceRead, + "write": sourceWrite, + "delete": sourceDelete, } - for name, cmdtest := range tests { - name, cmdtest := name, cmdtest + for name, source := range tests { + name, source := name, source t.Run(fmt.Sprintf("test=%s", name), func(t *testing.T) { t.Parallel() - source := cmdtest.source ops := testProg(t, source, AssemblerMaxVersion) @@ -1740,38 +1813,46 @@ func TestAppLocalGlobalErrorCases(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - ep, tx, ledger := makeSampleEnv() - ledger.NewApp(tx.Sender, 888, basics.AppParams{}) + testLogicRange(t, 2, 0, func(t *testing.T, ep *EvalParams, tx *transactions.Transaction, ledger *Ledger) { + ledger.NewApp(tx.Sender, 888, basics.AppParams{}) - testApp(t, fmt.Sprintf(`byte "%v"; int 1; app_global_put; int 1`, strings.Repeat("v", ep.Proto.MaxAppKeyLen+1)), ep, "key too long") + g, l := "app_global_put;", "app_local_put;" + sender := "txn Sender;" + if ep.Proto.LogicSigVersion < directRefEnabledVersion { + sender = "int 0;" + } + testApp(t, fmt.Sprintf(`byte "%v"; int 1;`+g+`int 1`, strings.Repeat("v", ep.Proto.MaxAppKeyLen+1)), ep, "key too long") - testApp(t, fmt.Sprintf(`byte "%v"; int 1; app_global_put; int 1`, strings.Repeat("v", ep.Proto.MaxAppKeyLen)), ep) + testApp(t, fmt.Sprintf(`byte "%v"; int 1;`+g+`int 1`, strings.Repeat("v", ep.Proto.MaxAppKeyLen)), ep) - ledger.NewLocals(tx.Sender, 888) - testApp(t, fmt.Sprintf(`txn Sender; byte "%v"; int 1; app_local_put; int 1`, strings.Repeat("v", ep.Proto.MaxAppKeyLen+1)), ep, "key too long") + ledger.NewLocals(tx.Sender, 888) + testApp(t, fmt.Sprintf(sender+`byte "%v"; int 1;`+l+`int 1`, strings.Repeat("v", ep.Proto.MaxAppKeyLen+1)), ep, "key too long") - testApp(t, fmt.Sprintf(`txn Sender; byte "%v"; int 1; app_local_put; int 1`, strings.Repeat("v", ep.Proto.MaxAppKeyLen)), ep) + testApp(t, fmt.Sprintf(sender+`byte "%v"; int 1;`+l+`int 1`, strings.Repeat("v", ep.Proto.MaxAppKeyLen)), ep) - testApp(t, fmt.Sprintf(`byte "foo"; byte "%v"; app_global_put; int 1`, strings.Repeat("v", ep.Proto.MaxAppBytesValueLen+1)), ep, "value too long for key") + testApp(t, fmt.Sprintf(`byte "foo"; byte "%v";`+g+`int 1`, strings.Repeat("v", ep.Proto.MaxAppBytesValueLen+1)), ep, "value too long for key") - testApp(t, fmt.Sprintf(`byte "foo"; byte "%v"; app_global_put; int 1`, strings.Repeat("v", ep.Proto.MaxAppBytesValueLen)), ep) + testApp(t, fmt.Sprintf(`byte "foo"; byte "%v";`+g+`int 1`, strings.Repeat("v", ep.Proto.MaxAppBytesValueLen)), ep) - testApp(t, fmt.Sprintf(`txn Sender; byte "foo"; byte "%v"; app_local_put; int 1`, strings.Repeat("v", ep.Proto.MaxAppBytesValueLen+1)), ep, "value too long for key") + testApp(t, fmt.Sprintf(sender+`byte "foo"; byte "%v";`+l+`int 1`, strings.Repeat("v", ep.Proto.MaxAppBytesValueLen+1)), ep, "value too long for key") - testApp(t, fmt.Sprintf(`txn Sender; byte "foo"; byte "%v"; app_local_put; int 1`, strings.Repeat("v", ep.Proto.MaxAppBytesValueLen)), ep) + testApp(t, fmt.Sprintf(sender+`byte "foo"; byte "%v";`+l+`int 1`, strings.Repeat("v", ep.Proto.MaxAppBytesValueLen)), ep) - ep.Proto.MaxAppSumKeyValueLens = 2 // Override to generate error. - testApp(t, `byte "foo"; byte "foo"; app_global_put; int 1`, ep, "key/value total too long for key") + ep.Proto.MaxAppSumKeyValueLens = 2 // Override to generate error. + testApp(t, `byte "foo"; byte "foo";`+g+`int 1`, ep, "key/value total too long for key") - testApp(t, `txn Sender; byte "foo"; byte "foo"; app_local_put; int 1`, ep, "key/value total too long for key") + testApp(t, sender+`byte "foo"; byte "foo";`+l+`int 1`, ep, "key/value total too long for key") + }) } func TestAppGlobalReadWriteDeleteErrors(t *testing.T) { partitiontest.PartitionTest(t) - t.Parallel() - sourceRead := `int 0 + testLogicRange(t, 2, 0, func(t *testing.T, ep *EvalParams, tx *transactions.Transaction, ledger *Ledger) { + v := ep.Proto.LogicSigVersion + + sourceRead := `int 0 byte "ALGO" // key app_global_get_ex bnz ok @@ -1780,39 +1861,16 @@ ok: int 0x77 == ` - sourceReadSimple := `byte "ALGO" // key -app_global_get -int 0x77 -== -` - - sourceWrite := `byte "ALGO" // key -int 100 -app_global_put -int 1 -` - sourceDelete := `byte "ALGO" // key -app_global_del -int 1 -` - tests := map[string]string{ - "read": sourceRead, - "reads": sourceReadSimple, - "write": sourceWrite, - "delete": sourceDelete, - } - for name, source := range tests { - name, source := name, source - t.Run(fmt.Sprintf("test=%s", name), func(t *testing.T) { - t.Parallel() - ops, err := AssembleStringWithVersion(source, AssemblerMaxVersion) - require.NoError(t, err) - - ep, txn, ledger := makeSampleEnv() - txn.ApplicationID = basics.AppIndex(100) - testAppBytes(t, ops.Program, ep, "no app 100") - - ledger.NewApp(txn.Sender, 100, makeApp(0, 0, 1, 0)) + tests := map[string]string{ + "read": sourceRead, + "reads": `byte "ALGO"; app_global_get; int 0x77; ==`, + "write": `byte "ALGO"; int 100; app_global_put; int 1`, + "delete": `byte "ALGO"; app_global_del; int 1`, + } + tx.ApplicationID = 100 + ledger.NewApp(tx.Sender, 100, makeApp(0, 0, 1, 0)) + for name, source := range tests { + ops := testProg(t, source, v) // a special test for read if name == "read" { @@ -1824,18 +1882,19 @@ int 1 delta := testAppBytes(t, ops.Program, ep) require.Empty(t, delta.LocalDeltas) - }) - } + } + }) } func TestAppGlobalReadWrite(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - testLogicRange(t, 2, 0, func(t *testing.T, ep *EvalParams, txn *transactions.Transaction, ledger *Ledger) { + for _, bySlot := range []bool{true, false} { + testLogicRange(t, 2, 0, func(t *testing.T, ep *EvalParams, txn *transactions.Transaction, ledger *Ledger) { - // check writing ints and bytes - source := `byte "ALGO" // key + // check writing ints and bytes + source := `byte "ALGO" // key int 0x77 // value app_global_put byte "ALGOA" // key "ALGOA" @@ -1894,34 +1953,37 @@ int 0x77 && ` - txn.Type = protocol.ApplicationCallTx - txn.ApplicationID = 100 - txn.ForeignApps = []basics.AppIndex{txn.ApplicationID} - ledger.NewAccount(txn.Sender, 1) - ledger.NewApp(txn.Sender, 100, basics.AppParams{}) + txn.Type = protocol.ApplicationCallTx + txn.ApplicationID = 100 + txn.ForeignApps = []basics.AppIndex{txn.ApplicationID} + ledger.NewAccount(txn.Sender, 1) + ledger.NewApp(txn.Sender, 100, basics.AppParams{}) - if ep.Proto.LogicSigVersion < sharedResourcesVersion { - // 100 is in the ForeignApps array, name it by slot - source = strings.ReplaceAll(source, "THISAPP", "int 1") - } else { - // use the actual app number, slots no longer allowed - source = strings.ReplaceAll(source, "THISAPP", "int 100") - } - delta := testApp(t, source, ep) + if bySlot { + // 100 is in the ForeignApps array, name it by slot + source = strings.ReplaceAll(source, "THISAPP", "int 1") + } else { + // use the actual app number + if ep.Proto.LogicSigVersion < directRefEnabledVersion { + return + } + source = strings.ReplaceAll(source, "THISAPP", "int 100") + } + delta := testApp(t, source, ep) - require.Len(t, delta.GlobalDelta, 2) - require.Empty(t, delta.LocalDeltas) + require.Len(t, delta.GlobalDelta, 2) + require.Empty(t, delta.LocalDeltas) - vd := delta.GlobalDelta["ALGO"] - require.Equal(t, basics.SetUintAction, vd.Action) - require.Equal(t, uint64(0x77), vd.Uint) + vd := delta.GlobalDelta["ALGO"] + require.Equal(t, basics.SetUintAction, vd.Action) + require.Equal(t, uint64(0x77), vd.Uint) - vd = delta.GlobalDelta["ALGOA"] - require.Equal(t, basics.SetBytesAction, vd.Action) - require.Equal(t, "ALGO", vd.Bytes) + vd = delta.GlobalDelta["ALGOA"] + require.Equal(t, basics.SetBytesAction, vd.Action) + require.Equal(t, "ALGO", vd.Bytes) - // write existing value before read - source = `byte "ALGO" // key + // write existing value before read + source = `byte "ALGO" // key int 0x77 // value app_global_put byte "ALGO" @@ -1929,19 +1991,19 @@ app_global_get int 0x77 == ` - ledger.Reset() - ledger.NoGlobal(100, "ALGOA") - ledger.NoGlobal(100, "ALGO") + ledger.Reset() + ledger.NoGlobal(100, "ALGOA") + ledger.NoGlobal(100, "ALGO") - algoValue := basics.TealValue{Type: basics.TealUintType, Uint: 0x77} - ledger.NewGlobal(100, "ALGO", algoValue) + algoValue := basics.TealValue{Type: basics.TealUintType, Uint: 0x77} + ledger.NewGlobal(100, "ALGO", algoValue) - delta = testApp(t, source, ep) - require.Empty(t, delta.GlobalDelta) - require.Empty(t, delta.LocalDeltas) + delta = testApp(t, source, ep) + require.Empty(t, delta.GlobalDelta) + require.Empty(t, delta.LocalDeltas) - // write existing value after read - source = `int 0 + // write existing value after read + source = `int 0 byte "ALGO" app_global_get_ex bnz ok @@ -1956,16 +2018,16 @@ app_global_get int 0x77 == ` - ledger.Reset() - ledger.NoGlobal(100, "ALGOA") - ledger.NewGlobal(100, "ALGO", algoValue) + ledger.Reset() + ledger.NoGlobal(100, "ALGOA") + ledger.NewGlobal(100, "ALGO", algoValue) - delta = testApp(t, source, ep) - require.Empty(t, delta.GlobalDelta) - require.Empty(t, delta.LocalDeltas) + delta = testApp(t, source, ep) + require.Empty(t, delta.GlobalDelta) + require.Empty(t, delta.LocalDeltas) - // write new values after and before read - source = `int 0 + // write new values after and before read + source = `int 0 byte "ALGO" app_global_get_ex bnz ok @@ -1996,23 +2058,24 @@ byte "ALGO" == && ` - ledger.Reset() - ledger.NoGlobal(100, "ALGOA") - ledger.NewGlobal(100, "ALGO", algoValue) + ledger.Reset() + ledger.NoGlobal(100, "ALGOA") + ledger.NewGlobal(100, "ALGO", algoValue) - delta = testApp(t, source, ep) + delta = testApp(t, source, ep) - require.Len(t, delta.GlobalDelta, 2) - require.Empty(t, delta.LocalDeltas) + require.Len(t, delta.GlobalDelta, 2) + require.Empty(t, delta.LocalDeltas) - vd = delta.GlobalDelta["ALGO"] - require.Equal(t, basics.SetUintAction, vd.Action) - require.Equal(t, uint64(0x78), vd.Uint) + vd = delta.GlobalDelta["ALGO"] + require.Equal(t, basics.SetUintAction, vd.Action) + require.Equal(t, uint64(0x78), vd.Uint) - vd = delta.GlobalDelta["ALGOA"] - require.Equal(t, basics.SetBytesAction, vd.Action) - require.Equal(t, "ALGO", vd.Bytes) - }) + vd = delta.GlobalDelta["ALGOA"] + require.Equal(t, basics.SetBytesAction, vd.Action) + require.Equal(t, "ALGO", vd.Bytes) + }) + } } func TestAppGlobalReadOtherApp(t *testing.T) { @@ -2020,8 +2083,9 @@ func TestAppGlobalReadOtherApp(t *testing.T) { t.Parallel() // app_global_get_ex starts in v2 - testLogicRange(t, 2, 0, func(t *testing.T, ep *EvalParams, txn *transactions.Transaction, ledger *Ledger) { - source := ` + for _, bySlot := range []bool{true, false} { + testLogicRange(t, 2, 0, func(t *testing.T, ep *EvalParams, txn *transactions.Transaction, ledger *Ledger) { + source := ` OTHERAPP byte "mykey1" app_global_get_ex @@ -2039,32 +2103,35 @@ byte "myval" == ` - if ep.Proto.LogicSigVersion < sharedResourcesVersion { - // 101 is in the ForeignApps array, name it by slot - source = strings.ReplaceAll(source, "OTHERAPP", "int 2") - } else { - // use the actual app number, slots no longer allowed - source = strings.ReplaceAll(source, "OTHERAPP", "int 101") - } + if bySlot { + source = strings.ReplaceAll(source, "OTHERAPP", "int 2") + } else { + // use the actual app number if allowed + if ep.Proto.LogicSigVersion < directRefEnabledVersion { + return + } + source = strings.ReplaceAll(source, "OTHERAPP", "int 101") + } - txn.ApplicationID = 100 - txn.ForeignApps = []basics.AppIndex{txn.ApplicationID, 101} - ledger.NewAccount(txn.Sender, 1) - ledger.NewApp(txn.Sender, 100, basics.AppParams{}) + txn.ApplicationID = 100 + txn.ForeignApps = []basics.AppIndex{txn.ApplicationID, 101} + ledger.NewAccount(txn.Sender, 1) + ledger.NewApp(txn.Sender, 100, basics.AppParams{}) - delta := testApp(t, source, ep, "no app 101") - require.Empty(t, delta.GlobalDelta) - require.Empty(t, delta.LocalDeltas) + delta := testApp(t, source, ep, "no app 101") + require.Empty(t, delta.GlobalDelta) + require.Empty(t, delta.LocalDeltas) - ledger.NewApp(txn.Receiver, 101, basics.AppParams{}) - ledger.NewApp(txn.Receiver, 100, basics.AppParams{}) // this keeps current app id = 100 - algoValue := basics.TealValue{Type: basics.TealBytesType, Bytes: "myval"} - ledger.NewGlobal(101, "mykey", algoValue) + ledger.NewApp(txn.Receiver, 101, basics.AppParams{}) + ledger.NewApp(txn.Receiver, 100, basics.AppParams{}) // this keeps current app id = 100 + algoValue := basics.TealValue{Type: basics.TealBytesType, Bytes: "myval"} + ledger.NewGlobal(101, "mykey", algoValue) - delta = testApp(t, source, ep) - require.Empty(t, delta.GlobalDelta) - require.Empty(t, delta.LocalDeltas) - }) + delta = testApp(t, source, ep) + require.Empty(t, delta.GlobalDelta) + require.Empty(t, delta.LocalDeltas) + }) + } } func TestBlankKey(t *testing.T) { @@ -2087,25 +2154,25 @@ app_global_get int 7 == ` - txn := makeSampleAppl(100) - ep := defaultEvalParams(txn) - ledger := NewLedger(nil) - ledger.NewAccount(txn.Txn.Sender, 1) - ep.Ledger = ledger - ep.SigLedger = ledger - ledger.NewApp(txn.Txn.Sender, 100, basics.AppParams{}) + // v3 gives "assert" + testLogicRange(t, 3, 0, func(t *testing.T, ep *EvalParams, txn *transactions.Transaction, ledger *Ledger) { + txn.ApplicationID = 100 + ledger.NewAccount(txn.Sender, 1) + ledger.NewApp(txn.Sender, 100, basics.AppParams{}) - delta := testApp(t, source, ep) - require.Empty(t, delta.LocalDeltas) + delta := testApp(t, source, ep) + require.Empty(t, delta.LocalDeltas) + }) } func TestAppGlobalDelete(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - testLogicRange(t, 2, 0, func(t *testing.T, ep *EvalParams, txn *transactions.Transaction, ledger *Ledger) { - // check write/delete/read - source := `byte "ALGO" + for _, bySlot := range []bool{true, false} { + testLogicRange(t, 2, 0, func(t *testing.T, ep *EvalParams, txn *transactions.Transaction, ledger *Ledger) { + // check write/delete/read + source := `byte "ALGO" int 0x77 // value app_global_put byte "ALGOA" @@ -2131,23 +2198,23 @@ ok: int 1 ` - ledger.NewAccount(txn.Sender, 1) - txn.ApplicationID = 100 - ledger.NewApp(txn.Sender, 100, basics.AppParams{}) + ledger.NewAccount(txn.Sender, 1) + txn.ApplicationID = 100 + ledger.NewApp(txn.Sender, 100, basics.AppParams{}) - delta := testApp(t, source, ep) - require.Len(t, delta.GlobalDelta, 2) - require.Empty(t, delta.LocalDeltas) + delta := testApp(t, source, ep) + require.Len(t, delta.GlobalDelta, 2) + require.Empty(t, delta.LocalDeltas) - ledger.Reset() - ledger.NoGlobal(100, "ALGOA") - ledger.NoGlobal(100, "ALGO") + ledger.Reset() + ledger.NoGlobal(100, "ALGOA") + ledger.NoGlobal(100, "ALGO") - algoValue := basics.TealValue{Type: basics.TealUintType, Uint: 0x77} - ledger.NewGlobal(100, "ALGO", algoValue) + algoValue := basics.TealValue{Type: basics.TealUintType, Uint: 0x77} + ledger.NewGlobal(100, "ALGO", algoValue) - // check delete existing - source = `byte "ALGO" + // check delete existing + source = `byte "ALGO" app_global_del THISAPP byte "ALGO" @@ -2155,30 +2222,33 @@ app_global_get_ex == // two zeros ` - if ep.Proto.LogicSigVersion < sharedResourcesVersion { - // 100 is in the ForeignApps array, name it by slot - source = strings.ReplaceAll(source, "THISAPP", "int 1") - } else { - // use the actual app number, slots no longer allowed - source = strings.ReplaceAll(source, "THISAPP", "int 100") - } - txn.ForeignApps = []basics.AppIndex{txn.ApplicationID} - delta = testApp(t, source, ep) - require.Len(t, delta.GlobalDelta, 1) - vd := delta.GlobalDelta["ALGO"] - require.Equal(t, basics.DeleteAction, vd.Action) - require.Equal(t, uint64(0), vd.Uint) - require.Equal(t, "", vd.Bytes) - require.Equal(t, 0, len(delta.LocalDeltas)) + if bySlot { + // 100 is in the ForeignApps array, name it by slot + source = strings.ReplaceAll(source, "THISAPP", "int 1") + } else { + // use the actual app number if allowed + if ep.Proto.LogicSigVersion < directRefEnabledVersion { + return + } + source = strings.ReplaceAll(source, "THISAPP", "int 100") + } + txn.ForeignApps = []basics.AppIndex{txn.ApplicationID} + delta = testApp(t, source, ep) + require.Len(t, delta.GlobalDelta, 1) + vd := delta.GlobalDelta["ALGO"] + require.Equal(t, basics.DeleteAction, vd.Action) + require.Equal(t, uint64(0), vd.Uint) + require.Equal(t, "", vd.Bytes) + require.Equal(t, 0, len(delta.LocalDeltas)) - ledger.Reset() - ledger.NoGlobal(100, "ALGOA") - ledger.NoGlobal(100, "ALGO") + ledger.Reset() + ledger.NoGlobal(100, "ALGOA") + ledger.NoGlobal(100, "ALGO") - ledger.NewGlobal(100, "ALGO", algoValue) + ledger.NewGlobal(100, "ALGO", algoValue) - // check delete and write non-existing - source = `byte "ALGOA" + // check delete and write non-existing + source = `byte "ALGOA" app_global_del int 0 byte "ALGOA" @@ -2188,43 +2258,43 @@ byte "ALGOA" int 0x78 app_global_put ` - delta = testApp(t, source, ep) - require.Len(t, delta.GlobalDelta, 1) - vd = delta.GlobalDelta["ALGOA"] - require.Equal(t, basics.SetUintAction, vd.Action) - require.Equal(t, uint64(0x78), vd.Uint) - require.Equal(t, "", vd.Bytes) - require.Empty(t, delta.LocalDeltas) + delta = testApp(t, source, ep) + require.Len(t, delta.GlobalDelta, 1) + vd = delta.GlobalDelta["ALGOA"] + require.Equal(t, basics.SetUintAction, vd.Action) + require.Equal(t, uint64(0x78), vd.Uint) + require.Equal(t, "", vd.Bytes) + require.Empty(t, delta.LocalDeltas) - ledger.Reset() - ledger.NoGlobal(100, "ALGOA") - ledger.NoGlobal(100, "ALGO") + ledger.Reset() + ledger.NoGlobal(100, "ALGOA") + ledger.NoGlobal(100, "ALGO") - ledger.NewGlobal(100, "ALGO", algoValue) + ledger.NewGlobal(100, "ALGO", algoValue) - // check delete and write existing - source = `byte "ALGO" + // check delete and write existing + source = `byte "ALGO" app_global_del byte "ALGO" int 0x78 app_global_put int 1 ` - delta = testApp(t, source, ep) - require.Len(t, delta.GlobalDelta, 1) - vd = delta.GlobalDelta["ALGO"] - require.Equal(t, basics.SetUintAction, vd.Action) - require.Empty(t, delta.LocalDeltas) + delta = testApp(t, source, ep) + require.Len(t, delta.GlobalDelta, 1) + vd = delta.GlobalDelta["ALGO"] + require.Equal(t, basics.SetUintAction, vd.Action) + require.Empty(t, delta.LocalDeltas) - ledger.Reset() - ledger.Reset() - ledger.NoGlobal(100, "ALGOA") - ledger.NoGlobal(100, "ALGO") + ledger.Reset() + ledger.Reset() + ledger.NoGlobal(100, "ALGOA") + ledger.NoGlobal(100, "ALGO") - ledger.NewGlobal(100, "ALGO", algoValue) + ledger.NewGlobal(100, "ALGO", algoValue) - // check delete,write,delete existing - source = `byte "ALGO" + // check delete,write,delete existing + source = `byte "ALGO" app_global_del byte "ALGO" int 0x78 @@ -2233,21 +2303,21 @@ byte "ALGO" app_global_del int 1 ` - delta = testApp(t, source, ep) - require.Len(t, delta.GlobalDelta, 1) - vd = delta.GlobalDelta["ALGO"] - require.Equal(t, basics.DeleteAction, vd.Action) - require.Empty(t, delta.LocalDeltas) + delta = testApp(t, source, ep) + require.Len(t, delta.GlobalDelta, 1) + vd = delta.GlobalDelta["ALGO"] + require.Equal(t, basics.DeleteAction, vd.Action) + require.Empty(t, delta.LocalDeltas) - ledger.Reset() - ledger.Reset() - ledger.NoGlobal(100, "ALGOA") - ledger.NoGlobal(100, "ALGO") + ledger.Reset() + ledger.Reset() + ledger.NoGlobal(100, "ALGOA") + ledger.NoGlobal(100, "ALGO") - ledger.NewGlobal(100, "ALGO", algoValue) + ledger.NewGlobal(100, "ALGO", algoValue) - // check delete, write, delete non-existing - source = `byte "ALGOA" // key "ALGOA" + // check delete, write, delete non-existing + source = `byte "ALGOA" // key "ALGOA" app_global_del byte "ALGOA" int 0x78 @@ -2256,10 +2326,11 @@ byte "ALGOA" app_global_del int 1 ` - delta = testApp(t, source, ep) - require.Len(t, delta.GlobalDelta, 1) - require.Len(t, delta.LocalDeltas, 0) - }) + delta = testApp(t, source, ep) + require.Len(t, delta.GlobalDelta, 1) + require.Len(t, delta.LocalDeltas, 0) + }) + } } func TestAppLocalDelete(t *testing.T) { @@ -2308,12 +2379,10 @@ int 1 ep.Trace = &strings.Builder{} - if ep.Proto.LogicSigVersion < sharedResourcesVersion { - delta := testApp(t, source, ep) - require.Equal(t, 0, len(delta.GlobalDelta)) - require.Equal(t, 2, len(delta.LocalDeltas)) - ledger.Reset() - } + delta := testApp(t, source, ep) + require.Equal(t, 0, len(delta.GlobalDelta)) + require.Equal(t, 2, len(delta.LocalDeltas)) + ledger.Reset() if ep.Proto.LogicSigVersion >= directRefEnabledVersion { // test that app_local_put and _app_local_del can use byte addresses @@ -2350,7 +2419,7 @@ app_local_get_ex if ep.Proto.LogicSigVersion < directRefEnabledVersion { source = strings.ReplaceAll(source, "txn Sender", "int 0") } - delta := testApp(t, source, ep) + delta = testApp(t, source, ep) require.Equal(t, 0, len(delta.GlobalDelta)) require.Equal(t, 1, len(delta.LocalDeltas)) vd := delta.LocalDeltas[0]["ALGO"] @@ -2568,50 +2637,52 @@ func TestReturnTypes(t *testing.T) { // opcodes that need to set up their own stack inputs, a ": at the front of // the string means "start with an empty stack". specialCmd := map[string]string{ - "txn": "txn Sender", - "txna": "txna ApplicationArgs 0", - "gtxn": "gtxn 0 Sender", - "gtxna": "gtxna 0 ApplicationArgs 0", - "global": "global MinTxnFee", - "gaids": ": int 0; gaids", - "gloads": ": int 0; gloads 0", // Needs txn index = 0 to work - "gloadss": ": int 0; int 1; gloadss", // Needs txn index = 0 to work - "intc": "intcblock 0; intc 0", - "intc_0": "intcblock 0; intc_0", - "intc_1": "intcblock 0 0; intc_1", - "intc_2": "intcblock 0 0 0; intc_2", - "intc_3": "intcblock 0 0 0 0; intc_3", - "bytec": "bytecblock 0x32; bytec 0", - "bytec_0": "bytecblock 0x32; bytec_0", - "bytec_1": "bytecblock 0x32 0x33; bytec_1", - "bytec_2": "bytecblock 0x32 0x33 0x34; bytec_2", - "bytec_3": "bytecblock 0x32 0x33 0x34 0x35; bytec_3", - "substring": "substring 0 2", - "extract_uint32": ": byte 0x0102030405; int 1; extract_uint32", - "extract_uint64": ": byte 0x010203040506070809; int 1; extract_uint64", - "replace2": ": byte 0x0102030405; byte 0x0809; replace2 2", - "replace3": ": byte 0x0102030405; int 2; byte 0x0809; replace3", - "asset_params_get": "asset_params_get AssetUnitName", - "gtxns": "gtxns Sender", - "gtxnsa": ": int 0; gtxnsa ApplicationArgs 0", - "app_params_get": "app_params_get AppGlobalNumUint", - "extract": "extract 0 2", - "txnas": "txnas ApplicationArgs", - "gtxnas": "gtxnas 0 ApplicationArgs", - "gtxnsas": ": int 0; int 0; gtxnsas ApplicationArgs", - "divw": ": int 1; int 2; int 3; divw", + "txn": "txn Sender", + "txna": "txna ApplicationArgs 0", + "gtxn": "gtxn 0 Sender", + "gtxna": "gtxna 0 ApplicationArgs 0", + "global": "global MinTxnFee", + "gaids": ": int 0; gaids", + "gloads": ": int 0; gloads 0", // Needs txn index = 0 to work + "gloadss": ": int 0; int 1; gloadss", // Needs txn index = 0 to work + "intc": "intcblock 0; intc 0", + "intc_0": "intcblock 0; intc_0", + "intc_1": "intcblock 0 0; intc_1", + "intc_2": "intcblock 0 0 0; intc_2", + "intc_3": "intcblock 0 0 0 0; intc_3", + "bytec": "bytecblock 0x32; bytec 0", + "bytec_0": "bytecblock 0x32; bytec_0", + "bytec_1": "bytecblock 0x32 0x33; bytec_1", + "bytec_2": "bytecblock 0x32 0x33 0x34; bytec_2", + "bytec_3": "bytecblock 0x32 0x33 0x34 0x35; bytec_3", + "substring": "substring 0 2", + "extract_uint32": ": byte 0x0102030405; int 1; extract_uint32", + "extract_uint64": ": byte 0x010203040506070809; int 1; extract_uint64", + "replace2": ": byte 0x0102030405; byte 0x0809; replace2 2", + "replace3": ": byte 0x0102030405; int 2; byte 0x0809; replace3", + "gtxns": "gtxns Sender", + "gtxnsa": ": int 0; gtxnsa ApplicationArgs 0", + "extract": "extract 0 2", + "txnas": "txnas ApplicationArgs", + "gtxnas": "gtxnas 0 ApplicationArgs", + "gtxnsas": ": int 0; int 0; gtxnsas ApplicationArgs", + "divw": ": int 1; int 2; int 3; divw", // opcodes that require addresses, not just bytes "balance": ": txn Sender; balance", "min_balance": ": txn Sender; min_balance", "acct_params_get": ": txn Sender; acct_params_get AcctMinBalance", + // Use "bury" here to take advantage of args pushed on stack by test - "app_local_get": "txn Accounts 1; bury 2; app_local_get", - "app_local_get_ex": "txn Accounts 1; bury 3; app_local_get_ex", - "app_local_del": "txn Accounts 1; bury 2; app_local_del", - "app_local_put": "txn Accounts 1; bury 3; app_local_put", - "app_opted_in": "txn Sender; bury 2; app_opted_in", - "asset_holding_get": "txn Sender; bury 2; asset_holding_get AssetBalance", + "app_local_get": "txn Accounts 1; bury 2; app_local_get", + "app_local_get_ex": "txn Accounts 1; bury 3; app_local_get_ex", + "app_local_del": "txn Accounts 1; bury 2; app_local_del", + "app_local_put": "txn Accounts 1; bury 3; app_local_put", + "app_opted_in": "txn Sender; bury 2; app_opted_in", + + "asset_params_get": ": int 400; asset_params_get AssetUnitName", + "asset_holding_get": ": txn Sender; int 400; asset_holding_get AssetBalance", + "app_params_get": "app_params_get AppGlobalNumUint", "itxn_field": "itxn_begin; itxn_field TypeEnum", "itxn_next": "itxn_begin; int pay; itxn_field TypeEnum; itxn_next", @@ -2722,9 +2793,9 @@ func TestReturnTypes(t *testing.T) { ep, tx, ledger := makeSampleEnv() tx.Type = protocol.ApplicationCallTx - tx.ApplicationID = 1 + tx.ApplicationID = 300 tx.ForeignApps = []basics.AppIndex{tx.ApplicationID} - tx.ForeignAssets = []basics.AssetIndex{basics.AssetIndex(1), basics.AssetIndex(1)} + tx.ForeignAssets = []basics.AssetIndex{400} tx.Boxes = []transactions.BoxRef{{ Name: []byte("3456"), }} @@ -2752,20 +2823,20 @@ func TestReturnTypes(t *testing.T) { Freeze: tx.Receiver, Clawback: tx.Receiver, } - ledger.NewAsset(tx.Sender, 1, params) - ledger.NewApp(tx.Sender, 1, basics.AppParams{}) + ledger.NewAsset(tx.Sender, 400, params) + ledger.NewApp(tx.Sender, 300, basics.AppParams{}) ledger.NewAccount(tx.Receiver, 1000000) - ledger.NewLocals(tx.Receiver, 1) + ledger.NewLocals(tx.Receiver, 300) key, err := hex.DecodeString("33343536") require.NoError(t, err) algoValue := basics.TealValue{Type: basics.TealUintType, Uint: 0x77} - ledger.NewLocal(tx.Receiver, 1, string(key), algoValue) - ledger.NewAccount(appAddr(1), 1000000) + 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(1, "3456", 10) + ledger.CreateBox(300, "3456", 10) ep.ioBudget = 50 cx := EvalContext{ @@ -2773,7 +2844,7 @@ func TestReturnTypes(t *testing.T) { runModeFlags: m, groupIndex: 1, txn: &ep.TxnGroup[1], - appID: 1, + appID: 300, } // These set conditions for some ops that examine the group. @@ -3152,3 +3223,43 @@ int 1 require.Equal(t, []basics.Address{tx.ApplicationID.Address()}, ed.SharedAccts) }) } + +func TestInfiniteRecursion(t *testing.T) { // nolint:paralleltest // manipulates maxAppCallDepth + partitiontest.PartitionTest(t) + + // test needs AppApprovalProgram, available in 7 + TestLogicRange(t, 7, 0, func(t *testing.T, ep *EvalParams, tx *transactions.Transaction, ledger *Ledger) { + v := ep.Proto.LogicSigVersion + source := ` +itxn_begin +int appl; itxn_field TypeEnum +int 0; app_params_get AppApprovalProgram +assert +itxn_field ApprovalProgram + +int 0; app_params_get AppClearStateProgram +assert +itxn_field ClearStateProgram + +itxn_submit +` + // This app looks itself up in the ledger, so we need to put it in there. + ledger.NewApp(tx.Sender, 888, basics.AppParams{ + ApprovalProgram: testProg(t, source, v).Program, + ClearStateProgram: testProg(t, "int 1", v).Program, + }) + // We're testing if this can recur forever. It's hard to fund all these + // apps, but we can put a huge credit in the ep. + *ep.FeeCredit = 1_000_000_000 + + testApp(t, source, ep, "appl depth (8) exceeded") + + was := maxAppCallDepth + defer func() { + maxAppCallDepth = was + }() + maxAppCallDepth = 10_000_000 + + testApp(t, source, ep, "too many inner transactions 1 with 0 left") + }) +} diff --git a/data/transactions/logic/fields_test.go b/data/transactions/logic/fields_test.go index 2d58854d5e..afbb20d535 100644 --- a/data/transactions/logic/fields_test.go +++ b/data/transactions/logic/fields_test.go @@ -270,18 +270,18 @@ func TestAcctParamsFieldsVersions(t *testing.T) { t.Parallel() for _, field := range acctParamsFieldSpecs { - text := fmt.Sprintf("txn Sender; acct_params_get %s; assert;", field.field.String()) + text := fmt.Sprintf("txn Sender; acct_params_get %s; assert;", field.field) if field.ftype == StackBytes { text += "global ZeroAddress; concat; len" // use concat to prove we have bytes } else { text += "global ZeroAddress; len; +" // use + to prove we have an int } - // check assembler fails if version before introduction - for v := uint64(2); v <= AssemblerMaxVersion; v++ { - ep, txn, ledger := makeSampleEnv() + + testLogicRange(t, 4, 0, func(t *testing.T, ep *EvalParams, txn *transactions.Transaction, ledger *Ledger) { + v := ep.Proto.LogicSigVersion ledger.NewAccount(txn.Sender, 200_000) - ep.Proto.LogicSigVersion = v if field.version > v { + // check assembler fails if version before introduction testProg(t, text, v, Expect{1, "...was introduced in..."}) ops := testProg(t, text, field.version) // assemble in the future ops.Program[0] = byte(v) // but set version back to before intro @@ -294,7 +294,7 @@ func TestAcctParamsFieldsVersions(t *testing.T) { testProg(t, text, v) testApp(t, text, ep) } - } + }) } } diff --git a/data/transactions/logic/langspec.json b/data/transactions/logic/langspec.json index a5c42c7b15..1182117a1d 100644 --- a/data/transactions/logic/langspec.json +++ b/data/transactions/logic/langspec.json @@ -1,5 +1,5 @@ { - "EvalMaxVersion": 8, + "EvalMaxVersion": 9, "LogicSigVersion": 8, "Ops": [ { diff --git a/data/transactions/logic/opcodes.go b/data/transactions/logic/opcodes.go index f7575f774c..3e1ae7e1e5 100644 --- a/data/transactions/logic/opcodes.go +++ b/data/transactions/logic/opcodes.go @@ -530,38 +530,28 @@ var OpSpecs = []OpSpec{ {0x60, "balance", opBalance, proto("i:i"), 2, only(ModeApp)}, {0x60, "balance", opBalance, proto("a:i"), directRefEnabledVersion, only(ModeApp)}, - {0x60, "balance", opBalance, proto("b:i"), sharedResourcesVersion, only(ModeApp)}, {0x61, "app_opted_in", opAppOptedIn, proto("ii:i"), 2, only(ModeApp)}, {0x61, "app_opted_in", opAppOptedIn, proto("ai:i"), directRefEnabledVersion, only(ModeApp)}, - {0x61, "app_opted_in", opAppOptedIn, proto("bi:i"), sharedResourcesVersion, only(ModeApp)}, {0x62, "app_local_get", opAppLocalGet, proto("ib:a"), 2, only(ModeApp)}, {0x62, "app_local_get", opAppLocalGet, proto("ab:a"), directRefEnabledVersion, only(ModeApp)}, - {0x62, "app_local_get", opAppLocalGet, proto("bb:a"), sharedResourcesVersion, only(ModeApp)}, {0x63, "app_local_get_ex", opAppLocalGetEx, proto("iib:ai"), 2, only(ModeApp)}, {0x63, "app_local_get_ex", opAppLocalGetEx, proto("aib:ai"), directRefEnabledVersion, only(ModeApp)}, - {0x63, "app_local_get_ex", opAppLocalGetEx, proto("bib:ai"), sharedResourcesVersion, only(ModeApp)}, {0x64, "app_global_get", opAppGlobalGet, proto("b:a"), 2, only(ModeApp)}, {0x65, "app_global_get_ex", opAppGlobalGetEx, proto("ib:ai"), 2, only(ModeApp)}, {0x66, "app_local_put", opAppLocalPut, proto("iba:"), 2, only(ModeApp)}, {0x66, "app_local_put", opAppLocalPut, proto("aba:"), directRefEnabledVersion, only(ModeApp)}, - {0x66, "app_local_put", opAppLocalPut, proto("bba:"), sharedResourcesVersion, only(ModeApp)}, {0x67, "app_global_put", opAppGlobalPut, proto("ba:"), 2, only(ModeApp)}, {0x68, "app_local_del", opAppLocalDel, proto("ib:"), 2, only(ModeApp)}, {0x68, "app_local_del", opAppLocalDel, proto("ab:"), directRefEnabledVersion, only(ModeApp)}, - {0x68, "app_local_del", opAppLocalDel, proto("bb:"), sharedResourcesVersion, only(ModeApp)}, {0x69, "app_global_del", opAppGlobalDel, proto("b:"), 2, only(ModeApp)}, - {0x70, "asset_holding_get", opAssetHoldingGet, proto("ii:ai"), 2, field("f", &AssetHoldingFields).only(ModeApp)}, {0x70, "asset_holding_get", opAssetHoldingGet, proto("ai:ai"), directRefEnabledVersion, field("f", &AssetHoldingFields).only(ModeApp)}, - {0x70, "asset_holding_get", opAssetHoldingGet, proto("bi:ai"), sharedResourcesVersion, field("f", &AssetHoldingFields).only(ModeApp)}, {0x71, "asset_params_get", opAssetParamsGet, proto("i:ai"), 2, field("f", &AssetParamsFields).only(ModeApp)}, {0x72, "app_params_get", opAppParamsGet, proto("i:ai"), 5, field("f", &AppParamsFields).only(ModeApp)}, {0x73, "acct_params_get", opAcctParamsGet, proto("a:ai"), 6, field("f", &AcctParamsFields).only(ModeApp)}, - {0x73, "acct_params_get", opAcctParamsGet, proto("b:ai"), sharedResourcesVersion, field("f", &AcctParamsFields).only(ModeApp)}, {0x78, "min_balance", opMinBalance, proto("i:i"), 3, only(ModeApp)}, {0x78, "min_balance", opMinBalance, proto("a:i"), directRefEnabledVersion, only(ModeApp)}, - {0x78, "min_balance", opMinBalance, proto("b:i"), sharedResourcesVersion, only(ModeApp)}, // Immediate bytes and ints. Smaller code size for single use of constant. {0x80, "pushbytes", opPushBytes, proto(":b"), 3, constants(asmPushBytes, opPushBytes, "bytes", immBytes)}, diff --git a/data/transactions/logic/resources.go b/data/transactions/logic/resources.go index 849440a26c..4db2d5d595 100644 --- a/data/transactions/logic/resources.go +++ b/data/transactions/logic/resources.go @@ -214,14 +214,14 @@ func (cx *EvalContext) requireHolding(acct basics.Address, id basics.AssetIndex) return nil } if !cx.allowsHolding(acct, id) { - return fmt.Errorf("invalid Holding access %s x %d would be possible", acct, id) + return fmt.Errorf("unavailable Holding %s x %d would be accessible", acct, id) } return nil } func (cx *EvalContext) requireLocals(acct basics.Address, id basics.AppIndex) error { if !cx.allowsLocals(acct, id) { - return fmt.Errorf("invalid Local State access %s x %d would be possible", acct, id) + return fmt.Errorf("unavailable Local State %s x %d would be accessible", acct, id) } return nil } diff --git a/data/transactions/logic/resources_test.go b/data/transactions/logic/resources_test.go index 01f784c12b..c4aed20b71 100644 --- a/data/transactions/logic/resources_test.go +++ b/data/transactions/logic/resources_test.go @@ -58,33 +58,21 @@ func TestAppSharing(t *testing.T) { Sender: basics.Address{1, 2, 3, 4}, } - getSchema := ` -int 500 -app_params_get AppGlobalNumByteSlice -!; assert; pop; int 1 -` - sources := []string{getSchema, getSchema} + 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, sources, txntest.Group(&appl0, &appl1), 8, nil, - logic.NewExpect(1, "invalid App reference 500")) + logic.TestApps(t, []string{getSchema, getSchema}, txntest.Group(&appl0, &appl1), 8, nil, + logic.NewExpect(1, "unavailable App 500")) // In v9, the second can, because the first can. - logic.TestApps(t, sources, txntest.Group(&appl0, &appl1), 9, nil) + logic.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 -` + getLocalEx := `txn Sender; int 500; byte "some-key"; app_local_get_ex; pop; pop; int 1` - sources = []string{getLocalEx, getLocalEx} // In contrast, here there's no help from v9, because the second tx is // reading the locals for a different account. // app_local_get* requires the address and the app exist, else the program fails - logic.TestApps(t, sources, txntest.Group(&appl0, &appl1), 8, nil, + logic.TestApps(t, []string{getLocalEx, getLocalEx}, txntest.Group(&appl0, &appl1), 8, nil, logic.NewExpect(0, "no account")) _, _, ledger := logic.MakeSampleEnv() @@ -93,66 +81,51 @@ pop; pop; int 1 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, sources, txntest.Group(&appl0, &appl1), 9, ledger, - logic.NewExpect(1, "invalid Local State access")) + logic.TestApps(t, []string{getLocalEx, getLocalEx}, txntest.Group(&appl0, &appl1), 9, ledger, + logic.NewExpect(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, sources, txntest.Group(&appl0, &appl2), 9, ledger) - logic.TestApps(t, sources, txntest.Group(&appl0, &appl2), 8, ledger, // version 8 does not get sharing - logic.NewExpect(1, "invalid App reference 500")) + 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.NewExpect(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 -` + optInCheck500 := "txn Sender; int 500; app_opted_in" - sources = []string{optInCheck500, optInCheck500} // app_opted_in requires the address and the app exist, else the program fails - logic.TestApps(t, sources, txntest.Group(&appl0, &appl1), 9, nil, // nil ledger, no account + logic.TestApps(t, []string{optInCheck500, optInCheck500}, txntest.Group(&appl0, &appl1), 9, nil, // nil ledger, no account logic.NewExpect(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, sources, txntest.Group(&appl0, &appl1), 9, ledger, - logic.NewExpect(1, "invalid Local State access "+appl1.Sender.String())) + logic.TestApps(t, []string{optInCheck500, optInCheck500}, txntest.Group(&appl0, &appl1), 9, ledger, + logic.NewExpect(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, sources, txntest.Group(&appl0, &appl2), 9, ledger) - logic.TestApps(t, sources, txntest.Group(&appl0, &appl2), 8, ledger, // version 8 does not get sharing - logic.NewExpect(1, "invalid App reference 500")) + 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.NewExpect(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 -` - sources = []string{optInCheck900, optInCheck900} + 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, sources, txntest.Group(&appl0, &appl1), 9, ledger, - logic.NewExpect(1, "invalid Local State access "+appl1.Sender.String())) - logic.TestApps(t, sources, txntest.Group(&appl0, &appl2), 9, ledger) - logic.TestApps(t, sources, txntest.Group(&appl0, &appl2), 8, ledger, // version 8 does not get sharing - logic.NewExpect(1, "invalid App reference 900")) + logic.TestApps(t, []string{optInCheck900, optInCheck900}, txntest.Group(&appl0, &appl1), 9, ledger, + logic.NewExpect(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.NewExpect(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 // properly in EvalDelta. - putLocal := ` -txn ApplicationArgs 0 -byte "X" -int 74 -app_local_put -int 1 -` + putLocal := `txn ApplicationArgs 0; byte "X"; int 74; app_local_put; int 1` + noop := `int 1` - sources = []string{noop, putLocal} + sources := []string{noop, putLocal} appl1.ApplicationArgs = [][]byte{appl0.Sender[:]} // tx1 will try to modify local state exposed in tx0 logic.TestApps(t, sources, txntest.Group(&appl0, &appl1), 9, ledger, logic.NewExpect(1, "account "+appl0.Sender.String()+" is not opted into 901")) @@ -174,12 +147,12 @@ int 1 // 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.NewExpect(2, "invalid Local State access")) // note that the error message is for Locals, not specialized + logic.NewExpect(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.NewExpect(2, "invalid Local State access")) // note that the error message is for Locals, not specialized + logic.NewExpect(2, "unavailable Local State")) // note that the error message is for Locals, not specialized } // TestBetterLocalErrors confirms that we get specific errors about the missing @@ -206,19 +179,19 @@ pop; pop; int 1 binary.BigEndian.PutUint64(app, 500) tx.ApplicationArgs = [][]byte{joe[:], app} - logic.TestApp(t, getLocalEx, ep, "invalid Local State") + logic.TestApp(t, getLocalEx, ep, "unavailable Account "+joe.String()+", unavailable App 500") tx.Accounts = []basics.Address{joe} - logic.TestApp(t, getLocalEx, ep, "invalid App reference 500") + logic.TestApp(t, getLocalEx, ep, "unavailable App 500") tx.ForeignApps = []basics.AppIndex{500} logic.TestApp(t, getLocalEx, ep) binary.BigEndian.PutUint64(tx.ApplicationArgs[1], 500) logic.TestApp(t, getLocalEx, ep) binary.BigEndian.PutUint64(tx.ApplicationArgs[1], 501) - logic.TestApp(t, getLocalEx, ep, "invalid App reference 501") + logic.TestApp(t, getLocalEx, ep, "unavailable App 501") binary.BigEndian.PutUint64(tx.ApplicationArgs[1], 500) tx.Accounts = []basics.Address{} - logic.TestApp(t, getLocalEx, ep, "invalid Account reference "+joe.String()) + logic.TestApp(t, getLocalEx, ep, "unavailable Account "+joe.String()) } // TestAssetSharing confirms that as of v9, assets can be accessed across @@ -245,46 +218,36 @@ func TestAssetSharing(t *testing.T) { Sender: basics.Address{1, 2, 3, 4}, } - getTotal := ` -int 400 -asset_params_get AssetTotal -pop; pop; int 1 -` - sources := []string{getTotal, getTotal} + getTotal := "int 400; asset_params_get AssetTotal; pop; pop; int 1" + // In v8, the first tx can read asset 400, because it's in its foreign array, // but the second can't - logic.TestApps(t, sources, txntest.Group(&appl0, &appl1), 8, nil, - logic.NewExpect(1, "invalid Asset reference 400")) + logic.TestApps(t, []string{getTotal, getTotal}, txntest.Group(&appl0, &appl1), 8, nil, + logic.NewExpect(1, "unavailable Asset 400")) // In v9, the second can, because the first can. - logic.TestApps(t, sources, txntest.Group(&appl0, &appl1), 9, nil) + logic.TestApps(t, []string{getTotal, getTotal}, txntest.Group(&appl0, &appl1), 9, nil) - getBalance := ` -txn Sender -int 400 -asset_holding_get AssetBalance -pop; pop; int 1 -` + getBalance := "txn Sender; int 400; asset_holding_get AssetBalance; pop; pop; int 1" - sources = []string{getBalance, getBalance} // In contrast, here there's no help from v9, because the second tx is // reading a holding for a different account. - logic.TestApps(t, sources, txntest.Group(&appl0, &appl1), 8, nil, - logic.NewExpect(1, "invalid Asset reference 400")) - logic.TestApps(t, sources, txntest.Group(&appl0, &appl1), 9, nil, - logic.NewExpect(1, "invalid Holding access")) + logic.TestApps(t, []string{getBalance, getBalance}, txntest.Group(&appl0, &appl1), 8, nil, + logic.NewExpect(1, "unavailable Asset 400")) + logic.TestApps(t, []string{getBalance, getBalance}, txntest.Group(&appl0, &appl1), 9, nil, + logic.NewExpect(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, sources, txntest.Group(&appl0, &appl2), 9, nil) + logic.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"} + 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.NewExpect(2, "invalid Holding access")) // note that the error message is for Holding, not specialized + logic.NewExpect(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.NewExpect(2, "invalid Holding access")) // note that the error message is for Holding, not specialized + logic.NewExpect(2, "unavailable Holding")) // note that the error message is for Holding, not specialized } // TestBetterHoldingErrors confirms that we get specific errors about the missing @@ -310,17 +273,17 @@ pop; pop; int 1 binary.BigEndian.PutUint64(asa, 200) tx.ApplicationArgs = [][]byte{joe[:], asa} - logic.TestApp(t, getHoldingBalance, ep, "invalid Holding access "+joe.String()) + logic.TestApp(t, getHoldingBalance, ep, "unavailable Account "+joe.String()+", unavailable Asset 200") tx.Accounts = []basics.Address{joe} - logic.TestApp(t, getHoldingBalance, ep, "invalid Asset reference 200") + logic.TestApp(t, getHoldingBalance, ep, "unavailable Asset 200") tx.ForeignAssets = []basics.AssetIndex{200} logic.TestApp(t, getHoldingBalance, ep) - binary.BigEndian.PutUint64(tx.ApplicationArgs[1], 0) - logic.TestApp(t, getHoldingBalance, ep, "invalid Asset reference 0") // slots not allowed + binary.BigEndian.PutUint64(tx.ApplicationArgs[1], 0) // slot=0 is same (200) + logic.TestApp(t, getHoldingBalance, ep) binary.BigEndian.PutUint64(tx.ApplicationArgs[1], 200) tx.Accounts = []basics.Address{} - logic.TestApp(t, getHoldingBalance, ep, "invalid Account reference "+joe.String()) + logic.TestApp(t, getHoldingBalance, ep, "unavailable Account "+joe.String()) } // TestAccountPassing checks that the current app account and foreign app's @@ -418,15 +381,13 @@ func TestOtherTxSharing(t *testing.T) { FreezeAccount: otherAcct, } - sources := []string{"", senderBalance} - rsources := []string{senderBalance, ""} for _, send := range []txntest.Txn{keyreg, pay, acfg, axfer, afrz} { - logic.TestApps(t, sources, txntest.Group(&send, &appl), 9, ledger) - logic.TestApps(t, rsources, txntest.Group(&appl, &send), 9, ledger) + logic.TestApps(t, []string{"", senderBalance}, txntest.Group(&send, &appl), 9, ledger) + logic.TestApps(t, []string{senderBalance, ""}, txntest.Group(&appl, &send), 9, ledger) - logic.TestApps(t, sources, txntest.Group(&send, &appl), 8, ledger, + logic.TestApps(t, []string{"", senderBalance}, txntest.Group(&send, &appl), 8, ledger, logic.NewExpect(1, "invalid Account reference")) - logic.TestApps(t, rsources, txntest.Group(&appl, &send), 8, ledger, + logic.TestApps(t, []string{senderBalance, ""}, txntest.Group(&appl, &send), 8, ledger, logic.NewExpect(0, "invalid Account reference")) } @@ -436,17 +397,15 @@ func TestOtherTxSharing(t *testing.T) { asset_holding_get AssetBalance pop; pop; int 1 ` - sources = []string{"", holdingAccess} - rsources = []string{holdingAccess, ""} t.Run("keyreg", func(t *testing.T) { appl.ApplicationArgs = [][]byte{senderAcct[:], {200}} - logic.TestApps(t, sources, txntest.Group(&keyreg, &appl), 9, ledger, - logic.NewExpect(1, "invalid Asset reference 200")) + logic.TestApps(t, []string{"", holdingAccess}, txntest.Group(&keyreg, &appl), 9, ledger, + logic.NewExpect(1, "unavailable Asset 200")) withRef := appl withRef.ForeignAssets = []basics.AssetIndex{200} - logic.TestApps(t, sources, txntest.Group(&keyreg, &withRef), 9, ledger, - logic.NewExpect(1, "invalid Holding access "+senderAcct.String())) + logic.TestApps(t, []string{"", holdingAccess}, txntest.Group(&keyreg, &withRef), 9, ledger, + logic.NewExpect(1, "unavailable Holding "+senderAcct.String())) }) t.Run("pay", func(t *testing.T) { // The receiver is available for algo balance reading @@ -497,7 +456,7 @@ func TestOtherTxSharing(t *testing.T) { logic.TestApps(t, []string{"", other2Balance}, txntest.Group(&axfer, &appl), 9, ledger, logic.NewExpect(1, "invalid Account reference "+other2Acct.String())) logic.TestApps(t, []string{"", holdingAccess}, txntest.Group(&axfer, &appl), 9, ledger, - logic.NewExpect(1, "invalid Account reference "+other2Acct.String())) + logic.NewExpect(1, "unavailable Account "+other2Acct.String())) withClose := axfer withClose.AssetCloseTo = other2Acct @@ -516,7 +475,7 @@ func TestOtherTxSharing(t *testing.T) { 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.NewExpect(1, "invalid Holding access "+senderAcct.String())) + logic.NewExpect(1, "unavailable Holding "+senderAcct.String())) }) } @@ -639,17 +598,17 @@ int 1 logic.NewExpect(1, "invalid Account reference "+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.NewExpect(1, "invalid Holding access")) + logic.NewExpect(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.NewExpect(0, "invalid Holding access")) + logic.NewExpect(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.NewExpect(1, "invalid Holding access")) + logic.NewExpect(1, "unavailable Holding")) // Those axfers become possible by adding the asa to the appl's ForeignAssets appl.ForeignAssets = []basics.AssetIndex{asa1} @@ -663,7 +622,7 @@ int 1 // but can't axfer a different asset appl.ApplicationArgs = [][]byte{senderAcct[:], {asa2}} logic.TestApps(t, []string{"", axferToArgs}, txntest.Group(&axfer, &appl), 9, ledger, - logic.NewExpect(1, fmt.Sprintf("invalid Asset reference %d", asa2))) + logic.NewExpect(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, @@ -674,7 +633,7 @@ int 1 logic.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.NewExpect(1, fmt.Sprintf("invalid Asset reference %d", asa2))) + logic.NewExpect(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 @@ -696,7 +655,7 @@ int 1 ledger.NewHolding(payAcct, asa1, 1, false) appl.ApplicationArgs = [][]byte{payAcct[:], {asa1}} logic.TestApps(t, []string{"", "", axferToArgs}, txntest.Group(&axfer, &pay, &appl), 9, ledger, - logic.NewExpect(2, "invalid Holding access "+payAcct.String())) + logic.NewExpect(2, "unavailable Holding "+payAcct.String())) }) t.Run("afrz", func(t *testing.T) { @@ -717,12 +676,12 @@ int 1 // 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.NewExpect(1, "invalid Holding access "+appAcct.String())) + logic.NewExpect(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.NewExpect(1, "invalid Holding access "+senderAcct.String())) + logic.NewExpect(1, "unavailable Holding "+senderAcct.String())) // and not to the receiver which isn't in afrz appl.ApplicationArgs = [][]byte{receiverAcct[:], {asa1}} @@ -739,11 +698,11 @@ int 1 // 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.NewExpect(1, fmt.Sprintf("invalid Asset reference %d", asa2))) + logic.NewExpect(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.NewExpect(1, "invalid Holding access "+otherAcct.String())) + logic.NewExpect(1, "unavailable Holding "+otherAcct.String())) // appl can acfg the asset from tx0 (which requires asset available, not holding) appl.ForeignAssets = []basics.AssetIndex{} @@ -751,7 +710,7 @@ int 1 logic.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.NewExpect(1, fmt.Sprintf("invalid Asset reference %d", asa2))) + logic.NewExpect(1, fmt.Sprintf("unavailable Asset %d", asa2))) }) @@ -772,7 +731,7 @@ int 1 logic.NewExpect(1, "invalid Account reference "+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.NewExpect(1, "axfer Sender: invalid Holding")) + logic.NewExpect(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) @@ -782,11 +741,11 @@ int 1 // can't be used at all. appl.ApplicationArgs = [][]byte{otherAcct[:], {asa2}} logic.TestApps(t, []string{"", axferToArgs}, txntest.Group(&appl0, &appl), 9, ledger, - logic.NewExpect(1, "invalid Asset reference 202")) + logic.NewExpect(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.NewExpect(1, "axfer AssetReceiver: invalid Holding access "+otherAcct.String()+" x 202")) + logic.NewExpect(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 @@ -828,14 +787,14 @@ int 1 // 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.NewExpect(1, "appl ApplicationID: invalid Local State access "+otherAcct.String())) + logic.NewExpect(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.NewExpect(1, "appl ApplicationID: invalid Local State access "+otherAcct.String())) + logic.NewExpect(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} @@ -848,7 +807,7 @@ int 1 appl.ApplicationArgs = [][]byte{{11}, otherAcct[:], {asa1}} appl0.ForeignApps = []basics.AppIndex{11} logic.TestApps(t, []string{"", innerCallWithBoth}, txntest.Group(&appl0, &appl), 9, ledger, - logic.NewExpect(1, "appl ForeignApps: invalid Local State access "+otherAcct.String())) + logic.NewExpect(1, "appl ForeignApps: unavailable Local State "+otherAcct.String())) }) @@ -893,17 +852,19 @@ func TestAccessMyLocals(t *testing.T) { app_local_get int 7 == + assert + int 0 + byte "X" + app_local_del + int 1 ` - if ep.Proto.LogicSigVersion >= 9 { + logic.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) } - logic.TestApp(t, source, ep) - // They can also see that they are opted in, though it's a weird question to ask. - if ep.Proto.LogicSigVersion >= 9 { - logic.TestApp(t, "txn Sender; int 0; app_opted_in", ep) - } else { - logic.TestApp(t, "int 0; int 0; app_opted_in", ep) - } + logic.TestApp(t, "int 0; int 0; app_opted_in", ep) }) } diff --git a/data/transactions/transaction.go b/data/transactions/transaction.go index 5877e99c7f..5a397dc7cd 100644 --- a/data/transactions/transaction.go +++ b/data/transactions/transaction.go @@ -118,7 +118,7 @@ type ApplyData struct { // If asa or app is being created, the id used. Else 0. // Names chosen to match naming the corresponding txn. - // These are populated on when MaxInnerTransactions > 0 (TEAL 5) + // These are populated only when MaxInnerTransactions > 0 (TEAL 5) ConfigAsset basics.AssetIndex `codec:"caid"` ApplicationID basics.AppIndex `codec:"apid"` } diff --git a/data/txntest/txn.go b/data/txntest/txn.go index 8e9f699f80..2ad4bf27f3 100644 --- a/data/txntest/txn.go +++ b/data/txntest/txn.go @@ -256,8 +256,8 @@ func (tx Txn) Txn() transactions.Transaction { OnCompletion: tx.OnCompletion, ApplicationArgs: tx.ApplicationArgs, Accounts: tx.Accounts, - ForeignApps: tx.ForeignApps, - ForeignAssets: tx.ForeignAssets, + ForeignApps: append([]basics.AppIndex(nil), tx.ForeignApps...), + ForeignAssets: append([]basics.AssetIndex(nil), tx.ForeignAssets...), Boxes: tx.Boxes, LocalStateSchema: tx.LocalStateSchema, GlobalStateSchema: tx.GlobalStateSchema, diff --git a/ledger/apply/application.go b/ledger/apply/application.go index a887309a45..3522b6080f 100644 --- a/ledger/apply/application.go +++ b/ledger/apply/application.go @@ -379,7 +379,7 @@ func ApplicationCall(ac transactions.ApplicationCallTxnFields, header transactio // Ensure that the only operation we can do is ClearState if the application // does not exist if !exists && ac.OnCompletion != transactions.ClearStateOC { - return fmt.Errorf("only clearing out is supported for applications that do not exist") + return fmt.Errorf("only ClearState is supported for an application (%d) that does not exist", appIdx) } // If this txn is going to set new programs (either for creation or diff --git a/ledger/apply/application_test.go b/ledger/apply/application_test.go index 54f7941081..37c132c135 100644 --- a/ledger/apply/application_test.go +++ b/ledger/apply/application_test.go @@ -546,7 +546,7 @@ func TestAppCallApplyCreate(t *testing.T) { // 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(), "applications that do not exist") + a.Contains(err.Error(), "only ClearState is supported") a.Equal(1, b.put) a.Equal(1, b.putAppParams) diff --git a/ledger/apptxn_test.go b/ledger/apptxn_test.go index 1d356b2cfd..dd4998ff41 100644 --- a/ledger/apptxn_test.go +++ b/ledger/apptxn_test.go @@ -58,8 +58,6 @@ func TestPayAction(t *testing.T) { itxn_submit `)) - require.Equal(t, ai, basics.AppIndex(1)) - payout1 := txntest.Txn{ Type: "appl", Sender: addrs[1], @@ -138,12 +136,11 @@ func TestPayAction(t *testing.T) { for i := 1; i < 10; i++ { dl.fullBlock() } - vb = dl.fullBlock(payout2.Noted("2")) + tib := dl.txn(payout2.Noted("2")) afterpay := micros(dl.t, dl.validator, ai.Address()) - payInBlock = vb.Block().Payset[0] - inners = payInBlock.ApplyData.EvalDelta.InnerTxns + inners = tib.ApplyData.EvalDelta.InnerTxns require.Len(t, inners, 1) appreward := inners[0].SenderRewards.Raw @@ -159,26 +156,24 @@ func TestAxferAction(t *testing.T) { t.Parallel() genBalances, addrs, _ := ledgertesting.NewTestGenesis() - cfg := config.GetDefaultLocal() - l := newSimpleLedgerWithConsensusVersion(t, genBalances, protocol.ConsensusFuture, cfg) - defer l.Close() + // Inner txns start in v30 + ledgertesting.TestConsensusRange(t, 30, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { + dl := NewDoubleLedger(t, genBalances, cv, cfg) + defer dl.Close() - asa := txntest.Txn{ - Type: "acfg", - Sender: addrs[0], - AssetParams: basics.AssetParams{ - Total: 1000000, - Decimals: 3, - UnitName: "oz", - AssetName: "Gold", - URL: "https://gold.rush/", - }, - } + asa := txntest.Txn{ + Type: "acfg", + Sender: addrs[0], + AssetParams: basics.AssetParams{ + Total: 1000000, + Decimals: 3, + UnitName: "oz", + AssetName: "Gold", + URL: "https://gold.rush/", + }, + } - app := txntest.Txn{ - Type: "appl", - Sender: addrs[0], - ApprovalProgram: main(` + source := main(` itxn_begin int axfer itxn_field TypeEnum @@ -207,145 +202,108 @@ skipamount: txn Accounts 1 itxn_field AssetReceiver submit: itxn_submit -`), - } - - eval := nextBlock(t, l) - txns(t, l, eval, &asa, &app) - vb := endBlock(t, l, eval) - - asaIndex := basics.AssetIndex(1) - require.Equal(t, asaIndex, vb.Block().Payset[0].ApplyData.ConfigAsset) - appIndex := basics.AppIndex(2) - require.Equal(t, appIndex, vb.Block().Payset[1].ApplyData.ApplicationID) +`) - fund := txntest.Txn{ - Type: "pay", - Sender: addrs[0], - Receiver: appIndex.Address(), - Amount: 300000, // account min balance, optin min balance, plus fees + asaID := dl.txn(&asa).ApplyData.ConfigAsset + // account min balance, optin min balance, plus fees // stay under 1M, to avoid rewards complications - } - - eval = nextBlock(t, l) - txn(t, l, eval, &fund) - endBlock(t, l, eval) + appID := dl.fundedApp(addrs[0], 300_000, source) - fundgold := txntest.Txn{ - Type: "axfer", - Sender: addrs[0], - XferAsset: asaIndex, - AssetReceiver: appIndex.Address(), - AssetAmount: 20000, - } + fundgold := txntest.Txn{ + Type: "axfer", + Sender: addrs[0], + XferAsset: asaID, + AssetReceiver: appID.Address(), + AssetAmount: 20000, + } - // Fail, because app account is not opted in. - eval = nextBlock(t, l) - txn(t, l, eval, &fundgold, fmt.Sprintf("asset %d missing", asaIndex)) - endBlock(t, l, eval) + // Fail, because app account is not opted in. + dl.txn(&fundgold, fmt.Sprintf("asset %d missing", asaID)) - amount, in := holding(t, l, appIndex.Address(), asaIndex) - require.False(t, in) - require.Equal(t, amount, uint64(0)) + amount, in := holding(t, dl.generator, appID.Address(), asaID) + require.False(t, in) + require.Zero(t, amount) - optin := txntest.Txn{ - Type: "appl", - ApplicationID: appIndex, - Sender: addrs[0], - ApplicationArgs: [][]byte{[]byte("optin")}, - ForeignAssets: []basics.AssetIndex{asaIndex}, - } - - // Tell the app to opt itself in. - eval = nextBlock(t, l) - txn(t, l, eval, &optin) - endBlock(t, l, eval) + // Tell the app to opt itself in. + optin := txntest.Txn{ + Type: "appl", + ApplicationID: appID, + Sender: addrs[0], + ApplicationArgs: [][]byte{[]byte("optin")}, + ForeignAssets: []basics.AssetIndex{asaID}, + } + dl.txn(&optin) - amount, in = holding(t, l, appIndex.Address(), asaIndex) - require.True(t, in) - require.Equal(t, amount, uint64(0)) + amount, in = holding(t, dl.generator, appID.Address(), asaID) + require.True(t, in) + require.Zero(t, amount) - // Now, succeed, because opted in. - eval = nextBlock(t, l) - txn(t, l, eval, &fundgold) - endBlock(t, l, eval) + // Now, succeed, because opted in. + dl.txn(&fundgold) - amount, in = holding(t, l, appIndex.Address(), asaIndex) - require.True(t, in) - require.Equal(t, amount, uint64(20000)) + amount, in = holding(t, dl.generator, appID.Address(), asaID) + require.True(t, in) + require.Equal(t, uint64(20000), amount) - withdraw := txntest.Txn{ - Type: "appl", - ApplicationID: appIndex, - Sender: addrs[0], - ApplicationArgs: [][]byte{[]byte("withdraw")}, - ForeignAssets: []basics.AssetIndex{asaIndex}, - Accounts: []basics.Address{addrs[0]}, - } - eval = nextBlock(t, l) - txn(t, l, eval, &withdraw) - endBlock(t, l, eval) + withdraw := txntest.Txn{ + Type: "appl", + ApplicationID: appID, + Sender: addrs[0], + ApplicationArgs: [][]byte{[]byte("withdraw")}, + ForeignAssets: []basics.AssetIndex{asaID}, + Accounts: []basics.Address{addrs[0]}, + } + dl.txn(&withdraw) - amount, in = holding(t, l, appIndex.Address(), asaIndex) - require.True(t, in) - require.Equal(t, amount, uint64(10000)) + amount, in = holding(t, dl.generator, appID.Address(), asaID) + require.True(t, in) + require.Equal(t, uint64(10000), amount) - eval = nextBlock(t, l) - txn(t, l, eval, withdraw.Noted("2")) - endBlock(t, l, eval) + dl.txn(withdraw.Noted("2")) - amount, in = holding(t, l, appIndex.Address(), asaIndex) - require.True(t, in) // Zero left, but still opted in - require.Equal(t, amount, uint64(0)) + amount, in = holding(t, dl.generator, appID.Address(), asaID) + require.True(t, in) // Zero left, but still opted in + require.Zero(t, amount) - eval = nextBlock(t, l) - txn(t, l, eval, withdraw.Noted("3"), "underflow on subtracting") - endBlock(t, l, eval) + dl.txn(withdraw.Noted("3"), "underflow on subtracting") - amount, in = holding(t, l, appIndex.Address(), asaIndex) - require.True(t, in) // Zero left, but still opted in - require.Equal(t, amount, uint64(0)) + amount, in = holding(t, dl.generator, appID.Address(), asaID) + require.True(t, in) // Zero left, but still opted in + require.Zero(t, amount) - close := txntest.Txn{ - Type: "appl", - ApplicationID: appIndex, - Sender: addrs[0], - ApplicationArgs: [][]byte{[]byte("close")}, - ForeignAssets: []basics.AssetIndex{asaIndex}, - Accounts: []basics.Address{addrs[0]}, - } + close := txntest.Txn{ + Type: "appl", + ApplicationID: appID, + Sender: addrs[0], + ApplicationArgs: [][]byte{[]byte("close")}, + ForeignAssets: []basics.AssetIndex{asaID}, + Accounts: []basics.Address{addrs[0]}, + } - eval = nextBlock(t, l) - txn(t, l, eval, &close) - endBlock(t, l, eval) + dl.txn(&close) - amount, in = holding(t, l, appIndex.Address(), asaIndex) - require.False(t, in) // Zero left, not opted in - require.Equal(t, amount, uint64(0)) + amount, in = holding(t, dl.generator, appID.Address(), asaID) + require.False(t, in) // Zero left, not opted in + require.Zero(t, amount) - // Now, fail again, opted out - eval = nextBlock(t, l) - txn(t, l, eval, fundgold.Noted("2"), fmt.Sprintf("asset %d missing", asaIndex)) - endBlock(t, l, eval) + // Now, fail again, opted out + dl.txn(fundgold.Noted("2"), fmt.Sprintf("asset %d missing", asaID)) - // Do it all again, so we can test closeTo when we have a non-zero balance - // Tell the app to opt itself in. - eval = nextBlock(t, l) - txns(t, l, eval, optin.Noted("a"), fundgold.Noted("a")) - endBlock(t, l, eval) + // Do it all again, so we can test closeTo when we have a non-zero balance + // Tell the app to opt itself in. + dl.txns(optin.Noted("a"), fundgold.Noted("a")) - amount, _ = holding(t, l, appIndex.Address(), asaIndex) - require.Equal(t, uint64(20000), amount) - left, _ := holding(t, l, addrs[0], asaIndex) + amount, _ = holding(t, dl.generator, appID.Address(), asaID) + require.Equal(t, uint64(20000), amount) + left, _ := holding(t, dl.generator, addrs[0], asaID) - eval = nextBlock(t, l) - txn(t, l, eval, close.Noted("a")) - endBlock(t, l, eval) + dl.txn(close.Noted("a")) - amount, _ = holding(t, l, appIndex.Address(), asaIndex) - require.Equal(t, uint64(0), amount) - back, _ := holding(t, l, addrs[0], asaIndex) - require.Equal(t, uint64(20000), back-left) + amount, _ = holding(t, dl.generator, appID.Address(), asaID) + require.Zero(t, amount) + back, _ := holding(t, dl.generator, addrs[0], asaID) + require.Equal(t, uint64(20000), back-left) + }) } // TestClawbackAction ensures an app address can act as clawback address. @@ -354,84 +312,66 @@ func TestClawbackAction(t *testing.T) { t.Parallel() genBalances, addrs, _ := ledgertesting.NewTestGenesis() - l := newTestLedger(t, genBalances) - defer l.Close() - - asaIndex := basics.AssetIndex(1) - appIndex := basics.AppIndex(2) - asa := txntest.Txn{ - Type: "acfg", - Sender: addrs[0], - AssetParams: basics.AssetParams{ - Total: 1000000, - Decimals: 3, - UnitName: "oz", - AssetName: "Gold", - URL: "https://gold.rush/", - Clawback: appIndex.Address(), - }, - } + // 31 allowed inner appl. + ledgertesting.TestConsensusRange(t, 31, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { + dl := NewDoubleLedger(t, genBalances, cv, cfg) + defer dl.Close() - app := txntest.Txn{ - Type: "appl", - Sender: addrs[0], - ApprovalProgram: main(` + app := txntest.Txn{ + Type: "appl", + Sender: addrs[0], + ApprovalProgram: main(` itxn_begin - - int axfer - itxn_field TypeEnum - - txn Assets 0 - itxn_field XferAsset - - txn Accounts 1 - itxn_field AssetSender - - txn Accounts 2 - itxn_field AssetReceiver - - int 1000 - itxn_field AssetAmount - + int axfer; itxn_field TypeEnum + txn Assets 0; itxn_field XferAsset + txn Accounts 1; itxn_field AssetSender + txn Accounts 2; itxn_field AssetReceiver + int 1000; itxn_field AssetAmount itxn_submit `), - } + } + appID := dl.txn(&app).ApplyData.ApplicationID - optin := txntest.Txn{ - Type: "axfer", - Sender: addrs[1], - AssetReceiver: addrs[1], - XferAsset: asaIndex, - } - eval := nextBlock(t, l) - txns(t, l, eval, &asa, &app, &optin) - vb := endBlock(t, l, eval) + asa := txntest.Txn{ + Type: "acfg", + Sender: addrs[0], + AssetParams: basics.AssetParams{ + Total: 1005, + Clawback: appID.Address(), + }, + } + asaID := dl.txn(&asa).ApplyData.ConfigAsset - require.Equal(t, asaIndex, vb.Block().Payset[0].ApplyData.ConfigAsset) - require.Equal(t, appIndex, vb.Block().Payset[1].ApplyData.ApplicationID) + optin := txntest.Txn{ + Type: "axfer", + Sender: addrs[1], + AssetReceiver: addrs[1], + XferAsset: asaID, + } + dl.txn(&optin) - bystander := addrs[2] // Has no authority of its own - overpay := txntest.Txn{ - Type: "pay", - Sender: bystander, - Receiver: bystander, - Fee: 2000, // Overpay fee so that app account can be unfunded - } - clawmove := txntest.Txn{ - Type: "appl", - Sender: bystander, - ApplicationID: appIndex, - ForeignAssets: []basics.AssetIndex{asaIndex}, - Accounts: []basics.Address{addrs[0], addrs[1]}, - } - eval = nextBlock(t, l) - err := txgroup(t, l, eval, &overpay, &clawmove) - require.NoError(t, err) - endBlock(t, l, eval) + bystander := addrs[2] // Has no authority of its own + overpay := txntest.Txn{ + Type: "pay", + Sender: bystander, + Receiver: bystander, + Fee: 2000, // Overpay fee so that app account can be unfunded + } + clawmove := txntest.Txn{ + Type: "appl", + Sender: bystander, + ApplicationID: appID, + ForeignAssets: []basics.AssetIndex{asaID}, + Accounts: []basics.Address{addrs[0], addrs[1]}, + } + dl.txgroup("", &overpay, &clawmove) - amount, _ := holding(t, l, addrs[1], asaIndex) - require.Equal(t, amount, uint64(1000)) + amount, _ := holding(t, dl.generator, addrs[1], asaID) + require.EqualValues(t, 1000, amount) + amount, _ = holding(t, dl.generator, addrs[0], asaID) + require.EqualValues(t, 5, amount) + }) } // TestRekeyAction ensures an app can transact for a rekeyed account @@ -440,102 +380,89 @@ func TestRekeyAction(t *testing.T) { t.Parallel() genBalances, addrs, _ := ledgertesting.NewTestGenesis() - l := newTestLedger(t, genBalances) - defer l.Close() - appIndex := basics.AppIndex(1) - ezpayer := txntest.Txn{ - Type: "appl", - Sender: addrs[5], - ApprovalProgram: main(` + // 30 allowed inner txns. + ledgertesting.TestConsensusRange(t, 30, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { + dl := NewDoubleLedger(t, genBalances, cv, cfg) + defer dl.Close() + + ezpayer := txntest.Txn{ + Type: "appl", + Sender: addrs[5], + ApprovalProgram: main(` itxn_begin - int pay - itxn_field TypeEnum - int 5000 - itxn_field Amount - txn Accounts 1 - itxn_field Sender - txn Accounts 2 - itxn_field Receiver - txn NumAccounts - int 3 - == - bz skipclose - txn Accounts 3 - itxn_field CloseRemainderTo + int pay; itxn_field TypeEnum + int 5000; itxn_field Amount + txn Accounts 1; itxn_field Sender + txn Accounts 2; itxn_field Receiver + txn NumAccounts + int 3 + == + bz skipclose + txn Accounts 3; itxn_field CloseRemainderTo skipclose: itxn_submit `), - } + } + appID := dl.txn(&ezpayer).ApplyData.ApplicationID - rekey := txntest.Txn{ - Type: "pay", - Sender: addrs[0], - Receiver: addrs[0], - RekeyTo: appIndex.Address(), - } + rekey := txntest.Txn{ + Type: "pay", + Sender: addrs[0], + Receiver: addrs[0], + RekeyTo: appID.Address(), + } - eval := nextBlock(t, l) - txns(t, l, eval, &ezpayer, &rekey) - endBlock(t, l, eval) + dl.txn(&rekey) - useacct := txntest.Txn{ - Type: "appl", - Sender: addrs[1], - ApplicationID: appIndex, - Accounts: []basics.Address{addrs[0], addrs[2]}, // pay 2 from 0 (which was rekeyed) - } - eval = nextBlock(t, l) - txn(t, l, eval, &useacct) - endBlock(t, l, eval) + useacct := txntest.Txn{ + Type: "appl", + Sender: addrs[1], + ApplicationID: appID, + Accounts: []basics.Address{addrs[0], addrs[2]}, // pay 2 from 0 (which was rekeyed) + } + dl.txn(&useacct) - // App was never funded (didn't spend from it's own acct) - require.Equal(t, uint64(0), micros(t, l, basics.AppIndex(1).Address())) - // addrs[2] got paid - require.Equal(t, uint64(5000), micros(t, l, addrs[2])-micros(t, l, addrs[6])) - // addrs[0] paid 5k + rekey fee + inner txn fee - require.Equal(t, uint64(7000), micros(t, l, addrs[6])-micros(t, l, addrs[0])) + // App was never funded (didn't spend from it's own acct) + require.Zero(t, micros(t, dl.generator, appID.Address())) + // addrs[2] got paid + require.Equal(t, uint64(5000), micros(t, dl.generator, addrs[2])-micros(t, dl.generator, addrs[6])) + // addrs[0] paid 5k + rekey fee + inner txn fee + require.Equal(t, uint64(7000), micros(t, dl.generator, addrs[6])-micros(t, dl.generator, addrs[0])) - baduse := txntest.Txn{ - Type: "appl", - Sender: addrs[1], - ApplicationID: appIndex, - Accounts: []basics.Address{addrs[2], addrs[0]}, // pay 0 from 2 - } - eval = nextBlock(t, l) - txn(t, l, eval, &baduse, "unauthorized") - endBlock(t, l, eval) + baduse := txntest.Txn{ + Type: "appl", + Sender: addrs[1], + ApplicationID: appID, + Accounts: []basics.Address{addrs[2], addrs[0]}, // pay 0 from 2 + } + dl.txn(&baduse, "unauthorized") - // Now, we close addrs[0], which wipes its rekey status. Reopen - // it, and make sure the app can't spend. + // Now, we close addrs[0], which wipes its rekey status. Reopen + // it, and make sure the app can't spend. - close := txntest.Txn{ - Type: "appl", - Sender: addrs[1], - ApplicationID: appIndex, - Accounts: []basics.Address{addrs[0], addrs[2], addrs[3]}, // close to 3 - } - eval = nextBlock(t, l) - txn(t, l, eval, &close) - endBlock(t, l, eval) + close := txntest.Txn{ + Type: "appl", + Sender: addrs[1], + ApplicationID: appID, + Accounts: []basics.Address{addrs[0], addrs[2], addrs[3]}, // close to 3 + } + dl.txn(&close) - require.Equal(t, uint64(0), micros(t, l, addrs[0])) + require.Zero(t, micros(t, dl.generator, addrs[0])) - payback := txntest.Txn{ - Type: "pay", - Sender: addrs[3], - Receiver: addrs[0], - Amount: 10_000_000, - } - eval = nextBlock(t, l) - txn(t, l, eval, &payback) - endBlock(t, l, eval) + payback := txntest.Txn{ + Type: "pay", + Sender: addrs[3], + Receiver: addrs[0], + Amount: 10_000_000, + } + dl.txn(&payback) - require.Equal(t, uint64(10_000_000), micros(t, l, addrs[0])) + require.Equal(t, uint64(10_000_000), micros(t, dl.generator, addrs[0])) - eval = nextBlock(t, l) - txn(t, l, eval, useacct.Noted("2"), "unauthorized") - endBlock(t, l, eval) + dl.txn(useacct.Noted("2"), "unauthorized") + }) } // TestRekeyActionCloseAccount ensures closing and reopening a rekeyed account in a single app call @@ -545,74 +472,58 @@ func TestRekeyActionCloseAccount(t *testing.T) { t.Parallel() genBalances, addrs, _ := ledgertesting.NewTestGenesis() - l := newTestLedger(t, genBalances) - defer l.Close() - appIndex := basics.AppIndex(1) - create := txntest.Txn{ - Type: "appl", - Sender: addrs[5], - ApprovalProgram: main(` - // close account 1 + // 30 allowed inner txs. + ledgertesting.TestConsensusRange(t, 30, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { + dl := NewDoubleLedger(t, genBalances, cv, cfg) + defer dl.Close() + + // use addrs[5] for creation, so addr[0] will be closeable + appID := dl.fundedApp(addrs[5], 1_000_000, + main(` + // pay from, and close, account 1 itxn_begin - int pay - itxn_field TypeEnum - txn Accounts 1 - itxn_field Sender - txn Accounts 2 - itxn_field CloseRemainderTo + int pay; itxn_field TypeEnum + txn Accounts 1; itxn_field Sender + txn Accounts 2; itxn_field CloseRemainderTo itxn_submit // reopen account 1 itxn_begin - int pay - itxn_field TypeEnum - int 5000 - itxn_field Amount - txn Accounts 1 - itxn_field Receiver + int pay; itxn_field TypeEnum + int 5000; itxn_field Amount + txn Accounts 1; itxn_field Receiver itxn_submit + // send from account 1 again (should fail because closing an account erases rekeying) itxn_begin - int pay - itxn_field TypeEnum - int 1 - itxn_field Amount - txn Accounts 1 - itxn_field Sender - txn Accounts 2 - itxn_field Receiver + int pay; itxn_field TypeEnum + int 1; itxn_field Amount + txn Accounts 1; itxn_field Sender + txn Accounts 2; itxn_field Receiver itxn_submit -`), - } - - rekey := txntest.Txn{ - Type: "pay", - Sender: addrs[0], - Receiver: addrs[0], - RekeyTo: appIndex.Address(), - } - - fund := txntest.Txn{ - Type: "pay", - Sender: addrs[1], - Receiver: appIndex.Address(), - Amount: 1_000_000, - } +`)) - eval := nextBlock(t, l) - txns(t, l, eval, &create, &rekey, &fund) - endBlock(t, l, eval) + // rekey addr[1] to the app + dl.txn(&txntest.Txn{ + Type: "pay", + Sender: addrs[0], + Receiver: addrs[0], + RekeyTo: appID.Address(), + }) - useacct := txntest.Txn{ - Type: "appl", - Sender: addrs[1], - ApplicationID: appIndex, - Accounts: []basics.Address{addrs[0], addrs[2]}, - } - eval = nextBlock(t, l) - txn(t, l, eval, &useacct, "unauthorized") - endBlock(t, l, eval) + useacct := txntest.Txn{ + Type: "appl", + Sender: addrs[1], + ApplicationID: appID, + Accounts: []basics.Address{addrs[0], addrs[2]}, + } + dl.txn(&useacct, "unauthorized") + // do it again, to ensure the lack of authorization is in the right + // place, by matching on the opcode that comes before the itxn_submit we + // want to know failed (it'll be in the error). + dl.txn(&useacct, "itxn_field Receiver") + }) } // TestDuplicatePayAction shows two pays with same parameters can be done as inner tarnsactions @@ -621,74 +532,52 @@ func TestDuplicatePayAction(t *testing.T) { t.Parallel() genBalances, addrs, _ := ledgertesting.NewTestGenesis() - l := newTestLedger(t, genBalances) - defer l.Close() + // Inner txns start in v30 + ledgertesting.TestConsensusRange(t, 30, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { + dl := NewDoubleLedger(t, genBalances, cv, cfg) + defer dl.Close() - appIndex := basics.AppIndex(1) - create := txntest.Txn{ - Type: "appl", - Sender: addrs[0], - ApprovalProgram: main(` + source := main(` itxn_begin - int pay - itxn_field TypeEnum - int 5000 - itxn_field Amount - txn Accounts 1 - itxn_field Receiver + int pay; itxn_field TypeEnum + int 5000; itxn_field Amount + txn Accounts 1; itxn_field Receiver itxn_submit itxn_begin - int pay - itxn_field TypeEnum - int 5000 - itxn_field Amount - txn Accounts 1 - itxn_field Receiver + int pay; itxn_field TypeEnum + int 5000; itxn_field Amount + txn Accounts 1; itxn_field Receiver itxn_submit -`), - } +`) + appID := dl.fundedApp(addrs[0], 200_000, source) - fund := txntest.Txn{ - Type: "pay", - Sender: addrs[0], - Receiver: appIndex.Address(), - Amount: 200000, // account min balance, plus fees - } - - paytwice := txntest.Txn{ - Type: "appl", - Sender: addrs[1], - ApplicationID: appIndex, - Accounts: []basics.Address{addrs[1]}, // pay self - } - - eval := nextBlock(t, l) - txns(t, l, eval, &create, &fund, &paytwice, create.Noted("in same block")) - vb := endBlock(t, l, eval) - - require.Equal(t, appIndex, vb.Block().Payset[0].ApplyData.ApplicationID) - require.Equal(t, 4, len(vb.Block().Payset)) - // create=1, fund=2, payTwice=3,4,5 - require.Equal(t, basics.AppIndex(6), vb.Block().Payset[3].ApplyData.ApplicationID) + paytwice := txntest.Txn{ + Type: "appl", + Sender: addrs[1], + ApplicationID: appID, + Accounts: []basics.Address{addrs[1]}, // pay self + } - ad0 := micros(t, l, addrs[0]) - ad1 := micros(t, l, addrs[1]) - app := micros(t, l, appIndex.Address()) + dl.txn(&paytwice) + copyID := dl.fundedApp(addrs[0], 200_000, source) + require.Equal(t, appID+5, copyID) // 4 between (fund, outer, two innner pays) - // create(1000) and fund(1000 + 200000), extra create (1000) - require.Equal(t, 203000, int(genBalances.Balances[addrs[0]].MicroAlgos.Raw-ad0)) - // paid 10000, but 1000 fee on tx - require.Equal(t, 9000, int(ad1-genBalances.Balances[addrs[1]].MicroAlgos.Raw)) - // app still has 188000 (paid out 10000, and paid 2 x fee to do it) - require.Equal(t, 188000, int(app)) + ad0 := micros(t, dl.generator, addrs[0]) + ad1 := micros(t, dl.generator, addrs[1]) + app := micros(t, dl.generator, appID.Address()) - // Now create another app, and see if it gets the index we expect. - eval = nextBlock(t, l) - txns(t, l, eval, create.Noted("again")) - vb = endBlock(t, l, eval) + // create(1000) and fund(1000 + 200000), extra create+fund (1000 + 201000) + require.Equal(t, 404000, int(genBalances.Balances[addrs[0]].MicroAlgos.Raw-ad0)) + // paid 10000, but 1000 fee on tx + require.Equal(t, 9000, int(ad1-genBalances.Balances[addrs[1]].MicroAlgos.Raw)) + // app still has 188000 (paid out 10000, and paid 2 x fee to do it) + require.Equal(t, 188000, int(app)) - // create=1, fund=2, payTwice=3,4,5, insameblock=6 - require.Equal(t, basics.AppIndex(7), vb.Block().Payset[0].ApplyData.ApplicationID) + // Now create another app, and see if it gets the ID we expect (2 + // higher, because of the intervening fund txn) + finalID := dl.fundedApp(addrs[0], 200_000, source) + require.Equal(t, copyID+2, finalID) + }) } // TestInnerTxCount ensures that inner transactions increment the TxnCounter @@ -697,13 +586,14 @@ func TestInnerTxnCount(t *testing.T) { t.Parallel() genBalances, addrs, _ := ledgertesting.NewTestGenesis() - l := newTestLedger(t, genBalances) - defer l.Close() - create := txntest.Txn{ - Type: "appl", - Sender: addrs[0], - ApprovalProgram: main(` + // 30 allowed inner txs. + ledgertesting.TestConsensusRange(t, 30, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { + dl := NewDoubleLedger(t, genBalances, cv, cfg) + defer dl.Close() + + appID := dl.fundedApp(addrs[0], 200000, // account min balance, plus fees + main(` itxn_begin int pay itxn_field TypeEnum @@ -712,32 +602,20 @@ func TestInnerTxnCount(t *testing.T) { txn Accounts 1 itxn_field Receiver itxn_submit -`), - } - - fund := txntest.Txn{ - Type: "pay", - Sender: addrs[0], - Receiver: basics.AppIndex(1).Address(), - Amount: 200000, // account min balance, plus fees - } - - payout1 := txntest.Txn{ - Type: "appl", - Sender: addrs[1], - ApplicationID: basics.AppIndex(1), - Accounts: []basics.Address{addrs[1]}, // pay self - } +`)) - eval := nextBlock(t, l) - txns(t, l, eval, &create, &fund) - vb := endBlock(t, l, eval) - require.Equal(t, 2, int(vb.Block().TxnCounter)) + payout1 := txntest.Txn{ + Type: "appl", + Sender: addrs[1], + ApplicationID: appID, + Accounts: []basics.Address{addrs[1]}, // pay self + } - eval = nextBlock(t, l) - txns(t, l, eval, &payout1) - vb = endBlock(t, l, eval) - require.Equal(t, 4, int(vb.Block().TxnCounter)) + vb := dl.fullBlock(&payout1) + before := vb.Block().TxnCounter + vb = dl.fullBlock(payout1.Noted("again")) + require.Equal(t, before+2, vb.Block().TxnCounter) + }) } // TestAcfgAction ensures assets can be created and configured in teal @@ -746,14 +624,14 @@ func TestAcfgAction(t *testing.T) { t.Parallel() genBalances, addrs, _ := ledgertesting.NewTestGenesis() - l := newTestLedger(t, genBalances) - defer l.Close() - appIndex := basics.AppIndex(1) - app := txntest.Txn{ - Type: "appl", - Sender: addrs[0], - ApprovalProgram: main(` + // 30 allowed inner txs. + ledgertesting.TestConsensusRange(t, 30, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { + dl := NewDoubleLedger(t, genBalances, cv, cfg) + defer dl.Close() + + appID := dl.fundedApp(addrs[0], 200_000, // exactly account min balance + one asset + main(` itxn_begin int acfg itxn_field TypeEnum @@ -845,73 +723,59 @@ clawback: b submit error: err submit: itxn_submit -`), - } - - fund := txntest.Txn{ - Type: "pay", - Sender: addrs[0], - Receiver: appIndex.Address(), - Amount: 200_000, // exactly account min balance + one asset - } - - eval := nextBlock(t, l) - txns(t, l, eval, &app, &fund) - endBlock(t, l, eval) - - createAsa := txntest.Txn{ - Type: "appl", - Sender: addrs[1], - ApplicationID: appIndex, - ApplicationArgs: [][]byte{[]byte("create")}, - } - - eval = nextBlock(t, l) - // Can't create an asset if you have exactly 200,000 and need to pay fee - txn(t, l, eval, &createAsa, "balance 199000 below min 200000") - // fund it some more and try again - txns(t, l, eval, fund.Noted("more!"), &createAsa) - vb := endBlock(t, l, eval) - - asaIndex := vb.Block().Payset[1].EvalDelta.InnerTxns[0].ConfigAsset - require.Equal(t, basics.AssetIndex(5), asaIndex) +`)) - asaParams, err := asaParams(t, l, basics.AssetIndex(5)) - require.NoError(t, err) + createAsa := txntest.Txn{ + Type: "appl", + Sender: addrs[1], + ApplicationID: appID, + ApplicationArgs: [][]byte{[]byte("create")}, + } - require.Equal(t, 1_000_000, int(asaParams.Total)) - require.Equal(t, 3, int(asaParams.Decimals)) - require.Equal(t, "oz", asaParams.UnitName) - require.Equal(t, "Gold", asaParams.AssetName) - require.Equal(t, "https://gold.rush/", asaParams.URL) + // Can't create an asset if you have exactly 200,000 and need to pay fee + dl.txn(&createAsa, "balance 199000 below min 200000") + // add some more + dl.txn(&txntest.Txn{ + Type: "pay", + Sender: addrs[0], + Receiver: appID.Address(), + Amount: 10_000, + }) + asaID := dl.txn(&createAsa).EvalDelta.InnerTxns[0].ConfigAsset + require.NotZero(t, asaID) - require.Equal(t, appIndex.Address(), asaParams.Manager) + asaParams, err := asaParams(t, dl.generator, asaID) + require.NoError(t, err) - for _, a := range []string{"reserve", "freeze", "clawback", "manager"} { - check := txntest.Txn{ + require.Equal(t, 1_000_000, int(asaParams.Total)) + require.Equal(t, 3, int(asaParams.Decimals)) + require.Equal(t, "oz", asaParams.UnitName) + require.Equal(t, "Gold", asaParams.AssetName) + require.Equal(t, "https://gold.rush/", asaParams.URL) + + require.Equal(t, appID.Address(), asaParams.Manager) + + for _, a := range []string{"reserve", "freeze", "clawback", "manager"} { + check := txntest.Txn{ + Type: "appl", + Sender: addrs[1], + ApplicationID: appID, + ApplicationArgs: [][]byte{[]byte(a), []byte("junkjunkjunkjunkjunkjunkjunkjunk")}, + ForeignAssets: []basics.AssetIndex{asaID}, + } + t.Log(a) + dl.txn(&check) + } + // Not the manager anymore so this won't work + nodice := txntest.Txn{ Type: "appl", Sender: addrs[1], - ApplicationID: appIndex, - ApplicationArgs: [][]byte{[]byte(a), []byte("junkjunkjunkjunkjunkjunkjunkjunk")}, - ForeignAssets: []basics.AssetIndex{asaIndex}, + ApplicationID: appID, + ApplicationArgs: [][]byte{[]byte("freeze"), []byte("junkjunkjunkjunkjunkjunkjunkjunk")}, + ForeignAssets: []basics.AssetIndex{asaID}, } - eval = nextBlock(t, l) - t.Log(a) - txn(t, l, eval, &check) - endBlock(t, l, eval) - } - // Not the manager anymore so this won't work - nodice := txntest.Txn{ - Type: "appl", - Sender: addrs[1], - ApplicationID: appIndex, - ApplicationArgs: [][]byte{[]byte("freeze"), []byte("junkjunkjunkjunkjunkjunkjunkjunk")}, - ForeignAssets: []basics.AssetIndex{asaIndex}, - } - eval = nextBlock(t, l) - txn(t, l, eval, &nodice, "this transaction should be issued by the manager") - endBlock(t, l, eval) - + dl.txn(&nodice, "this transaction should be issued by the manager") + }) } // TestAsaDuringInit ensures an ASA can be made while initilizing an @@ -923,67 +787,62 @@ func TestAsaDuringInit(t *testing.T) { t.Parallel() genBalances, addrs, _ := ledgertesting.NewTestGenesis() - l := newTestLedger(t, genBalances) - defer l.Close() - appIndex := basics.AppIndex(2) - prefund := txntest.Txn{ - Type: "pay", - Sender: addrs[0], - Receiver: appIndex.Address(), - Amount: 300000, // plenty for min balances, fees - } + // 30 allowed inner txs. + ledgertesting.TestConsensusRange(t, 30, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { + dl := NewDoubleLedger(t, genBalances, cv, cfg) + defer dl.Close() - app := txntest.Txn{ - Type: "appl", - Sender: addrs[0], - ApprovalProgram: ` + appID := basics.AppIndex(2) + if ver >= 37 { // AppForbidLowResources + appID += 1000 + } + prefund := txntest.Txn{ + Type: "pay", + Sender: addrs[0], + Receiver: appID.Address(), + Amount: 300000, // plenty for min balances, fees + } + + app := txntest.Txn{ + Type: "appl", + Sender: addrs[0], + ApprovalProgram: ` itxn_begin - int acfg - itxn_field TypeEnum - int 1000000 - itxn_field ConfigAssetTotal - byte "oz" - itxn_field ConfigAssetUnitName - byte "Gold" - itxn_field ConfigAssetName + int acfg; itxn_field TypeEnum + int 1000000; itxn_field ConfigAssetTotal + byte "oz"; itxn_field ConfigAssetUnitName + byte "Gold"; itxn_field ConfigAssetName itxn_submit itxn CreatedAssetID - int 3 - == - assert - itxn CreatedApplicationID - int 0 + int ` + strconv.Itoa(int(appID+1)) + ` == assert - itxn NumLogs - int 0 - == -`, - } - - eval := nextBlock(t, l) - txns(t, l, eval, &prefund, &app) - vb := endBlock(t, l, eval) + itxn CreatedApplicationID; int 0; ==; assert + itxn NumLogs; int 0; ==`, + } - require.Equal(t, appIndex, vb.Block().Payset[1].ApplicationID) + payset := dl.txns(&prefund, &app) + require.Equal(t, appID, payset[1].ApplicationID) - asaIndex := vb.Block().Payset[1].EvalDelta.InnerTxns[0].ConfigAsset - require.Equal(t, basics.AssetIndex(3), asaIndex) + asaID := payset[1].EvalDelta.InnerTxns[0].ConfigAsset + require.EqualValues(t, appID+1, asaID) + }) } -func TestRekey(t *testing.T) { +func TestInnerRekey(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() genBalances, addrs, _ := ledgertesting.NewTestGenesis() - l := newTestLedger(t, genBalances) - defer l.Close() - app := txntest.Txn{ - Type: "appl", - Sender: addrs[0], - ApprovalProgram: main(` + // 31 allowed inner rekeys. + ledgertesting.TestConsensusRange(t, 31, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { + dl := NewDoubleLedger(t, genBalances, cv, cfg) + defer dl.Close() + + appID := dl.fundedApp(addrs[0], 1_000_000, + main(` itxn_begin int pay itxn_field TypeEnum @@ -997,31 +856,17 @@ func TestRekey(t *testing.T) { concat itxn_field RekeyTo itxn_submit -`), - } - - eval := nextBlock(t, l) - txns(t, l, eval, &app) - vb := endBlock(t, l, eval) - appIndex := vb.Block().Payset[0].ApplicationID - require.Equal(t, basics.AppIndex(1), appIndex) - - fund := txntest.Txn{ - Type: "pay", - Sender: addrs[0], - Receiver: appIndex.Address(), - Amount: 1_000_000, - } - rekey := txntest.Txn{ - Type: "appl", - Sender: addrs[1], - ApplicationID: appIndex, - } - eval = nextBlock(t, l) - txns(t, l, eval, &fund, &rekey) - txn(t, l, eval, rekey.Noted("2"), "unauthorized") - endBlock(t, l, eval) +`)) + require.NotZero(t, appID) + rekey := txntest.Txn{ + Type: "appl", + Sender: addrs[1], + ApplicationID: appID, + } + dl.fullBlock(&rekey) + dl.txn(rekey.Noted("2"), "unauthorized") + }) } func TestNote(t *testing.T) { @@ -1029,13 +874,14 @@ func TestNote(t *testing.T) { t.Parallel() genBalances, addrs, _ := ledgertesting.NewTestGenesis() - l := newTestLedger(t, genBalances) - defer l.Close() - app := txntest.Txn{ - Type: "appl", - Sender: addrs[0], - ApprovalProgram: main(` + // 31 allowed inner note setting. + ledgertesting.TestConsensusRange(t, 31, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { + dl := NewDoubleLedger(t, genBalances, cv, cfg) + defer dl.Close() + + appID := dl.fundedApp(addrs[0], 1_000_000, + main(` itxn_begin int pay itxn_field TypeEnum @@ -1046,31 +892,17 @@ func TestNote(t *testing.T) { byte "abcdefghijklmnopqrstuvwxyz01234567890" itxn_field Note itxn_submit -`), - } +`)) - eval := nextBlock(t, l) - txns(t, l, eval, &app) - vb := endBlock(t, l, eval) - appIndex := vb.Block().Payset[0].ApplicationID - require.Equal(t, basics.AppIndex(1), appIndex) - - fund := txntest.Txn{ - Type: "pay", - Sender: addrs[0], - Receiver: appIndex.Address(), - Amount: 1_000_000, - } - note := txntest.Txn{ - Type: "appl", - Sender: addrs[1], - ApplicationID: appIndex, - } - eval = nextBlock(t, l) - txns(t, l, eval, &fund, ¬e) - vb = endBlock(t, l, eval) - alphabet := vb.Block().Payset[1].EvalDelta.InnerTxns[0].Txn.Note - require.Equal(t, "abcdefghijklmnopqrstuvwxyz01234567890", string(alphabet)) + note := txntest.Txn{ + Type: "appl", + Sender: addrs[1], + ApplicationID: appID, + } + + alphabet := dl.txn(¬e).EvalDelta.InnerTxns[0].Txn.Note + require.Equal(t, "abcdefghijklmnopqrstuvwxyz01234567890", string(alphabet)) + }) } func TestKeyreg(t *testing.T) { @@ -1113,21 +945,21 @@ nonpart: eval := nextBlock(t, l) txns(t, l, eval, &app) vb := endBlock(t, l, eval) - appIndex := vb.Block().Payset[0].ApplicationID - require.Equal(t, basics.AppIndex(1), appIndex) + appID := vb.Block().Payset[0].ApplicationID + require.NotZero(t, appID) // Give the app a lot of money fund := txntest.Txn{ Type: "pay", Sender: addrs[0], - Receiver: appIndex.Address(), + Receiver: appID.Address(), Amount: 1_000_000_000, } eval = nextBlock(t, l) txn(t, l, eval, &fund) endBlock(t, l, eval) - require.Equal(t, 1_000_000_000, int(micros(t, l, appIndex.Address()))) + require.Equal(t, 1_000_000_000, int(micros(t, l, appID.Address()))) // Build up Residue in RewardsState so it's ready to pay for i := 1; i < 10; i++ { @@ -1139,26 +971,26 @@ nonpart: pay := txntest.Txn{ Type: "appl", Sender: addrs[0], - ApplicationID: appIndex, + ApplicationID: appID, ApplicationArgs: [][]byte{[]byte("pay")}, } eval = nextBlock(t, l) txn(t, l, eval, &pay) endBlock(t, l, eval) // 2000 was earned in rewards (- 1000 fee, -1 pay) - require.Equal(t, 1_000_000_999, int(micros(t, l, appIndex.Address()))) + require.Equal(t, 1_000_000_999, int(micros(t, l, appID.Address()))) // Go nonpart nonpart := txntest.Txn{ Type: "appl", Sender: addrs[0], - ApplicationID: appIndex, + ApplicationID: appID, ApplicationArgs: [][]byte{[]byte("nonpart")}, } eval = nextBlock(t, l) txn(t, l, eval, &nonpart) endBlock(t, l, eval) - require.Equal(t, 999_999_999, int(micros(t, l, appIndex.Address()))) + require.Equal(t, 999_999_999, int(micros(t, l, appID.Address()))) // Build up Residue in RewardsState so it's ready to pay AGAIN // But expect no rewards @@ -1171,7 +1003,7 @@ nonpart: txn(t, l, eval, nonpart.Noted("again"), "cannot change online/offline") endBlock(t, l, eval) // Paid fee + 1. Did not get rewards - require.Equal(t, 999_998_998, int(micros(t, l, appIndex.Address()))) + require.Equal(t, 999_998_998, int(micros(t, l, appID.Address()))) } func TestInnerAppCall(t *testing.T) { @@ -1199,7 +1031,7 @@ func TestInnerAppCall(t *testing.T) { eval := nextBlock(t, l) txn(t, l, eval, &app0) vb := endBlock(t, l, eval) - index0 := vb.Block().Payset[0].ApplicationID + id0 := vb.Block().Payset[0].ApplicationID app1 := txntest.Txn{ Type: "appl", @@ -1217,22 +1049,22 @@ func TestInnerAppCall(t *testing.T) { eval = nextBlock(t, l) txns(t, l, eval, &app1) vb = endBlock(t, l, eval) - index1 := vb.Block().Payset[0].ApplicationID + id1 := vb.Block().Payset[0].ApplicationID fund0 := txntest.Txn{ Type: "pay", Sender: addrs[0], - Receiver: index0.Address(), + Receiver: id0.Address(), Amount: 1_000_000_000, } fund1 := fund0 - fund1.Receiver = index1.Address() + fund1.Receiver = id1.Address() call1 := txntest.Txn{ Type: "appl", Sender: addrs[2], - ApplicationID: index1, - ForeignApps: []basics.AppIndex{index0}, + ApplicationID: id1, + ForeignApps: []basics.AppIndex{id0}, } eval = nextBlock(t, l) txns(t, l, eval, &fund0, &fund1, &call1) @@ -1247,15 +1079,17 @@ func TestInnerAppManipulate(t *testing.T) { t.Parallel() genBalances, addrs, _ := ledgertesting.NewTestGenesis() - l := newTestLedger(t, genBalances) - defer l.Close() - calleeIndex := basics.AppIndex(1) - callee := txntest.Txn{ - Type: "appl", - Sender: addrs[0], - // This app set a global key arg[1] to arg[2] or get arg[1] and log it - ApprovalProgram: main(` + // 31 allowed inner appl. + ledgertesting.TestConsensusRange(t, 31, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { + dl := NewDoubleLedger(t, genBalances, cv, cfg) + defer dl.Close() + + callee := txntest.Txn{ + Type: "appl", + Sender: addrs[0], + // This app set a global key arg[1] to arg[2] or get arg[1] and log it + ApprovalProgram: main(` txn ApplicationArgs 0 byte "set" == @@ -1276,26 +1110,23 @@ next1: next2: err `), - GlobalStateSchema: basics.StateSchema{ - NumByteSlice: 1, - }, - } - fund := txntest.Txn{ - Type: "pay", - Sender: addrs[0], - Receiver: calleeIndex.Address(), - Amount: 1_000_000, - } - eval := nextBlock(t, l) - txns(t, l, eval, &callee, &fund) - vb := endBlock(t, l, eval) - require.Equal(t, calleeIndex, vb.Block().Payset[0].ApplicationID) + GlobalStateSchema: basics.StateSchema{ + NumByteSlice: 1, + }, + } - callerIndex := basics.AppIndex(3) - caller := txntest.Txn{ - Type: "appl", - Sender: addrs[0], - ApprovalProgram: main(` + calleeIndex := dl.txn(&callee).ApplyData.ApplicationID + require.NotZero(t, calleeIndex) + + fund := txntest.Txn{ + Type: "pay", + Sender: addrs[0], + Receiver: calleeIndex.Address(), + Amount: 1_000_000, + } + dl.fullBlock(&fund) + + callerIndex := dl.fundedApp(addrs[0], 1_000_000, main(` itxn_begin int appl itxn_field TypeEnum @@ -1313,37 +1144,28 @@ next2: == assert b end -`), - } - fund.Receiver = callerIndex.Address() - - eval = nextBlock(t, l) - txns(t, l, eval, &caller, &fund) - vb = endBlock(t, l, eval) - require.Equal(t, callerIndex, vb.Block().Payset[0].ApplicationID) +`)) - call := txntest.Txn{ - Type: "appl", - Sender: addrs[0], - ApplicationID: callerIndex, - ForeignApps: []basics.AppIndex{calleeIndex}, - } - eval = nextBlock(t, l) - txns(t, l, eval, &call) - vb = endBlock(t, l, eval) - tib := vb.Block().Payset[0] - // No changes in the top-level EvalDelta - require.Empty(t, tib.EvalDelta.GlobalDelta) - require.Empty(t, tib.EvalDelta.LocalDeltas) - - inner := tib.EvalDelta.InnerTxns[0] - require.Empty(t, inner.EvalDelta.LocalDeltas) - - require.Len(t, inner.EvalDelta.GlobalDelta, 1) - require.Equal(t, basics.ValueDelta{ - Action: basics.SetBytesAction, - Bytes: "A", - }, inner.EvalDelta.GlobalDelta["X"]) + call := txntest.Txn{ + Type: "appl", + Sender: addrs[0], + ApplicationID: callerIndex, + ForeignApps: []basics.AppIndex{calleeIndex}, + } + tib := dl.txn(&call) + // No changes in the top-level EvalDelta + require.Empty(t, tib.EvalDelta.GlobalDelta) + require.Empty(t, tib.EvalDelta.LocalDeltas) + + inner := tib.EvalDelta.InnerTxns[0] + require.Empty(t, inner.EvalDelta.LocalDeltas) + + require.Len(t, inner.EvalDelta.GlobalDelta, 1) + require.Equal(t, basics.ValueDelta{ + Action: basics.SetBytesAction, + Bytes: "A", + }, inner.EvalDelta.GlobalDelta["X"]) + }) } // TestCreateAndUse checks that an ASA can be created in an early tx, and then @@ -1359,63 +1181,40 @@ func TestCreateAndUse(t *testing.T) { dl := NewDoubleLedger(t, genBalances, cv, cfg) defer dl.Close() - createapp := txntest.Txn{ - Type: "appl", - Sender: addrs[0], - ApprovalProgram: main(` + appID := dl.fundedApp(addrs[0], 1_000_000, main(` itxn_begin - int axfer; itxn_field TypeEnum - int 0; itxn_field Amount - gaid 0; itxn_field XferAsset - global CurrentApplicationAddress; itxn_field Sender - global CurrentApplicationAddress; itxn_field AssetReceiver + int axfer; itxn_field TypeEnum + int 0; itxn_field Amount + gaid 0; itxn_field XferAsset + global CurrentApplicationAddress; itxn_field Sender + global CurrentApplicationAddress; itxn_field AssetReceiver itxn_submit -`), - } - appIndex := basics.AppIndex(1) - - fund := txntest.Txn{ - Type: "pay", - Sender: addrs[0], - Receiver: appIndex.Address(), - Amount: 1_000_000, - } +`)) createasa := txntest.Txn{ Type: "acfg", Sender: addrs[0], AssetParams: basics.AssetParams{ - Total: 1000000, - Decimals: 3, - UnitName: "oz", - AssetName: "Gold", - URL: "https://gold.rush/", + Total: 1000000, }, } - asaIndex := basics.AssetIndex(3) + asaID := basics.AssetIndex(appID + 2) // accounts for intervening fund txn use := txntest.Txn{ Type: "appl", Sender: addrs[0], - ApplicationID: basics.AppIndex(1), + ApplicationID: appID, // The point of this test is to show the following (psychic) setting is unnecessary. - //ForeignAssets: []basics.AssetIndex{asaIndex}, + //ForeignAssets: []basics.AssetIndex{asaID}, } - dl.beginBlock() - dl.txn(&createapp) - dl.txn(&fund) if ver == 30 { - dl.txgroup("invalid Asset reference", &createasa, &use) - dl.endBlock() + dl.txgroup("unavailable Asset", &createasa, &use) return } // v31 onward, create & use works - dl.txgroup("", &createasa, &use) - vb := dl.endBlock() - - require.Equal(t, appIndex, vb.Block().Payset[0].ApplyData.ApplicationID) - require.Equal(t, asaIndex, vb.Block().Payset[2].ApplyData.ConfigAsset) + payset := dl.txgroup("", &createasa, &use) + require.Equal(t, asaID, payset[0].ApplyData.ConfigAsset) }) } @@ -1424,31 +1223,21 @@ func TestGtxnEffects(t *testing.T) { t.Parallel() genBalances, addrs, _ := ledgertesting.NewTestGenesis() - // At 30 `gtxn CreatedAssetId is illegal, then from v31 it works. + // At 30 `gtxn CreatedAssetID` is illegal, then from v31 it works. ledgertesting.TestConsensusRange(t, 30, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { dl := NewDoubleLedger(t, genBalances, cv, cfg) defer dl.Close() - createapp := txntest.Txn{ - Type: "appl", - Sender: addrs[0], - ApprovalProgram: main(` + // needed in very first app, so hardcode + asaID := basics.AssetIndex(3) + if ver >= 37 { + asaID += 1000 + } + appID := dl.fundedApp(addrs[0], 1_000_000, main(` gtxn 0 CreatedAssetID - int 3 + int `+strconv.Itoa(int(asaID))+` == - assert`), - } - appIndex := basics.AppIndex(1) - - fund := txntest.Txn{ - Type: "pay", - Sender: addrs[0], - Receiver: appIndex.Address(), - Amount: 1_000_000, - } - - dl.beginBlock() - dl.txns(&createapp, &fund) + assert`)) createasa := txntest.Txn{ Type: "acfg", @@ -1461,24 +1250,18 @@ func TestGtxnEffects(t *testing.T) { URL: "https://gold.rush/", }, } - asaIndex := basics.AssetIndex(3) - see := txntest.Txn{ Type: "appl", Sender: addrs[0], - ApplicationID: basics.AppIndex(1), + ApplicationID: appID, } if ver == 30 { dl.txgroup("Unable to obtain effects from top-level transactions", &createasa, &see) - dl.endBlock() return } - dl.txgroup("", &createasa, &see) - vb := dl.endBlock() - - require.Equal(t, appIndex, vb.Block().Payset[0].ApplyData.ApplicationID) - require.Equal(t, asaIndex, vb.Block().Payset[2].ApplyData.ConfigAsset) + payset := dl.txgroup("", &createasa, &see) + require.Equal(t, asaID, payset[0].ApplyData.ConfigAsset) }) } @@ -1502,14 +1285,13 @@ func TestBasicReentry(t *testing.T) { itxn_field ApplicationID itxn_submit`), } - vb := dl.fullBlock(&app0) - index0 := vb.Block().Payset[0].ApplicationID + id0 := dl.txn(&app0).ApplyData.ApplicationID call1 := txntest.Txn{ Type: "appl", Sender: addrs[2], - ApplicationID: index0, - ForeignApps: []basics.AppIndex{index0}, + ApplicationID: id0, + ForeignApps: []basics.AppIndex{id0}, } dl.txn(&call1, "self-call") }) @@ -1540,12 +1322,12 @@ func TestIndirectReentry(t *testing.T) { eval := nextBlock(t, l) txn(t, l, eval, &app0) vb := endBlock(t, l, eval) - index0 := vb.Block().Payset[0].ApplicationID + id0 := vb.Block().Payset[0].ApplicationID fund := txntest.Txn{ Type: "pay", Sender: addrs[0], - Receiver: index0.Address(), + Receiver: id0.Address(), Amount: 1_000_000, } @@ -1564,13 +1346,13 @@ func TestIndirectReentry(t *testing.T) { eval = nextBlock(t, l) txns(t, l, eval, &app1, &fund) vb = endBlock(t, l, eval) - index1 := vb.Block().Payset[0].ApplicationID + id1 := vb.Block().Payset[0].ApplicationID call1 := txntest.Txn{ Type: "appl", Sender: addrs[0], - ApplicationID: index0, - ForeignApps: []basics.AppIndex{index1, index0}, + ApplicationID: id0, + ForeignApps: []basics.AppIndex{id1, id0}, } eval = nextBlock(t, l) txn(t, l, eval, &call1, "attempt to re-enter") @@ -1612,12 +1394,12 @@ func TestValidAppReentry(t *testing.T) { eval := nextBlock(t, l) txn(t, l, eval, &app0) vb := endBlock(t, l, eval) - index0 := vb.Block().Payset[0].ApplicationID + id0 := vb.Block().Payset[0].ApplicationID fund0 := txntest.Txn{ Type: "pay", Sender: addrs[0], - Receiver: index0.Address(), + Receiver: id0.Address(), Amount: 1_000_000, } @@ -1634,7 +1416,7 @@ func TestValidAppReentry(t *testing.T) { eval = nextBlock(t, l) txns(t, l, eval, &app1, &fund0) vb = endBlock(t, l, eval) - index1 := vb.Block().Payset[0].ApplicationID + id1 := vb.Block().Payset[0].ApplicationID app2 := txntest.Txn{ Type: "appl", @@ -1651,12 +1433,12 @@ func TestValidAppReentry(t *testing.T) { eval = nextBlock(t, l) txn(t, l, eval, &app2) vb = endBlock(t, l, eval) - index2 := vb.Block().Payset[0].ApplicationID + id2 := vb.Block().Payset[0].ApplicationID fund2 := txntest.Txn{ Type: "pay", Sender: addrs[0], - Receiver: index2.Address(), + Receiver: id2.Address(), Amount: 1_000_000, } @@ -1667,8 +1449,8 @@ func TestValidAppReentry(t *testing.T) { call1 := txntest.Txn{ Type: "appl", Sender: addrs[0], - ApplicationID: index0, - ForeignApps: []basics.AppIndex{index2, index1, index0}, + ApplicationID: id0, + ForeignApps: []basics.AppIndex{id2, id1, id0}, } eval = nextBlock(t, l) txn(t, l, eval, &call1) @@ -1715,13 +1497,12 @@ assert Sender: addrs[0], ApprovalProgram: main(program), } - vb := dl.fullBlock(&app0) - index0 := vb.Block().Payset[0].ApplicationID + id0 := dl.txn(&app0).ApplyData.ApplicationID fund0 := txntest.Txn{ Type: "pay", Sender: addrs[0], - Receiver: index0.Address(), + Receiver: id0.Address(), Amount: 1_000_000, } @@ -1736,22 +1517,22 @@ assert `), } - vb = dl.fullBlock(&app1, &fund0) - index1 := vb.Block().Payset[0].ApplicationID + payset := dl.txns(&app1, &fund0) + id1 := payset[0].ApplicationID callTxGroup := make([]*txntest.Txn, 16) callTxGroup[0] = &txntest.Txn{ Type: "appl", Sender: addrs[0], - ApplicationID: index0, - ForeignApps: []basics.AppIndex{index1}, + ApplicationID: id0, + ForeignApps: []basics.AppIndex{id1}, ApplicationArgs: [][]byte{{1, 0}}, // 256 inner calls } for i := 1; i < 16; i++ { callTxGroup[i] = &txntest.Txn{ Type: "appl", Sender: addrs[0], - ApplicationID: index1, + ApplicationID: id1, Note: []byte{byte(i)}, } } @@ -1797,12 +1578,12 @@ assert eval := nextBlock(t, l) txn(t, l, eval, &app0) vb := endBlock(t, l, eval) - index0 := vb.Block().Payset[0].ApplicationID + id0 := vb.Block().Payset[0].ApplicationID fund0 := txntest.Txn{ Type: "pay", Sender: addrs[0], - Receiver: index0.Address(), + Receiver: id0.Address(), Amount: 1_000_000, } @@ -1819,13 +1600,13 @@ assert eval = nextBlock(t, l) txns(t, l, eval, &app1, &fund0) vb = endBlock(t, l, eval) - index1 := vb.Block().Payset[0].ApplicationID + id1 := vb.Block().Payset[0].ApplicationID callTx := txntest.Txn{ Type: "appl", Sender: addrs[0], - ApplicationID: index0, - ForeignApps: []basics.AppIndex{index1}, + ApplicationID: id0, + ForeignApps: []basics.AppIndex{id1}, } eval = nextBlock(t, l) @@ -1858,8 +1639,7 @@ func TestSelfCheckHoldingNewApp(t *testing.T) { AssetName: "TEN", }, } - vb := dl.fullBlock(&asset) - assetID := vb.Block().Payset[0].ApplyData.ConfigAsset + assetID := dl.txn(&asset).ApplyData.ConfigAsset selfcheck := txntest.Txn{ Type: "appl", @@ -1873,8 +1653,7 @@ func TestSelfCheckHoldingNewApp(t *testing.T) { `, ForeignAssets: []basics.AssetIndex{assetID}, } - vb = dl.fullBlock(&selfcheck) - selfcheck.ApplicationID = vb.Block().Payset[0].ApplicationID + selfcheck.ApplicationID = dl.txn(&selfcheck).ApplicationID dl.txn(&selfcheck) @@ -1907,8 +1686,7 @@ func TestCheckHoldingNewApp(t *testing.T) { AssetName: "TEN", }, } - vb := dl.fullBlock(&asset) - assetID := vb.Block().Payset[0].ApplyData.ConfigAsset + assetID := dl.txn(&asset).ApplyData.ConfigAsset check := txntest.Txn{ Type: "appl", @@ -1924,8 +1702,7 @@ func TestCheckHoldingNewApp(t *testing.T) { `), ForeignAssets: []basics.AssetIndex{assetID}, } - vb = dl.fullBlock(&check) - check.ApplicationID = vb.Block().Payset[0].ApplicationID + check.ApplicationID = dl.txn(&check).ApplyData.ApplicationID create := txntest.Txn{ Type: "appl", @@ -1933,7 +1710,6 @@ func TestCheckHoldingNewApp(t *testing.T) { ApplicationID: 0, } dl.txgroup("", &create, &check) - }) } @@ -1977,10 +1753,10 @@ func TestInnerAppVersionCalling(t *testing.T) { ClearStateProgram: three.Program, } - vb := dl.fullBlock(&create5, &create6, &create5with3) - v5id := vb.Block().Payset[0].ApplicationID - v6id := vb.Block().Payset[1].ApplicationID - v5withv3csp := vb.Block().Payset[2].ApplicationID + payset := dl.txns(&create5, &create6, &create5with3) + v5id := payset[0].ApplicationID + v6id := payset[1].ApplicationID + v5withv3csp := payset[2].ApplicationID call := txntest.Txn{ Type: "appl", @@ -2150,8 +1926,7 @@ func TestAppDowngrade(t *testing.T) { ClearStateProgram: four.Program, } - vb := dl.fullBlock(&create) - app := vb.Block().Payset[0].ApplicationID + app := dl.txn(&create).ApplicationID update := txntest.Txn{ Type: "appl", @@ -2181,8 +1956,7 @@ func TestAppDowngrade(t *testing.T) { ClearStateProgram: four.Program, } - vb := dl.fullBlock(&create) - app := vb.Block().Payset[0].ApplicationID + app := dl.txn(&create).ApplicationID update := txntest.Txn{ Type: "appl", @@ -2227,70 +2001,50 @@ func TestAppDowngrade(t *testing.T) { }) } -func TestCreatedAppsAreAvailable(t *testing.T) { +func TestInnerCreatedAppsAreCallable(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() genBalances, addrs, _ := ledgertesting.NewTestGenesis() - l := newTestLedger(t, genBalances) - defer l.Close() - - ops, err := logic.AssembleStringWithVersion("int 1\nint 1\nassert", logic.AssemblerMaxVersion) - require.NoError(t, err) - program := "byte 0x" + hex.EncodeToString(ops.Program) - createapp := txntest.Txn{ - Type: "appl", - Sender: addrs[0], - ApprovalProgram: main(` - itxn_begin - int appl; itxn_field TypeEnum - ` + program + `; itxn_field ApprovalProgram - ` + program + `; itxn_field ClearStateProgram - int 1; itxn_field GlobalNumUint - int 2; itxn_field LocalNumByteSlice - int 3; itxn_field LocalNumUint - itxn_submit`), - } - - eval := nextBlock(t, l) - txn(t, l, eval, &createapp) - vb := endBlock(t, l, eval) - index0 := vb.Block().Payset[0].ApplicationID - - fund0 := txntest.Txn{ - Type: "pay", - Sender: addrs[0], - Receiver: index0.Address(), - Amount: 1_000_000, - } + // 31 allowed inner appl. + ledgertesting.TestConsensusRange(t, 31, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { + dl := NewDoubleLedger(t, genBalances, cv, cfg) + defer dl.Close() - eval = nextBlock(t, l) - txn(t, l, eval, &fund0) - endBlock(t, l, eval) + ops, err := logic.AssembleStringWithVersion("int 1\nint 1\nassert", dl.generator.GenesisProto().LogicSigVersion) + require.NoError(t, err) + program := "byte 0x" + hex.EncodeToString(ops.Program) - callTx := txntest.Txn{ - Type: "appl", - Sender: addrs[0], - ApplicationID: index0, - ForeignApps: []basics.AppIndex{}, - } + appID := dl.fundedApp(addrs[0], 1_000_000, + main(` + itxn_begin + int appl; itxn_field TypeEnum + `+program+`; itxn_field ApprovalProgram + `+program+`; itxn_field ClearStateProgram + int 1; itxn_field GlobalNumUint + int 2; itxn_field LocalNumByteSlice + int 3; itxn_field LocalNumUint + itxn_submit`)) + + callCreator := txntest.Txn{ + Type: "appl", + Sender: addrs[0], + ApplicationID: appID, + } - eval = nextBlock(t, l) - txn(t, l, eval, &callTx) - endBlock(t, l, eval) - index1 := basics.AppIndex(1) + tib := dl.txn(&callCreator) + createdID := tib.ApplyData.EvalDelta.InnerTxns[0].ApplyData.ApplicationID + require.NotZero(t, createdID) - callTx = txntest.Txn{ - Type: "appl", - Sender: addrs[0], - ApplicationID: index1, - ForeignApps: []basics.AppIndex{}, - } + callCreated := txntest.Txn{ + Type: "appl", + Sender: addrs[0], + ApplicationID: createdID, + } - eval = nextBlock(t, l) - txn(t, l, eval, &callTx) - endBlock(t, l, eval) + dl.txn(&callCreated) + }) } func TestInvalidAppsNotAccessible(t *testing.T) { @@ -2298,56 +2052,53 @@ func TestInvalidAppsNotAccessible(t *testing.T) { t.Parallel() genBalances, addrs, _ := ledgertesting.NewTestGenesis() - l := newTestLedger(t, genBalances) - defer l.Close() - app0 := txntest.Txn{ - Type: "appl", - Sender: addrs[0], - ApprovalProgram: main(` + // v31 = inner appl + ledgertesting.TestConsensusRange(t, 31, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { + dl := NewDoubleLedger(t, genBalances, cv, cfg) + defer dl.Close() + + // make an app, which we'll try to use without setting up foreign array + tib := dl.txn(&txntest.Txn{ + Type: "appl", + Sender: addrs[0], + }) + appID := tib.ApplyData.ApplicationID + + // an app that tries to access appID when called + app0 := txntest.Txn{ + Type: "appl", + Sender: addrs[0], + ApprovalProgram: main(` itxn_begin int appl itxn_field TypeEnum - int 2 + int ` + strconv.Itoa(int(appID)) + ` itxn_field ApplicationID itxn_submit`), - } - eval := nextBlock(t, l) - txn(t, l, eval, &app0) - vb := endBlock(t, l, eval) - index0 := vb.Block().Payset[0].ApplicationID + } + callerID := dl.txn(&app0).ApplicationID - fund0 := txntest.Txn{ - Type: "pay", - Sender: addrs[0], - Receiver: index0.Address(), - Amount: 1_000_000, - } + fundCaller := txntest.Txn{ + Type: "pay", + Sender: addrs[0], + Receiver: callerID.Address(), + Amount: 1_000_000, + } + dl.fullBlock(&fundCaller) - app1 := txntest.Txn{ - Type: "appl", - Sender: addrs[0], - ApprovalProgram: main(` -int 2 -int 2 -== -assert -`), - } - eval = nextBlock(t, l) - txns(t, l, eval, &app1, &fund0) - endBlock(t, l, eval) + callTx := txntest.Txn{ + Type: "appl", + Sender: addrs[0], + ApplicationID: callerID, + } - callTx := txntest.Txn{ - Type: "appl", - Sender: addrs[0], - ApplicationID: index0, - ForeignApps: []basics.AppIndex{}, - } + dl.txn(&callTx, "unavailable App "+strconv.Itoa(int(appID))) - eval = nextBlock(t, l) - txn(t, l, eval, &callTx, "invalid App reference 2") - endBlock(t, l, eval) + // confirm everything is done right if ForeignApps _is_ set up + callTx.ForeignApps = []basics.AppIndex{appID} + dl.txn(&callTx) + }) } func TestInvalidAssetsNotAccessible(t *testing.T) { @@ -2355,56 +2106,48 @@ func TestInvalidAssetsNotAccessible(t *testing.T) { t.Parallel() genBalances, addrs, _ := ledgertesting.NewTestGenesis() - l := newTestLedger(t, genBalances) - defer l.Close() - createapp := txntest.Txn{ - Type: "appl", - Sender: addrs[0], - ApprovalProgram: main(` + // v31 = inner appl + ledgertesting.TestConsensusRange(t, 31, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { + dl := NewDoubleLedger(t, genBalances, cv, cfg) + defer dl.Close() + + createasa := txntest.Txn{ + Type: "acfg", + Sender: addrs[0], + AssetParams: basics.AssetParams{ + Total: 1000000, + UnitName: "oz", + AssetName: "Gold", + URL: "https://gold.rush/", + }, + } + asaID := dl.txn(&createasa).ConfigAsset + require.NotZero(t, asaID) + + appID := dl.fundedApp(addrs[0], 1_000_000, + main(` itxn_begin int axfer; itxn_field TypeEnum int 0; itxn_field Amount - int 3; itxn_field XferAsset + int `+strconv.Itoa(int(asaID))+`; itxn_field XferAsset global CurrentApplicationAddress; itxn_field Sender global CurrentApplicationAddress; itxn_field AssetReceiver itxn_submit -`), - } - appIndex := basics.AppIndex(1) - - fund := txntest.Txn{ - Type: "pay", - Sender: addrs[0], - Receiver: appIndex.Address(), - Amount: 1_000_000, - } - - createasa := txntest.Txn{ - Type: "acfg", - Sender: addrs[0], - AssetParams: basics.AssetParams{ - Total: 1000000, - Decimals: 3, - UnitName: "oz", - AssetName: "Gold", - URL: "https://gold.rush/", - }, - } +`)) - eval := nextBlock(t, l) - txns(t, l, eval, &createapp, &fund, &createasa) - endBlock(t, l, eval) + use := txntest.Txn{ + Type: "appl", + Sender: addrs[0], + ApplicationID: appID, + } - use := txntest.Txn{ - Type: "appl", - Sender: addrs[0], - ApplicationID: basics.AppIndex(1), - } + dl.txn(&use, "unavailable Asset "+strconv.Itoa(int(asaID))) + // confirm everything is done right if ForeignAssets _is_ set up + use.ForeignAssets = []basics.AssetIndex{asaID} + dl.txn(&use) - eval = nextBlock(t, l) - txn(t, l, eval, &use, "invalid Asset reference 3") - endBlock(t, l, eval) + }) } func executeMegaContract(b *testing.B) { @@ -2872,19 +2615,19 @@ itxn_submit eval := nextBlock(t, l) txn(t, l, eval, &app0) vb := endBlock(t, l, eval) - index0 := vb.Block().Payset[0].ApplicationID + id0 := vb.Block().Payset[0].ApplicationID fund0 := txntest.Txn{ Type: "pay", Sender: addrs[0], - Receiver: index0.Address(), + Receiver: id0.Address(), Amount: 1_000_000, } optin := txntest.Txn{ Type: "appl", Sender: addrs[1], - ApplicationID: index0, + ApplicationID: id0, OnCompletion: transactions.OptInOC, } @@ -2904,7 +2647,7 @@ itxn_submit clear := txntest.Txn{ Type: "appl", Sender: addrs[1], - ApplicationID: index0, + ApplicationID: id0, OnCompletion: transactions.ClearStateOC, } @@ -2920,7 +2663,7 @@ itxn_submit // had 2000 bump, now paid 2k, charge 1k, left with 3k total bump require.Equal(t, uint64(3000), ad1-genBalances.Balances[addrs[1]].MicroAlgos.Raw) // InnerTxn in block - require.Equal(t, vb.Block().Payset[0].Txn.ApplicationID, index0) + require.Equal(t, vb.Block().Payset[0].Txn.ApplicationID, id0) require.Equal(t, vb.Block().Payset[0].Txn.OnCompletion, transactions.ClearStateOC) require.Len(t, vb.Block().Payset[0].EvalDelta.InnerTxns, 1) require.Equal(t, vb.Block().Payset[0].EvalDelta.InnerTxns[0].Txn.Amount.Raw, uint64(2000)) @@ -2928,7 +2671,7 @@ itxn_submit // Only the fee is paid because pay is "erased", so goes from 2k down to 1k require.Equal(t, uint64(1000), ad1-genBalances.Balances[addrs[1]].MicroAlgos.Raw) // no InnerTxn in block - require.Equal(t, vb.Block().Payset[0].Txn.ApplicationID, index0) + require.Equal(t, vb.Block().Payset[0].Txn.ApplicationID, id0) require.Equal(t, vb.Block().Payset[0].Txn.OnCompletion, transactions.ClearStateOC) require.Len(t, vb.Block().Payset[0].EvalDelta.InnerTxns, 0) } @@ -3022,22 +2765,22 @@ check: eval := nextBlock(t, l) txns(t, l, eval, &appA, &appB, &appC) vb := endBlock(t, l, eval) - indexA := vb.Block().Payset[0].ApplicationID - indexB := vb.Block().Payset[1].ApplicationID - indexC := vb.Block().Payset[2].ApplicationID + idA := vb.Block().Payset[0].ApplicationID + idB := vb.Block().Payset[1].ApplicationID + idC := vb.Block().Payset[2].ApplicationID fundA := txntest.Txn{ Type: "pay", Sender: addrs[0], - Receiver: indexA.Address(), + Receiver: idA.Address(), Amount: 1_000_000, } callA := txntest.Txn{ Type: "appl", Sender: addrs[0], - ApplicationID: indexA, - ForeignApps: []basics.AppIndex{indexB, indexC}, + ApplicationID: idA, + ForeignApps: []basics.AppIndex{idB, idC}, } eval = nextBlock(t, l) @@ -3136,22 +2879,22 @@ check: eval := nextBlock(t, l) txns(t, l, eval, &appA, &appB, &appC) vb := endBlock(t, l, eval) - indexA := vb.Block().Payset[0].ApplicationID - indexB := vb.Block().Payset[1].ApplicationID - indexC := vb.Block().Payset[2].ApplicationID + idA := vb.Block().Payset[0].ApplicationID + idB := vb.Block().Payset[1].ApplicationID + idC := vb.Block().Payset[2].ApplicationID fundA := txntest.Txn{ Type: "pay", Sender: addrs[0], - Receiver: indexA.Address(), + Receiver: idA.Address(), Amount: 1_000_000, } callA := txntest.Txn{ Type: "appl", Sender: addrs[0], - ApplicationID: indexA, - ForeignApps: []basics.AppIndex{indexB, indexC}, + ApplicationID: idA, + ForeignApps: []basics.AppIndex{idB, idC}, } eval = nextBlock(t, l) @@ -3187,38 +2930,33 @@ itxn_submit `), } - vb := dl.fullBlock(&appA, &appB) - index0 := vb.Block().Payset[0].ApplicationID - index1 := vb.Block().Payset[1].ApplicationID + payset := dl.txns(&appA, &appB) + id0 := payset[0].ApplicationID + id1 := payset[1].ApplicationID fund1 := txntest.Txn{ Type: "pay", Sender: addrs[0], - Receiver: index1.Address(), + Receiver: id1.Address(), Amount: 1_000_000_000, } fund0 := fund1 - fund0.Receiver = index0.Address() + fund0.Receiver = id0.Address() callTx := txntest.Txn{ Type: "appl", Sender: addrs[2], - ApplicationID: index1, - ForeignApps: []basics.AppIndex{index0}, + ApplicationID: id1, + ForeignApps: []basics.AppIndex{id0}, } - dl.beginBlock() if ver <= 33 { dl.txgroup("invalid Account reference", &fund0, &fund1, &callTx) - dl.endBlock() return } - - dl.txgroup("", &fund0, &fund1, &callTx) - vb = dl.endBlock() - - require.Equal(t, index0.Address(), vb.Block().Payset[2].EvalDelta.InnerTxns[0].Txn.Receiver) - require.Equal(t, uint64(100), vb.Block().Payset[2].EvalDelta.InnerTxns[0].Txn.Amount.Raw) + payset = dl.txgroup("", &fund0, &fund1, &callTx) + require.Equal(t, id0.Address(), payset[2].EvalDelta.InnerTxns[0].Txn.Receiver) + require.Equal(t, uint64(100), payset[2].EvalDelta.InnerTxns[0].Txn.Amount.Raw) }) } @@ -3261,37 +2999,37 @@ app_local_put LocalStateSchema: basics.StateSchema{NumByteSlice: 1}, } - vb := dl.fullBlock(&appA, &appB) - index0 := vb.Block().Payset[0].ApplicationID - index1 := vb.Block().Payset[1].ApplicationID + payset := dl.txns(&appA, &appB) + id0 := payset[0].ApplicationID + id1 := payset[1].ApplicationID fund1 := txntest.Txn{ Type: "pay", Sender: addrs[0], - Receiver: index1.Address(), + Receiver: id1.Address(), Amount: 1_000_000_000, } fund0 := fund1 - fund0.Receiver = index0.Address() + fund0.Receiver = id0.Address() optin := txntest.Txn{ Type: "appl", Sender: addrs[2], - ApplicationID: index0, - ForeignApps: []basics.AppIndex{index1}, + ApplicationID: id0, + ForeignApps: []basics.AppIndex{id1}, } callTx := txntest.Txn{ Type: "appl", Sender: addrs[2], - ApplicationID: index1, - ForeignApps: []basics.AppIndex{index0}, + ApplicationID: id1, + ForeignApps: []basics.AppIndex{id0}, } var problem string switch { case ver < 34: // before v7, app accounts not available at all - problem = "invalid Account reference " + index0.Address().String() + problem = "invalid Account reference " + id0.Address().String() case ver < 37: // as of v7, it's the mutation that's the problem problem = "invalid Account reference for mutation" } @@ -3345,38 +3083,37 @@ done: }, } - vb := dl.fullBlock(&appA, &appB) - index0 := vb.Block().Payset[0].ApplicationID - index1 := vb.Block().Payset[1].ApplicationID + payset := dl.txns(&appA, &appB) + id0 := payset[0].ApplicationID + id1 := payset[1].ApplicationID fund1 := txntest.Txn{ Type: "pay", Sender: addrs[0], - Receiver: index1.Address(), + Receiver: id1.Address(), Amount: 1_000_000_000, } fund0 := fund1 - fund0.Receiver = index0.Address() - fund1.Receiver = index1.Address() + fund0.Receiver = id0.Address() + fund1.Receiver = id1.Address() callA := txntest.Txn{ Type: "appl", Sender: addrs[2], - ApplicationID: index0, - ForeignApps: []basics.AppIndex{index1}, + ApplicationID: id0, + ForeignApps: []basics.AppIndex{id1}, } callB := txntest.Txn{ Type: "appl", Sender: addrs[2], - ApplicationID: index1, - ForeignApps: []basics.AppIndex{index0}, - Accounts: []basics.Address{index0.Address()}, + ApplicationID: id1, + ForeignApps: []basics.AppIndex{id0}, + Accounts: []basics.Address{id0.Address()}, } - vb = dl.fullBlock(&fund0, &fund1, &callA, &callB) - - require.Equal(t, "Y", vb.Block().Payset[3].EvalDelta.LocalDeltas[1]["X"].Bytes) + payset = dl.txns(&fund0, &fund1, &callA, &callB) + require.Equal(t, "Y", payset[3].EvalDelta.LocalDeltas[1]["X"].Bytes) }) } @@ -3419,6 +3156,10 @@ func TestEvalAppState(t *testing.T) { dl := NewDoubleLedger(t, genBalances, cv, cfg) defer dl.Close() + appID := basics.AppIndex(1) + if ver >= 37 { // AppForbidLowResources + appID += 1000 + } appcall1 := txntest.Txn{ Type: protocol.ApplicationCallTx, Sender: addrs[0], @@ -3442,7 +3183,7 @@ ok: appcall2 := txntest.Txn{ Type: protocol.ApplicationCallTx, Sender: addrs[0], - ApplicationID: 1, + ApplicationID: appID, } dl.beginBlock() @@ -3453,11 +3194,11 @@ ok: vb := dl.endBlock() deltas := vb.Delta() - params, ok := deltas.Accts.GetAppParams(addrs[0], 1) - require.True(t, ok) - state := params.Params.GlobalState - require.Equal(t, basics.TealValue{Type: basics.TealBytesType, Bytes: string(addrs[0][:])}, state["caller"]) - require.Equal(t, basics.TealValue{Type: basics.TealBytesType, Bytes: string(addrs[0][:])}, state["creator"]) + params, _ := deltas.Accts.GetAppParams(addrs[0], appID) + require.Equal(t, basics.TealKeyValue{ + "caller": {Type: basics.TealBytesType, Bytes: string(addrs[0][:])}, + "creator": {Type: basics.TealBytesType, Bytes: string(addrs[0][:])}, + }, params.Params.GlobalState) }) } @@ -3510,9 +3251,9 @@ func TestRewardsInAD(t *testing.T) { dl.fullBlock() } - vb := dl.fullBlock(&payTxn, &payNonPart) - payInBlock := vb.Block().Payset[0] - nonPartInBlock := vb.Block().Payset[1] + payset := dl.txns(&payTxn, &payNonPart) + payInBlock := payset[0] + nonPartInBlock := payset[1] if ver >= 15 { require.Greater(t, payInBlock.ApplyData.SenderRewards.Raw, uint64(1000)) require.Greater(t, payInBlock.ApplyData.ReceiverRewards.Raw, uint64(1000)) @@ -3544,8 +3285,6 @@ func TestDeleteNonExistentKeys(t *testing.T) { dl := NewDoubleLedger(t, genBalances, cv, cfg) defer dl.Close() - const appID basics.AppIndex = 1 - createTxn := txntest.Txn{ Type: "appl", Sender: addrs[0], @@ -3558,6 +3297,8 @@ app_local_del `), } + appID := dl.txn(&createTxn).ApplyData.ApplicationID + optInTxn := txntest.Txn{ Type: "appl", Sender: addrs[1], @@ -3565,14 +3306,14 @@ app_local_del OnCompletion: transactions.OptInOC, } - vb := dl.fullBlock(&createTxn, &optInTxn) - require.Len(t, vb.Block().Payset[1].EvalDelta.GlobalDelta, 0) + tib := dl.txn(&optInTxn) + require.Len(t, tib.EvalDelta.GlobalDelta, 0) // For a while, we encoded an empty localdelta deltas := 1 if ver >= 27 { deltas = 0 } - require.Len(t, vb.Block().Payset[1].EvalDelta.LocalDeltas, deltas) + require.Len(t, tib.EvalDelta.LocalDeltas, deltas) }) } @@ -3678,8 +3419,7 @@ func TestLogsInBlock(t *testing.T) { // Fail the clear state ClearStateProgram: "byte \"CLR\"\n log\n int 0", } - vb := dl.fullBlock(&createTxn) - createInBlock := vb.Block().Payset[0] + createInBlock := dl.txn(&createTxn) appID := createInBlock.ApplyData.ApplicationID require.Equal(t, "APP", createInBlock.ApplyData.EvalDelta.Logs[0]) @@ -3689,8 +3429,7 @@ func TestLogsInBlock(t *testing.T) { ApplicationID: appID, OnCompletion: transactions.OptInOC, } - vb = dl.fullBlock(&optInTxn) - optInInBlock := vb.Block().Payset[0] + optInInBlock := dl.txn(&optInTxn) require.Equal(t, "APP", optInInBlock.ApplyData.EvalDelta.Logs[0]) clearTxn := txntest.Txn{ @@ -3699,8 +3438,7 @@ func TestLogsInBlock(t *testing.T) { ApplicationID: appID, OnCompletion: transactions.ClearStateOC, } - vb = dl.fullBlock(&clearTxn) - clearInBlock := vb.Block().Payset[0] + clearInBlock := dl.txn(&clearTxn) // Logs do not appear if the ClearState failed require.Len(t, clearInBlock.ApplyData.EvalDelta.Logs, 0) }) @@ -3731,8 +3469,6 @@ func TestUnfundedSenders(t *testing.T) { dl := NewDoubleLedger(t, genBalances, cv, cfg) defer dl.Close() - asaIndex := basics.AssetIndex(1) - ghost := basics.Address{0x01} asaCreate := txntest.Txn{ @@ -3751,7 +3487,17 @@ func TestUnfundedSenders(t *testing.T) { Sender: addrs[0], } - dl.fullBlock(&asaCreate, &appCreate) + payset := dl.txns(&asaCreate, &appCreate) + asaID := payset[0].ApplyData.ConfigAsset + // we are testing some versions before ApplyData.ConfigAsset was + // populated. At that time, initial ID was 1, so we can hardcode. + if asaID == 0 { + asaID = 1 + } + appID := payset[1].ApplyData.ApplicationID + if appID == 0 { + appID = 2 + } // Advance so that rewardsLevel increases for i := 1; i < 10; i++ { @@ -3787,14 +3533,14 @@ func TestUnfundedSenders(t *testing.T) { Sender: ghost, AssetReceiver: addrs[0], AssetSender: addrs[1], - XferAsset: asaIndex, + XferAsset: asaID, Fee: 0, }, { // Freeze Type: "afrz", Sender: ghost, FreezeAccount: addrs[0], // creator, therefore is opted in - FreezeAsset: asaIndex, + FreezeAsset: asaID, AssetFrozen: true, Fee: 0, }, @@ -3802,14 +3548,14 @@ func TestUnfundedSenders(t *testing.T) { Type: "afrz", Sender: ghost, FreezeAccount: addrs[0], // creator, therefore is opted in - FreezeAsset: asaIndex, + FreezeAsset: asaID, AssetFrozen: false, Fee: 0, }, { // App call Type: "appl", Sender: ghost, - ApplicationID: basics.AppIndex(2), + ApplicationID: appID, Fee: 0, }, { // App creation (only works because it's also deleted) @@ -3820,7 +3566,7 @@ func TestUnfundedSenders(t *testing.T) { }, } - // v34 is the likely version for UnfundedSenders. Change if that doesn't happen. + // v34 enabled UnfundedSenders var problem string if ver < 34 { // In the old days, balances.Move would try to increase the rewardsState on the unfunded account @@ -3849,17 +3595,15 @@ func TestAppCallAppDuringInit(t *testing.T) { Sender: addrs[0], } - // construct a simple app - vb := dl.fullBlock(&approve) - - // now make a new app that calls it during init - approveID := vb.Block().Payset[0].ApplicationID + // construct a simple approval app + approveID := dl.txn(&approve).ApplicationID // Advance so that rewardsLevel increases for i := 1; i < 10; i++ { dl.fullBlock() } + // now make a new app that calls it during init callInInit := txntest.Txn{ Type: "appl", Sender: addrs[0], diff --git a/ledger/boxtxn_test.go b/ledger/boxtxn_test.go index b892a21f08..3a66583d83 100644 --- a/ledger/boxtxn_test.go +++ b/ledger/boxtxn_test.go @@ -147,21 +147,19 @@ func TestBoxCreate(t *testing.T) { proto := config.Consensus[cv] mbr := boxFee(proto, 28) - appIndex := dl.fundedApp(addrs[0], proto.MinBalance+3*mbr, boxAppSource) + appID := dl.fundedApp(addrs[0], proto.MinBalance+3*mbr, boxAppSource) call := txntest.Txn{ Type: "appl", Sender: addrs[0], - ApplicationID: appIndex, + ApplicationID: appID, } adam := call.Args("create", "adam") dl.txn(adam, fmt.Sprintf("invalid Box reference %#x", "adam")) adam.Boxes = []transactions.BoxRef{{Index: 0, Name: []byte("adam")}} - dl.beginBlock() - dl.txn(adam) - vb := dl.endBlock() + vb := dl.fullBlock(adam) // confirm the deltas has the creation require.Len(t, vb.Delta().KvMods, 1) @@ -219,12 +217,12 @@ func TestBoxRecreate(t *testing.T) { proto := config.Consensus[cv] mbr := boxFee(proto, 8) - appIndex := dl.fundedApp(addrs[0], proto.MinBalance+mbr, boxAppSource) + appID := dl.fundedApp(addrs[0], proto.MinBalance+mbr, boxAppSource) call := txntest.Txn{ Type: "appl", Sender: addrs[0], - ApplicationID: appIndex, + ApplicationID: appID, Boxes: []transactions.BoxRef{{Index: 0, Name: []byte("adam")}}, } @@ -286,8 +284,11 @@ func TestBoxCreateAvailability(t *testing.T) { // the app address that we know the app will get. So this is a nice // test, but unrealistic way to actual create a box. psychic := basics.AppIndex(2) - proto := config.Consensus[cv] + if proto.AppForbidLowResources { + psychic += 1000 + } + dl.txn(&txntest.Txn{ Type: "pay", Sender: addrs[0], @@ -353,12 +354,10 @@ func TestBoxCreateAvailability(t *testing.T) { ApplicationID: trampoline, } - dl.beginBlock() - dl.txgroup("", &accessWhenCalled, &call) - vb := dl.endBlock() + payset := dl.txgroup("", &accessWhenCalled, &call) // Make sure that we actually did it. - require.Equal(t, "we did it", vb.Block().Payset[1].ApplyData.EvalDelta.InnerTxns[1].EvalDelta.Logs[0]) + require.Equal(t, "we did it", payset[1].ApplyData.EvalDelta.InnerTxns[1].EvalDelta.Logs[0]) }) } @@ -377,18 +376,16 @@ func TestBoxRW(t *testing.T) { log := logging.NewLogger() log.SetOutput(&bufNewLogger) - appIndex := dl.fundedApp(addrs[0], 1_000_000, boxAppSource) + appID := dl.fundedApp(addrs[0], 1_000_000, boxAppSource) call := txntest.Txn{ Type: "appl", Sender: addrs[0], - ApplicationID: appIndex, + ApplicationID: appID, Boxes: []transactions.BoxRef{{Index: 0, Name: []byte("x")}}, } - dl.txn(call.Args("create", "x", "\x10")) // 16 - dl.beginBlock() - dl.txn(call.Args("set", "x", "ABCDEFGHIJ")) // 10 long - vb := dl.endBlock() + dl.txn(call.Args("create", "x", "\x10")) // 16 + vb := dl.fullBlock(call.Args("set", "x", "ABCDEFGHIJ")) // 10 long // confirm the deltas has the change, including the old value require.Len(t, vb.Delta().KvMods, 1) for _, kvDelta := range vb.Delta().KvMods { // There's only one @@ -453,11 +450,11 @@ func TestBoxAccountData(t *testing.T) { log := logging.NewLogger() log.SetOutput(&bufNewLogger) - appIndex := dl.fundedApp(addrs[0], 1_000_000, boxAppSource) + appID := dl.fundedApp(addrs[0], 1_000_000, boxAppSource) call := txntest.Txn{ Type: "appl", Sender: addrs[0], - ApplicationID: appIndex, + ApplicationID: appID, Boxes: []transactions.BoxRef{{Index: 0, Name: []byte("x")}, {Index: 0, Name: []byte("y")}}, } @@ -486,12 +483,12 @@ assert == assert `) - verifyAppIndex := dl.fundedApp(addrs[0], 0, verifyAppSrc) + verifyAppID := dl.fundedApp(addrs[0], 0, verifyAppSrc) verifyAppCall := txntest.Txn{ Type: "appl", Sender: addrs[0], - ApplicationID: verifyAppIndex, - Accounts: []basics.Address{appIndex.Address()}, + ApplicationID: verifyAppID, + Accounts: []basics.Address{appID.Address()}, } // The app account has no box data initially @@ -535,11 +532,11 @@ func TestBoxIOBudgets(t *testing.T) { dl := NewDoubleLedger(t, genBalances, cv, cfg) defer dl.Close() - appIndex := dl.fundedApp(addrs[0], 0, boxAppSource) + appID := dl.fundedApp(addrs[0], 0, boxAppSource) call := txntest.Txn{ Type: "appl", Sender: addrs[0], - ApplicationID: appIndex, + ApplicationID: appID, Boxes: []transactions.BoxRef{{Index: 0, Name: []byte("x")}}, } dl.txn(call.Args("create", "x", "\x10\x00"), // 4096 @@ -561,7 +558,7 @@ func TestBoxIOBudgets(t *testing.T) { fundApp := txntest.Txn{ Type: "pay", Sender: addrs[0], - Receiver: appIndex.Address(), + Receiver: appID.Address(), Amount: proto.MinBalance + boxFee(proto, 4096+1), // remember key len! } create := call.Args("create", "x", "\x10\x00") @@ -606,13 +603,13 @@ func TestBoxInners(t *testing.T) { dl.txn(&txntest.Txn{Type: "pay", Sender: addrs[0], Receiver: addrs[0]}) dl.txn(&txntest.Txn{Type: "pay", Sender: addrs[0], Receiver: addrs[0]}) - boxIndex := dl.fundedApp(addrs[0], 2_000_000, boxAppSource) // there are some big boxes made - passIndex := dl.fundedApp(addrs[0], 120_000, passThruSource) // lowish, show it's not paying for boxes + boxID := dl.fundedApp(addrs[0], 2_000_000, boxAppSource) // there are some big boxes made + passID := dl.fundedApp(addrs[0], 120_000, passThruSource) // lowish, show it's not paying for boxes call := txntest.Txn{ Type: "appl", Sender: addrs[0], - ApplicationID: passIndex, - ForeignApps: []basics.AppIndex{boxIndex}, + ApplicationID: passID, + ForeignApps: []basics.AppIndex{boxID}, Boxes: []transactions.BoxRef{{Index: 0, Name: []byte("x")}}, } // The current Boxes gives top-level access to "x", not the inner app @@ -620,7 +617,7 @@ func TestBoxInners(t *testing.T) { fmt.Sprintf("invalid Box reference %#x", 'x')) // This isn't right: Index should be index into ForeignApps - call.Boxes = []transactions.BoxRef{{Index: uint64(boxIndex), Name: []byte("x")}} + call.Boxes = []transactions.BoxRef{{Index: uint64(boxID), Name: []byte("x")}} require.Error(t, call.Txn().WellFormed(transactions.SpecialAddresses{}, dl.generator.genesisProto)) call.Boxes = []transactions.BoxRef{{Index: 1, Name: []byte("x")}} @@ -673,15 +670,15 @@ func TestBoxInners(t *testing.T) { // Try some get/put action dl.txn(call.Args("put", "x", "john doe")) - vb := dl.fullBlock(call.Args("get", "x")) + tib := dl.txn(call.Args("get", "x")) // we are passing this thru to the underlying box app which logs the get - require.Equal(t, "john doe", vb.Block().Payset[0].ApplyData.EvalDelta.InnerTxns[0].EvalDelta.Logs[0]) + require.Equal(t, "john doe", tib.ApplyData.EvalDelta.InnerTxns[0].EvalDelta.Logs[0]) dl.txn(call.Args("check", "x", "john")) // bad change because of length dl.txn(call.Args("put", "x", "steve doe"), "box_put wrong size") - vb = dl.fullBlock(call.Args("get", "x")) - require.Equal(t, "john doe", vb.Block().Payset[0].ApplyData.EvalDelta.InnerTxns[0].EvalDelta.Logs[0]) + tib = dl.txn(call.Args("get", "x")) + require.Equal(t, "john doe", tib.ApplyData.EvalDelta.InnerTxns[0].EvalDelta.Logs[0]) // good change dl.txn(call.Args("put", "x", "mark doe")) diff --git a/ledger/double_test.go b/ledger/double_test.go index 86ae3b6600..1a894e6719 100644 --- a/ledger/double_test.go +++ b/ledger/double_test.go @@ -67,7 +67,7 @@ func (dl *DoubleLedger) beginBlock() *eval.BlockEvaluator { return dl.eval } -func (dl *DoubleLedger) txn(tx *txntest.Txn, problem ...string) { +func (dl *DoubleLedger) txn(tx *txntest.Txn, problem ...string) (stib *transactions.SignedTxnInBlock) { dl.t.Helper() if dl.eval == nil { dl.beginBlock() @@ -76,25 +76,31 @@ func (dl *DoubleLedger) txn(tx *txntest.Txn, problem ...string) { if len(problem) > 0 { dl.eval = nil } else { - dl.endBlock() + vb := dl.endBlock() + stib = &vb.Block().Payset[0] } }() } txn(dl.t, dl.generator, dl.eval, tx, problem...) + return nil } -func (dl *DoubleLedger) txns(txns ...*txntest.Txn) { +func (dl *DoubleLedger) txns(txns ...*txntest.Txn) (payset []transactions.SignedTxnInBlock) { dl.t.Helper() if dl.eval == nil { dl.beginBlock() - defer dl.endBlock() + defer func() { + vb := dl.endBlock() + payset = vb.Block().Payset + }() } for _, tx := range txns { dl.txn(tx) } + return nil } -func (dl *DoubleLedger) txgroup(problem string, txns ...*txntest.Txn) { +func (dl *DoubleLedger) txgroup(problem string, txns ...*txntest.Txn) (payset []transactions.SignedTxnInBlock) { dl.t.Helper() if dl.eval == nil { dl.beginBlock() @@ -103,7 +109,8 @@ func (dl *DoubleLedger) txgroup(problem string, txns ...*txntest.Txn) { if problem != "" { dl.eval = nil } else { - dl.endBlock() + vb := dl.endBlock() + payset = vb.Block().Payset } }() } @@ -114,6 +121,7 @@ func (dl *DoubleLedger) txgroup(problem string, txns ...*txntest.Txn) { require.Error(dl.t, err) require.Contains(dl.t, err.Error(), problem) } + return nil } func (dl *DoubleLedger) fullBlock(txs ...*txntest.Txn) *ledgercore.ValidatedBlock { diff --git a/ledger/eval/eval_test.go b/ledger/eval/eval_test.go index 2e61c92801..aa16634d2f 100644 --- a/ledger/eval/eval_test.go +++ b/ledger/eval/eval_test.go @@ -136,7 +136,7 @@ ok: Type: protocol.ApplicationCallTx, Header: header, ApplicationCallTxnFields: transactions.ApplicationCallTxnFields{ - ApplicationID: 1, + ApplicationID: 1001, }, } @@ -154,7 +154,7 @@ ok: EvalDelta: transactions.EvalDelta{GlobalDelta: map[string]basics.ValueDelta{ "creator": {Action: basics.SetBytesAction, Bytes: string(addrs[0][:])}}, }, - ApplicationID: 1, + ApplicationID: 1001, }, }, { @@ -195,7 +195,7 @@ func TestEvalAppAllocStateWithTxnGroup(t *testing.T) { require.NoError(t, err) deltas := eval.state.deltas() ad, _ := deltas.Accts.GetBasicsAccountData(addr) - state := ad.AppParams[1].GlobalState + state := ad.AppParams[1001].GlobalState require.Equal(t, basics.TealValue{Type: basics.TealBytesType, Bytes: string(addr[:])}, state["caller"]) require.Equal(t, basics.TealValue{Type: basics.TealBytesType, Bytes: string(addr[:])}, state["creator"]) } @@ -275,7 +275,7 @@ func TestTransactionGroupWithTracer(t *testing.T) { t.Parallel() genesisInitState, addrs, keys := ledgertesting.Genesis(10) - innerAppID := basics.AppIndex(3) + innerAppID := basics.AppIndex(1003) innerAppAddress := innerAppID.Address() balances := genesisInitState.Accounts balances[innerAppAddress] = basics_testing.MakeAccountData(basics.Offline, basics.MicroAlgos{Raw: 1_000_000}) @@ -388,7 +388,7 @@ int 1`, } expectedBasicAppCallAD := transactions.ApplyData{ - ApplicationID: 1, + ApplicationID: 1001, EvalDelta: transactions.EvalDelta{ GlobalDelta: basics.StateDelta{}, LocalDeltas: map[uint64]basics.StateDelta{}, @@ -701,7 +701,7 @@ func (ledger *evalTestLedger) LookupApplication(rnd basics.Round, addr basics.Ad res := ledgercore.AppResource{} ad, ok := ledger.roundBalances[rnd][addr] if !ok { - return res, fmt.Errorf("no such account %s", addr.String()) + return res, fmt.Errorf("no such account %s while looking up app", addr.String()) } if params, ok := ad.AppParams[aidx]; ok { res.AppParams = ¶ms @@ -716,7 +716,7 @@ func (ledger *evalTestLedger) LookupAsset(rnd basics.Round, addr basics.Address, res := ledgercore.AssetResource{} ad, ok := ledger.roundBalances[rnd][addr] if !ok { - return res, fmt.Errorf("no such account %s", addr.String()) + return res, fmt.Errorf("no such account %s while looking up asset", addr.String()) } if params, ok := ad.AssetParams[aidx]; ok { res.AssetParams = ¶ms diff --git a/ledger/eval/txntracer_test.go b/ledger/eval/txntracer_test.go index 1efed23d6d..8b60a0c090 100644 --- a/ledger/eval/txntracer_test.go +++ b/ledger/eval/txntracer_test.go @@ -21,6 +21,7 @@ import ( "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/basics" basics_testing "github.com/algorand/go-algorand/data/basics/testing" @@ -63,7 +64,16 @@ func TestTransactionGroupWithDeltaTracer(t *testing.T) { // SETUP THE BLOCK genesisInitState, addrs, keys := ledgertesting.Genesis(4) - innerAppID := basics.AppIndex(3) + // newTestLedger uses ConsensusFuture, so we check it to find out if + // we should use 1001 as the initial resources ID. + protoVersion := protocol.ConsensusFuture + proto := config.Consensus[protoVersion] + offset := basics.AppIndex(0) + if proto.AppForbidLowResources { + offset += 1000 + } + + innerAppID := basics.AppIndex(3) + offset innerAppAddress := innerAppID.Address() balances := genesisInitState.Accounts balances[innerAppAddress] = basics_testing.MakeAccountData(basics.Offline, basics.MicroAlgos{Raw: 1_000_000}) @@ -199,7 +209,7 @@ int 1`, }, AppResources: []ledgercore.AppResourceRecord{ { - Aidx: 1, + Aidx: 1 + offset, Addr: addrs[0], Params: ledgercore.AppParamsDelta{ Params: &basics.AppParams{ @@ -209,7 +219,7 @@ int 1`, }, }, { - Aidx: 3, + Aidx: 3 + offset, Addr: addrs[0], Params: ledgercore.AppParamsDelta{ Params: &basics.AppParams{ @@ -223,7 +233,7 @@ int 1`, }, }, { - Aidx: 4, + Aidx: 4 + offset, Addr: innerAppAddress, Params: ledgercore.AppParamsDelta{ Params: &basics.AppParams{ diff --git a/ledger/eval_simple_test.go b/ledger/eval_simple_test.go index 243e92912d..bb2f9f52be 100644 --- a/ledger/eval_simple_test.go +++ b/ledger/eval_simple_test.go @@ -18,6 +18,7 @@ package ledger import ( "context" + "encoding/binary" "fmt" "reflect" "strings" @@ -256,26 +257,28 @@ bad: err dl.fullBlock(&txntest.Txn{Type: "pay", Sender: addrs[2], Receiver: addrs[2]}) } - checker := basics.AppIndex(11) - gold := basics.AssetIndex(13) create := txntest.Txn{ Type: protocol.ApplicationCallTx, Sender: addrs[0], ApprovalProgram: source, } - dl.fullBlock(&create) // create the app + vb := dl.fullBlock(&create) // create the app + checker := basics.AppIndex(vb.Block().TxnCounter) + gold := basics.AssetIndex(checker + 2) // doesn't exist yet + goldBytes := make([]byte, 8) + binary.BigEndian.PutUint64(goldBytes, uint64(gold)) check := txntest.Txn{ Type: protocol.ApplicationCallTx, Sender: addrs[0], ApplicationID: checker, - ApplicationArgs: [][]byte{{byte(gold)}, {0}, {0}}, // exist=0 value=0 + ApplicationArgs: [][]byte{goldBytes, {0}, {0}}, // exist=0 value=0 } dl.fullBlock(&check) - - dl.fullBlock(&makegold) + vb = dl.fullBlock(&makegold) // Works, despite asset not existing + require.EqualValues(t, gold, vb.Block().TxnCounter) // confirm hardcoded "gold" is correct b, ok := holding(t, dl.generator, addrs[0], gold) @@ -284,23 +287,23 @@ bad: err // The asset exists now. asset_holding_get gives 1,10 for the creator // (who is auto-opted in) - check.ApplicationArgs = [][]byte{{byte(gold)}, {1}, {10}} // exist=1 value=10 + check.ApplicationArgs = [][]byte{goldBytes, {1}, {10}} // exist=1 value=10 dl.fullBlock(&check) // but still gives 0,0 for un opted-in addrs[1], because it means // "exists" in the given account, i.e. opted in check.Sender = addrs[1] - check.ApplicationArgs = [][]byte{{byte(gold)}, {0}, {0}} + check.ApplicationArgs = [][]byte{goldBytes, {0}, {0}} dl.fullBlock(&check) // opt-in addr[1] dl.fullBlock(&txntest.Txn{Type: "axfer", XferAsset: gold, Sender: addrs[1], AssetReceiver: addrs[1]}) - check.ApplicationArgs = [][]byte{{byte(gold)}, {1}, {0}} + check.ApplicationArgs = [][]byte{goldBytes, {1}, {0}} dl.fullBlock(&check) // non-existent account, with existing asset, cleanly reports exists=0, value=0 check.Accounts = []basics.Address{{0x01, 0x02}} - check.ApplicationArgs = [][]byte{{byte(gold)}, {0}, {0}} + check.ApplicationArgs = [][]byte{goldBytes, {0}, {0}} dl.fullBlock(&check) }) } @@ -348,32 +351,34 @@ main: bad: err ` - // Advance the ledger so that there's ambiguity of app index or foreign array index + // Advance the ledger so that there's no ambiguity of app ID or foreign array slot for i := 0; i < 10; i++ { dl.fullBlock(&txntest.Txn{Type: "pay", Sender: addrs[2], Receiver: addrs[2]}) } - checker := basics.AppIndex(11) // the app that checks state - state := basics.AppIndex(12) // the app with state create := txntest.Txn{ Type: protocol.ApplicationCallTx, Sender: addrs[0], ApprovalProgram: source, } - dl.fullBlock(&create) // create the checker app - + vb := dl.fullBlock(&create) // create the checker app + // Since we are testing back to v24, we can't get appID from EvalDelta + checker := basics.AppIndex(vb.Block().TxnCounter) + state := checker + 1 // doesn't exist yet + stateBytes := make([]byte, 8) + binary.BigEndian.PutUint64(stateBytes, uint64(state)) check := txntest.Txn{ Type: protocol.ApplicationCallTx, Sender: addrs[0], ApplicationID: checker, - ApplicationArgs: [][]byte{{byte(state)}, {0}, {0}}, // exist=0 value=0 + ApplicationArgs: [][]byte{stateBytes, {0}, {0}}, // exist=0 value=0 } - // unlike assets, you can't even as `app_local_get_ex` for address that - // have not been opted in. For local state, the existence bit is only - // used to distinguish "key existence". The local state bundle MUST - // exist of program fails. + // unlike assets, you can't even do `app_local_get_ex` for an address + // that has not been opted into the app. For local state, the existence + // bit is only used to distinguish "key existence". The local state + // bundle MUST exist or the program fails. dl.txn(&check, "cannot fetch key") // so we make the app and try again @@ -388,7 +393,7 @@ bad: err // opt-in addr[0] dl.fullBlock(&txntest.Txn{Type: "appl", ApplicationID: state, Sender: addrs[0], OnCompletion: transactions.OptInOC}) - check.ApplicationArgs = [][]byte{{byte(state)}, {0}, {0}} + check.ApplicationArgs = [][]byte{stateBytes, {0}, {0}} dl.fullBlock(&check) }) } @@ -661,7 +666,7 @@ func TestAppInsMinBalance(t *testing.T) { l := newSimpleLedgerWithConsensusVersion(t, genBalances, protocol.ConsensusV30, cfg) defer l.Close() - const appid basics.AppIndex = 1 + const appID basics.AppIndex = 1 maxAppsOptedIn := config.Consensus[protocol.ConsensusV30].MaxAppsOptedIn require.Greater(t, maxAppsOptedIn, 0) @@ -695,7 +700,7 @@ func TestAppInsMinBalance(t *testing.T) { optInTxn := txntest.Txn{ Type: protocol.ApplicationCallTx, Sender: addrs[9], - ApplicationID: appid + basics.AppIndex(i), + ApplicationID: appID + basics.AppIndex(i), OnCompletion: transactions.OptInOC, } txnsOptIn = append(txnsOptIn, &optInTxn) diff --git a/test/e2e-go/cli/goal/expect/goalAccountTest.exp b/test/e2e-go/cli/goal/expect/goalAccountTest.exp old mode 100644 new mode 100755 index 234ef5cec0..6f9563cbed --- a/test/e2e-go/cli/goal/expect/goalAccountTest.exp +++ b/test/e2e-go/cli/goal/expect/goalAccountTest.exp @@ -81,9 +81,9 @@ if { [catch { set TEAL_PROGS_DIR "$TEST_DATA_DIR/../scripts/e2e_subs/tealprogs" set APP_ID [::AlgorandGoal::AppCreate0 $PRIMARY_WALLET_NAME "" $NEW_ACCOUNT_ADDRESS ${TEAL_PROGS_DIR}/clear_program_state.teal $GLOBAL_BYTE_SLICES $LOCAL_BYTE_SLICES ${TEAL_PROGS_DIR}/clear_program_state.teal $TEST_PRIMARY_NODE_DIR] - # expect app idx = 2 since a pre-recorded response is checked down the road - if { $APP_ID != 2 } { - ::AlgorandGoal::Abort "Expected app id to be 2 but got $APP_ID. Have you posted additional transactions? Only transfer txn is expected before app call txn" + # expect app idx = 1002 since a pre-recorded response is checked down the road + if { $APP_ID != 1002 } { + ::AlgorandGoal::Abort "Expected app id to be 1002 but got $APP_ID. Have you posted additional transactions? Only transfer txn is expected before app call txn" } # check JSON output to stdout @@ -91,7 +91,7 @@ if { [catch { \"addr\": \"47YPQTIGQEO7T4Y4RWDYWEKV6RTR2UNBQXBABEEGM72ESWDQNCQ52OPASU\", \"algo\": 299000, \"appp\": { - \"2\": { + \"1002\": { \"approv\": \"AiABASI=\", \"clearp\": \"AiABASI=\", \"gsch\": { @@ -110,7 +110,7 @@ if { [catch { } # check msgpack output to a file with zero exit code - set MSGP_EXPECTED_BASE64 "hKRhZGRyxCDn8PhNBoEd+fMcjYeLEVX0Zx1RoYXCAJCGZ/RJWHBooaRhbGdvzgAEj/ikYXBwcIECg6ZhcHByb3bEBQIgAQEipmNsZWFycMQFAiABASKkZ3NjaIGjbmJzAaR0c2NogaNuYnMB" + set MSGP_EXPECTED_BASE64 "hKRhZGRyxCDn8PhNBoEd+fMcjYeLEVX0Zx1RoYXCAJCGZ/RJWHBooaRhbGdvzgAEj/ikYXBwcIHNA+qDpmFwcHJvdsQFAiABASKmY2xlYXJwxAUCIAEBIqRnc2NogaNuYnMBpHRzY2iBo25icwE=" set MSGP_EXPECTED [ exec echo -n $MSGP_EXPECTED_BASE64 | base64 --decode ] set BALREC_FILE "$TEST_ROOT_DIR/brec.msgp" spawn goal account dump -a $NEW_ACCOUNT_ADDRESS -o $BALREC_FILE --datadir $TEST_PRIMARY_NODE_DIR diff --git a/test/scripts/e2e_subs/assets-app-b.sh b/test/scripts/e2e_subs/assets-app-b.sh index 688b773712..168a35c911 100755 --- a/test/scripts/e2e_subs/assets-app-b.sh +++ b/test/scripts/e2e_subs/assets-app-b.sh @@ -39,7 +39,7 @@ XFER3=99999 XFER4=11 APP_CREATED_STR='Created app with app index' -ERR_APP_CL_STR='only clearing out is supported for applications that do not exist' +ERR_APP_CL_STR='only ClearState is supported for an application' ERR_APP_NE_STR='application does not exist' ERR_APP_OI_STR1='has not opted in to application' ERR_APP_OI_STR2='not opted in to app' diff --git a/test/scripts/e2e_subs/assets-app.sh b/test/scripts/e2e_subs/assets-app.sh index 2525129517..4c25cd663d 100755 --- a/test/scripts/e2e_subs/assets-app.sh +++ b/test/scripts/e2e_subs/assets-app.sh @@ -39,7 +39,7 @@ XFER3=99999 XFER4=11 APP_CREATED_STR='Created app with app index' -ERR_APP_CL_STR='only clearing out is supported for applications that do not exist' +ERR_APP_CL_STR='only ClearState is supported for an application' ERR_APP_NE_STR='application does not exist' ERR_APP_OI_STR1='has not opted in to application' ERR_APP_OI_STR2='not opted in to app' diff --git a/test/scripts/e2e_subs/e2e-app-stateful-global.sh b/test/scripts/e2e_subs/e2e-app-stateful-global.sh index eac38d3136..4b892116d2 100755 --- a/test/scripts/e2e_subs/e2e-app-stateful-global.sh +++ b/test/scripts/e2e_subs/e2e-app-stateful-global.sh @@ -51,7 +51,7 @@ ${gcmd} app delete --app-id $APPID --app-arg "str:hello" --from $ACCOUNT # Check should fail since we can't find program to execute RES=$(${gcmd} app call --app-id $APPID --app-arg "str:check" --app-arg "str:bar" --from $ACCOUNT 2>&1 || true) -EXPERROR='only clearing out is supported' +EXPERROR='only ClearState is supported' if [[ $RES != *"${EXPERROR}"* ]]; then date '+app-create-test FAIL app call should fail if app has been deleted %Y%m%d_%H%M%S' false diff --git a/test/scripts/e2e_subs/e2e-app-stateful-local.sh b/test/scripts/e2e_subs/e2e-app-stateful-local.sh index 4f92014999..83859bcc93 100755 --- a/test/scripts/e2e_subs/e2e-app-stateful-local.sh +++ b/test/scripts/e2e_subs/e2e-app-stateful-local.sh @@ -59,7 +59,7 @@ ${gcmd} app delete --app-id $APPID --app-arg "str:hello" --from $ACCOUNT # Check should fail since we can't find program to execute RES=$(${gcmd} app call --app-id $APPID --app-arg "str:check" --app-arg "str:bar" --from $ACCOUNT 2>&1 || true) -EXPERROR='only clearing out is supported' +EXPERROR='only ClearState is supported' if [[ $RES != *"${EXPERROR}"* ]]; then date '+app-create-test FAIL app call should fail if app has been deleted %Y%m%d_%H%M%S' false diff --git a/test/scripts/e2e_subs/e2e-app-x-app-reads.sh b/test/scripts/e2e_subs/e2e-app-x-app-reads.sh index 9b42e9e263..86b61339ea 100755 --- a/test/scripts/e2e_subs/e2e-app-x-app-reads.sh +++ b/test/scripts/e2e_subs/e2e-app-x-app-reads.sh @@ -21,7 +21,7 @@ APPID=$(${gcmd} app create --creator ${ACCOUNT} --approval-prog ${DIR}/tealprogs # Creating an app that attempts to read APPID's global state without setting # foreignapps should fail -EXPERR="invalid App reference 1" +EXPERR="App index 1 beyond txn.ForeignApps" RES=$(${gcmd} app create --creator ${ACCOUNT} --approval-prog ${DIR}/tealprogs/xappreads.teal --global-byteslices 0 --global-ints 0 --local-byteslices 0 --local-ints 0 --clear-prog <(printf '#pragma version 2\nint 1') 2>&1 || true) if [[ $RES != *"$EXPERR"* ]]; then date '+x-app-reads FAIL expected disallowed foreign global read to fail %Y%m%d_%H%M%S' diff --git a/test/scripts/e2e_subs/sectok-app.sh b/test/scripts/e2e_subs/sectok-app.sh index 5ed3e2d5eb..8e87c57c3c 100755 --- a/test/scripts/e2e_subs/sectok-app.sh +++ b/test/scripts/e2e_subs/sectok-app.sh @@ -40,7 +40,7 @@ XFER4=11 VERY_LATE=9999999999 APP_CREATED_STR='Created app with app index' -ERR_APP_CL_STR='only clearing out is supported for applications that do not exist' +ERR_APP_CL_STR='only ClearState is supported for an application' ERR_APP_NE_STR='application does not exist' ERR_APP_OI_STR1='has not opted in to application' ERR_APP_OI_STR2='not opted in to app' From 9e980d3ecce678818e9a25cdef58612f990ccb8b Mon Sep 17 00:00:00 2001 From: Hang Su <87964331+ahangsu@users.noreply.github.com> Date: Fri, 5 May 2023 18:10:12 -0400 Subject: [PATCH 084/125] Algod: Simulation run with extra budget per transaction group (#5354) Moar opcode budget --- cmd/goal/clerk.go | 67 ++- daemon/algod/api/algod.oas2.json | 14 +- daemon/algod/api/algod.oas3.yml | 14 +- .../api/server/v2/generated/data/routes.go | 74 +-- .../v2/generated/experimental/routes.go | 58 +-- .../api/server/v2/generated/model/types.go | 6 + .../nonparticipating/private/routes.go | 68 +-- .../nonparticipating/public/routes.go | 477 +++++++++--------- .../generated/participating/private/routes.go | 347 ++++++------- .../generated/participating/public/routes.go | 90 ++-- daemon/algod/api/server/v2/handlers.go | 3 +- daemon/algod/api/server/v2/utils.go | 2 + ledger/simulation/simulation_eval_test.go | 207 +++++++- ledger/simulation/simulator.go | 23 +- ledger/simulation/trace.go | 11 +- ledger/simulation/tracer.go | 6 + test/e2e-go/restAPI/restClient_test.go | 129 +++++ test/scripts/e2e_subs/e2e-app-simulate.sh | 100 +++- 18 files changed, 1090 insertions(+), 606 deletions(-) diff --git a/cmd/goal/clerk.go b/cmd/goal/clerk.go index 715468514e..839cb1b6e0 100644 --- a/cmd/goal/clerk.go +++ b/cmd/goal/clerk.go @@ -37,6 +37,7 @@ import ( "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/data/transactions/logic" "github.com/algorand/go-algorand/data/transactions/verify" + "github.com/algorand/go-algorand/ledger/simulation" "github.com/algorand/go-algorand/libgoal" "github.com/algorand/go-algorand/protocol" @@ -44,31 +45,34 @@ import ( ) var ( - toAddress string - account string - amount uint64 - txFilename string - rejectsFilename string - closeToAddress string - noProgramOutput bool - writeSourceMap bool - signProgram bool - programSource string - argB64Strings []string - disassemble bool - verbose bool - progByteFile string - msigParams string - logicSigFile string - timeStamp int64 - protoVersion string - rekeyToAddress string - signerAddress string - rawOutput bool - requestFilename string - requestOutFilename string - simulateAllowEmptySignatures bool - simulateAllowMoreLogging bool + toAddress string + account string + amount uint64 + txFilename string + rejectsFilename string + closeToAddress string + noProgramOutput bool + writeSourceMap bool + signProgram bool + programSource string + argB64Strings []string + disassemble bool + verbose bool + progByteFile string + msigParams string + logicSigFile string + timeStamp int64 + protoVersion string + rekeyToAddress string + signerAddress string + rawOutput bool + requestFilename string + requestOutFilename string + + simulateAllowEmptySignatures bool + simulateAllowMoreLogging bool + simulateAllowExtraOpcodeBudget bool + simulateExtraOpcodeBudget uint64 ) func init() { @@ -155,6 +159,8 @@ func init() { simulateCmd.Flags().StringVarP(&outFilename, "result-out", "o", "", "Filename for writing simulation result") simulateCmd.Flags().BoolVar(&simulateAllowEmptySignatures, "allow-empty-signatures", false, "Allow transactions without signatures to be simulated as if they had correct signatures") simulateCmd.Flags().BoolVar(&simulateAllowMoreLogging, "allow-more-logging", false, "Lift the limits on log opcode during simulation") + simulateCmd.Flags().BoolVar(&simulateAllowExtraOpcodeBudget, "allow-extra-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") } var clerkCmd = &cobra.Command{ @@ -1240,6 +1246,15 @@ var simulateCmd = &cobra.Command{ reportErrorf("exactly one of --txfile or --request must be provided") } + allowExtraBudgetProvided := cmd.Flags().Changed("allow-extra-opcode-budget") + extraBudgetProvided := cmd.Flags().Changed("extra-opcode-budget") + if allowExtraBudgetProvided && extraBudgetProvided { + reportErrorf("--allow-extra-opcode-budget and --extra-opcode-budget are mutually exclusive") + } + if allowExtraBudgetProvided { + simulateExtraOpcodeBudget = simulation.MaxExtraOpcodeBudget + } + requestOutProvided := cmd.Flags().Changed("request-only-out") resultOutProvided := cmd.Flags().Changed("result-out") if requestOutProvided && resultOutProvided { @@ -1261,6 +1276,7 @@ var simulateCmd = &cobra.Command{ }, AllowEmptySignatures: simulateAllowEmptySignatures, AllowMoreLogging: simulateAllowMoreLogging, + ExtraOpcodeBudget: simulateExtraOpcodeBudget, } err := writeFile(requestOutFilename, protocol.EncodeJSON(simulateRequest), 0600) if err != nil { @@ -1283,6 +1299,7 @@ var simulateCmd = &cobra.Command{ }, AllowEmptySignatures: simulateAllowEmptySignatures, AllowMoreLogging: simulateAllowMoreLogging, + ExtraOpcodeBudget: simulateExtraOpcodeBudget, } simulateResponse, responseErr = client.SimulateTransactions(simulateRequest) } else { diff --git a/daemon/algod/api/algod.oas2.json b/daemon/algod/api/algod.oas2.json index 7c930ead30..7735a512df 100644 --- a/daemon/algod/api/algod.oas2.json +++ b/daemon/algod/api/algod.oas2.json @@ -3293,6 +3293,10 @@ "allow-more-logging": { "description": "Lifts limits on log opcode usage during simulation.", "type": "boolean" + }, + "extra-opcode-budget": { + "description": "Applies extra opcode budget during simulation for each transaction group.", + "type": "integer" } } }, @@ -3659,13 +3663,15 @@ }, "max-log-calls": { "description": "The maximum log calls one can make during simulation", - "type": "integer", - "x-algorand-format": "uint64" + "type": "integer" }, "max-log-size": { "description": "The maximum byte number to log during simulation", - "type": "integer", - "x-algorand-format": "uint64" + "type": "integer" + }, + "extra-opcode-budget": { + "description": "The extra opcode budget added to each transaction group during simulation", + "type": "integer" } } } diff --git a/daemon/algod/api/algod.oas3.yml b/daemon/algod/api/algod.oas3.yml index 9ef5c66cd8..625a05b1e5 100644 --- a/daemon/algod/api/algod.oas3.yml +++ b/daemon/algod/api/algod.oas3.yml @@ -1868,6 +1868,10 @@ "description": "Lifts limits on log opcode usage during simulation.", "type": "boolean" }, + "extra-opcode-budget": { + "description": "Applies extra opcode budget during simulation for each transaction group.", + "type": "integer" + }, "txn-groups": { "description": "The transaction groups to simulate.", "items": { @@ -1962,15 +1966,17 @@ "description": "If true, transactions without signatures are allowed and simulated as if they were properly signed.", "type": "boolean" }, + "extra-opcode-budget": { + "description": "The extra opcode budget added to each transaction group during simulation", + "type": "integer" + }, "max-log-calls": { "description": "The maximum log calls one can make during simulation", - "type": "integer", - "x-algorand-format": "uint64" + "type": "integer" }, "max-log-size": { "description": "The maximum byte number to log during simulation", - "type": "integer", - "x-algorand-format": "uint64" + "type": "integer" } }, "type": "object" diff --git a/daemon/algod/api/server/v2/generated/data/routes.go b/daemon/algod/api/server/v2/generated/data/routes.go index 084735657c..0e68a996ac 100644 --- a/daemon/algod/api/server/v2/generated/data/routes.go +++ b/daemon/algod/api/server/v2/generated/data/routes.go @@ -283,43 +283,43 @@ var swaggerSpec = []string{ "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", - "XP//AAAA//8bsE0vTM4AAA==", + "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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 6ca5bbde6c..2f209db44f 100644 --- a/daemon/algod/api/server/v2/generated/experimental/routes.go +++ b/daemon/algod/api/server/v2/generated/experimental/routes.go @@ -213,35 +213,35 @@ var swaggerSpec = []string{ "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", - "/8cfsTf1vAAA", + "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", } // 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 438d260875..e1b7c20320 100644 --- a/daemon/algod/api/server/v2/generated/model/types.go +++ b/daemon/algod/api/server/v2/generated/model/types.go @@ -619,6 +619,9 @@ type SimulateRequest struct { // AllowMoreLogging Lifts limits on log opcode usage during simulation. AllowMoreLogging *bool `json:"allow-more-logging,omitempty"` + // ExtraOpcodeBudget Applies extra opcode budget during simulation for each transaction group. + ExtraOpcodeBudget *uint64 `json:"extra-opcode-budget,omitempty"` + // TxnGroups The transaction groups to simulate. TxnGroups []SimulateRequestTransactionGroup `json:"txn-groups"` } @@ -664,6 +667,9 @@ type SimulationEvalOverrides struct { // AllowEmptySignatures If true, transactions without signatures are allowed and simulated as if they were properly signed. AllowEmptySignatures *bool `json:"allow-empty-signatures,omitempty"` + // ExtraOpcodeBudget The extra opcode budget added to each transaction group during simulation + ExtraOpcodeBudget *uint64 `json:"extra-opcode-budget,omitempty"` + // MaxLogCalls The maximum log calls one can make during simulation MaxLogCalls *uint64 `json:"max-log-calls,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 5f16e2b311..c1359ef02a 100644 --- a/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go +++ b/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go @@ -268,40 +268,40 @@ var swaggerSpec = []string{ "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", - "P9aL6evg1Yt6/fH6fwIAAP//+2oQp1bHAAA=", + "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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 3ffa72282a..22599a8823 100644 --- a/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go +++ b/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go @@ -609,244 +609,245 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "H4sIAAAAAAAC/+y9a5PbtpIA+ldQ2q3yY0WNn9mTqUrtndhJzmwcx+WZZPds7JtAZEvCGRLgAUCNFF//", - "91toACRIghI1M7bjZD7ZI+LRaDQajX6+m6SiKAUHrtXk+N2kpJIWoEHiXzRNRcV1wjLzVwYqlazUTPDJ", - "sf9GlJaMLyfTCTO/llSvJtMJpwU0bUz/6UTCvyomIZsca1nBdKLSFRTUDKy3pWldj7RJliJxQ5zYIU6f", - "T97v+ECzTIJSfSh/5PmWMJ7mVQZES8oVTc0nRS6ZXhG9Yoq4zoRxIjgQsSB61WpMFgzyTM38Iv9VgdwG", - "q3STDy/pfQNiIkUOfTifiWLOOHiooAaq3hCiBclggY1WVBMzg4HVN9SCKKAyXZGFkHtAtUCE8AKvisnx", - "LxMFPAOJu5UCW+N/FxLgd0g0lUvQk7fT2OIWGmSiWRFZ2qnDvgRV5VoRbItrXLI1cGJ6zcgPldJkDoRy", - "8vrbZ+Tx48dfmoUUVGvIHJENrqqZPVyT7T45nmRUg//cpzWaL4WkPEvq9q+/fYbzn7kFjm1FlYL4YTkx", - "X8jp86EF+I4REmJcwxL3oUX9pkfkUDQ/z2EhJIzcE9v4RjclnP+T7kpKdboqBeM6si8EvxL7OcrDgu67", - "eFgNQKt9aTAlzaC/PEi+fPvu4fThg/f/9stJ8n/uz6eP349c/rN63D0YiDZMKymBp9tkKYHiaVlR3sfH", - "a0cPaiWqPCMrusbNpwWyeteXmL6Wda5pXhk6YakUJ/lSKEIdGWWwoFWuiZ+YVDw3bMqM5qidMEVKKdYs", - "g2xquO/liqUrklJlh8B25JLluaHBSkE2RGvx1e04TO9DlBi4roQPXNAfFxnNuvZgAjbIDZI0FwoSLfZc", - "T/7GoTwj4YXS3FXqsMuKnK+A4OTmg71sEXfc0HSeb4nGfc0IVYQSfzVNCVuQrajIJW5Ozi6wv1uNwVpB", - "DNJwc1r3qDm8Q+jrISOCvLkQOVCOyPPnro8yvmDLSoIilyvQK3fnSVCl4AqImP8TUm22/b/PfnxJhCQ/", - "gFJ0Ca9oekGApyKDbEZOF4QLHZCGoyXEoek5tA4HV+yS/6cShiYKtSxpehG/0XNWsMiqfqAbVlQF4VUx", - "B2m21F8hWhAJupJ8CCA74h5SLOimP+m5rHiK+99M25LlDLUxVeZ0iwgr6OarB1MHjiI0z0kJPGN8SfSG", - "D8pxZu794CVSVDwbIeZos6fBxapKSNmCQUbqUXZA4qbZBw/jh8HTCF8BOH6QQXDqWfaAw2EToRlzus0X", - "UtIlBCQzIz855oZftbgAXhM6mW/xUylhzUSl6k4DMOLUuyVwLjQkpYQFi9DYmUOHYTC2jePAhZOBUsE1", - "ZRwyw5wRaKHBMqtBmIIJd793+rf4nCr44snQHd98Hbn7C9Hd9Z07Pmq3sVFij2Tk6jRf3YGNS1at/iPe", - "h+Hcii0T+3NvI9ny3Nw2C5bjTfRPs38eDZVCJtBChL+bFFtyqisJx2/4ffMXSciZpjyjMjO/FPanH6pc", - "szO2ND/l9qcXYsnSM7YcQGYNa/TBhd0K+48ZL86O9Sb6rnghxEVVhgtKWw/X+ZacPh/aZDvmoYR5Ur92", - "w4fH+cY/Rg7toTf1Rg4AOYi7kpqGF7CVYKCl6QL/2SyQnuhC/m7+Kcvc9NblIoZaQ8fuSkb1gVMrnJRl", - "zlJqkPjafTZfDRMA+5CgTYsjvFCP3wUgllKUIDWzg9KyTHKR0jxRmmoc6d8lLCbHk387avQvR7a7Ogom", - "f2F6nWEnI7JaMSihZXnAGK+M6KN2MAvDoPETsgnL9lBoYtxuoiElZlhwDmvK9ax5srT4QX2Af3EzNfi2", - "0o7Fd+cJNohwYhvOQVkJ2Da8o0iAeoJoJYhWFEiXuZjXP9w9KcsGg/j9pCwtPlB6BIaCGWyY0uoeLp82", - "Jymc5/T5jHwXjo2iuOD51lwOVtQwd8PC3VruFqt1S24NzYh3FMHtFHJmtsajwYj5N0Fx+KxYidxIPXtp", - "xTT+u2sbkpn5fVTnz4PEQtwOExc+tBzm7BsHfwkeN3c7lNMnHKfumZGTbt+rkY0ZJU4wV6KVnftpx92B", - "xxqFl5KWFkD3xd6ljOMjzTaysF6Tm45kdFGYgzMc0BpCdeWztvc8RCFBUujA8HUu0ou/U7W6gTM/92P1", - "jx9OQ1ZAM5BkRdVqNolJGeHxakYbc8RMQ3zgk3kw1axe4k0tb8/SMqppsDQHb1wssajHfsj0QEbeLj/i", - "f2hOzGdztg3rt8POyDkyMGWPszMyZOa1bx8IdibTALUQghT2gU/Mq/sgKJ81k8f3adQefWN1Cm6H3CJw", - "h8Tmxo/B12ITg+FrsekdAbEBdRP0YcZBMVJDoUbA99xBJnD/HfqolHTbRzKOPQbJZoFGdFV4Gnh445tZ", - "GuXsyVzIq3GfDlvhpFE5E2pGDZjvtIMkbFqViSPFiNrKNugM1Fj5djON7vAxjLWwcKbpB8CCMqPeBBba", - "A900FkRRshxugPRXUaY/pwoePyJnfz95+vDRr4+efmFIspRiKWlB5lsNitx1bzOi9DaHe/2V4euoynV8", - "9C+eeEVle9zYOEpUMoWClv2hrALUikC2GTHt+lhroxlXXQM45nCeg+HkFu3E6vYNaM+ZMhJWMb+RzRhC", - "WNbMkhEHSQZ7ienQ5TXTbMMlyq2sbuIpC1IKGdGv4RHTIhV5sgapmIhYU165FsS18OJt2f3dQksuqSJm", - "blT9VhwFighl6Q0fz/ft0Ocb3uBmJ+e3642szs07Zl/ayPeaREVKkInecJLBvFq2XkILKQpCSYYd8Y7+", - "DjSKAuesgDNNi/LHxeJmnooCB4o82VgBysxEbAsj1ytIBbeeEHteZ27UMejpIsar6PQwAA4jZ1ueop7x", - "Jo7t8MO1YByNHmrL0+AVa2DMIVu2yPL6r9UhdNip7qgIOAYdL/AzKjqeQ67pjUt03QlisD/zpG2BJZlp", - "iLv1gi1XOhC5X0khFjcPY2yWGKD4wT5YctOn/2x5KTJz0HSlbkA8aQZrTr/Z0/DM07moNKGEiwxQx1Sp", - "uOAy4MuARlS0/epQFtIr+waZgyGklFZmtVVJ0LLZ46VNx4SmlnoTRI0asOvUBjnbyk5n7eS5BJptyRyA", - "EzF3xhNn1sFFUjTLan/1O7EpcpZacJVSpKAUZIlT2uwFzbezbFXvwBMCjgDXsxAlyILKawN7sd4L5wVs", - "E3QiUOTu9z+re58AXi00zfcgFtvE0Fs/gZ2FrA/1uOl3EVx38pDsqATiea55bxsGkYOGIRQehJPB/etC", - "1NvF66NlDRJtVR+U4v0k1yOgGtQPTO/XhbYqB1zj3NPPSD9mwzjlwgsdscFyqnSyjy2bRq33qVlBwAlj", - "nBgHHhBKXlClrX2V8QzVQvY6wXmsgGKmGAZ4UEQ3I//spfP+2Km5B7mqVC2qq6oshdSQxdbAYbNjrpew", - "qecSi2Ds+j2gBakU7Bt5CEvB+A5ZdiUWQVTXZgjngNBfHCrrzT2/jaKyBUSDiF2AnPlWAXZD96ABQJhq", - "EG0Jh6kO5dQ+SdOJ0qIsDbfQScXrfkNoOrOtT/RPTds+cVHd3NuZAIVeSa69g/zSYtY6hq2oIg4OUtAL", - "I3ugisAagvswm8OYKMZTSHZRPj5/TKvwCOw9pFW5lDSDJIOcbvuD/mQ/E/t51wC4481TUGhIrIdPfNMb", - "SvYOFTuGFjieigmPBL+Q1BxB8/JoCMT13jNyBjh2jDk5OrpTD4VzRbfIj4fLtlsdGRFvw7XQZscdPSDI", - "jqOPAXgAD/XQV0cFdk6ad1l3in+AchPUcsThk2xBDS2hGf+gBQzoF53zdHBeOuy9w4GjbHOQje3hI0NH", - "dkDZ+YpKzVJW4lvne9je+NOvO0HUBEcy0JTlkJHgg30GlmF/Yn1TumNe7Sk4Si/VB7+nmIosJ2cKRZ42", - "8BewxTf3K+v0eB64St7AWzYyqrmfKCcIqHelMiJ42AQ2NNX51ghqegVbcgkSiKrmBdPaOjO3n7palEk4", - "QFTnv2NGZ+CyDoN+B8ZY3M5wqGB5/a2YTuybYDd8552HQQsd7i1QCpGP0B71kBGFYJQvBCmF2XXm/Kq9", - "Z62npBaQjmmjdbO+/u+oFppxBeQfoiIp5fjkqjTUMo2QKCigAGlmMCJYPafzemgwBDkUYF+S+OX+/e7C", - "7993e84UWcClD0YwDbvouH8f9TivhNKtw3UDukJz3E4j1wcaQ8zF514hXZ6y3+ruRh6zk686g9cWFHOm", - "lHKEa5Z/bQbQOZmbMWsPaWScxwGOO8rOEQwdWzfu+xkrqpzqm7DowJrmiViDlCyDvZzcTcwE/2ZN8x/r", - "bnvedI2HFCsKyBjVkG9JKSEF67luRDVVjz0j1qctXVG+RAldimrpnKrsOMhhK2V1IbLivSGiUoze8GQp", - "RVXGOK5zpPXBC0Z+AWreUMGOYGf7Yrik9XwuXmXMVeh3Ltjm78yYQxab6WTwiWmQum6emBY57QiMEdy3", - "JWAF+GkmHqnXR9QZYaOPr3BbDPWazf0wOvJm6BiU/YkDN6/m45Cnl3nf5tsbkDLsQERCKUHhnRDqhZT9", - "KhZhtJW7NNRWaSj6qnPb9deB4/d68IEmeM44JIXgsI0GGDMOP+DH6HHCe2mgM0oIQ327Qn8L/g5Y7XnG", - "UON18Yu7HZzQV7WL4w1sfnfcjtUkjDNDrSDkJaEkzRnqDAVXWlapfsMpaiWCwxZxBfHPr2E91TPfJK4Y", - "i+it3FBvOEU3oFpXETVfLyDyMP8WwKurVLVcgtId6XYB8Ia7VoyTijONcxVmvxK7YSVI9MeY2ZYF3ZIF", - "zVGt9jtIQeaVbst7GOuiNMtzZ8Ix0xCxeMOpJjmYJ+sPjJ9vcDhvAvU0w0FfCnlRYyF+vSyBg2Iqibus", - "fGe/ojehW/7KeRZibLL9bJX+ZvwmIGaLSosm3vb/vftfx7+cJP9Hk98fJF/+x9Hbd0/e37vf+/HR+6++", - "+v/aPz1+/9W9//r32E552GORGA7y0+fuLXT6HAXeRuvfg/2jaXwLxpMokYW27Q5tkbsYdegI6F5bHaJX", - "8IbrDTeEtKY5y4wgchVy6LK43lm0p6NDNa2N6Kg//FoPFCOvwWVIhMl0WOOVr/G+l1c85gnNUC6MCc/L", - "ouJ2K734Z136vbeNWEzruDab8uKYYNDTinpXMffno6dfTKZNsFL9fTKduK9vI5TMsk0sJC2DTex14A4I", - "How7ipR0q0DHuQfCHnUsstb8cNgCzLNSrVj58TmF0mwe53DeUdppGTb8lFsPZnN+0Ki1dbpysfj4cGsJ", - "kEGpV7FQ+JakgK2a3QToOBqUUqyBTwmbwaz7ys/Mg8W5OOVAFxiSjc8fMUYcr8+BJTRPFQHWw4WMekrH", - "6AeFW8et308n7vJXNy6Pu4FjcHXnrC1Y/m8tyJ3vvjknR45hqjs2OtIOHcSzRd5yLmSj5YJiuJlNAGLD", - "Q9/wN/w5LBhn5vvxG55RTY/mVLFUHVUK5Nc0pzyF2VKQYx8F8pxq+ob3JK3BHD1B/A0pq3nOUnIRSsQN", - "edq8C/0R3rz5heZL8ebN2541vi+/uqmi/MVOkFwyvRKVTlzUeCLhksqYtUPVUcM4sk0LsWvWKXFjW1bs", - "otLd+HGeR8tSdaMH+8svy9wsPyBD5WLjzJYRpYX0sogRUCw0uL8vhbsYJL30D/tKgSK/FbT8hXH9liRv", - "qgcPHgNphdP95q58Q5PbEkY/7wejG7uvely4fdfARkualHQZM6q8efOLBlri7qO8XOAjO88JdmuF8Xk3", - "ZRyqWYDHx/AGWDgODknCxZ3ZXj5DUHwJ+Am3ENsYcaMx9V51v4LAvitvVyc4sLdLlV4l5mxHV6UMifud", - "qROHLI2Q5e3vii3Rx9HlWJkDSVeQXrjkF1CUejttdfcuHk7Q9KyDKZsWxYblYGA+qqTnQKoyo04Up3zb", - "jZBWoLV3snwNF7A9F01c/yEh0e0IXTV0UJFSA+nSEGt4bN0Y3c13fkQYlViWPtAVI548WRzXdOH7DB9k", - "K/LewCGOEUUrgnQIEVRGEGGJfwAFV1ioGe9apB9bnnllzO3NF0mR4nk/cU2ax5Nz+QlXg2pf+70AzLEk", - "LhWZUyO3C5ceyEahBlysUnQJAxJyaBUYGevZsiTgIPvuvehNJxbdC61330RBto0Ts+YopYD5YkgFHzMd", - "Ry8/kzU8OdU4Zv1zCJvnKCbVHnGW6VDZss7YNGZDoMUJGCRvBA4PRhsjoWSzospnLsIET/4sj5IBPmBU", - "9a5cGqeBj1KQxanOlOF5bvec9l6XLqOGT6Phc2eET8sReTCMhI9u0bHtEBwFoAxyWNqF28aeUJoI72aD", - "DBw/LhY540CSmLsTVUqkzKaeaq4ZNwcY+fg+IVYFTEaPECPjAGw0qOLA5KUIzyZfHgIkdxHq1I+Nptjg", - "b4gH01gHYCPyiNKwcDZgVkk9B6DOR66+vzqemjgMYXxKDJtb09ywOffiawbppXRAsbWTwMGZ9O8NibM7", - "NPD2YjloTfYquspqQpnJAx0X6HZAPBebxEbTRSXe+WZu6D3qE42xfbGDaZNn3FFkLjboJoJXi/XB3QPL", - "MBwejOCFv2EK6RX7Dd3mFphd0+6WpmJUqJBknDqvJpchcWLM1AMSzBC53A3yYVwJgI6yo0ku6x6/ex+p", - "bfGkf5k3t9q0yfPkw01ix3/oCEV3aQB/fS1MncHiVVdiieop2t4O7eQdgQgZI3rDJvpGmr4pSEEO+ChI", - "WkJUchEz3Zm3DeCNc+a7BcoLTBFC+fZe4EIjYcmUhkaJ7g31n0I9STEzmRCL4dXpUi7M+l4LUV9TNvUN", - "dmwt86OvAH1QF0wqnaAFIroE0+hbhY/qb03TuKzUdtKxeTxZFucNOO0FbJOM5VWcXt283z83076sWaKq", - "5shvGbceE3PMOxt13dsxtfXu3LngF3bBL+iNrXfcaTBNzcTSkEt7js/kXHQ47y52ECHAGHH0d20QpTsY", - "ZBBy2eeOgdxkDyeGXM52aV97hynzY+91G/GBn0N3lB0pupZAYbBzFQzNREYsYTpI29qPhRw4A7QsWbbp", - "6ELtqIMvZnqQwsMnu+pgAXfXDbYHA4HeMxaOIUG185o1Ar5NwNtKKzIbhZnzdvaxkCGEUzHl08f3EVWH", - "a+3D1TnQ/HvY/mza4nIm76eT66lOY7h2I+7B9at6e6N4RtO8VaW1LCEHopyWpRRrmidOwTxEmlKsHWli", - "c6+P/sisLq7GPP/m5MUrB/776STNgcqkFhUGV4Xtys9mVTaF2sAB8empzZvPy+xWlAw2v877FCqlL1fg", - "8vwG0mgvIWFjcAiOolNSL+IeQntVzs42Ype4w0YCZW0iadR31kLStorQNWW515t5aAe8eXBx47JaRrlC", - "OMC1rSuBkSy5UXbTO93x09FQ1x6eFM61IxNxYZNtKyJ414SOTrfb0lndC4rpBK1WpM+ceFWgJiFROUvj", - "OlY+V4Y4uLWdmcYEGw8Io2bEig2YYnnFgrFMszEJQzpABnNEkamiOUsa3M2FK6RScfavCgjLgGvzSeKp", - "7BxUzN/otO3969TIDv253MBWQ98Mfx0ZI0yl2b3xEIjdAkZoqeuB+7x+MvuF1hop80NgkjjA4B/O2LsS", - "dxjrHX04arbOi6u2xS2se9Lnf4YwbALs/UVX/OPV5fQcmCNaRIWpZCHF7xB/5+HzOBLp4pOHMvRy+R1C", - "T/uwdECLxdTanaYWTDP74HYPSTehFqrtpDBA9bjzgVkOsxh6DTXldqttTYOWr1ucYEKv0iM7fkMwDuae", - "J25OL+c0luLRCBkGppPGANzSpWtBfGePe1W7+9vZSWBLrtsyG8VcgmyC0PoZUa4oMNhpR4sKjWSAVBvK", - "BFNr/8uViAxT8UvKbWkM088eJddbgVV+mV6XQmIOAhVX+2eQsoLmcckhS/sq3owtma36UCkIygq4gWxF", - "HUtFrjRDHcTiUHO6IA+mQW0TtxsZWzPF5jlgi4e2xZwq5OS1IqruYpYHXK8UNn80ovmq4pmETK+URawS", - "pBbq8HlTG6/moC8BOHmA7R5+Se6i2U6xNdwzWHT38+T44ZeodLV/PIhdAK5qxy5ukiE7+R/HTuJ0jHZL", - "O4Zh3G7UWTRc25btGmZcO06T7TrmLGFLx+v2n6WCcrqEuKdIsQcm2xd3ExVpHbzwzNacUVqKLWE6Pj9o", - "avjTgPe5YX8WDJKKomC6cMYdJQpDT03NADupH84WsHHpXj1c/iPaSEtvIuo8Ij+u0tTeb7FVoyX7JS2g", - "jdYpoTbxRM4a7wWfhJqc+rw2mP+2TntrcWPmMktHMQedGRaklIxrfFhUepH8jaQrKmlq2N9sCNxk/sWT", - "SM7fdu5JfhjgHx3vEhTIdRz1coDsvQzh+pK7XPCkMBwlu9dEewSnctCYGzfbDdkOdw89VigzoySD5Fa1", - "yI0GnPpahMd3DHhNUqzXcxA9Hryyj06ZlYyTB63MDv30+oWTMgohY8nqmuPuJA4JWjJYo+9efJPMmNfc", - "C5mP2oXrQP9pLQ9e5AzEMn+WYw+Br0XkderzUNeadOerHtEODB1T88GQwdwNNSXtnL8fn4/ejBdU3NLl", - "Fdt9w5b54vGAf3QR8YnJBTewseXblQwQSpDzPEoyWf09sLFT8rXYjCWczin0xPMHQFEUJRXLs5+byM9O", - "SnlJebqK2szmpuOvTfGrenH2DozmnVtRziGPDmflzV+9XBqRnP8pxs5TMD6ybTfLvV1uZ3EN4G0wPVB+", - "QoNepnMzQYjVdlBd7bSdL0VGcJ4myVlzXPvVEYIc1v+qQOlYgBJ+sI5jqBs17MCmUCbAM3yRzsh3tr7t", - "Ckgrgw2+BH2qAp++1irtqzIXNJtiCoXzb05eEDur7WNLuNgUzkt8CLVX0dGJBfkbx7kg+2os8fCI8ePs", - "9tc2q1Y6qTMuxwJQTYsmJzTr2AnwiRRiZ0aeB5UqbayqGcLQw4LJwrzq6tGsfIQ0Yf6jNU1X+OxrsdZh", - "kh+fe9xTpQrq/dV1e+qkhnjuDNwu/bjNPj4lwrzNL5myZU1hDe2Y1zoA3KkdfAxse3my4txSyuyAW65O", - "YXgo2j1w9or0poQoZB3EHyj029T9h6ZiP8Ne0RxL3bzuvUJ/NoKyrsfiy1WnlAvOUsxwFLuiXf3TMXa2", - "Ecmguopcf8TdCY0crmg2+doVz2FxML+8Z4QOcX1Ff/DVbKqlDvunxkKbK6rJErRynA2yqS+K4HSNjCtw", - "SSqxWm7AJ4Vs2S6RQ0bN4UltNjmQjDD0ZuDx+K359tKpFtAn/YJxfEQ4tDnBz2oDsTyjNi8PpslSgHLr", - "accfq19MnxmG4maweTvz5RxxDGv6M8u2du7+UCfe6u2szKbtM9PWZeipf255OdtJT8rSTTpcMiMqD+gN", - "H0RwxHqZePNRgNx6/HC0HeS2010F71NDaLBGYzeUeA/3CKMuH9EpTWSEVktR2IJYN7FolgTGI2C8YBya", - "YqORCyKNXgm4MXheB/qpVFJtRcBRPO0caI4W7hhDU9qZN647VDc/kUEJrtHPMbyNTeWLAcZRN2gEN8q3", - "dY1TQ92BMPEMiys7RPbrWKBU5YSoDKMWOpUtYozDMG5fO6d9AfSPQV8mst21pPbkHHITDQWizqtsCTqh", - "WRbLGfo1fiX4lWQVSg6wgbSqc0uWJUkx70o7EU2f2txEqeCqKnbM5Rtcc7qgVEyEGsJyNX6HMdBlvsV/", - "Y4kVh3fGOXoc7GrovTpcJYkD5eb2SD2p19B0otgyGY8JvFOuj45m6qsRetP/Rik9F8s2IB85/cQuLhfu", - "UYy/fWMujjA7Qy9bqL1a6uQJ6NgnfIE/fDbWYb9troRXWS99KBqU6gJiuxUQw6XApnj5Dbj3Bkk3qL1f", - "rYVyyMk3HfRJp9pFx2lKdrKgwYgj6yFkY4sQirh2dsgryDoFmc+93uMkw56creOZ9wKEenezPkDfe19W", - "UlLmzO8Ns+hj1nm99+MQxvjDNhvcXYTzJR/U2H2/HvL7JorxZQ4Ev3dLBV2AC5mvq+fbtXrPJ/8ktL+6", - "4rV2vNrzPrr+vuIVp/q06tBB5e25S0tvl+ne5N//bP3kCHAtt38AVW5v03uFlvrSrlVPNU1IndF4VIbj", - "1q0Yr5k0nGOpyauE9FQKxZos2rFiSiP96c6xHlKQI6o/lndmWUOqMXV6Y6SXAIdkjDKTBaULb3MtDbwd", - "a7dDl2JpV16lfr70PRdaL/QpCN+zuaZn47MIndSuWMiUMPfsErirHtgOahjtWr1YQKrZek+o2f+sgAdh", - "TFOvhLBVgIPIM1a76mKmksNVbA1AuyLBdsITZAy8NjhDgSYXsL2jSIsaosmvp/5euUqSCsQAcofEkIhQ", - "MVcHqzV11memaspALHjXItsdmnRfg3VzgsDJK87lSdLcuE0w5Y4p44U7Rs1luh4UYoxep0PRaP28/8PC", - "9nMss6DqmnY+yUX4JCWn/VSAly5JBgYG1oYCny4DlP/NRwHbWXJ2AWFlHzTLXFKZ+RZRPYNXYSQ77qNe", - "CJnPWd8FelHPzBpH0H7QUCS5FLr7prkw8lcy5DPd9r2sHRfuKOthYpNto1epgWsB0lVAQ2EvFwoSLbzj", - "6C44dqHCla+/ChLUYEJHC9xgmpXXTR4ZTGxLMa0Kdd4z4QKJhIIa6GSQ7WV4zl3Ifma/+ygZn9h0rzql", - "ptf9Kd69CzBTPSSGVL8g7rbcH31zFc0K49xWoFWx1C/coDJU/ZdSZFVqL+jwYNTap9GJlXawkqhSIu2v", - "siMQByGMF7A9shK/z43vdzAE2kpOFvQgZUBnk29U16RicC9vBLxPqaaZTkoh8mRAs3/az1fTpfgLll5A", - "RsxN4V3lBuqMkLuoUK5Nt5errc/PUpbAIbs3I+SEW+dkb8VtJ0zuTM7v6F3zb3DWrLIppJwGafaGx708", - "MbmTvCY388Ps5mEKDKu75lR2kD3ZUDYDuXIkvYxU3ZmNfYL27ardSigNUVkoYjJJU+Rjj1NI7Q/S1Flo", - "fEL60kGei8sEqSipk13F3hymXZtJ+vSeTTeD7TkEziVUuQt0S1Y0I6mQEtKwR9yf3wJVCAlJLtDXJGYG", - "W2gjDxXoxMtJLpZElOaZa3PGeYNBtAhIWI1vRxGQ884psu3QGcEt8eBKH26XugU/9qqEAzBHUEdv+IhW", - "LVLIpL2ubimcocJUWhQs7Q/XQs1n4c8w6IWwp0xLZH31yXNVZHz82QCuosbB3bY4W3JrPtYiV2fnje5T", - "NBNsstdG14JhlKXuUDAWWMIuoREkn9Yi47RVYZR1ivT4zGmWxlNqn4wrIGbsSoKLh7K1tjpFQkqqV/4K", - "Mc37DzvzSACFwUq20ARVVg3h1SGu0Ff3bhZlksMaWqZLF6RVpSkoxdYQFgmznUkGUKJysCuyxmxy4d3W", - "kWPc2pPAqjMGu1HBxiLW7hTZI7VEZawNT+wxUWOPkoFozbKKtvCnrlF2aajiUoQNe1hHcoqDmUR8cbtY", - "xF4rOtJ89FzyuBE9jBGsNRI4W1ZrLi0RNidblfSSD0vwEaVPbdm9/joIDkZUJ2Z3sO6XrHflqq+3QcrY", - "RRi9UmlRmUOBL3UZptfwso7rGxFwrJ6JqcgATDXnGf3EoPFDCpoVdEsytliAtJp0pSnPqMzC5oyTFKSm", - "zDwrturqMqWBVlYw3StWGu6Kg3oGExMwUSlkAcm3Tl6Pi3wF3Ri5Et1nBnbAxbuiVGmpW3CUTkhBLyKo", - "P1yh7IFQ7HfYDQOmqHAqMS0QpGvPHzPGXTH51iiO27dTRy6loFzbbmtKmJuvCfqR1t0Bta/+XuuS5g/N", - "fTeucJzvsAe80MgWlI7z+g4HzieOnvmhRkqwlLdDlNBa/j67nVtgIyAEW+TOr9ZgM6Vaj+v2vgRGWfWs", - "tnUOVTnsmkQxEZ/gmJy0b0q1LAVrnISEYw6KXNP845tDMUPjCeLD1Z+PLzS0p4VItqhUV3Ndf0FHzR3Y", - "zm5uav4Kzbf/A2aPoo9JN5STPGpp3Ht74IVAc/vYX/gaT2vg5BLHtI5OD78gcxfaW0pImepKNJe+/EJt", - "PsJqRC5cYKP32Kv2rfNnoa9Bxgv/QCAvm1Tu+DRf8gbC5oh+YqYycHKjVB6jvh5ZRPAX41Fhjq0918VF", - "ywPKlsbouPYLCTfsCRX4NB/oCdXPHjZ2edbbx1w6lYL+Okff1i3cRi7qZm1j3fj6yN2V73uM9108jb/p", - "ju5/FiFYA4MgqOS3h78RCQsscifI/fs4wf37U9f0t0ftz+Y4378ffT98NMc/iyM3hps3RjE/D4WC2XCn", - "gajDzn5ULM/2EUYrhrQpE4lRkr+6SPVPUqjyV+uf0T+qrljYIS7H3U1AxETW2po8mCqIDh0RGOq6RcJA", - "0faRVpLpLSbQ889+9mvURfG72gPIeZDVzzp392lxAXUKxsZfqFL+dv1O0BzvI/va5OYWEvmMfLOhRZmD", - "Oyhf3Zn/Jzz+25PsweOH/zn/24OnD1J48vTLBw/ol0/owy8fP4RHf3v65AE8XHzx5fxR9ujJo/mTR0++", - "ePpl+vjJw/mTL778zzuGDxmQLaATn65l8r9YzTU5eXWanBtgG5zQkn0PW1s4zpCxL0lHUzyJUFCWT479", - "T/+PP2GzVBTN8P7XicsGMVlpXarjo6PLy8tZ2OVoiQ4CiRZVujry8/Rq1p28Oq0tK1Z5gztqAym9Us6T", - "wgl+e/3N2Tk5eXU6CypqH08ezB7MHmIB5hI4LdnkePIYf8LTs8J9P3LENjl+9346OVoBzdGfzvxRgJYs", - "9Z8k0Gzr/q8u6XIJcubq9Jmf1o+OvFhx9M45Srw3MyxjaQZtDHEQONovX+ecrlCHZGOEW+VglKtOMq2L", - "BDkdIc8wtNP6Hhg2VyPuNGuy4Z82TMvnBLRJko9/iVQvXrAlagx8qrpWCUFXQYwp8t9nP74kQhL3vHlF", - "04va3EBOFza/kxRrhhGDWRBmanrOPP3+qwK5bejLcb4wAbCv+eLsFoValu2gpUaqiplWYqUCcWZDFgFh", - "125NDeNCzUpYYLVmw4a1Pki+fPvu6d/eT0YAgj52CjA91G80z38jlwwrzqFa0CdYdAm0ppH6JihNTxs3", - "GezQ7OQUo67qr2EJu7pNO9b3Ny44/Da0DQ6w6D7QPDcNBYfYHrzFBEZILHjmHj14cGO1L+vwdmtcqUfx", - "JHGFgfoMyX6KlLP3JTAHatk/ucGFtoNTrr3c7nC9RX9NMywrBkrbpTz8bJdyytHN1VwQxF6A76eTp5/x", - "3pxyw3NoTrBlkB2wf9H8xC+4uOS+pRF+qqKgcouiTVD7sJM6gy4V+lAgi7Rnu1XtbPL2/eCtdxQWczp6", - "1/KUzK51J/bq2J0+33NN3lFDnLOfW7tTK8p8r0sBoS+dK4iFxYnUvRn5LuyN3BtTVdlEUJXkkHlHR3/r", - "1bk3fUbPBrY7KsziFb20A3Xx7f39qe/vk7ayo5W/OQZM6xTshKnnbX3dC7Rv4exUA75Std2gatMVal98", - "0JKEnbemnelt7Cm4l1Hf4m4Ad0NiUgBvLTG1q219eNbsIxLrm6R1ZXxAxv2ZC30/0NzQSbDcTrYWm9T8", - "Vhj8ywiDdQCOLerv63hcTzzEin5H73yi+hsQCV2i/hHCYPisDvoGzhp3O+zk3sxmnQ/bXI1nuIibvWIe", - "lg+4FfD+AAJevzRHDIym4MKnE+oQhlVTu+OQivytUpsH1Rj5TKW4vzCyBsU2A+l+ge0K7LMnjDlm/cHY", - "6p9SCHNIuxW//tLiVx0Hey0BrFVcx0VWB2asa2nvuto5pmtJrB0LHXA29IQ2DMUd4WlTCNCwGMyb6FNm", - "qal/GaI51T4a7WZNe+/Gvoj1HYQP1K+3p8/3SVefkZ5ndP7eyC0Q35sPzUujZofXH8fsMI43PXnw5ONB", - "EO7CS6HJt3iLf2AO+UFZWpysDmVhuzjS0dxWL9jFlXiHLSGjaKoSBDyqTgIxDb6b1tZL464rvR1mfLo3", - "I75WgqorP7l4rKUwjMqHglC5tJ0MrzPIIHf8n8c4/p0Z+RbjcLSaorOZdmWByB3G9fHDR4+fuCaSXlpf", - "rm67+RdPjk+++so1aypj2HdOr7nS8ngFeS5cB3dH9Mc1H47/9x//N5vN7uxlq2Lz9falTRH7R+Gt/edd", - "SABDu/WZb1Lste6LPexD3Ucx338tNtFbQGxub6FPdgsZ7P8pbp95m4zcQ7TWZLby6tzgbWSPySH30dRX", - "gTB8p75MZuSlcCnOqpxKImQG0pXKW1ZUUq4BspmnVIwVVTalU5oz4No8HLH4l0wUy8BmhllWEuqot1LC", - "Gn3kcXp807cg2M/o0ZP2D8vkf6CbIO3RvL6mtXBLRrVnQTe+/CAW2BISf/rqK/Jg2rxe8twMkNSIiTHX", - "gm4mH1HrVxPbKP/zdvWevQ66OPYYDVIj/dShr2GpkL825/5sJXdL7m5jb4hzHmz4aQw7oR7BJRLbqUGw", - "gp0tTojV8rZNEL2R8rwIFWdxZoaxyoE/sI1gr2o6+gjtovf2EN8qAa7FSroEdSDbwKhTdfQO3+Uhz+id", - "W4ya+2uZSwPbkRSFNx4JsgCdrlzAbgf1EfbkawcN86Zd1alvWqrBXewnqwjzOGPV5JGpwoJYSjTggYwQ", - "8Y8+jb/5zBY2L4zPo+SLsKNpivm6pHVJUle42WWI0KKO6zW7eBCUz5rJ+wIZouUm7J+3CD4MwT3m+I2v", - "TYkYc4v4M3j8+6dkQl6KJmzclUX6M5oeP+TN/qEX9FJwsDZ2I/laWrw1p9Zih2EcFik+X4h9v9QFKq4s", - "ghz5Kqs75ZC/2xqnO2WRMbe3meyzvML/7rC045Yxa5vtTYbQjDaGOZuGNnlVu4rEJ3zFfBJ++gd82nwK", - "jvVxWAweUs9nnFjAb5bpYAoeS8xHdQGBIQ4Ur8kymhtpUbuhRcuozCEXfKn+mKxoF3XE8RKhkrpaTbwk", - "zV/v7D7D7D7myWs9IF2+J8V4CraKMBZAY4oUTCnnLPnkwd8+HoSaFT4LNw9jVz8xd3n64PHHm/4M5Jql", - "QM6hKIWkkuVb8hOvKz5fh9thwZ06/5rXBkdrLKG1qZ0XLA2TGF2dCbZc197pDcve72eGQaLJA/kg4wEf", - "DLOz0rIEKq/OAPebrrpZsk+fh97BrTowdUatCCgGRQc6yP/HZKTeCcPexcJdfhW3gPrsX45NONddsZjW", - "zjFGChCLY/KG3ydqRZ8+fPTro6df+D8fPf1iQHNm5nFJe/q6s2Yg89kOM0aB9lmrA29Waq/xe/yxd/uw", - "TZxOWLaJVopoar/1clU7seyOIiXdDpaTKffUrguHberYffxkh0qz+Sr6vvLPnzob/Cn/un4F24x8ruTb", - "bc26geCJgM8YQmuK19VY313Hboc02SHLumDYx36cNkEG9qLzyJOdO+eTCrr6Uz1SE3yjAveCTRstn06m", - "xGom08DcXUqhRSpy67tSlaWQuj7dajZK3IMhs11L2hsi3IOEuZTqdFWVR+/wP5jh630TeGArdAd2vvr3", - "dSEy8KKgWCxc5FXcdQB0OyGceRYoTYuS2J5Riz+yyHNWwJlp+aOd4kYv1wbsztXaAc9gV0EqeKZGsCo3", - "6hgeVAd3dWr96mEAPjpbqnfAw+J8smZXfri8Dhy7e5RAushXmMjPR6A5ZGSwJoWrJXoAsUfJ9uid/RfN", - "2KWIVRw68wTc25i7bltsSJ0dtwUgeYXip43N873EgjywkXUVR/NZk7EXyznLrbkevSOxBJqTtKU2r+Ho", - "n5yzwZOz94HVW93AmuLPGtGc0JvUMXVMlt9/9APwjHJH8n0EaUEo4bCkmq3BK5Nnt25uV9ZpOCezHQxw", - "SmiW2dPYbAKsQW6JquYK/lU1QoLVftxR7fNyAMOATQmSFcA1FqJ1v+ZYqvvI+rDt0nKc2RbXvLQ6vMh6", - "znXq9/ib1fnViQX5gaVSnGCJOPfYUluloejlw3Vdfx2IhPbJq/sPM8FzxiEpBI9laf0Rv/6AH6O1SYSm", - "+VBnrLc01Ldz37bh74DVnmfMnXxd/P5BTv+1DCid1UowQmxTxdvS/4FHyR+aLU/7J2nL076Q2SolPvDz", - "0bvWn86D1bVUq0pn4jLoi+pPK4mPcV4LqkeMtxrXGsFOFQZFMlCGaD8/E02Ah9iJqb9G8nEGNUIGU3L+", - "RY02C8azDpGgPiUVa6yEFNopby03fy7Lzeh9P4jH2vzT+zhapW5WInkpMrDjttO/x5ImcJGBS5PdF0Rq", - "DURc2+1vpaZdR/+Y0mq50gQLfMY0nU3HhKaWySb2ebOvOrt7BLkKf2sgNMfk42QOwImYm0U39yMukioM", - "FPPqUqdniRcZb+AqpUhBKcgSnyRiH2h18nFUruodeELAEeB6FqIEWVB5bWAv1nvhrIt3KHL3+5/Ng/mj", - "w2tFwd2IteEpEfTWLrBO2utDPW76XQTXnTwkOyqBeNEArTuiKHNw9p0ICg/CyeD+dSHq7eL10YIGEPaB", - "Kd5Pcj0CqkH9wPR+XWirMjH3dx/EZ/brOStQEuOUC69XjNatpEon+9gyFtAK1qLMCgJOGOPEOPDAg/MF", - "Vfq1M/WHlWSDQl1mimGA10NFYszIP9clYnpjp+Y+5KpSdR0Zp76PV3PlsNkx10vY1HOhr4Ufu7YPWA3f", - "vpGHsBSM75AVZMogVAdOElhGq7841D9Sp6Doo7IFRIOIXYCc+VatMsWNAX8AEKYaRNc1VduUE9S0VFqU", - "JVZKTipe9xtC05ltfaJ/atr2icuV8sN7OxOgQtuNg/zSYtYWKF1RRRwcpKAXzryzdJkPI6XXWQEJumUl", - "uygfVbamVXgE9h7SqlxKmkGSQU4jqpSf7GdiP+8aAHfck2eyFhqSOSyiZbnMpjeULAdVRPXQAsdTMeGR", - "4BeSmiO4wDpxnkBc7z0jZ4Bjx5iTo6M79VA4V3SL/Hi4bLvVQ2XA1wINeo4eEGTH0ccAPICHeuirowI7", - "J436oDvFP0C5CWo54vBJtqCGltCMf9ACuuq88AJr3RQd9t7hwFG2OcjG9vCRoSMbUyB+lsr+rtfSB3TJ", - "bitQgwfg7CqP26NLynSyENIK0gldaJARXV6nsg9l2ofMW9OAFs5hkOAI7t504yCTD/NPOS5iQSC+NDwr", - "IunszFTfCjkq7rXt3U2ZJhXXLA9yf9RP5T+ewvBWCXCrBLhVAtwqAW6VALdKgFslwK0S4FYJcKsEuFUC", - "3CoB/rpKgE8VyZ54icPH93DBk65XIrn1SvxTRX7Wd5VXSqAa45Iy7VJZE+rlAPxyvcB3DTRHHLAchv2k", - "rfvm+TcnL4gSlUyBpAZCxkmZU/M2gI2uE6u2U3b7YgI2O7PNBk4VPH5Ezv5+4gPUVi6Qqt327okryqH0", - "Nod7LnVRXa/c5zACbpDuUhhRfyf4BKwuHS3L0cdckW+w9XNYQy5KkDb2hWhZRVQ+50DzZw43ezQ+rRLT", - "ZrTfpi1Fk0NbQUsv5/u1UkWojVpsV4he0FwNl4i24xW0jOVArW8+qwtCbvK1wJLq4Qkxu3aEG9g+G02Y", - "GuNUbiMhqv1ggi5paGH4lSOsvjLr/Y0HU/aJtk9m+ygsJq5LUNFzvIvKo1GE9Yb1hrLBrosOnUxi2ce6", - "oXOTGsAxLrDnGHBg94S8tv0+baoWhMgdsYaZ/2E8B9sta6aBbc0rwrGez9Ur3yM+enrx7E8NYWdVCoRp", - "RXw85v7rZTrZJGakJfDEMaBkLrJt0mJfk9YtlDFFlYJivv8mCvmny/rvLh/zZfc99WmukefB4nbx5JBo", - "NoljwAPceathNG+usYUjOvYcYPxDs+ghNhqCQBx/immVujXXDmR6zTTbW8Z3y/iC09iRCBh38etdJjL7", - "gIxPbmXFh3neNxtIKwNceJLvonoebXKw0S3DZgbzarnE6gU9I51ZGuB4TPBPxArtcsdywcMoyA5eZ7S+", - "bhLF7nB97hLEat8VkiylqMp7tlwj36I1oygp33qbLySKFVVucWgTv94so7Uh5n1PALTHOuXfkFr7ldf5", - "Bcpbd9W2f7doIZdUEbu/kJGKZy52qJeIYsPHV06wQ59veMOmd9ZOsOuNrM7NO+aK8LvcDtpWpASZ6A23", - "B6pd3sQmvLAnd3abtf2vcW3YkG8YYLD95A0NQ7ih20MGfA2vjyBFVxMM1645aSviDoWOhPm6bMsb9R7p", - "Dd92Ignq0VojKeQlob6kTiq40rJK9RtO0UgTLGzWdzDx2uhh/vbMN4nbCSNmPDfUG06x4kptuonyuQVE", - "7BTfAng2qqrlEpThlSGRLADecNeKcVJx89ISC1KwVIrEBqKaM2Tkk5ltWdAtWdAcrYy/gxRkbm72YNet", - "wlhplufOo8VMQ8TiDaea5ECVJj8ww2XNcD75QO3KBfpSyIsaC/H0TUvgoJhK4sqX7+xXzJDklu+VfKiw", - "tJ+bzCYfNzWSh51lg5CfPjdwU0wGlzOlGyeIHuwfzQBeMJ5Eiex8BcT5hHVpi9zFXGmOgO61rUN6BW+4", - "ueG0IMjVqb4aOXTNPL2zaE9Hh2paG9GxBvm1jnri3QiXIREmc2ta+ROFZgZ04M2XuPGYw6W79weaUXaW", - "eY59dRk1Bxq5R0JLEdZJB+NanLdA/vNWY3n7Yd6LHo039mLsD9hnV+2ciYg3v+FTQnPBl+SS6RW+IAXu", - "E+NlpdGx+kMq6WBN80SsQUqWgRq5Uib4N2ua/1h323MBBhlfiwIyRjXkW1JKSCGzebKYIs0jeWYzDZB0", - "RfkS70opquXKNrPjXIKEOjmmeZd2h4jnKdnwBIXziLPDCbEKRn8kDLvAcsTh5Yqd7Y1iHsJ+B20aiDFP", - "3cgR/s6MOfTynU4GJVuD1HXjkGaR0z7XI67t1gUc4KeZ+CZK4txS2S2VHUxlsRR7iLpF581t8RVuy5+n", - "VtCfsizQn6uEzocUlj/0aj6U7O05kCIU67SHx7WTBdeeXqoI0+QSE+rMgZgLo0Ids6vz4V6kM2IYUqBP", - "t5kXlUs/na4o4y4bS+2ej3BolyJf+5y8H0Q9Z5kZ6uUMOiCtJNNblMtpyX69APP/t0awtYWjrcheyXxy", - "PFlpXR4fHeUipflKKH00eT8Nv6nOx7c1/O+8tF1KtsbSa2/f//8BAAD//0LRY245WAEA", + "H4sIAAAAAAAC/+y9a5PbtpIA+ldQ2q3yY0WNn9mTqUrtndhOzmxsx+WZZPds7JtAZEvCGQrgAUCNFF//", + "91toACRIghI1I4/jZD7ZI+LRaDQajX5+GKViWQgOXKvR8YdRQSVdggaJf9E0FSXXCcvMXxmoVLJCM8FH", + "x/4bUVoyPh+NR8z8WlC9GI1HnC6hbmP6j0cS/lUyCdnoWMsSxiOVLmBJzcB6U5jW1UjrZC4SN8SJHeL0", + "+ejjlg80yyQo1YXyR55vCONpXmZAtKRc0dR8UuSS6QXRC6aI60wYJ4IDETOiF43GZMYgz9TEL/JfJchN", + "sEo3ef+SPtYgJlLk0IXzmVhOGQcPFVRAVRtCtCAZzLDRgmpiZjCw+oZaEAVUpgsyE3IHqBaIEF7g5XJ0", + "/MtIAc9A4m6lwFb435kE+B0STeUc9Oj9OLa4mQaZaLaMLO3UYV+CKnOtCLbFNc7ZCjgxvSbkVak0mQKh", + "nLz97hl5/Pjx12YhS6o1ZI7IeldVzx6uyXYfHY8yqsF/7tIazedCUp4lVfu33z3D+c/cAoe2okpB/LCc", + "mC/k9HnfAnzHCAkxrmGO+9CgftMjcijqn6cwExIG7oltfNBNCef/rLuSUp0uCsG4juwLwa/Efo7ysKD7", + "Nh5WAdBoXxhMSTPoLw+Sr99/eDh++ODjv/1ykvyf+/Pp448Dl/+sGncHBqIN01JK4OkmmUugeFoWlHfx", + "8dbRg1qIMs/Igq5w8+kSWb3rS0xfyzpXNC8NnbBUipN8LhShjowymNEy18RPTEqeGzZlRnPUTpgihRQr", + "lkE2Ntz3csHSBUmpskNgO3LJ8tzQYKkg66O1+Oq2HKaPIUoMXFfCBy7oj4uMel07MAFr5AZJmgsFiRY7", + "rid/41CekfBCqe8qtd9lRc4XQHBy88Fetog7bmg6zzdE475mhCpCib+axoTNyEaU5BI3J2cX2N+txmBt", + "SQzScHMa96g5vH3o6yAjgrypEDlQjsjz566LMj5j81KCIpcL0At350lQheAKiJj+E1Jttv2/z358TYQk", + "r0ApOoc3NL0gwFORQTYhpzPChQ5Iw9ES4tD07FuHgyt2yf9TCUMTSzUvaHoRv9FztmSRVb2ia7Ysl4SX", + "yylIs6X+CtGCSNCl5H0A2RF3kOKSrruTnsuSp7j/9bQNWc5QG1NFTjeIsCVdf/Ng7MBRhOY5KYBnjM+J", + "XvNeOc7MvRu8RIqSZwPEHG32NLhYVQEpmzHISDXKFkjcNLvgYXw/eGrhKwDHD9ILTjXLDnA4rCM0Y063", + "+UIKOoeAZCbkJ8fc8KsWF8ArQifTDX4qJKyYKFXVqQdGnHq7BM6FhqSQMGMRGjtz6DAMxrZxHHjpZKBU", + "cE0Zh8wwZwRaaLDMqhemYMLt753uLT6lCr560nfH118H7v5MtHd9644P2m1slNgjGbk6zVd3YOOSVaP/", + "gPdhOLdi88T+3NlINj83t82M5XgT/dPsn0dDqZAJNBDh7ybF5pzqUsLxO37f/EUScqYpz6jMzC9L+9Or", + "MtfsjM3NT7n96aWYs/SMzXuQWcEafXBht6X9x4wXZ8d6HX1XvBTioizCBaWNh+t0Q06f922yHXNfwjyp", + "Xrvhw+N87R8j+/bQ62oje4DsxV1BTcML2Egw0NJ0hv+sZ0hPdCZ/N/8URW5662IWQ62hY3clo/rAqRVO", + "iiJnKTVIfOs+m6+GCYB9SNC6xRFeqMcfAhALKQqQmtlBaVEkuUhpnihNNY707xJmo+PRvx3V+pcj210d", + "BZO/NL3OsJMRWa0YlNCi2GOMN0b0UVuYhWHQ+AnZhGV7KDQxbjfRkBIzLDiHFeV6Uj9ZGvygOsC/uJlq", + "fFtpx+K79QTrRTixDaegrARsG95RJEA9QbQSRCsKpPNcTKsf7p4URY1B/H5SFBYfKD0CQ8EM1kxpdQ+X", + "T+uTFM5z+nxCvg/HRlFc8HxjLgcrapi7YeZuLXeLVbolt4Z6xDuK4HYKOTFb49FgxPxDUBw+KxYiN1LP", + "Tloxjf/u2oZkZn4f1PnLILEQt/3EhQ8thzn7xsFfgsfN3RbldAnHqXsm5KTd92pkY0aJE8yVaGXrftpx", + "t+CxQuGlpIUF0H2xdynj+EizjSys1+SmAxldFObgDAe0hlBd+aztPA9RSJAUWjB8m4v04u9ULQ5w5qd+", + "rO7xw2nIAmgGkiyoWkxGMSkjPF71aEOOmGmID3wyDaaaVEs81PJ2LC2jmgZLc/DGxRKLeuyHTA9k5O3y", + "I/6H5sR8NmfbsH477IScIwNT9jg7I0NmXvv2gWBnMg1QCyHI0j7wiXl17wXls3ry+D4N2qMXVqfgdsgt", + "AndIrA9+DL4V6xgM34p15wiINahD0IcZB8VIDUs1AL7nDjKB++/QR6Wkmy6ScewhSDYLNKKrwtPAwxvf", + "zFIrZ0+mQl6N+7TYCie1yplQM2rAfMctJGHTskgcKUbUVrZBa6DayredabSHj2GsgYUzTT8BFpQZ9RBY", + "aA50aCyIZcFyOADpL6JMf0oVPH5Ezv5+8vTho18fPf3KkGQhxVzSJZluNChy173NiNKbHO51V4avozLX", + "8dG/euIVlc1xY+MoUcoUlrToDmUVoFYEss2IadfFWhPNuOoKwCGH8xwMJ7doJ1a3b0B7zpSRsJbTg2xG", + "H8KyepaMOEgy2ElM+y6vnmYTLlFuZHmIpyxIKWREv4ZHTItU5MkKpGIiYk1541oQ18KLt0X7dwstuaSK", + "mLlR9VtyFCgilKXXfDjft0Ofr3mNm62c3643sjo375B9aSLfaxIVKUAmes1JBtNy3ngJzaRYEkoy7Ih3", + "9PegURQ4Z0s403RZ/DibHeapKHCgyJONLUGZmYhtYeR6Bang1hNix+vMjToEPW3EeBWd7gfAYeRsw1PU", + "Mx7i2PY/XJeMo9FDbXgavGINjDlk8wZZXv+12ocOO9UdFQHHoOMlfkZFx3PINT24RNeeIAb7M0/aFliS", + "mYa4Wy/ZfKEDkfuNFGJ2eBhjs8QAxQ/2wZKbPt1ny2uRmYOmS3UA8aQerD79Zk/DM0+notSEEi4yQB1T", + "qeKCS48vAxpR0farQ1lIL+wbZAqGkFJamtWWBUHLZoeX1h0TmlrqTRA1qseuUxnkbCs7nbWT5xJotiFT", + "AE7E1BlPnFkHF0nRLKv91e/EpshZasBVSJGCUpAlTmmzEzTfzrJVvQVPCDgCXM1ClCAzKq8N7MVqJ5wX", + "sEnQiUCRuz/8rO59Bni10DTfgVhsE0Nv9QR2FrIu1MOm30Zw7clDsqMSiOe55r1tGEQOGvpQuBdOevev", + "DVFnF6+PlhVItFV9Uor3k1yPgCpQPzG9XxfasuhxjXNPPyP9mA3jlAsvdMQGy6nSyS62bBo13qdmBQEn", + "jHFiHLhHKHlJlbb2VcYzVAvZ6wTnsQKKmaIf4F4R3Yz8s5fOu2On5h7kqlSVqK7KohBSQxZbA4f1lrle", + "w7qaS8yCsav3gBakVLBr5D4sBeM7ZNmVWARRXZkhnANCd3GorDf3/CaKygYQNSK2AXLmWwXYDd2DegBh", + "qka0JRymWpRT+SSNR0qLojDcQiclr/r1oenMtj7RP9Vtu8RFdX1vZwIUeiW59g7yS4tZ6xi2oIo4OMiS", + "XhjZA1UE1hDchdkcxkQxnkKyjfLx+WNahUdg5yEti7mkGSQZ5HTTHfQn+5nYz9sGwB2vn4JCQ2I9fOKb", + "XlOyd6jYMrTA8VRMeCT4haTmCJqXR00grveOkTPAsWPMydHRnWoonCu6RX48XLbd6siIeBuuhDY77ugB", + "QXYcfQjAPXiohr46KrBzUr/L2lP8A5SboJIj9p9kA6pvCfX4ey2gR7/onKeD89Ji7y0OHGWbvWxsBx/p", + "O7I9ys43VGqWsgLfOj/A5uBPv/YEURMcyUBTlkNGgg/2GViE/Yn1TWmPebWn4CC9VBf8jmIqspycKRR5", + "msBfwAbf3G+s0+N54Cp5gLdsZFRzP1FOEFDvSmVE8LAJrGmq840R1PQCNuQSJBBVTpdMa+vM3HzqalEk", + "4QBRnf+WGZ2ByzoM+h0YYnE7w6GC5XW3Yjyyb4Lt8J23HgYNdLi3QCFEPkB71EFGFIJBvhCkEGbXmfOr", + "9p61npIaQDqmjdbN6vq/oxpoxhWQf4iSpJTjk6vUUMk0QqKggAKkmcGIYNWczuuhxhDksAT7ksQv9++3", + "F37/vttzpsgMLn0wgmnYRsf9+6jHeSOUbhyuA+gKzXE7jVwfaAwxF597hbR5ym6ruxt5yE6+aQ1eWVDM", + "mVLKEa5Z/rUZQOtkroesPaSRYR4HOO4gO0cwdGzduO9nbFnmVB/CogMrmidiBVKyDHZycjcxE/zFiuY/", + "Vt12vOlqDym2XELGqIZ8QwoJKVjPdSOqqWrsCbE+bemC8jlK6FKUc+dUZcdBDlsqqwuRJe8MEZVi9Jon", + "cynKIsZxnSOtD14w8gtQ84YKdgQ72xfDJa3mc/EqQ65Cv3PBNn9vxuyz2IxHvU9Mg9RV/cS0yGlGYAzg", + "vg0BK8BPPfFAvT6izggbXXyF22Ko12zup9GR10PHoOxOHLh51R/7PL3M+zbfHEDKsAMRCYUEhXdCqBdS", + "9quYhdFW7tJQG6Vh2VWd266/9hy/t70PNMFzxiFZCg6baIAx4/AKP0aPE95LPZ1RQujr2xb6G/C3wGrO", + "M4Qar4tf3O3ghL6pXBwPsPntcVtWkzDODLWCkBeEkjRnqDMUXGlZpvodp6iVCA5bxBXEP7/69VTPfJO4", + "Yiyit3JDveMU3YAqXUXUfD2DyMP8OwCvrlLlfA5Kt6TbGcA77loxTkrONM61NPuV2A0rQKI/xsS2XNIN", + "mdEc1Wq/gxRkWuqmvIexLkqzPHcmHDMNEbN3nGqSg3myvmL8fI3DeROopxkO+lLIiwoL8etlDhwUU0nc", + "ZeV7+xW9Cd3yF86zEGOT7Wer9Dfj1wExG1Ra1PG2/+/d/zr+5ST5P5r8/iD5+j+O3n948vHe/c6Pjz5+", + "883/1/zp8cdv7v3Xv8d2ysMei8RwkJ8+d2+h0+co8NZa/w7sN6bxXTKeRIkstG23aIvcxahDR0D3muoQ", + "vYB3XK+5IaQVzVlmBJGrkEObxXXOoj0dLappbERL/eHXuqcYeQ0uQyJMpsUar3yNd7284jFPaIZyYUx4", + "XmYlt1vpxT/r0u+9bcRsXMW12ZQXxwSDnhbUu4q5Px89/Wo0roOVqu+j8ch9fR+hZJatYyFpGaxjrwN3", + "QPBg3FGkoBsFOs49EPaoY5G15ofDLsE8K9WCFTfPKZRm0ziH847STsuw5qfcejCb84NGrY3TlYvZzcOt", + "JUAGhV7EQuEbkgK2qncToOVoUEixAj4mbAKT9is/Mw8W5+KUA51hSDY+f8QQcbw6B5bQPFUEWA8XMugp", + "HaMfFG4dt/44HrnLXx1cHncDx+Bqz1lZsPzfWpA73784J0eOYao7NjrSDh3Es0Xeci5ko+GCYriZTQBi", + "w0Pf8Xf8OcwYZ+b78TueUU2PplSxVB2VCuS3NKc8hclckGMfBfKcavqOdySt3hw9QfwNKcppzlJyEUrE", + "NXnavAvdEd69+4Xmc/Hu3fuONb4rv7qpovzFTpBcMr0QpU5c1Hgi4ZLKmLVDVVHDOLJNC7Ft1jFxY1tW", + "7KLS3fhxnkeLQrWjB7vLL4rcLD8gQ+Vi48yWEaWF9LKIEVAsNLi/r4W7GCS99A/7UoEivy1p8Qvj+j1J", + "3pUPHjwG0gin+81d+YYmNwUMft73Rje2X/W4cPuugbWWNCnoPGZUeffuFw20wN1HeXmJj+w8J9itEcbn", + "3ZRxqHoBHh/9G2Dh2DskCRd3Znv5DEHxJeAn3EJsY8SN2tR71f0KAvuuvF2t4MDOLpV6kZizHV2VMiTu", + "d6ZKHDI3Qpa3vys2Rx9Hl2NlCiRdQHrhkl/AstCbcaO7d/FwgqZnHUzZtCg2LAcD81ElPQVSFhl1ojjl", + "m3aEtAKtvZPlW7iAzbmo4/r3CYluRuiqvoOKlBpIl4ZYw2PrxmhvvvMjwqjEovCBrhjx5MniuKIL36f/", + "IFuR9wCHOEYUjQjSPkRQGUGEJf4eFFxhoWa8a5F+bHnmlTG1N18kRYrn/cQ1qR9PzuUnXA2qfe33JWCO", + "JXGpyJQauV249EA2CjXgYqWic+iRkEOrwMBYz4YlAQfZde9Fbzoxa19onfsmCrJtnJg1RykFzBdDKviY", + "aTl6+Zms4cmpxjHrn0PYNEcxqfKIs0yHyoZ1xqYx6wMtTsAgeS1weDCaGAklmwVVPnMRJnjyZ3mQDPAJ", + "o6q35dI4DXyUgixOVaYMz3Pb57TzunQZNXwaDZ87I3xaDsiDYSR8dIuObYfgKABlkMPcLtw29oRSR3jX", + "G2Tg+HE2yxkHksTcnahSImU29VR9zbg5wMjH9wmxKmAyeIQYGQdgo0EVByavRXg2+XwfILmLUKd+bDTF", + "Bn9DPJjGOgAbkUcUhoWzHrNK6jkAdT5y1f3V8tTEYQjjY2LY3Irmhs25F189SCelA4qtrQQOzqR/r0+c", + "3aKBtxfLXmuyV9FVVhPKTB7ouEC3BeKpWCc2mi4q8U7XU0PvUZ9ojO2LHUybPOOOIlOxRjcRvFqsD+4O", + "WPrh8GAEL/w1U0iv2K/vNrfAbJt2uzQVo0KFJOPUeRW59IkTQ6bukWD6yOVukA/jSgC0lB11cln3+N35", + "SG2KJ93LvL7VxnWeJx9uEjv+fUcouks9+OtqYaoMFm/aEktUT9H0dmgm7whEyBjRGzbRNdJ0TUEKcsBH", + "QdIQopKLmOnOvG0Ab5wz3y1QXmCKEMo39wIXGglzpjTUSnRvqP8c6kmKmcmEmPWvThdyZtb3VojqmrKp", + "b7BjY5k3vgL0QZ0xqXSCFojoEkyj7xQ+qr8zTeOyUtNJx+bxZFmcN+C0F7BJMpaXcXp18/7w3Ez7umKJ", + "qpwiv2XcekxMMe9s1HVvy9TWu3Prgl/aBb+kB1vvsNNgmpqJpSGX5hxfyLlocd5t7CBCgDHi6O5aL0q3", + "MMgg5LLLHQO5yR5ODLmcbNO+dg5T5sfe6TbiAz/77ig7UnQtgcJg6yoYmomMWMJ0kLa1GwvZcwZoUbBs", + "3dKF2lF7X8x0L4WHT3bVwgLurhtsBwYCvWcsHEOCauY1qwV8m4C3kVZkMggz583sYyFDCKdiyqeP7yKq", + "CtfahatzoPkPsPnZtMXljD6OR9dTncZw7Ubcges31fZG8YymeatKa1hC9kQ5LQopVjRPnIK5jzSlWDnS", + "xOZeH33DrC6uxjx/cfLyjQP/43iU5kBlUokKvavCdsUXsyqbQq3ngPj01ObN52V2K0oGm1/lfQqV0pcL", + "cHl+A2m0k5CwNjgER9EpqWdxD6GdKmdnG7FL3GIjgaIykdTqO2shaVpF6Iqy3OvNPLQ93jy4uGFZLaNc", + "IRzg2taVwEiWHJTddE53/HTU1LWDJ4VzbclEvLTJthURvG1CR6fbTeGs7kuK6QStVqTLnHi5RE1ConKW", + "xnWsfKoMcXBrOzONCTbuEUbNiCXrMcXykgVjmWZDEoa0gAzmiCJTRXOW1LibCldIpeTsXyUQlgHX5pPE", + "U9k6qJi/0Wnbu9epkR26c7mBrYa+Hv46MkaYSrN94yEQ2wWM0FLXAfd59WT2C600UuaHwCSxh8E/nLFz", + "JW4x1jv6cNRsnRcXTYtbWPeky/8MYdgE2LuLrvjHq8vp2TNHtIgKU8lMit8h/s7D53Ek0sUnD2Xo5fI7", + "hJ72YemABouptDt1LZh69t7t7pNuQi1U00mhh+px5wOzHGYx9Bpqyu1W25oGDV+3OMGEXqVHdvyaYBzM", + "HU/cnF5OaSzFoxEyDEwntQG4oUvXgvjOHveqcve3s5PAlly1ZTaKuQBZB6F1M6JcUWCw0w4WFWrJAKk2", + "lAnG1v6XKxEZpuSXlNvSGKafPUqutwKr/DK9LoXEHAQqrvbPIGVLmsclhyztqngzNme26kOpICgr4Aay", + "FXUsFbnSDFUQi0PN6Yw8GAe1TdxuZGzFFJvmgC0e2hZTqpCTV4qoqotZHnC9UNj80YDmi5JnEjK9UBax", + "SpBKqMPnTWW8moK+BODkAbZ7+DW5i2Y7xVZwz2DR3c+j44dfo9LV/vEgdgG4qh3buEmG7OR/HDuJ0zHa", + "Le0YhnG7USfRcG1btqufcW05TbbrkLOELR2v232WlpTTOcQ9RZY7YLJ9cTdRkdbCC89szRmlpdgQpuPz", + "g6aGP/V4nxv2Z8EgqVgumV46444SS0NPdc0AO6kfzhawcelePVz+I9pIC28iaj0ib1Zpau+32KrRkv2a", + "LqGJ1jGhNvFEzmrvBZ+Empz6vDaY/7ZKe2txY+YyS0cxB50ZZqSQjGt8WJR6lvyNpAsqaWrY36QP3GT6", + "1ZNIzt9m7km+H+A3jncJCuQqjnrZQ/ZehnB9yV0ueLI0HCW7V0d7BKey15gbN9v12Q63Dz1UKDOjJL3k", + "VjbIjQac+lqEx7cMeE1SrNazFz3uvbIbp8xSxsmDlmaHfnr70kkZSyFjyerq4+4kDglaMlih7158k8yY", + "19wLmQ/ahetA/3ktD17kDMQyf5ZjD4FvReR16vNQV5p056se0Q70HVPzwZDB1A01Js2cvzfPRw/jBRW3", + "dHnFdtewZb54POAfbUR8ZnLBDaxt+XYlPYQS5DyPkkxWfQ9s7JR8K9ZDCad1Cj3x/AFQFEVJyfLs5zry", + "s5VSXlKeLqI2s6np+Gtd/KpanL0Do3nnFpRzyKPDWXnzVy+XRiTnf4qh8ywZH9i2neXeLre1uBrwJpge", + "KD+hQS/TuZkgxGozqK5y2s7nIiM4T53krD6u3eoIQQ7rf5WgdCxACT9YxzHUjRp2YFMoE+AZvkgn5Htb", + "33YBpJHBBl+CPlWBT19rlfZlkQuajTGFwvmLk5fEzmr72BIuNoXzHB9CzVW0dGJB/sZhLsi+Gks8PGL4", + "ONv9tc2qlU6qjMuxAFTTos4JzVp2AnwihdiZkOdBpUobq2qGMPQwY3JpXnXVaFY+Qpow/9Gapgt89jVY", + "az/JD8897qlSBfX+qro9VVJDPHcGbpd+3GYfHxNh3uaXTNmyprCCZsxrFQDu1A4+Bra5PFlybillssct", + "V6Uw3BftHjh7RXpTQhSyFuL3FPpt6v59U7GfYa9ojqV2XvdOoT8bQVnVY/HlqlPKBWcpZjiKXdGu/ukQ", + "O9uAZFBtRa4/4u6ERg5XNJt85YrnsNibX94zQoe4rqI/+Go21VKH/VNjoc0F1WQOWjnOBtnYF0VwukbG", + "FbgklVgtN+CTQjZsl8gho+bwpDKb7ElGGHrT83j8znx77VQL6JN+wTg+IhzanOBntYFYnlGblwfTZC5A", + "ufU044/VL6bPBENxM1i/n/hyjjiGNf2ZZVs7d3eoE2/1dlZm0/aZaesy9FQ/N7yc7aQnReEm7S+ZEZUH", + "9Jr3IjhivUy8+ShAbjV+ONoWctvqroL3qSE0WKGxGwq8hzuEUZWPaJUmMkKrpShsQaybWDRLAuMRMF4y", + "DnWx0cgFkUavBNwYPK89/VQqqbYi4CCedg40Rwt3jKEp7cwb1x2qnZ/IoATX6Ofo38a68kUP46ga1IIb", + "5Zuqxqmh7kCYeIbFlR0iu3UsUKpyQlSGUQutyhYxxmEYt6+d07wAusegKxPZ7lpSe3L2uYn6AlGnZTYH", + "ndAsi+UM/Ra/EvxKshIlB1hDWla5JYuCpJh3pZmIpkttbqJUcFUut8zlG1xzuqBUTIQawnI1focx0GW6", + "wX9jiRX7d8Y5euztaui9OlwliT3l5uZIHanX0HSi2DwZjgm8U66PjnrqqxF63f+glJ6LeROQG04/sY3L", + "hXsU428vzMURZmfoZAu1V0uVPAEd+4Qv8IfPxirst8mV8CrrpA9Fg1JVQGy7AqK/FNgYL78e994g6Qa1", + "96u1UPY5+aa9PulUu+g4TclWFtQbcWQ9hGxsEUIR1872eQVZpyDzudN7mGTYkbN1PPNegFDvbtYF6Afv", + "y0oKypz5vWYWXcw6r/duHMIQf9h6g9uLcL7kvRq7H1Z9ft9EMT7PgeD3dqmgC3Ah81X1fLtW7/nkn4T2", + "V1e81o5Xed5H199VvOJUn1cd2qu8PXdp6e0y3Zv8h5+tnxwBruXmD6DK7Wx6p9BSV9q16qm6CakyGg/K", + "cNy4FeM1k/pzLNV5lZCeCqFYnUU7VkxpoD/dOdZDCnJEdcfyziwrSDWmTq+N9BJgn4xRZrKgdOFtrqWe", + "t2PlduhSLG3Lq9TNl77jQuuEPgXhezbX9GR4FqGTyhULmRLmnp0Dd9UDm0ENg12rZzNINVvtCDX7nwXw", + "IIxp7JUQtgpwEHnGKlddzFSyv4qtBmhbJNhWeIKMgdcGpy/Q5AI2dxRpUEM0+fXY3ytXSVKBGEDukBgS", + "ESrm6mC1ps76zFRFGYgF71pku0Od7qu3bk4QOHnFuTxJmhu3DqbcMmW8cMeguUzXvUKM0eu0Lxqtm/e/", + "X9h+jmUWVFXTzie5CJ+k5LSbCvDSJcnAwMDKUODTZYDyv/koYDtLzi4grOyDZplLKjPfIqpn8CqMZMt9", + "1Akh8znr20DPqplZ7QjaDRqKJJdCd980F0b+Svp8ppu+l5Xjwh1lPUxssm30KjVwzUC6Cmgo7OVCQaKF", + "dxzdBsc2VLjy9VdBgupN6GiB602z8rbOI4OJbSmmVaHOeyZcIJGwpAY6GWR76Z9zG7Kf2e8+SsYnNt2p", + "TqnodXeKd+8CzFQHiSHVz4i7LXdH31xFs8I4txVoVSz1CzeoDFX/hRRZmdoLOjwYlfZpcGKlLawkqpRI", + "u6tsCcRBCOMFbI6sxO9z4/sdDIG2kpMFPUgZ0Nrkg+qaVAzu+UHA+5xqmvGoECJPejT7p918NW2Kv2Dp", + "BWTE3BTeVa6nzgi5iwrlynR7udj4/CxFARyyexNCTrh1TvZW3GbC5Nbk/I7eNv8aZ81Km0LKaZAm73jc", + "yxOTO8lrcjM/zHYepsCwumtOZQfZkQ1l3ZMrR9LLSNWdydAnaNeu2q6EUhOVhSImk9RFPnY4hVT+IHWd", + "hdonpCsd5Lm4TJCKkirZVezNYdo1maRP71l3M9ieQuBcQpW7QDdkQTOSCikhDXvE/fktUEshIckF+prE", + "zGAzbeShJTrxcpKLORGFeebanHHeYBAtAhLMZWNKbc/EWiV6ovZBuRhSN41t3J1nS62Q/euQnLcOsm2H", + "/hAOy3sXG3GE0q45slMrHYA5gEA7w0cUe5FaKs11tavx9NXG0mLJ0ji6vyyXil5HiB2VYiLrq8jRFbLx", + "IXA9uIraJ7ebA23Vr+lQo2CVIHjgsQgA6DcTNmAYZCzcF4wZVtFLaATJp5XUOm4UOWWts++Tt1kaT6l9", + "tS6AmLFLCS4ky5b7atUpKahe+FvMNO++Lc07BRTGS9laF1RZTYjXyLhaY23xQBRJDitoWE9dnFiZpqAU", + "W0FYp8x2JhlAgfrJttQcMwuGXK4lSrm1J4FhaQh2o7KVRazdKbJDcIqKeWue2GOihh4lA9GKZSVt4E9d", + "o/JTX9GnCBv2sA7kFHszifjitrGInYZ8pPnoueRxO34YplgpRXC2rFKeWiKsT7Yq6CXvf0RE9E6Vcfn6", + "6yA4GFGtsOHeK19Wu3LVB2QvZWwjjE61tqjMocBX2wwzfHhxy/WNyFhW1cVUZACm6vOMrmpQu0IFzZZ0", + "QzI2m4G0ynylKc+ozMLmjJMUpKbMvGw26upirYFWljDeKdka7oqDegYTk3FRL2UByTfuyXANqRMtNxGJ", + "0161WvQVpOvsStx3nq6NdI1ORD1E4KJ+Uba2B0xwFJDIkl7AnvMo9jtsnwZzcTjdnxY465ApYobFKyYS", + "G8S6uzb3yO0WlJ7bbhkK8wzWAUzSum6gJtlfkG0af1VfnMOK4PkOO8ALDYZBGTyvu3HgfOZIoFcVUoKl", + "vO+jhMbyd9kg3QJrSSPYIscItAab9dV6jzf3JTAwq2eV3bavYmPbvItJBQXHRKtds7DlTVivJSQccxbk", + "iuY3b9rFbJMniA9XSz++0NA2GCLZolJdzQ3/JR00d2AHPNzU/A2aov8HzB5FX6VuKCfCVGK991zBm4Xm", + "VnEx8/WqVsDJJY5pnbYefkWmLky5kJAy1RaNLn0picoUhpWVXOjDWu+wve1a589CX4OMZ/6lQV7Xaenx", + "jT/nNYT1Ef3MTKXn5EapPEZ9HbKI4C/Go8J8YTuui4uGN5ct89EKUxASDuzVFfhn7+nV1c2ENnR51nPJ", + "XDqlgu46B9/WDdxGLup6bUNdErvI3Za7fIgnYbwkgemOrowWIVjPgyCo5LeHvxEJMyzYJ8j9+zjB/ftj", + "1/S3R83P5jjfvx+Vzm7MidHiyI3h5o1RzM99YW02dKsngrK1HyXLs12E0YiHrUteYsTnry7q/rMU3fzV", + "+pp0j6orfLaP+3R7ExAxkbU2Jg+mCiJdBwS5um6RkFa046SlZHqDyQC9/oD9GnW3/L7yZnLecNX70N19", + "WlxAlU6y9n0qlb9dvxc0x/vIPlu5uYVEPiEv1nRZ5OAOyjd3pv8Jj//2JHvw+OF/Tv/24OmDFJ48/frB", + "A/r1E/rw68cP4dHfnj55AA9nX309fZQ9evJo+uTRk6+efp0+fvJw+uSrr//zjuFDBmQL6Minnhn9L1am", + "TU7enCbnBtgaJ7RgP8DGFsEzZOzL69EUTyIsKctHx/6n/8efsEkqlvXw/teRy2wxWmhdqOOjo8vLy0nY", + "5WiOzg6JFmW6OPLzdOrvnbw5raxEVguEO2qDQr12z5PCCX57++LsnJy8OZ0E1cGPRw8mDyYPsZh0AZwW", + "bHQ8eow/4elZ4L4fOWIbHX/4OB4dLYDm6Bto/liCliz1nyTQbOP+ry7pfA5y4moOmp9Wj468WHH0wTl9", + "fDQzRN/TNh46CILtluJzDmSojLLxzo3SNspVWhlXBY+cspFnGKZq/SgMm6sQd5rVmf1Pa6bl8xvahM/H", + "v0QqMc/YHFUPPu1eoxyiq4bGFPnvsx9fEyGJe968oelFZbcgpzObq0qKFcPoxywImTU9J55+/1WC3NT0", + "5ThfmMzY169xBpClmhfNAKxaqorZaGJlD3FmQxYBYVcuWjXjQhVNWCy2YsOGtT5Ivn7/4enfPo4GAIL+", + "ggow1dVvNM9/I5cMq+ehftEni3TJwMaRWi0oTY9rlx/sUO/kGCPIqq9hOb6qTTNu+TcuOPzWtw0OsOg+", + "0Dw3DQWH2B68x2RMSCx45h49eHCwOp5VqL610lSjeJK4wkBdhmQ/RUrz+3KePXX5nxxwoc1Am2svtz1c", + "Z9Hf0gxLpIHSdikPv9ilnHJ02TUXBLEX4Mfx6OkXvDen3PAcmhNsGWQ67F40P/ELLi65b2mEn3K5pHKD", + "ok1Qx7GVBoTOFfqDIIu0Z7tRuW30/mPvrXcUFqY6+tDw+syudSd2avKdPt9xTd5RfZyzmye8VffKfK/K", + "GqFfoCvuhYWW1L0J+T7sjdwb027ZpFal5JB5p01/61V5RH120hq2OyrMSBa9tAN18e39/bnv75OmsqOR", + "izoGTOMUbIWpY1W47gXaNZW2KhtfqXJwUIHqCnU8Pml5xdZb0870PvYU3Mmob3HXg7s+MSmAt5KYmpXD", + "Pj1r9tGV1U3SuDI+IeP+woW+VzQ3dBIst5V5xiZovxUG/zLCYBVMNLfSmatJcj3xEKsTHn3wSfcPIBK6", + "ogMDhMHwWR30Dbw+7rbYyb2JzaAftrkaz3DRQzvFPCyFcCvg/QEEvG6ZkRgYdfGIzyfUIQyLug7JzpIn", + "voJIu3D8XvVSvlAp7i+MrF6xzUC6W2C7AvvsCGOOWX8ytvqnFMIc0m7Fr7+0+FXF9F5LAGsUCnJR4oEZ", + "61rau7Z2julKEmvGdQecDV2qDUNxR3hcFzU0LAZzQPr0X2rsX4ZoTrWPRrtZ4867sStifQ/hA/Xbzenz", + "XdLVF6TnGZyLOHILxPfmU/PSqNnh7c2YHYbxpicPntwcBOEuvBaafIe3+CfmkJ+UpcXJal8Wto0jHU1t", + "JYZtXIm32BIyirrCQsCjqoQW4+C7aW29NO66MuJh9qp7E+LrPqiqipUL7JoLw6h8TAmVc9vJ8DqDDHLH", + "/3mM49+ZkO8woEerMTqbaVfiiNxhXB8/fPT4iWsi6aX15Wq3m3715Pjkm29cs7rKh33ndJorLY8XkOfC", + "dXB3RHdc8+H4f//xf5PJ5M5OtirW325e23S3fxTe2n3ehQTQt1tf+CbFXuu+cMUu1N2I+f5bsY7eAmJ9", + "ewt9tlvIYP9PcftMm2TkHqKVJrORI+iAt5E9JvvcR2Nf0cLwneoymZDXwqVrK3MqiZAZSFf2b15SSbkG", + "yCaeUjHoVNn0VGnOgGvzcMRCZjJRLAOb5WZeSqjC5woJK/SRx+nxTd+AYDejR0/aPyyTf0XXQQqnaXVN", + "a+GWjGrPJV37UopYLExI/Ombb8iDcf16yXMzQFIhJsZcl3Q9ukGtX0Vsg/zPm5WIdjro4thDNEi19FPF", + "0IZlT/7anPuLldwtubuNPRDn3NvwUxt2Qj2CS4q2VYNgBTtbaBEr/23qaHwj5XkRKs7izAxDlQN/YBvB", + "TtV09BHaRu/tIb5VAlyLlbQJak+2gVGn6ugDvstDntE5txg199cylwa2IymW3ngkyAx0unABuy3UR9iT", + "r4PUz5u2Vdo+tFSDu9jNehHmpMYK0APTngWxlGjAAxkh4h99SQLzmc1sghmfkMkXlEfTFPM1Vqvyqq4I", + "tUs1oUUV12t2cS8on9WTdwUyRMsh7J+3CN4PwR3m+MLX2USMuUX8GTz+/VMyIa9FHTbuSjz9GU2Pn/Jm", + "/9QLei04WBu7kXwtLd6aUyuxwzAOixSfL8S+X6piG1cWQY58xditcsjfbb3WrbLIkNvbTPZFXuF/d1ja", + "csuYtU12JkOoRxvCnE1DmwWrWRHjM75iPgs//QM+bT4Hx7oZFoOH1PMZJxbwwzIdTMFjifmoKobQx4Hi", + "9WUGcyMtKje0aEmYKeSCz9UfkxVto444XiJUUlXeiZfX+eud3WeY3cc8ea0HpMv3pBhPwVZExmJuTJEl", + "U8o5Sz558Lebg1Czpc8ozsPY1c/MXZ4+eHxz05+BXLEUyDksCyGpZPmG/MSr6tXX4XZYPKjKv+a1wdF6", + "UWhtauYFS8MkRldngg3XtQ96zbKPu5lhkLFyTz7IeMAHw/SGtCiAyqszwN2mq3a67dPnoXdwo6ZNlVEr", + "AopB0Z4O8v8xGqh3wrB3MXOXX8ktoD77l2MTznVXzMaVc4yRAsTsmLzj94la0KcPH/366OlX/s9HT7/q", + "0ZyZeVzSnq7urB7IfLbDDFGgfdHqwMNK7RV+j296t/fbxPGIZeto1Yu6jl0n6bUTy+4oUtBNb2mcYkcd", + "vnDYuibfzSc7VJpNF9H3lX/+VGnlT/m31SvYZuRz5etu6+/1BE8EfMYQWl2Ir8L69pp8W6TJFllWxc9u", + "+nFaBxnYi84jT7bunM8q6OrP9UhN8I0K3As2TbR8PpkSK7OMA3N3IYUWqcit70pZFELq6nSrySBxD/rM", + "dg1pr49w9xLmUqrTRVkcfcD/YIavj3Xgga02Htj5qt9XS5GBFwXFbOYir+KuA6CbCeHMs0BpuiyI7Rm1", + "+COLPGdLODMtf7RTHPRyrcFuXa0t8Ax2FaSCZ2oAq3KjDuFBVXBXq26x7gfgxtlStQMeFueTNbnyw+Vt", + "4NjdoQTSRr7CRH4+As0hI4MVWbq6qHsQe5Rsjz7Yf9GMXYhY9aQzT8CdjbnrtsWG1NlxGwCSNyh+2tg8", + "30vMyAMbWVdyNJ/VGXuxNLXcmOvROxJLoDlJG2rzCo7uyTnrPTk7H1id1fWsKf6sEfUJPaSOqWWy/OHG", + "D8Azyh3JdxGkBaGEw5xqtgKvTJ7curldWafhnMy2MMAxoVlmT2O9CbACuSGqnCr4V1kLCVb7cUc1z8se", + "DAPWBUi2BK6xqK77Ncey40fWh22bluPMtrjmpdXiRdZzrlUIyN+szq9OzMgrlkpxguXu3GNLbZSGZScf", + "ruv6a08ktE9e3X2YCZ4zDslS8FiW1h/x6yv8GC1yIjTN+zpj4aa+vq37tgl/C6zmPEPu5Ovi9w9y+q9l", + "QGmtVoIRYuuK5Jb+9zxK/tBseNo9SRuedoXMRln0np+PPjT+dB6srqValDoTl0FfVH9aSXyI81pQPWK4", + "1bjSCLaqMCiSgTJE++WZaAI8xE5M9TWSjzOoEdKbkvMvarSZMZ61iAT1KalYYUml0E55a7n5c1luBu/7", + "XjzW5p/exdFKdViJ5LXIwI7bTP8eS5rARQYuTXZXEKk0EHFtt7+V6nYt/WNKy/lCE6wUGtN01h0Tmlom", + "m9jnza5K8+4R5EoFroDQHJOPkykAJ2JqFl3fj7hIqjBQzKtLnZ4lXjC9hquQIgWlIEt8kohdoFXJx1G5", + "qrfgCQFHgKtZiBJkRuW1gb1Y7YSzKt6hyN0ffjYP5huH14qC2xFrw1Mi6K1cYJ2014V62PTbCK49eUh2", + "VALxogFad8SyyMHZdyIo3AsnvfvXhqizi9dHCxpA2CemeD/J9QioAvUT0/t1oS2LxNzfXRCf2a/nbImS", + "GKdceL1itAAmVTrZxZaxgFawFmVWEHDCGCfGgXsenC+p0m+dqT8sSRsU6jJT9AO86isSY0b+uSoR0xk7", + "NfchV6Wq6sg49X28LCyH9Za5XsO6mgt9LfzYlX3Aavh2jdyHpWB8h6wgUwahOnCSwDJa3cWh/pE6BUUX", + "lQ0gakRsA+TMt2rUO64N+D2AMFUjuirO2qScoDim0qIosORyUvKqXx+azmzrE/1T3bZLXK6UH97bmQAV", + "2m4c5JcWs7bS6YIq4uAgS3rhzDtzl/mwC7M5jAm6ZSXbKB9VtqZVeAR2HtKymEuaQZJBTiOqlJ/sZ2I/", + "bxsAd9yTZ7ISGpIpzKJlucym15Qse1VE1dACx1Mx4ZHgF5KaIzjDOnGeQFzvHSNngGPHmJOjozvVUDhX", + "dIv8eLhsu9V99cRXAg16jh4QZMfRhwDcg4dq6KujAjsntfqgPcU/QLkJKjli/0k2oPqWUI+/1wLa6rzw", + "AmvcFC323uLAUbbZy8Z28JG+IxtTIH6Ryv6219IndMluKlCDB+DkKo/bo0vKdDIT0grSCZ1pkBFdXquy", + "D2Xah8xb04AWzmGQ4Aju3nTjIJMP8085LmJBIL7GPFtG0tmZqb4TclDca9O7mzJNSq5ZHuT+qJ7KfzyF", + "4a0S4FYJcKsEuFUC3CoBbpUAt0qAWyXArRLgVglwqwS4VQL8dZUAnyuSPfESh4/v4YInba9EcuuV+KeK", + "/KzuKq+UQDXGJWXapbIm1MsB+OV6ge8aaI44YDn0+0lb983zFycviRKlTIGkBkLGSZFT8zaAta4SqzZT", + "dvtiAjY7s80GThU8fkTO/n7iA9QWLpCq2fbuiSvKofQmh3sudVFVr9znMAJukO5SGFF/J/gErC4dLcvR", + "x1yRF9j6OawgFwVIG/tCtCwjKp9zoPkzh5sdGp9GiWkz2m/jhqLJoW1JCy/n+7VSRaiNWmxWiJ7RXPWX", + "iLbjLWkRy4Fa3XxWF4Tc5FuBJdXDE2J27Qg3sHk26jA1xqncREJUu8EEbdLQwvArR1hdZdbHgwdTdom2", + "S2a7KCwmrktQ0XO8jcqjUYTVhnWGssGusxadjGLZx9qhc6MKwCEusOcYcGD3hLy1/T5vqhaEyB2xmpn/", + "YTwHmy0rpoFtzSvCsZ4v1SvfIz56evHsjw1hZ2UKhGlFfDzm7utlPFonZqQ58MQxoGQqsk3SYF+jxi2U", + "MUWVguV0900U8k+X9d9dPubL9nvq81wjz4PFbePJIdGsE8eAe7jzRsNg3lxhC0d07DnA+Kdm0X1sNASB", + "OP4U0yq1a67tyfTqaTa3jO+W8QWnsSURMO7i19tMZPIJGZ/cyJL387wXa0hLA1x4ku+ieh5tcrDWDcNm", + "BtNyPsfqBR0jnVka4HhM8M/ECu1yh3LB/SjIDl5ltL5uEsX2cF3uEsRq3xWSzKUoi3u2XCPfoDVjWVC+", + "8TZfSBRblrnFoU38elhGa0PMu54AaI91yr8+tfYbr/MLlLfuqm3+btFCLqkidn8hIyXPXOxQJxHFmg+v", + "nGCHPl/zmk1vrZ1g1xtZnZt3yBXhd7kZtK1IATLRa24PVLO8iU14YU/u5DZr+1/j2rAh39DDYLvJG2qG", + "cKDbQwZ8Da+PIEVXHQzXrDlpK+L2hY6E+bpsy4N6j3SGbzqRBPVorZEU8oJQX1InFVxpWab6HadopAkW", + "Nuk6mHhtdD9/e+abxO2EETOeG+odp1hxpTLdRPncDCJ2iu8APBtV5XwOyvDKkEhmAO+4a8U4Kbl5aYkZ", + "WbJUisQGopozZOSTiW25pBsyozlaGX8HKcjU3OzBrluFsdIsz51Hi5mGiNk7TjXJgSpNXjHDZc1wPvlA", + "5coF+lLIiwoL8fRNc+CgmEriypfv7VfMkOSW75V8qLC0n+vMJjebGsnDzrJeyE+fG7gpJoPLmdK1E0QH", + "9hszgC8ZT6JEdr4A4nzC2rRF7mKuNEdA95rWIb2Ad9zccFoQ5OpUX40c2maezlm0p6NFNY2NaFmD/FoH", + "PfEOwmVIhMncmlb+RKGZAR148yVuPOZwae/9nmaUrWWeY19dRs2eRu6R0FCEtdLBuBbnDZD/vNVY3n+a", + "96JH48FejN0Bu+yqmTMR8eY3fExoLvicXDK9wBekwH1ivCg1OlZ/SiUdrGieiBVIyTJQA1fKBH+xovmP", + "VbcdF2CQ8XW5hIxRDfmGFBJSyGyeLKZI/Uie2EwDJF1QPse7UopyvrDN7DiXIKFKjmnepe0h4nlK1jxB", + "4Tzi7HBCrILRHwnDLrAccXi5Ymd7o5iHsN9BmwZiyFM3coS/N2P2vXzHo17J1iB1VTukWeQ0z/WAa7tx", + "AQf4qSc+REmcWyq7pbK9qSyWYg9RN2u9uS2+wm3589QK+lOWBfpzldD5lMLyp17Np5K9PQdShGKd9vC4", + "trLg2tNLFWGaXGJCnSkQc2GUqGN2dT7ci3RCDEMK9Ok286Jy6afTBWXcZWOp3PMRDu1S5Gufk/eTqOcs", + "M0O9nEEHpKVkeoNyOS3Yrxdg/v/eCLa2cLQV2UuZj45HC62L46OjXKQ0Xwilj0Yfx+E31fr4voL/g5e2", + "C8lWWHrt/cf/PwAA//9bOA9uBVkBAA==", } // 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 a4b540a2ca..b740542914 100644 --- a/daemon/algod/api/server/v2/generated/participating/private/routes.go +++ b/daemon/algod/api/server/v2/generated/participating/private/routes.go @@ -159,179 +159,180 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL var swaggerSpec = []string{ "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", + "/zB0GVM60gAA", } // 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 5c417aca3e..d2209d3f63 100644 --- a/daemon/algod/api/server/v2/generated/participating/public/routes.go +++ b/daemon/algod/api/server/v2/generated/participating/public/routes.go @@ -315,51 +315,51 @@ var swaggerSpec = []string{ "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", - "Ukq2wuq/7z/+vwAAAP//l0i9xf3fAAA=", + "l5NcLIgojZpra8Z5h0H0EZBgLptTansm1isxkLUPyuWQumls4/48W94K2f8dkovOQbbtMB7CYXnvx0Yc", + "oXTfHNlplQ7AHEGgveEjhr3IWyrtdXVf4xl6G0uLgqVxdP+xQioGAyF2vBQTWV9Nju4hG58CN4CrqH9y", + "uzvQvvo1G+sUrAsEjzwWAQDDbsIWDKOchfuCMcdX9BIaQfJZLbVOW4+css7Z98XbLI2n1GqtSyBm7EqC", + "S8myz3113ikpqV76W8w07+uWRk8BhflS9q0LqqwlxFtk3FtjXfFAlEkOK2h5T12eWJWmoBRbQfhOme1M", + "MoAS7ZNdqTnmFgy5XEeUcmtPAsfSGOxGZSuLWLtTZIfgFBXz1jyxx0SNPUoGohXLKtrCn7rFy09Djz5F", + "2LCHdSSn2JtJxBe3jUXsdOQjzUfPJY/78cM0xdoogrNltfHUEmFzslVJr/mwEhGxO9XO5duvg+BgRHXS", + "hgevfFnvyk0VyEHK2EYYvdfaojKHAv/aZljhw4tbrm9ExrKmLqYiAzDVnGcMVYMmFCpoVtANydh8DtIa", + "85WmPKMyC5szTlKQmjKj2WzUzcVaA62sYLpTsjXcFQf1DCYm46JdygKSb5zKcAupEz03EYnTXrVaDD1I", + "19uVeOw8XRvpGoOIBojAZf2ibG0PmOAoIJGCXsGe8yj2G2yfBmtxONufFjjrmClijsUbFhIbxbr7PvfI", + "7RY8PbfdMxTWGWwSmKQN3UBLsr8guzT+qrk4xz2C5zvsAC90GAbP4HnbjQPnM2cCvaqREizl/RAltJa/", + "ywfpFthIGsEWOUagNdiqrzZ6vL0vgYNZPa/9tkMvNnbdu1hUUHAstNp3C1vehO+1hIRjzoJc0fzTu3ax", + "2uQp4sO9pR9faOgbDJFsUaluFob/ko6aO/AD3t3U/A26ov8KZo+iWqkbyokwtVjvI1fwZqG5NVzM/XtV", + "K+DkGse0QVsPvyYzl6ZcSkiZ6opG1/4pidoVhi8rudSHtd7he9u1zp+FvgUZz72mQV43ZelRx1/wBsLm", + "iH5mpjJwcqNUHqO+HllE8BfjUWG9sB3XxVUrmss+89FJUxAS7jiqK4jP3jOqq18JbezybOSSuXQqBf11", + "jr6tW7iNXNTN2saGJPaRu612+ZhIwviTBKY7hjJahOB7HgRBJf94+A8iYY4P9glycIATHBxMXdN/PGp/", + "Nsf54CAqnX2yIEaLIzeGmzdGMT8PpbXZ1K2BDMrOflQsz3YRRisftnnyEjM+f3FZ95/l0c1fbKxJ/6i6", + "h8/2CZ/ubgIiJrLW1uTBVEGm64gkV9ctktKKfpy0kkxvsBigtx+wX6Lhlt/X0UwuGq7WD93dp8UV1OUk", + "m9inSvnb9XtBc7yPrNrKzS0k8kPy7ZoWZQ7uoHxzb/YnePznJ9nx44d/mv35+KvjFJ589fT4mD59Qh8+", + "ffwQHv35qyfH8HD+9dPZo+zRk0ezJ4+efP3V0/Txk4ezJ18//dM9w4cMyBbQiS89M/kbvkybnL45Sy4M", + "sA1OaMl+gI19BM+QsX9ej6Z4EqGgLJ+c+J/+f3/CDlNRNMP7XyeussVkqXWpTo6Orq+vD8MuRwsMdki0", + "qNLlkZ+n9/7e6Zuz2ktkrUC4ozYp1Fv3PCmc4re3355fkNM3Z4fB6+Ank+PD48OH+Jh0CZyWbHIyeYw/", + "4elZ4r4fOWKbnHz4OJ0cLYHmGBto/ihAS5b6TxJotnH/V9d0sQB56N4cND+tHh15seLogwv6+Ljt21H4", + "fMfRh1ZsTLajJ5b3P/rgq9Ztb90qC+digszSo4r+96BdGKjS4dtCLaPEbOPDWqZECel85aVkwpyqqbki", + "M0glUDwDQmIWr5YVT62Zwk4BHP/76vRvaKp5dfo38g05nrrkboVqR2x66wmuyeEss2D3rVPq2ea0jrIK", + "alqfvIs5bmJvIeJxMrQSUHs9YsPN0G4TviBb82bDb4+Tp+8/fPXnjzGZr/8Gt0dSEHgUol4LX9kNkVbQ", + "9TdDKFs7O7gZ99cK5KZZREHXkxDgvh0j8hz3nC3Q/uRrL7bexHRP4jFF/uv8x9dESOJ03Dc0vaqdVwZk", + "LFgmxYphCmwW5E2bnkMQu+svBNo/YuS8YIValO0svBrN77EaFAKKh/7R8fEt3kmPEJp7ftp6TdxbTW0X", + "vSKwpqnON4SqwEKnqllTua3jYhRl0jLnR92NwzP6d1BivpB9owQiaeL43sh2+C46Va5a6HCeL3x3aXe0", + "ZA8ZUQjexy77cGs9jXzZ3f8Zu9uXHUgpzJlm6DNvrhx/nbWAbF7DcOAOBEAdkr+LCiU8+94dxMrP4gzo", + "5/FzunjNIC2gcWDil4OD7sIPDtyeM0XmcI1MlnJs2EXHwQE+kPxkT1a21ZrcyuUbdXb2Ga63Wa/ouq76", + "SQkXPOH4HNsKSKAWPjl++Idd4RnHZAEjmhIren+cTr76A2/ZGTeCDc0JtrSrefyHXc05yBVLgVxAUQpJ", + "Jcs35Cdel3oJSsj22d9P/IqLa+4RYbTKqiio3DghmtY8p+JB8Z2t/KcXedkI2shF6UJhkB6KqJPWs6N8", + "MXn/0esAIxWLbc2OZljfbmxTUEHjYe0E/Qfq6ANawAd/P3IFueIf0RNhVdwjnxMSb9lSfD7otYG10yOl", + "Ol1W5dEH/A+qnAFYNv29D24Gq0Jk4OcT87mti7/t89EH+28wDKxLkMxcCZhE4361VbCOsFrlpv/zhqfR", + "H/tQdh+ljf189KH9KFJrt9Sy0pm4DvqiAd96n/rz1c+Etv4+uqZMG/HDZQ1h0ep+Zw00P3L1cDq/Nino", + "vS+YVx/82BFYSmGjU9u64lt6fdGKJJE2TPCZQP1+iJWtkxnjeL5D/tOY5ezHvvLR4zoXS7D+Ze/ZjEh3", + "WpCZFDRLqcJayK5yVE/r/HhLzaYb1XgW8VshmKjI9xNQzEk93OnMwHHHiG/BvgRPCKAYraw573cWeXoQ", + "PaMZ8eHMCXlFc7PhkJFTJ1i3sPF7iyufX774zALBJ7vBn/nDpwjFoPuW6iXj4cJBibcx17XRzwwDWABP", + "HAtKZiLb+BcxJL3Waxui32VuR/XTJtGPd2Dj+9c27O2y530xo30xo30xtHwxo33Z3S9mtJFmtC9Gpi9G", + "pv+VRqZ9LEsxMdNZVoalTSz9TVvzWt2ONlUnahbfzgBiupbJ+i9JMH1IyAXm9FNzS8AKJM3xtS0VFOko", + "MIIR84ggO7nkSQsSGydoJr7f/NcGaF5Wx8ePgRw/6PZRmuV5yJv7fVHexU+2/N035HJyOemNJKEQK8hs", + "dkGY9Wx77Rz2/6vH/bFXLgGzF5Z0BXW6EVHVfM5SZlGeC74gdCGa4GLDtwkX+AWkAc4WnSJMT10VM6bI", + "tVm8K8DeTs5uS+59CeCs2cKdDvkOucR98Ybw9nTE/8cYL/z/ain9FhlAt2KkW8fucdUvXOVTcJXPzlf+", + "6C7OwHz4P1LMfHL85A+7oNDY/Fpo8h0Gzt9OHKsftYjV3rqpoOUT/Ly5rwm+DYNZ8Ratw1jfvTcXAb5i", + "5y7YJjbz5OgIq/sshdJHE3P9teM2w4/va5j900OTUrIVVjJ+//H/BQAA///Pl9k/yeAAAA==", } // 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 045b69553d..48a36052e7 100644 --- a/daemon/algod/api/server/v2/handlers.go +++ b/daemon/algod/api/server/v2/handlers.go @@ -959,6 +959,7 @@ type PreEncodedSimulateRequest struct { TxnGroups []PreEncodedSimulateRequestTransactionGroup `codec:"txn-groups"` AllowEmptySignatures bool `codec:"allow-empty-signatures,omitempty"` AllowMoreLogging bool `codec:"allow-more-logging,omitempty"` + ExtraOpcodeBudget uint64 `codec:"extra-opcode-budget,omitempty"` } // SimulateTransaction simulates broadcasting a raw transaction to the network, returning relevant simulation results. @@ -1005,7 +1006,7 @@ func (v2 *Handlers) SimulateTransaction(ctx echo.Context, params model.SimulateT // Simulate transaction simulationResult, err := v2.Node.Simulate(convertSimulationRequest(simulateRequest)) if err != nil { - var invalidTxErr simulation.InvalidTxGroupError + var invalidTxErr simulation.InvalidRequestError switch { case errors.As(err, &invalidTxErr): return badRequest(ctx, invalidTxErr, invalidTxErr.Error(), v2.Log) diff --git a/daemon/algod/api/server/v2/utils.go b/daemon/algod/api/server/v2/utils.go index ce1e6340bd..7ffd0d665c 100644 --- a/daemon/algod/api/server/v2/utils.go +++ b/daemon/algod/api/server/v2/utils.go @@ -393,6 +393,7 @@ func convertSimulationResult(result simulation.Result) PreEncodedSimulateRespons AllowEmptySignatures: trueOrNil(result.EvalOverrides.AllowEmptySignatures), MaxLogSize: result.EvalOverrides.MaxLogSize, MaxLogCalls: result.EvalOverrides.MaxLogCalls, + ExtraOpcodeBudget: numOrNil(result.EvalOverrides.ExtraOpcodeBudget), } } @@ -419,6 +420,7 @@ func convertSimulationRequest(request PreEncodedSimulateRequest) simulation.Requ TxnGroups: txnGroups, AllowEmptySignatures: request.AllowEmptySignatures, AllowMoreLogging: request.AllowMoreLogging, + ExtraOpcodeBudget: request.ExtraOpcodeBudget, } } diff --git a/ledger/simulation/simulation_eval_test.go b/ledger/simulation/simulation_eval_test.go index e4cd00454e..555379a94a 100644 --- a/ledger/simulation/simulation_eval_test.go +++ b/ledger/simulation/simulation_eval_test.go @@ -786,7 +786,6 @@ func TestAppCallOverBudget(t *testing.T) { simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { sender := accounts[0] - receiver := accounts[1] futureAppID := basics.AppIndex(1) // App create with cost 4 @@ -805,7 +804,6 @@ int 0 Type: protocol.ApplicationCallTx, Sender: sender.Addr, ApplicationID: futureAppID, - Accounts: []basics.Address{receiver.Addr}, }) txntest.Group(&createTxn, &expensiveTxn) @@ -848,6 +846,202 @@ int 0 }) } +func TestAppCallWithExtraBudget(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + // Transaction group has a cost of 4 + 1404 + expensiveAppSource := `#pragma version 6 + txn ApplicationID // [appId] + bz end // [] +` + strings.Repeat(`int 1; pop;`, 700) + `end: + int 1` + + simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { + sender := accounts[0] + + futureAppID := basics.AppIndex(1) + // App create with cost 4 + createTxn := txnInfo.NewTxn(txntest.Txn{ + Type: protocol.ApplicationCallTx, + Sender: sender.Addr, + ApplicationID: 0, + ApprovalProgram: expensiveAppSource, + ClearStateProgram: `#pragma version 6; int 0`, + }) + // Expensive 700 repetition of int 1 and pop total cost 1404 + expensiveTxn := txnInfo.NewTxn(txntest.Txn{ + Type: protocol.ApplicationCallTx, + Sender: sender.Addr, + ApplicationID: futureAppID, + }) + + txntest.Group(&createTxn, &expensiveTxn) + + signedCreateTxn := createTxn.Txn().Sign(sender.Sk) + signedExpensiveTxn := expensiveTxn.Txn().Sign(sender.Sk) + extraOpcodeBudget := uint64(100) + + return simulationTestCase{ + input: simulation.Request{ + TxnGroups: [][]transactions.SignedTxn{ + {signedCreateTxn, signedExpensiveTxn}, + }, + ExtraOpcodeBudget: extraOpcodeBudget, + }, + expected: simulation.Result{ + Version: simulation.ResultLatestVersion, + LastRound: txnInfo.LatestRound(), + TxnGroups: []simulation.TxnGroupResult{ + { + Txns: []simulation.TxnResult{ + { + Txn: transactions.SignedTxnWithAD{ + ApplyData: transactions.ApplyData{ + ApplicationID: futureAppID, + }, + }, + AppBudgetConsumed: 4, + }, + { + AppBudgetConsumed: 1404, + }, + }, + AppBudgetAdded: 1500, + AppBudgetConsumed: 1408, + }, + }, + EvalOverrides: simulation.ResultEvalOverrides{ExtraOpcodeBudget: extraOpcodeBudget}, + }, + } + }) +} + +func TestAppCallWithExtraBudgetOverBudget(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + // Transaction group has a cost of 4 + 1404 + expensiveAppSource := `#pragma version 6 + txn ApplicationID // [appId] + bz end // [] +` + strings.Repeat(`int 1; pop;`, 700) + `end: + int 1` + + simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { + sender := accounts[0] + + futureAppID := basics.AppIndex(1) + // App create with cost 4 + createTxn := txnInfo.NewTxn(txntest.Txn{ + Type: protocol.ApplicationCallTx, + Sender: sender.Addr, + ApplicationID: 0, + ApprovalProgram: expensiveAppSource, + ClearStateProgram: `#pragma version 6; int 0`, + }) + // Expensive 700 repetition of int 1 and pop total cost 1404 + expensiveTxn := txnInfo.NewTxn(txntest.Txn{ + Type: protocol.ApplicationCallTx, + Sender: sender.Addr, + ApplicationID: futureAppID, + }) + + txntest.Group(&createTxn, &expensiveTxn) + + signedCreateTxn := createTxn.Txn().Sign(sender.Sk) + signedExpensiveTxn := expensiveTxn.Txn().Sign(sender.Sk) + // Add a small bit of extra budget, but not enough + extraBudget := uint64(5) + + return simulationTestCase{ + input: simulation.Request{ + TxnGroups: [][]transactions.SignedTxn{ + {signedCreateTxn, signedExpensiveTxn}, + }, + ExtraOpcodeBudget: extraBudget, + }, + expectedError: "dynamic cost budget exceeded", + expected: simulation.Result{ + Version: simulation.ResultLatestVersion, + LastRound: txnInfo.LatestRound(), + TxnGroups: []simulation.TxnGroupResult{ + { + Txns: []simulation.TxnResult{ + { + Txn: transactions.SignedTxnWithAD{ + ApplyData: transactions.ApplyData{ + ApplicationID: futureAppID, + }, + }, + AppBudgetConsumed: 4, + }, + { + AppBudgetConsumed: 1401, + }, + }, + FailedAt: simulation.TxnPath{1}, + AppBudgetAdded: 1405, + AppBudgetConsumed: 1405, + }, + }, + EvalOverrides: simulation.ResultEvalOverrides{ExtraOpcodeBudget: extraBudget}, + }, + } + }) +} + +func TestAppCallWithExtraBudgetExceedsInternalLimit(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + // Transaction group has a cost of 4 + 1404 + expensiveAppSource := `#pragma version 6 + txn ApplicationID // [appId] + bz end // [] +` + strings.Repeat(`int 1; pop;`, 700) + `end: + int 1` + + l, accounts, txnInfo := simulationtesting.PrepareSimulatorTest(t) + defer l.Close() + s := simulation.MakeSimulator(l) + + sender := accounts[0] + + futureAppID := basics.AppIndex(1) + // App create with cost 4 + createTxn := txnInfo.NewTxn(txntest.Txn{ + Type: protocol.ApplicationCallTx, + Sender: sender.Addr, + ApplicationID: 0, + ApprovalProgram: expensiveAppSource, + ClearStateProgram: `#pragma version 6; int 0`, + }) + // Expensive 700 repetition of int 1 and pop total cost 1404 + expensiveTxn := txnInfo.NewTxn(txntest.Txn{ + Type: protocol.ApplicationCallTx, + Sender: sender.Addr, + ApplicationID: futureAppID, + }) + + txntest.Group(&createTxn, &expensiveTxn) + + signedCreateTxn := createTxn.Txn().Sign(sender.Sk) + signedExpensiveTxn := expensiveTxn.Txn().Sign(sender.Sk) + + // Add an extra budget that is exceeding simulation.MaxExtraOpcodeBudget + extraBudget := simulation.MaxExtraOpcodeBudget + 1 + + // should error on too high extra budgets + _, err := s.Simulate( + simulation.Request{ + TxnGroups: [][]transactions.SignedTxn{{signedCreateTxn, signedExpensiveTxn}}, + ExtraOpcodeBudget: extraBudget, + }) + require.ErrorAs(t, err, &simulation.InvalidRequestError{}) + require.ErrorContains(t, err, "extra budget 320001 > simulation extra budget limit 320000") +} + func TestLogicSigOverBudget(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() @@ -1049,7 +1243,7 @@ func TestDefaultSignatureCheck(t *testing.T) { // should error with invalid signature stxn.Sig[0] += byte(1) // will wrap if > 255 result, err = s.Simulate(simulation.Request{TxnGroups: [][]transactions.SignedTxn{{stxn}}}) - require.ErrorAs(t, err, &simulation.InvalidTxGroupError{}) + require.ErrorAs(t, err, &simulation.InvalidRequestError{}) require.ErrorContains(t, err, "one signature didn't pass") } @@ -1453,7 +1647,7 @@ func TestOptionalSignaturesIncorrect(t *testing.T) { // should error with invalid signature stxn.Sig[0] += byte(1) // will wrap if > 255 _, err := s.Simulate(simulation.Request{TxnGroups: [][]transactions.SignedTxn{{stxn}}}) - require.ErrorAs(t, err, &simulation.InvalidTxGroupError{}) + require.ErrorAs(t, err, &simulation.InvalidRequestError{}) require.ErrorContains(t, err, "one signature didn't pass") } @@ -1532,8 +1726,9 @@ func TestPartialMissingSignatures(t *testing.T) { // TestPooledFeesAcrossSignedAndUnsigned tests that the simulator's transaction group checks // allow for pooled fees across a mix of signed and unsigned transactions when AllowEmptySignatures is // enabled. -// Transaction 1 is a signed transaction with not enough fees paid on its own. -// Transaction 2 is an unsigned transaction with enough fees paid to cover transaction 1. +// +// Transaction 1 is a signed transaction with not enough fees paid on its own. +// Transaction 2 is an unsigned transaction with enough fees paid to cover transaction 1. func TestPooledFeesAcrossSignedAndUnsigned(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() diff --git a/ledger/simulation/simulator.go b/ledger/simulation/simulator.go index 3794b2597f..1bcd3a5bab 100644 --- a/ledger/simulation/simulator.go +++ b/ledger/simulation/simulator.go @@ -42,6 +42,7 @@ type Request struct { TxnGroups [][]transactions.SignedTxn AllowEmptySignatures bool AllowMoreLogging bool + ExtraOpcodeBudget uint64 } // Latest is part of the LedgerForSimulator interface. @@ -72,8 +73,8 @@ func (s SimulatorError) Unwrap() error { return s.err } -// InvalidTxGroupError occurs when an invalid transaction group was submitted to the simulator. -type InvalidTxGroupError struct { +// InvalidRequestError occurs when an invalid transaction group was submitted to the simulator. +type InvalidRequestError struct { SimulatorError } @@ -146,7 +147,7 @@ func (s Simulator) check(hdr bookkeeping.BlockHeader, txgroup []transactions.Sig // Verify the signed transactions are well-formed and have valid signatures _, err = verify.TxnGroupWithTracer(txnsToVerify, &hdr, nil, s.ledger, tracer) if err != nil { - err = InvalidTxGroupError{SimulatorError{err}} + err = InvalidRequestError{SimulatorError{err}} } return err } @@ -189,6 +190,18 @@ func (s Simulator) simulateWithTracer(txgroup []transactions.SignedTxn, tracer l return nil, err } + // check that the extra budget is not exceeding simulation extra budget limit + if overrides.ExtraOpcodeBudget > MaxExtraOpcodeBudget { + return nil, + InvalidRequestError{ + SimulatorError{ + fmt.Errorf( + "extra budget %d > simulation extra budget limit %d", + overrides.ExtraOpcodeBudget, MaxExtraOpcodeBudget), + }, + } + } + vb, err := s.evaluate(hdr, txgroup, tracer) return vb, err } @@ -198,7 +211,7 @@ func (s Simulator) Simulate(simulateRequest Request) (Result, error) { simulatorTracer := makeEvalTracer(s.ledger.start, simulateRequest) if len(simulateRequest.TxnGroups) != 1 { - return Result{}, InvalidTxGroupError{ + return Result{}, InvalidRequestError{ SimulatorError{ err: fmt.Errorf("expected 1 transaction group, got %d", len(simulateRequest.TxnGroups)), }, @@ -212,7 +225,7 @@ func (s Simulator) Simulate(simulateRequest Request) (Result, error) { case errors.As(err, &verifyError): if verifyError.GroupIndex < 0 { // This group failed verification, but the problem can't be blamed on a single transaction. - return Result{}, InvalidTxGroupError{SimulatorError{err}} + return Result{}, InvalidRequestError{SimulatorError{err}} } simulatorTracer.result.TxnGroups[0].FailureMessage = verifyError.Error() simulatorTracer.result.TxnGroups[0].FailedAt = TxnPath{uint64(verifyError.GroupIndex)} diff --git a/ledger/simulation/trace.go b/ledger/simulation/trace.go index 8e24b9c70f..7e18cee882 100644 --- a/ledger/simulation/trace.go +++ b/ledger/simulation/trace.go @@ -70,17 +70,21 @@ type ResultEvalOverrides struct { AllowEmptySignatures bool MaxLogCalls *uint64 MaxLogSize *uint64 + ExtraOpcodeBudget uint64 } -// SimulateLogBytesLimit hardcode limit of how much bytes one can log per transaction during simulation (with AllowMoreLogging) -const SimulateLogBytesLimit = uint64(65536) +// LogBytesLimit hardcode limit of how much bytes one can log per transaction during simulation (with AllowMoreLogging) +const LogBytesLimit = uint64(65536) + +// MaxExtraOpcodeBudget hardcode limit of how much extra budget one can add to one transaction group (which is group-size * logic-sig-budget) +const MaxExtraOpcodeBudget = uint64(20000 * 16) // AllowMoreLogging method modify the log limits from lift option: // - if lift log limits, then overload result from local Config // - otherwise, set `LogLimits` field to be nil func (eo ResultEvalOverrides) AllowMoreLogging(allow bool) ResultEvalOverrides { if allow { - maxLogCalls, maxLogSize := uint64(config.MaxLogCalls), SimulateLogBytesLimit + maxLogCalls, maxLogSize := uint64(config.MaxLogCalls), LogBytesLimit eo.MaxLogCalls = &maxLogCalls eo.MaxLogSize = &maxLogSize } @@ -122,6 +126,7 @@ func makeSimulationResultWithVersion(lastRound basics.Round, request Request, ve resultEvalConstants := ResultEvalOverrides{ AllowEmptySignatures: request.AllowEmptySignatures, + ExtraOpcodeBudget: request.ExtraOpcodeBudget, }.AllowMoreLogging(request.AllowMoreLogging) return Result{ diff --git a/ledger/simulation/tracer.go b/ledger/simulation/tracer.go index 74ab23c92c..2b30a2e7f8 100644 --- a/ledger/simulation/tracer.go +++ b/ledger/simulation/tracer.go @@ -140,6 +140,12 @@ func (tracer *evalTracer) BeforeTxnGroup(ep *logic.EvalParams) { tracer.result.TxnGroups[0].AppBudgetAdded = uint64(*ep.PooledApplicationBudget) } + // Override transaction group budget if specified in request, retrieve from tracer.result + if ep.PooledApplicationBudget != nil { + tracer.result.TxnGroups[0].AppBudgetAdded += tracer.result.EvalOverrides.ExtraOpcodeBudget + *ep.PooledApplicationBudget += int(tracer.result.EvalOverrides.ExtraOpcodeBudget) + } + // Override runtime related constraints against ep, before entering txn group ep.EvalConstants = tracer.result.EvalOverrides.LogicEvalConstants() } diff --git a/test/e2e-go/restAPI/restClient_test.go b/test/e2e-go/restAPI/restClient_test.go index fabb32c35f..7dbc02e341 100644 --- a/test/e2e-go/restAPI/restClient_test.go +++ b/test/e2e-go/restAPI/restClient_test.go @@ -1859,3 +1859,132 @@ int 1` } a.Equal(expectedResult, resp) } + +func TestSimulateWithExtraBudget(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + a := require.New(fixtures.SynchronizedTest(t)) + var localFixture fixtures.RestClientFixture + localFixture.Setup(t, filepath.Join("nettemplates", "TwoNodes50EachFuture.json")) + defer localFixture.Shutdown() + + testClient := localFixture.LibGoalClient + + _, 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) + if senderAddress == "" { + t.Error("no addr with funds") + } + a.NoError(err) + + toAddress := getDestAddr(t, testClient, nil, senderAddress, wh) + closeToAddress := getDestAddr(t, testClient, nil, senderAddress, wh) + + // Ensure these accounts don't exist + receiverBalance, err := testClient.GetBalance(toAddress) + a.NoError(err) + a.Zero(receiverBalance) + closeToBalance, err := testClient.GetBalance(closeToAddress) + a.NoError(err) + a.Zero(closeToBalance) + + // construct program that uses a lot of budget + prog := `#pragma version 8 +txn ApplicationID +bz end +` + prog += strings.Repeat(`int 1; pop; `, 700) + prog += `end: +int 1` + + ops, err := logic.AssembleString(prog) + a.NoError(err) + approval := ops.Program + ops, err = logic.AssembleString("#pragma version 8; int 1") + a.NoError(err) + clearState := ops.Program + + gl := basics.StateSchema{} + lc := basics.StateSchema{} + + // create app + appCreateTxn, err := testClient.MakeUnsignedApplicationCallTx( + 0, nil, nil, nil, + nil, nil, transactions.NoOpOC, + approval, clearState, gl, lc, 0, + ) + a.NoError(err) + appCreateTxn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, 0, appCreateTxn) + a.NoError(err) + // sign and broadcast + appCreateTxID, err := testClient.SignAndBroadcastTransaction(wh, nil, appCreateTxn) + a.NoError(err) + _, err = waitForTransaction(t, testClient, senderAddress, appCreateTxID, 30*time.Second) + a.NoError(err) + + // get app ID + submittedAppCreateTxn, err := testClient.PendingTransactionInformation(appCreateTxID) + a.NoError(err) + a.NotNil(submittedAppCreateTxn.ApplicationIndex) + createdAppID := basics.AppIndex(*submittedAppCreateTxn.ApplicationIndex) + a.Greater(uint64(createdAppID), uint64(0)) + + // fund app account + appFundTxn, err := testClient.SendPaymentFromWallet( + wh, nil, senderAddress, createdAppID.Address().String(), + 0, 10_000_000, nil, "", 0, 0, + ) + a.NoError(err) + appFundTxID := appFundTxn.ID() + _, err = waitForTransaction(t, testClient, senderAddress, appFundTxID.String(), 30*time.Second) + a.NoError(err) + + // construct app call + appCallTxn, err := testClient.MakeUnsignedAppNoOpTx( + uint64(createdAppID), nil, nil, nil, nil, nil, + ) + a.NoError(err) + appCallTxn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, 0, appCallTxn) + a.NoError(err) + appCallTxnSigned, err := testClient.SignTransactionWithWallet(wh, nil, appCallTxn) + a.NoError(err) + + extraBudget := uint64(704) + resp, err := testClient.SimulateTransactions(v2.PreEncodedSimulateRequest{ + TxnGroups: []v2.PreEncodedSimulateRequestTransactionGroup{ + { + Txns: []transactions.SignedTxn{appCallTxnSigned}, + }, + }, + ExtraOpcodeBudget: extraBudget, + }) + a.NoError(err) + + budgetAdded, budgetUsed := uint64(1404), uint64(1404) + + expectedResult := v2.PreEncodedSimulateResponse{ + Version: 2, + LastRound: resp.LastRound, + EvalOverrides: &model.SimulationEvalOverrides{ExtraOpcodeBudget: &extraBudget}, + TxnGroups: []v2.PreEncodedSimulateTxnGroupResult{ + { + Txns: []v2.PreEncodedSimulateTxnResult{ + { + Txn: v2.PreEncodedTxInfo{Txn: appCallTxnSigned}, + AppBudgetConsumed: &budgetUsed, + }, + }, + AppBudgetAdded: &budgetAdded, + AppBudgetConsumed: &budgetUsed, + }, + }, + } + a.Equal(expectedResult, resp) +} diff --git a/test/scripts/e2e_subs/e2e-app-simulate.sh b/test/scripts/e2e_subs/e2e-app-simulate.sh index 9e07074e2b..f4abd7df59 100755 --- a/test/scripts/e2e_subs/e2e-app-simulate.sh +++ b/test/scripts/e2e_subs/e2e-app-simulate.sh @@ -139,7 +139,7 @@ fi printf '#pragma version 2\nint 1' > "${TEMPDIR}/simple-v2.teal" # Real Create -RES=$(${gcmd} app method --method "create(uint64)uint64" --arg "1234" --create --approval-prog ${DIR}/tealprogs/app-abi-method-example.teal --clear-prog ${TEMPDIR}/simple-v2.teal --global-byteslices 0 --global-ints 0 --local-byteslices 1 --local-ints 0 --extra-pages 0 --from $ACCOUNT 2>&1 || true) +RES=$(${gcmd} app method --method "create(uint64)uint64" --arg "1234" --create --approval-prog ${DIR}/tealprogs/app-abi-method-example.teal --clear-prog ${TEMPDIR}/simple-v2.teal --local-byteslices 1 --from $ACCOUNT 2>&1 || true) EXPECTED="method create(uint64)uint64 succeeded with output: 2468" if [[ $RES != *"${EXPECTED}"* ]]; then date '+app-simulate-test FAIL the method call to create(uint64)uint64 should not fail %Y%m%d_%H%M%S' @@ -198,7 +198,7 @@ printf '#pragma version 6\nint 1' > "${TEMPDIR}/simple-v6.teal" # NOTE: logs-a-lot.teal contains a method that logs 1.4kb info, which is well over 1kb limit in binary # we test it here to see if the simulate unlimit log works under goal clerk simulate -RES=$(${gcmd} app create --creator ${ACCOUNT} --approval-prog "${TEAL}/logs-a-lot.teal" --clear-prog "${TEMPDIR}/simple-v6.teal" --global-byteslices 0 --global-ints 0 --local-byteslices 0 --local-ints 0 2>&1 || true) +RES=$(${gcmd} app create --creator ${ACCOUNT} --approval-prog "${TEAL}/logs-a-lot.teal" --clear-prog "${TEMPDIR}/simple-v6.teal" 2>&1 || true) EXPSUCCESS='Created app with app index' if [[ $RES != *"${EXPSUCCESS}"* ]]; then date '+app-simulate-test FAIL the app creation for logs-a-lot.teal should succeed %Y%m%d_%H%M%S' @@ -288,3 +288,99 @@ if [[ $(echo "$RES" | jq '."txn-groups"[0]."txn-results"[0]."txn-result"."logs"[ date '+app-simulate-test FAIL the app call to logs-a-lot.teal for unlimited_log_test()void should succeed %Y%m%d_%H%M%S' false fi + +############################################################ +# WE WANT TO FURTHER TEST EXTRA BUDGET IN SIMULATION WORKS # +############################################################ + +function generate_teal() { + FILE=$1 + VERSION=$2 + REPETITION=$3 + + printf '#pragma version %d\n txn ApplicationID\n bz end\n' $VERSION > "${FILE}" + + # iterating in interval [0, REPETITION - 1] + for i in $(seq 0 1 $(expr $REPETITION - 1)); do + printf "int 1\npop\n" >> "${FILE}" + done + + printf "end:\n int 1\n" >> "${FILE}" +} + +BIG_TEAL_FILE="$TEMPDIR/int-pop-400-cost-a-lot.teal" +generate_teal "$BIG_TEAL_FILE" 8 400 + +printf '#pragma version 8\nint 1' > "${TEMPDIR}/simple-v8.teal" + +RES=$(${gcmd} app create --creator ${ACCOUNT} --approval-prog "${BIG_TEAL_FILE}" --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 }') + +# SIMULATION! without extra budget should fail direct call +${gcmd} app call --app-id $APPID --from $ACCOUNT 2>&1 -o "${TEMPDIR}/no-extra-opcode-budget.tx" +${gcmd} clerk sign -i "${TEMPDIR}/no-extra-opcode-budget.tx" -o "${TEMPDIR}/no-extra-opcode-budget.stx" +RES=$(${gcmd} clerk simulate -t "${TEMPDIR}/no-extra-opcode-budget.stx") + +if [[ $(echo "$RES" | jq '."txn-groups" | any(has("failure-message"))') != $CONST_TRUE ]]; then + date '+app-simulate-test FAIL the app call to generated large TEAL without extra budget should fail %Y%m%d_%H%M%S' + false +fi + +EXPECTED_FAILURE='dynamic cost budget exceeded' + +if [[ $(echo "$RES" | jq '."txn-groups"[0]."failure-message"') != *"${EXPECTED_FAILURE}"* ]]; then + date '+app-simulate-test FAIL the app call to generated large TEAL should fail %Y%m%d_%H%M%S' + false +fi + +# SIMULATION! with extra budget should pass direct call +RES=$(${gcmd} clerk simulate --extra-opcode-budget 200 -t "${TEMPDIR}/no-extra-opcode-budget.stx") + +if [[ $(echo "$RES" | jq '."txn-groups" | any(has("failure-message"))') != $CONST_FALSE ]]; then + date '+app-simulate-test FAIL the app call to generated large TEAL with extra budget should pass %Y%m%d_%H%M%S' + false +fi + +if [[ $(echo "$RES" | jq '."eval-overrides"."extra-opcode-budget"') -ne 200 ]]; then + date '+app-simulate-test FAIL the app call to generated large TEAL should have extra-opcode-budget 200 %Y%m%d_%H%M%S' + false +fi + +if [[ $(echo "$RES" | jq '."txn-groups"[0]."app-budget-added"') -ne 900 ]]; then + date '+app-simulate-test FAIL the app call to generated large TEAL should have app-budget-added 900 %Y%m%d_%H%M%S' + false +fi + +if [[ $(echo "$RES" | jq '."txn-groups"[0]."app-budget-consumed"') -ne 804 ]]; then + 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 + +# SIMULATION! with --allow-extra-budget should pass direct call +RES=$(${gcmd} clerk simulate --allow-extra-opcode-budget -t "${TEMPDIR}/no-extra-opcode-budget.stx") + +if [[ $(echo "$RES" | jq '."txn-groups" | any(has("failure-message"))') != $CONST_FALSE ]]; then + date '+app-simulate-test FAIL the app call to generated large TEAL with extra budget should pass %Y%m%d_%H%M%S' + false +fi + +if [[ $(echo "$RES" | jq '."eval-overrides"."extra-opcode-budget"') -ne 320000 ]]; then + date '+app-simulate-test FAIL the app call to generated large TEAL should have extra-opcode-budget 320000 %Y%m%d_%H%M%S' + false +fi + +if [[ $(echo "$RES" | jq '."txn-groups"[0]."app-budget-added"') -ne 320700 ]]; then + date '+app-simulate-test FAIL the app call to generated large TEAL should have app-budget-added 320700 %Y%m%d_%H%M%S' + false +fi + +if [[ $(echo "$RES" | jq '."txn-groups"[0]."app-budget-consumed"') -ne 804 ]]; then + 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 From 2bb251675a738eeca540feb1c3af83caaef012ef Mon Sep 17 00:00:00 2001 From: Eric Warehime Date: Tue, 9 May 2023 10:06:07 -0700 Subject: [PATCH 085/125] api: Txn Group Delta Apis (#5350) --- daemon/algod/api/algod.oas2.json | 174 +++++++ daemon/algod/api/algod.oas3.yml | 312 ++++++++++++ daemon/algod/api/server/v2/errors.go | 3 +- .../api/server/v2/generated/data/routes.go | 418 ++++++++------- .../v2/generated/experimental/routes.go | 336 ++++++------ .../api/server/v2/generated/model/types.go | 43 ++ .../nonparticipating/private/routes.go | 346 ++++++------- .../nonparticipating/public/routes.go | 478 +++++++++--------- .../generated/participating/private/routes.go | 352 ++++++------- .../generated/participating/public/routes.go | 366 +++++++------- daemon/algod/api/server/v2/handlers.go | 50 ++ .../server/v2/test/handlers_resources_test.go | 6 + .../algod/api/server/v2/test/handlers_test.go | 136 +++++ daemon/algod/api/server/v2/utils.go | 4 + ledger/eval/txntracer.go | 68 ++- ledger/ledger.go | 5 + 16 files changed, 1976 insertions(+), 1121 deletions(-) diff --git a/daemon/algod/api/algod.oas2.json b/daemon/algod/api/algod.oas2.json index 7735a512df..b167ee4de3 100644 --- a/daemon/algod/api/algod.oas2.json +++ b/daemon/algod/api/algod.oas2.json @@ -1534,6 +1534,146 @@ } } }, + "/v2/deltas/{round}/txn/group": { + "get": { + "description": "Get ledger deltas for transaction groups in a given round.", + "tags": [ + "public", + "data", + "experimental" + ], + "produces": [ + "application/json", + "application/msgpack" + ], + "schemes": [ + "http" + ], + "summary": "Get LedgerStateDelta objects for all transaction groups in a given round", + "operationId": "GetTransactionGroupLedgerStateDeltasForRound", + "parameters": [ + { + "type": "integer", + "description": "The round for which the deltas are desired.", + "name": "round", + "in": "path", + "required": true, + "minimum": 0 + }, + { + "$ref": "#/parameters/format" + } + ], + "responses": { + "200": { + "$ref": "#/responses/TransactionGroupLedgerStateDeltaForRoundResponse" + }, + "401": { + "description": "Invalid API Token", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + }, + "404": { + "description": "Could not find deltas for round", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + }, + "408": { + "description": "timed out on request", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + }, + "500": { + "description": "Internal Error", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + }, + "501": { + "description": "Not Implemented", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + }, + "default": { + "description": "Unknown Error" + } + } + } + }, + "/v2/deltas/txn/group/{id}": { + "get": { + "description": "Get a ledger delta for a given transaction group.", + "tags": [ + "public", + "data", + "experimental" + ], + "produces": [ + "application/json", + "application/msgpack" + ], + "schemes": [ + "http" + ], + "summary": "Get a LedgerStateDelta object for a given transaction group", + "operationId": "GetLedgerStateDeltaForTransactionGroup", + "parameters": [ + { + "pattern": "[A-Z0-9]+", + "type": "string", + "description": "A transaction ID, or transaction group ID", + "name": "id", + "in": "path", + "required": true + }, + { + "$ref": "#/parameters/format" + } + ], + "responses": { + "200": { + "$ref": "#/responses/LedgerStateDeltaForTransactionGroupResponse" + }, + "401": { + "description": "Invalid API Token", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + }, + "404": { + "description": "Could not find a delta for transaction ID or group ID", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + }, + "408": { + "description": "timed out on request", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + }, + "500": { + "description": "Internal Error", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + }, + "501": { + "description": "Not Implemented", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + }, + "default": { + "description": "Unknown Error" + } + } + } + }, "/v2/stateproofs/{round}": { "get": { "tags": [ @@ -2520,6 +2660,25 @@ "type": "object", "x-algorand-format": "StateDelta" }, + "LedgerStateDeltaForTransactionGroup": { + "description": "Contains a ledger delta for a single transaction group", + "type": "object", + "required": [ + "delta", + "ids" + ], + "properties": { + "delta": { + "$ref": "#/definitions/LedgerStateDelta" + }, + "ids": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, "Account": { "description": "Account information at a given round.\n\nDefinition:\ndata/basics/userBalance.go : AccountData\n", "type": "object", @@ -3878,6 +4037,21 @@ } } }, + "LedgerStateDeltaForTransactionGroupResponse": { + "description": "Response containing a ledger state delta for a single transaction group.", + "schema": { + "$ref": "#/definitions/LedgerStateDelta" + } + }, + "TransactionGroupLedgerStateDeltaForRoundResponse": { + "description": "Response containing all ledger state deltas for transaction groups, with their associated Ids, in a single round.", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/LedgerStateDeltaForTransactionGroup" + } + } + }, "LedgerStateDeltaResponse": { "description": "Contains ledger deltas", "schema": { diff --git a/daemon/algod/api/algod.oas3.yml b/daemon/algod/api/algod.oas3.yml index 625a05b1e5..8ebbc057b6 100644 --- a/daemon/algod/api/algod.oas3.yml +++ b/daemon/algod/api/algod.oas3.yml @@ -535,6 +535,16 @@ }, "description": "Response containing the ledger's minimum sync round" }, + "LedgerStateDeltaForTransactionGroupResponse": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LedgerStateDelta" + } + } + }, + "description": "Response containing a ledger state delta for a single transaction group." + }, "LedgerStateDeltaResponse": { "content": { "application/json": { @@ -847,6 +857,19 @@ }, "description": "Supply represents the current supply of MicroAlgos in the system." }, + "TransactionGroupLedgerStateDeltaForRoundResponse": { + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/LedgerStateDeltaForTransactionGroup" + }, + "type": "array" + } + } + }, + "description": "Response containing all ledger state deltas for transaction groups, with their associated Ids, in a single round." + }, "TransactionParametersResponse": { "content": { "application/json": { @@ -1709,6 +1732,25 @@ "type": "object", "x-algorand-format": "StateDelta" }, + "LedgerStateDeltaForTransactionGroup": { + "description": "Contains a ledger delta for a single transaction group", + "properties": { + "delta": { + "$ref": "#/components/schemas/LedgerStateDelta" + }, + "ids": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "delta", + "ids" + ], + "type": "object" + }, "LightBlockHeaderProof": { "description": "Proof of membership and position of a light block header.", "properties": { @@ -3806,6 +3848,138 @@ ] } }, + "/v2/deltas/txn/group/{id}": { + "get": { + "description": "Get a ledger delta for a given transaction group.", + "operationId": "GetLedgerStateDeltaForTransactionGroup", + "parameters": [ + { + "description": "A transaction ID, or transaction group ID", + "in": "path", + "name": "id", + "required": true, + "schema": { + "pattern": "[A-Z0-9]+", + "type": "string" + } + }, + { + "description": "Configures whether the response object is JSON or MessagePack encoded. If not provided, defaults to JSON.", + "in": "query", + "name": "format", + "schema": { + "enum": [ + "json", + "msgpack" + ], + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LedgerStateDelta" + } + }, + "application/msgpack": { + "schema": { + "$ref": "#/components/schemas/LedgerStateDelta" + } + } + }, + "description": "Response containing a ledger state delta for a single transaction group." + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + }, + "application/msgpack": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Invalid API Token" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + }, + "application/msgpack": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Could not find a delta for transaction ID or group ID" + }, + "408": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + }, + "application/msgpack": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "timed out on request" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + }, + "application/msgpack": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Internal Error" + }, + "501": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + }, + "application/msgpack": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Not Implemented" + }, + "default": { + "content": {}, + "description": "Unknown Error" + } + }, + "summary": "Get a LedgerStateDelta object for a given transaction group", + "tags": [ + "public", + "data", + "experimental" + ] + } + }, "/v2/deltas/{round}": { "get": { "description": "Get ledger deltas for a round.", @@ -3938,6 +4112,144 @@ ] } }, + "/v2/deltas/{round}/txn/group": { + "get": { + "description": "Get ledger deltas for transaction groups in a given round.", + "operationId": "GetTransactionGroupLedgerStateDeltasForRound", + "parameters": [ + { + "description": "The round for which the deltas are desired.", + "in": "path", + "name": "round", + "required": true, + "schema": { + "minimum": 0, + "type": "integer" + } + }, + { + "description": "Configures whether the response object is JSON or MessagePack encoded. If not provided, defaults to JSON.", + "in": "query", + "name": "format", + "schema": { + "enum": [ + "json", + "msgpack" + ], + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/LedgerStateDeltaForTransactionGroup" + }, + "type": "array" + } + }, + "application/msgpack": { + "schema": { + "items": { + "$ref": "#/components/schemas/LedgerStateDeltaForTransactionGroup" + }, + "type": "array" + } + } + }, + "description": "Response containing all ledger state deltas for transaction groups, with their associated Ids, in a single round." + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + }, + "application/msgpack": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Invalid API Token" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + }, + "application/msgpack": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Could not find deltas for round" + }, + "408": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + }, + "application/msgpack": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "timed out on request" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + }, + "application/msgpack": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Internal Error" + }, + "501": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + }, + "application/msgpack": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Not Implemented" + }, + "default": { + "content": {}, + "description": "Unknown Error" + } + }, + "summary": "Get LedgerStateDelta objects for all transaction groups in a given round", + "tags": [ + "public", + "data", + "experimental" + ] + } + }, "/v2/devmode/blocks/offset": { "get": { "description": "Gets the current timestamp offset.", diff --git a/daemon/algod/api/server/v2/errors.go b/daemon/algod/api/server/v2/errors.go index e076d6c8a5..18a389edf6 100644 --- a/daemon/algod/api/server/v2/errors.go +++ b/daemon/algod/api/server/v2/errors.go @@ -26,7 +26,7 @@ var ( errFailedLookingUpTransactionPool = "failed to retrieve information from the transaction pool" errFailedRetrievingStateDelta = "failed retrieving State Delta: %v" errFailedRetrievingNodeStatus = "failed retrieving node status" - errFailedRetrievingLatestBlockHeaderStatus = "failed retrieving latests block header" + errFailedRetrievingLatestBlockHeaderStatus = "failed retrieving latest block header" errFailedRetrievingTimeStampOffset = "failed retrieving timestamp offset from node: %v" errFailedSettingTimeStampOffset = "failed to set timestamp offset on the node: %v" errFailedRetrievingSyncRound = "failed retrieving sync round from ledger" @@ -47,4 +47,5 @@ var ( errOperationNotAvailableDuringCatchup = "operation not available during catchup" errRESTPayloadZeroLength = "payload was of zero length" errRoundGreaterThanTheLatest = "given round is greater than the latest round" + errFailedRetrievingTracer = "failed retrieving the expected tracer from ledger" ) diff --git a/daemon/algod/api/server/v2/generated/data/routes.go b/daemon/algod/api/server/v2/generated/data/routes.go index 0e68a996ac..da91c144c9 100644 --- a/daemon/algod/api/server/v2/generated/data/routes.go +++ b/daemon/algod/api/server/v2/generated/data/routes.go @@ -21,9 +21,15 @@ import ( // ServerInterface represents all server handlers. type ServerInterface interface { + // Get a LedgerStateDelta object for a given transaction group + // (GET /v2/deltas/txn/group/{id}) + GetLedgerStateDeltaForTransactionGroup(ctx echo.Context, id string, params GetLedgerStateDeltaForTransactionGroupParams) error // Get a LedgerStateDelta object for a given round // (GET /v2/deltas/{round}) GetLedgerStateDelta(ctx echo.Context, round uint64, params GetLedgerStateDeltaParams) error + // Get LedgerStateDelta objects for all transaction groups in a given round + // (GET /v2/deltas/{round}/txn/group) + GetTransactionGroupLedgerStateDeltasForRound(ctx echo.Context, round uint64, params GetTransactionGroupLedgerStateDeltasForRoundParams) error // Removes minimum sync round restriction from the ledger. // (DELETE /v2/ledger/sync) UnsetSyncRound(ctx echo.Context) error @@ -40,6 +46,33 @@ type ServerInterfaceWrapper struct { Handler ServerInterface } +// GetLedgerStateDeltaForTransactionGroup converts echo context to params. +func (w *ServerInterfaceWrapper) GetLedgerStateDeltaForTransactionGroup(ctx echo.Context) error { + var err error + // ------------- Path parameter "id" ------------- + var id string + + err = runtime.BindStyledParameterWithLocation("simple", false, "id", runtime.ParamLocationPath, ctx.Param("id"), &id) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter id: %s", err)) + } + + ctx.Set(Api_keyScopes, []string{""}) + + // Parameter object where we will unmarshal all parameters from the context + var params GetLedgerStateDeltaForTransactionGroupParams + // ------------- Optional query parameter "format" ------------- + + err = runtime.BindQueryParameter("form", true, false, "format", ctx.QueryParams(), ¶ms.Format) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter format: %s", err)) + } + + // Invoke the callback with all the unmarshalled arguments + err = w.Handler.GetLedgerStateDeltaForTransactionGroup(ctx, id, params) + return err +} + // GetLedgerStateDelta converts echo context to params. func (w *ServerInterfaceWrapper) GetLedgerStateDelta(ctx echo.Context) error { var err error @@ -67,6 +100,33 @@ func (w *ServerInterfaceWrapper) GetLedgerStateDelta(ctx echo.Context) error { return err } +// GetTransactionGroupLedgerStateDeltasForRound converts echo context to params. +func (w *ServerInterfaceWrapper) GetTransactionGroupLedgerStateDeltasForRound(ctx echo.Context) error { + var err error + // ------------- Path parameter "round" ------------- + var round uint64 + + err = runtime.BindStyledParameterWithLocation("simple", false, "round", runtime.ParamLocationPath, ctx.Param("round"), &round) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter round: %s", err)) + } + + ctx.Set(Api_keyScopes, []string{""}) + + // Parameter object where we will unmarshal all parameters from the context + var params GetTransactionGroupLedgerStateDeltasForRoundParams + // ------------- Optional query parameter "format" ------------- + + err = runtime.BindQueryParameter("form", true, false, "format", ctx.QueryParams(), ¶ms.Format) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter format: %s", err)) + } + + // Invoke the callback with all the unmarshalled arguments + err = w.Handler.GetTransactionGroupLedgerStateDeltasForRound(ctx, round, params) + return err +} + // UnsetSyncRound converts echo context to params. func (w *ServerInterfaceWrapper) UnsetSyncRound(ctx echo.Context) error { var err error @@ -135,7 +195,9 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL Handler: si, } + router.GET(baseURL+"/v2/deltas/txn/group/:id", wrapper.GetLedgerStateDeltaForTransactionGroup, m...) router.GET(baseURL+"/v2/deltas/:round", wrapper.GetLedgerStateDelta, m...) + router.GET(baseURL+"/v2/deltas/:round/txn/group", wrapper.GetTransactionGroupLedgerStateDeltasForRound, m...) router.DELETE(baseURL+"/v2/ledger/sync", wrapper.UnsetSyncRound, m...) router.GET(baseURL+"/v2/ledger/sync", wrapper.GetSyncRound, m...) router.POST(baseURL+"/v2/ledger/sync/:round", wrapper.SetSyncRound, m...) @@ -145,181 +207,187 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", + "///lmFLaz+IAAA==", } // 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 2f209db44f..df54602af8 100644 --- a/daemon/algod/api/server/v2/generated/experimental/routes.go +++ b/daemon/algod/api/server/v2/generated/experimental/routes.go @@ -75,173 +75,175 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/generated/model/types.go b/daemon/algod/api/server/v2/generated/model/types.go index e1b7c20320..b133369a7b 100644 --- a/daemon/algod/api/server/v2/generated/model/types.go +++ b/daemon/algod/api/server/v2/generated/model/types.go @@ -106,12 +106,24 @@ const ( GetTransactionProofParamsFormatMsgpack GetTransactionProofParamsFormat = "msgpack" ) +// Defines values for GetLedgerStateDeltaForTransactionGroupParamsFormat. +const ( + GetLedgerStateDeltaForTransactionGroupParamsFormatJson GetLedgerStateDeltaForTransactionGroupParamsFormat = "json" + GetLedgerStateDeltaForTransactionGroupParamsFormatMsgpack GetLedgerStateDeltaForTransactionGroupParamsFormat = "msgpack" +) + // Defines values for GetLedgerStateDeltaParamsFormat. const ( GetLedgerStateDeltaParamsFormatJson GetLedgerStateDeltaParamsFormat = "json" GetLedgerStateDeltaParamsFormatMsgpack GetLedgerStateDeltaParamsFormat = "msgpack" ) +// Defines values for GetTransactionGroupLedgerStateDeltasForRoundParamsFormat. +const ( + GetTransactionGroupLedgerStateDeltasForRoundParamsFormatJson GetTransactionGroupLedgerStateDeltasForRoundParamsFormat = "json" + GetTransactionGroupLedgerStateDeltasForRoundParamsFormatMsgpack GetTransactionGroupLedgerStateDeltasForRoundParamsFormat = "msgpack" +) + // Defines values for GetPendingTransactionsParamsFormat. const ( GetPendingTransactionsParamsFormatJson GetPendingTransactionsParamsFormat = "json" @@ -527,6 +539,13 @@ type KvDelta struct { // LedgerStateDelta Ledger StateDelta object type LedgerStateDelta = map[string]interface{} +// LedgerStateDeltaForTransactionGroup Contains a ledger delta for a single transaction group +type LedgerStateDeltaForTransactionGroup struct { + // Delta Ledger StateDelta object + Delta LedgerStateDelta `json:"delta"` + Ids []string `json:"ids"` +} + // LightBlockHeaderProof Proof of membership and position of a light block header. type LightBlockHeaderProof struct { // Index The index of the light block header in the vector commitment tree @@ -922,6 +941,9 @@ type GetSyncRoundResponse struct { Round uint64 `json:"round"` } +// LedgerStateDeltaForTransactionGroupResponse Ledger StateDelta object +type LedgerStateDeltaForTransactionGroupResponse = LedgerStateDelta + // LedgerStateDeltaResponse Ledger StateDelta object type LedgerStateDeltaResponse = LedgerStateDelta @@ -1066,6 +1088,9 @@ type SupplyResponse struct { TotalMoney uint64 `json:"total-money"` } +// TransactionGroupLedgerStateDeltaForRoundResponse defines model for TransactionGroupLedgerStateDeltaForRoundResponse. +type TransactionGroupLedgerStateDeltaForRoundResponse = []LedgerStateDeltaForTransactionGroup + // TransactionParametersResponse TransactionParams contains the parameters that help a client construct // a new transaction. type TransactionParametersResponse struct { @@ -1204,6 +1229,15 @@ type GetTransactionProofParamsHashtype string // GetTransactionProofParamsFormat defines parameters for GetTransactionProof. type GetTransactionProofParamsFormat string +// GetLedgerStateDeltaForTransactionGroupParams defines parameters for GetLedgerStateDeltaForTransactionGroup. +type GetLedgerStateDeltaForTransactionGroupParams struct { + // Format Configures whether the response object is JSON or MessagePack encoded. If not provided, defaults to JSON. + Format *GetLedgerStateDeltaForTransactionGroupParamsFormat `form:"format,omitempty" json:"format,omitempty"` +} + +// GetLedgerStateDeltaForTransactionGroupParamsFormat defines parameters for GetLedgerStateDeltaForTransactionGroup. +type GetLedgerStateDeltaForTransactionGroupParamsFormat string + // GetLedgerStateDeltaParams defines parameters for GetLedgerStateDelta. type GetLedgerStateDeltaParams struct { // Format Configures whether the response object is JSON or MessagePack encoded. If not provided, defaults to JSON. @@ -1213,6 +1247,15 @@ type GetLedgerStateDeltaParams struct { // GetLedgerStateDeltaParamsFormat defines parameters for GetLedgerStateDelta. type GetLedgerStateDeltaParamsFormat string +// GetTransactionGroupLedgerStateDeltasForRoundParams defines parameters for GetTransactionGroupLedgerStateDeltasForRound. +type GetTransactionGroupLedgerStateDeltasForRoundParams struct { + // Format Configures whether the response object is JSON or MessagePack encoded. If not provided, defaults to JSON. + Format *GetTransactionGroupLedgerStateDeltasForRoundParamsFormat `form:"format,omitempty" json:"format,omitempty"` +} + +// GetTransactionGroupLedgerStateDeltasForRoundParamsFormat defines parameters for GetTransactionGroupLedgerStateDeltasForRound. +type GetTransactionGroupLedgerStateDeltasForRoundParamsFormat string + // ShutdownNodeParams defines parameters for ShutdownNode. type ShutdownNodeParams struct { Timeout *uint64 `form:"timeout,omitempty" json:"timeout,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 c1359ef02a..7cff7a4400 100644 --- a/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go +++ b/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go @@ -130,178 +130,180 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go b/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go index 22599a8823..d3b3fb7299 100644 --- a/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go +++ b/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go @@ -610,244 +610,246 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL var swaggerSpec = []string{ "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", - "C8lWWHrt/cf/PwAA//9bOA9uBVkBAA==", + "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", } // 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 b740542914..62afbd9f1c 100644 --- a/daemon/algod/api/server/v2/generated/participating/private/routes.go +++ b/daemon/algod/api/server/v2/generated/participating/private/routes.go @@ -158,181 +158,183 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "/zB0GVM60gAA", + "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", + "AP//ScSRWrDVAAA=", } // 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 d2209d3f63..92327891a4 100644 --- a/daemon/algod/api/server/v2/generated/participating/public/routes.go +++ b/daemon/algod/api/server/v2/generated/participating/public/routes.go @@ -178,188 +178,190 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL var swaggerSpec = []string{ "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/handlers.go b/daemon/algod/api/server/v2/handlers.go index 48a36052e7..971a24b7c1 100644 --- a/daemon/algod/api/server/v2/handlers.go +++ b/daemon/algod/api/server/v2/handlers.go @@ -22,6 +22,7 @@ import ( "encoding/base64" "errors" "fmt" + "github.com/algorand/go-algorand/ledger/eval" "io" "math" "net/http" @@ -91,6 +92,7 @@ type LedgerForAPI interface { Block(rnd basics.Round) (blk bookkeeping.Block, err error) AddressTxns(id basics.Address, r basics.Round) ([]transactions.SignedTxnWithAD, error) GetStateDeltaForRound(rnd basics.Round) (ledgercore.StateDelta, error) + GetTracer() logic.EvalTracer } // NodeInterface represents node fns used by the handlers. @@ -1702,6 +1704,54 @@ func (v2 *Handlers) TealDisassemble(ctx echo.Context) error { return ctx.JSON(http.StatusOK, response) } +// GetLedgerStateDeltaForTransactionGroup retrieves the delta for a specified transaction group. +// (GET /v2/deltas/txn/group/{id}) +func (v2 *Handlers) GetLedgerStateDeltaForTransactionGroup(ctx echo.Context, id string, params model.GetLedgerStateDeltaForTransactionGroupParams) error { + handle, contentType, err := getCodecHandle((*string)(params.Format)) + if err != nil { + return badRequest(ctx, err, errFailedParsingFormatOption, v2.Log) + } + idDigest, err := crypto.DigestFromString(id) + if err != nil { + return badRequest(ctx, err, errNoValidTxnSpecified, v2.Log) + } + tracer, ok := v2.Node.LedgerForAPI().GetTracer().(*eval.TxnGroupDeltaTracer) + if !ok { + return notImplemented(ctx, err, errFailedRetrievingTracer, v2.Log) + } + delta, err := tracer.GetDeltaForID(idDigest) + if err != nil { + return notFound(ctx, err, errFailedRetrievingStateDelta, v2.Log) + } + data, err := encode(handle, delta) + if err != nil { + return internalError(ctx, err, errFailedToEncodeResponse, v2.Log) + } + return ctx.Blob(http.StatusOK, contentType, data) +} + +// GetTransactionGroupLedgerStateDeltasForRound retrieves the deltas for transaction groups in a given round. +// (GET /v2/deltas/{round}/txn/group) +func (v2 *Handlers) GetTransactionGroupLedgerStateDeltasForRound(ctx echo.Context, round uint64, params model.GetTransactionGroupLedgerStateDeltasForRoundParams) error { + handle, contentType, err := getCodecHandle((*string)(params.Format)) + if err != nil { + return badRequest(ctx, err, errFailedParsingFormatOption, v2.Log) + } + tracer, ok := v2.Node.LedgerForAPI().GetTracer().(*eval.TxnGroupDeltaTracer) + if !ok { + return notImplemented(ctx, err, errFailedRetrievingTracer, v2.Log) + } + deltas, err := tracer.GetDeltasForRound(basics.Round(round)) + if err != nil { + return notFound(ctx, err, errFailedRetrievingStateDelta, v2.Log) + } + data, err := encode(handle, deltas) + if err != nil { + return internalError(ctx, err, errFailedToEncodeResponse, v2.Log) + } + return ctx.Blob(http.StatusOK, contentType, data) +} + // ExperimentalCheck is only available when EnabledExperimentalAPI is true func (v2 *Handlers) ExperimentalCheck(ctx echo.Context) error { return ctx.JSON(http.StatusOK, true) diff --git a/daemon/algod/api/server/v2/test/handlers_resources_test.go b/daemon/algod/api/server/v2/test/handlers_resources_test.go index 2b013f69b2..1de86ddc19 100644 --- a/daemon/algod/api/server/v2/test/handlers_resources_test.go +++ b/daemon/algod/api/server/v2/test/handlers_resources_test.go @@ -19,6 +19,7 @@ package test import ( "encoding/json" "fmt" + "github.com/algorand/go-algorand/data/transactions/logic" "net/http" "net/http/httptest" "testing" @@ -48,6 +49,11 @@ type mockLedger struct { kvstore map[string][]byte latest basics.Round blocks []bookkeeping.Block + tracer logic.EvalTracer +} + +func (l *mockLedger) GetTracer() logic.EvalTracer { + return l.tracer } func (l *mockLedger) GetStateDeltaForRound(rnd basics.Round) (ledgercore.StateDelta, error) { diff --git a/daemon/algod/api/server/v2/test/handlers_test.go b/daemon/algod/api/server/v2/test/handlers_test.go index fd31a1bef5..0f4dcac91a 100644 --- a/daemon/algod/api/server/v2/test/handlers_test.go +++ b/daemon/algod/api/server/v2/test/handlers_test.go @@ -30,6 +30,9 @@ import ( "testing" "time" + "github.com/algorand/go-algorand/ledger/eval" + "github.com/algorand/go-algorand/ledger/ledgercore" + "github.com/labstack/echo/v4" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -2066,3 +2069,136 @@ func TestTimestampOffsetInDevMode(t *testing.T) { require.Equal(t, 400, rec.Code) require.Equal(t, "{\"message\":\"failed to set timestamp offset on the node: block timestamp offset cannot be larger than max int64 value\"}\n", rec.Body.String()) } + +func TestDeltasForTxnGroup(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + blk1 := bookkeeping.BlockHeader{Round: 1} + blk2 := bookkeeping.BlockHeader{Round: 2} + delta1 := ledgercore.StateDelta{Hdr: &blk1} + delta2 := ledgercore.StateDelta{Hdr: &blk2} + txn1 := transactions.SignedTxnWithAD{SignedTxn: transactions.SignedTxn{Txn: transactions.Transaction{Type: protocol.PaymentTx}}} + groupID1, err := crypto.DigestFromString(crypto.Hash([]byte("hello")).String()) + require.NoError(t, err) + txn2 := transactions.SignedTxnWithAD{SignedTxn: transactions.SignedTxn{Txn: transactions.Transaction{ + Type: protocol.AssetTransferTx, + Header: transactions.Header{Group: groupID1}}, + }} + + tracer := eval.MakeTxnGroupDeltaTracer(2) + handlers := v2.Handlers{ + Node: &mockNode{ + ledger: &mockLedger{ + tracer: tracer, + }, + }, + Log: logging.Base(), + } + e := echo.New() + req := httptest.NewRequest(http.MethodGet, "/", nil) + rec := httptest.NewRecorder() + c := e.NewContext(req, rec) + // Add blocks to tracer + tracer.BeforeBlock(&blk1) + tracer.AfterTxnGroup(&logic.EvalParams{TxnGroup: []transactions.SignedTxnWithAD{txn1}}, &delta1, nil) + tracer.BeforeBlock(&blk2) + tracer.AfterTxnGroup(&logic.EvalParams{TxnGroup: []transactions.SignedTxnWithAD{txn2}}, &delta2, nil) + + // Test /v2/deltas/{round}/txn/group + jsonFormatForRound := model.GetTransactionGroupLedgerStateDeltasForRoundParamsFormatJson + err = handlers.GetTransactionGroupLedgerStateDeltasForRound( + c, + uint64(1), + model.GetTransactionGroupLedgerStateDeltasForRoundParams{Format: &jsonFormatForRound}, + ) + require.NoError(t, err) + + var roundResponse model.TransactionGroupLedgerStateDeltaForRoundResponse + err = json.Unmarshal(rec.Body.Bytes(), &roundResponse) + require.NoError(t, err) + require.Equal(t, 1, len(roundResponse)) + require.Equal(t, []string{txn1.ID().String()}, roundResponse[0].Ids) + hdr, ok := roundResponse[0].Delta["Hdr"].(map[string]interface{}) + require.True(t, ok) + require.Equal(t, delta1.Hdr.Round, basics.Round(hdr["rnd"].(float64))) + + // Test invalid round parameter + c, rec = newReq(t) + err = handlers.GetTransactionGroupLedgerStateDeltasForRound( + c, + uint64(4), + model.GetTransactionGroupLedgerStateDeltasForRoundParams{Format: &jsonFormatForRound}, + ) + require.NoError(t, err) + require.Equal(t, 404, rec.Code) + + // Test /v2/deltas/txn/group/{id} + jsonFormatForTxn := model.GetLedgerStateDeltaForTransactionGroupParamsFormatJson + c, rec = newReq(t) + // Use TxID + err = handlers.GetLedgerStateDeltaForTransactionGroup( + c, + txn2.Txn.ID().String(), + model.GetLedgerStateDeltaForTransactionGroupParams{Format: &jsonFormatForTxn}, + ) + require.NoError(t, err) + var groupResponse model.LedgerStateDeltaForTransactionGroupResponse + err = json.Unmarshal(rec.Body.Bytes(), &groupResponse) + require.NoError(t, err) + groupHdr, ok := groupResponse["Hdr"].(map[string]interface{}) + require.True(t, ok) + require.Equal(t, delta2.Hdr.Round, basics.Round(groupHdr["rnd"].(float64))) + + // Use Group ID + c, rec = newReq(t) + err = handlers.GetLedgerStateDeltaForTransactionGroup( + c, + groupID1.String(), + model.GetLedgerStateDeltaForTransactionGroupParams{Format: &jsonFormatForTxn}, + ) + require.NoError(t, err) + err = json.Unmarshal(rec.Body.Bytes(), &groupResponse) + require.NoError(t, err) + groupHdr, ok = groupResponse["Hdr"].(map[string]interface{}) + require.True(t, ok) + require.Equal(t, delta2.Hdr.Round, basics.Round(groupHdr["rnd"].(float64))) + + // Test invalid ID + c, rec = newReq(t) + badID := crypto.Hash([]byte("invalidID")).String() + err = handlers.GetLedgerStateDeltaForTransactionGroup( + c, + badID, + model.GetLedgerStateDeltaForTransactionGroupParams{Format: &jsonFormatForTxn}, + ) + require.NoError(t, err) + require.Equal(t, 404, rec.Code) + + // Test nil Tracer + nilTracerHandler := v2.Handlers{ + Node: &mockNode{ + ledger: &mockLedger{ + tracer: nil, + }, + }, + Log: logging.Base(), + } + c, rec = newReq(t) + err = nilTracerHandler.GetLedgerStateDeltaForTransactionGroup( + c, + groupID1.String(), + model.GetLedgerStateDeltaForTransactionGroupParams{Format: &jsonFormatForTxn}, + ) + require.NoError(t, err) + require.Equal(t, 501, rec.Code) + + c, rec = newReq(t) + err = nilTracerHandler.GetTransactionGroupLedgerStateDeltasForRound( + c, + 0, + model.GetTransactionGroupLedgerStateDeltasForRoundParams{Format: &jsonFormatForRound}, + ) + require.NoError(t, err) + require.Equal(t, 501, rec.Code) +} diff --git a/daemon/algod/api/server/v2/utils.go b/daemon/algod/api/server/v2/utils.go index 7ffd0d665c..0aa4c9c941 100644 --- a/daemon/algod/api/server/v2/utils.go +++ b/daemon/algod/api/server/v2/utils.go @@ -62,6 +62,10 @@ func notFound(ctx echo.Context, internal error, external string, log logging.Log return returnError(ctx, http.StatusNotFound, internal, external, log) } +func notImplemented(ctx echo.Context, internal error, external string, log logging.Logger) error { + return returnError(ctx, http.StatusNotImplemented, internal, external, log) +} + func addrOrNil(addr basics.Address) *string { if addr.IsZero() { return nil diff --git a/ledger/eval/txntracer.go b/ledger/eval/txntracer.go index a8f7baa37f..f1ce6b2e1d 100644 --- a/ledger/eval/txntracer.go +++ b/ledger/eval/txntracer.go @@ -19,15 +19,48 @@ package eval import ( "fmt" + "github.com/algorand/go-deadlock" + "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/ledger/ledgercore" ) +// TxnGroupDeltaWithIds associates all the Ids (group and Txn) with a single state delta object +type TxnGroupDeltaWithIds struct { + _struct struct{} `codec:",omitempty,omitemptyarray"` + Ids []string + Delta StateDeltaSubset +} + +// StateDeltaSubset exports a subset of ledgercore.StateDelta fields for a sparse encoding +type StateDeltaSubset struct { + _struct struct{} `codec:",omitempty,omitemptyarray"` + Accts ledgercore.AccountDeltas + KvMods map[string]ledgercore.KvValueDelta + Txids map[transactions.Txid]ledgercore.IncludedTransactions + Txleases map[ledgercore.Txlease]basics.Round + Creatables map[basics.CreatableIndex]ledgercore.ModifiedCreatable + Hdr *bookkeeping.BlockHeader +} + +func convertStateDelta(delta ledgercore.StateDelta) StateDeltaSubset { + return StateDeltaSubset{ + Accts: delta.Accts, + KvMods: delta.KvMods, + Txids: delta.Txids, + Txleases: delta.Txleases, + Creatables: delta.Creatables, + Hdr: delta.Hdr, + } +} + // TxnGroupDeltaTracer collects groups of StateDelta objects covering groups of txns type TxnGroupDeltaTracer struct { + deltasLock deadlock.RWMutex // lookback is the number of rounds stored at any given time lookback uint64 // no-op methods we don't care about @@ -48,6 +81,8 @@ func MakeTxnGroupDeltaTracer(lookback uint64) *TxnGroupDeltaTracer { // BeforeBlock implements the EvalTracer interface for pre-block evaluation func (tracer *TxnGroupDeltaTracer) BeforeBlock(hdr *bookkeeping.BlockHeader) { + tracer.deltasLock.Lock() + defer tracer.deltasLock.Unlock() // Drop older rounds based on the lookback parameter delete(tracer.txnGroupDeltas, hdr.Round-basics.Round(tracer.lookback)) tracer.latestRound = hdr.Round @@ -60,6 +95,8 @@ func (tracer *TxnGroupDeltaTracer) AfterTxnGroup(ep *logic.EvalParams, deltas *l if deltas == nil { return } + tracer.deltasLock.Lock() + defer tracer.deltasLock.Unlock() txnDeltaMap := tracer.txnGroupDeltas[tracer.latestRound] for _, txn := range ep.TxnGroup { // Add Group ID @@ -72,29 +109,38 @@ func (tracer *TxnGroupDeltaTracer) AfterTxnGroup(ep *logic.EvalParams, deltas *l } // GetDeltasForRound supplies all StateDelta objects for txn groups in a given rnd -func (tracer *TxnGroupDeltaTracer) GetDeltasForRound(rnd basics.Round) ([]ledgercore.StateDelta, error) { +func (tracer *TxnGroupDeltaTracer) GetDeltasForRound(rnd basics.Round) ([]TxnGroupDeltaWithIds, error) { + tracer.deltasLock.RLock() + defer tracer.deltasLock.RUnlock() rndEntries, exists := tracer.txnGroupDeltas[rnd] if !exists { return nil, fmt.Errorf("round %d not found in txnGroupDeltaTracer", rnd) } - // Dedupe entries in our map - var deltas = map[*ledgercore.StateDelta]bool{} - var entries []ledgercore.StateDelta - for _, delta := range rndEntries { + // Dedupe entries in our map and collect Ids + var deltas = map[*ledgercore.StateDelta][]string{} + for id, delta := range rndEntries { if _, present := deltas[delta]; !present { - deltas[delta] = true - entries = append(entries, *delta) + deltas[delta] = append(deltas[delta], id.String()) } } - return entries, nil + var deltasForRound []TxnGroupDeltaWithIds + for delta, ids := range deltas { + deltasForRound = append(deltasForRound, TxnGroupDeltaWithIds{ + Ids: ids, + Delta: convertStateDelta(*delta), + }) + } + return deltasForRound, nil } // GetDeltaForID retruns the StateDelta associated with the group of transaction executed for the supplied ID (txn or group) -func (tracer *TxnGroupDeltaTracer) GetDeltaForID(id crypto.Digest) (ledgercore.StateDelta, error) { +func (tracer *TxnGroupDeltaTracer) GetDeltaForID(id crypto.Digest) (StateDeltaSubset, error) { + tracer.deltasLock.RLock() + defer tracer.deltasLock.RUnlock() for _, deltasForRound := range tracer.txnGroupDeltas { if delta, exists := deltasForRound[id]; exists { - return *delta, nil + return convertStateDelta(*delta), nil } } - return ledgercore.StateDelta{}, fmt.Errorf("unable to find delta for id: %s", id) + return StateDeltaSubset{}, fmt.Errorf("unable to find delta for id: %s", id) } diff --git a/ledger/ledger.go b/ledger/ledger.go index 7f2d6e69bb..b29ec61a5a 100644 --- a/ledger/ledger.go +++ b/ledger/ledger.go @@ -474,6 +474,11 @@ func (l *Ledger) GetStateDeltaForRound(rnd basics.Round) (ledgercore.StateDelta, return l.accts.lookupStateDelta(rnd) } +// GetTracer returns the logic.EvalTracer attached to the ledger--can be nil. +func (l *Ledger) GetTracer() logic.EvalTracer { + return l.tracer +} + // VotersForStateProof returns the top online accounts at round rnd. // The result might be nil, even with err=nil, if there are no voters // for that round because state proofs were not enabled. From 7920f214cb58c01494a02331ffcb7d93b1064b3e Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Tue, 9 May 2023 10:11:54 -0700 Subject: [PATCH 086/125] Algod: Use future consensus version to test with no low resources (#5362) --- .../algod/api/server/v2/test/handlers_test.go | 4 +- daemon/algod/api/server/v2/test/helpers.go | 6 +- ledger/applications_test.go | 8 +-- ledger/simulation/simulation_eval_test.go | 64 +++++++++++-------- ledger/simulation/testing/utils.go | 3 +- ledger/testing/initState.go | 35 ++-------- 6 files changed, 53 insertions(+), 67 deletions(-) diff --git a/daemon/algod/api/server/v2/test/handlers_test.go b/daemon/algod/api/server/v2/test/handlers_test.go index 0f4dcac91a..e2278c505b 100644 --- a/daemon/algod/api/server/v2/test/handlers_test.go +++ b/daemon/algod/api/server/v2/test/handlers_test.go @@ -937,7 +937,7 @@ func TestSimulateTransaction(t *testing.T) { for name, scenarioFn := range scenarios { t.Run(name, func(t *testing.T) { //nolint:paralleltest // Uses shared testing env sender := roots[0] - futureAppID := basics.AppIndex(2) + futureAppID := basics.AppIndex(1002) payTxn := txnInfo.NewTxn(txntest.Txn{ Type: protocol.PaymentTx, @@ -1517,7 +1517,7 @@ func TestTealDryrun(t *testing.T) { gdr.ProtocolVersion = "" ddr := tealDryrunTest(t, &gdr, "json", 200, "PASS", true) - require.Equal(t, string(protocol.ConsensusCurrentVersion), ddr.ProtocolVersion) + require.Equal(t, string(protocol.ConsensusFuture), ddr.ProtocolVersion) gdr.ProtocolVersion = string(protocol.ConsensusFuture) ddr = tealDryrunTest(t, &gdr, "json", 200, "PASS", true) require.Equal(t, string(protocol.ConsensusFuture), ddr.ProtocolVersion) diff --git a/daemon/algod/api/server/v2/test/helpers.go b/daemon/algod/api/server/v2/test/helpers.go index d9f368c2ad..bcc1e15d88 100644 --- a/daemon/algod/api/server/v2/test/helpers.go +++ b/daemon/algod/api/server/v2/test/helpers.go @@ -266,7 +266,7 @@ var genesisHash = crypto.Digest{0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, var genesisID = "testingid" var retOneProgram = []byte{2, 0x20, 1, 1, 0x22} -var proto = config.Consensus[protocol.ConsensusCurrentVersion] +var proto = config.Consensus[protocol.ConsensusFuture] func testingenv(t testing.TB, numAccounts, numTxs int, offlineAccounts bool) (*data.Ledger, []account.Root, []account.Participation, []transactions.SignedTxn, func()) { minMoneyAtStart := 100000 // min money start @@ -313,7 +313,7 @@ func testingenvWithBalances(t testing.TB, minMoneyAtStart, maxMoneyAtStart, numA } accessors = append(accessors, access) - part, err := account.FillDBWithParticipationKeys(access, root.Address(), 0, lastValid, config.Consensus[protocol.ConsensusCurrentVersion].DefaultKeyDilution) + part, err := account.FillDBWithParticipationKeys(access, root.Address(), 0, lastValid, proto.DefaultKeyDilution) if err != nil { panic(err) } @@ -351,7 +351,7 @@ func testingenvWithBalances(t testing.TB, minMoneyAtStart, maxMoneyAtStart, numA const inMem = true cfg := config.GetDefaultLocal() cfg.Archival = true - ledger, err := data.LoadLedger(logging.Base(), t.Name(), inMem, protocol.ConsensusCurrentVersion, bootstrap, genesisID, genesisHash, nil, cfg) + ledger, err := data.LoadLedger(logging.Base(), t.Name(), inMem, protocol.ConsensusFuture, bootstrap, genesisID, genesisHash, nil, cfg) if err != nil { panic(err) } diff --git a/ledger/applications_test.go b/ledger/applications_test.go index 436791b66a..54470f5f97 100644 --- a/ledger/applications_test.go +++ b/ledger/applications_test.go @@ -1331,8 +1331,8 @@ return a.Greater(len(ops.Program), 1) program := ops.Program - proto := config.Consensus[protocol.ConsensusCurrentVersion] - genesisInitState, initKeys := ledgertesting.GenerateInitState(t, protocol.ConsensusCurrentVersion, 1000000) + proto := config.Consensus[protocol.ConsensusFuture] + genesisInitState, initKeys := ledgertesting.GenerateInitState(t, protocol.ConsensusFuture, 1000000) creator, err := basics.UnmarshalChecksumAddress("3LN5DBFC2UTPD265LQDP3LMTLGZCQ5M3JV7XTVTGRH5CKSVNQVDFPN6FG4") a.NoError(err) @@ -1357,7 +1357,7 @@ return GenesisHash: genesisInitState.GenesisHash, } - appIdx := basics.AppIndex(2) // second tnx => idx = 2 + appIdx := basics.AppIndex(1002) // second tnx => idx = 1002 // fund app account fundingPayment := transactions.Transaction{ @@ -1386,7 +1386,7 @@ return Header: txHeader, ApplicationCallTxnFields: appCreateFields, } - err = l1.appendUnvalidatedTx(t, genesisInitState.Accounts, initKeys, appCreate, transactions.ApplyData{ApplicationID: 2}) + err = l1.appendUnvalidatedTx(t, genesisInitState.Accounts, initKeys, appCreate, transactions.ApplyData{ApplicationID: appIdx}) a.NoError(err) // few empty blocks to reset deltas and flush diff --git a/ledger/simulation/simulation_eval_test.go b/ledger/simulation/simulation_eval_test.go index 555379a94a..5869b49f53 100644 --- a/ledger/simulation/simulation_eval_test.go +++ b/ledger/simulation/simulation_eval_test.go @@ -535,7 +535,7 @@ int 1`, var AppBudgetConsumed, AppBudgetAdded uint64 if expectedSuccess { expectedAppCallAD = transactions.ApplyData{ - ApplicationID: 2, + ApplicationID: 1002, EvalDelta: transactions.EvalDelta{ Logs: []string{"hello"}, }, @@ -586,7 +586,7 @@ func TestSimpleAppCall(t *testing.T) { sender := accounts[0] // Create program and call it - futureAppID := basics.AppIndex(1) + futureAppID := basics.AppIndex(1001) createTxn := txnInfo.NewTxn(txntest.Txn{ Type: protocol.ApplicationCallTx, Sender: sender.Addr, @@ -667,7 +667,7 @@ func TestRejectAppCall(t *testing.T) { simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { sender := accounts[0] - futureAppID := basics.AppIndex(1) + futureAppID := basics.AppIndex(1001) createTxn := txnInfo.NewTxn(txntest.Txn{ Type: protocol.ApplicationCallTx, Sender: sender.Addr, @@ -722,7 +722,7 @@ func TestErrorAppCall(t *testing.T) { simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { sender := accounts[0] - futureAppID := basics.AppIndex(1) + futureAppID := basics.AppIndex(1001) createTxn := txnInfo.NewTxn(txntest.Txn{ Type: protocol.ApplicationCallTx, Sender: sender.Addr, @@ -787,7 +787,7 @@ func TestAppCallOverBudget(t *testing.T) { simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { sender := accounts[0] - futureAppID := basics.AppIndex(1) + futureAppID := basics.AppIndex(1001) // App create with cost 4 createTxn := txnInfo.NewTxn(txntest.Txn{ Type: protocol.ApplicationCallTx, @@ -860,7 +860,7 @@ func TestAppCallWithExtraBudget(t *testing.T) { simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { sender := accounts[0] - futureAppID := basics.AppIndex(1) + futureAppID := basics.AppIndex(1001) // App create with cost 4 createTxn := txnInfo.NewTxn(txntest.Txn{ Type: protocol.ApplicationCallTx, @@ -931,7 +931,7 @@ func TestAppCallWithExtraBudgetOverBudget(t *testing.T) { simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { sender := accounts[0] - futureAppID := basics.AppIndex(1) + futureAppID := basics.AppIndex(1001) // App create with cost 4 createTxn := txnInfo.NewTxn(txntest.Txn{ Type: protocol.ApplicationCallTx, @@ -1008,7 +1008,7 @@ func TestAppCallWithExtraBudgetExceedsInternalLimit(t *testing.T) { sender := accounts[0] - futureAppID := basics.AppIndex(1) + futureAppID := basics.AppIndex(1001) // App create with cost 4 createTxn := txnInfo.NewTxn(txntest.Txn{ Type: protocol.ApplicationCallTx, @@ -1143,7 +1143,7 @@ pop simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { sender := accounts[0] - futureAppID := basics.AppIndex(2) + futureAppID := basics.AppIndex(1002) // fund outer app fund := txnInfo.NewTxn(txntest.Txn{ Type: protocol.PaymentTx, @@ -1305,7 +1305,7 @@ int 1` sender := accounts[0] receiver := accounts[1] - futureAppID := basics.AppIndex(1) + futureAppID := basics.AppIndex(1001) createTxn := txnInfo.NewTxn(txntest.Txn{ Type: protocol.ApplicationCallTx, @@ -1399,7 +1399,7 @@ int 1` sender := accounts[0] receiver := accounts[1] - futureAppID := basics.AppIndex(1) + futureAppID := basics.AppIndex(1001) createTxn := txnInfo.NewTxn(txntest.Txn{ Type: protocol.ApplicationCallTx, @@ -1487,7 +1487,7 @@ func TestBalanceChangesWithApp(t *testing.T) { receiver := accounts[1] receiverBalance := receiver.AcctData.MicroAlgos.Raw - futureAppID := basics.AppIndex(1) + futureAppID := basics.AppIndex(1001) createTxn := txnInfo.NewTxn(txntest.Txn{ Type: protocol.ApplicationCallTx, Sender: sender.Addr, @@ -1702,13 +1702,13 @@ func TestPartialMissingSignatures(t *testing.T) { { Txn: transactions.SignedTxnWithAD{ ApplyData: transactions.ApplyData{ - ConfigAsset: 1, + ConfigAsset: 1001, }, }, }, { Txn: transactions.SignedTxnWithAD{ ApplyData: transactions.ApplyData{ - ConfigAsset: 2, + ConfigAsset: 1002, }, }, }, @@ -1836,18 +1836,21 @@ func TestAppCallInnerTxnApplyDataOnFail(t *testing.T) { singleInnerLogAndFail := makeProgramToCallInner(t, logAndFail) nestedInnerLogAndFail := makeProgramToCallInner(t, singleInnerLogAndFail) + futureOuterAppID := basics.AppIndex(1003) + futureInnerAppID := futureOuterAppID + 1 + // fund outer app pay1 := txnInfo.NewTxn(txntest.Txn{ Type: protocol.PaymentTx, Sender: sender.Addr, - Receiver: basics.AppIndex(3).Address(), + Receiver: futureOuterAppID.Address(), Amount: 401_000, // 400_000 min balance plus 1_000 for 1 txn }) // fund inner app pay2 := txnInfo.NewTxn(txntest.Txn{ Type: protocol.PaymentTx, Sender: sender.Addr, - Receiver: basics.AppIndex(4).Address(), + Receiver: futureInnerAppID.Address(), Amount: 401_000, // 400_000 min balance plus 1_000 for 1 txn }) // create app @@ -1882,19 +1885,19 @@ int 1`, { Txn: transactions.SignedTxnWithAD{ ApplyData: transactions.ApplyData{ - ApplicationID: 3, + ApplicationID: futureOuterAppID, EvalDelta: transactions.EvalDelta{ Logs: []string{"starting inner txn"}, InnerTxns: []transactions.SignedTxnWithAD{ { ApplyData: transactions.ApplyData{ - ApplicationID: 4, + ApplicationID: futureInnerAppID, EvalDelta: transactions.EvalDelta{ Logs: []string{"starting inner txn"}, InnerTxns: []transactions.SignedTxnWithAD{ { ApplyData: transactions.ApplyData{ - ApplicationID: 5, + ApplicationID: futureInnerAppID + 1, EvalDelta: transactions.EvalDelta{ Logs: []string{"message"}, }, @@ -1943,11 +1946,13 @@ func TestNonAppCallInnerTxnApplyDataOnFail(t *testing.T) { logAndFailItxnCode := makeItxnSubmitToCallInner(t, logAndFail) approvalProgram := wrapCodeWithVersionAndReturn(createAssetCode + logAndFailItxnCode) + futureAppID := basics.AppIndex(1002) + // fund outer app pay1 := txnInfo.NewTxn(txntest.Txn{ Type: protocol.PaymentTx, Sender: sender.Addr, - Receiver: basics.AppIndex(2).Address(), + Receiver: futureAppID.Address(), Amount: 401_000, // 400_000 min balance plus 1_000 for 1 txn }) // create app @@ -1981,18 +1986,18 @@ int 1`, { Txn: transactions.SignedTxnWithAD{ ApplyData: transactions.ApplyData{ - ApplicationID: 2, + ApplicationID: futureAppID, EvalDelta: transactions.EvalDelta{ Logs: []string{"starting asset create", "finished asset create", "starting inner txn"}, InnerTxns: []transactions.SignedTxnWithAD{ { ApplyData: transactions.ApplyData{ - ConfigAsset: 3, + ConfigAsset: basics.AssetIndex(futureAppID) + 1, }, }, { ApplyData: transactions.ApplyData{ - ApplicationID: 4, + ApplicationID: futureAppID + 2, EvalDelta: transactions.EvalDelta{ Logs: []string{"message"}, }, @@ -2035,14 +2040,17 @@ func TestInnerTxnNonAppCallFailure(t *testing.T) { simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { sender := accounts[0] + futureAppID := basics.AppIndex(1002) + futureAssetID := basics.AssetIndex(1003) + // configAssetCode should fail because createAssetCode does not set an asset manager - approvalProgram := wrapCodeWithVersionAndReturn(createAssetCode + fmt.Sprintf(configAssetCode, 3)) + approvalProgram := wrapCodeWithVersionAndReturn(createAssetCode + fmt.Sprintf(configAssetCode, futureAssetID)) // fund outer app pay1 := txnInfo.NewTxn(txntest.Txn{ Type: protocol.PaymentTx, Sender: sender.Addr, - Receiver: basics.AppIndex(2).Address(), + Receiver: futureAppID.Address(), Amount: 402_000, // 400_000 min balance plus 2_000 for 2 inners }) // create app @@ -2076,13 +2084,13 @@ int 1`, { Txn: transactions.SignedTxnWithAD{ ApplyData: transactions.ApplyData{ - ApplicationID: 2, + ApplicationID: futureAppID, EvalDelta: transactions.EvalDelta{ Logs: []string{"starting asset create", "finished asset create", "starting asset config"}, InnerTxns: []transactions.SignedTxnWithAD{ { ApplyData: transactions.ApplyData{ - ConfigAsset: 3, + ConfigAsset: futureAssetID, }, }, {}, @@ -2116,7 +2124,7 @@ func TestMockTracerScenarios(t *testing.T) { simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { sender := accounts[0] - futureAppID := basics.AppIndex(2) + futureAppID := basics.AppIndex(1002) payTxn := txnInfo.NewTxn(txntest.Txn{ Type: protocol.PaymentTx, Sender: sender.Addr, diff --git a/ledger/simulation/testing/utils.go b/ledger/simulation/testing/utils.go index a18a791e1c..2e33c94117 100644 --- a/ledger/simulation/testing/utils.go +++ b/ledger/simulation/testing/utils.go @@ -78,7 +78,7 @@ func (info TxnInfo) InnerTxn(parent transactions.SignedTxn, inner txntest.Txn) t // PrepareSimulatorTest creates an environment to test transaction simulations func PrepareSimulatorTest(t *testing.T) (l *data.Ledger, accounts []Account, txnInfo TxnInfo) { - genesisInitState, keys := ledgertesting.GenerateInitState(t, protocol.ConsensusCurrentVersion, 100) + genesisInitState, keys := ledgertesting.GenerateInitState(t, protocol.ConsensusFuture, 100) // Prepare ledger const inMem = true @@ -119,6 +119,7 @@ func PrepareSimulatorTest(t *testing.T) (l *data.Ledger, accounts []Account, txn numBlocks := rand.Intn(4) for i := 0; i < numBlocks; i++ { nextBlock := bookkeeping.MakeBlock(latestHeader) + nextBlock.TxnCounter = latestHeader.TxnCounter err = l.AddBlock(nextBlock, agreement.Certificate{}) require.NoError(t, err) diff --git a/ledger/testing/initState.go b/ledger/testing/initState.go index 00cbe0d1ba..374c1d6c4e 100644 --- a/ledger/testing/initState.go +++ b/ledger/testing/initState.go @@ -21,7 +21,6 @@ import ( "github.com/stretchr/testify/require" - "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/basics" basics_testing "github.com/algorand/go-algorand/data/basics/testing" @@ -46,7 +45,6 @@ func init() { // GenerateInitState generates testing init state func GenerateInitState(tb testing.TB, proto protocol.ConsensusVersion, baseAlgoPerAccount int) (genesisInitState ledgercore.InitState, initKeys map[basics.Address]*crypto.SignatureSecrets) { - params := config.Consensus[proto] poolAddr := testPoolAddr sinkAddr := testSinkAddr @@ -76,40 +74,19 @@ func GenerateInitState(tb testing.TB, proto protocol.ConsensusVersion, baseAlgoP initKeys[sinkAddr] = sinkSecret initAccounts[sinkAddr] = basics_testing.MakeAccountData(basics.NotParticipating, basics.MicroAlgos{Raw: 7654321}) - incentivePoolBalanceAtGenesis := initAccounts[poolAddr].MicroAlgos - var initialRewardsPerRound uint64 - if params.InitialRewardsRateCalculation { - initialRewardsPerRound = basics.SubSaturate(incentivePoolBalanceAtGenesis.Raw, params.MinBalance) / uint64(params.RewardsRateRefreshInterval) - } else { - initialRewardsPerRound = incentivePoolBalanceAtGenesis.Raw / uint64(params.RewardsRateRefreshInterval) - } + genesisBalances := bookkeeping.MakeTimestampedGenesisBalances(initAccounts, sinkAddr, poolAddr, 0) + genesisID := tb.Name() + genesisHash := crypto.Hash([]byte(genesisID)) - initBlock := bookkeeping.Block{ - BlockHeader: bookkeeping.BlockHeader{ - GenesisID: tb.Name(), - Round: 0, - RewardsState: bookkeeping.RewardsState{ - RewardsRate: initialRewardsPerRound, - RewardsPool: poolAddr, - FeeSink: sinkAddr, - }, - UpgradeState: bookkeeping.UpgradeState{ - CurrentProtocol: proto, - }, - }, - } + initBlock, err := bookkeeping.MakeGenesisBlock(proto, genesisBalances, genesisID, genesisHash) + require.NoError(tb, err) - var err error initBlock.TxnCommitments, err = initBlock.PaysetCommit() require.NoError(tb, err) - if params.SupportGenesisHash { - initBlock.BlockHeader.GenesisHash = crypto.Hash([]byte(tb.Name())) - } - genesisInitState.Block = initBlock genesisInitState.Accounts = initAccounts - genesisInitState.GenesisHash = crypto.Hash([]byte(tb.Name())) + genesisInitState.GenesisHash = genesisHash return } From 6bfe03ab250778fb0b8067008294e567541db2ee Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Tue, 9 May 2023 12:31:26 -0500 Subject: [PATCH 087/125] tools: Run the Cross Repo Type Checker in C.I. (#5326) --- .gitignore | 2 +- tools/x-repo-types/Makefile | 14 +- tools/x-repo-types/README.md | 12 +- tools/x-repo-types/go.mod | 20 +++ tools/x-repo-types/go.sum | 29 ++++ tools/x-repo-types/typeAnalyzer/main.go | 12 +- .../x-repo-types/typeAnalyzer/typeAnalyzer.go | 4 +- tools/x-repo-types/xrt.go | 153 ++++++++++++++++-- tools/x-repo-types/xrt_test.go | 103 ++++++++++++ 9 files changed, 315 insertions(+), 34 deletions(-) create mode 100644 tools/x-repo-types/go.mod create mode 100644 tools/x-repo-types/go.sum create mode 100644 tools/x-repo-types/xrt_test.go diff --git a/.gitignore b/.gitignore index 00df89eb2c..7c774b834a 100644 --- a/.gitignore +++ b/.gitignore @@ -73,4 +73,4 @@ testresults.json tools/block-generator/block-generator # cross repo types tool binary -tools/x-repo-types/xrt +tools/x-repo-types/x-repo-types diff --git a/tools/x-repo-types/Makefile b/tools/x-repo-types/Makefile index 7d21516da1..05094a8484 100644 --- a/tools/x-repo-types/Makefile +++ b/tools/x-repo-types/Makefile @@ -5,35 +5,35 @@ all: goal-v-sdk goal-v-spv 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: - ./xrt --x-package "github.com/algorand/go-algorand/ledger/ledgercore" \ + 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: - ./xrt --x-package "github.com/algorand/go-algorand/data/bookkeeping" \ + 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: - ./xrt --x-package "github.com/algorand/go-algorand/data/bookkeeping" \ + 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: - ./xrt --x-package "github.com/algorand/go-algorand/data/bookkeeping" \ + 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: - ./xrt --x-package "github.com/algorand/go-algorand/crypto/stateproof" \ + x-repo-types --x-package "github.com/algorand/go-algorand/crypto/stateproof" \ --x-type "StateProof" \ --y-branch "develop" \ --y-package "github.com/algorand/go-algorand-sdk/v2/types" \ @@ -44,7 +44,7 @@ goal-v-sdk-stateproof: goal-v-spv: goal-v-spv-stateproof goal-v-spv-stateproof: - ./xrt --x-package "github.com/algorand/go-algorand/crypto/stateproof" \ + x-repo-types --x-package "github.com/algorand/go-algorand/crypto/stateproof" \ --x-type "StateProof" \ --y-package "github.com/algorand/go-stateproof-verification/stateproof" \ --y-type "StateProof" @@ -52,7 +52,7 @@ goal-v-spv-stateproof: # reset typeAnalyzer/main.go for passing checks: reset-dummy-main: - ./xrt --x-package "github.com/algorand/go-algorand/ledger/ledgercore" \ + x-repo-types --x-package "github.com/algorand/go-algorand/ledger/ledgercore" \ --x-type "StateDelta" \ --y-package "github.com/algorand/go-algorand/data/bookkeeping" \ --y-type "Genesis" diff --git a/tools/x-repo-types/README.md b/tools/x-repo-types/README.md index 03755da256..97e7706e73 100644 --- a/tools/x-repo-types/README.md +++ b/tools/x-repo-types/README.md @@ -3,17 +3,11 @@ Given two types **X** and **Y** from separate repositories, compare the types and generate a report of any differences to the serialized shape of the types. In particular it ignores different embedding of structs, different field names if `codec` tags are used, and different types if they map to the same primitives. This tool is designed to be used in CI systems to alert us if a change is made to one repo without a corresponding change to another. For example the `Genesis` type in `go-algorand` and `go-algorand-sdk`. See the [Makefile](./Makefile) for additional examples. -## Build the `xrt` binary - -```sh -go build -o xrt -``` - ## Example run ```sh goal-v-sdk-state-delta-xrt: - ./xrt --x-package "github.com/algorand/go-algorand/ledger/ledgercore" \ + 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" \ @@ -24,8 +18,8 @@ goal-v-sdk-state-delta-xrt: ### Cross Type Comparison Process -1. Inside of `tools/x-repo-types` run the command `./xrt --x-package X_PACKAGE_NAME ...` -2. `xrt` then does the following: +1. Inside of `tools/x-repo-types` run the command `x-repo-types --x-package X_PACKAGE_NAME ...` +2. `x-repo-types` then does the following: 1. `go get`'s the package 2. Populates the template `typeAnalyzer/main.tmpl` with comparison types 3. Saves it in `typeAnalyzer/main.go` diff --git a/tools/x-repo-types/go.mod b/tools/x-repo-types/go.mod new file mode 100644 index 0000000000..0fba7a8da4 --- /dev/null +++ b/tools/x-repo-types/go.mod @@ -0,0 +1,20 @@ +module github.com/algorand/go-algorand/tools/x-repo-types + +go 1.17 + +replace github.com/algorand/go-algorand => ../.. + +require ( + github.com/algorand/go-algorand v0.0.0-20230502140608-e24a35add0bb + github.com/spf13/cobra v1.7.0 + github.com/stretchr/testify v1.8.2 +) + +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 new file mode 100644 index 0000000000..dba66ee268 --- /dev/null +++ b/tools/x-repo-types/go.sum @@ -0,0 +1,29 @@ +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= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +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= +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/tools/x-repo-types/typeAnalyzer/main.go b/tools/x-repo-types/typeAnalyzer/main.go index 3de25af203..f5225a54a0 100644 --- a/tools/x-repo-types/typeAnalyzer/main.go +++ b/tools/x-repo-types/typeAnalyzer/main.go @@ -17,7 +17,7 @@ /* WARNING THIS FILE ONLY EXISTS FOR DEBUGGING AND TO MAKE THE BUILD HAPPY - !!!!! IT IS OVERWRITTEN BY AT RUNTIME !!!!! + !!!!! IT IS OVERWRITTEN AT RUNTIME !!!!! */ package main @@ -26,13 +26,13 @@ import ( "fmt" "os" - ypkg "github.com/algorand/go-algorand/data/bookkeeping" - xpkg "github.com/algorand/go-algorand/ledger/ledgercore" + xpkg "net/http" + ypkg "time" ) func main() { - xRoot := MakeType(xpkg.StateDelta{}) - yRoot := MakeType(ypkg.Genesis{}) + xRoot := MakeType(xpkg.Request{}) + yRoot := MakeType(ypkg.Time{}) // ---- BUILD ---- // x, y := xRoot.Type, yRoot.Type @@ -71,7 +71,7 @@ func main() { // ---- DIFF ---- // fmt.Printf("\n\nCompare the Type Trees %q v %q\n", x, y) - xType, yType, diff, err := StructDiff(xpkg.StateDelta{}, ypkg.Genesis{}, diffExclusions) + xType, yType, diff, err := StructDiff(xpkg.Request{}, ypkg.Time{}, diffExclusions) if err != nil { fmt.Printf("Error: %s\n", err) os.Exit(1) diff --git a/tools/x-repo-types/typeAnalyzer/typeAnalyzer.go b/tools/x-repo-types/typeAnalyzer/typeAnalyzer.go index a0e3b0fb40..3626e01ad3 100644 --- a/tools/x-repo-types/typeAnalyzer/typeAnalyzer.go +++ b/tools/x-repo-types/typeAnalyzer/typeAnalyzer.go @@ -400,7 +400,7 @@ func Report(x, y TypeNode, d *Diff) string { sb.WriteString(` ======================================================== - REPORT + STRUCT DIFF REPORT comparing <<<<<`) sb.WriteString(x.String()) @@ -469,7 +469,7 @@ SOURCE: %s } sb.WriteString(` ======================================================== -=============== REPORT END =============== +=============== STRUCT DIFF REPORT END =============== ========================================================`) return sb.String() diff --git a/tools/x-repo-types/xrt.go b/tools/x-repo-types/xrt.go index 84d72d6f85..016f50ff3a 100644 --- a/tools/x-repo-types/xrt.go +++ b/tools/x-repo-types/xrt.go @@ -18,7 +18,9 @@ package main import ( "bytes" + "errors" "fmt" + "io/ioutil" "log" "os" "os/exec" @@ -34,14 +36,17 @@ import ( //go:embed typeAnalyzer/main.tmpl var differTmpl string +//go:embed typeAnalyzer/typeAnalyzer.go +var typeAnalyzerGo string + func main() { - var xPkg, xBranch, xType, yPkg, yBranch, yType string + var xPkg, xBranch, xType, yPkg, yBranch, yType, artifactPath string rootCmd := &cobra.Command{ - Use: "xrt", + Use: "x-repo-types", Short: "Compare types across repos", Run: func(cmd *cobra.Command, args []string) { - if err := runApp(xPkg, xBranch, xType, yPkg, yBranch, yType); err != nil { + if err := runApp(xPkg, xBranch, xType, yPkg, yBranch, yType, artifactPath); err != nil { log.Fatal(err) } }, @@ -53,13 +58,28 @@ func main() { rootCmd.Flags().StringVar(&yPkg, "y-package", "", "Go repo and package for type for type y") rootCmd.Flags().StringVar(&yBranch, "y-branch", "", "repository branch for type y") rootCmd.Flags().StringVar(&yType, "y-type", "", "Exported type in the package for type y") + rootCmd.Flags().StringVar(&artifactPath, "artifact-path", "", "Path to write auxiliary code which will run after downloading go-types. If not provided, a temporary folder will be created.") if err := rootCmd.Execute(); err != nil { log.Fatal(err) } } -func runApp(xPkg, xBranch, xType, yPkg, yBranch, yType string) error { +func runApp(xPkg, xBranch, xType, yPkg, yBranch, yType, artifactPath string) (err error) { + fileBackups, err := setUp() + fmt.Printf("fileBackups: %#v\n\n", fileBackups) + if err != nil { + return err + } + defer func() { + fmt.Printf("tearDown to restore: %#v\n\n", fileBackups) + teardownErr := tearDown(fileBackups) + if teardownErr != nil { + fmt.Printf("problem during tearDown: %v\n", teardownErr) + err = teardownErr + } + }() + if xPkg == "" || xType == "" { return fmt.Errorf("package:%s, and type:%s flags are required", xPkg, xType) } @@ -76,7 +96,7 @@ func runApp(xPkg, xBranch, xType, yPkg, yBranch, yType string) error { yPkgBranch += "@" + yBranch } - err := goGet(xPkgBranch) + err = goGet(xPkgBranch) if err != nil { return err } @@ -107,10 +127,104 @@ func runApp(xPkg, xBranch, xType, yPkg, yBranch, yType string) error { // Compare the types by running the template typeAnalyzer/main.tmpl in a separate process // typeAnalyzer/main will return an error if the types are not the same // here we propagate the error to the caller, so as to fail the test. - err = serializationDiff(xRepo, xPkgSuffix, xType, yRepo, yPkgSuffix, yType) + err = serializationDiff(artifactPath, xRepo, xPkgSuffix, xType, yRepo, yPkgSuffix, yType) + if err != nil { + return err + } + return nil +} + +func setUp() (map[string]string, error) { + pkgRoot, err := findPkgRoot() + if err != nil { + return nil, err + } + if pkgRoot == "" { + fmt.Print("No package root found. Will not attempt to backup go.mod and go.sum files.\n\n") + return nil, nil + } + + fmt.Printf("Will look for and backup go.mod and go.sum files in pkgRoot: %s\n\n", pkgRoot) + + goModPath := filepath.Join(pkgRoot, "go.mod") + goSumPath := filepath.Join(pkgRoot, "go.sum") + + backups := make(map[string]string) + for _, path := range []string{goModPath, goSumPath} { + backup, err := backupFile(path) + if err != nil { + return nil, err + } + backups[backup] = path + } + return backups, nil +} + +func tearDown(fileBackups map[string]string) error { + for backup, path := range fileBackups { + err := restoreFile(backup, path) + if err != nil { + return err + } + } + return nil +} + +func backupFile(src string) (string, error) { + content, err := ioutil.ReadFile(src) + if err != nil { + return "", err + } + + tmpFile, err := ioutil.TempFile("", "backup-*") + if err != nil { + return "", err + } + + err = ioutil.WriteFile(tmpFile.Name(), content, 0644) + if err != nil { + return "", err + } + + return tmpFile.Name(), nil +} + +func findPkgRoot() (string, error) { + cmd := exec.Command("go", "list", "-m", "-f", "{{.Dir}}") + var stdout, stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + + err := cmd.Run() + if err != nil { + return "", errors.New(stderr.String()) + } + + return strings.TrimSpace(stdout.String()), nil +} + +func restoreFile(src, dst string) error { + // assuming that dst already exists + dstFileInfo, err := os.Stat(dst) + if err != nil { + return err + } + + content, err := ioutil.ReadFile(src) if err != nil { return err } + + err = ioutil.WriteFile(dst, content, dstFileInfo.Mode()) + if err != nil { + return err + } + + err = os.Remove(src) + if err != nil { + return err + } + return nil } @@ -160,7 +274,9 @@ func main() { return cmd.Run() } -func serializationDiff(xRepo, xPkgPath, xType, yRepo, yPkgPath, yType string) error { +// serializationDiff runs the typeAnalyzer/main.tmpl template in a separate process. +// If you want to persist the generated artifacts, pass in a non-empty artifactPath. +func serializationDiff(artifactPath, xRepo, xPkgPath, xType, yRepo, yPkgPath, yType string) error { fmt.Printf("Diffing %s from package %s VS %s from package %s...\n", xType, xPkgPath, yType, yPkgPath) tmpl, err := template.New("code").Parse(differTmpl) @@ -183,13 +299,32 @@ func serializationDiff(xRepo, xPkgPath, xType, yRepo, yPkgPath, yType string) er os.Exit(1) } - main := filepath.Join("typeAnalyzer", "main.go") - typeAnalyzer := filepath.Join("typeAnalyzer", "typeAnalyzer.go") + var main, typeAnalyzer string + if artifactPath == "" { + ap, err := os.MkdirTemp("", "typeAnalyzer") + if err != nil { + fmt.Println("Error creating typeAnalyzer temp directory:", err) + os.Exit(1) + } + artifactPath = ap + defer os.RemoveAll(artifactPath) + } + + main = filepath.Join(artifactPath, "main.go") + typeAnalyzer = filepath.Join(artifactPath, "typeAnalyzer.go") + err = os.WriteFile(main, buf.Bytes(), 0644) if err != nil { return err } + err = os.WriteFile(typeAnalyzer, []byte(typeAnalyzerGo), 0644) + if err != nil { + return err + } + + fmt.Printf("Saved x-repo-types code to directory: [%s]\n", artifactPath) + //nolint:gosec // main and typeAnalyzer are hard-coded above so no security concerns here cmd := exec.Command("go", "run", main, typeAnalyzer) cmd.Stdout = os.Stdout diff --git a/tools/x-repo-types/xrt_test.go b/tools/x-repo-types/xrt_test.go new file mode 100644 index 0000000000..119954e638 --- /dev/null +++ b/tools/x-repo-types/xrt_test.go @@ -0,0 +1,103 @@ +// Copyright (C) 2019-2023 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package main + +import ( + "testing" + + "github.com/algorand/go-algorand/test/partitiontest" + "github.com/stretchr/testify/require" +) + +type testCase struct { + name string + xPkg, xBranch, xType, yPkg, yBranch, yType string + skip bool + skipReason string +} + +func TestCrossRepoTypes(t *testing.T) { + partitiontest.PartitionTest(t) + + testCases := []testCase{ + { + name: "SDK: StateDelta", + xPkg: "github.com/algorand/go-algorand/ledger/ledgercore", + xBranch: "", + xType: "StateDelta", + yPkg: "github.com/algorand/go-algorand-sdk/v2/types", + yBranch: "develop", + yType: "LedgerStateDelta", + }, + { + name: "goal-v-sdk-genesis", + xPkg: "github.com/algorand/go-algorand/data/bookkeeping", + xType: "Genesis", + yPkg: "github.com/algorand/go-algorand-sdk/v2/types", + yBranch: "develop", + yType: "Genesis", + skip: true, + skipReason: `LEVEL 3 of goal basics.AccountData has 12 fields missing from SDK types.Account`, + }, + { + name: "goal-v-sdk-block", + xPkg: "github.com/algorand/go-algorand/data/bookkeeping", + xType: "Block", + yPkg: "github.com/algorand/go-algorand-sdk/v2/types", + yBranch: "develop", + yType: "Block", + skip: true, + skipReason: `Several issues. For example: LEVEL 5 of goal bookkeeping.Block is EvalDelta with field [SharedAccts](codec:"sa,allocbound=config.MaxEvalDeltaAccounts") VS SDK types.EvalDelta is missing SharedAccts field`, + }, + { + name: "goal-v-sdk-blockheader", + xPkg: "github.com/algorand/go-algorand/data/bookkeeping", + xType: "BlockHeader", + yPkg: "github.com/algorand/go-algorand-sdk/v2/types", + yBranch: "develop", + yType: "BlockHeader", + }, + { + name: "goal-v-sdk-stateproof", + xPkg: "github.com/algorand/go-algorand/crypto/stateproof", + xType: "StateProof", + yPkg: "github.com/algorand/go-algorand-sdk/v2/types", + yBranch: "develop", + yType: "StateProof", + }, + { + name: "goal-v-spv-stateproof", + xPkg: "github.com/algorand/go-algorand/crypto/stateproof", + xType: "StateProof", + yPkg: "github.com/algorand/go-stateproof-verification/stateproof", + yType: "StateProof", + }, + } + + for _, tc := range testCases { + // These should be run in serial as they modify go.mod, go.sum and typeAnalyzer/main.go + // TODO: it probably is preferrable to setup and `go get` everything _before_ running the tests + // and tearDown after the tests are done. + t.Run(tc.name, func(t *testing.T) { + if tc.skip { + t.Skip(tc.skipReason) + } + err := runApp(tc.xPkg, tc.xBranch, tc.xType, tc.yPkg, tc.yBranch, tc.yType, "") + require.NoError(t, err) + }) + } +} From 74c3caa50e87588be99fd169366a9025360d05d8 Mon Sep 17 00:00:00 2001 From: algoidan <79864820+algoidan@users.noreply.github.com> Date: Tue, 9 May 2023 23:24:53 +0300 Subject: [PATCH 088/125] Tests: Fix error messages in ledger tests (#5363) --- ledger/acctonline_test.go | 6 ++--- ledger/acctupdates_test.go | 38 +++++++++++++++----------------- ledger/catchpointtracker_test.go | 24 +++++++++----------- ledger/voters_test.go | 4 +--- 4 files changed, 31 insertions(+), 41 deletions(-) diff --git a/ledger/acctonline_test.go b/ledger/acctonline_test.go index 45b1b4f8e5..049a703d5a 100644 --- a/ledger/acctonline_test.go +++ b/ledger/acctonline_test.go @@ -129,9 +129,7 @@ func newBlock(t *testing.T, ml *mockLedgerForTracker, testProtocolVersion protoc delta.Accts.MergeAccounts(updates) delta.Totals = newTotals - err := ml.addMockBlock(blockEntry{block: blk}, delta) - require.NoError(t, err) - ml.trackers.newBlock(blk, delta) + ml.addBlock(blockEntry{block: blk}, delta) return newTotals } @@ -779,7 +777,7 @@ func TestAcctOnlineRoundParamsCache(t *testing.T) { delta.Totals = accumulateTotals(t, consensusVersion, []map[basics.Address]ledgercore.AccountData{totals}, rewardLevel) allTotals[i] = delta.Totals - ml.trackers.newBlock(blk, delta) + ml.addBlock(blockEntry{block: blk}, delta) accts = append(accts, newAccts) if i > basics.Round(maxBalLookback) && i%10 == 0 { diff --git a/ledger/acctupdates_test.go b/ledger/acctupdates_test.go index 596b85c72b..9049447775 100644 --- a/ledger/acctupdates_test.go +++ b/ledger/acctupdates_test.go @@ -190,20 +190,24 @@ func (ml *mockLedgerForTracker) Latest() basics.Round { return basics.Round(len(ml.blocks)) - 1 } -func (ml *mockLedgerForTracker) addMockBlock(be blockEntry, delta ledgercore.StateDelta) error { +func (ml *mockLedgerForTracker) addBlock(be blockEntry, delta ledgercore.StateDelta) { + ml.addToBlockQueue(be, delta) + ml.trackers.newBlock(be.block, delta) +} + +func (ml *mockLedgerForTracker) addToBlockQueue(be blockEntry, delta ledgercore.StateDelta) { ml.mu.Lock() defer ml.mu.Unlock() ml.blocks = append(ml.blocks, be) ml.deltas = append(ml.deltas, delta) - return nil } func (ml *mockLedgerForTracker) trackerEvalVerified(blk bookkeeping.Block, accUpdatesLedger eval.LedgerForEvaluator) (ledgercore.StateDelta, error) { ml.mu.RLock() defer ml.mu.RUnlock() - // support returning the deltas if the client explicitly provided them by calling addMockBlock, otherwise, + // support returning the deltas if the client explicitly provided them by calling addToBlockQueue, otherwise, // just return an empty state delta ( since the client clearly didn't care about these ) if len(ml.deltas) > int(blk.Round()) { return ml.deltas[uint64(blk.Round())], nil @@ -564,7 +568,7 @@ func testAcctUpdates(t *testing.T, conf config.Local) { delta.Creatables = creatablesFromUpdates(base, updates, knownCreatables) delta.Totals = accumulateTotals(t, protocol.ConsensusCurrentVersion, []map[basics.Address]ledgercore.AccountData{totals}, rewardLevel) - ml.trackers.newBlock(blk, delta) + ml.addBlock(blockEntry{block: blk}, delta) accts = append(accts, newAccts) rewardsLevels = append(rewardsLevels, rewardLevel) @@ -684,7 +688,7 @@ func BenchmarkBalancesChanges(b *testing.B) { delta := ledgercore.MakeStateDelta(&blk.BlockHeader, 0, updates.Len(), 0) delta.Accts.MergeAccounts(updates) - ml.trackers.newBlock(blk, delta) + ml.addBlock(blockEntry{block: blk}, delta) accts = append(accts, newAccts) rewardsLevels = append(rewardsLevels, rewardLevel) } @@ -871,7 +875,7 @@ func testAcctUpdatesUpdatesCorrectness(t *testing.T, cfg config.Local) { for addr, ad := range updates { delta.Accts.Upsert(addr, ad) } - ml.trackers.newBlock(blk, delta) + ml.addBlock(blockEntry{block: blk}, delta) ml.trackers.committedUpTo(i) } lastRound := i - 1 @@ -1752,8 +1756,7 @@ func TestAcctUpdatesCachesInitialization(t *testing.T) { delta := ledgercore.MakeStateDelta(&blk.BlockHeader, 0, updates.Len(), 0) delta.Accts.MergeAccounts(updates) delta.Totals = accumulateTotals(t, protocol.ConsensusCurrentVersion, []map[basics.Address]ledgercore.AccountData{totals}, rewardLevel) - ml.addMockBlock(blockEntry{block: blk}, delta) - ml.trackers.newBlock(blk, delta) + ml.addBlock(blockEntry{block: blk}, delta) ml.trackers.committedUpTo(basics.Round(i)) ml.trackers.waitAccountsWriting() accts = append(accts, newAccts) @@ -1842,8 +1845,7 @@ func TestAcctUpdatesSplittingConsensusVersionCommits(t *testing.T) { delta := ledgercore.MakeStateDelta(&blk.BlockHeader, 0, updates.Len(), 0) delta.Accts.MergeAccounts(updates) delta.Totals = accumulateTotals(t, protocol.ConsensusCurrentVersion, []map[basics.Address]ledgercore.AccountData{totals}, rewardLevel) - ml.addMockBlock(blockEntry{block: blk}, delta) - ml.trackers.newBlock(blk, delta) + ml.addBlock(blockEntry{block: blk}, delta) accts = append(accts, newAccts) rewardsLevels = append(rewardsLevels, rewardLevel) } @@ -1880,8 +1882,7 @@ func TestAcctUpdatesSplittingConsensusVersionCommits(t *testing.T) { delta := ledgercore.MakeStateDelta(&blk.BlockHeader, 0, updates.Len(), 0) delta.Accts.MergeAccounts(updates) delta.Totals = accumulateTotals(t, protocol.ConsensusCurrentVersion, []map[basics.Address]ledgercore.AccountData{totals}, rewardLevel) - ml.addMockBlock(blockEntry{block: blk}, delta) - ml.trackers.newBlock(blk, delta) + ml.addBlock(blockEntry{block: blk}, delta) accts = append(accts, newAccts) rewardsLevels = append(rewardsLevels, rewardLevel) } @@ -1949,8 +1950,7 @@ func TestAcctUpdatesSplittingConsensusVersionCommitsBoundary(t *testing.T) { delta := ledgercore.MakeStateDelta(&blk.BlockHeader, 0, updates.Len(), 0) delta.Accts.MergeAccounts(updates) delta.Totals = accumulateTotals(t, protocol.ConsensusCurrentVersion, []map[basics.Address]ledgercore.AccountData{totals}, rewardLevel) - ml.addMockBlock(blockEntry{block: blk}, delta) - ml.trackers.newBlock(blk, delta) + ml.addBlock(blockEntry{block: blk}, delta) accts = append(accts, newAccts) rewardsLevels = append(rewardsLevels, rewardLevel) } @@ -1986,8 +1986,7 @@ func TestAcctUpdatesSplittingConsensusVersionCommitsBoundary(t *testing.T) { delta := ledgercore.MakeStateDelta(&blk.BlockHeader, 0, updates.Len(), 0) delta.Accts.MergeAccounts(updates) delta.Totals = accumulateTotals(t, protocol.ConsensusCurrentVersion, []map[basics.Address]ledgercore.AccountData{totals}, rewardLevel) - ml.addMockBlock(blockEntry{block: blk}, delta) - ml.trackers.newBlock(blk, delta) + ml.addBlock(blockEntry{block: blk}, delta) accts = append(accts, newAccts) rewardsLevels = append(rewardsLevels, rewardLevel) } @@ -2024,8 +2023,7 @@ func TestAcctUpdatesSplittingConsensusVersionCommitsBoundary(t *testing.T) { delta := ledgercore.MakeStateDelta(&blk.BlockHeader, 0, updates.Len(), 0) delta.Accts.MergeAccounts(updates) delta.Totals = accumulateTotals(t, protocol.ConsensusCurrentVersion, []map[basics.Address]ledgercore.AccountData{totals}, rewardLevel) - ml.addMockBlock(blockEntry{block: blk}, delta) - ml.trackers.newBlock(blk, delta) + ml.addBlock(blockEntry{block: blk}, delta) accts = append(accts, newAccts) rewardsLevels = append(rewardsLevels, rewardLevel) } @@ -2154,7 +2152,7 @@ func TestAcctUpdatesResources(t *testing.T) { delta.Creatables = creatablesFromUpdates(base, updates, knownCreatables) delta.Totals = newTotals - ml.trackers.newBlock(blk, delta) + ml.addBlock(blockEntry{block: blk}, delta) // commit changes synchroniously _, maxLookback := au.committedUpTo(i) @@ -2337,7 +2335,7 @@ func testAcctUpdatesLookupRetry(t *testing.T, assertFn func(au *accountUpdates, delta.Accts.MergeAccounts(updates) delta.Creatables = creatablesFromUpdates(base, updates, knownCreatables) delta.Totals = accumulateTotals(t, testProtocolVersion, []map[basics.Address]ledgercore.AccountData{totals}, rewardLevel) - ml.trackers.newBlock(blk, delta) + ml.addBlock(blockEntry{block: blk}, delta) accts = append(accts, newAccts) rewardsLevels = append(rewardsLevels, rewardLevel) diff --git a/ledger/catchpointtracker_test.go b/ledger/catchpointtracker_test.go index bcb9f81d2e..a9d4eff35b 100644 --- a/ledger/catchpointtracker_test.go +++ b/ledger/catchpointtracker_test.go @@ -558,8 +558,7 @@ func TestCatchpointReproducibleLabels(t *testing.T) { delta.Creatables = creatablesFromUpdates(base, updates, knownCreatables) delta.Totals = newTotals - ml.trackers.newBlock(blk, delta) - ml.addMockBlock(blockEntry{block: blk}, delta) + ml.addBlock(blockEntry{block: blk}, delta) accts = append(accts, newAccts) rewardsLevels = append(rewardsLevels, rewardLevel) roundDeltas[i] = delta @@ -607,7 +606,7 @@ func TestCatchpointReproducibleLabels(t *testing.T) { blk.CurrentProtocol = testProtocolVersion delta := roundDeltas[i] - ml2.trackers.newBlock(blk, delta) + ml2.addBlock(blockEntry{block: blk}, delta) if isDataFileRound(i) || isCatchpointRound(i) { ml2.trackers.committedUpTo(i) @@ -1084,8 +1083,7 @@ func TestCatchpointFirstStageInfoPruning(t *testing.T) { } delta := ledgercore.MakeStateDelta(&blk.BlockHeader, 0, 0, 0) - ml.trackers.newBlock(blk, delta) - ml.addMockBlock(blockEntry{block: blk}, delta) + ml.addBlock(blockEntry{block: blk}, delta) if isDataFileRound(i) || isCatchpointRound(i) { ml.trackers.committedUpTo(i) @@ -1172,8 +1170,7 @@ func TestCatchpointFirstStagePersistence(t *testing.T) { } delta := ledgercore.MakeStateDelta(&blk.BlockHeader, 0, 0, 0) - ml.trackers.newBlock(blk, delta) - ml.addMockBlock(blockEntry{block: blk}, delta) + ml.addBlock(blockEntry{block: blk}, delta) } ml.trackers.committedUpTo(firstStageRound) ml.trackers.waitAccountsWriting() @@ -1301,8 +1298,7 @@ func TestCatchpointSecondStagePersistence(t *testing.T) { } delta := ledgercore.MakeStateDelta(&blk.BlockHeader, 0, 0, 0) - ml.trackers.newBlock(blk, delta) - ml.addMockBlock(blockEntry{block: blk}, delta) + ml.addBlock(blockEntry{block: blk}, delta) if isDataFileRound(i) || isCatchpointRound(i) { ml.trackers.committedUpTo(i) @@ -1423,7 +1419,7 @@ func TestCatchpointSecondStageDeletesUnfinishedCatchpointRecord(t *testing.T) { ml.trackers.newBlock(blk, delta) ml.trackers.committedUpTo(i) - ml.addMockBlock(blockEntry{block: blk}, delta) + ml.addToBlockQueue(blockEntry{block: blk}, delta) } ml.trackers.waitAccountsWriting() @@ -1454,7 +1450,7 @@ func TestCatchpointSecondStageDeletesUnfinishedCatchpointRecord(t *testing.T) { ml2.trackers.newBlock(blk, delta) ml2.trackers.committedUpTo(secondStageRound) - ml2.addMockBlock(blockEntry{block: blk}, delta) + ml2.addToBlockQueue(blockEntry{block: blk}, delta) } ml2.trackers.waitAccountsWriting() @@ -1510,7 +1506,7 @@ func TestCatchpointSecondStageDeletesUnfinishedCatchpointRecordAfterRestart(t *t ml.trackers.newBlock(blk, delta) ml.trackers.committedUpTo(i) - ml.addMockBlock(blockEntry{block: blk}, delta) + ml.addToBlockQueue(blockEntry{block: blk}, delta) // Let catchpoint data generation finish so that nothing gets skipped. for ct.IsWritingCatchpointDataFile() { @@ -1760,7 +1756,7 @@ func TestCatchpointFastUpdates(t *testing.T) { delta := ledgercore.MakeStateDelta(&blk.BlockHeader, 0, updates.Len(), 0) delta.Accts.MergeAccounts(updates) - ml.trackers.newBlock(blk, delta) + ml.addBlock(blockEntry{block: blk}, delta) accts = append(accts, newAccts) rewardsLevels = append(rewardsLevels, rewardLevel) @@ -1861,7 +1857,7 @@ func TestCatchpointLargeAccountCountCatchpointGeneration(t *testing.T) { delta := ledgercore.MakeStateDelta(&blk.BlockHeader, 0, updates.Len(), 0) delta.Accts.MergeAccounts(updates) - ml.trackers.newBlock(blk, delta) + ml.addBlock(blockEntry{block: blk}, delta) accts = append(accts, newAccts) rewardsLevels = append(rewardsLevels, rewardLevel) diff --git a/ledger/voters_test.go b/ledger/voters_test.go index 363f8225f6..190997c92d 100644 --- a/ledger/voters_test.go +++ b/ledger/voters_test.go @@ -38,9 +38,7 @@ func addBlockToAccountsUpdate(t *testing.T, blk bookkeeping.Block, ml *mockLedge _, totals, err := ml.trackers.accts.LatestTotals() require.NoError(t, err) delta.Totals = totals - err = ml.addMockBlock(blockEntry{block: blk}, delta) - require.NoError(t, err) - ml.trackers.newBlock(blk, delta) + ml.addBlock(blockEntry{block: blk}, delta) } func addRandomBlock(t *testing.T, ml *mockLedgerForTracker) { From 89265103d2ac3c9478e1f9ec0de28620b9e5a0b1 Mon Sep 17 00:00:00 2001 From: Ben Guidarelli Date: Wed, 10 May 2023 11:54:29 -0400 Subject: [PATCH 089/125] AVM: Modify StackType to provide additional information (#5130) Co-authored-by: John Jannotti Co-authored-by: Bob Broderick <118225939+bbroder-algo@users.noreply.github.com> --- cmd/opdoc/opdoc.go | 186 +- data/transactions/logic/README.md | 100 +- data/transactions/logic/README_in.md | 11 + data/transactions/logic/TEAL_opcodes.md | 726 +++--- data/transactions/logic/assembler.go | 102 +- data/transactions/logic/assembler_test.go | 64 +- data/transactions/logic/box_test.go | 8 +- data/transactions/logic/doc.go | 183 +- data/transactions/logic/doc_test.go | 42 +- data/transactions/logic/eval.go | 358 ++- data/transactions/logic/evalAppTxn_test.go | 11 +- data/transactions/logic/evalStateful_test.go | 14 +- data/transactions/logic/eval_test.go | 30 +- data/transactions/logic/fields.go | 74 +- data/transactions/logic/fields_test.go | 10 +- data/transactions/logic/langspec.json | 2448 +++++++++++++++--- data/transactions/logic/opcodes.go | 118 +- 17 files changed, 3392 insertions(+), 1093 deletions(-) diff --git a/cmd/opdoc/opdoc.go b/cmd/opdoc/opdoc.go index a79496824a..acd110d36f 100644 --- a/cmd/opdoc/opdoc.go +++ b/cmd/opdoc/opdoc.go @@ -21,6 +21,7 @@ import ( "fmt" "io" "os" + "sort" "strings" "github.com/algorand/go-algorand/config" @@ -30,6 +31,38 @@ import ( var docVersion = 9 +// OpImmediateNote returns a short string about immediate data which follows the op byte +func opImmediateNoteSyntaxMarkdown(name string, oids []logic.OpImmediateDetails) string { + if len(oids) == 0 { + return "" + } + + argNames := make([]string, len(oids)) + argDocs := make([]string, len(oids)) + for idx, oid := range oids { + argNote := oid.Comment + if oid.Reference != "" { + argNote = fmt.Sprintf("[%s](#field-group-%s)", oid.Reference, strings.ToLower(oid.Reference)) + } + argNames[idx] = oid.Name + argDocs[idx] = fmt.Sprintf("%s: %s", oid.Name, argNote) + } + + return fmt.Sprintf("`%s %s` ∋ %s", name, strings.Join(argNames, " "), strings.Join(argDocs, ", ")) +} + +func opImmediateNoteEncoding(opcode byte, oids []logic.OpImmediateDetails) string { + if len(oids) == 0 { + return fmt.Sprintf("0x%02x", opcode) + } + + notes := make([]string, len(oids)) + for idx, oid := range oids { + notes[idx] = oid.Encoding + } + return fmt.Sprintf("0x%02x {%s}", opcode, strings.Join(notes, "}, {")) +} + func opGroupMarkdownTable(names []string, out io.Writer) { fmt.Fprint(out, `| Opcode | Description | | - | -- | @@ -50,6 +83,17 @@ func markdownTableEscape(x string) string { return strings.ReplaceAll(x, "|", "\\|") } +func namedStackTypesMarkdown(out io.Writer, stackTypes []namedType) { + fmt.Fprintf(out, "#### Definitions\n\n") + fmt.Fprintf(out, "| Name | Bound | AVM Type |\n") + fmt.Fprintf(out, "| ---- | ---- | -------- |\n") + + for _, st := range stackTypes { + fmt.Fprintf(out, "| %s | %s | %s |\n", st.Name, st.boundString(), st.AVMType) + } + out.Write([]byte("\n")) +} + func integerConstantsTableMarkdown(out io.Writer) { fmt.Fprintf(out, "#### OnComplete\n\n") fmt.Fprintf(out, "%s\n\n", logic.OnCompletionPreamble) @@ -169,14 +213,22 @@ func stackMarkdown(op *logic.OpSpec) string { } func opToMarkdown(out io.Writer, op *logic.OpSpec, groupDocWritten map[string]bool) (err error) { - ws := "" - opextra := logic.OpImmediateNote(op.Name) - if opextra != "" { - ws = " " + + deets := logic.OpImmediateDetailsFromSpec(*op) + + // Only need syntax line if there are immediates + // so it carries its own newline + syntax := "" + if opSyntax := opImmediateNoteSyntaxMarkdown(op.Name, deets); opSyntax != "" { + syntax = fmt.Sprintf("- Syntax: %s\n", opSyntax) } + + encoding := fmt.Sprintf("- Bytecode: %s", opImmediateNoteEncoding(op.Opcode, deets)) + stackEffects := stackMarkdown(op) - fmt.Fprintf(out, "\n## %s%s\n\n- Opcode: 0x%02x%s%s\n%s", - op.Name, immediateMarkdown(op), op.Opcode, ws, opextra, stackEffects) + + fmt.Fprintf(out, "\n## %s\n\n%s%s\n%s", op.Name, syntax, encoding, stackEffects) + fmt.Fprintf(out, "- %s\n", logic.OpDoc(op.Name)) // if cost changed with versions print all of them costs := logic.OpAllCosts(op.Name) @@ -209,7 +261,7 @@ func opToMarkdown(out io.Writer, op *logic.OpSpec, groupDocWritten map[string]bo for i := range op.OpDetails.Immediates { group := op.OpDetails.Immediates[i].Group if group != nil && group.Doc != "" && !groupDocWritten[group.Name] { - fmt.Fprintf(out, "\n`%s` %s:\n\n", group.Name, group.Doc) + fmt.Fprintf(out, "\n### %s\n\n%s\n\n", group.Name, group.Doc) fieldGroupMarkdown(out, group) groupDocWritten[group.Name] = true } @@ -238,56 +290,86 @@ func opsToMarkdown(out io.Writer) (err error) { type OpRecord struct { Opcode byte Name string - Args string `json:",omitempty"` - Returns string `json:",omitempty"` + Args []string `json:",omitempty"` + Returns []string `json:",omitempty"` Size int ArgEnum []string `json:",omitempty"` - ArgEnumTypes string `json:",omitempty"` + ArgEnumTypes []string `json:",omitempty"` Doc string - DocExtra string `json:",omitempty"` - ImmediateNote string `json:",omitempty"` + DocExtra string `json:",omitempty"` + ImmediateNote []logic.OpImmediateDetails `json:",omitempty"` IntroducedVersion uint64 Groups []string } +type namedType struct { + Name string + Abbreviation string + Bound []uint64 + AVMType string +} + +func (nt namedType) boundString() string { + if nt.Bound[0] == 0 && nt.Bound[1] == 0 { + return "" + } + + val := "x" + // if its bytes, the length is bounded + if nt.AVMType == "[]byte" { + val = "len(x)" + } + + // If they're equal, the val should match exactly + if nt.Bound[0] > 0 && nt.Bound[0] == nt.Bound[1] { + return fmt.Sprintf("%s == %d", val, nt.Bound[0]) + } + + // otherwise, provide min/max bounds as lte expression + minBound, maxBound := "", "" + if nt.Bound[0] > 0 { + minBound = fmt.Sprintf("%d <= ", nt.Bound[0]) + } + if nt.Bound[1] > 0 { + maxBound = fmt.Sprintf(" <= %d", nt.Bound[1]) + } + + return fmt.Sprintf("%s%s%s", minBound, val, maxBound) + +} + // LanguageSpec records the ops of the language at some version type LanguageSpec struct { EvalMaxVersion int LogicSigVersion uint64 + NamedTypes []namedType Ops []OpRecord } -func typeString(types []logic.StackType) string { - out := make([]byte, len(types)) - for i, t := range types { - switch t { - case logic.StackUint64: - out[i] = 'U' - case logic.StackBytes: - out[i] = 'B' - case logic.StackAny: - out[i] = '.' - case logic.StackNone: - out[i] = '_' - default: - panic("unexpected type in opdoc typeString") +func typeStrings(types logic.StackTypes) []string { + out := make([]string, len(types)) + allNones := true + for idx, t := range types { + out[idx] = t.String() + if out[idx] != "none" { + allNones = false } } - // Cant return None and !None from same op - if strings.Contains(string(out), "_") { - if strings.ContainsAny(string(out), "UB.") { - panic("unexpected StackNone in opdoc typeString") - } - return "" + // If all the types are none, we just return + // an empty array, otherwise leave the nones + // in so we don't break the indices by omitting + // a valid none in a fields array + if allNones { + return nil } - return string(out) + return out } -func fieldsAndTypes(group logic.FieldGroup) ([]string, string) { +func fieldsAndTypes(group logic.FieldGroup) ([]string, []string) { // reminder: group.Names can be "sparse" See: logic.TxnaFields fields := make([]string, 0, len(group.Names)) types := make([]logic.StackType, 0, len(group.Names)) @@ -297,10 +379,10 @@ func fieldsAndTypes(group logic.FieldGroup) ([]string, string) { types = append(types, spec.Type()) } } - return fields, typeString(types) + return fields, typeStrings(types) } -func argEnums(name string) ([]string, string) { +func argEnums(name string) ([]string, []string) { // reminder: this needs to be manually updated every time // a new opcode is added with an associated FieldGroup // it'd be nice to have this auto-update @@ -334,29 +416,31 @@ func argEnums(name string) ([]string, string) { case "ecdsa_pk_recover", "ecdsa_verify", "ecdsa_pk_decompress": return fieldsAndTypes(logic.EcdsaCurves) default: - return nil, "" + return nil, nil } } -func buildLanguageSpec(opGroups map[string][]string) *LanguageSpec { +func buildLanguageSpec(opGroups map[string][]string, namedTypes []namedType) *LanguageSpec { opSpecs := logic.OpcodesByVersion(uint64(docVersion)) records := make([]OpRecord, len(opSpecs)) for i, spec := range opSpecs { records[i].Opcode = spec.Opcode records[i].Name = spec.Name - records[i].Args = typeString(spec.Arg.Types) - records[i].Returns = typeString(spec.Return.Types) + records[i].Args = typeStrings(spec.Arg.Types) + records[i].Returns = typeStrings(spec.Return.Types) records[i].Size = spec.OpDetails.Size records[i].ArgEnum, records[i].ArgEnumTypes = argEnums(spec.Name) records[i].Doc = strings.ReplaceAll(logic.OpDoc(spec.Name), "
", "\n") records[i].DocExtra = logic.OpDocExtra(spec.Name) - records[i].ImmediateNote = logic.OpImmediateNote(spec.Name) + records[i].ImmediateNote = logic.OpImmediateDetailsFromSpec(spec) records[i].Groups = opGroups[spec.Name] records[i].IntroducedVersion = spec.Version } + return &LanguageSpec{ EvalMaxVersion: docVersion, LogicSigVersion: config.Consensus[protocol.ConsensusCurrentVersion].LogicSigVersion, + NamedTypes: namedTypes, Ops: records, } } @@ -389,6 +473,21 @@ func main() { integerConstantsTableMarkdown(constants) constants.Close() + named := make([]namedType, 0, len(logic.AllStackTypes)) + for abbr, t := range logic.AllStackTypes { + named = append(named, namedType{ + Name: t.String(), + Bound: []uint64{t.Bound[0], t.Bound[1]}, + Abbreviation: string(abbr), + AVMType: t.AVMType.String(), + }) + } + sort.Slice(named, func(i, j int) bool { return strings.Compare(named[i].Name, named[j].Name) > 0 }) + + namedStackTypes := create("named_stack_types.md") + namedStackTypesMarkdown(namedStackTypes, named) + namedStackTypes.Close() + written := make(map[string]bool) opSpecs := logic.OpcodesByVersion(uint64(docVersion)) for _, spec := range opSpecs { @@ -405,7 +504,10 @@ func main() { langspecjs := create("langspec.json") enc := json.NewEncoder(langspecjs) enc.SetIndent("", " ") - enc.Encode(buildLanguageSpec(opGroups)) + err := enc.Encode(buildLanguageSpec(opGroups, named)) + if err != nil { + panic(err.Error()) + } langspecjs.Close() tealtm := create("teal.tmLanguage.json") diff --git a/data/transactions/logic/README.md b/data/transactions/logic/README.md index 1c8e0bcb04..7c51a9c233 100644 --- a/data/transactions/logic/README.md +++ b/data/transactions/logic/README.md @@ -40,6 +40,32 @@ has fewer than two elements, the operation fails. Some operations, like `frame_dig` and `proto` could fail because of an attempt to access above the current stack. +## Stack Types + +While every element of the stack is restricted to the types `uint64` and `bytes`, +the values of these types may be known to be bounded. The more common bounded types are +named to provide more semantic information in the documentation. They're also used during +assembly time to do type checking and to provide more informative error messages. + + +#### Definitions + +| Name | Bound | AVM Type | +| ---- | ---- | -------- | +| uint64 | x <= 18446744073709551615 | uint64 | +| stateKey | len(x) <= 64 | []byte | +| none | | none | +| method | len(x) == 4 | []byte | +| boxName | 1 <= len(x) <= 64 | []byte | +| bool | x <= 1 | uint64 | +| bigint | len(x) <= 64 | []byte | +| any | | any | +| address | len(x) == 32 | []byte | +| []byte | len(x) <= 4096 | []byte | +| [32]byte | len(x) == 32 | []byte | + + + ## Scratch Space In addition to the stack there are 256 positions of scratch @@ -491,18 +517,18 @@ Some of these have immediate data in the byte or bytes after the opcode. ##### Scalar Fields | Index | Name | Type | In | Notes | | - | ------ | -- | - | --------- | -| 0 | Sender | []byte | | 32 byte address | +| 0 | Sender | address | | 32 byte address | | 1 | Fee | uint64 | | microalgos | | 2 | FirstValid | uint64 | | round number | | 3 | FirstValidTime | uint64 | v7 | UNIX timestamp of block before txn.FirstValid. Fails if negative | | 4 | LastValid | uint64 | | round number | | 5 | Note | []byte | | Any data up to 1024 bytes | -| 6 | Lease | []byte | | 32 byte lease value | -| 7 | Receiver | []byte | | 32 byte address | +| 6 | Lease | [32]byte | | 32 byte lease value | +| 7 | Receiver | address | | 32 byte address | | 8 | Amount | uint64 | | microalgos | -| 9 | CloseRemainderTo | []byte | | 32 byte address | -| 10 | VotePK | []byte | | 32 byte address | -| 11 | SelectionPK | []byte | | 32 byte address | +| 9 | CloseRemainderTo | address | | 32 byte address | +| 10 | VotePK | [32]byte | | 32 byte address | +| 11 | SelectionPK | [32]byte | | 32 byte address | | 12 | VoteFirst | uint64 | | The first round that the participation key is valid. | | 13 | VoteLast | uint64 | | The last round that the participation key is valid. | | 14 | VoteKeyDilution | uint64 | | Dilution for the 2-level participation key | @@ -510,33 +536,33 @@ Some of these have immediate data in the byte or bytes after the opcode. | 16 | TypeEnum | uint64 | | Transaction type as integer | | 17 | XferAsset | uint64 | | Asset ID | | 18 | AssetAmount | uint64 | | value in Asset's units | -| 19 | AssetSender | []byte | | 32 byte address. Source of assets if Sender is the Asset's Clawback address. | -| 20 | AssetReceiver | []byte | | 32 byte address | -| 21 | AssetCloseTo | []byte | | 32 byte address | +| 19 | AssetSender | address | | 32 byte address. Source of assets if Sender is the Asset's Clawback address. | +| 20 | AssetReceiver | address | | 32 byte address | +| 21 | AssetCloseTo | address | | 32 byte address | | 22 | GroupIndex | uint64 | | Position of this transaction within an atomic transaction group. A stand-alone transaction is implicitly element 0 in a group of 1 | -| 23 | TxID | []byte | | The computed ID for this transaction. 32 bytes. | +| 23 | TxID | [32]byte | | The computed ID for this transaction. 32 bytes. | | 24 | ApplicationID | uint64 | v2 | ApplicationID from ApplicationCall transaction | | 25 | OnCompletion | uint64 | v2 | ApplicationCall transaction on completion action | | 27 | NumAppArgs | uint64 | v2 | Number of ApplicationArgs | | 29 | NumAccounts | uint64 | v2 | Number of Accounts | | 30 | ApprovalProgram | []byte | v2 | Approval program | | 31 | ClearStateProgram | []byte | v2 | Clear state program | -| 32 | RekeyTo | []byte | v2 | 32 byte Sender's new AuthAddr | +| 32 | RekeyTo | address | v2 | 32 byte Sender's new AuthAddr | | 33 | ConfigAsset | uint64 | v2 | Asset ID in asset config transaction | | 34 | ConfigAssetTotal | uint64 | v2 | Total number of units of this asset created | | 35 | ConfigAssetDecimals | uint64 | v2 | Number of digits to display after the decimal place when displaying the asset | -| 36 | ConfigAssetDefaultFrozen | uint64 | v2 | Whether the asset's slots are frozen by default or not, 0 or 1 | +| 36 | ConfigAssetDefaultFrozen | bool | v2 | Whether the asset's slots are frozen by default or not, 0 or 1 | | 37 | ConfigAssetUnitName | []byte | v2 | Unit name of the asset | | 38 | ConfigAssetName | []byte | v2 | The asset name | | 39 | ConfigAssetURL | []byte | v2 | URL | -| 40 | ConfigAssetMetadataHash | []byte | v2 | 32 byte commitment to unspecified asset metadata | -| 41 | ConfigAssetManager | []byte | v2 | 32 byte address | -| 42 | ConfigAssetReserve | []byte | v2 | 32 byte address | -| 43 | ConfigAssetFreeze | []byte | v2 | 32 byte address | -| 44 | ConfigAssetClawback | []byte | v2 | 32 byte address | +| 40 | ConfigAssetMetadataHash | [32]byte | v2 | 32 byte commitment to unspecified asset metadata | +| 41 | ConfigAssetManager | address | v2 | 32 byte address | +| 42 | ConfigAssetReserve | address | v2 | 32 byte address | +| 43 | ConfigAssetFreeze | address | v2 | 32 byte address | +| 44 | ConfigAssetClawback | address | v2 | 32 byte address | | 45 | FreezeAsset | uint64 | v2 | Asset ID being frozen or un-frozen | -| 46 | FreezeAssetAccount | []byte | v2 | 32 byte address of the account whose asset slot is being frozen or un-frozen | -| 47 | FreezeAssetFrozen | uint64 | v2 | The new frozen value, 0 or 1 | +| 46 | FreezeAssetAccount | address | v2 | 32 byte address of the account whose asset slot is being frozen or un-frozen | +| 47 | FreezeAssetFrozen | bool | v2 | The new frozen value, 0 or 1 | | 49 | NumAssets | uint64 | v3 | Number of Assets | | 51 | NumApplications | uint64 | v3 | Number of Applications | | 52 | GlobalNumUint | uint64 | v3 | Number of global state integers in ApplicationCall | @@ -544,7 +570,7 @@ Some of these have immediate data in the byte or bytes after the opcode. | 54 | LocalNumUint | uint64 | v3 | Number of local state integers in ApplicationCall | | 55 | LocalNumByteSlice | uint64 | v3 | Number of local state byteslices in ApplicationCall | | 56 | ExtraProgramPages | uint64 | v4 | Number of additional pages for each of the application's approval and clear state programs. An ExtraProgramPages of 1 means 2048 more total bytes, or 1024 for each program. | -| 57 | Nonparticipation | uint64 | v5 | Marks an account nonparticipating for rewards | +| 57 | Nonparticipation | bool | v5 | Marks an account nonparticipating for rewards | | 59 | NumLogs | uint64 | v5 | Number of Logs (only with `itxn` in v5). Application mode only | | 60 | CreatedAssetID | uint64 | v5 | Asset ID allocated by the creation of an ASA (only with `itxn` in v5). Application mode only | | 61 | CreatedApplicationID | uint64 | v5 | ApplicationID allocated by the creation of an application (only with `itxn` in v5). Application mode only | @@ -557,7 +583,7 @@ Some of these have immediate data in the byte or bytes after the opcode. | Index | Name | Type | In | Notes | | - | ------ | -- | - | --------- | | 26 | ApplicationArgs | []byte | v2 | Arguments passed to the application in the ApplicationCall transaction | -| 28 | Accounts | []byte | v2 | Accounts listed in the ApplicationCall transaction | +| 28 | Accounts | address | v2 | Accounts listed in the ApplicationCall transaction | | 48 | Assets | uint64 | v3 | Foreign Assets listed in the ApplicationCall transaction | | 50 | Applications | uint64 | v3 | Foreign Apps listed in the ApplicationCall transaction | | 58 | Logs | []byte | v5 | Log messages emitted by an application call (only with `itxn` in v5). Application mode only | @@ -576,18 +602,18 @@ Global fields are fields that are common to all the transactions in the group. I | 0 | MinTxnFee | uint64 | | microalgos | | 1 | MinBalance | uint64 | | microalgos | | 2 | MaxTxnLife | uint64 | | rounds | -| 3 | ZeroAddress | []byte | | 32 byte address of all zero bytes | +| 3 | ZeroAddress | address | | 32 byte address of all zero bytes | | 4 | GroupSize | uint64 | | Number of transactions in this atomic transaction group. At least 1 | | 5 | LogicSigVersion | uint64 | v2 | Maximum supported version | | 6 | Round | uint64 | v2 | Current round number. Application mode only. | | 7 | LatestTimestamp | uint64 | v2 | Last confirmed block UNIX timestamp. Fails if negative. Application mode only. | | 8 | CurrentApplicationID | uint64 | v2 | ID of current application executing. Application mode only. | -| 9 | CreatorAddress | []byte | v3 | Address of the creator of the current application. Application mode only. | -| 10 | CurrentApplicationAddress | []byte | v5 | Address that the current application controls. Application mode only. | -| 11 | GroupID | []byte | v5 | ID of the transaction group. 32 zero bytes if the transaction is not part of a group. | +| 9 | CreatorAddress | address | v3 | Address of the creator of the current application. Application mode only. | +| 10 | CurrentApplicationAddress | address | v5 | Address that the current application controls. Application mode only. | +| 11 | GroupID | [32]byte | v5 | ID of the transaction group. 32 zero bytes if the transaction is not part of a group. | | 12 | OpcodeBudget | uint64 | v6 | The remaining cost that can be spent by opcodes in this program. | | 13 | CallerApplicationID | uint64 | v6 | The application ID of the application that called this application. 0 if this application is at the top-level. Application mode only. | -| 14 | CallerApplicationAddress | []byte | v6 | The application address of the application that called this application. ZeroAddress if this application is at the top-level. Application mode only. | +| 14 | CallerApplicationAddress | address | v6 | The application address of the application that called this application. ZeroAddress if this application is at the top-level. Application mode only. | **Asset Fields** @@ -597,23 +623,23 @@ Asset fields include `AssetHolding` and `AssetParam` fields that are used in the | Index | Name | Type | Notes | | - | ------ | -- | --------- | | 0 | AssetBalance | uint64 | Amount of the asset unit held by this account | -| 1 | AssetFrozen | uint64 | Is the asset frozen or not | +| 1 | AssetFrozen | bool | Is the asset frozen or not | | Index | Name | Type | In | Notes | | - | ------ | -- | - | --------- | | 0 | AssetTotal | uint64 | | Total number of units of this asset | | 1 | AssetDecimals | uint64 | | See AssetParams.Decimals | -| 2 | AssetDefaultFrozen | uint64 | | Frozen by default or not | +| 2 | AssetDefaultFrozen | bool | | Frozen by default or not | | 3 | AssetUnitName | []byte | | Asset unit name | | 4 | AssetName | []byte | | Asset name | | 5 | AssetURL | []byte | | URL with additional info about the asset | -| 6 | AssetMetadataHash | []byte | | Arbitrary commitment | -| 7 | AssetManager | []byte | | Manager address | -| 8 | AssetReserve | []byte | | Reserve address | -| 9 | AssetFreeze | []byte | | Freeze address | -| 10 | AssetClawback | []byte | | Clawback address | -| 11 | AssetCreator | []byte | v5 | Creator address | +| 6 | AssetMetadataHash | [32]byte | | Arbitrary commitment | +| 7 | AssetManager | address | | Manager address | +| 8 | AssetReserve | address | | Reserve address | +| 9 | AssetFreeze | address | | Freeze address | +| 10 | AssetClawback | address | | Clawback address | +| 11 | AssetCreator | address | v5 | Creator address | **App Fields** @@ -629,8 +655,8 @@ App fields used in the `app_params_get` opcode. | 4 | AppLocalNumUint | uint64 | Number of uint64 values allowed in Local State | | 5 | AppLocalNumByteSlice | uint64 | Number of byte array values allowed in Local State | | 6 | AppExtraProgramPages | uint64 | Number of Extra Program Pages of code space | -| 7 | AppCreator | []byte | Creator address | -| 8 | AppAddress | []byte | Address for which this application has authority | +| 7 | AppCreator | address | Creator address | +| 8 | AppAddress | address | Address for which this application has authority | **Account Fields** @@ -641,7 +667,7 @@ Account fields used in the `acct_params_get` opcode. | - | ------ | -- | - | --------- | | 0 | AcctBalance | uint64 | | Account balance in microalgos | | 1 | AcctMinBalance | uint64 | | Minimum required balance for account, in microalgos | -| 2 | AcctAuthAddr | []byte | | Address the account is rekeyed to. | +| 2 | AcctAuthAddr | address | | Address the account is rekeyed to. | | 3 | AcctTotalNumUint | uint64 | v8 | The total number of uint64 values allocated by this account in Global and Local States. | | 4 | AcctTotalNumByteSlice | uint64 | v8 | The total number of byte array values allocated by this account in Global and Local States. | | 5 | AcctTotalExtraAppPages | uint64 | v8 | The number of extra app code pages used by this account. | diff --git a/data/transactions/logic/README_in.md b/data/transactions/logic/README_in.md index de8f437f7c..e0cc32c33a 100644 --- a/data/transactions/logic/README_in.md +++ b/data/transactions/logic/README_in.md @@ -40,6 +40,17 @@ has fewer than two elements, the operation fails. Some operations, like `frame_dig` and `proto` could fail because of an attempt to access above the current stack. +## Stack Types + +While every element of the stack is restricted to the types `uint64` and `bytes`, +the values of these types may be known to be bounded. The more common bounded types are +named to provide more semantic information in the documentation. They're also used during +assembly time to do type checking and to provide more informative error messages. + + +@@ named_stack_types.md @@ + + ## Scratch Space In addition to the stack there are 256 positions of scratch diff --git a/data/transactions/logic/TEAL_opcodes.md b/data/transactions/logic/TEAL_opcodes.md index 7752b1c182..5a30ce35c7 100644 --- a/data/transactions/logic/TEAL_opcodes.md +++ b/data/transactions/logic/TEAL_opcodes.md @@ -5,14 +5,14 @@ Ops have a 'cost' of 1 unless otherwise specified. ## err -- Opcode: 0x00 +- Bytecode: 0x00 - Stack: ... → _exits_ - Fail immediately. ## sha256 -- Opcode: 0x01 -- Stack: ..., A: []byte → ..., []byte +- Bytecode: 0x01 +- Stack: ..., A: []byte → ..., [32]byte - SHA256 hash of value A, yields [32]byte - **Cost**: - 7 (v1) @@ -20,8 +20,8 @@ Ops have a 'cost' of 1 unless otherwise specified. ## keccak256 -- Opcode: 0x02 -- Stack: ..., A: []byte → ..., []byte +- Bytecode: 0x02 +- Stack: ..., A: []byte → ..., [32]byte - Keccak256 hash of value A, yields [32]byte - **Cost**: - 26 (v1) @@ -29,8 +29,8 @@ Ops have a 'cost' of 1 unless otherwise specified. ## sha512_256 -- Opcode: 0x03 -- Stack: ..., A: []byte → ..., []byte +- Bytecode: 0x03 +- Stack: ..., A: []byte → ..., [32]byte - SHA512_256 hash of value A, yields [32]byte - **Cost**: - 9 (v1) @@ -38,22 +38,25 @@ Ops have a 'cost' of 1 unless otherwise specified. ## ed25519verify -- Opcode: 0x04 -- Stack: ..., A: []byte, B: []byte, C: []byte → ..., uint64 +- Bytecode: 0x04 +- Stack: ..., A: []byte, B: []byte, C: []byte → ..., bool - for (data A, signature B, pubkey C) verify the signature of ("ProgData" || program_hash || data) against the pubkey => {0 or 1} - **Cost**: 1900 The 32 byte public key is the last element on the stack, preceded by the 64 byte signature at the second-to-last element on the stack, preceded by the data which was signed at the third-to-last element on the stack. -## ecdsa_verify v +## ecdsa_verify -- Opcode: 0x05 {uint8 curve index} -- Stack: ..., A: []byte, B: []byte, C: []byte, D: []byte, E: []byte → ..., uint64 +- Syntax: `ecdsa_verify V` ∋ V: [ECDSA](#field-group-ecdsa) +- Bytecode: 0x05 {uint8} +- Stack: ..., A: []byte, B: []byte, C: []byte, D: []byte, E: []byte → ..., bool - for (data A, signature B, C and pubkey D, E) verify the signature of the data against the pubkey => {0 or 1} - **Cost**: Secp256k1=1700 Secp256r1=2500 - Availability: v5 -`ECDSA` Curves: +### ECDSA + +Curves | Index | Name | In | Notes | | - | ------ | - | --------- | @@ -63,9 +66,10 @@ The 32 byte public key is the last element on the stack, preceded by the 64 byte The 32 byte Y-component of a public key is the last element on the stack, preceded by X-component of a pubkey, preceded by S and R components of a signature, preceded by the data that is fifth element on the stack. All values are big-endian encoded. The signed data must be 32 bytes long, and signatures in lower-S form are only accepted. -## ecdsa_pk_decompress v +## ecdsa_pk_decompress -- Opcode: 0x06 {uint8 curve index} +- Syntax: `ecdsa_pk_decompress V` ∋ V: [ECDSA](#field-group-ecdsa) +- Bytecode: 0x06 {uint8} - Stack: ..., A: []byte → ..., X: []byte, Y: []byte - decompress pubkey A into components X, Y - **Cost**: Secp256k1=650 Secp256r1=2400 @@ -73,9 +77,10 @@ The 32 byte Y-component of a public key is the last element on the stack, preced The 33 byte public key in a compressed form to be decompressed into X and Y (top) components. All values are big-endian encoded. -## ecdsa_pk_recover v +## ecdsa_pk_recover -- Opcode: 0x07 {uint8 curve index} +- Syntax: `ecdsa_pk_recover V` ∋ V: [ECDSA](#field-group-ecdsa) +- Bytecode: 0x07 {uint8} - Stack: ..., A: []byte, B: uint64, C: []byte, D: []byte → ..., X: []byte, Y: []byte - for (data A, recovery id B, signature C, D) recover a public key - **Cost**: 2000 @@ -85,7 +90,7 @@ S (top) and R elements of a signature, recovery id and data (bottom) are expecte ## + -- Opcode: 0x08 +- Bytecode: 0x08 - Stack: ..., A: uint64, B: uint64 → ..., uint64 - A plus B. Fail on overflow. @@ -93,13 +98,13 @@ Overflow is an error condition which halts execution and fails the transaction. ## - -- Opcode: 0x09 +- Bytecode: 0x09 - Stack: ..., A: uint64, B: uint64 → ..., uint64 - A minus B. Fail if B > A. ## / -- Opcode: 0x0a +- Bytecode: 0x0a - Stack: ..., A: uint64, B: uint64 → ..., uint64 - A divided by B (truncated division). Fail if B == 0. @@ -107,7 +112,7 @@ Overflow is an error condition which halts execution and fails the transaction. ## * -- Opcode: 0x0b +- Bytecode: 0x0b - Stack: ..., A: uint64, B: uint64 → ..., uint64 - A times B. Fail on overflow. @@ -115,73 +120,73 @@ Overflow is an error condition which halts execution and fails the transaction. ## < -- Opcode: 0x0c -- Stack: ..., A: uint64, B: uint64 → ..., uint64 +- Bytecode: 0x0c +- Stack: ..., A: uint64, B: uint64 → ..., bool - A less than B => {0 or 1} ## > -- Opcode: 0x0d -- Stack: ..., A: uint64, B: uint64 → ..., uint64 +- Bytecode: 0x0d +- Stack: ..., A: uint64, B: uint64 → ..., bool - A greater than B => {0 or 1} ## <= -- Opcode: 0x0e -- Stack: ..., A: uint64, B: uint64 → ..., uint64 +- Bytecode: 0x0e +- Stack: ..., A: uint64, B: uint64 → ..., bool - A less than or equal to B => {0 or 1} ## >= -- Opcode: 0x0f -- Stack: ..., A: uint64, B: uint64 → ..., uint64 +- Bytecode: 0x0f +- Stack: ..., A: uint64, B: uint64 → ..., bool - A greater than or equal to B => {0 or 1} ## && -- Opcode: 0x10 -- Stack: ..., A: uint64, B: uint64 → ..., uint64 +- Bytecode: 0x10 +- Stack: ..., A: uint64, B: uint64 → ..., bool - A is not zero and B is not zero => {0 or 1} ## || -- Opcode: 0x11 -- Stack: ..., A: uint64, B: uint64 → ..., uint64 +- Bytecode: 0x11 +- Stack: ..., A: uint64, B: uint64 → ..., bool - A is not zero or B is not zero => {0 or 1} ## == -- Opcode: 0x12 -- Stack: ..., A, B → ..., uint64 +- Bytecode: 0x12 +- Stack: ..., A, B → ..., bool - A is equal to B => {0 or 1} ## != -- Opcode: 0x13 -- Stack: ..., A, B → ..., uint64 +- Bytecode: 0x13 +- Stack: ..., A, B → ..., bool - A is not equal to B => {0 or 1} ## ! -- Opcode: 0x14 +- Bytecode: 0x14 - Stack: ..., A: uint64 → ..., uint64 - A == 0 yields 1; else 0 ## len -- Opcode: 0x15 +- Bytecode: 0x15 - Stack: ..., A: []byte → ..., uint64 - yields length of byte value A ## itob -- Opcode: 0x16 +- Bytecode: 0x16 - Stack: ..., A: uint64 → ..., []byte - converts uint64 A to big-endian byte array, always of length 8 ## btoi -- Opcode: 0x17 +- Bytecode: 0x17 - Stack: ..., A: []byte → ..., uint64 - converts big-endian byte array A to uint64. Fails if len(A) > 8. Padded by leading 0s if len(A) < 8. @@ -189,50 +194,50 @@ Overflow is an error condition which halts execution and fails the transaction. ## % -- Opcode: 0x18 +- Bytecode: 0x18 - Stack: ..., A: uint64, B: uint64 → ..., uint64 - A modulo B. Fail if B == 0. ## | -- Opcode: 0x19 +- Bytecode: 0x19 - Stack: ..., A: uint64, B: uint64 → ..., uint64 - A bitwise-or B ## & -- Opcode: 0x1a +- Bytecode: 0x1a - Stack: ..., A: uint64, B: uint64 → ..., uint64 - A bitwise-and B ## ^ -- Opcode: 0x1b +- Bytecode: 0x1b - Stack: ..., A: uint64, B: uint64 → ..., uint64 - A bitwise-xor B ## ~ -- Opcode: 0x1c +- Bytecode: 0x1c - Stack: ..., A: uint64 → ..., uint64 - bitwise invert value A ## mulw -- Opcode: 0x1d +- Bytecode: 0x1d - Stack: ..., A: uint64, B: uint64 → ..., X: uint64, Y: uint64 - A times B as a 128-bit result in two uint64s. X is the high 64 bits, Y is the low ## addw -- Opcode: 0x1e +- Bytecode: 0x1e - Stack: ..., A: uint64, B: uint64 → ..., X: uint64, Y: uint64 - A plus B as a 128-bit result. X is the carry-bit, Y is the low-order 64 bits. - Availability: v2 ## divmodw -- Opcode: 0x1f +- Bytecode: 0x1f - Stack: ..., A: uint64, B: uint64, C: uint64, D: uint64 → ..., W: uint64, X: uint64, Y: uint64, Z: uint64 - W,X = (A,B / C,D); Y,Z = (A,B modulo C,D) - **Cost**: 20 @@ -240,139 +245,147 @@ Overflow is an error condition which halts execution and fails the transaction. The notation J,K indicates that two uint64 values J and K are interpreted as a uint128 value, with J as the high uint64 and K the low. -## intcblock uint ... +## intcblock -- Opcode: 0x20 {varuint count} [{varuint value}, ...] +- Syntax: `intcblock UINT ...` ∋ UINT ...: a block of int constant values +- Bytecode: 0x20 {varuint count, [varuint ...]} - Stack: ... → ... - prepare block of uint64 constants for use by intc `intcblock` loads following program bytes into an array of integer constants in the evaluator. These integer constants can be referred to by `intc` and `intc_*` which will push the value onto the stack. Subsequent calls to `intcblock` reset and replace the integer constants available to the script. -## intc i +## intc -- Opcode: 0x21 {uint8 int constant index} +- Syntax: `intc I` ∋ I: an index in the intcblock +- Bytecode: 0x21 {uint8} - Stack: ... → ..., uint64 - Ith constant from intcblock ## intc_0 -- Opcode: 0x22 +- Bytecode: 0x22 - Stack: ... → ..., uint64 - constant 0 from intcblock ## intc_1 -- Opcode: 0x23 +- Bytecode: 0x23 - Stack: ... → ..., uint64 - constant 1 from intcblock ## intc_2 -- Opcode: 0x24 +- Bytecode: 0x24 - Stack: ... → ..., uint64 - constant 2 from intcblock ## intc_3 -- Opcode: 0x25 +- Bytecode: 0x25 - Stack: ... → ..., uint64 - constant 3 from intcblock -## bytecblock bytes ... +## bytecblock -- Opcode: 0x26 {varuint count} [({varuint length} bytes), ...] +- Syntax: `bytecblock BYTES ...` ∋ BYTES ...: a block of byte constant values +- Bytecode: 0x26 {varuint count, [varuint length, bytes ...]} - Stack: ... → ... - prepare block of byte-array constants for use by bytec `bytecblock` loads the following program bytes into an array of byte-array constants in the evaluator. These constants can be referred to by `bytec` and `bytec_*` which will push the value onto the stack. Subsequent calls to `bytecblock` reset and replace the bytes constants available to the script. -## bytec i +## bytec -- Opcode: 0x27 {uint8 byte constant index} +- Syntax: `bytec I` ∋ I: an index in the bytecblock +- Bytecode: 0x27 {uint8} - Stack: ... → ..., []byte - Ith constant from bytecblock ## bytec_0 -- Opcode: 0x28 +- Bytecode: 0x28 - Stack: ... → ..., []byte - constant 0 from bytecblock ## bytec_1 -- Opcode: 0x29 +- Bytecode: 0x29 - Stack: ... → ..., []byte - constant 1 from bytecblock ## bytec_2 -- Opcode: 0x2a +- Bytecode: 0x2a - Stack: ... → ..., []byte - constant 2 from bytecblock ## bytec_3 -- Opcode: 0x2b +- Bytecode: 0x2b - Stack: ... → ..., []byte - constant 3 from bytecblock -## arg n +## arg -- Opcode: 0x2c {uint8 arg index} +- Syntax: `arg N` ∋ N: an arg index +- Bytecode: 0x2c {uint8} - Stack: ... → ..., []byte - Nth LogicSig argument - Mode: Signature ## arg_0 -- Opcode: 0x2d +- Bytecode: 0x2d - Stack: ... → ..., []byte - LogicSig argument 0 - Mode: Signature ## arg_1 -- Opcode: 0x2e +- Bytecode: 0x2e - Stack: ... → ..., []byte - LogicSig argument 1 - Mode: Signature ## arg_2 -- Opcode: 0x2f +- Bytecode: 0x2f - Stack: ... → ..., []byte - LogicSig argument 2 - Mode: Signature ## arg_3 -- Opcode: 0x30 +- Bytecode: 0x30 - Stack: ... → ..., []byte - LogicSig argument 3 - Mode: Signature -## txn f +## txn -- Opcode: 0x31 {uint8 transaction field index} +- Syntax: `txn F` ∋ F: [txn](#field-group-txn) +- Bytecode: 0x31 {uint8} - Stack: ... → ..., any - field F of current transaction -`txn` Fields (see [transaction reference](https://developer.algorand.org/docs/reference/transactions/)): +### txn + +Fields (see [transaction reference](https://developer.algorand.org/docs/reference/transactions/)) | Index | Name | Type | In | Notes | | - | ------ | -- | - | --------- | -| 0 | Sender | []byte | | 32 byte address | +| 0 | Sender | address | | 32 byte address | | 1 | Fee | uint64 | | microalgos | | 2 | FirstValid | uint64 | | round number | | 3 | FirstValidTime | uint64 | v7 | UNIX timestamp of block before txn.FirstValid. Fails if negative | | 4 | LastValid | uint64 | | round number | | 5 | Note | []byte | | Any data up to 1024 bytes | -| 6 | Lease | []byte | | 32 byte lease value | -| 7 | Receiver | []byte | | 32 byte address | +| 6 | Lease | [32]byte | | 32 byte lease value | +| 7 | Receiver | address | | 32 byte address | | 8 | Amount | uint64 | | microalgos | -| 9 | CloseRemainderTo | []byte | | 32 byte address | -| 10 | VotePK | []byte | | 32 byte address | -| 11 | SelectionPK | []byte | | 32 byte address | +| 9 | CloseRemainderTo | address | | 32 byte address | +| 10 | VotePK | [32]byte | | 32 byte address | +| 11 | SelectionPK | [32]byte | | 32 byte address | | 12 | VoteFirst | uint64 | | The first round that the participation key is valid. | | 13 | VoteLast | uint64 | | The last round that the participation key is valid. | | 14 | VoteKeyDilution | uint64 | | Dilution for the 2-level participation key | @@ -380,33 +393,33 @@ The notation J,K indicates that two uint64 values J and K are interpreted as a u | 16 | TypeEnum | uint64 | | Transaction type as integer | | 17 | XferAsset | uint64 | | Asset ID | | 18 | AssetAmount | uint64 | | value in Asset's units | -| 19 | AssetSender | []byte | | 32 byte address. Source of assets if Sender is the Asset's Clawback address. | -| 20 | AssetReceiver | []byte | | 32 byte address | -| 21 | AssetCloseTo | []byte | | 32 byte address | +| 19 | AssetSender | address | | 32 byte address. Source of assets if Sender is the Asset's Clawback address. | +| 20 | AssetReceiver | address | | 32 byte address | +| 21 | AssetCloseTo | address | | 32 byte address | | 22 | GroupIndex | uint64 | | Position of this transaction within an atomic transaction group. A stand-alone transaction is implicitly element 0 in a group of 1 | -| 23 | TxID | []byte | | The computed ID for this transaction. 32 bytes. | +| 23 | TxID | [32]byte | | The computed ID for this transaction. 32 bytes. | | 24 | ApplicationID | uint64 | v2 | ApplicationID from ApplicationCall transaction | | 25 | OnCompletion | uint64 | v2 | ApplicationCall transaction on completion action | | 27 | NumAppArgs | uint64 | v2 | Number of ApplicationArgs | | 29 | NumAccounts | uint64 | v2 | Number of Accounts | | 30 | ApprovalProgram | []byte | v2 | Approval program | | 31 | ClearStateProgram | []byte | v2 | Clear state program | -| 32 | RekeyTo | []byte | v2 | 32 byte Sender's new AuthAddr | +| 32 | RekeyTo | address | v2 | 32 byte Sender's new AuthAddr | | 33 | ConfigAsset | uint64 | v2 | Asset ID in asset config transaction | | 34 | ConfigAssetTotal | uint64 | v2 | Total number of units of this asset created | | 35 | ConfigAssetDecimals | uint64 | v2 | Number of digits to display after the decimal place when displaying the asset | -| 36 | ConfigAssetDefaultFrozen | uint64 | v2 | Whether the asset's slots are frozen by default or not, 0 or 1 | +| 36 | ConfigAssetDefaultFrozen | bool | v2 | Whether the asset's slots are frozen by default or not, 0 or 1 | | 37 | ConfigAssetUnitName | []byte | v2 | Unit name of the asset | | 38 | ConfigAssetName | []byte | v2 | The asset name | | 39 | ConfigAssetURL | []byte | v2 | URL | -| 40 | ConfigAssetMetadataHash | []byte | v2 | 32 byte commitment to unspecified asset metadata | -| 41 | ConfigAssetManager | []byte | v2 | 32 byte address | -| 42 | ConfigAssetReserve | []byte | v2 | 32 byte address | -| 43 | ConfigAssetFreeze | []byte | v2 | 32 byte address | -| 44 | ConfigAssetClawback | []byte | v2 | 32 byte address | +| 40 | ConfigAssetMetadataHash | [32]byte | v2 | 32 byte commitment to unspecified asset metadata | +| 41 | ConfigAssetManager | address | v2 | 32 byte address | +| 42 | ConfigAssetReserve | address | v2 | 32 byte address | +| 43 | ConfigAssetFreeze | address | v2 | 32 byte address | +| 44 | ConfigAssetClawback | address | v2 | 32 byte address | | 45 | FreezeAsset | uint64 | v2 | Asset ID being frozen or un-frozen | -| 46 | FreezeAssetAccount | []byte | v2 | 32 byte address of the account whose asset slot is being frozen or un-frozen | -| 47 | FreezeAssetFrozen | uint64 | v2 | The new frozen value, 0 or 1 | +| 46 | FreezeAssetAccount | address | v2 | 32 byte address of the account whose asset slot is being frozen or un-frozen | +| 47 | FreezeAssetFrozen | bool | v2 | The new frozen value, 0 or 1 | | 49 | NumAssets | uint64 | v3 | Number of Assets | | 51 | NumApplications | uint64 | v3 | Number of Applications | | 52 | GlobalNumUint | uint64 | v3 | Number of global state integers in ApplicationCall | @@ -414,7 +427,7 @@ The notation J,K indicates that two uint64 values J and K are interpreted as a u | 54 | LocalNumUint | uint64 | v3 | Number of local state integers in ApplicationCall | | 55 | LocalNumByteSlice | uint64 | v3 | Number of local state byteslices in ApplicationCall | | 56 | ExtraProgramPages | uint64 | v4 | Number of additional pages for each of the application's approval and clear state programs. An ExtraProgramPages of 1 means 2048 more total bytes, or 1024 for each program. | -| 57 | Nonparticipation | uint64 | v5 | Marks an account nonparticipating for rewards | +| 57 | Nonparticipation | bool | v5 | Marks an account nonparticipating for rewards | | 59 | NumLogs | uint64 | v5 | Number of Logs (only with `itxn` in v5). Application mode only | | 60 | CreatedAssetID | uint64 | v5 | Asset ID allocated by the creation of an ASA (only with `itxn` in v5). Application mode only | | 61 | CreatedApplicationID | uint64 | v5 | ApplicationID allocated by the creation of an application (only with `itxn` in v5). Application mode only | @@ -424,66 +437,75 @@ The notation J,K indicates that two uint64 values J and K are interpreted as a u | 67 | NumClearStateProgramPages | uint64 | v7 | Number of ClearState Program pages | -## global f +## global -- Opcode: 0x32 {uint8 global field index} +- Syntax: `global F` ∋ F: [global](#field-group-global) +- Bytecode: 0x32 {uint8} - Stack: ... → ..., any - global field F -`global` Fields: +### global + +Fields | Index | Name | Type | In | Notes | | - | ------ | -- | - | --------- | | 0 | MinTxnFee | uint64 | | microalgos | | 1 | MinBalance | uint64 | | microalgos | | 2 | MaxTxnLife | uint64 | | rounds | -| 3 | ZeroAddress | []byte | | 32 byte address of all zero bytes | +| 3 | ZeroAddress | address | | 32 byte address of all zero bytes | | 4 | GroupSize | uint64 | | Number of transactions in this atomic transaction group. At least 1 | | 5 | LogicSigVersion | uint64 | v2 | Maximum supported version | | 6 | Round | uint64 | v2 | Current round number. Application mode only. | | 7 | LatestTimestamp | uint64 | v2 | Last confirmed block UNIX timestamp. Fails if negative. Application mode only. | | 8 | CurrentApplicationID | uint64 | v2 | ID of current application executing. Application mode only. | -| 9 | CreatorAddress | []byte | v3 | Address of the creator of the current application. Application mode only. | -| 10 | CurrentApplicationAddress | []byte | v5 | Address that the current application controls. Application mode only. | -| 11 | GroupID | []byte | v5 | ID of the transaction group. 32 zero bytes if the transaction is not part of a group. | +| 9 | CreatorAddress | address | v3 | Address of the creator of the current application. Application mode only. | +| 10 | CurrentApplicationAddress | address | v5 | Address that the current application controls. Application mode only. | +| 11 | GroupID | [32]byte | v5 | ID of the transaction group. 32 zero bytes if the transaction is not part of a group. | | 12 | OpcodeBudget | uint64 | v6 | The remaining cost that can be spent by opcodes in this program. | | 13 | CallerApplicationID | uint64 | v6 | The application ID of the application that called this application. 0 if this application is at the top-level. Application mode only. | -| 14 | CallerApplicationAddress | []byte | v6 | The application address of the application that called this application. ZeroAddress if this application is at the top-level. Application mode only. | +| 14 | CallerApplicationAddress | address | v6 | The application address of the application that called this application. ZeroAddress if this application is at the top-level. Application mode only. | -## gtxn t f +## gtxn -- Opcode: 0x33 {uint8 transaction group index} {uint8 transaction field index} +- Syntax: `gtxn T F` ∋ T: transaction group index, F: [txn](#field-group-txn) +- Bytecode: 0x33 {uint8}, {uint8} - Stack: ... → ..., any - field F of the Tth transaction in the current group for notes on transaction fields available, see `txn`. If this transaction is _i_ in the group, `gtxn i field` is equivalent to `txn field`. -## load i +## load -- Opcode: 0x34 {uint8 position in scratch space to load from} +- Syntax: `load I` ∋ I: position in scratch space to load from +- Bytecode: 0x34 {uint8} - Stack: ... → ..., any - Ith scratch space value. All scratch spaces are 0 at program start. -## store i +## store -- Opcode: 0x35 {uint8 position in scratch space to store to} +- Syntax: `store I` ∋ I: position in scratch space to store to +- Bytecode: 0x35 {uint8} - Stack: ..., A → ... - store A to the Ith scratch space -## txna f i +## txna -- Opcode: 0x36 {uint8 transaction field index} {uint8 transaction field array index} +- Syntax: `txna F I` ∋ F: [txna](#field-group-txna), I: transaction field array index +- Bytecode: 0x36 {uint8}, {uint8} - Stack: ... → ..., any - Ith value of the array field F of the current transaction
`txna` can be called using `txn` with 2 immediates. - Availability: v2 -`txna` Fields (see [transaction reference](https://developer.algorand.org/docs/reference/transactions/)): +### txna + +Fields (see [transaction reference](https://developer.algorand.org/docs/reference/transactions/)) | Index | Name | Type | In | Notes | | - | ------ | -- | - | --------- | | 26 | ApplicationArgs | []byte | v2 | Arguments passed to the application in the ApplicationCall transaction | -| 28 | Accounts | []byte | v2 | Accounts listed in the ApplicationCall transaction | +| 28 | Accounts | address | v2 | Accounts listed in the ApplicationCall transaction | | 48 | Assets | uint64 | v3 | Foreign Assets listed in the ApplicationCall transaction | | 50 | Applications | uint64 | v3 | Foreign Apps listed in the ApplicationCall transaction | | 58 | Logs | []byte | v5 | Log messages emitted by an application call (only with `itxn` in v5). Application mode only | @@ -491,32 +513,36 @@ for notes on transaction fields available, see `txn`. If this transaction is _i_ | 66 | ClearStateProgramPages | []byte | v7 | ClearState Program as an array of pages | -## gtxna t f i +## gtxna -- Opcode: 0x37 {uint8 transaction group index} {uint8 transaction field index} {uint8 transaction field array index} +- Syntax: `gtxna T F I` ∋ T: transaction group index, F: [txna](#field-group-txna), I: transaction field array index +- Bytecode: 0x37 {uint8}, {uint8}, {uint8} - Stack: ... → ..., any - Ith value of the array field F from the Tth transaction in the current group
`gtxna` can be called using `gtxn` with 3 immediates. - Availability: v2 -## gtxns f +## gtxns -- Opcode: 0x38 {uint8 transaction field index} +- Syntax: `gtxns F` ∋ F: [txn](#field-group-txn) +- Bytecode: 0x38 {uint8} - Stack: ..., A: uint64 → ..., any - field F of the Ath transaction in the current group - Availability: v3 for notes on transaction fields available, see `txn`. If top of stack is _i_, `gtxns field` is equivalent to `gtxn _i_ field`. gtxns exists so that _i_ can be calculated, often based on the index of the current transaction. -## gtxnsa f i +## gtxnsa -- Opcode: 0x39 {uint8 transaction field index} {uint8 transaction field array index} +- Syntax: `gtxnsa F I` ∋ F: [txna](#field-group-txna), I: transaction field array index +- Bytecode: 0x39 {uint8}, {uint8} - Stack: ..., A: uint64 → ..., any - Ith value of the array field F from the Ath transaction in the current group
`gtxnsa` can be called using `gtxns` with 2 immediates. - Availability: v3 -## gload t i +## gload -- Opcode: 0x3a {uint8 transaction group index} {uint8 position in scratch space to load from} +- Syntax: `gload T I` ∋ T: transaction group index, I: position in scratch space to load from +- Bytecode: 0x3a {uint8}, {uint8} - Stack: ... → ..., any - Ith scratch space value of the Tth transaction in the current group - Availability: v4 @@ -524,9 +550,10 @@ for notes on transaction fields available, see `txn`. If top of stack is _i_, `g `gload` fails unless the requested transaction is an ApplicationCall and T < GroupIndex. -## gloads i +## gloads -- Opcode: 0x3b {uint8 position in scratch space to load from} +- Syntax: `gloads I` ∋ I: position in scratch space to load from +- Bytecode: 0x3b {uint8} - Stack: ..., A: uint64 → ..., any - Ith scratch space value of the Ath transaction in the current group - Availability: v4 @@ -534,9 +561,10 @@ for notes on transaction fields available, see `txn`. If top of stack is _i_, `g `gloads` fails unless the requested transaction is an ApplicationCall and A < GroupIndex. -## gaid t +## gaid -- Opcode: 0x3c {uint8 transaction group index} +- Syntax: `gaid T` ∋ T: transaction group index +- Bytecode: 0x3c {uint8} - Stack: ... → ..., uint64 - ID of the asset or application created in the Tth transaction of the current group - Availability: v4 @@ -546,7 +574,7 @@ for notes on transaction fields available, see `txn`. If top of stack is _i_, `g ## gaids -- Opcode: 0x3d +- Bytecode: 0x3d - Stack: ..., A: uint64 → ..., uint64 - ID of the asset or application created in the Ath transaction of the current group - Availability: v4 @@ -556,21 +584,22 @@ for notes on transaction fields available, see `txn`. If top of stack is _i_, `g ## loads -- Opcode: 0x3e +- Bytecode: 0x3e - Stack: ..., A: uint64 → ..., any - Ath scratch space value. All scratch spaces are 0 at program start. - Availability: v5 ## stores -- Opcode: 0x3f +- Bytecode: 0x3f - Stack: ..., A: uint64, B → ... - store B to the Ath scratch space - Availability: v5 -## bnz target +## bnz -- Opcode: 0x40 {int16 branch offset, big-endian} +- Syntax: `bnz TARGET` ∋ TARGET: branch offset +- Bytecode: 0x40 {int16 (big-endian)} - Stack: ..., A: uint64 → ... - branch to TARGET if value A is not zero @@ -578,18 +607,20 @@ The `bnz` instruction opcode 0x40 is followed by two immediate data bytes which At v2 it became allowed to branch to the end of the program exactly after the last instruction: bnz to byte N (with 0-indexing) was illegal for a TEAL program with N bytes before v2, and is legal after it. This change eliminates the need for a last instruction of no-op as a branch target at the end. (Branching beyond the end--in other words, to a byte larger than N--is still illegal and will cause the program to fail.) -## bz target +## bz -- Opcode: 0x41 {int16 branch offset, big-endian} +- Syntax: `bz TARGET` ∋ TARGET: branch offset +- Bytecode: 0x41 {int16 (big-endian)} - Stack: ..., A: uint64 → ... - branch to TARGET if value A is zero - Availability: v2 See `bnz` for details on how branches work. `bz` inverts the behavior of `bnz`. -## b target +## b -- Opcode: 0x42 {int16 branch offset, big-endian} +- Syntax: `b TARGET` ∋ TARGET: branch offset +- Bytecode: 0x42 {int16 (big-endian)} - Stack: ... → ... - branch unconditionally to TARGET - Availability: v2 @@ -598,119 +629,126 @@ See `bnz` for details on how branches work. `b` always jumps to the offset. ## return -- Opcode: 0x43 +- Bytecode: 0x43 - Stack: ..., A: uint64 → _exits_ - use A as success value; end - Availability: v2 ## assert -- Opcode: 0x44 +- Bytecode: 0x44 - Stack: ..., A: uint64 → ... - immediately fail unless A is a non-zero number - Availability: v3 -## bury n +## bury -- Opcode: 0x45 {uint8 depth} +- Syntax: `bury N` ∋ N: depth +- Bytecode: 0x45 {uint8} - Stack: ..., A → ... - replace the Nth value from the top of the stack with A. bury 0 fails. - Availability: v8 -## popn n +## popn -- Opcode: 0x46 {uint8 stack depth} +- Syntax: `popn N` ∋ N: stack depth +- Bytecode: 0x46 {uint8} - Stack: ..., [N items] → ... - remove N values from the top of the stack - Availability: v8 -## dupn n +## dupn -- Opcode: 0x47 {uint8 copy count} +- Syntax: `dupn N` ∋ N: copy count +- Bytecode: 0x47 {uint8} - Stack: ..., A → ..., A, [N copies of A] - duplicate A, N times - Availability: v8 ## pop -- Opcode: 0x48 +- Bytecode: 0x48 - Stack: ..., A → ... - discard A ## dup -- Opcode: 0x49 +- Bytecode: 0x49 - Stack: ..., A → ..., A, A - duplicate A ## dup2 -- Opcode: 0x4a +- Bytecode: 0x4a - Stack: ..., A, B → ..., A, B, A, B - duplicate A and B - Availability: v2 -## dig n +## dig -- Opcode: 0x4b {uint8 depth} +- Syntax: `dig N` ∋ N: depth +- Bytecode: 0x4b {uint8} - Stack: ..., A, [N items] → ..., A, [N items], A - Nth value from the top of the stack. dig 0 is equivalent to dup - Availability: v3 ## swap -- Opcode: 0x4c +- Bytecode: 0x4c - Stack: ..., A, B → ..., B, A - swaps A and B on stack - Availability: v3 ## select -- Opcode: 0x4d +- Bytecode: 0x4d - Stack: ..., A, B, C: uint64 → ..., A or B - selects one of two values based on top-of-stack: B if C != 0, else A - Availability: v3 -## cover n +## cover -- Opcode: 0x4e {uint8 depth} +- Syntax: `cover N` ∋ N: depth +- Bytecode: 0x4e {uint8} - Stack: ..., [N items], A → ..., A, [N items] - remove top of stack, and place it deeper in the stack such that N elements are above it. Fails if stack depth <= N. - Availability: v5 -## uncover n +## uncover -- Opcode: 0x4f {uint8 depth} +- Syntax: `uncover N` ∋ N: depth +- Bytecode: 0x4f {uint8} - Stack: ..., A, [N items] → ..., [N items], A - remove the value at depth N in the stack and shift above items down so the Nth deep value is on top of the stack. Fails if stack depth <= N. - Availability: v5 ## concat -- Opcode: 0x50 +- Bytecode: 0x50 - Stack: ..., A: []byte, B: []byte → ..., []byte - join A and B - Availability: v2 `concat` fails if the result would be greater than 4096 bytes. -## substring s e +## substring -- Opcode: 0x51 {uint8 start position} {uint8 end position} +- Syntax: `substring S E` ∋ S: start position, E: end position +- Bytecode: 0x51 {uint8}, {uint8} - Stack: ..., A: []byte → ..., []byte - A range of bytes from A starting at S up to but not including E. If E < S, or either is larger than the array length, the program fails - Availability: v2 ## substring3 -- Opcode: 0x52 +- Bytecode: 0x52 - Stack: ..., A: []byte, B: uint64, C: uint64 → ..., []byte - A range of bytes from A starting at B up to but not including C. If C < B, or either is larger than the array length, the program fails - Availability: v2 ## getbit -- Opcode: 0x53 +- Bytecode: 0x53 - Stack: ..., A, B: uint64 → ..., uint64 - Bth bit of (byte-array or integer) A. If B is greater than or equal to the bit length of the value (8*byte length), the program fails - Availability: v3 @@ -719,7 +757,7 @@ see explanation of bit ordering in setbit ## setbit -- Opcode: 0x54 +- Bytecode: 0x54 - Stack: ..., A, B: uint64, C: uint64 → ..., any - Copy of (byte-array or integer) A, with the Bth bit set to (0 or 1) C. If B is greater than or equal to the bit length of the value (8*byte length), the program fails - Availability: v3 @@ -728,76 +766,81 @@ When A is a uint64, index 0 is the least significant bit. Setting bit 3 to 1 on ## getbyte -- Opcode: 0x55 +- Bytecode: 0x55 - Stack: ..., A: []byte, B: uint64 → ..., uint64 - Bth byte of A, as an integer. If B is greater than or equal to the array length, the program fails - Availability: v3 ## setbyte -- Opcode: 0x56 +- Bytecode: 0x56 - Stack: ..., A: []byte, B: uint64, C: uint64 → ..., []byte - Copy of A with the Bth byte set to small integer (between 0..255) C. If B is greater than or equal to the array length, the program fails - Availability: v3 -## extract s l +## extract -- Opcode: 0x57 {uint8 start position} {uint8 length} +- Syntax: `extract S L` ∋ S: start position, L: length +- Bytecode: 0x57 {uint8}, {uint8} - Stack: ..., A: []byte → ..., []byte - A range of bytes from A starting at S up to but not including S+L. If L is 0, then extract to the end of the string. If S or S+L is larger than the array length, the program fails - Availability: v5 ## extract3 -- Opcode: 0x58 +- Bytecode: 0x58 - Stack: ..., A: []byte, B: uint64, C: uint64 → ..., []byte - A range of bytes from A starting at B up to but not including B+C. If B+C is larger than the array length, the program fails
`extract3` can be called using `extract` with no immediates. - Availability: v5 ## extract_uint16 -- Opcode: 0x59 +- Bytecode: 0x59 - Stack: ..., A: []byte, B: uint64 → ..., uint64 - A uint16 formed from a range of big-endian bytes from A starting at B up to but not including B+2. If B+2 is larger than the array length, the program fails - Availability: v5 ## extract_uint32 -- Opcode: 0x5a +- Bytecode: 0x5a - Stack: ..., A: []byte, B: uint64 → ..., uint64 - A uint32 formed from a range of big-endian bytes from A starting at B up to but not including B+4. If B+4 is larger than the array length, the program fails - Availability: v5 ## extract_uint64 -- Opcode: 0x5b +- Bytecode: 0x5b - Stack: ..., A: []byte, B: uint64 → ..., uint64 - A uint64 formed from a range of big-endian bytes from A starting at B up to but not including B+8. If B+8 is larger than the array length, the program fails - Availability: v5 -## replace2 s +## replace2 -- Opcode: 0x5c {uint8 start position} +- Syntax: `replace2 S` ∋ S: start position +- Bytecode: 0x5c {uint8} - Stack: ..., A: []byte, B: []byte → ..., []byte - Copy of A with the bytes starting at S replaced by the bytes of B. Fails if S+len(B) exceeds len(A)
`replace2` can be called using `replace` with 1 immediate. - Availability: v7 ## replace3 -- Opcode: 0x5d +- Bytecode: 0x5d - Stack: ..., A: []byte, B: uint64, C: []byte → ..., []byte - Copy of A with the bytes starting at B replaced by the bytes of C. Fails if B+len(C) exceeds len(A)
`replace3` can be called using `replace` with no immediates. - Availability: v7 -## base64_decode e +## base64_decode -- Opcode: 0x5e {uint8 encoding index} +- Syntax: `base64_decode E` ∋ E: [base64](#field-group-base64) +- Bytecode: 0x5e {uint8} - Stack: ..., A: []byte → ..., []byte - decode A which was base64-encoded using _encoding_ E. Fail if A is not base64 encoded with encoding E - **Cost**: 1 + 1 per 16 bytes of A - Availability: v7 -`base64` Encodings: +### base64 + +Encodings | Index | Name | Notes | | - | ------ | --------- | @@ -809,15 +852,18 @@ When A is a uint64, index 0 is the least significant bit. Setting bit 3 to 1 on Decodes A using the base64 encoding E. Specify the encoding with an immediate arg either as URL and Filename Safe (`URLEncoding`) or Standard (`StdEncoding`). See [RFC 4648 sections 4 and 5](https://rfc-editor.org/rfc/rfc4648.html#section-4). It is assumed that the encoding ends with the exact number of `=` padding characters as required by the RFC. When padding occurs, any unused pad bits in the encoding must be set to zero or the decoding will fail. The special cases of `\n` and `\r` are allowed but completely ignored. An error will result when attempting to decode a string with a character that is not in the encoding alphabet or not one of `=`, `\r`, or `\n`. -## json_ref r +## json_ref -- Opcode: 0x5f {uint8 return type index} +- Syntax: `json_ref R` ∋ R: [json_ref](#field-group-json_ref) +- Bytecode: 0x5f {uint8} - Stack: ..., A: []byte, B: []byte → ..., any - key B's value, of type R, from a [valid](jsonspec.md) utf-8 encoded json object A - **Cost**: 25 + 2 per 7 bytes of A - Availability: v7 -`json_ref` Types: +### json_ref + +Types | Index | Name | Type | Notes | | - | ------ | -- | --------- | @@ -832,7 +878,7 @@ Almost all smart contracts should use simpler and smaller methods (such as the [ ## balance -- Opcode: 0x60 +- Bytecode: 0x60 - Stack: ..., A → ..., uint64 - balance for account A, in microalgos. The balance is observed after the effects of previous transactions in the group, and after the fee for the current transaction is deducted. Changes caused by inner transactions are observable immediately following `itxn_submit` - Availability: v2 @@ -842,8 +888,8 @@ params: Txn.Accounts offset (or, since v4, an _available_ account address). Retu ## app_opted_in -- Opcode: 0x61 -- Stack: ..., A, B: uint64 → ..., uint64 +- Bytecode: 0x61 +- Stack: ..., A, B: uint64 → ..., bool - 1 if account A is opted in to application B, else 0 - Availability: v2 - Mode: Application @@ -852,7 +898,7 @@ params: Txn.Accounts offset (or, since v4, an _available_ account address), _ava ## app_local_get -- Opcode: 0x62 +- Bytecode: 0x62 - Stack: ..., A, B: []byte → ..., any - local state of the key B in the current application in account A - Availability: v2 @@ -862,8 +908,8 @@ params: Txn.Accounts offset (or, since v4, an _available_ account address), stat ## app_local_get_ex -- Opcode: 0x63 -- Stack: ..., A, B: uint64, C: []byte → ..., X: any, Y: uint64 +- Bytecode: 0x63 +- Stack: ..., A, B: uint64, C: []byte → ..., X: any, Y: bool - X is the local state of application B, key C in account A. Y is 1 if key existed, else 0 - Availability: v2 - Mode: Application @@ -872,7 +918,7 @@ params: Txn.Accounts offset (or, since v4, an _available_ account address), _ava ## app_global_get -- Opcode: 0x64 +- Bytecode: 0x64 - Stack: ..., A: []byte → ..., any - global state of the key A in the current application - Availability: v2 @@ -882,8 +928,8 @@ params: state key. Return: value. The value is zero (of type uint64) if the key ## app_global_get_ex -- Opcode: 0x65 -- Stack: ..., A: uint64, B: []byte → ..., X: any, Y: uint64 +- Bytecode: 0x65 +- Stack: ..., A: uint64, B: []byte → ..., X: any, Y: bool - X is the global state of application A, key B. Y is 1 if key existed, else 0 - Availability: v2 - Mode: Application @@ -892,7 +938,7 @@ params: Txn.ForeignApps offset (or, since v4, an _available_ application id), st ## app_local_put -- Opcode: 0x66 +- Bytecode: 0x66 - Stack: ..., A, B: []byte, C → ... - write C to key B in account A's local state of the current application - Availability: v2 @@ -902,7 +948,7 @@ params: Txn.Accounts offset (or, since v4, an _available_ account address), stat ## app_global_put -- Opcode: 0x67 +- Bytecode: 0x67 - Stack: ..., A: []byte, B → ... - write B to key A in the global state of the current application - Availability: v2 @@ -910,7 +956,7 @@ params: Txn.Accounts offset (or, since v4, an _available_ account address), stat ## app_local_del -- Opcode: 0x68 +- Bytecode: 0x68 - Stack: ..., A, B: []byte → ... - delete key B from account A's local state of the current application - Availability: v2 @@ -922,7 +968,7 @@ Deleting a key which is already absent has no effect on the application local st ## app_global_del -- Opcode: 0x69 +- Bytecode: 0x69 - Stack: ..., A: []byte → ... - delete key A from the global state of the current application - Availability: v2 @@ -932,61 +978,70 @@ params: state key. Deleting a key which is already absent has no effect on the application global state. (In particular, it does _not_ cause the program to fail.) -## asset_holding_get f +## asset_holding_get -- Opcode: 0x70 {uint8 asset holding field index} -- Stack: ..., A, B: uint64 → ..., X: any, Y: uint64 +- Syntax: `asset_holding_get F` ∋ F: [asset_holding](#field-group-asset_holding) +- Bytecode: 0x70 {uint8} +- Stack: ..., A, B: uint64 → ..., X: any, Y: bool - X is field F from account A's holding of asset B. Y is 1 if A is opted into B, else 0 - Availability: v2 - Mode: Application -`asset_holding` Fields: +### asset_holding + +Fields | Index | Name | Type | Notes | | - | ------ | -- | --------- | | 0 | AssetBalance | uint64 | Amount of the asset unit held by this account | -| 1 | AssetFrozen | uint64 | Is the asset frozen or not | +| 1 | AssetFrozen | bool | Is the asset frozen or not | params: Txn.Accounts offset (or, since v4, an _available_ address), asset id (or, since v4, a Txn.ForeignAssets offset). Return: did_exist flag (1 if the asset existed and 0 otherwise), value. -## asset_params_get f +## asset_params_get -- Opcode: 0x71 {uint8 asset params field index} -- Stack: ..., A: uint64 → ..., X: any, Y: uint64 +- Syntax: `asset_params_get F` ∋ F: [asset_params](#field-group-asset_params) +- Bytecode: 0x71 {uint8} +- Stack: ..., A: uint64 → ..., X: any, Y: bool - X is field F from asset A. Y is 1 if A exists, else 0 - Availability: v2 - Mode: Application -`asset_params` Fields: +### asset_params + +Fields | Index | Name | Type | In | Notes | | - | ------ | -- | - | --------- | | 0 | AssetTotal | uint64 | | Total number of units of this asset | | 1 | AssetDecimals | uint64 | | See AssetParams.Decimals | -| 2 | AssetDefaultFrozen | uint64 | | Frozen by default or not | +| 2 | AssetDefaultFrozen | bool | | Frozen by default or not | | 3 | AssetUnitName | []byte | | Asset unit name | | 4 | AssetName | []byte | | Asset name | | 5 | AssetURL | []byte | | URL with additional info about the asset | -| 6 | AssetMetadataHash | []byte | | Arbitrary commitment | -| 7 | AssetManager | []byte | | Manager address | -| 8 | AssetReserve | []byte | | Reserve address | -| 9 | AssetFreeze | []byte | | Freeze address | -| 10 | AssetClawback | []byte | | Clawback address | -| 11 | AssetCreator | []byte | v5 | Creator address | +| 6 | AssetMetadataHash | [32]byte | | Arbitrary commitment | +| 7 | AssetManager | address | | Manager address | +| 8 | AssetReserve | address | | Reserve address | +| 9 | AssetFreeze | address | | Freeze address | +| 10 | AssetClawback | address | | Clawback address | +| 11 | AssetCreator | address | v5 | Creator address | params: Txn.ForeignAssets offset (or, since v4, an _available_ asset id. Return: did_exist flag (1 if the asset existed and 0 otherwise), value. -## app_params_get f +## app_params_get -- Opcode: 0x72 {uint8 app params field index} -- Stack: ..., A: uint64 → ..., X: any, Y: uint64 +- Syntax: `app_params_get F` ∋ F: [app_params](#field-group-app_params) +- Bytecode: 0x72 {uint8} +- Stack: ..., A: uint64 → ..., X: any, Y: bool - X is field F from app A. Y is 1 if A exists, else 0 - Availability: v5 - Mode: Application -`app_params` Fields: +### app_params + +Fields | Index | Name | Type | Notes | | - | ------ | -- | --------- | @@ -997,27 +1052,30 @@ params: Txn.ForeignAssets offset (or, since v4, an _available_ asset id. Return: | 4 | AppLocalNumUint | uint64 | Number of uint64 values allowed in Local State | | 5 | AppLocalNumByteSlice | uint64 | Number of byte array values allowed in Local State | | 6 | AppExtraProgramPages | uint64 | Number of Extra Program Pages of code space | -| 7 | AppCreator | []byte | Creator address | -| 8 | AppAddress | []byte | Address for which this application has authority | +| 7 | AppCreator | address | Creator address | +| 8 | AppAddress | address | Address for which this application has authority | params: Txn.ForeignApps offset or an _available_ app id. Return: did_exist flag (1 if the application existed and 0 otherwise), value. -## acct_params_get f +## acct_params_get -- Opcode: 0x73 {uint8 account params field index} -- Stack: ..., A → ..., X: any, Y: uint64 +- Syntax: `acct_params_get F` ∋ F: [acct_params](#field-group-acct_params) +- Bytecode: 0x73 {uint8} +- Stack: ..., A → ..., X: any, Y: bool - X is field F from account A. Y is 1 if A owns positive algos, else 0 - Availability: v6 - Mode: Application -`acct_params` Fields: +### acct_params + +Fields | Index | Name | Type | In | Notes | | - | ------ | -- | - | --------- | | 0 | AcctBalance | uint64 | | Account balance in microalgos | | 1 | AcctMinBalance | uint64 | | Minimum required balance for account, in microalgos | -| 2 | AcctAuthAddr | []byte | | Address the account is rekeyed to. | +| 2 | AcctAuthAddr | address | | Address the account is rekeyed to. | | 3 | AcctTotalNumUint | uint64 | v8 | The total number of uint64 values allocated by this account in Global and Local States. | | 4 | AcctTotalNumByteSlice | uint64 | v8 | The total number of byte array values allocated by this account in Global and Local States. | | 5 | AcctTotalExtraAppPages | uint64 | v8 | The number of extra app code pages used by this account. | @@ -1031,7 +1089,7 @@ params: Txn.ForeignApps offset or an _available_ app id. Return: did_exist flag ## min_balance -- Opcode: 0x78 +- Bytecode: 0x78 - Stack: ..., A → ..., uint64 - minimum required balance for account A, in microalgos. Required balance is affected by ASA, App, and Box usage. When creating or opting into an app, the minimum balance grows before the app code runs, therefore the increase is visible there. When deleting or closing out, the minimum balance decreases after the app executes. Changes caused by inner transactions or box usage are observable immediately following the opcode effecting the change. - Availability: v3 @@ -1039,36 +1097,40 @@ params: Txn.ForeignApps offset or an _available_ app id. Return: did_exist flag params: Txn.Accounts offset (or, since v4, an _available_ account address). Return: value. -## pushbytes bytes +## pushbytes -- Opcode: 0x80 {varuint length} {bytes} +- Syntax: `pushbytes BYTES` ∋ BYTES: a byte constant +- Bytecode: 0x80 {varuint length, bytes} - Stack: ... → ..., []byte - immediate BYTES - Availability: v3 pushbytes args are not added to the bytecblock during assembly processes -## pushint uint +## pushint -- Opcode: 0x81 {varuint int} +- Syntax: `pushint UINT` ∋ UINT: an int constant +- Bytecode: 0x81 {varuint} - Stack: ... → ..., uint64 - immediate UINT - Availability: v3 pushint args are not added to the intcblock during assembly processes -## pushbytess bytes ... +## pushbytess -- Opcode: 0x82 {varuint count} [({varuint length} bytes), ...] +- Syntax: `pushbytess BYTES ...` ∋ BYTES ...: a list of byte constants +- Bytecode: 0x82 {varuint count, [varuint length, bytes ...]} - Stack: ... → ..., [N items] - push sequences of immediate byte arrays to stack (first byte array being deepest) - Availability: v8 pushbytess args are not added to the bytecblock during assembly processes -## pushints uint ... +## pushints -- Opcode: 0x83 {varuint count} [{varuint value}, ...] +- Syntax: `pushints UINT ...` ∋ UINT ...: a list of int constants +- Bytecode: 0x83 {varuint count, [varuint ...]} - Stack: ... → ..., [N items] - push sequence of immediate uints to stack in the order they appear (first uint being deepest) - Availability: v8 @@ -1077,15 +1139,16 @@ pushints args are not added to the intcblock during assembly processes ## ed25519verify_bare -- Opcode: 0x84 -- Stack: ..., A: []byte, B: []byte, C: []byte → ..., uint64 +- Bytecode: 0x84 +- Stack: ..., A: []byte, B: []byte, C: []byte → ..., bool - for (data A, signature B, pubkey C) verify the signature of the data against the pubkey => {0 or 1} - **Cost**: 1900 - Availability: v7 -## callsub target +## callsub -- Opcode: 0x88 {int16 branch offset, big-endian} +- Syntax: `callsub TARGET` ∋ TARGET: branch offset +- Bytecode: 0x88 {int16 (big-endian)} - Stack: ... → ... - branch unconditionally to TARGET, saving the next instruction on the call stack - Availability: v4 @@ -1094,46 +1157,51 @@ The call stack is separate from the data stack. Only `callsub`, `retsub`, and `p ## retsub -- Opcode: 0x89 +- Bytecode: 0x89 - Stack: ... → ... - pop the top instruction from the call stack and branch to it - Availability: v4 If the current frame was prepared by `proto A R`, `retsub` will remove the 'A' arguments from the stack, move the `R` return values down, and pop any stack locations above the relocated return values. -## proto a r +## proto -- Opcode: 0x8a {uint8 arguments} {uint8 return values} +- Syntax: `proto A R` ∋ A: number of arguments, R: number of return values +- Bytecode: 0x8a {uint8}, {uint8} - Stack: ... → ... - Prepare top call frame for a retsub that will assume A args and R return values. - Availability: v8 Fails unless the last instruction executed was a `callsub`. -## frame_dig i +## frame_dig -- Opcode: 0x8b {int8 frame slot} +- Syntax: `frame_dig I` ∋ I: frame slot +- Bytecode: 0x8b {int8} - Stack: ... → ..., any - Nth (signed) value from the frame pointer. - Availability: v8 -## frame_bury i +## frame_bury -- Opcode: 0x8c {int8 frame slot} +- Syntax: `frame_bury I` ∋ I: frame slot +- Bytecode: 0x8c {int8} - Stack: ..., A → ... - replace the Nth (signed) value from the frame pointer in the stack with A - Availability: v8 -## switch target ... +## switch -- Opcode: 0x8d {uint8 branch count} [{int16 branch offset, big-endian}, ...] +- Syntax: `switch TARGET ...` ∋ TARGET ...: list of labels +- Bytecode: 0x8d {varuint count, [int16 (big-endian) ...]} - Stack: ..., A: uint64 → ... - branch to the Ath label. Continue at following instruction if index A exceeds the number of labels. - Availability: v8 -## match target ... +## match -- Opcode: 0x8e {uint8 branch count} [{int16 branch offset, big-endian}, ...] +- Syntax: `match TARGET ...` ∋ TARGET ...: list of labels +- Bytecode: 0x8e {varuint count, [int16 (big-endian) ...]} - Stack: ..., [A1, A2, ..., AN], B → ... - given match cases from A[1] to A[N], branch to the Ith label where A[I] = B. Continue to the following instruction if no matches are found. - Availability: v8 @@ -1142,21 +1210,21 @@ Fails unless the last instruction executed was a `callsub`. ## shl -- Opcode: 0x90 +- Bytecode: 0x90 - Stack: ..., A: uint64, B: uint64 → ..., uint64 - A times 2^B, modulo 2^64 - Availability: v4 ## shr -- Opcode: 0x91 +- Bytecode: 0x91 - Stack: ..., A: uint64, B: uint64 → ..., uint64 - A divided by 2^B - Availability: v4 ## sqrt -- Opcode: 0x92 +- Bytecode: 0x92 - Stack: ..., A: uint64 → ..., uint64 - The largest integer I such that I^2 <= A - **Cost**: 4 @@ -1164,7 +1232,7 @@ Fails unless the last instruction executed was a `callsub`. ## bitlen -- Opcode: 0x93 +- Bytecode: 0x93 - Stack: ..., A → ..., uint64 - The highest set bit in A. If A is a byte-array, it is interpreted as a big-endian unsigned integer. bitlen of 0 is 0, bitlen of 8 is 4 - Availability: v4 @@ -1173,14 +1241,14 @@ bitlen interprets arrays as big-endian integers, unlike setbit/getbit ## exp -- Opcode: 0x94 +- Bytecode: 0x94 - Stack: ..., A: uint64, B: uint64 → ..., uint64 - A raised to the Bth power. Fail if A == B == 0 and on overflow - Availability: v4 ## expw -- Opcode: 0x95 +- Bytecode: 0x95 - Stack: ..., A: uint64, B: uint64 → ..., X: uint64, Y: uint64 - A raised to the Bth power as a 128-bit result in two uint64s. X is the high 64 bits, Y is the low. Fail if A == B == 0 or if the results exceeds 2^128-1 - **Cost**: 10 @@ -1188,7 +1256,7 @@ bitlen interprets arrays as big-endian integers, unlike setbit/getbit ## bsqrt -- Opcode: 0x96 +- Bytecode: 0x96 - Stack: ..., A: []byte → ..., []byte - The largest integer I such that I^2 <= A. A and I are interpreted as big-endian unsigned integers - **Cost**: 40 @@ -1196,7 +1264,7 @@ bitlen interprets arrays as big-endian integers, unlike setbit/getbit ## divw -- Opcode: 0x97 +- Bytecode: 0x97 - Stack: ..., A: uint64, B: uint64, C: uint64 → ..., uint64 - A,B / C. Fail if C == 0 or if result overflows. - Availability: v6 @@ -1205,7 +1273,7 @@ The notation A,B indicates that A and B are interpreted as a uint128 value, with ## sha3_256 -- Opcode: 0x98 +- Bytecode: 0x98 - Stack: ..., A: []byte → ..., []byte - SHA3_256 hash of value A, yields [32]byte - **Cost**: 130 @@ -1213,81 +1281,81 @@ The notation A,B indicates that A and B are interpreted as a uint128 value, with ## b+ -- Opcode: 0xa0 -- Stack: ..., A: []byte, B: []byte → ..., []byte +- Bytecode: 0xa0 +- Stack: ..., A: bigint, B: bigint → ..., []byte - A plus B. A and B are interpreted as big-endian unsigned integers - **Cost**: 10 - Availability: v4 ## b- -- Opcode: 0xa1 -- Stack: ..., A: []byte, B: []byte → ..., []byte +- Bytecode: 0xa1 +- Stack: ..., A: bigint, B: bigint → ..., bigint - A minus B. A and B are interpreted as big-endian unsigned integers. Fail on underflow. - **Cost**: 10 - Availability: v4 ## b/ -- Opcode: 0xa2 -- Stack: ..., A: []byte, B: []byte → ..., []byte +- Bytecode: 0xa2 +- Stack: ..., A: bigint, B: bigint → ..., bigint - A divided by B (truncated division). A and B are interpreted as big-endian unsigned integers. Fail if B is zero. - **Cost**: 20 - Availability: v4 ## b* -- Opcode: 0xa3 -- Stack: ..., A: []byte, B: []byte → ..., []byte +- Bytecode: 0xa3 +- Stack: ..., A: bigint, B: bigint → ..., []byte - A times B. A and B are interpreted as big-endian unsigned integers. - **Cost**: 20 - Availability: v4 ## b< -- Opcode: 0xa4 -- Stack: ..., A: []byte, B: []byte → ..., uint64 +- Bytecode: 0xa4 +- Stack: ..., A: bigint, B: bigint → ..., bool - 1 if A is less than B, else 0. A and B are interpreted as big-endian unsigned integers - Availability: v4 ## b> -- Opcode: 0xa5 -- Stack: ..., A: []byte, B: []byte → ..., uint64 +- Bytecode: 0xa5 +- Stack: ..., A: bigint, B: bigint → ..., bool - 1 if A is greater than B, else 0. A and B are interpreted as big-endian unsigned integers - Availability: v4 ## b<= -- Opcode: 0xa6 -- Stack: ..., A: []byte, B: []byte → ..., uint64 +- Bytecode: 0xa6 +- Stack: ..., A: bigint, B: bigint → ..., bool - 1 if A is less than or equal to B, else 0. A and B are interpreted as big-endian unsigned integers - Availability: v4 ## b>= -- Opcode: 0xa7 -- Stack: ..., A: []byte, B: []byte → ..., uint64 +- Bytecode: 0xa7 +- Stack: ..., A: bigint, B: bigint → ..., bool - 1 if A is greater than or equal to B, else 0. A and B are interpreted as big-endian unsigned integers - Availability: v4 ## b== -- Opcode: 0xa8 -- Stack: ..., A: []byte, B: []byte → ..., uint64 +- Bytecode: 0xa8 +- Stack: ..., A: bigint, B: bigint → ..., bool - 1 if A is equal to B, else 0. A and B are interpreted as big-endian unsigned integers - Availability: v4 ## b!= -- Opcode: 0xa9 -- Stack: ..., A: []byte, B: []byte → ..., uint64 +- Bytecode: 0xa9 +- Stack: ..., A: bigint, B: bigint → ..., bool - 0 if A is equal to B, else 1. A and B are interpreted as big-endian unsigned integers - Availability: v4 ## b% -- Opcode: 0xaa +- Bytecode: 0xaa - Stack: ..., A: []byte, B: []byte → ..., []byte - A modulo B. A and B are interpreted as big-endian unsigned integers. Fail if B is zero. - **Cost**: 20 @@ -1295,7 +1363,7 @@ The notation A,B indicates that A and B are interpreted as a uint128 value, with ## b| -- Opcode: 0xab +- Bytecode: 0xab - Stack: ..., A: []byte, B: []byte → ..., []byte - A bitwise-or B. A and B are zero-left extended to the greater of their lengths - **Cost**: 6 @@ -1303,7 +1371,7 @@ The notation A,B indicates that A and B are interpreted as a uint128 value, with ## b& -- Opcode: 0xac +- Bytecode: 0xac - Stack: ..., A: []byte, B: []byte → ..., []byte - A bitwise-and B. A and B are zero-left extended to the greater of their lengths - **Cost**: 6 @@ -1311,7 +1379,7 @@ The notation A,B indicates that A and B are interpreted as a uint128 value, with ## b^ -- Opcode: 0xad +- Bytecode: 0xad - Stack: ..., A: []byte, B: []byte → ..., []byte - A bitwise-xor B. A and B are zero-left extended to the greater of their lengths - **Cost**: 6 @@ -1319,7 +1387,7 @@ The notation A,B indicates that A and B are interpreted as a uint128 value, with ## b~ -- Opcode: 0xae +- Bytecode: 0xae - Stack: ..., A: []byte → ..., []byte - A with all bits inverted - **Cost**: 4 @@ -1327,14 +1395,14 @@ The notation A,B indicates that A and B are interpreted as a uint128 value, with ## bzero -- Opcode: 0xaf +- Bytecode: 0xaf - Stack: ..., A: uint64 → ..., []byte - zero filled byte-array of length A - Availability: v4 ## log -- Opcode: 0xb0 +- Bytecode: 0xb0 - Stack: ..., A: []byte → ... - write A to log state of the current application - Availability: v5 @@ -1344,7 +1412,7 @@ The notation A,B indicates that A and B are interpreted as a uint128 value, with ## itxn_begin -- Opcode: 0xb1 +- Bytecode: 0xb1 - Stack: ... → ... - begin preparation of a new inner transaction in a new transaction group - Availability: v5 @@ -1352,9 +1420,10 @@ The notation A,B indicates that A and B are interpreted as a uint128 value, with `itxn_begin` initializes Sender to the application address; Fee to the minimum allowable, taking into account MinTxnFee and credit from overpaying in earlier transactions; FirstValid/LastValid to the values in the invoking transaction, and all other fields to zero or empty values. -## itxn_field f +## itxn_field -- Opcode: 0xb2 {uint8 transaction field index} +- Syntax: `itxn_field F` ∋ F: [txn](#field-group-txn) +- Bytecode: 0xb2 {uint8} - Stack: ..., A → ... - set field F of the current inner transaction to A - Availability: v5 @@ -1364,7 +1433,7 @@ The notation A,B indicates that A and B are interpreted as a uint128 value, with ## itxn_submit -- Opcode: 0xb3 +- Bytecode: 0xb3 - Stack: ... → ... - execute the current inner transaction group. Fail if executing this group would exceed the inner transaction limit, or if any transaction in the group fails. - Availability: v5 @@ -1372,17 +1441,19 @@ The notation A,B indicates that A and B are interpreted as a uint128 value, with `itxn_submit` resets the current transaction so that it can not be resubmitted. A new `itxn_begin` is required to prepare another inner transaction. -## itxn f +## itxn -- Opcode: 0xb4 {uint8 transaction field index} +- Syntax: `itxn F` ∋ F: [txn](#field-group-txn) +- Bytecode: 0xb4 {uint8} - Stack: ... → ..., any - field F of the last inner transaction - Availability: v5 - Mode: Application -## itxna f i +## itxna -- Opcode: 0xb5 {uint8 transaction field index} {uint8 transaction field array index} +- Syntax: `itxna F I` ∋ F: [txna](#field-group-txna), I: a transaction field array index +- Bytecode: 0xb5 {uint8}, {uint8} - Stack: ... → ..., any - Ith value of the array field F of the last inner transaction - Availability: v5 @@ -1390,7 +1461,7 @@ The notation A,B indicates that A and B are interpreted as a uint128 value, with ## itxn_next -- Opcode: 0xb6 +- Bytecode: 0xb6 - Stack: ... → ... - begin preparation of a new inner transaction in the same transaction group - Availability: v6 @@ -1398,17 +1469,19 @@ The notation A,B indicates that A and B are interpreted as a uint128 value, with `itxn_next` initializes the transaction exactly as `itxn_begin` does -## gitxn t f +## gitxn -- Opcode: 0xb7 {uint8 transaction group index} {uint8 transaction field index} +- Syntax: `gitxn T F` ∋ T: transaction group index, F: [txn](#field-group-txn) +- Bytecode: 0xb7 {uint8}, {uint8} - Stack: ... → ..., any - field F of the Tth transaction in the last inner group submitted - Availability: v6 - Mode: Application -## gitxna t f i +## gitxna -- Opcode: 0xb8 {uint8 transaction group index} {uint8 transaction field index} {uint8 transaction field array index} +- Syntax: `gitxna T F I` ∋ T: transaction group index, F: [txna](#field-group-txna), I: transaction field array index +- Bytecode: 0xb8 {uint8}, {uint8}, {uint8} - Stack: ... → ..., any - Ith value of the array field F from the Tth transaction in the last inner group submitted - Availability: v6 @@ -1416,8 +1489,8 @@ The notation A,B indicates that A and B are interpreted as a uint128 value, with ## box_create -- Opcode: 0xb9 -- Stack: ..., A: []byte, B: uint64 → ..., uint64 +- Bytecode: 0xb9 +- Stack: ..., A: boxName, B: uint64 → ..., bool - create a box named A, of length B. Fail if A is empty or B exceeds 32,768. Returns 0 if A already existed, else 1 - Availability: v8 - Mode: Application @@ -1426,40 +1499,40 @@ Newly created boxes are filled with 0 bytes. `box_create` will fail if the refer ## box_extract -- Opcode: 0xba -- Stack: ..., A: []byte, B: uint64, C: uint64 → ..., []byte +- Bytecode: 0xba +- Stack: ..., A: boxName, B: uint64, C: uint64 → ..., []byte - read C bytes from box A, starting at offset B. Fail if A does not exist, or the byte range is outside A's size. - Availability: v8 - Mode: Application ## box_replace -- Opcode: 0xbb -- Stack: ..., A: []byte, B: uint64, C: []byte → ... +- Bytecode: 0xbb +- Stack: ..., A: boxName, B: uint64, C: []byte → ... - write byte-array C into box A, starting at offset B. Fail if A does not exist, or the byte range is outside A's size. - Availability: v8 - Mode: Application ## box_del -- Opcode: 0xbc -- Stack: ..., A: []byte → ..., uint64 +- Bytecode: 0xbc +- Stack: ..., A: boxName → ..., bool - delete box named A if it exists. Return 1 if A existed, 0 otherwise - Availability: v8 - Mode: Application ## box_len -- Opcode: 0xbd -- Stack: ..., A: []byte → ..., X: uint64, Y: uint64 +- Bytecode: 0xbd +- Stack: ..., A: boxName → ..., X: uint64, Y: bool - X is the length of box A if A exists, else 0. Y is 1 if A exists, else 0. - Availability: v8 - Mode: Application ## box_get -- Opcode: 0xbe -- Stack: ..., A: []byte → ..., X: []byte, Y: uint64 +- Bytecode: 0xbe +- Stack: ..., A: boxName → ..., X: []byte, Y: bool - X is the contents of box A if A exists, else ''. Y is 1 if A exists, else 0. - Availability: v8 - Mode: Application @@ -1468,38 +1541,41 @@ For boxes that exceed 4,096 bytes, consider `box_create`, `box_extract`, and `bo ## box_put -- Opcode: 0xbf -- Stack: ..., A: []byte, B: []byte → ... +- Bytecode: 0xbf +- Stack: ..., A: boxName, B: []byte → ... - replaces the contents of box A with byte-array B. Fails if A exists and len(B) != len(box A). Creates A if it does not exist - Availability: v8 - Mode: Application For boxes that exceed 4,096 bytes, consider `box_create`, `box_extract`, and `box_replace` -## txnas f +## txnas -- Opcode: 0xc0 {uint8 transaction field index} +- Syntax: `txnas F` ∋ F: [txna](#field-group-txna) +- Bytecode: 0xc0 {uint8} - Stack: ..., A: uint64 → ..., any - Ath value of the array field F of the current transaction - Availability: v5 -## gtxnas t f +## gtxnas -- Opcode: 0xc1 {uint8 transaction group index} {uint8 transaction field index} +- Syntax: `gtxnas T F` ∋ T: transaction group index, F: [txna](#field-group-txna) +- Bytecode: 0xc1 {uint8}, {uint8} - Stack: ..., A: uint64 → ..., any - Ath value of the array field F from the Tth transaction in the current group - Availability: v5 -## gtxnsas f +## gtxnsas -- Opcode: 0xc2 {uint8 transaction field index} +- Syntax: `gtxnsas F` ∋ F: [txna](#field-group-txna) +- Bytecode: 0xc2 {uint8} - Stack: ..., A: uint64, B: uint64 → ..., any - Bth value of the array field F from the Ath transaction in the current group - Availability: v5 ## args -- Opcode: 0xc3 +- Bytecode: 0xc3 - Stack: ..., A: uint64 → ..., []byte - Ath LogicSig argument - Availability: v5 @@ -1507,37 +1583,42 @@ For boxes that exceed 4,096 bytes, consider `box_create`, `box_extract`, and `bo ## gloadss -- Opcode: 0xc4 +- Bytecode: 0xc4 - Stack: ..., A: uint64, B: uint64 → ..., any - Bth scratch space value of the Ath transaction in the current group - Availability: v6 - Mode: Application -## itxnas f +## itxnas -- Opcode: 0xc5 {uint8 transaction field index} +- Syntax: `itxnas F` ∋ F: [txna](#field-group-txna) +- Bytecode: 0xc5 {uint8} - Stack: ..., A: uint64 → ..., any - Ath value of the array field F of the last inner transaction - Availability: v6 - Mode: Application -## gitxnas t f +## gitxnas -- Opcode: 0xc6 {uint8 transaction group index} {uint8 transaction field index} +- Syntax: `gitxnas T F` ∋ T: transaction group index, F: [txna](#field-group-txna) +- Bytecode: 0xc6 {uint8}, {uint8} - Stack: ..., A: uint64 → ..., any - Ath value of the array field F from the Tth transaction in the last inner group submitted - Availability: v6 - Mode: Application -## vrf_verify s +## vrf_verify -- Opcode: 0xd0 {uint8 parameters index} -- Stack: ..., A: []byte, B: []byte, C: []byte → ..., X: []byte, Y: uint64 +- Syntax: `vrf_verify S` ∋ S: [vrf_verify](#field-group-vrf_verify) +- Bytecode: 0xd0 {uint8} +- Stack: ..., A: []byte, B: []byte, C: []byte → ..., X: []byte, Y: bool - Verify the proof B of message A against pubkey C. Returns vrf output and verification flag. - **Cost**: 5700 - Availability: v7 -`vrf_verify` Standards: +### vrf_verify + +Standards | Index | Name | Notes | | - | ------ | --------- | @@ -1546,14 +1627,17 @@ For boxes that exceed 4,096 bytes, consider `box_create`, `box_extract`, and `bo `VrfAlgorand` is the VRF used in Algorand. It is ECVRF-ED25519-SHA512-Elligator2, specified in the IETF internet draft [draft-irtf-cfrg-vrf-03](https://datatracker.ietf.org/doc/draft-irtf-cfrg-vrf/03/). -## block f +## block -- Opcode: 0xd1 {uint8 block field index} +- Syntax: `block F` ∋ F: [block](#field-group-block) +- Bytecode: 0xd1 {uint8} - Stack: ..., A: uint64 → ..., any - field F of block A. Fail unless A falls between txn.LastValid-1002 and txn.FirstValid (exclusive) - Availability: v7 -`block` Fields: +### block + +Fields | Index | Name | Type | Notes | | - | ------ | -- | --------- | diff --git a/data/transactions/logic/assembler.go b/data/transactions/logic/assembler.go index a5631d43b0..7aa1ac48f8 100644 --- a/data/transactions/logic/assembler.go +++ b/data/transactions/logic/assembler.go @@ -274,7 +274,7 @@ func newOpStream(version uint64) OpStream { } for i := range o.known.scratchSpace { - o.known.scratchSpace[i] = StackUint64 + o.known.scratchSpace[i] = StackZeroUint64 } return o @@ -312,7 +312,7 @@ type ProgramKnowledge struct { func (pgm *ProgramKnowledge) top() (StackType, bool) { if len(pgm.stack) == 0 { - return pgm.bottom, pgm.bottom != StackNone + return pgm.bottom, pgm.bottom.AVMType != avmNone } last := len(pgm.stack) - 1 return pgm.stack[last], true @@ -387,7 +387,7 @@ func (ops *OpStream) returns(spec *OpSpec, replacement StackType) { end := len(ops.known.stack) tip := ops.known.stack[end-len(spec.Return.Types):] for i := range tip { - if tip[i] == StackAny { + if tip[i].AVMType == avmAny { tip[i] = replacement return } @@ -1236,7 +1236,7 @@ func typeBury(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, err idx := top - n if idx < 0 { - if pgm.bottom == StackNone { + if pgm.bottom.AVMType == avmNone { // By demanding n+1 elements, we'll trigger an error return anyTypes(n + 1), nil, nil } @@ -1316,8 +1316,10 @@ func typeFrameBury(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes func typeEquals(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, error) { top := len(pgm.stack) - 1 if top >= 0 { - //Require arg0 and arg1 to have same type - return StackTypes{pgm.stack[top], pgm.stack[top]}, nil, nil + // Require arg0 and arg1 to have same avm type + // but the bounds shouldn't matter + widened := pgm.stack[top].widened() + return StackTypes{widened, widened}, nil, nil } return nil, nil, nil } @@ -1345,9 +1347,7 @@ func typeDupTwo(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, e func typeSelect(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, error) { top := len(pgm.stack) - 1 if top >= 2 { - if pgm.stack[top-1] == pgm.stack[top-2] { - return nil, StackTypes{pgm.stack[top-1]}, nil - } + return nil, StackTypes{pgm.stack[top-1].union(pgm.stack[top-2])}, nil } return nil, nil, nil } @@ -1400,6 +1400,12 @@ func typeUncover(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, return anyTypes(depth), returns, nil } +func typeByteMath(resultSize uint64) refineFunc { + return func(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, error) { + return nil, StackTypes{NewStackType(avmBytes, bound(0, resultSize))}, nil + } +} + func typeTxField(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, error) { if len(args) != 1 { return nil, nil, nil @@ -1428,12 +1434,21 @@ func typeStores(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, e if top < 0 { return nil, nil, nil } - for i := range pgm.scratchSpace { - // We can't know what slot stacktop is being stored in, but we can at least keep the slots that are the same type as stacktop - if pgm.scratchSpace[i] != pgm.stack[top] { - pgm.scratchSpace[i] = StackAny + + // If the index of the scratch slot is a const + // we can modify only that scratch slots type + if top >= 1 { + if idx, isConst := pgm.stack[top-1].constant(); isConst { + pgm.scratchSpace[idx] = pgm.stack[top] + return nil, nil, nil } } + + for i := range pgm.scratchSpace { + // We can't know what slot stacktop is being stored in + // so we adjust the bounds and type of each slot as if the stacktop type were stored there. + pgm.scratchSpace[i] = pgm.scratchSpace[i].union(pgm.stack[top]) + } return nil, nil, nil } @@ -1452,7 +1467,7 @@ func typeProto(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, er return nil, nil, nil } - if len(pgm.stack) != 0 || pgm.bottom != StackAny { + if len(pgm.stack) != 0 || pgm.bottom.AVMType != avmAny { return nil, nil, fmt.Errorf("proto must be unreachable from previous PC") } pgm.stack = anyTypes(a) @@ -1461,6 +1476,16 @@ func typeProto(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, er } func typeLoads(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, error) { + + top := len(pgm.stack) - 1 + if top < 0 { + return nil, nil, nil + } + + if val, isConst := pgm.stack[top].constant(); isConst { + return nil, StackTypes{pgm.scratchSpace[val]}, nil + } + scratchType := pgm.scratchSpace[0] for _, item := range pgm.scratchSpace { // If all the scratch slots are one type, then we can say we are loading that type @@ -1516,6 +1541,33 @@ func typePushInts(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, return nil, types, nil } +func typePushInt(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, error) { + types := make(StackTypes, len(args)) + for i := range types { + val, err := strconv.ParseUint(args[i], 10, 64) + if err != nil { + types[i] = StackUint64 + } else { + types[i] = NewStackType(avmUint64, bound(val, val)) + } + } + return nil, types, nil +} + +func typeBzero(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, error) { + // Bzero should only allow its input int to be up to maxStringSize bytes + return StackTypes{StackUint64.narrowed(bound(0, maxStringSize))}, StackTypes{StackBytes}, nil +} + +func typeByte(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, error) { + if len(args) == 0 { + return nil, StackTypes{StackBytes}, nil + } + val, _, _ := parseBinaryArgs(args) + l := uint64(len(val)) + return nil, StackTypes{NewStackType(avmBytes, static(l), fmt.Sprintf("[%d]byte", l))}, nil +} + func joinIntsOnOr(singularTerminator string, list ...int) string { if len(list) == 1 { switch list[0] { @@ -1618,8 +1670,8 @@ func unknownOpcodeComplaint(name string, v uint64) (OpSpec, error) { const anyImmediates = -1 var pseudoOps = map[string]map[int]OpSpec{ - "int": {anyImmediates: OpSpec{Name: "int", Proto: proto(":i"), OpDetails: assembler(asmInt)}}, - "byte": {anyImmediates: OpSpec{Name: "byte", Proto: proto(":b"), OpDetails: assembler(asmByte)}}, + "int": {anyImmediates: OpSpec{Name: "int", Proto: proto(":i"), OpDetails: assembler(asmInt).typed(typePushInt)}}, + "byte": {anyImmediates: OpSpec{Name: "byte", Proto: proto(":b"), OpDetails: assembler(asmByte).typed(typeByte)}}, // parse basics.Address, actually just another []byte constant "addr": {anyImmediates: OpSpec{Name: "addr", Proto: proto(":b"), OpDetails: assembler(asmAddr)}}, // take a signature, hash it, and take first 4 bytes, actually just another []byte constant @@ -1725,18 +1777,6 @@ func (le lineError) Unwrap() error { return le.Err } -func typecheck(expected, got StackType) bool { - // Some ops push 'any' and we wait for run time to see what it is. - // Some of those 'any' are based on fields that we _could_ know now but haven't written a more detailed system of typecheck for (yet). - if expected == StackAny && got == StackNone { // Any is lenient, but stack can't be empty - return false - } - if (expected == StackAny) || (got == StackAny) { - return true - } - return expected == got -} - // newline not included since handled in scanner var tokenSeparators = [256]bool{'\t': true, ' ': true, ';': true} @@ -1845,7 +1885,7 @@ func (ops *OpStream) trackStack(args StackTypes, returns StackTypes, instruction return } argcount := len(args) - if argcount > len(ops.known.stack) && ops.known.bottom == StackNone { + if argcount > len(ops.known.stack) && ops.known.bottom.AVMType == avmNone { ops.typeErrorf("%s expects %d stack arguments but stack height is %d", strings.Join(instruction, " "), argcount, len(ops.known.stack)) } else { @@ -1859,7 +1899,7 @@ func (ops *OpStream) trackStack(args StackTypes, returns StackTypes, instruction } else { ops.trace(", %s", argType) } - if !typecheck(argType, stype) { + if !stype.overlaps(argType) { ops.typeErrorf("%s arg %d wanted type %s got %s", strings.Join(instruction, " "), i, argType, stype) } @@ -2018,7 +2058,7 @@ func (ops *OpStream) assemble(text string) error { if ops.Errors != nil { l := len(ops.Errors) if l == 1 { - return errors.New("1 error") + return fmt.Errorf("1 error: %w", ops.Errors[0]) } return fmt.Errorf("%d errors", l) } diff --git a/data/transactions/logic/assembler_test.go b/data/transactions/logic/assembler_test.go index 8f6b86998f..dbc665e26e 100644 --- a/data/transactions/logic/assembler_test.go +++ b/data/transactions/logic/assembler_test.go @@ -778,7 +778,7 @@ int 1 + // comment ` - testProg(t, source, AssemblerMaxVersion, Expect{3, "+ arg 0 wanted type uint64 got []byte"}) + testProg(t, source, AssemblerMaxVersion, Expect{3, "+ arg 0 wanted type uint64 got [5]byte"}) } // mutateProgVersion replaces version (first two symbols) in hex-encoded program @@ -1837,7 +1837,7 @@ balance int 1 ==` for v := uint64(2); v < directRefEnabledVersion; v++ { - testProg(t, source, v, Expect{2, "balance arg 0 wanted type uint64 got []byte"}) + testProg(t, source, v, Expect{2, "balance arg 0 wanted type uint64 got [1]byte"}) } for v := uint64(directRefEnabledVersion); v <= AssemblerMaxVersion; v++ { testProg(t, source, v) @@ -1853,7 +1853,7 @@ min_balance int 1 ==` for v := uint64(3); v < directRefEnabledVersion; v++ { - testProg(t, source, v, Expect{2, "min_balance arg 0 wanted type uint64 got []byte"}) + testProg(t, source, v, Expect{2, "min_balance arg 0 wanted type uint64 got [1]byte"}) } for v := uint64(directRefEnabledVersion); v <= AssemblerMaxVersion; v++ { testProg(t, source, v) @@ -2581,8 +2581,10 @@ func TestScratchTypeCheck(t *testing.T) { testProg(t, "byte 0x01; store 0; load 0; int 1; +", AssemblerMaxVersion, Expect{1, "+ arg 0..."}) // Loads should know the type it's loading if all the slots are the same type testProg(t, "int 0; loads; btoi", AssemblerMaxVersion, Expect{1, "btoi arg 0..."}) - // Loads doesn't know the type when slot types vary - testProg(t, "byte 0x01; store 0; int 1; loads; btoi", AssemblerMaxVersion) + // Loads only knows the type when slot is a const + testProg(t, "byte 0x01; store 0; int 1; loads; btoi", AssemblerMaxVersion, Expect{1, "btoi arg 0..."}) + // Loads doesnt know the type if its the result of some other expression where we lose information + testProg(t, "byte 0x01; store 0; load 0; btoi; loads; btoi", AssemblerMaxVersion) // Stores should only set slots to StackAny if they are not the same type as what is being stored testProg(t, "byte 0x01; store 0; int 3; byte 0x01; stores; load 0; int 1; +", AssemblerMaxVersion, Expect{1, "+ arg 0..."}) // ScratchSpace should reset after hitting label in deadcode @@ -2593,6 +2595,45 @@ func TestScratchTypeCheck(t *testing.T) { testProg(t, "callsub A; load 0; btoi; return; A: byte 0x01; store 0; retsub", AssemblerMaxVersion) // But the scratchspace should still be tracked after the callsub testProg(t, "callsub A; int 1; store 0; load 0; btoi; return; A: retsub", AssemblerMaxVersion, Expect{1, "btoi arg 0..."}) + +} + +func TestScratchBounds(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + os := testProg(t, "int 5; store 1; load 1; return;", AssemblerMaxVersion) + sv := os.known.scratchSpace[1] + require.Equal(t, sv.AVMType, avmUint64) + require.ElementsMatch(t, sv.Bound, static(5)) + + os = testProg(t, "int 5; store 1; load 1; int 1; int 1; stores; return;", AssemblerMaxVersion) + sv = os.known.scratchSpace[1] + require.Equal(t, sv.AVMType, avmUint64) + require.ElementsMatch(t, sv.Bound, bound(1, 1)) + + // If the stack type for the slot index is a const, known at assembly time + // we can be sure of the slot we need to update + os = testProg(t, "int 5; store 1; load 1; int 1; byte 0xff; stores; return;", AssemblerMaxVersion) + sv = os.known.scratchSpace[1] + require.Equal(t, sv.AVMType, avmBytes) + require.ElementsMatch(t, sv.Bound, static(1)) + + osv := os.known.scratchSpace[0] + require.Equal(t, osv.AVMType, avmUint64) + require.ElementsMatch(t, osv.Bound, static(0)) + + // Otherwise, we just union all stack types with the incoming type + os = testProg(t, "int 5; store 1; load 1; byte 0xaa; btoi; byte 0xff; stores; return;", AssemblerMaxVersion) + sv = os.known.scratchSpace[1] + require.Equal(t, sv.AVMType, avmAny) + require.ElementsMatch(t, sv.Bound, static(0)) + + osv = os.known.scratchSpace[0] + require.Equal(t, osv.AVMType, avmAny) + require.ElementsMatch(t, osv.Bound, static(0)) + + testProg(t, "byte 0xff; store 1; load 1; return", AssemblerMaxVersion, Expect{1, "return arg 0 wanted type uint64 ..."}) } // TestProtoAsm confirms that the assembler will yell at you if you are @@ -2644,11 +2685,12 @@ func TestTxTypes(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() testProg(t, "itxn_begin; itxn_field Sender", 5, Expect{1, "itxn_field Sender expects 1 stack argument..."}) - testProg(t, "itxn_begin; int 1; itxn_field Sender", 5, Expect{1, "...wanted type []byte got uint64"}) - testProg(t, "itxn_begin; byte 0x56127823; itxn_field Sender", 5) + testProg(t, "itxn_begin; int 1; itxn_field Sender", 5, Expect{1, "...wanted type address got 1"}) + testProg(t, "itxn_begin; byte 0x56127823; itxn_field Sender", 5, Expect{1, "...wanted type address got [4]byte"}) + testProg(t, "itxn_begin; global ZeroAddress; itxn_field Sender", 5) testProg(t, "itxn_begin; itxn_field Amount", 5, Expect{1, "itxn_field Amount expects 1 stack argument..."}) - testProg(t, "itxn_begin; byte 0x87123376; itxn_field Amount", 5, Expect{1, "...wanted type uint64 got []byte"}) + testProg(t, "itxn_begin; byte 0x87123376; itxn_field Amount", 5, Expect{1, "...wanted type uint64 got [4]byte"}) testProg(t, "itxn_begin; int 1; itxn_field Amount", 5) } @@ -2660,14 +2702,14 @@ func TestBadInnerFields(t *testing.T) { testProg(t, "itxn_begin; int 1000; itxn_field LastValid", 5, Expect{1, "...is not allowed."}) testProg(t, "itxn_begin; int 32; bzero; itxn_field Lease", 5, Expect{1, "...is not allowed."}) testProg(t, "itxn_begin; byte 0x7263; itxn_field Note", 5, Expect{1, "...Note field was introduced in v6..."}) - testProg(t, "itxn_begin; byte 0x7263; itxn_field VotePK", 5, Expect{1, "...VotePK field was introduced in v6..."}) + testProg(t, "itxn_begin; global ZeroAddress; itxn_field VotePK", 5, Expect{1, "...VotePK field was introduced in v6..."}) testProg(t, "itxn_begin; int 32; bzero; itxn_field TxID", 5, Expect{1, "...is not allowed."}) testProg(t, "itxn_begin; int 1000; itxn_field FirstValid", 6, Expect{1, "...is not allowed."}) testProg(t, "itxn_begin; int 1000; itxn_field LastValid", 6, Expect{1, "...is not allowed."}) testProg(t, "itxn_begin; int 32; bzero; itxn_field Lease", 6, Expect{1, "...is not allowed."}) testProg(t, "itxn_begin; byte 0x7263; itxn_field Note", 6) - testProg(t, "itxn_begin; byte 0x7263; itxn_field VotePK", 6) + testProg(t, "itxn_begin; global ZeroAddress; itxn_field VotePK", 6) testProg(t, "itxn_begin; int 32; bzero; itxn_field TxID", 6, Expect{1, "...is not allowed."}) } @@ -2821,7 +2863,7 @@ func TestReplacePseudo(t *testing.T) { testProg(t, "byte 0x0000; byte 0x1234; replace 0", v) testProg(t, "byte 0x0000; int 0; byte 0x1234; replace", v) testProg(t, "byte 0x0000; byte 0x1234; replace", v, Expect{1, "replace without immediates expects 3 stack arguments but stack height is 2"}) - testProg(t, "byte 0x0000; int 0; byte 0x1234; replace 0", v, Expect{1, "replace 0 arg 0 wanted type []byte got uint64"}) + testProg(t, "byte 0x0000; int 0; byte 0x1234; replace 0", v, Expect{1, "replace 0 arg 0 wanted type []byte got 0"}) } } diff --git a/data/transactions/logic/box_test.go b/data/transactions/logic/box_test.go index 9a98a6455d..323a64e5a8 100644 --- a/data/transactions/logic/box_test.go +++ b/data/transactions/logic/box_test.go @@ -90,10 +90,10 @@ func TestBoxNewBad(t *testing.T) { long := strings.Repeat("x", 65) txn.Boxes = []transactions.BoxRef{{Name: []byte(long)}} - TestApp(t, fmt.Sprintf(`byte "%s"; int 1000; box_create`, long), ep, "name too long") + TestApp(t, NoTrack(fmt.Sprintf(`byte "%s"; int 1000; box_create`, long)), ep, "name too long") txn.Boxes = []transactions.BoxRef{{Name: []byte("")}} // irrelevant, zero check comes first anyway - TestApp(t, `byte ""; int 1000; box_create`, ep, "zero length") + TestApp(t, NoTrack(`byte ""; int 1000; box_create`), ep, "zero length") } func TestBoxReadWrite(t *testing.T) { @@ -531,7 +531,7 @@ func TestEarlyPanics(t *testing.T) { t.Parallel() ep, _, l := MakeSampleEnv() l.NewApp(basics.Address{}, 888, basics.AppParams{}) - TestApp(t, fmt.Sprintf(program, ""), ep, "zero length") + TestApp(t, NoTrack(fmt.Sprintf(program, "")), ep, "zero length") }) } @@ -542,7 +542,7 @@ func TestEarlyPanics(t *testing.T) { t.Parallel() ep, _, l := MakeSampleEnv() l.NewApp(basics.Address{}, 888, basics.AppParams{}) - TestApp(t, fmt.Sprintf(program, big), ep, "name too long") + TestApp(t, NoTrack(fmt.Sprintf(program, big)), ep, "name too long") }) } diff --git a/data/transactions/logic/doc.go b/data/transactions/logic/doc.go index c64dbff672..e4e5248802 100644 --- a/data/transactions/logic/doc.go +++ b/data/transactions/logic/doc.go @@ -17,6 +17,8 @@ package logic import ( + "strings" + "github.com/algorand/go-algorand/protocol" ) @@ -219,84 +221,113 @@ func OpDoc(opName string) string { return opDocByName[opName] } -var opcodeImmediateNotes = map[string]string{ - "intcblock": "{varuint count} [{varuint value}, ...]", - "intc": "{uint8 int constant index}", - "pushint": "{varuint int}", - "pushints": "{varuint count} [{varuint value}, ...]", - "bytecblock": "{varuint count} [({varuint length} bytes), ...]", - "bytec": "{uint8 byte constant index}", - "pushbytes": "{varuint length} {bytes}", - "pushbytess": "{varuint count} [({varuint length} bytes), ...]", - - "arg": "{uint8 arg index}", - "global": "{uint8 global field index}", - - "txn": "{uint8 transaction field index}", - "gtxn": "{uint8 transaction group index} {uint8 transaction field index}", - "gtxns": "{uint8 transaction field index}", - "txna": "{uint8 transaction field index} {uint8 transaction field array index}", - "gtxna": "{uint8 transaction group index} {uint8 transaction field index} {uint8 transaction field array index}", - "gtxnsa": "{uint8 transaction field index} {uint8 transaction field array index}", - "txnas": "{uint8 transaction field index}", - "gtxnas": "{uint8 transaction group index} {uint8 transaction field index}", - "gtxnsas": "{uint8 transaction field index}", - - "bnz": "{int16 branch offset, big-endian}", - "bz": "{int16 branch offset, big-endian}", - "b": "{int16 branch offset, big-endian}", - "callsub": "{int16 branch offset, big-endian}", - - "load": "{uint8 position in scratch space to load from}", - "store": "{uint8 position in scratch space to store to}", - "gload": "{uint8 transaction group index} {uint8 position in scratch space to load from}", - "gloads": "{uint8 position in scratch space to load from}", - "gaid": "{uint8 transaction group index}", - - "substring": "{uint8 start position} {uint8 end position}", - "extract": "{uint8 start position} {uint8 length}", - "replace2": "{uint8 start position}", - "dig": "{uint8 depth}", - "bury": "{uint8 depth}", - "cover": "{uint8 depth}", - "uncover": "{uint8 depth}", - - "asset_holding_get": "{uint8 asset holding field index}", - "asset_params_get": "{uint8 asset params field index}", - "app_params_get": "{uint8 app params field index}", - "acct_params_get": "{uint8 account params field index}", - - "itxn_field": "{uint8 transaction field index}", - "itxn": "{uint8 transaction field index}", - "itxna": "{uint8 transaction field index} {uint8 transaction field array index}", - "itxnas": "{uint8 transaction field index}", - "gitxn": "{uint8 transaction group index} {uint8 transaction field index}", - "gitxna": "{uint8 transaction group index} {uint8 transaction field index} {uint8 transaction field array index}", - "gitxnas": "{uint8 transaction group index} {uint8 transaction field index}", - - "ecdsa_verify": "{uint8 curve index}", - "ecdsa_pk_decompress": "{uint8 curve index}", - "ecdsa_pk_recover": "{uint8 curve index}", - - "base64_decode": "{uint8 encoding index}", - "json_ref": "{uint8 return type index}", - - "vrf_verify": "{uint8 parameters index}", - "block": "{uint8 block field index}", - - "switch": "{uint8 branch count} [{int16 branch offset, big-endian}, ...]", - "match": "{uint8 branch count} [{int16 branch offset, big-endian}, ...]", - - "proto": "{uint8 arguments} {uint8 return values}", - "frame_dig": "{int8 frame slot}", - "frame_bury": "{int8 frame slot}", - "popn": "{uint8 stack depth}", - "dupn": "{uint8 copy count}", +var opcodeImmediateNotes = map[string][]string{ + "intcblock": {"a block of int constant values"}, + "intc": {"an index in the intcblock"}, + "pushint": {"an int constant"}, + "pushints": {"a list of int constants"}, + "bytecblock": {"a block of byte constant values"}, + "bytec": {"an index in the bytecblock"}, + "pushbytes": {"a byte constant"}, + "pushbytess": {"a list of byte constants"}, + + "arg": {"an arg index"}, + "global": {"a global field index"}, + + "txn": {"transaction field index"}, + "gtxn": {"transaction group index", "transaction field index"}, + "gtxns": {"transaction field index"}, + "txna": {"transaction field index", "transaction field array index"}, + "gtxna": {"transaction group index", "transaction field index", "transaction field array index"}, + "gtxnsa": {"transaction field index", "transaction field array index"}, + "txnas": {"transaction field index"}, + "gtxnas": {"transaction group index", "transaction field index"}, + "gtxnsas": {"transaction field index"}, + + "bnz": {"branch offset"}, + "bz": {"branch offset"}, + "b": {"branch offset"}, + "callsub": {"branch offset"}, + + "load": {"position in scratch space to load from"}, + "store": {"position in scratch space to store to"}, + "gload": {"transaction group index", "position in scratch space to load from"}, + "gloads": {"position in scratch space to load from"}, + "gaid": {"transaction group index"}, + + "substring": {"start position", "end position"}, + "extract": {"start position", "length"}, + "replace2": {"start position"}, + "dig": {"depth"}, + "bury": {"depth"}, + "cover": {"depth"}, + "uncover": {"depth"}, + + "asset_holding_get": {"asset holding field index"}, + "asset_params_get": {"asset params field index"}, + "app_params_get": {"app params field index"}, + "acct_params_get": {"account params field index"}, + + "itxn_field": {"transaction field index"}, + "itxn": {"transaction field index"}, + "itxna": {"transaction field index", "a transaction field array index"}, + "itxnas": {"transaction field index"}, + "gitxn": {"transaction group index", "transaction field index"}, + "gitxna": {"transaction group index", "transaction field index", "transaction field array index"}, + "gitxnas": {"transaction group index", "transaction field index"}, + + "ecdsa_verify": {"curve index"}, + "ecdsa_pk_decompress": {"curve index"}, + "ecdsa_pk_recover": {"curve index"}, + + "base64_decode": {"encoding index"}, + "json_ref": {"return type index"}, + + "vrf_verify": {" parameters index"}, + "block": {" block field index"}, + + "switch": {"list of labels"}, + "match": {"list of labels"}, + + "proto": {"number of arguments", "number of return values"}, + "frame_dig": {"frame slot"}, + "frame_bury": {"frame slot"}, + "popn": {"stack depth"}, + "dupn": {"copy count"}, +} + +// OpImmediateDetails contains information about the an immediate argument for +// a given opcode, combining OpSpec details with the extra note in +// the opcodeImmediateNotes map +type OpImmediateDetails struct { + Comment string `json:",omitempty"` + Encoding string `json:",omitempty"` + Name string `json:",omitempty"` + Reference string `json:",omitempty"` } -// OpImmediateNote returns a short string about immediate data which follows the op byte -func OpImmediateNote(opName string) string { - return opcodeImmediateNotes[opName] +// OpImmediateDetailsFromSpec provides a slice of OpImmediateDetails +// for a given OpSpec +func OpImmediateDetailsFromSpec(spec OpSpec) []OpImmediateDetails { + argNotes := opcodeImmediateNotes[spec.Name] + if len(argNotes) == 0 { + return nil + } + + details := make([]OpImmediateDetails, len(spec.Immediates)) + for idx, imm := range spec.Immediates { + details[idx] = OpImmediateDetails{ + Name: strings.ToTitle(imm.Name), + Comment: argNotes[idx], + Encoding: imm.kind.String(), + } + + if imm.Group != nil { + details[idx].Reference = imm.Group.Name + } + } + + return details } // further documentation on the function of the opcode diff --git a/data/transactions/logic/doc_test.go b/data/transactions/logic/doc_test.go index 404a934d99..cf1c6f0295 100644 --- a/data/transactions/logic/doc_test.go +++ b/data/transactions/logic/doc_test.go @@ -98,43 +98,23 @@ func TestOpDoc(t *testing.T) { require.Empty(t, xd) } -func TestOpImmediateNote(t *testing.T) { +func TestOpImmediateDetails(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - xd := OpImmediateNote("txn") - require.NotEmpty(t, xd) - xd = OpImmediateNote("+") - require.Empty(t, xd) -} + for _, os := range OpSpecs { + deets := OpImmediateDetailsFromSpec(os) + require.Equal(t, len(os.Immediates), len(deets)) -func TestAllImmediatesDocumented(t *testing.T) { - partitiontest.PartitionTest(t) - t.Parallel() + for idx, d := range deets { + imm := os.Immediates[idx] + require.NotEmpty(t, d.Comment) + require.Equal(t, strings.ToLower(d.Name), imm.Name) + require.Equal(t, d.Encoding, imm.kind.String()) - for _, op := range OpSpecs { - count := len(op.Immediates) - note := OpImmediateNote(op.Name) - if count == 1 && op.Immediates[0].kind >= immBytes { - // More elaborate than can be checked by easy count. - assert.NotEmpty(t, note) - continue - } - assert.Equal(t, count, strings.Count(note, "{"), "opcodeImmediateNotes for %s is wrong", op.Name) - assert.Equal(t, count, strings.Count(note, "}"), "opcodeImmediateNotes for %s is wrong", op.Name) - for _, imm := range op.Immediates { - switch imm.kind { - case immByte: - require.True(t, strings.HasPrefix(note, "{uint8 "), "%v %v", op.Name, note) - case immInt8: - require.True(t, strings.HasPrefix(note, "{int8 "), "%v %v", op.Name, note) - case immLabel: - require.True(t, strings.HasPrefix(note, "{int16 "), "%v %v", op.Name, note) - case immInt: - require.True(t, strings.HasPrefix(note, "{varuint "), "%v %v", op.Name, note) + if imm.Group != nil { + require.Equal(t, d.Reference, imm.Group.Name) } - close := strings.Index(note, "}") - note = strings.TrimPrefix(note[close+1:], " ") } } } diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index cbf93a9a9f..af54a23f93 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -82,11 +82,18 @@ type stackValue struct { Bytes []byte } -func (sv stackValue) argType() StackType { +func (sv stackValue) avmType() avmType { if sv.Bytes != nil { - return StackBytes + return avmBytes + } + return avmUint64 +} + +func (sv stackValue) stackType() StackType { + if sv.Bytes != nil { + return NewStackType(sv.avmType(), static(uint64(len(sv.Bytes)))) } - return StackUint64 + return NewStackType(sv.avmType(), static(sv.Uint)) } func (sv stackValue) typeName() string { @@ -154,7 +161,7 @@ func (sv stackValue) string(limit int) (string, error) { } func (sv stackValue) toTealValue() (tv basics.TealValue) { - if sv.argType() == StackBytes { + if sv.avmType() == avmBytes { return basics.TealValue{Type: basics.TealBytesType, Bytes: string(sv.Bytes)} } return basics.TealValue{Type: basics.TealUintType, Uint: sv.Uint} @@ -613,71 +620,276 @@ func (cx *EvalContext) RunMode() RunMode { return cx.runModeFlags } -// StackType describes the type of a value on the operand stack -type StackType byte +// avmType describes the type of a value on the operand stack +// avmTypes are a subset of StackTypes +type avmType byte const ( - // StackNone in an OpSpec shows that the op pops or yields nothing - StackNone StackType = iota + // avmNone in an OpSpec shows that the op pops or yields nothing + avmNone avmType = iota - // StackAny in an OpSpec shows that the op pops or yield any type - StackAny + // avmAny in an OpSpec shows that the op pops or yield any type + avmAny - // StackUint64 in an OpSpec shows that the op pops or yields a uint64 - StackUint64 + // avmUint64 in an OpSpec shows that the op pops or yields a uint64 + avmUint64 - // StackBytes in an OpSpec shows that the op pops or yields a []byte - StackBytes + // avmBytes in an OpSpec shows that the op pops or yields a []byte + avmBytes ) -// StackTypes is an alias for a list of StackType with syntactic sugar -type StackTypes []StackType +func (at avmType) String() string { + switch at { + case avmNone: + return "none" + case avmAny: + return "any" + case avmUint64: + return "uint64" + case avmBytes: + return "[]byte" + } + return "internal error, unknown type" +} -func parseStackTypes(spec string) StackTypes { - if spec == "" { - return nil +// stackType lifts the avmType to a StackType +// it can do this because the base StackTypes +// are a superset of avmType +func (at avmType) stackType() StackType { + switch at { + case avmNone: + return StackNone + case avmAny: + return StackAny + case avmUint64: + return StackUint64 + case avmBytes: + return StackBytes } - types := make(StackTypes, len(spec)) - for i, letter := range spec { - switch letter { - case 'a': - types[i] = StackAny - case 'b': - types[i] = StackBytes - case 'i': - types[i] = StackUint64 - case 'x': - types[i] = StackNone - default: - panic(spec) + return StackNone +} + +var ( + // StackUint64 is any valid uint64 + StackUint64 = NewStackType(avmUint64, bound(0, math.MaxUint64)) + // StackBytes is any valid bytestring + StackBytes = NewStackType(avmBytes, bound(0, maxStringSize)) + // StackAny could be Bytes or Uint64 + StackAny = StackType{ + Name: avmAny.String(), + AVMType: avmAny, + Bound: [2]uint64{0, 0}, + } + // StackNone is used when there is no input or output to + // an opcode + StackNone = StackType{ + Name: avmNone.String(), + AVMType: avmNone, + } + + // StackBoolean constrains the int to 1 or 0, representing True or False + StackBoolean = NewStackType(avmUint64, bound(0, 1), "bool") + // StackAddress represents an address + StackAddress = NewStackType(avmBytes, static(32), "address") + // StackBytes32 represents a bytestring that should have exactly 32 bytes + StackBytes32 = NewStackType(avmBytes, static(32), "[32]byte") + // StackBigInt represents a bytestring that should be treated like an int + StackBigInt = NewStackType(avmBytes, bound(0, maxByteMathSize), "bigint") + // StackMethodSelector represents a bytestring that should be treated like a method selector + StackMethodSelector = NewStackType(avmBytes, static(4), "method") + // StackStateKey represents a bytestring that can be used as a key to some storage (global/local/box) + StackStateKey = NewStackType(avmBytes, bound(0, 64), "stateKey") + // StackBoxName represents a bytestring that can be used as a key to a box + StackBoxName = NewStackType(avmBytes, bound(1, 64), "boxName") + + // StackZeroUint64 is a StackUint64 with a minimum value of 0 and a maximum value of 0 + StackZeroUint64 = NewStackType(avmUint64, bound(0, 0), "0") + // StackZeroBytes is a StackBytes with a minimum length of 0 and a maximum length of 0 + StackZeroBytes = NewStackType(avmUint64, bound(0, 0), "''") + + // AllStackTypes is a map of all the stack types we recognize + // so that we can iterate over them in doc prep + // and use them for opcode proto shorthand + AllStackTypes = map[rune]StackType{ + 'a': StackAny, + 'b': StackBytes, + 'i': StackUint64, + 'x': StackNone, + 'A': StackAddress, + 'I': StackBigInt, + 'T': StackBoolean, + 'H': StackBytes32, + 'M': StackMethodSelector, + 'K': StackStateKey, + 'N': StackBoxName, + } +) + +func bound(min, max uint64) [2]uint64 { + return [2]uint64{min, max} +} + +func static(size uint64) [2]uint64 { + return bound(size, size) +} + +func union(a, b [2]uint64) [2]uint64 { + u := [2]uint64{a[0], a[1]} + if b[0] < u[0] { + u[0] = b[0] + } + + if b[1] > u[1] { + u[1] = b[1] + } + return u +} + +// StackType describes the type of a value on the operand stack +type StackType struct { + Name string // alias (address, boolean, ...) or derived name [5]byte + AVMType avmType + Bound [2]uint64 // represents max/min value for uint64 or max/min length for byte[] +} + +// NewStackType Initializes a new StackType with fields passed +func NewStackType(at avmType, bounds [2]uint64, stname ...string) StackType { + name := at.String() + + // It's static, set the name to show + // the static value + if bounds[0] == bounds[1] { + switch at { + case avmBytes: + name = fmt.Sprintf("[%d]byte", bounds[0]) + case avmUint64: + name = fmt.Sprintf("%d", bounds[0]) } } - return types + + if len(stname) > 0 { + name = stname[0] + } + + return StackType{Name: name, AVMType: at, Bound: bounds} } -func (st StackType) String() string { - switch st { - case StackNone: - return "None" - case StackAny: - return "any" - case StackUint64: - return "uint64" - case StackBytes: - return "[]byte" +func (st StackType) union(b StackType) StackType { + // TODO: Can we ever receive one or the other + // as None? should that be a panic? + if st.AVMType != b.AVMType { + return StackAny } - return "internal error, unknown type" + + // Same type now, so we can just take the union of the bounds + return NewStackType(st.AVMType, union(st.Bound, b.Bound)) +} + +func (st StackType) narrowed(bounds [2]uint64) StackType { + return NewStackType(st.AVMType, bounds) +} + +func (st StackType) widened() StackType { + // Take only the avm type + switch st.AVMType { + case avmBytes: + return StackBytes + case avmUint64: + return StackUint64 + case avmAny: + return StackAny + default: + panic(fmt.Sprintf("What are you tyring to widen?: %+v", st)) + } +} + +func (st StackType) constant() (uint64, bool) { + if st.Bound[0] == st.Bound[1] { + return st.Bound[0], true + } + return 0, false +} + +// overlaps checks if there is enough overlap +// between the given types that the receiver can +// possible fit in the expected type +func (st StackType) overlaps(expected StackType) bool { + if st.AVMType == avmNone || expected.AVMType == avmNone { + return false + } + + if st.AVMType == avmAny || expected.AVMType == avmAny { + return true + } + + // By now, both are either uint or bytes + // and must match + if st.AVMType != expected.AVMType { + return false + } + + // Same type now + // Check if our constraints will satisfy the other type + smin, smax := st.Bound[0], st.Bound[1] + emin, emax := expected.Bound[0], expected.Bound[1] + + return smin <= emax && smax >= emin +} + +func (st StackType) String() string { + return st.Name } // Typed tells whether the StackType is a specific concrete type. func (st StackType) Typed() bool { - switch st { - case StackUint64, StackBytes: + switch st.AVMType { + case avmUint64, avmBytes: return true } return false } +// StackTypes is an alias for a list of StackType with syntactic sugar +type StackTypes []StackType + +// Reversed returns the StackTypes in reverse order +// useful for displaying the stack as an op sees it +func (st StackTypes) Reversed() StackTypes { + nst := make(StackTypes, len(st)) + for idx := 0; idx < len(st); idx++ { + nst[idx] = st[len(st)-1-idx] + } + return nst +} + +func (st StackTypes) String() string { + // Note this reverses the stack so top appears first + return fmt.Sprintf("(%s)", strings.Join(st.strings(), ", ")) +} + +func (st StackTypes) strings() []string { + var strs = make([]string, len(st)) + for idx, s := range st { + strs[idx] = s.String() + } + return strs +} + +func parseStackTypes(spec string) StackTypes { + if spec == "" { + return nil + } + types := make(StackTypes, len(spec)) + for i, letter := range spec { + st, ok := AllStackTypes[letter] + if !ok { + panic(spec) + } + types[i] = st + } + return types +} + // panicError wraps a recover() catching a panic() type panicError struct { PanicValue interface{} @@ -1037,8 +1249,8 @@ func versionCheck(program []byte, params *EvalParams) (uint64, int, error) { return version, vlen, nil } -func opCompat(expected, got StackType) bool { - if expected == StackAny { +func opCompat(expected, got avmType) bool { + if expected == avmAny { return true } return expected == got @@ -1121,7 +1333,7 @@ func (cx *EvalContext) step() error { } first := len(cx.stack) - len(spec.Arg.Types) for i, argType := range spec.Arg.Types { - if !opCompat(argType, cx.stack[first+i].argType()) { + if !opCompat(argType.AVMType, cx.stack[first+i].avmType()) { return fmt.Errorf("%s arg %d wanted %s but got %s", spec.Name, i, argType, cx.stack[first+i].typeName()) } } @@ -1169,14 +1381,14 @@ func (cx *EvalContext) step() error { } first = postheight - len(spec.Return.Types) for i, argType := range spec.Return.Types { - stackType := cx.stack[first+i].argType() - if !opCompat(argType, stackType) { + stackType := cx.stack[first+i].avmType() + if !opCompat(argType.AVMType, stackType) { if spec.AlwaysExits() { // We test in the loop because it's the uncommon case. break } return fmt.Errorf("%s produced %s but intended %s", spec.Name, cx.stack[first+i].typeName(), argType) } - if stackType == StackBytes && len(cx.stack[first+i].Bytes) > maxStringSize { + if stackType == avmBytes && len(cx.stack[first+i].Bytes) > maxStringSize { return fmt.Errorf("%s produced a too big (%d) byte-array", spec.Name, len(cx.stack[first+i].Bytes)) } } @@ -1566,13 +1778,13 @@ func opOr(cx *EvalContext) error { func opEq(cx *EvalContext) error { last := len(cx.stack) - 1 prev := last - 1 - ta := cx.stack[prev].argType() - tb := cx.stack[last].argType() + ta := cx.stack[prev].avmType() + tb := cx.stack[last].avmType() if ta != tb { return fmt.Errorf("cannot compare (%s to %s)", cx.stack[prev].typeName(), cx.stack[last].typeName()) } var cond bool - if ta == StackBytes { + if ta == avmBytes { cond = bytes.Equal(cx.stack[prev].Bytes, cx.stack[last].Bytes) } else { cond = cx.stack[prev].Uint == cx.stack[last].Uint @@ -1608,7 +1820,7 @@ func opItob(cx *EvalContext) error { ibytes := make([]byte, 8) binary.BigEndian.PutUint64(ibytes, cx.stack[last].Uint) // cx.stack[last].Uint is not cleared out as optimization - // stackValue.argType() checks Bytes field first + // stackValue.avmType() checks Bytes field first cx.stack[last].Bytes = ibytes return nil } @@ -1711,7 +1923,7 @@ func opSqrt(cx *EvalContext) error { func opBitLen(cx *EvalContext) error { last := len(cx.stack) - 1 - if cx.stack[last].argType() == StackUint64 { + if cx.stack[last].avmType() == avmUint64 { cx.stack[last].Uint = uint64(bits.Len64(cx.stack[last].Uint)) return nil } @@ -2365,14 +2577,14 @@ func opMatch(cx *EvalContext) error { matchedIdx := n for i, stackArg := range matchList { - if stackArg.argType() != matchVal.argType() { + if stackArg.avmType() != matchVal.avmType() { continue } - if matchVal.argType() == StackBytes && bytes.Equal(matchVal.Bytes, stackArg.Bytes) { + if matchVal.avmType() == avmBytes && bytes.Equal(matchVal.Bytes, stackArg.Bytes) { matchedIdx = i break - } else if matchVal.argType() == StackUint64 && matchVal.Uint == stackArg.Uint { + } else if matchVal.avmType() == avmUint64 && matchVal.Uint == stackArg.Uint { matchedIdx = i break } @@ -2510,8 +2722,8 @@ func (cx *EvalContext) assetHoldingToValue(holding *basics.AssetHolding, fs asse return sv, fmt.Errorf("invalid asset_holding_get field %d", fs.field) } - if fs.ftype != sv.argType() { - return sv, fmt.Errorf("%s expected field type is %s but got %s", fs.field, fs.ftype, sv.argType()) + if fs.ftype.AVMType != sv.avmType() { + return sv, fmt.Errorf("%s expected field type is %s but got %s", fs.field, fs.ftype, sv.avmType()) } return sv, nil } @@ -2546,8 +2758,8 @@ func (cx *EvalContext) assetParamsToValue(params *basics.AssetParams, creator ba return sv, fmt.Errorf("invalid asset_params_get field %d", fs.field) } - if fs.ftype != sv.argType() { - return sv, fmt.Errorf("%s expected field type is %s but got %s", fs.field, fs.ftype, sv.argType()) + if fs.ftype.AVMType != sv.avmType() { + return sv, fmt.Errorf("%s expected field type is %s but got %s", fs.field, fs.ftype, sv.avmType()) } return sv, nil } @@ -2573,8 +2785,8 @@ func (cx *EvalContext) appParamsToValue(params *basics.AppParams, fs appParamsFi return sv, fmt.Errorf("invalid app_params_get field %d", fs.field) } - if fs.ftype != sv.argType() { - return sv, fmt.Errorf("%s expected field type is %s but got %s", fs.field, fs.ftype, sv.argType()) + if fs.ftype.AVMType != sv.avmType() { + return sv, fmt.Errorf("%s expected field type is %s but got %s", fs.field, fs.ftype, sv.avmType()) } return sv, nil } @@ -2907,8 +3119,8 @@ func (cx *EvalContext) txnFieldToStack(stxn *transactions.SignedTxnWithAD, fs *t return sv, fmt.Errorf("invalid txn field %s", fs.field) } - if fs.ftype != sv.argType() { - return sv, fmt.Errorf("%s expected field type is %s but got %s", fs.field, fs.ftype, sv.argType()) + if fs.ftype.AVMType != sv.avmType() { + return sv, fmt.Errorf("%s expected field type is %s but got %s", fs.field, fs.ftype, sv.avmType()) } return sv, nil } @@ -3355,8 +3567,8 @@ func (cx *EvalContext) globalFieldToValue(fs globalFieldSpec) (sv stackValue, er err = fmt.Errorf("invalid global field %d", fs.field) } - if fs.ftype != sv.argType() { - return sv, fmt.Errorf("%s expected field type is %s but got %s", fs.field, fs.ftype, sv.argType()) + if fs.ftype.AVMType != sv.avmType() { + return sv, fmt.Errorf("%s expected field type is %s but got %s", fs.field, fs.ftype, sv.avmType()) } return sv, err @@ -3784,7 +3996,7 @@ func opGetBit(cx *EvalContext) error { target := cx.stack[prev] var bit uint64 - if target.argType() == StackUint64 { + if target.avmType() == avmUint64 { if idx > 63 { return errors.New("getbit index > 63 with with Uint") } @@ -3826,7 +4038,7 @@ func opSetBit(cx *EvalContext) error { return errors.New("setbit value > 1") } - if target.argType() == StackUint64 { + if target.avmType() == avmUint64 { if idx > 63 { return errors.New("setbit index > 63 with Uint") } @@ -4085,7 +4297,7 @@ func (cx *EvalContext) accountReference(account stackValue) (basics.Address, uin // it in txn.Accounts or returning -1), or it is performing the lookup of the // integer arg in txn.Accounts. func (cx *EvalContext) resolveAccount(account stackValue) (basics.Address, int, error) { - if account.argType() == StackUint64 { + if account.avmType() == avmUint64 { addr, err := cx.txn.Txn.AddressByIndex(account.Uint, cx.txn.Txn.Sender) return addr, int(account.Uint), err } diff --git a/data/transactions/logic/evalAppTxn_test.go b/data/transactions/logic/evalAppTxn_test.go index 0204d04e98..22c0e1691d 100644 --- a/data/transactions/logic/evalAppTxn_test.go +++ b/data/transactions/logic/evalAppTxn_test.go @@ -105,15 +105,16 @@ func TestFieldTypes(t *testing.T) { t.Parallel() ep, _, _ := MakeSampleEnv() - TestApp(t, "itxn_begin; byte \"pay\"; itxn_field Sender;", ep, "not an address") + // Use NoTrack to skip assembly errors + TestApp(t, NoTrack("itxn_begin; byte \"pay\"; itxn_field Sender;"), ep, "not an address") TestApp(t, NoTrack("itxn_begin; int 7; itxn_field Receiver;"), ep, "not an address") - TestApp(t, "itxn_begin; byte \"\"; itxn_field CloseRemainderTo;", ep, "not an address") - TestApp(t, "itxn_begin; byte \"\"; itxn_field AssetSender;", ep, "not an address") + TestApp(t, NoTrack("itxn_begin; byte \"\"; itxn_field CloseRemainderTo;"), ep, "not an address") + TestApp(t, NoTrack("itxn_begin; byte \"\"; itxn_field AssetSender;"), ep, "not an address") // can't really tell if it's an addres, so 32 bytes gets further TestApp(t, "itxn_begin; byte \"01234567890123456789012345678901\"; itxn_field AssetReceiver; int 1", ep, "invalid Account reference") // but a b32 string rep is not an account - TestApp(t, "itxn_begin; byte \"GAYTEMZUGU3DOOBZGAYTEMZUGU3DOOBZGAYTEMZUGU3DOOBZGAYZIZD42E\"; itxn_field AssetCloseTo;", + TestApp(t, NoTrack("itxn_begin; byte \"GAYTEMZUGU3DOOBZGAYTEMZUGU3DOOBZGAYTEMZUGU3DOOBZGAYZIZD42E\"; itxn_field AssetCloseTo;"), ep, "not an address") TestApp(t, NoTrack("itxn_begin; byte \"pay\"; itxn_field Fee;"), ep, "not a uint64") @@ -849,7 +850,7 @@ func TestFieldSetting(t *testing.T) { TestApp(t, "itxn_begin; int 0; itxn_field Nonparticipation; int 1", ep) TestApp(t, "itxn_begin; int 1; itxn_field Nonparticipation; int 1", ep) - TestApp(t, "itxn_begin; int 2; itxn_field Nonparticipation; int 1", ep, + TestApp(t, NoTrack("itxn_begin; int 2; itxn_field Nonparticipation; int 1"), ep, "boolean is neither 1 nor 0") TestApp(t, "itxn_begin; int 32; bzero; itxn_field RekeyTo; int 1", ep) diff --git a/data/transactions/logic/evalStateful_test.go b/data/transactions/logic/evalStateful_test.go index ae34c1303e..be36eb6859 100644 --- a/data/transactions/logic/evalStateful_test.go +++ b/data/transactions/logic/evalStateful_test.go @@ -2623,10 +2623,10 @@ func TestReturnTypes(t *testing.T) { t.Parallel() // Ensure all opcodes return values they are supposed to according to the OpSpecs table - typeToArg := map[StackType]string{ - StackUint64: "int 1\n", - StackAny: "int 1\n", - StackBytes: "byte 0x33343536\n", // Which is the string "3456" + typeToArg := map[avmType]string{ + avmUint64: "int 1\n", + avmAny: "int 1\n", + avmBytes: "byte 0x33343536\n", // Which is the string "3456" } // We try to form a snippet that will test every opcode, by sandwiching it @@ -2784,7 +2784,7 @@ func TestReturnTypes(t *testing.T) { var sb strings.Builder if provideStackInput { for _, t := range spec.Arg.Types { - sb.WriteString(typeToArg[t]) + sb.WriteString(typeToArg[t.AVMType]) } } sb.WriteString(cmd + "\n") @@ -2867,10 +2867,10 @@ func TestReturnTypes(t *testing.T) { } require.Len(t, cx.stack, len(spec.Return.Types), "%s", ep.Trace) for i := 0; i < len(spec.Return.Types); i++ { - stackType := cx.stack[i].argType() + stackType := cx.stack[i].stackType() retType := spec.Return.Types[i] require.True( - t, typecheck(retType, stackType), + t, stackType.overlaps(retType), "%s expected to return %s but actual is %s", spec.Name, retType, stackType, ) } diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index 42bd615444..9317be9203 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -3363,10 +3363,11 @@ intc_1 import random def foo(): - for i in range(64): - print('int {}'.format(random.randint(0,0x01ffffffffffffff))) - for i in range(63): - print('+') + + for i in range(64): + print('int {}'.format(random.randint(0,0x01ffffffffffffff))) + for i in range(63): + print('+') */ const addBenchmarkSource = `int 20472989571761113 int 80135167795737348 @@ -3501,10 +3502,11 @@ int 28939890412103745 import random def foo(): - print('int {}'.format(random.randint(0,0x01ffffffffffffff))) - for i in range(63): - print('int {}'.format(random.randint(0,0x01ffffffffffffff))) - print('+') + + print('int {}'.format(random.randint(0,0x01ffffffffffffff))) + for i in range(63): + print('int {}'.format(random.randint(0,0x01ffffffffffffff))) + print('+') */ const addBenchmark2Source = `int 8371863094338737 int 29595196041051360 @@ -4157,13 +4159,13 @@ func TestArgType(t *testing.T) { t.Parallel() var sv stackValue - require.Equal(t, StackUint64, sv.argType()) + require.Equal(t, avmUint64, sv.avmType()) sv.Bytes = []byte("") - require.Equal(t, StackBytes, sv.argType()) + require.Equal(t, avmBytes, sv.avmType()) sv.Uint = 1 - require.Equal(t, StackBytes, sv.argType()) + require.Equal(t, avmBytes, sv.avmType()) sv.Bytes = nil - require.Equal(t, StackUint64, sv.argType()) + require.Equal(t, avmUint64, sv.avmType()) } func TestApplicationsDisallowOldTeal(t *testing.T) { @@ -4904,7 +4906,7 @@ func TestBytesMath(t *testing.T) { // 64 byte long inputs are accepted, even if they produce longer outputs testAccepts(t, fmt.Sprintf("byte 0x%s; byte 0x10; b+; len; int 65; ==", effs), 4) // 65 byte inputs are not ok. - testPanics(t, fmt.Sprintf("byte 0x%s00; byte 0x10; b-; len; int 65; ==", effs), 4) + testPanics(t, NoTrack(fmt.Sprintf("byte 0x%s00; byte 0x10; b-; len; int 65; ==", effs)), 4) testAccepts(t, `byte 0x01; byte 0x01; b-; byte ""; ==`, 4) testAccepts(t, "byte 0x0200; byte 0x01; b-; byte 0x01FF; ==", 4) @@ -5008,7 +5010,7 @@ func TestBytesBits(t *testing.T) { testAccepts(t, "int 33; bzero; byte 0x000000000000000000000000000000000000000000000000000000000000000000; ==", 4) testAccepts(t, "int 4096; bzero; len; int 4096; ==", 4) - testPanics(t, "int 4097; bzero; len; int 4097; ==", 4) + testPanics(t, NoTrack("int 4097; bzero; len; int 4097; =="), 4) } func TestBytesConversions(t *testing.T) { diff --git a/data/transactions/logic/fields.go b/data/transactions/logic/fields.go index 0af2079a35..40660f1f40 100644 --- a/data/transactions/logic/fields.go +++ b/data/transactions/logic/fields.go @@ -267,18 +267,18 @@ func (fs txnFieldSpec) Note() string { } var txnFieldSpecs = [...]txnFieldSpec{ - {Sender, StackBytes, false, 0, 5, false, "32 byte address"}, + {Sender, StackAddress, false, 0, 5, false, "32 byte address"}, {Fee, StackUint64, false, 0, 5, false, "microalgos"}, {FirstValid, StackUint64, false, 0, 0, false, "round number"}, {FirstValidTime, StackUint64, false, randomnessVersion, 0, false, "UNIX timestamp of block before txn.FirstValid. Fails if negative"}, {LastValid, StackUint64, false, 0, 0, false, "round number"}, {Note, StackBytes, false, 0, 6, false, "Any data up to 1024 bytes"}, - {Lease, StackBytes, false, 0, 0, false, "32 byte lease value"}, - {Receiver, StackBytes, false, 0, 5, false, "32 byte address"}, + {Lease, StackBytes32, false, 0, 0, false, "32 byte lease value"}, + {Receiver, StackAddress, false, 0, 5, false, "32 byte address"}, {Amount, StackUint64, false, 0, 5, false, "microalgos"}, - {CloseRemainderTo, StackBytes, false, 0, 5, false, "32 byte address"}, - {VotePK, StackBytes, false, 0, 6, false, "32 byte address"}, - {SelectionPK, StackBytes, false, 0, 6, false, "32 byte address"}, + {CloseRemainderTo, StackAddress, false, 0, 5, false, "32 byte address"}, + {VotePK, StackBytes32, false, 0, 6, false, "32 byte address"}, + {SelectionPK, StackBytes32, false, 0, 6, false, "32 byte address"}, {VoteFirst, StackUint64, false, 0, 6, false, "The first round that the participation key is valid."}, {VoteLast, StackUint64, false, 0, 6, false, "The last round that the participation key is valid."}, {VoteKeyDilution, StackUint64, false, 0, 6, false, "Dilution for the 2-level participation key"}, @@ -286,42 +286,42 @@ var txnFieldSpecs = [...]txnFieldSpec{ {TypeEnum, StackUint64, false, 0, 5, false, "Transaction type as integer"}, {XferAsset, StackUint64, false, 0, 5, false, "Asset ID"}, {AssetAmount, StackUint64, false, 0, 5, false, "value in Asset's units"}, - {AssetSender, StackBytes, false, 0, 5, false, + {AssetSender, StackAddress, false, 0, 5, false, "32 byte address. Source of assets if Sender is the Asset's Clawback address."}, - {AssetReceiver, StackBytes, false, 0, 5, false, "32 byte address"}, - {AssetCloseTo, StackBytes, false, 0, 5, false, "32 byte address"}, + {AssetReceiver, StackAddress, false, 0, 5, false, "32 byte address"}, + {AssetCloseTo, StackAddress, false, 0, 5, false, "32 byte address"}, {GroupIndex, StackUint64, false, 0, 0, false, "Position of this transaction within an atomic transaction group. A stand-alone transaction is implicitly element 0 in a group of 1"}, - {TxID, StackBytes, false, 0, 0, false, "The computed ID for this transaction. 32 bytes."}, + {TxID, StackBytes32, false, 0, 0, false, "The computed ID for this transaction. 32 bytes."}, {ApplicationID, StackUint64, false, 2, 6, false, "ApplicationID from ApplicationCall transaction"}, {OnCompletion, StackUint64, false, 2, 6, false, "ApplicationCall transaction on completion action"}, {ApplicationArgs, StackBytes, true, 2, 6, false, "Arguments passed to the application in the ApplicationCall transaction"}, {NumAppArgs, StackUint64, false, 2, 0, false, "Number of ApplicationArgs"}, - {Accounts, StackBytes, true, 2, 6, false, "Accounts listed in the ApplicationCall transaction"}, + {Accounts, StackAddress, true, 2, 6, false, "Accounts listed in the ApplicationCall transaction"}, {NumAccounts, StackUint64, false, 2, 0, false, "Number of Accounts"}, {ApprovalProgram, StackBytes, false, 2, 6, false, "Approval program"}, {ClearStateProgram, StackBytes, false, 2, 6, false, "Clear state program"}, - {RekeyTo, StackBytes, false, 2, 6, false, "32 byte Sender's new AuthAddr"}, + {RekeyTo, StackAddress, false, 2, 6, false, "32 byte Sender's new AuthAddr"}, {ConfigAsset, StackUint64, false, 2, 5, false, "Asset ID in asset config transaction"}, {ConfigAssetTotal, StackUint64, false, 2, 5, false, "Total number of units of this asset created"}, {ConfigAssetDecimals, StackUint64, false, 2, 5, false, "Number of digits to display after the decimal place when displaying the asset"}, - {ConfigAssetDefaultFrozen, StackUint64, false, 2, 5, false, + {ConfigAssetDefaultFrozen, StackBoolean, false, 2, 5, false, "Whether the asset's slots are frozen by default or not, 0 or 1"}, {ConfigAssetUnitName, StackBytes, false, 2, 5, false, "Unit name of the asset"}, {ConfigAssetName, StackBytes, false, 2, 5, false, "The asset name"}, {ConfigAssetURL, StackBytes, false, 2, 5, false, "URL"}, - {ConfigAssetMetadataHash, StackBytes, false, 2, 5, false, + {ConfigAssetMetadataHash, StackBytes32, false, 2, 5, false, "32 byte commitment to unspecified asset metadata"}, - {ConfigAssetManager, StackBytes, false, 2, 5, false, "32 byte address"}, - {ConfigAssetReserve, StackBytes, false, 2, 5, false, "32 byte address"}, - {ConfigAssetFreeze, StackBytes, false, 2, 5, false, "32 byte address"}, - {ConfigAssetClawback, StackBytes, false, 2, 5, false, "32 byte address"}, + {ConfigAssetManager, StackAddress, false, 2, 5, false, "32 byte address"}, + {ConfigAssetReserve, StackAddress, false, 2, 5, false, "32 byte address"}, + {ConfigAssetFreeze, StackAddress, false, 2, 5, false, "32 byte address"}, + {ConfigAssetClawback, StackAddress, false, 2, 5, false, "32 byte address"}, {FreezeAsset, StackUint64, false, 2, 5, false, "Asset ID being frozen or un-frozen"}, - {FreezeAssetAccount, StackBytes, false, 2, 5, false, + {FreezeAssetAccount, StackAddress, false, 2, 5, false, "32 byte address of the account whose asset slot is being frozen or un-frozen"}, - {FreezeAssetFrozen, StackUint64, false, 2, 5, false, "The new frozen value, 0 or 1"}, + {FreezeAssetFrozen, StackBoolean, false, 2, 5, false, "The new frozen value, 0 or 1"}, {Assets, StackUint64, true, 3, 6, false, "Foreign Assets listed in the ApplicationCall transaction"}, {NumAssets, StackUint64, false, 3, 0, false, "Number of Assets"}, {Applications, StackUint64, true, 3, 6, false, "Foreign Apps listed in the ApplicationCall transaction"}, @@ -332,7 +332,7 @@ var txnFieldSpecs = [...]txnFieldSpec{ {LocalNumByteSlice, StackUint64, false, 3, 6, false, "Number of local state byteslices in ApplicationCall"}, {ExtraProgramPages, StackUint64, false, 4, 6, false, "Number of additional pages for each of the application's approval and clear state programs. An ExtraProgramPages of 1 means 2048 more total bytes, or 1024 for each program."}, - {Nonparticipation, StackUint64, false, 5, 6, false, "Marks an account nonparticipating for rewards"}, + {Nonparticipation, StackBoolean, false, 5, 6, false, "Marks an account nonparticipating for rewards"}, // "Effects" Last two things are always going to: 0, true {Logs, StackBytes, true, 5, 0, true, "Log messages emitted by an application call (only with `itxn` in v5)"}, @@ -568,7 +568,7 @@ var globalFieldSpecs = [...]globalFieldSpec{ {MinTxnFee, StackUint64, modeAny, 0, "microalgos"}, {MinBalance, StackUint64, modeAny, 0, "microalgos"}, {MaxTxnLife, StackUint64, modeAny, 0, "rounds"}, - {ZeroAddress, StackBytes, modeAny, 0, "32 byte address of all zero bytes"}, + {ZeroAddress, StackAddress, modeAny, 0, "32 byte address of all zero bytes"}, {GroupSize, StackUint64, modeAny, 0, "Number of transactions in this atomic transaction group. At least 1"}, {LogicSigVersion, StackUint64, modeAny, 2, "Maximum supported version"}, @@ -576,17 +576,17 @@ var globalFieldSpecs = [...]globalFieldSpec{ {LatestTimestamp, StackUint64, ModeApp, 2, "Last confirmed block UNIX timestamp. Fails if negative"}, {CurrentApplicationID, StackUint64, ModeApp, 2, "ID of current application executing"}, - {CreatorAddress, StackBytes, ModeApp, 3, + {CreatorAddress, StackAddress, ModeApp, 3, "Address of the creator of the current application"}, - {CurrentApplicationAddress, StackBytes, ModeApp, 5, + {CurrentApplicationAddress, StackAddress, ModeApp, 5, "Address that the current application controls"}, - {GroupID, StackBytes, modeAny, 5, + {GroupID, StackBytes32, modeAny, 5, "ID of the transaction group. 32 zero bytes if the transaction is not part of a group."}, {OpcodeBudget, StackUint64, modeAny, 6, "The remaining cost that can be spent by opcodes in this program."}, {CallerApplicationID, StackUint64, ModeApp, 6, "The application ID of the application that called this application. 0 if this application is at the top-level."}, - {CallerApplicationAddress, StackBytes, ModeApp, 6, + {CallerApplicationAddress, StackAddress, ModeApp, 6, "The application address of the application that called this application. ZeroAddress if this application is at the top-level."}, } @@ -983,7 +983,7 @@ func (fs assetHoldingFieldSpec) Note() string { var assetHoldingFieldSpecs = [...]assetHoldingFieldSpec{ {AssetBalance, StackUint64, 2, "Amount of the asset unit held by this account"}, - {AssetFrozen, StackUint64, 2, "Is the asset frozen or not"}, + {AssetFrozen, StackBoolean, 2, "Is the asset frozen or not"}, } func assetHoldingFieldSpecByField(f AssetHoldingField) (assetHoldingFieldSpec, bool) { @@ -1070,16 +1070,16 @@ func (fs assetParamsFieldSpec) Note() string { var assetParamsFieldSpecs = [...]assetParamsFieldSpec{ {AssetTotal, StackUint64, 2, "Total number of units of this asset"}, {AssetDecimals, StackUint64, 2, "See AssetParams.Decimals"}, - {AssetDefaultFrozen, StackUint64, 2, "Frozen by default or not"}, + {AssetDefaultFrozen, StackBoolean, 2, "Frozen by default or not"}, {AssetUnitName, StackBytes, 2, "Asset unit name"}, {AssetName, StackBytes, 2, "Asset name"}, {AssetURL, StackBytes, 2, "URL with additional info about the asset"}, - {AssetMetadataHash, StackBytes, 2, "Arbitrary commitment"}, - {AssetManager, StackBytes, 2, "Manager address"}, - {AssetReserve, StackBytes, 2, "Reserve address"}, - {AssetFreeze, StackBytes, 2, "Freeze address"}, - {AssetClawback, StackBytes, 2, "Clawback address"}, - {AssetCreator, StackBytes, 5, "Creator address"}, + {AssetMetadataHash, StackBytes32, 2, "Arbitrary commitment"}, + {AssetManager, StackAddress, 2, "Manager address"}, + {AssetReserve, StackAddress, 2, "Reserve address"}, + {AssetFreeze, StackAddress, 2, "Freeze address"}, + {AssetClawback, StackAddress, 2, "Clawback address"}, + {AssetCreator, StackAddress, 5, "Creator address"}, } func assetParamsFieldSpecByField(f AssetParamsField) (assetParamsFieldSpec, bool) { @@ -1166,8 +1166,8 @@ var appParamsFieldSpecs = [...]appParamsFieldSpec{ {AppLocalNumUint, StackUint64, 5, "Number of uint64 values allowed in Local State"}, {AppLocalNumByteSlice, StackUint64, 5, "Number of byte array values allowed in Local State"}, {AppExtraProgramPages, StackUint64, 5, "Number of Extra Program Pages of code space"}, - {AppCreator, StackBytes, 5, "Creator address"}, - {AppAddress, StackBytes, 5, "Address for which this application has authority"}, + {AppCreator, StackAddress, 5, "Creator address"}, + {AppAddress, StackAddress, 5, "Address for which this application has authority"}, } func appParamsFieldSpecByField(f AppParamsField) (appParamsFieldSpec, bool) { @@ -1259,7 +1259,7 @@ func (fs acctParamsFieldSpec) Note() string { var acctParamsFieldSpecs = [...]acctParamsFieldSpec{ {AcctBalance, StackUint64, 6, "Account balance in microalgos"}, {AcctMinBalance, StackUint64, 6, "Minimum required balance for account, in microalgos"}, - {AcctAuthAddr, StackBytes, 6, "Address the account is rekeyed to."}, + {AcctAuthAddr, StackAddress, 6, "Address the account is rekeyed to."}, {AcctTotalNumUint, StackUint64, 8, "The total number of uint64 values allocated by this account in Global and Local States."}, {AcctTotalNumByteSlice, StackUint64, 8, "The total number of byte array values allocated by this account in Global and Local States."}, diff --git a/data/transactions/logic/fields_test.go b/data/transactions/logic/fields_test.go index afbb20d535..e0819e7073 100644 --- a/data/transactions/logic/fields_test.go +++ b/data/transactions/logic/fields_test.go @@ -220,10 +220,10 @@ func TestAssetParamsFieldsVersions(t *testing.T) { // Need to use intc so we can "backversion" the // program and not have it fail because of pushint. text := fmt.Sprintf("intcblock 55 1; intc_0; asset_params_get %s; bnz ok; err; ok: ", field.field.String()) - switch field.ftype { - case StackUint64: // ensure the return type is uint64 by adding + switch field.ftype.AVMType { + case avmUint64: // ensure the return type is uint64 by adding text += " intc_1; +" - case StackBytes: // ensure the return type is bytes by using len + case avmBytes: // ensure the return type is bytes by using len text += " len" // also happens to ensure that we get non empty - the params fields are fixed width } // check assembler fails if version before introduction @@ -270,8 +270,8 @@ func TestAcctParamsFieldsVersions(t *testing.T) { t.Parallel() for _, field := range acctParamsFieldSpecs { - text := fmt.Sprintf("txn Sender; acct_params_get %s; assert;", field.field) - if field.ftype == StackBytes { + text := fmt.Sprintf("txn Sender; acct_params_get %s; assert;", field.field.String()) + if field.ftype.AVMType == avmBytes { text += "global ZeroAddress; concat; len" // use concat to prove we have bytes } else { text += "global ZeroAddress; len; +" // use + to prove we have an int diff --git a/data/transactions/logic/langspec.json b/data/transactions/logic/langspec.json index 1182117a1d..921a64943e 100644 --- a/data/transactions/logic/langspec.json +++ b/data/transactions/logic/langspec.json @@ -1,6 +1,107 @@ { "EvalMaxVersion": 9, "LogicSigVersion": 8, + "NamedTypes": [ + { + "Name": "uint64", + "Abbreviation": "i", + "Bound": [ + 0, + 18446744073709551615 + ], + "AVMType": "uint64" + }, + { + "Name": "stateKey", + "Abbreviation": "K", + "Bound": [ + 0, + 64 + ], + "AVMType": "[]byte" + }, + { + "Name": "none", + "Abbreviation": "x", + "Bound": [ + 0, + 0 + ], + "AVMType": "none" + }, + { + "Name": "method", + "Abbreviation": "M", + "Bound": [ + 4, + 4 + ], + "AVMType": "[]byte" + }, + { + "Name": "boxName", + "Abbreviation": "N", + "Bound": [ + 1, + 64 + ], + "AVMType": "[]byte" + }, + { + "Name": "bool", + "Abbreviation": "T", + "Bound": [ + 0, + 1 + ], + "AVMType": "uint64" + }, + { + "Name": "bigint", + "Abbreviation": "I", + "Bound": [ + 0, + 64 + ], + "AVMType": "[]byte" + }, + { + "Name": "any", + "Abbreviation": "a", + "Bound": [ + 0, + 0 + ], + "AVMType": "any" + }, + { + "Name": "address", + "Abbreviation": "A", + "Bound": [ + 32, + 32 + ], + "AVMType": "[]byte" + }, + { + "Name": "[]byte", + "Abbreviation": "b", + "Bound": [ + 0, + 4096 + ], + "AVMType": "[]byte" + }, + { + "Name": "[32]byte", + "Abbreviation": "H", + "Bound": [ + 32, + 32 + ], + "AVMType": "[]byte" + } + ], "Ops": [ { "Opcode": 0, @@ -15,8 +116,12 @@ { "Opcode": 1, "Name": "sha256", - "Args": "B", - "Returns": "B", + "Args": [ + "[]byte" + ], + "Returns": [ + "[32]byte" + ], "Size": 1, "Doc": "SHA256 hash of value A, yields [32]byte", "IntroducedVersion": 1, @@ -27,8 +132,12 @@ { "Opcode": 2, "Name": "keccak256", - "Args": "B", - "Returns": "B", + "Args": [ + "[]byte" + ], + "Returns": [ + "[32]byte" + ], "Size": 1, "Doc": "Keccak256 hash of value A, yields [32]byte", "IntroducedVersion": 1, @@ -39,8 +148,12 @@ { "Opcode": 3, "Name": "sha512_256", - "Args": "B", - "Returns": "B", + "Args": [ + "[]byte" + ], + "Returns": [ + "[32]byte" + ], "Size": 1, "Doc": "SHA512_256 hash of value A, yields [32]byte", "IntroducedVersion": 1, @@ -51,8 +164,14 @@ { "Opcode": 4, "Name": "ed25519verify", - "Args": "BBB", - "Returns": "U", + "Args": [ + "[]byte", + "[]byte", + "[]byte" + ], + "Returns": [ + "bool" + ], "Size": 1, "Doc": "for (data A, signature B, pubkey C) verify the signature of (\"ProgData\" || program_hash || data) against the pubkey =\u003e {0 or 1}", "DocExtra": "The 32 byte public key is the last element on the stack, preceded by the 64 byte signature at the second-to-last element on the stack, preceded by the data which was signed at the third-to-last element on the stack.", @@ -64,8 +183,16 @@ { "Opcode": 5, "Name": "ecdsa_verify", - "Args": "BBBBB", - "Returns": "U", + "Args": [ + "[]byte", + "[]byte", + "[]byte", + "[]byte", + "[]byte" + ], + "Returns": [ + "bool" + ], "Size": 2, "ArgEnum": [ "Secp256k1", @@ -73,7 +200,14 @@ ], "Doc": "for (data A, signature B, C and pubkey D, E) verify the signature of the data against the pubkey =\u003e {0 or 1}", "DocExtra": "The 32 byte Y-component of a public key is the last element on the stack, preceded by X-component of a pubkey, preceded by S and R components of a signature, preceded by the data that is fifth element on the stack. All values are big-endian encoded. The signed data must be 32 bytes long, and signatures in lower-S form are only accepted.", - "ImmediateNote": "{uint8 curve index}", + "ImmediateNote": [ + { + "Comment": "curve index", + "Encoding": "uint8", + "Name": "V", + "Reference": "ECDSA" + } + ], "IntroducedVersion": 5, "Groups": [ "Arithmetic" @@ -82,8 +216,13 @@ { "Opcode": 6, "Name": "ecdsa_pk_decompress", - "Args": "B", - "Returns": "BB", + "Args": [ + "[]byte" + ], + "Returns": [ + "[]byte", + "[]byte" + ], "Size": 2, "ArgEnum": [ "Secp256k1", @@ -91,7 +230,14 @@ ], "Doc": "decompress pubkey A into components X, Y", "DocExtra": "The 33 byte public key in a compressed form to be decompressed into X and Y (top) components. All values are big-endian encoded.", - "ImmediateNote": "{uint8 curve index}", + "ImmediateNote": [ + { + "Comment": "curve index", + "Encoding": "uint8", + "Name": "V", + "Reference": "ECDSA" + } + ], "IntroducedVersion": 5, "Groups": [ "Arithmetic" @@ -100,8 +246,16 @@ { "Opcode": 7, "Name": "ecdsa_pk_recover", - "Args": "BUBB", - "Returns": "BB", + "Args": [ + "[]byte", + "uint64", + "[]byte", + "[]byte" + ], + "Returns": [ + "[]byte", + "[]byte" + ], "Size": 2, "ArgEnum": [ "Secp256k1", @@ -109,7 +263,14 @@ ], "Doc": "for (data A, recovery id B, signature C, D) recover a public key", "DocExtra": "S (top) and R elements of a signature, recovery id and data (bottom) are expected on the stack and used to deriver a public key. All values are big-endian encoded. The signed data must be 32 bytes long.", - "ImmediateNote": "{uint8 curve index}", + "ImmediateNote": [ + { + "Comment": "curve index", + "Encoding": "uint8", + "Name": "V", + "Reference": "ECDSA" + } + ], "IntroducedVersion": 5, "Groups": [ "Arithmetic" @@ -118,8 +279,13 @@ { "Opcode": 8, "Name": "+", - "Args": "UU", - "Returns": "U", + "Args": [ + "uint64", + "uint64" + ], + "Returns": [ + "uint64" + ], "Size": 1, "Doc": "A plus B. Fail on overflow.", "DocExtra": "Overflow is an error condition which halts execution and fails the transaction. Full precision is available from `addw`.", @@ -131,8 +297,13 @@ { "Opcode": 9, "Name": "-", - "Args": "UU", - "Returns": "U", + "Args": [ + "uint64", + "uint64" + ], + "Returns": [ + "uint64" + ], "Size": 1, "Doc": "A minus B. Fail if B \u003e A.", "IntroducedVersion": 1, @@ -143,8 +314,13 @@ { "Opcode": 10, "Name": "/", - "Args": "UU", - "Returns": "U", + "Args": [ + "uint64", + "uint64" + ], + "Returns": [ + "uint64" + ], "Size": 1, "Doc": "A divided by B (truncated division). Fail if B == 0.", "DocExtra": "`divmodw` is available to divide the two-element values produced by `mulw` and `addw`.", @@ -156,8 +332,13 @@ { "Opcode": 11, "Name": "*", - "Args": "UU", - "Returns": "U", + "Args": [ + "uint64", + "uint64" + ], + "Returns": [ + "uint64" + ], "Size": 1, "Doc": "A times B. Fail on overflow.", "DocExtra": "Overflow is an error condition which halts execution and fails the transaction. Full precision is available from `mulw`.", @@ -169,8 +350,13 @@ { "Opcode": 12, "Name": "\u003c", - "Args": "UU", - "Returns": "U", + "Args": [ + "uint64", + "uint64" + ], + "Returns": [ + "bool" + ], "Size": 1, "Doc": "A less than B =\u003e {0 or 1}", "IntroducedVersion": 1, @@ -181,8 +367,13 @@ { "Opcode": 13, "Name": "\u003e", - "Args": "UU", - "Returns": "U", + "Args": [ + "uint64", + "uint64" + ], + "Returns": [ + "bool" + ], "Size": 1, "Doc": "A greater than B =\u003e {0 or 1}", "IntroducedVersion": 1, @@ -193,8 +384,13 @@ { "Opcode": 14, "Name": "\u003c=", - "Args": "UU", - "Returns": "U", + "Args": [ + "uint64", + "uint64" + ], + "Returns": [ + "bool" + ], "Size": 1, "Doc": "A less than or equal to B =\u003e {0 or 1}", "IntroducedVersion": 1, @@ -205,8 +401,13 @@ { "Opcode": 15, "Name": "\u003e=", - "Args": "UU", - "Returns": "U", + "Args": [ + "uint64", + "uint64" + ], + "Returns": [ + "bool" + ], "Size": 1, "Doc": "A greater than or equal to B =\u003e {0 or 1}", "IntroducedVersion": 1, @@ -217,8 +418,13 @@ { "Opcode": 16, "Name": "\u0026\u0026", - "Args": "UU", - "Returns": "U", + "Args": [ + "uint64", + "uint64" + ], + "Returns": [ + "bool" + ], "Size": 1, "Doc": "A is not zero and B is not zero =\u003e {0 or 1}", "IntroducedVersion": 1, @@ -229,8 +435,13 @@ { "Opcode": 17, "Name": "||", - "Args": "UU", - "Returns": "U", + "Args": [ + "uint64", + "uint64" + ], + "Returns": [ + "bool" + ], "Size": 1, "Doc": "A is not zero or B is not zero =\u003e {0 or 1}", "IntroducedVersion": 1, @@ -241,8 +452,13 @@ { "Opcode": 18, "Name": "==", - "Args": "..", - "Returns": "U", + "Args": [ + "any", + "any" + ], + "Returns": [ + "bool" + ], "Size": 1, "Doc": "A is equal to B =\u003e {0 or 1}", "IntroducedVersion": 1, @@ -253,8 +469,13 @@ { "Opcode": 19, "Name": "!=", - "Args": "..", - "Returns": "U", + "Args": [ + "any", + "any" + ], + "Returns": [ + "bool" + ], "Size": 1, "Doc": "A is not equal to B =\u003e {0 or 1}", "IntroducedVersion": 1, @@ -265,8 +486,12 @@ { "Opcode": 20, "Name": "!", - "Args": "U", - "Returns": "U", + "Args": [ + "uint64" + ], + "Returns": [ + "uint64" + ], "Size": 1, "Doc": "A == 0 yields 1; else 0", "IntroducedVersion": 1, @@ -277,8 +502,12 @@ { "Opcode": 21, "Name": "len", - "Args": "B", - "Returns": "U", + "Args": [ + "[]byte" + ], + "Returns": [ + "uint64" + ], "Size": 1, "Doc": "yields length of byte value A", "IntroducedVersion": 1, @@ -289,8 +518,12 @@ { "Opcode": 22, "Name": "itob", - "Args": "U", - "Returns": "B", + "Args": [ + "uint64" + ], + "Returns": [ + "[]byte" + ], "Size": 1, "Doc": "converts uint64 A to big-endian byte array, always of length 8", "IntroducedVersion": 1, @@ -301,8 +534,12 @@ { "Opcode": 23, "Name": "btoi", - "Args": "B", - "Returns": "U", + "Args": [ + "[]byte" + ], + "Returns": [ + "uint64" + ], "Size": 1, "Doc": "converts big-endian byte array A to uint64. Fails if len(A) \u003e 8. Padded by leading 0s if len(A) \u003c 8.", "DocExtra": "`btoi` fails if the input is longer than 8 bytes.", @@ -314,8 +551,13 @@ { "Opcode": 24, "Name": "%", - "Args": "UU", - "Returns": "U", + "Args": [ + "uint64", + "uint64" + ], + "Returns": [ + "uint64" + ], "Size": 1, "Doc": "A modulo B. Fail if B == 0.", "IntroducedVersion": 1, @@ -326,8 +568,13 @@ { "Opcode": 25, "Name": "|", - "Args": "UU", - "Returns": "U", + "Args": [ + "uint64", + "uint64" + ], + "Returns": [ + "uint64" + ], "Size": 1, "Doc": "A bitwise-or B", "IntroducedVersion": 1, @@ -338,8 +585,13 @@ { "Opcode": 26, "Name": "\u0026", - "Args": "UU", - "Returns": "U", + "Args": [ + "uint64", + "uint64" + ], + "Returns": [ + "uint64" + ], "Size": 1, "Doc": "A bitwise-and B", "IntroducedVersion": 1, @@ -350,8 +602,13 @@ { "Opcode": 27, "Name": "^", - "Args": "UU", - "Returns": "U", + "Args": [ + "uint64", + "uint64" + ], + "Returns": [ + "uint64" + ], "Size": 1, "Doc": "A bitwise-xor B", "IntroducedVersion": 1, @@ -362,8 +619,12 @@ { "Opcode": 28, "Name": "~", - "Args": "U", - "Returns": "U", + "Args": [ + "uint64" + ], + "Returns": [ + "uint64" + ], "Size": 1, "Doc": "bitwise invert value A", "IntroducedVersion": 1, @@ -374,8 +635,14 @@ { "Opcode": 29, "Name": "mulw", - "Args": "UU", - "Returns": "UU", + "Args": [ + "uint64", + "uint64" + ], + "Returns": [ + "uint64", + "uint64" + ], "Size": 1, "Doc": "A times B as a 128-bit result in two uint64s. X is the high 64 bits, Y is the low", "IntroducedVersion": 1, @@ -386,8 +653,14 @@ { "Opcode": 30, "Name": "addw", - "Args": "UU", - "Returns": "UU", + "Args": [ + "uint64", + "uint64" + ], + "Returns": [ + "uint64", + "uint64" + ], "Size": 1, "Doc": "A plus B as a 128-bit result. X is the carry-bit, Y is the low-order 64 bits.", "IntroducedVersion": 2, @@ -398,8 +671,18 @@ { "Opcode": 31, "Name": "divmodw", - "Args": "UUUU", - "Returns": "UUUU", + "Args": [ + "uint64", + "uint64", + "uint64", + "uint64" + ], + "Returns": [ + "uint64", + "uint64", + "uint64", + "uint64" + ], "Size": 1, "Doc": "W,X = (A,B / C,D); Y,Z = (A,B modulo C,D)", "DocExtra": "The notation J,K indicates that two uint64 values J and K are interpreted as a uint128 value, with J as the high uint64 and K the low.", @@ -414,7 +697,13 @@ "Size": 0, "Doc": "prepare block of uint64 constants for use by intc", "DocExtra": "`intcblock` loads following program bytes into an array of integer constants in the evaluator. These integer constants can be referred to by `intc` and `intc_*` which will push the value onto the stack. Subsequent calls to `intcblock` reset and replace the integer constants available to the script.", - "ImmediateNote": "{varuint count} [{varuint value}, ...]", + "ImmediateNote": [ + { + "Comment": "a block of int constant values", + "Encoding": "varuint count, [varuint ...]", + "Name": "UINT ..." + } + ], "IntroducedVersion": 1, "Groups": [ "Loading Values" @@ -423,10 +712,18 @@ { "Opcode": 33, "Name": "intc", - "Returns": "U", + "Returns": [ + "uint64" + ], "Size": 2, "Doc": "Ith constant from intcblock", - "ImmediateNote": "{uint8 int constant index}", + "ImmediateNote": [ + { + "Comment": "an index in the intcblock", + "Encoding": "uint8", + "Name": "I" + } + ], "IntroducedVersion": 1, "Groups": [ "Loading Values" @@ -435,7 +732,9 @@ { "Opcode": 34, "Name": "intc_0", - "Returns": "U", + "Returns": [ + "uint64" + ], "Size": 1, "Doc": "constant 0 from intcblock", "IntroducedVersion": 1, @@ -446,7 +745,9 @@ { "Opcode": 35, "Name": "intc_1", - "Returns": "U", + "Returns": [ + "uint64" + ], "Size": 1, "Doc": "constant 1 from intcblock", "IntroducedVersion": 1, @@ -457,7 +758,9 @@ { "Opcode": 36, "Name": "intc_2", - "Returns": "U", + "Returns": [ + "uint64" + ], "Size": 1, "Doc": "constant 2 from intcblock", "IntroducedVersion": 1, @@ -468,7 +771,9 @@ { "Opcode": 37, "Name": "intc_3", - "Returns": "U", + "Returns": [ + "uint64" + ], "Size": 1, "Doc": "constant 3 from intcblock", "IntroducedVersion": 1, @@ -482,7 +787,13 @@ "Size": 0, "Doc": "prepare block of byte-array constants for use by bytec", "DocExtra": "`bytecblock` loads the following program bytes into an array of byte-array constants in the evaluator. These constants can be referred to by `bytec` and `bytec_*` which will push the value onto the stack. Subsequent calls to `bytecblock` reset and replace the bytes constants available to the script.", - "ImmediateNote": "{varuint count} [({varuint length} bytes), ...]", + "ImmediateNote": [ + { + "Comment": "a block of byte constant values", + "Encoding": "varuint count, [varuint length, bytes ...]", + "Name": "BYTES ..." + } + ], "IntroducedVersion": 1, "Groups": [ "Loading Values" @@ -491,10 +802,18 @@ { "Opcode": 39, "Name": "bytec", - "Returns": "B", + "Returns": [ + "[]byte" + ], "Size": 2, "Doc": "Ith constant from bytecblock", - "ImmediateNote": "{uint8 byte constant index}", + "ImmediateNote": [ + { + "Comment": "an index in the bytecblock", + "Encoding": "uint8", + "Name": "I" + } + ], "IntroducedVersion": 1, "Groups": [ "Loading Values" @@ -503,7 +822,9 @@ { "Opcode": 40, "Name": "bytec_0", - "Returns": "B", + "Returns": [ + "[]byte" + ], "Size": 1, "Doc": "constant 0 from bytecblock", "IntroducedVersion": 1, @@ -514,7 +835,9 @@ { "Opcode": 41, "Name": "bytec_1", - "Returns": "B", + "Returns": [ + "[]byte" + ], "Size": 1, "Doc": "constant 1 from bytecblock", "IntroducedVersion": 1, @@ -525,7 +848,9 @@ { "Opcode": 42, "Name": "bytec_2", - "Returns": "B", + "Returns": [ + "[]byte" + ], "Size": 1, "Doc": "constant 2 from bytecblock", "IntroducedVersion": 1, @@ -536,7 +861,9 @@ { "Opcode": 43, "Name": "bytec_3", - "Returns": "B", + "Returns": [ + "[]byte" + ], "Size": 1, "Doc": "constant 3 from bytecblock", "IntroducedVersion": 1, @@ -547,10 +874,18 @@ { "Opcode": 44, "Name": "arg", - "Returns": "B", + "Returns": [ + "[]byte" + ], "Size": 2, "Doc": "Nth LogicSig argument", - "ImmediateNote": "{uint8 arg index}", + "ImmediateNote": [ + { + "Comment": "an arg index", + "Encoding": "uint8", + "Name": "N" + } + ], "IntroducedVersion": 1, "Groups": [ "Loading Values" @@ -559,7 +894,9 @@ { "Opcode": 45, "Name": "arg_0", - "Returns": "B", + "Returns": [ + "[]byte" + ], "Size": 1, "Doc": "LogicSig argument 0", "IntroducedVersion": 1, @@ -570,7 +907,9 @@ { "Opcode": 46, "Name": "arg_1", - "Returns": "B", + "Returns": [ + "[]byte" + ], "Size": 1, "Doc": "LogicSig argument 1", "IntroducedVersion": 1, @@ -581,7 +920,9 @@ { "Opcode": 47, "Name": "arg_2", - "Returns": "B", + "Returns": [ + "[]byte" + ], "Size": 1, "Doc": "LogicSig argument 2", "IntroducedVersion": 1, @@ -592,7 +933,9 @@ { "Opcode": 48, "Name": "arg_3", - "Returns": "B", + "Returns": [ + "[]byte" + ], "Size": 1, "Doc": "LogicSig argument 3", "IntroducedVersion": 1, @@ -603,7 +946,9 @@ { "Opcode": 49, "Name": "txn", - "Returns": ".", + "Returns": [ + "any" + ], "Size": 2, "ArgEnum": [ "Sender", @@ -675,9 +1020,85 @@ "ClearStateProgramPages", "NumClearStateProgramPages" ], - "ArgEnumTypes": "BUUUUBBBUBBBUUUBUUUBBBUBUUBUBUBBBUUUUBBBBBBBBUBUUUUUUUUUUUBUUUBBBUBU", + "ArgEnumTypes": [ + "address", + "uint64", + "uint64", + "uint64", + "uint64", + "[]byte", + "[32]byte", + "address", + "uint64", + "address", + "[32]byte", + "[32]byte", + "uint64", + "uint64", + "uint64", + "[]byte", + "uint64", + "uint64", + "uint64", + "address", + "address", + "address", + "uint64", + "[32]byte", + "uint64", + "uint64", + "[]byte", + "uint64", + "address", + "uint64", + "[]byte", + "[]byte", + "address", + "uint64", + "uint64", + "uint64", + "bool", + "[]byte", + "[]byte", + "[]byte", + "[32]byte", + "address", + "address", + "address", + "address", + "uint64", + "address", + "bool", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64", + "bool", + "[]byte", + "uint64", + "uint64", + "uint64", + "[]byte", + "[]byte", + "[]byte", + "uint64", + "[]byte", + "uint64" + ], "Doc": "field F of current transaction", - "ImmediateNote": "{uint8 transaction field index}", + "ImmediateNote": [ + { + "Comment": "transaction field index", + "Encoding": "uint8", + "Name": "F", + "Reference": "txn" + } + ], "IntroducedVersion": 1, "Groups": [ "Loading Values" @@ -686,7 +1107,9 @@ { "Opcode": 50, "Name": "global", - "Returns": ".", + "Returns": [ + "any" + ], "Size": 2, "ArgEnum": [ "MinTxnFee", @@ -705,9 +1128,32 @@ "CallerApplicationID", "CallerApplicationAddress" ], - "ArgEnumTypes": "UUUBUUUUUBBBUUB", + "ArgEnumTypes": [ + "uint64", + "uint64", + "uint64", + "address", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64", + "address", + "address", + "[32]byte", + "uint64", + "uint64", + "address" + ], "Doc": "global field F", - "ImmediateNote": "{uint8 global field index}", + "ImmediateNote": [ + { + "Comment": "a global field index", + "Encoding": "uint8", + "Name": "F", + "Reference": "global" + } + ], "IntroducedVersion": 1, "Groups": [ "Loading Values" @@ -716,7 +1162,9 @@ { "Opcode": 51, "Name": "gtxn", - "Returns": ".", + "Returns": [ + "any" + ], "Size": 3, "ArgEnum": [ "Sender", @@ -788,10 +1236,91 @@ "ClearStateProgramPages", "NumClearStateProgramPages" ], - "ArgEnumTypes": "BUUUUBBBUBBBUUUBUUUBBBUBUUBUBUBBBUUUUBBBBBBBBUBUUUUUUUUUUUBUUUBBBUBU", + "ArgEnumTypes": [ + "address", + "uint64", + "uint64", + "uint64", + "uint64", + "[]byte", + "[32]byte", + "address", + "uint64", + "address", + "[32]byte", + "[32]byte", + "uint64", + "uint64", + "uint64", + "[]byte", + "uint64", + "uint64", + "uint64", + "address", + "address", + "address", + "uint64", + "[32]byte", + "uint64", + "uint64", + "[]byte", + "uint64", + "address", + "uint64", + "[]byte", + "[]byte", + "address", + "uint64", + "uint64", + "uint64", + "bool", + "[]byte", + "[]byte", + "[]byte", + "[32]byte", + "address", + "address", + "address", + "address", + "uint64", + "address", + "bool", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64", + "bool", + "[]byte", + "uint64", + "uint64", + "uint64", + "[]byte", + "[]byte", + "[]byte", + "uint64", + "[]byte", + "uint64" + ], "Doc": "field F of the Tth transaction in the current group", "DocExtra": "for notes on transaction fields available, see `txn`. If this transaction is _i_ in the group, `gtxn i field` is equivalent to `txn field`.", - "ImmediateNote": "{uint8 transaction group index} {uint8 transaction field index}", + "ImmediateNote": [ + { + "Comment": "transaction group index", + "Encoding": "uint8", + "Name": "T" + }, + { + "Comment": "transaction field index", + "Encoding": "uint8", + "Name": "F", + "Reference": "txn" + } + ], "IntroducedVersion": 1, "Groups": [ "Loading Values" @@ -800,10 +1329,18 @@ { "Opcode": 52, "Name": "load", - "Returns": ".", + "Returns": [ + "any" + ], "Size": 2, "Doc": "Ith scratch space value. All scratch spaces are 0 at program start.", - "ImmediateNote": "{uint8 position in scratch space to load from}", + "ImmediateNote": [ + { + "Comment": "position in scratch space to load from", + "Encoding": "uint8", + "Name": "I" + } + ], "IntroducedVersion": 1, "Groups": [ "Loading Values" @@ -812,10 +1349,18 @@ { "Opcode": 53, "Name": "store", - "Args": ".", + "Args": [ + "any" + ], "Size": 2, "Doc": "store A to the Ith scratch space", - "ImmediateNote": "{uint8 position in scratch space to store to}", + "ImmediateNote": [ + { + "Comment": "position in scratch space to store to", + "Encoding": "uint8", + "Name": "I" + } + ], "IntroducedVersion": 1, "Groups": [ "Loading Values" @@ -824,7 +1369,9 @@ { "Opcode": 54, "Name": "txna", - "Returns": ".", + "Returns": [ + "any" + ], "Size": 3, "ArgEnum": [ "ApplicationArgs", @@ -835,9 +1382,29 @@ "ApprovalProgramPages", "ClearStateProgramPages" ], - "ArgEnumTypes": "BBUUBBB", + "ArgEnumTypes": [ + "[]byte", + "address", + "uint64", + "uint64", + "[]byte", + "[]byte", + "[]byte" + ], "Doc": "Ith value of the array field F of the current transaction\n`txna` can be called using `txn` with 2 immediates.", - "ImmediateNote": "{uint8 transaction field index} {uint8 transaction field array index}", + "ImmediateNote": [ + { + "Comment": "transaction field index", + "Encoding": "uint8", + "Name": "F", + "Reference": "txna" + }, + { + "Comment": "transaction field array index", + "Encoding": "uint8", + "Name": "I" + } + ], "IntroducedVersion": 2, "Groups": [ "Loading Values" @@ -846,7 +1413,9 @@ { "Opcode": 55, "Name": "gtxna", - "Returns": ".", + "Returns": [ + "any" + ], "Size": 4, "ArgEnum": [ "ApplicationArgs", @@ -857,9 +1426,34 @@ "ApprovalProgramPages", "ClearStateProgramPages" ], - "ArgEnumTypes": "BBUUBBB", + "ArgEnumTypes": [ + "[]byte", + "address", + "uint64", + "uint64", + "[]byte", + "[]byte", + "[]byte" + ], "Doc": "Ith value of the array field F from the Tth transaction in the current group\n`gtxna` can be called using `gtxn` with 3 immediates.", - "ImmediateNote": "{uint8 transaction group index} {uint8 transaction field index} {uint8 transaction field array index}", + "ImmediateNote": [ + { + "Comment": "transaction group index", + "Encoding": "uint8", + "Name": "T" + }, + { + "Comment": "transaction field index", + "Encoding": "uint8", + "Name": "F", + "Reference": "txna" + }, + { + "Comment": "transaction field array index", + "Encoding": "uint8", + "Name": "I" + } + ], "IntroducedVersion": 2, "Groups": [ "Loading Values" @@ -868,8 +1462,12 @@ { "Opcode": 56, "Name": "gtxns", - "Args": "U", - "Returns": ".", + "Args": [ + "uint64" + ], + "Returns": [ + "any" + ], "Size": 2, "ArgEnum": [ "Sender", @@ -941,10 +1539,86 @@ "ClearStateProgramPages", "NumClearStateProgramPages" ], - "ArgEnumTypes": "BUUUUBBBUBBBUUUBUUUBBBUBUUBUBUBBBUUUUBBBBBBBBUBUUUUUUUUUUUBUUUBBBUBU", + "ArgEnumTypes": [ + "address", + "uint64", + "uint64", + "uint64", + "uint64", + "[]byte", + "[32]byte", + "address", + "uint64", + "address", + "[32]byte", + "[32]byte", + "uint64", + "uint64", + "uint64", + "[]byte", + "uint64", + "uint64", + "uint64", + "address", + "address", + "address", + "uint64", + "[32]byte", + "uint64", + "uint64", + "[]byte", + "uint64", + "address", + "uint64", + "[]byte", + "[]byte", + "address", + "uint64", + "uint64", + "uint64", + "bool", + "[]byte", + "[]byte", + "[]byte", + "[32]byte", + "address", + "address", + "address", + "address", + "uint64", + "address", + "bool", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64", + "bool", + "[]byte", + "uint64", + "uint64", + "uint64", + "[]byte", + "[]byte", + "[]byte", + "uint64", + "[]byte", + "uint64" + ], "Doc": "field F of the Ath transaction in the current group", "DocExtra": "for notes on transaction fields available, see `txn`. If top of stack is _i_, `gtxns field` is equivalent to `gtxn _i_ field`. gtxns exists so that _i_ can be calculated, often based on the index of the current transaction.", - "ImmediateNote": "{uint8 transaction field index}", + "ImmediateNote": [ + { + "Comment": "transaction field index", + "Encoding": "uint8", + "Name": "F", + "Reference": "txn" + } + ], "IntroducedVersion": 3, "Groups": [ "Loading Values" @@ -953,8 +1627,12 @@ { "Opcode": 57, "Name": "gtxnsa", - "Args": "U", - "Returns": ".", + "Args": [ + "uint64" + ], + "Returns": [ + "any" + ], "Size": 3, "ArgEnum": [ "ApplicationArgs", @@ -965,9 +1643,29 @@ "ApprovalProgramPages", "ClearStateProgramPages" ], - "ArgEnumTypes": "BBUUBBB", + "ArgEnumTypes": [ + "[]byte", + "address", + "uint64", + "uint64", + "[]byte", + "[]byte", + "[]byte" + ], "Doc": "Ith value of the array field F from the Ath transaction in the current group\n`gtxnsa` can be called using `gtxns` with 2 immediates.", - "ImmediateNote": "{uint8 transaction field index} {uint8 transaction field array index}", + "ImmediateNote": [ + { + "Comment": "transaction field index", + "Encoding": "uint8", + "Name": "F", + "Reference": "txna" + }, + { + "Comment": "transaction field array index", + "Encoding": "uint8", + "Name": "I" + } + ], "IntroducedVersion": 3, "Groups": [ "Loading Values" @@ -976,11 +1674,24 @@ { "Opcode": 58, "Name": "gload", - "Returns": ".", + "Returns": [ + "any" + ], "Size": 3, "Doc": "Ith scratch space value of the Tth transaction in the current group", "DocExtra": "`gload` fails unless the requested transaction is an ApplicationCall and T \u003c GroupIndex.", - "ImmediateNote": "{uint8 transaction group index} {uint8 position in scratch space to load from}", + "ImmediateNote": [ + { + "Comment": "transaction group index", + "Encoding": "uint8", + "Name": "T" + }, + { + "Comment": "position in scratch space to load from", + "Encoding": "uint8", + "Name": "I" + } + ], "IntroducedVersion": 4, "Groups": [ "Loading Values" @@ -989,12 +1700,22 @@ { "Opcode": 59, "Name": "gloads", - "Args": "U", - "Returns": ".", + "Args": [ + "uint64" + ], + "Returns": [ + "any" + ], "Size": 2, "Doc": "Ith scratch space value of the Ath transaction in the current group", "DocExtra": "`gloads` fails unless the requested transaction is an ApplicationCall and A \u003c GroupIndex.", - "ImmediateNote": "{uint8 position in scratch space to load from}", + "ImmediateNote": [ + { + "Comment": "position in scratch space to load from", + "Encoding": "uint8", + "Name": "I" + } + ], "IntroducedVersion": 4, "Groups": [ "Loading Values" @@ -1003,11 +1724,19 @@ { "Opcode": 60, "Name": "gaid", - "Returns": "U", + "Returns": [ + "uint64" + ], "Size": 2, "Doc": "ID of the asset or application created in the Tth transaction of the current group", "DocExtra": "`gaid` fails unless the requested transaction created an asset or application and T \u003c GroupIndex.", - "ImmediateNote": "{uint8 transaction group index}", + "ImmediateNote": [ + { + "Comment": "transaction group index", + "Encoding": "uint8", + "Name": "T" + } + ], "IntroducedVersion": 4, "Groups": [ "Loading Values" @@ -1016,8 +1745,12 @@ { "Opcode": 61, "Name": "gaids", - "Args": "U", - "Returns": "U", + "Args": [ + "uint64" + ], + "Returns": [ + "uint64" + ], "Size": 1, "Doc": "ID of the asset or application created in the Ath transaction of the current group", "DocExtra": "`gaids` fails unless the requested transaction created an asset or application and A \u003c GroupIndex.", @@ -1029,8 +1762,12 @@ { "Opcode": 62, "Name": "loads", - "Args": "U", - "Returns": ".", + "Args": [ + "uint64" + ], + "Returns": [ + "any" + ], "Size": 1, "Doc": "Ath scratch space value. All scratch spaces are 0 at program start.", "IntroducedVersion": 5, @@ -1041,7 +1778,10 @@ { "Opcode": 63, "Name": "stores", - "Args": "U.", + "Args": [ + "uint64", + "any" + ], "Size": 1, "Doc": "store B to the Ath scratch space", "IntroducedVersion": 5, @@ -1052,11 +1792,19 @@ { "Opcode": 64, "Name": "bnz", - "Args": "U", + "Args": [ + "uint64" + ], "Size": 3, "Doc": "branch to TARGET if value A is not zero", "DocExtra": "The `bnz` instruction opcode 0x40 is followed by two immediate data bytes which are a high byte first and low byte second which together form a 16 bit offset which the instruction may branch to. For a bnz instruction at `pc`, if the last element of the stack is not zero then branch to instruction at `pc + 3 + N`, else proceed to next instruction at `pc + 3`. Branch targets must be aligned instructions. (e.g. Branching to the second byte of a 2 byte op will be rejected.) Starting at v4, the offset is treated as a signed 16 bit integer allowing for backward branches and looping. In prior version (v1 to v3), branch offsets are limited to forward branches only, 0-0x7fff.\n\nAt v2 it became allowed to branch to the end of the program exactly after the last instruction: bnz to byte N (with 0-indexing) was illegal for a TEAL program with N bytes before v2, and is legal after it. This change eliminates the need for a last instruction of no-op as a branch target at the end. (Branching beyond the end--in other words, to a byte larger than N--is still illegal and will cause the program to fail.)", - "ImmediateNote": "{int16 branch offset, big-endian}", + "ImmediateNote": [ + { + "Comment": "branch offset", + "Encoding": "int16 (big-endian)", + "Name": "TARGET" + } + ], "IntroducedVersion": 1, "Groups": [ "Flow Control" @@ -1065,11 +1813,19 @@ { "Opcode": 65, "Name": "bz", - "Args": "U", + "Args": [ + "uint64" + ], "Size": 3, "Doc": "branch to TARGET if value A is zero", "DocExtra": "See `bnz` for details on how branches work. `bz` inverts the behavior of `bnz`.", - "ImmediateNote": "{int16 branch offset, big-endian}", + "ImmediateNote": [ + { + "Comment": "branch offset", + "Encoding": "int16 (big-endian)", + "Name": "TARGET" + } + ], "IntroducedVersion": 2, "Groups": [ "Flow Control" @@ -1081,7 +1837,13 @@ "Size": 3, "Doc": "branch unconditionally to TARGET", "DocExtra": "See `bnz` for details on how branches work. `b` always jumps to the offset.", - "ImmediateNote": "{int16 branch offset, big-endian}", + "ImmediateNote": [ + { + "Comment": "branch offset", + "Encoding": "int16 (big-endian)", + "Name": "TARGET" + } + ], "IntroducedVersion": 2, "Groups": [ "Flow Control" @@ -1090,7 +1852,9 @@ { "Opcode": 67, "Name": "return", - "Args": "U", + "Args": [ + "uint64" + ], "Size": 1, "Doc": "use A as success value; end", "IntroducedVersion": 2, @@ -1101,7 +1865,9 @@ { "Opcode": 68, "Name": "assert", - "Args": "U", + "Args": [ + "uint64" + ], "Size": 1, "Doc": "immediately fail unless A is a non-zero number", "IntroducedVersion": 3, @@ -1112,10 +1878,18 @@ { "Opcode": 69, "Name": "bury", - "Args": ".", + "Args": [ + "any" + ], "Size": 2, "Doc": "replace the Nth value from the top of the stack with A. bury 0 fails.", - "ImmediateNote": "{uint8 depth}", + "ImmediateNote": [ + { + "Comment": "depth", + "Encoding": "uint8", + "Name": "N" + } + ], "IntroducedVersion": 8, "Groups": [ "Flow Control" @@ -1126,7 +1900,13 @@ "Name": "popn", "Size": 2, "Doc": "remove N values from the top of the stack", - "ImmediateNote": "{uint8 stack depth}", + "ImmediateNote": [ + { + "Comment": "stack depth", + "Encoding": "uint8", + "Name": "N" + } + ], "IntroducedVersion": 8, "Groups": [ "Flow Control" @@ -1135,10 +1915,18 @@ { "Opcode": 71, "Name": "dupn", - "Args": ".", + "Args": [ + "any" + ], "Size": 2, "Doc": "duplicate A, N times", - "ImmediateNote": "{uint8 copy count}", + "ImmediateNote": [ + { + "Comment": "copy count", + "Encoding": "uint8", + "Name": "N" + } + ], "IntroducedVersion": 8, "Groups": [ "Flow Control" @@ -1147,7 +1935,9 @@ { "Opcode": 72, "Name": "pop", - "Args": ".", + "Args": [ + "any" + ], "Size": 1, "Doc": "discard A", "IntroducedVersion": 1, @@ -1158,8 +1948,13 @@ { "Opcode": 73, "Name": "dup", - "Args": ".", - "Returns": "..", + "Args": [ + "any" + ], + "Returns": [ + "any", + "any" + ], "Size": 1, "Doc": "duplicate A", "IntroducedVersion": 1, @@ -1170,8 +1965,16 @@ { "Opcode": 74, "Name": "dup2", - "Args": "..", - "Returns": "....", + "Args": [ + "any", + "any" + ], + "Returns": [ + "any", + "any", + "any", + "any" + ], "Size": 1, "Doc": "duplicate A and B", "IntroducedVersion": 2, @@ -1182,11 +1985,22 @@ { "Opcode": 75, "Name": "dig", - "Args": ".", - "Returns": "..", + "Args": [ + "any" + ], + "Returns": [ + "any", + "any" + ], "Size": 2, "Doc": "Nth value from the top of the stack. dig 0 is equivalent to dup", - "ImmediateNote": "{uint8 depth}", + "ImmediateNote": [ + { + "Comment": "depth", + "Encoding": "uint8", + "Name": "N" + } + ], "IntroducedVersion": 3, "Groups": [ "Flow Control" @@ -1195,8 +2009,14 @@ { "Opcode": 76, "Name": "swap", - "Args": "..", - "Returns": "..", + "Args": [ + "any", + "any" + ], + "Returns": [ + "any", + "any" + ], "Size": 1, "Doc": "swaps A and B on stack", "IntroducedVersion": 3, @@ -1207,8 +2027,14 @@ { "Opcode": 77, "Name": "select", - "Args": "..U", - "Returns": ".", + "Args": [ + "any", + "any", + "uint64" + ], + "Returns": [ + "any" + ], "Size": 1, "Doc": "selects one of two values based on top-of-stack: B if C != 0, else A", "IntroducedVersion": 3, @@ -1219,11 +2045,21 @@ { "Opcode": 78, "Name": "cover", - "Args": ".", - "Returns": ".", + "Args": [ + "any" + ], + "Returns": [ + "any" + ], "Size": 2, "Doc": "remove top of stack, and place it deeper in the stack such that N elements are above it. Fails if stack depth \u003c= N.", - "ImmediateNote": "{uint8 depth}", + "ImmediateNote": [ + { + "Comment": "depth", + "Encoding": "uint8", + "Name": "N" + } + ], "IntroducedVersion": 5, "Groups": [ "Flow Control" @@ -1232,11 +2068,21 @@ { "Opcode": 79, "Name": "uncover", - "Args": ".", - "Returns": ".", + "Args": [ + "any" + ], + "Returns": [ + "any" + ], "Size": 2, "Doc": "remove the value at depth N in the stack and shift above items down so the Nth deep value is on top of the stack. Fails if stack depth \u003c= N.", - "ImmediateNote": "{uint8 depth}", + "ImmediateNote": [ + { + "Comment": "depth", + "Encoding": "uint8", + "Name": "N" + } + ], "IntroducedVersion": 5, "Groups": [ "Flow Control" @@ -1245,8 +2091,13 @@ { "Opcode": 80, "Name": "concat", - "Args": "BB", - "Returns": "B", + "Args": [ + "[]byte", + "[]byte" + ], + "Returns": [ + "[]byte" + ], "Size": 1, "Doc": "join A and B", "DocExtra": "`concat` fails if the result would be greater than 4096 bytes.", @@ -1258,11 +2109,26 @@ { "Opcode": 81, "Name": "substring", - "Args": "B", - "Returns": "B", + "Args": [ + "[]byte" + ], + "Returns": [ + "[]byte" + ], "Size": 3, "Doc": "A range of bytes from A starting at S up to but not including E. If E \u003c S, or either is larger than the array length, the program fails", - "ImmediateNote": "{uint8 start position} {uint8 end position}", + "ImmediateNote": [ + { + "Comment": "start position", + "Encoding": "uint8", + "Name": "S" + }, + { + "Comment": "end position", + "Encoding": "uint8", + "Name": "E" + } + ], "IntroducedVersion": 2, "Groups": [ "Byte Array Manipulation" @@ -1271,8 +2137,14 @@ { "Opcode": 82, "Name": "substring3", - "Args": "BUU", - "Returns": "B", + "Args": [ + "[]byte", + "uint64", + "uint64" + ], + "Returns": [ + "[]byte" + ], "Size": 1, "Doc": "A range of bytes from A starting at B up to but not including C. If C \u003c B, or either is larger than the array length, the program fails", "IntroducedVersion": 2, @@ -1283,8 +2155,13 @@ { "Opcode": 83, "Name": "getbit", - "Args": ".U", - "Returns": "U", + "Args": [ + "any", + "uint64" + ], + "Returns": [ + "uint64" + ], "Size": 1, "Doc": "Bth bit of (byte-array or integer) A. If B is greater than or equal to the bit length of the value (8*byte length), the program fails", "DocExtra": "see explanation of bit ordering in setbit", @@ -1296,8 +2173,14 @@ { "Opcode": 84, "Name": "setbit", - "Args": ".UU", - "Returns": ".", + "Args": [ + "any", + "uint64", + "uint64" + ], + "Returns": [ + "any" + ], "Size": 1, "Doc": "Copy of (byte-array or integer) A, with the Bth bit set to (0 or 1) C. If B is greater than or equal to the bit length of the value (8*byte length), the program fails", "DocExtra": "When A is a uint64, index 0 is the least significant bit. Setting bit 3 to 1 on the integer 0 yields 8, or 2^3. When A is a byte array, index 0 is the leftmost bit of the leftmost byte. Setting bits 0 through 11 to 1 in a 4-byte-array of 0s yields the byte array 0xfff00000. Setting bit 3 to 1 on the 1-byte-array 0x00 yields the byte array 0x10.", @@ -1309,8 +2192,13 @@ { "Opcode": 85, "Name": "getbyte", - "Args": "BU", - "Returns": "U", + "Args": [ + "[]byte", + "uint64" + ], + "Returns": [ + "uint64" + ], "Size": 1, "Doc": "Bth byte of A, as an integer. If B is greater than or equal to the array length, the program fails", "IntroducedVersion": 3, @@ -1321,8 +2209,14 @@ { "Opcode": 86, "Name": "setbyte", - "Args": "BUU", - "Returns": "B", + "Args": [ + "[]byte", + "uint64", + "uint64" + ], + "Returns": [ + "[]byte" + ], "Size": 1, "Doc": "Copy of A with the Bth byte set to small integer (between 0..255) C. If B is greater than or equal to the array length, the program fails", "IntroducedVersion": 3, @@ -1333,11 +2227,26 @@ { "Opcode": 87, "Name": "extract", - "Args": "B", - "Returns": "B", + "Args": [ + "[]byte" + ], + "Returns": [ + "[]byte" + ], "Size": 3, "Doc": "A range of bytes from A starting at S up to but not including S+L. If L is 0, then extract to the end of the string. If S or S+L is larger than the array length, the program fails", - "ImmediateNote": "{uint8 start position} {uint8 length}", + "ImmediateNote": [ + { + "Comment": "start position", + "Encoding": "uint8", + "Name": "S" + }, + { + "Comment": "length", + "Encoding": "uint8", + "Name": "L" + } + ], "IntroducedVersion": 5, "Groups": [ "Byte Array Manipulation" @@ -1346,8 +2255,14 @@ { "Opcode": 88, "Name": "extract3", - "Args": "BUU", - "Returns": "B", + "Args": [ + "[]byte", + "uint64", + "uint64" + ], + "Returns": [ + "[]byte" + ], "Size": 1, "Doc": "A range of bytes from A starting at B up to but not including B+C. If B+C is larger than the array length, the program fails\n`extract3` can be called using `extract` with no immediates.", "IntroducedVersion": 5, @@ -1358,8 +2273,13 @@ { "Opcode": 89, "Name": "extract_uint16", - "Args": "BU", - "Returns": "U", + "Args": [ + "[]byte", + "uint64" + ], + "Returns": [ + "uint64" + ], "Size": 1, "Doc": "A uint16 formed from a range of big-endian bytes from A starting at B up to but not including B+2. If B+2 is larger than the array length, the program fails", "IntroducedVersion": 5, @@ -1370,8 +2290,13 @@ { "Opcode": 90, "Name": "extract_uint32", - "Args": "BU", - "Returns": "U", + "Args": [ + "[]byte", + "uint64" + ], + "Returns": [ + "uint64" + ], "Size": 1, "Doc": "A uint32 formed from a range of big-endian bytes from A starting at B up to but not including B+4. If B+4 is larger than the array length, the program fails", "IntroducedVersion": 5, @@ -1382,8 +2307,13 @@ { "Opcode": 91, "Name": "extract_uint64", - "Args": "BU", - "Returns": "U", + "Args": [ + "[]byte", + "uint64" + ], + "Returns": [ + "uint64" + ], "Size": 1, "Doc": "A uint64 formed from a range of big-endian bytes from A starting at B up to but not including B+8. If B+8 is larger than the array length, the program fails", "IntroducedVersion": 5, @@ -1394,11 +2324,22 @@ { "Opcode": 92, "Name": "replace2", - "Args": "BB", - "Returns": "B", + "Args": [ + "[]byte", + "[]byte" + ], + "Returns": [ + "[]byte" + ], "Size": 2, "Doc": "Copy of A with the bytes starting at S replaced by the bytes of B. Fails if S+len(B) exceeds len(A)\n`replace2` can be called using `replace` with 1 immediate.", - "ImmediateNote": "{uint8 start position}", + "ImmediateNote": [ + { + "Comment": "start position", + "Encoding": "uint8", + "Name": "S" + } + ], "IntroducedVersion": 7, "Groups": [ "Byte Array Manipulation" @@ -1407,8 +2348,14 @@ { "Opcode": 93, "Name": "replace3", - "Args": "BUB", - "Returns": "B", + "Args": [ + "[]byte", + "uint64", + "[]byte" + ], + "Returns": [ + "[]byte" + ], "Size": 1, "Doc": "Copy of A with the bytes starting at B replaced by the bytes of C. Fails if B+len(C) exceeds len(A)\n`replace3` can be called using `replace` with no immediates.", "IntroducedVersion": 7, @@ -1419,17 +2366,31 @@ { "Opcode": 94, "Name": "base64_decode", - "Args": "B", - "Returns": "B", + "Args": [ + "[]byte" + ], + "Returns": [ + "[]byte" + ], "Size": 2, "ArgEnum": [ "URLEncoding", "StdEncoding" ], - "ArgEnumTypes": "..", + "ArgEnumTypes": [ + "any", + "any" + ], "Doc": "decode A which was base64-encoded using _encoding_ E. Fail if A is not base64 encoded with encoding E", "DocExtra": "*Warning*: Usage should be restricted to very rare use cases. In almost all cases, smart contracts should directly handle non-encoded byte-strings.\tThis opcode should only be used in cases where base64 is the only available option, e.g. interoperability with a third-party that only signs base64 strings.\n\n Decodes A using the base64 encoding E. Specify the encoding with an immediate arg either as URL and Filename Safe (`URLEncoding`) or Standard (`StdEncoding`). See [RFC 4648 sections 4 and 5](https://rfc-editor.org/rfc/rfc4648.html#section-4). It is assumed that the encoding ends with the exact number of `=` padding characters as required by the RFC. When padding occurs, any unused pad bits in the encoding must be set to zero or the decoding will fail. The special cases of `\\n` and `\\r` are allowed but completely ignored. An error will result when attempting to decode a string with a character that is not in the encoding alphabet or not one of `=`, `\\r`, or `\\n`.", - "ImmediateNote": "{uint8 encoding index}", + "ImmediateNote": [ + { + "Comment": "encoding index", + "Encoding": "uint8", + "Name": "E", + "Reference": "base64" + } + ], "IntroducedVersion": 7, "Groups": [ "Byte Array Manipulation" @@ -1438,18 +2399,34 @@ { "Opcode": 95, "Name": "json_ref", - "Args": "BB", - "Returns": ".", + "Args": [ + "[]byte", + "[]byte" + ], + "Returns": [ + "any" + ], "Size": 2, "ArgEnum": [ "JSONString", "JSONUint64", "JSONObject" ], - "ArgEnumTypes": "BUB", + "ArgEnumTypes": [ + "[]byte", + "uint64", + "[]byte" + ], "Doc": "key B's value, of type R, from a [valid](jsonspec.md) utf-8 encoded json object A", "DocExtra": "*Warning*: Usage should be restricted to very rare use cases, as JSON decoding is expensive and quite limited. In addition, JSON objects are large and not optimized for size.\n\nAlmost all smart contracts should use simpler and smaller methods (such as the [ABI](https://arc.algorand.foundation/ARCs/arc-0004). This opcode should only be used in cases where JSON is only available option, e.g. when a third-party only signs JSON.", - "ImmediateNote": "{uint8 return type index}", + "ImmediateNote": [ + { + "Comment": "return type index", + "Encoding": "uint8", + "Name": "R", + "Reference": "json_ref" + } + ], "IntroducedVersion": 7, "Groups": [ "Byte Array Manipulation" @@ -1458,8 +2435,12 @@ { "Opcode": 96, "Name": "balance", - "Args": ".", - "Returns": "U", + "Args": [ + "any" + ], + "Returns": [ + "uint64" + ], "Size": 1, "Doc": "balance for account A, in microalgos. The balance is observed after the effects of previous transactions in the group, and after the fee for the current transaction is deducted. Changes caused by inner transactions are observable immediately following `itxn_submit`", "DocExtra": "params: Txn.Accounts offset (or, since v4, an _available_ account address). Return: value.", @@ -1471,8 +2452,13 @@ { "Opcode": 97, "Name": "app_opted_in", - "Args": ".U", - "Returns": "U", + "Args": [ + "any", + "uint64" + ], + "Returns": [ + "bool" + ], "Size": 1, "Doc": "1 if account A is opted in to application B, else 0", "DocExtra": "params: Txn.Accounts offset (or, since v4, an _available_ account address), _available_ application id (or, since v4, a Txn.ForeignApps offset). Return: 1 if opted in and 0 otherwise.", @@ -1484,8 +2470,13 @@ { "Opcode": 98, "Name": "app_local_get", - "Args": ".B", - "Returns": ".", + "Args": [ + "any", + "[]byte" + ], + "Returns": [ + "any" + ], "Size": 1, "Doc": "local state of the key B in the current application in account A", "DocExtra": "params: Txn.Accounts offset (or, since v4, an _available_ account address), state key. Return: value. The value is zero (of type uint64) if the key does not exist.", @@ -1497,8 +2488,15 @@ { "Opcode": 99, "Name": "app_local_get_ex", - "Args": ".UB", - "Returns": ".U", + "Args": [ + "any", + "uint64", + "[]byte" + ], + "Returns": [ + "any", + "bool" + ], "Size": 1, "Doc": "X is the local state of application B, key C in account A. Y is 1 if key existed, else 0", "DocExtra": "params: Txn.Accounts offset (or, since v4, an _available_ account address), _available_ application id (or, since v4, a Txn.ForeignApps offset), state key. Return: did_exist flag (top of the stack, 1 if the application and key existed and 0 otherwise), value. The value is zero (of type uint64) if the key does not exist.", @@ -1510,8 +2508,12 @@ { "Opcode": 100, "Name": "app_global_get", - "Args": "B", - "Returns": ".", + "Args": [ + "[]byte" + ], + "Returns": [ + "any" + ], "Size": 1, "Doc": "global state of the key A in the current application", "DocExtra": "params: state key. Return: value. The value is zero (of type uint64) if the key does not exist.", @@ -1523,8 +2525,14 @@ { "Opcode": 101, "Name": "app_global_get_ex", - "Args": "UB", - "Returns": ".U", + "Args": [ + "uint64", + "[]byte" + ], + "Returns": [ + "any", + "bool" + ], "Size": 1, "Doc": "X is the global state of application A, key B. Y is 1 if key existed, else 0", "DocExtra": "params: Txn.ForeignApps offset (or, since v4, an _available_ application id), state key. Return: did_exist flag (top of the stack, 1 if the application and key existed and 0 otherwise), value. The value is zero (of type uint64) if the key does not exist.", @@ -1536,7 +2544,11 @@ { "Opcode": 102, "Name": "app_local_put", - "Args": ".B.", + "Args": [ + "any", + "[]byte", + "any" + ], "Size": 1, "Doc": "write C to key B in account A's local state of the current application", "DocExtra": "params: Txn.Accounts offset (or, since v4, an _available_ account address), state key, value.", @@ -1548,7 +2560,10 @@ { "Opcode": 103, "Name": "app_global_put", - "Args": "B.", + "Args": [ + "[]byte", + "any" + ], "Size": 1, "Doc": "write B to key A in the global state of the current application", "IntroducedVersion": 2, @@ -1559,7 +2574,10 @@ { "Opcode": 104, "Name": "app_local_del", - "Args": ".B", + "Args": [ + "any", + "[]byte" + ], "Size": 1, "Doc": "delete key B from account A's local state of the current application", "DocExtra": "params: Txn.Accounts offset (or, since v4, an _available_ account address), state key.\n\nDeleting a key which is already absent has no effect on the application local state. (In particular, it does _not_ cause the program to fail.)", @@ -1571,7 +2589,9 @@ { "Opcode": 105, "Name": "app_global_del", - "Args": "B", + "Args": [ + "[]byte" + ], "Size": 1, "Doc": "delete key A from the global state of the current application", "DocExtra": "params: state key.\n\nDeleting a key which is already absent has no effect on the application global state. (In particular, it does _not_ cause the program to fail.)", @@ -1583,17 +2603,33 @@ { "Opcode": 112, "Name": "asset_holding_get", - "Args": ".U", - "Returns": ".U", + "Args": [ + "any", + "uint64" + ], + "Returns": [ + "any", + "bool" + ], "Size": 2, "ArgEnum": [ "AssetBalance", "AssetFrozen" ], - "ArgEnumTypes": "UU", + "ArgEnumTypes": [ + "uint64", + "bool" + ], "Doc": "X is field F from account A's holding of asset B. Y is 1 if A is opted into B, else 0", "DocExtra": "params: Txn.Accounts offset (or, since v4, an _available_ address), asset id (or, since v4, a Txn.ForeignAssets offset). Return: did_exist flag (1 if the asset existed and 0 otherwise), value.", - "ImmediateNote": "{uint8 asset holding field index}", + "ImmediateNote": [ + { + "Comment": "asset holding field index", + "Encoding": "uint8", + "Name": "F", + "Reference": "asset_holding" + } + ], "IntroducedVersion": 2, "Groups": [ "State Access" @@ -1602,8 +2638,13 @@ { "Opcode": 113, "Name": "asset_params_get", - "Args": "U", - "Returns": ".U", + "Args": [ + "uint64" + ], + "Returns": [ + "any", + "bool" + ], "Size": 2, "ArgEnum": [ "AssetTotal", @@ -1619,10 +2660,30 @@ "AssetClawback", "AssetCreator" ], - "ArgEnumTypes": "UUUBBBBBBBBB", + "ArgEnumTypes": [ + "uint64", + "uint64", + "bool", + "[]byte", + "[]byte", + "[]byte", + "[32]byte", + "address", + "address", + "address", + "address", + "address" + ], "Doc": "X is field F from asset A. Y is 1 if A exists, else 0", "DocExtra": "params: Txn.ForeignAssets offset (or, since v4, an _available_ asset id. Return: did_exist flag (1 if the asset existed and 0 otherwise), value.", - "ImmediateNote": "{uint8 asset params field index}", + "ImmediateNote": [ + { + "Comment": "asset params field index", + "Encoding": "uint8", + "Name": "F", + "Reference": "asset_params" + } + ], "IntroducedVersion": 2, "Groups": [ "State Access" @@ -1631,8 +2692,13 @@ { "Opcode": 114, "Name": "app_params_get", - "Args": "U", - "Returns": ".U", + "Args": [ + "uint64" + ], + "Returns": [ + "any", + "bool" + ], "Size": 2, "ArgEnum": [ "AppApprovalProgram", @@ -1645,10 +2711,27 @@ "AppCreator", "AppAddress" ], - "ArgEnumTypes": "BBUUUUUBB", + "ArgEnumTypes": [ + "[]byte", + "[]byte", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64", + "address", + "address" + ], "Doc": "X is field F from app A. Y is 1 if A exists, else 0", "DocExtra": "params: Txn.ForeignApps offset or an _available_ app id. Return: did_exist flag (1 if the application existed and 0 otherwise), value.", - "ImmediateNote": "{uint8 app params field index}", + "ImmediateNote": [ + { + "Comment": "app params field index", + "Encoding": "uint8", + "Name": "F", + "Reference": "app_params" + } + ], "IntroducedVersion": 5, "Groups": [ "State Access" @@ -1657,8 +2740,13 @@ { "Opcode": 115, "Name": "acct_params_get", - "Args": ".", - "Returns": ".U", + "Args": [ + "any" + ], + "Returns": [ + "any", + "bool" + ], "Size": 2, "ArgEnum": [ "AcctBalance", @@ -1674,9 +2762,29 @@ "AcctTotalBoxes", "AcctTotalBoxBytes" ], - "ArgEnumTypes": "UUBUUUUUUUUU", + "ArgEnumTypes": [ + "uint64", + "uint64", + "address", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64" + ], "Doc": "X is field F from account A. Y is 1 if A owns positive algos, else 0", - "ImmediateNote": "{uint8 account params field index}", + "ImmediateNote": [ + { + "Comment": "account params field index", + "Encoding": "uint8", + "Name": "F", + "Reference": "acct_params" + } + ], "IntroducedVersion": 6, "Groups": [ "State Access" @@ -1685,8 +2793,12 @@ { "Opcode": 120, "Name": "min_balance", - "Args": ".", - "Returns": "U", + "Args": [ + "any" + ], + "Returns": [ + "uint64" + ], "Size": 1, "Doc": "minimum required balance for account A, in microalgos. Required balance is affected by ASA, App, and Box usage. When creating or opting into an app, the minimum balance grows before the app code runs, therefore the increase is visible there. When deleting or closing out, the minimum balance decreases after the app executes. Changes caused by inner transactions or box usage are observable immediately following the opcode effecting the change.", "DocExtra": "params: Txn.Accounts offset (or, since v4, an _available_ account address). Return: value.", @@ -1698,11 +2810,19 @@ { "Opcode": 128, "Name": "pushbytes", - "Returns": "B", + "Returns": [ + "[]byte" + ], "Size": 0, "Doc": "immediate BYTES", "DocExtra": "pushbytes args are not added to the bytecblock during assembly processes", - "ImmediateNote": "{varuint length} {bytes}", + "ImmediateNote": [ + { + "Comment": "a byte constant", + "Encoding": "varuint length, bytes", + "Name": "BYTES" + } + ], "IntroducedVersion": 3, "Groups": [ "Loading Values" @@ -1711,11 +2831,19 @@ { "Opcode": 129, "Name": "pushint", - "Returns": "U", + "Returns": [ + "uint64" + ], "Size": 0, "Doc": "immediate UINT", "DocExtra": "pushint args are not added to the intcblock during assembly processes", - "ImmediateNote": "{varuint int}", + "ImmediateNote": [ + { + "Comment": "an int constant", + "Encoding": "varuint", + "Name": "UINT" + } + ], "IntroducedVersion": 3, "Groups": [ "Loading Values" @@ -1727,7 +2855,13 @@ "Size": 0, "Doc": "push sequences of immediate byte arrays to stack (first byte array being deepest)", "DocExtra": "pushbytess args are not added to the bytecblock during assembly processes", - "ImmediateNote": "{varuint count} [({varuint length} bytes), ...]", + "ImmediateNote": [ + { + "Comment": "a list of byte constants", + "Encoding": "varuint count, [varuint length, bytes ...]", + "Name": "BYTES ..." + } + ], "IntroducedVersion": 8, "Groups": [ "Loading Values" @@ -1739,7 +2873,13 @@ "Size": 0, "Doc": "push sequence of immediate uints to stack in the order they appear (first uint being deepest)", "DocExtra": "pushints args are not added to the intcblock during assembly processes", - "ImmediateNote": "{varuint count} [{varuint value}, ...]", + "ImmediateNote": [ + { + "Comment": "a list of int constants", + "Encoding": "varuint count, [varuint ...]", + "Name": "UINT ..." + } + ], "IntroducedVersion": 8, "Groups": [ "Loading Values" @@ -1748,8 +2888,14 @@ { "Opcode": 132, "Name": "ed25519verify_bare", - "Args": "BBB", - "Returns": "U", + "Args": [ + "[]byte", + "[]byte", + "[]byte" + ], + "Returns": [ + "bool" + ], "Size": 1, "Doc": "for (data A, signature B, pubkey C) verify the signature of the data against the pubkey =\u003e {0 or 1}", "IntroducedVersion": 7, @@ -1763,7 +2909,13 @@ "Size": 3, "Doc": "branch unconditionally to TARGET, saving the next instruction on the call stack", "DocExtra": "The call stack is separate from the data stack. Only `callsub`, `retsub`, and `proto` manipulate it.", - "ImmediateNote": "{int16 branch offset, big-endian}", + "ImmediateNote": [ + { + "Comment": "branch offset", + "Encoding": "int16 (big-endian)", + "Name": "TARGET" + } + ], "IntroducedVersion": 4, "Groups": [ "Flow Control" @@ -1786,7 +2938,18 @@ "Size": 3, "Doc": "Prepare top call frame for a retsub that will assume A args and R return values.", "DocExtra": "Fails unless the last instruction executed was a `callsub`.", - "ImmediateNote": "{uint8 arguments} {uint8 return values}", + "ImmediateNote": [ + { + "Comment": "number of arguments", + "Encoding": "uint8", + "Name": "A" + }, + { + "Comment": "number of return values", + "Encoding": "uint8", + "Name": "R" + } + ], "IntroducedVersion": 8, "Groups": [ "Flow Control" @@ -1795,10 +2958,18 @@ { "Opcode": 139, "Name": "frame_dig", - "Returns": ".", + "Returns": [ + "any" + ], "Size": 2, "Doc": "Nth (signed) value from the frame pointer.", - "ImmediateNote": "{int8 frame slot}", + "ImmediateNote": [ + { + "Comment": "frame slot", + "Encoding": "int8", + "Name": "I" + } + ], "IntroducedVersion": 8, "Groups": [ "Flow Control" @@ -1807,10 +2978,18 @@ { "Opcode": 140, "Name": "frame_bury", - "Args": ".", + "Args": [ + "any" + ], "Size": 2, "Doc": "replace the Nth (signed) value from the frame pointer in the stack with A", - "ImmediateNote": "{int8 frame slot}", + "ImmediateNote": [ + { + "Comment": "frame slot", + "Encoding": "int8", + "Name": "I" + } + ], "IntroducedVersion": 8, "Groups": [ "Flow Control" @@ -1819,10 +2998,18 @@ { "Opcode": 141, "Name": "switch", - "Args": "U", + "Args": [ + "uint64" + ], "Size": 0, "Doc": "branch to the Ath label. Continue at following instruction if index A exceeds the number of labels.", - "ImmediateNote": "{uint8 branch count} [{int16 branch offset, big-endian}, ...]", + "ImmediateNote": [ + { + "Comment": "list of labels", + "Encoding": "varuint count, [int16 (big-endian) ...]", + "Name": "TARGET ..." + } + ], "IntroducedVersion": 8, "Groups": [ "Flow Control" @@ -1834,7 +3021,13 @@ "Size": 0, "Doc": "given match cases from A[1] to A[N], branch to the Ith label where A[I] = B. Continue to the following instruction if no matches are found.", "DocExtra": "`match` consumes N+1 values from the stack. Let the top stack value be B. The following N values represent an ordered list of match cases/constants (A), where the first value (A[0]) is the deepest in the stack. The immediate arguments are an ordered list of N labels (T). `match` will branch to target T[I], where A[I] = B. If there are no matches then execution continues on to the next instruction.", - "ImmediateNote": "{uint8 branch count} [{int16 branch offset, big-endian}, ...]", + "ImmediateNote": [ + { + "Comment": "list of labels", + "Encoding": "varuint count, [int16 (big-endian) ...]", + "Name": "TARGET ..." + } + ], "IntroducedVersion": 8, "Groups": [ "Flow Control" @@ -1843,8 +3036,13 @@ { "Opcode": 144, "Name": "shl", - "Args": "UU", - "Returns": "U", + "Args": [ + "uint64", + "uint64" + ], + "Returns": [ + "uint64" + ], "Size": 1, "Doc": "A times 2^B, modulo 2^64", "IntroducedVersion": 4, @@ -1855,8 +3053,13 @@ { "Opcode": 145, "Name": "shr", - "Args": "UU", - "Returns": "U", + "Args": [ + "uint64", + "uint64" + ], + "Returns": [ + "uint64" + ], "Size": 1, "Doc": "A divided by 2^B", "IntroducedVersion": 4, @@ -1867,8 +3070,12 @@ { "Opcode": 146, "Name": "sqrt", - "Args": "U", - "Returns": "U", + "Args": [ + "uint64" + ], + "Returns": [ + "uint64" + ], "Size": 1, "Doc": "The largest integer I such that I^2 \u003c= A", "IntroducedVersion": 4, @@ -1879,8 +3086,12 @@ { "Opcode": 147, "Name": "bitlen", - "Args": ".", - "Returns": "U", + "Args": [ + "any" + ], + "Returns": [ + "uint64" + ], "Size": 1, "Doc": "The highest set bit in A. If A is a byte-array, it is interpreted as a big-endian unsigned integer. bitlen of 0 is 0, bitlen of 8 is 4", "DocExtra": "bitlen interprets arrays as big-endian integers, unlike setbit/getbit", @@ -1892,8 +3103,13 @@ { "Opcode": 148, "Name": "exp", - "Args": "UU", - "Returns": "U", + "Args": [ + "uint64", + "uint64" + ], + "Returns": [ + "uint64" + ], "Size": 1, "Doc": "A raised to the Bth power. Fail if A == B == 0 and on overflow", "IntroducedVersion": 4, @@ -1904,8 +3120,14 @@ { "Opcode": 149, "Name": "expw", - "Args": "UU", - "Returns": "UU", + "Args": [ + "uint64", + "uint64" + ], + "Returns": [ + "uint64", + "uint64" + ], "Size": 1, "Doc": "A raised to the Bth power as a 128-bit result in two uint64s. X is the high 64 bits, Y is the low. Fail if A == B == 0 or if the results exceeds 2^128-1", "IntroducedVersion": 4, @@ -1916,8 +3138,12 @@ { "Opcode": 150, "Name": "bsqrt", - "Args": "B", - "Returns": "B", + "Args": [ + "[]byte" + ], + "Returns": [ + "[]byte" + ], "Size": 1, "Doc": "The largest integer I such that I^2 \u003c= A. A and I are interpreted as big-endian unsigned integers", "IntroducedVersion": 6, @@ -1928,8 +3154,14 @@ { "Opcode": 151, "Name": "divw", - "Args": "UUU", - "Returns": "U", + "Args": [ + "uint64", + "uint64", + "uint64" + ], + "Returns": [ + "uint64" + ], "Size": 1, "Doc": "A,B / C. Fail if C == 0 or if result overflows.", "DocExtra": "The notation A,B indicates that A and B are interpreted as a uint128 value, with A as the high uint64 and B the low.", @@ -1941,8 +3173,12 @@ { "Opcode": 152, "Name": "sha3_256", - "Args": "B", - "Returns": "B", + "Args": [ + "[]byte" + ], + "Returns": [ + "[]byte" + ], "Size": 1, "Doc": "SHA3_256 hash of value A, yields [32]byte", "IntroducedVersion": 7, @@ -1953,8 +3189,13 @@ { "Opcode": 160, "Name": "b+", - "Args": "BB", - "Returns": "B", + "Args": [ + "bigint", + "bigint" + ], + "Returns": [ + "[]byte" + ], "Size": 1, "Doc": "A plus B. A and B are interpreted as big-endian unsigned integers", "IntroducedVersion": 4, @@ -1965,8 +3206,13 @@ { "Opcode": 161, "Name": "b-", - "Args": "BB", - "Returns": "B", + "Args": [ + "bigint", + "bigint" + ], + "Returns": [ + "bigint" + ], "Size": 1, "Doc": "A minus B. A and B are interpreted as big-endian unsigned integers. Fail on underflow.", "IntroducedVersion": 4, @@ -1977,8 +3223,13 @@ { "Opcode": 162, "Name": "b/", - "Args": "BB", - "Returns": "B", + "Args": [ + "bigint", + "bigint" + ], + "Returns": [ + "bigint" + ], "Size": 1, "Doc": "A divided by B (truncated division). A and B are interpreted as big-endian unsigned integers. Fail if B is zero.", "IntroducedVersion": 4, @@ -1989,8 +3240,13 @@ { "Opcode": 163, "Name": "b*", - "Args": "BB", - "Returns": "B", + "Args": [ + "bigint", + "bigint" + ], + "Returns": [ + "[]byte" + ], "Size": 1, "Doc": "A times B. A and B are interpreted as big-endian unsigned integers.", "IntroducedVersion": 4, @@ -2001,8 +3257,13 @@ { "Opcode": 164, "Name": "b\u003c", - "Args": "BB", - "Returns": "U", + "Args": [ + "bigint", + "bigint" + ], + "Returns": [ + "bool" + ], "Size": 1, "Doc": "1 if A is less than B, else 0. A and B are interpreted as big-endian unsigned integers", "IntroducedVersion": 4, @@ -2013,8 +3274,13 @@ { "Opcode": 165, "Name": "b\u003e", - "Args": "BB", - "Returns": "U", + "Args": [ + "bigint", + "bigint" + ], + "Returns": [ + "bool" + ], "Size": 1, "Doc": "1 if A is greater than B, else 0. A and B are interpreted as big-endian unsigned integers", "IntroducedVersion": 4, @@ -2025,8 +3291,13 @@ { "Opcode": 166, "Name": "b\u003c=", - "Args": "BB", - "Returns": "U", + "Args": [ + "bigint", + "bigint" + ], + "Returns": [ + "bool" + ], "Size": 1, "Doc": "1 if A is less than or equal to B, else 0. A and B are interpreted as big-endian unsigned integers", "IntroducedVersion": 4, @@ -2037,8 +3308,13 @@ { "Opcode": 167, "Name": "b\u003e=", - "Args": "BB", - "Returns": "U", + "Args": [ + "bigint", + "bigint" + ], + "Returns": [ + "bool" + ], "Size": 1, "Doc": "1 if A is greater than or equal to B, else 0. A and B are interpreted as big-endian unsigned integers", "IntroducedVersion": 4, @@ -2049,8 +3325,13 @@ { "Opcode": 168, "Name": "b==", - "Args": "BB", - "Returns": "U", + "Args": [ + "bigint", + "bigint" + ], + "Returns": [ + "bool" + ], "Size": 1, "Doc": "1 if A is equal to B, else 0. A and B are interpreted as big-endian unsigned integers", "IntroducedVersion": 4, @@ -2061,8 +3342,13 @@ { "Opcode": 169, "Name": "b!=", - "Args": "BB", - "Returns": "U", + "Args": [ + "bigint", + "bigint" + ], + "Returns": [ + "bool" + ], "Size": 1, "Doc": "0 if A is equal to B, else 1. A and B are interpreted as big-endian unsigned integers", "IntroducedVersion": 4, @@ -2073,8 +3359,13 @@ { "Opcode": 170, "Name": "b%", - "Args": "BB", - "Returns": "B", + "Args": [ + "[]byte", + "[]byte" + ], + "Returns": [ + "[]byte" + ], "Size": 1, "Doc": "A modulo B. A and B are interpreted as big-endian unsigned integers. Fail if B is zero.", "IntroducedVersion": 4, @@ -2085,8 +3376,13 @@ { "Opcode": 171, "Name": "b|", - "Args": "BB", - "Returns": "B", + "Args": [ + "[]byte", + "[]byte" + ], + "Returns": [ + "[]byte" + ], "Size": 1, "Doc": "A bitwise-or B. A and B are zero-left extended to the greater of their lengths", "IntroducedVersion": 4, @@ -2097,8 +3393,13 @@ { "Opcode": 172, "Name": "b\u0026", - "Args": "BB", - "Returns": "B", + "Args": [ + "[]byte", + "[]byte" + ], + "Returns": [ + "[]byte" + ], "Size": 1, "Doc": "A bitwise-and B. A and B are zero-left extended to the greater of their lengths", "IntroducedVersion": 4, @@ -2109,8 +3410,13 @@ { "Opcode": 173, "Name": "b^", - "Args": "BB", - "Returns": "B", + "Args": [ + "[]byte", + "[]byte" + ], + "Returns": [ + "[]byte" + ], "Size": 1, "Doc": "A bitwise-xor B. A and B are zero-left extended to the greater of their lengths", "IntroducedVersion": 4, @@ -2121,8 +3427,12 @@ { "Opcode": 174, "Name": "b~", - "Args": "B", - "Returns": "B", + "Args": [ + "[]byte" + ], + "Returns": [ + "[]byte" + ], "Size": 1, "Doc": "A with all bits inverted", "IntroducedVersion": 4, @@ -2133,8 +3443,12 @@ { "Opcode": 175, "Name": "bzero", - "Args": "U", - "Returns": "B", + "Args": [ + "uint64" + ], + "Returns": [ + "[]byte" + ], "Size": 1, "Doc": "zero filled byte-array of length A", "IntroducedVersion": 4, @@ -2145,7 +3459,9 @@ { "Opcode": 176, "Name": "log", - "Args": "B", + "Args": [ + "[]byte" + ], "Size": 1, "Doc": "write A to log state of the current application", "DocExtra": "`log` fails if called more than MaxLogCalls times in a program, or if the sum of logged bytes exceeds 1024 bytes.", @@ -2168,7 +3484,9 @@ { "Opcode": 178, "Name": "itxn_field", - "Args": ".", + "Args": [ + "any" + ], "Size": 2, "ArgEnum": [ "Sender", @@ -2223,10 +3541,69 @@ "ApprovalProgramPages", "ClearStateProgramPages" ], - "ArgEnumTypes": "BUBBUBBBUUUBUUUBBBUUBBBBBUUUUBBBBBBBBUBUUUUUUUUUBBB", + "ArgEnumTypes": [ + "address", + "uint64", + "[]byte", + "address", + "uint64", + "address", + "[32]byte", + "[32]byte", + "uint64", + "uint64", + "uint64", + "[]byte", + "uint64", + "uint64", + "uint64", + "address", + "address", + "address", + "uint64", + "uint64", + "[]byte", + "address", + "[]byte", + "[]byte", + "address", + "uint64", + "uint64", + "uint64", + "bool", + "[]byte", + "[]byte", + "[]byte", + "[32]byte", + "address", + "address", + "address", + "address", + "uint64", + "address", + "bool", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64", + "bool", + "[]byte", + "[]byte", + "[]byte" + ], "Doc": "set field F of the current inner transaction to A", "DocExtra": "`itxn_field` fails if A is of the wrong type for F, including a byte array of the wrong size for use as an address when F is an address field. `itxn_field` also fails if A is an account, asset, or app that is not _available_, or an attempt is made extend an array field beyond the limit imposed by consensus parameters. (Addresses set into asset params of acfg transactions need not be _available_.)", - "ImmediateNote": "{uint8 transaction field index}", + "ImmediateNote": [ + { + "Comment": "transaction field index", + "Encoding": "uint8", + "Name": "F", + "Reference": "txn" + } + ], "IntroducedVersion": 5, "Groups": [ "Inner Transactions" @@ -2246,7 +3623,9 @@ { "Opcode": 180, "Name": "itxn", - "Returns": ".", + "Returns": [ + "any" + ], "Size": 2, "ArgEnum": [ "Sender", @@ -2318,9 +3697,85 @@ "ClearStateProgramPages", "NumClearStateProgramPages" ], - "ArgEnumTypes": "BUUUUBBBUBBBUUUBUUUBBBUBUUBUBUBBBUUUUBBBBBBBBUBUUUUUUUUUUUBUUUBBBUBU", + "ArgEnumTypes": [ + "address", + "uint64", + "uint64", + "uint64", + "uint64", + "[]byte", + "[32]byte", + "address", + "uint64", + "address", + "[32]byte", + "[32]byte", + "uint64", + "uint64", + "uint64", + "[]byte", + "uint64", + "uint64", + "uint64", + "address", + "address", + "address", + "uint64", + "[32]byte", + "uint64", + "uint64", + "[]byte", + "uint64", + "address", + "uint64", + "[]byte", + "[]byte", + "address", + "uint64", + "uint64", + "uint64", + "bool", + "[]byte", + "[]byte", + "[]byte", + "[32]byte", + "address", + "address", + "address", + "address", + "uint64", + "address", + "bool", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64", + "bool", + "[]byte", + "uint64", + "uint64", + "uint64", + "[]byte", + "[]byte", + "[]byte", + "uint64", + "[]byte", + "uint64" + ], "Doc": "field F of the last inner transaction", - "ImmediateNote": "{uint8 transaction field index}", + "ImmediateNote": [ + { + "Comment": "transaction field index", + "Encoding": "uint8", + "Name": "F", + "Reference": "txn" + } + ], "IntroducedVersion": 5, "Groups": [ "Inner Transactions" @@ -2329,7 +3784,9 @@ { "Opcode": 181, "Name": "itxna", - "Returns": ".", + "Returns": [ + "any" + ], "Size": 3, "ArgEnum": [ "ApplicationArgs", @@ -2340,9 +3797,29 @@ "ApprovalProgramPages", "ClearStateProgramPages" ], - "ArgEnumTypes": "BBUUBBB", + "ArgEnumTypes": [ + "[]byte", + "address", + "uint64", + "uint64", + "[]byte", + "[]byte", + "[]byte" + ], "Doc": "Ith value of the array field F of the last inner transaction", - "ImmediateNote": "{uint8 transaction field index} {uint8 transaction field array index}", + "ImmediateNote": [ + { + "Comment": "transaction field index", + "Encoding": "uint8", + "Name": "F", + "Reference": "txna" + }, + { + "Comment": "a transaction field array index", + "Encoding": "uint8", + "Name": "I" + } + ], "IntroducedVersion": 5, "Groups": [ "Inner Transactions" @@ -2362,7 +3839,9 @@ { "Opcode": 183, "Name": "gitxn", - "Returns": ".", + "Returns": [ + "any" + ], "Size": 3, "ArgEnum": [ "Sender", @@ -2434,9 +3913,90 @@ "ClearStateProgramPages", "NumClearStateProgramPages" ], - "ArgEnumTypes": "BUUUUBBBUBBBUUUBUUUBBBUBUUBUBUBBBUUUUBBBBBBBBUBUUUUUUUUUUUBUUUBBBUBU", + "ArgEnumTypes": [ + "address", + "uint64", + "uint64", + "uint64", + "uint64", + "[]byte", + "[32]byte", + "address", + "uint64", + "address", + "[32]byte", + "[32]byte", + "uint64", + "uint64", + "uint64", + "[]byte", + "uint64", + "uint64", + "uint64", + "address", + "address", + "address", + "uint64", + "[32]byte", + "uint64", + "uint64", + "[]byte", + "uint64", + "address", + "uint64", + "[]byte", + "[]byte", + "address", + "uint64", + "uint64", + "uint64", + "bool", + "[]byte", + "[]byte", + "[]byte", + "[32]byte", + "address", + "address", + "address", + "address", + "uint64", + "address", + "bool", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64", + "uint64", + "bool", + "[]byte", + "uint64", + "uint64", + "uint64", + "[]byte", + "[]byte", + "[]byte", + "uint64", + "[]byte", + "uint64" + ], "Doc": "field F of the Tth transaction in the last inner group submitted", - "ImmediateNote": "{uint8 transaction group index} {uint8 transaction field index}", + "ImmediateNote": [ + { + "Comment": "transaction group index", + "Encoding": "uint8", + "Name": "T" + }, + { + "Comment": "transaction field index", + "Encoding": "uint8", + "Name": "F", + "Reference": "txn" + } + ], "IntroducedVersion": 6, "Groups": [ "Inner Transactions" @@ -2445,7 +4005,9 @@ { "Opcode": 184, "Name": "gitxna", - "Returns": ".", + "Returns": [ + "any" + ], "Size": 4, "ArgEnum": [ "ApplicationArgs", @@ -2456,9 +4018,34 @@ "ApprovalProgramPages", "ClearStateProgramPages" ], - "ArgEnumTypes": "BBUUBBB", + "ArgEnumTypes": [ + "[]byte", + "address", + "uint64", + "uint64", + "[]byte", + "[]byte", + "[]byte" + ], "Doc": "Ith value of the array field F from the Tth transaction in the last inner group submitted", - "ImmediateNote": "{uint8 transaction group index} {uint8 transaction field index} {uint8 transaction field array index}", + "ImmediateNote": [ + { + "Comment": "transaction group index", + "Encoding": "uint8", + "Name": "T" + }, + { + "Comment": "transaction field index", + "Encoding": "uint8", + "Name": "F", + "Reference": "txna" + }, + { + "Comment": "transaction field array index", + "Encoding": "uint8", + "Name": "I" + } + ], "IntroducedVersion": 6, "Groups": [ "Inner Transactions" @@ -2467,8 +4054,13 @@ { "Opcode": 185, "Name": "box_create", - "Args": "BU", - "Returns": "U", + "Args": [ + "boxName", + "uint64" + ], + "Returns": [ + "bool" + ], "Size": 1, "Doc": "create a box named A, of length B. Fail if A is empty or B exceeds 32,768. Returns 0 if A already existed, else 1", "DocExtra": "Newly created boxes are filled with 0 bytes. `box_create` will fail if the referenced box already exists with a different size. Otherwise, existing boxes are unchanged by `box_create`.", @@ -2480,8 +4072,14 @@ { "Opcode": 186, "Name": "box_extract", - "Args": "BUU", - "Returns": "B", + "Args": [ + "boxName", + "uint64", + "uint64" + ], + "Returns": [ + "[]byte" + ], "Size": 1, "Doc": "read C bytes from box A, starting at offset B. Fail if A does not exist, or the byte range is outside A's size.", "IntroducedVersion": 8, @@ -2492,7 +4090,11 @@ { "Opcode": 187, "Name": "box_replace", - "Args": "BUB", + "Args": [ + "boxName", + "uint64", + "[]byte" + ], "Size": 1, "Doc": "write byte-array C into box A, starting at offset B. Fail if A does not exist, or the byte range is outside A's size.", "IntroducedVersion": 8, @@ -2503,8 +4105,12 @@ { "Opcode": 188, "Name": "box_del", - "Args": "B", - "Returns": "U", + "Args": [ + "boxName" + ], + "Returns": [ + "bool" + ], "Size": 1, "Doc": "delete box named A if it exists. Return 1 if A existed, 0 otherwise", "IntroducedVersion": 8, @@ -2515,8 +4121,13 @@ { "Opcode": 189, "Name": "box_len", - "Args": "B", - "Returns": "UU", + "Args": [ + "boxName" + ], + "Returns": [ + "uint64", + "bool" + ], "Size": 1, "Doc": "X is the length of box A if A exists, else 0. Y is 1 if A exists, else 0.", "IntroducedVersion": 8, @@ -2527,8 +4138,13 @@ { "Opcode": 190, "Name": "box_get", - "Args": "B", - "Returns": "BU", + "Args": [ + "boxName" + ], + "Returns": [ + "[]byte", + "bool" + ], "Size": 1, "Doc": "X is the contents of box A if A exists, else ''. Y is 1 if A exists, else 0.", "DocExtra": "For boxes that exceed 4,096 bytes, consider `box_create`, `box_extract`, and `box_replace`", @@ -2540,7 +4156,10 @@ { "Opcode": 191, "Name": "box_put", - "Args": "BB", + "Args": [ + "boxName", + "[]byte" + ], "Size": 1, "Doc": "replaces the contents of box A with byte-array B. Fails if A exists and len(B) != len(box A). Creates A if it does not exist", "DocExtra": "For boxes that exceed 4,096 bytes, consider `box_create`, `box_extract`, and `box_replace`", @@ -2552,8 +4171,12 @@ { "Opcode": 192, "Name": "txnas", - "Args": "U", - "Returns": ".", + "Args": [ + "uint64" + ], + "Returns": [ + "any" + ], "Size": 2, "ArgEnum": [ "ApplicationArgs", @@ -2564,9 +4187,24 @@ "ApprovalProgramPages", "ClearStateProgramPages" ], - "ArgEnumTypes": "BBUUBBB", + "ArgEnumTypes": [ + "[]byte", + "address", + "uint64", + "uint64", + "[]byte", + "[]byte", + "[]byte" + ], "Doc": "Ath value of the array field F of the current transaction", - "ImmediateNote": "{uint8 transaction field index}", + "ImmediateNote": [ + { + "Comment": "transaction field index", + "Encoding": "uint8", + "Name": "F", + "Reference": "txna" + } + ], "IntroducedVersion": 5, "Groups": [ "Loading Values" @@ -2575,8 +4213,12 @@ { "Opcode": 193, "Name": "gtxnas", - "Args": "U", - "Returns": ".", + "Args": [ + "uint64" + ], + "Returns": [ + "any" + ], "Size": 3, "ArgEnum": [ "ApplicationArgs", @@ -2587,9 +4229,29 @@ "ApprovalProgramPages", "ClearStateProgramPages" ], - "ArgEnumTypes": "BBUUBBB", + "ArgEnumTypes": [ + "[]byte", + "address", + "uint64", + "uint64", + "[]byte", + "[]byte", + "[]byte" + ], "Doc": "Ath value of the array field F from the Tth transaction in the current group", - "ImmediateNote": "{uint8 transaction group index} {uint8 transaction field index}", + "ImmediateNote": [ + { + "Comment": "transaction group index", + "Encoding": "uint8", + "Name": "T" + }, + { + "Comment": "transaction field index", + "Encoding": "uint8", + "Name": "F", + "Reference": "txna" + } + ], "IntroducedVersion": 5, "Groups": [ "Loading Values" @@ -2598,8 +4260,13 @@ { "Opcode": 194, "Name": "gtxnsas", - "Args": "UU", - "Returns": ".", + "Args": [ + "uint64", + "uint64" + ], + "Returns": [ + "any" + ], "Size": 2, "ArgEnum": [ "ApplicationArgs", @@ -2610,9 +4277,24 @@ "ApprovalProgramPages", "ClearStateProgramPages" ], - "ArgEnumTypes": "BBUUBBB", + "ArgEnumTypes": [ + "[]byte", + "address", + "uint64", + "uint64", + "[]byte", + "[]byte", + "[]byte" + ], "Doc": "Bth value of the array field F from the Ath transaction in the current group", - "ImmediateNote": "{uint8 transaction field index}", + "ImmediateNote": [ + { + "Comment": "transaction field index", + "Encoding": "uint8", + "Name": "F", + "Reference": "txna" + } + ], "IntroducedVersion": 5, "Groups": [ "Loading Values" @@ -2621,8 +4303,12 @@ { "Opcode": 195, "Name": "args", - "Args": "U", - "Returns": "B", + "Args": [ + "uint64" + ], + "Returns": [ + "[]byte" + ], "Size": 1, "Doc": "Ath LogicSig argument", "IntroducedVersion": 5, @@ -2633,8 +4319,13 @@ { "Opcode": 196, "Name": "gloadss", - "Args": "UU", - "Returns": ".", + "Args": [ + "uint64", + "uint64" + ], + "Returns": [ + "any" + ], "Size": 1, "Doc": "Bth scratch space value of the Ath transaction in the current group", "IntroducedVersion": 6, @@ -2645,11 +4336,22 @@ { "Opcode": 197, "Name": "itxnas", - "Args": "U", - "Returns": ".", + "Args": [ + "uint64" + ], + "Returns": [ + "any" + ], "Size": 2, "Doc": "Ath value of the array field F of the last inner transaction", - "ImmediateNote": "{uint8 transaction field index}", + "ImmediateNote": [ + { + "Comment": "transaction field index", + "Encoding": "uint8", + "Name": "F", + "Reference": "txna" + } + ], "IntroducedVersion": 6, "Groups": [ "Inner Transactions" @@ -2658,11 +4360,27 @@ { "Opcode": 198, "Name": "gitxnas", - "Args": "U", - "Returns": ".", + "Args": [ + "uint64" + ], + "Returns": [ + "any" + ], "Size": 3, "Doc": "Ath value of the array field F from the Tth transaction in the last inner group submitted", - "ImmediateNote": "{uint8 transaction group index} {uint8 transaction field index}", + "ImmediateNote": [ + { + "Comment": "transaction group index", + "Encoding": "uint8", + "Name": "T" + }, + { + "Comment": "transaction field index", + "Encoding": "uint8", + "Name": "F", + "Reference": "txna" + } + ], "IntroducedVersion": 6, "Groups": [ "Inner Transactions" @@ -2671,15 +4389,29 @@ { "Opcode": 208, "Name": "vrf_verify", - "Args": "BBB", - "Returns": "BU", + "Args": [ + "[]byte", + "[]byte", + "[]byte" + ], + "Returns": [ + "[]byte", + "bool" + ], "Size": 2, "ArgEnum": [ "VrfAlgorand" ], "Doc": "Verify the proof B of message A against pubkey C. Returns vrf output and verification flag.", "DocExtra": "`VrfAlgorand` is the VRF used in Algorand. It is ECVRF-ED25519-SHA512-Elligator2, specified in the IETF internet draft [draft-irtf-cfrg-vrf-03](https://datatracker.ietf.org/doc/draft-irtf-cfrg-vrf/03/).", - "ImmediateNote": "{uint8 parameters index}", + "ImmediateNote": [ + { + "Comment": " parameters index", + "Encoding": "uint8", + "Name": "S", + "Reference": "vrf_verify" + } + ], "IntroducedVersion": 7, "Groups": [ "Arithmetic" @@ -2688,16 +4420,30 @@ { "Opcode": 209, "Name": "block", - "Args": "U", - "Returns": ".", + "Args": [ + "uint64" + ], + "Returns": [ + "any" + ], "Size": 2, "ArgEnum": [ "BlkSeed", "BlkTimestamp" ], - "ArgEnumTypes": "BU", + "ArgEnumTypes": [ + "[]byte", + "uint64" + ], "Doc": "field F of block A. Fail unless A falls between txn.LastValid-1002 and txn.FirstValid (exclusive)", - "ImmediateNote": "{uint8 block field index}", + "ImmediateNote": [ + { + "Comment": " block field index", + "Encoding": "uint8", + "Name": "F", + "Reference": "block" + } + ], "IntroducedVersion": 7, "Groups": [ "State Access" diff --git a/data/transactions/logic/opcodes.go b/data/transactions/logic/opcodes.go index 3e1ae7e1e5..4ba1b10d2a 100644 --- a/data/transactions/logic/opcodes.go +++ b/data/transactions/logic/opcodes.go @@ -319,6 +319,28 @@ const ( immLabels ) +func (ik immKind) String() string { + switch ik { + case immByte: + return "uint8" + case immInt8: + return "int8" + case immLabel: + return "int16 (big-endian)" + case immInt: + return "varuint" + case immBytes: + return "varuint length, bytes" + case immInts: + return fmt.Sprintf("varuint count, [%s ...]", immInt.String()) + case immBytess: // "ss" not a typo. Multiple "bytes" + return fmt.Sprintf("varuint count, [%s ...]", immBytes.String()) + case immLabels: + return fmt.Sprintf("varuint count, [%s ...]", immLabel.String()) + } + return "unknown" +} + type immediate struct { Name string kind immKind @@ -379,7 +401,7 @@ type OpSpec struct { // AlwaysExits is true iff the opcode always ends the program. func (spec *OpSpec) AlwaysExits() bool { - return len(spec.Return.Types) == 1 && spec.Return.Types[0] == StackNone + return len(spec.Return.Types) == 1 && spec.Return.Types[0].AVMType == avmNone } func (spec *OpSpec) deadens() bool { @@ -399,17 +421,17 @@ func (spec *OpSpec) deadens() bool { // assembly-time, with ops.returns() var OpSpecs = []OpSpec{ {0x00, "err", opErr, proto(":x"), 1, detDefault()}, - {0x01, "sha256", opSHA256, proto("b:b"), 1, costly(7)}, - {0x02, "keccak256", opKeccak256, proto("b:b"), 1, costly(26)}, - {0x03, "sha512_256", opSHA512_256, proto("b:b"), 1, costly(9)}, + {0x01, "sha256", opSHA256, proto("b:H"), 1, costly(7)}, + {0x02, "keccak256", opKeccak256, proto("b:H"), 1, costly(26)}, + {0x03, "sha512_256", opSHA512_256, proto("b:H"), 1, costly(9)}, // Cost of these opcodes increases in AVM version 2 based on measured // performance. Should be able to run max hashes during stateful TEAL // and achieve reasonable TPS. Same opcode for different versions // is OK. - {0x01, "sha256", opSHA256, proto("b:b"), 2, costly(35)}, - {0x02, "keccak256", opKeccak256, proto("b:b"), 2, costly(130)}, - {0x03, "sha512_256", opSHA512_256, proto("b:b"), 2, costly(45)}, + {0x01, "sha256", opSHA256, proto("b:H"), 2, costly(35)}, + {0x02, "keccak256", opKeccak256, proto("b:H"), 2, costly(130)}, + {0x03, "sha512_256", opSHA512_256, proto("b:H"), 2, costly(45)}, /* Tabling these changes until we offer unlimited global storage as there @@ -421,10 +443,10 @@ var OpSpecs = []OpSpec{ {0x03, "sha512_256", opSHA512_256, proto("b:b"), 7, unlimitedStorage, costByLength(17, 5, 8)}, */ - {0x04, "ed25519verify", opEd25519Verify, proto("bbb:i"), 1, costly(1900).only(ModeSig)}, - {0x04, "ed25519verify", opEd25519Verify, proto("bbb:i"), 5, costly(1900)}, + {0x04, "ed25519verify", opEd25519Verify, proto("bbb:T"), 1, costly(1900).only(ModeSig)}, + {0x04, "ed25519verify", opEd25519Verify, proto("bbb:T"), 5, costly(1900)}, - {0x05, "ecdsa_verify", opEcdsaVerify, proto("bbbbb:i"), 5, costByField("v", &EcdsaCurves, ecdsaVerifyCosts)}, + {0x05, "ecdsa_verify", opEcdsaVerify, proto("bbbbb:T"), 5, costByField("v", &EcdsaCurves, ecdsaVerifyCosts)}, {0x06, "ecdsa_pk_decompress", opEcdsaPkDecompress, proto("b:bb"), 5, costByField("v", &EcdsaCurves, ecdsaDecompressCosts)}, {0x07, "ecdsa_pk_recover", opEcdsaPkRecover, proto("bibb:bb"), 5, field("v", &EcdsaCurves).costs(2000)}, @@ -432,14 +454,14 @@ var OpSpecs = []OpSpec{ {0x09, "-", opMinus, proto("ii:i"), 1, detDefault()}, {0x0a, "/", opDiv, proto("ii:i"), 1, detDefault()}, {0x0b, "*", opMul, proto("ii:i"), 1, detDefault()}, - {0x0c, "<", opLt, proto("ii:i"), 1, detDefault()}, - {0x0d, ">", opGt, proto("ii:i"), 1, detDefault()}, - {0x0e, "<=", opLe, proto("ii:i"), 1, detDefault()}, - {0x0f, ">=", opGe, proto("ii:i"), 1, detDefault()}, - {0x10, "&&", opAnd, proto("ii:i"), 1, detDefault()}, - {0x11, "||", opOr, proto("ii:i"), 1, detDefault()}, - {0x12, "==", opEq, proto("aa:i"), 1, typed(typeEquals)}, - {0x13, "!=", opNeq, proto("aa:i"), 1, typed(typeEquals)}, + {0x0c, "<", opLt, proto("ii:T"), 1, detDefault()}, + {0x0d, ">", opGt, proto("ii:T"), 1, detDefault()}, + {0x0e, "<=", opLe, proto("ii:T"), 1, detDefault()}, + {0x0f, ">=", opGe, proto("ii:T"), 1, detDefault()}, + {0x10, "&&", opAnd, proto("ii:T"), 1, detDefault()}, + {0x11, "||", opOr, proto("ii:T"), 1, detDefault()}, + {0x12, "==", opEq, proto("aa:T"), 1, typed(typeEquals)}, + {0x13, "!=", opNeq, proto("aa:T"), 1, typed(typeEquals)}, {0x14, "!", opNot, proto("i:i"), 1, detDefault()}, {0x15, "len", opLen, proto("b:i"), 1, detDefault()}, {0x16, "itob", opItob, proto("i:b"), 1, detDefault()}, @@ -530,25 +552,25 @@ var OpSpecs = []OpSpec{ {0x60, "balance", opBalance, proto("i:i"), 2, only(ModeApp)}, {0x60, "balance", opBalance, proto("a:i"), directRefEnabledVersion, only(ModeApp)}, - {0x61, "app_opted_in", opAppOptedIn, proto("ii:i"), 2, only(ModeApp)}, - {0x61, "app_opted_in", opAppOptedIn, proto("ai:i"), directRefEnabledVersion, only(ModeApp)}, + {0x61, "app_opted_in", opAppOptedIn, proto("ii:T"), 2, only(ModeApp)}, + {0x61, "app_opted_in", opAppOptedIn, proto("ai:T"), directRefEnabledVersion, only(ModeApp)}, {0x62, "app_local_get", opAppLocalGet, proto("ib:a"), 2, only(ModeApp)}, {0x62, "app_local_get", opAppLocalGet, proto("ab:a"), directRefEnabledVersion, only(ModeApp)}, - {0x63, "app_local_get_ex", opAppLocalGetEx, proto("iib:ai"), 2, only(ModeApp)}, - {0x63, "app_local_get_ex", opAppLocalGetEx, proto("aib:ai"), directRefEnabledVersion, only(ModeApp)}, + {0x63, "app_local_get_ex", opAppLocalGetEx, proto("iib:aT"), 2, only(ModeApp)}, + {0x63, "app_local_get_ex", opAppLocalGetEx, proto("aib:aT"), directRefEnabledVersion, only(ModeApp)}, {0x64, "app_global_get", opAppGlobalGet, proto("b:a"), 2, only(ModeApp)}, - {0x65, "app_global_get_ex", opAppGlobalGetEx, proto("ib:ai"), 2, only(ModeApp)}, + {0x65, "app_global_get_ex", opAppGlobalGetEx, proto("ib:aT"), 2, only(ModeApp)}, {0x66, "app_local_put", opAppLocalPut, proto("iba:"), 2, only(ModeApp)}, {0x66, "app_local_put", opAppLocalPut, proto("aba:"), directRefEnabledVersion, only(ModeApp)}, {0x67, "app_global_put", opAppGlobalPut, proto("ba:"), 2, only(ModeApp)}, {0x68, "app_local_del", opAppLocalDel, proto("ib:"), 2, only(ModeApp)}, {0x68, "app_local_del", opAppLocalDel, proto("ab:"), directRefEnabledVersion, only(ModeApp)}, {0x69, "app_global_del", opAppGlobalDel, proto("b:"), 2, only(ModeApp)}, - {0x70, "asset_holding_get", opAssetHoldingGet, proto("ii:ai"), 2, field("f", &AssetHoldingFields).only(ModeApp)}, - {0x70, "asset_holding_get", opAssetHoldingGet, proto("ai:ai"), directRefEnabledVersion, field("f", &AssetHoldingFields).only(ModeApp)}, - {0x71, "asset_params_get", opAssetParamsGet, proto("i:ai"), 2, field("f", &AssetParamsFields).only(ModeApp)}, - {0x72, "app_params_get", opAppParamsGet, proto("i:ai"), 5, field("f", &AppParamsFields).only(ModeApp)}, - {0x73, "acct_params_get", opAcctParamsGet, proto("a:ai"), 6, field("f", &AcctParamsFields).only(ModeApp)}, + {0x70, "asset_holding_get", opAssetHoldingGet, proto("ii:aT"), 2, field("f", &AssetHoldingFields).only(ModeApp)}, + {0x70, "asset_holding_get", opAssetHoldingGet, proto("ai:aT"), directRefEnabledVersion, field("f", &AssetHoldingFields).only(ModeApp)}, + {0x71, "asset_params_get", opAssetParamsGet, proto("i:aT"), 2, field("f", &AssetParamsFields).only(ModeApp)}, + {0x72, "app_params_get", opAppParamsGet, proto("i:aT"), 5, field("f", &AppParamsFields).only(ModeApp)}, + {0x73, "acct_params_get", opAcctParamsGet, proto("a:aT"), 6, field("f", &AcctParamsFields).only(ModeApp)}, {0x78, "min_balance", opMinBalance, proto("i:i"), 3, only(ModeApp)}, {0x78, "min_balance", opMinBalance, proto("a:i"), directRefEnabledVersion, only(ModeApp)}, @@ -559,7 +581,7 @@ var OpSpecs = []OpSpec{ {0x82, "pushbytess", opPushBytess, proto(":", "", "[N items]"), 8, constants(asmPushBytess, checkByteImmArgs, "bytes ...", immBytess).typed(typePushBytess).trust()}, {0x83, "pushints", opPushInts, proto(":", "", "[N items]"), 8, constants(asmPushInts, checkIntImmArgs, "uint ...", immInts).typed(typePushInts).trust()}, - {0x84, "ed25519verify_bare", opEd25519VerifyBare, proto("bbb:i"), 7, costly(1900)}, + {0x84, "ed25519verify_bare", opEd25519VerifyBare, proto("bbb:T"), 7, costly(1900)}, // "Function oriented" {0x88, "callsub", opCallSub, proto(":"), 4, detBranch()}, @@ -590,22 +612,22 @@ var OpSpecs = []OpSpec{ {0x9b, "bn256_pairing", opBn256Pairing, proto("bb:i"), pairingVersion, costly(8700)}, // Byteslice math. - {0xa0, "b+", opBytesPlus, proto("bb:b"), 4, costly(10)}, - {0xa1, "b-", opBytesMinus, proto("bb:b"), 4, costly(10)}, - {0xa2, "b/", opBytesDiv, proto("bb:b"), 4, costly(20)}, - {0xa3, "b*", opBytesMul, proto("bb:b"), 4, costly(20)}, - {0xa4, "b<", opBytesLt, proto("bb:i"), 4, detDefault()}, - {0xa5, "b>", opBytesGt, proto("bb:i"), 4, detDefault()}, - {0xa6, "b<=", opBytesLe, proto("bb:i"), 4, detDefault()}, - {0xa7, "b>=", opBytesGe, proto("bb:i"), 4, detDefault()}, - {0xa8, "b==", opBytesEq, proto("bb:i"), 4, detDefault()}, - {0xa9, "b!=", opBytesNeq, proto("bb:i"), 4, detDefault()}, + {0xa0, "b+", opBytesPlus, proto("II:b"), 4, costly(10).typed(typeByteMath(maxByteMathSize + 1))}, + {0xa1, "b-", opBytesMinus, proto("II:I"), 4, costly(10)}, + {0xa2, "b/", opBytesDiv, proto("II:I"), 4, costly(20)}, + {0xa3, "b*", opBytesMul, proto("II:b"), 4, costly(20).typed(typeByteMath(maxByteMathSize * 2))}, + {0xa4, "b<", opBytesLt, proto("II:T"), 4, detDefault()}, + {0xa5, "b>", opBytesGt, proto("II:T"), 4, detDefault()}, + {0xa6, "b<=", opBytesLe, proto("II:T"), 4, detDefault()}, + {0xa7, "b>=", opBytesGe, proto("II:T"), 4, detDefault()}, + {0xa8, "b==", opBytesEq, proto("II:T"), 4, detDefault()}, + {0xa9, "b!=", opBytesNeq, proto("II:T"), 4, detDefault()}, {0xaa, "b%", opBytesModulo, proto("bb:b"), 4, costly(20)}, {0xab, "b|", opBytesBitOr, proto("bb:b"), 4, costly(6)}, {0xac, "b&", opBytesBitAnd, proto("bb:b"), 4, costly(6)}, {0xad, "b^", opBytesBitXor, proto("bb:b"), 4, costly(6)}, {0xae, "b~", opBytesBitNot, proto("b:b"), 4, costly(4)}, - {0xaf, "bzero", opBytesZero, proto("i:b"), 4, detDefault()}, + {0xaf, "bzero", opBytesZero, proto("i:b"), 4, detDefault().typed(typeBzero)}, // AVM "effects" {0xb0, "log", opLog, proto("b:"), 5, only(ModeApp)}, @@ -619,13 +641,13 @@ var OpSpecs = []OpSpec{ {0xb8, "gitxna", opGitxna, proto(":a"), 6, immediates("t", "f", "i").field("f", &TxnArrayFields).only(ModeApp)}, // Unlimited Global Storage - Boxes - {0xb9, "box_create", opBoxCreate, proto("bi:i"), boxVersion, only(ModeApp)}, - {0xba, "box_extract", opBoxExtract, proto("bii:b"), boxVersion, only(ModeApp)}, - {0xbb, "box_replace", opBoxReplace, proto("bib:"), boxVersion, only(ModeApp)}, - {0xbc, "box_del", opBoxDel, proto("b:i"), boxVersion, only(ModeApp)}, - {0xbd, "box_len", opBoxLen, proto("b:ii"), boxVersion, only(ModeApp)}, - {0xbe, "box_get", opBoxGet, proto("b:bi"), boxVersion, only(ModeApp)}, - {0xbf, "box_put", opBoxPut, proto("bb:"), boxVersion, only(ModeApp)}, + {0xb9, "box_create", opBoxCreate, proto("Ni:T"), boxVersion, only(ModeApp)}, + {0xba, "box_extract", opBoxExtract, proto("Nii:b"), boxVersion, only(ModeApp)}, + {0xbb, "box_replace", opBoxReplace, proto("Nib:"), boxVersion, only(ModeApp)}, + {0xbc, "box_del", opBoxDel, proto("N:T"), boxVersion, only(ModeApp)}, + {0xbd, "box_len", opBoxLen, proto("N:iT"), boxVersion, only(ModeApp)}, + {0xbe, "box_get", opBoxGet, proto("N:bT"), boxVersion, only(ModeApp)}, + {0xbf, "box_put", opBoxPut, proto("Nb:"), boxVersion, only(ModeApp)}, // Dynamic indexing {0xc0, "txnas", opTxnas, proto("i:a"), 5, field("f", &TxnArrayFields)}, @@ -637,7 +659,7 @@ var OpSpecs = []OpSpec{ {0xc6, "gitxnas", opGitxnas, proto("i:a"), 6, immediates("t", "f").field("f", &TxnArrayFields).only(ModeApp)}, // randomness support - {0xd0, "vrf_verify", opVrfVerify, proto("bbb:bi"), randomnessVersion, field("s", &VrfStandards).costs(5700)}, + {0xd0, "vrf_verify", opVrfVerify, proto("bbb:bT"), randomnessVersion, field("s", &VrfStandards).costs(5700)}, {0xd1, "block", opBlock, proto("i:a"), randomnessVersion, field("f", &BlockFields)}, } From b988971925f5c9e7399c0df0393bb99f7376b1a6 Mon Sep 17 00:00:00 2001 From: Juan Granados <114556291+jsgranados@users.noreply.github.com> Date: Wed, 10 May 2023 13:37:13 -0400 Subject: [PATCH 090/125] Documentation: Add container image tagging information (#5372) Co-authored-by: algobarb <78746954+algobarb@users.noreply.github.com> --- docker/README.md | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/docker/README.md b/docker/README.md index db0aac7980..a97e3296d8 100644 --- a/docker/README.md +++ b/docker/README.md @@ -6,7 +6,21 @@ General purpose algod container image. ## Image Configuration -There are a number of special files and environment variables used to control how a container is started. +Algorand maintains a Docker image with recent snapshot builds from our `master` branch on DockerHub to support users who prefer to run containerized processes. There are a couple of different images avaliable for running the latest stable or development versions of Algod. + +- `algorand/algod:latest` is the latest stable release version of Algod (default) +- `algorand/algod:stable` is the latest stable version of Algod +- `algorand/algod:{version}-stable` is the stable version of Algod at a specific version number +- `algorand/algod:beta` is the version of Algod being considered for the next stable release +- `algorand/algod:nightly` is the latest development version of Algod + +Algorand also publishes experimental versions of Algod. + +- `algorand/algod:{LONGSHA}` is a version containing a specific commit to `master` +- `algorand/algod:master` is the version running on our `master` branch +- `algorand/algod:feature-{branch}` is the latest version of Algod on any of the go-algorand feature branches + +Furthermore, There are a number of special files and environment variables used to control how a container is started. See below for more detail. ### Default Configuration From 460aebf89e7626b0c9ef4c514a8a344799f2034f Mon Sep 17 00:00:00 2001 From: cce <51567+cce@users.noreply.github.com> Date: Thu, 11 May 2023 15:58:29 -0400 Subject: [PATCH 091/125] lint: enable nilerr linter and fix errors (#5361) --- .golangci.yml | 2 ++ cmd/loadgenerator/main.go | 4 ++-- crypto/merklesignature/keysBuilder.go | 2 +- data/account/participation_test.go | 6 +++--- data/transactions/logic/eval.go | 4 ++-- ledger/acctdeltas_test.go | 2 +- ledger/acctonline.go | 2 +- ledger/catchpointtracker.go | 2 +- libgoal/libgoal.go | 2 +- node/follower_node_test.go | 2 +- 10 files changed, 15 insertions(+), 13 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index ff7892d4f6..44fba83682 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -13,6 +13,7 @@ linters: - govet - ineffassign - misspell + - nilerr - nolintlint - revive - staticcheck @@ -114,6 +115,7 @@ issues: # - govet - ineffassign - misspell + # - nilerr - nolintlint # - revive - staticcheck diff --git a/cmd/loadgenerator/main.go b/cmd/loadgenerator/main.go index 6ef8cd2383..594f893801 100644 --- a/cmd/loadgenerator/main.go +++ b/cmd/loadgenerator/main.go @@ -69,14 +69,14 @@ func findRootKeys(algodDir string) []*crypto.SignatureSecrets { var handle db.Accessor handle, err := db.MakeErasableAccessor(path) if err != nil { - return nil // don't care, move on + return nil //nolint:nilerr // don't care, move on } defer handle.Close() // Fetch an account.Participation from the database root, err := algodAcct.RestoreRoot(handle) if err != nil { - return nil // don't care, move on + return nil //nolint:nilerr // don't care, move on } keylist = append(keylist, root.Secrets()) return nil diff --git a/crypto/merklesignature/keysBuilder.go b/crypto/merklesignature/keysBuilder.go index d284ca29b7..50498b519a 100644 --- a/crypto/merklesignature/keysBuilder.go +++ b/crypto/merklesignature/keysBuilder.go @@ -84,7 +84,7 @@ func calculateRanges(numberOfKeys uint64) (numOfKeysPerRoutine uint64, numOfRout func generateKeysForRange(ctx context.Context, startIdx uint64, endIdx uint64, keys []crypto.FalconSigner) error { for k := startIdx; k < endIdx; k++ { if ctx.Err() != nil { - break + return nil //nolint:nilerr // we don't need to return the ctx error, since the other goroutine will report it. } sigAlgo, err := crypto.NewFalconSigner() if err != nil { diff --git a/data/account/participation_test.go b/data/account/participation_test.go index b9a417a8fd..4a933d72b7 100644 --- a/data/account/participation_test.go +++ b/data/account/participation_test.go @@ -344,7 +344,7 @@ func setupTestDBAtVer2(partDB db.Accessor, part Participation) error { keyDilution INTEGER NOT NULL DEFAULT 0 );`) if err != nil { - return nil + return err } if err := setupSchemaForTest(tx, 2); err != nil { @@ -362,12 +362,12 @@ func setupTestDBAtVer2(partDB db.Accessor, part Participation) error { func setupSchemaForTest(tx *sql.Tx, version int) error { _, err := tx.Exec(`CREATE TABLE schema (tablename TEXT PRIMARY KEY, version INTEGER);`) if err != nil { - return nil + return err } _, err = tx.Exec("INSERT INTO schema (tablename, version) VALUES (?, ?)", PartTableSchemaName, version) if err != nil { - return nil + return err } return err } diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index af54a23f93..2cd78c40ff 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -4308,9 +4308,9 @@ func (cx *EvalContext) resolveAccount(account stackValue) (basics.Address, int, idx, err := cx.txn.Txn.IndexByAddress(addr, cx.txn.Txn.Sender) if err != nil { - // we don't want to convery `err`, because the supplied `account` does + // we don't want to convey `err`, because the supplied `account` does // seem to be an address, but we can't give a valid index. - return addr, -1, nil + return addr, -1, nil //nolint:nilerr // see above comment } return addr, int(idx), nil } diff --git a/ledger/acctdeltas_test.go b/ledger/acctdeltas_test.go index c5f74aed72..2dc748225b 100644 --- a/ledger/acctdeltas_test.go +++ b/ledger/acctdeltas_test.go @@ -1451,7 +1451,7 @@ func TestLookupAccountAddressFromAddressID(t *testing.T) { err = dbs.Transaction(func(ctx context.Context, tx trackerdb.TransactionScope) (err error) { arw, err := tx.MakeAccountsReaderWriter() if err != nil { - return nil + return err } for addr, addrid := range addrsids { diff --git a/ledger/acctonline.go b/ledger/acctonline.go index 66aff375a5..adb7c6693c 100644 --- a/ledger/acctonline.go +++ b/ledger/acctonline.go @@ -547,7 +547,7 @@ func (ao *onlineAccounts) onlineTotalsEx(rnd basics.Round) (basics.MicroAlgos, e totalsOnline, err := ao.onlineTotalsImpl(rnd) ao.accountsMu.RUnlock() if err == nil { - return totalsOnline, err + return totalsOnline, nil } var roundOffsetError *RoundOffsetError diff --git a/ledger/catchpointtracker.go b/ledger/catchpointtracker.go index 26e7972e9d..495f4adb7d 100644 --- a/ledger/catchpointtracker.go +++ b/ledger/catchpointtracker.go @@ -1364,7 +1364,7 @@ func (ct *catchpointTracker) GetCatchpointStream(round basics.Round) (ReadCloseS fileInfo, err := file.Stat() if err != nil { // we couldn't get the stat, so just return with the file. - return &readCloseSizer{ReadCloser: file, size: -1}, nil + return &readCloseSizer{ReadCloser: file, size: -1}, nil //nolint:nilerr // intentionally ignoring Stat error } crw, err := ct.dbs.MakeCatchpointReaderWriter() if err != nil { diff --git a/libgoal/libgoal.go b/libgoal/libgoal.go index 0e45c6f6f8..65621e6108 100644 --- a/libgoal/libgoal.go +++ b/libgoal/libgoal.go @@ -1024,7 +1024,7 @@ func (c *Client) VerifyParticipationKey(timeout time.Duration, participationID s func (c *Client) RemoveParticipationKey(participationID string) error { algod, err := c.ensureAlgodClient() if err != nil { - return nil + return err } return algod.RemoveParticipationKeyByID(participationID) diff --git a/node/follower_node_test.go b/node/follower_node_test.go index c15fb3317d..83b30364e5 100644 --- a/node/follower_node_test.go +++ b/node/follower_node_test.go @@ -220,7 +220,7 @@ func TestSyncRoundWithRemake(t *testing.T) { return true } return false - }, 10*time.Second, 500*time.Millisecond, "failed to reach newRound within the allowed time") + }, 20*time.Second, 500*time.Millisecond, "failed to reach newRound within the allowed time") require.Equal(t, newRound, status.LastRound) From d7c5e54b5e179fc828389eb3acc72b8fcac7d9bf Mon Sep 17 00:00:00 2001 From: Eric Warehime Date: Thu, 11 May 2023 16:18:54 -0700 Subject: [PATCH 092/125] txn tracer: Fix pointer bug copying deltas for txn groups (#5375) --- .../algod/api/server/v2/test/handlers_test.go | 3 +- ledger/eval/eval_test.go | 6 +- ledger/eval/txntracer.go | 69 +++++-- ledger/eval/txntracer_test.go | 185 ++++++++++++++---- 4 files changed, 208 insertions(+), 55 deletions(-) diff --git a/daemon/algod/api/server/v2/test/handlers_test.go b/daemon/algod/api/server/v2/test/handlers_test.go index e2278c505b..ed9b8d83bc 100644 --- a/daemon/algod/api/server/v2/test/handlers_test.go +++ b/daemon/algod/api/server/v2/test/handlers_test.go @@ -2077,7 +2077,7 @@ func TestDeltasForTxnGroup(t *testing.T) { blk1 := bookkeeping.BlockHeader{Round: 1} blk2 := bookkeeping.BlockHeader{Round: 2} delta1 := ledgercore.StateDelta{Hdr: &blk1} - delta2 := ledgercore.StateDelta{Hdr: &blk2} + delta2 := ledgercore.StateDelta{Hdr: &blk2, KvMods: map[string]ledgercore.KvValueDelta{"bx1": {Data: []byte("foobar")}}} txn1 := transactions.SignedTxnWithAD{SignedTxn: transactions.SignedTxn{Txn: transactions.Transaction{Type: protocol.PaymentTx}}} groupID1, err := crypto.DigestFromString(crypto.Hash([]byte("hello")).String()) require.NoError(t, err) @@ -2160,6 +2160,7 @@ func TestDeltasForTxnGroup(t *testing.T) { require.NoError(t, err) err = json.Unmarshal(rec.Body.Bytes(), &groupResponse) require.NoError(t, err) + require.NotNil(t, groupResponse["KvMods"]) groupHdr, ok = groupResponse["Hdr"].(map[string]interface{}) require.True(t, ok) require.Equal(t, delta2.Hdr.Round, basics.Round(groupHdr["rnd"].(float64))) diff --git a/ledger/eval/eval_test.go b/ledger/eval/eval_test.go index aa16634d2f..84de779ec1 100644 --- a/ledger/eval/eval_test.go +++ b/ledger/eval/eval_test.go @@ -600,6 +600,7 @@ type evalTestLedger struct { rewardsPool basics.Address latestTotals ledgercore.AccountTotals tracer logic.EvalTracer + boxes map[string][]byte } // newTestLedger creates a in memory Ledger that is as realistic as @@ -611,6 +612,7 @@ func newTestLedger(t testing.TB, balances bookkeeping.GenesisBalances) *evalTest feeSink: balances.FeeSink, rewardsPool: balances.RewardsPool, tracer: nil, + boxes: make(map[string][]byte), } protoVersion := protocol.ConsensusFuture @@ -728,7 +730,9 @@ func (ledger *evalTestLedger) LookupAsset(rnd basics.Round, addr basics.Address, } func (ledger *evalTestLedger) LookupKv(rnd basics.Round, key string) ([]byte, error) { - panic("unimplemented") + // The test ledger only has one view of the value of a box--no rnd based retrieval is implemented currently + val, _ := ledger.boxes[key] + return val, nil } // GenesisHash returns the genesis hash for this ledger. diff --git a/ledger/eval/txntracer.go b/ledger/eval/txntracer.go index f1ce6b2e1d..036ad773de 100644 --- a/ledger/eval/txntracer.go +++ b/ledger/eval/txntracer.go @@ -48,12 +48,50 @@ type StateDeltaSubset struct { } func convertStateDelta(delta ledgercore.StateDelta) StateDeltaSubset { + // The StateDelta object returned through the EvalTracer has its values deleted between txn groups to avoid + // reallocation during evaluation. + // This means the map values need to be copied (to avoid deletion) since they are all passed by reference. + kvmods := make(map[string]ledgercore.KvValueDelta, len(delta.KvMods)) + for k1, v1 := range delta.KvMods { + kvmods[k1] = v1 + } + txids := make(map[transactions.Txid]ledgercore.IncludedTransactions, len(delta.Txids)) + for k2, v2 := range delta.Txids { + txids[k2] = v2 + } + txleases := make(map[ledgercore.Txlease]basics.Round, len(delta.Txleases)) + for k3, v3 := range delta.Txleases { + txleases[k3] = v3 + } + creatables := make(map[basics.CreatableIndex]ledgercore.ModifiedCreatable, len(delta.Creatables)) + for k4, v4 := range delta.Creatables { + creatables[k4] = v4 + } + var accR []ledgercore.BalanceRecord + var appR []ledgercore.AppResourceRecord + var assetR []ledgercore.AssetResourceRecord + if len(delta.Accts.Accts) > 0 { + accR = make([]ledgercore.BalanceRecord, len(delta.Accts.Accts)) + copy(accR, delta.Accts.Accts) + } + if len(delta.Accts.AppResources) > 0 { + appR = make([]ledgercore.AppResourceRecord, len(delta.Accts.AppResources)) + copy(appR, delta.Accts.AppResources) + } + if len(delta.Accts.AssetResources) > 0 { + assetR = make([]ledgercore.AssetResourceRecord, len(delta.Accts.AssetResources)) + copy(assetR, delta.Accts.AssetResources) + } return StateDeltaSubset{ - Accts: delta.Accts, - KvMods: delta.KvMods, - Txids: delta.Txids, - Txleases: delta.Txleases, - Creatables: delta.Creatables, + Accts: ledgercore.AccountDeltas{ + Accts: accR, + AppResources: appR, + AssetResources: assetR, + }, + KvMods: kvmods, + Txids: txids, + Txleases: txleases, + Creatables: creatables, Hdr: delta.Hdr, } } @@ -65,8 +103,8 @@ type TxnGroupDeltaTracer struct { lookback uint64 // no-op methods we don't care about logic.NullEvalTracer - // txnGroupDeltas stores the StateDelta objects for each round, indexed by all the IDs within the group - txnGroupDeltas map[basics.Round]map[crypto.Digest]*ledgercore.StateDelta + // txnGroupDeltas stores the StateDeltaSubset objects for each round, indexed by all the IDs within the group + txnGroupDeltas map[basics.Round]map[crypto.Digest]*StateDeltaSubset // latestRound is the most recent round seen via the BeforeBlock hdr latestRound basics.Round } @@ -75,7 +113,7 @@ type TxnGroupDeltaTracer struct { func MakeTxnGroupDeltaTracer(lookback uint64) *TxnGroupDeltaTracer { return &TxnGroupDeltaTracer{ lookback: lookback, - txnGroupDeltas: make(map[basics.Round]map[crypto.Digest]*ledgercore.StateDelta), + txnGroupDeltas: make(map[basics.Round]map[crypto.Digest]*StateDeltaSubset), } } @@ -87,7 +125,7 @@ func (tracer *TxnGroupDeltaTracer) BeforeBlock(hdr *bookkeeping.BlockHeader) { delete(tracer.txnGroupDeltas, hdr.Round-basics.Round(tracer.lookback)) tracer.latestRound = hdr.Round // Initialize the delta map for the round - tracer.txnGroupDeltas[tracer.latestRound] = make(map[crypto.Digest]*ledgercore.StateDelta) + tracer.txnGroupDeltas[tracer.latestRound] = make(map[crypto.Digest]*StateDeltaSubset) } // AfterTxnGroup implements the EvalTracer interface for txn group boundaries @@ -95,16 +133,17 @@ func (tracer *TxnGroupDeltaTracer) AfterTxnGroup(ep *logic.EvalParams, deltas *l if deltas == nil { return } + deltaSub := convertStateDelta(*deltas) tracer.deltasLock.Lock() defer tracer.deltasLock.Unlock() txnDeltaMap := tracer.txnGroupDeltas[tracer.latestRound] for _, txn := range ep.TxnGroup { // Add Group ID if !txn.Txn.Group.IsZero() { - txnDeltaMap[txn.Txn.Group] = deltas + txnDeltaMap[txn.Txn.Group] = &deltaSub } // Add Txn ID - txnDeltaMap[crypto.Digest(txn.ID())] = deltas + txnDeltaMap[crypto.Digest(txn.ID())] = &deltaSub } } @@ -117,7 +156,7 @@ func (tracer *TxnGroupDeltaTracer) GetDeltasForRound(rnd basics.Round) ([]TxnGro return nil, fmt.Errorf("round %d not found in txnGroupDeltaTracer", rnd) } // Dedupe entries in our map and collect Ids - var deltas = map[*ledgercore.StateDelta][]string{} + var deltas = map[*StateDeltaSubset][]string{} for id, delta := range rndEntries { if _, present := deltas[delta]; !present { deltas[delta] = append(deltas[delta], id.String()) @@ -127,19 +166,19 @@ func (tracer *TxnGroupDeltaTracer) GetDeltasForRound(rnd basics.Round) ([]TxnGro for delta, ids := range deltas { deltasForRound = append(deltasForRound, TxnGroupDeltaWithIds{ Ids: ids, - Delta: convertStateDelta(*delta), + Delta: *delta, }) } return deltasForRound, nil } -// GetDeltaForID retruns the StateDelta associated with the group of transaction executed for the supplied ID (txn or group) +// GetDeltaForID returns the StateDelta associated with the group of transaction executed for the supplied ID (txn or group) func (tracer *TxnGroupDeltaTracer) GetDeltaForID(id crypto.Digest) (StateDeltaSubset, error) { tracer.deltasLock.RLock() defer tracer.deltasLock.RUnlock() for _, deltasForRound := range tracer.txnGroupDeltas { if delta, exists := deltasForRound[id]; exists { - return convertStateDelta(*delta), nil + return *delta, nil } } return StateDeltaSubset{}, fmt.Errorf("unable to find delta for id: %s", id) diff --git a/ledger/eval/txntracer_test.go b/ledger/eval/txntracer_test.go index 8b60a0c090..4711709c68 100644 --- a/ledger/eval/txntracer_test.go +++ b/ledger/eval/txntracer_test.go @@ -27,6 +27,7 @@ import ( basics_testing "github.com/algorand/go-algorand/data/basics/testing" "github.com/algorand/go-algorand/data/bookkeeping" "github.com/algorand/go-algorand/data/transactions" + "github.com/algorand/go-algorand/data/transactions/logic" "github.com/algorand/go-algorand/data/transactions/logic/mocktracer" "github.com/algorand/go-algorand/data/txntest" "github.com/algorand/go-algorand/ledger/ledgercore" @@ -75,8 +76,13 @@ func TestTransactionGroupWithDeltaTracer(t *testing.T) { innerAppID := basics.AppIndex(3) + offset innerAppAddress := innerAppID.Address() + appID := basics.AppIndex(1) + offset + appAddress := appID.Address() + innerBoxAppID := basics.AppIndex(7) + offset + innerBoxAppAddress := innerBoxAppID.Address() balances := genesisInitState.Accounts balances[innerAppAddress] = basics_testing.MakeAccountData(basics.Offline, basics.MicroAlgos{Raw: 1_000_000}) + balances[appAddress] = basics_testing.MakeAccountData(basics.Offline, basics.MicroAlgos{Raw: 1_000_000}) genesisBalances := bookkeeping.GenesisBalances{ Balances: genesisInitState.Accounts, @@ -95,24 +101,38 @@ func TestTransactionGroupWithDeltaTracer(t *testing.T) { eval.generate = true genHash := l.GenesisHash() + basicAppCallApproval := `#pragma version 8 +byte "hellobox" +int 10 +box_create +pop +int 1` + basicAppCallClear := `#pragma version 8 +int 1` + basicAppCallClearOps, err := logic.AssembleString(basicAppCallClear) + require.NoError(t, err) + basicAppCallApprovalOps, err := logic.AssembleString(basicAppCallApproval) + require.NoError(t, err) // a basic app call basicAppCallTxn := txntest.Txn{ - Type: protocol.ApplicationCallTx, - Sender: addrs[0], - ApprovalProgram: `#pragma version 6 -byte "hello" -log -int 1`, - ClearStateProgram: `#pragma version 6 -int 1`, - FirstValid: newBlock.Round(), - LastValid: newBlock.Round() + 1000, - Fee: minFee, - GenesisHash: genHash, - Note: []byte("one"), + Type: protocol.ApplicationCallTx, + Sender: addrs[0], + ApprovalProgram: basicAppCallApproval, + ClearStateProgram: basicAppCallClear, + FirstValid: newBlock.Round(), + LastValid: newBlock.Round() + 1000, + Fee: minFee, + GenesisHash: genHash, + Note: []byte("one"), + Boxes: []transactions.BoxRef{{ + Index: 0, + Name: []byte("hellobox"), + }}, } // a non-app call txn + var txnLease [32]byte + copy(txnLease[:], "txnLeaseTest") payTxn := txntest.Txn{ Type: protocol.PaymentTx, Sender: addrs[1], @@ -124,18 +144,22 @@ int 1`, Fee: minFee, GenesisHash: genHash, Note: []byte("two"), + Lease: txnLease, } // an app call with inner txn + v6Clear := `#pragma version 6 +int 1` + v6ClearOps, err := logic.AssembleString(v6Clear) + require.NoError(t, err) innerAppCallTxn := txntest.Txn{ - Type: protocol.ApplicationCallTx, - Sender: addrs[0], - ClearStateProgram: `#pragma version 6 -int 1`, - FirstValid: newBlock.Round(), - LastValid: newBlock.Round() + 1000, - Fee: minFee, - GenesisHash: genHash, - Note: []byte("three"), + Type: protocol.ApplicationCallTx, + Sender: addrs[0], + ClearStateProgram: v6Clear, + FirstValid: newBlock.Round(), + LastValid: newBlock.Round() + 1000, + Fee: minFee, + GenesisHash: genHash, + Note: []byte("three"), } scenario := testCase.innerAppCallScenario(mocktracer.TestScenarioInfo{ CallingTxn: innerAppCallTxn.Txn(), @@ -143,20 +167,70 @@ int 1`, CreatedAppID: innerAppID, }) innerAppCallTxn.ApprovalProgram = scenario.Program + innerAppCallApprovalOps, err := logic.AssembleString(scenario.Program) + require.NoError(t, err) - txntest.Group(&basicAppCallTxn, &payTxn, &innerAppCallTxn) + // inner txn with more box mods + innerAppCallBoxApproval := `#pragma version 8 +byte "goodbyebox" +int 10 +box_create +pop +byte "goodbyebox" +int 0 +byte "2" +box_replace +byte "goodbyebox" +box_del +pop +int 1` + innerAppCallBoxApprovalOps, err := logic.AssembleString(innerAppCallBoxApproval) + require.NoError(t, err) + innerAppCallBoxTxn := txntest.Txn{ + Type: protocol.ApplicationCallTx, + Sender: addrs[0], + ApprovalProgram: innerAppCallBoxApproval, + ClearStateProgram: basicAppCallClear, + FirstValid: newBlock.Round(), + LastValid: newBlock.Round() + 1000, + Fee: minFee, + GenesisHash: genHash, + Boxes: []transactions.BoxRef{{ + Index: 0, + Name: []byte("goodbyebox"), + }}, + } + + txntest.Group(&basicAppCallTxn, &payTxn, &innerAppCallTxn, &innerAppCallBoxTxn) txgroup := transactions.WrapSignedTxnsWithAD([]transactions.SignedTxn{ basicAppCallTxn.Txn().Sign(keys[0]), payTxn.Txn().Sign(keys[1]), innerAppCallTxn.Txn().Sign(keys[0]), + innerAppCallBoxTxn.Txn().Sign(keys[0]), }) require.Len(t, eval.block.Payset, 0) err = eval.TransactionGroup(txgroup) require.NoError(t, err) - require.Len(t, eval.block.Payset, 3) + require.Len(t, eval.block.Payset, 4) + + secondPayTxn := txntest.Txn{ + Type: protocol.PaymentTx, + Sender: addrs[2], + Receiver: addrs[1], + Amount: 100_000, + FirstValid: newBlock.Round(), + LastValid: newBlock.Round() + 1000, + Fee: minFee, + GenesisHash: genHash, + } + secondTxGroup := transactions.WrapSignedTxnsWithAD([]transactions.SignedTxn{ + secondPayTxn.Txn().Sign(keys[2]), + }) + err = eval.TransactionGroup(secondTxGroup) + require.NoError(t, err) expectedAccts := ledgercore.AccountDeltas{ Accts: []ledgercore.BalanceRecord{ @@ -164,8 +238,8 @@ int 1`, Addr: addrs[0], AccountData: ledgercore.AccountData{ AccountBaseData: ledgercore.AccountBaseData{ - MicroAlgos: basics.MicroAlgos{Raw: 1666666666664666}, - TotalAppParams: 2, + MicroAlgos: basics.MicroAlgos{Raw: 1666666666663666}, + TotalAppParams: 3, }, }, }, @@ -174,7 +248,17 @@ int 1`, AccountData: ledgercore.AccountData{ AccountBaseData: ledgercore.AccountBaseData{ Status: basics.Status(2), - MicroAlgos: basics.MicroAlgos{Raw: 1666666666672666}, + MicroAlgos: basics.MicroAlgos{Raw: 1666666666673666}, + }, + }, + }, + { + Addr: appAddress, + AccountData: ledgercore.AccountData{ + AccountBaseData: ledgercore.AccountBaseData{ + MicroAlgos: basics.MicroAlgos{Raw: 1000000}, + TotalBoxes: 1, + TotalBoxBytes: 18, }, }, }, @@ -206,6 +290,10 @@ int 1`, }, }, }, + { + Addr: innerBoxAppAddress, + AccountData: ledgercore.AccountData{}, + }, }, AppResources: []ledgercore.AppResourceRecord{ { @@ -213,8 +301,8 @@ int 1`, Addr: addrs[0], Params: ledgercore.AppParamsDelta{ Params: &basics.AppParams{ - ApprovalProgram: []byte{0x06, 0x80, 0x05, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0xb0, 0x81, 0x01}, - ClearStateProgram: []byte{0x06, 0x81, 0x01}, + ApprovalProgram: basicAppCallApprovalOps.Program, + ClearStateProgram: basicAppCallClearOps.Program, }, }, }, @@ -223,12 +311,8 @@ int 1`, Addr: addrs[0], Params: ledgercore.AppParamsDelta{ Params: &basics.AppParams{ - ApprovalProgram: []byte{0x06, 0x80, 0x01, 0x61, 0xb0, 0xb1, 0x81, 0x06, 0xb2, 0x10, 0x81, 0x00, 0xb2, 0x19, 0x80, 0x07, - 0x06, 0x80, 0x01, 0x78, 0xb0, 0x81, 0x01, 0xb2, 0x1e, 0x80, 0x03, 0x06, 0x81, 0x01, 0xb2, 0x1f, - 0xb3, 0x80, 0x01, 0x62, 0xb0, 0xb1, 0x81, 0x01, 0xb2, 0x10, 0x81, 0x01, 0xb2, 0x08, 0x32, 0x0a, - 0xb2, 0x07, 0xb6, 0x81, 0x01, 0xb2, 0x10, 0x81, 0x02, 0xb2, 0x08, 0x32, 0x0a, 0xb2, 0x07, 0xb3, - 0x80, 0x01, 0x63, 0xb0, 0x81, 0x01}, - ClearStateProgram: []byte{0x06, 0x81, 0x01}, + ApprovalProgram: innerAppCallApprovalOps.Program, + ClearStateProgram: v6ClearOps.Program, }, }, }, @@ -237,12 +321,35 @@ int 1`, Addr: innerAppAddress, Params: ledgercore.AppParamsDelta{ Params: &basics.AppParams{ - ApprovalProgram: []byte{0x06, 0x80, 0x01, 0x78, 0xb0, 0x81, 0x01}, - ClearStateProgram: []byte{0x06, 0x81, 0x01}, + ApprovalProgram: []byte{0x06, 0x80, 0x01, 0x78, 0xb0, 0x81, 0x01}, // #pragma version 6; pushbytes "x"; log; pushint 1 + ClearStateProgram: v6ClearOps.Program, }, }, }, + { + Aidx: innerBoxAppID, + Addr: addrs[0], + Params: ledgercore.AppParamsDelta{ + Params: &basics.AppParams{ + ApprovalProgram: innerAppCallBoxApprovalOps.Program, + ClearStateProgram: basicAppCallClearOps.Program, + }, + }, + }, + }, + } + expectedKvMods := map[string]ledgercore.KvValueDelta{ + "bx:\x00\x00\x00\x00\x00\x00\x03\xe9hellobox": { + OldData: []uint8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + Data: []uint8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, }, + "bx:\x00\x00\x00\x00\x00\x00\x03\xefgoodbyebox": { + OldData: nil, + Data: nil, + }, + } + expectedLeases := map[ledgercore.Txlease]basics.Round{ + {Sender: payTxn.Sender, Lease: payTxn.Lease}: payTxn.LastValid, } actualDelta, err := tracer.GetDeltaForID(crypto.Digest(txgroup[0].ID())) @@ -253,11 +360,13 @@ int 1`, require.NoError(t, err) allDeltas, err := tracer.GetDeltasForRound(basics.Round(1)) require.NoError(t, err) - require.Len(t, allDeltas, 1) + require.Len(t, allDeltas, 2) require.Equal(t, expectedAccts.Accts, actualDelta.Accts.Accts) require.Equal(t, expectedAccts.AppResources, actualDelta.Accts.AppResources) require.Equal(t, expectedAccts.AssetResources, actualDelta.Accts.AssetResources) + require.Equal(t, expectedKvMods, actualDelta.KvMods) + require.Equal(t, expectedLeases, actualDelta.Txleases) }) } } From c5c59a79349478fd5ee9a76f7c16b21962a63f14 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Fri, 12 May 2023 17:47:45 -0400 Subject: [PATCH 093/125] tools: allow dumpblocks work with wal-enabled dbs (#5380) --- tools/debug/dumpblocks/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/debug/dumpblocks/main.go b/tools/debug/dumpblocks/main.go index 461e7949cf..a932d9e60a 100644 --- a/tools/debug/dumpblocks/main.go +++ b/tools/debug/dumpblocks/main.go @@ -54,7 +54,7 @@ func main() { fmt.Println("-blockdb=file required") usage() } - uri := fmt.Sprintf("file:%s?mode=ro", *blockDBfile) + uri := fmt.Sprintf("file:%s?_journal_mode=wal", *blockDBfile) fmt.Println("Opening", uri) db, err := sql.Open("sqlite3", uri) if err != nil { From 377d724a87b631e5a4928a968bafec1e7a238c74 Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Fri, 12 May 2023 17:59:55 -0700 Subject: [PATCH 094/125] Algod: Add EvalTracer tests for StateDeltas (#5368) --- .../logic/mocktracer/scenarios.go | 289 +++++++++++++-- data/transactions/logic/mocktracer/tracer.go | 55 ++- ledger/eval/cow_test.go | 45 +++ ledger/eval/eval_test.go | 157 +++++++- ledger/ledgercore/statedelta.go | 104 +++++- ledger/ledgercore/statedelta_test.go | 338 ++++++++++++++++- ledger/simulation/simulation_eval_test.go | 342 +++++++++--------- ledger/simulation/simulator_test.go | 99 ++++- ledger/simulation/testing/utils.go | 67 ++-- ledger/simulation/trace.go | 7 +- ledger/simulation/tracer_test.go | 3 +- 11 files changed, 1232 insertions(+), 274 deletions(-) diff --git a/data/transactions/logic/mocktracer/scenarios.go b/data/transactions/logic/mocktracer/scenarios.go index 89fbdafe9c..0b336db502 100644 --- a/data/transactions/logic/mocktracer/scenarios.go +++ b/data/transactions/logic/mocktracer/scenarios.go @@ -23,9 +23,11 @@ import ( "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/data/bookkeeping" "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/data/transactions/logic" "github.com/algorand/go-algorand/data/txntest" + "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/protocol" ) @@ -78,12 +80,18 @@ func fillProgramTemplate(beforeInnersOps, innerApprovalProgram, betweenInnersOps // TestScenarioInfo holds arguments used to call a TestScenarioGenerator type TestScenarioInfo struct { - CallingTxn transactions.Transaction - MinFee basics.MicroAlgos - CreatedAppID basics.AppIndex + CallingTxn transactions.Transaction + SenderData ledgercore.AccountData + AppAccountData ledgercore.AccountData + FeeSinkData ledgercore.AccountData + FeeSinkAddr basics.Address + MinFee basics.MicroAlgos + CreatedAppID basics.AppIndex + BlockHeader bookkeeping.BlockHeader + PrevTimestamp int64 } -func expectedApplyData(info TestScenarioInfo) transactions.ApplyData { +func expectedApplyDataAndStateDelta(info TestScenarioInfo, appCallProgram string, innerProgramBytes []byte) (transactions.ApplyData, ledgercore.StateDelta, ledgercore.StateDelta, ledgercore.StateDelta, ledgercore.StateDelta) { expectedInnerAppCall := txntest.Txn{ Type: protocol.ApplicationCallTx, Sender: info.CreatedAppID.Address(), @@ -128,7 +136,8 @@ pushint 1`, Fee: info.MinFee, } expectedInnerPay2AD := transactions.ApplyData{} - return transactions.ApplyData{ + + expectedAD := transactions.ApplyData{ ApplicationID: info.CreatedAppID, EvalDelta: transactions.EvalDelta{ GlobalDelta: basics.StateDelta{}, @@ -150,6 +159,153 @@ pushint 1`, Logs: []string{"a", "b", "c"}, }, } + + ops, err := logic.AssembleString(appCallProgram) + if err != nil { + panic(err) + } + + expectedSenderData := info.SenderData + expectedSenderData.MicroAlgos.Raw -= info.CallingTxn.Fee.Raw + expectedSenderData.TotalAppParams++ + expectedFeeSinkData := info.FeeSinkData + expectedFeeSinkData.MicroAlgos.Raw += info.CallingTxn.Fee.Raw + + expectedDeltaCallingTxn := ledgercore.StateDelta{ + Accts: ledgercore.AccountDeltas{ + Accts: []ledgercore.BalanceRecord{ + { + Addr: info.CallingTxn.Sender, + AccountData: expectedSenderData, + }, + { + Addr: info.FeeSinkAddr, + AccountData: expectedFeeSinkData, + }, + }, + AppResources: []ledgercore.AppResourceRecord{ + { + Aidx: info.CreatedAppID, + Addr: info.CallingTxn.Sender, + Params: ledgercore.AppParamsDelta{ + Params: &basics.AppParams{ + ApprovalProgram: ops.Program, + ClearStateProgram: info.CallingTxn.ClearStateProgram, + StateSchemas: basics.StateSchemas{ + LocalStateSchema: info.CallingTxn.LocalStateSchema, + GlobalStateSchema: info.CallingTxn.GlobalStateSchema, + }, + ExtraProgramPages: info.CallingTxn.ExtraProgramPages, + }, + }, + }, + }, + }, + Creatables: map[basics.CreatableIndex]ledgercore.ModifiedCreatable{ + basics.CreatableIndex(info.CreatedAppID): { + Ctype: basics.AppCreatable, + Created: true, + Creator: info.CallingTxn.Sender, + }, + }, + Txids: map[transactions.Txid]ledgercore.IncludedTransactions{ + // Cannot call info.CallingTxn.ID() yet, since the txn and its group are not yet final. Instead, + // use the Txid zero value as a placeholder. It's up to the caller to update this if they need it. + {}: { + LastValid: info.CallingTxn.LastValid, + Intra: 0, + }, + }, + Hdr: &info.BlockHeader, + PrevTimestamp: info.PrevTimestamp, + } + expectedDeltaCallingTxn.Hydrate() + + expectedAppAccountData := info.AppAccountData + expectedAppAccountData.TotalAppParams++ + expectedAppAccountData.MicroAlgos.Raw -= info.MinFee.Raw + expectedFeeSinkData.MicroAlgos.Raw += info.MinFee.Raw + + expectedDeltaInnerAppCall := ledgercore.StateDelta{ + Accts: ledgercore.AccountDeltas{ + Accts: []ledgercore.BalanceRecord{ + { + Addr: info.CreatedAppID.Address(), + AccountData: expectedAppAccountData, + }, + { + Addr: info.FeeSinkAddr, + AccountData: expectedFeeSinkData, + }, + }, + AppResources: []ledgercore.AppResourceRecord{ + { + Aidx: info.CreatedAppID + 1, + Addr: info.CreatedAppID.Address(), + Params: ledgercore.AppParamsDelta{ + Params: &basics.AppParams{ + ApprovalProgram: innerProgramBytes, + ClearStateProgram: []byte{0x06, 0x81, 0x01}, // #pragma version 6; int 1; + }, + }, + }, + }, + }, + Creatables: map[basics.CreatableIndex]ledgercore.ModifiedCreatable{ + basics.CreatableIndex(info.CreatedAppID + 1): { + Ctype: basics.AppCreatable, + Created: true, + Creator: info.CreatedAppID.Address(), + }, + }, + Hdr: &info.BlockHeader, + PrevTimestamp: info.PrevTimestamp, + } + expectedDeltaInnerAppCall.Hydrate() + + expectedAppAccountData.MicroAlgos.Raw -= info.MinFee.Raw + expectedFeeSinkData.MicroAlgos.Raw += info.MinFee.Raw + + expectedDeltaInnerPay1 := ledgercore.StateDelta{ + Accts: ledgercore.AccountDeltas{ + Accts: []ledgercore.BalanceRecord{ + { + Addr: info.CreatedAppID.Address(), + AccountData: expectedAppAccountData, + }, + { + Addr: info.FeeSinkAddr, + AccountData: expectedFeeSinkData, + }, + }, + }, + Hdr: &info.BlockHeader, + PrevTimestamp: info.PrevTimestamp, + } + expectedDeltaInnerPay1.Hydrate() + + expectedAppAccountData.MicroAlgos.Raw -= info.MinFee.Raw + expectedFeeSinkData.MicroAlgos.Raw += info.MinFee.Raw + + expectedDeltaInnerPay2 := ledgercore.StateDelta{ + Accts: ledgercore.AccountDeltas{ + Accts: []ledgercore.BalanceRecord{ + { + Addr: info.CreatedAppID.Address(), + AccountData: expectedAppAccountData, + }, + { + Addr: info.FeeSinkAddr, + AccountData: expectedFeeSinkData, + }, + }, + }, + Hdr: &info.BlockHeader, + PrevTimestamp: info.PrevTimestamp, + } + expectedDeltaInnerPay2.Hydrate() + + return expectedAD, expectedDeltaCallingTxn, expectedDeltaInnerAppCall, expectedDeltaInnerPay1, expectedDeltaInnerPay2 } // TestScenarioOutcome represents an outcome of a TestScenario @@ -172,6 +328,7 @@ type TestScenario struct { FailedAt []uint64 ExpectedEvents []Event ExpectedSimulationAD transactions.ApplyData + ExpectedStateDelta ledgercore.StateDelta AppBudgetAdded uint64 AppBudgetConsumed uint64 TxnAppBudgetConsumed []uint64 @@ -199,8 +356,10 @@ func GetTestScenarios() map[string]TestScenarioGenerator { noFailureName := "none" noFailure := func(info TestScenarioInfo) TestScenario { - expectedAD := expectedApplyData(info) program := fillProgramTemplate("", successInnerProgram, "", 1, 2, "pushint 1") + expectedAD, expectedDeltaCallingTxn, expectedDeltaInnerAppCall, expectedDeltaInnerPay1, expectedDeltaInnerPay2 := expectedApplyDataAndStateDelta(info, program, successInnerProgramBytes) + expectedDelta := MergeStateDeltas(expectedDeltaCallingTxn, expectedDeltaInnerAppCall, expectedDeltaInnerPay1, expectedDeltaInnerPay2) + return TestScenario{ Outcome: ApprovalOutcome, Program: program, @@ -222,7 +381,7 @@ func GetTestScenarios() map[string]TestScenarioGenerator { { AfterProgram(logic.ModeApp, false), AfterTxn(protocol.ApplicationCallTx, expectedAD.EvalDelta.InnerTxns[0].ApplyData, false), - AfterTxnGroup(1, false), // end first itxn group + AfterTxnGroup(1, nil, false), // end first itxn group AfterOpcode(false), }, OpcodeEvents(16, false), @@ -233,7 +392,7 @@ func GetTestScenarios() map[string]TestScenarioGenerator { AfterTxn(protocol.PaymentTx, expectedAD.EvalDelta.InnerTxns[1].ApplyData, false), BeforeTxn(protocol.PaymentTx), AfterTxn(protocol.PaymentTx, expectedAD.EvalDelta.InnerTxns[2].ApplyData, false), - AfterTxnGroup(2, false), // end second itxn group + AfterTxnGroup(2, nil, false), // end second itxn group AfterOpcode(false), }, OpcodeEvents(3, false), @@ -243,6 +402,7 @@ func GetTestScenarios() map[string]TestScenarioGenerator { }, }), ExpectedSimulationAD: expectedAD, + ExpectedStateDelta: expectedDelta, AppBudgetAdded: 2100, AppBudgetConsumed: 35, TxnAppBudgetConsumed: []uint64{0, 35}, @@ -273,8 +433,13 @@ func GetTestScenarios() map[string]TestScenarioGenerator { beforeInnersName := fmt.Sprintf("before inners,error=%t", shouldError) beforeInners := func(info TestScenarioInfo) TestScenario { - expectedAD := expectedApplyData(info) program := fillProgramTemplate(failureOps, successInnerProgram, "", 1, 2, "pushint 1") + expectedAD, expectedDeltaCallingTxn, _, _, _ := expectedApplyDataAndStateDelta(info, program, successInnerProgramBytes) + expectedDelta := expectedDeltaCallingTxn + + // remove failed txids from delta + expectedDeltaCallingTxn.Txids = nil + // EvalDeltas are removed from failed app call transactions expectedADNoED := expectedAD expectedADNoED.EvalDelta = transactions.EvalDelta{} @@ -298,6 +463,7 @@ func GetTestScenarios() map[string]TestScenarioGenerator { }, }), ExpectedSimulationAD: expectedAD, + ExpectedStateDelta: expectedDelta, AppBudgetAdded: 700, AppBudgetConsumed: 4, TxnAppBudgetConsumed: []uint64{0, 4}, @@ -307,7 +473,12 @@ func GetTestScenarios() map[string]TestScenarioGenerator { firstInnerName := fmt.Sprintf("first inner,error=%t", shouldError) firstInner := func(info TestScenarioInfo) TestScenario { - expectedAD := expectedApplyData(info) + program := fillProgramTemplate("", failureInnerProgram, "", 1, 2, "pushint 1") + expectedAD, expectedDeltaCallingTxn, _, _, _ := expectedApplyDataAndStateDelta(info, program, failureInnerProgramBytes) + expectedDelta := expectedDeltaCallingTxn + + // remove failed txids from delta + expectedDeltaCallingTxn.Txids = nil // EvalDeltas are removed from failed app call transactions expectedInnerAppCallADNoEvalDelta := expectedAD.EvalDelta.InnerTxns[0].ApplyData @@ -320,8 +491,6 @@ func GetTestScenarios() map[string]TestScenarioGenerator { expectedAD.EvalDelta.InnerTxns = expectedAD.EvalDelta.InnerTxns[:1] expectedAD.EvalDelta.InnerTxns[0].Txn.ApprovalProgram = failureInnerProgramBytes - - program := fillProgramTemplate("", failureInnerProgram, "", 1, 2, "pushint 1") return TestScenario{ Outcome: outcome, Program: program, @@ -343,13 +512,14 @@ func GetTestScenarios() map[string]TestScenarioGenerator { { AfterProgram(logic.ModeApp, shouldError), AfterTxn(protocol.ApplicationCallTx, expectedInnerAppCallADNoEvalDelta, true), - AfterTxnGroup(1, true), // end first itxn group + AfterTxnGroup(1, nil, true), // end first itxn group AfterOpcode(true), AfterProgram(logic.ModeApp, true), AfterTxn(protocol.ApplicationCallTx, expectedADNoED, true), }, }), ExpectedSimulationAD: expectedAD, + ExpectedStateDelta: expectedDelta, AppBudgetAdded: 1400, AppBudgetConsumed: 15, TxnAppBudgetConsumed: []uint64{0, 15}, @@ -359,7 +529,13 @@ func GetTestScenarios() map[string]TestScenarioGenerator { betweenInnersName := fmt.Sprintf("between inners,error=%t", shouldError) betweenInners := func(info TestScenarioInfo) TestScenario { - expectedAD := expectedApplyData(info) + program := fillProgramTemplate("", successInnerProgram, failureOps, 1, 2, "pushint 1") + expectedAD, expectedDeltaCallingTxn, _, _, _ := expectedApplyDataAndStateDelta(info, program, successInnerProgramBytes) + expectedDelta := expectedDeltaCallingTxn + + // remove failed txids from delta + expectedDeltaCallingTxn.Txids = nil + expectedInnerAppCallAD := expectedAD.EvalDelta.InnerTxns[0].ApplyData // EvalDeltas are removed from failed app call transactions @@ -370,8 +546,6 @@ func GetTestScenarios() map[string]TestScenarioGenerator { expectedAD.EvalDelta.Logs = expectedAD.EvalDelta.Logs[:2] expectedAD.EvalDelta.InnerTxns = expectedAD.EvalDelta.InnerTxns[:1] - - program := fillProgramTemplate("", successInnerProgram, failureOps, 1, 2, "pushint 1") return TestScenario{ Outcome: outcome, Program: program, @@ -393,7 +567,7 @@ func GetTestScenarios() map[string]TestScenarioGenerator { { AfterProgram(logic.ModeApp, false), AfterTxn(protocol.ApplicationCallTx, expectedInnerAppCallAD, false), - AfterTxnGroup(1, false), // end first itxn group + AfterTxnGroup(1, nil, false), // end first itxn group AfterOpcode(false), }, OpcodeEvents(4, shouldError), @@ -403,6 +577,7 @@ func GetTestScenarios() map[string]TestScenarioGenerator { }, }), ExpectedSimulationAD: expectedAD, + ExpectedStateDelta: expectedDelta, AppBudgetAdded: 1400, AppBudgetConsumed: 19, TxnAppBudgetConsumed: []uint64{0, 19}, @@ -413,7 +588,13 @@ func GetTestScenarios() map[string]TestScenarioGenerator { if shouldError { secondInnerName := "second inner" secondInner := func(info TestScenarioInfo) TestScenario { - expectedAD := expectedApplyData(info) + program := fillProgramTemplate("", successInnerProgram, "", math.MaxUint64, 2, "pushint 1") + expectedAD, expectedDeltaCallingTxn, _, _, _ := expectedApplyDataAndStateDelta(info, program, successInnerProgramBytes) + expectedDelta := expectedDeltaCallingTxn + + // remove failed txids from delta + expectedDeltaCallingTxn.Txids = nil + expectedInnerAppCallAD := expectedAD.EvalDelta.InnerTxns[0].ApplyData expectedInnerPay1AD := expectedAD.EvalDelta.InnerTxns[1].ApplyData @@ -425,8 +606,6 @@ func GetTestScenarios() map[string]TestScenarioGenerator { expectedAD.EvalDelta.Logs = expectedAD.EvalDelta.Logs[:2] expectedAD.EvalDelta.InnerTxns[1].Txn.Amount.Raw = math.MaxUint64 - - program := fillProgramTemplate("", successInnerProgram, "", math.MaxUint64, 2, "pushint 1") return TestScenario{ Outcome: ErrorOutcome, Program: program, @@ -448,7 +627,7 @@ func GetTestScenarios() map[string]TestScenarioGenerator { { AfterProgram(logic.ModeApp, false), AfterTxn(protocol.ApplicationCallTx, expectedInnerAppCallAD, false), - AfterTxnGroup(1, false), // end first itxn group + AfterTxnGroup(1, nil, false), // end first itxn group AfterOpcode(false), }, OpcodeEvents(16, false), @@ -457,13 +636,14 @@ func GetTestScenarios() map[string]TestScenarioGenerator { BeforeTxnGroup(2), // start second itxn group BeforeTxn(protocol.PaymentTx), AfterTxn(protocol.PaymentTx, expectedInnerPay1AD, true), - AfterTxnGroup(2, true), // end second itxn group + AfterTxnGroup(2, nil, true), // end second itxn group AfterOpcode(true), AfterProgram(logic.ModeApp, true), AfterTxn(protocol.ApplicationCallTx, expectedADNoED, true), }, }), ExpectedSimulationAD: expectedAD, + ExpectedStateDelta: expectedDelta, AppBudgetAdded: 2100, AppBudgetConsumed: 32, TxnAppBudgetConsumed: []uint64{0, 32}, @@ -473,7 +653,12 @@ func GetTestScenarios() map[string]TestScenarioGenerator { thirdInnerName := "third inner" thirdInner := func(info TestScenarioInfo) TestScenario { - expectedAD := expectedApplyData(info) + program := fillProgramTemplate("", successInnerProgram, "", 1, math.MaxUint64, "pushint 1") + expectedAD, expectedDeltaCallingTxn, _, _, _ := expectedApplyDataAndStateDelta(info, program, successInnerProgramBytes) + expectedDelta := expectedDeltaCallingTxn + + // remove failed txids from delta + expectedDeltaCallingTxn.Txids = nil expectedInnerAppCallAD := expectedAD.EvalDelta.InnerTxns[0].ApplyData expectedInnerPay1AD := expectedAD.EvalDelta.InnerTxns[1].ApplyData @@ -487,8 +672,6 @@ func GetTestScenarios() map[string]TestScenarioGenerator { expectedAD.EvalDelta.Logs = expectedAD.EvalDelta.Logs[:2] expectedAD.EvalDelta.InnerTxns[2].Txn.Amount.Raw = math.MaxUint64 - - program := fillProgramTemplate("", successInnerProgram, "", 1, math.MaxUint64, "pushint 1") return TestScenario{ Outcome: ErrorOutcome, Program: program, @@ -510,7 +693,7 @@ func GetTestScenarios() map[string]TestScenarioGenerator { { AfterProgram(logic.ModeApp, false), AfterTxn(protocol.ApplicationCallTx, expectedInnerAppCallAD, false), - AfterTxnGroup(1, false), // end first itxn group + AfterTxnGroup(1, nil, false), // end first itxn group AfterOpcode(false), }, OpcodeEvents(16, false), @@ -521,13 +704,14 @@ func GetTestScenarios() map[string]TestScenarioGenerator { AfterTxn(protocol.PaymentTx, expectedInnerPay1AD, false), BeforeTxn(protocol.PaymentTx), AfterTxn(protocol.PaymentTx, expectedInnerPay2AD, true), - AfterTxnGroup(2, true), // end second itxn group + AfterTxnGroup(2, nil, true), // end second itxn group AfterOpcode(true), AfterProgram(logic.ModeApp, true), AfterTxn(protocol.ApplicationCallTx, expectedADNoED, true), }, }), ExpectedSimulationAD: expectedAD, + ExpectedStateDelta: expectedDelta, AppBudgetAdded: 2100, AppBudgetConsumed: 32, TxnAppBudgetConsumed: []uint64{0, 32}, @@ -538,14 +722,19 @@ func GetTestScenarios() map[string]TestScenarioGenerator { afterInnersName := fmt.Sprintf("after inners,error=%t", shouldError) afterInners := func(info TestScenarioInfo) TestScenario { - expectedAD := expectedApplyData(info) + program := fillProgramTemplate("", successInnerProgram, "", 1, 2, singleFailureOp) + expectedAD, expectedDeltaCallingTxn, _, _, _ := expectedApplyDataAndStateDelta(info, program, successInnerProgramBytes) + expectedDelta := expectedDeltaCallingTxn + + // remove failed txids from delta + expectedDeltaCallingTxn.Txids = nil + expectedInnerAppCallAD := expectedAD.EvalDelta.InnerTxns[0].ApplyData expectedInnerPay1AD := expectedAD.EvalDelta.InnerTxns[1].ApplyData expectedInnerPay2AD := expectedAD.EvalDelta.InnerTxns[2].ApplyData // EvalDeltas are removed from failed app call transactions expectedADNoED := expectedAD expectedADNoED.EvalDelta = transactions.EvalDelta{} - program := fillProgramTemplate("", successInnerProgram, "", 1, 2, singleFailureOp) return TestScenario{ Outcome: outcome, Program: program, @@ -567,7 +756,7 @@ func GetTestScenarios() map[string]TestScenarioGenerator { { AfterProgram(logic.ModeApp, false), AfterTxn(protocol.ApplicationCallTx, expectedInnerAppCallAD, false), - AfterTxnGroup(1, false), // end first itxn group + AfterTxnGroup(1, nil, false), // end first itxn group AfterOpcode(false), }, OpcodeEvents(16, false), @@ -578,7 +767,7 @@ func GetTestScenarios() map[string]TestScenarioGenerator { AfterTxn(protocol.PaymentTx, expectedInnerPay1AD, false), BeforeTxn(protocol.PaymentTx), AfterTxn(protocol.PaymentTx, expectedInnerPay2AD, false), - AfterTxnGroup(2, false), // end second itxn group + AfterTxnGroup(2, nil, false), // end second itxn group AfterOpcode(false), }, OpcodeEvents(3, shouldError), @@ -588,6 +777,7 @@ func GetTestScenarios() map[string]TestScenarioGenerator { }, }), ExpectedSimulationAD: expectedAD, + ExpectedStateDelta: expectedDelta, AppBudgetAdded: 2100, AppBudgetConsumed: 35, TxnAppBudgetConsumed: []uint64{0, 35}, @@ -614,3 +804,40 @@ func StripInnerTxnGroupIDsFromEvents(events []Event) []Event { } return events } + +// MergeStateDeltas merges multiple state deltas into one. The arguments are not modified, but the +// first delta is used to populate non-mergeable fields in the result. +func MergeStateDeltas(deltas ...ledgercore.StateDelta) ledgercore.StateDelta { + if len(deltas) == 0 { + return ledgercore.StateDelta{} + } + + result := ledgercore.StateDelta{ + // copy basic fields from the first delta + Hdr: deltas[0].Hdr, + StateProofNext: deltas[0].StateProofNext, + PrevTimestamp: deltas[0].PrevTimestamp, + Totals: deltas[0].Totals, + + // initialize structure for later use + Txids: make(map[transactions.Txid]ledgercore.IncludedTransactions), + } + for _, delta := range deltas { + result.Accts.MergeAccounts(delta.Accts) + for key, delta := range delta.KvMods { + result.AddKvMod(key, delta) + } + for id, delta := range delta.Creatables { + result.AddCreatable(id, delta) + } + txidBase := uint64(len(result.Txids)) + for txid, includedTx := range delta.Txids { + includedTx.Intra += txidBase + result.Txids[txid] = includedTx + } + for lease, round := range delta.Txleases { + result.Txleases[lease] = round + } + } + return result +} diff --git a/data/transactions/logic/mocktracer/tracer.go b/data/transactions/logic/mocktracer/tracer.go index 30c4bff70f..13a6d92d7a 100644 --- a/data/transactions/logic/mocktracer/tracer.go +++ b/data/transactions/logic/mocktracer/tracer.go @@ -17,12 +17,16 @@ package mocktracer import ( + "testing" + "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/ledger/ledgercore" "github.com/algorand/go-algorand/protocol" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) // EventType represents a type of logic.EvalTracer event @@ -64,6 +68,9 @@ type Event struct { // only for AfterTxn TxnApplyData transactions.ApplyData + // only for AfterTxnGroup and AfterTxn + Deltas *ledgercore.StateDelta + // only for BeforeTxnGroup and AfterTxnGroup GroupSize int @@ -85,8 +92,8 @@ func BeforeTxnGroup(groupSize int) Event { } // AfterTxnGroup creates a new Event with the type AfterTxnGroupEvent -func AfterTxnGroup(groupSize int, hasError bool) Event { - return Event{Type: AfterTxnGroupEvent, GroupSize: groupSize, HasError: hasError} +func AfterTxnGroup(groupSize int, deltas *ledgercore.StateDelta, hasError bool) Event { + return Event{Type: AfterTxnGroupEvent, GroupSize: groupSize, Deltas: copyDeltas(deltas), HasError: hasError} } // BeforeProgram creates a new Event with the type BeforeProgramEvent @@ -163,7 +170,7 @@ func (d *Tracer) BeforeTxnGroup(ep *logic.EvalParams) { // AfterTxnGroup mocks the logic.EvalTracer.AfterTxnGroup method func (d *Tracer) AfterTxnGroup(ep *logic.EvalParams, deltas *ledgercore.StateDelta, evalError error) { - d.Events = append(d.Events, AfterTxnGroup(len(ep.TxnGroup), evalError != nil)) + d.Events = append(d.Events, AfterTxnGroup(len(ep.TxnGroup), deltas, evalError != nil)) } // BeforeTxn mocks the logic.EvalTracer.BeforeTxn method @@ -200,3 +207,45 @@ func (d *Tracer) AfterOpcode(cx *logic.EvalContext, evalError error) { func (d *Tracer) AfterBlock(hdr *bookkeeping.BlockHeader) { d.Events = append(d.Events, AfterBlock(hdr.Round)) } + +// copyDeltas makes a deep copy of the given ledgercore.StateDelta pointer, if it's not nil. +// This is inefficient, but it should only be used for testing. +func copyDeltas(deltas *ledgercore.StateDelta) *ledgercore.StateDelta { + if deltas == nil { + return nil + } + encoded := protocol.EncodeReflect(deltas) + var clone ledgercore.StateDelta + err := protocol.DecodeReflect(encoded, &clone) + if err != nil { + panic(err) + } + return &clone +} + +// AssertEventsEqual asserts that two slices of Events are equal, taking into account complex +// equality of StateDeltas. The arguments will be modified in-place to normalize any StateDeltas. +func AssertEventsEqual(t *testing.T, expected, actual []Event) { + t.Helper() + + // Dehydrate deltas for better comparison + for i := range expected { + if expected[i].Deltas != nil { + expected[i].Deltas.Dehydrate() + } + } + for i := range actual { + if actual[i].Deltas != nil { + actual[i].Deltas.Dehydrate() + } + } + + // These extra checks are not necessary for correctness, but they provide more targeted information on failure + if assert.Equal(t, len(expected), len(actual)) { + for i := range expected { + assert.Equal(t, expected[i].Deltas, actual[i].Deltas, "StateDelta disagreement: i=%d, expected event type: %v, actual event type: %v", i, expected[i].Type, actual[i].Type) + } + } + + require.Equal(t, expected, actual) +} diff --git a/ledger/eval/cow_test.go b/ledger/eval/cow_test.go index f5e4fe25af..df33df168e 100644 --- a/ledger/eval/cow_test.go +++ b/ledger/eval/cow_test.go @@ -192,6 +192,51 @@ func TestCowBalance(t *testing.T) { checkCowByUpdate(t, c0, updates2) } +// TestCowDeltasAfterCommit tests that deltas are still valid after committing to parent. +func TestCowDeltasAfterCommit(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + accts0 := ledgertesting.RandomAccounts(20, true) + ml := mockLedger{balanceMap: accts0} + + c0 := makeRoundCowState( + &ml, bookkeeping.BlockHeader{}, config.Consensus[protocol.ConsensusCurrentVersion], + 0, ledgercore.AccountTotals{}, 0) + checkCow(t, c0, accts0) + + c1 := c0.child(0) + + acctUpdates, _, _ := ledgertesting.RandomDeltas(10, accts0, 0) + applyUpdates(c1, acctUpdates) + acctUpdates.Dehydrate() // Prep for comparison + + c1.kvPut("key", []byte("value")) + expectedKvMods := map[string]ledgercore.KvValueDelta{ + "key": { + Data: []byte("value"), + }, + } + + actualDeltas := c1.deltas() + actualDeltas.Dehydrate() // Prep for comparison + require.Equal(t, acctUpdates, actualDeltas.Accts) + require.Equal(t, expectedKvMods, actualDeltas.KvMods) + + // Parent should now have deltas + c1.commitToParent() + actualDeltas = c0.deltas() + actualDeltas.Dehydrate() // Prep for comparison + require.Equal(t, acctUpdates, actualDeltas.Accts) + require.Equal(t, expectedKvMods, actualDeltas.KvMods) + + // Deltas remain valid in child after commit + actualDeltas = c0.deltas() + actualDeltas.Dehydrate() // Prep for comparison + require.Equal(t, acctUpdates, actualDeltas.Accts) + require.Equal(t, expectedKvMods, actualDeltas.KvMods) +} + func BenchmarkCowChild(b *testing.B) { b.ReportAllocs() cow := makeRoundCowState(nil, bookkeeping.BlockHeader{}, config.ConsensusParams{}, 10000, ledgercore.AccountTotals{}, 16) diff --git a/ledger/eval/eval_test.go b/ledger/eval/eval_test.go index 84de779ec1..08a8b8c824 100644 --- a/ledger/eval/eval_test.go +++ b/ledger/eval/eval_test.go @@ -275,13 +275,14 @@ func TestTransactionGroupWithTracer(t *testing.T) { t.Parallel() genesisInitState, addrs, keys := ledgertesting.Genesis(10) + basicAppID := basics.AppIndex(1001) innerAppID := basics.AppIndex(1003) innerAppAddress := innerAppID.Address() balances := genesisInitState.Accounts balances[innerAppAddress] = basics_testing.MakeAccountData(basics.Offline, basics.MicroAlgos{Raw: 1_000_000}) genesisBalances := bookkeeping.GenesisBalances{ - Balances: genesisInitState.Accounts, + Balances: balances, FeeSink: testSinkAddr, RewardsPool: testPoolAddr, Timestamp: 0, @@ -343,7 +344,7 @@ int 1`, // an app call with inner txn innerAppCallTxn := txntest.Txn{ Type: protocol.ApplicationCallTx, - Sender: addrs[0], + Sender: addrs[4], ClearStateProgram: `#pragma version 6 int 1`, @@ -352,19 +353,50 @@ int 1`, Fee: minFee, GenesisHash: genHash, } + + expectedFeeSinkDataForScenario := ledgercore.ToAccountData(balances[testSinkAddr]) + expectedFeeSinkDataForScenario.MicroAlgos.Raw += basicAppCallTxn.Txn().Fee.Raw + if testCase.firstTxnBehavior == "approve" { + expectedFeeSinkDataForScenario.MicroAlgos.Raw += payTxn.Txn().Fee.Raw + } + scenario := testCase.innerAppCallScenario(mocktracer.TestScenarioInfo{ - CallingTxn: innerAppCallTxn.Txn(), - MinFee: minFee, - CreatedAppID: innerAppID, + CallingTxn: innerAppCallTxn.Txn(), + SenderData: ledgercore.ToAccountData(balances[addrs[4]]), + AppAccountData: ledgercore.ToAccountData(balances[innerAppAddress]), + FeeSinkData: expectedFeeSinkDataForScenario, + FeeSinkAddr: testSinkAddr, + MinFee: minFee, + CreatedAppID: innerAppID, + BlockHeader: eval.block.BlockHeader, + PrevTimestamp: blkHeader.TimeStamp, }) innerAppCallTxn.ApprovalProgram = scenario.Program txntest.Group(&basicAppCallTxn, &payTxn, &innerAppCallTxn) + // Update the expected state delta to reflect the inner app call txid + scenarioTxidValue, ok := scenario.ExpectedStateDelta.Txids[transactions.Txid{}] + if ok { + delete(scenario.ExpectedStateDelta.Txids, transactions.Txid{}) + scenario.ExpectedStateDelta.Txids[innerAppCallTxn.Txn().ID()] = scenarioTxidValue + } + for i := range scenario.ExpectedEvents { + deltas := scenario.ExpectedEvents[i].Deltas + if deltas == nil { + continue + } + txidValue, ok := deltas.Txids[transactions.Txid{}] + if ok { + delete(deltas.Txids, transactions.Txid{}) + deltas.Txids[innerAppCallTxn.Txn().ID()] = txidValue + } + } + txgroup := transactions.WrapSignedTxnsWithAD([]transactions.SignedTxn{ basicAppCallTxn.Txn().Sign(keys[0]), payTxn.Txn().Sign(keys[1]), - innerAppCallTxn.Txn().Sign(keys[0]), + innerAppCallTxn.Txn().Sign(keys[4]), }) require.Len(t, eval.block.Payset, 0) @@ -388,7 +420,7 @@ int 1`, } expectedBasicAppCallAD := transactions.ApplyData{ - ApplicationID: 1001, + ApplicationID: basicAppID, EvalDelta: transactions.EvalDelta{ GlobalDelta: basics.StateDelta{}, LocalDeltas: map[uint64]basics.StateDelta{}, @@ -402,10 +434,107 @@ int 1`, }, } - var expectedEvents = []mocktracer.Event{mocktracer.BeforeBlock(eval.block.Round())} + expectedFeeSinkData := ledgercore.ToAccountData(balances[testSinkAddr]) + expectedFeeSinkData.MicroAlgos.Raw += txgroup[0].Txn.Fee.Raw + expectedAcct0Data := ledgercore.ToAccountData(balances[addrs[0]]) + expectedAcct0Data.MicroAlgos.Raw -= txgroup[0].Txn.Fee.Raw + expectedAcct0Data.TotalAppParams = 1 + + expectedBlockHeader := eval.block.BlockHeader + expectedBasicAppCallDelta := ledgercore.StateDelta{ + Accts: ledgercore.AccountDeltas{ + Accts: []ledgercore.BalanceRecord{ + { + Addr: addrs[0], + AccountData: expectedAcct0Data, + }, + { + Addr: testSinkAddr, + AccountData: expectedFeeSinkData, + }, + }, + AppResources: []ledgercore.AppResourceRecord{ + { + Aidx: basicAppID, + Addr: addrs[0], + Params: ledgercore.AppParamsDelta{ + Params: &basics.AppParams{ + ApprovalProgram: txgroup[0].Txn.ApprovalProgram, + ClearStateProgram: txgroup[0].Txn.ClearStateProgram, + }, + }, + }, + }, + }, + Creatables: map[basics.CreatableIndex]ledgercore.ModifiedCreatable{ + basics.CreatableIndex(basicAppID): { + Ctype: basics.AppCreatable, + Created: true, + Creator: addrs[0], + }, + }, + Txids: map[transactions.Txid]ledgercore.IncludedTransactions{ + txgroup[0].Txn.ID(): { + LastValid: txgroup[0].Txn.LastValid, + Intra: 0, + }, + }, + Hdr: &expectedBlockHeader, + PrevTimestamp: blkHeader.TimeStamp, + } + expectedBasicAppCallDelta.Hydrate() + + expectedEvents := []mocktracer.Event{mocktracer.BeforeBlock(eval.block.Round())} if testCase.firstTxnBehavior == "approve" { err = eval.endOfBlock() require.NoError(t, err) + + expectedAcct1Data := ledgercore.AccountData{} + expectedAcct2Data := ledgercore.ToAccountData(balances[addrs[2]]) + expectedAcct2Data.MicroAlgos.Raw += payTxn.Amount + expectedAcct3Data := ledgercore.ToAccountData(balances[addrs[3]]) + expectedAcct3Data.MicroAlgos.Raw += expectedPayTxnAD.ClosingAmount.Raw + expectedFeeSinkData.MicroAlgos.Raw += txgroup[1].Txn.Fee.Raw + + expectedPayTxnDelta := ledgercore.StateDelta{ + Accts: ledgercore.AccountDeltas{ + Accts: []ledgercore.BalanceRecord{ + { + Addr: addrs[1], + AccountData: expectedAcct1Data, + }, + { + Addr: testSinkAddr, + AccountData: expectedFeeSinkData, + }, + { + Addr: addrs[2], + AccountData: expectedAcct2Data, + }, + { + Addr: addrs[3], + AccountData: expectedAcct3Data, + }, + }, + }, + Txids: map[transactions.Txid]ledgercore.IncludedTransactions{ + txgroup[1].Txn.ID(): { + LastValid: txgroup[1].Txn.LastValid, + Intra: 0, // will be incremented once merged + }, + }, + Hdr: &expectedBlockHeader, + PrevTimestamp: blkHeader.TimeStamp, + } + expectedPayTxnDelta.Hydrate() + + expectedDelta := mocktracer.MergeStateDeltas(expectedBasicAppCallDelta, expectedPayTxnDelta, scenario.ExpectedStateDelta) + + // If the scenario failed, we expect the failed txn ID to be removed from the group state delta + if scenario.Outcome != mocktracer.ApprovalOutcome { + delete(expectedDelta.Txids, txgroup[2].ID()) + } + expectedEvents = append(expectedEvents, mocktracer.FlattenEvents([][]mocktracer.Event{ { mocktracer.BeforeTxnGroup(3), @@ -421,13 +550,14 @@ int 1`, }, scenario.ExpectedEvents, { - mocktracer.AfterTxnGroup(3, scenario.Outcome != mocktracer.ApprovalOutcome), - }, - { + mocktracer.AfterTxnGroup(3, &expectedDelta, scenario.Outcome != mocktracer.ApprovalOutcome), mocktracer.AfterBlock(eval.block.Round()), }, })...) } else { + // Removed failed txid from expected state delta + delete(expectedBasicAppCallDelta.Txids, txgroup[0].Txn.ID()) + hasError := testCase.firstTxnBehavior == "error" // EvalDeltas are removed from failed app call transactions expectedBasicAppCallAD.EvalDelta = transactions.EvalDelta{} @@ -441,11 +571,12 @@ int 1`, { mocktracer.AfterProgram(logic.ModeApp, hasError), mocktracer.AfterTxn(protocol.ApplicationCallTx, expectedBasicAppCallAD, true), // end basicAppCallTxn - mocktracer.AfterTxnGroup(3, true), + mocktracer.AfterTxnGroup(3, &expectedBasicAppCallDelta, true), }, })...) } - require.Equal(t, expectedEvents, mocktracer.StripInnerTxnGroupIDsFromEvents(tracer.Events)) + actualEvents := mocktracer.StripInnerTxnGroupIDsFromEvents(tracer.Events) + mocktracer.AssertEventsEqual(t, expectedEvents, actualEvents) }) } } diff --git a/ledger/ledgercore/statedelta.go b/ledger/ledgercore/statedelta.go index 1a0f824de1..7e9e25e0d0 100644 --- a/ledger/ledgercore/statedelta.go +++ b/ledger/ledgercore/statedelta.go @@ -219,6 +219,32 @@ func (sd *StateDelta) PopulateStateDelta(hdr *bookkeeping.BlockHeader, prevTimes sd.PrevTimestamp = prevTimestamp } +// Hydrate reverses the effects of Dehydrate, restoring internal data. +func (sd *StateDelta) Hydrate() { + sd.Accts.Hydrate() +} + +// Dehydrate normalized the fields of this StateDelta, and clears any redundant internal caching. +// This is useful for comparing StateDelta objects for equality. +// +// NOTE: initialHint is lost in dehydration. All other fields can be restored by calling Hydrate() +func (sd *StateDelta) Dehydrate() { + sd.Accts.Dehydrate() + sd.initialHint = 0 + if sd.KvMods == nil { + sd.KvMods = make(map[string]KvValueDelta) + } + if sd.Txids == nil { + sd.Txids = make(map[transactions.Txid]IncludedTransactions) + } + if sd.Txleases == nil { + sd.Txleases = make(map[Txlease]basics.Round) + } + if sd.Creatables == nil { + sd.Creatables = make(map[basics.CreatableIndex]ModifiedCreatable) + } +} + // MakeAccountDeltas creates account delta // if adding new fields make sure to add them to the .reset() and .isEmpty() methods func MakeAccountDeltas(hint int) AccountDeltas { @@ -228,6 +254,62 @@ func MakeAccountDeltas(hint int) AccountDeltas { } } +// Hydrate reverses the effects of Dehydrate, restoring internal data. +func (ad *AccountDeltas) Hydrate() { + if ad.acctsCache == nil { + ad.acctsCache = make(map[basics.Address]int, len(ad.Accts)) + } + for idx, acct := range ad.Accts { + ad.acctsCache[acct.Addr] = idx + } + + if ad.appResourcesCache == nil { + ad.appResourcesCache = make(map[AccountApp]int, len(ad.AppResources)) + } + for idx, app := range ad.AppResources { + ad.appResourcesCache[AccountApp{app.Addr, app.Aidx}] = idx + } + + if ad.assetResourcesCache == nil { + ad.assetResourcesCache = make(map[AccountAsset]int, len(ad.AssetResources)) + } + for idx, asset := range ad.AssetResources { + ad.assetResourcesCache[AccountAsset{asset.Addr, asset.Aidx}] = idx + } +} + +// Dehydrate normalized the fields of this AccountDeltas, and clears any redundant internal caching. +// This is useful for comparing AccountDeltas objects for equality. +func (ad *AccountDeltas) Dehydrate() { + if ad.Accts == nil { + ad.Accts = []BalanceRecord{} + } + if ad.AppResources == nil { + ad.AppResources = []AppResourceRecord{} + } + if ad.AssetResources == nil { + ad.AssetResources = []AssetResourceRecord{} + } + if ad.acctsCache == nil { + ad.acctsCache = make(map[basics.Address]int) + } + for key := range ad.acctsCache { + delete(ad.acctsCache, key) + } + if ad.appResourcesCache == nil { + ad.appResourcesCache = make(map[AccountApp]int) + } + for key := range ad.appResourcesCache { + delete(ad.appResourcesCache, key) + } + if ad.assetResourcesCache == nil { + ad.assetResourcesCache = make(map[AccountAsset]int) + } + for key := range ad.assetResourcesCache { + delete(ad.assetResourcesCache, key) + } +} + // Reset resets the StateDelta for re-use with sync.Pool func (sd *StateDelta) Reset() { sd.Accts.reset() @@ -360,21 +442,17 @@ func (ad AccountDeltas) ModifiedAccounts() []basics.Address { // MergeAccounts applies other accounts into this StateDelta accounts func (ad *AccountDeltas) MergeAccounts(other AccountDeltas) { - for new := range other.Accts { - addr := other.Accts[new].Addr - acct := other.Accts[new].AccountData - ad.Upsert(addr, acct) + for i := range other.Accts { + balanceRecord := &other.Accts[i] + ad.Upsert(balanceRecord.Addr, balanceRecord.AccountData) } - - for aapp, idx := range other.appResourcesCache { - params := other.AppResources[idx].Params - state := other.AppResources[idx].State - ad.UpsertAppResource(aapp.Address, aapp.App, params, state) + for i := range other.AppResources { + appResource := &other.AppResources[i] + ad.UpsertAppResource(appResource.Addr, appResource.Aidx, appResource.Params, appResource.State) } - for aapp, idx := range other.assetResourcesCache { - params := other.AssetResources[idx].Params - holding := other.AssetResources[idx].Holding - ad.UpsertAssetResource(aapp.Address, aapp.Asset, params, holding) + for i := range other.AssetResources { + assetResource := &other.AssetResources[i] + ad.UpsertAssetResource(assetResource.Addr, assetResource.Aidx, assetResource.Params, assetResource.Holding) } } diff --git a/ledger/ledgercore/statedelta_test.go b/ledger/ledgercore/statedelta_test.go index dd0f3e201d..2e683a410d 100644 --- a/ledger/ledgercore/statedelta_test.go +++ b/ledger/ledgercore/statedelta_test.go @@ -20,6 +20,7 @@ import ( "reflect" "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/algorand/go-algorand/crypto" @@ -99,6 +100,339 @@ func TestAccountDeltas(t *testing.T) { a.Equal(sample1, data) } +func TestAccountDeltasMergeAccountsOrder(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + addr1 := randomAddress() + data1 := AccountData{AccountBaseData: AccountBaseData{MicroAlgos: basics.MicroAlgos{Raw: 111}}} + addr2 := randomAddress() + data2 := AccountData{AccountBaseData: AccountBaseData{MicroAlgos: basics.MicroAlgos{Raw: 222}}} + addr3 := randomAddress() + data3 := AccountData{AccountBaseData: AccountBaseData{MicroAlgos: basics.MicroAlgos{Raw: 333}}} + addr4 := randomAddress() + data4 := AccountData{AccountBaseData: AccountBaseData{MicroAlgos: basics.MicroAlgos{Raw: 444}}} + + asset1 := basics.AssetIndex(100) + asset1Params := AssetParamsDelta{ + Params: &basics.AssetParams{Total: 1}, + } + asset2 := basics.AssetIndex(200) + asset2Params := AssetParamsDelta{ + Params: &basics.AssetParams{Total: 2}, + } + asset3 := basics.AssetIndex(300) + asset3Params := AssetParamsDelta{ + Params: &basics.AssetParams{Total: 3}, + } + asset4 := basics.AssetIndex(400) + asset4Params := AssetParamsDelta{ + Params: &basics.AssetParams{Total: 4}, + } + + app1 := basics.AppIndex(101) + app1Params := AppParamsDelta{ + Params: &basics.AppParams{ApprovalProgram: []byte("app1")}, + } + app2 := basics.AppIndex(201) + app2Params := AppParamsDelta{ + Params: &basics.AppParams{ApprovalProgram: []byte("app2")}, + } + app3 := basics.AppIndex(301) + app3Params := AppParamsDelta{ + Params: &basics.AppParams{ApprovalProgram: []byte("app3")}, + } + app4 := basics.AppIndex(401) + app4Params := AppParamsDelta{ + Params: &basics.AppParams{ApprovalProgram: []byte("app4")}, + } + + var ad1 AccountDeltas + ad1.Upsert(addr1, data1) + ad1.Upsert(addr2, data2) + ad1.UpsertAssetResource(addr1, asset1, asset1Params, AssetHoldingDelta{}) + ad1.UpsertAssetResource(addr2, asset2, asset2Params, AssetHoldingDelta{}) + ad1.UpsertAppResource(addr1, app1, app1Params, AppLocalStateDelta{}) + ad1.UpsertAppResource(addr2, app2, app2Params, AppLocalStateDelta{}) + + var ad2 AccountDeltas + ad2.Upsert(addr3, data3) + ad2.Upsert(addr4, data4) + ad2.UpsertAssetResource(addr3, asset3, asset3Params, AssetHoldingDelta{}) + ad2.UpsertAssetResource(addr4, asset4, asset4Params, AssetHoldingDelta{}) + ad2.UpsertAppResource(addr3, app3, app3Params, AppLocalStateDelta{}) + ad2.UpsertAppResource(addr4, app4, app4Params, AppLocalStateDelta{}) + + // Iterate to ensure deterministic order + for i := 0; i < 10; i++ { + var merged AccountDeltas + merged.MergeAccounts(ad1) + merged.MergeAccounts(ad2) + + var expectedAccounts []BalanceRecord + expectedAccounts = append(expectedAccounts, ad1.Accts...) + expectedAccounts = append(expectedAccounts, ad2.Accts...) + require.Equal(t, expectedAccounts, merged.Accts) + + var expectedAppResources []AppResourceRecord + expectedAppResources = append(expectedAppResources, ad1.AppResources...) + expectedAppResources = append(expectedAppResources, ad2.AppResources...) + require.Equal(t, expectedAppResources, merged.AppResources) + + var expectedAssetResources []AssetResourceRecord + expectedAssetResources = append(expectedAssetResources, ad1.AssetResources...) + expectedAssetResources = append(expectedAssetResources, ad2.AssetResources...) + require.Equal(t, expectedAssetResources, merged.AssetResources) + } +} + +func TestAccountDeltasDehydrateAndHydrate(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + addr1 := randomAddress() + data1 := AccountData{AccountBaseData: AccountBaseData{MicroAlgos: basics.MicroAlgos{Raw: 111}}} + addr2 := randomAddress() + data2 := AccountData{AccountBaseData: AccountBaseData{MicroAlgos: basics.MicroAlgos{Raw: 222}}} + + asset1 := basics.AssetIndex(100) + asset1Params := AssetParamsDelta{ + Params: &basics.AssetParams{Total: 1}, + } + asset2 := basics.AssetIndex(200) + asset2Params := AssetParamsDelta{ + Params: &basics.AssetParams{Total: 2}, + } + + app1 := basics.AppIndex(101) + app1Params := AppParamsDelta{ + Params: &basics.AppParams{ApprovalProgram: []byte("app1")}, + } + app2 := basics.AppIndex(201) + app2Params := AppParamsDelta{ + Params: &basics.AppParams{ApprovalProgram: []byte("app2")}, + } + + var ad AccountDeltas + ad.Upsert(addr1, data1) + ad.Upsert(addr2, data2) + ad.UpsertAssetResource(addr1, asset1, asset1Params, AssetHoldingDelta{}) + ad.UpsertAssetResource(addr2, asset2, asset2Params, AssetHoldingDelta{}) + ad.UpsertAppResource(addr1, app1, app1Params, AppLocalStateDelta{}) + ad.UpsertAppResource(addr2, app2, app2Params, AppLocalStateDelta{}) + + var adCopy AccountDeltas + adCopy.Upsert(addr1, data1) + adCopy.Upsert(addr2, data2) + adCopy.UpsertAssetResource(addr1, asset1, asset1Params, AssetHoldingDelta{}) + adCopy.UpsertAssetResource(addr2, asset2, asset2Params, AssetHoldingDelta{}) + adCopy.UpsertAppResource(addr1, app1, app1Params, AppLocalStateDelta{}) + adCopy.UpsertAppResource(addr2, app2, app2Params, AppLocalStateDelta{}) + + shallowAd := AccountDeltas{ + Accts: []BalanceRecord{ + { + Addr: addr1, + AccountData: data1, + }, + { + Addr: addr2, + AccountData: data2, + }, + }, + acctsCache: make(map[basics.Address]int), + AssetResources: []AssetResourceRecord{ + { + Aidx: asset1, + Addr: addr1, + Params: asset1Params, + }, + { + Aidx: asset2, + Addr: addr2, + Params: asset2Params, + }, + }, + assetResourcesCache: make(map[AccountAsset]int), + AppResources: []AppResourceRecord{ + { + Aidx: app1, + Addr: addr1, + Params: app1Params, + }, + { + Aidx: app2, + Addr: addr2, + Params: app2Params, + }, + }, + appResourcesCache: make(map[AccountApp]int), + } + + require.Equal(t, adCopy, ad) // should be identical + require.NotEqual(t, shallowAd, ad) // shallowAd has empty internal fields + + ad.Dehydrate() + + // Dehydration empties the internal fields + require.Equal(t, shallowAd, ad) + require.NotEqual(t, adCopy, ad) + + ad.Hydrate() + + // Hydration restores the internal fields + require.Equal(t, adCopy, ad) + require.NotEqual(t, shallowAd, ad) + + t.Run("NewFieldDetection", func(t *testing.T) { + v := reflect.ValueOf(&ad).Elem() + st := v.Type() + for i := 0; i < v.NumField(); i++ { + field := v.Field(i) + structField := st.Field(i) + isContainer := field.Kind() == reflect.Map || field.Kind() == reflect.Slice + if isContainer || !structField.IsExported() { + assert.False(t, v.Field(i).IsZero(), "new container or private field \"%v\" added to AccountDeltas, please update AccountDeltas.Hydrate() and .Dehydrate() to handle it before fixing the test", structField.Name) + } + } + }) +} + +func TestStateDeltaDehydrateAndHydrate(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + addr := randomAddress() + data := AccountData{AccountBaseData: AccountBaseData{MicroAlgos: basics.MicroAlgos{Raw: 111}}} + + asset := basics.AssetIndex(100) + assetParams := AssetParamsDelta{ + Params: &basics.AssetParams{Total: 1}, + } + + app := basics.AppIndex(101) + appParams := AppParamsDelta{ + Params: &basics.AppParams{ApprovalProgram: []byte("app1")}, + } + + prevTimestamp := int64(77) + stateProofNextRound := basics.Round(88) + var hdr bookkeeping.BlockHeader + + sd := MakeStateDelta(&hdr, prevTimestamp, 10, stateProofNextRound) + sd.Accts.Upsert(addr, data) + sd.Accts.UpsertAssetResource(addr, asset, assetParams, AssetHoldingDelta{}) + sd.Accts.UpsertAppResource(addr, app, appParams, AppLocalStateDelta{}) + sd.AddKvMod("key", KvValueDelta{Data: []byte("value")}) + sd.AddCreatable(100, ModifiedCreatable{ + Ctype: basics.AssetCreatable, + Created: true, + Creator: addr, + }) + sd.AddTxLease(Txlease{Sender: addr, Lease: [32]byte{1, 2, 3}}, 2000) + sd.Txids = map[transactions.Txid]IncludedTransactions{ + {5, 4, 3}: { + LastValid: 5, + }, + } + + sdCopy := MakeStateDelta(&hdr, prevTimestamp, 10, stateProofNextRound) + sdCopy.Accts.Upsert(addr, data) + sdCopy.Accts.UpsertAssetResource(addr, asset, assetParams, AssetHoldingDelta{}) + sdCopy.Accts.UpsertAppResource(addr, app, appParams, AppLocalStateDelta{}) + sdCopy.AddKvMod("key", KvValueDelta{Data: []byte("value")}) + sdCopy.AddCreatable(100, ModifiedCreatable{ + Ctype: basics.AssetCreatable, + Created: true, + Creator: addr, + }) + sdCopy.AddTxLease(Txlease{Sender: addr, Lease: [32]byte{1, 2, 3}}, 2000) + sdCopy.Txids = map[transactions.Txid]IncludedTransactions{ + {5, 4, 3}: { + LastValid: 5, + }, + } + + shallowSd := StateDelta{ + PrevTimestamp: prevTimestamp, + StateProofNext: stateProofNextRound, + Hdr: &hdr, + Accts: AccountDeltas{ + Accts: []BalanceRecord{ + { + Addr: addr, + AccountData: data, + }, + }, + acctsCache: make(map[basics.Address]int), + AssetResources: []AssetResourceRecord{ + { + Aidx: asset, + Addr: addr, + Params: assetParams, + }, + }, + assetResourcesCache: make(map[AccountAsset]int), + AppResources: []AppResourceRecord{ + { + Aidx: app, + Addr: addr, + Params: appParams, + }, + }, + appResourcesCache: make(map[AccountApp]int), + }, + KvMods: map[string]KvValueDelta{ + "key": {Data: []byte("value")}, + }, + Creatables: map[basics.CreatableIndex]ModifiedCreatable{ + 100: { + Ctype: basics.AssetCreatable, + Created: true, + Creator: addr, + }, + }, + Txleases: map[Txlease]basics.Round{ + {addr, [32]byte{1, 2, 3}}: 2000, + }, + Txids: map[transactions.Txid]IncludedTransactions{ + {5, 4, 3}: { + LastValid: 5, + }, + }, + } + + require.Equal(t, sdCopy, sd) // should be identical + require.NotEqual(t, shallowSd, sd) // shallowSd has empty internal fields + + sd.Dehydrate() + + // Dehydration empties the internal fields + require.Equal(t, shallowSd, sd) + require.NotEqual(t, sdCopy, sd) + + sd.Hydrate() + + // Hydration restores the internal fields, except for initialHint + require.NotEqual(t, sdCopy.initialHint, sd.initialHint) + sd.initialHint = sdCopy.initialHint + require.Equal(t, sdCopy, sd) + require.NotEqual(t, shallowSd, sd) + + t.Run("NewFieldDetection", func(t *testing.T) { + v := reflect.ValueOf(&sd).Elem() + st := v.Type() + for i := 0; i < v.NumField(); i++ { + field := v.Field(i) + structField := st.Field(i) + isContainer := field.Kind() == reflect.Map || field.Kind() == reflect.Slice + if isContainer || !structField.IsExported() { + assert.False(t, v.Field(i).IsZero(), "new container or private field \"%v\" added to StateDelta, please update StateDelta.Hydrate() and .Dehydrate() to handle it before fixing the test", structField.Name) + } + } + }) +} + func TestMakeStateDeltaMaps(t *testing.T) { partitiontest.PartitionTest(t) @@ -201,7 +535,7 @@ func TestStateDeltaReflect(t *testing.T) { st := v.Type() for i := 0; i < v.NumField(); i++ { reflectedStateDeltaName := st.Field(i).Name - require.Containsf(t, stateDeltaFieldNames, reflectedStateDeltaName, "new field:\"%v\" added to StateDelta, please update StateDelta.Reset() to handle it before fixing the test", reflectedStateDeltaName) + assert.Containsf(t, stateDeltaFieldNames, reflectedStateDeltaName, "new field:\"%v\" added to StateDelta, please update StateDelta.Reset() to handle it before fixing the test", reflectedStateDeltaName) } } @@ -222,7 +556,7 @@ func TestAccountDeltaReflect(t *testing.T) { st := v.Type() for i := 0; i < v.NumField(); i++ { reflectedAccountDeltaName := st.Field(i).Name - require.Containsf(t, AccountDeltaFieldNames, reflectedAccountDeltaName, "new field:\"%v\" added to AccountDeltas, please update AccountDeltas.reset() to handle it before fixing the test", reflectedAccountDeltaName) + assert.Containsf(t, AccountDeltaFieldNames, reflectedAccountDeltaName, "new field:\"%v\" added to AccountDeltas, please update AccountDeltas.reset() to handle it before fixing the test", reflectedAccountDeltaName) } } diff --git a/ledger/simulation/simulation_eval_test.go b/ledger/simulation/simulation_eval_test.go index 5869b49f53..115f52d5d6 100644 --- a/ledger/simulation/simulation_eval_test.go +++ b/ledger/simulation/simulation_eval_test.go @@ -118,13 +118,13 @@ func validateSimulationResult(t *testing.T, result simulation.Result) { } } -func simulationTest(t *testing.T, f func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase) { +func simulationTest(t *testing.T, f func(env simulationtesting.Environment) simulationTestCase) { t.Helper() - l, accounts, txnInfo := simulationtesting.PrepareSimulatorTest(t) - defer l.Close() - s := simulation.MakeSimulator(l) + env := simulationtesting.PrepareSimulatorTest(t) + defer env.Close() + s := simulation.MakeSimulator(env.Ledger) - testcase := f(accounts, txnInfo) + testcase := f(env) actual, err := s.Simulate(testcase.input) require.NoError(t, err) @@ -161,11 +161,11 @@ func TestPayTxn(t *testing.T) { t.Run("simple", func(t *testing.T) { t.Parallel() - simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { - sender := accounts[0] - receiver := accounts[1] + simulationTest(t, func(env simulationtesting.Environment) simulationTestCase { + sender := env.Accounts[0] + receiver := env.Accounts[1] - txn := txnInfo.NewTxn(txntest.Txn{ + txn := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.PaymentTx, Sender: sender.Addr, Receiver: receiver.Addr, @@ -178,7 +178,7 @@ func TestPayTxn(t *testing.T) { }, expected: simulation.Result{ Version: simulation.ResultLatestVersion, - LastRound: txnInfo.LatestRound(), + LastRound: env.TxnInfo.LatestRound(), TxnGroups: []simulation.TxnGroupResult{ { Txns: []simulation.TxnResult{{}}, @@ -191,13 +191,13 @@ func TestPayTxn(t *testing.T) { t.Run("close to", func(t *testing.T) { t.Parallel() - simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { - sender := accounts[0] - receiver := accounts[1] - closeTo := accounts[2] + simulationTest(t, func(env simulationtesting.Environment) simulationTestCase { + sender := env.Accounts[0] + receiver := env.Accounts[1] + closeTo := env.Accounts[2] amount := uint64(1_000_000) - txn := txnInfo.NewTxn(txntest.Txn{ + txn := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.PaymentTx, Sender: sender.Addr, Receiver: receiver.Addr, @@ -214,7 +214,7 @@ func TestPayTxn(t *testing.T) { }, expected: simulation.Result{ Version: simulation.ResultLatestVersion, - LastRound: txnInfo.LatestRound(), + LastRound: env.TxnInfo.LatestRound(), TxnGroups: []simulation.TxnGroupResult{ { Txns: []simulation.TxnResult{ @@ -235,12 +235,12 @@ func TestPayTxn(t *testing.T) { t.Run("overspend", func(t *testing.T) { t.Parallel() - simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { - sender := accounts[0] - receiver := accounts[1] + simulationTest(t, func(env simulationtesting.Environment) simulationTestCase { + sender := env.Accounts[0] + receiver := env.Accounts[1] amount := sender.AcctData.MicroAlgos.Raw + 100 - txn := txnInfo.NewTxn(txntest.Txn{ + txn := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.PaymentTx, Sender: sender.Addr, Receiver: receiver.Addr, @@ -254,7 +254,7 @@ func TestPayTxn(t *testing.T) { expectedError: fmt.Sprintf("tried to spend {%d}", amount), expected: simulation.Result{ Version: simulation.ResultLatestVersion, - LastRound: txnInfo.LatestRound(), + LastRound: env.TxnInfo.LatestRound(), TxnGroups: []simulation.TxnGroupResult{ { Txns: []simulation.TxnResult{{}}, @@ -274,11 +274,11 @@ func TestWrongAuthorizerTxn(t *testing.T) { optionalSigs := optionalSigs t.Run(fmt.Sprintf("optionalSigs=%t", optionalSigs), func(t *testing.T) { t.Parallel() - simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { - sender := accounts[0] - authority := accounts[1] + simulationTest(t, func(env simulationtesting.Environment) simulationTestCase { + sender := env.Accounts[0] + authority := env.Accounts[1] - txn := txnInfo.NewTxn(txntest.Txn{ + txn := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.PaymentTx, Sender: sender.Addr, Receiver: sender.Addr, @@ -298,7 +298,7 @@ func TestWrongAuthorizerTxn(t *testing.T) { expectedError: fmt.Sprintf("should have been authorized by %s but was actually authorized by %s", sender.Addr, authority.Addr), expected: simulation.Result{ Version: simulation.ResultLatestVersion, - LastRound: txnInfo.LatestRound(), + LastRound: env.TxnInfo.LatestRound(), TxnGroups: []simulation.TxnGroupResult{ { Txns: []simulation.TxnResult{{}}, @@ -318,18 +318,18 @@ func TestWrongAuthorizerTxn(t *testing.T) { func TestRekey(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { - sender := accounts[0] - authority := accounts[1] + simulationTest(t, func(env simulationtesting.Environment) simulationTestCase { + sender := env.Accounts[0] + authority := env.Accounts[1] - txn1 := txnInfo.NewTxn(txntest.Txn{ + txn1 := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.PaymentTx, Sender: sender.Addr, Receiver: sender.Addr, Amount: 1, RekeyTo: authority.Addr, }) - txn2 := txnInfo.NewTxn(txntest.Txn{ + txn2 := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.PaymentTx, Sender: sender.Addr, Receiver: sender.Addr, @@ -349,7 +349,7 @@ func TestRekey(t *testing.T) { }, expected: simulation.Result{ Version: simulation.ResultLatestVersion, - LastRound: txnInfo.LatestRound(), + LastRound: env.TxnInfo.LatestRound(), TxnGroups: []simulation.TxnGroupResult{ { Txns: []simulation.TxnResult{ @@ -367,12 +367,12 @@ func TestStateProofTxn(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - l, _, txnInfo := simulationtesting.PrepareSimulatorTest(t) - defer l.Close() - s := simulation.MakeSimulator(l) + env := simulationtesting.PrepareSimulatorTest(t) + defer env.Close() + s := simulation.MakeSimulator(env.Ledger) txgroup := []transactions.SignedTxn{ - txnInfo.NewTxn(txntest.Txn{ + env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.StateProofTx, // No need to fill out StateProofTxnFields, this should fail at signature verification }).SignedTxn(), @@ -386,22 +386,22 @@ func TestSimpleGroupTxn(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - l, accounts, txnInfo := simulationtesting.PrepareSimulatorTest(t) - defer l.Close() - s := simulation.MakeSimulator(l) - sender1 := accounts[0] - sender1Balance := accounts[0].AcctData.MicroAlgos - sender2 := accounts[1] - sender2Balance := accounts[1].AcctData.MicroAlgos + env := simulationtesting.PrepareSimulatorTest(t) + defer env.Close() + s := simulation.MakeSimulator(env.Ledger) + sender1 := env.Accounts[0] + sender1Balance := env.Accounts[0].AcctData.MicroAlgos + sender2 := env.Accounts[1] + sender2Balance := env.Accounts[1].AcctData.MicroAlgos // Send money back and forth - txn1 := txnInfo.NewTxn(txntest.Txn{ + txn1 := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.PaymentTx, Sender: sender1.Addr, Receiver: sender2.Addr, Amount: 1_000_000, }) - txn2 := txnInfo.NewTxn(txntest.Txn{ + txn2 := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.PaymentTx, Sender: sender2.Addr, Receiver: sender1.Addr, @@ -434,11 +434,11 @@ func TestSimpleGroupTxn(t *testing.T) { } // Check balances before transaction - sender1Data, _, err := l.LookupWithoutRewards(l.Latest(), sender1.Addr) + sender1Data, _, err := env.Ledger.LookupWithoutRewards(env.Ledger.Latest(), sender1.Addr) require.NoError(t, err) require.Equal(t, sender1Balance, sender1Data.MicroAlgos) - sender2Data, _, err := l.LookupWithoutRewards(l.Latest(), sender2.Addr) + sender2Data, _, err := env.Ledger.LookupWithoutRewards(env.Ledger.Latest(), sender2.Addr) require.NoError(t, err) require.Equal(t, sender2Balance, sender2Data.MicroAlgos) @@ -450,11 +450,11 @@ func TestSimpleGroupTxn(t *testing.T) { require.Zero(t, result.TxnGroups[0].FailureMessage) // Confirm balances have not changed - sender1Data, _, err = l.LookupWithoutRewards(l.Latest(), sender1.Addr) + sender1Data, _, err = env.Ledger.LookupWithoutRewards(env.Ledger.Latest(), sender1.Addr) require.NoError(t, err) require.Equal(t, sender1Balance, sender1Data.MicroAlgos) - sender2Data, _, err = l.LookupWithoutRewards(l.Latest(), sender2.Addr) + sender2Data, _, err = env.Ledger.LookupWithoutRewards(env.Ledger.Latest(), sender2.Addr) require.NoError(t, err) require.Equal(t, sender2Balance, sender2Data.MicroAlgos) } @@ -500,16 +500,16 @@ btoi`) testCase := testCase t.Run(testCase.name, func(t *testing.T) { t.Parallel() - simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { - sender := accounts[0] + simulationTest(t, func(env simulationtesting.Environment) simulationTestCase { + sender := env.Accounts[0] - payTxn := txnInfo.NewTxn(txntest.Txn{ + payTxn := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.PaymentTx, Sender: sender.Addr, Receiver: lsigAddr, Amount: 1_000_000, }) - appCallTxn := txnInfo.NewTxn(txntest.Txn{ + appCallTxn := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.ApplicationCallTx, Sender: lsigAddr, ApprovalProgram: `#pragma version 8 @@ -554,7 +554,7 @@ int 1`, expectedError: testCase.expectedError, expected: simulation.Result{ Version: simulation.ResultLatestVersion, - LastRound: txnInfo.LatestRound(), + LastRound: env.TxnInfo.LatestRound(), TxnGroups: []simulation.TxnGroupResult{ { Txns: []simulation.TxnResult{ @@ -582,12 +582,12 @@ int 1`, func TestSimpleAppCall(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { - sender := accounts[0] + simulationTest(t, func(env simulationtesting.Environment) simulationTestCase { + sender := env.Accounts[0] // Create program and call it futureAppID := basics.AppIndex(1001) - createTxn := txnInfo.NewTxn(txntest.Txn{ + createTxn := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.ApplicationCallTx, Sender: sender.Addr, ApplicationID: 0, @@ -607,7 +607,7 @@ int 1 int 0 `, }) - callTxn := txnInfo.NewTxn(txntest.Txn{ + callTxn := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.ApplicationCallTx, Sender: sender.Addr, ApplicationID: futureAppID, @@ -626,7 +626,7 @@ int 0 }, expected: simulation.Result{ Version: simulation.ResultLatestVersion, - LastRound: txnInfo.LatestRound(), + LastRound: env.TxnInfo.LatestRound(), TxnGroups: []simulation.TxnGroupResult{ { Txns: []simulation.TxnResult{ @@ -664,11 +664,11 @@ int 0 func TestRejectAppCall(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { - sender := accounts[0] + simulationTest(t, func(env simulationtesting.Environment) simulationTestCase { + sender := env.Accounts[0] futureAppID := basics.AppIndex(1001) - createTxn := txnInfo.NewTxn(txntest.Txn{ + createTxn := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.ApplicationCallTx, Sender: sender.Addr, ApplicationID: 0, @@ -690,7 +690,7 @@ int 0 expectedError: "transaction rejected by ApprovalProgram", expected: simulation.Result{ Version: simulation.ResultLatestVersion, - LastRound: txnInfo.LatestRound(), + LastRound: env.TxnInfo.LatestRound(), TxnGroups: []simulation.TxnGroupResult{ { Txns: []simulation.TxnResult{ @@ -719,11 +719,11 @@ int 0 func TestErrorAppCall(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { - sender := accounts[0] + simulationTest(t, func(env simulationtesting.Environment) simulationTestCase { + sender := env.Accounts[0] futureAppID := basics.AppIndex(1001) - createTxn := txnInfo.NewTxn(txntest.Txn{ + createTxn := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.ApplicationCallTx, Sender: sender.Addr, ApplicationID: 0, @@ -745,7 +745,7 @@ int 0 expectedError: "err opcode executed", expected: simulation.Result{ Version: simulation.ResultLatestVersion, - LastRound: txnInfo.LatestRound(), + LastRound: env.TxnInfo.LatestRound(), TxnGroups: []simulation.TxnGroupResult{ { Txns: []simulation.TxnResult{ @@ -784,12 +784,12 @@ func TestAppCallOverBudget(t *testing.T) { `, 697) + `end: int 1` - simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { - sender := accounts[0] + simulationTest(t, func(env simulationtesting.Environment) simulationTestCase { + sender := env.Accounts[0] futureAppID := basics.AppIndex(1001) // App create with cost 4 - createTxn := txnInfo.NewTxn(txntest.Txn{ + createTxn := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.ApplicationCallTx, Sender: sender.Addr, ApplicationID: 0, @@ -800,7 +800,7 @@ int 0 }) // App call with cost 1398 - will cause a budget exceeded error, // but will only report a cost up to 1396. - expensiveTxn := txnInfo.NewTxn(txntest.Txn{ + expensiveTxn := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.ApplicationCallTx, Sender: sender.Addr, ApplicationID: futureAppID, @@ -820,7 +820,7 @@ int 0 expectedError: "dynamic cost budget exceeded", expected: simulation.Result{ Version: simulation.ResultLatestVersion, - LastRound: txnInfo.LatestRound(), + LastRound: env.TxnInfo.LatestRound(), TxnGroups: []simulation.TxnGroupResult{ { Txns: []simulation.TxnResult{ @@ -857,12 +857,12 @@ func TestAppCallWithExtraBudget(t *testing.T) { ` + strings.Repeat(`int 1; pop;`, 700) + `end: int 1` - simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { - sender := accounts[0] + simulationTest(t, func(env simulationtesting.Environment) simulationTestCase { + sender := env.Accounts[0] futureAppID := basics.AppIndex(1001) // App create with cost 4 - createTxn := txnInfo.NewTxn(txntest.Txn{ + createTxn := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.ApplicationCallTx, Sender: sender.Addr, ApplicationID: 0, @@ -870,7 +870,7 @@ func TestAppCallWithExtraBudget(t *testing.T) { ClearStateProgram: `#pragma version 6; int 0`, }) // Expensive 700 repetition of int 1 and pop total cost 1404 - expensiveTxn := txnInfo.NewTxn(txntest.Txn{ + expensiveTxn := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.ApplicationCallTx, Sender: sender.Addr, ApplicationID: futureAppID, @@ -891,7 +891,7 @@ func TestAppCallWithExtraBudget(t *testing.T) { }, expected: simulation.Result{ Version: simulation.ResultLatestVersion, - LastRound: txnInfo.LatestRound(), + LastRound: env.TxnInfo.LatestRound(), TxnGroups: []simulation.TxnGroupResult{ { Txns: []simulation.TxnResult{ @@ -928,12 +928,12 @@ func TestAppCallWithExtraBudgetOverBudget(t *testing.T) { ` + strings.Repeat(`int 1; pop;`, 700) + `end: int 1` - simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { - sender := accounts[0] + simulationTest(t, func(env simulationtesting.Environment) simulationTestCase { + sender := env.Accounts[0] futureAppID := basics.AppIndex(1001) // App create with cost 4 - createTxn := txnInfo.NewTxn(txntest.Txn{ + createTxn := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.ApplicationCallTx, Sender: sender.Addr, ApplicationID: 0, @@ -941,7 +941,7 @@ func TestAppCallWithExtraBudgetOverBudget(t *testing.T) { ClearStateProgram: `#pragma version 6; int 0`, }) // Expensive 700 repetition of int 1 and pop total cost 1404 - expensiveTxn := txnInfo.NewTxn(txntest.Txn{ + expensiveTxn := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.ApplicationCallTx, Sender: sender.Addr, ApplicationID: futureAppID, @@ -964,7 +964,7 @@ func TestAppCallWithExtraBudgetOverBudget(t *testing.T) { expectedError: "dynamic cost budget exceeded", expected: simulation.Result{ Version: simulation.ResultLatestVersion, - LastRound: txnInfo.LatestRound(), + LastRound: env.TxnInfo.LatestRound(), TxnGroups: []simulation.TxnGroupResult{ { Txns: []simulation.TxnResult{ @@ -1002,15 +1002,15 @@ func TestAppCallWithExtraBudgetExceedsInternalLimit(t *testing.T) { ` + strings.Repeat(`int 1; pop;`, 700) + `end: int 1` - l, accounts, txnInfo := simulationtesting.PrepareSimulatorTest(t) - defer l.Close() - s := simulation.MakeSimulator(l) + env := simulationtesting.PrepareSimulatorTest(t) + defer env.Close() + s := simulation.MakeSimulator(env.Ledger) - sender := accounts[0] + sender := env.Accounts[0] futureAppID := basics.AppIndex(1001) // App create with cost 4 - createTxn := txnInfo.NewTxn(txntest.Txn{ + createTxn := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.ApplicationCallTx, Sender: sender.Addr, ApplicationID: 0, @@ -1018,7 +1018,7 @@ func TestAppCallWithExtraBudgetExceedsInternalLimit(t *testing.T) { ClearStateProgram: `#pragma version 6; int 0`, }) // Expensive 700 repetition of int 1 and pop total cost 1404 - expensiveTxn := txnInfo.NewTxn(txntest.Txn{ + expensiveTxn := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.ApplicationCallTx, Sender: sender.Addr, ApplicationID: futureAppID, @@ -1055,16 +1055,16 @@ pop program := logic.Program(op.Program) lsigAddr := basics.Address(crypto.HashObj(&program)) - simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { - sender := accounts[0] + simulationTest(t, func(env simulationtesting.Environment) simulationTestCase { + sender := env.Accounts[0] - payTxn := txnInfo.NewTxn(txntest.Txn{ + payTxn := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.PaymentTx, Sender: sender.Addr, Receiver: lsigAddr, Amount: 1_000_000, }) - appCallTxn := txnInfo.NewTxn(txntest.Txn{ + appCallTxn := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.ApplicationCallTx, Sender: lsigAddr, ApprovalProgram: `#pragma version 8 @@ -1096,7 +1096,7 @@ int 1`, expectedError: "dynamic cost budget exceeded", expected: simulation.Result{ Version: simulation.ResultLatestVersion, - LastRound: txnInfo.LatestRound(), + LastRound: env.TxnInfo.LatestRound(), TxnGroups: []simulation.TxnGroupResult{ { Txns: []simulation.TxnResult{ @@ -1140,19 +1140,19 @@ itxn_submit pop `, 345)) - simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { - sender := accounts[0] + simulationTest(t, func(env simulationtesting.Environment) simulationTestCase { + sender := env.Accounts[0] futureAppID := basics.AppIndex(1002) // fund outer app - fund := txnInfo.NewTxn(txntest.Txn{ + fund := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.PaymentTx, Sender: sender.Addr, Receiver: futureAppID.Address(), Amount: 401_000, }) // create app - appCall := txnInfo.NewTxn(txntest.Txn{ + appCall := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.ApplicationCallTx, Sender: sender.Addr, ApprovalProgram: exactly700AndCallInner, @@ -1173,7 +1173,7 @@ int 1`, }, expected: simulation.Result{ Version: simulation.ResultLatestVersion, - LastRound: txnInfo.LatestRound(), + LastRound: env.TxnInfo.LatestRound(), TxnGroups: []simulation.TxnGroupResult{ { Txns: []simulation.TxnResult{ @@ -1210,12 +1210,12 @@ func TestDefaultSignatureCheck(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - l, accounts, txnInfo := simulationtesting.PrepareSimulatorTest(t) - defer l.Close() - s := simulation.MakeSimulator(l) - sender := accounts[0] + env := simulationtesting.PrepareSimulatorTest(t) + defer env.Close() + s := simulation.MakeSimulator(env.Ledger) + sender := env.Accounts[0] - stxn := txnInfo.NewTxn(txntest.Txn{ + stxn := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.PaymentTx, Sender: sender.Addr, Receiver: sender.Addr, @@ -1252,10 +1252,10 @@ func TestDefaultSignatureCheck(t *testing.T) { func TestInvalidTxGroup(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { - receiver := accounts[0].Addr + simulationTest(t, func(env simulationtesting.Environment) simulationTestCase { + receiver := env.Accounts[0].Addr - txn := txnInfo.NewTxn(txntest.Txn{ + txn := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.PaymentTx, // should error with invalid transaction group error Sender: ledgertesting.PoolAddr(), @@ -1270,7 +1270,7 @@ func TestInvalidTxGroup(t *testing.T) { expectedError: "transaction from incentive pool is invalid", expected: simulation.Result{ Version: simulation.ResultLatestVersion, - LastRound: txnInfo.LatestRound(), + LastRound: env.TxnInfo.LatestRound(), TxnGroups: []simulation.TxnGroupResult{ { FailedAt: simulation.TxnPath{0}, @@ -1301,13 +1301,13 @@ log `, LogLongLine), LogTimes) + `final: int 1` - simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { - sender := accounts[0] - receiver := accounts[1] + simulationTest(t, func(env simulationtesting.Environment) simulationTestCase { + sender := env.Accounts[0] + receiver := env.Accounts[1] futureAppID := basics.AppIndex(1001) - createTxn := txnInfo.NewTxn(txntest.Txn{ + createTxn := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.ApplicationCallTx, Sender: sender.Addr, ApplicationID: 0, @@ -1315,7 +1315,7 @@ int 1` ClearStateProgram: "#pragma version 8\nint 1", }) - callsABunchLogs := txnInfo.NewTxn(txntest.Txn{ + callsABunchLogs := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.ApplicationCallTx, Sender: sender.Addr, ApplicationID: futureAppID, @@ -1342,7 +1342,7 @@ int 1` }, expected: simulation.Result{ Version: simulation.ResultLatestVersion, - LastRound: txnInfo.LatestRound(), + LastRound: env.TxnInfo.LatestRound(), TxnGroups: []simulation.TxnGroupResult{ { Txns: []simulation.TxnResult{ @@ -1395,13 +1395,13 @@ log `, LogLongLine), LogTimes) + `final: int 1` - simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { - sender := accounts[0] - receiver := accounts[1] + simulationTest(t, func(env simulationtesting.Environment) simulationTestCase { + sender := env.Accounts[0] + receiver := env.Accounts[1] futureAppID := basics.AppIndex(1001) - createTxn := txnInfo.NewTxn(txntest.Txn{ + createTxn := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.ApplicationCallTx, Sender: sender.Addr, ApplicationID: 0, @@ -1409,7 +1409,7 @@ int 1` ClearStateProgram: "#pragma version 8\nint 1", }) - callsABunchLogs := txnInfo.NewTxn(txntest.Txn{ + callsABunchLogs := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.ApplicationCallTx, Sender: sender.Addr, ApplicationID: futureAppID, @@ -1437,7 +1437,7 @@ int 1` }, expected: simulation.Result{ Version: simulation.ResultLatestVersion, - LastRound: txnInfo.LatestRound(), + LastRound: env.TxnInfo.LatestRound(), TxnGroups: []simulation.TxnGroupResult{ { FailedAt: simulation.TxnPath{1}, @@ -1480,15 +1480,15 @@ int 1` func TestBalanceChangesWithApp(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { - sender := accounts[0] + simulationTest(t, func(env simulationtesting.Environment) simulationTestCase { + sender := env.Accounts[0] senderBalance := sender.AcctData.MicroAlgos.Raw sendAmount := senderBalance - 500_000 // Leave 0.5 Algos in the sender account - receiver := accounts[1] + receiver := env.Accounts[1] receiverBalance := receiver.AcctData.MicroAlgos.Raw futureAppID := basics.AppIndex(1001) - createTxn := txnInfo.NewTxn(txntest.Txn{ + createTxn := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.ApplicationCallTx, Sender: sender.Addr, ApprovalProgram: `#pragma version 6 @@ -1506,20 +1506,20 @@ int 1 // [1] ClearStateProgram: `#pragma version 6 int 1`, }) - checkStartingBalanceTxn := txnInfo.NewTxn(txntest.Txn{ + checkStartingBalanceTxn := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.ApplicationCallTx, Sender: sender.Addr, ApplicationID: futureAppID, Accounts: []basics.Address{receiver.Addr}, ApplicationArgs: [][]byte{uint64ToBytes(receiverBalance)}, }) - paymentTxn := txnInfo.NewTxn(txntest.Txn{ + paymentTxn := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.PaymentTx, Sender: sender.Addr, Receiver: receiver.Addr, Amount: sendAmount, }) - checkEndingBalanceTxn := txnInfo.NewTxn(txntest.Txn{ + checkEndingBalanceTxn := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.ApplicationCallTx, Sender: sender.Addr, ApplicationID: futureAppID, @@ -1548,7 +1548,7 @@ int 1`, }, expected: simulation.Result{ Version: simulation.ResultLatestVersion, - LastRound: txnInfo.LatestRound(), + LastRound: env.TxnInfo.LatestRound(), TxnGroups: []simulation.TxnGroupResult{ { Txns: []simulation.TxnResult{ @@ -1585,10 +1585,10 @@ func TestOptionalSignatures(t *testing.T) { for _, signed := range []bool{true, false} { signed := signed t.Run(fmt.Sprintf("signed=%t", signed), func(t *testing.T) { - simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { - sender := accounts[0] + simulationTest(t, func(env simulationtesting.Environment) simulationTestCase { + sender := env.Accounts[0] - txn := txnInfo.NewTxn(txntest.Txn{ + txn := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.PaymentTx, Sender: sender.Addr, Receiver: sender.Addr, @@ -1610,7 +1610,7 @@ func TestOptionalSignatures(t *testing.T) { }, expected: simulation.Result{ Version: simulation.ResultLatestVersion, - LastRound: txnInfo.LatestRound(), + LastRound: env.TxnInfo.LatestRound(), TxnGroups: []simulation.TxnGroupResult{ { Txns: []simulation.TxnResult{{}}, @@ -1632,12 +1632,12 @@ func TestOptionalSignaturesIncorrect(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - l, accounts, txnInfo := simulationtesting.PrepareSimulatorTest(t) - defer l.Close() - s := simulation.MakeSimulator(l) - sender := accounts[0] + env := simulationtesting.PrepareSimulatorTest(t) + defer env.Close() + s := simulation.MakeSimulator(env.Ledger) + sender := env.Accounts[0] - stxn := txnInfo.NewTxn(txntest.Txn{ + stxn := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.PaymentTx, Sender: sender.Addr, Receiver: sender.Addr, @@ -1656,10 +1656,10 @@ func TestOptionalSignaturesIncorrect(t *testing.T) { func TestPartialMissingSignatures(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { - sender := accounts[0] + simulationTest(t, func(env simulationtesting.Environment) simulationTestCase { + sender := env.Accounts[0] - txn1 := txnInfo.NewTxn(txntest.Txn{ + txn1 := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.AssetConfigTx, Sender: sender.Addr, AssetParams: basics.AssetParams{ @@ -1669,7 +1669,7 @@ func TestPartialMissingSignatures(t *testing.T) { UnitName: "A", }, }) - txn2 := txnInfo.NewTxn(txntest.Txn{ + txn2 := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.AssetConfigTx, Sender: sender.Addr, AssetParams: basics.AssetParams{ @@ -1695,7 +1695,7 @@ func TestPartialMissingSignatures(t *testing.T) { }, expected: simulation.Result{ Version: simulation.ResultLatestVersion, - LastRound: txnInfo.LatestRound(), + LastRound: env.TxnInfo.LatestRound(), TxnGroups: []simulation.TxnGroupResult{ { Txns: []simulation.TxnResult{ @@ -1732,23 +1732,23 @@ func TestPartialMissingSignatures(t *testing.T) { func TestPooledFeesAcrossSignedAndUnsigned(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { - sender1 := accounts[0] - sender2 := accounts[1] + simulationTest(t, func(env simulationtesting.Environment) simulationTestCase { + sender1 := env.Accounts[0] + sender2 := env.Accounts[1] - pay1 := txnInfo.NewTxn(txntest.Txn{ + pay1 := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.PaymentTx, Sender: sender1.Addr, Receiver: sender2.Addr, Amount: 1_000_000, - Fee: txnInfo.CurrentProtocolParams().MinTxnFee - 100, + Fee: env.TxnInfo.CurrentProtocolParams().MinTxnFee - 100, }) - pay2 := txnInfo.NewTxn(txntest.Txn{ + pay2 := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.PaymentTx, Sender: sender2.Addr, Receiver: sender1.Addr, Amount: 0, - Fee: txnInfo.CurrentProtocolParams().MinTxnFee + 100, + Fee: env.TxnInfo.CurrentProtocolParams().MinTxnFee + 100, }) txntest.Group(&pay1, &pay2) @@ -1766,7 +1766,7 @@ func TestPooledFeesAcrossSignedAndUnsigned(t *testing.T) { }, expected: simulation.Result{ Version: simulation.ResultLatestVersion, - LastRound: txnInfo.LatestRound(), + LastRound: env.TxnInfo.LatestRound(), TxnGroups: []simulation.TxnGroupResult{ { Txns: []simulation.TxnResult{ @@ -1830,8 +1830,8 @@ func TestAppCallInnerTxnApplyDataOnFail(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { - sender := accounts[0] + simulationTest(t, func(env simulationtesting.Environment) simulationTestCase { + sender := env.Accounts[0] singleInnerLogAndFail := makeProgramToCallInner(t, logAndFail) nestedInnerLogAndFail := makeProgramToCallInner(t, singleInnerLogAndFail) @@ -1840,21 +1840,21 @@ func TestAppCallInnerTxnApplyDataOnFail(t *testing.T) { futureInnerAppID := futureOuterAppID + 1 // fund outer app - pay1 := txnInfo.NewTxn(txntest.Txn{ + pay1 := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.PaymentTx, Sender: sender.Addr, Receiver: futureOuterAppID.Address(), Amount: 401_000, // 400_000 min balance plus 1_000 for 1 txn }) // fund inner app - pay2 := txnInfo.NewTxn(txntest.Txn{ + pay2 := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.PaymentTx, Sender: sender.Addr, Receiver: futureInnerAppID.Address(), Amount: 401_000, // 400_000 min balance plus 1_000 for 1 txn }) // create app - appCall := txnInfo.NewTxn(txntest.Txn{ + appCall := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.ApplicationCallTx, Sender: sender.Addr, ApplicationArgs: [][]byte{uint64ToBytes(uint64(1))}, @@ -1876,7 +1876,7 @@ int 1`, expectedError: "rejected by ApprovalProgram", expected: simulation.Result{ Version: simulation.ResultLatestVersion, - LastRound: txnInfo.LatestRound(), + LastRound: env.TxnInfo.LatestRound(), TxnGroups: []simulation.TxnGroupResult{ { Txns: []simulation.TxnResult{ @@ -1940,8 +1940,8 @@ func TestNonAppCallInnerTxnApplyDataOnFail(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { - sender := accounts[0] + simulationTest(t, func(env simulationtesting.Environment) simulationTestCase { + sender := env.Accounts[0] logAndFailItxnCode := makeItxnSubmitToCallInner(t, logAndFail) approvalProgram := wrapCodeWithVersionAndReturn(createAssetCode + logAndFailItxnCode) @@ -1949,14 +1949,14 @@ func TestNonAppCallInnerTxnApplyDataOnFail(t *testing.T) { futureAppID := basics.AppIndex(1002) // fund outer app - pay1 := txnInfo.NewTxn(txntest.Txn{ + pay1 := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.PaymentTx, Sender: sender.Addr, Receiver: futureAppID.Address(), Amount: 401_000, // 400_000 min balance plus 1_000 for 1 txn }) // create app - appCall := txnInfo.NewTxn(txntest.Txn{ + appCall := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.ApplicationCallTx, Sender: sender.Addr, ApplicationArgs: [][]byte{uint64ToBytes(uint64(1))}, @@ -1978,7 +1978,7 @@ int 1`, expectedError: "rejected by ApprovalProgram", expected: simulation.Result{ Version: simulation.ResultLatestVersion, - LastRound: txnInfo.LatestRound(), + LastRound: env.TxnInfo.LatestRound(), TxnGroups: []simulation.TxnGroupResult{ { Txns: []simulation.TxnResult{ @@ -2037,8 +2037,8 @@ log func TestInnerTxnNonAppCallFailure(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { - sender := accounts[0] + simulationTest(t, func(env simulationtesting.Environment) simulationTestCase { + sender := env.Accounts[0] futureAppID := basics.AppIndex(1002) futureAssetID := basics.AssetIndex(1003) @@ -2047,14 +2047,14 @@ func TestInnerTxnNonAppCallFailure(t *testing.T) { approvalProgram := wrapCodeWithVersionAndReturn(createAssetCode + fmt.Sprintf(configAssetCode, futureAssetID)) // fund outer app - pay1 := txnInfo.NewTxn(txntest.Txn{ + pay1 := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.PaymentTx, Sender: sender.Addr, Receiver: futureAppID.Address(), Amount: 402_000, // 400_000 min balance plus 2_000 for 2 inners }) // create app - appCall := txnInfo.NewTxn(txntest.Txn{ + appCall := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.ApplicationCallTx, Sender: sender.Addr, ApplicationArgs: [][]byte{uint64ToBytes(uint64(1))}, @@ -2076,7 +2076,7 @@ int 1`, expectedError: "logic eval error: this transaction should be issued by the manager", expected: simulation.Result{ Version: simulation.ResultLatestVersion, - LastRound: txnInfo.LatestRound(), + LastRound: env.TxnInfo.LatestRound(), TxnGroups: []simulation.TxnGroupResult{ { Txns: []simulation.TxnResult{ @@ -2121,17 +2121,17 @@ func TestMockTracerScenarios(t *testing.T) { scenarioFn := scenarioFn t.Run(name, func(t *testing.T) { t.Parallel() - simulationTest(t, func(accounts []simulationtesting.Account, txnInfo simulationtesting.TxnInfo) simulationTestCase { - sender := accounts[0] + simulationTest(t, func(env simulationtesting.Environment) simulationTestCase { + sender := env.Accounts[0] futureAppID := basics.AppIndex(1002) - payTxn := txnInfo.NewTxn(txntest.Txn{ + payTxn := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.PaymentTx, Sender: sender.Addr, Receiver: futureAppID.Address(), Amount: 2_000_000, }) - appCallTxn := txnInfo.NewTxn(txntest.Txn{ + appCallTxn := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.ApplicationCallTx, Sender: sender.Addr, ClearStateProgram: `#pragma version 6 @@ -2139,7 +2139,7 @@ func TestMockTracerScenarios(t *testing.T) { }) scenario := scenarioFn(mocktracer.TestScenarioInfo{ CallingTxn: appCallTxn.Txn(), - MinFee: basics.MicroAlgos{Raw: txnInfo.CurrentProtocolParams().MinTxnFee}, + MinFee: basics.MicroAlgos{Raw: env.TxnInfo.CurrentProtocolParams().MinTxnFee}, CreatedAppID: futureAppID, }) appCallTxn.ApprovalProgram = scenario.Program @@ -2156,7 +2156,7 @@ func TestMockTracerScenarios(t *testing.T) { } expected := simulation.Result{ Version: simulation.ResultLatestVersion, - LastRound: txnInfo.LatestRound(), + LastRound: env.TxnInfo.LatestRound(), TxnGroups: []simulation.TxnGroupResult{ { AppBudgetAdded: scenario.AppBudgetAdded, diff --git a/ledger/simulation/simulator_test.go b/ledger/simulation/simulator_test.go index fea209906a..90eada3f42 100644 --- a/ledger/simulation/simulator_test.go +++ b/ledger/simulation/simulator_test.go @@ -22,11 +22,13 @@ import ( "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/data/bookkeeping" "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/data/transactions/logic" "github.com/algorand/go-algorand/data/transactions/logic/mocktracer" "github.com/algorand/go-algorand/data/txntest" "github.com/algorand/go-algorand/ledger/eval" + "github.com/algorand/go-algorand/ledger/ledgercore" simulationtesting "github.com/algorand/go-algorand/ledger/simulation/testing" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" @@ -40,7 +42,7 @@ func TestNonOverridenDataLedgerMethodsUseRoundParameter(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - l, _, _ := simulationtesting.PrepareSimulatorTest(t) + env := simulationtesting.PrepareSimulatorTest(t) // methods overriden by `simulatorLedger`` overridenMethods := []string{ @@ -89,7 +91,7 @@ func TestNonOverridenDataLedgerMethodsUseRoundParameter(t *testing.T) { return false } - ledgerType := reflect.TypeOf(l) + ledgerType := reflect.TypeOf(env.Ledger) for i := 0; i < ledgerType.NumMethod(); i++ { method := ledgerType.Method(i) if methodExistsInEvalLedger(method.Name) && !methodIsSkipped(method.Name) { @@ -104,10 +106,10 @@ func TestSimulateWithTrace(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - l, accounts, txnInfo := simulationtesting.PrepareSimulatorTest(t) - defer l.Close() - s := MakeSimulator(l) - sender := accounts[0] + env := simulationtesting.PrepareSimulatorTest(t) + defer env.Close() + s := MakeSimulator(env.Ledger) + sender := env.Accounts[0] op, err := logic.AssembleString(`#pragma version 8 int 1`) @@ -115,13 +117,13 @@ int 1`) program := logic.Program(op.Program) lsigAddr := basics.Address(crypto.HashObj(&program)) - payTxn := txnInfo.NewTxn(txntest.Txn{ + payTxn := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.PaymentTx, Sender: sender.Addr, Receiver: lsigAddr, Amount: 1_000_000, }) - appCallTxn := txnInfo.NewTxn(txntest.Txn{ + appCallTxn := env.TxnInfo.NewTxn(txntest.Txn{ Type: protocol.ApplicationCallTx, Sender: lsigAddr, ApprovalProgram: `#pragma version 8 @@ -142,8 +144,77 @@ int 1`, block, err := s.simulateWithTracer(txgroup, mockTracer, ResultEvalOverrides{}) require.NoError(t, err) - payset := block.Block().Payset - require.Len(t, payset, 2) + evalBlock := block.Block() + require.Len(t, evalBlock.Payset, 2) + + expectedSenderData := ledgercore.ToAccountData(sender.AcctData) + expectedSenderData.MicroAlgos.Raw -= signedPayTxn.Txn.Amount.Raw + signedPayTxn.Txn.Fee.Raw + expectedLsigData := ledgercore.AccountData{} + expectedLsigData.MicroAlgos.Raw += signedPayTxn.Txn.Amount.Raw - signedAppCallTxn.Txn.Fee.Raw + expectedLsigData.TotalAppParams = 1 + expectedFeeSinkData := ledgercore.ToAccountData(env.FeeSinkAccount.AcctData) + expectedFeeSinkData.MicroAlgos.Raw += signedPayTxn.Txn.Fee.Raw + signedAppCallTxn.Txn.Fee.Raw + + expectedAppID := evalBlock.Payset[1].ApplyData.ApplicationID + expectedAppParams := ledgercore.AppParamsDelta{ + Params: &basics.AppParams{ + ApprovalProgram: signedAppCallTxn.Txn.ApprovalProgram, + ClearStateProgram: signedAppCallTxn.Txn.ClearStateProgram, + }, + } + + // Cannot use evalBlock directly because the tracer is called before many block details are finalized + expectedBlockHeader := bookkeeping.MakeBlock(env.TxnInfo.LatestHeader).BlockHeader + expectedBlockHeader.TimeStamp = evalBlock.TimeStamp + expectedBlockHeader.RewardsLevel = evalBlock.RewardsLevel + expectedBlockHeader.RewardsResidue = evalBlock.RewardsResidue + expectedBlockHeader.RewardsRate = evalBlock.RewardsRate + expectedBlockHeader.RewardsRecalculationRound = evalBlock.RewardsRecalculationRound + + expectedDelta := ledgercore.StateDelta{ + Accts: ledgercore.AccountDeltas{ + Accts: []ledgercore.BalanceRecord{ + { + Addr: sender.Addr, + AccountData: expectedSenderData, + }, + { + Addr: env.FeeSinkAccount.Addr, + AccountData: expectedFeeSinkData, + }, + { + Addr: lsigAddr, + AccountData: expectedLsigData, + }, + }, + AppResources: []ledgercore.AppResourceRecord{ + { + Aidx: expectedAppID, + Addr: lsigAddr, + Params: expectedAppParams, + }, + }, + }, + Creatables: map[basics.CreatableIndex]ledgercore.ModifiedCreatable{ + basics.CreatableIndex(expectedAppID): { + Ctype: basics.AppCreatable, + Created: true, + Creator: lsigAddr, + }, + }, + Txids: map[transactions.Txid]ledgercore.IncludedTransactions{ + signedPayTxn.Txn.ID(): { + LastValid: signedPayTxn.Txn.LastValid, + Intra: 0, + }, + signedAppCallTxn.Txn.ID(): { + LastValid: signedAppCallTxn.Txn.LastValid, + Intra: 1, + }, + }, + Hdr: &expectedBlockHeader, + PrevTimestamp: env.TxnInfo.LatestHeader.TimeStamp, + } expectedEvents := []mocktracer.Event{ // LogicSig evaluation @@ -155,16 +226,16 @@ int 1`, mocktracer.BeforeBlock(block.Block().Round()), mocktracer.BeforeTxnGroup(2), mocktracer.BeforeTxn(protocol.PaymentTx), - mocktracer.AfterTxn(protocol.PaymentTx, payset[0].ApplyData, false), + mocktracer.AfterTxn(protocol.PaymentTx, evalBlock.Payset[0].ApplyData, false), mocktracer.BeforeTxn(protocol.ApplicationCallTx), mocktracer.BeforeProgram(logic.ModeApp), mocktracer.BeforeOpcode(), mocktracer.AfterOpcode(false), mocktracer.AfterProgram(logic.ModeApp, false), - mocktracer.AfterTxn(protocol.ApplicationCallTx, payset[1].ApplyData, false), - mocktracer.AfterTxnGroup(2, false), + mocktracer.AfterTxn(protocol.ApplicationCallTx, evalBlock.Payset[1].ApplyData, false), + mocktracer.AfterTxnGroup(2, &expectedDelta, false), //Block evaluation mocktracer.AfterBlock(block.Block().Round()), } - require.Equal(t, expectedEvents, mockTracer.Events) + mocktracer.AssertEventsEqual(t, expectedEvents, mockTracer.Events) } diff --git a/ledger/simulation/testing/utils.go b/ledger/simulation/testing/utils.go index 2e33c94117..0ff0d529fc 100644 --- a/ledger/simulation/testing/utils.go +++ b/ledger/simulation/testing/utils.go @@ -76,8 +76,24 @@ func (info TxnInfo) InnerTxn(parent transactions.SignedTxn, inner txntest.Txn) t return inner } -// PrepareSimulatorTest creates an environment to test transaction simulations -func PrepareSimulatorTest(t *testing.T) (l *data.Ledger, accounts []Account, txnInfo TxnInfo) { +// Environment contains the ledger and testing environment for transaction simulations +type Environment struct { + Ledger *data.Ledger + // Accounts is a list of all accounts in the ledger, excluding the fee sink and rewards pool + Accounts []Account + FeeSinkAccount Account + RewardsPoolAccount Account + TxnInfo TxnInfo +} + +// Close reclaims resources used by the testing environment +func (env *Environment) Close() { + env.Ledger.Close() +} + +// PrepareSimulatorTest creates an environment to test transaction simulations. The caller is +// responsible for calling Close() on the returned environment. +func PrepareSimulatorTest(t *testing.T) Environment { genesisInitState, keys := ledgertesting.GenerateInitState(t, protocol.ConsensusFuture, 100) // Prepare ledger @@ -89,28 +105,33 @@ func PrepareSimulatorTest(t *testing.T) (l *data.Ledger, accounts []Account, txn realLedger, err := ledger.OpenLedger(log, t.Name(), inMem, genesisInitState, cfg) require.NoError(t, err, "could not open ledger") - l = &data.Ledger{Ledger: realLedger} - require.NotNil(t, l) + ledger := &data.Ledger{Ledger: realLedger} // Reformat accounts - accounts = make([]Account, len(keys)-2) // -2 for pool and sink accounts - i := 0 + accounts := make([]Account, 0, len(keys)-2) // -2 for pool and sink accounts + var feeSinkAccount Account + var rewardsPoolAccount Account for addr, key := range keys { - if addr == ledgertesting.PoolAddr() || addr == ledgertesting.SinkAddr() { - continue - } - - acctData := genesisInitState.Accounts[addr] - accounts[i] = Account{ + account := Account{ Addr: addr, Sk: key, - AcctData: acctData, + AcctData: genesisInitState.Accounts[addr], } - i++ + + if addr == ledgertesting.SinkAddr() { + feeSinkAccount = account + continue + } + if addr == ledgertesting.PoolAddr() { + rewardsPoolAccount = account + continue + } + + accounts = append(accounts, account) } - latest := l.Latest() - latestHeader, err := l.BlockHdr(latest) + latest := ledger.Latest() + latestHeader, err := ledger.BlockHdr(latest) require.NoError(t, err) rand.Seed(time.Now().UnixNano()) @@ -120,17 +141,21 @@ func PrepareSimulatorTest(t *testing.T) (l *data.Ledger, accounts []Account, txn for i := 0; i < numBlocks; i++ { nextBlock := bookkeeping.MakeBlock(latestHeader) nextBlock.TxnCounter = latestHeader.TxnCounter - err = l.AddBlock(nextBlock, agreement.Certificate{}) + err = ledger.AddBlock(nextBlock, agreement.Certificate{}) require.NoError(t, err) // round has advanced by 1 - require.Equal(t, latest+1, l.Latest()) + require.Equal(t, latest+1, ledger.Latest()) latest++ latestHeader = nextBlock.BlockHeader } - txnInfo = TxnInfo{latestHeader} - - return + return Environment{ + Ledger: ledger, + Accounts: accounts, + FeeSinkAccount: feeSinkAccount, + RewardsPoolAccount: rewardsPoolAccount, + TxnInfo: TxnInfo{latestHeader}, + } } diff --git a/ledger/simulation/trace.go b/ledger/simulation/trace.go index 7e18cee882..1c112928cb 100644 --- a/ledger/simulation/trace.go +++ b/ledger/simulation/trace.go @@ -39,15 +39,14 @@ type TxnResult struct { // TxnGroupResult contains the simulation result for a single transaction group type TxnGroupResult struct { - Txns []TxnResult + Txns []TxnResult + // FailureMessage will be the error message for the first transaction in the group which errors. + // If the group succeeds, this will be empty. FailureMessage string - // FailedAt is the path to the txn that failed inside of this group FailedAt TxnPath - // AppBudgetAdded is the total opcode budget for this group AppBudgetAdded uint64 - // AppBudgetConsumed is the total opcode cost used for this group AppBudgetConsumed uint64 } diff --git a/ledger/simulation/tracer_test.go b/ledger/simulation/tracer_test.go index 06a31fc94b..190739e73c 100644 --- a/ledger/simulation/tracer_test.go +++ b/ledger/simulation/tracer_test.go @@ -17,7 +17,6 @@ package simulation import ( - "fmt" "testing" "github.com/algorand/go-algorand/data/transactions" @@ -219,7 +218,7 @@ func TestCursorEvalTracer(t *testing.T) { for _, tc := range testCases { tc := tc - t.Run(fmt.Sprintf("%s", tc.name), func(t *testing.T) { + t.Run(tc.name, func(t *testing.T) { t.Parallel() var tracer cursorEvalTracer From 0152eae0ea2688955d41a70cefffa40cf0347ca3 Mon Sep 17 00:00:00 2001 From: algoidan <79864820+algoidan@users.noreply.github.com> Date: Mon, 15 May 2023 18:34:31 +0300 Subject: [PATCH 095/125] tests: fix flaky stateproofs ledger tests (#5379) --- ledger/ledger_test.go | 99 +++++++++++++++++-------------------------- 1 file changed, 38 insertions(+), 61 deletions(-) diff --git a/ledger/ledger_test.go b/ledger/ledger_test.go index ebe4b26a18..9e89e82ce9 100644 --- a/ledger/ledger_test.go +++ b/ledger/ledger_test.go @@ -1490,17 +1490,18 @@ func triggerTrackerFlush(t *testing.T, l *Ledger, genesisInitState ledgercore.In l.trackers.lastFlushTime = time.Time{} l.trackers.mu.Unlock() - addEmptyValidatedBlock(t, l, genesisInitState.Accounts) - - const timeout = 2 * time.Second + const timeout = 3 * time.Second started := time.Now() // We can't truly wait for scheduleCommit to take place, which means without waiting using sleeps // we might beat scheduleCommit's addition to accountsWriting, making our wait on it continue immediately. - // The solution is to wait for the advancement of l.trackers.dbRound, which is a side effect of postCommit's success. + // The solution is to continue to add blocks and wait for the advancement of l.trackers.dbRound, + // which is a side effect of postCommit's success. for currentDbRound == initialDbRound { time.Sleep(50 * time.Microsecond) require.True(t, time.Since(started) < timeout) + addEmptyValidatedBlock(t, l, genesisInitState.Accounts) + l.WaitForCommit(l.Latest()) l.trackers.mu.RLock() currentDbRound = l.trackers.dbRound l.trackers.mu.RUnlock() @@ -1761,6 +1762,11 @@ func TestLedgerVerifiesOldStateProofs(t *testing.T) { } triggerTrackerFlush(t, l, genesisInitState) + addDummyBlock(t, addresses, proto, l, initKeys, genesisInitState) + l.WaitForCommit(l.Latest()) + // At this point the block queue go-routine will start removing block . However, it might not complete the task + // for that reason, we wait for the next block to be committed. + addDummyBlock(t, addresses, proto, l, initKeys, genesisInitState) l.WaitForCommit(l.Latest()) // we make sure that the voters header does not exist and that the voters tracker @@ -2866,7 +2872,7 @@ func testVotersReloadFromDisk(t *testing.T, cfg config.Local) { proto := config.Consensus[protocol.ConsensusCurrentVersion] dbName := fmt.Sprintf("%s.%d", t.Name(), crypto.RandUint64()) - genesisInitState := getInitState() + genesisInitState, _ := ledgertesting.GenerateInitState(t, protocol.ConsensusFuture, 100) genesisInitState.Block.CurrentProtocol = protocol.ConsensusCurrentVersion const inMem = true @@ -2876,24 +2882,15 @@ func testVotersReloadFromDisk(t *testing.T, cfg config.Local) { require.NoError(t, err) defer l.Close() - blk := genesisInitState.Block - var sp bookkeeping.StateProofTrackingData - sp.StateProofNextRound = basics.Round(proto.StateProofInterval * 2) - blk.BlockHeader.StateProofTracking = map[protocol.StateProofType]bookkeeping.StateProofTrackingData{ - protocol.StateProofBasic: sp, - } - // we add blocks to the ledger to test reload from disk. we would like the history of the acctonline to extend. // but we don't want to go behind stateproof recovery interval for i := uint64(0); i < (proto.StateProofInterval*(proto.StateProofMaxRecoveryIntervals-2) - proto.StateProofVotersLookback); i++ { - blk.BlockHeader.Round++ - blk.BlockHeader.TimeStamp += 10 - err = l.AddBlock(blk, agreement.Certificate{}) - require.NoError(t, err) + addEmptyValidatedBlock(t, l, genesisInitState.Accounts) } // at this point the database should contain the voter for round 256 but the voters for round 512 should be in deltas - l.WaitForCommit(blk.BlockHeader.Round) + l.WaitForCommit(l.Latest()) + triggerTrackerFlush(t, l, genesisInitState) vtSnapshot := l.acctsOnline.voters.votersForRoundCache // ensuring no tree was evicted. @@ -3013,7 +3010,9 @@ func testVotersReloadFromDiskPassRecoveryPeriod(t *testing.T, cfg config.Local) } // the voters tracker should contain all the voters for each stateproof round. nothing should be removed - l.WaitForCommit(blk.BlockHeader.Round) + l.WaitForCommit(l.Latest()) + triggerTrackerFlush(t, l, genesisInitState) + vtSnapshot := l.acctsOnline.voters.votersForRoundCache beforeRemoveVotersLen := len(vtSnapshot) err = l.reloadLedger() @@ -3108,7 +3107,10 @@ func TestLedgerContinuesOnVotersCallbackFailure(t *testing.T) { previousCachedDbRound := l.trackers.dbRound triggerTrackerFlush(t, l, genesisInitState) - require.Equal(t, previousCachedDbRound+1, l.trackers.dbRound) + l.trackers.mu.Lock() + newDbRound := l.trackers.dbRound + l.trackers.mu.Unlock() + require.Equal(t, previousCachedDbRound+1, newDbRound) } func TestLedgerSPVerificationTracker(t *testing.T) { @@ -3116,7 +3118,7 @@ func TestLedgerSPVerificationTracker(t *testing.T) { proto := config.Consensus[protocol.ConsensusCurrentVersion] dbName := fmt.Sprintf("%s.%d", t.Name(), crypto.RandUint64()) - genesisInitState := getInitState() + genesisInitState, _ := ledgertesting.GenerateInitState(t, protocol.ConsensusCurrentVersion, 100) genesisInitState.Block.CurrentProtocol = protocol.ConsensusCurrentVersion const inMem = true cfg := config.GetDefaultLocal() @@ -3134,56 +3136,43 @@ func TestLedgerSPVerificationTracker(t *testing.T) { lastStateProofContextConfirmedRound := firstStateProofContextConfirmedRound + proto.StateProofInterval*(numOfStateProofs-1) lastStateProofContextTargetRound := lastStateProofContextConfirmedRound + proto.StateProofInterval - blk := genesisInitState.Block - var sp bookkeeping.StateProofTrackingData - sp.StateProofNextRound = basics.Round(firstStateProofContextTargetRound) - blk.BlockHeader.StateProofTracking = map[protocol.StateProofType]bookkeeping.StateProofTrackingData{ - protocol.StateProofBasic: sp, - } - for i := uint64(0); i < firstStateProofContextConfirmedRound-1; i++ { - blk.BlockHeader.Round++ - blk.BlockHeader.TimeStamp += 10 - err = l.AddBlock(blk, agreement.Certificate{}) - require.NoError(t, err) + addEmptyValidatedBlock(t, l, genesisInitState.Accounts) } verifyStateProofVerificationTracking(t, &l.spVerification, basics.Round(firstStateProofContextTargetRound), 1, proto.StateProofInterval, false, any) - blk.BlockHeader.Round++ - blk.BlockHeader.TimeStamp += 10 - err = l.AddBlock(blk, agreement.Certificate{}) - require.NoError(t, err) + addEmptyValidatedBlock(t, l, genesisInitState.Accounts) verifyStateProofVerificationTracking(t, &l.spVerification, basics.Round(firstStateProofContextTargetRound), 1, proto.StateProofInterval, true, trackerMemory) for i := firstStateProofContextConfirmedRound; i < lastStateProofContextConfirmedRound; i++ { - blk.BlockHeader.Round++ - blk.BlockHeader.TimeStamp += 10 - err = l.AddBlock(blk, agreement.Certificate{}) - require.NoError(t, err) + addEmptyValidatedBlock(t, l, genesisInitState.Accounts) } + l.WaitForCommit(l.Latest()) + triggerTrackerFlush(t, l, genesisInitState) + verifyStateProofVerificationTracking(t, &l.spVerification, basics.Round(firstStateProofContextTargetRound), numOfStateProofs-1, proto.StateProofInterval, true, trackerDB) // Last one should be in memory as a result of cfg.MaxAcctLookback not being equal to 0. verifyStateProofVerificationTracking(t, &l.spVerification, basics.Round(lastStateProofContextTargetRound), 1, proto.StateProofInterval, true, trackerMemory) - l.WaitForCommit(blk.BlockHeader.Round) + l.WaitForCommit(l.Latest()) + triggerTrackerFlush(t, l, genesisInitState) verifyStateProofVerificationTracking(t, &l.spVerification, basics.Round(firstStateProofContextTargetRound), numOfStateProofs, proto.StateProofInterval, true, any) + blk := makeNewEmptyBlock(t, l, t.Name(), genesisInitState.Accounts) var stateProofReceived bookkeeping.StateProofTrackingData stateProofReceived.StateProofNextRound = basics.Round(firstStateProofContextTargetRound + proto.StateProofInterval) blk.BlockHeader.StateProofTracking = map[protocol.StateProofType]bookkeeping.StateProofTrackingData{ protocol.StateProofBasic: stateProofReceived, } - blk.BlockHeader.Round++ - blk.BlockHeader.TimeStamp += 10 // This implementation is an easy way to feed the delta, which the state proof verification tracker relies on, // to the ledger. @@ -3195,13 +3184,11 @@ func TestLedgerSPVerificationTracker(t *testing.T) { require.NoError(t, err) for i := uint64(0); i < proto.MaxBalLookback; i++ { - blk.BlockHeader.Round++ - blk.BlockHeader.TimeStamp += 10 - err = l.AddBlock(blk, agreement.Certificate{}) - require.NoError(t, err) + addEmptyValidatedBlock(t, l, genesisInitState.Accounts) } l.WaitForCommit(blk.BlockHeader.Round) + triggerTrackerFlush(t, l, genesisInitState) verifyStateProofVerificationTracking(t, &l.spVerification, basics.Round(firstStateProofContextTargetRound), 1, proto.StateProofInterval, false, any) @@ -3214,7 +3201,7 @@ func TestLedgerReloadStateProofVerificationTracker(t *testing.T) { proto := config.Consensus[protocol.ConsensusCurrentVersion] dbName := fmt.Sprintf("%s.%d", t.Name(), crypto.RandUint64()) - genesisInitState := getInitState() + genesisInitState, _ := ledgertesting.GenerateInitState(t, protocol.ConsensusCurrentVersion, 100) genesisInitState.Block.CurrentProtocol = protocol.ConsensusCurrentVersion const inMem = true cfg := config.GetDefaultLocal() @@ -3232,21 +3219,11 @@ func TestLedgerReloadStateProofVerificationTracker(t *testing.T) { lastStateProofContextConfirmedRound := firstStateProofContextConfirmedRound + proto.StateProofInterval*(numOfStateProofs-1) lastStateProofContextTargetRound := lastStateProofContextConfirmedRound + proto.StateProofInterval - blk := genesisInitState.Block - var sp bookkeeping.StateProofTrackingData - sp.StateProofNextRound = basics.Round(firstStateProofContextTargetRound) - blk.BlockHeader.StateProofTracking = map[protocol.StateProofType]bookkeeping.StateProofTrackingData{ - protocol.StateProofBasic: sp, - } - for i := uint64(0); i < lastStateProofContextConfirmedRound; i++ { - blk.BlockHeader.Round++ - blk.BlockHeader.TimeStamp += 10 - err = l.AddBlock(blk, agreement.Certificate{}) - require.NoError(t, err) + addEmptyValidatedBlock(t, l, genesisInitState.Accounts) } - l.WaitForCommit(blk.BlockHeader.Round) + triggerTrackerFlush(t, l, genesisInitState) verifyStateProofVerificationTracking(t, &l.spVerification, basics.Round(firstStateProofContextTargetRound), numOfStateProofs-1, proto.StateProofInterval, true, trackerDB) @@ -3265,7 +3242,6 @@ func TestLedgerReloadStateProofVerificationTracker(t *testing.T) { func feedBlocksUntilRound(t *testing.T, l *Ledger, prevBlk bookkeeping.Block, targetRound basics.Round) bookkeeping.Block { for prevBlk.Round() < targetRound { prevBlk.BlockHeader.Round++ - prevBlk.BlockHeader.TimeStamp += 10 err := l.AddBlock(prevBlk, agreement.Certificate{}) require.NoError(t, err) } @@ -3304,6 +3280,7 @@ func TestLedgerCatchpointSPVerificationTracker(t *testing.T) { // Feeding blocks until we can know for sure we have at least one catchpoint written. blk = feedBlocksUntilRound(t, l, blk, basics.Round(cfg.CatchpointInterval*2)) l.WaitForCommit(basics.Round(cfg.CatchpointInterval * 2)) + triggerTrackerFlush(t, l, genesisInitState) numTrackedDataFirstCatchpoint := (cfg.CatchpointInterval - proto.MaxBalLookback) / proto.StateProofInterval @@ -3377,7 +3354,7 @@ func TestLedgerSPTrackerAfterReplay(t *testing.T) { // To be deleted, but not yet deleted (waiting for commit) verifyStateProofVerificationTracking(t, &l.spVerification, firstStateProofRound, 1, proto.StateProofInterval, true, any) - l.WaitForCommit(l.Latest()) + triggerTrackerFlush(t, l, genesisInitState) err = l.reloadLedger() a.NoError(err) From cee1c810f4e4789bcbe5c5b6c4f6f7aa7be0dd10 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Mon, 15 May 2023 20:03:24 -0500 Subject: [PATCH 096/125] tests: reduce flakiness in TestSyncRoundWithRemake (#5367) --- node/follower_node_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/node/follower_node_test.go b/node/follower_node_test.go index 83b30364e5..489846083f 100644 --- a/node/follower_node_test.go +++ b/node/follower_node_test.go @@ -157,7 +157,7 @@ func TestSyncRoundWithRemake(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - maxAcctLookback := uint64(100) + maxAcctLookback := uint64(10) followNode, tempDir := remakeableFollowNode(t, "", maxAcctLookback) addBlock := func(round basics.Round) { @@ -184,7 +184,7 @@ func TestSyncRoundWithRemake(t *testing.T) { // Part I. redo TestSyncRound // main differences are: // * cfg.DisableNetworking = true - // * cfg.MaxAcctLookback = 100 (instead of 4) + // * cfg.MaxAcctLookback = 10 (instead of 4) addBlock(basics.Round(1)) From 1425f3fd1a59265784429da7a0c3946de4361cca Mon Sep 17 00:00:00 2001 From: John Lee Date: Tue, 16 May 2023 14:06:30 -0400 Subject: [PATCH 097/125] DevOps: Update CODEOWNERS to only refer to the devops group (#5389) --- CODEOWNERS | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index aa26c82a49..3c88c6e71e 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1,2 +1,2 @@ -.github/ @algorand/dev -.circleci/ @algorand/dev +.github/ @algorand/devops +.circleci/ @algorand/devops From 57feb2c0f400944d225c6b12870621cd81bcdf99 Mon Sep 17 00:00:00 2001 From: algobarb <78746954+algobarb@users.noreply.github.com> Date: Wed, 17 May 2023 11:47:55 -0400 Subject: [PATCH 098/125] Dependency: Update codec version to 1.1.9 (#5395) --- go.mod | 2 +- go.sum | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index e1ac463199..cb5ce71426 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/DataDog/zstd v1.5.2 github.com/algorand/avm-abi v0.2.0 github.com/algorand/falcon v0.0.0-20220727072124-02a2a64c4414 - github.com/algorand/go-codec/codec v1.1.8 + github.com/algorand/go-codec/codec v1.1.9 github.com/algorand/go-deadlock v0.2.2 github.com/algorand/go-sumhash v0.1.0 github.com/algorand/graphtrace v0.1.0 diff --git a/go.sum b/go.sum index 85b7f0af3d..9728196212 100644 --- a/go.sum +++ b/go.sum @@ -5,9 +5,8 @@ github.com/algorand/avm-abi v0.2.0 h1:bkjsG+BOEcxUcnGSALLosmltE0JZdg+ZisXKx0UDX2 github.com/algorand/avm-abi v0.2.0/go.mod h1:+CgwM46dithy850bpTeHh9MC99zpn2Snirb3QTl2O/g= github.com/algorand/falcon v0.0.0-20220727072124-02a2a64c4414 h1:nwYN+GQ7Z5OOfZwqBO1ma7DSlP7S1YrKWICOyjkwqrc= github.com/algorand/falcon v0.0.0-20220727072124-02a2a64c4414/go.mod h1:OkQyHlGvS0kLNcIWbC21/uQcnbfwSOQm+wiqWwBG9pQ= -github.com/algorand/go-codec v1.1.8/go.mod h1:XhzVs6VVyWMLu6cApb9/192gBjGRVGm5cX5j203Heg4= -github.com/algorand/go-codec/codec v1.1.8 h1:lsFuhcOH2LiEhpBH3BVUUkdevVmwCRyvb7FCAAPeY6U= -github.com/algorand/go-codec/codec v1.1.8/go.mod h1:tQ3zAJ6ijTps6V+wp8KsGDnPC2uhHVC7ANyrtkIY0bA= +github.com/algorand/go-codec/codec v1.1.9 h1:el4HFSPZhP+YCgOZxeFGB/BqlNkaUIs55xcALulUTCM= +github.com/algorand/go-codec/codec v1.1.9/go.mod h1:YkEx5nmr/zuCeaDYOIhlDg92Lxju8tj2d2NrYqP7g7k= github.com/algorand/go-deadlock v0.2.2 h1:L7AKATSUCzoeVuOgpTipfCEjdUu5ECmlje8R7lP9DOY= github.com/algorand/go-deadlock v0.2.2/go.mod h1:Hat1OXKqKNUcN/iv74FjGhF4hsOE2l7gOgQ9ZVIq6Fk= github.com/algorand/go-sumhash v0.1.0 h1:b/QRhyLuF//vOcicBIxBXYW8bERNoeLxieht/dUYpVg= From 31ae1d659f2a16fa2478d201d18f28c05885926e Mon Sep 17 00:00:00 2001 From: Eric Warehime Date: Wed, 17 May 2023 14:42:39 -0700 Subject: [PATCH 099/125] algod: Move delta API tags (#5396) --- daemon/algod/api/algod.oas2.json | 9 +- daemon/algod/api/algod.oas3.yml | 9 +- .../api/server/v2/generated/data/routes.go | 448 ++++++-------- .../nonparticipating/public/routes.go | 566 ++++++++++-------- 4 files changed, 512 insertions(+), 520 deletions(-) diff --git a/daemon/algod/api/algod.oas2.json b/daemon/algod/api/algod.oas2.json index b167ee4de3..b5c2376125 100644 --- a/daemon/algod/api/algod.oas2.json +++ b/daemon/algod/api/algod.oas2.json @@ -1469,8 +1469,7 @@ "description": "Get ledger deltas for a round.", "tags": [ "public", - "data", - "experimental" + "nonparticipating" ], "produces": [ "application/json", @@ -1539,8 +1538,7 @@ "description": "Get ledger deltas for transaction groups in a given round.", "tags": [ "public", - "data", - "experimental" + "nonparticipating" ], "produces": [ "application/json", @@ -1609,8 +1607,7 @@ "description": "Get a ledger delta for a given transaction group.", "tags": [ "public", - "data", - "experimental" + "nonparticipating" ], "produces": [ "application/json", diff --git a/daemon/algod/api/algod.oas3.yml b/daemon/algod/api/algod.oas3.yml index 8ebbc057b6..3903fb4db3 100644 --- a/daemon/algod/api/algod.oas3.yml +++ b/daemon/algod/api/algod.oas3.yml @@ -3975,8 +3975,7 @@ "summary": "Get a LedgerStateDelta object for a given transaction group", "tags": [ "public", - "data", - "experimental" + "nonparticipating" ] } }, @@ -4107,8 +4106,7 @@ "summary": "Get a LedgerStateDelta object for a given round", "tags": [ "public", - "data", - "experimental" + "nonparticipating" ] } }, @@ -4245,8 +4243,7 @@ "summary": "Get LedgerStateDelta objects for all transaction groups in a given round", "tags": [ "public", - "data", - "experimental" + "nonparticipating" ] } }, diff --git a/daemon/algod/api/server/v2/generated/data/routes.go b/daemon/algod/api/server/v2/generated/data/routes.go index da91c144c9..8c925b6d0c 100644 --- a/daemon/algod/api/server/v2/generated/data/routes.go +++ b/daemon/algod/api/server/v2/generated/data/routes.go @@ -21,15 +21,6 @@ import ( // ServerInterface represents all server handlers. type ServerInterface interface { - // Get a LedgerStateDelta object for a given transaction group - // (GET /v2/deltas/txn/group/{id}) - GetLedgerStateDeltaForTransactionGroup(ctx echo.Context, id string, params GetLedgerStateDeltaForTransactionGroupParams) error - // Get a LedgerStateDelta object for a given round - // (GET /v2/deltas/{round}) - GetLedgerStateDelta(ctx echo.Context, round uint64, params GetLedgerStateDeltaParams) error - // Get LedgerStateDelta objects for all transaction groups in a given round - // (GET /v2/deltas/{round}/txn/group) - GetTransactionGroupLedgerStateDeltasForRound(ctx echo.Context, round uint64, params GetTransactionGroupLedgerStateDeltasForRoundParams) error // Removes minimum sync round restriction from the ledger. // (DELETE /v2/ledger/sync) UnsetSyncRound(ctx echo.Context) error @@ -46,87 +37,6 @@ type ServerInterfaceWrapper struct { Handler ServerInterface } -// GetLedgerStateDeltaForTransactionGroup converts echo context to params. -func (w *ServerInterfaceWrapper) GetLedgerStateDeltaForTransactionGroup(ctx echo.Context) error { - var err error - // ------------- Path parameter "id" ------------- - var id string - - err = runtime.BindStyledParameterWithLocation("simple", false, "id", runtime.ParamLocationPath, ctx.Param("id"), &id) - if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter id: %s", err)) - } - - ctx.Set(Api_keyScopes, []string{""}) - - // Parameter object where we will unmarshal all parameters from the context - var params GetLedgerStateDeltaForTransactionGroupParams - // ------------- Optional query parameter "format" ------------- - - err = runtime.BindQueryParameter("form", true, false, "format", ctx.QueryParams(), ¶ms.Format) - if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter format: %s", err)) - } - - // Invoke the callback with all the unmarshalled arguments - err = w.Handler.GetLedgerStateDeltaForTransactionGroup(ctx, id, params) - return err -} - -// GetLedgerStateDelta converts echo context to params. -func (w *ServerInterfaceWrapper) GetLedgerStateDelta(ctx echo.Context) error { - var err error - // ------------- Path parameter "round" ------------- - var round uint64 - - err = runtime.BindStyledParameterWithLocation("simple", false, "round", runtime.ParamLocationPath, ctx.Param("round"), &round) - if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter round: %s", err)) - } - - ctx.Set(Api_keyScopes, []string{""}) - - // Parameter object where we will unmarshal all parameters from the context - var params GetLedgerStateDeltaParams - // ------------- Optional query parameter "format" ------------- - - err = runtime.BindQueryParameter("form", true, false, "format", ctx.QueryParams(), ¶ms.Format) - if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter format: %s", err)) - } - - // Invoke the callback with all the unmarshalled arguments - err = w.Handler.GetLedgerStateDelta(ctx, round, params) - return err -} - -// GetTransactionGroupLedgerStateDeltasForRound converts echo context to params. -func (w *ServerInterfaceWrapper) GetTransactionGroupLedgerStateDeltasForRound(ctx echo.Context) error { - var err error - // ------------- Path parameter "round" ------------- - var round uint64 - - err = runtime.BindStyledParameterWithLocation("simple", false, "round", runtime.ParamLocationPath, ctx.Param("round"), &round) - if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter round: %s", err)) - } - - ctx.Set(Api_keyScopes, []string{""}) - - // Parameter object where we will unmarshal all parameters from the context - var params GetTransactionGroupLedgerStateDeltasForRoundParams - // ------------- Optional query parameter "format" ------------- - - err = runtime.BindQueryParameter("form", true, false, "format", ctx.QueryParams(), ¶ms.Format) - if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter format: %s", err)) - } - - // Invoke the callback with all the unmarshalled arguments - err = w.Handler.GetTransactionGroupLedgerStateDeltasForRound(ctx, round, params) - return err -} - // UnsetSyncRound converts echo context to params. func (w *ServerInterfaceWrapper) UnsetSyncRound(ctx echo.Context) error { var err error @@ -195,9 +105,6 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL Handler: si, } - router.GET(baseURL+"/v2/deltas/txn/group/:id", wrapper.GetLedgerStateDeltaForTransactionGroup, m...) - router.GET(baseURL+"/v2/deltas/:round", wrapper.GetLedgerStateDelta, m...) - router.GET(baseURL+"/v2/deltas/:round/txn/group", wrapper.GetTransactionGroupLedgerStateDeltasForRound, m...) router.DELETE(baseURL+"/v2/ledger/sync", wrapper.UnsetSyncRound, m...) router.GET(baseURL+"/v2/ledger/sync", wrapper.GetSyncRound, m...) router.POST(baseURL+"/v2/ledger/sync/:round", wrapper.SetSyncRound, m...) @@ -207,187 +114,180 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "///lmFLaz+IAAA==", + "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", + "BRzLAAA=", } // 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 d3b3fb7299..56670010cb 100644 --- a/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go +++ b/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go @@ -54,6 +54,15 @@ type ServerInterface interface { // Get a proof for a transaction in a block. // (GET /v2/blocks/{round}/transactions/{txid}/proof) GetTransactionProof(ctx echo.Context, round uint64, txid string, params GetTransactionProofParams) error + // Get a LedgerStateDelta object for a given transaction group + // (GET /v2/deltas/txn/group/{id}) + GetLedgerStateDeltaForTransactionGroup(ctx echo.Context, id string, params GetLedgerStateDeltaForTransactionGroupParams) error + // Get a LedgerStateDelta object for a given round + // (GET /v2/deltas/{round}) + GetLedgerStateDelta(ctx echo.Context, round uint64, params GetLedgerStateDeltaParams) error + // Get LedgerStateDelta objects for all transaction groups in a given round + // (GET /v2/deltas/{round}/txn/group) + GetTransactionGroupLedgerStateDeltasForRound(ctx echo.Context, round uint64, params GetTransactionGroupLedgerStateDeltasForRoundParams) error // Returns the timestamp offset. Timestamp offsets can only be set in dev mode. // (GET /v2/devmode/blocks/offset) GetBlockTimeStampOffset(ctx echo.Context) error @@ -393,6 +402,87 @@ func (w *ServerInterfaceWrapper) GetTransactionProof(ctx echo.Context) error { return err } +// GetLedgerStateDeltaForTransactionGroup converts echo context to params. +func (w *ServerInterfaceWrapper) GetLedgerStateDeltaForTransactionGroup(ctx echo.Context) error { + var err error + // ------------- Path parameter "id" ------------- + var id string + + err = runtime.BindStyledParameterWithLocation("simple", false, "id", runtime.ParamLocationPath, ctx.Param("id"), &id) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter id: %s", err)) + } + + ctx.Set(Api_keyScopes, []string{""}) + + // Parameter object where we will unmarshal all parameters from the context + var params GetLedgerStateDeltaForTransactionGroupParams + // ------------- Optional query parameter "format" ------------- + + err = runtime.BindQueryParameter("form", true, false, "format", ctx.QueryParams(), ¶ms.Format) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter format: %s", err)) + } + + // Invoke the callback with all the unmarshalled arguments + err = w.Handler.GetLedgerStateDeltaForTransactionGroup(ctx, id, params) + return err +} + +// GetLedgerStateDelta converts echo context to params. +func (w *ServerInterfaceWrapper) GetLedgerStateDelta(ctx echo.Context) error { + var err error + // ------------- Path parameter "round" ------------- + var round uint64 + + err = runtime.BindStyledParameterWithLocation("simple", false, "round", runtime.ParamLocationPath, ctx.Param("round"), &round) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter round: %s", err)) + } + + ctx.Set(Api_keyScopes, []string{""}) + + // Parameter object where we will unmarshal all parameters from the context + var params GetLedgerStateDeltaParams + // ------------- Optional query parameter "format" ------------- + + err = runtime.BindQueryParameter("form", true, false, "format", ctx.QueryParams(), ¶ms.Format) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter format: %s", err)) + } + + // Invoke the callback with all the unmarshalled arguments + err = w.Handler.GetLedgerStateDelta(ctx, round, params) + return err +} + +// GetTransactionGroupLedgerStateDeltasForRound converts echo context to params. +func (w *ServerInterfaceWrapper) GetTransactionGroupLedgerStateDeltasForRound(ctx echo.Context) error { + var err error + // ------------- Path parameter "round" ------------- + var round uint64 + + err = runtime.BindStyledParameterWithLocation("simple", false, "round", runtime.ParamLocationPath, ctx.Param("round"), &round) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter round: %s", err)) + } + + ctx.Set(Api_keyScopes, []string{""}) + + // Parameter object where we will unmarshal all parameters from the context + var params GetTransactionGroupLedgerStateDeltasForRoundParams + // ------------- Optional query parameter "format" ------------- + + err = runtime.BindQueryParameter("form", true, false, "format", ctx.QueryParams(), ¶ms.Format) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter format: %s", err)) + } + + // Invoke the callback with all the unmarshalled arguments + err = w.Handler.GetTransactionGroupLedgerStateDeltasForRound(ctx, round, params) + return err +} + // GetBlockTimeStampOffset converts echo context to params. func (w *ServerInterfaceWrapper) GetBlockTimeStampOffset(ctx echo.Context) error { var err error @@ -592,6 +682,9 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL router.GET(baseURL+"/v2/blocks/:round/hash", wrapper.GetBlockHash, m...) router.GET(baseURL+"/v2/blocks/:round/lightheader/proof", wrapper.GetLightBlockHeaderProof, m...) router.GET(baseURL+"/v2/blocks/:round/transactions/:txid/proof", wrapper.GetTransactionProof, m...) + router.GET(baseURL+"/v2/deltas/txn/group/:id", wrapper.GetLedgerStateDeltaForTransactionGroup, m...) + router.GET(baseURL+"/v2/deltas/:round", wrapper.GetLedgerStateDelta, m...) + router.GET(baseURL+"/v2/deltas/:round/txn/group", wrapper.GetTransactionGroupLedgerStateDeltasForRound, m...) router.GET(baseURL+"/v2/devmode/blocks/offset", wrapper.GetBlockTimeStampOffset, m...) router.POST(baseURL+"/v2/devmode/blocks/offset/:offset", wrapper.SetBlockTimeStampOffset, m...) router.GET(baseURL+"/v2/ledger/supply", wrapper.GetSupply, m...) @@ -609,247 +702,252 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "H4sIAAAAAAAC/+y9a5PbtpIA+ldQ2q3yY0WNn9mTqUrtndhOzmxsx+WZZPds7JtAZEvCGQrgAUCNFF//", - "91toACRIghI1I4/jZD7ZI5JAo9Fo9Ls/jFKxLAQHrtXo+MOooJIuQYPEv2iaipLrhGXmrwxUKlmhmeCj", - "Y/+MKC0Zn4/GI2Z+LahejMYjTpdQv2O+H48k/KtkErLRsZYljEcqXcCSmoH1pjBvVyOtk7lI3BAndojT", - "56OPWx7QLJOgVBfKH3m+IYyneZkB0ZJyRVPzSJFLphdEL5gi7mPCOBEciJgRvWi8TGYM8kxN/CL/VYLc", - "BKt0k/cv6WMNYiJFDl04n4nllHHwUEEFVLUhRAuSwQxfWlBNzAwGVv+iFkQBlemCzITcAaoFIoQXeLkc", - "Hf8yUsAzkLhbKbAV/ncmAX6HRFM5Bz16P44tbqZBJpotI0s7ddiXoMpcK4Lv4hrnbAWcmK8m5FWpNJkC", - "oZy8/e4Zefz48ddmIUuqNWSOyHpXVc8ersl+PjoeZVSDf9ylNZrPhaQ8S6r33373DOc/cwsc+hZVCuKH", - "5cQ8IafP+xbgP4yQEOMa5rgPDeo3X0QORf3zFGZCwsA9sS8fdFPC+T/rrqRUp4tCMK4j+0LwKbGPozws", - "+HwbD6sAaLxfGExJM+gvD5Kv3394OH744OO//XKS/J/78+njjwOX/6wadwcGoi+mpZTA000yl0DxtCwo", - "7+LjraMHtRBlnpEFXeHm0yWyevctMd9a1rmieWnohKVSnORzoQh1ZJTBjJa5Jn5iUvLcsCkzmqN2whQp", - "pFixDLKx4b6XC5YuSEqVHQLfI5cszw0NlgqyPlqLr27LYfoYosTAdSV84IL+uMio17UDE7BGbpCkuVCQ", - "aLHjevI3DuUZCS+U+q5S+11W5HwBBCc3D+xli7jjhqbzfEM07mtGqCKU+KtpTNiMbERJLnFzcnaB37vV", - "GKwtiUEabk7jHjWHtw99HWREkDcVIgfKEXn+3HVRxmdsXkpQ5HIBeuHuPAmqEFwBEdN/QqrNtv/32Y+v", - "iZDkFShF5/CGphcEeCoyyCbkdEa40AFpOFpCHJov+9bh4Ipd8v9UwtDEUs0Lml7Eb/ScLVlkVa/omi3L", - "JeHlcgrSbKm/QrQgEnQpeR9AdsQdpLik6+6k57LkKe5/PW1DljPUxlSR0w0ibEnX3zwYO3AUoXlOCuAZ", - "43Oi17xXjjNz7wYvkaLk2QAxR5s9DS5WVUDKZgwyUo2yBRI3zS54GN8Pnlr4CsDxg/SCU82yAxwO6wjN", - "mNNtnpCCziEgmQn5yTE3fKrFBfCK0Ml0g48KCSsmSlV91AMjTr1dAudCQ1JImLEIjZ05dBgGY99xHHjp", - "ZKBUcE0Zh8wwZwRaaLDMqhemYMLt+k73Fp9SBV896bvj66cDd38m2ru+dccH7Ta+lNgjGbk6zVN3YOOS", - "VeP7AfphOLdi88T+3NlINj83t82M5XgT/dPsn0dDqZAJNBDh7ybF5pzqUsLxO37f/EUScqYpz6jMzC9L", - "+9OrMtfsjM3NT7n96aWYs/SMzXuQWcEaVbjws6X9x4wXZ8d6HdUrXgpxURbhgtKG4jrdkNPnfZtsx9yX", - "ME8qbTdUPM7XXhnZ9wu9rjayB8he3BXUvHgBGwkGWprO8J/1DOmJzuTv5p+iyM3XupjFUGvo2F3JaD5w", - "ZoWToshZSg0S37rH5qlhAmAVCVq/cYQX6vGHAMRCigKkZnZQWhRJLlKaJ0pTjSP9u4TZ6Hj0b0e1/eXI", - "fq6Ogslfmq/O8CMjsloxKKFFsccYb4zoo7YwC8Og8RGyCcv2UGhi3G6iISVmWHAOK8r1pFZZGvygOsC/", - "uJlqfFtpx+K7pYL1IpzYF6egrARsX7yjSIB6gmgliFYUSOe5mFY/3D0pihqD+PykKCw+UHoEhoIZrJnS", - "6h4un9YnKZzn9PmEfB+OjaK44PnGXA5W1DB3w8zdWu4Wq2xLbg31iHcUwe0UcmK2xqPBiPmHoDhUKxYi", - "N1LPTloxL//dvRuSmfl90MdfBomFuO0nLlS0HOasjoO/BMrN3RbldAnHmXsm5KT97dXIxowSJ5gr0crW", - "/bTjbsFjhcJLSQsLoHti71LGUUmzL1lYr8lNBzK6KMzBGQ5oDaG68lnbeR6ikCAptGD4Nhfpxd+pWhzg", - "zE/9WN3jh9OQBdAMJFlQtZiMYlJGeLzq0YYcMfMiKvhkGkw1qZZ4qOXtWFpGNQ2W5uCNiyUW9fgdMj2Q", - "Ed3lR/wPzYl5bM62Yf122Ak5Rwam7HF2TobMaPtWQbAzmRfQCiHI0ir4xGjde0H5rJ48vk+D9uiFtSm4", - "HXKLwB0S64Mfg2/FOgbDt2LdOQJiDeoQ9GHGQTFSw1INgO+5g0zg/jv0USnppotkHHsIks0Cjeiq8DTw", - "8MY3s9TG2ZOpkFfjPi22wkltcibUjBow33ELSfhqWSSOFCNmK/tCa6Day7edabSHj2GsgYUzTT8BFpQZ", - "9RBYaA50aCyIZcFyOADpL6JMf0oVPH5Ezv5+8vTho18fPf3KkGQhxVzSJZluNChy1+lmROlNDve6K0Pt", - "qMx1fPSvnnhDZXPc2DhKlDKFJS26Q1kDqBWB7GvEvNfFWhPNuOoKwCGH8xwMJ7doJ9a2b0B7zpSRsJbT", - "g2xGH8KyepaMOEgy2ElM+y6vnmYTLlFuZHkIVRakFDJiX8MjpkUq8mQFUjER8aa8cW8Q94YXb4v27xZa", - "ckkVMXOj6bfkKFBEKEuv+XC+b4c+X/MaN1s5v11vZHVu3iH70kS+tyQqUoBM9JqTDKblvKEJzaRYEkoy", - "/BDv6O9BoyhwzpZwpumy+HE2O4yqKHCgiMrGlqDMTMS+YeR6BangNhJih3bmRh2CnjZivIlO9wPgMHK2", - "4SnaGQ9xbPsV1yXj6PRQG54GWqyBMYds3iDL62urfeiwU91REXAMOl7iYzR0PIdc0++EPK8tgd9LURYH", - "F/Lacw5dDnWLcaaUzHzrdWjG53kz+mZuYJ/E1vhZFvTMH1+3BoQeKfIlmy90oFa8kULMDg9jbJYYoPjA", - "KmW5+aarmr0WmWEmulQHEMHqwWoOZ+g25Gt0KkpNKOEiA9z8UsWFs554DXQUo39bh/KeXlg9awqGulJa", - "mtWWBUHvbee+qD9MaGpPaIKoUT2+q8rpaN+y09lYgFwCzTZkCsCJmDoHkXNd4SIpup61F2+caBjhFw24", - "CilSUAqyxBmmdoLm37NXh96CJwQcAa5mIUqQGZXXBvZitRPOC9gkGCihyN0fflb3PgO8Wmia70AsvhND", - "b6XmOy9gF+ph028juPbkIdlRCcTfK0QLlGZz0NCHwr1w0rt/bYg6u3h9tKxAoj/uk1K8n+R6BFSB+onp", - "/brQlkVP+J9Tb42EZzaMUy68YBUbLKdKJ7vYsnmpoYObFQScMMaJceAeweslVdr6kBnP0PRlrxOcxwph", - "Zop+gHvVEDPyz14D6Y6dmnuQq1JV6ogqi0JIDVlsDRzWW+Z6DetqLjELxq50Hi1IqWDXyH1YCsZ3yLIr", - "sQiiunK1uCCL7uLQIWHu+U0UlQ0gakRsA+TMvxVgNwyB6gGEqRrRlnCYalFOFXc1HiktisJwC52UvPqu", - "D01n9u0T/VP9bpe4qK7v7UyAwsgr976D/NJi1ga/LagiDg6ypBdG9kAziHV2d2E2hzFRjKeQbKN8VPHM", - "W+ER2HlIy2IuaQZJBjnddAf9yT4m9vG2AXDHa3VXaEhsFFN802tK9kEjW4YWOJ6KCY8En5DUHEGjCtQE", - "4r7eMXIGOHaMOTk6ulMNhXNFt8iPh8u2Wx0ZEW/DldBmxx09IMiOow8BuAcP1dBXRwV+nNS6Z3uKf4By", - "E1RyxP6TbED1LaEef68F9NhQXYB4cF5a7L3FgaNss5eN7eAjfUe2x6D7hkrNUlagrvMDbA6u+rUniLoZ", - "SQaashwyEjywamARfk9s/E17zKupgoNsb13wO8a3yHJyplDkaQJ/ARvUud/YwM7A1HEIXTYyqrmfKCcI", - "qA8XMyJ4+AqsaarzjRHU9AI25BIkEFVOl0xrG7DdVHW1KJJwgKhfY8uMzolngyL9DgzxKp7hUMHyulsx", - "HlmdYDt85y3FoIEOpwsUQuQDLGQdZEQhGBTvQQphdp252HEfPewpqQGkY9rowa2u/zuqgWZcAfmHKElK", - "OapcpYZKphESBQUUIM0MRgSr5nSRHTWGIIclWE0Sn9y/3174/ftuz5kiM7j0CRfmxTY67t9HO84boXTj", - "cB3AHmqO22nk+kCHj7n4nBbS5im7IwvcyEN28k1r8MpLZM6UUo5wzfKvzQBaJ3M9ZO0hjQyLqsBxB/ly", - "gqFj68Z9P2PLMqf6EF4rWNE8ESuQkmWwk5O7iZngL1Y0/7H6bIdOV0eBseUSMkY15BtSSEjBRucbUU1V", - "Y0+IjdtLF5TPUUKXopy7wDE7DnLYUllbiCx5Z4ioFKPXPEGrcozjumBhn6Bh5BegRodqm6StxnBJq/lc", - "Ts6Qq9DvXMREH/VKjUe9KqZB6qpWMS1ymlkmA7hvQ8AK8FNPPNB3gagzwkYXX+G2GOo1m/tpbOT10DEo", - "uxMHoWz1w75oNqPf5psDSBl2ICKhkKDwTgjtQso+FbMwo8xdGmqjNCy7pnP76a89x+9tr4ImeM44JEvB", - "YRNNomYcXuHD6HHCe6nnY5QQ+r5tC/0N+FtgNecZQo3XxS/udvuERvxsV3dBDuIVAzx7QyTpqCMuzyOu", - "OJcu0j6/alylpzNJqFIiZSjjnGZqbM+J89653JIm9t5UQbAHODrtcVs+pzATEW2qkBeEkjRnaHEVXGlZ", - "pvodp2jTCZYaCRbyymu/le+ZfyVuVoxY/dxQ7zjFQLHK0hMNcJhBxKzxHYA39qlyPgelW7rBDOAdd28x", - "TkrONM61NNSeWHIvQGLEzsS+uaQbMjM0oQX5HaQg01I3pWXMhlKa5blzgJlpiJi941STHIzC/4rx8zUO", - "553k/sRx0JdCXlRYiF/Oc+CgmEriQU3f26cYb+qWv3Cxp5i9bh9bl4kZv06Z2qDJp87I/n/v/tfxLyfJ", - "/9Hk9wfJ1/9x9P7Dk4/37nd+fPTxm2/+v+ZPjz9+c++//j22Ux72WK6Og/z0udMkT5+julD7TDqw35i9", - "fMl4EiWyMPqhRVvkLualOgK61zQm6QW843rNDSGtaM4yw1uuQg7tC6JzFu3paFFNYyNaxiO/1j2F8Gtw", - "GRJhMi3WeGUhqBsHGM+KQyeeS3TD8zIrud1KLzzbpA8fjyVm4yrz0RZFOSaYFregPpjQ/fno6VejcZ3O", - "Vj0fjUfu6fsIJbNsHUtazGAd063cAcGDcUeRgm4U6Dj3QNijoWc2FiIcdglGKVcLVtw8p1CaTeMczofS", - "OxvNmp9yG+Nuzg+6BDfO0yBmNw+3lgAZFHoRK5bQkLPwrXo3AVphGoUUK+BjwiYwadtIMqPuuSC4HOgM", - "k/ZReRRDlJnqHFhC81QRYD1cyCBDRIx+UORx3PrjeOQuf3VwbcYNHIOrPWfl//N/a0HufP/inBw5hqnu", - "2PxZO3SQ8RjRhF1STyOAx3AzWyLGCnnv+Dv+HGaMM/P8+B3PqKZHU6pYqo5KBfJbmlOewmQuyLHPE3pO", - "NX3HO5JWbxWnIEOLFOU0Zym5CPWJmjxtZY7uCO/e/ULzuXj37n0nlqEr/bupovzFTpAYQViUOnF1BRIJ", - "l1TGfEWqyivHkW3hkG2zWiFblNag6OsWuPHjPI8WhWrnl3aXXxS5WX5AhsplT5otI0oL6WURI6BYaHB/", - "Xwt3MUh66c0ipQJFflvS4hfG9XuSvCsfPHgMpJFw+Zu78g1NbgoYbBzpzX9t20Rw4VYrhLWWNCnoPOaS", - "evfuFw20wN1HeXmJJoo8J/hZI9HTB7LjUPUCPD76N8DCsXfSGi7uzH7la0jFl4CPcAvxHSNu1I7yq+5X", - "kPp55e1qpY92dqnUi8Sc7eiqlCFxvzNVaZm5EbJ89IJic9RWXRWeKZB0AemFK48Cy0Jvxo3PfYCMEzQ9", - "62DKFs6xiVtYugEN+lMgZZFRJ4pTvmnn0CvQ2ofhvoUL2JyLuvLDPknzzRxu1XdQkVID6dIQa3hs3Rjt", - "zXdRWKjYF4VPhcacOE8WxxVd+G/6D7IVeQ9wiGNE0cgx7kMElRFEWOLvQcEVFmrGuxbpx5ZntIypvfki", - "RXQ87yfulVp5cgFT4WrQaG6fLwGrcIlLRabUyO3CFZCyecoBFysVnUOPhBz6VAZmAzf8MDjIrnsvetOJ", - "WftC69w3UZDty4lZc5RSwDwxpILKTCtMzs9k3XbOsYB1IR3CpjmKSVU8oWU6VDZ8W7bQXR9ocQIGyWuB", - "w4PRxEgo2Syo8rWtsASYP8uDZIBPmHe/rdrKaRDhFdT5qmqpeJ7bPqcd7dLVXPGFVnx1lVC1HFApxUj4", - "GFQe2w7BUQDKIIe5Xbh92RNKXQOg3iADx4+zWc44kCQWLBaYQYNrxs0BRj6+T4g1oJPBI8TIOAAb3dE4", + "H4sIAAAAAAAC/+x9a5PbtpLoX0Fpt8qPFTV+Zk+mKrV3YucxG8dxeSY5ezb2TSCyJeEMBfAAoEaKr//7", + "LTQAEiRBiZqnncwne0QSaDQajX73h1EqloXgwLUaHX4YFVTSJWiQ+BdNU1FynbDM/JWBSiUrNBN8dOif", + "EaUl4/PReMTMrwXVi9F4xOkS6nfM9+ORhH+VTEI2OtSyhPFIpQtYUjOw3hTm7WqkdTIXiRviyA5x/HL0", + "ccsDmmUSlOpC+RPPN4TxNC8zIFpSrmhqHilyzvSC6AVTxH1MGCeCAxEzoheNl8mMQZ6piV/kv0qQm2CV", + "bvL+JX2sQUykyKEL5wuxnDIOHiqogKo2hGhBMpjhSwuqiZnBwOpf1IIooDJdkJmQO0C1QITwAi+Xo8Nf", + "Rwp4BhJ3KwW2wv/OJMAfkGgq56BH78exxc00yESzZWRpxw77ElSZa0XwXVzjnK2AE/PVhPxYKk2mQCgn", + "b799QZ4+ffqlWciSag2ZI7LeVdWzh2uyn48ORxnV4B93aY3mcyEpz5Lq/bffvsD5T9wCh75FlYL4YTky", + "T8jxy74F+A8jJMS4hjnuQ4P6zReRQ1H/PIWZkDBwT+zLV7op4fy3uisp1emiEIzryL4QfErs4ygPCz7f", + "xsMqABrvFwZT0gz666Pky/cfHo8fP/r4b78eJf/r/nz+9OPA5b+oxt2BgeiLaSkl8HSTzCVQPC0Lyrv4", + "eOvoQS1EmWdkQVe4+XSJrN59S8y3lnWuaF4aOmGpFEf5XChCHRllMKNlromfmJQ8N2zKjOaonTBFCilW", + "LINsbLjv+YKlC5JSZYfA98g5y3NDg6WCrI/W4qvbcpg+higxcF0IH7igTxcZ9bp2YALWyA2SNBcKEi12", + "XE/+xqE8I+GFUt9Var/LipwugODk5oG9bBF33NB0nm+Ixn3NCFWEEn81jQmbkY0oyTluTs7O8Hu3GoO1", + "JTFIw81p3KPm8Pahr4OMCPKmQuRAOSLPn7suyviMzUsJipwvQC/cnSdBFYIrIGL6T0i12fb/PvnpNRGS", + "/AhK0Tm8oekZAZ6KDLIJOZ4RLnRAGo6WEIfmy751OLhil/w/lTA0sVTzgqZn8Rs9Z0sWWdWPdM2W5ZLw", + "cjkFabbUXyFaEAm6lLwPIDviDlJc0nV30lNZ8hT3v562IcsZamOqyOkGEbak668ejR04itA8JwXwjPE5", + "0WveK8eZuXeDl0hR8myAmKPNngYXqyogZTMGGalG2QKJm2YXPIzvB08tfAXg+EF6walm2QEOh3WEZszp", + "Nk9IQecQkMyE/OyYGz7V4gx4RehkusFHhYQVE6WqPuqBEafeLoFzoSEpJMxYhMZOHDoMg7HvOA68dDJQ", + "KrimjENmmDMCLTRYZtULUzDhdn2ne4tPqYIvnvXd8fXTgbs/E+1d37rjg3YbX0rskYxcneapO7Bxyarx", + "/QD9MJxbsXlif+5sJJufmttmxnK8if5p9s+joVTIBBqI8HeTYnNOdSnh8B1/aP4iCTnRlGdUZuaXpf3p", + "xzLX7ITNzU+5/emVmLP0hM17kFnBGlW48LOl/ceMF2fHeh3VK14JcVYW4YLShuI63ZDjl32bbMfclzCP", + "Km03VDxO114Z2fcLva42sgfIXtwV1Lx4BhsJBlqazvCf9Qzpic7kH+afosjN17qYxVBr6NhdyWg+cGaF", + "o6LIWUoNEt+6x+apYQJgFQlav3GAF+rhhwDEQooCpGZ2UFoUSS5SmidKU40j/buE2ehw9G8Htf3lwH6u", + "DoLJX5mvTvAjI7JaMSihRbHHGG+M6KO2MAvDoPERsgnL9lBoYtxuoiElZlhwDivK9aRWWRr8oDrAv7qZ", + "anxbacfiu6WC9SKc2BenoKwEbF+8p0iAeoJoJYhWFEjnuZhWP9w/Kooag/j8qCgsPlB6BIaCGayZ0uoB", + "Lp/WJymc5/jlhHwXjo2iuOD5xlwOVtQwd8PM3VruFqtsS24N9Yj3FMHtFHJitsajwYj5V0FxqFYsRG6k", + "np20Yl7+3r0bkpn5fdDHnweJhbjtJy5UtBzmrI6DvwTKzf0W5XQJx5l7JuSo/e3FyMaMEieYC9HK1v20", + "427BY4XCc0kLC6B7Yu9SxlFJsy9ZWC/JTQcyuijMwRkOaA2huvBZ23keopAgKbRg+DoX6dn3VC2u4MxP", + "/Vjd44fTkAXQDCRZULWYjGJSRni86tGGHDHzIir4ZBpMNamWeFXL27G0jGoaLM3BGxdLLOrxO2R6ICO6", + "y0/4H5oT89icbcP67bATcooMTNnj7JwMmdH2rYJgZzIvoBVCkKVV8InRuveC8kU9eXyfBu3RN9am4HbI", + "LQJ3SKyv/Bh8LdYxGL4W684REGtQV0EfZhwUIzUs1QD4XjrIBO6/Qx+Vkm66SMaxhyDZLNCIrgpPAw9v", + "fDNLbZw9mgp5Me7TYiuc1CZnQs2oAfMdt5CEr5ZF4kgxYrayL7QGqr1825lGe/gYxhpYONH0GrCgzKhX", + "gYXmQFeNBbEsWA5XQPqLKNOfUgVPn5CT74+eP37y25PnXxiSLKSYS7ok040GRe473YwovcnhQXdlqB2V", + "uY6P/sUzb6hsjhsbR4lSprCkRXcoawC1IpB9jZj3ulhrohlXXQE45HCeguHkFu3E2vYNaC+ZMhLWcnol", + "m9GHsKyeJSMOkgx2EtO+y6un2YRLlBtZXoUqC1IKGbGv4RHTIhV5sgKpmIh4U964N4h7w4u3Rft3Cy05", + "p4qYudH0W3IUKCKUpdd8ON+3Q5+ueY2brZzfrjeyOjfvkH1pIt9bEhUpQCZ6zUkG03Le0IRmUiwJJRl+", + "iHf0d6BRFDhlSzjRdFn8NJtdjaoocKCIysaWoMxMxL5h5HoFqeA2EmKHduZGHYKeNmK8iU73A+AwcrLh", + "KdoZr+LY9iuuS8bR6aE2PA20WANjDtm8QZaX11b70GGnuqci4Bh0vMLHaOh4Cbmm3wp5WlsCv5OiLK5c", + "yGvPOXQ51C3GmVIy863XoRmf583om7mBfRJb460s6IU/vm4NCD1S5Cs2X+hArXgjhZhdPYyxWWKA4gOr", + "lOXmm65q9lpkhpnoUl2BCFYPVnM4Q7chX6NTUWpCCRcZ4OaXKi6c9cRroKMY/ds6lPf0wupZUzDUldLS", + "rLYsCHpvO/dF/WFCU3tCE0SN6vFdVU5H+5adzsYC5BJotiFTAE7E1DmInOsKF0nR9ay9eONEwwi/aMBV", + "SJGCUpAlzjC1EzT/nr069BY8IeAIcDULUYLMqLw0sGernXCewSbBQAlF7v/wi3pwC/BqoWm+A7H4Tgy9", + "lZrvvIBdqIdNv43g2pOHZEclEH+vEC1Qms1BQx8K98JJ7/61Iers4uXRsgKJ/rhrpXg/yeUIqAL1mun9", + "stCWRU/4n1NvjYRnNoxTLrxgFRssp0onu9iyeamhg5sVBJwwxolx4B7B6xVV2vqQGc/Q9GWvE5zHCmFm", + "in6Ae9UQM/IvXgPpjp2ae5CrUlXqiCqLQkgNWWwNHNZb5noN62ouMQvGrnQeLUipYNfIfVgKxnfIsiux", + "CKK6crW4IIvu4tAhYe75TRSVDSBqRGwD5MS/FWA3DIHqAYSpGtGWcJhqUU4VdzUeKS2KwnALnZS8+q4P", + "TSf27SP9c/1ul7ioru/tTIDCyCv3voP83GLWBr8tqCIODrKkZ0b2QDOIdXZ3YTaHMVGMp5Bso3xU8cxb", + "4RHYeUjLYi5pBkkGOd10B/3ZPib28bYBcMdrdVdoSGwUU3zTa0r2QSNbhhY4nooJjwSfkNQcQaMK1ATi", + "vt4xcgY4dow5OTq6Vw2Fc0W3yI+Hy7ZbHRkRb8OV0GbHHT0gyI6jDwG4Bw/V0BdHBX6c1Lpne4p/gHIT", + "VHLE/pNsQPUtoR5/rwX02FBdgHhwXlrsvcWBo2yzl43t4CN9R7bHoPuGSs1SVqCu8wNsrlz1a08QdTOS", + "DDRlOWQkeGDVwCL8ntj4m/aYF1MFB9neuuB3jG+R5eRMocjTBP4MNqhzv7GBnYGp4yp02cio5n6inCCg", + "PlzMiODhK7Cmqc43RlDTC9iQc5BAVDldMq1twHZT1dWiSMIBon6NLTM6J54NivQ7MMSreIJDBcvrbsV4", + "ZHWC7fCdthSDBjqcLlAIkQ+wkHWQEYVgULwHKYTZdeZix330sKekBpCOaaMHt7r+76kGmnEF5B+iJCnl", + "qHKVGiqZRkgUFFCANDMYEaya00V21BiCHJZgNUl88vBhe+EPH7o9Z4rM4NwnXJgX2+h4+BDtOG+E0o3D", + "dQX2UHPcjiPXBzp8zMXntJA2T9kdWeBGHrKTb1qDV14ic6aUcoRrln9pBtA6meshaw9pZFhUBY47yJcT", + "DB1bN+77CVuWOdVX4bWCFc0TsQIpWQY7ObmbmAn+zYrmP1Wf7dDp6igwtlxCxqiGfEMKCSnY6Hwjqqlq", + "7AmxcXvpgvI5SuhSlHMXOGbHQQ5bKmsLkSXvDBGVYvSaJ2hVjnFcFyzsEzSM/ALU6FBtk7TVGM5pNZ/L", + "yRlyFfqdi5joo16p8ahXxTRIXdUqpkVOM8tkAPdtCFgBfuqJB/ouEHVG2OjiK9wWQ71mc6/HRl4PHYOy", + "O3EQylY/7ItmM/ptvrkCKcMORCQUEhTeCaFdSNmnYhZmlLlLQ22UhmXXdG4//a3n+L3tVdAEzxmHZCk4", + "bKJJ1IzDj/gwepzwXur5GCWEvm/bQn8D/hZYzXmGUONl8Yu73T6hET/bxV2Qg3jFAM/eEEk66ojL84gr", + "zqWLtM+vGlfp6UwSqpRIGco4x5ka23PivHcut6SJvTdVEOwVHJ32uC2fU5iJiDZVyAtCSZoztLgKrrQs", + "U/2OU7TpBEuNBAt55bXfyvfCvxI3K0asfm6od5xioFhl6YkGOMwgYtb4FsAb+1Q5n4PSLd1gBvCOu7cY", + "JyVnGudaGmpPLLkXIDFiZ2LfXNINmRma0IL8AVKQaamb0jJmQynN8tw5wMw0RMzecapJDkbh/5Hx0zUO", + "553k/sRx0OdCnlVYiF/Oc+CgmEriQU3f2acYb+qWv3Cxp5i9bh9bl4kZv06Z2qDJp87I/r/3/+vw16Pk", + "f2nyx6Pky/84eP/h2ccHDzs/Pvn41Vf/r/nT049fPfivf4/tlIc9lqvjID9+6TTJ45eoLtQ+kw7sN2Yv", + "XzKeRIksjH5o0Ra5j3mpjoAeNI1JegHvuF5zQ0grmrPM8JaLkEP7guicRXs6WlTT2IiW8civdU8h/BJc", + "hkSYTIs1XlgI6sYBxrPi0InnEt3wvMxKbrfSC8826cPHY4nZuMp8tEVRDgmmxS2oDyZ0fz55/sVoXKez", + "Vc9H45F7+j5CySxbx5IWM1jHdCt3QPBg3FOkoBsFOs49EPZo6JmNhQiHXYJRytWCFTfPKZRm0ziH86H0", + "zkaz5sfcxrib84MuwY3zNIjZzcOtJUAGhV7EiiU05Cx8q95NgFaYRiHFCviYsAlM2jaSzKh7LgguBzrD", + "pH1UHsUQZaY6B5bQPFUEWA8XMsgQEaMfFHkct/44HrnLX125NuMGjsHVnrPy//m/tSD3vvvmlBw4hqnu", + "2fxZO3SQ8RjRhF1STyOAx3AzWyLGCnnv+Dv+EmaMM/P88B3PqKYHU6pYqg5KBfJrmlOewmQuyKHPE3pJ", + "NX3HO5JWbxWnIEOLFOU0Zyk5C/WJmjxtZY7uCO/e/UrzuXj37n0nlqEr/bupovzFTpAYQViUOnF1BRIJ", + "51TGfEWqyivHkW3hkG2zWiFblNag6OsWuPHjPI8WhWrnl3aXXxS5WX5AhsplT5otI0oL6WURI6BYaHB/", + "Xwt3MUh67s0ipQJFfl/S4lfG9XuSvCsfPXoKpJFw+bu78g1NbgoYbBzpzX9t20Rw4VYrhLWWNCnoPOaS", + "evfuVw20wN1HeXmJJoo8J/hZI9HTB7LjUPUCPD76N8DCsXfSGi7uxH7la0jFl4CPcAvxHSNu1I7yi+5X", + "kPp54e1qpY92dqnUi8Sc7eiqlCFxvzNVaZm5EbJ89IJic9RWXRWeKZB0AemZK48Cy0Jvxo3PfYCMEzQ9", + "62DKFs6xiVtYugEN+lMgZZFRJ4pTvmnn0CvQ2ofhvoUz2JyKuvLDPknzzRxu1XdQkVID6dIQa3hs3Rjt", + "zXdRWKjYF4VPhcacOE8WhxVd+G/6D7IVea/gEMeIopFj3IcIKiOIsMTfg4ILLNSMdynSjy3PaBlTe/NF", + "iuh43k/cK7Xy5AKmwtWg0dw+XwJW4RLnikypkduFKyBl85QDLlYqOoceCTn0qQzMBm74YXCQXfde9KYT", + "s/aF1rlvoiDblxOz5iilgHliSAWVmVaYnJ/Juu2cYwHrQjqETXMUk6p4Qst0qGz4tmyhuz7Q4gQMktcC", + "hwejiZFQsllQ5WtbYQkwf5YHyQDXmHe/rdrKcRDhFdT5qmqpeJ7bPqcd7dLVXPGFVnx1lVC1HFApxUj4", + "GFQe2w7BUQDKIIe5Xbh92RNKXQOg3iADx0+zWc44kCQWLBaYQYNrxs0BRj5+SIg1oJPBI8TIOAAb3dE4", "MHktwrPJ5/sAyV0NA+rHRkd28DfE061s+LQReURhWDjrcUqlngNQF2FY3V+tOFcchjA+JobNrWhu2JzT", - "+OpBOkU/UGxtlfhwARH3+sTZLf4Le7HstSZ7FV1lNaHM5IGOC3RbIJ6KdWLzLaMS73Q9NfQejSjH7M/Y", - "wbTlVe4oMhVrDLLBq8VGMO+ApR8OD0ag4a+ZQnrF7/pucwvMtmm3S1MxKlRIMs6cV5FLnzgxZOoeCaaP", - "XO4GFVOuBEDL2FGXH3bK704ltSmedC/z+lYb15XAfLJO7Pj3HaHoLvXgr2uFqWqcvGlLLFE7RTNWpFne", - "JRAhY0Rv2ETXSdN1BSnIAZWCpCFEJRcxx6fRbQBvnDP/WWC8wCIylG/uBQFIEuZMaaiN6D7M4XOYJynW", - "rhNi1r86XciZWd9bIapryroR8cPGMm98BRjBO2NS6QQ9ENElmJe+U6hUf2dejctKzRAnW+mVZXHegNNe", - "wCbJWF7G6dXN+8NzM+3riiWqcor8lnEbbzLFysTRwMctU9vY2K0LfmkX/JIebL3DToN51UwsDbk05/hC", + "+OpBOkU/UGxtlfhwAREP+sTZLf4Le7HstSZ7FV1kNaHM5IGOC3RbIJ6KdWLzLaMS73Q9NfQejSjH7M/Y", + "wbTlVe4pMhVrDLLBq8VGMO+ApR8OD0ag4a+ZQnrF7/pucwvMtmm3S1MxKlRIMs6cV5FLnzgxZOoeCaaP", + "XO4HFVMuBEDL2FGXH3bK704ltSmedC/z+lYb15XAfLJO7Pj3HaHoLvXgr2uFqWqcvGlLLFE7RTNWpFne", + "JRAhY0Rv2ETXSdN1BSnIAZWCpCFEJWcxx6fRbQBvnBP/WWC8wCIylG8eBAFIEuZMaaiN6D7M4TbMkxRr", + "1wkx61+dLuTMrO+tENU1Zd2I+GFjmTe+AozgnTGpdIIeiOgSzEvfKlSqvzWvxmWlZoiTrfTKsjhvwGnP", + "YJNkLC/j9Orm/eGlmfZ1xRJVOUV+y7iNN5liZeJo4OOWqW1s7NYFv7ILfkWvbL3DToN51UwsDbk05/hM", "zkWL825jBxECjBFHd9d6UbqFQQYJq13uGMhNgY9/ss362jlMmR97Z9CNT5vtu6PsSNG1BAaDratg6CYy", "YgnTQWHfbiZpzxmgRcGydcsWakft1ZjpXgYPXw6thQXcXTfYDgwEds9YMosE1ax8Vwv4tkRzo/DMZBBm", - "zpv16UKGEE7FlG8w0EVUley2C1fnQPMfYPOzeReXM/o4Hl3PdBrDtRtxB67fVNsbxTO65q0preEJ2RPl", - "tCikWNE8cQbmPtKUYuVIE1/39ugbZnVxM+b5i5OXbxz4H8ejNAcqk0pU6F0Vvld8MauyRfZ6DogvYG50", - "Pi+zW1Ey2PyqMlholL5cgKsEHUijnZKVtcMhOIrOSD2LRwjtNDk734hd4hYfCRSVi6Q231kPSdMrQleU", - "5d5u5qHtiebBxQ2rexrlCuEA1/auBE6y5KDspnO646ejpq4dPCmca0ut6qUtx66I4G0XOoYsbwrndV9S", - "LDhprSJd5sTLJVoSEpWzNG5j5VNliINb35l5meDLPcKoGbFkPa5YXrJgLPPakJIyLSCDOaLIVNGqNjXu", - "psK12ik5+1cJhGXAtXkk8VS2DipWJ3HW9u51amSH7lxuYGuhr4e/jowRFltt33gIxHYBI/TUdcB9XqnM", - "fqGVRcr8ELgk9nD4hzN2rsQtznpHH46abfDioulxCzvjdPmfIQxbIn13Wx6vvLqqrz1zRNvsMJXMpPgd", - "4noeqseRPCFfXpZhlMvvEOYphM0lGiymsu7U3YLq2Xu3u0+6Ca1QzSCFHqrHnQ/ccljn0luoKbdbbbte", - "NGLd4gQTRpUe2fFrgnEwdyJxc3o5pbEioEbIMDCd1A7ghi1dC+I/9rhXVbKEnZ0EvuTqXWZzwAuQdQpf", - "t57MFQUGO+1gUaGWDJBqQ5lgbP1/uRKRYUp+SbltnmK+s0fJfa3AGr/MV5dCYgUHFTf7Z5CyJc3jkkOW", - "dk28GZsz2xekVBA0nnAD2Z5Llopc844qBcih5nRGHoyD7jduNzK2YopNc8A3Hto3plQhJ68MUdUnZnnA", - "9ULh648GvL4oeSYh0wtlEasEqYQ6VG8q59UU9CUAJw/wvYdfk7votlNsBfcMFt39PDp++DUaXe0fD2IX", - "gOvrso2bZMhO/sexkzgdo9/SjmEYtxt1Ek12t43d+hnXltNkPx1ylvBNx+t2n6Ul5XQO8UiR5Q6Y7Le4", - "m2hIa+GFZ7YrkdJSbAjT8flBU8OfeqLPDfuzYJBULJdML51zR4mloae6q4Sd1A9nWxy5gsAeLv8QfaSF", - "dxG1lMibNZra+y22avRkv6ZLaKJ1TKgt25GzOnrBlyknp74qEFZIrgojW9yYuczSUczBYIYZKSTjGhWL", - "Us+Sv5F0QSVNDfub9IGbTL96EqkK3axOyvcD/MbxLkGBXMVRL3vI3ssQ7ltylwueLA1Hye7V2R7Bqex1", - "5sbddn2+w+1DDxXKzChJL7mVDXKjAae+FuHxLQNekxSr9exFj3uv7MYps5Rx8qCl2aGf3r50UsZSyFip", - "v/q4O4lDgpYMVhi7F98kM+Y190Lmg3bhOtB/Xs+DFzkDscyf5Zgi8K2IaKe+UnllSXex6hHrQN8xNQ8M", - "GUzdUGPSrAp983z0MFFQcU+XN2x3HVvmiccD/tFGxGcmF9zA2pdvV9JDKEFV/CjJZNXzwMdOybdiPZRw", - "WqfQE88fAEVRlJQsz36uMz9bTQck5eki6jObmg9/rdujVYuzd2C0at+Ccg55dDgrb/7q5dKI5PxPMXSe", - "JeMD3233QbDLbS2uBrwJpgfKT2jQy3RuJgix2kyqq4K287nICM5Tl4irj2u3f0ZQ5fxfJSgdS1DCBzZw", - "DG2jhh3YItsEeIYa6YR8bzsgL4A06v+gJugLPTSzpssiFzQbYwGK8xcnL4md1X5jm/zYIt9zVISaq2jZ", - "xILql8NCkH2/nnh6xPBxtsdrm1UrnVQ1uWMJqOaNumo4a/kJUEUKsTMhz4NepjZX1Qxh6GHG5NJoddVo", - "Vj5CmjD/0ZqmC1T7Gqy1n+SHV6f3VKmCjpBVZ6eqJCSeOwO3K1Bv69OPiTC6+SVTtvEtrKCZ81olgDuz", - "g8+BbS5PlpxbSpnscctVBSD3RbsHzl6R3pUQhayF+D2FftvcYd9i/Wf4VbRCVbvyf6cVpM2grDr2+Ibm", + "Tpv16UKGEE7FlG8w0EVUley2C1enQPMfYPOLeReXM/o4Hl3OdBrDtRtxB67fVNsbxTO65q0preEJ2RPl", + "tCikWNE8cQbmPtKUYuVIE1/39ugbZnVxM+bpN0ev3jjwP45HaQ5UJpWo0LsqfK/4bFZli+z1HBBfwNzo", + "fF5mt6JksPlVZbDQKH2+AFcJOpBGOyUra4dDcBSdkXoWjxDaaXJ2vhG7xC0+EigqF0ltvrMekqZXhK4o", + "y73dzEPbE82DixtW9zTKFcIBLu1dCZxkyZWym87pjp+Omrp28KRwri21qpe2HLsigrdd6BiyvCmc131J", + "seCktYp0mRMvl2hJSFTO0riNlU+VIQ5ufWfmZYIv9wijZsSS9bhiecmCscxrQ0rKtIAM5ogiU0Wr2tS4", + "mwrXaqfk7F8lEJYB1+aRxFPZOqhYncRZ27vXqZEdunO5ga2Fvh7+MjJGWGy1feMhENsFjNBT1wH3ZaUy", + "+4VWFinzQ+CS2MPhH87YuRK3OOsdfThqtsGLi6bHLeyM0+V/hjBsifTdbXm88uqqvvbMEW2zw1Qyk+IP", + "iOt5qB5H8oR8eVmGUS5/QJinEDaXaLCYyrpTdwuqZ+/d7j7pJrRCNYMUeqgedz5wy2GdS2+hptxute16", + "0Yh1ixNMGFV6YMevCcbB3InEzen5lMaKgBohw8B0VDuAG7Z0LYj/2ONeVckSdnYS+JKrd5nNAS9A1il8", + "3XoyFxQY7LSDRYVaMkCqDWWCsfX/5UpEhin5OeW2eYr5zh4l97UCa/wyX50LiRUcVNzsn0HKljSPSw5Z", + "2jXxZmzObF+QUkHQeMINZHsuWSpyzTuqFCCHmuMZeTQOut+43cjYiik2zQHfeGzfmFKFnLwyRFWfmOUB", + "1wuFrz8Z8Pqi5JmETC+URawSpBLqUL2pnFdT0OcAnDzC9x5/Se6j206xFTwwWHT38+jw8ZdodLV/PIpd", + "AK6vyzZukiE7+btjJ3E6Rr+lHcMwbjfqJJrsbhu79TOuLafJfjrkLOGbjtftPktLyukc4pEiyx0w2W9x", + "N9GQ1sILz2xXIqWl2BCm4/ODpoY/9USfG/ZnwSCpWC6ZXjrnjhJLQ091Vwk7qR/OtjhyBYE9XP4h+kgL", + "7yJqKZE3azS191ts1ejJfk2X0ETrmFBbtiNndfSCL1NOjn1VIKyQXBVGtrgxc5mlo5iDwQwzUkjGNSoW", + "pZ4lfyPpgkqaGvY36QM3mX7xLFIVulmdlO8H+I3jXYICuYqjXvaQvZch3LfkPhc8WRqOkj2osz2CU9nr", + "zI277fp8h9uHHiqUmVGSXnIrG+RGA059KcLjWwa8JClW69mLHvde2Y1TZinj5EFLs0M/v33lpIylkLFS", + "f/VxdxKHBC0ZrDB2L75JZsxL7oXMB+3CZaC/Xc+DFzkDscyf5Zgi8LWIaKe+UnllSXex6hHrQN8xNQ8M", + "GUzdUGPSrAp983z0aqKg4p4ub9juOrbME48H/KONiFsmF9zA2pdvV9JDKEFV/CjJZNXzwMdOyddiPZRw", + "WqfQE88ngKIoSkqWZ7/UmZ+tpgOS8nQR9ZlNzYe/1e3RqsXZOzBatW9BOYc8OpyVN3/zcmlEcv6nGDrP", + "kvGB77b7INjlthZXA94E0wPlJzToZTo3E4RYbSbVVUHb+VxkBOepS8TVx7XbPyOocv6vEpSOJSjhAxs4", + "hrZRww5skW0CPEONdEK+sx2QF0Aa9X9QE/SFHppZ02WRC5qNsQDF6TdHr4id1X5jm/zYIt9zVISaq2jZ", + "xILql8NCkH2/nnh6xPBxtsdrm1UrnVQ1uWMJqOaNumo4a/kJUEUKsTMhL4NepjZX1Qxh6GHG5NJoddVo", + "Vj5CmjD/0ZqmC1T7Gqy1n+SHV6f3VKmCjpBVZ6eqJCSeOwO3K1Bv69OPiTC6+TlTtvEtrKCZ81olgDuz", + "g8+BbS5PlpxbSpnscctVBSD3RbsHzl6R3pUQhayF+D2FftvcYd9i/Sf4VbRCVbvyf6cVpM2grDr2+Ibm", "KeWCsxTrQ8WuaNchd4ifbUAprbYh1x9xd0Ijhyvab6AKxXNY7O1A4BmhQ1zX0B88NZtqqcP+qbEV64Jq", - "MgetHGeDbOzbZjhbI+MKXIlP7Kcc8EkhG75L5JBRd3hSuU32JCNMvelRHr8zz1470wLGpF8wjkqEQ5sT", - "/Kw1EBt4aqN5ME3mApRbTzP/WP1ivplgKm4G6/cT3/ATx7CuP7Ns6+fuDnXivd7Oy2zefWbedfWNqp8b", - "Uc520pOicJP2N1WJygN6zXsRHPFeJt59FCC3Gj8cbQu5bQ1XwfvUEBqs0NkNBd7DHcKoGoy0mlcZodVS", - "FL5BbJhYtEoC4xEwXjIOdTvayAWRRq8E3Bg8rz3fqVRSbUXAQTztHGiOHu4YQ1PauTeuO1S7upNBCa7R", - "z9G/jXVvlB7GUb1QC26Ub6ouuIa6A2HiGbbfdojsdjpBqcoJURlmLbR6n8QYh2HcvrtS8wLoHoOuTGQ/", - "15Lak7PPTdSXiDotsznohGZZrOLqt/iU4FOSlSg5wBrSsqrMWRQkxborzUI0XWpzE6WCq3K5ZS7/wjWn", - "C5oJRaghbGjkdxgTXaYb/DdWlrJ/Z1ygx96hhj6qw/Xh2FNubo7UkXoNTSeKzZPhmMA75froqKe+GqHX", - "3x+U0nMxbwJyw+UntnG5cI9i/O2FuTjC6gydWqv2aqmKJ2Bgn/AtIFFtrNJ+m1wJr7JO8VV0KFUt5rYb", - "IPqbxY3x8usJ7w2KblB7v1oPZV+Qb9obk061y47TlGxlQb0ZRzZCyOYWIRRx62xfVJANCjKPO18Pkww7", - "craO1y0MEOrDzboA/eBjWUlBmXO/18yii1kX9d7NQxgSD1tvcHsRLpa812L3w6ov7tsXY8Pn7WZSF+BS", - "5gsJKyZK79j2kU9eJbS/NlozVZH30fV3Da841ec1h/Yab89dUX+7TKeT//CzjZMjwLXc/AFMuZ1N77Sp", + "MgetHGeDbOzbZjhbI+MKXIlP7Kcc8EkhG75L5JBRd3hSuU32JCNMvelRHr81z1470wLGpJ8xjkqEQ5sT", + "/Kw1EBt4aqN5ME3mApRbTzP/WP1qvplgKm4G6/cT3/ATx7CuP7Ns6+fuDnXkvd7Oy2zefWHedfWNqp8b", + "Uc520qOicJP2N1WJygN6zXsRHPFeJt59FCC3Gj8cbQu5bQ1XwfvUEBqs0NkNBd7DHcKoGoy0mlcZodVS", + "FL5BbJhYtEoC4xEwXjEOdTvayAWRRq8E3Bg8rz3fqVRSbUXAQTztFGiOHu4YQ1PauTcuO1S7upNBCa7R", + "z9G/jXVvlB7GUb1QC26Ub6ouuIa6A2HiBbbfdojsdjpBqcoJURlmLbR6n8QYh2HcvrtS8wLoHoOuTGQ/", + "15Lak7PPTdSXiDotsznohGZZrOLq1/iU4FOSlSg5wBrSsqrMWRQkxborzUI0XWpzE6WCq3K5ZS7/wiWn", + "C5oJRaghbGjkdxgTXaYb/DdWlrJ/Z1ygx96hhj6qw/Xh2FNubo7UkXoNTSeKzZPhmMA75fLoqKe+GKHX", + "318ppedi3gTkhstPbONy4R7F+Ns35uIIqzN0aq3aq6UqnoCBfcK3gES1sUr7bXIlvMo6xVfRoVS1mNtu", + "gOhvFjfGy68nvDcoukHt/Wo9lH1BvmlvTDrVLjtOU7KVBfVmHNkIIZtbhFDErbN9UUE2KMg87nw9TDLs", + "yNk6XrcwQKgPN+sC9IOPZSUFZc79XjOLLmZd1Hs3D2FIPGy9we1FuFjyXovdD6u+uG9fjA2ft5tJnYFL", + "mS8krJgovWPbRz55ldD+2mjNVEXeR9ffNbziVLdrDu013p66ov52mU4n/+EXGydHgGu5+QRMuZ1N77Sp", "6kq71jxVv0KqetCD6kM3bsUhBQhjNfGcbNholLWjzVeXsQ4RB7ptu8Yjlu11YbavEhzGjhI7dvEmXP1l", - "p+pSU3jECqFYXZY91p1rYIjhOTbYCspmdcfy8T0rSDXW4q/jFiTAPkW0zGRBv8/b8lM96nQViemqTm0r", - "NdUtwL/jju9kgwUZjbZ4+WR4YaWTKjoN+TQWM54Ddy03m3keg6PNZzNINVvtyL77nwXwILNr7O0ytnV2", - "kIzHquhlLN6yv9WxBmhbctxWeIIiitcGpy/35gI2dxRpUEO0mvrYX7VXqduBGEDukBgSESoW/WENyc4h", - "z1RFGYgFH21lP4e6AlpvI6Ygl/SKc3mSNBdHnV+6Zcp4J5hBc5lP98q6xkDcvgS9biOJfv3jOfbtUFWT", - "RF/3I9TSyWm3OuKlqxuCuZKV78RXEAHlf/OJ0XaWnF1A2CoKPVWXVGb+jajpxVt1ki33USerzjdBaAM9", - "q2ZmdWxsN48qUm8LI6DTXBgxIukLI2+Go1axHHeUDbqx1dsx0NbANQPpWuqh/JsLBYkWPpZ2GxzbUGEj", - "i66EBNVb49IC11t55m1dWgdr/VKsNENdQFG4QCJhSQ10MiiA0z/nNmQ/s8994pCv9brTwlTR6+6eAT4q", - "mqkOEkOqnxF3W+5OSLqKsYlxbts2q1g1HA6y6Q0ppMjK1F7Q4cGoDHKDa01tYSVRO03aXWVLRwiyOi9g", - "c2SVIN9swe9gCLSVnCzoQRWF1iYf1PymYnDPDwLe57RcjUeFEHnS4+w47ZbwaVP8BUsvICPmpvDRgz2N", - "a8hdtLFX3uzLxcaXrCkK4JDdmxBywm28tndsN2tItybnd/S2+dc4a1baqlrOqDZ5x+OBr1jvSl6Tm/lh", - "tvMwBYbVXXMqO8iOAjHrnvJBkl5G2jhNhmrlXVdzu7VOTVQWiphMUneN2REnU4XI1I076jCZrnSQ5+Iy", + "p+pSU3jECqFYXZY91p1rYIjhKTbYCspmdcfy8T0rSDXW4q/jFiTAPkW0zGRBv8+78lM96nQViemqTm0r", + "NdUtwL/jju9kgwUZjbZ4+WR4YaWjKjoN+TQWM54Ddy03m3keg6PNZzNINVvtyL77+wJ4kNk19nYZ2zo7", + "SMZjVfQyFm/Z3+pYA7QtOW4rPEERxUuD05d7cwabe4o0qCFaTX3sr9qL1O1ADCB3SAyJCBWL/rCGZOeQ", + "Z6qiDMSCj7ayn0NdAa23EVOQS3rBuTxJmoujzi/dMmW8E8ygucyne2VdYyBuX4Jet5FEv/7xEvt2qKpJ", + "oq/7EWrp5LhbHfHc1Q3BXMnKd+IriIDyv/nEaDtLzs4gbBWFnqpzKjP/RtT04q06yZb7qJNV55sgtIGe", + "VTOzOja2m0cVqbeFEdBpLowYkfSFkTfDUatYjnvKBt3Y6u0YaGvgmoF0LfVQ/s2FgkQLH0u7DY5tqLCR", + "RRdCguqtcWmB660887YurYO1filWmqEuoChcIJGwpAY6GRTA6Z9zG7Jf2Oc+ccjXet1pYarodXfPAB8V", + "zVQHiSHVz4i7LXcnJF3E2MQ4t22bVawaDgfZ9IYUUmRlai/o8GBUBrnBtaa2sJKonSbtrrKlIwRZnWew", + "ObBKkG+24HcwBNpKThb0oIpCa5Ov1PymYnDPrwS827RcjUeFEHnS4+w47pbwaVP8GUvPICPmpvDRgz2N", + "a8h9tLFX3uzzxcaXrCkK4JA9mBByxG28tndsN2tItybn9/S2+dc4a1baqlrOqDZ5x+OBr1jvSl6Sm/lh", + "tvMwBYbVXXIqO8iOAjHrnvJBkp5H2jhNhmrlXVdzu7VOTVQWiphMUneN2REnU4XI1I076jCZrnSQ5+I8", "QSpKqvpfMZ3DvNdkkr7iaf2ZwfYUgngbqtwFuiELmpFUSAlp+EU8xcECtRQSklxg+E3MMzjTRh5aYlwz", - "J7mYE1EYNdeW0fM+lGhXmWAum2Zrv0yso6ankAEol1brprEvd+fZ0nxm/8Y254uIvQUR7bG8d/caRygD", - "ulG0uyBVYA4g0N22ppNYc57mutrtnfqarWmxZGkc3V9WlElvbMiO1kOR9VXk6Doj+azAHlxFXbbbPaS2", - "jdx0qJ+0qpk88FgEAPR7ThswDPKf7gvGDNsyJjSC5NNKah03uuay1tn39ewsjafUaq0LIGbsUoLLUrP9", + "J7mYE1EYNdeW0fM+lGhXmWAum2Zrv0yso6ankAEol1brprEvd+fZ0nxm/8Y2p4uIvQUR7bG8d/caRygD", + "ulG0uyBVYA4g0N22pqNYc57mutrtnfqarWmxZGkc3Z9XlElvbMiO1kOR9VXk6Doj+azAHlxFXbbbPaS2", + "jdx0qJ+0qpk88FgEAPR7ThswDPKf7gvGDNsyJjSC5ONKah03uuay1tn39ewsjafUaq0LIGbsUoLLUrP9", "41qNbwqqF/4WM693dUujp4DCFDLb/oMqawnxFhnXvK4tHogiyWEFDYeyS50r0xSUYisIG9/Zj0kGUKB9", - "si01xzylIZdriVJu7UngaxuC3ahsZRFrd4rsEJyiYt6aJ/aYqKFHyUC0YllJG/hT12gl1tdFLMKGPawD", - "OcXeTCK+uG0sYmdsA9J89FzyeGhDmLlZGUVwtqwynloirE+2Kugl71ciInanyt9+/XUQHIyoViZ175Uv", - "q125qgLZSxnbCKPT/i8qcyjw7VvDoide3HLfRmQsa+piKjIAU/V5xug9qKPDgteWdEMyNpuBtMZ8pSnP", - "qMzC1xknKUhNmdFsNurqYq2BVpYw3inZGu6Kg3oGE5Nx0S5lAck3TmW4htSJnpuIxGmvWi36Ohx2diWe", - "TkDXRrrGuKoeInCJ0Chb2wMmOApIZEkvYM95FPsdtk+D5Umc7U8LnHXIFDFf6xVrqw1i3d0whMjtFvQy", - "3O4ZCksv1jld0kazoCXZX5BtGn9VX5zDuir6D3aAFzoMg76K3nbjwPnMyVGvKqQES3nfRwmN5e/yQboF", - "1pJGsEWOEWgNthCuDahv7kvgYFbPKr9tXwvQtnsX6ywKbpv8ddzCljfZrn0B4ZizIFc0v3nXLhbgPEF8", - "QPa23xgc+gZDJFtUqqtlJrykg+YO/ICHm5q/QVf0/4DZo6hW6oZyIkwl1vtgHrxZaG4NFzPfwmsFnFzi", - "mDaO7eFXZOoytwsJKVNt0ejSd9eoXGHYbMplg6z1Dt/brnX+LPQ1yHjmNQ3yuq7Ujzr+nNcQ1kf0MzOV", - "npMbpfIY9XXIIoK/GI8KS6jtuC4uGgFutvNJK3NDSDhwoFsQsr5noFu3ONzQ5dlgLnPplAq66xx8Wzdw", - "G7mo67UNjdLsIndbOfchwZXxLg3mc4zutAjBFicEQSW/PfyNSJhhD0NB7t/HCe7fH7tXf3vUfGyO8/37", - "UensxuI6LY7cGG7eGMX83JfpZ7PZepJKW/tRsjzbRRiNFOG6Cygmwf7qChF8lj6kv9pYk+5Rdb3grhEg", - "ZxETWWtj8mCqIPl3QN6v+yyS5Yt+nLSUTG+wPqK3H7BfoxGo31fRTC4artIP3d2nxQVUFTbr2KdS+dv1", - "e0FzvI+s2srNLSTyCXmxpssiB3dQvrkz/U94/Lcn2YPHD/9z+rcHTx+k8OTp1w8e0K+f0IdfP34Ij/72", - "9MkDeDj76uvpo+zRk0fTJ4+efPX06/Txk4fTJ199/Z93DB8yIFtAR74az+h/sVlvcvLmNDk3wNY4oQX7", - "AVyzZ0PGvuMgTfEkwpKyfHTsf/p//AmbpGJZD+9/HbliH6OF1oU6Pjq6vLychJ8czTHYIdGiTBdHfp5O", - "S8KTN6eVl8hagXBHbZ6st+55UjjBZ29fnJ2Tkzenk6Dd/PHoweTB5CF2Jy+A04KNjkeP8Sc8PQvc9yNH", - "bKPjDx/Ho6MF0BxjA80fS9CSpf6RBJpt3P/VJZ3PQU5cG0bz0+rRkRcrjj64oI+PZoaoPm1TxIO84G53", - "QhdAhsYomwLe6PajXPOZcdUDyhkbeYaZuzaOwrC5CnGnWd3s4LRmWr7ko62BffxLJBB3xuZoevCVCBsd", - "Il2DOKbIf5/9+JoISZx684amF5XfgpzObPkuKVYME0KzIIvYfDnx9PuvEuSmpi/H+cL6zr6lj3OALNW8", - "aOak1VJVzEcT6wSJMxuyCAi7CtGqGReaaML+uRUbNqz1QfL1+w9P//ZxNAAQjBdUgNW/fqN5/hu5ZNhQ", - "EO2Lvn6mq482jrSvQWl6XIf84Af1To4xqa56GnYorN5ppnL/xgWH3/q2wQEW3Qea5+ZFwSG2B++xPhUS", - "C565Rw8eHKy1aVW9wHppqlE8SVxhoC5Dso+qFqmXkhb2LPoOp+joRlXYLxQbuj454EKbuUfXXm57uM6i", - "v6UZdo0Dpe1SHn6xSznlGLJrLghiL8CP49HTL3hvTrnhOTQn+GZQ/LF70fzEL7i45P5NI/yUyyWVGxRt", - "gtaWrcoodK4wHgRZpD3bjWZ2o/cfe2+9o7BX19GHRtRndq07sdOm8PT5jmvyjurjnN3S6a1WYOZ51ekJ", - "4wJdvzPsPaXuTcj34dfIvbESma3zVUoOmQ/a9LdeVVrVF2ytYbujwiJt0Us7MBff3t+f+/4+aRo7GuW5", - "Y8A0TsFWmDpeheteoF1XaavZ85WaKQdNua7Q2uSTdpxs6Zp2pvcxVXAno77FXQ/u+sSkAN5KYmo2U/v0", - "rNknCVY3SePK+ISM+wsX+l7R3NBJsNxWMR5bs/5WGPzLCINVMtHcSmeuTcv1xENs2Hj0wfchOIBI6Pow", - "DBAGQ7U6+DaI+rjbYif3JrapQPjO1XiGyx7aKeZhd4hbAe8PIOB1O6/EwKj7aXw+oQ5hWNStWXZ2gfFN", - "Vdq99PdqIfOFSnF/YWT1im0G0t0C2xXYZ0cYc8z6k7HVP6UQ5pB2K379pcWvKqf3WgJYo3eSyxIP3FjX", - "st61rXNMV5JYM6874GwYUm0YijvC47rPo2ExWBbTV0RTY68ZojvVKo12s8YdvbErYn0PoYL67eb0+S7p", - "6guy8wwuzxy5BeJ786l5adTt8PZm3A7DeNOTB09uDoJwF14LTb7DW/wTc8hPytLiZLUvC9vGkY6mtjnF", - "Nq7EW2wJGUXddCLgUVVBi3Hw3LxtozTuus7qYUGvexPiW2GoqrGXS+yaC8OofE4JlXP7keF1Bhnkjv/z", - "GMe/MyHfYUKPVmMMNtOu6xO5w7g+fvjo8RP3iqSXNpar/d70qyfHJ998416rG59YPafzutLyeAF5LtwH", - "7o7ojmseHP/vP/5vMpnc2clWxfrbzWtbAfiPwlu76l1IAH279YVvUkxb9708dqHuRtz334p19BYQ69tb", - "6LPdQgb7f4rbZ9okI6eIVpbMRo2gA95G9pjscx+NfZMPw3eqy2RCXgtXrq3MqSRCZiBdJ8R5SSXlGiCb", - "eErFpFNly1OlOQOujeKIvd1kolgGtsrNvJRQpc8VElYYI4/To07fgGA3o8dI2j8sk39F10EJp2l1TWvh", - "loxmzyVd++6S2D9NSPzpm2/Ig3GtveS5GSCpEBNjrku6Ht2g1a8itkHx583mTDsDdHHsIRakWvqpcmjD", - "TjB/bc79xUrultzdxh6Ic+7t+KkdO6EdwRVF22pBsIKd7T2JzRA3dTa+kfK8CBVncWaGocaBP7CPYKdp", - "OqqEttF7e4hvjQDXYiVtgtqTbWDWqTr6gHp5yDM65xaz5v5a7tLAdyTF0juPBJmBThcuYbeF+gh78q2h", - "+nnTtubjh5ZqcBe7VS/CmtTYFHtg2bMglxIdeCAjRPyj79JgHrOZLTDjCzL5HvvommK+7WzVcdb15Xal", - "JrSo8nrNLu4F5bN68q5Ahmg5hP/zFsH7IbjDHF/41qOIMbeIP0PEv1clE/Ja1GnjruvVn9H1+Clv9k+9", - "oNeCg/WxG8nX0uKtO7USOwzjsEjx9UKs/lL1H7myCHLkm+hulUP+blvYbpVFhtzeZrIv8gr/u8PSllvG", - "rG2ysxhCPdoQ5mxetFWwmh0xPqMW81n46R9QtfkcHOtmWAweUs9nnFjAD8t0sASPJeajqhlCHweK95cZ", - "zI20qMLQoi1hppALPld/TFa0tdNPFC8RKqk678Tb6/z1zu4zrO5jVF4bAenqPSnGU7BNorG/HVNkyZRy", - "wZJPHvzt5iDUbOkrivMwd/Uzc5enDx7f3PRnIFcsBXIOy0JIKlm+IT/xqqH3dbgdNg+q6q95a3C0XxR6", - "m5p1wdKwiNHVmWAjdO2DXrPs425mGFSs3JMPMh7wwbC8IS0KoPLqDHC366pdbvv0eRgd3OhpU1XUioBi", - "ULRngPx/jAbanTDtXczc5VdyC6iv/uXYhAvdFbNxFRxjpAAxOybv+H2iFvTpw0e/Pnr6lf/z0dOveixn", - "Zh5XtKdrO6sHMo/tMEMMaF+0OfCwUnuF3+Ob3u39NnE8Ytk62vWi7mPXKXrtxLI7ihR009sap9jRhy8c", - "tu7Jd/PFDpVm00VUv/LqT1VW/pR/W2nBtiKfa19323+vJ3ki4DOG0OpGfBXWt/fk2yJNtsiyan5208pp", - "nWRgLzqPPNm6cz6roKs/l5KaoI4K3As2TbR8PpkSO7OMA3d3IYUWqcht7EpZFELq6nSrySBxD/rcdg1p", - "r49w9xLmUqrTRVkcfcD/YIWvj3XigW3AfqTX/AirRx99sCECzce1GzD6e/15+MZqKTLwsqSYzVzqVjz2", - "AHSzopzRK5Smy4LYL6MhA8hjz9kSzsybP9opDno712C37uYWeGZ7FKSCZ2oAr3OjDmFiVXZYqxe07gfg", - "xvlatQMeFhfUNbmy5vM2iAzvUAJpI19hJUCfwuaQkcGKLF1j1T1OS5Rsjz7Yf9EPXohY+6UzT8Cdjbnr", - "tsXm5NlxGwCSNyi/2uQ+/5WYkQc2Na/k6H+rS/5iu2+5Mferj0SWQHOSNuzuFRzdk3PWe3J2amid1fWs", - "Ka4XifqEHtJI1fJ5/nDjB+AZ5Y7kuwjSglDCYU41W4G3Rk9u4+SubBRxUWpbGOCY0Cyzp7HeBFiB3BBV", - "ThX8q6ylDGs+uaOa52UPhgHrAiRbAtfYldf9arunH9kguG1mkjP7xjUvrRYvsqF3rU5C/mZ1gXliRl6x", - "VIoT7JfntDW1URqWnYK67tNfe1KpffXrrmYneM44JEvBY2Vef8Snr/BhtEuK0DTv+xg7P/V927pvm/C3", - "wGrOM+ROvi5+/yCn/1oemNZqJRgpuG5pbul/z6PkD82Gp92TtOFpVwxt9FXv+fnoQ+PPpCHfqkWpM3EZ", - "fIv2UyvKD4l+C9pPDHc7VybFVhsHRTJQhmi/PB9PgIfYiameRgp6Bk1Gemt6/kW9PjPGsxaRoEEmFSvs", - "yRQ6Om9dP38u18/gfd+Lx9oC1rs4WqkOK5G8FhnYcZv142NVF7jIwNXZ7goilQkjbi73t1L9XsuAmdJy", - "vtAEW43GTKX1hwlNLZNNrHqzq1W9U4Jcr8EVEJpj9XIyBeBETM2i6/sRF0kVZpp5e6sz1MQ7rtdwFVKk", - "oBRkia8ysQu0qno5Wmf1Fjwh4AhwNQtRgsyovDawF6udcFbdPxS5+8PPRmG+cXitKLgdsTa/JYLeKobW", - "SXtdqIdNv43g2pOHZEclEC8aoHtILIscnIMogsK9cNK7f22IOrt4fbSgB4V9Yor3k1yPgCpQPzG9Xxfa", - "skjM/d0F8Zl9es6WKIlxyoW3K0Y7aFKlk11sGTtwBWtRZgUBJ4xxYhy4R+F8SZV+62IFwp62QacvM0U/", - "wKu+LjNm5J+rHjOdsVNzH3JVqqoRjbP/x/vKclhvmes1rKu5MFjDj105GKyFb9fIfVgKxnfICkptEKqD", - "KAvsw9VdHNofqTNQdFHZAKJGxDZAzvxbjYbJdQRADyBM1Yiuurs2KSforqm0KArs2ZyUvPquD01n9u0T", - "/VP9bpe4XC9AvLczASp0/jjILy1mbavUBVXEwUGW9ML5h+audGIXZnMYE4zrSrZRPppszVvhEdh5SMti", - "LmkGSQY5jZhSfrKPiX28bQDccU+eyUpoSKYwi/b1MpteU7LsNRFVQwscT8WER4JPSGqO4AwbzXkCcV/v", - "GDkDHDvGnBwd3amGwrmiW+THw2Xbre5rSL4S6BF09IAgO44+BOAePFRDXx0V+HFSmw/aU/wDlJugkiP2", - "n2QDqm8J9fh7LaBtzgsvsMZN0WLvLQ4cZZu9bGwHH+k7sjED4hdp7G+HPX3CmO6mATVQACdXUW6PLinT", - "yUxIK0gndKZBRmx5rdZAlGmfc29dA1q4iEOCI7h7042DTD4sYOW4iAWB+Cb1bBmph2em+k7IQYmzzfBw", - "yjQpuWZ5UDykUpX/eAbDWyPArRHg1ghwawS4NQLcGgFujQC3RoBbI8CtEeDWCHBrBPjrGgE+Vyp84iUO", - "nyDEBU/aUYnkNirxT5U6Wt1V3iiBZoxLyrSrhU2olwPwyfUy5zXQHHHAcuiPk7bhm+cvTl4SJUqZAkkN", - "hIyTIqdGN4C1riqzNmt++24EtryzLSdOFTx+RM7+fuIz3BYuE6v57t0T19VD6U0O91zto6rhuS+CBNwg", - "3dVAov5O8BVcXT1blmOMuSIv8O3nsIJcFCBt8gzRsoyYfM6B5s8cbnZYfBo9qs1ov40bhiaHtiUtvJzv", - "10oVoTbtsdliekZz1d9j2o63pEWsiGp181lbEHKTbwX2ZA9PiNm1I9zA5tmo89wYp3ITyXHtJhO0SUML", - "w68cYXWNWR8Pno3ZJdoume2isJi4LkFFz/E2Ko+mIVYb1hnKZsvOWnQyipUva+fejSoAh4TAnmPCgd0T", - "8tZ+93lrvSBE7ojVzPwPEznYfLNiGviu0SIc6/lSo/I94qOnF8/+2BB2VqZAmFbEJ3Tuvl7Go3ViRpoD", - "TxwDSqYi2yQN9jVq3EIZU1QpWE5330Qh/3RtA9zlY55sv6c+zzXyPFjcNp4cEs06cQy4hztvNAzmzRW2", - "cETHngOMf2oW3cdGQxCI408xq1K7adueTK+eZnPL+G4ZX3AaWxIB4y4Bvs1EJp+Q8cmNLHk/z3uxhrQ0", - "wIUn+S6a59EnB2vdcGxmMC3nc2x/0HHSmaUBjscE/0ys0C53KBfcj4Ls4FVJ7OtWYWwP1+UuQbL3XSEJ", - "5lPfs/0e+Qa9GcuC8o33+UKi2LLMLQ5t5djDMlqbo96NBEB/rDP+9Zm133ibX2C8dVdt83eLFnJJFbH7", - "CxkpeeZyhzqVLNZ8eOsFO/T5mtdsemvzBbveyOrcvEOuCL/LzaRtRQqQiV5ze6Ca/VFsxQx7cie3Zd//", - "GteGTfmGHgbbrf5QM4QD3R4y4Gt4fQQ1vupkuGbTSttSty91JCz4Zd88aPRIZ/hmEEnQ0NY6SSEvCPU9", - "eVLBlZZlqt9xik6aYGGTboCJt0b387dn/pW4nzDixnNDveMUW7ZUrpson5tBxE/xHYBno6qcz0EZXhkS", - "yQzgHXdvMU5KbjQtMSNLlkqR2ERUc4aMfDKxby7phsxojl7G30EKMjU3e7Dr1mCsNMtzF9FipiFi9o5T", - "TXKgSpNXzHBZM5wvPlCFcoG+FPKiwkK8/tMcOCimkrjx5Xv7FEssueV7Ix8aLO3jujTKzdZW8rCzrBfy", - "0+cGborV5HKmdB0E0YH9xhzgS8aTKJGdL4C4mLA2bZG7WGzNEdC9pndIL+AdNzecFgS5OtVXI4e2m6dz", - "Fu3paFFNYyNa3iC/1kEq3kG4DIkwmVvXyp8oNTOgA+++xI3HGi7tvd/TjbK1T3TsqSvJ2fOSUxIahrBW", - "ORj3xnkD5D9vO5f3n0Zf9Gg8mMbYHbDLrppFFxFvfsPHhOaCz8kl0wvUIAXuE+NFqTGw+lMa6WBF80Ss", - "QEqWgRq4Uib4ixXNf6w+23EBBiVjl0vIGNWQb0ghIYXM1sliitRK8sRWGiDpgvI53pVSlPOFfc2OcwkS", - "quqaRi9tDxGvU7LmCQrnkWCHE2INjP5IGHaB/YzDyxU/tjeKUYT9DtoyEENU3cgR/t6M2af5jke9kq1B", - "6qoOSLPIaZ7rAdd24wIO8FNPfIieOrdUdktle1NZrMQeom7W0rktvsJt+fM0G/pT9hX6c/Xg+ZTC8qde", - "zaeSvT0HUoRio/fwuLbK6NrTSxVhmlxiQZ0pEHNhlGhjdo1CnEY6IYYhBfZ0W3lRufrV6YIy7qqxVOH5", - "CId2Nfa1L+r7ScxzlpmhXc6gA9JSMr1BuZwW7NcLMP9/bwRb23naiuylzEfHo4XWxfHRUS5Smi+E0kej", - "j+PwmWo9fF/B/8FL24VkK+zd9v7j/x8AAP//iN/ientcAQA=", + "si01xzylIZdriVJu7UngaxuC3ahsZRFrd4rsEJyiYt6aJ/aYqKFHyUC0YllJG/hTl2gl1tdFLMKGPawD", + "OcXeTCK+uG0sYmdsA9J89FzyeGhDmLlZGUVwtqwynloirE+2Kug571ciInanyt9++XUQHIyoViZ175Uv", + "q125qALZSxnbCKPT/i8qcyjw7VvDoide3HLfRmQsa+piKjIAU/V5xug9qKPDgteWdEMyNpuBtMZ8pSnP", + "qMzC1xknKUhNmdFsNuriYq2BVpYw3inZGu6Kg3oGE5Nx0S5lAck3TmW4hNSJnpuIxGmvWi36Ohx2diWe", + "TkDXRrrGuKoeInCJ0Chb2wMmOApIZEnPYM95FPsDtk+D5Umc7U8LnHXIFDFf6wVrqw1i3d0whMjtFvQy", + "3O4ZCksv1jld0kazoCXZX5BtGv+xvjiHdVX0H+wAL3QYBn0Vve3GgXPLyVE/VkgJlvK+jxIay9/lg3QL", + "rCWNYIscI9AabCFcG1Df3JfAwaxeVH7bvhagbfcu1lkU3Db567iFLW+yXfsCwjFnQa5ofvOuXSzAeYT4", + "gOxtvzE49A2GSLaoVBfLTHhFB80d+AGvbmr+Bl3RfwezR1Gt1A3lRJhKrPfBPHiz0NwaLma+hdcKODnH", + "MW0c2+MvyNRlbhcSUqbaotG5765RucKw2ZTLBlnrHb63Xev8RehLkPHMaxrkdV2pH3X8Oa8hrI/oLTOV", + "npMbpfIY9XXIIoK/GI8KS6jtuC7OGgFutvNJK3NDSLjiQLcgZH3PQLducbihy7PBXObSKRV01zn4tm7g", + "NnJR12sbGqXZRe62cu5DgivjXRrM5xjdaRGCLU4Igkp+f/w7kTDDHoaCPHyIEzx8OHav/v6k+dgc54cP", + "o9LZjcV1Why5Mdy8MYr5pS/Tz2az9SSVtvajZHm2izAaKcJ1F1BMgv3NFSK4lT6kv9lYk+5Rdb3gLhEg", + "ZxETWWtj8mCqIPl3QN6v+yyS5Yt+nLSUTG+wPqK3H7DfohGo31XRTC4artIP3d2nxRlUFTbr2KdS+dv1", + "O0FzvI+s2srNLSTyCflmTZdFDu6gfHVv+p/w9G/PskdPH//n9G+Pnj9K4dnzLx89ol8+o4+/fPoYnvzt", + "+bNH8Hj2xZfTJ9mTZ0+mz548++L5l+nTZ4+nz7748j/vGT5kQLaAjnw1ntH/YLPe5OjNcXJqgK1xQgv2", + "A7hmz4aMfcdBmuJJhCVl+ejQ//R//AmbpGJZD+9/HbliH6OF1oU6PDg4Pz+fhJ8czDHYIdGiTBcHfp5O", + "S8KjN8eVl8hagXBHbZ6st+55UjjCZ2+/OTklR2+OJ0G7+cPRo8mjyWPsTl4ApwUbHY6e4k94eha47weO", + "2EaHHz6ORwcLoDnGBpo/lqAlS/0jCTTbuP+rczqfg5y4Nozmp9WTAy9WHHxwQR8fzQxRfdqmiAd5wd3u", + "hC6ADI1RNgW80e1HueYz46oHlDM28gwzd20chWFzFeKOs7rZwXHNtHzJR1sD+/DXSCDujM3R9OArETY6", + "RLoGcUyR/z756TURkjj15g1Nzyq/BTme2fJdUqwYJoRmQRax+XLi6fdfJchNTV+O84X1nX1LH+cAWap5", + "0cxJq6WqmI8m1gkSZzZkERB2FaJVMy400YT9cys2bFjro+TL9x+e/+3jaAAgGC+oAKt//U7z/HdyzrCh", + "INoXff1MVx9tHGlfg9L0uA75wQ/qnRxjUl31NOxQWL3TTOX+nQsOv/dtgwMsug80z82LgkNsD95jfSok", + "FjxzTx49urLWplX1AuulqUbxJHGBgboMyT6qWqSeS1rYs+g7nKKjG1Vhv1Bs6PrsChfazD269HLbw3UW", + "/TXNsGscKG2X8vizXcoxx5Bdc0EQewF+HI+ef8Z7c8wNz6E5wTeD4o/di+ZnfsbFOfdvGuGnXC6p3KBo", + "E7S2bFVGoXOF8SDIIu3ZbjSzG73/2HvrHYS9ug4+NKI+s0vdiZ02hccvd1yT91Qf5+yWTm+1AjPPq05P", + "GBfo+p1h7yn1YEK+C79G7o2VyGydr1JyyHzQpr/1qtKqvmBrDds9FRZpi17agbn47v6+7fv7qGnsaJTn", + "jgHTOAVbYep4FS57gXZdpa1mzxdqphw05bpAa5Nr7TjZ0jXtTO9jquBORn2Hux7c9YlJAbyVxNRspnb9", + "rNknCVY3SePKuEbG/ZkLfT/S3NBJsNxWMR5bs/5OGPzLCINVMtHcSmeuTcvlxENs2HjwwfchuAKR0PVh", + "GCAMhmp18G0Q9XG/xU4eTGxTgfCdi/EMlz20U8zD7hB3At4nIOB1O6/EwKj7adyeUIcwLOrWLDu7wPim", + "Ku1e+nu1kPlMpbi/MLJ6xTYD6W6B7QLssyOMOWZ9bWz1TymEOaTdiV9/afGryum9lADW6J3kssQDN9al", + "rHdt6xzTlSTWzOsOOBuGVBuG4o7wuO7zaFgMlsX0FdHU2GuG6E61SqPdrHFHb+yKWN9BqKB+vTl+uUu6", + "+ozsPIPLM0dugfjeXDcvjbod3t6M22EYb3r26NnNQRDuwmuhybd4i18zh7xWlhYnq31Z2DaOdDC1zSm2", + "cSXeYkvIKOqmEwGPqgpajIPn5m0bpXHfdVYPC3o9mBDfCkNVjb1cYtdcGEblc0qonNuPDK8zyCD3/J+H", + "OP69CfkWE3q0GmOwmXZdn8g9xvXh4ydPn7lXJD23sVzt96ZfPDs8+uor91rd+MTqOZ3XlZaHC8hz4T5w", + "d0R3XPPg8H/+8b+TyeTeTrYq1l9vXtsKwJ8Kb+2qdyEB9O3WZ75JMW3d9/LYhbobcd9/LdbRW0Cs726h", + "W7uFDPb/FLfPtElGThGtLJmNGkFXeBvZY7LPfTT2TT4M36kukwl5LVy5tjKnkgiZgXSdEOcllZRrgGzi", + "KRWTTpUtT5XmDLg2iiP2dpOJYhnYKjfzUkKVPldIWGGMPE6POn0Dgt2MHiNpP1km/yNdByWcptU1rYVb", + "Mpo9l3Ttu0ti/zQh8aevviKPxrX2kudmgKRCTIy5Lul6dINWv4rYBsWfN5sz7QzQxbGHWJBq6afKoQ07", + "wfy1OfdnK7lbcncbe0Wcc2/HT+3YCe0IrijaVguCFexs70lshrips/GNlOdFqDiLMzMMNQ58wj6Cnabp", + "qBLaRu/dIb4zAlyKlbQJak+2gVmn6uAD6uUhz+icW8ya+2u5SwPfkRRL7zwSZAY6XbiE3RbqI+zJt4bq", + "503bmo9ftVSDu9itehHWpMam2APLngW5lOjAAxkh4p98lwbzmM1sgRlfkMn32EfXFPNtZ6uOs64vtys1", + "oUWV12t2cS8oX9STdwUyRMtV+D/vELwfgjvM8RvfehQx5hbxZ4j496pkQl6LOm3cdb36M7oer/Nmv+4F", + "vRYcrI/dSL6WFu/cqZXYYRiHRYqvF2L1l6r/yIVFkAPfRHerHPK9bWG7VRYZcnubyT7LK/x7h6Utt4xZ", + "22RnMYR6tCHM2bxoq2A1O2LcohZzK/z0E1RtboNj3QyLwUPq+YwTC/jVMh0swWOJ+aBqhtDHgeL9ZQZz", + "Iy2qMLRoS5gp5ILP1afJirZ2+oniJUIlVeedeHudv97ZfYHVfYzKayMgXb0nxXgKtkk09rdjiiyZUi5Y", + "8tmjv90chJotfUVxHuau3jJ3ef7o6c1NfwJyxVIgp7AshKSS5RvyM68ael+G22HzoKr+mrcGR/tFobep", + "WRcsDYsYXZwJNkLXPug1yz7uZoZBxco9+SDjAR8MyxvSogAqL84Ad7uu2uW2j1+G0cGNnjZVRa0IKAZF", + "ewbI/8dooN0J097FzF1+JbeA+upfjk240F0xG1fBMUYKELND8o4/JGpBnz9+8tuT51/4P588/6LHcmbm", + "cUV7urazeiDz2A4zxID2WZsDr1Zqr/B7eNO7vd8mjkcsW0e7XtR97DpFr51Ydk+Rgm56W+MUO/rwhcPW", + "Pfluvtih0my6iOpXXv2pysof868rLdhW5HPt6+767/UkTwR8xhBa3Yivwvr2nnxbpMkWWVbNz25aOa2T", + "DOxF55EnW3fOrQq6+raU1AR1VOBesGmi5fZkSuzMMg7c3YUUWqQit7ErZVEIqavTrSaDxD3oc9s1pL0+", + "wt1LmEupThdlcfAB/4MVvj7WiQe2AfuBXvMDrB598GFriACCGGkNa+XSaEuFrpo8oDPtrhCA1okZtw+R", + "rYSNsQQR+ex6pLO/tFCzX6ffy5q0IyN2DnCVVxd00a5oNyj8vaO58eTOBfOJLag2iswYzwgNtrGluwlZ", + "M4JrNoxc96Jvw85y836n55/xOXstNDleFrZhDmSXi94hbQ7nb4+t1+1+goG7+rshPt07P7zxfWBiZV3f", + "ecHv4ZALUrHBT0cl5kabu/p6bN93N/mnfZO/8CWHG2R4dy9/Pvey9OGUd1fwp38FP/1sV3ONjpiBV7K/", + "iS58Ddea+J4XcqRLKJoMWq7wbX4aVL3bq1TfCunbW9zd4p/NLT4oOWmIJSaWsrTzFF/j7MP0/DyPaPp9", + "B2Vse+3oBTAs+iJShvW7jzM1tofIGQfcKboTPD5pwSPY6zu54071/8xU/x4pw2ndzSapfRf9vgLIaiky", + "8FEfYjZzRdb6pI9m7xdDnkrTZUHsl1EpA72hp2wJJ+bNn+wUV+pHr8FuiSUt8AyyFKSCZ2qAV9KNOsTd", + "GLuH0I3aD8CNeyCrHfCwuPTryYVJ9m1Qw6VDCaSNfIU9e3yxOYeMDFbEEODkCsj24IP9F81ZhVCxrsee", + "gDsbc99ti62eZ8dtAEjeoBBoy/D5r8SMPLJF9EqOmTJ1cz7KM6LlxgiKvmaIBJqTtBEhX8HRPTknvSdn", + "pyjeWV3PmuKyuKhP6FWGk7ayk3648QPwgnJH8l0EaUEo4TCnmq3Ax41P7jLaL3ybuXzyLQxwTGiW2dNY", + "bwKsQG6IKqfKyDq8Geh4TzXPyx4MA9YFSGauaJrXDnCrJhzYdPVtAY0n9o1LXlotXmST5Fs9//3N6lLo", + "xYz8yFIpjvK5UD6uSm2UhmWn9Z379Leeoqdeke/GYAmeMw7JUvBYQ7af8OmP+DDaz1xomvd9fGoe9n3b", + "um+b8LfAas4z5E6+LH4/kdN/qVyJ1molFEJq3yQfnJq851Hyh2bD0+5J2vA0cCq5h8FAYfu2xs8HHxp/", + "umIV7k21KHUmzoNvUbO3QTdD8tSDRtEXsGS1Gi6r67VlXacPJ8BD7MRUTyOtt4J24L3dt/6i+RnO5RES", + "CYZOpmIFUrXUs7skjT9Vksbgfd+Lx9pWk7s4WqmuViJ5LTKw4zY7vcbqI3ORgeuI2RVEqmDDeGC7v5Xq", + "91qhxikt5wtNyoJoEQtqrj9MaGqZbGLVm/iEQUUyqwThdAu6AkJz7DNKpgCciKlZdH0/4iKpwppwPjLa", + "hVRGRaEArkKKFJSCLPH1oHeBVvUZxThqvQVPCDgCXM1ClCAzKi8N7NlqJ5xVn25F7v/wi1GYbxxeKwpu", + "R6ytRBVBb1Xtwkl7XaiHTb+N4NqTh2RHJRAvGmAih1gWObhUjggK98JJ7/61Iers4uXRgrkO7Jop3k9y", + "OQKqQL1mer8stGWRmPu7C+IL+/SULVES45QLb1eMDZZTpZNdbNm8FK5FmRUEnDDGiXHgHoXzFVX6rcvq", + "y7ACjL1OcB4rY5sp+gFe9fWDNyP/UnWD74ydmvuQq1JVLeNdpD5ksTVwWG+Z6zWsq7kwrdKPXaUCWAvf", + "rpH7sBSM75AVFMUmVAfedDNcZHFof6TOQNFFZQOIGhHbADnxbwXYDd3oPYAwVSPaEg4W+QwpZypEDpTb", + "jCpRFIZb6KTk1Xd9aDqxbx/pn+t3u8RFdX1vZwJUmKbhID+3mFVooF1QRRwcZEnPXCbH3DU56sJsDmOC", + "GdjJNspHk615KzwCOw9pWcwlzSDJIKcRU8rP9jGxj7cNgDvuyTNZCQ3JFGZCQnzTa0qWvSaiamiB46mY", + "8EjwCUnNETTKc00g7usdI2eAY8eYk6Oje9VQOFd0i/x4uGy71T1mKTOG2XFHDwiy4+hDAO7BQzX0xVGB", + "Hye1+aA9xT9AuQkqOWL/STag+pZQj7/XAtrmvPACa9wULfbe4sBRttnLxnbwkb4jGzMgfpbG/nbs0DVW", + "X2kaUAMFcHIR5fbgnDKdzIS0gnRCZxrkzoD0v1Pm3eHONaCFqw1AcAR3b7pxkMmHrSYcF7EgEHddGBLp", + "+t/MVN8KOajEZbOQC2WalFyzPCjzXanKn57B8M4IcGcEuDMC3BkB7owAd0aAOyPAnRHgzghwZwS4MwLc", + "GQH+ukaA2ypam3iJw5fy4oIn7ahEcheV+Kcq8ljdVd4ogWaMc8q061rp8+3dk8vVuNVAc8QBy6E/TtqG", + "b55+c/SKKFHKFEhqIGScFDk1ugGsddVDrdmd0/cNto0YbeNPquDpE3Ly/ZGvRbdwNdOa794/cv23ld7k", + "8MB1KQCeWVHUtysAbpDuuhVQfyf4Xmuu8xzLMcZckW/w7ZewglwUIG2ZK6JlGTH5nALNXzjc7LD4/N1M", + "7oJWfzej/T5uGJoc2pa08HK+XytVhNrcQfIyyCb8fUZzBb/3JRTa8Za0iLU7q24+awtCbvK1yDatE2J2", + "7QA3sHk26op0jFO5idQ76iYTtElDC8OvHGF1jVkfr7xuYpdou2S2i8Ji4roEFT3H26g8WjCw2rDOUDbl", + "dNaik1Gs0Ui7St6oAnBICOwpJhzYPSFv7Xe3W5UdIXJHrGbmn0zkYPPNimngu0aLcKznc43K94iPnl48", + "+2ND2FmZAmFaEV96cff1Mh6tEzPSHHjiGFAyFdkmabCvUeMWypiiSsFyuvsmCvmna/DrLh/zZPs9dTvX", + "yMtgcdt4ckg068Qx4B7uvNEwmDdX2MIRHXsOMH7dLLqPjYYgEMefYlalFu/bl+nV02zuGN8d4wtOY0si", + "YNyVqm0zkck1Mj65kSXv53nfrCEtDXDhSb6P5nn0ycFaNxybGUzL+RwbFXecdGZpgOMxwW+JFdrlDuWC", + "+1GQHbxqXnnZdO/2cF3uEmRg3/c1Bh/gdlC+QW/GsqB8432+kCi2LHOLQ9vj7WoZra0m240EQH+sM/71", + "mbXfeJtfYLx1V23zd4sWck4VsfsLGSl55nKHOjWn13x4k2Q79Oma12x6a5tku97I6ty8Q64Iv8vNpG1F", + "CpCJXnN7oJqdzG1ta3tyJ3cNWv8a14ZN+YYeBtut01wzhCu6PWTA1/D6CLpx1MlwjR4daLXoTx0JW3PY", + "N680eqQzfDOIpDapOCcp5AWhvnt+KrjSskz1O07RSRMsbNINMPHW6H7+9sK/EvcTRtx4bqh3nGJz9cp1", + "E+VzM4j4Kb4F8GxUlfM5KMMrQyKZAbzj7i3GScmNpiVmZMlSKRKbiGrOkJFPJvbNJd2QGdb/EOQPkIJM", + "zc0e7Lo1GCvN8txFtJhpiJi941STHKjS5EdmuKwZzhcfqEK5QJ8LeVZhId6pYQ4cFFNJ3PjynX2KzRDc", + "8r2RDw2W9nFdxPxmuyB42FnWC/nxSwM3xdrBOVO6DoLowH5jDvAl40mUyE4XQFxMWJu2yH2sWOYI6EHT", + "O6QX8I6bG04Lglyd6ouRQ9vN0zmL9nS0qKaxES1vkF/rIBXvSrgMiTCZO9fKnyg1M6AD777EjbfV4Ft7", + "v6cbpXHlAs/M054L2T51zbN6XnJKQsMQ1ioH4944bYD85228/v569EWPxivTGLsDdtlVsz0S4s1v+JjQ", + "XPC5rUJoNEiB+8R4UWoMrL5OIx2saJ6IFUjJMlADV8oE/2ZF85+qz3ZcgEFzt+USMkY15BtSSEghs3Wy", + "mCK1kjyxlQZIuqB8jnelFOV8YV+z45yDhKoPltFL20PE65SseWJrpnVhPCLWwBiWdQWaLiJ9RfBGMYqw", + "30FbBmKIqhs5wljpsk/zHY96JVuD1FUdkGaR0zzXA67txgUc4Kee+Cq6399R2R2V7U1lsRJ7iLpZS+e2", + "+Aq35ZqNM9ddUPIGbT23Um32rmT6n71kuudAilAiaUPKjvfqooowTc6xoM4UiLkwSrQxu5beTiOdEMOQ", + "Anu6rbyoXKfJdEEZd9VYqvB8hEO7brjat9+7FvOcZWZolzPogLSUTG9QLqcF++0MzP/fG8FWgVx5kb2U", + "+ehwtNC6ODw4yEVK84VQ+mD0cRw+U62H7yv4P3hpu5BsZTSIj+8//v8AAAD//xNodLIldAEA", } // GetSwagger returns the content of the embedded swagger specification file From b376efd7fc669d1582797b40eead94637a7c37b8 Mon Sep 17 00:00:00 2001 From: AlgoAxel <113933518+AlgoAxel@users.noreply.github.com> Date: Thu, 18 May 2023 14:45:23 -0400 Subject: [PATCH 100/125] netdeploy: Copy ledger directory for kv tracker database (#5392) --- netdeploy/remote/nodecfg/nodeConfigurator.go | 31 +++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/netdeploy/remote/nodecfg/nodeConfigurator.go b/netdeploy/remote/nodecfg/nodeConfigurator.go index 4621373b25..c398e8c448 100644 --- a/netdeploy/remote/nodecfg/nodeConfigurator.go +++ b/netdeploy/remote/nodecfg/nodeConfigurator.go @@ -39,6 +39,7 @@ type nodeConfigurator struct { genesisData bookkeeping.Genesis bootstrappedBlockFile string bootstrappedTrackerFile string + bootstrappedTrackerDir string relayEndpoints []srvEntry metricsEndpoints []srvEntry } @@ -78,6 +79,12 @@ func (nc *nodeConfigurator) apply(rootConfigDir, rootNodeDir string) (err error) nc.bootstrappedTrackerFile = trackerFile } + trackerDir := filepath.Join(rootConfigDir, "genesisdata", "bootstrapped") + trackerDirExists := util.FileExists(trackerDir) + if trackerDirExists { + nc.bootstrappedTrackerDir = trackerDir + } + nc.genesisFile = filepath.Join(rootConfigDir, "genesisdata", config.GenesisJSONFile) nc.genesisData, err = bookkeeping.LoadGenesisFromFile(nc.genesisFile) nodeDirs, err := nc.prepareNodeDirs(nc.config.Nodes, rootConfigDir, rootNodeDir) @@ -150,20 +157,30 @@ func (nc *nodeConfigurator) prepareNodeDirs(configs []remote.NodeConfig, rootCon } // Copy the bootstrapped files into current ledger folder - if nc.bootstrappedBlockFile != "" && nc.bootstrappedTrackerFile != "" { + if nc.bootstrappedBlockFile != "" && + (nc.bootstrappedTrackerFile != "" || nc.bootstrappedTrackerDir != "") { fmt.Fprintf(os.Stdout, "... copying block database file to ledger folder ...\n") dest := filepath.Join(nodeDest, genesisDir, fmt.Sprintf("%s.block.sqlite", config.LedgerFilenamePrefix)) _, err = util.CopyFile(nc.bootstrappedBlockFile, dest) if err != nil { return nil, fmt.Errorf("failed to copy database file %s from %s to %s : %w", "bootstrapped.block.sqlite", filepath.Dir(nc.bootstrappedBlockFile), dest, err) } - fmt.Fprintf(os.Stdout, "... copying tracker database file to ledger folder ...\n") - dest = filepath.Join(nodeDest, genesisDir, fmt.Sprintf("%s.tracker.sqlite", config.LedgerFilenamePrefix)) - _, err = util.CopyFile(nc.bootstrappedTrackerFile, dest) - if err != nil { - return nil, fmt.Errorf("failed to copy database file %s from %s to %s : %w", "bootstrapped.tracker.sqlite", filepath.Dir(nc.bootstrappedBlockFile), dest, err) + if nc.bootstrappedTrackerFile != "" { + fmt.Fprintf(os.Stdout, "... copying tracker database file to ledger folder ...\n") + dest = filepath.Join(nodeDest, genesisDir, fmt.Sprintf("%s.tracker.sqlite", config.LedgerFilenamePrefix)) + _, err = util.CopyFile(nc.bootstrappedTrackerFile, dest) + if err != nil { + return nil, fmt.Errorf("failed to copy database file %s from %s to %s : %w", filepath.Base(nc.bootstrappedBlockFile), filepath.Dir(nc.bootstrappedBlockFile), dest, err) + } + } + if nc.bootstrappedTrackerDir != "" { + fmt.Fprintf(os.Stdout, "... copying tracker database directory to ledger folder ...\n") + dest = filepath.Join(nodeDest, genesisDir, config.LedgerFilenamePrefix) + err = util.CopyFolder(nc.bootstrappedTrackerDir, dest) + if err != nil { + return nil, fmt.Errorf("failed to copy database directory from %s to %s : %w", nc.bootstrappedTrackerDir, dest, err) + } } - } nodeDirs = append(nodeDirs, nodeDir{ From 5e1f8a2077bc6ca2f181001f00a9c64903a609aa Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Fri, 19 May 2023 08:03:02 -0700 Subject: [PATCH 101/125] assembler: Error if extra args are present in pragmas (#5400) --- data/transactions/logic/assembler.go | 6 ++++++ data/transactions/logic/assembler_test.go | 20 ++++++++++++++++++++ data/txntest/txn.go | 3 +-- ledger/simulation/simulation_eval_test.go | 6 +++--- netdeploy/remote/deployedNetwork.go | 2 +- test/e2e-go/restAPI/restClient_test.go | 2 +- 6 files changed, 32 insertions(+), 7 deletions(-) diff --git a/data/transactions/logic/assembler.go b/data/transactions/logic/assembler.go index 7aa1ac48f8..65f9f1dfb4 100644 --- a/data/transactions/logic/assembler.go +++ b/data/transactions/logic/assembler.go @@ -2193,6 +2193,9 @@ func pragma(ops *OpStream, tokens []string) error { if len(tokens) < 3 { return ops.error("no version value") } + if len(tokens) > 3 { + return ops.errorf("unexpected extra tokens: %s", strings.Join(tokens[3:], " ")) + } value := tokens[2] var ver uint64 if ops.pending.Len() > 0 { @@ -2222,6 +2225,9 @@ func pragma(ops *OpStream, tokens []string) error { if len(tokens) < 3 { return ops.error("no typetrack value") } + if len(tokens) > 3 { + return ops.errorf("unexpected extra tokens: %s", strings.Join(tokens[3:], " ")) + } value := tokens[2] on, err := strconv.ParseBool(value) if err != nil { diff --git a/data/transactions/logic/assembler_test.go b/data/transactions/logic/assembler_test.go index dbc665e26e..dc970ef64f 100644 --- a/data/transactions/logic/assembler_test.go +++ b/data/transactions/logic/assembler_test.go @@ -2325,6 +2325,26 @@ func TestPragmas(t *testing.T) { ops = testProg(t, " #pragma version 5 ", assemblerNoVersion) require.Equal(t, uint64(5), ops.Version) + + testProg(t, "#pragma version 5 blah", assemblerNoVersion, + Expect{1, "unexpected extra tokens: blah"}) + + testProg(t, "#pragma typetrack", assemblerNoVersion, + Expect{1, "no typetrack value"}) + + testProg(t, "#pragma typetrack blah", assemblerNoVersion, + Expect{1, `bad #pragma typetrack: "blah"`}) + + testProg(t, "#pragma typetrack false blah", assemblerNoVersion, + Expect{1, "unexpected extra tokens: blah"}) + + // Currently pragmas don't treat semicolons as newlines. It would probably + // be nice to fix this. + testProg(t, "#pragma version 5; int 1", assemblerNoVersion, + Expect{1, "unexpected extra tokens: ; int 1"}) + + testProg(t, "#pragma typetrack false; int 1", assemblerNoVersion, + Expect{1, "unexpected extra tokens: ; int 1"}) } func TestAssemblePragmaVersion(t *testing.T) { diff --git a/data/txntest/txn.go b/data/txntest/txn.go index 2ad4bf27f3..5815aacc60 100644 --- a/data/txntest/txn.go +++ b/data/txntest/txn.go @@ -182,8 +182,7 @@ func assemble(source interface{}) []byte { } ops, err := logic.AssembleString(program) if err != nil { - fmt.Printf("Bad program %v", ops.Errors) - panic(ops.Errors) + panic(fmt.Sprintf("Bad program %v", ops.Errors)) } return ops.Program case []byte: diff --git a/ledger/simulation/simulation_eval_test.go b/ledger/simulation/simulation_eval_test.go index 115f52d5d6..ee5c800f39 100644 --- a/ledger/simulation/simulation_eval_test.go +++ b/ledger/simulation/simulation_eval_test.go @@ -867,7 +867,7 @@ func TestAppCallWithExtraBudget(t *testing.T) { Sender: sender.Addr, ApplicationID: 0, ApprovalProgram: expensiveAppSource, - ClearStateProgram: `#pragma version 6; int 0`, + ClearStateProgram: "#pragma version 6\nint 0", }) // Expensive 700 repetition of int 1 and pop total cost 1404 expensiveTxn := env.TxnInfo.NewTxn(txntest.Txn{ @@ -938,7 +938,7 @@ func TestAppCallWithExtraBudgetOverBudget(t *testing.T) { Sender: sender.Addr, ApplicationID: 0, ApprovalProgram: expensiveAppSource, - ClearStateProgram: `#pragma version 6; int 0`, + ClearStateProgram: "#pragma version 6\nint 0", }) // Expensive 700 repetition of int 1 and pop total cost 1404 expensiveTxn := env.TxnInfo.NewTxn(txntest.Txn{ @@ -1015,7 +1015,7 @@ func TestAppCallWithExtraBudgetExceedsInternalLimit(t *testing.T) { Sender: sender.Addr, ApplicationID: 0, ApprovalProgram: expensiveAppSource, - ClearStateProgram: `#pragma version 6; int 0`, + ClearStateProgram: "#pragma version 6\nint 0", }) // Expensive 700 repetition of int 1 and pop total cost 1404 expensiveTxn := env.TxnInfo.NewTxn(txntest.Txn{ diff --git a/netdeploy/remote/deployedNetwork.go b/netdeploy/remote/deployedNetwork.go index a6b7be3f77..4bf3955142 100644 --- a/netdeploy/remote/deployedNetwork.go +++ b/netdeploy/remote/deployedNetwork.go @@ -757,7 +757,7 @@ func createSignedTx(src basics.Address, round basics.Round, params config.Consen return []transactions.SignedTxn{}, err } approval := ops.Program - ops, err = logic.AssembleString("#pragma version 2 int 1") + ops, err = logic.AssembleString("#pragma version 2\nint 1") if err != nil { panic(err) } diff --git a/test/e2e-go/restAPI/restClient_test.go b/test/e2e-go/restAPI/restClient_test.go index 7dbc02e341..2ff0c08e52 100644 --- a/test/e2e-go/restAPI/restClient_test.go +++ b/test/e2e-go/restAPI/restClient_test.go @@ -1907,7 +1907,7 @@ int 1` ops, err := logic.AssembleString(prog) a.NoError(err) approval := ops.Program - ops, err = logic.AssembleString("#pragma version 8; int 1") + ops, err = logic.AssembleString("#pragma version 8\nint 1") a.NoError(err) clearState := ops.Program From b684243cab532cb1aa33d154b22a8b65c952b1d8 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Fri, 19 May 2023 14:26:02 -0400 Subject: [PATCH 102/125] ledger: Exclude stake at R-320 that is expired by R (#5403) Co-authored-by: cce <51567+cce@users.noreply.github.com> --- agreement/abstractions.go | 6 +- agreement/agreementtest/simulate_test.go | 2 +- agreement/common_test.go | 2 +- agreement/demux_test.go | 2 +- agreement/fuzzer/ledger_test.go | 2 +- agreement/selector.go | 2 +- catchup/service_test.go | 2 +- config/consensus.go | 7 + data/datatest/impls.go | 4 +- data/ledger.go | 18 +- data/ledger_test.go | 23 +- ledger/acctonline.go | 174 ++++- ledger/acctonline_expired_test.go | 689 ++++++++++++++++++ ledger/acctonline_test.go | 396 +++++++++- ledger/acctupdates.go | 14 - ledger/acctupdates_test.go | 25 +- ledger/catchpointtracker_test.go | 1 + ledger/double_test.go | 10 +- ledger/ledger.go | 7 +- ledger/ledger_test.go | 12 +- ledger/simple_test.go | 30 +- ledger/store/trackerdb/interface.go | 1 + .../trackerdb/sqlitedriver/accountsV2.go | 44 ++ ledger/store/trackerdb/sqlitedriver/schema.go | 10 + .../trackerdb/sqlitedriver/trackerdbV2.go | 5 + ledger/testing/testGenesis.go | 26 +- 26 files changed, 1417 insertions(+), 97 deletions(-) create mode 100644 ledger/acctonline_expired_test.go diff --git a/agreement/abstractions.go b/agreement/abstractions.go index f5d09dc1ef..44aafa4fd8 100644 --- a/agreement/abstractions.go +++ b/agreement/abstractions.go @@ -133,14 +133,14 @@ type LedgerReader interface { // protocol may lose liveness. LookupAgreement(basics.Round, basics.Address) (basics.OnlineAccountData, error) - // Circulation returns the total amount of money in circulation at the - // conclusion of a given round. + // Circulation returns the total amount of online money in circulation at the + // conclusion of a given round rnd that is eligible for voting at voteRnd. // // This method returns an error if the given Round has not yet been // confirmed. It may also return an error if the given Round is // unavailable by the storage device. In that case, the agreement // protocol may lose liveness. - Circulation(basics.Round) (basics.MicroAlgos, error) + Circulation(rnd basics.Round, voteRnd basics.Round) (basics.MicroAlgos, error) // LookupDigest returns the Digest of the entry that was agreed on in a // given round. diff --git a/agreement/agreementtest/simulate_test.go b/agreement/agreementtest/simulate_test.go index 6d8b2b43b6..0516592072 100644 --- a/agreement/agreementtest/simulate_test.go +++ b/agreement/agreementtest/simulate_test.go @@ -215,7 +215,7 @@ func (l *testLedger) LookupAgreement(r basics.Round, a basics.Address) (basics.O return l.state[a].OnlineAccountData(), nil } -func (l *testLedger) Circulation(r basics.Round) (basics.MicroAlgos, error) { +func (l *testLedger) Circulation(r basics.Round, voteRnd basics.Round) (basics.MicroAlgos, error) { l.mu.Lock() defer l.mu.Unlock() diff --git a/agreement/common_test.go b/agreement/common_test.go index 361ab2e2ad..8f2ad8c4f0 100644 --- a/agreement/common_test.go +++ b/agreement/common_test.go @@ -335,7 +335,7 @@ func (l *testLedger) LookupAgreement(r basics.Round, a basics.Address) (basics.O return l.state[a].OnlineAccountData(), nil } -func (l *testLedger) Circulation(r basics.Round) (basics.MicroAlgos, error) { +func (l *testLedger) Circulation(r basics.Round, voteRnd basics.Round) (basics.MicroAlgos, error) { l.mu.Lock() defer l.mu.Unlock() diff --git a/agreement/demux_test.go b/agreement/demux_test.go index 692d2cc5f9..027dbc9e13 100644 --- a/agreement/demux_test.go +++ b/agreement/demux_test.go @@ -495,7 +495,7 @@ func (t *demuxTester) LookupAgreement(basics.Round, basics.Address) (basics.Onli } // implement Ledger -func (t *demuxTester) Circulation(basics.Round) (basics.MicroAlgos, error) { +func (t *demuxTester) Circulation(basics.Round, basics.Round) (basics.MicroAlgos, error) { // we don't care about this function in this test. return basics.MicroAlgos{}, nil } diff --git a/agreement/fuzzer/ledger_test.go b/agreement/fuzzer/ledger_test.go index c0ffb8b531..a62caee4d9 100644 --- a/agreement/fuzzer/ledger_test.go +++ b/agreement/fuzzer/ledger_test.go @@ -236,7 +236,7 @@ func (l *testLedger) LookupAgreement(r basics.Round, a basics.Address) (basics.O return l.state[a].OnlineAccountData(), nil } -func (l *testLedger) Circulation(r basics.Round) (basics.MicroAlgos, error) { +func (l *testLedger) Circulation(r basics.Round, voteRnd basics.Round) (basics.MicroAlgos, error) { l.mu.Lock() defer l.mu.Unlock() diff --git a/agreement/selector.go b/agreement/selector.go index 505d54188a..ba90be3b89 100644 --- a/agreement/selector.go +++ b/agreement/selector.go @@ -70,7 +70,7 @@ func membership(l LedgerReader, addr basics.Address, r basics.Round, p period, s return } - total, err := l.Circulation(balanceRound) + total, err := l.Circulation(balanceRound, r) if err != nil { err = fmt.Errorf("Service.initializeVote (r=%d): Failed to obtain total circulation in round %d: %v", r, balanceRound, err) return diff --git a/catchup/service_test.go b/catchup/service_test.go index 4d1bafb285..217c23bf77 100644 --- a/catchup/service_test.go +++ b/catchup/service_test.go @@ -796,7 +796,7 @@ func (m *mockedLedger) Block(r basics.Round) (bookkeeping.Block, error) { func (m *mockedLedger) Lookup(basics.Round, basics.Address) (basics.AccountData, error) { return basics.AccountData{}, errors.New("not needed for mockedLedger") } -func (m *mockedLedger) Circulation(basics.Round) (basics.MicroAlgos, error) { +func (m *mockedLedger) Circulation(basics.Round, basics.Round) (basics.MicroAlgos, error) { return basics.MicroAlgos{}, errors.New("not needed for mockedLedger") } func (m *mockedLedger) ConsensusVersion(basics.Round) (protocol.ConsensusVersion, error) { diff --git a/config/consensus.go b/config/consensus.go index 6a131c8e38..185e55ab95 100644 --- a/config/consensus.go +++ b/config/consensus.go @@ -507,6 +507,11 @@ type ConsensusParams struct { // EnableBoxRefNameError specifies that box ref names should be validated early EnableBoxRefNameError bool + + // ExcludeExpiredCirculation excludes expired stake from the total online stake + // used by agreement for Circulation, and updates the calculation of StateProofOnlineTotalWeight used + // by state proofs to use the same method (rather than excluding stake from the top N stakeholders as before). + ExcludeExpiredCirculation bool } // PaysetCommitType enumerates possible ways for the block header to commit to @@ -1281,6 +1286,8 @@ func initConsensusProtocols() { vFuture.StateProofUseTrackerVerification = true vFuture.EnableCatchpointsWithSPContexts = true + vFuture.ExcludeExpiredCirculation = true + Consensus[protocol.ConsensusFuture] = vFuture // vAlphaX versions are an separate series of consensus parameters and versions for alphanet diff --git a/data/datatest/impls.go b/data/datatest/impls.go index 960b12e374..fefcb054da 100644 --- a/data/datatest/impls.go +++ b/data/datatest/impls.go @@ -107,8 +107,8 @@ func (i ledgerImpl) LookupAgreement(r basics.Round, addr basics.Address) (basics } // Circulation implements Ledger.Circulation. -func (i ledgerImpl) Circulation(r basics.Round) (basics.MicroAlgos, error) { - return i.l.Circulation(r) +func (i ledgerImpl) Circulation(r basics.Round, voteRnd basics.Round) (basics.MicroAlgos, error) { + return i.l.Circulation(r, voteRnd) } // Wait implements Ledger.Wait. diff --git a/data/ledger.go b/data/ledger.go index 04e5320614..27949168fe 100644 --- a/data/ledger.go +++ b/data/ledger.go @@ -51,16 +51,17 @@ type Ledger struct { lastRoundSeed atomic.Value } -// roundCirculationPair used to hold a pair of matching round number and the amount of online money -type roundCirculationPair struct { +// roundCirculationItem used to hold matching round number, vote round and the amount of online money +type roundCirculationItem struct { round basics.Round + voteRound basics.Round onlineMoney basics.MicroAlgos } // roundCirculation is the cache for the circulating coins type roundCirculation struct { // elements holds several round-onlineMoney pairs - elements [2]roundCirculationPair + elements [2]roundCirculationItem } // roundSeedPair is the cache for a single seed at a given round @@ -174,28 +175,29 @@ func (l *Ledger) NextRound() basics.Round { } // Circulation implements agreement.Ledger.Circulation. -func (l *Ledger) Circulation(r basics.Round) (basics.MicroAlgos, error) { +func (l *Ledger) Circulation(r basics.Round, voteRnd basics.Round) (basics.MicroAlgos, error) { circulation, cached := l.lastRoundCirculation.Load().(roundCirculation) if cached && r != basics.Round(0) { for _, element := range circulation.elements { - if element.round == r { + if element.round == r && element.voteRound == voteRnd { return element.onlineMoney, nil } } } - totals, err := l.OnlineTotals(r) + totals, err := l.OnlineCirculation(r, voteRnd) if err != nil { return basics.MicroAlgos{}, err } - if !cached || r > circulation.elements[1].round { + if !cached || r > circulation.elements[1].round || voteRnd > circulation.elements[1].voteRound { l.lastRoundCirculation.Store( roundCirculation{ - elements: [2]roundCirculationPair{ + elements: [2]roundCirculationItem{ circulation.elements[1], { round: r, + voteRound: voteRnd, onlineMoney: totals}, }, }) diff --git a/data/ledger_test.go b/data/ledger_test.go index 540dfea1d3..20a0828397 100644 --- a/data/ledger_test.go +++ b/data/ledger_test.go @@ -120,7 +120,8 @@ func testGenerateInitState(tb testing.TB, proto protocol.ConsensusVersion) (gene func TestLedgerCirculation(t *testing.T) { partitiontest.PartitionTest(t) - genesisInitState, keys := testGenerateInitState(t, protocol.ConsensusCurrentVersion) + proto := protocol.ConsensusCurrentVersion + genesisInitState, keys := testGenerateInitState(t, proto) const inMem = true cfg := config.GetDefaultLocal() @@ -171,6 +172,8 @@ func TestLedgerCirculation(t *testing.T) { srcAccountKey := keys[sourceAccount] require.NotNil(t, srcAccountKey) + params := config.Consensus[proto] + for rnd := basics.Round(1); rnd < basics.Round(600); rnd++ { blk.BlockHeader.Round++ blk.BlockHeader.TimeStamp += int64(crypto.RandUint64() % 100 * 1000) @@ -191,6 +194,8 @@ func TestLedgerCirculation(t *testing.T) { require.NoError(t, l.AddBlock(blk, agreement.Certificate{})) l.WaitForCommit(rnd) + var voteRoundOffset = basics.Round(2 * params.SeedRefreshInterval * params.SeedLookback) + // test most recent round if rnd < basics.Round(500) { data, validThrough, _, err = realLedger.LookupAccount(rnd, destAccount) @@ -202,11 +207,11 @@ func TestLedgerCirculation(t *testing.T) { require.Equal(t, rnd, validThrough) require.Equal(t, baseDestValue+uint64(rnd), data.MicroAlgos.Raw) - roundCirculation, err := realLedger.OnlineTotals(rnd) + roundCirculation, err := realLedger.OnlineCirculation(rnd, rnd+voteRoundOffset) require.NoError(t, err) require.Equal(t, baseCirculation-uint64(rnd)*(10001), roundCirculation.Raw) - roundCirculation, err = l.OnlineTotals(rnd) + roundCirculation, err = l.OnlineCirculation(rnd, rnd+voteRoundOffset) require.NoError(t, err) require.Equal(t, baseCirculation-uint64(rnd)*(10001), roundCirculation.Raw) } else if rnd < basics.Round(510) { @@ -220,11 +225,11 @@ func TestLedgerCirculation(t *testing.T) { require.Equal(t, rnd-1, validThrough) require.Equal(t, baseDestValue+uint64(rnd)-1, data.MicroAlgos.Raw) - roundCirculation, err := realLedger.OnlineTotals(rnd - 1) + roundCirculation, err := realLedger.OnlineCirculation(rnd-1, rnd-1+voteRoundOffset) require.NoError(t, err) require.Equal(t, baseCirculation-uint64(rnd-1)*(10001), roundCirculation.Raw) - roundCirculation, err = l.OnlineTotals(rnd - 1) + roundCirculation, err = l.OnlineCirculation(rnd-1, rnd-1+voteRoundOffset) require.NoError(t, err) require.Equal(t, baseCirculation-uint64(rnd-1)*(10001), roundCirculation.Raw) } else if rnd < basics.Round(520) { @@ -236,17 +241,17 @@ func TestLedgerCirculation(t *testing.T) { require.Error(t, err) require.Equal(t, uint64(0), data.MicroAlgos.Raw) - _, err = realLedger.OnlineTotals(rnd + 1) + _, err = realLedger.OnlineCirculation(rnd+1, rnd+1+voteRoundOffset) require.Error(t, err) - _, err = l.OnlineTotals(rnd + 1) + _, err = l.OnlineCirculation(rnd+1, rnd+1+voteRoundOffset) require.Error(t, err) } else if rnd < basics.Round(520) { // test expired round ( expected error ) - _, err = realLedger.OnlineTotals(rnd - 500) + _, err = realLedger.OnlineCirculation(rnd-500, rnd-500+voteRoundOffset) require.Error(t, err) - _, err = l.OnlineTotals(rnd - 500) + _, err = l.OnlineCirculation(rnd-500, rnd-500+voteRoundOffset) require.Error(t, err) } } diff --git a/ledger/acctonline.go b/ledger/acctonline.go index adb7c6693c..1a3544ba12 100644 --- a/ledger/acctonline.go +++ b/ledger/acctonline.go @@ -34,6 +34,7 @@ import ( "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/algorand/go-algorand/util/metrics" ) @@ -533,41 +534,64 @@ func (ao *onlineAccounts) postCommit(ctx context.Context, dcc *deferredCommitCon func (ao *onlineAccounts) postCommitUnlocked(ctx context.Context, dcc *deferredCommitContext) { } -// onlineTotals return the total online balance for the given round. -func (ao *onlineAccounts) onlineTotals(rnd basics.Round) (basics.MicroAlgos, error) { - ao.accountsMu.RLock() - defer ao.accountsMu.RUnlock() - return ao.onlineTotalsImpl(rnd) +// onlineCirculation return the total online balance for the given round, for use by agreement. +func (ao *onlineAccounts) onlineCirculation(rnd basics.Round, voteRnd basics.Round) (basics.MicroAlgos, error) { + // Get cached total stake for rnd + totalStake, proto, err := ao.onlineTotals(rnd) + if err != nil { + return basics.MicroAlgos{}, err + } + + // Check if we need to subtract expired stake + if params := config.Consensus[proto]; params.ExcludeExpiredCirculation { + // Handle case when the balanceRound() used by agreement is 0, resulting in rnd=0. + // Agreement will ask us for the circulation at round 0 for the first 320 blocks. + // In this case, we don't subtract expired stake, since we are still using genesis balances. + // Agreement will later ask us for the balance of round 1 when the voteRnd is 321. + if rnd == 0 { + return totalStake, nil + } + expiredStake, err := ao.ExpiredOnlineCirculation(rnd, voteRnd) + if err != nil { + return basics.MicroAlgos{}, err + } + ot := basics.OverflowTracker{} + totalStake = ot.SubA(totalStake, expiredStake) + if ot.Overflowed { + return basics.MicroAlgos{}, fmt.Errorf("onlineTotals: overflow subtracting %v from %v", expiredStake, totalStake) + } + } + return totalStake, nil } // onlineTotalsEx return the total online balance for the given round for extended rounds range // by looking into DB func (ao *onlineAccounts) onlineTotalsEx(rnd basics.Round) (basics.MicroAlgos, error) { - ao.accountsMu.RLock() - totalsOnline, err := ao.onlineTotalsImpl(rnd) - ao.accountsMu.RUnlock() + totalsOnline, _, err := ao.onlineTotals(rnd) if err == nil { return totalsOnline, nil } var roundOffsetError *RoundOffsetError if !errors.As(err, &roundOffsetError) { - ao.log.Errorf("onlineTotalsImpl error: %v", err) + ao.log.Errorf("onlineTotals error: %v", err) } totalsOnline, err = ao.accountsq.LookupOnlineTotalsHistory(rnd) return totalsOnline, err } -// onlineTotalsImpl returns the online totals of all accounts at the end of round rnd. -func (ao *onlineAccounts) onlineTotalsImpl(rnd basics.Round) (basics.MicroAlgos, error) { +// onlineTotals returns the online totals of all accounts at the end of round rnd. +func (ao *onlineAccounts) onlineTotals(rnd basics.Round) (basics.MicroAlgos, protocol.ConsensusVersion, error) { + ao.accountsMu.RLock() + defer ao.accountsMu.RUnlock() offset, err := ao.roundParamsOffset(rnd) if err != nil { - return basics.MicroAlgos{}, err + return basics.MicroAlgos{}, "", err } onlineRoundParams := ao.onlineRoundParamsData[offset] - return basics.MicroAlgos{Raw: onlineRoundParams.OnlineSupply}, nil + return basics.MicroAlgos{Raw: onlineRoundParams.OnlineSupply}, onlineRoundParams.CurrentProtocol, nil } // LookupOnlineAccountData returns the online account data for a given address at a given round. @@ -815,6 +839,7 @@ func (ao *onlineAccounts) TopOnlineAccounts(rnd basics.Round, voteRnd basics.Rou if !(d.VoteFirstValid <= voteRnd && voteRnd <= d.VoteLastValid) { modifiedAccounts[addr] = nil invalidOnlineAccounts[addr] = accountDataToOnline(addr, &d, genesisProto) + continue } @@ -928,6 +953,21 @@ func (ao *onlineAccounts) TopOnlineAccounts(rnd basics.Round, voteRnd basics.Rou if err != nil { return nil, basics.MicroAlgos{}, err } + + // If set, return total online stake minus all future expired stake by voteRnd + if params.ExcludeExpiredCirculation { + expiredStake, err := ao.ExpiredOnlineCirculation(rnd, voteRnd) + if err != nil { + return nil, basics.MicroAlgos{}, err + } + ot := basics.OverflowTracker{} + onlineStake := ot.SubA(totalOnlineStake, expiredStake) + if ot.Overflowed { + return nil, basics.MicroAlgos{}, fmt.Errorf("TopOnlineAccounts: overflow subtracting ExpiredOnlineCirculation: %d - %d", totalOnlineStake, expiredStake) + } + return topOnlineAccounts, onlineStake, nil + } + ot := basics.OverflowTracker{} for _, oa := range invalidOnlineAccounts { totalOnlineStake = ot.SubA(totalOnlineStake, oa.MicroAlgos) @@ -947,5 +987,113 @@ func (ao *onlineAccounts) TopOnlineAccounts(rnd basics.Round, voteRnd basics.Rou } } +func (ao *onlineAccounts) onlineAcctsExpiredByRound(rnd, voteRnd basics.Round) (map[basics.Address]*ledgercore.OnlineAccountData, error) { + needUnlock := false + defer func() { + if needUnlock { + ao.accountsMu.RUnlock() + } + }() + + var expiredAccounts map[basics.Address]*ledgercore.OnlineAccountData + ao.accountsMu.RLock() + needUnlock = true + for { + currentDbRound := ao.cachedDBRoundOnline + currentDeltaLen := len(ao.deltas) + offset, err := ao.roundOffset(rnd) + if err != nil { + var roundOffsetError *RoundOffsetError + if !errors.As(err, &roundOffsetError) { + return nil, err + } + // roundOffsetError was returned, so the round number cannot be found in deltas, it is in history. + // This means offset will be 0 and ao.deltas[:offset] will be an empty slice. + } + paramsOffset, err := ao.roundParamsOffset(rnd) + if err != nil { + return nil, err + } + + rewardsParams := config.Consensus[ao.onlineRoundParamsData[paramsOffset].CurrentProtocol] + rewardsLevel := ao.onlineRoundParamsData[paramsOffset].RewardsLevel + + // Step 1: get all online accounts from DB for rnd + // Not unlocking ao.accountsMu yet, to stay consistent with Step 2 + var dbRound basics.Round + err = ao.dbs.Snapshot(func(ctx context.Context, tx trackerdb.SnapshotScope) (err error) { + ar, err := tx.MakeAccountsReader() + if err != nil { + return err + } + expiredAccounts, err = ar.ExpiredOnlineAccountsForRound(rnd, voteRnd, rewardsParams, rewardsLevel) + if err != nil { + return err + } + dbRound, err = ar.AccountsRound() + return err + }) + if err != nil { + return nil, err + } + + // If dbRound has advanced beyond the last read of ao.cachedDBRoundOnline, postCommmit has + // occurred since then, so wait until deltas is consistent with dbRound and try again. + if dbRound > currentDbRound { + // database round doesn't match the last au.dbRound we sampled. + for currentDbRound >= ao.cachedDBRoundOnline && currentDeltaLen == len(ao.deltas) { + ao.accountsReadCond.Wait() + } + continue // retry (restart for loop) + } + if dbRound < currentDbRound { + ao.log.Errorf("onlineAccounts.ValidOnlineCirculation: database round %d is behind in-memory round %d", dbRound, currentDbRound) + return nil, &StaleDatabaseRoundError{databaseRound: dbRound, memoryRound: currentDbRound} + } + + // Step 2: Apply pending changes for each block in deltas + // Iterate through per-round deltas up to offset: target round `rnd` is ao.deltas[offset-1]. + for o := uint64(0); o < offset; o++ { + for i := 0; i < ao.deltas[o].Len(); i++ { + addr, d := ao.deltas[o].GetByIdx(i) + // Each round's deltas can insert, update, or delete values in the onlineAccts map. + // Note, VoteFirstValid is not checked here on purpose since the current implementation does not allow + // setting VoteFirstValid into future. + if d.Status == basics.Online && d.VoteLastValid != 0 && voteRnd > d.VoteLastValid { + // Online expired: insert or overwrite the old data in expiredAccounts. + oadata := d.OnlineAccountData(rewardsParams, rewardsLevel) + expiredAccounts[addr] = &oadata + } else { + // addr went offline not expired, so do not report as an expired ONLINE account. + delete(expiredAccounts, addr) + } + } + } + break // successfully retrieved onlineAccts from DB & deltas + } + ao.accountsMu.RUnlock() + needUnlock = false + + return expiredAccounts, nil +} + +// ExpiredOnlineCirculation returns the total online stake for accounts with participation keys registered +// at round `rnd` that are expired by round `voteRnd`. +func (ao *onlineAccounts) ExpiredOnlineCirculation(rnd, voteRnd basics.Round) (basics.MicroAlgos, error) { + expiredAccounts, err := ao.onlineAcctsExpiredByRound(rnd, voteRnd) + if err != nil { + return basics.MicroAlgos{}, err + } + ot := basics.OverflowTracker{} + expiredStake := basics.MicroAlgos{} + for _, d := range expiredAccounts { + expiredStake = ot.AddA(expiredStake, d.MicroAlgosWithRewards) + if ot.Overflowed { + return basics.MicroAlgos{}, fmt.Errorf("ExpiredOnlineCirculation: overflow totaling expired stake") + } + } + return expiredStake, nil +} + var ledgerAccountsonlinetopCount = metrics.NewCounter("ledger_accountsonlinetop_count", "calls") var ledgerAccountsonlinetopMicros = metrics.NewCounter("ledger_accountsonlinetop_micros", "µs spent") diff --git a/ledger/acctonline_expired_test.go b/ledger/acctonline_expired_test.go new file mode 100644 index 0000000000..0645d0dd7f --- /dev/null +++ b/ledger/acctonline_expired_test.go @@ -0,0 +1,689 @@ +// Copyright (C) 2019-2023 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package ledger + +import ( + "encoding/binary" + "math/rand" + "os" + "strconv" + "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/data/bookkeeping" + "github.com/algorand/go-algorand/data/txntest" + ledgertesting "github.com/algorand/go-algorand/ledger/testing" + "github.com/algorand/go-algorand/protocol" + "github.com/algorand/go-algorand/test/partitiontest" + "github.com/stretchr/testify/require" +) + +// onlineAcctModel provides a simple interface for tracking accounts +// as they come online, go offline, and change their amount of stake. +// It is implemented by a real ledger (doubleLedgerAcctModel) for testing +// against a reference implementation (mapOnlineAcctModel). +type onlineAcctModel interface { + currentRound() basics.Round + nextRound() + advanceToRound(rnd basics.Round) + goOnline(addr basics.Address, firstvalid, lastvalid basics.Round) + goOffline(addr basics.Address) + updateStake(addr basics.Address, stake basics.MicroAlgos) + teardown() + + LookupAgreement(rnd basics.Round, addr basics.Address) onlineAcctModelAcct + OnlineCirculation(rnd basics.Round, voteRnd basics.Round) basics.MicroAlgos + ExpiredOnlineCirculation(rnd, voteRnd basics.Round) basics.MicroAlgos +} + +// mapOnlineAcctModel provides a reference implementation for tracking online accounts used +// for testing TopOnlineAccounts, ExpiredOnlineCirculation, and onlineAcctsExpiredByRound. +// It is an oracle that the doubleLedgerAcctModel is compared against. +type mapOnlineAcctModel struct { + t *testing.T + cur basics.Round + accts map[basics.Address]map[basics.Round]onlineAcctModelAcct + expiring map[basics.Round]map[basics.Address]struct{} +} + +type onlineAcctModelAcct struct { + Status basics.Status + VoteFirstValid, VoteLastValid basics.Round + Stake basics.MicroAlgos +} + +func newMapOnlineAcctModel(t *testing.T) *mapOnlineAcctModel { + return &mapOnlineAcctModel{ + t: t, + accts: make(map[basics.Address]map[basics.Round]onlineAcctModelAcct), + expiring: make(map[basics.Round]map[basics.Address]struct{}), + } +} + +func (m *mapOnlineAcctModel) teardown() {} +func (m *mapOnlineAcctModel) currentRound() basics.Round { return m.cur } +func (m *mapOnlineAcctModel) nextRound() { m.cur++ } +func (m *mapOnlineAcctModel) advanceToRound(rnd basics.Round) { + if rnd == m.cur { + return + } + require.Greater(m.t, rnd, m.cur, "cannot advance to previous round") + m.cur = rnd +} + +func (m *mapOnlineAcctModel) lookupAcctAsOf(rnd basics.Round, addr basics.Address) onlineAcctModelAcct { + require.LessOrEqual(m.t, rnd, m.cur, "cannot lookup acct for future round") + acctRounds, ok := m.accts[addr] + if !ok { + return onlineAcctModelAcct{} + } + // find the acct record for the most recent round <= rnd + for r := rnd; r >= 0; r-- { + if acct, ok := acctRounds[r]; ok { + return acct + } + } + // not found + return onlineAcctModelAcct{} +} + +func (m *mapOnlineAcctModel) LookupAgreement(rnd basics.Round, addr basics.Address) onlineAcctModelAcct { + return m.lookupAcctAsOf(rnd, addr) +} + +// look up all online accounts as of the given round +func (m *mapOnlineAcctModel) allOnlineAsOf(rnd basics.Round) map[basics.Address]onlineAcctModelAcct { + require.LessOrEqual(m.t, rnd, m.cur, "cannot lookup acct for future round") + accts := make(map[basics.Address]onlineAcctModelAcct) + for addr, acctRounds := range m.accts { + // find the acct record for the most recent round <= rnd + for r := rnd; r >= 0; r-- { + if acct, ok := acctRounds[r]; ok { + if acct.Status == basics.Online { + accts[addr] = acct + } + // found the most recent round <= rnd, so stop looking + // we will break even if the acct is offline + break + } + } + } + return accts +} + +func (m *mapOnlineAcctModel) OnlineCirculation(rnd basics.Round, voteRnd basics.Round) basics.MicroAlgos { + accts := m.allOnlineAsOf(rnd) + return m.sumAcctStake(accts) +} + +func (m *mapOnlineAcctModel) ExpiredOnlineCirculation(rnd, voteRnd basics.Round) basics.MicroAlgos { + accts := m.onlineAcctsExpiredByRound(rnd, voteRnd) + return m.sumAcctStake(accts) +} + +func (m *mapOnlineAcctModel) sumAcctStake(accts map[basics.Address]onlineAcctModelAcct) basics.MicroAlgos { + algops := MicroAlgoOperations{a: require.New(m.t)} + var ret basics.MicroAlgos + for _, acct := range accts { + ret = algops.Add(ret, acct.Stake) + } + return ret +} + +func (m *mapOnlineAcctModel) setAcct(rnd basics.Round, addr basics.Address, acct onlineAcctModelAcct) { + require.Equal(m.t, rnd, m.cur, "cannot set acct for round other than current round") + + acctRounds, ok := m.accts[addr] + if !ok { + acctRounds = make(map[basics.Round]onlineAcctModelAcct) + } + acctRounds[rnd] = acct + m.accts[addr] = acctRounds +} + +func (m *mapOnlineAcctModel) goOnline(addr basics.Address, firstvalid, lastvalid basics.Round) { + rnd := m.cur + oldAcct := m.lookupAcctAsOf(rnd, addr) + + // if is already online, remove old lastvalid round from expiring map + if oldAcct.Status == basics.Online { + require.Contains(m.t, m.expiring, oldAcct.VoteLastValid, "round should be in expiring map") + require.Contains(m.t, m.expiring[oldAcct.VoteLastValid], addr, "address should be in expiring map") + delete(m.expiring[oldAcct.VoteLastValid], addr) + } + + // create new acct record + newAcct := onlineAcctModelAcct{ + Status: basics.Online, + VoteFirstValid: firstvalid, + VoteLastValid: lastvalid, + Stake: oldAcct.Stake, + } + m.setAcct(rnd, addr, newAcct) + + // remember when this account will expire + expiring, ok := m.expiring[lastvalid] + if !ok { + expiring = make(map[basics.Address]struct{}) + } + expiring[addr] = struct{}{} + m.expiring[lastvalid] = expiring + +} + +func (m *mapOnlineAcctModel) goOffline(addr basics.Address) { + rnd := m.cur + oldAcct := m.lookupAcctAsOf(rnd, addr) + + // must already be online: remove old lastvalid round from expiring map + require.Equal(m.t, basics.Online, oldAcct.Status, "cannot go offline if not online") + require.Contains(m.t, m.expiring, oldAcct.VoteLastValid, "round should be in expiring map") + require.Contains(m.t, m.expiring[oldAcct.VoteLastValid], addr, "address should be in expiring map") + delete(m.expiring[oldAcct.VoteLastValid], addr) + + newAcct := onlineAcctModelAcct{ + Status: basics.Offline, + VoteFirstValid: 0, + VoteLastValid: 0, + Stake: oldAcct.Stake, + } + m.setAcct(rnd, addr, newAcct) +} + +func (m *mapOnlineAcctModel) updateStake(addr basics.Address, stake basics.MicroAlgos) { + rnd := m.cur + acct := m.lookupAcctAsOf(rnd, addr) + acct.Stake = stake + m.setAcct(rnd, addr, acct) +} + +func (m *mapOnlineAcctModel) onlineAcctsExpiredByRound(rnd, voteRnd basics.Round) map[basics.Address]onlineAcctModelAcct { + require.LessOrEqual(m.t, rnd, m.cur, "cannot lookup expired accts for future round") + + // get all online addresses as of rnd + ret := make(map[basics.Address]onlineAcctModelAcct) + for addr, acct := range m.allOnlineAsOf(rnd) { + require.NotZero(m.t, acct.VoteLastValid, "offline acct returned by allOnlineAsOf") + // will this acct be expired by voteRnd? + if voteRnd > acct.VoteLastValid { + ret[addr] = acct + } + } + return ret +} + +// doubleLedgerAcctModel implements an onlineAcctModel using DoubleLedger, which starts up two +// Ledger instances, a generator and a validator. +type doubleLedgerAcctModel struct { + t testing.TB + params *config.ConsensusParams + dl *DoubleLedger + ops *MicroAlgoOperations + genAddrs []basics.Address + genBalances bookkeeping.GenesisBalances + genSecrets []*crypto.SignatureSecrets + // new accounts made by goOnline, balance value tracks uncommitted balance changes before dl.endBlock() + accts map[basics.Address]basics.MicroAlgos +} + +func newDoubleLedgerAcctModel(t testing.TB, proto protocol.ConsensusVersion, inMem bool) *doubleLedgerAcctModel { + // set 1 Algo for rewards pool size -- rewards math not supported by newMapOnlineAcctModel + genesisOpt := ledgertesting.TestGenesisRewardsPoolSize(basics.MicroAlgos{Raw: 1_000_000}) + genBalances, genAddrs, genSecrets := ledgertesting.NewTestGenesis(genesisOpt) + cfg := config.GetDefaultLocal() + opts := []simpleLedgerOption{simpleLedgerNotArchival()} + if !inMem { + opts = append(opts, simpleLedgerOnDisk()) + } + dl := NewDoubleLedger(t, genBalances, proto, cfg, opts...) + dl.beginBlock() + params := config.Consensus[proto] + return &doubleLedgerAcctModel{ + t: t, + params: ¶ms, + ops: &MicroAlgoOperations{a: require.New(t)}, + dl: &dl, + genAddrs: genAddrs, + genBalances: genBalances, + genSecrets: genSecrets, + accts: make(map[basics.Address]basics.MicroAlgos), + } +} + +func (m *doubleLedgerAcctModel) teardown() { m.dl.Close() } + +func (m *doubleLedgerAcctModel) nextRound() { + m.dl.endBlock() + m.dl.beginBlock() +} + +func (m *doubleLedgerAcctModel) currentRound() basics.Round { + genRound := m.dl.generator.Latest() + valRound := m.dl.validator.Latest() + require.Equal(m.t, genRound, valRound) + return genRound + 1 +} + +func (m *doubleLedgerAcctModel) advanceToRound(rnd basics.Round) { + if rnd == m.currentRound() { + return + } + require.Greater(m.t, rnd, m.currentRound(), "cannot advance to previous round") + for m.currentRound() < rnd { + m.nextRound() + } + require.Equal(m.t, rnd, m.currentRound()) +} + +const doubleLedgerAcctModelAcctInitialBalance = 1_234_567 + +func (m *doubleLedgerAcctModel) goOnline(addr basics.Address, firstvalid, lastvalid basics.Round) { + if _, ok := m.accts[addr]; !ok { + // not yet in the ledger: send 1 algo from a genesis account + m.dl.txn(&txntest.Txn{ + Type: protocol.PaymentTx, + Sender: m.genAddrs[0], + Receiver: addr, + Amount: doubleLedgerAcctModelAcctInitialBalance, + }) + m.accts[addr] = basics.MicroAlgos{Raw: doubleLedgerAcctModelAcctInitialBalance} + } + + require.NotZero(m.t, addr, "cannot go online with zero address") + + minFee := m.params.MinTxnFee // subtract minFee from account balance + m.dl.txn(&txntest.Txn{ + Type: protocol.KeyRegistrationTx, + Sender: addr, + VoteFirst: firstvalid, + VoteLast: lastvalid, + Fee: minFee, + + Nonparticipation: false, // XXX test nonparticipating accounts + + // meaningless non-zero voting data + VotePK: crypto.OneTimeSignatureVerifier(addr), + SelectionPK: crypto.VRFVerifier(addr), + VoteKeyDilution: 1024, + }) + m.accts[addr] = m.ops.Sub(m.accts[addr], basics.MicroAlgos{Raw: minFee}) +} + +func (m *doubleLedgerAcctModel) goOffline(addr basics.Address) { + require.Contains(m.t, m.accts, addr, "cannot go offline with unknown address") + + minFee := m.params.MinTxnFee // subtract minFee from account balance + m.dl.txn(&txntest.Txn{ + Type: protocol.KeyRegistrationTx, + Sender: addr, + Fee: minFee, + + // not necessary to specify + VoteFirst: 0, + VoteLast: 0, + VotePK: crypto.OneTimeSignatureVerifier{}, + SelectionPK: crypto.VRFVerifier{}, + VoteKeyDilution: 0, + }) + m.accts[addr] = m.ops.Sub(m.accts[addr], basics.MicroAlgos{Raw: minFee}) +} + +func (m *doubleLedgerAcctModel) updateStake(addr basics.Address, amount basics.MicroAlgos) { + curStake := m.accts[addr] + require.GreaterOrEqual(m.t, amount.Raw, curStake.Raw, "currently cannot decrease stake") + if amount == curStake { + return + } + if amount.Raw > curStake.Raw { + sendAmt := m.ops.Sub(amount, curStake) + // send more algo from a genesis account + m.dl.txn(&txntest.Txn{ + Type: protocol.PaymentTx, + Sender: m.genAddrs[0], + Receiver: addr, + Amount: sendAmt.Raw, + Fee: m.params.MinTxnFee, + }) + m.accts[addr] = amount + m.t.Logf("updateStake addr %s sent %d, bal %d", addr, sendAmt, amount) + } +} + +// OnlineCirculation returns the total online stake at rnd this model produced, while +// also asserting that the validator and generator Ledgers both agree, and that different +// Ledger/tracker methods used to retrieve and calculate the stake internally agree. +func (m *doubleLedgerAcctModel) OnlineCirculation(rnd basics.Round, voteRnd basics.Round) basics.MicroAlgos { + valTotal, err := m.dl.validator.OnlineTotalStake(rnd) + require.NoError(m.t, err) + genTotal, err := m.dl.generator.OnlineTotalStake(rnd) + require.NoError(m.t, err) + require.Equal(m.t, valTotal, genTotal) + + valStake, err := m.dl.validator.OnlineCirculation(rnd, voteRnd) + require.NoError(m.t, err) + genStake, err := m.dl.generator.OnlineCirculation(rnd, voteRnd) + require.NoError(m.t, err) + require.Equal(m.t, valStake, genStake) + + // If ExcludeExpiredCirculation is set, this means OnlineCirculation + // has already subtracted the expired stake. So to get the total, add + // it back in by querying ExpiredOnlineCirculation. + if m.params.ExcludeExpiredCirculation { + expiredStake := m.ExpiredOnlineCirculation(rnd, rnd+320) + valStake = m.ops.Add(valStake, expiredStake) + } + + // This should equal the value of onlineTotalsImpl(rnd) which provides + // the total online stake without subtracting expired stake. + require.Equal(m.t, valTotal, valStake) + + return valStake +} + +// OnlineTotalStake is a wrapper to access onlineAccounts.onlineTotalsImpl safely. +func (l *Ledger) OnlineTotalStake(rnd basics.Round) (basics.MicroAlgos, error) { + l.trackerMu.RLock() + defer l.trackerMu.RUnlock() + totalStake, _, err := l.acctsOnline.onlineTotals(rnd) + return totalStake, err +} + +// ExpiredOnlineCirculation is a wrapper to call onlineAccounts.ExpiredOnlineCirculation safely. +func (l *Ledger) ExpiredOnlineCirculation(rnd, voteRnd basics.Round) (basics.MicroAlgos, error) { + l.trackerMu.RLock() + defer l.trackerMu.RUnlock() + return l.acctsOnline.ExpiredOnlineCirculation(rnd, voteRnd) +} + +// ExpiredOnlineCirculation returns the total expired stake at rnd this model produced, while +// also asserting that the validator and generator Ledgers both agree. +func (m *doubleLedgerAcctModel) ExpiredOnlineCirculation(rnd, voteRnd basics.Round) basics.MicroAlgos { + valStake, err := m.dl.validator.ExpiredOnlineCirculation(rnd, voteRnd) + require.NoError(m.t, err) + genStake, err := m.dl.generator.ExpiredOnlineCirculation(rnd, voteRnd) + require.NoError(m.t, err) + require.Equal(m.t, valStake, genStake) + return valStake +} + +func (m *doubleLedgerAcctModel) LookupAgreement(rnd basics.Round, addr basics.Address) onlineAcctModelAcct { + valAcct, err := m.dl.validator.LookupAgreement(rnd, addr) + require.NoError(m.t, err) + genAcct, err := m.dl.generator.LookupAgreement(rnd, addr) + require.NoError(m.t, err) + require.Equal(m.t, valAcct, genAcct) + + status := basics.Offline + if valAcct.VoteLastValid > 0 || valAcct.VoteFirstValid > 0 { + status = basics.Online + } + return onlineAcctModelAcct{ + VoteFirstValid: valAcct.VoteFirstValid, + VoteLastValid: valAcct.VoteLastValid, + Status: status, + Stake: valAcct.MicroAlgosWithRewards, + } +} + +//nolint:paralleltest // don't want to parallelize this test +func TestOnlineAcctModelSimple(t *testing.T) { + partitiontest.PartitionTest(t) + + // first test using the in-memory model + t.Run("Map", func(t *testing.T) { + m := newMapOnlineAcctModel(t) + testOnlineAcctModelSimple(t, m) + }) + // test same scenario on double ledger + t.Run("DoubleLedger", func(t *testing.T) { + m := newDoubleLedgerAcctModel(t, protocol.ConsensusFuture, true) + defer m.teardown() + testOnlineAcctModelSimple(t, m) + }) +} + +func testOnlineAcctModelSimple(t *testing.T, m onlineAcctModel) { + // acct 1 has 10 algos expiring at round 2000 + m.goOnline(basics.Address{1}, 1, 2000) + m.updateStake(basics.Address{1}, basics.MicroAlgos{Raw: 10_000_000}) + // acct 2 has 11 algos expiring at round 999 + m.goOnline(basics.Address{2}, 1, 999) + m.updateStake(basics.Address{2}, basics.MicroAlgos{Raw: 11_000_000}) + + m.advanceToRound(500) + // acct 3 has 11.1 algos expiring at round 2500 + m.goOnline(basics.Address{3}, 500, 2500) + m.updateStake(basics.Address{3}, basics.MicroAlgos{Raw: 11_100_000}) + + m.advanceToRound(600) + // acct 4 has 11.11 algos expiring at round 900 + m.goOnline(basics.Address{4}, 600, 900) + m.updateStake(basics.Address{4}, basics.MicroAlgos{Raw: 11_110_000}) + + m.advanceToRound(1000) + // total stake is all 4 accounts + a := require.New(t) + onlineStake := m.OnlineCirculation(680, 1000) + a.Equal(basics.MicroAlgos{Raw: 43_210_000}, onlineStake) + + // expired stake is acct 2 + acct 4 + expiredStake := m.ExpiredOnlineCirculation(680, 1000) + a.Equal(basics.MicroAlgos{Raw: 22_110_000}, expiredStake) +} + +// An onlineScenario is a list of actions to take at each round, which are +// applied to the onlineAcctModel implementations (real and oracle) being tested. +type onlineScenario struct { + // roundActions is a list of actions to take in each round, must be in rnd order + roundActions []onlineScenarioRound +} + +type onlineScenarioRound struct { + rnd basics.Round + actions []onlineScenarioRoundAction +} + +// An onlineScenarioRoundAction is an action to take on an onlineAcctModel in a given round. +type onlineScenarioRoundAction interface { + apply(t *testing.T, m onlineAcctModel) +} + +type goOnlineWithStakeAction struct { + addr basics.Address + fv, lv basics.Round + stake uint64 +} + +func (a goOnlineWithStakeAction) apply(t *testing.T, m onlineAcctModel) { + m.goOnline(a.addr, a.fv, a.lv) + m.updateStake(a.addr, basics.MicroAlgos{Raw: a.stake}) +} + +type goOfflineAction struct{ addr basics.Address } + +func (a goOfflineAction) apply(t *testing.T, m onlineAcctModel) { m.goOffline(a.addr) } + +type updateStakeAction struct { + addr basics.Address + stake uint64 +} + +func (a updateStakeAction) apply(t *testing.T, m onlineAcctModel) { + m.updateStake(a.addr, basics.MicroAlgos{Raw: a.stake}) +} + +type checkOnlineStakeAction struct { + rnd, voteRnd basics.Round + online, expired uint64 +} + +func (a checkOnlineStakeAction) apply(t *testing.T, m onlineAcctModel) { + onlineStake := m.OnlineCirculation(a.rnd, a.voteRnd) + expiredStake := m.ExpiredOnlineCirculation(a.rnd, a.voteRnd) + require.Equal(t, basics.MicroAlgos{Raw: a.online}, onlineStake, "round %d, cur %d", a.rnd, m.currentRound()) + require.Equal(t, basics.MicroAlgos{Raw: a.expired}, expiredStake, "rnd %d voteRnd %d, cur %d", a.rnd, a.voteRnd, m.currentRound()) +} + +// simpleOnlineScenario is the same as the TestOnlineAcctModelSimple test +// but expressed as an onlineScenario +var simpleOnlineScenario = onlineScenario{ + roundActions: []onlineScenarioRound{ + {1, []onlineScenarioRoundAction{ + // acct 1 has 10 algos expiring at round 2000 + goOnlineWithStakeAction{basics.Address{1}, 1, 2000, 10_000_000}, + // acct 2 has 11 algos expiring at round 999 + goOnlineWithStakeAction{basics.Address{2}, 1, 999, 11_000_000}, + }}, + {500, []onlineScenarioRoundAction{ + // acct 3 has 11.1 algos expiring at round 2500 + goOnlineWithStakeAction{basics.Address{3}, 500, 2500, 11_100_000}, + }}, + {600, []onlineScenarioRoundAction{ + // acct 4 has 11.11 algos expiring at round 900 + goOnlineWithStakeAction{basics.Address{4}, 600, 900, 11_110_000}, + }}, + {681, []onlineScenarioRoundAction{ + // total stake is all 4 accounts + // expired stake is acct 2 + acct 4 + checkOnlineStakeAction{680, 1000, 43_210_000, 22_110_000}, + }}, + {1000, []onlineScenarioRoundAction{ + // check total & expired stake again at round 1000, should be the same + checkOnlineStakeAction{680, 1000, 43_210_000, 22_110_000}, + }}, + }, +} + +// a quick helper function for making it easier to identify whose balances are missing +func shift1AlgoBy(n uint64) uint64 { return 1_000_000 << n } + +// simpleOfflineOnlineScenario is like simpleOnlineScenario but with acct 2 +// going from online+expired to offline at round 999. +var simpleOfflineOnlineScenario = onlineScenario{ + roundActions: []onlineScenarioRound{ + {1, []onlineScenarioRoundAction{ + goOnlineWithStakeAction{basics.Address{1}, 1, 2000, shift1AlgoBy(1)}, + goOnlineWithStakeAction{basics.Address{2}, 1, 999, shift1AlgoBy(2)}, + }}, + {500, []onlineScenarioRoundAction{ + goOnlineWithStakeAction{basics.Address{3}, 500, 2500, shift1AlgoBy(3)}, + }}, + {600, []onlineScenarioRoundAction{ + goOnlineWithStakeAction{basics.Address{4}, 600, 900, shift1AlgoBy(4)}, // expired by 1000 + }}, + {679, []onlineScenarioRoundAction{ + goOnlineWithStakeAction{basics.Address{5}, 679, 999, shift1AlgoBy(5)}, // expired by 1000 + goOfflineAction{basics.Address{2}}, // was going to expire at 999 but now is offline + }}, + {680, []onlineScenarioRoundAction{ + goOnlineWithStakeAction{basics.Address{6}, 680, 999, shift1AlgoBy(6)}, // expired by 1000 + goOnlineWithStakeAction{basics.Address{7}, 680, 1000, shift1AlgoBy(7)}, + }}, + {1000, []onlineScenarioRoundAction{ + checkOnlineStakeAction{680, 1000, 250_000_000, 112_000_000}, + }}, + }, +} + +//nolint:paralleltest // don't want to parallelize this test +func TestOnlineAcctModelScenario(t *testing.T) { + partitiontest.PartitionTest(t) + + runScenario := func(t *testing.T, m onlineAcctModel, s onlineScenario) { + for _, ra := range s.roundActions { + m.advanceToRound(ra.rnd) + for _, action := range ra.actions { + action.apply(t, m) + } + } + } + + for _, tc := range []struct { + name string + scenario onlineScenario + }{ + {"Simple", simpleOnlineScenario}, + {"SimpleOffline", simpleOfflineOnlineScenario}, + } { + t.Run(tc.name, func(t *testing.T) { + // first test using the in-memory model + t.Run("Map", func(t *testing.T) { + m := newMapOnlineAcctModel(t) + runScenario(t, m, tc.scenario) + }) + // test same scenario on double ledger + t.Run("DoubleLedger", func(t *testing.T) { + m := newDoubleLedgerAcctModel(t, protocol.ConsensusFuture, true) + defer m.teardown() + runScenario(t, m, tc.scenario) + }) + }) + } +} + +func BenchmarkExpiredOnlineCirculation(b *testing.B) { + // set up totalAccounts online accounts in 10k batches + totalAccounts := 100_000 + const maxKeyregPerBlock = 10_000 + // if TOTAL_ACCOUNTS env var set, override totalAccounts + if n, err := strconv.Atoi(os.Getenv("TOTAL_ACCOUNTS")); err == nil { + b.Logf("using %d accounts", n) + if n%maxKeyregPerBlock != 0 { + b.Fatalf("TOTAL_ACCOUNTS %d must be a multiple of %d", n, maxKeyregPerBlock) + } + totalAccounts = n + } + + proto := protocol.ConsensusFuture + m := newDoubleLedgerAcctModel(b, proto, false) + defer m.teardown() + + addrFromUint64 := func(n uint64) basics.Address { + var addr basics.Address + binary.BigEndian.PutUint64(addr[:], n) + return addr + } + + var blockCounter, acctCounter uint64 + for i := 0; i < totalAccounts/maxKeyregPerBlock; i++ { + blockCounter++ + for j := 0; j < maxKeyregPerBlock; j++ { + acctCounter++ + // go online for a random number of rounds, from 400 to 1600 + validFor := 400 + uint64(rand.Intn(1200)) + m.goOnline(addrFromUint64(acctCounter), basics.Round(blockCounter), basics.Round(blockCounter+validFor)) + } + b.Log("built block", blockCounter, "accts", acctCounter) + m.nextRound() + } + // then advance ~1K rounds to exercise the exercise accounts going offline + m.advanceToRound(basics.Round(blockCounter + 1000)) + b.Log("advanced to round", m.currentRound()) + + b.ResetTimer() + for i := uint64(0); i < uint64(b.N); i++ { + // query expired circulation across the available range (last 320 rounds, from ~680 to ~1000) + startRnd := m.currentRound() - 320 + offset := basics.Round(i % 320) + _, err := m.dl.validator.ExpiredOnlineCirculation(startRnd+offset, startRnd+offset+320) + require.NoError(b, err) + //total, err := m.dl.validator.OnlineTotalStake(startRnd + offset) + //b.Log("expired circulation", startRnd+offset, startRnd+offset+320, "returned", expiredStake, "total", total) + } +} diff --git a/ledger/acctonline_test.go b/ledger/acctonline_test.go index 049a703d5a..803cf9f773 100644 --- a/ledger/acctonline_test.go +++ b/ledger/acctonline_test.go @@ -21,6 +21,7 @@ import ( "database/sql" "fmt" "sort" + "strconv" "testing" "time" @@ -781,7 +782,7 @@ func TestAcctOnlineRoundParamsCache(t *testing.T) { accts = append(accts, newAccts) if i > basics.Round(maxBalLookback) && i%10 == 0 { - onlineTotal, err := ao.onlineTotals(i - basics.Round(maxBalLookback)) + onlineTotal, err := ao.onlineCirculation(i-basics.Round(maxBalLookback), i) require.NoError(t, err) require.Equal(t, allTotals[i-basics.Round(maxBalLookback)].Online.Money, onlineTotal) expectedConsensusVersion := testProtocolVersion1 @@ -822,7 +823,7 @@ func TestAcctOnlineRoundParamsCache(t *testing.T) { require.Equal(t, ao.onlineRoundParamsData[:basics.Round(maxBalLookback)], dbOnlineRoundParams) for i := ml.Latest() - basics.Round(maxBalLookback); i < ml.Latest(); i++ { - onlineTotal, err := ao.onlineTotals(i) + onlineTotal, err := ao.onlineCirculation(i, i+basics.Round(maxBalLookback)) require.NoError(t, err) require.Equal(t, allTotals[i].Online.Money, onlineTotal) } @@ -1421,7 +1422,7 @@ func TestAcctOnlineTop(t *testing.T) { conf := config.GetDefaultLocal() au, oa := newAcctUpdates(t, ml, conf) defer oa.close() - initialOnlineTotals, err := oa.onlineTotals(0) + initialOnlineTotals, err := oa.onlineCirculation(0, basics.Round(oa.maxBalLookback())) a.NoError(err) top := compareOnlineTotals(a, oa, 0, 0, 5, initialOnlineTotals, initialOnlineTotals) compareTopAccounts(a, top, allAccts) @@ -1484,7 +1485,20 @@ func TestAcctOnlineTop(t *testing.T) { func TestAcctOnlineTopInBatches(t *testing.T) { partitiontest.PartitionTest(t) - a := require.New(t) + + intToAddress := func(n int) basics.Address { + var addr basics.Address + pos := 0 + for { + addr[pos] = byte(n % 10) + n /= 10 + if n == 0 { + break + } + pos++ + } + return addr + } const numAccts = 2048 allAccts := make([]basics.BalanceRecord, numAccts) @@ -1493,11 +1507,11 @@ func TestAcctOnlineTopInBatches(t *testing.T) { for i := 0; i < numAccts; i++ { allAccts[i] = basics.BalanceRecord{ - Addr: ledgertesting.RandomAddress(), + Addr: intToAddress(i + 1), AccountData: basics.AccountData{ MicroAlgos: basics.MicroAlgos{Raw: uint64(i + 1)}, Status: basics.Online, - VoteLastValid: 1000, + VoteLastValid: basics.Round(i + 1), VoteFirstValid: 0, RewardsBase: 0}, } @@ -1505,17 +1519,55 @@ func TestAcctOnlineTopInBatches(t *testing.T) { } addSinkAndPoolAccounts(genesisAccts) - ml := makeMockLedgerForTracker(t, true, 1, protocol.ConsensusCurrentVersion, genesisAccts) - defer ml.Close() + for _, proto := range []protocol.ConsensusVersion{protocol.ConsensusV36, protocol.ConsensusFuture} { + t.Run(string(proto), func(t *testing.T) { + a := require.New(t) + params := config.Consensus[proto] + ml := makeMockLedgerForTracker(t, true, 1, proto, genesisAccts) + defer ml.Close() - conf := config.GetDefaultLocal() - _, oa := newAcctUpdates(t, ml, conf) - defer oa.close() + conf := config.GetDefaultLocal() + au, oa := newAcctUpdates(t, ml, conf) + defer oa.close() - proto := config.Consensus[protocol.ConsensusCurrentVersion] - top, _, err := oa.TopOnlineAccounts(0, 0, 2048, &proto, 0) - a.NoError(err) - compareTopAccounts(a, top, allAccts) + top, totalOnlineStake, err := oa.TopOnlineAccounts(0, 0, numAccts, ¶ms, 0) + a.NoError(err) + compareTopAccounts(a, top, allAccts) + a.Equal(basics.MicroAlgos{Raw: 2048 * 2049 / 2}, totalOnlineStake) + + // add 300 blocks so the first 300 accounts expire + // at the last block put the 299th account offline to trigger TopOnlineAccounts behavior difference + _, totals, err := au.LatestTotals() + a.NoError(err) + acct299 := allAccts[298] + for i := 1; i <= 300; i++ { + var updates ledgercore.AccountDeltas + if i == 300 { + updates.Upsert(acct299.Addr, ledgercore.AccountData{ + AccountBaseData: ledgercore.AccountBaseData{Status: basics.Offline, MicroAlgos: acct299.MicroAlgos}, + VotingData: ledgercore.VotingData{}, + }) + } + newBlockWithUpdates(genesisAccts, updates, totals, t, ml, i, oa) + } + a.Equal(basics.Round(300), oa.latest()) + + // 299 accts expired at voteRnd = 300 + top, totalOnlineStake, err = oa.TopOnlineAccounts(0, 300, numAccts, ¶ms, 0) + a.NoError(err) + compareTopAccounts(a, top, allAccts) + a.Equal(basics.MicroAlgos{Raw: 2048*2049/2 - 299*300/2}, totalOnlineStake) + + // check the behavior difference between ConsensusV36 and ConsensusFuture + var correction uint64 + if proto == protocol.ConsensusV36 { + correction = acct299.MicroAlgos.Raw + } + _, totalOnlineStake, err = oa.TopOnlineAccounts(300, 300, numAccts, ¶ms, 0) + a.NoError(err) + a.Equal(basics.MicroAlgos{Raw: 2048*2049/2 - 299*300/2 - correction}, totalOnlineStake) + }) + } } func TestAcctOnlineTopBetweenCommitAndPostCommit(t *testing.T) { @@ -1763,7 +1815,8 @@ func TestAcctOnlineTop_ChangeOnlineStake(t *testing.T) { totals = newBlockWithUpdates(genesisAccts, updates, totals, t, ml, i, oa) } - initialOnlineStake, err := oa.onlineTotals(0) + params := config.Consensus[protocol.ConsensusCurrentVersion] + initialOnlineStake, err := oa.onlineCirculation(0, basics.Round(params.MaxBalLookback)) a.NoError(err) rnd15TotalOnlineStake := algops.Sub(initialOnlineStake, allAccts[15].MicroAlgos) // 15 is offline @@ -1808,9 +1861,318 @@ func compareOnlineTotals(a *require.Assertions, oa *onlineAccounts, rnd, voteRnd top, onlineTotalVoteRnd, err := oa.TopOnlineAccounts(rnd, voteRnd, n, &proto, 0) a.NoError(err) a.Equal(expectedForVoteRnd, onlineTotalVoteRnd) - onlineTotalsRnd, err := oa.onlineTotals(rnd) + onlineTotalsRnd, err := oa.onlineCirculation(rnd, voteRnd) a.NoError(err) a.Equal(expectedForRnd, onlineTotalsRnd) a.LessOrEqual(onlineTotalVoteRnd.Raw, onlineTotalsRnd.Raw) return top } + +// TestAcctOnline_ExpiredOnlineCirculation mutates online state in deltas and DB +// to ensure ExpiredOnlineCirculation returns expected online stake value +// The test exercises all possible combinations for offline, online and expired values for two accounts. +func TestAcctOnline_ExpiredOnlineCirculation(t *testing.T) { + partitiontest.PartitionTest(t) + a := require.New(t) + algops := MicroAlgoOperations{a: a} + + // powInt is a helper function to calculate powers of uint64 + powInt := func(x, y uint64) uint64 { + ret := uint64(1) + if x == 0 { + return ret + } + for i := uint64(0); i < y; i++ { + ret *= x + } + return ret + } + + // add some genesis online accounts with stake 1, 10, 20, 30... in order to see which account stake + // not included into results while debugging + const numAccts = 20 + allAccts := make([]basics.BalanceRecord, numAccts) + genesisAccts := []map[basics.Address]basics.AccountData{{}} + genesisAccts[0] = make(map[basics.Address]basics.AccountData, numAccts) + totalStake := basics.MicroAlgos{Raw: 0} + for i := 0; i < numAccts-1; i++ { + stake := i * 10 + if stake == 0 { + stake = 1 + } + allAccts[i] = basics.BalanceRecord{ + Addr: ledgertesting.RandomAddress(), + AccountData: basics.AccountData{ + MicroAlgos: basics.MicroAlgos{Raw: uint64(stake)}, + Status: basics.Online, + VoteLastValid: 10000, + VoteFirstValid: 0, + RewardsBase: 0}, + } + genesisAccts[0][allAccts[i].Addr] = allAccts[i].AccountData + totalStake = algops.Add(totalStake, allAccts[i].MicroAlgos) + } + + addSinkAndPoolAccounts(genesisAccts) + + proto := protocol.ConsensusFuture + params := config.Consensus[proto] + ml := makeMockLedgerForTracker(t, true, 1, proto, genesisAccts) + defer ml.Close() + + conf := config.GetDefaultLocal() + conf.MaxAcctLookback = 4 // technically the test work for any value of MaxAcctLookback but takes too long + // t.Logf("Running MaxAcctLookback=%d", conf.MaxAcctLookback) + au, oa := newAcctUpdates(t, ml, conf) + defer oa.close() + + // close commitSyncer goroutine to prevent possible race between commitSyncer and commitSync + ml.trackers.ctxCancel() + ml.trackers.ctxCancel = nil + <-ml.trackers.commitSyncerClosed + ml.trackers.commitSyncerClosed = nil + + // initial precondition checks on online stake + _, totals, err := au.LatestTotals() + a.NoError(err) + a.Equal(totalStake, totals.Online.Money) + initialOnlineStake, err := oa.onlineCirculation(0, basics.Round(oa.maxBalLookback())) + a.NoError(err) + a.Equal(totalStake, initialOnlineStake) + initialExpired, err := oa.ExpiredOnlineCirculation(0, 1000) + a.NoError(err) + a.Equal(basics.MicroAlgos{Raw: 0}, initialExpired) + + type dbState uint64 + const ( + dbOffline dbState = iota + dbOnline + dbOnlineExpired + ) + + type deltaState uint64 + const ( + deltaNoChange deltaState = iota + deltaOffpired // offline (addrA) or expired (addrB) + deltaOnline + ) + + type acctState uint64 + const ( + acctStateUnknown acctState = iota + acctStateOffline + acctStateOnline + acctStateExpired + ) + + // take two first accounts for the test - 0 and 1 - with stake 1 and 10 correspondingly + addrA := allAccts[0].Addr + stakeA := allAccts[0].MicroAlgos + statesA := map[acctState]ledgercore.AccountData{ + acctStateOffline: {AccountBaseData: ledgercore.AccountBaseData{Status: basics.Offline, MicroAlgos: stakeA}, VotingData: ledgercore.VotingData{}}, + acctStateOnline: {AccountBaseData: ledgercore.AccountBaseData{Status: basics.Online, MicroAlgos: stakeA}, VotingData: ledgercore.VotingData(allAccts[0].OnlineAccountData().VotingData)}, + } + + addrB := allAccts[1].Addr + stakeB := allAccts[1].MicroAlgos + votingDataB := allAccts[1].OnlineAccountData().VotingData + statesB := map[acctState]ledgercore.AccountData{ + acctStateOffline: {AccountBaseData: ledgercore.AccountBaseData{Status: basics.Offline, MicroAlgos: stakeB}, VotingData: ledgercore.VotingData{}}, + acctStateOnline: {AccountBaseData: ledgercore.AccountBaseData{Status: basics.Online, MicroAlgos: stakeB}, VotingData: ledgercore.VotingData(votingDataB)}, + } + expStatesB := func(state acctState, voteRnd basics.Round) ledgercore.AccountData { + vd := ledgercore.VotingData(votingDataB) + switch state { + case acctStateExpired: + vd.VoteLastValid = voteRnd - 1 + case acctStateOnline: + vd.VoteLastValid = voteRnd + 1 + default: + a.Fail("invalid acct state") + } + return ledgercore.AccountData{ + AccountBaseData: ledgercore.AccountBaseData{Status: basics.Online, MicroAlgos: stakeB}, + VotingData: vd, + } + } + + // try all possible online/offline delta states for account A + // try all possible valid/expired VoteLastValid for account B + // - generate {offline, online, online-expired} db states (two rounds committed) for account A and B + // - generate all combinations of deltaState {not changed, offline/expired, online} of size conf.MaxAcctLookback arrays + // - test all combinations in 3^2 * 3^conf.MaxAcctLookback tests + rnd := basics.Round(1) + accounts := []map[basics.Address]basics.AccountData{genesisAccts[0]} // base state + dbStates := []dbState{dbOffline, dbOnline, dbOnlineExpired} + deltaStates := []deltaState{deltaNoChange, deltaOffpired, deltaOnline} + const dbRoundsToCommit = 2 + for dbCombo := uint64(0); dbCombo < powInt(uint64(len(dbStates)), dbRoundsToCommit); dbCombo++ { + for deltaCombo := uint64(0); deltaCombo < powInt(uint64(len(deltaStates)), conf.MaxAcctLookback); deltaCombo++ { + var stateA acctState + var stateB acctState + + ternDb := strconv.FormatUint(dbCombo, 3) + ternDb = fmt.Sprintf("%0*s", dbRoundsToCommit, ternDb) + + ternDelta := strconv.FormatUint(deltaCombo, 3) + ternDelta = fmt.Sprintf("%0*s", conf.MaxAcctLookback, ternDelta) + // uncomment for debugging + // t.Logf("db=%d|delta=%d <==> older->%s<-db top | first->%s<-last", dbCombo, deltaCombo, ternDb, ternDelta) + + targetVoteRnd := rnd + + basics.Round(conf.MaxAcctLookback) /* all deltas */ + + 2 /* db state committed */ + + basics.Round(params.MaxBalLookback) + + // mutate the committed state + // addrA, addrB: offline, online not expired, online expired + dbSeed := dbState(9999) // not initialized + for i := uint64(0); i < dbRoundsToCommit; i++ { + combo := ternDb[i] + d, err := strconv.Atoi(string(combo)) + a.NoError(err) + if i == dbRoundsToCommit-1 { + dbSeed = dbState(d) + } + + var updates ledgercore.AccountDeltas + switch dbState(d) { + case dbOffline: + updates.Upsert(addrA, statesA[acctStateOffline]) + updates.Upsert(addrB, statesB[acctStateOffline]) + case dbOnline: + updates.Upsert(addrA, statesA[acctStateOnline]) + updates.Upsert(addrB, statesB[acctStateOnline]) + case dbOnlineExpired: + state := statesA[acctStateOnline] + state.VoteLastValid = targetVoteRnd - 1 + updates.Upsert(addrA, state) + state = statesB[acctStateOnline] + state.VoteLastValid = targetVoteRnd - 1 + updates.Upsert(addrB, state) + default: + a.Fail("unknown db state") + } + base := accounts[rnd-1] + accounts = append(accounts, applyPartialDeltas(base, updates)) + totals = newBlock(t, ml, proto, params, rnd, base, updates, totals) + rnd++ + } + + // assert on expected online totals + switch dbSeed { + case dbOffline: + // both accounts are offline, decrease the original stake + a.Equal(initialOnlineStake.Raw-(stakeA.Raw+stakeB.Raw), totals.Online.Money.Raw) + case dbOnline, dbOnlineExpired: // being expired does not decrease the stake + a.Equal(initialOnlineStake, totals.Online.Money) + } + + // mutate in-memory state + for i := uint64(0); i < conf.MaxAcctLookback; i++ { + combo := ternDelta[i] + d, err := strconv.Atoi(string(combo)) + a.NoError(err) + + var updates ledgercore.AccountDeltas + switch deltaState(d) { + case deltaNoChange: + case deltaOffpired: + updates.Upsert(addrA, statesA[acctStateOffline]) + updates.Upsert(addrB, expStatesB(acctStateExpired, targetVoteRnd)) + stateA = acctStateOffline + stateB = acctStateExpired + case deltaOnline: + updates.Upsert(addrA, statesA[acctStateOnline]) + updates.Upsert(addrB, expStatesB(acctStateOnline, targetVoteRnd)) + stateA = acctStateOnline + stateB = acctStateOnline + + default: + a.Fail("unknown delta seed") + } + base := accounts[rnd-1] + accounts = append(accounts, applyPartialDeltas(base, updates)) + totals = newBlock(t, ml, proto, params, rnd, base, updates, totals) + rnd++ + } + + commitSync(t, oa, ml, basics.Round(rnd-1)) + a.Equal(int(conf.MaxAcctLookback), len(oa.deltas)) // ensure the only expected deltas are not flushed + + var expiredAccts map[basics.Address]*ledgercore.OnlineAccountData + err = ml.trackers.dbs.Snapshot(func(ctx context.Context, tx trackerdb.SnapshotScope) error { + reader, err := tx.MakeAccountsReader() + if err != nil { + return err + } + expiredAccts, err = reader.ExpiredOnlineAccountsForRound(rnd-1, targetVoteRnd, params, 0) + if err != nil { + return err + } + return nil + }) + a.NoError(err) + + if dbSeed == dbOffline || dbSeed == dbOnline { + a.Empty(expiredAccts) + } else { + a.Len(expiredAccts, 2) + for _, acct := range expiredAccts { + a.NotZero(acct.VoteLastValid) + } + } + + expectedExpiredStake := basics.MicroAlgos{} + // if both A and B were offline or online in DB then the expired stake is changed only if account is expired in deltas + // => check if B expired + // if both A and B were expired in DB then the expired stake is changed when any of them goes offline or online + // => check if A or B are offline or online + switch dbSeed { + case dbOffline, dbOnline: + if stateB == acctStateExpired { + expectedExpiredStake.Raw += stakeB.Raw + } + case dbOnlineExpired: + expectedExpiredStake.Raw += stakeA.Raw + expectedExpiredStake.Raw += stakeB.Raw + if stateA == acctStateOnline || stateA == acctStateOffline { + expectedExpiredStake.Raw -= stakeA.Raw + } + if stateB == acctStateOnline || stateB == acctStateOffline { + expectedExpiredStake.Raw -= stakeB.Raw + } + default: + a.Fail("unknown db seed") + } + a.Equal(targetVoteRnd, rnd+basics.Round(params.MaxBalLookback)) + _, err := oa.ExpiredOnlineCirculation(rnd, targetVoteRnd) + a.Error(err) + a.Contains(err.Error(), fmt.Sprintf("round %d too high", rnd)) + expiredStake, err := oa.ExpiredOnlineCirculation(rnd-1, targetVoteRnd) + a.NoError(err) + a.Equal(expectedExpiredStake, expiredStake) + + // restore the original state of accounts A and B + updates := ledgercore.AccountDeltas{} + base := accounts[rnd-1] + updates.Upsert(addrA, statesA[acctStateOnline]) + updates.Upsert(addrB, ledgercore.AccountData{ + AccountBaseData: ledgercore.AccountBaseData{Status: basics.Online, MicroAlgos: stakeB}, VotingData: ledgercore.VotingData(votingDataB), + }) + accounts = append(accounts, applyPartialDeltas(base, updates)) + totals = newBlock(t, ml, proto, params, rnd, base, updates, totals) + rnd++ + // add conf.MaxAcctLookback empty blocks to flush/restore the original state + for i := uint64(0); i < conf.MaxAcctLookback; i++ { + var updates ledgercore.AccountDeltas + base = accounts[rnd-1] + accounts = append(accounts, base) + totals = newBlock(t, ml, proto, params, rnd, base, updates, totals) + rnd++ + } + commitSync(t, oa, ml, basics.Round(rnd-1)) + a.Equal(int(conf.MaxAcctLookback), len(oa.deltas)) + } + } +} diff --git a/ledger/acctupdates.go b/ledger/acctupdates.go index 4e934bbf55..369f7db147 100644 --- a/ledger/acctupdates.go +++ b/ledger/acctupdates.go @@ -906,20 +906,6 @@ func (aul *accountUpdatesLedgerEvaluator) GetCreatorForRound(rnd basics.Round, c return aul.au.getCreatorForRound(rnd, cidx, ctype, false /* don't sync */) } -// onlineTotals returns the online totals of all accounts at the end of round rnd. -// used in tests only -func (au *accountUpdates) onlineTotals(rnd basics.Round) (basics.MicroAlgos, error) { - au.accountsMu.RLock() - defer au.accountsMu.RUnlock() - offset, err := au.roundOffset(rnd) - if err != nil { - return basics.MicroAlgos{}, err - } - - totals := au.roundTotals[offset] - return totals.Online.Money, nil -} - // latestTotalsImpl returns the totals of all accounts for the most recent round, as well as the round number func (au *accountUpdates) latestTotalsImpl() (basics.Round, ledgercore.AccountTotals, error) { offset := len(au.deltas) diff --git a/ledger/acctupdates_test.go b/ledger/acctupdates_test.go index 9049447775..0faaa4de69 100644 --- a/ledger/acctupdates_test.go +++ b/ledger/acctupdates_test.go @@ -21,8 +21,6 @@ import ( "context" "errors" "fmt" - "github.com/algorand/go-algorand/ledger/eval" - "github.com/algorand/go-deadlock" "os" "runtime" "strings" @@ -37,6 +35,7 @@ import ( "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/bookkeeping" + "github.com/algorand/go-algorand/ledger/eval" "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/ledger/store/trackerdb" "github.com/algorand/go-algorand/ledger/store/trackerdb/sqlitedriver" @@ -45,6 +44,7 @@ import ( "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" "github.com/algorand/go-algorand/util/db" + "github.com/algorand/go-deadlock" ) var testPoolAddr = basics.Address{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff} @@ -67,6 +67,20 @@ type mockLedgerForTracker struct { trackers trackerRegistry } +// onlineTotals returns the online totals of all accounts at the end of round rnd. +// used in tests only +func (au *accountUpdates) onlineTotals(rnd basics.Round) (basics.MicroAlgos, error) { + au.accountsMu.RLock() + defer au.accountsMu.RUnlock() + offset, err := au.roundOffset(rnd) + if err != nil { + return basics.MicroAlgos{}, err + } + + totals := au.roundTotals[offset] + return totals.Online.Money, nil +} + func accumulateTotals(t testing.TB, consensusVersion protocol.ConsensusVersion, accts []map[basics.Address]ledgercore.AccountData, rewardLevel uint64) (totals ledgercore.AccountTotals) { var ot basics.OverflowTracker proto := config.Consensus[consensusVersion] @@ -325,7 +339,7 @@ func checkAcctUpdates(t *testing.T, au *accountUpdates, ao *onlineAccounts, base require.Equal(t, latestRnd, latest) // the log has "onlineAccounts failed to fetch online totals for rnd" warning that is expected - _, err := ao.onlineTotals(latest + 1) + _, err := ao.onlineCirculation(latest+1, latest+1+basics.Round(ao.maxBalLookback())) require.Error(t, err) var validThrough basics.Round @@ -334,7 +348,8 @@ func checkAcctUpdates(t *testing.T, au *accountUpdates, ao *onlineAccounts, base require.Equal(t, basics.Round(0), validThrough) if base > 0 && base >= basics.Round(ao.maxBalLookback()) { - _, err := ao.onlineTotals(base - basics.Round(ao.maxBalLookback())) + rnd := base - basics.Round(ao.maxBalLookback()) + _, err := ao.onlineCirculation(rnd, base) require.Error(t, err) _, validThrough, err = au.LookupWithoutRewards(base-1, ledgertesting.RandomAddress()) @@ -397,7 +412,7 @@ func checkAcctUpdates(t *testing.T, au *accountUpdates, ao *onlineAccounts, base bll := accts[rnd] require.Equal(t, all, bll) - totals, err := ao.onlineTotals(rnd) + totals, err := ao.onlineCirculation(rnd, rnd+basics.Round(ao.maxBalLookback())) require.NoError(t, err) require.Equal(t, totals.Raw, totalOnline) diff --git a/ledger/catchpointtracker_test.go b/ledger/catchpointtracker_test.go index a9d4eff35b..dcf6c68c64 100644 --- a/ledger/catchpointtracker_test.go +++ b/ledger/catchpointtracker_test.go @@ -1756,6 +1756,7 @@ func TestCatchpointFastUpdates(t *testing.T) { delta := ledgercore.MakeStateDelta(&blk.BlockHeader, 0, updates.Len(), 0) delta.Accts.MergeAccounts(updates) + delta.Totals = accumulateTotals(t, protocol.ConsensusCurrentVersion, []map[basics.Address]ledgercore.AccountData{totals}, rewardLevel) ml.addBlock(blockEntry{block: blk}, delta) accts = append(accts, newAccts) rewardsLevels = append(rewardsLevels, rewardLevel) diff --git a/ledger/double_test.go b/ledger/double_test.go index 1a894e6719..1c53e54b09 100644 --- a/ledger/double_test.go +++ b/ledger/double_test.go @@ -42,7 +42,7 @@ import ( // then temporarily placed in `generate` mode so that the entire block can be // generated in the copy second ledger, and compared. type DoubleLedger struct { - t *testing.T + t testing.TB generator *Ledger validator *Ledger @@ -56,9 +56,9 @@ func (dl DoubleLedger) Close() { } // NewDoubleLedger creates a new DoubleLedger with the supplied balances and consensus version. -func NewDoubleLedger(t *testing.T, balances bookkeeping.GenesisBalances, cv protocol.ConsensusVersion, cfg config.Local) DoubleLedger { - g := newSimpleLedgerWithConsensusVersion(t, balances, cv, cfg) - v := newSimpleLedgerFull(t, balances, cv, g.GenesisHash(), cfg) +func NewDoubleLedger(t testing.TB, balances bookkeeping.GenesisBalances, cv protocol.ConsensusVersion, cfg config.Local, opts ...simpleLedgerOption) DoubleLedger { + g := newSimpleLedgerWithConsensusVersion(t, balances, cv, cfg, opts...) + v := newSimpleLedgerFull(t, balances, cv, g.GenesisHash(), cfg, opts...) return DoubleLedger{t, g, v, nil} } @@ -165,7 +165,7 @@ func (dl *DoubleLedger) reloadLedgers() { require.NoError(dl.t, dl.validator.reloadLedger()) } -func checkBlock(t *testing.T, checkLedger *Ledger, vb *ledgercore.ValidatedBlock) { +func checkBlock(t testing.TB, checkLedger *Ledger, vb *ledgercore.ValidatedBlock) { bl := vb.Block() msg := bl.MarshalMsg(nil) var reconstituted bookkeeping.Block diff --git a/ledger/ledger.go b/ledger/ledger.go index b29ec61a5a..65c7964e47 100644 --- a/ledger/ledger.go +++ b/ledger/ledger.go @@ -630,11 +630,12 @@ func (l *Ledger) LatestTotals() (basics.Round, ledgercore.AccountTotals, error) return l.accts.LatestTotals() } -// OnlineTotals returns the online totals of all accounts at the end of round rnd. -func (l *Ledger) OnlineTotals(rnd basics.Round) (basics.MicroAlgos, error) { +// OnlineCirculation returns the online totals of all accounts at the end of round rnd. +// It implements agreement's calls for Circulation(rnd) +func (l *Ledger) OnlineCirculation(rnd basics.Round, voteRnd basics.Round) (basics.MicroAlgos, error) { l.trackerMu.RLock() defer l.trackerMu.RUnlock() - return l.acctsOnline.onlineTotals(rnd) + return l.acctsOnline.onlineCirculation(rnd, voteRnd) } // CheckDup return whether a transaction is a duplicate one. diff --git a/ledger/ledger_test.go b/ledger/ledger_test.go index 9e89e82ce9..5b7ceb33c1 100644 --- a/ledger/ledger_test.go +++ b/ledger/ledger_test.go @@ -21,7 +21,6 @@ import ( "context" "errors" "fmt" - "github.com/algorand/go-algorand/ledger/eval" "math/rand" "os" "path/filepath" @@ -42,6 +41,7 @@ import ( "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/data/transactions/logic" "github.com/algorand/go-algorand/data/transactions/verify" + "github.com/algorand/go-algorand/ledger/eval" "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/ledger/store/trackerdb" ledgertesting "github.com/algorand/go-algorand/ledger/testing" @@ -2205,10 +2205,11 @@ func TestLedgerReloadShrinkDeltas(t *testing.T) { l.cfg = cfg l.reloadLedger() - _, err = l.OnlineTotals(basics.Round(proto.MaxBalLookback - shorterLookback)) + rnd := basics.Round(proto.MaxBalLookback - shorterLookback) + _, err = l.OnlineCirculation(rnd, rnd+basics.Round(proto.MaxBalLookback)) require.Error(t, err) for i := basics.Round(proto.MaxBalLookback - shorterLookback + 1); i <= l.Latest(); i++ { - online, err := l.OnlineTotals(i) + online, err := l.OnlineCirculation(i, i+basics.Round(proto.MaxBalLookback)) require.NoError(t, err) require.Equal(t, onlineTotals[i], online) } @@ -2631,10 +2632,11 @@ func TestLedgerMigrateV6ShrinkDeltas(t *testing.T) { l2.Close() }() - _, err = l2.OnlineTotals(basics.Round(proto.MaxBalLookback - shorterLookback)) + rnd := basics.Round(proto.MaxBalLookback - shorterLookback) + _, err = l2.OnlineCirculation(rnd, rnd+basics.Round(proto.MaxBalLookback)) require.Error(t, err) for i := l2.Latest() - basics.Round(proto.MaxBalLookback-1); i <= l2.Latest(); i++ { - online, err := l2.OnlineTotals(i) + online, err := l2.OnlineCirculation(i, i+basics.Round(proto.MaxBalLookback)) require.NoError(t, err) require.Equal(t, onlineTotals[i], online) } diff --git a/ledger/simple_test.go b/ledger/simple_test.go index 31601bd25c..1f4c612902 100644 --- a/ledger/simple_test.go +++ b/ledger/simple_test.go @@ -35,20 +35,40 @@ import ( "github.com/stretchr/testify/require" ) -func newSimpleLedgerWithConsensusVersion(t testing.TB, balances bookkeeping.GenesisBalances, cv protocol.ConsensusVersion, cfg config.Local) *Ledger { +type simpleLedgerCfg struct { + onDisk bool // default is in-memory + notArchival bool // default is archival +} + +type simpleLedgerOption func(*simpleLedgerCfg) + +func simpleLedgerOnDisk() simpleLedgerOption { + return func(cfg *simpleLedgerCfg) { cfg.onDisk = true } +} + +func simpleLedgerNotArchival() simpleLedgerOption { + return func(cfg *simpleLedgerCfg) { cfg.notArchival = true } +} + +func newSimpleLedgerWithConsensusVersion(t testing.TB, balances bookkeeping.GenesisBalances, cv protocol.ConsensusVersion, cfg config.Local, opts ...simpleLedgerOption) *Ledger { var genHash crypto.Digest crypto.RandBytes(genHash[:]) - return newSimpleLedgerFull(t, balances, cv, genHash, cfg) + return newSimpleLedgerFull(t, balances, cv, genHash, cfg, opts...) } -func newSimpleLedgerFull(t testing.TB, balances bookkeeping.GenesisBalances, cv protocol.ConsensusVersion, genHash crypto.Digest, cfg config.Local) *Ledger { +func newSimpleLedgerFull(t testing.TB, balances bookkeeping.GenesisBalances, cv protocol.ConsensusVersion, genHash crypto.Digest, cfg config.Local, opts ...simpleLedgerOption) *Ledger { + var slCfg simpleLedgerCfg + for _, opt := range opts { + opt(&slCfg) + } genBlock, err := bookkeeping.MakeGenesisBlock(cv, balances, "test", genHash) require.NoError(t, err) require.False(t, genBlock.FeeSink.IsZero()) require.False(t, genBlock.RewardsPool.IsZero()) dbName := fmt.Sprintf("%s.%d", t.Name(), crypto.RandUint64()) - cfg.Archival = true - l, err := OpenLedger(logging.Base(), dbName, true, ledgercore.InitState{ + dbName = strings.Replace(dbName, "/", "_", -1) + cfg.Archival = !slCfg.notArchival + l, err := OpenLedger(logging.Base(), dbName, !slCfg.onDisk, ledgercore.InitState{ Block: genBlock, Accounts: balances.Balances, GenesisHash: genHash, diff --git a/ledger/store/trackerdb/interface.go b/ledger/store/trackerdb/interface.go index f65dc8695e..9e9fbb1a10 100644 --- a/ledger/store/trackerdb/interface.go +++ b/ledger/store/trackerdb/interface.go @@ -113,6 +113,7 @@ type AccountsReaderExt interface { LookupOnlineAccountDataByAddress(addr basics.Address) (ref OnlineAccountRef, data []byte, err error) AccountsOnlineTop(rnd basics.Round, offset uint64, n uint64, proto config.ConsensusParams) (map[basics.Address]*ledgercore.OnlineAccount, error) AccountsOnlineRoundParams() (onlineRoundParamsData []ledgercore.OnlineRoundParamsData, endRound basics.Round, err error) + ExpiredOnlineAccountsForRound(rnd, voteRnd basics.Round, proto config.ConsensusParams, rewardsLevel uint64) (map[basics.Address]*ledgercore.OnlineAccountData, error) OnlineAccountsAll(maxAccounts uint64) ([]PersistedOnlineAccountData, error) LoadTxTail(ctx context.Context, dbRound basics.Round) (roundData []*TxTailRound, roundHash []crypto.Digest, baseRound basics.Round, err error) LoadAllFullAccounts(ctx context.Context, balancesTable string, resourcesTable string, acctCb func(basics.Address, basics.AccountData)) (count int, err error) diff --git a/ledger/store/trackerdb/sqlitedriver/accountsV2.go b/ledger/store/trackerdb/sqlitedriver/accountsV2.go index 3ffb069999..d6e7330903 100644 --- a/ledger/store/trackerdb/sqlitedriver/accountsV2.go +++ b/ledger/store/trackerdb/sqlitedriver/accountsV2.go @@ -297,6 +297,50 @@ func (r *accountsV2Reader) OnlineAccountsAll(maxAccounts uint64) ([]trackerdb.Pe return result, nil } +// ExpiredOnlineAccountsForRound returns all online accounts known at `rnd` that will be expired by `voteRnd`. +func (r *accountsV2Reader) ExpiredOnlineAccountsForRound(rnd, voteRnd basics.Round, proto config.ConsensusParams, rewardsLevel uint64) (map[basics.Address]*ledgercore.OnlineAccountData, error) { + // This relies on SQLite's handling of max(updround) and bare columns not in the GROUP BY. + // The values of votelastvalid, votefirstvalid, and data will all be from the same row as max(updround) + rows, err := r.q.Query(`SELECT address, data, max(updround) +FROM onlineaccounts +WHERE updround <= ? +GROUP BY address +HAVING votelastvalid < ? and votelastvalid > 0 +ORDER BY address`, rnd, voteRnd) + if err != nil { + return nil, err + } + defer rows.Close() + + ret := make(map[basics.Address]*ledgercore.OnlineAccountData) + for rows.Next() { + var addrbuf []byte + var buf []byte + var addr basics.Address + var baseData trackerdb.BaseOnlineAccountData + var updround sql.NullInt64 + err := rows.Scan(&addrbuf, &buf, &updround) + if err != nil { + return nil, err + } + if len(addrbuf) != len(addr) { + err = fmt.Errorf("account DB address length mismatch: %d != %d", len(addrbuf), len(addr)) + return nil, err + } + copy(addr[:], addrbuf) + err = protocol.Decode(buf, &baseData) + if err != nil { + return nil, err + } + oadata := baseData.GetOnlineAccountData(proto, rewardsLevel) + if _, ok := ret[addr]; ok { + return nil, fmt.Errorf("duplicate address in expired online accounts: %s", addr.String()) + } + ret[addr] = &oadata + } + return ret, nil +} + // TotalResources returns the total number of resources func (r *accountsV2Reader) TotalResources(ctx context.Context) (total uint64, err error) { err = r.q.QueryRowContext(ctx, "SELECT count(1) FROM resources").Scan(&total) diff --git a/ledger/store/trackerdb/sqlitedriver/schema.go b/ledger/store/trackerdb/sqlitedriver/schema.go index e99e9c68e2..47cb0180df 100644 --- a/ledger/store/trackerdb/sqlitedriver/schema.go +++ b/ledger/store/trackerdb/sqlitedriver/schema.go @@ -154,6 +154,10 @@ const createStateProofVerificationTableQuery = ` lastattestedround integer primary key NOT NULL, verificationcontext blob NOT NULL)` +const createVoteLastValidIndex = ` + CREATE INDEX IF NOT EXISTS onlineaccounts_votelastvalid_idx + ON onlineaccounts ( votelastvalid )` + var accountsResetExprs = []string{ `DROP TABLE IF EXISTS acctrounds`, `DROP TABLE IF EXISTS accounttotals`, @@ -927,3 +931,9 @@ func reencodeAccounts(ctx context.Context, tx *sql.Tx) (modifiedAccounts uint, e updateStmt.Close() return } + +func convertOnlineRoundParamsTail(ctx context.Context, tx *sql.Tx) error { + // create vote last index + _, err := tx.ExecContext(ctx, createVoteLastValidIndex) + return err +} diff --git a/ledger/store/trackerdb/sqlitedriver/trackerdbV2.go b/ledger/store/trackerdb/sqlitedriver/trackerdbV2.go index 1940e54a21..2c7a370f02 100644 --- a/ledger/store/trackerdb/sqlitedriver/trackerdbV2.go +++ b/ledger/store/trackerdb/sqlitedriver/trackerdbV2.go @@ -499,6 +499,11 @@ func (tu *trackerDBSchemaInitializer) upgradeDatabaseSchema9(ctx context.Context return fmt.Errorf("upgradeDatabaseSchema9 unable to replace kvstore nil entries with empty byte slices : %v", err) } + err = convertOnlineRoundParamsTail(ctx, tx) + if err != nil { + return fmt.Errorf("upgradeDatabaseSchema10 unable to convert onlineroundparamstail: %v", err) + } + // update version return tu.setVersion(ctx, tx, 10) } diff --git a/ledger/testing/testGenesis.go b/ledger/testing/testGenesis.go index 80d752c759..98a41d06d5 100644 --- a/ledger/testing/testGenesis.go +++ b/ledger/testing/testGenesis.go @@ -25,11 +25,29 @@ import ( "github.com/algorand/go-algorand/protocol" ) +// testGenesisCfg provides a configuration object for NewTestGenesis. +type testGenesisCfg struct { + rewardsPoolAmount basics.MicroAlgos +} + +// TestGenesisOption provides functional options for testGenesisCfg. +type TestGenesisOption func(*testGenesisCfg) + +// TestGenesisRewardsPoolSize configures the rewards pool size in the genesis block. +func TestGenesisRewardsPoolSize(amount basics.MicroAlgos) TestGenesisOption { + return func(cfg *testGenesisCfg) { cfg.rewardsPoolAmount = amount } +} + // NewTestGenesis creates a bunch of accounts, splits up 10B algos // between them and the rewardspool and feesink, and gives out the // addresses and secrets it creates to enable tests. For special // scenarios, manipulate these return values before using newTestLedger. -func NewTestGenesis() (bookkeeping.GenesisBalances, []basics.Address, []*crypto.SignatureSecrets) { +func NewTestGenesis(opts ...TestGenesisOption) (bookkeeping.GenesisBalances, []basics.Address, []*crypto.SignatureSecrets) { + var cfg testGenesisCfg + for _, opt := range opts { + opt(&cfg) + } + // irrelevant, but deterministic sink, err := basics.UnmarshalChecksumAddress("YTPRLJ2KK2JRFSZZNAF57F3K5Y2KCG36FZ5OSYLW776JJGAUW5JXJBBD7Q") if err != nil { @@ -66,8 +84,12 @@ func NewTestGenesis() (bookkeeping.GenesisBalances, []basics.Address, []*crypto. Status: basics.NotParticipating, } + poolBal := basics.MicroAlgos{Raw: amount} + if cfg.rewardsPoolAmount.Raw > 0 { + poolBal = cfg.rewardsPoolAmount + } accts[rewards] = basics.AccountData{ - MicroAlgos: basics.MicroAlgos{Raw: amount}, + MicroAlgos: poolBal, } genBalances := bookkeeping.MakeGenesisBalances(accts, sink, rewards) From a497df043cdd8627e76b192c1208029d0734fbd5 Mon Sep 17 00:00:00 2001 From: Eric Warehime Date: Fri, 19 May 2023 13:15:26 -0700 Subject: [PATCH 103/125] algod: Don't return a top level array from algod (#5404) --- daemon/algod/api/algod.oas2.json | 18 +- daemon/algod/api/algod.oas3.yml | 44 +- .../api/server/v2/generated/data/routes.go | 232 ++++----- .../v2/generated/experimental/routes.go | 336 ++++++------ .../api/server/v2/generated/model/types.go | 6 +- .../nonparticipating/private/routes.go | 233 ++++----- .../nonparticipating/public/routes.go | 493 +++++++++--------- .../generated/participating/private/routes.go | 238 ++++----- .../generated/participating/public/routes.go | 255 ++++----- daemon/algod/api/server/v2/handlers.go | 7 +- .../algod/api/server/v2/test/handlers_test.go | 8 +- 11 files changed, 956 insertions(+), 914 deletions(-) diff --git a/daemon/algod/api/algod.oas2.json b/daemon/algod/api/algod.oas2.json index b5c2376125..0adcc18c8b 100644 --- a/daemon/algod/api/algod.oas2.json +++ b/daemon/algod/api/algod.oas2.json @@ -1564,7 +1564,7 @@ ], "responses": { "200": { - "$ref": "#/responses/TransactionGroupLedgerStateDeltaForRoundResponse" + "$ref": "#/responses/TransactionGroupLedgerStateDeltasForRoundResponse" }, "401": { "description": "Invalid API Token", @@ -4040,12 +4040,20 @@ "$ref": "#/definitions/LedgerStateDelta" } }, - "TransactionGroupLedgerStateDeltaForRoundResponse": { + "TransactionGroupLedgerStateDeltasForRoundResponse": { "description": "Response containing all ledger state deltas for transaction groups, with their associated Ids, in a single round.", "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/LedgerStateDeltaForTransactionGroup" + "type": "object", + "required": [ + "deltas" + ], + "properties": { + "deltas": { + "type": "array", + "items": { + "$ref": "#/definitions/LedgerStateDeltaForTransactionGroup" + } + } } } }, diff --git a/daemon/algod/api/algod.oas3.yml b/daemon/algod/api/algod.oas3.yml index 3903fb4db3..1acacc4daa 100644 --- a/daemon/algod/api/algod.oas3.yml +++ b/daemon/algod/api/algod.oas3.yml @@ -857,14 +857,22 @@ }, "description": "Supply represents the current supply of MicroAlgos in the system." }, - "TransactionGroupLedgerStateDeltaForRoundResponse": { + "TransactionGroupLedgerStateDeltasForRoundResponse": { "content": { "application/json": { "schema": { - "items": { - "$ref": "#/components/schemas/LedgerStateDeltaForTransactionGroup" + "properties": { + "deltas": { + "items": { + "$ref": "#/components/schemas/LedgerStateDeltaForTransactionGroup" + }, + "type": "array" + } }, - "type": "array" + "required": [ + "deltas" + ], + "type": "object" } } }, @@ -4143,18 +4151,34 @@ "content": { "application/json": { "schema": { - "items": { - "$ref": "#/components/schemas/LedgerStateDeltaForTransactionGroup" + "properties": { + "deltas": { + "items": { + "$ref": "#/components/schemas/LedgerStateDeltaForTransactionGroup" + }, + "type": "array" + } }, - "type": "array" + "required": [ + "deltas" + ], + "type": "object" } }, "application/msgpack": { "schema": { - "items": { - "$ref": "#/components/schemas/LedgerStateDeltaForTransactionGroup" + "properties": { + "deltas": { + "items": { + "$ref": "#/components/schemas/LedgerStateDeltaForTransactionGroup" + }, + "type": "array" + } }, - "type": "array" + "required": [ + "deltas" + ], + "type": "object" } } }, diff --git a/daemon/algod/api/server/v2/generated/data/routes.go b/daemon/algod/api/server/v2/generated/data/routes.go index 8c925b6d0c..03aa23d8c8 100644 --- a/daemon/algod/api/server/v2/generated/data/routes.go +++ b/daemon/algod/api/server/v2/generated/data/routes.go @@ -172,122 +172,122 @@ var swaggerSpec = []string{ "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", - "BRzLAAA=", + "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", + "AAD//yvhnHBdywAA", } // 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 df54602af8..0d4e810054 100644 --- a/daemon/algod/api/server/v2/generated/experimental/routes.go +++ b/daemon/algod/api/server/v2/generated/experimental/routes.go @@ -76,174 +76,174 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL var swaggerSpec = []string{ "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/generated/model/types.go b/daemon/algod/api/server/v2/generated/model/types.go index b133369a7b..50fe884e3c 100644 --- a/daemon/algod/api/server/v2/generated/model/types.go +++ b/daemon/algod/api/server/v2/generated/model/types.go @@ -1088,8 +1088,10 @@ type SupplyResponse struct { TotalMoney uint64 `json:"total-money"` } -// TransactionGroupLedgerStateDeltaForRoundResponse defines model for TransactionGroupLedgerStateDeltaForRoundResponse. -type TransactionGroupLedgerStateDeltaForRoundResponse = []LedgerStateDeltaForTransactionGroup +// TransactionGroupLedgerStateDeltasForRoundResponse defines model for TransactionGroupLedgerStateDeltasForRoundResponse. +type TransactionGroupLedgerStateDeltasForRoundResponse struct { + Deltas []LedgerStateDeltaForTransactionGroup `json:"deltas"` +} // TransactionParametersResponse TransactionParams contains the parameters that help a client construct // a new transaction. 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 7cff7a4400..e4ec6dd160 100644 --- a/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go +++ b/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go @@ -188,122 +188,123 @@ var swaggerSpec = []string{ "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", + "b43ZywAA", } // 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 56670010cb..6c99f788e9 100644 --- a/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go +++ b/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go @@ -702,252 +702,253 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "H4sIAAAAAAAC/+x9a5PbtpLoX0Fpt8qPFTV+Zk+mKrV3YucxG8dxeSY5ezb2TSCyJeEMBfAAoEaKr//7", - "LTQAEiRBiZqnncwne0QSaDQajX73h1EqloXgwLUaHX4YFVTSJWiQ+BdNU1FynbDM/JWBSiUrNBN8dOif", - "EaUl4/PReMTMrwXVi9F4xOkS6nfM9+ORhH+VTEI2OtSyhPFIpQtYUjOw3hTm7WqkdTIXiRviyA5x/HL0", - "ccsDmmUSlOpC+RPPN4TxNC8zIFpSrmhqHilyzvSC6AVTxH1MGCeCAxEzoheNl8mMQZ6piV/kv0qQm2CV", - "bvL+JX2sQUykyKEL5wuxnDIOHiqogKo2hGhBMpjhSwuqiZnBwOpf1IIooDJdkJmQO0C1QITwAi+Xo8Nf", - "Rwp4BhJ3KwW2wv/OJMAfkGgq56BH78exxc00yESzZWRpxw77ElSZa0XwXVzjnK2AE/PVhPxYKk2mQCgn", - "b799QZ4+ffqlWciSag2ZI7LeVdWzh2uyn48ORxnV4B93aY3mcyEpz5Lq/bffvsD5T9wCh75FlYL4YTky", - "T8jxy74F+A8jJMS4hjnuQ4P6zReRQ1H/PIWZkDBwT+zLV7op4fy3uisp1emiEIzryL4QfErs4ygPCz7f", - "xsMqABrvFwZT0gz666Pky/cfHo8fP/r4b78eJf/r/nz+9OPA5b+oxt2BgeiLaSkl8HSTzCVQPC0Lyrv4", - "eOvoQS1EmWdkQVe4+XSJrN59S8y3lnWuaF4aOmGpFEf5XChCHRllMKNlromfmJQ8N2zKjOaonTBFCilW", - "LINsbLjv+YKlC5JSZYfA98g5y3NDg6WCrI/W4qvbcpg+higxcF0IH7igTxcZ9bp2YALWyA2SNBcKEi12", - "XE/+xqE8I+GFUt9Var/LipwugODk5oG9bBF33NB0nm+Ixn3NCFWEEn81jQmbkY0oyTluTs7O8Hu3GoO1", - "JTFIw81p3KPm8Pahr4OMCPKmQuRAOSLPn7suyviMzUsJipwvQC/cnSdBFYIrIGL6T0i12fb/PvnpNRGS", - "/AhK0Tm8oekZAZ6KDLIJOZ4RLnRAGo6WEIfmy751OLhil/w/lTA0sVTzgqZn8Rs9Z0sWWdWPdM2W5ZLw", - "cjkFabbUXyFaEAm6lLwPIDviDlJc0nV30lNZ8hT3v562IcsZamOqyOkGEbak668ejR04itA8JwXwjPE5", - "0WveK8eZuXeDl0hR8myAmKPNngYXqyogZTMGGalG2QKJm2YXPIzvB08tfAXg+EF6walm2QEOh3WEZszp", - "Nk9IQecQkMyE/OyYGz7V4gx4RehkusFHhYQVE6WqPuqBEafeLoFzoSEpJMxYhMZOHDoMg7HvOA68dDJQ", - "KrimjENmmDMCLTRYZtULUzDhdn2ne4tPqYIvnvXd8fXTgbs/E+1d37rjg3YbX0rskYxcneapO7Bxyarx", - "/QD9MJxbsXlif+5sJJufmttmxnK8if5p9s+joVTIBBqI8HeTYnNOdSnh8B1/aP4iCTnRlGdUZuaXpf3p", - "xzLX7ITNzU+5/emVmLP0hM17kFnBGlW48LOl/ceMF2fHeh3VK14JcVYW4YLShuI63ZDjl32bbMfclzCP", - "Km03VDxO114Z2fcLva42sgfIXtwV1Lx4BhsJBlqazvCf9Qzpic7kH+afosjN17qYxVBr6NhdyWg+cGaF", - "o6LIWUoNEt+6x+apYQJgFQlav3GAF+rhhwDEQooCpGZ2UFoUSS5SmidKU40j/buE2ehw9G8Htf3lwH6u", - "DoLJX5mvTvAjI7JaMSihRbHHGG+M6KO2MAvDoPERsgnL9lBoYtxuoiElZlhwDivK9aRWWRr8oDrAv7qZ", - "anxbacfiu6WC9SKc2BenoKwEbF+8p0iAeoJoJYhWFEjnuZhWP9w/Kooag/j8qCgsPlB6BIaCGayZ0uoB", - "Lp/WJymc5/jlhHwXjo2iuOD5xlwOVtQwd8PM3VruFqtsS24N9Yj3FMHtFHJitsajwYj5V0FxqFYsRG6k", - "np20Yl7+3r0bkpn5fdDHnweJhbjtJy5UtBzmrI6DvwTKzf0W5XQJx5l7JuSo/e3FyMaMEieYC9HK1v20", - "427BY4XCc0kLC6B7Yu9SxlFJsy9ZWC/JTQcyuijMwRkOaA2huvBZ23keopAgKbRg+DoX6dn3VC2u4MxP", - "/Vjd44fTkAXQDCRZULWYjGJSRni86tGGHDHzIir4ZBpMNamWeFXL27G0jGoaLM3BGxdLLOrxO2R6ICO6", - "y0/4H5oT89icbcP67bATcooMTNnj7JwMmdH2rYJgZzIvoBVCkKVV8InRuveC8kU9eXyfBu3RN9am4HbI", - "LQJ3SKyv/Bh8LdYxGL4W684REGtQV0EfZhwUIzUs1QD4XjrIBO6/Qx+Vkm66SMaxhyDZLNCIrgpPAw9v", - "fDNLbZw9mgp5Me7TYiuc1CZnQs2oAfMdt5CEr5ZF4kgxYrayL7QGqr1825lGe/gYxhpYONH0GrCgzKhX", - "gYXmQFeNBbEsWA5XQPqLKNOfUgVPn5CT74+eP37y25PnXxiSLKSYS7ok040GRe473YwovcnhQXdlqB2V", - "uY6P/sUzb6hsjhsbR4lSprCkRXcoawC1IpB9jZj3ulhrohlXXQE45HCeguHkFu3E2vYNaC+ZMhLWcnol", - "m9GHsKyeJSMOkgx2EtO+y6un2YRLlBtZXoUqC1IKGbGv4RHTIhV5sgKpmIh4U964N4h7w4u3Rft3Cy05", - "p4qYudH0W3IUKCKUpdd8ON+3Q5+ueY2brZzfrjeyOjfvkH1pIt9bEhUpQCZ6zUkG03Le0IRmUiwJJRl+", - "iHf0d6BRFDhlSzjRdFn8NJtdjaoocKCIysaWoMxMxL5h5HoFqeA2EmKHduZGHYKeNmK8iU73A+AwcrLh", - "KdoZr+LY9iuuS8bR6aE2PA20WANjDtm8QZaX11b70GGnuqci4Bh0vMLHaOh4Cbmm3wp5WlsCv5OiLK5c", - "yGvPOXQ51C3GmVIy863XoRmf583om7mBfRJb460s6IU/vm4NCD1S5Cs2X+hArXgjhZhdPYyxWWKA4gOr", - "lOXmm65q9lpkhpnoUl2BCFYPVnM4Q7chX6NTUWpCCRcZ4OaXKi6c9cRroKMY/ds6lPf0wupZUzDUldLS", - "rLYsCHpvO/dF/WFCU3tCE0SN6vFdVU5H+5adzsYC5BJotiFTAE7E1DmInOsKF0nR9ay9eONEwwi/aMBV", - "SJGCUpAlzjC1EzT/nr069BY8IeAIcDULUYLMqLw0sGernXCewSbBQAlF7v/wi3pwC/BqoWm+A7H4Tgy9", - "lZrvvIBdqIdNv43g2pOHZEclEH+vEC1Qms1BQx8K98JJ7/61Iers4uXRsgKJ/rhrpXg/yeUIqAL1mun9", - "stCWRU/4n1NvjYRnNoxTLrxgFRssp0onu9iyeamhg5sVBJwwxolx4B7B6xVV2vqQGc/Q9GWvE5zHCmFm", - "in6Ae9UQM/IvXgPpjp2ae5CrUlXqiCqLQkgNWWwNHNZb5noN62ouMQvGrnQeLUipYNfIfVgKxnfIsiux", - "CKK6crW4IIvu4tAhYe75TRSVDSBqRGwD5MS/FWA3DIHqAYSpGtGWcJhqUU4VdzUeKS2KwnALnZS8+q4P", - "TSf27SP9c/1ul7ioru/tTIDCyCv3voP83GLWBr8tqCIODrKkZ0b2QDOIdXZ3YTaHMVGMp5Bso3xU8cxb", - "4RHYeUjLYi5pBkkGOd10B/3ZPib28bYBcMdrdVdoSGwUU3zTa0r2QSNbhhY4nooJjwSfkNQcQaMK1ATi", - "vt4xcgY4dow5OTq6Vw2Fc0W3yI+Hy7ZbHRkRb8OV0GbHHT0gyI6jDwG4Bw/V0BdHBX6c1Lpne4p/gHIT", - "VHLE/pNsQPUtoR5/rwX02FBdgHhwXlrsvcWBo2yzl43t4CN9R7bHoPuGSs1SVqCu8wNsrlz1a08QdTOS", - "DDRlOWQkeGDVwCL8ntj4m/aYF1MFB9neuuB3jG+R5eRMocjTBP4MNqhzv7GBnYGp4yp02cio5n6inCCg", - "PlzMiODhK7Cmqc43RlDTC9iQc5BAVDldMq1twHZT1dWiSMIBon6NLTM6J54NivQ7MMSreIJDBcvrbsV4", - "ZHWC7fCdthSDBjqcLlAIkQ+wkHWQEYVgULwHKYTZdeZix330sKekBpCOaaMHt7r+76kGmnEF5B+iJCnl", - "qHKVGiqZRkgUFFCANDMYEaya00V21BiCHJZgNUl88vBhe+EPH7o9Z4rM4NwnXJgX2+h4+BDtOG+E0o3D", - "dQX2UHPcjiPXBzp8zMXntJA2T9kdWeBGHrKTb1qDV14ic6aUcoRrln9pBtA6meshaw9pZFhUBY47yJcT", - "DB1bN+77CVuWOdVX4bWCFc0TsQIpWQY7ObmbmAn+zYrmP1Wf7dDp6igwtlxCxqiGfEMKCSnY6Hwjqqlq", - "7AmxcXvpgvI5SuhSlHMXOGbHQQ5bKmsLkSXvDBGVYvSaJ2hVjnFcFyzsEzSM/ALU6FBtk7TVGM5pNZ/L", - "yRlyFfqdi5joo16p8ahXxTRIXdUqpkVOM8tkAPdtCFgBfuqJB/ouEHVG2OjiK9wWQ71mc6/HRl4PHYOy", - "O3EQylY/7ItmM/ptvrkCKcMORCQUEhTeCaFdSNmnYhZmlLlLQ22UhmXXdG4//a3n+L3tVdAEzxmHZCk4", - "bKJJ1IzDj/gwepzwXur5GCWEvm/bQn8D/hZYzXmGUONl8Yu73T6hET/bxV2Qg3jFAM/eEEk66ojL84gr", - "zqWLtM+vGlfp6UwSqpRIGco4x5ka23PivHcut6SJvTdVEOwVHJ32uC2fU5iJiDZVyAtCSZoztLgKrrQs", - "U/2OU7TpBEuNBAt55bXfyvfCvxI3K0asfm6od5xioFhl6YkGOMwgYtb4FsAb+1Q5n4PSLd1gBvCOu7cY", - "JyVnGudaGmpPLLkXIDFiZ2LfXNINmRma0IL8AVKQaamb0jJmQynN8tw5wMw0RMzecapJDkbh/5Hx0zUO", - "553k/sRx0OdCnlVYiF/Oc+CgmEriQU3f2acYb+qWv3Cxp5i9bh9bl4kZv06Z2qDJp87I/r/3/+vw16Pk", - "f2nyx6Pky/84eP/h2ccHDzs/Pvn41Vf/r/nT049fPfivf4/tlIc9lqvjID9+6TTJ45eoLtQ+kw7sN2Yv", - "XzKeRIksjH5o0Ra5j3mpjoAeNI1JegHvuF5zQ0grmrPM8JaLkEP7guicRXs6WlTT2IiW8civdU8h/BJc", - "hkSYTIs1XlgI6sYBxrPi0InnEt3wvMxKbrfSC8826cPHY4nZuMp8tEVRDgmmxS2oDyZ0fz55/sVoXKez", - "Vc9H45F7+j5CySxbx5IWM1jHdCt3QPBg3FOkoBsFOs49EPZo6JmNhQiHXYJRytWCFTfPKZRm0ziH86H0", - "zkaz5sfcxrib84MuwY3zNIjZzcOtJUAGhV7EiiU05Cx8q95NgFaYRiHFCviYsAlM2jaSzKh7LgguBzrD", - "pH1UHsUQZaY6B5bQPFUEWA8XMsgQEaMfFHkct/44HrnLX125NuMGjsHVnrPy//m/tSD3vvvmlBw4hqnu", - "2fxZO3SQ8RjRhF1STyOAx3AzWyLGCnnv+Dv+EmaMM/P88B3PqKYHU6pYqg5KBfJrmlOewmQuyKHPE3pJ", - "NX3HO5JWbxWnIEOLFOU0Zyk5C/WJmjxtZY7uCO/e/UrzuXj37n0nlqEr/bupovzFTpAYQViUOnF1BRIJ", - "51TGfEWqyivHkW3hkG2zWiFblNag6OsWuPHjPI8WhWrnl3aXXxS5WX5AhsplT5otI0oL6WURI6BYaHB/", - "Xwt3MUh67s0ipQJFfl/S4lfG9XuSvCsfPXoKpJFw+bu78g1NbgoYbBzpzX9t20Rw4VYrhLWWNCnoPOaS", - "evfuVw20wN1HeXmJJoo8J/hZI9HTB7LjUPUCPD76N8DCsXfSGi7uxH7la0jFl4CPcAvxHSNu1I7yi+5X", - "kPp54e1qpY92dqnUi8Sc7eiqlCFxvzNVaZm5EbJ89IJic9RWXRWeKZB0AemZK48Cy0Jvxo3PfYCMEzQ9", - "62DKFs6xiVtYugEN+lMgZZFRJ4pTvmnn0CvQ2ofhvoUz2JyKuvLDPknzzRxu1XdQkVID6dIQa3hs3Rjt", - "zXdRWKjYF4VPhcacOE8WhxVd+G/6D7IVea/gEMeIopFj3IcIKiOIsMTfg4ILLNSMdynSjy3PaBlTe/NF", - "iuh43k/cK7Xy5AKmwtWg0dw+XwJW4RLnikypkduFKyBl85QDLlYqOoceCTn0qQzMBm74YXCQXfde9KYT", - "s/aF1rlvoiDblxOz5iilgHliSAWVmVaYnJ/Juu2cYwHrQjqETXMUk6p4Qst0qGz4tmyhuz7Q4gQMktcC", - "hwejiZFQsllQ5WtbYQkwf5YHyQDXmHe/rdrKcRDhFdT5qmqpeJ7bPqcd7dLVXPGFVnx1lVC1HFApxUj4", - "GFQe2w7BUQDKIIe5Xbh92RNKXQOg3iADx0+zWc44kCQWLBaYQYNrxs0BRj5+SIg1oJPBI8TIOAAb3dE4", - "MHktwrPJ5/sAyV0NA+rHRkd28DfE061s+LQReURhWDjrcUqlngNQF2FY3V+tOFcchjA+JobNrWhu2JzT", - "+OpBOkU/UGxtlfhwAREP+sTZLf4Le7HstSZ7FV1kNaHM5IGOC3RbIJ6KdWLzLaMS73Q9NfQejSjH7M/Y", - "wbTlVe4pMhVrDLLBq8VGMO+ApR8OD0ag4a+ZQnrF7/pucwvMtmm3S1MxKlRIMs6cV5FLnzgxZOoeCaaP", - "XO4HFVMuBEDL2FGXH3bK704ltSmedC/z+lYb15XAfLJO7Pj3HaHoLvXgr2uFqWqcvGlLLFE7RTNWpFne", - "JRAhY0Rv2ETXSdN1BSnIAZWCpCFEJWcxx6fRbQBvnBP/WWC8wCIylG8eBAFIEuZMaaiN6D7M4TbMkxRr", - "1wkx61+dLuTMrO+tENU1Zd2I+GFjmTe+AozgnTGpdIIeiOgSzEvfKlSqvzWvxmWlZoiTrfTKsjhvwGnP", - "YJNkLC/j9Orm/eGlmfZ1xRJVOUV+y7iNN5liZeJo4OOWqW1s7NYFv7ILfkWvbL3DToN51UwsDbk05/hM", - "zkWL825jBxECjBFHd9d6UbqFQQYJq13uGMhNgY9/ss362jlMmR97Z9CNT5vtu6PsSNG1BAaDratg6CYy", - "YgnTQWHfbiZpzxmgRcGydcsWakft1ZjpXgYPXw6thQXcXTfYDgwEds9YMosE1ax8Vwv4tkRzo/DMZBBm", - "Tpv16UKGEE7FlG8w0EVUley2C1enQPMfYPOLeReXM/o4Hl3OdBrDtRtxB67fVNsbxTO65q0preEJ2RPl", - "tCikWNE8cQbmPtKUYuVIE1/39ugbZnVxM+bpN0ev3jjwP45HaQ5UJpWo0LsqfK/4bFZli+z1HBBfwNzo", - "fF5mt6JksPlVZbDQKH2+AFcJOpBGOyUra4dDcBSdkXoWjxDaaXJ2vhG7xC0+EigqF0ltvrMekqZXhK4o", - "y73dzEPbE82DixtW9zTKFcIBLu1dCZxkyZWym87pjp+Omrp28KRwri21qpe2HLsigrdd6BiyvCmc131J", - "seCktYp0mRMvl2hJSFTO0riNlU+VIQ5ufWfmZYIv9wijZsSS9bhiecmCscxrQ0rKtIAM5ogiU0Wr2tS4", - "mwrXaqfk7F8lEJYB1+aRxFPZOqhYncRZ27vXqZEdunO5ga2Fvh7+MjJGWGy1feMhENsFjNBT1wH3ZaUy", - "+4VWFinzQ+CS2MPhH87YuRK3OOsdfThqtsGLi6bHLeyM0+V/hjBsifTdbXm88uqqvvbMEW2zw1Qyk+IP", - "iOt5qB5H8oR8eVmGUS5/QJinEDaXaLCYyrpTdwuqZ+/d7j7pJrRCNYMUeqgedz5wy2GdS2+hptxute16", - "0Yh1ixNMGFV6YMevCcbB3InEzen5lMaKgBohw8B0VDuAG7Z0LYj/2ONeVckSdnYS+JKrd5nNAS9A1il8", - "3XoyFxQY7LSDRYVaMkCqDWWCsfX/5UpEhin5OeW2eYr5zh4l97UCa/wyX50LiRUcVNzsn0HKljSPSw5Z", - "2jXxZmzObF+QUkHQeMINZHsuWSpyzTuqFCCHmuMZeTQOut+43cjYiik2zQHfeGzfmFKFnLwyRFWfmOUB", - "1wuFrz8Z8Pqi5JmETC+URawSpBLqUL2pnFdT0OcAnDzC9x5/Se6j206xFTwwWHT38+jw8ZdodLV/PIpd", - "AK6vyzZukiE7+btjJ3E6Rr+lHcMwbjfqJJrsbhu79TOuLafJfjrkLOGbjtftPktLyukc4pEiyx0w2W9x", - "N9GQ1sILz2xXIqWl2BCm4/ODpoY/9USfG/ZnwSCpWC6ZXjrnjhJLQ091Vwk7qR/OtjhyBYE9XP4h+kgL", - "7yJqKZE3azS191ts1ejJfk2X0ETrmFBbtiNndfSCL1NOjn1VIKyQXBVGtrgxc5mlo5iDwQwzUkjGNSoW", - "pZ4lfyPpgkqaGvY36QM3mX7xLFIVulmdlO8H+I3jXYICuYqjXvaQvZch3LfkPhc8WRqOkj2osz2CU9nr", - "zI277fp8h9uHHiqUmVGSXnIrG+RGA059KcLjWwa8JClW69mLHvde2Y1TZinj5EFLs0M/v33lpIylkLFS", - "f/VxdxKHBC0ZrDB2L75JZsxL7oXMB+3CZaC/Xc+DFzkDscyf5Zgi8LWIaKe+UnllSXex6hHrQN8xNQ8M", - "GUzdUGPSrAp983z0aqKg4p4ub9juOrbME48H/KONiFsmF9zA2pdvV9JDKEFV/CjJZNXzwMdOyddiPZRw", - "WqfQE88ngKIoSkqWZ7/UmZ+tpgOS8nQR9ZlNzYe/1e3RqsXZOzBatW9BOYc8OpyVN3/zcmlEcv6nGDrP", - "kvGB77b7INjlthZXA94E0wPlJzToZTo3E4RYbSbVVUHb+VxkBOepS8TVx7XbPyOocv6vEpSOJSjhAxs4", - "hrZRww5skW0CPEONdEK+sx2QF0Aa9X9QE/SFHppZ02WRC5qNsQDF6TdHr4id1X5jm/zYIt9zVISaq2jZ", - "xILql8NCkH2/nnh6xPBxtsdrm1UrnVQ1uWMJqOaNumo4a/kJUEUKsTMhL4NepjZX1Qxh6GHG5NJoddVo", - "Vj5CmjD/0ZqmC1T7Gqy1n+SHV6f3VKmCjpBVZ6eqJCSeOwO3K1Bv69OPiTC6+TlTtvEtrKCZ81olgDuz", - "g8+BbS5PlpxbSpnscctVBSD3RbsHzl6R3pUQhayF+D2FftvcYd9i/Sf4VbRCVbvyf6cVpM2grDr2+Ibm", - "KeWCsxTrQ8WuaNchd4ifbUAprbYh1x9xd0Ijhyvab6AKxXNY7O1A4BmhQ1zX0B88NZtqqcP+qbEV64Jq", - "MgetHGeDbOzbZjhbI+MKXIlP7Kcc8EkhG75L5JBRd3hSuU32JCNMvelRHr81z1470wLGpJ8xjkqEQ5sT", - "/Kw1EBt4aqN5ME3mApRbTzP/WP1qvplgKm4G6/cT3/ATx7CuP7Ns6+fuDnXkvd7Oy2zefWHedfWNqp8b", - "Uc520qOicJP2N1WJygN6zXsRHPFeJt59FCC3Gj8cbQu5bQ1XwfvUEBqs0NkNBd7DHcKoGoy0mlcZodVS", - "FL5BbJhYtEoC4xEwXjEOdTvayAWRRq8E3Bg8rz3fqVRSbUXAQTztFGiOHu4YQ1PauTcuO1S7upNBCa7R", - "z9G/jXVvlB7GUb1QC26Ub6ouuIa6A2HiBbbfdojsdjpBqcoJURlmLbR6n8QYh2HcvrtS8wLoHoOuTGQ/", - "15Lak7PPTdSXiDotsznohGZZrOLq1/iU4FOSlSg5wBrSsqrMWRQkxborzUI0XWpzE6WCq3K5ZS7/wiWn", - "C5oJRaghbGjkdxgTXaYb/DdWlrJ/Z1ygx96hhj6qw/Xh2FNubo7UkXoNTSeKzZPhmMA75fLoqKe+GKHX", - "318ppedi3gTkhstPbONy4R7F+Ns35uIIqzN0aq3aq6UqnoCBfcK3gES1sUr7bXIlvMo6xVfRoVS1mNtu", - "gOhvFjfGy68nvDcoukHt/Wo9lH1BvmlvTDrVLjtOU7KVBfVmHNkIIZtbhFDErbN9UUE2KMg87nw9TDLs", - "yNk6XrcwQKgPN+sC9IOPZSUFZc79XjOLLmZd1Hs3D2FIPGy9we1FuFjyXovdD6u+uG9fjA2ft5tJnYFL", - "mS8krJgovWPbRz55ldD+2mjNVEXeR9ffNbziVLdrDu013p66ov52mU4n/+EXGydHgGu5+QRMuZ1N77Sp", - "6kq71jxVv0KqetCD6kM3bsUhBQhjNfGcbNholLWjzVeXsQ4RB7ptu8Yjlu11YbavEhzGjhI7dvEmXP1l", - "p+pSU3jECqFYXZY91p1rYIjhKTbYCspmdcfy8T0rSDXW4q/jFiTAPkW0zGRBv8+78lM96nQViemqTm0r", - "NdUtwL/jju9kgwUZjbZ4+WR4YaWjKjoN+TQWM54Ddy03m3keg6PNZzNINVvtyL77+wJ4kNk19nYZ2zo7", - "SMZjVfQyFm/Z3+pYA7QtOW4rPEERxUuD05d7cwabe4o0qCFaTX3sr9qL1O1ADCB3SAyJCBWL/rCGZOeQ", - "Z6qiDMSCj7ayn0NdAa23EVOQS3rBuTxJmoujzi/dMmW8E8ygucyne2VdYyBuX4Jet5FEv/7xEvt2qKpJ", - "oq/7EWrp5LhbHfHc1Q3BXMnKd+IriIDyv/nEaDtLzs4gbBWFnqpzKjP/RtT04q06yZb7qJNV55sgtIGe", - "VTOzOja2m0cVqbeFEdBpLowYkfSFkTfDUatYjnvKBt3Y6u0YaGvgmoF0LfVQ/s2FgkQLH0u7DY5tqLCR", - "RRdCguqtcWmB660887YurYO1filWmqEuoChcIJGwpAY6GRTA6Z9zG7Jf2Oc+ccjXet1pYarodXfPAB8V", - "zVQHiSHVz4i7LXcnJF3E2MQ4t22bVawaDgfZ9IYUUmRlai/o8GBUBrnBtaa2sJKonSbtrrKlIwRZnWew", - "ObBKkG+24HcwBNpKThb0oIpCa5Ov1PymYnDPrwS827RcjUeFEHnS4+w47pbwaVP8GUvPICPmpvDRgz2N", - "a8h9tLFX3uzzxcaXrCkK4JA9mBByxG28tndsN2tItybn9/S2+dc4a1baqlrOqDZ5x+OBr1jvSl6Sm/lh", - "tvMwBYbVXXIqO8iOAjHrnvJBkp5H2jhNhmrlXVdzu7VOTVQWiphMUneN2REnU4XI1I076jCZrnSQ5+I8", - "QSpKqvpfMZ3DvNdkkr7iaf2ZwfYUgngbqtwFuiELmpFUSAlp+EU8xcECtRQSklxg+E3MMzjTRh5aYlwz", - "J7mYE1EYNdeW0fM+lGhXmWAum2Zrv0yso6ankAEol1brprEvd+fZ0nxm/8Y2p4uIvQUR7bG8d/caRygD", - "ulG0uyBVYA4g0N22pqNYc57mutrtnfqarWmxZGkc3Z9XlElvbMiO1kOR9VXk6Doj+azAHlxFXbbbPaS2", - "jdx0qJ+0qpk88FgEAPR7ThswDPKf7gvGDNsyJjSC5ONKah03uuay1tn39ewsjafUaq0LIGbsUoLLUrP9", - "41qNbwqqF/4WM693dUujp4DCFDLb/oMqawnxFhnXvK4tHogiyWEFDYeyS50r0xSUYisIG9/Zj0kGUKB9", - "si01xzylIZdriVJu7UngaxuC3ahsZRFrd4rsEJyiYt6aJ/aYqKFHyUC0YllJG/hTl2gl1tdFLMKGPawD", - "OcXeTCK+uG0sYmdsA9J89FzyeGhDmLlZGUVwtqwynloirE+2Kug571ciInanyt9++XUQHIyoViZ175Uv", - "q125qALZSxnbCKPT/i8qcyjw7VvDoide3HLfRmQsa+piKjIAU/V5xug9qKPDgteWdEMyNpuBtMZ8pSnP", - "qMzC1xknKUhNmdFsNuriYq2BVpYw3inZGu6Kg3oGE5Nx0S5lAck3TmW4hNSJnpuIxGmvWi36Ohx2diWe", - "TkDXRrrGuKoeInCJ0Chb2wMmOApIZEnPYM95FPsDtk+D5Umc7U8LnHXIFDFf6wVrqw1i3d0whMjtFvQy", - "3O4ZCksv1jld0kazoCXZX5BtGv+xvjiHdVX0H+wAL3QYBn0Vve3GgXPLyVE/VkgJlvK+jxIay9/lg3QL", - "rCWNYIscI9AabCFcG1Df3JfAwaxeVH7bvhagbfcu1lkU3Db567iFLW+yXfsCwjFnQa5ofvOuXSzAeYT4", - "gOxtvzE49A2GSLaoVBfLTHhFB80d+AGvbmr+Bl3RfwezR1Gt1A3lRJhKrPfBPHiz0NwaLma+hdcKODnH", - "MW0c2+MvyNRlbhcSUqbaotG5765RucKw2ZTLBlnrHb63Xev8RehLkPHMaxrkdV2pH3X8Oa8hrI/oLTOV", - "npMbpfIY9XXIIoK/GI8KS6jtuC7OGgFutvNJK3NDSLjiQLcgZH3PQLducbihy7PBXObSKRV01zn4tm7g", - "NnJR12sbGqXZRe62cu5DgivjXRrM5xjdaRGCLU4Igkp+f/w7kTDDHoaCPHyIEzx8OHav/v6k+dgc54cP", - "o9LZjcV1Why5Mdy8MYr5pS/Tz2az9SSVtvajZHm2izAaKcJ1F1BMgv3NFSK4lT6kv9lYk+5Rdb3gLhEg", - "ZxETWWtj8mCqIPl3QN6v+yyS5Yt+nLSUTG+wPqK3H7DfohGo31XRTC4artIP3d2nxRlUFTbr2KdS+dv1", - "O0FzvI+s2srNLSTyCflmTZdFDu6gfHVv+p/w9G/PskdPH//n9G+Pnj9K4dnzLx89ol8+o4+/fPoYnvzt", - "+bNH8Hj2xZfTJ9mTZ0+mz548++L5l+nTZ4+nz7748j/vGT5kQLaAjnw1ntH/YLPe5OjNcXJqgK1xQgv2", - "A7hmz4aMfcdBmuJJhCVl+ejQ//R//AmbpGJZD+9/HbliH6OF1oU6PDg4Pz+fhJ8czDHYIdGiTBcHfp5O", - "S8KjN8eVl8hagXBHbZ6st+55UjjCZ2+/OTklR2+OJ0G7+cPRo8mjyWPsTl4ApwUbHY6e4k94eha47weO", - "2EaHHz6ORwcLoDnGBpo/lqAlS/0jCTTbuP+rczqfg5y4Nozmp9WTAy9WHHxwQR8fzQxRfdqmiAd5wd3u", - "hC6ADI1RNgW80e1HueYz46oHlDM28gwzd20chWFzFeKOs7rZwXHNtHzJR1sD+/DXSCDujM3R9OArETY6", - "RLoGcUyR/z756TURkjj15g1Nzyq/BTme2fJdUqwYJoRmQRax+XLi6fdfJchNTV+O84X1nX1LH+cAWap5", - "0cxJq6WqmI8m1gkSZzZkERB2FaJVMy400YT9cys2bFjro+TL9x+e/+3jaAAgGC+oAKt//U7z/HdyzrCh", - "INoXff1MVx9tHGlfg9L0uA75wQ/qnRxjUl31NOxQWL3TTOX+nQsOv/dtgwMsug80z82LgkNsD95jfSok", - "FjxzTx49urLWplX1AuulqUbxJHGBgboMyT6qWqSeS1rYs+g7nKKjG1Vhv1Bs6PrsChfazD269HLbw3UW", - "/TXNsGscKG2X8vizXcoxx5Bdc0EQewF+HI+ef8Z7c8wNz6E5wTeD4o/di+ZnfsbFOfdvGuGnXC6p3KBo", - "E7S2bFVGoXOF8SDIIu3ZbjSzG73/2HvrHYS9ug4+NKI+s0vdiZ02hccvd1yT91Qf5+yWTm+1AjPPq05P", - "GBfo+p1h7yn1YEK+C79G7o2VyGydr1JyyHzQpr/1qtKqvmBrDds9FRZpi17agbn47v6+7fv7qGnsaJTn", - "jgHTOAVbYep4FS57gXZdpa1mzxdqphw05bpAa5Nr7TjZ0jXtTO9jquBORn2Hux7c9YlJAbyVxNRspnb9", - "rNknCVY3SePKuEbG/ZkLfT/S3NBJsNxWMR5bs/5OGPzLCINVMtHcSmeuTcvlxENs2HjwwfchuAKR0PVh", - "GCAMhmp18G0Q9XG/xU4eTGxTgfCdi/EMlz20U8zD7hB3At4nIOB1O6/EwKj7adyeUIcwLOrWLDu7wPim", - "Ku1e+nu1kPlMpbi/MLJ6xTYD6W6B7QLssyOMOWZ9bWz1TymEOaTdiV9/afGryum9lADW6J3kssQDN9al", - "rHdt6xzTlSTWzOsOOBuGVBuG4o7wuO7zaFgMlsX0FdHU2GuG6E61SqPdrHFHb+yKWN9BqKB+vTl+uUu6", - "+ozsPIPLM0dugfjeXDcvjbod3t6M22EYb3r26NnNQRDuwmuhybd4i18zh7xWlhYnq31Z2DaOdDC1zSm2", - "cSXeYkvIKOqmEwGPqgpajIPn5m0bpXHfdVYPC3o9mBDfCkNVjb1cYtdcGEblc0qonNuPDK8zyCD3/J+H", - "OP69CfkWE3q0GmOwmXZdn8g9xvXh4ydPn7lXJD23sVzt96ZfPDs8+uor91rd+MTqOZ3XlZaHC8hz4T5w", - "d0R3XPPg8H/+8b+TyeTeTrYq1l9vXtsKwJ8Kb+2qdyEB9O3WZ75JMW3d9/LYhbobcd9/LdbRW0Cs726h", - "W7uFDPb/FLfPtElGThGtLJmNGkFXeBvZY7LPfTT2TT4M36kukwl5LVy5tjKnkgiZgXSdEOcllZRrgGzi", - "KRWTTpUtT5XmDLg2iiP2dpOJYhnYKjfzUkKVPldIWGGMPE6POn0Dgt2MHiNpP1km/yNdByWcptU1rYVb", - "Mpo9l3Ttu0ti/zQh8aevviKPxrX2kudmgKRCTIy5Lul6dINWv4rYBsWfN5sz7QzQxbGHWJBq6afKoQ07", - "wfy1OfdnK7lbcncbe0Wcc2/HT+3YCe0IrijaVguCFexs70lshrips/GNlOdFqDiLMzMMNQ58wj6Cnabp", - "qBLaRu/dIb4zAlyKlbQJak+2gVmn6uAD6uUhz+icW8ya+2u5SwPfkRRL7zwSZAY6XbiE3RbqI+zJt4bq", - "503bmo9ftVSDu9itehHWpMam2APLngW5lOjAAxkh4p98lwbzmM1sgRlfkMn32EfXFPNtZ6uOs64vtys1", - "oUWV12t2cS8oX9STdwUyRMtV+D/vELwfgjvM8RvfehQx5hbxZ4j496pkQl6LOm3cdb36M7oer/Nmv+4F", - "vRYcrI/dSL6WFu/cqZXYYRiHRYqvF2L1l6r/yIVFkAPfRHerHPK9bWG7VRYZcnubyT7LK/x7h6Utt4xZ", - "22RnMYR6tCHM2bxoq2A1O2LcohZzK/z0E1RtboNj3QyLwUPq+YwTC/jVMh0swWOJ+aBqhtDHgeL9ZQZz", - "Iy2qMLRoS5gp5ILP1afJirZ2+oniJUIlVeedeHudv97ZfYHVfYzKayMgXb0nxXgKtkk09rdjiiyZUi5Y", - "8tmjv90chJotfUVxHuau3jJ3ef7o6c1NfwJyxVIgp7AshKSS5RvyM68ael+G22HzoKr+mrcGR/tFobep", - "WRcsDYsYXZwJNkLXPug1yz7uZoZBxco9+SDjAR8MyxvSogAqL84Ad7uu2uW2j1+G0cGNnjZVRa0IKAZF", - "ewbI/8dooN0J097FzF1+JbeA+upfjk240F0xG1fBMUYKELND8o4/JGpBnz9+8tuT51/4P588/6LHcmbm", - "cUV7urazeiDz2A4zxID2WZsDr1Zqr/B7eNO7vd8mjkcsW0e7XtR97DpFr51Ydk+Rgm56W+MUO/rwhcPW", - "Pfluvtih0my6iOpXXv2pysof868rLdhW5HPt6+767/UkTwR8xhBa3Yivwvr2nnxbpMkWWVbNz25aOa2T", - "DOxF55EnW3fOrQq6+raU1AR1VOBesGmi5fZkSuzMMg7c3YUUWqQit7ErZVEIqavTrSaDxD3oc9s1pL0+", - "wt1LmEupThdlcfAB/4MVvj7WiQe2AfuBXvMDrB598GFriACCGGkNa+XSaEuFrpo8oDPtrhCA1okZtw+R", - "rYSNsQQR+ex6pLO/tFCzX6ffy5q0IyN2DnCVVxd00a5oNyj8vaO58eTOBfOJLag2iswYzwgNtrGluwlZ", - "M4JrNoxc96Jvw85y836n55/xOXstNDleFrZhDmSXi94hbQ7nb4+t1+1+goG7+rshPt07P7zxfWBiZV3f", - "ecHv4ZALUrHBT0cl5kabu/p6bN93N/mnfZO/8CWHG2R4dy9/Pvey9OGUd1fwp38FP/1sV3ONjpiBV7K/", - "iS58Ddea+J4XcqRLKJoMWq7wbX4aVL3bq1TfCunbW9zd4p/NLT4oOWmIJSaWsrTzFF/j7MP0/DyPaPp9", - "B2Vse+3oBTAs+iJShvW7jzM1tofIGQfcKboTPD5pwSPY6zu54071/8xU/x4pw2ndzSapfRf9vgLIaiky", - "8FEfYjZzRdb6pI9m7xdDnkrTZUHsl1EpA72hp2wJJ+bNn+wUV+pHr8FuiSUt8AyyFKSCZ2qAV9KNOsTd", - "GLuH0I3aD8CNeyCrHfCwuPTryYVJ9m1Qw6VDCaSNfIU9e3yxOYeMDFbEEODkCsj24IP9F81ZhVCxrsee", - "gDsbc99ti62eZ8dtAEjeoBBoy/D5r8SMPLJF9EqOmTJ1cz7KM6LlxgiKvmaIBJqTtBEhX8HRPTknvSdn", - "pyjeWV3PmuKyuKhP6FWGk7ayk3648QPwgnJH8l0EaUEo4TCnmq3Ax41P7jLaL3ybuXzyLQxwTGiW2dNY", - "bwKsQG6IKqfKyDq8Geh4TzXPyx4MA9YFSGauaJrXDnCrJhzYdPVtAY0n9o1LXlotXmST5Fs9//3N6lLo", - "xYz8yFIpjvK5UD6uSm2UhmWn9Z379Leeoqdeke/GYAmeMw7JUvBYQ7af8OmP+DDaz1xomvd9fGoe9n3b", - "um+b8LfAas4z5E6+LH4/kdN/qVyJ1molFEJq3yQfnJq851Hyh2bD0+5J2vA0cCq5h8FAYfu2xs8HHxp/", - "umIV7k21KHUmzoNvUbO3QTdD8tSDRtEXsGS1Gi6r67VlXacPJ8BD7MRUTyOtt4J24L3dt/6i+RnO5RES", - "CYZOpmIFUrXUs7skjT9Vksbgfd+Lx9pWk7s4WqmuViJ5LTKw4zY7vcbqI3ORgeuI2RVEqmDDeGC7v5Xq", - "91qhxikt5wtNyoJoEQtqrj9MaGqZbGLVm/iEQUUyqwThdAu6AkJz7DNKpgCciKlZdH0/4iKpwppwPjLa", - "hVRGRaEArkKKFJSCLPH1oHeBVvUZxThqvQVPCDgCXM1ClCAzKi8N7NlqJ5xVn25F7v/wi1GYbxxeKwpu", - "R6ytRBVBb1Xtwkl7XaiHTb+N4NqTh2RHJRAvGmAih1gWObhUjggK98JJ7/61Iers4uXRgrkO7Jop3k9y", - "OQKqQL1mer8stGWRmPu7C+IL+/SULVES45QLb1eMDZZTpZNdbNm8FK5FmRUEnDDGiXHgHoXzFVX6rcvq", - "y7ACjL1OcB4rY5sp+gFe9fWDNyP/UnWD74ydmvuQq1JVLeNdpD5ksTVwWG+Z6zWsq7kwrdKPXaUCWAvf", - "rpH7sBSM75AVFMUmVAfedDNcZHFof6TOQNFFZQOIGhHbADnxbwXYDd3oPYAwVSPaEg4W+QwpZypEDpTb", - "jCpRFIZb6KTk1Xd9aDqxbx/pn+t3u8RFdX1vZwJUmKbhID+3mFVooF1QRRwcZEnPXCbH3DU56sJsDmOC", - "GdjJNspHk615KzwCOw9pWcwlzSDJIKcRU8rP9jGxj7cNgDvuyTNZCQ3JFGZCQnzTa0qWvSaiamiB46mY", - "8EjwCUnNETTKc00g7usdI2eAY8eYk6Oje9VQOFd0i/x4uGy71T1mKTOG2XFHDwiy4+hDAO7BQzX0xVGB", - "Hye1+aA9xT9AuQkqOWL/STag+pZQj7/XAtrmvPACa9wULfbe4sBRttnLxnbwkb4jGzMgfpbG/nbs0DVW", - "X2kaUAMFcHIR5fbgnDKdzIS0gnRCZxrkzoD0v1Pm3eHONaCFqw1AcAR3b7pxkMmHrSYcF7EgEHddGBLp", - "+t/MVN8KOajEZbOQC2WalFyzPCjzXanKn57B8M4IcGcEuDMC3BkB7owAd0aAOyPAnRHgzghwZwS4MwLc", - "GQH+ukaA2ypam3iJw5fy4oIn7ahEcheV+Kcq8ljdVd4ogWaMc8q061rp8+3dk8vVuNVAc8QBy6E/TtqG", - "b55+c/SKKFHKFEhqIGScFDk1ugGsddVDrdmd0/cNto0YbeNPquDpE3Ly/ZGvRbdwNdOa794/cv23ld7k", - "8MB1KQCeWVHUtysAbpDuuhVQfyf4Xmuu8xzLMcZckW/w7ZewglwUIG2ZK6JlGTH5nALNXzjc7LD4/N1M", - "7oJWfzej/T5uGJoc2pa08HK+XytVhNrcQfIyyCb8fUZzBb/3JRTa8Za0iLU7q24+awtCbvK1yDatE2J2", - "7QA3sHk26op0jFO5idQ76iYTtElDC8OvHGF1jVkfr7xuYpdou2S2i8Ji4roEFT3H26g8WjCw2rDOUDbl", - "dNaik1Gs0Ui7St6oAnBICOwpJhzYPSFv7Xe3W5UdIXJHrGbmn0zkYPPNimngu0aLcKznc43K94iPnl48", - "+2ND2FmZAmFaEV96cff1Mh6tEzPSHHjiGFAyFdkmabCvUeMWypiiSsFyuvsmCvmna/DrLh/zZPs9dTvX", - "yMtgcdt4ckg068Qx4B7uvNEwmDdX2MIRHXsOMH7dLLqPjYYgEMefYlalFu/bl+nV02zuGN8d4wtOY0si", - "YNyVqm0zkck1Mj65kSXv53nfrCEtDXDhSb6P5nn0ycFaNxybGUzL+RwbFXecdGZpgOMxwW+JFdrlDuWC", - "+1GQHbxqXnnZdO/2cF3uEmRg3/c1Bh/gdlC+QW/GsqB8432+kCi2LHOLQ9vj7WoZra0m240EQH+sM/71", - "mbXfeJtfYLx1V23zd4sWck4VsfsLGSl55nKHOjWn13x4k2Q79Oma12x6a5tku97I6ty8Q64Iv8vNpG1F", - "CpCJXnN7oJqdzG1ta3tyJ3cNWv8a14ZN+YYeBtut01wzhCu6PWTA1/D6CLpx1MlwjR4daLXoTx0JW3PY", - "N680eqQzfDOIpDapOCcp5AWhvnt+KrjSskz1O07RSRMsbNINMPHW6H7+9sK/EvcTRtx4bqh3nGJz9cp1", - "E+VzM4j4Kb4F8GxUlfM5KMMrQyKZAbzj7i3GScmNpiVmZMlSKRKbiGrOkJFPJvbNJd2QGdb/EOQPkIJM", - "zc0e7Lo1GCvN8txFtJhpiJi941STHKjS5EdmuKwZzhcfqEK5QJ8LeVZhId6pYQ4cFFNJ3PjynX2KzRDc", - "8r2RDw2W9nFdxPxmuyB42FnWC/nxSwM3xdrBOVO6DoLowH5jDvAl40mUyE4XQFxMWJu2yH2sWOYI6EHT", - "O6QX8I6bG04Lglyd6ouRQ9vN0zmL9nS0qKaxES1vkF/rIBXvSrgMiTCZO9fKnyg1M6AD777EjbfV4Ft7", - "v6cbpXHlAs/M054L2T51zbN6XnJKQsMQ1ioH4944bYD85228/v569EWPxivTGLsDdtlVsz0S4s1v+JjQ", - "XPC5rUJoNEiB+8R4UWoMrL5OIx2saJ6IFUjJMlADV8oE/2ZF85+qz3ZcgEFzt+USMkY15BtSSEghs3Wy", - "mCK1kjyxlQZIuqB8jnelFOV8YV+z45yDhKoPltFL20PE65SseWJrpnVhPCLWwBiWdQWaLiJ9RfBGMYqw", - "30FbBmKIqhs5wljpsk/zHY96JVuD1FUdkGaR0zzXA67txgUc4Kee+Cq6399R2R2V7U1lsRJ7iLpZS+e2", - "+Aq35ZqNM9ddUPIGbT23Um32rmT6n71kuudAilAiaUPKjvfqooowTc6xoM4UiLkwSrQxu5beTiOdEMOQ", - "Anu6rbyoXKfJdEEZd9VYqvB8hEO7brjat9+7FvOcZWZolzPogLSUTG9QLqcF++0MzP/fG8FWgVx5kb2U", - "+ehwtNC6ODw4yEVK84VQ+mD0cRw+U62H7yv4P3hpu5BsZTSIj+8//v8AAAD//xNodLIldAEA", + "H4sIAAAAAAAC/+x9a5PbtpLoX0Fpt8qPFTV+Zk+mKrV3YucxG8dx2ZOcPRv7JhDZknCGAngAUCPF1//9", + "FhoACZKgRM1oxnYyn+wRSaDRaDT63e9HqVgWggPXanT8flRQSZegQeJfNE1FyXXCMvNXBiqVrNBM8NGx", + "f0aUlozPR+MRM78WVC9G4xGnS6jfMd+PRxL+VTIJ2ehYyxLGI5UuYEnNwHpTmLerkdbJXCRuiBM7xOnz", + "0YctD2iWSVCqC+VPPN8QxtO8zIBoSbmiqXmkyAXTC6IXTBH3MWGcCA5EzIheNF4mMwZ5piZ+kf8qQW6C", + "VbrJ+5f0oQYxkSKHLpzPxHLKOHiooAKq2hCiBclghi8tqCZmBgOrf1ELooDKdEFmQu4A1QIRwgu8XI6O", + "fx0p4BlI3K0U2Ar/O5MAf0CiqZyDHr0bxxY30yATzZaRpZ067EtQZa4VwXdxjXO2Ak7MVxPyY6k0mQKh", + "nLz+9hl5/Pjxl2YhS6o1ZI7IeldVzx6uyX4+Oh5lVIN/3KU1ms+FpDxLqvdff/sM53/jFjj0LaoUxA/L", + "iXlCTp/3LcB/GCEhxjXMcR8a1G++iByK+ucpzISEgXtiXz7opoTzf9RdSalOF4VgXEf2heBTYh9HeVjw", + "+TYeVgHQeL8wmJJm0F8fJF++e/9w/PDBh3/79ST5X/fn08cfBi7/WTXuDgxEX0xLKYGnm2QugeJpWVDe", + "xcdrRw9qIco8Iwu6ws2nS2T17ltivrWsc0Xz0tAJS6U4yedCEerIKIMZLXNN/MSk5LlhU2Y0R+2EKVJI", + "sWIZZGPDfS8WLF2QlCo7BL5HLlieGxosFWR9tBZf3ZbD9CFEiYHrUvjABX26yKjXtQMTsEZukKS5UJBo", + "seN68jcO5RkJL5T6rlL7XVbkbAEEJzcP7GWLuOOGpvN8QzTua0aoIpT4q2lM2IxsREkucHNydo7fu9UY", + "rC2JQRpuTuMeNYe3D30dZESQNxUiB8oRef7cdVHGZ2xeSlDkYgF64e48CaoQXAER039Cqs22//ebn14S", + "IcmPoBSdwyuanhPgqcggm5DTGeFCB6ThaAlxaL7sW4eDK3bJ/1MJQxNLNS9oeh6/0XO2ZJFV/UjXbFku", + "CS+XU5BmS/0VogWRoEvJ+wCyI+4gxSVddyc9kyVPcf/raRuynKE2poqcbhBhS7r+6sHYgaMIzXNSAM8Y", + "nxO95r1ynJl7N3iJFCXPBog52uxpcLGqAlI2Y5CRapQtkLhpdsHD+H7w1MJXAI4fpBecapYd4HBYR2jG", + "nG7zhBR0DgHJTMjPjrnhUy3OgVeETqYbfFRIWDFRquqjHhhx6u0SOBcakkLCjEVo7I1Dh2Ew9h3HgZdO", + "BkoF15RxyAxzRqCFBsusemEKJtyu73Rv8SlV8MWTvju+fjpw92eivetbd3zQbuNLiT2SkavTPHUHNi5Z", + "Nb4foB+Gcys2T+zPnY1k8zNz28xYjjfRP83+eTSUCplAAxH+blJszqkuJRy/5ffNXyQhbzTlGZWZ+WVp", + "f/qxzDV7w+bmp9z+9ELMWfqGzXuQWcEaVbjws6X9x4wXZ8d6HdUrXghxXhbhgtKG4jrdkNPnfZtsx9yX", + "ME8qbTdUPM7WXhnZ9wu9rjayB8he3BXUvHgOGwkGWprO8J/1DOmJzuQf5p+iyM3XupjFUGvo2F3JaD5w", + "ZoWToshZSg0SX7vH5qlhAmAVCVq/cYQX6vH7AMRCigKkZnZQWhRJLlKaJ0pTjSP9u4TZ6Hj0b0e1/eXI", + "fq6OgslfmK/e4EdGZLViUEKLYo8xXhnRR21hFoZB4yNkE5btodDEuN1EQ0rMsOAcVpTrSa2yNPhBdYB/", + "dTPV+LbSjsV3SwXrRTixL05BWQnYvnhHkQD1BNFKEK0okM5zMa1+uHtSFDUG8flJUVh8oPQIDAUzWDOl", + "1T1cPq1PUjjP6fMJ+S4cG0VxwfONuRysqGHuhpm7tdwtVtmW3BrqEe8ogtsp5MRsjUeDEfMPQXGoVixE", + "bqSenbRiXv7evRuSmfl90MefB4mFuO0nLlS0HOasjoO/BMrN3RbldAnHmXsm5KT97eXIxowSJ5hL0crW", + "/bTjbsFjhcILSQsLoHti71LGUUmzL1lYr8hNBzK6KMzBGQ5oDaG69FnbeR6ikCAptGD4Ohfp+fdULQ5w", + "5qd+rO7xw2nIAmgGkiyoWkxGMSkjPF71aEOOmHkRFXwyDaaaVEs81PJ2LC2jmgZLc/DGxRKLevwOmR7I", + "iO7yE/6H5sQ8NmfbsH477IScIQNT9jg7J0NmtH2rINiZzAtohRBkaRV8YrTuvaB8Vk8e36dBe/SNtSm4", + "HXKLwB0S64Mfg6/FOgbD12LdOQJiDeoQ9GHGQTFSw1INgO+5g0zg/jv0USnppotkHHsIks0Cjeiq8DTw", + "8MY3s9TG2ZOpkJfjPi22wkltcibUjBow33ELSfhqWSSOFCNmK/tCa6Day7edabSHj2GsgYU3ml4DFpQZ", + "9RBYaA50aCyIZcFyOADpL6JMf0oVPH5E3nx/8vTho98ePf3CkGQhxVzSJZluNChy1+lmROlNDve6K0Pt", + "qMx1fPQvnnhDZXPc2DhKlDKFJS26Q1kDqBWB7GvEvNfFWhPNuOoKwCGH8wwMJ7doJ9a2b0B7zpSRsJbT", + "g2xGH8KyepaMOEgy2ElM+y6vnmYTLlFuZHkIVRakFDJiX8MjpkUq8mQFUjER8aa8cm8Q94YXb4v27xZa", + "ckEVMXOj6bfkKFBEKEuv+XC+b4c+W/MaN1s5v11vZHVu3iH70kS+tyQqUoBM9JqTDKblvKEJzaRYEkoy", + "/BDv6O9AoyhwxpbwRtNl8dNsdhhVUeBAEZWNLUGZmYh9w8j1ClLBbSTEDu3MjToEPW3EeBOd7gfAYeTN", + "hqdoZzzEse1XXJeMo9NDbXgaaLEGxhyyeYMsr66t9qHDTnVHRcAx6HiBj9HQ8RxyTb8V8qy2BH4nRVkc", + "XMhrzzl0OdQtxplSMvOt16EZn+fN6Ju5gX0SW+NHWdAzf3zdGhB6pMgXbL7QgVrxSgoxOzyMsVligOID", + "q5Tl5puuavZSZIaZ6FIdQASrB6s5nKHbkK/RqSg1oYSLDHDzSxUXznriNdBRjP5tHcp7emH1rCkY6kpp", + "aVZbFgS9t537ov4woak9oQmiRvX4riqno33LTmdjAXIJNNuQKQAnYuocRM51hYuk6HrWXrxxomGEXzTg", + "KqRIQSnIEmeY2gmaf89eHXoLnhBwBLiahShBZlReGdjz1U44z2GTYKCEInd/+EXd+wjwaqFpvgOx+E4M", + "vZWa77yAXaiHTb+N4NqTh2RHJRB/rxAtUJrNQUMfCvfCSe/+tSHq7OLV0bICif64a6V4P8nVCKgC9Zrp", + "/arQlkVP+J9Tb42EZzaMUy68YBUbLKdKJ7vYsnmpoYObFQScMMaJceAewesFVdr6kBnP0PRlrxOcxwph", + "Zop+gHvVEDPyL14D6Y6dmnuQq1JV6ogqi0JIDVlsDRzWW+Z6CetqLjELxq50Hi1IqWDXyH1YCsZ3yLIr", + "sQiiunK1uCCL7uLQIWHu+U0UlQ0gakRsA+SNfyvAbhgC1QMIUzWiLeEw1aKcKu5qPFJaFIXhFjopefVd", + "H5re2LdP9M/1u13iorq+tzMBCiOv3PsO8guLWRv8tqCKODjIkp4b2QPNINbZ3YXZHMZEMZ5Cso3yUcUz", + "b4VHYOchLYu5pBkkGeR00x30Z/uY2MfbBsAdr9VdoSGxUUzxTa8p2QeNbBla4HgqJjwSfEJScwSNKlAT", + "iPt6x8gZ4Ngx5uTo6E41FM4V3SI/Hi7bbnVkRLwNV0KbHXf0gCA7jj4E4B48VENfHhX4cVLrnu0p/gHK", + "TVDJEftPsgHVt4R6/L0W0GNDdQHiwXlpsfcWB46yzV42toOP9B3ZHoPuKyo1S1mBus4PsDm46teeIOpm", + "JBloynLISPDAqoFF+D2x8TftMS+nCg6yvXXB7xjfIsvJmUKRpwn8OWxQ535lAzsDU8chdNnIqOZ+opwg", + "oD5czIjg4SuwpqnON0ZQ0wvYkAuQQFQ5XTKtbcB2U9XVokjCAaJ+jS0zOieeDYr0OzDEq/gGhwqW192K", + "8cjqBNvhO2spBg10OF2gECIfYCHrICMKwaB4D1IIs+vMxY776GFPSQ0gHdNGD251/d9RDTTjCsg/RElS", + "ylHlKjVUMo2QKCigAGlmMCJYNaeL7KgxBDkswWqS+OT+/fbC7993e84UmcGFT7gwL7bRcf8+2nFeCaUb", + "h+sA9lBz3E4j1wc6fMzF57SQNk/ZHVngRh6yk69ag1deInOmlHKEa5Z/ZQbQOpnrIWsPaWRYVAWOO8iX", + "EwwdWzfu+xu2LHOqD+G1ghXNE7ECKVkGOzm5m5gJ/s2K5j9Vn+3Q6eooMLZcQsaohnxDCgkp2Oh8I6qp", + "auwJsXF76YLyOUroUpRzFzhmx0EOWyprC5El7wwRlWL0midoVY5xXBcs7BM0jPwC1OhQbZO01RguaDWf", + "y8kZchX6nYuY6KNeqfGoV8U0SF3VKqZFTjPLZAD3bQhYAX7qiQf6LhB1Rtjo4ivcFkO9ZnOvx0ZeDx2D", + "sjtxEMpWP+yLZjP6bb45gJRhByISCgkK74TQLqTsUzELM8rcpaE2SsOyazq3n/7Wc/xe9ypogueMQ7IU", + "HDbRJGrG4Ud8GD1OeC/1fIwSQt+3baG/AX8LrOY8Q6jxqvjF3W6f0LaLSH0r5KF8kM6VNFSeHuDy2+nf", + "dlNe1jFJ8zziy3P5Jm0GoMZVfjuThColUoZC0mmmxvagOfefS05pov9VFUV7gLPXHrfltApTGdEoC3lB", + "KElzhiZbwZWWZarfcopGoWCpkWgjr/32mwmf+VfidsmI2dAN9ZZTjDSrTEXRCIkZROwi3wJ4a6Eq53NQ", + "uqVczADecvcW46TkTONcS3NcEnteCpAY8jOxby7phswMTWhB/gApyLTUTXEb06mUZnnuPGhmGiJmbznV", + "JAeqNPmR8bM1Due97P7IctAXQp5XWIjf7nPgoJhK4lFR39mnGLDqlr9wwauY/m4fW5+LGb/OudqgzahO", + "6f6/d//r+NeT5H9p8seD5Mv/OHr3/smHe/c7Pz768NVX/6/50+MPX937r3+P7ZSHPZbs4yA/fe5U0dPn", + "qG/UTpcO7DdmcF8ynkSJLAyfaNEWuYuJrY6A7jWtUXoBb7lec0NIK5qzzPCWy5BD+4bpnEV7OlpU09iI", + "lvXJr3VPKf4KXIZEmEyLNV5aiuoGEsbT6tAL6DLl8LzMSm630kvfNmvEB3SJ2bhKnbRVVY4J5tUtqI9G", + "dH8+evrFaFznw1XPR+ORe/ouQsksW8eyHjNYx5Qzd0DwYNxRpKAbBTrOPRD2aOyaDaYIh12C0erVghU3", + "zymUZtM4h/Ox+M7Is+an3AbJm/ODPsWNc1WI2c3DrSVABoVexKotNAQ1fKveTYBWnEchxQr4mLAJTNpG", + "lszoiy6KLgc6w6x/1D7FEG2oOgeW0DxVBFgPFzLIkhGjHxR5HLf+MB65y18dXB1yA8fgas9ZORD931qQ", + "O999c0aOHMNUd2wCrh06SJmMqNIuK6gRAWS4ma0xY4W8t/wtfw4zxpl5fvyWZ1TToylVLFVHpQL5Nc0p", + "T2EyF+TYJxo9p5q+5R1Jq7cMVJDiRYpymrOUnIcKSU2etrRHd4S3b3+l+Vy8ffuuEwzRVR/cVFH+YidI", + "jCAsSp24wgSJhAsqY84mVSWm48i28si2Wa2QLUprkfSFD9z4cZ5Hi0K1E1S7yy+K3Cw/IEPl0i/NlhGl", + "hfSyiBFQLDS4vy+FuxgkvfB2lVKBIr8vafEr4/odSd6WDx48BtLI2PzdXfmGJjcFDLau9CbQto0quHCr", + "VsJaS5oUdB7zab19+6sGWuDuo7y8RBtHnhP8rJEp6iPhcah6AR4f/Rtg4dg76w0X98Z+5YtQxZeAj3AL", + "8R0jbtSe9svuV5A7euntauWfdnap1IvEnO3oqpQhcb8zVW2auRGyfPiDYnPUVl0ZnymQdAHpuauvAstC", + "b8aNz32EjRM0PetgylbesZlfWPsBPQJTIGWRUSeKU75pJ+Er0NrH8b6Gc9icibp0xD5Z980kcNV3UJFS", + "A+nSEGt4bN0Y7c13YVyo2BeFz6XGpDpPFscVXfhv+g+yFXkPcIhjRNFIUu5DBJURRFji70HBJRZqxrsS", + "6ceWZ7SMqb35IlV4PO8n7pVaeXIRV+Fq0Opuny8By3iJC0Wm1MjtwlWgsonOARcrFZ1Dj4QcOmUGphM3", + "HDk4yK57L3rTiVn7QuvcN1GQ7cuJWXOUUsA8MaSCykwrzs7PZP1+zjOBhSUdwqY5iklVQKJlOlQ2nGO2", + "Ul4faHECBslrgcOD0cRIKNksqPLFsbCGmD/Lg2SAa0zc31au5TQIEQsKhVXFWDzPbZ/Tjnbpirb4Si2+", + "PEuoWg4otWIkfIxKj22H4CgAZZDD3C7cvuwJpS4iUG+QgeOn2SxnHEgSizYLzKDBNePmACMf3yfEWuDJ", + "4BFiZByAjf5sHJi8FOHZ5PN9gOSuCAL1Y6MnPPgb4vlaNv7aiDyiMCyc9Xi1Us8BqAtRrO6vVqAsDkMY", + "HxPD5lY0N2zOaXz1IJ2qISi2tmqEuIiKe33i7BYHiL1Y9lqTvYous5pQZvJAxwW6LRBPxTqxCZtRiXe6", + "nhp6j4akY/po7GDa+ix3FJmKNUbp4NViQ6B3wNIPhwcj0PDXTCG94nd9t7kFZtu026WpGBUqJBlnzqvI", + "pU+cGDJ1jwTTRy53g5IrlwKgZeyo6xc75XenktoUT7qXeX2rjetSYj7bJ3b8+45QdJd68Ne1wlRFUl61", + "JZaonaIZbNKsDxOIkDGiN2yi66TpuoIU5IBKQdIQopLzmOfU6DaAN84b/1lgvMAqNJRv7gURTBLmTGmo", + "jeg+TuJjmCcpFr8TYta/Ol3ImVnfayGqa8q6EfHDxjJvfAUYAjxjUukEPRDRJZiXvlWoVH9rXo3LSs0Y", + "KVsqlmVx3oDTnsMmyVhexunVzfvDczPty4olqnKK/JZxG7AyxdLG0cjJLVPb4NqtC35hF/yCHmy9w06D", + "edVMLA25NOf4TM5Fi/NuYwcRAowRR3fXelG6hUEGGa9d7hjITYGPf7LN+to5TJkfe2fUjs+77buj7EjR", + "tQQGg62rYOgmMmIJ00Fl4G4qas8ZoEXBsnXLFmpH7dWY6V4GD19PrYUF3F032A4MBHbPWDaMBNUsnVcL", + "+LbGc6NyzWQQZs6aBe5ChhBOxZTvUNBFVJUttwtXZ0DzH2Dzi3kXlzP6MB5dzXQaw7UbcQeuX1XbG8Uz", + "uuatKa3hCdkT5bQopFjRPHEG5j7SlGLlSBNf9/boG2Z1cTPm2TcnL1458D+MR2kOVCaVqNC7Knyv+GxW", + "Zav09RwQXwHd6HxeZreiZLD5VWmx0Ch9sQBXSjqQRjs1L2uHQ3AUnZF6Fo8Q2mlydr4Ru8QtPhIoKhdJ", + "bb6zHpKmV4SuKMu93cxD2xPNg4sbVjg1yhXCAa7sXQmcZMlB2U3ndMdPR01dO3hSONeWYtdLW89dEcHb", + "LnSMed4Uzuu+pFix0lpFusyJl0u0JCQqZ2ncxsqnyhAHt74z8zLBl3uEUTNiyXpcsbxkwVjmtSE1aVpA", + "BnNEkamiZXFq3E2F69VTcvavEgjLgGvzSOKpbB1ULG/irO3d69TIDt253MDWQl8PfxUZI6zW2r7xEIjt", + "AkboqeuA+7xSmf1CK4uU+SFwSezh8A9n7FyJW5z1jj4cNdvgxUXT4xa21unyP0MYtsb67r4+Xnl1ZWN7", + "5oj26WEqmUnxB8T1PFSPI4lGvj4twyiXPyBMdAi7UzRYTGXdqdsN1bP3bnefdBNaoZpBCj1UjzsfuOWw", + "UKa3UFNut9q2zWjEusUJJowqPbLj1wTjYO5E4ub0YkpjVUSNkGFgOqkdwA1buhbEf+xxr6psCzs7CXzJ", + "1bvMJpEXIOscwG5BmksKDHbawaJCLRkg1YYywdj6/3IlIsOU/IJy233FfGePkvtagTV+ma8uhMQSECpu", + "9s8gZUuaxyWHLO2aeDM2Z7axSKkg6FzhBrJNmywVue4fVQ6RQ83pjDwYB+1z3G5kbMUUm+aAbzy0b0yp", + "Qk5eGaKqT8zygOuFwtcfDXh9UfJMQqYXyiJWCVIJdajeVM6rKegLAE4e4HsPvyR30W2n2AruGSy6+3l0", + "/PBLNLraPx7ELgDXGGYbN8mQnfzdsZM4HaPf0o5hGLcbdRLNlred4foZ15bTZD8dcpbwTcfrdp+lJeV0", + "DvFIkeUOmOy3uJtoSGvhhWe2rZHSUmwI0/H5QVPDn3qizw37s2CQVCyXTC+dc0eJpaGnui2FndQPZ3sk", + "uYrCHi7/EH2khXcRtZTImzWa2vsttmr0ZL+kS2iidUyorfuRszp6wdc5J6e+rBCWWK4qK1vcmLnM0lHM", + "wWCGGSkk4xoVi1LPkr+RdEElTQ37m/SBm0y/eBIpK90sb8r3A/zG8S5BgVzFUS97yN7LEO5bcpcLniwN", + "R8nu1dkewansdebG3XZ9vsPtQw8VyswoSS+5lQ1yowGnvhLh8S0DXpEUq/XsRY97r+zGKbOUcfKgpdmh", + "n1+/cFLGUshYrcD6uDuJQ4KWDFYYuxffJDPmFfdC5oN24SrQf1zPgxc5A7HMn+WYIvC1iGinvtR5ZUl3", + "seoR60DfMTUPDBlM3VBj0iwrffN89DBRUHFPlzdsdx1b5onHA/7RRsRHJhfcwNqXb1fSQyhBWf0oyWTV", + "88DHTsnXYj2UcFqn0BPPJ4CiKEpKlme/1Jmfra4FkvJ0EfWZTc2Hv9X91arF2TswWvZvQTmHPDqclTd/", + "83JpRHL+pxg6z5Lxge+2GynY5bYWVwPeBNMD5Sc06GU6NxOEWG0m1VVB2/lcZATnqWvM1ce124AjKJP+", + "rxKUjiUo4QMbOIa2UcMObJVuAjxDjXRCvrMtlBdAGgWEUBP0lSKaWdNlkQuajbGCxdk3Jy+IndV+Y7sE", + "2Srhc1SEmqto2cSC8pnDQpB9w594esTwcbbHa5tVK51URb1jCajmjbrsOGv5CVBFCrEzIc+DZqg2V9UM", + "YehhxuTSaHXVaFY+Qpow/9GapgtU+xqstZ/kh5e391SpgpaSVWuoqqYknjsDt6twbwvcj4kwuvkFU7Zz", + "LqygmfNaJYA7s4PPgW0uT5acW0qZ7HHLVRUk90W7B85ekd6VEIWshfg9hX7bHWLfav9v8Ktoiat264BO", + "L0mbQVm1/PEd0VPKBWcpFpiKXdGuxe4QP9uAWlxtQ64/4u6ERg5XtGFBFYrnsNjbwsAzQoe4rqE/eGo2", + "1VKH/VNjL9cF1WQOWjnOBtnY991wtkbGFbgaodiQOeCTQjZ8l8gho+7wpHKb7ElGmHrTozx+a569dKYF", + "jEk/ZxyVCIc2J/hZayB2ANVG82CazAUot55m/rH61XwzwVTcDNbvJr5jKI5hXX9m2dbP3R3qxHu9nZfZ", + "vPvMvOsKJFU/N6Kc7aQnReEm7e/KEpUH9Jr3IjjivUy8+yhAbjV+ONoWctsaroL3qSE0WKGzGwq8hzuE", + "UXUoaXW/MkKrpSh8g9gwsWiVBMYjYLxgHOp+tpELIo1eCbgxeF57vlOppNqKgIN42hnQHD3cMYamtHNv", + "XHWodnkogxJco5+jfxvr5io9jKN6oRbcKN9UbXQNdQfCxDPs3+0Q2W2VglKVE6IyzFpoNU+JMQ7DuH17", + "puYF0D0GXZnIfq4ltSdnn5uoLxF1WmZz0AnNsljJ1q/xKcGnJCtRcoA1pGVV2rMoSIp1V5qFaLrU5iZK", + "BVflcstc/oUrThd0I4pQQ9gRye8wJrpMN/hvrK5l/864QI+9Qw19VMee1Ze6oZMxqdfQdKLYPBmOCbxT", + "ro6OeurLEXr9/UEpPRfzJiA3XH5iazGsYI9i/O0bc3GE1Rk6xVrt1VIVT8DAPuF7SKLaWKX9tkp/UU27", + "1VvRoVT1qNtugOjvNjfGy68nvDcoukHt/Wo9lH1BvmlvTDrVLjtOU7KVBfVmHNkIIZtbhFDErbN9UUE2", + "KMg87nw9TDLsyNk6XvgwQKgPN+sC9IOPZSUFZc79XjOLLmZd1Hs3D2FIPGy9we1FuFjyXovdD6u+uG9f", + "jA2ft7tRnYNLmS8krJgovWPbRz55ldD+2ujtVEXeR9ffNbziVB/XHNprvD1zXQHsMp1O/sMvNk6OANdy", + "8wmYcjub3ulz1ZV2rXmqfoVUBaUHFZhu3IpDChXGauI52bDRaWtHn7AuYx0iDnT7fo1HLNvrwozVVRzZ", + "UWLHLt7Fq7/sVF1qCo9YIRSr67rH2nsNDDE8ww5dQdms7lg+vmcFqcZi/nXcggTYp4iWmSxoGHpbfqpH", + "na4iMV3VqW2lproV/Hfc8Z1ssCCj0VY/nwwvrHRSRachn8ZqyHPgrmdnM89jcLT5bAapZqsd2Xd/XwAP", + "MrvG3i5je28HyXisil7G4i37Wx1rgLYlx22FJyiieGVw+nJvzmFzR5EGNUTLsY/9VXuZuh2IAeQOiSER", + "oWLRH9aQ7BzyTFWUgVjw0Vb2c6groPV2cgpySS85lydJc3HU+aVbpoy3khk0l/l0r6xrDMTtS9DrdqLo", + "1z+eY+MPVXVZ9HU/Qi2dnHarI164uiGYK1n5TnwFEVD+N58YbWfJ2TmEvabQU3VBZebfiJpevFUn2XIf", + "dbLqfBeFNtCzamZWx8Z286gi9bYwAjrNhREjkr4w8mY4ahXLcUfZoBtb/h0DbQ1cM5CuJx/Kv7lQkGjh", + "Y2m3wbENFTay6FJIUL01Li1wvZVnXteldbDWL8VKM9QFFIULJBKW1EAngwI4/XNuQ/Yz+9wnDvlarzst", + "TBW97m464KOimeogMaT6GXG35e6EpMsYmxjntu+zilXD4SCb3pBCiqxM7QUdHozKIDe41tQWVhK106Td", + "VbZ0hCCr8xw2R1YJ8t0a/A6GQFvJyYIeVFFobfJBzW8qBvf8IOB9TMvVeFQIkSc9zo7TbgmfNsWfs/Qc", + "MmJuCh892NP5htxFG3vlzb5YbHzJmqIADtm9CSEn3MZre8d2s4Z0a3J+R2+bf42zZqWtquWMapO3PB74", + "ivWu5BW5mR9mOw9TYFjdFaeyg+woELPuKR8k6UWkD9RkqFbedTW3e/PURGWhiMkkdduZHXEyVYhM3fmj", + "DpPpSgd5Li4SpKKkqv8V0znMe00m6Sue1p8ZbE8hiLehyl2gG7KgGUmFlJCGX8RTHCxQSyEhyQWG38Q8", + "gzNt5KElxjVzkos5EYVRc20ZPe9DibalCeayabb2y8Q6anoKGYByabVuGvtyd54t3Wv274xztojYWxDR", + "Hst7t79xhLJ314oAzAEEutvWdBLr7tNcV7s/VF+3Ni2WLI2j+/OKMumNDdnRuyiyvoocXWslnxXYg6uo", + "y3a7h9T2oZsO9ZNWNZMHHosAgH7PaQOGQf7TfcGYYV/HhEaQfFpJreNG213WOvu+np2l8ZRarXUBxIxd", + "SnBZarYBXatzTkH1wt9i5vWubmn0FFCYQmbbf1BlLSHeIuO637XFA1EkOayg4VB2qXNlmoJSbAVh5zz7", + "MckACrRPtqXmmKc05HItUcqtPQl8bUOwG5WtLGLtTpEdglNUzFvzxB4TNfQoGYhWLCtpA3/qCr3I+tqQ", + "Rdiwh3Ugp9ibScQXt41F7IxtQJqPnkseD20IMzcrowjOllXGU0uE9clWBb3g/UpExO5U+duvvg6CgxHV", + "yqTuvfJltSuXVSB7KWMbYXT6B0ZlDgW+/2tY9MSLW+7biIxlTV1MRQZgqj7PGL0HdXRY8NqSbkjGZjOQ", + "1pivNOUZlVn4OuMkBakpM5rNRl1erDXQyhLGOyVbw11xUM9gYjIu2qUsIPnGqQxXkDrRcxOROO1Vq0Vf", + "i8TOrsTTCejaSNcYV9VDBC4RGmVre8AERwGJLOk57DmPYn/A9mmwPImz/WmBsw6ZIuZrvWRttUGsuxuG", + "ELndgmaI2z1DYenFOqdL2mgWtCT7C7JN4z/WF+ewtoz+gx3ghQ7DoDGjt904cD5yctSPFVKCpbzro4TG", + "8nf5IN0Ca0kj2CLHCLQGWwjXBtQ39yVwMKtnld+2r4do272LdRYFt03+Om5hy5ts176AcMxZkCua37xr", + "FwtwniA+IHvdbwwOfYMhki0q1eUyE17QQXMHfsDDTc1foSv672D2KKqVuqGcCFOJ9T6YB28WmlvDxcy3", + "8FoBJxc4po1je/gFmbrM7UJCylRbNLrw3TUqVxg2m3LZIGu9w/e2a52/CH0FMp55TYO8rCv1o44/5zWE", + "9RH9yEyl5+RGqTxGfR2yiOAvxqPCEmo7rovzRoCb7XzSytwQEg4c6BaErO8Z6NYtDjd0eTaYy1w6pYLu", + "Ogff1g3cRi7qem1DozS7yN1Wzn1IcGW8S4P5HKM7LUKwxQlBUMnvD38nEmbYw1CQ+/dxgvv3x+7V3x81", + "H5vjfP9+VDq7sbhOiyM3hps3RjG/9GX62Wy2nqTS1n6ULM92EUYjRbjuAopJsL+5QgQfpQ/pbzbWpHtU", + "XS+4KwTIWcRE1tqYPJgqSP4dkPfrPotk+aIfJy0l0xusj+jtB+y3aATqd1U0k4uGq/RDd/dpcQ5Vhc06", + "9qlU/nb9TtAc7yOrtnJzC4l8Qr5Z02WRgzsoX92Z/ic8/tuT7MHjh/85/duDpw9SePL0ywcP6JdP6MMv", + "Hz+ER397+uQBPJx98eX0UfboyaPpk0dPvnj6Zfr4ycPpky++/M87hg8ZkC2gI1+NZ/Q/2Kw3OXl1mpwZ", + "YGuc0IL9ABvbF9CQse84SFM8ibCkLB8d+5/+jz9hk1Qs6+H9ryNX7GO00LpQx0dHFxcXk/CTozkGOyRa", + "lOniyM/TaUl48uq08hJZKxDuqM2T9dY9Twon+Oz1N2/OyMmr00nQr/549GDyYPIQ25sXwGnBRsejx/gT", + "np4F7vuRI7bR8fsP49HRAmiOsYHmjyVoyVL/SALNNu7/6oLO5yAnrg2j+Wn16MiLFUfvXdDHBzNDVJ+2", + "KeJBXnC3O6ELIENjlE0Bb3T7Ua75zLjqAeWMjTzDzF0bR2HYXIW406xudnBaMy1f8tHWwD7+NRKIO2Nz", + "ND34SoSNDpGuQRxT5L/f/PSSCEmcevOKpueV34Kczmz5LilWDBNCsyCL2Hw58fT7rxLkpqYvx/nC+s6+", + "pY9zgCzVvGjmpNVSVcxHE+sEiTMbsggIuwrRqhkXmmjC/rkVGzas9UHy5bv3T//2YTQAEIwXVIDVv36n", + "ef47uWDYUBDti75+pquPNo60r0FpelyH/OAH9U6OMamuehp2KKzeaaZy/84Fh9/7tsEBFt0HmufmRcEh", + "tgfvsD4VEgueuUcPHhystWlVvcB6aapRPElcYqAuQ7KPqhapF5IW9iz6Dqfo6EZV2C8UG7o+OeBCm7lH", + "V15ue7jOor+mGXaNA6XtUh5+tks55Riyay4IYi/AD+PR0894b0654Tk0J/hmUPyxe9H8zM+5uOD+TSP8", + "lMsllRsUbYLWlq3KKHSuMB4EWaQ9241mdqN3H3pvvaOwV9fR+0bUZ3alO7HTpvD0+Y5r8o7q45zd0umt", + "VmDmedXpCeMCXb8z7D2l7k3Id+HXyL2xEpmt81VKDpkP2vS3XlVa1RdsrWG7o8IibdFLOzAX397fH/v+", + "PmkaOxrluWPANE7BVpg6XoWrXqBdV2mr2fOlmikHTbku0drkWjtOtnRNO9O7mCq4k1Hf4q4Hd31iUgBv", + "JTE1m6ldP2v2SYLVTdK4Mq6RcX/mQt+PNDd0Eiy3VYzH1qy/FQb/MsJglUw0t9KZa9NyNfEQGzYevfd9", + "CA4gEro+DAOEwVCtDr4Noj7uttjJvYltKhC+czme4bKHdop52B3iVsD7BAS8bueVGBh1P42PJ9QhDIu6", + "NcvOLjC+qUq7l/5eLWQ+UynuL4ysXrHNQLpbYLsE++wIY45ZXxtb/VMKYQ5pt+LXX1r8qnJ6rySANXon", + "uSzxwI11Jetd2zrHdCWJNfO6A86GIdWGobgjPK77PBoWg2UxfUU0NfaaIbpTrdJoN2vc0Ru7ItZ3ECqo", + "X29On++Srj4jO8/g8syRWyC+N9fNS6Nuh9c343YYxpuePHhycxCEu/BSaPIt3uLXzCGvlaXFyWpfFraN", + "Ix1NbXOKbVyJt9gSMoq66UTAo6qCFuPguXnbRmncdZ3Vw4Je9ybEt8JQVWMvl9g1F4ZR+ZwSKuf2I8Pr", + "DDLIHf/nMY5/Z0K+xYQercYYbKZd1ydyh3F9/PDR4yfuFUkvbCxX+73pF0+OT776yr1WNz6xek7ndaXl", + "8QLyXLgP3B3RHdc8OP6ff/zvZDK5s5OtivXXm5e2AvCnwlu76l1IAH279ZlvUkxb9708dqHuRtz3X4t1", + "9BYQ69tb6KPdQgb7f4rbZ9okI6eIVpbMRo2gA95G9pjscx+NfZMPw3eqy2RCXgpXrq3MqSRCZiBdJ8R5", + "SSXlGiCbeErFpFNly1OlOQOujeKIvd1kolgGtsrNvJRQpc8VElYYI4/To07fgGA3o8dI2k+Wyf9I10EJ", + "p2l1TWvhloxmzyVd++6S2D9NSPzpq6/Ig3GtveS5GSCpEBNjrku6Ht2g1a8itkHx583mTDsDdHHsIRak", + "WvqpcmjDTjB/bc792Urultzdxh6Ic+7t+KkdO6EdwRVF22pBsIKd7T2JzRA3dTa+kfK8CBVncWaGocaB", + "T9hHsNM0HVVC2+i9PcS3RoArsZI2Qe3JNjDrVB29R7085Bmdc4tZc38td2ngO5Ji6Z1HgsxApwuXsNtC", + "fYQ9+dZQ/bxpW/PxQ0s1uIvdqhdhTWpsij2w7FmQS4kOPJARIv7Jd2kwj9nMFpjxBZl8j310TTHfdrbq", + "OOv6crtSE1pUeb1mF/eC8lk9eVcgQ7Qcwv95i+D9ENxhjt/41qOIMbeIP0PEv1clE/JS1GnjruvVn9H1", + "eJ03+3Uv6KXgYH3sRvK1tHjrTq3EDsM4LFJ8vRCrv1T9Ry4tghz5Jrpb5ZDvbQvbrbLIkNvbTPZZXuHf", + "OyxtuWXM2iY7iyHUow1hzuZFWwWr2RHjI2oxH4WffoKqzcfgWDfDYvCQej7jxAJ+WKaDJXgsMR9VzRD6", + "OFC8v8xgbqRFFYYWbQkzhVzwufo0WdHWTj9RvESopOq8E2+v89c7u8+wuo9ReW0EpKv3pBhPwTaJxv52", + "TJElU8oFSz558Lebg1Czpa8ozsPc1Y/MXZ4+eHxz078BuWIpkDNYFkJSyfIN+ZlXDb2vwu2weVBVf81b", + "g6P9otDb1KwLloZFjC7PBBuha+/1mmUfdjPDoGLlnnyQ8YAPhuUNaVEAlZdngLtdV+1y26fPw+jgRk+b", + "qqJWBBSDoj0D5P9jNNDuhGnvYuYuv5JbQH31L8cmXOiumI2r4BgjBYjZMXnL7xO1oE8fPvrt0dMv/J+P", + "nn7RYzkz87iiPV3bWT2QeWyHGWJA+6zNgYeV2iv8Ht/0bu+3ieMRy9bRrhd1H7tO0Wsnlt1RpKCb3tY4", + "xY4+fOGwdU++my92qDSbLqL6lVd/qrLyp/zrSgu2Fflc+7rb/ns9yRMBnzGEVjfiq7C+vSffFmmyRZZV", + "87ObVk7rJAN70Xnkydad81EFXf2xlNQEdVTgXrBpouXjyZTYmWUcuLsLKbRIRW5jV8qiEFJXp1tNBol7", + "0Oe2a0h7fYS7lzCXUp0uyuLoPf4HK3x9qBMPbAP2I73mR1g9+uj91hABBDHSGtbKpdGWCl01eUBn2l0h", + "AK0TM24fIlsJG2MJIvLZ9Uhnf2mhZr9Ov1c1aUdG7BzgKq8u6KJd0W5Q+HtHc+PJrQvmE1tQbRSZMZ4R", + "GmxjS3cTsmYE12wYue5Ffww7y837nZ5+xufspdDkdFnYhjmQXS16h7Q5nL89tl63+wkG7urvhvh07/zw", + "xveBiZV1fecFv4dDLkjFBj8dlZgbbe7q67F9397kn/ZN/syXHG6Q4e29/Pncy9KHU95ewZ/+Ffz4s13N", + "NTpiBl7J/ia69DVca+J7XsiRLqFoMmi5wrf5aVD1bq9SfSukb29xe4t/pk4Gd2MOTVoaYqHZlcrkpjxE", + "6OwnBf0wO0OeRywNfQd1bHv96AUwLDojUob1w08zNbaH2Bkn3Cm+FXw+acEn2OtbuefW9PCZmR56pByn", + "9TebtPYJGvsKQKulyMBHnYjZzBV565N+mr1nDHkqTZcFsV9GpRz0xp6xJbwxb/5kpzjoFVuD3RKLWuAZ", + "ZClIBc/UAK+oG/Wy9xC6cfsBuHEPaLUDHhaX/j25NMm+DmrIdCiBtJGvsGeQL3bnkJHBihgCnByAbI/e", + "23/RnFYIFeu67Am4szF33bbY6n123AaA5BUKobYMoP9KzMgDW8Sv5JipUzcHpDwjWm6MoOprlkigOUkb", + "EfoVHN2T86b35OxUBTqr61lTXBcQ9Qk9ZDhrKzvqhxs/AM8odyTfRZAWhBIOc6rZCnzc+uQ2o/7St5nL", + "Z9/CAMeEZpk9jfUmwArkhqhyqoysw5uBlndU87zswTBgXYBk5oqmee2At2rCkU2X3xZQ+ca+ccVLq8WL", + "bJK+bEYB+ZvVpfCLGfmRpVKc5HOhfFyX2igNy07rPffpbz1FV70hoRsDJnjOOCRLwWMN4X7Cpz/iw2g/", + "daFp3vfxmXnY923rvm3C3wKrOc+QO/mq+P1ETv+VcjVaq5VQCKl9k35wavKeR8kfmg1Puydpw9PAqeUe", + "BgOF7eMaPx+9b/zpimW4N9Wi1Jm4CL5Fzd4G/QzJkw8aVV/CktZq+Kyu15Z2nT6kAA+xE1M9jbT+CtqR", + "93b/+ovmhziXS0gkGLqZihVI1VLPbpNE/lRJIoP3fS8ea1td7uJopTqsRPJSZGDHbXaajdVn5iID15Gz", + "K4hUwY7xwHp/K9XvtUKdU1rOF5qUBdEiFlRdf5jQ1DLZxKo38QmDimhWCcLpFnQFhObY55RMATgRU7Po", + "+n7ERVKFNel8ZLYL6YyKQgFchRQpKAVZ4utR7wKt6nOKcdx6C54QcAS4moUoQWZUXhnY89VOOKs+4Yrc", + "/eEXozDfOLxWFNyOWFsJK4LeqtqGk/a6UA+bfhvBtScPyY5KIF40wEQSsSxycKkkERTuhZPe/WtD1NnF", + "q6MFcy3YNVO8n+RqBFSBes30flVoyyIx93cXxGf26RlboiTGKRferhgbLKdKJ7vYsnkpXIsyKwg4YYwT", + "48A9CucLqvRrl1WYYQUae53gPFbGNlP0A7zq60dvRv6l6kbfGTs19yFXpapa1rtMAchia+Cw3jLXS1hX", + "c2Fapx+7SkWwFr5dI/dhKRjfISsoyk2oDrz5ZrjI4tD+SJ2BoovKBhA1IrYB8sa/FWA3dOP3AMJUjWhL", + "OFhkNKScqRA5UG4zukRRGG6hk5JX3/Wh6Y19+0T/XL/bJS6q63s7E6DCNBEH+YXFrEID7YIq4uAgS3ru", + "MknmrslSF2ZzGBPMAE+2UT6abM1b4RHYeUjLYi5pBkkGOY2YUn62j4l9vG0A3HFPnslKaEimMBMS4pte", + "U7LsNRFVQwscT8WER4JPSGqOoFGeawJxX+8YOQMcO8acHB3dqYbCuaJb5MfDZdut7jFLmTHMjjt6QJAd", + "Rx8CcA8eqqEvjwr8OKnNB+0p/gHKTVDJEftPsgHVt4R6/L0W0DbnhRdY46ZosfcWB46yzV42toOP9B3Z", + "mAHxszT2t2OXrrH6S9OAGiiAk8sot0cXlOlkJqQVpBM60yB3BsT/nTLvDneuAS1cbQKCI7h7042DTD5s", + "deG4iAWBuOvCkEjX/2am+lbIQSU2m4VkKNOk5JrlQZnxSlX+9AyGt0aAWyPArRHg1ghwawS4NQLcGgFu", + "jQC3RoBbI8CtEeDWCPDXNQJ8rKK5iZc4fCkxLnjSjkokt1GJf6oik9Vd5Y0SaMa4oEy7rpk+3989uVqN", + "XQ00RxywHPrjpG345tk3Jy+IEqVMgaQGQsZJkVOjG8BaVz3cmt1Bfd9i2wjSNh6lCh4/Im++P/G18Bau", + "Zlvz3bsnrv+30psc7rkuCcAzK4r6dgnADdJdtwTq7wTf6811vmM5xpgr8g2+/RxWkIsCpC2zRbQsIyaf", + "M6D5M4ebHRafv5vJXdDq72a038cNQ5ND25IWXs73a6WKUJu7SJ4H2Yy/z2iu4Pe+hEY73pIWsXZr1c1n", + "bUHITb4W2aZ1QsyuHeEGNs9GXRGPcSo3kXpL3WSCNmloYfiVI6yuMevDwes2dom2S2a7KCwmrktQ0XO8", + "jcqjBQurDesMZVNeZy06GcWyNdtV+kYVgENCYM8w4cDuCXltv/u4VeERInfEamb+yUQONt+smAa+a7QI", + "x3o+16h8j/jo6cWzPzaEnZUpEKYV8aUfd18v49E6MSPNgSeOASVTkW2SBvsaNW6hjCmqFCynu2+ikH+6", + "BsPu8jFPtt9TH+caeR4sbhtPDolmnTgG3MOdNxoG8+YKWziiY88Bxq+bRfex0RAE4vhTzKrU4n37Mr16", + "ms0t47tlfMFpbEkEjLtSuW0mMrlGxic3suT9PO+bNaSlAS48yXfRPI8+OVjrhmMzg2k5n2Oj5I6TziwN", + "cDwm+EdihXa5Q7ngfhRkB6+aZ1413bs9XJe7BBnYd32Nw3u4HZRv0JuxLCjfeJ8vJIoty9zi0PaYOyyj", + "tdVsu5EA6I91xr8+s/Yrb/MLjLfuqm3+btFCLqgidn8hIyXPXO5Qp+b1mg+vGGKHPlvzmk1vrQ5i1xtZ", + "nZt3yBXhd7mZtK1IATLRa24PVLOTuq2tbU/u5LZB7F/j2rAp39DDYLt1omuGcKDbQwZ8Da+PoBtInQzX", + "6BGCVov+1JGwNYh986DRI53hm0EktUnFOUkhLwj13ftTwZWWZarfcopOmmBhk26AibdG9/O3Z/6VuJ8w", + "4sZzQ73lFJu7V66bKJ+bQcRP8S2AZ6OqnM9BGV4ZEskM4C13bzFOSm40LTEjS5ZKkdhEVHOGjHwysW8u", + "6YbMsP6HIH+AFGRqbvZg163BWGmW5y6ixUxDxOwtp5rkQJUmPzLDZc1wvvhAFcoF+kLI8woL8U4Rc+Cg", + "mErixpfv7FNsxuCW7418aLC0j+si6jfbhcHDzrJeyE+fG7gp1i7OmdJ1EEQH9htzgC8ZT6JEdrYA4mLC", + "2rRF7mLFNEdA95reIb2At9zccFoQ5OpUX44c2m6ezlm0p6NFNY2NaHmD/FoHqXgH4TIkwmRuXSt/otTM", + "gA68+xI33lajb+39nm6UxpULPDNPey5k+9Q17+p5ySkJDUNYqxyMe+OsAfKft/H7u+vRFz0aD6Yxdgfs", + "sqtmeybEm9/wMaG54HNbhdBokAL3ifGi1BhYfZ1GOljRPBErkJJloAaulAn+zYrmP1Wf7bgAg+ZyyyVk", + "jGrIN6SQkEJm62QxRWoleWIrDZB0Qfkc70opyvnCvmbHuQAJVR8uo5e2h4jXKVnzxNZM68J4QqyBMSwr", + "CzRdRPqa4I1iFGG/g7YMxBBVN3KEsSJmn+Y7HvVKtgapqzogzSKnea4HXNuNCzjATz3xIUqI3lLZLZXt", + "TWWxEnuIullL57b4Crflmo0z111Q8gZtPR+l2uxtyfY/e8l2z4EUoUTShpQd7xVGFWGaXGBBnSkQc2GU", + "aGN2LcWdRjohhiEF9nRbeVG5TpfpgjLuqrFU4fkIh3bdeLVv/3ct5jnLzNAuZ9ABaSmZ3qBcTgv22zmY", + "/78zgq0CufIieynz0fFooXVxfHSUi5TmC6H00ejDOHymWg/fVfC/99J2IdnKaBAf3n34/wEAAP//VHyg", + "HOZ0AQA=", } // 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 62afbd9f1c..15b8e7e02d 100644 --- a/daemon/algod/api/server/v2/generated/participating/private/routes.go +++ b/daemon/algod/api/server/v2/generated/participating/private/routes.go @@ -216,125 +216,125 @@ var swaggerSpec = []string{ "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", - "AP//ScSRWrDVAAA=", + "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", + "wpHC9Yfr/wkAAP//n/TLvvHVAAA=", } // 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 92327891a4..14745a0baf 100644 --- a/daemon/algod/api/server/v2/generated/participating/public/routes.go +++ b/daemon/algod/api/server/v2/generated/participating/public/routes.go @@ -235,133 +235,134 @@ var swaggerSpec = []string{ "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", + "AAA=", } // 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 971a24b7c1..a8699c37c0 100644 --- a/daemon/algod/api/server/v2/handlers.go +++ b/daemon/algod/api/server/v2/handlers.go @@ -1745,7 +1745,12 @@ func (v2 *Handlers) GetTransactionGroupLedgerStateDeltasForRound(ctx echo.Contex if err != nil { return notFound(ctx, err, errFailedRetrievingStateDelta, v2.Log) } - data, err := encode(handle, deltas) + response := struct { + Deltas []eval.TxnGroupDeltaWithIds + }{ + Deltas: deltas, + } + data, err := encode(handle, response) if err != nil { return internalError(ctx, err, errFailedToEncodeResponse, v2.Log) } diff --git a/daemon/algod/api/server/v2/test/handlers_test.go b/daemon/algod/api/server/v2/test/handlers_test.go index ed9b8d83bc..e30169b4ad 100644 --- a/daemon/algod/api/server/v2/test/handlers_test.go +++ b/daemon/algod/api/server/v2/test/handlers_test.go @@ -2114,12 +2114,12 @@ func TestDeltasForTxnGroup(t *testing.T) { ) require.NoError(t, err) - var roundResponse model.TransactionGroupLedgerStateDeltaForRoundResponse + var roundResponse model.TransactionGroupLedgerStateDeltasForRoundResponse err = json.Unmarshal(rec.Body.Bytes(), &roundResponse) require.NoError(t, err) - require.Equal(t, 1, len(roundResponse)) - require.Equal(t, []string{txn1.ID().String()}, roundResponse[0].Ids) - hdr, ok := roundResponse[0].Delta["Hdr"].(map[string]interface{}) + require.Equal(t, 1, len(roundResponse.Deltas)) + require.Equal(t, []string{txn1.ID().String()}, roundResponse.Deltas[0].Ids) + hdr, ok := roundResponse.Deltas[0].Delta["Hdr"].(map[string]interface{}) require.True(t, ok) require.Equal(t, delta1.Hdr.Round, basics.Round(hdr["rnd"].(float64))) From 06b77278a077572d0461a8544d31419db0d900a1 Mon Sep 17 00:00:00 2001 From: Ikko Eltociear Ashimine Date: Mon, 22 May 2023 22:36:23 +0900 Subject: [PATCH 104/125] typo: fix typo in event.go (#5405) --- logging/telemetryspec/event.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/logging/telemetryspec/event.go b/logging/telemetryspec/event.go index 77a8ea1d76..4a8280591c 100644 --- a/logging/telemetryspec/event.go +++ b/logging/telemetryspec/event.go @@ -196,7 +196,7 @@ type PeerEventDetails struct { InstanceName string // Endpoint is the dialed-to address, for an outgoing connection. Not being used for incoming connection. Endpoint string `json:",omitempty"` - // MessageDelay is the avarage relative message delay. Not being used for incoming connection. + // MessageDelay is the average relative message delay. Not being used for incoming connection. MessageDelay int64 `json:",omitempty"` } @@ -291,7 +291,7 @@ type PeerConnectionDetails struct { ConnectionDuration uint // Endpoint is the dialed-to address, for an outgoing connection. Not being used for incoming connection. Endpoint string `json:",omitempty"` - // MessageDelay is the avarage relative message delay. Not being used for incoming connection. + // MessageDelay is the average relative message delay. Not being used for incoming connection. MessageDelay int64 `json:",omitempty"` // DuplicateFilterCount is the number of times this peer has sent us a message hash to filter that it had already sent before. DuplicateFilterCount uint64 From 99081cbb0d3998ef54a412f31be57c1257424288 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Mon, 22 May 2023 11:17:44 -0400 Subject: [PATCH 105/125] consensus: Introduce versions v37 and v38 (#5397) --- cmd/tealdbg/localLedger_test.go | 2 +- cmd/tealdbg/local_test.go | 2 +- config/consensus.go | 47 +++++++++++++----- data/transactions/logic/assembler_test.go | 21 ++++---- data/transactions/logic/eval_test.go | 49 ++++++++++++------- data/transactions/logic/langspec.json | 2 +- data/transactions/logic/opcodes.go | 2 +- ledger/acctonline_test.go | 11 +++-- ledger/applications_test.go | 22 ++++----- ledger/apply/stateproof_test.go | 8 +-- ledger/apptxn_test.go | 8 +-- ledger/eval_simple_test.go | 2 +- ledger/testing/consensusRange.go | 4 +- ledger/testing/consensusRange_test.go | 2 +- protocol/consensus.go | 20 ++++++-- .../features/accountPerf/sixMillion_test.go | 7 +-- .../features/transactions/asset_test.go | 2 +- test/scripts/e2e_subs/e2e-app-simulate.sh | 6 +-- 18 files changed, 134 insertions(+), 83 deletions(-) diff --git a/cmd/tealdbg/localLedger_test.go b/cmd/tealdbg/localLedger_test.go index 05640abd4c..623215b463 100644 --- a/cmd/tealdbg/localLedger_test.go +++ b/cmd/tealdbg/localLedger_test.go @@ -83,7 +83,7 @@ int 2 a.NoError(err) assetIdx := basics.AssetIndex(50) - appIdx := basics.AppIndex(100) + appIdx := basics.AppIndex(1001) br := makeSampleBalanceRecord(addr, assetIdx, appIdx) balances := map[basics.Address]basics.AccountData{ addr: br.AccountData, diff --git a/cmd/tealdbg/local_test.go b/cmd/tealdbg/local_test.go index 01d5ca3b52..d0f6a3c2c3 100644 --- a/cmd/tealdbg/local_test.go +++ b/cmd/tealdbg/local_test.go @@ -523,7 +523,7 @@ int 100 ProgramBlobs: [][]byte{[]byte(source)}, BalanceBlob: balanceBlob, TxnBlob: txnBlob, - Proto: string(protocol.ConsensusCurrentVersion), + Proto: string(protocol.ConsensusV37), Round: 222, LatestTimestamp: 333, GroupIndex: 0, diff --git a/config/consensus.go b/config/consensus.go index 185e55ab95..d4e1890a8a 100644 --- a/config/consensus.go +++ b/config/consensus.go @@ -1272,21 +1272,46 @@ func initConsensusProtocols() { v35.ApprovedUpgrades[protocol.ConsensusV36] = 140000 + v37 := v36 + v37.ApprovedUpgrades = map[protocol.ConsensusVersion]uint64{} + + Consensus[protocol.ConsensusV37] = v37 + + // v36 can be upgraded to v37, with an update delay of 7 days ( see calculation above ) + v36.ApprovedUpgrades[protocol.ConsensusV37] = 140000 + + v38 := v37 + v38.ApprovedUpgrades = map[protocol.ConsensusVersion]uint64{} + + // enables state proof recoverability + v38.StateProofUseTrackerVerification = true + v38.EnableCatchpointsWithSPContexts = true + + // online circulation on-demand expiration + v38.ExcludeExpiredCirculation = true + + // TEAL resources sharing and other features + v38.LogicSigVersion = 9 + v38.EnablePrecheckECDSACurve = true + v38.AppForbidLowResources = true + v38.EnableBareBudgetError = true + v38.EnableBoxRefNameError = true + + v38.AgreementFilterTimeoutPeriod0 = 3000 * time.Millisecond + + Consensus[protocol.ConsensusV38] = v38 + + // v37 can be upgraded to v38, with an update delay of 12h: + // 10046 = (12 * 60 * 60 / 4.3) + // for the sake of future manual calculations, we'll round that down a bit : + v37.ApprovedUpgrades[protocol.ConsensusV38] = 10000 + // ConsensusFuture is used to test features that are implemented // but not yet released in a production protocol version. - vFuture := v36 + vFuture := v38 vFuture.ApprovedUpgrades = map[protocol.ConsensusVersion]uint64{} - vFuture.LogicSigVersion = 9 // When moving this to a release, put a new higher LogicSigVersion here - vFuture.EnablePrecheckECDSACurve = true - vFuture.AppForbidLowResources = true - vFuture.EnableBareBudgetError = true - vFuture.EnableBoxRefNameError = true - - vFuture.StateProofUseTrackerVerification = true - vFuture.EnableCatchpointsWithSPContexts = true - - vFuture.ExcludeExpiredCirculation = true + vFuture.LogicSigVersion = 10 // When moving this to a release, put a new higher LogicSigVersion here Consensus[protocol.ConsensusFuture] = vFuture diff --git a/data/transactions/logic/assembler_test.go b/data/transactions/logic/assembler_test.go index dc970ef64f..fd3b527b43 100644 --- a/data/transactions/logic/assembler_test.go +++ b/data/transactions/logic/assembler_test.go @@ -463,17 +463,16 @@ var nonsense = map[uint64]string{ } var compiled = map[uint64]string{ - 1: "012008b7a60cf8acd19181cf959a12f8acd19181cf951af8acd19181cf15f8acd191810f01020026050212340c68656c6c6f20776f726c6421208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d024242047465737400320032013202320328292929292a0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e0102222324252104082209240a220b230c240d250e230f2310231123122313231418191a1b1c2b1716154000032903494", - 2: "022008b7a60cf8acd19181cf959a12f8acd19181cf951af8acd19181cf15f8acd191810f01020026050212340c68656c6c6f20776f726c6421208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d024242047465737400320032013202320328292929292a0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e0102222324252104082209240a220b230c240d250e230f2310231123122313231418191a1b1c2b171615400003290349483403350222231d4a484848482a50512a63222352410003420000432105602105612105270463484821052b62482b642b65484821052b2106662b21056721072b682b692107210570004848210771004848361c0037001a0031183119311b311d311e311f3120210721051e312131223123312431253126312731283129312a312b312c312d312e312f", - 3: "032008b7a60cf8acd19181cf959a12f8acd19181cf951af8acd19181cf15f8acd191810f01020026050212340c68656c6c6f20776f726c6421208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d024242047465737400320032013202320328292929292a0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e0102222324252104082209240a220b230c240d250e230f2310231123122313231418191a1b1c2b171615400003290349483403350222231d4a484848482a50512a63222352410003420000432105602105612105270463484821052b62482b642b65484821052b2106662b21056721072b682b692107210570004848210771004848361c0037001a0031183119311b311d311e311f3120210721051e312131223123312431253126312731283129312a312b312c312d312e312f4478222105531421055427042106552105082106564c4d4b02210538212106391c0081e80780046a6f686e", - 4: "042004010200b7a60c26040242420c68656c6c6f20776f726c6421208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d047465737400320032013202320380021234292929292a0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e01022581f8acd19181cf959a1281f8acd19181cf951a81f8acd19181cf1581f8acd191810f082209240a220b230c240d250e230f2310231123122313231418191a1b1c28171615400003290349483403350222231d4a484848482a50512a632223524100034200004322602261222b634848222862482864286548482228236628226724286828692422700048482471004848361c0037001a0031183119311b311d311e311f312024221e312131223123312431253126312731283129312a312b312c312d312e312f44782522531422542b2355220823564c4d4b0222382123391c0081e80780046a6f686e2281d00f24231f880003420001892223902291922394239593a0a1a2a3a4a5a6a7a8a9aaabacadae23af3a00003b003c003d8164", - 5: "052004010002b7a60c26050242420c68656c6c6f20776f726c6421070123456789abcd208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d047465737400320032013202320380021234292929292b0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e01022581f8acd19181cf959a1281f8acd19181cf951a81f8acd19181cf1581f8acd191810f082209240a220b230c240d250e230f2310231123122313231418191a1b1c28171615400003290349483403350222231d4a484848482b50512a632223524100034200004322602261222704634848222862482864286548482228246628226723286828692322700048482371004848361c0037001a0031183119311b311d311e311f312023221e312131223123312431253126312731283129312a312b312c312d312e312f447825225314225427042455220824564c4d4b0222382124391c0081e80780046a6f686e2281d00f23241f880003420001892224902291922494249593a0a1a2a3a4a5a6a7a8a9aaabacadae24af3a00003b003c003d816472064e014f012a57000823810858235b235a2359b03139330039b1b200b322c01a23c1001a2323c21a23c3233e233f8120af06002a494905002a49490700b400b53a03", - 6: "06" + v6Compiled, - 7: "07" + v7Compiled, - 8: "08" + v8Compiled, - 9: "09" + v9Compiled, - - 10: "10" + v10Compiled, + 1: "012008b7a60cf8acd19181cf959a12f8acd19181cf951af8acd19181cf15f8acd191810f01020026050212340c68656c6c6f20776f726c6421208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d024242047465737400320032013202320328292929292a0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e0102222324252104082209240a220b230c240d250e230f2310231123122313231418191a1b1c2b1716154000032903494", + 2: "022008b7a60cf8acd19181cf959a12f8acd19181cf951af8acd19181cf15f8acd191810f01020026050212340c68656c6c6f20776f726c6421208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d024242047465737400320032013202320328292929292a0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e0102222324252104082209240a220b230c240d250e230f2310231123122313231418191a1b1c2b171615400003290349483403350222231d4a484848482a50512a63222352410003420000432105602105612105270463484821052b62482b642b65484821052b2106662b21056721072b682b692107210570004848210771004848361c0037001a0031183119311b311d311e311f3120210721051e312131223123312431253126312731283129312a312b312c312d312e312f", + 3: "032008b7a60cf8acd19181cf959a12f8acd19181cf951af8acd19181cf15f8acd191810f01020026050212340c68656c6c6f20776f726c6421208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d024242047465737400320032013202320328292929292a0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e0102222324252104082209240a220b230c240d250e230f2310231123122313231418191a1b1c2b171615400003290349483403350222231d4a484848482a50512a63222352410003420000432105602105612105270463484821052b62482b642b65484821052b2106662b21056721072b682b692107210570004848210771004848361c0037001a0031183119311b311d311e311f3120210721051e312131223123312431253126312731283129312a312b312c312d312e312f4478222105531421055427042106552105082106564c4d4b02210538212106391c0081e80780046a6f686e", + 4: "042004010200b7a60c26040242420c68656c6c6f20776f726c6421208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d047465737400320032013202320380021234292929292a0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e01022581f8acd19181cf959a1281f8acd19181cf951a81f8acd19181cf1581f8acd191810f082209240a220b230c240d250e230f2310231123122313231418191a1b1c28171615400003290349483403350222231d4a484848482a50512a632223524100034200004322602261222b634848222862482864286548482228236628226724286828692422700048482471004848361c0037001a0031183119311b311d311e311f312024221e312131223123312431253126312731283129312a312b312c312d312e312f44782522531422542b2355220823564c4d4b0222382123391c0081e80780046a6f686e2281d00f24231f880003420001892223902291922394239593a0a1a2a3a4a5a6a7a8a9aaabacadae23af3a00003b003c003d8164", + 5: "052004010002b7a60c26050242420c68656c6c6f20776f726c6421070123456789abcd208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d047465737400320032013202320380021234292929292b0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e01022581f8acd19181cf959a1281f8acd19181cf951a81f8acd19181cf1581f8acd191810f082209240a220b230c240d250e230f2310231123122313231418191a1b1c28171615400003290349483403350222231d4a484848482b50512a632223524100034200004322602261222704634848222862482864286548482228246628226723286828692322700048482371004848361c0037001a0031183119311b311d311e311f312023221e312131223123312431253126312731283129312a312b312c312d312e312f447825225314225427042455220824564c4d4b0222382124391c0081e80780046a6f686e2281d00f23241f880003420001892224902291922494249593a0a1a2a3a4a5a6a7a8a9aaabacadae24af3a00003b003c003d816472064e014f012a57000823810858235b235a2359b03139330039b1b200b322c01a23c1001a2323c21a23c3233e233f8120af06002a494905002a49490700b400b53a03", + 6: "06" + v6Compiled, + 7: "07" + v7Compiled, + 8: "08" + v8Compiled, + 9: "09" + v9Compiled, + 10: "0a" + v10Compiled, } func pseudoOp(opcode string) bool { diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index 9317be9203..f93df4149e 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -1150,6 +1150,10 @@ const globalV9TestProgram = globalV8TestProgram + ` // No new globals in v9 ` +const globalV10TestProgram = globalV9TestProgram + ` +// No new globals in v10 +` + func TestGlobal(t *testing.T) { partitiontest.PartitionTest(t) @@ -1160,16 +1164,17 @@ func TestGlobal(t *testing.T) { } // Associate the highest allowed global constant with each version's test program tests := map[uint64]desc{ - 0: {GroupSize, globalV1TestProgram}, - 1: {GroupSize, globalV1TestProgram}, - 2: {CurrentApplicationID, globalV2TestProgram}, - 3: {CreatorAddress, globalV3TestProgram}, - 4: {CreatorAddress, globalV4TestProgram}, - 5: {GroupID, globalV5TestProgram}, - 6: {CallerApplicationAddress, globalV6TestProgram}, - 7: {CallerApplicationAddress, globalV7TestProgram}, - 8: {CallerApplicationAddress, globalV8TestProgram}, - 9: {CallerApplicationAddress, globalV9TestProgram}, + 0: {GroupSize, globalV1TestProgram}, + 1: {GroupSize, globalV1TestProgram}, + 2: {CurrentApplicationID, globalV2TestProgram}, + 3: {CreatorAddress, globalV3TestProgram}, + 4: {CreatorAddress, globalV4TestProgram}, + 5: {GroupID, globalV5TestProgram}, + 6: {CallerApplicationAddress, globalV6TestProgram}, + 7: {CallerApplicationAddress, globalV7TestProgram}, + 8: {CallerApplicationAddress, globalV8TestProgram}, + 9: {CallerApplicationAddress, globalV9TestProgram}, + 10: {CallerApplicationAddress, globalV10TestProgram}, } // tests keys are versions so they must be in a range 1..AssemblerMaxVersion plus zero version require.LessOrEqual(t, len(tests), AssemblerMaxVersion+1) @@ -1673,6 +1678,11 @@ assert int 1 ` +const testTxnProgramTextV10 = testTxnProgramTextV9 + ` +assert +int 1 +` + func makeSampleTxn() transactions.SignedTxn { var txn transactions.SignedTxn copy(txn.Txn.Sender[:], []byte("aoeuiaoeuiaoeuiaoeuiaoeuiaoeui00")) @@ -1776,15 +1786,16 @@ func TestTxn(t *testing.T) { t.Parallel() tests := map[uint64]string{ - 1: testTxnProgramTextV1, - 2: testTxnProgramTextV2, - 3: testTxnProgramTextV3, - 4: testTxnProgramTextV4, - 5: testTxnProgramTextV5, - 6: testTxnProgramTextV6, - 7: testTxnProgramTextV7, - 8: testTxnProgramTextV8, - 9: testTxnProgramTextV9, + 1: testTxnProgramTextV1, + 2: testTxnProgramTextV2, + 3: testTxnProgramTextV3, + 4: testTxnProgramTextV4, + 5: testTxnProgramTextV5, + 6: testTxnProgramTextV6, + 7: testTxnProgramTextV7, + 8: testTxnProgramTextV8, + 9: testTxnProgramTextV9, + 10: testTxnProgramTextV10, } for i, txnField := range TxnFieldNames { diff --git a/data/transactions/logic/langspec.json b/data/transactions/logic/langspec.json index 921a64943e..1892aa42be 100644 --- a/data/transactions/logic/langspec.json +++ b/data/transactions/logic/langspec.json @@ -1,6 +1,6 @@ { "EvalMaxVersion": 9, - "LogicSigVersion": 8, + "LogicSigVersion": 9, "NamedTypes": [ { "Name": "uint64", diff --git a/data/transactions/logic/opcodes.go b/data/transactions/logic/opcodes.go index 4ba1b10d2a..90c52f0865 100644 --- a/data/transactions/logic/opcodes.go +++ b/data/transactions/logic/opcodes.go @@ -24,7 +24,7 @@ import ( ) // LogicVersion defines default assembler and max eval versions -const LogicVersion = 9 +const LogicVersion = 10 // rekeyingEnabledVersion is the version of TEAL where RekeyTo functionality // was enabled. This is important to remember so that old TEAL accounts cannot diff --git a/ledger/acctonline_test.go b/ledger/acctonline_test.go index 803cf9f773..0bee1f35fc 100644 --- a/ledger/acctonline_test.go +++ b/ledger/acctonline_test.go @@ -1368,7 +1368,9 @@ func addSinkAndPoolAccounts(genesisAccts []map[basics.Address]basics.AccountData func newBlockWithUpdates(genesisAccts []map[basics.Address]basics.AccountData, updates ledgercore.AccountDeltas, prevTotals ledgercore.AccountTotals, t *testing.T, ml *mockLedgerForTracker, round int, oa *onlineAccounts) ledgercore.AccountTotals { base := genesisAccts[0] - newTotals := newBlock(t, ml, protocol.ConsensusCurrentVersion, config.Consensus[protocol.ConsensusCurrentVersion], basics.Round(round), base, updates, prevTotals) + proto := ml.GenesisProtoVersion() + params := ml.GenesisProto() + newTotals := newBlock(t, ml, proto, params, basics.Round(round), base, updates, prevTotals) commitSync(t, oa, ml, basics.Round(round)) return newTotals } @@ -1416,7 +1418,8 @@ func TestAcctOnlineTop(t *testing.T) { genesisAccts[0][allAccts[i].Addr] = allAccts[i].AccountData addSinkAndPoolAccounts(genesisAccts) - ml := makeMockLedgerForTracker(t, true, 1, protocol.ConsensusCurrentVersion, genesisAccts) + // run this test on ConsensusV37 rules, run TestAcctOnlineTop_ChangeOnlineStake on current + ml := makeMockLedgerForTracker(t, true, 1, protocol.ConsensusV37, genesisAccts) defer ml.Close() conf := config.GetDefaultLocal() @@ -1752,7 +1755,7 @@ func TestAcctOnlineTopDBBehindMemRound(t *testing.T) { a.Contains(err.Error(), "is behind in-memory round") case <-time.After(1 * time.Minute): - a.FailNow("timedout while waiting for post commit") + a.FailNow("timeout while waiting for post commit") } } @@ -1835,7 +1838,7 @@ func TestAcctOnlineTop_ChangeOnlineStake(t *testing.T) { voteRndExpectedStake = algops.Sub(voteRndExpectedStake, allAccts[18].MicroAlgos) // Online on rnd but not valid on voteRnd updatedAccts[15].Status = basics.Offline // Mark account 15 offline for comparison updatedAccts[18].Status = basics.Offline // Mark account 18 offline for comparison - top = compareOnlineTotals(a, oa, 18, 19, 5, rnd15TotalOnlineStake, voteRndExpectedStake) + top = compareOnlineTotals(a, oa, 18, 19, 5, voteRndExpectedStake, voteRndExpectedStake) compareTopAccounts(a, top, updatedAccts) } diff --git a/ledger/applications_test.go b/ledger/applications_test.go index 54470f5f97..d89f03a67f 100644 --- a/ledger/applications_test.go +++ b/ledger/applications_test.go @@ -203,11 +203,10 @@ return` Header: txHeader, ApplicationCallTxnFields: appCreateFields, } - err = l.appendUnvalidatedTx(t, genesisInitState.Accounts, initKeys, appCreate, transactions.ApplyData{ApplicationID: 1}) + appIdx := basics.AppIndex(1001) // first tnx => idx = 1001 + err = l.appendUnvalidatedTx(t, genesisInitState.Accounts, initKeys, appCreate, transactions.ApplyData{ApplicationID: appIdx}) a.NoError(err) - appIdx := basics.AppIndex(1) // first tnx => idx = 1 - // opt-in, do no write txHeader.Sender = userOptin appCallFields := transactions.ApplicationCallTxnFields{ @@ -430,11 +429,10 @@ return` Header: txHeader, ApplicationCallTxnFields: appCreateFields, } - err = l.appendUnvalidatedTx(t, genesisInitState.Accounts, initKeys, appCreate, transactions.ApplyData{ApplicationID: 1}) + appIdx := basics.AppIndex(1001) // first txn => idx = 1001 since AppForbidLowResources sets tx counter to 1000 + err = l.appendUnvalidatedTx(t, genesisInitState.Accounts, initKeys, appCreate, transactions.ApplyData{ApplicationID: appIdx}) a.NoError(err) - appIdx := basics.AppIndex(1) // first tnx => idx = 1 - // opt-in, write to local txHeader.Sender = userLocal appCallFields := transactions.ApplicationCallTxnFields{ @@ -673,11 +671,10 @@ return` Header: txHeader, ApplicationCallTxnFields: appCreateFields, } - err = l.appendUnvalidatedTx(t, genesisInitState.Accounts, initKeys, appCreate, transactions.ApplyData{ApplicationID: 1}) + appIdx := basics.AppIndex(1001) // first tnx => idx = 1001 + err = l.appendUnvalidatedTx(t, genesisInitState.Accounts, initKeys, appCreate, transactions.ApplyData{ApplicationID: appIdx}) a.NoError(err) - appIdx := basics.AppIndex(1) // first tnx => idx = 1 - // opt-in, write to local txHeader.Sender = userLocal appCallFields := transactions.ApplicationCallTxnFields{ @@ -827,11 +824,10 @@ return` Header: txHeader, ApplicationCallTxnFields: appCreateFields, } - err = l.appendUnvalidatedTx(t, genesisInitState.Accounts, initKeys, appCreate, transactions.ApplyData{ApplicationID: 1}) + appIdx := basics.AppIndex(1001) // first tnx => idx = 1001 + err = l.appendUnvalidatedTx(t, genesisInitState.Accounts, initKeys, appCreate, transactions.ApplyData{ApplicationID: appIdx}) a.NoError(err) - appIdx := basics.AppIndex(1) // first tnx => idx = 1 - // destoy the app txHeader.Sender = creator appCallFields := transactions.ApplicationCallTxnFields{ @@ -1150,7 +1146,7 @@ int 1 } // create application - appIdx := basics.AppIndex(1) // first tnx => idx = 1 + appIdx := basics.AppIndex(1001) // first tnx => idx = 1001 approvalProgram := program clearStateProgram := []byte("\x02") // empty diff --git a/ledger/apply/stateproof_test.go b/ledger/apply/stateproof_test.go index 72c898475d..155a4eef5d 100644 --- a/ledger/apply/stateproof_test.go +++ b/ledger/apply/stateproof_test.go @@ -205,7 +205,7 @@ func TestApplyStateProof(t *testing.T) { blocks: blocks, blockErr: blockErr, stateProofVerification: stateProofVerification, - version: protocol.ConsensusFuture, + version: protocol.ConsensusCurrentVersion, } spType = protocol.StateProofType(1234) // bad stateproof type @@ -234,7 +234,7 @@ func TestApplyStateProof(t *testing.T) { a.ErrorIs(err, ErrExpectedDifferentStateProofRound) atRoundBlock := bookkeeping.BlockHeader{} - atRoundBlock.CurrentProtocol = protocol.ConsensusFuture + atRoundBlock.CurrentProtocol = protocol.ConsensusCurrentVersion blocks[atRound] = atRoundBlock validate = true @@ -258,7 +258,7 @@ func TestApplyStateProof(t *testing.T) { LastAttestedRound: basics.Round(stateProofTx.Message.LastAttestedRound), VotersCommitment: []byte{0x1}[:], OnlineTotalWeight: basics.MicroAlgos{Raw: 5}, - Version: protocol.ConsensusFuture, + Version: protocol.ConsensusCurrentVersion, } // crypto verification should fail since it is not a valid stateproof @@ -271,5 +271,5 @@ func TestApplyStateProof(t *testing.T) { err = StateProof(stateProofTx, atRound, applier, false) a.NoError(err) // make sure that the StateProofNext was updated correctly after applying - a.Equal(basics.Round(512+config.Consensus[protocol.ConsensusFuture].StateProofInterval), applier.GetStateProofNextRound()) + a.Equal(basics.Round(512+config.Consensus[protocol.ConsensusCurrentVersion].StateProofInterval), applier.GetStateProofNextRound()) } diff --git a/ledger/apptxn_test.go b/ledger/apptxn_test.go index dd4998ff41..28a5b10404 100644 --- a/ledger/apptxn_test.go +++ b/ledger/apptxn_test.go @@ -794,7 +794,7 @@ func TestAsaDuringInit(t *testing.T) { defer dl.Close() appID := basics.AppIndex(2) - if ver >= 37 { // AppForbidLowResources + if ver >= 38 { // AppForbidLowResources appID += 1000 } prefund := txntest.Txn{ @@ -1230,7 +1230,7 @@ func TestGtxnEffects(t *testing.T) { // needed in very first app, so hardcode asaID := basics.AssetIndex(3) - if ver >= 37 { + if ver >= 38 { asaID += 1000 } appID := dl.fundedApp(addrs[0], 1_000_000, main(` @@ -3030,7 +3030,7 @@ app_local_put switch { case ver < 34: // before v7, app accounts not available at all problem = "invalid Account reference " + id0.Address().String() - case ver < 37: // as of v7, it's the mutation that's the problem + case ver < 38: // as of v7, it's the mutation that's the problem problem = "invalid Account reference for mutation" } dl.txgroup(problem, &fund0, &fund1, &optin, &callTx) @@ -3157,7 +3157,7 @@ func TestEvalAppState(t *testing.T) { defer dl.Close() appID := basics.AppIndex(1) - if ver >= 37 { // AppForbidLowResources + if ver >= 38 { // AppForbidLowResources appID += 1000 } appcall1 := txntest.Txn{ diff --git a/ledger/eval_simple_test.go b/ledger/eval_simple_test.go index bb2f9f52be..606ee4931d 100644 --- a/ledger/eval_simple_test.go +++ b/ledger/eval_simple_test.go @@ -599,7 +599,7 @@ func TestMinBalanceChanges(t *testing.T) { }, } - const expectedID basics.AssetIndex = 1 + const expectedID basics.AssetIndex = 1001 optInTxn := txntest.Txn{ Type: "axfer", Sender: addrs[5], diff --git a/ledger/testing/consensusRange.go b/ledger/testing/consensusRange.go index 5408195f71..019b502717 100644 --- a/ledger/testing/consensusRange.go +++ b/ledger/testing/consensusRange.go @@ -57,7 +57,9 @@ var consensusByNumber = []protocol.ConsensusVersion{ protocol.ConsensusV33, // 320 rounds protocol.ConsensusV34, // AVM v7, stateproofs protocol.ConsensusV35, // minor, double upgrade withe v34 - protocol.ConsensusV36, // box storage + protocol.ConsensusV36, // AVM v8, box storage + protocol.ConsensusV37, + protocol.ConsensusV38, // AVM v9, ECDSA pre-check, stateproofs recoverability protocol.ConsensusFuture, } diff --git a/ledger/testing/consensusRange_test.go b/ledger/testing/consensusRange_test.go index cd5baaa817..26e0420946 100644 --- a/ledger/testing/consensusRange_test.go +++ b/ledger/testing/consensusRange_test.go @@ -56,6 +56,6 @@ func TestReleasedVersion(t *testing.T) { } require.Equal(t, versionStringFromIndex(len(consensusByNumber)-1), "vFuture") - require.Equal(t, versionStringFromIndex(36), "v36") + require.Equal(t, versionStringFromIndex(38), "v38") } diff --git a/protocol/consensus.go b/protocol/consensus.go index 00857875d6..54aa5939dc 100644 --- a/protocol/consensus.go +++ b/protocol/consensus.go @@ -182,7 +182,7 @@ const ConsensusV33 = ConsensusVersion( "https://github.com/algorandfoundation/specs/tree/830a4e673148498cc7230a0d1ba1ed0a5471acc6", ) -// ConsensusV34 enables the TEAL v7 opcodes, stateproofs, shorter lambda. +// ConsensusV34 enables the TEAL v7 opcodes, stateproofs, shorter lambda to 1.7s. const ConsensusV34 = ConsensusVersion( "https://github.com/algorandfoundation/specs/tree/2dd5435993f6f6d65691140f592ebca5ef19ffbd", ) @@ -192,11 +192,25 @@ const ConsensusV35 = ConsensusVersion( "https://github.com/algorandfoundation/specs/tree/433d8e9a7274b6fca703d91213e05c7e6a589e69", ) -// ConsensusV36 adds box storage +// ConsensusV36 adds box storage in TEAL v8 const ConsensusV36 = ConsensusVersion( "https://github.com/algorandfoundation/specs/tree/44fa607d6051730f5264526bf3c108d51f0eadb6", ) +// ConsensusV37 is a technical upgrade and released in the same time as ConsensusV38. +// It is needed to allow nodes to build up a necessary state to support state proofs related +// options in ConsensusV38 +const ConsensusV37 = ConsensusVersion( + "https://github.com/algorandfoundation/specs/tree/1ac4dd1f85470e1fb36c8a65520e1313d7dfed5e", +) + +// ConsensusV38 enables state proof verification using a special tracker, +// TEAL v9 resources sharing, pre-check ECDSA curve and extra features, and +// shortens the lambda to 1.5s. +const ConsensusV38 = ConsensusVersion( + "https://github.com/algorandfoundation/specs/tree/abd3d4823c6f77349fc04c3af7b1e99fe4df699f", +) + // ConsensusFuture is a protocol that should not appear in any production // network, but is used to test features before they are released. const ConsensusFuture = ConsensusVersion( @@ -226,7 +240,7 @@ const ConsensusVAlpha5 = ConsensusVersion("alpha5") // ConsensusCurrentVersion is the latest version and should be used // when a specific version is not provided. -const ConsensusCurrentVersion = ConsensusV36 +const ConsensusCurrentVersion = ConsensusV38 // Error is used to indicate that an unsupported protocol has been detected. type Error ConsensusVersion diff --git a/test/e2e-go/features/accountPerf/sixMillion_test.go b/test/e2e-go/features/accountPerf/sixMillion_test.go index cc2095dfbd..7548b259bd 100644 --- a/test/e2e-go/features/accountPerf/sixMillion_test.go +++ b/test/e2e-go/features/accountPerf/sixMillion_test.go @@ -680,12 +680,13 @@ func scenarioB( require.Equal(t, numberOfAssets, info.TotalCreatedAssets) log.Infof("Verifying assets...") - // Verify the assets are transfered here + // Verify the assets are transferred here tAssetAmt := uint64(0) counter = 0 - // this loop iterates over all the range of potentail assets, tries to confirm all of them. + // this loop iterates over all the range of potential assets, tries to confirm all of them. // many of these are expected to be non-existing. - for aid := uint64(0); counter < numberOfAssets && aid < 2*numberOfAssets; aid++ { + startIdx := uint64(1000) // tx counter starts from 1000 + for aid := startIdx; counter < numberOfAssets && aid < 2*startIdx*numberOfAssets; aid++ { select { case <-stopChan: require.False(t, true, "Test interrupted") diff --git a/test/e2e-go/features/transactions/asset_test.go b/test/e2e-go/features/transactions/asset_test.go index 3c6987b42f..7715f01f41 100644 --- a/test/e2e-go/features/transactions/asset_test.go +++ b/test/e2e-go/features/transactions/asset_test.go @@ -549,7 +549,7 @@ func TestAssetGroupCreateSendDestroy(t *testing.T) { a.NoError(err) account1 := accountList[0] - txCount := uint64(0) + txCount := uint64(1000) // starting with v38 tx count is initialized to 1000 fee := uint64(1000000) manager := account0 diff --git a/test/scripts/e2e_subs/e2e-app-simulate.sh b/test/scripts/e2e_subs/e2e-app-simulate.sh index f4abd7df59..c17960eccf 100755 --- a/test/scripts/e2e_subs/e2e-app-simulate.sh +++ b/test/scripts/e2e_subs/e2e-app-simulate.sh @@ -106,7 +106,7 @@ fi # WE ALSO TEST OVERSPEND IN TRANSACTION GROUP # ############################################### -${gcmd} clerk send -a 1000000000000 -f ${ACCOUNT} -t ${ACCOUNT} -o "${TEMPDIR}/pay1.tx" +${gcmd} clerk send -a 1000000000000000 -f ${ACCOUNT} -t ${ACCOUNT} -o "${TEMPDIR}/pay1.tx" ${gcmd} clerk send -a 10000 -f ${ACCOUNT} -t ${ACCOUNT} -o "${TEMPDIR}/pay2.tx" cat "${TEMPDIR}/pay1.tx" "${TEMPDIR}/pay2.tx" | ${gcmd} clerk group -i - -o "${TEMPDIR}/grouped.tx" @@ -121,14 +121,14 @@ cat "${TEMPDIR}/grouped-0.stx" "${TEMPDIR}/grouped-1.stx" > "${TEMPDIR}/grouped. RES=$(${gcmd} clerk simulate -t "${TEMPDIR}/grouped.stx") if [[ $(echo "$RES" | jq '."txn-groups" | any(has("failure-message"))') != $CONST_TRUE ]]; then - data '+app-simulate-test FAIL should FAIL for overspending in simulate self pay transaction group %Y%m%d_%H%M%S' + date '+app-simulate-test FAIL should FAIL for overspending in simulate self pay transaction group %Y%m%d_%H%M%S' false fi OVERSPEND_INFO="overspend" if [[ $(echo "$RES" | jq '."txn-groups"[0]."failure-message"') != *"$OVERSPEND_INFO"* ]]; then - data '+app-simulate-test FAIL first overspending transaction in transaction group should contain message OVERSPEND %Y%m%d_%H%M%S' + date '+app-simulate-test FAIL first overspending transaction in transaction group should contain message OVERSPEND %Y%m%d_%H%M%S' false fi From f256d9d35121aba29357271815a898fcf1b06f09 Mon Sep 17 00:00:00 2001 From: DevOps Service Date: Mon, 22 May 2023 15:19:56 +0000 Subject: [PATCH 106/125] Update the Version, BuildNumber, genesistimestamp.data --- buildnumber.dat | 1 + genesistimestamp.dat | 1 + 2 files changed, 2 insertions(+) create mode 100644 buildnumber.dat create mode 100644 genesistimestamp.dat diff --git a/buildnumber.dat b/buildnumber.dat new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/buildnumber.dat @@ -0,0 +1 @@ +0 diff --git a/genesistimestamp.dat b/genesistimestamp.dat new file mode 100644 index 0000000000..c72c6a7795 --- /dev/null +++ b/genesistimestamp.dat @@ -0,0 +1 @@ +1558657885 From a873070852bea44ab26f9450c0efc14e446c812a Mon Sep 17 00:00:00 2001 From: shiqizng <80276844+shiqizng@users.noreply.github.com> Date: Mon, 22 May 2023 13:44:05 -0400 Subject: [PATCH 107/125] tools: run block generator using a preloaded db (#5384) --- tools/block-generator/generator/generate.go | 87 +++++++--- .../generator/generate_test.go | 154 +++++++++++++++--- tools/block-generator/generator/server.go | 13 +- tools/block-generator/run_runner.sh | 6 +- tools/block-generator/runner/run.go | 14 +- tools/block-generator/runner/runner.go | 2 + 6 files changed, 219 insertions(+), 57 deletions(-) diff --git a/tools/block-generator/generator/generate.go b/tools/block-generator/generator/generate.go index d1e5b2bda6..8a619fa4dd 100644 --- a/tools/block-generator/generator/generate.go +++ b/tools/block-generator/generator/generate.go @@ -62,7 +62,8 @@ const ( assetTotal = uint64(100000000000000000) - consensusTimeMilli int64 = 4500 + consensusTimeMilli int64 = 4500 + startingTxnCounter uint64 = 1000 ) // GenerationConfig defines the tunable parameters for block generation. @@ -99,7 +100,7 @@ func sumIsCloseToOne(numbers ...float32) bool { } // MakeGenerator initializes the Generator object. -func MakeGenerator(config GenerationConfig) (Generator, error) { +func MakeGenerator(dbround uint64, bkGenesis bookkeeping.Genesis, config GenerationConfig) (Generator, error) { if !sumIsCloseToOne(config.PaymentTransactionFraction, config.AssetTransactionFraction) { return nil, fmt.Errorf("transaction distribution ratios should equal 1") } @@ -117,26 +118,33 @@ func MakeGenerator(config GenerationConfig) (Generator, error) { config: config, protocol: proto, params: cconfig.Consensus[proto], + genesis: bkGenesis, genesisHash: [32]byte{}, genesisID: "blockgen-test", prevBlockHash: "", round: 0, - txnCounter: 0, + txnCounter: startingTxnCounter, timestamp: 0, rewardsLevel: 0, rewardsResidue: 0, rewardsRate: 0, rewardsRecalculationRound: 0, reportData: make(map[TxTypeID]TxData), + nextdbround: dbround, } gen.feeSink[31] = 1 gen.rewardsPool[31] = 2 gen.genesisHash[31] = 3 + // if genesis is provided + if bkGenesis.Network != "" { + gen.genesisID = bkGenesis.ID() + gen.genesisHash = bkGenesis.Hash() + } + gen.initializeAccounting() gen.initializeLedger() - for _, val := range getTransactionOptions() { switch val { case paymentTx: @@ -201,6 +209,7 @@ type generator struct { timestamp int64 protocol protocol.ConsensusVersion params cconfig.ConsensusParams + genesis bookkeeping.Genesis genesisID string genesisHash crypto.Digest @@ -232,6 +241,9 @@ type generator struct { // ledger ledger *ledger.Ledger + + // next_account_round in the preloaded database + nextdbround uint64 } type assetData struct { @@ -274,15 +286,22 @@ func (g *generator) WriteReport(output io.Writer) error { func (g *generator) WriteStatus(output io.Writer) error { response := model.NodeStatusResponse{ - LastRound: g.round, + LastRound: g.round + g.nextdbround, } return json.NewEncoder(output).Encode(response) } func (g *generator) WriteGenesis(output io.Writer) error { defer g.recordData(track(genesis)) - var allocations []bookkeeping.GenesisAllocation + // return user provided genesis + if g.genesis.Network != "" { + _, err := output.Write(protocol.EncodeJSON(g.genesis)) + return err + } + + // return synthetic genesis + var allocations []bookkeeping.GenesisAllocation for i := uint64(0); i < g.config.NumGenesisAccounts; i++ { addr := indexToAccount(i) allocations = append(allocations, bookkeeping.GenesisAllocation{ @@ -359,21 +378,27 @@ func (g *generator) finishRound(txnCount uint64) { // WriteBlock generates a block full of new transactions and writes it to the writer. func (g *generator) WriteBlock(output io.Writer, round uint64) error { - - if round != g.round { - fmt.Printf("Generator only supports sequential block access. Expected %d but received request for %d.\n", g.round, round) + if round < g.nextdbround { + return fmt.Errorf("cannot generate block for round %d, already in database", round) } + if round-g.nextdbround != g.round { + return fmt.Errorf("generator only supports sequential block access. Expected %d but received request for %d", g.round+g.nextdbround, round) + } + numTxnForBlock := g.txnForRound(g.round) - numTxnForBlock := g.txnForRound(round) - - // return genesis block - if round == 0 { + // return genesis block. offset round for non-empty database + if round-g.nextdbround == 0 { // write the msgpack bytes for a block - block, err := rpcs.RawBlockBytes(g.ledger, basics.Round(round)) - if err != nil { - return err + block, cert, _ := g.ledger.BlockCert(basics.Round(round - g.nextdbround)) + // return the block with the requested round number + block.BlockHeader.Round = basics.Round(round) + encodedblock := rpcs.EncodedBlockCert{ + Block: block, + Certificate: cert, } - _, err = output.Write(block) + blk := protocol.EncodeMsgp(&encodedblock) + // write the msgpack bytes for a block + _, err := output.Write(blk) if err != nil { return err } @@ -436,11 +461,13 @@ func (g *generator) WriteBlock(output io.Writer, round uint64) error { if err != nil { return err } - // write the msgpack bytes for a block - block, err := rpcs.RawBlockBytes(g.ledger, basics.Round(round)) + // return the block with the requested round number + cert.Block.BlockHeader.Round = basics.Round(round) + block := protocol.EncodeMsgp(&cert) if err != nil { return err } + // write the msgpack bytes for a block _, err = output.Write(block) if err != nil { return err @@ -451,7 +478,16 @@ func (g *generator) WriteBlock(output io.Writer, round uint64) error { // WriteDeltas generates returns the deltas for payset. func (g *generator) WriteDeltas(output io.Writer, round uint64) error { - delta, err := g.ledger.GetStateDeltaForRound(basics.Round(round)) + // offset round for non-empty database + if round-g.nextdbround == 0 { + data, _ := encode(protocol.CodecHandle, ledgercore.StateDelta{}) + _, err := output.Write(data) + if err != nil { + return err + } + return nil + } + delta, err := g.ledger.GetStateDeltaForRound(basics.Round(round - g.nextdbround)) if err != nil { return fmt.Errorf("err getting state delta for round %d: %w", round, err) } @@ -566,7 +602,6 @@ func (g *generator) generateAssetTxnInternalHint(txType TxTypeID, round uint64, numAssets := uint64(len(g.assets)) var senderIndex uint64 - if actual == assetCreate { numAssets = uint64(len(g.assets)) + uint64(len(g.pendingAssets)) senderIndex = numAssets % g.config.NumGenesisAccounts @@ -576,7 +611,6 @@ func (g *generator) generateAssetTxnInternalHint(txType TxTypeID, round uint64, assetID := g.txnCounter + intra + 1 assetName := fmt.Sprintf("asset #%d", assetID) txn = g.makeAssetCreateTxn(g.makeTxnHeader(senderAcct, round, intra), total, false, assetName) - // Compute asset ID and initialize holdings holding := assetHolding{ acctIndex: senderIndex, @@ -651,7 +685,6 @@ func (g *generator) generateAssetTxnInternalHint(txType TxTypeID, round uint64, receiverArrayIndex := (rand.Uint64() % (uint64(len(asset.holdings)) - uint64(1))) + uint64(1) receiver := indexToAccount(asset.holdings[receiverArrayIndex].acctIndex) - amount := uint64(10) txn = g.makeAssetTransferTxn(g.makeTxnHeader(sender, round, intra), receiver, amount, basics.Address{}, asset.assetID) @@ -739,7 +772,13 @@ func (g *generator) initializeLedger() { fmt.Printf("error making genesis: %v\n.", err) os.Exit(1) } - l, err := ledger.OpenLedger(logging.Base(), "block-generator", true, ledgercore.InitState{ + var prefix string + if g.genesisID == "" { + prefix = "block-generator" + } else { + prefix = g.genesisID + } + l, err := ledger.OpenLedger(logging.Base(), prefix, true, ledgercore.InitState{ Block: block, Accounts: bal.Balances, GenesisHash: g.genesisHash, diff --git a/tools/block-generator/generator/generate_test.go b/tools/block-generator/generator/generate_test.go index e57d3f8006..78a07b98de 100644 --- a/tools/block-generator/generator/generate_test.go +++ b/tools/block-generator/generator/generate_test.go @@ -18,11 +18,13 @@ package generator import ( "bytes" + "fmt" "net/http" "net/http/httptest" "testing" "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/protocol" "github.com/algorand/go-algorand/rpcs" @@ -30,9 +32,8 @@ import ( "github.com/stretchr/testify/require" ) -func makePrivateGenerator(t *testing.T) *generator { - partitiontest.PartitionTest(t) - publicGenerator, err := MakeGenerator(GenerationConfig{ +func makePrivateGenerator(t *testing.T, round uint64, genesis bookkeeping.Genesis) *generator { + publicGenerator, err := MakeGenerator(round, genesis, GenerationConfig{ NumGenesisAccounts: 10, GenesisAccountInitialBalance: 1000000000000, PaymentTransactionFraction: 1.0, @@ -45,21 +46,21 @@ func makePrivateGenerator(t *testing.T) *generator { func TestPaymentAcctCreate(t *testing.T) { partitiontest.PartitionTest(t) - g := makePrivateGenerator(t) + g := makePrivateGenerator(t, 0, bookkeeping.Genesis{}) g.generatePaymentTxnInternal(paymentAcctCreateTx, 0, 0) require.Len(t, g.balances, int(g.config.NumGenesisAccounts+1)) } func TestPaymentTransfer(t *testing.T) { partitiontest.PartitionTest(t) - g := makePrivateGenerator(t) + g := makePrivateGenerator(t, 0, bookkeeping.Genesis{}) g.generatePaymentTxnInternal(paymentTx, 0, 0) require.Len(t, g.balances, int(g.config.NumGenesisAccounts)) } func TestAssetXferNoAssetsOverride(t *testing.T) { partitiontest.PartitionTest(t) - g := makePrivateGenerator(t) + g := makePrivateGenerator(t, 0, bookkeeping.Genesis{}) // First asset transaction must create. actual, txn := g.generateAssetTxnInternal(assetXfer, 1, 0) @@ -73,7 +74,7 @@ func TestAssetXferNoAssetsOverride(t *testing.T) { func TestAssetXferOneHolderOverride(t *testing.T) { partitiontest.PartitionTest(t) - g := makePrivateGenerator(t) + g := makePrivateGenerator(t, 0, bookkeeping.Genesis{}) g.finishRound(0) g.generateAssetTxnInternal(assetCreate, 1, 0) g.finishRound(1) @@ -90,7 +91,7 @@ func TestAssetXferOneHolderOverride(t *testing.T) { func TestAssetCloseCreatorOverride(t *testing.T) { partitiontest.PartitionTest(t) - g := makePrivateGenerator(t) + g := makePrivateGenerator(t, 0, bookkeeping.Genesis{}) g.finishRound(0) g.generateAssetTxnInternal(assetCreate, 1, 0) g.finishRound(1) @@ -107,7 +108,7 @@ func TestAssetCloseCreatorOverride(t *testing.T) { func TestAssetOptinEveryAccountOverride(t *testing.T) { partitiontest.PartitionTest(t) - g := makePrivateGenerator(t) + g := makePrivateGenerator(t, 0, bookkeeping.Genesis{}) g.finishRound(0) g.generateAssetTxnInternal(assetCreate, 1, 0) g.finishRound(1) @@ -140,7 +141,7 @@ func TestAssetOptinEveryAccountOverride(t *testing.T) { func TestAssetDestroyWithHoldingsOverride(t *testing.T) { partitiontest.PartitionTest(t) - g := makePrivateGenerator(t) + g := makePrivateGenerator(t, 0, bookkeeping.Genesis{}) g.finishRound(0) g.generateAssetTxnInternal(assetCreate, 1, 0) g.finishRound(1) @@ -161,7 +162,7 @@ func TestAssetDestroyWithHoldingsOverride(t *testing.T) { func TestAssetTransfer(t *testing.T) { partitiontest.PartitionTest(t) - g := makePrivateGenerator(t) + g := makePrivateGenerator(t, 0, bookkeeping.Genesis{}) g.finishRound(0) g.generateAssetTxnInternal(assetCreate, 1, 0) @@ -175,7 +176,7 @@ func TestAssetTransfer(t *testing.T) { func TestAssetDestroy(t *testing.T) { partitiontest.PartitionTest(t) - g := makePrivateGenerator(t) + g := makePrivateGenerator(t, 0, bookkeeping.Genesis{}) g.finishRound(0) g.generateAssetTxnInternal(assetCreate, 1, 0) g.finishRound(1) @@ -189,18 +190,45 @@ func TestAssetDestroy(t *testing.T) { func TestWriteRoundZero(t *testing.T) { partitiontest.PartitionTest(t) - g := makePrivateGenerator(t) - var data []byte - writer := bytes.NewBuffer(data) - g.WriteBlock(writer, 0) - var block rpcs.EncodedBlockCert - protocol.Decode(data, &block) - require.Len(t, block.Block.Payset, 0) + var testcases = []struct { + name string + dbround uint64 + round uint64 + genesis bookkeeping.Genesis + }{ + { + name: "empty database", + dbround: 0, + round: 0, + genesis: bookkeeping.Genesis{}, + }, + { + name: "preloaded database", + dbround: 1, + round: 1, + genesis: bookkeeping.Genesis{Network: "TestWriteRoundZero"}, + }, + } + for _, tc := range testcases { + tc := tc + t.Run(fmt.Sprintf("%s", tc.name), func(t *testing.T) { + t.Parallel() + g := makePrivateGenerator(t, tc.dbround, tc.genesis) + var data []byte + writer := bytes.NewBuffer(data) + g.WriteBlock(writer, tc.round) + var block rpcs.EncodedBlockCert + protocol.Decode(data, &block) + require.Len(t, block.Block.Payset, 0) + g.ledger.Close() + }) + } + } func TestWriteRound(t *testing.T) { partitiontest.PartitionTest(t) - g := makePrivateGenerator(t) + g := makePrivateGenerator(t, 0, bookkeeping.Genesis{}) var data []byte writer := bytes.NewBuffer(data) g.WriteBlock(writer, 0) @@ -212,11 +240,95 @@ func TestWriteRound(t *testing.T) { require.Equal(t, basics.Round(1), g.ledger.Latest()) _, err := g.ledger.GetStateDeltaForRound(1) require.NoError(t, err) + // request a block that is several rounds ahead of the current round + err = g.WriteBlock(writer, 10) + require.NotNil(t, err) + require.Equal(t, err.Error(), "generator only supports sequential block access. Expected 2 but received request for 10") +} + +func TestWriteRoundWithPreloadedDB(t *testing.T) { + partitiontest.PartitionTest(t) + var testcases = []struct { + name string + dbround uint64 + round uint64 + genesis bookkeeping.Genesis + err error + }{ + { + name: "preloaded database starting at round 1", + dbround: 1, + round: 1, + genesis: bookkeeping.Genesis{Network: "generator-test1"}, + err: nil, + }, + { + name: "invalid request", + dbround: 10, + round: 1, + genesis: bookkeeping.Genesis{Network: "generator-test"}, + err: fmt.Errorf("cannot generate block for round 1, already in database"), + }, + { + name: "invalid request 2", + dbround: 1, + round: 10, + genesis: bookkeeping.Genesis{Network: "generator-test"}, + err: fmt.Errorf("generator only supports sequential block access. Expected 2 but received request for 10"), + }, + { + name: "preloaded database starting at 10", + dbround: 10, + round: 11, + genesis: bookkeeping.Genesis{Network: "generator-test2"}, + err: nil, + }, + { + name: "preloaded database request round 20", + dbround: 10, + round: 20, + genesis: bookkeeping.Genesis{Network: "generator-test3"}, + err: nil, + }, + } + for _, tc := range testcases { + tc := tc + t.Run(fmt.Sprintf("%s", tc.name), func(t *testing.T) { + t.Parallel() + g := makePrivateGenerator(t, tc.dbround, tc.genesis) + defer g.ledger.Close() + var data []byte + writer := bytes.NewBuffer(data) + err := g.WriteBlock(writer, tc.dbround) + require.Nil(t, err) + // invalid block request + if tc.round != tc.dbround && tc.err != nil { + err = g.WriteBlock(writer, tc.round) + require.NotNil(t, err) + require.Equal(t, err.Error(), tc.err.Error()) + return + } + // write the rest of the blocks + for i := tc.dbround + 1; i <= tc.round; i++ { + err = g.WriteBlock(writer, i) + require.Nil(t, err) + } + var block rpcs.EncodedBlockCert + protocol.Decode(data, &block) + require.Len(t, block.Block.Payset, int(g.config.TxnPerBlock)) + require.NotNil(t, g.ledger) + require.Equal(t, basics.Round(tc.round-tc.dbround), g.ledger.Latest()) + if tc.round > tc.dbround { + _, err = g.ledger.GetStateDeltaForRound(basics.Round(tc.round - tc.dbround)) + require.NoError(t, err) + } + }) + } } func TestHandlers(t *testing.T) { partitiontest.PartitionTest(t) - g := makePrivateGenerator(t) + g := makePrivateGenerator(t, 0, bookkeeping.Genesis{}) handler := getBlockHandler(g) var testcases = []struct { name string diff --git a/tools/block-generator/generator/server.go b/tools/block-generator/generator/server.go index e0c3314082..81a7546b14 100644 --- a/tools/block-generator/generator/server.go +++ b/tools/block-generator/generator/server.go @@ -24,6 +24,7 @@ import ( "strings" "time" + "github.com/algorand/go-algorand/data/bookkeeping" "github.com/algorand/go-algorand/tools/block-generator/util" "gopkg.in/yaml.v3" ) @@ -45,7 +46,7 @@ func MakeServer(configFile string, addr string) (*http.Server, Generator) { noOp := func(next http.Handler) http.Handler { return next } - return MakeServerWithMiddleware(configFile, addr, noOp) + return MakeServerWithMiddleware(0, "", configFile, addr, noOp) } // BlocksMiddleware is a middleware for the blocks endpoint. @@ -54,11 +55,15 @@ 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(configFile string, addr string, blocksMiddleware BlocksMiddleware) (*http.Server, Generator) { +func MakeServerWithMiddleware(dbround uint64, genesisFile string, configFile string, addr string, blocksMiddleware BlocksMiddleware) (*http.Server, Generator) { config, err := initializeConfigFile(configFile) util.MaybeFail(err, "problem loading config file. Use '--config' or create a config file.") - - gen, err := MakeGenerator(config) + var bkGenesis bookkeeping.Genesis + if genesisFile != "" { + bkGenesis, err = bookkeeping.LoadGenesisFromFile(genesisFile) + util.MaybeFail(err, "Failed to parse genesis file '%s'", genesisFile) + } + gen, err := MakeGenerator(dbround, bkGenesis, config) util.MaybeFail(err, "Failed to make generator with config file '%s'", configFile) mux := http.NewServeMux() diff --git a/tools/block-generator/run_runner.sh b/tools/block-generator/run_runner.sh index 7f1205cae5..3c73035f32 100755 --- a/tools/block-generator/run_runner.sh +++ b/tools/block-generator/run_runner.sh @@ -13,7 +13,7 @@ fi POSTGRES_CONTAINER=generator-test-container POSTGRES_PORT=15432 POSTGRES_DATABASE=generator_db -CONFIG=${2:-"$(dirname $0)/test_config.yml"} +CONFIG=${4:-"$(dirname $0)/test_config.yml"} echo "Using config file: $CONFIG" function start_postgres() { @@ -54,4 +54,6 @@ $(dirname "$0")/block-generator runner \ --test-duration 30s \ --log-level trace \ --postgres-connection-string "host=localhost user=algorand password=algorand dbname=generator_db port=15432 sslmode=disable" \ - --scenario ${CONFIG} + --scenario ${CONFIG} \ + --db-round ${2:-0} \ + --genesis-file ${3:-""} \ diff --git a/tools/block-generator/runner/run.go b/tools/block-generator/runner/run.go index 2113b36a45..5e237bd660 100644 --- a/tools/block-generator/runner/run.go +++ b/tools/block-generator/runner/run.go @@ -19,7 +19,6 @@ package runner import ( "bytes" "context" - // embed conduit template config file _ "embed" "encoding/json" @@ -56,6 +55,8 @@ type Args struct { ResetReportDir bool RunValidation bool KeepDataDir bool + NextDBRound uint64 + GenesisFile string } type config struct { @@ -124,7 +125,7 @@ func (r *Args) run() error { // Start services algodNet := fmt.Sprintf("localhost:%d", 11112) metricsNet := fmt.Sprintf("localhost:%d", r.MetricsPort) - generatorShutdownFunc, _ := startGenerator(r.Path, algodNet, blockMiddleware) + generatorShutdownFunc, _ := startGenerator(r.Path, r.NextDBRound, r.GenesisFile, algodNet, blockMiddleware) defer func() { // Shutdown generator. if err := generatorShutdownFunc(); err != nil { @@ -156,7 +157,7 @@ func (r *Args) run() error { } // Start conduit - conduitShutdownFunc, err := startConduit(dataDir, r.ConduitBinary) + conduitShutdownFunc, err := startConduit(dataDir, r.ConduitBinary, r.NextDBRound) if err != nil { return fmt.Errorf("failed to start Conduit: %w", err) } @@ -390,9 +391,9 @@ func (r *Args) runTest(report *os.File, metricsURL string, generatorURL string) } // startGenerator starts the generator server. -func startGenerator(configFile string, addr string, blockMiddleware func(http.Handler) http.Handler) (func() error, generator.Generator) { +func startGenerator(configFile string, dbround uint64, genesisFile string, addr string, blockMiddleware func(http.Handler) http.Handler) (func() error, generator.Generator) { // Start generator. - server, generator := generator.MakeServerWithMiddleware(configFile, addr, blockMiddleware) + server, generator := generator.MakeServerWithMiddleware(dbround, genesisFile, configFile, addr, blockMiddleware) // Start the server go func() { @@ -415,9 +416,10 @@ func startGenerator(configFile string, addr string, blockMiddleware func(http.Ha } // startConduit starts the conduit binary. -func startConduit(dataDir string, conduitBinary string) (func() error, error) { +func startConduit(dataDir string, conduitBinary string, round uint64) (func() error, error) { cmd := exec.Command( conduitBinary, + "-r", strconv.FormatUint(round, 10), "-d", dataDir, ) diff --git a/tools/block-generator/runner/runner.go b/tools/block-generator/runner/runner.go index 89e4ac88c4..580179a2e3 100644 --- a/tools/block-generator/runner/runner.go +++ b/tools/block-generator/runner/runner.go @@ -53,6 +53,8 @@ func init() { RunnerCmd.Flags().BoolVarP(&runnerArgs.ResetReportDir, "reset", "", false, "If set any existing report directory will be deleted before running tests.") RunnerCmd.Flags().BoolVarP(&runnerArgs.RunValidation, "validate", "", false, "If set the validator will run after test-duration has elapsed to verify data is correct. An extra line in each report indicates validator success or failure.") 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().Uint64VarP(&runnerArgs.NextDBRound, "next-db-round", "n", 0, "next round on db. It is the next_account_round from Indexer's metastate table.") + RunnerCmd.Flags().StringVarP(&runnerArgs.GenesisFile, "genesis-file", "f", "", "file path to the genesis associated with the db snapshot") RunnerCmd.MarkFlagRequired("scenario") RunnerCmd.MarkFlagRequired("conduit-binary") From 1abbd50ae3c6e447690a1c8c3ce22f1b95341383 Mon Sep 17 00:00:00 2001 From: shiqizng <80276844+shiqizng@users.noreply.github.com> Date: Mon, 22 May 2023 16:36:45 -0400 Subject: [PATCH 108/125] bug fix: block generator test (#5410) --- tools/block-generator/generator/generate_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/block-generator/generator/generate_test.go b/tools/block-generator/generator/generate_test.go index 78a07b98de..dddcf17588 100644 --- a/tools/block-generator/generator/generate_test.go +++ b/tools/block-generator/generator/generate_test.go @@ -266,28 +266,28 @@ func TestWriteRoundWithPreloadedDB(t *testing.T) { name: "invalid request", dbround: 10, round: 1, - genesis: bookkeeping.Genesis{Network: "generator-test"}, + genesis: bookkeeping.Genesis{Network: "generator-test2"}, err: fmt.Errorf("cannot generate block for round 1, already in database"), }, { name: "invalid request 2", dbround: 1, round: 10, - genesis: bookkeeping.Genesis{Network: "generator-test"}, + genesis: bookkeeping.Genesis{Network: "generator-test3"}, err: fmt.Errorf("generator only supports sequential block access. Expected 2 but received request for 10"), }, { name: "preloaded database starting at 10", dbround: 10, round: 11, - genesis: bookkeeping.Genesis{Network: "generator-test2"}, + genesis: bookkeeping.Genesis{Network: "generator-test4"}, err: nil, }, { name: "preloaded database request round 20", dbround: 10, round: 20, - genesis: bookkeeping.Genesis{Network: "generator-test3"}, + genesis: bookkeeping.Genesis{Network: "generator-test5"}, err: nil, }, } From ce3802ecfe2faebe19bc512c90c51ee31e9763ab Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Wed, 24 May 2023 11:22:59 -0500 Subject: [PATCH 109/125] test: remove follower node flaky TestSyncRoundWithRemake (#5415) --- node/follower_node_test.go | 79 -------------------------------------- 1 file changed, 79 deletions(-) diff --git a/node/follower_node_test.go b/node/follower_node_test.go index 489846083f..17dbd52ca5 100644 --- a/node/follower_node_test.go +++ b/node/follower_node_test.go @@ -19,7 +19,6 @@ package node import ( "context" "testing" - "time" "github.com/sirupsen/logrus" "github.com/sirupsen/logrus/hooks/test" @@ -152,84 +151,6 @@ func TestDevModeWarning(t *testing.T) { require.Contains(t, foundEntry.Message, "Follower running on a devMode network. Must submit txns to a different node.") } -// TestSyncRoundWithRemake extends TestSyncRound to simulate starting and stopping the network -func TestSyncRoundWithRemake(t *testing.T) { - partitiontest.PartitionTest(t) - t.Parallel() - - maxAcctLookback := uint64(10) - - followNode, tempDir := remakeableFollowNode(t, "", maxAcctLookback) - addBlock := func(round basics.Round) { - b := bookkeeping.Block{ - BlockHeader: bookkeeping.BlockHeader{ - GenesisHash: followNode.ledger.GenesisHash(), - Round: round, - RewardsState: bookkeeping.RewardsState{ - RewardsRate: 0, - RewardsPool: poolAddr, - FeeSink: sinkAddr, - }, - }, - } - b.CurrentProtocol = protocol.ConsensusCurrentVersion - err := followNode.Ledger().AddBlock(b, agreement.Certificate{}) - require.NoError(t, err) - - status, err := followNode.Status() - require.NoError(t, err) - require.Equal(t, round, status.LastRound) - } - - // Part I. redo TestSyncRound - // main differences are: - // * cfg.DisableNetworking = true - // * cfg.MaxAcctLookback = 10 (instead of 4) - - addBlock(basics.Round(1)) - - dbRound := uint64(followNode.Ledger().LatestTrackerCommitted()) - // Sync Round should be initialized to the ledger's dbRound + 1 - require.Equal(t, dbRound+1, followNode.GetSyncRound()) - // Set a new sync round - require.NoError(t, followNode.SetSyncRound(dbRound+11)) - // Ensure it is persisted - require.Equal(t, dbRound+11, followNode.GetSyncRound()) - // Unset the sync round and make sure get returns 0 - followNode.UnsetSyncRound() - require.Equal(t, uint64(0), followNode.GetSyncRound()) - - // Part II. fast forward and then remake the node - - newRound := basics.Round(2 * maxAcctLookback) - for i := basics.Round(2); i <= newRound; i++ { - addBlock(i) - } - - followNode, _ = remakeableFollowNode(t, tempDir, maxAcctLookback) - - // Wait for follower to catch up. This rarely is needed, but can happen - // and cause flakey test failures. Timing out can still occur, but is less - // likely than the being caught behind a few rounds. - var status *StatusReport - require.Eventually(t, func() bool { - st, err := followNode.Status() - require.NoError(t, err) - if st.LastRound >= newRound { - status = &st - return true - } - return false - }, 20*time.Second, 500*time.Millisecond, "failed to reach newRound within the allowed time") - - require.Equal(t, newRound, status.LastRound) - - // syncRound should be at - // newRound - maxAcctLookback + 1 = maxAcctLookback + 1 - syncRound := followNode.GetSyncRound() - require.Equal(t, uint64(maxAcctLookback+1), syncRound) -} - func TestFastCatchupResume(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() From 9d2bfef24ffa960522ff1d7961813359e9234d21 Mon Sep 17 00:00:00 2001 From: Will Winder Date: Wed, 24 May 2023 12:48:11 -0400 Subject: [PATCH 110/125] docker: Fix ADMIN_TOKEN and add missing variables to echo statements. (#5357) --- docker/files/run/run.sh | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/docker/files/run/run.sh b/docker/files/run/run.sh index 0244f0ff23..f6b12798f1 100755 --- a/docker/files/run/run.sh +++ b/docker/files/run/run.sh @@ -78,14 +78,14 @@ function configure_data_dir() { algocfg -d . set -p EndpointAddress -v "0.0.0.0:${ALGOD_PORT}" # check for token overrides - if [ "$TOKEN" != "" ]; then - for dir in ${ALGORAND_DATA}/../*/; do - echo "$TOKEN" > "$dir/algod.token" - done - fi - if [ "$ADMIN_TOKEN" != "" ]; then - echo "$ADMIN_TOKEN" >algod.admin.token - fi + for dir in ${ALGORAND_DATA}/../*/; do + if [ "$TOKEN" != "" ]; then + echo "$TOKEN" > "$dir/algod.token" + fi + if [ "$ADMIN_TOKEN" != "" ]; then + echo "$ADMIN_TOKEN" > "$dir/algod.admin.token" + fi + done # configure telemetry if [ "$TELEMETRY_NAME" != "" ]; then @@ -184,13 +184,17 @@ function start_new_private_network() { echo "Starting Algod Docker Container" echo " ALGORAND_DATA: $ALGORAND_DATA" echo " NETWORK: $NETWORK" -echo " ALGOD_PORT: $ALGOD_PORT" -echo " FAST_CATCHUP: $FAST_CATCHUP" +echo " PROFILE: $PROFILE" echo " DEV_MODE: $DEV_MODE" +echo " START_KMD: ${START_KMD:-"Not Set"}" +echo " FAST_CATCHUP: $FAST_CATCHUP" echo " TOKEN: ${TOKEN:-"Not Set"}" +echo " ADMIN_TOKEN: ${ADMIN_TOKEN:-"Not Set"}" echo " KMD_TOKEN: ${KMD_TOKEN:-"Not Set"}" echo " TELEMETRY_NAME: $TELEMETRY_NAME" -echo " START_KMD: ${START_KMD:-"Not Set"}" +echo " NUM_ROUNDS: $NUM_ROUNDS" +echo " PEER_ADDRESS: $PEER_ADDRESS" +echo " ALGOD_PORT: $ALGOD_PORT" # If data directory is initialized, start existing environment. if [ -f "$ALGORAND_DATA/../network.json" ]; then From 3fb696411c131d9e334f84639a95865af64601cb Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Wed, 24 May 2023 14:58:24 -0400 Subject: [PATCH 111/125] ledger: report catchpoint writing only when it actually started (#5413) --- ledger/acctonline.go | 12 ++--------- ledger/acctupdates.go | 19 +----------------- ledger/bulletin.go | 2 +- ledger/catchpointtracker.go | 18 +++++++++-------- ledger/catchpointtracker_test.go | 18 ++++++++--------- ledger/ledger.go | 2 +- ledger/metrics.go | 2 +- ledger/notifier.go | 2 +- ledger/spverificationtracker.go | 2 +- ledger/tracker.go | 34 +++++++++++++++++++------------- ledger/tracker_test.go | 4 ++-- ledger/txtail.go | 2 +- 12 files changed, 50 insertions(+), 67 deletions(-) diff --git a/ledger/acctonline.go b/ledger/acctonline.go index 1a3544ba12..a9834d61c5 100644 --- a/ledger/acctonline.go +++ b/ledger/acctonline.go @@ -23,7 +23,6 @@ import ( "fmt" "sort" "sync" - "sync/atomic" "time" "github.com/algorand/go-deadlock" @@ -348,7 +347,7 @@ func (ao *onlineAccounts) consecutiveVersion(offset uint64) uint64 { return offset } -func (ao *onlineAccounts) handleUnorderedCommit(dcc *deferredCommitContext) { +func (ao *onlineAccounts) handleUnorderedCommitOrError(dcc *deferredCommitContext) { } func (ao *onlineAccounts) maxBalLookback() uint64 { @@ -366,7 +365,7 @@ func (ao *onlineAccounts) prepareCommit(dcc *deferredCommitContext) error { return ao.voters.prepareCommit(dcc) } -// prepareCommitInternal preforms preapreCommit's logic without locking the tracker's mutex. +// prepareCommitInternal preforms prepareCommit's logic without locking the tracker's mutex. func (ao *onlineAccounts) prepareCommitInternal(dcc *deferredCommitContext) error { offset := dcc.offset @@ -381,13 +380,6 @@ func (ao *onlineAccounts) prepareCommitInternal(dcc *deferredCommitContext) erro // Index that corresponds to the oldest round still in deltas startIndex := len(ao.onlineRoundParamsData) - len(ao.deltas) - 1 if ao.onlineRoundParamsData[startIndex+1].CurrentProtocol != ao.onlineRoundParamsData[startIndex+int(offset)].CurrentProtocol { - // in scheduleCommit, we expect that this function to update the catchpointWriting when - // it's on a catchpoint round and the node is configured to generate catchpoints. Doing this in a deferred function - // here would prevent us from "forgetting" to update this variable later on. - // The same is repeated in commitRound on errors. - if dcc.catchpointFirstStage && dcc.enableGeneratingCatchpointFiles { - atomic.StoreInt32(dcc.catchpointDataWriting, 0) - } return fmt.Errorf("attempted to commit series of rounds with non-uniform consensus versions") } diff --git a/ledger/acctupdates.go b/ledger/acctupdates.go index 369f7db147..5640800cee 100644 --- a/ledger/acctupdates.go +++ b/ledger/acctupdates.go @@ -24,7 +24,6 @@ import ( "sort" "strings" "sync" - "sync/atomic" "time" "github.com/algorand/go-deadlock" @@ -1602,7 +1601,7 @@ func (au *accountUpdates) roundOffset(rnd basics.Round) (offset uint64, err erro return off, nil } -func (au *accountUpdates) handleUnorderedCommit(dcc *deferredCommitContext) { +func (au *accountUpdates) handleUnorderedCommitOrError(dcc *deferredCommitContext) { } // prepareCommit prepares data to write to the database a "chunk" of rounds, and update the cached dbRound accordingly. @@ -1625,14 +1624,6 @@ func (au *accountUpdates) prepareCommit(dcc *deferredCommitContext) error { // verify version correctness : all the entries in the au.versions[1:offset+1] should have the *same* version, and the committedUpTo should be enforcing that. if au.versions[1] != au.versions[offset] { au.accountsMu.RUnlock() - - // in scheduleCommit, we expect that this function to update the catchpointWriting when - // it's on a catchpoint round and the node is configured to generate catchpoints. Doing this in a deferred function - // here would prevent us from "forgetting" to update this variable later on. - // The same is repeated in commitRound on errors. - if dcc.catchpointFirstStage && dcc.enableGeneratingCatchpointFiles { - atomic.StoreInt32(dcc.catchpointDataWriting, 0) - } return fmt.Errorf("attempted to commit series of rounds with non-uniform consensus versions") } @@ -1664,14 +1655,6 @@ func (au *accountUpdates) commitRound(ctx context.Context, tx trackerdb.Transact offset := dcc.offset dbRound := dcc.oldBase - defer func() { - if err != nil { - if dcc.catchpointFirstStage && dcc.enableGeneratingCatchpointFiles { - atomic.StoreInt32(dcc.catchpointDataWriting, 0) - } - } - }() - _, err = tx.ResetTransactionWarnDeadline(ctx, time.Now().Add(accountsUpdatePerRoundHighWatermark*time.Duration(offset))) if err != nil { return err diff --git a/ledger/bulletin.go b/ledger/bulletin.go index 49b1951b76..b05848193c 100644 --- a/ledger/bulletin.go +++ b/ledger/bulletin.go @@ -123,7 +123,7 @@ func (b *bulletin) postCommit(ctx context.Context, dcc *deferredCommitContext) { func (b *bulletin) postCommitUnlocked(ctx context.Context, dcc *deferredCommitContext) { } -func (b *bulletin) handleUnorderedCommit(*deferredCommitContext) { +func (b *bulletin) handleUnorderedCommitOrError(*deferredCommitContext) { } func (b *bulletin) produceCommittingTask(committedRound basics.Round, dbRound basics.Round, dcr *deferredCommitRange) *deferredCommitRange { diff --git a/ledger/catchpointtracker.go b/ledger/catchpointtracker.go index 495f4adb7d..355559ef8b 100644 --- a/ledger/catchpointtracker.go +++ b/ledger/catchpointtracker.go @@ -449,7 +449,7 @@ func (ct *catchpointTracker) produceCommittingTask(committedRound basics.Round, ct.catchpointInterval, dcr.catchpointLookback) // if we're still writing the previous balances, we can't move forward yet. - if ct.IsWritingCatchpointDataFile() { + if ct.isWritingCatchpointDataFile() { // if we hit this path, it means that we're still writing a catchpoint. // see if the new delta range contains another catchpoint. if hasIntermediateFirstStageRound { @@ -469,8 +469,6 @@ func (ct *catchpointTracker) produceCommittingTask(committedRound basics.Round, dcr.catchpointFirstStage = true if ct.enableGeneratingCatchpointFiles { - // store non-zero ( all ones ) into the catchpointWriting atomic variable to indicate that a catchpoint is being written ( or, queued to be written ) - atomic.StoreInt32(&ct.catchpointDataWriting, int32(-1)) ct.catchpointDataSlowWriting = make(chan struct{}, 1) if hasMultipleIntermediateFirstStageRounds { close(ct.catchpointDataSlowWriting) @@ -478,7 +476,6 @@ func (ct *catchpointTracker) produceCommittingTask(committedRound basics.Round, } } - dcr.catchpointDataWriting = &ct.catchpointDataWriting dcr.enableGeneratingCatchpointFiles = ct.enableGeneratingCatchpointFiles rounds := ct.calculateCatchpointRounds(dcr) @@ -493,6 +490,11 @@ func (ct *catchpointTracker) prepareCommit(dcc *deferredCommitContext) error { ct.catchpointsMu.RLock() defer ct.catchpointsMu.RUnlock() + if ct.enableGeneratingCatchpointFiles && dcc.catchpointFirstStage { + // store non-zero ( all ones ) into the catchpointWriting atomic variable to indicate that a catchpoint is being written + atomic.StoreInt32(&ct.catchpointDataWriting, int32(-1)) + } + dcc.committedRoundDigests = make([]crypto.Digest, dcc.offset) copy(dcc.committedRoundDigests, ct.roundDigest[:dcc.offset]) @@ -926,10 +928,10 @@ func (ct *catchpointTracker) postCommitUnlocked(ctx context.Context, dcc *deferr } } -// handleUnorderedCommit is a special method for handling deferred commits that are out of order. +// handleUnorderedCommitOrError is a special method for handling deferred commits that are out of order. // Tracker might update own state in this case. For example, account catchpoint tracker cancels // scheduled catchpoint writing that deferred commit. -func (ct *catchpointTracker) handleUnorderedCommit(dcc *deferredCommitContext) { +func (ct *catchpointTracker) handleUnorderedCommitOrError(dcc *deferredCommitContext) { // if the node is configured to generate catchpoint files, we might need to update the catchpointWriting variable. if ct.enableGeneratingCatchpointFiles { // determine if this was a catchpoint round @@ -1085,9 +1087,9 @@ func (ct *catchpointTracker) accountsUpdateBalances(accountsDeltas compactAccoun return } -// IsWritingCatchpointDataFile returns true iff a (first stage) catchpoint data file +// isWritingCatchpointDataFile returns true iff a (first stage) catchpoint data file // is being generated. -func (ct *catchpointTracker) IsWritingCatchpointDataFile() bool { +func (ct *catchpointTracker) isWritingCatchpointDataFile() bool { return atomic.LoadInt32(&ct.catchpointDataWriting) != 0 } diff --git a/ledger/catchpointtracker_test.go b/ledger/catchpointtracker_test.go index dcf6c68c64..84a103300f 100644 --- a/ledger/catchpointtracker_test.go +++ b/ledger/catchpointtracker_test.go @@ -54,11 +54,11 @@ func TestCatchpointIsWritingCatchpointFile(t *testing.T) { ct := &catchpointTracker{} ct.catchpointDataWriting = -1 - ans := ct.IsWritingCatchpointDataFile() + ans := ct.isWritingCatchpointDataFile() require.True(t, ans) ct.catchpointDataWriting = 0 - ans = ct.IsWritingCatchpointDataFile() + ans = ct.isWritingCatchpointDataFile() require.False(t, ans) } @@ -569,7 +569,7 @@ func TestCatchpointReproducibleLabels(t *testing.T) { ml.trackers.waitAccountsWriting() // Let catchpoint data generation finish so that nothing gets skipped. - for ct.IsWritingCatchpointDataFile() { + for ct.isWritingCatchpointDataFile() { time.Sleep(time.Millisecond) } } @@ -612,7 +612,7 @@ func TestCatchpointReproducibleLabels(t *testing.T) { ml2.trackers.committedUpTo(i) ml2.trackers.waitAccountsWriting() // Let catchpoint data generation finish so that nothing gets skipped. - for ct.IsWritingCatchpointDataFile() { + for ct.isWritingCatchpointDataFile() { time.Sleep(time.Millisecond) } } @@ -694,8 +694,8 @@ func (bt *blockingTracker) postCommitUnlocked(ctx context.Context, dcc *deferred } } -// handleUnorderedCommit is not used by the blockingTracker -func (bt *blockingTracker) handleUnorderedCommit(*deferredCommitContext) { +// handleUnorderedCommitOrError is not used by the blockingTracker +func (bt *blockingTracker) handleUnorderedCommitOrError(*deferredCommitContext) { } // close is not used by the blockingTracker @@ -1089,7 +1089,7 @@ func TestCatchpointFirstStageInfoPruning(t *testing.T) { ml.trackers.committedUpTo(i) ml.trackers.waitAccountsWriting() // Let catchpoint data generation finish so that nothing gets skipped. - for ct.IsWritingCatchpointDataFile() { + for ct.isWritingCatchpointDataFile() { time.Sleep(time.Millisecond) } } @@ -1304,7 +1304,7 @@ func TestCatchpointSecondStagePersistence(t *testing.T) { ml.trackers.committedUpTo(i) ml.trackers.waitAccountsWriting() // Let catchpoint data generation finish so that nothing gets skipped. - for ct.IsWritingCatchpointDataFile() { + for ct.isWritingCatchpointDataFile() { time.Sleep(time.Millisecond) } } @@ -1509,7 +1509,7 @@ func TestCatchpointSecondStageDeletesUnfinishedCatchpointRecordAfterRestart(t *t ml.addToBlockQueue(blockEntry{block: blk}, delta) // Let catchpoint data generation finish so that nothing gets skipped. - for ct.IsWritingCatchpointDataFile() { + for ct.isWritingCatchpointDataFile() { time.Sleep(time.Millisecond) } } diff --git a/ledger/ledger.go b/ledger/ledger.go index 65c7964e47..0c77470873 100644 --- a/ledger/ledger.go +++ b/ledger/ledger.go @@ -832,7 +832,7 @@ func (l *Ledger) trackerEvalVerified(blk bookkeeping.Block, accUpdatesLedger eva func (l *Ledger) IsWritingCatchpointDataFile() bool { l.trackerMu.RLock() defer l.trackerMu.RUnlock() - return l.catchpoint.IsWritingCatchpointDataFile() + return l.catchpoint.isWritingCatchpointDataFile() } // VerifiedTransactionCache returns the verify.VerifiedTransactionCache diff --git a/ledger/metrics.go b/ledger/metrics.go index 16030c95f0..49375ed83e 100644 --- a/ledger/metrics.go +++ b/ledger/metrics.go @@ -80,7 +80,7 @@ func (mt *metricsTracker) postCommit(ctx context.Context, dcc *deferredCommitCon func (mt *metricsTracker) postCommitUnlocked(ctx context.Context, dcc *deferredCommitContext) { } -func (mt *metricsTracker) handleUnorderedCommit(*deferredCommitContext) { +func (mt *metricsTracker) handleUnorderedCommitOrError(*deferredCommitContext) { } func (mt *metricsTracker) produceCommittingTask(committedRound basics.Round, dbRound basics.Round, dcr *deferredCommitRange) *deferredCommitRange { diff --git a/ledger/notifier.go b/ledger/notifier.go index 9a2a08f959..d89badce7b 100644 --- a/ledger/notifier.go +++ b/ledger/notifier.go @@ -123,7 +123,7 @@ func (bn *blockNotifier) postCommit(ctx context.Context, dcc *deferredCommitCont func (bn *blockNotifier) postCommitUnlocked(ctx context.Context, dcc *deferredCommitContext) { } -func (bn *blockNotifier) handleUnorderedCommit(*deferredCommitContext) { +func (bn *blockNotifier) handleUnorderedCommitOrError(*deferredCommitContext) { } func (bn *blockNotifier) produceCommittingTask(committedRound basics.Round, dbRound basics.Round, dcr *deferredCommitRange) *deferredCommitRange { diff --git a/ledger/spverificationtracker.go b/ledger/spverificationtracker.go index 0d4597633e..d98974897d 100644 --- a/ledger/spverificationtracker.go +++ b/ledger/spverificationtracker.go @@ -160,7 +160,7 @@ func (spt *spVerificationTracker) postCommit(_ context.Context, dcc *deferredCom func (spt *spVerificationTracker) postCommitUnlocked(context.Context, *deferredCommitContext) { } -func (spt *spVerificationTracker) handleUnorderedCommit(*deferredCommitContext) { +func (spt *spVerificationTracker) handleUnorderedCommitOrError(*deferredCommitContext) { } func (spt *spVerificationTracker) close() { diff --git a/ledger/tracker.go b/ledger/tracker.go index 6eab2e1d02..39ea9d4b18 100644 --- a/ledger/tracker.go +++ b/ledger/tracker.go @@ -118,10 +118,11 @@ type ledgerTracker interface { // An optional context is provided for long-running operations. postCommitUnlocked(context.Context, *deferredCommitContext) - // handleUnorderedCommit is a special method for handling deferred commits that are out of order. + // handleUnorderedCommitOrError is a special method for handling deferred commits that are out of order + // or to handle errors reported by other trackers while committing a batch. // Tracker might update own state in this case. For example, account updates tracker cancels - // scheduled catchpoint writing that deferred commit. - handleUnorderedCommit(*deferredCommitContext) + // scheduled catchpoint writing flag for this batch. + handleUnorderedCommitOrError(*deferredCommitContext) // close terminates the tracker, reclaiming any resources // like open database connections or goroutines. close may @@ -214,12 +215,6 @@ type deferredCommitRange struct { // a catchpoint data file, in this commit cycle iteration. catchpointFirstStage bool - // catchpointDataWriting is a pointer to a variable with the same name in the - // catchpointTracker. It's used in order to reset the catchpointDataWriting flag from - // the acctupdates's prepareCommit/commitRound (which is called before the - // corresponding catchpoint tracker method. - catchpointDataWriting *int32 - // enableGeneratingCatchpointFiles controls whether the node produces catchpoint files or not. enableGeneratingCatchpointFiles bool @@ -514,7 +509,7 @@ func (tr *trackerRegistry) commitRound(dcc *deferredCommitContext) error { if tr.dbRound < dbRound || offset < uint64(tr.dbRound-dbRound) { tr.log.Warnf("out of order deferred commit: offset %d, dbRound %d but current tracker DB round is %d", offset, dbRound, tr.dbRound) for _, lt := range tr.trackers { - lt.handleUnorderedCommit(dcc) + lt.handleUnorderedCommitOrError(dcc) } tr.mu.RUnlock() return nil @@ -538,19 +533,27 @@ func (tr *trackerRegistry) commitRound(dcc *deferredCommitContext) error { dcc.oldBase = dbRound dcc.flushTime = time.Now() + var err error for _, lt := range tr.trackers { - err := lt.prepareCommit(dcc) + err = lt.prepareCommit(dcc) if err != nil { tr.log.Errorf(err.Error()) - tr.mu.RUnlock() - return err + break } } + if err != nil { + for _, lt := range tr.trackers { + lt.handleUnorderedCommitOrError(dcc) + } + tr.mu.RUnlock() + return err + } + tr.mu.RUnlock() start := time.Now() ledgerCommitroundCount.Inc(nil) - err := tr.dbs.Transaction(func(ctx context.Context, tx trackerdb.TransactionScope) (err error) { + err = tr.dbs.Transaction(func(ctx context.Context, tx trackerdb.TransactionScope) (err error) { arw, err := tx.MakeAccountsReaderWriter() if err != nil { return err @@ -568,6 +571,9 @@ func (tr *trackerRegistry) commitRound(dcc *deferredCommitContext) error { ledgerCommitroundMicros.AddMicrosecondsSince(start, nil) if err != nil { + for _, lt := range tr.trackers { + lt.handleUnorderedCommitOrError(dcc) + } tr.log.Warnf("unable to advance tracker db snapshot (%d-%d): %v", dbRound, dbRound+basics.Round(offset), err) return err } diff --git a/ledger/tracker_test.go b/ledger/tracker_test.go index 1671e03024..09c7401b87 100644 --- a/ledger/tracker_test.go +++ b/ledger/tracker_test.go @@ -191,8 +191,8 @@ func (bt *producePrepareBlockingTracker) postCommit(ctx context.Context, dcc *de func (bt *producePrepareBlockingTracker) postCommitUnlocked(ctx context.Context, dcc *deferredCommitContext) { } -// handleUnorderedCommit is not used by the blockingTracker -func (bt *producePrepareBlockingTracker) handleUnorderedCommit(*deferredCommitContext) { +// handleUnorderedCommitOrError is not used by the blockingTracker +func (bt *producePrepareBlockingTracker) handleUnorderedCommitOrError(*deferredCommitContext) { } // close is not used by the blockingTracker diff --git a/ledger/txtail.go b/ledger/txtail.go index a4095c6449..a86a8af5bc 100644 --- a/ledger/txtail.go +++ b/ledger/txtail.go @@ -313,7 +313,7 @@ func (t *txTail) postCommit(ctx context.Context, dcc *deferredCommitContext) { func (t *txTail) postCommitUnlocked(ctx context.Context, dcc *deferredCommitContext) { } -func (t *txTail) handleUnorderedCommit(*deferredCommitContext) { +func (t *txTail) handleUnorderedCommitOrError(*deferredCommitContext) { } func (t *txTail) produceCommittingTask(committedRound basics.Round, dbRound basics.Round, dcr *deferredCommitRange) *deferredCommitRange { From 058a9d58a4eb1575977a990a1f8eeba57c8c80b2 Mon Sep 17 00:00:00 2001 From: Hang Su <87964331+ahangsu@users.noreply.github.com> Date: Wed, 24 May 2023 15:10:29 -0400 Subject: [PATCH 112/125] Goal: rename `--allow-extra-opcode-budget` to `--allow-more-opcode-budget` (#5407) extra more double plus good --- cmd/goal/clerk.go | 15 +++++++-------- test/scripts/e2e_subs/e2e-app-simulate.sh | 4 ++-- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/cmd/goal/clerk.go b/cmd/goal/clerk.go index 839cb1b6e0..e27d5e031f 100644 --- a/cmd/goal/clerk.go +++ b/cmd/goal/clerk.go @@ -69,10 +69,10 @@ var ( requestFilename string requestOutFilename string - simulateAllowEmptySignatures bool - simulateAllowMoreLogging bool - simulateAllowExtraOpcodeBudget bool - simulateExtraOpcodeBudget uint64 + simulateAllowEmptySignatures bool + simulateAllowMoreLogging bool + simulateAllowMoreOpcodeBudget bool + simulateExtraOpcodeBudget uint64 ) func init() { @@ -159,7 +159,7 @@ func init() { simulateCmd.Flags().StringVarP(&outFilename, "result-out", "o", "", "Filename for writing simulation result") simulateCmd.Flags().BoolVar(&simulateAllowEmptySignatures, "allow-empty-signatures", false, "Allow transactions without signatures to be simulated as if they had correct signatures") simulateCmd.Flags().BoolVar(&simulateAllowMoreLogging, "allow-more-logging", false, "Lift the limits on log opcode during simulation") - simulateCmd.Flags().BoolVar(&simulateAllowExtraOpcodeBudget, "allow-extra-opcode-budget", false, "Apply max extra opcode budget for apps per transaction group (default 320000) 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") } @@ -1246,12 +1246,11 @@ var simulateCmd = &cobra.Command{ reportErrorf("exactly one of --txfile or --request must be provided") } - allowExtraBudgetProvided := cmd.Flags().Changed("allow-extra-opcode-budget") extraBudgetProvided := cmd.Flags().Changed("extra-opcode-budget") - if allowExtraBudgetProvided && extraBudgetProvided { + if simulateAllowMoreOpcodeBudget && extraBudgetProvided { reportErrorf("--allow-extra-opcode-budget and --extra-opcode-budget are mutually exclusive") } - if allowExtraBudgetProvided { + if simulateAllowMoreOpcodeBudget { simulateExtraOpcodeBudget = simulation.MaxExtraOpcodeBudget } diff --git a/test/scripts/e2e_subs/e2e-app-simulate.sh b/test/scripts/e2e_subs/e2e-app-simulate.sh index c17960eccf..7efc7ae3c6 100755 --- a/test/scripts/e2e_subs/e2e-app-simulate.sh +++ b/test/scripts/e2e_subs/e2e-app-simulate.sh @@ -362,8 +362,8 @@ if [[ $(echo "$RES" | jq '."txn-groups"[0]."app-budget-consumed"') -ne 804 ]]; t false fi -# SIMULATION! with --allow-extra-budget should pass direct call -RES=$(${gcmd} clerk simulate --allow-extra-opcode-budget -t "${TEMPDIR}/no-extra-opcode-budget.stx") +# SIMULATION! with --allow-more-opcode-budget should pass direct call +RES=$(${gcmd} clerk simulate --allow-more-opcode-budget -t "${TEMPDIR}/no-extra-opcode-budget.stx") if [[ $(echo "$RES" | jq '."txn-groups" | any(has("failure-message"))') != $CONST_FALSE ]]; then date '+app-simulate-test FAIL the app call to generated large TEAL with extra budget should pass %Y%m%d_%H%M%S' From 44f4da856473723d8ff7491894342e93f3492494 Mon Sep 17 00:00:00 2001 From: algochoi <86622919+algochoi@users.noreply.github.com> Date: Wed, 24 May 2023 16:04:29 -0400 Subject: [PATCH 113/125] lint: Fix lint errors in `config/` (#5416) --- config/consensus.go | 2 +- config/migrate.go | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/config/consensus.go b/config/consensus.go index d4e1890a8a..4f4680c5e6 100644 --- a/config/consensus.go +++ b/config/consensus.go @@ -679,7 +679,7 @@ func (cp ConsensusProtocols) Merge(configurableConsensus ConsensusProtocols) Con for cVer, cParam := range staticConsensus { if cVer == consensusVersion { delete(staticConsensus, cVer) - } else if _, has := cParam.ApprovedUpgrades[consensusVersion]; has { + } else { // delete upgrade to deleted version delete(cParam.ApprovedUpgrades, consensusVersion) } diff --git a/config/migrate.go b/config/migrate.go index 35b4ec82fb..11c8aa108a 100644 --- a/config/migrate.go +++ b/config/migrate.go @@ -128,9 +128,6 @@ func getLatestConfigVersion() uint32 { } func getVersionedDefaultLocalConfig(version uint32) (local Local) { - if version < 0 { - return - } if version > 0 { local = getVersionedDefaultLocalConfig(version - 1) } From 1d9638a8385f73a75a25cb9c230c13cec515fc8b Mon Sep 17 00:00:00 2001 From: algochoi <86622919+algochoi@users.noreply.github.com> Date: Thu, 25 May 2023 08:15:51 -0400 Subject: [PATCH 114/125] algod: Capitalize API fields for state deltas (#5414) --- daemon/algod/api/algod.oas2.json | 12 +- daemon/algod/api/algod.oas3.yml | 20 +- .../api/server/v2/generated/data/routes.go | 232 +++++------ .../v2/generated/experimental/routes.go | 222 +++++----- .../api/server/v2/generated/model/types.go | 6 +- .../nonparticipating/private/routes.go | 234 +++++------ .../nonparticipating/public/routes.go | 378 +++++++++--------- .../generated/participating/private/routes.go | 238 +++++------ .../generated/participating/public/routes.go | 256 ++++++------ 9 files changed, 799 insertions(+), 799 deletions(-) diff --git a/daemon/algod/api/algod.oas2.json b/daemon/algod/api/algod.oas2.json index 0adcc18c8b..dfeaaa01b6 100644 --- a/daemon/algod/api/algod.oas2.json +++ b/daemon/algod/api/algod.oas2.json @@ -2661,14 +2661,14 @@ "description": "Contains a ledger delta for a single transaction group", "type": "object", "required": [ - "delta", - "ids" + "Delta", + "Ids" ], "properties": { - "delta": { + "Delta": { "$ref": "#/definitions/LedgerStateDelta" }, - "ids": { + "Ids": { "type": "array", "items": { "type": "string" @@ -4045,10 +4045,10 @@ "schema": { "type": "object", "required": [ - "deltas" + "Deltas" ], "properties": { - "deltas": { + "Deltas": { "type": "array", "items": { "$ref": "#/definitions/LedgerStateDeltaForTransactionGroup" diff --git a/daemon/algod/api/algod.oas3.yml b/daemon/algod/api/algod.oas3.yml index 1acacc4daa..2c65bdde11 100644 --- a/daemon/algod/api/algod.oas3.yml +++ b/daemon/algod/api/algod.oas3.yml @@ -862,7 +862,7 @@ "application/json": { "schema": { "properties": { - "deltas": { + "Deltas": { "items": { "$ref": "#/components/schemas/LedgerStateDeltaForTransactionGroup" }, @@ -870,7 +870,7 @@ } }, "required": [ - "deltas" + "Deltas" ], "type": "object" } @@ -1743,10 +1743,10 @@ "LedgerStateDeltaForTransactionGroup": { "description": "Contains a ledger delta for a single transaction group", "properties": { - "delta": { + "Delta": { "$ref": "#/components/schemas/LedgerStateDelta" }, - "ids": { + "Ids": { "items": { "type": "string" }, @@ -1754,8 +1754,8 @@ } }, "required": [ - "delta", - "ids" + "Delta", + "Ids" ], "type": "object" }, @@ -4152,7 +4152,7 @@ "application/json": { "schema": { "properties": { - "deltas": { + "Deltas": { "items": { "$ref": "#/components/schemas/LedgerStateDeltaForTransactionGroup" }, @@ -4160,7 +4160,7 @@ } }, "required": [ - "deltas" + "Deltas" ], "type": "object" } @@ -4168,7 +4168,7 @@ "application/msgpack": { "schema": { "properties": { - "deltas": { + "Deltas": { "items": { "$ref": "#/components/schemas/LedgerStateDeltaForTransactionGroup" }, @@ -4176,7 +4176,7 @@ } }, "required": [ - "deltas" + "Deltas" ], "type": "object" } diff --git a/daemon/algod/api/server/v2/generated/data/routes.go b/daemon/algod/api/server/v2/generated/data/routes.go index 03aa23d8c8..7d29827051 100644 --- a/daemon/algod/api/server/v2/generated/data/routes.go +++ b/daemon/algod/api/server/v2/generated/data/routes.go @@ -172,122 +172,122 @@ var swaggerSpec = []string{ "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", - "AAD//yvhnHBdywAA", + "9DjhvTTQGSWEob5dob8Ffwes9jxTqPG2+MXd7p7QrotIfSvkXfkg7YCT5ekJLr+D/m035U0dkzTPI748", + "996kywDUvH7fziShSomUoZB0nqm5PWjO/ecep7TR/7qOor2Ds9cdt+O0Cp8yolEW8pJQkuYMTbaCKy2r", + "VL/jFI1CwVIj0UZe+x02E77wTeJ2yYjZ0A31jlOMNKtNRdEIiRVE7CLfAnhroarWa1C6o1ysAN5x14px", + "UnGmca7CHJfEnpcSJIb8nNiWBd2TlaEJLcjvIAVZVrotbuNzKqVZnjsPmpmGiNU7TjXJgSpNfmD8cofD", + "eS+7P7Ic9LWQVzUW4rf7GjgoppJ4VNR39isGrLrlb1zwKj5/t5+tz8WM37y52qPNqHnS/X/u/9fp27Pk", + "bzT5/VHy/H8s3n949vHBw96PTz5+9dX/bf/09ONXD/7rP2M75WGPPfZxkJ+/dKro+UvUNxqnSw/2T2Zw", + "LxhPokQWhk90aIvcx4etjoAetK1RegPvuN5xQ0hbmrPM8JabkEP3humdRXs6OlTT2oiO9cmv9Ugp/hZc", + "hkSYTIc13liK6gcSxp/VoRfQvZTD87KquN1KL33bVyM+oEus5vXTSZtV5ZTgu7oN9dGI7s8nX3w5mzfv", + "4ervs/nMfX0foWSW7WKvHjPYxZQzd0DwYNxTpKR7BTrOPRD2aOyaDaYIhy3AaPVqw8pPzymUZss4h/Ox", + "+M7Is+Pn3AbJm/ODPsW9c1WI1aeHW0uADEq9iWVbaAlq2KrZTYBOnEcpxRb4nLATOOkaWTKjL7oouhzo", + "Cl/9o/YppmhD9TmwhOapIsB6uJBJlowY/aDI47j1x/nMXf7qztUhN3AMru6ctQPR/60FuffdN5dk4Rim", + "umcf4NqhgyeTEVXavQpqRQAZbmZzzFgh7x1/x1/CinFmvp++4xnVdLGkiqVqUSmQX9Oc8hRO1oKc+odG", + "L6mm73hP0hpMAxU88SJltcxZSq5ChaQhT5vaoz/Cu3dvab4W79697wVD9NUHN1WUv9gJEiMIi0onLjFB", + "IuGaypizSdUP03Fkm3lkbFYrZIvKWiR94gM3fpzn0bJU3Qeq/eWXZW6WH5Chcs8vzZYRpYX0sogRUCw0", + "uL8/CncxSHrt7SqVAkV+K2j5lnH9niTvqkePngJpvdj8zV35hib3JUy2rgw+oO0aVXDhVq2EnZY0Kek6", + "5tN69+6tBlri7qO8XKCNI88Jdmu9FPWR8DhUswCPj+ENsHAc/eoNF3dhe/kkVPEl4CfcQmxjxI3G037T", + "/Qrejt54uzrvT3u7VOlNYs52dFXKkLjfmTo3zdoIWT78QbE1aqsujc8SSLqB9MrlV4Gi1Pt5q7uPsHGC", + "pmcdTNnMO/blF+Z+QI/AEkhVZtSJ4pTvu4/wFWjt43jfwBXsL0WTOuKYV/ftR+Bq6KAipQbSpSHW8Ni6", + "Mbqb78K4ULEvS/+WGh/VebI4renC9xk+yFbkvYNDHCOK1iPlIURQGUGEJf4BFNxgoWa8W5F+bHlGy1ja", + "my+ShcfzfuKaNMqTi7gKV4NWd/u9AEzjJa4VWVIjtwuXgco+dA64WKXoGgYk5NApM/E5ccuRg4Mcuvei", + "N51YdS+03n0TBdk2Tsyao5QC5oshFVRmOnF2fibr93OeCUws6RC2zFFMqgMSLdOhsuUcs5nyhkCLEzBI", + "3ggcHow2RkLJZkOVT46FOcT8WZ4kA/yBD/fH0rWcByFiQaKwOhmL57ndc9rTLl3SFp+pxadnCVXLCalW", + "jISPUemx7RAcBaAMcljbhdvGnlCaJALNBhk4flqtcsaBJLFos8AMGlwzbg4w8vFDQqwFnkweIUbGAdjo", + "z8aByY8iPJt8fQyQ3CVBoH5s9IQHf0P8vZaNvzYijygNC2cDXq3UcwDqQhTr+6sTKIvDEMbnxLC5Lc0N", + "m3MaXzNIL2sIiq2dHCEuouLBkDg74gCxF8tRa7JX0U1WE8pMHui4QDcC8VLsEvtgMyrxLndLQ+/RkHR8", + "Pho7mDY/yz1FlmKHUTp4tdgQ6AOwDMPhwQg0/B1TSK/Yb+g2t8CMTTsuTcWoUCHJOHNeTS5D4sSUqQck", + "mCFyuR+kXLkRAB1jR5O/2Cm/B5XUtnjSv8ybW23epBLzr31ix3/oCEV3aQB/fStMnSTldVdiidop2sEm", + "7fwwgQgZI3rDJvpOmr4rSEEOqBQkLSEquYp5To1uA3jjXPhugfECs9BQvn8QRDBJWDOloTGi+ziJz2Ge", + "pJj8TojV8Op0KVdmfW+EqK8p60bEjq1lfvIVYAjwikmlE/RARJdgGn2rUKn+1jSNy0rtGCmbKpZlcd6A", + "017BPslYXsXp1c37/Usz7Y81S1TVEvkt4zZgZYmpjaORkyNT2+Da0QW/sgt+Re9svdNOg2lqJpaGXNpz", + "/Iuciw7nHWMHEQKMEUd/1wZROsIggxevfe4YyE2Bj/9kzPraO0yZH/tg1I5/dzt0R9mRomsJDAajq2Do", + "JjJiCdNBZuD+U9SBM0DLkmW7ji3UjjqoMdOjDB4+n1oHC7i7brADGAjsnrHXMBJUO3VeI+DbHM+tzDUn", + "kzBz2U5wFzKEcCqmfIWCPqLq13KHcHUJNP8e9r+Ytric2cf57Ham0xiu3YgHcP263t4ontE1b01pLU/I", + "kSinZSnFluaJMzAPkaYUW0ea2Nzboz8xq4ubMS+/OXv12oH/cT5Lc6AyqUWFwVVhu/JfZlU2S9/AAfEZ", + "0I3O52V2K0oGm1+nFguN0tcbcKmkA2m0l/OycTgER9EZqVfxCKGDJmfnG7FLHPGRQFm7SBrznfWQtL0i", + "dEtZ7u1mHtqBaB5c3LTEqVGuEA5wa+9K4CRL7pTd9E53/HQ01HWAJ4VzjSS7Lmw+d0UE77rQMeZ5Xzqv", + "e0ExY6W1ivSZE68KtCQkKmdp3MbKl8oQB7e+M9OYYOMBYdSMWLEBVyyvWDCWaTYlJ00HyGCOKDJVNC1O", + "g7ulcLV6Ks7+UQFhGXBtPkk8lZ2DiulNnLW9f50a2aE/lxvYWuib4W8jY4TZWrs3HgIxLmCEnroeuC9r", + "ldkvtLZImR8Cl8QRDv9wxt6VOOKsd/ThqNkGL27aHrewtE6f/xnCsDnWD9f18cqrSxs7MEe0Tg9TyUqK", + "3yGu56F6HHlo5PPTMoxy+R3Chw5hdYoWi6mtO025oWb2we0ekm5CK1Q7SGGA6nHnA7ccJsr0FmrK7Vbb", + "shmtWLc4wYRRpQs7fkMwDuZeJG5Or5c0lkXUCBkGprPGAdyypWtBfGePe1W/trCzk8CXXLdl9hF5CbJ5", + "A9hPSHNDgcFOO1lUaCQDpNpQJphb/1+uRGSYil9TbquvmH72KLneCqzxy/S6FhJTQKi42T+DlBU0j0sO", + "Wdo38WZszWxhkUpBULnCDWSLNlkqctU/6jdEDjXnK/JoHpTPcbuRsS1TbJkDtnhsWyypQk5eG6LqLmZ5", + "wPVGYfMnE5pvKp5JyPRGWcQqQWqhDtWb2nm1BH0NwMkjbPf4ObmPbjvFtvDAYNHdz7PTx8/R6Gr/eBS7", + "AFxhmDFukiE7+atjJ3E6Rr+lHcMwbjfqSfS1vK0MN8y4Rk6T7TrlLGFLx+sOn6WCcrqGeKRIcQAm2xd3", + "Ew1pHbzwzJY1UlqKPWE6Pj9oavjTQPS5YX8WDJKKomC6cM4dJQpDT01ZCjupH87WSHIZhT1c/iP6SEvv", + "IuookZ/WaGrvt9iq0ZP9Iy2gjdY5oTbvR86a6AWf55yc+7RCmGK5zqxscWPmMktHMQeDGVaklIxrVCwq", + "vUr+TNINlTQ17O9kCNxk+eWzSFrpdnpTfhzgnxzvEhTIbRz1coDsvQzh+pL7XPCkMBwle9C89ghO5aAz", + "N+62G/Idjg89VSgzoySD5Fa1yI0GnPpWhMdHBrwlKdbrOYoej17ZJ6fMSsbJg1Zmh35+88pJGYWQsVyB", + "zXF3EocELRlsMXYvvklmzFvuhcwn7cJtoP+8ngcvcgZimT/LMUXgaxHRTn2q89qS7mLVI9aBoWNqPhgy", + "WLqh5qSdVvrT89G7iYKKe7q8Ybvv2DJfPB7wjy4iPjO54AY2vny7kgFCCdLqR0kmq78HPnZKvha7qYTT", + "OYWeeP4JUBRFScXy7Jfm5WenaoGkPN1EfWZL0/HXpr5avTh7B0bT/m0o55BHh7Py5q9eLo1Izn8XU+cp", + "GJ/YtltIwS63s7gG8DaYHig/oUEv07mZIMRq+1FdHbSdr0VGcJ4mx1xzXPsFOII06f+oQOnYAyX8YAPH", + "0DZq2IHN0k2AZ6iRnpDvbAnlDZBWAiHUBH2miPar6arMBc3mmMHi8puzV8TOavvYKkE2S/gaFaH2Kjo2", + "sSB95rQQZF/wJ/48Yvo44/HaZtVKJ3VS79gDVNOiSTvOOn4CVJFC7JyQl0ExVPtW1Qxh6GHFZGG0uno0", + "Kx8hTZj/aE3TDap9LdY6TPLT09t7qlRBScm6NFSdUxLPnYHbZbi3Ce7nRBjd/JopWzkXttB+81o/AHdm", + "B/8Gtr08WXFuKeXkiFuuziB5LNo9cPaK9K6EKGQdxB8p9NvqEMdm+7/AXtEUV93SAb1akvYFZV3yx1dE", + "TykXnKWYYCp2RbsSu1P8bBNycXUNuf6IuxMaOVzRggV1KJ7D4mAJA88IHeL6hv7gq9lUSx32T421XDdU", + "kzVo5TgbZHNfd8PZGhlX4HKEYkHmgE8K2fJdIoeMusOT2m1yJBnh05sB5fFb8+1HZ1rAmPQrxlGJcGhz", + "gp+1BmIFUG00D6bJWoBy62m/P1ZvTZ8TfIqbwe79ia8YimNY159ZtvVz94c6815v52U2bV+Yti5BUv1z", + "K8rZTnpWlm7S4aosUXlA7/gggiPey8S7jwLk1uOHo42Q22i4Ct6nhtBgi85uKPEe7hFGXaGkU/3KCK2W", + "orAFsWFi0SwJjEfAeMU4NPVsIxdEGr0ScGPwvA70U6mk2oqAk3jaJdAcPdwxhqa0c2/cdqhueiiDElyj", + "n2N4G5viKgOMo27QCG6U7+syuoa6A2HiBdbvdojsl0pBqcoJURm+WugUT4kxDsO4fXmm9gXQPwZ9mch2", + "15Lak3PMTTT0EHVZZWvQCc2yWMrWr/Erwa8kq1BygB2kVZ3asyxJinlX2olo+tTmJkoFV1UxMpdvcMvp", + "gmpEEWoIKyL5HcaHLss9/hvLazm8My7Q4+hQQx/VkR2XfakfOhmTeg1NJ4qtk+mYwDvl9uhopr4ZoTf9", + "75TSc7FuA/KJ00+Mcblwj2L87RtzcYTZGXrJWu3VUidPwMA+4WtIotpYP/ttcyW8ynrZW9GhVNeoGzdA", + "DFebm+PlNxDeGyTdoPZ+tR7KoSDfdDAmnWr3Ok5TMsqCBl8c2Qgh+7YIoYhbZ4eigmxQkPnc6z1NMuzJ", + "2Tqe+DBAqA836wP0vY9lJSVlzv3eMIs+Zl3Ue/8dwpR42GaDu4twseSDFrvvt0Nx3z4ZG37vVqO6Avdk", + "vpSwZaLyjm0f+eRVQvtrq7ZTHXkfXX/f8IpTfV5z6KDx9tJVBbDLdDr597/YODkCXMv9P4Ept7fpvTpX", + "fWnXmqeaJqROKD0pwXTrVpySqDCWE8/Jhq1KWwfqhPXI6uUUcaBf92s+O8+OujBjeRVndpTYsYtX8RpO", + "O9WkmsIjVgrFmrzusfJeE0MML7FCV5A2qz+Wj+/ZQqoxmX8TtyABjkmiZSYLCob+O/3UgDpdR2K6rFNj", + "qab6GfwP3PG912DBi0ab/fxkemKlszo6Dfk0ZkNeA3c1O9vvPCZHm69WkGq2PfD67q8b4MHLrrm3y9ja", + "28FjPFZHL2PyluOtjg1AY4/jRuEJkijeGpyhtzdXsL+nSIsaounY5/6qvUneDsQAcofEkIhQsegPa0h2", + "DnmmaspALPhoK9sdmgxog5WcgrekN5zLk6S5OJr3pSNTxkvJTJrLdD3q1TUG4g490OtXohjWP15i4Q9V", + "V1n0eT9CLZ2c97MjXru8IfhWsvad+AwioPxv/mG0nSVnVxDWmkJP1TWVmW8RNb14q04ych/1XtX5Kgpd", + "oFf1zKyJje2/o4rk28II6DQXRoxIhsLI2+GodSzHPWWDbmz6dwy0NXCtQLqafCj/5kJBooWPpR2DYwwV", + "NrLoRkhQgzkuLXCDmWfeNKl1MNcvxUwz1AUUhQskEgpqoJNBApzhOceQ/cJ+9w+HfK7Xgxamml4PFx3w", + "UdFM9ZAYUv2KuNvy8IOkmxibGOe27rOKZcPhINvekFKKrErtBR0ejNogNznX1Agridpp0v4qOzpC8Krz", + "CvYLqwT5ag1+B0OgreRkQQ+yKHQ2+U7NbyoG9/pOwPuclqv5rBQiTwacHef9FD5dir9i6RVkxNwUPnpw", + "oPINuY829tqbfb3Z+5Q1ZQkcsgcnhJxxG6/tHdvtHNKdyfk9PTb/DmfNKptVyxnVTt7xeOAr5ruSt+Rm", + "fphxHqbAsLpbTmUHOZAgZjeQPkjS60gdqJOpWnnf1dytzdMQlYUiJpM0ZWcOxMnUITJN5Y8mTKYvHeS5", + "uE6QipI6/1dM5zDt2kzSZzxtuhlsLyGIt6HKXaB7sqEZSYWUkIY94k8cLFCFkJDkAsNvYp7BlTbyUIFx", + "zZzkYk1EadRcm0bP+1CiZWmCuewzW9szsY6agUQGoNyzWjeNbdyfZ6R6zfGVcS43EXsLItpj+ejyN45Q", + "jq5aEYA5gUAP25rOYtV92uvq1ocaqtamRcHSOLr/taJMBmNDDtQuiqyvJkdXWsm/ChzAVdRlO+4htXXo", + "llP9pHXO5InHIgBg2HPagmGS//RYMFZY1zGhESSf11LrvFV2l3XOvs9nZ2k8pVZr3QAxY1cS3Cs1W4Cu", + "UzmnpHrjbzHTvK9bGj0FFD4hs+U/qLKWEG+RcdXvuuKBKJMcttByKLunc1WaglJsC2HlPNuZZAAl2ie7", + "UnPMUxpyuY4o5daeBL62KdiNylYWsXanyAHBKSrm7Xhij4maepQMRFuWVbSFP3WLWmRDZcgibNjDOpFT", + "HM0k4osbYxEHYxuQ5qPnksdDG8KXm7VRBGfLauOpJcLmZKuSXvNhJSJid6r97bdfB8HBiOq8pB688mW9", + "KzdVIAcpY4wwevUDozKHAl//NUx64sUt1zciY1lTF1ORAZhqzjNG70ETHRY0K+ieZGy1AmmN+UpTnlGZ", + "hc0ZJylITZnRbPbq5mKtgVZWMD8o2RruioN6BhOTcdEuZQHJ905luIXUiZ6biMRpr1othkok9nYl/pyA", + "7ox0jXFVA0TgHkKjbG0PmOAoIJGCXsGR8yj2O4xPg+lJnO1PC5x1yhQxX+sNc6tNYt39MITI7RYUQxz3", + "DIWpF5s3XdJGs6Al2V+QXRr/obk4p5Vl9B0OgBc6DIPCjN5248D5zI+jfqiREizl/RAltJZ/yAfpFthI", + "GsEWOUagNdhEuDagvr0vgYNZvaj9tkM1RLvuXcyzKLgt8tdzC1veZKv2BYRjzoLc0vzTu3YxAecZ4gOy", + "N8PG4NA3GCLZolLd7GXCKzpp7sAPeHdT89foiv4rmD2KaqVuKCfC1GK9D+bBm4Xm1nCx8iW8tsDJNY5p", + "49gef0mW7uV2KSFlqisaXfvqGrUrDItNudcgO33A93Zonb8IfQsyXnlNg/zYZOpHHX/NGwibI/qZmcrA", + "yY1SeYz6emQRwV+MR4Up1A5cF1etADdb+aTzckNIuONAtyBk/chAt35yuKnLs8Fc5tKpFPTXOfm2buE2", + "clE3a5sapdlH7lg69ynBlfEqDaY7RndahGCJE4Kgkt8e/0YkrLCGoSAPH+IEDx/OXdPfnrQ/m+P88GFU", + "OvtkcZ0WR24MN2+MYn4ZeulnX7MNPCrt7EfF8uwQYbSeCDdVQPER7K8uEcFnqUP6q4016R9VVwvuFgFy", + "FjGRtbYmD6YKHv9OePfrukVe+aIfJ60k03vMj+jtB+zXaATqd3U0k4uGq/VDd/dpcQV1hs0m9qlS/nb9", + "TtAc7yOrtnJzC4n8hHyzo0WZgzsoX91b/gme/vlZ9ujp4z8t//zoi0cpPPvi+aNH9Pkz+vj508fw5M9f", + "PHsEj1dfPl8+yZ48e7J89uTZl188T58+e7x89uXzP90zfMiAbAGd+Ww8s/+NxXqTs9fnyaUBtsEJLdn3", + "sLd1AQ0Z+4qDNMWTCAVl+ezU//Q//Qk7SUXRDO9/nblkH7ON1qU6XSyur69Pwi6LNQY7JFpU6Wbh5+mV", + "JDx7fV57iawVCHfUvpP11j1PCmf47c03F5fk7PX5SVCv/nT26OTRyWMsb14CpyWbnc6e4k94eja47wtH", + "bLPTDx/ns8UGaI6xgeaPArRkqf8kgWZ79391TddrkCeuDKP5aftk4cWKxQcX9PFx7NsirGiy+NCKjckO", + "9MSKB4sPPpHfeOtWpjwXExR0mAjFWLPFEvODTG0KKmg8vBRUNtTiA4rLg78vXEKD+EdUW+x5WPgAsnjL", + "FpY+6J2BtdMjpTrdVOXiA/4H6TMAyz4fWugdX6DtY/GhtRr3ubea9u9N97DFthAZeIDFamUTk459Xnyw", + "/wYTwa4EyYzghyF77lcbWr2wxfD7P++5e9WYQywg7meuwCqmPp3BnqdNgH99ZM8z3/hiz1MvofpnMngQ", + "nzx6ZKd/hv+5m8Kn7Qc7kfKnFzW8mFsOI6YQhsefDoZzjhGlhn8Ry58/zmdffEosnBudndOcYEs7/dNP", + "uAkgtywFcglFKSSVLN+Tn3mdhCBIbhijwCsurrmH3FzuVVFQuUehuRBbUHUB8oY4iQQjprha5FIUAQ3j", + "7ULXCsMhsKzEbG6fZ71HwUjHZARvr+nP5G1VzeDtU/HdwTNx02LeI/Fwk+A8EMBqh59S+7iuLdx5Q2Sn", + "uhfboNm/GcG/GcEdMgJdST54RIP7C4O6obQRbCSl6QbG+EH/tgwu+FkpYsFRFyPMwqVOGeIVF21eEVQu", + "OX07LWWZczBY23EGirls7qg3GKG4EetlzZH8mUf3VLDXY/loP77/p7jfX1Duz3Nrx21cIZU5A1lTAeX9", + "bDb/5gL/33ABm5aL2n2dEw15rsKzrwWefetscW91uHWCTeQD3cq4sZ8XH9qVmVpKgtpUOhPXQV80mVt/", + "T193qGuVtv5eXFOmk5WQ7p0OZs7ud9ZA84VLytP5tXkH3/uCj/uDH8N4k+ivi7owQfRjVx2NfXXq2EAj", + "7/72nxvTVGjqQQ5ZG3nevjf8CdPeOubZWC5OFwuMfd8IpRezj/MPHatG+PF9TRI+V+GslGyLqQ/ef/x/", + "AQAA//+92+QcXcsAAA==", } // 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 0d4e810054..e253331bf2 100644 --- a/daemon/algod/api/server/v2/generated/experimental/routes.go +++ b/daemon/algod/api/server/v2/generated/experimental/routes.go @@ -133,117 +133,117 @@ var swaggerSpec = []string{ "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/generated/model/types.go b/daemon/algod/api/server/v2/generated/model/types.go index 50fe884e3c..3589e9e117 100644 --- a/daemon/algod/api/server/v2/generated/model/types.go +++ b/daemon/algod/api/server/v2/generated/model/types.go @@ -542,8 +542,8 @@ type LedgerStateDelta = map[string]interface{} // LedgerStateDeltaForTransactionGroup Contains a ledger delta for a single transaction group type LedgerStateDeltaForTransactionGroup struct { // Delta Ledger StateDelta object - Delta LedgerStateDelta `json:"delta"` - Ids []string `json:"ids"` + Delta LedgerStateDelta `json:"Delta"` + Ids []string `json:"Ids"` } // LightBlockHeaderProof Proof of membership and position of a light block header. @@ -1090,7 +1090,7 @@ type SupplyResponse struct { // TransactionGroupLedgerStateDeltasForRoundResponse defines model for TransactionGroupLedgerStateDeltasForRoundResponse. type TransactionGroupLedgerStateDeltasForRoundResponse struct { - Deltas []LedgerStateDeltaForTransactionGroup `json:"deltas"` + Deltas []LedgerStateDeltaForTransactionGroup `json:"Deltas"` } // TransactionParametersResponse TransactionParams contains the parameters that help a client construct 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 e4ec6dd160..41fef3739d 100644 --- a/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go +++ b/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go @@ -188,123 +188,123 @@ var swaggerSpec = []string{ "oUtRLV3gmB0HOWylrC1EVrw3RFSK0RueoFU5xnFdsLB/oGHkF6BGh+qapK3GcEXr+dybnDFXod+5iIk+", "6pWaTgZVTIPUdaNiWuS0X5mM4L4tASvATzPxSN8Fos4IG318hdtiqNds7h9jI2+GjkHZnzgIZWs+DkWz", "Gf02396BlGEHIhJKCQrvhNAupOxXsQhflLlLQ22VhqJvOrddfxk4fu8GFTTBc8YhKQSHbfQRNePwA36M", - "Hie8lwY6o4Qw1Lcr9Lfg74DVnmcMNd4Wv7jb3RPadRGpb4W8Kx+kcyWNladHuPz2+rfdlDd1TNI8j/jy", - "3HuTLgNQ0/p9O5OEKiVShkLSWaam9qA59597nNJG/9s6ivYOzl533I7TKnzKiEZZyEtCSZozNNkKrrSs", - "Uv2BUzQKBUuNRBt57XfYTPjSN4nbJSNmQzfUB04x0qw2FUUjJBYQsYt8C+CthapaLkHpjnKxAPjAXSvG", - "ScWZxrkKc1wSe15KkBjyc2RbFnRLFoYmtCC/gxRkXum2uI3PqZRmee48aGYaIhYfONUkB6o0+YHxiw0O", - "573s/shy0FdCXtZYiN/uS+CgmEriUVHf2a8YsOqWv3LBq/j83X62PhczfvPmaos2o+ZJ9/+7/58n70+T", - "/6bJ78fJi/81+/jp6fWDh70fH19/9dX/b//05PqrB//5H7Gd8rDHHvs4yM9eOVX07BXqG43TpQf7ZzO4", - "F4wnUSILwyc6tEXu48NWR0AP2tYovYIPXG+4IaQ1zVlmeMtNyKF7w/TOoj0dHappbUTH+uTXeqAUfwsu", - "QyJMpsMabyxF9QMJ48/q0AvoXsrheVlU3G6ll77tqxEf0CUW0/rppM2qckLwXd2K+mhE9+fjZ88n0+Y9", - "XP19Mp24rx8jlMyyTezVYwabmHLmDggejHuKlHSrQMe5B8IejV2zwRThsAUYrV6tWPn5OYXSbB7ncD4W", - "3xl5NvyM2yB5c37Qp7h1rgqx+PxwawmQQalXsWwLLUENWzW7CdCJ8yilWAOfEnYER10jS2b0RRdFlwNd", - "4Kt/1D7FGG2oPgeW0DxVBFgPFzLKkhGjHxR5HLe+nk7c5a/uXB1yA8fg6s5ZOxD931qQe999c0FmjmGq", - "e/YBrh06eDIZUaXdq6BWBJDhZjbHjBXyPvAP/BUsGGfm+8kHnlFNZ3OqWKpmlQL5Nc0pT+FoKciJf2j0", - "imr6gfckrcE0UMETL1JW85yl5DJUSBrytKk9+iN8+PCe5kvx4cPHXjBEX31wU0X5i50gMYKwqHTiEhMk", - "Eq6ojDmbVP0wHUe2mUd2zWqFbFFZi6RPfODGj/M8Wpaq+0C1v/yyzM3yAzJU7vml2TKitJBeFjECioUG", - "9/eNcBeDpFferlIpUOTXgpbvGdcfSfKhOj5+AqT1YvNXd+UbmtyWMNq6MviAtmtUwYVbtRI2WtKkpMuY", - "T+vDh/caaIm7j/JygTaOPCfYrfVS1EfC41DNAjw+hjfAwnHwqzdc3Lnt5ZNQxZeAn3ALsY0RNxpP+033", - "K3g7euPt6rw/7e1SpVeJOdvRVSlD4n5n6tw0SyNk+fAHxZaorbo0PnMg6QrSS5dfBYpSb6et7j7Cxgma", - "nnUwZTPv2JdfmPsBPQJzIFWZUSeKU77tPsJXoLWP430Hl7C9EE3qiENe3bcfgauhg4qUGkiXhljDY+vG", - "6G6+C+NCxb4s/VtqfFTnyeKkpgvfZ/ggW5H3Dg5xjChaj5SHEEFlBBGW+AdQcIOFmvFuRfqx5RktY25v", - "vkgWHs/7iWvSKE8u4ipcDVrd7fcCMI2XuFJkTo3cLlwGKvvQOeBilaJLGJCQQ6fMyOfELUcODrLv3ove", - "dGLRvdB6900UZNs4MWuOUgqYL4ZUUJnpxNn5mazfz3kmMLGkQ9g8RzGpDki0TIfKlnPMZsobAi1OwCB5", - "I3B4MNoYCSWbFVU+ORbmEPNneZQM8Ac+3N+VruUsCBELEoXVyVg8z+2e05526ZK2+EwtPj1LqFqOSLVi", - "JHyMSo9th+AoAGWQw9Iu3Db2hNIkEWg2yMDx42KRMw4kiUWbBWbQ4Jpxc4CRjx8SYi3wZPQIMTIOwEZ/", - "Ng5M3ojwbPLlIUBylwSB+rHREx78DfH3Wjb+2og8ojQsnA14tVLPAagLUazvr06gLA5DGJ8Sw+bWNDds", - "zml8zSC9rCEotnZyhLiIigdD4uwOB4i9WA5ak72KbrKaUGbyQMcFuh0Qz8UmsQ82oxLvfDM39B4NScfn", - "o7GDafOz3FNkLjYYpYNXiw2B3gPLMBwejEDD3zCF9Ir9hm5zC8yuaXdLUzEqVEgyzpxXk8uQODFm6gEJ", - "Zohc7gcpV24EQMfY0eQvdsrvXiW1LZ70L/PmVps2qcT8a5/Y8R86QtFdGsBf3wpTJ0l525VYonaKdrBJ", - "Oz9MIELGiN6wib6Tpu8KUpADKgVJS4hKLmOeU6PbAN44575bYLzALDSUbx8EEUwSlkxpaIzoPk7iS5gn", - "KSa/E2IxvDpdyoVZ3zsh6mvKuhGxY2uZn30FGAK8YFLpBD0Q0SWYRt8qVKq/NU3jslI7RsqmimVZnDfg", - "tJewTTKWV3F6dfN+/8pM+6ZmiaqaI79l3AaszDG1cTRycsfUNrh254Jf2wW/pne23nGnwTQ1E0tDLu05", - "/iTnosN5d7GDCAHGiKO/a4Mo3cEggxevfe4YyE2Bj/9ol/W1d5gyP/beqB3/7nbojrIjRdcSGAx2roKh", - "m8iIJUwHmYH7T1EHzgAtS5ZtOrZQO+qgxkwPMnj4fGodLODuusH2YCCwe8Zew0hQ7dR5jYBvczy3Mtcc", - "jcLMRTvBXcgQwqmY8hUK+oiqX8vtw9UF0Px72P5s2uJyJtfTye1MpzFcuxH34Pptvb1RPKNr3prSWp6Q", - "A1FOy1KKNc0TZ2AeIk0p1o40sbm3R39mVhc3Y158c/r6rQP/ejpJc6AyqUWFwVVhu/JPsyqbpW/ggPgM", - "6Ebn8zK7FSWDza9Ti4VG6asVuFTSgTTay3nZOByCo+iM1It4hNBek7Pzjdgl7vCRQFm7SBrznfWQtL0i", - "dE1Z7u1mHtqBaB5c3LjEqVGuEA5wa+9K4CRL7pTd9E53/HQ01LWHJ4Vz7Uh2Xdh87ooI3nWhY8zztnRe", - "94JixkprFekzJ14VaElIVM7SuI2Vz5UhDm59Z6YxwcYDwqgZsWIDrlhesWAs02xMTpoOkMEcUWSqaFqc", - "Bndz4Wr1VJz9VgFhGXBtPkk8lZ2DiulNnLW9f50a2aE/lxvYWuib4W8jY4TZWrs3HgKxW8AIPXU9cF/V", - "KrNfaG2RMj8ELokDHP7hjL0rcYez3tGHo2YbvLhqe9zC0jp9/mcIw+ZY31/XxyuvLm3swBzROj1MJQsp", - "foe4nofqceShkc9PyzDK5XcIHzqE1SlaLKa27jTlhprZB7d7SLoJrVDtIIUBqsedD9xymCjTW6gpt1tt", - "y2a0Yt3iBBNGlc7s+A3BOJh7kbg5vZrTWBZRI2QYmE4bB3DLlq4F8Z097lX92sLOTgJfct2W2UfkJcjm", - "DWA/Ic0NBQY77WhRoZEMkGpDmWBq/X+5EpFhKn5Fua2+YvrZo+R6K7DGL9PrSkhMAaHiZv8MUlbQPC45", - "ZGnfxJuxJbOFRSoFQeUKN5At2mSpyFX/qN8QOdScLcjxNCif43YjY2um2DwHbPHItphThZy8NkTVXczy", - "gOuVwuaPRzRfVTyTkOmVsohVgtRCHao3tfNqDvoKgJNjbPfoBbmPbjvF1vDAYNHdz5OTRy/Q6Gr/OI5d", - "AK4wzC5ukiE7+YdjJ3E6Rr+lHcMwbjfqUfS1vK0MN8y4dpwm23XMWcKWjtftP0sF5XQJ8UiRYg9Mti/u", - "JhrSOnjhmS1rpLQUW8J0fH7Q1PCngehzw/4sGCQVRcF04Zw7ShSGnpqyFHZSP5ytkeQyCnu4/Ef0kZbe", - "RdRRIj+v0dTeb7FVoyf7DS2gjdYpoTbvR86a6AWf55yc+bRCmGK5zqxscWPmMktHMQeDGRaklIxrVCwq", - "vUj+RtIVlTQ17O9oCNxk/vxpJK10O70pPwzwz453CQrkOo56OUD2XoZwfcl9LnhSGI6SPWheewSnctCZ", - "G3fbDfkOdw89VigzoySD5Fa1yI0GnPpWhMd3DHhLUqzXcxA9Hryyz06ZlYyTB63MDv307rWTMgohY7kC", - "m+PuJA4JWjJYY+xefJPMmLfcC5mP2oXbQP9lPQ9e5AzEMn+WY4rA1yKinfpU57Ul3cWqR6wDQ8fUfDBk", - "MHdDTUk7rfTn56N3EwUV93R5w3bfsWW+eDzgH11EfGFywQ1sfPl2JQOEEqTVj5JMVn8PfOyUfC02Ywmn", - "cwo98fwLoCiKkorl2c/Ny89O1QJJebqK+szmpuMvTX21enH2Doym/VtRziGPDmflzV+8XBqRnP8pxs5T", - "MD6ybbeQgl1uZ3EN4G0wPVB+QoNepnMzQYjV9qO6Omg7X4qM4DxNjrnmuPYLcARp0n+rQOnYAyX8YAPH", - "0DZq2IHN0k2AZ6iRHpHvbAnlFZBWAiHUBH2miPar6arMBc2mmMHi4pvT18TOavvYKkE2S/gSFaH2Kjo2", - "sSB95rgQZF/wJ/48Yvw4u+O1zaqVTuqk3rEHqKZFk3acdfwEqCKF2Dkir4JiqPatqhnC0MOCycJodfVo", - "Vj5CmjD/0ZqmK1T7Wqx1mOTHp7f3VKmCkpJ1aag6pySeOwO3y3BvE9xPiTC6+RVTtnIurKH95rV+AO7M", - "Dv4NbHt5suLcUsrRAbdcnUHyULR74OwV6V0JUcg6iD9Q6LfVIQ7N9n+OvaIprrqlA3q1JO0Lyrrkj6+I", - "nlIuOEsxwVTsinYldsf42Ubk4uoacv0Rdyc0criiBQvqUDyHxcESBp4ROsT1Df3BV7Opljrsnxprua6o", - "JkvQynE2yKa+7oazNTKuwOUIxYLMAZ8UsuW7RA4ZdYcntdvkQDLCpzcDyuO35tsbZ1rAmPRLxlGJcGhz", - "gp+1BmIFUG00D6bJUoBy62m/P1bvTZ8jfIqbwebjka8YimNY159ZtvVz94c69V5v52U2bV+ati5BUv1z", - "K8rZTnpalm7S4aosUXlAb/gggiPey8S7jwLk1uOHo+0gt53hKnifGkKDNTq7ocR7uEcYdYWSTvUrI7Ra", - "isIWxIaJRbMkMB4B4zXj0NSzjVwQafRKwI3B8zrQT6WSaisCjuJpF0Bz9HDHGJrSzr1x26G66aEMSnCN", - "fo7hbWyKqwwwjrpBI7hRvq3L6BrqDoSJl1i/2yGyXyoFpSonRGX4aqFTPCXGOAzj9uWZ2hdA/xj0ZSLb", - "XUtqT84hN9HQQ9R5lS1BJzTLYilbv8avBL+SrELJATaQVnVqz7IkKeZdaSei6VObmygVXFXFjrl8g1tO", - "F1QjilBDWBHJ7zA+dJlv8d9YXsvhnXGBHgeHGvqojgOzL/VDJ2NSr6HpRLFlMh4TeKfcHh3N1Dcj9Kb/", - "nVJ6LpZtQD5z+omdybCCPYrxt2/MxRFmZ+gla7VXS508AQP7hK8hiWpj/ey3k/qLatrP3ooOpbpG3W4D", - "xHC1uSlefgPhvUHSDWrvV+uhHAryTQdj0ql2r+M0JTtZ0OCLIxshZN8WIRRx6+xQVJANCjKfe73HSYY9", - "OVvHEx8GCPXhZn2AvvexrKSkzLnfG2bRx6yLeu+/QxgTD9tscHcRLpZ80GL3/Xoo7tsnY8Pv3WpUl+Ce", - "zJcS1kxU3rHtI5+8Smh/bdV2qiPvo+vvG15xqi9rDh003l64qgB2mU4n//5nGydHgGu5/Rcw5fY2vVfn", - "qi/tWvNU04TUCaVHJZhu3YpjEhXGcuI52bBVaWtPnbA+Yx0jDvTrfk0nLDvowozlVZzYUWLHLl7Fazjt", - "VJNqCo9YKRRr8rrHynuNDDG8wApdQdqs/lg+vmcNqcZk/k3cggQ4JImWmSwoGPrv9FMD6nQdiemyTu1K", - "NdXP4L/nju+9BgteNNrs50fjEyud1tFpyKcxG/ISuKvZ2X7nMTrafLGAVLP1ntd3/1gBD152Tb1dxtbe", - "Dh7jsTp6GZO3HG51bADa9ThuJzxBEsVbgzP09uYStvcUaVFDNB371F+1N8nbgRhA7pAYEhEqFv1hDcnO", - "Ic9UTRmIBR9tZbtDkwFtsJJT8Jb0hnN5kjQXR/O+dMeU8VIyo+YyXQ96dY2BuEMP9PqVKIb1j1dY+EPV", - "VRZ93o9QSydn/eyIVy5vCL6VrH0nPoMIKP+bfxhtZ8nZJYS1ptBTdUVl5ltETS/eqpPsuI96r+p8FYUu", - "0It6ZtbExvbfUUXybWEEdJoLI0YkQ2Hk7XDUOpbjnrJBNzb9OwbaGrgWIF1NPpR/c6Eg0cLH0u6CYxcq", - "bGTRjZCgBnNcWuAGM8+8a1LrYK5fiplmqAsoChdIJBTUQCeDBDjDc+5C9kv73T8c8rle91qYanrdX3TA", - "R0Uz1UNiSPUL4m7L/Q+SbmJsYpzbus8qlg2Hg2x7Q0opsiq1F3R4MGqD3OhcUztYSdROk/ZX2dERgled", - "l7CdWSXIV2vwOxgCbSUnC3qQRaGzyXdqflMxuJd3At6XtFxNJ6UQeTLg7Djrp/DpUvwlSy8hI+am8NGD", - "A5VvyH20sdfe7KvV1qesKUvgkD04IuSU23ht79hu55DuTM7v6V3zb3DWrLJZtZxR7egDjwe+Yr4reUtu", - "5ofZzcMUGFZ3y6nsIHsSxGwG0gdJehWpA3U0Vivvu5q7tXkaorJQxGSSpuzMnjiZOkSmqfzRhMn0pYM8", - "F1cJUlFS5/+K6RymXZtJ+oynTTeD7TkE8TZUuQt0S1Y0I6mQEtKwR/yJgwWqEBKSXGD4TcwzuNBGHiow", - "rpmTXCyJKI2aa9PoeR9KtCxNMJd9Zmt7JtZRM5DIAJR7VuumsY378+yoXnN4ZZyLVcTegoj2WD64/I0j", - "lIOrVgRgjiDQ/bam01h1n/a6uvWhhqq1aVGwNI7uP1eUyWBsyJ7aRZH11eToSiv5V4EDuIq6bHd7SG0d", - "uvlYP2mdM3nksQgAGPactmAY5T89FIwF1nVMaATJZ7XUOm2V3WWds+/z2VkaT6nVWldAzNiVBPdKzRag", - "61TOKale+VvMNO/rlkZPAYVPyGz5D6qsJcRbZFz1u654IMokhzW0HMru6VyVpqAUW0NYOc92JhlAifbJ", - "rtQc85SGXK4jSrm1J4GvbQx2o7KVRazdKbJHcIqKeRue2GOixh4lA9GaZRVt4U/dohbZUBmyCBv2sI7k", - "FAczifjidrGIvbENSPPRc8njoQ3hy83aKIKzZbXx1BJhc7JVSa/4sBIRsTvV/vbbr4PgYER1XlIPXvmy", - "3pWbKpCDlLGLMHr1A6MyhwJf/zVMeuLFLdc3ImNZUxdTkQGYas4zRu9BEx0WNCvolmRssQBpjflKU55R", - "mYXNGScpSE2Z0Wy26uZirYFWVjDdK9ka7oqDegYTk3HRLmUBybdOZbiF1Imem4jEaa9aLYZKJPZ2Jf6c", - "gG6MdI1xVQNE4B5Co2xtD5jgKCCRgl7CgfMo9jvsngbTkzjbnxY465gpYr7WG+ZWG8W6+2EIkdstKIa4", - "2zMUpl5s3nRJG82ClmR/QXZp/Ifm4hxXltF32ANe6DAMCjN6240D5ws/jvqhRkqwlI9DlNBa/j4fpFtg", - "I2kEW+QYgdZgE+HagPr2vgQOZvWy9tsO1RDtuncxz6Lgtshfzy1seZOt2hcQjjkLck3zz+/axQScp4gP", - "yN4NG4ND32CIZItKdbOXCa/pqLkDP+DdTc3foiv6H2D2KKqVuqGcCFOL9T6YB28WmlvDxcKX8FoDJ1c4", - "po1je/SczN3L7VJCylRXNLry1TVqVxgWm3KvQTZ6j+9t3zp/FvoWZLzwmgZ502TqRx1/yRsImyP6hZnK", - "wMmNUnmM+npkEcFfjEeFKdT2XBeXrQA3W/mk83JDSLjjQLcgZP3AQLd+crixy7PBXObSqRT01zn6tm7h", - "NnJRN2sbG6XZR+6udO5jgivjVRpMd4zutAjBEicEQSW/PvqVSFhgDUNBHj7ECR4+nLqmvz5ufzbH+eHD", - "qHT22eI6LY7cGG7eGMX8PPTSz75mG3hU2tmPiuXZPsJoPRFuqoDiI9hfXCKCL1KH9Bcba9I/qq4W3C0C", - "5CxiImttTR5MFTz+HfHu13WLvPJFP05aSaa3mB/R2w/YL9EI1O/qaCYXDVfrh+7u0+IS6gybTexTpfzt", - "+p2gOd5HVm3l5hYS+RH5ZkOLMgd3UL66N/8rPPnb0+z4yaO/zv92/Ow4hafPXhwf0xdP6aMXTx7B4789", - "e3oMjxbPX8wfZ4+fPp4/ffz0+bMX6ZOnj+ZPn7/46z3DhwzIFtCJz8Yz+b9YrDc5fXuWXBhgG5zQkn0P", - "W1sX0JCxrzhIUzyJUFCWT078T//bn7CjVBTN8P7XiUv2MVlpXaqT2ezq6uoo7DJbYrBDokWVrmZ+nl5J", - "wtO3Z7WXyFqBcEftO1lv3fOkcIrf3n1zfkFO354dBfXqTybHR8dHj7C8eQmclmxyMnmCP+HpWeG+zxyx", - "TU4+XU8nsxXQHGMDzR8FaMlS/0kCzbbu/+qKLpcgj1wZRvPT+vHMixWzTy7o43rXt1lY0WT2qRUbk+3p", - "iRUPZp98Ir/drVuZ8lxMUNBhJBS7ms3mmB9kbFNQQePhpaCyoWafUFwe/H3mEhrEP6LaYs/DzAeQxVu2", - "sPRJbwysnR4p1emqKmef8D9In9cuxBhi4WI2DwAlTfMpYZrQuZCYQU+nK8MjfOoupoKWE6RaS/BnmSF0", - "0+ulhcAn6bRZy0/e991ZOBDxIyFXMCTfHNrWTA1fRgtUkEi7vnVa7Zu75/1x8uLjp0fTR8fXfzF3i/vz", - "2ZPrkX6pl/W45Ly+OEY2/Ih5r9AAiWf58fHxLSrCn/IA/XaT6oc8kaLudieGXQZuqzoDkRoZe/LzdIaP", - "lZi9nk6eHrjinbak1uOmSKnYr2lGvMsf5370+eY+4xh1a3g8sXfY9XTy7HOu/owbkqc5wZZBwsX+1v/E", - "L7m44r6lETiqoqBy64+xajEF4jYbrzW6VBiHIdmaopzHBW9VkZt8xNifWNjFAL9Rmt6A35ybXv/mN5+L", - "3+Am3QW/aQ90x/zm8YFn/s+/4n9z2D8bhz237O5WHNYJfPZF+Exv+AzdWbNPLQHVfe4JqO3fm+5hi3Uh", - "MvAyqFgsbK75XZ9nn+y/wUSwKUGyArjNwel+ta/lZpgBctv/ecvT6I/9dXQLvcZ+nn1qFxpqIUitKp2J", - "K5v2LHplYTZ9mrvUu2iurVU/LYgfoHmaRH50r6nzLdqoWQaEYponUelGNzed6zCR2ntiRmiKQC8ZxwnQ", - "DI6z2BzTNAj6V5AKbkumdq5HB9kbkUH/esQL8LcK5La5AR2Mk2mLPzoCj2R0vvV102dn14eRP5rrra+p", - "Txx1ndTW37MryrS5RN0bIcRov7MGms9cQqDOr80b/N4XTCwQ/BjGukR/ndVFEaIfu6pw7KtTBQcaede7", - "/9yYxUIzE5JEbWB6/9HsLKbcddTSWE1OZjOMu18JpWeT6+mnjkUl/Pix3kyfJ7He1OuP1/8TAAD///aV", - "b43ZywAA", + "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", + "Qz/sGdnLAAA=", } // 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 6c99f788e9..8eb9f99c11 100644 --- a/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go +++ b/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go @@ -760,195 +760,195 @@ var swaggerSpec = []string{ "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", - "HOZ0AQA=", + "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", + "ReZ0AQA=", } // 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 15b8e7e02d..1e585cd015 100644 --- a/daemon/algod/api/server/v2/generated/participating/private/routes.go +++ b/daemon/algod/api/server/v2/generated/participating/private/routes.go @@ -216,125 +216,125 @@ var swaggerSpec = []string{ "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", - "wpHC9Yfr/wkAAP//n/TLvvHVAAA=", + "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", + "XeFI4frD9f8EAAD//4XxG93x1QAA", } // 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 14745a0baf..ade11ffe95 100644 --- a/daemon/algod/api/server/v2/generated/participating/public/routes.go +++ b/daemon/algod/api/server/v2/generated/participating/public/routes.go @@ -235,134 +235,134 @@ var swaggerSpec = []string{ "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", - "AAA=", + "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", + "5AAA", } // GetSwagger returns the content of the embedded swagger specification file From 79d1e3a0cb8c41370938654ebcd016829b776abc Mon Sep 17 00:00:00 2001 From: Will Winder Date: Thu, 25 May 2023 09:29:14 -0400 Subject: [PATCH 115/125] community: Create CODE_OF_CONDUCT.md (#5408) --- CODE_OF_CONDUCT.md | 128 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 CODE_OF_CONDUCT.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000000..966aa2cf9a --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,128 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +conduct@algorand.com. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. From e7e76fcbaf127ac99f702dee1be1a96ac7b22de2 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Thu, 25 May 2023 09:45:30 -0500 Subject: [PATCH 116/125] ci: make tidy and diffutils for macos --- Makefile | 18 +++++++++++++++++- scripts/configure_dev.sh | 1 + scripts/get_golang_version.sh | 2 +- scripts/travis/codegen_verification.sh | 11 +++++++++++ 4 files changed, 30 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index d17855e6dc..2d179a952f 100644 --- a/Makefile +++ b/Makefile @@ -13,6 +13,13 @@ ARCH := $(shell ./scripts/archtype.sh) OS_TYPE := $(shell ./scripts/ostype.sh) S3_RELEASE_BUCKET = $$S3_RELEASE_BUCKET +GOLANG_VERSIONS := $(shell ./scripts/get_golang_version.sh all) +GOLANG_VERSION_BUILD := $(firstword $(GOLANG_VERSIONS)) +GOLANG_VERSION_SUPPORT := $(lastword $(GOLANG_VERSIONS)) +GOLANG_VERSION_BUILD_MAJOR := $(shell echo $(GOLANG_VERSION_BUILD) | cut -d'.' -f1,2) +CURRENT_GO_VERSION := $(shell go version | cut -d " " -f 3 | tr -d 'go') +CURRENT_GO_VERSION_MAJOR := $(shell echo $(CURRENT_GO_VERSION) | cut -d'.' -f1,2) + # If build number already set, use it - to ensure same build number across multiple platforms being built BUILDNUMBER ?= $(shell ./scripts/compute_build_number.sh) FULLBUILDNUMBER ?= $(shell ./scripts/compute_build_number.sh -f) @@ -101,10 +108,19 @@ fix: build lint: deps $(GOPATH1)/bin/golangci-lint run -c .golangci.yml +check_go_version: + @if [ $(CURRENT_GO_VERSION_MAJOR) != $(GOLANG_VERSION_BUILD_MAJOR) ]; then \ + echo "Wrong major version of Go installed ($(CURRENT_GO_VERSION_MAJOR)). Please use $(GOLANG_VERSION_BUILD_MAJOR)"; \ + exit 1; \ + fi + +tidy: check_go_version + go mod tidy -compat=$(GOLANG_VERSION_SUPPORT) + check_shell: find . -type f -name "*.sh" -exec shellcheck {} + -sanity: fix lint fmt +sanity: fix lint fmt tidy cover: go test $(GOTAGS) -coverprofile=cover.out $(UNIT_TEST_SOURCES) diff --git a/scripts/configure_dev.sh b/scripts/configure_dev.sh index c1190bf5bd..df28dd5cc2 100755 --- a/scripts/configure_dev.sh +++ b/scripts/configure_dev.sh @@ -86,6 +86,7 @@ elif [ "${OS}" = "darwin" ]; then install_or_upgrade automake install_or_upgrade python3 install_or_upgrade lnav + install_or_upgrade diffutils lnav -i "$SCRIPTPATH/algorand_node_log.json" fi elif [ "${OS}" = "windows" ]; then diff --git a/scripts/get_golang_version.sh b/scripts/get_golang_version.sh index 4e3525a548..7036e716ae 100755 --- a/scripts/get_golang_version.sh +++ b/scripts/get_golang_version.sh @@ -4,7 +4,7 @@ # and parsed as an array to check against the system's golang version depending # upon the context in which the project is being built. # -# "dev" is to be used to satisfy the minium requirement we have to successfully +# "dev" is to be used to satisfy the minimum requirement we have to successfully # build the project. # # The default is to return the pinned version needed for our production builds. diff --git a/scripts/travis/codegen_verification.sh b/scripts/travis/codegen_verification.sh index 9ac11f1724..356b8444e8 100755 --- a/scripts/travis/codegen_verification.sh +++ b/scripts/travis/codegen_verification.sh @@ -61,5 +61,16 @@ else echo Enlistment is clean fi +echo Checking Tidiness... +make tidy +if [[ -n $(git status --porcelain) ]]; then + echo Dirty after go mod tidy - did you forget to run make tidy? + git status -s + git --no-pager diff + exit 1 +else + echo All tidy +fi + # test binary compatibility "${SCRIPTPATH}/../../test/platform/test_linux_amd64_compatibility.sh" From a10efe52cbbd1ca63ac5f3cdf7f2940f0620fb47 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Fri, 26 May 2023 10:16:38 -0400 Subject: [PATCH 117/125] AVM: Simplify conversion and fix a spurious complaint from static analysis (#5421) --- data/transactions/logic/debugger.go | 27 ++++++++---------------- data/transactions/logic/debugger_test.go | 18 ---------------- data/transactions/logic/eval.go | 2 +- 3 files changed, 10 insertions(+), 37 deletions(-) diff --git a/data/transactions/logic/debugger.go b/data/transactions/logic/debugger.go index 0d62b8dc84..bc10ca79dc 100644 --- a/data/transactions/logic/debugger.go +++ b/data/transactions/logic/debugger.go @@ -184,7 +184,7 @@ func makeDebugState(cx *EvalContext) *DebugState { if err != nil { sv = stackValue{Bytes: []byte(err.Error())} } - globals[fs.field] = stackValueToTealValue(&sv) + globals[fs.field] = sv.toEncodedTealValue() } ds.Globals = globals @@ -244,22 +244,13 @@ func (d *DebugState) PCToLine(pc int) int { return len(strings.Split(d.Disassembly[:offset], "\n")) - one } -func stackValueToTealValue(sv *stackValue) basics.TealValue { - tv := sv.toTealValue() - return basics.TealValue{ - Type: tv.Type, - Bytes: base64.StdEncoding.EncodeToString([]byte(tv.Bytes)), - Uint: tv.Uint, - } -} - -// valueDeltaToValueDelta converts delta's bytes to base64 in a new struct -func valueDeltaToValueDelta(vd *basics.ValueDelta) basics.ValueDelta { - return basics.ValueDelta{ - Action: vd.Action, - Bytes: base64.StdEncoding.EncodeToString([]byte(vd.Bytes)), - Uint: vd.Uint, +// toEncodedTealValue converts stackValue to basics.TealValue, with the Bytes +// field b64 encoded, so it is suitable for conversion to JSON. +func (sv stackValue) toEncodedTealValue() basics.TealValue { + if sv.avmType() == avmBytes { + return basics.TealValue{Type: basics.TealBytesType, Bytes: base64.StdEncoding.EncodeToString(sv.Bytes)} } + return basics.TealValue{Type: basics.TealUintType, Uint: sv.Uint} } // parseCallStack initializes an array of CallFrame objects from the raw @@ -296,12 +287,12 @@ func (a *debuggerEvalTracerAdaptor) refreshDebugState(cx *EvalContext, evalError stack := make([]basics.TealValue, len(cx.stack)) for i, sv := range cx.stack { - stack[i] = stackValueToTealValue(&sv) + stack[i] = sv.toEncodedTealValue() } scratch := make([]basics.TealValue, len(cx.scratch)) for i, sv := range cx.scratch { - scratch[i] = stackValueToTealValue(&sv) + scratch[i] = sv.toEncodedTealValue() } ds.Stack = stack diff --git a/data/transactions/logic/debugger_test.go b/data/transactions/logic/debugger_test.go index 1303e57131..283bcaa6df 100644 --- a/data/transactions/logic/debugger_test.go +++ b/data/transactions/logic/debugger_test.go @@ -17,10 +17,8 @@ package logic import ( - "encoding/base64" "testing" - "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/require" ) @@ -63,22 +61,6 @@ func TestLineToPC(t *testing.T) { require.Equal(t, 0, pc) } -func TestValueDeltaToValueDelta(t *testing.T) { - partitiontest.PartitionTest(t) - t.Parallel() - - vDelta := basics.ValueDelta{ - Action: basics.SetUintAction, - Bytes: "some string", - Uint: uint64(0xffffffff), - } - ans := valueDeltaToValueDelta(&vDelta) - require.Equal(t, vDelta.Action, ans.Action) - require.NotEqual(t, vDelta.Bytes, ans.Bytes) - require.Equal(t, base64.StdEncoding.EncodeToString([]byte(vDelta.Bytes)), ans.Bytes) - require.Equal(t, vDelta.Uint, ans.Uint) -} - const testCallStackProgram string = `intcblock 1 callsub label1 intc_0 diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index 2cd78c40ff..4d24261915 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -160,7 +160,7 @@ func (sv stackValue) string(limit int) (string, error) { return string(sv.Bytes), nil } -func (sv stackValue) toTealValue() (tv basics.TealValue) { +func (sv stackValue) toTealValue() basics.TealValue { if sv.avmType() == avmBytes { return basics.TealValue{Type: basics.TealBytesType, Bytes: string(sv.Bytes)} } From 56b7e82a08d9acca1d47ef0c0426121db6049e9b Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Fri, 26 May 2023 13:42:45 -0400 Subject: [PATCH 118/125] TEAL: Add columns to assembly error messages. (#5402) --- .golangci.yml | 8 - cmd/opdoc/opdoc.go | 2 +- data/transactions/logic/assembler.go | 880 ++++++++++-------- data/transactions/logic/assembler_test.go | 572 +++++++----- .../transactions/logic/backwardCompat_test.go | 30 +- data/transactions/logic/box_test.go | 6 +- data/transactions/logic/evalAppTxn_test.go | 6 +- data/transactions/logic/evalStateful_test.go | 24 +- data/transactions/logic/eval_test.go | 116 +-- data/transactions/logic/export_test.go | 5 +- data/transactions/logic/fields_test.go | 4 +- data/transactions/logic/frames_test.go | 20 +- data/transactions/logic/resources_test.go | 108 +-- 13 files changed, 963 insertions(+), 818 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 44fba83682..40299509bf 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -31,11 +31,6 @@ linters-settings: require-explanation: true errcheck: exclude-functions: - # data/transactions/logic/assembler.go uses ops.error, warn, to append log messages: OK to ignore for this case - - (*github.com/algorand/go-algorand/data/transactions/logic.OpStream).errorf - - (*github.com/algorand/go-algorand/data/transactions/logic.OpStream).error - - (*github.com/algorand/go-algorand/data/transactions/logic.OpStream).warnf - - (*github.com/algorand/go-algorand/data/transactions/logic.OpStream).warn # We do this 121 times and never check the error. - (*github.com/spf13/cobra.Command).MarkFlagRequired govet: @@ -62,9 +57,6 @@ linters-settings: - (github.com/algorand/go-algorand/logging.Logger).Error - (github.com/algorand/go-algorand/logging.Logger).Fatal - (github.com/algorand/go-algorand/logging.Logger).Panic - - (github.com/algorand/go-algorand/data/transactions/logic.OpStream).warnf - - (github.com/algorand/go-algorand/data/transactions/logic.OpStream).errorf - - (github.com/algorand/go-algorand/data/transactions/logic.OpStream).lineErrorf - (github.com/algorand/go-algorand/cmd/goal/main).reportInfof - (github.com/algorand/go-algorand/cmd/goal/main).reportInfoln - (github.com/algorand/go-algorand/cmd/goal/main).reportWarnf diff --git a/cmd/opdoc/opdoc.go b/cmd/opdoc/opdoc.go index acd110d36f..b5e039a2b8 100644 --- a/cmd/opdoc/opdoc.go +++ b/cmd/opdoc/opdoc.go @@ -91,7 +91,7 @@ func namedStackTypesMarkdown(out io.Writer, stackTypes []namedType) { for _, st := range stackTypes { fmt.Fprintf(out, "| %s | %s | %s |\n", st.Name, st.boundString(), st.AVMType) } - out.Write([]byte("\n")) + fmt.Fprintf(out, "\n") } func integerConstantsTableMarkdown(out io.Writer) { diff --git a/data/transactions/logic/assembler.go b/data/transactions/logic/assembler.go index 65f9f1dfb4..86719cb6b7 100644 --- a/data/transactions/logic/assembler.go +++ b/data/transactions/logic/assembler.go @@ -48,14 +48,13 @@ type Writer interface { } type labelReference struct { - sourceLine int - - // position of the label reference + // position (PC) of the label reference position int - label string + // token holding the label name (and line, column) + label token - // ending positions of the opcode containing the label reference. + // ending position of the opcode containing the label reference. offsetPosition int } @@ -108,7 +107,7 @@ func (ref intReference) length(ops *OpStream, assembled []byte) (int, error) { case opIntc: return 2, nil default: - return 0, ops.lineErrorf(ops.OffsetToLine[ref.position], "Unexpected op at intReference: %d", assembled[ref.position]) + return 0, errorLinef(ops.OffsetToLine[ref.position], "unexpected op at intReference: %d", assembled[ref.position]) } } @@ -177,7 +176,7 @@ func (ref byteReference) length(ops *OpStream, assembled []byte) (int, error) { case opBytec: return 2, nil default: - return 0, ops.lineErrorf(ops.OffsetToLine[ref.position], "Unexpected op at byteReference: %d", assembled[ref.position]) + return 0, errorLinef(ops.OffsetToLine[ref.position], "unexpected op at byteReference: %d", assembled[ref.position]) } } @@ -219,9 +218,9 @@ func (ref byteReference) makeNewReference(ops *OpStream, singleton bool, newInde type OpStream struct { Version uint64 Trace *strings.Builder - Warnings []error // informational warnings, shouldn't stop assembly - Errors []lineError // errors that should prevent final assembly - Program []byte // Final program bytes. Will stay nil if any errors + Warnings []error // informational warnings, shouldn't stop assembly + Errors []sourceError // errors that should prevent final assembly + Program []byte // Final program bytes. Will stay nil if any errors // Running bytes as they are assembled. jumps must be resolved // and cblocks added before these bytes become a legal program. @@ -258,7 +257,7 @@ type OpStream struct { // Need new copy for each opstream versionedPseudoOps map[string]map[int]OpSpec - macros map[string][]string + macros map[string][]token } // newOpStream constructs OpStream instances ready to invoke assemble. A new @@ -269,7 +268,7 @@ func newOpStream(version uint64) OpStream { OffsetToLine: make(map[int]int), typeTracking: true, Version: version, - macros: make(map[string][]string), + macros: make(map[string][]token), known: ProgramKnowledge{fp: -1}, } @@ -357,9 +356,10 @@ func (pgm *ProgramKnowledge) reset() { // createLabel inserts a label to point to the next instruction, reporting an // error for a duplicate. -func (ops *OpStream) createLabel(label string) { +func (ops *OpStream) createLabel(withColon token) { + label := strings.TrimSuffix(withColon.str, ":") if _, ok := ops.labels[label]; ok { - ops.errorf("duplicate label %#v", label) + ops.record(withColon.errorf("duplicate label %#v", label)) } ops.labels[label] = ops.pending.Len() ops.known.label() @@ -371,11 +371,11 @@ func (ops *OpStream) recordSourceLine() { } // referToLabel records an opcode label reference to resolve later -func (ops *OpStream) referToLabel(pc int, label string, offsetPosition int) { - ops.labelReferences = append(ops.labelReferences, labelReference{ops.sourceLine, pc, label, offsetPosition}) +func (ops *OpStream) referToLabel(pc int, label token, offsetPosition int) { + ops.labelReferences = append(ops.labelReferences, labelReference{pc, label, offsetPosition}) } -type refineFunc func(pgm *ProgramKnowledge, immediates []string) (StackTypes, StackTypes, error) +type refineFunc func(pgm *ProgramKnowledge, immediates []token) (StackTypes, StackTypes, error) // returns allows opcodes like `txn` to be specific about their return value // types, based on the field requested, rather than use Any as specified by @@ -396,8 +396,8 @@ func (ops *OpStream) returns(spec *OpSpec, replacement StackType) { panic(fmt.Sprintf("%+v", spec)) } -// Intc writes opcodes for loading a uint64 constant onto the stack. -func (ops *OpStream) Intc(constIndex uint) { +// writeIntc writes opcodes for loading a uint64 constant onto the stack. +func (ops *OpStream) writeIntc(constIndex uint) error { switch constIndex { case 0: ops.pending.WriteByte(OpsByName[ops.Version]["intc_0"].Opcode) @@ -409,20 +409,20 @@ func (ops *OpStream) Intc(constIndex uint) { ops.pending.WriteByte(OpsByName[ops.Version]["intc_3"].Opcode) default: if constIndex > 0xff { - ops.error("cannot have more than 256 int constants") + return errors.New("cannot have more than 256 int constants") } ops.pending.WriteByte(OpsByName[ops.Version]["intc"].Opcode) ops.pending.WriteByte(uint8(constIndex)) } if constIndex >= uint(len(ops.intc)) { - ops.errorf("intc %d is not defined", constIndex) - } else { - ops.trace("intc %d: %d", constIndex, ops.intc[constIndex]) + return fmt.Errorf("intc %d is not defined", constIndex) } + ops.trace("intc %d: %d", constIndex, ops.intc[constIndex]) + return nil } -// IntLiteral writes opcodes for loading a uint literal -func (ops *OpStream) IntLiteral(val uint64) { +// intLiteral writes opcodes for loading a uint literal +func (ops *OpStream) intLiteral(val uint64) error { ops.hasPseudoInt = true found := false @@ -437,7 +437,7 @@ func (ops *OpStream) IntLiteral(val uint64) { if !found { if ops.cntIntcBlock > 0 { - ops.errorf("int %d used without %d in intcblock", val, val) + return fmt.Errorf("value %d does not appear in existing intcblock", val) } constIndex = uint(len(ops.intc)) ops.intc = append(ops.intc, val) @@ -446,11 +446,11 @@ func (ops *OpStream) IntLiteral(val uint64) { value: val, position: ops.pending.Len(), }) - ops.Intc(constIndex) + return ops.writeIntc(constIndex) } -// Bytec writes opcodes for loading a []byte constant onto the stack. -func (ops *OpStream) Bytec(constIndex uint) { +// writeBytec writes opcodes for loading a []byte constant onto the stack. +func (ops *OpStream) writeBytec(constIndex uint) error { switch constIndex { case 0: ops.pending.WriteByte(OpsByName[ops.Version]["bytec_0"].Opcode) @@ -462,21 +462,21 @@ func (ops *OpStream) Bytec(constIndex uint) { ops.pending.WriteByte(OpsByName[ops.Version]["bytec_3"].Opcode) default: if constIndex > 0xff { - ops.error("cannot have more than 256 byte constants") + return errors.New("cannot have more than 256 byte constants") } ops.pending.WriteByte(OpsByName[ops.Version]["bytec"].Opcode) ops.pending.WriteByte(uint8(constIndex)) } if constIndex >= uint(len(ops.bytec)) { - ops.errorf("bytec %d is not defined", constIndex) - } else { - ops.trace("bytec %d %s", constIndex, hex.EncodeToString(ops.bytec[constIndex])) + return fmt.Errorf("bytec %d is not defined", constIndex) } + ops.trace("bytec %d %s", constIndex, hex.EncodeToString(ops.bytec[constIndex])) + return nil } -// ByteLiteral writes opcodes and data for loading a []byte literal +// byteLiteral writes opcodes and data for loading a []byte literal // Values are accumulated so that they can be put into a bytecblock -func (ops *OpStream) ByteLiteral(val []byte) { +func (ops *OpStream) byteLiteral(val []byte) error { ops.hasPseudoByte = true found := false @@ -490,7 +490,7 @@ func (ops *OpStream) ByteLiteral(val []byte) { } if !found { if ops.cntBytecBlock > 0 { - ops.errorf("byte/addr/method used without value in bytecblock") + return fmt.Errorf("value 0x%x does not appear in existing bytecblock", val) } constIndex = uint(len(ops.bytec)) ops.bytec = append(ops.bytec, val) @@ -499,21 +499,21 @@ func (ops *OpStream) ByteLiteral(val []byte) { value: val, position: ops.pending.Len(), }) - ops.Bytec(constIndex) + return ops.writeBytec(constIndex) } -func asmInt(ops *OpStream, spec *OpSpec, args []string) error { - if len(args) != 1 { - return ops.errorf("%s needs one immediate argument, was given %d", spec.Name, len(args)) +func asmInt(ops *OpStream, spec *OpSpec, mnemonic token, args []token) *sourceError { + if err := ops.checkArgCount(spec.Name, mnemonic, args, 1); err != nil { + return err } // After backBranchEnabledVersion, control flow is confusing, so if there's // a manual cblock, use push instead of trying to use what's given. if ops.cntIntcBlock > 0 && ops.Version >= backBranchEnabledVersion { // We don't understand control-flow, so use pushint - ops.warnf("int %s used with explicit intcblock. must pushint", args[0]) + ops.warn(args[0], "int %s used with explicit intcblock. must pushint", args[0].str) pushint := OpsByName[ops.Version]["pushint"] - return asmPushInt(ops, &pushint, args) + return asmPushInt(ops, &pushint, mnemonic, args) } // There are no backjumps, but there are multiple cblocks. Maybe one is @@ -521,65 +521,71 @@ func asmInt(ops *OpStream, spec *OpSpec, args []string) error { if ops.cntIntcBlock > 1 { pushint, ok := OpsByName[ops.Version]["pushint"] if ok { - return asmPushInt(ops, &pushint, args) + return asmPushInt(ops, &pushint, mnemonic, args) } - return ops.errorf("int %s used with manual intcblocks. Use intc.", args[0]) + return mnemonic.errorf("int %s used with manual intcblocks. Use intc.", args[0].str) } // In both of the above clauses, we _could_ track whether a particular // intcblock dominates the current instruction. If so, we could use it. // check txn type constants - i, ok := txnTypeMap[args[0]] - if ok { - ops.IntLiteral(i) - return nil + i, ok := txnTypeMap[args[0].str] + if !ok { + // check OnCompletion constants + i, ok = onCompletionMap[args[0].str] } - // check OnCompletion constants - oc, isOCStr := onCompletionMap[args[0]] - if isOCStr { - ops.IntLiteral(oc) - return nil + if !ok { + val, err := strconv.ParseUint(args[0].str, 0, 64) + if err != nil { + return args[0].errorf("unable to parse %#v as integer", args[0].str) + } + i = val } - val, err := strconv.ParseUint(args[0], 0, 64) + err := ops.intLiteral(i) if err != nil { - return ops.error(err) + return args[0].error(err) } - ops.IntLiteral(val) return nil } // Explicit invocation of const lookup and push -func asmIntC(ops *OpStream, spec *OpSpec, args []string) error { - if len(args) != 1 { - return ops.errorf("%s needs one immediate argument, was given %d", spec.Name, len(args)) +func asmIntC(ops *OpStream, spec *OpSpec, mnemonic token, args []token) *sourceError { + if err := ops.checkArgCount(spec.Name, mnemonic, args, 1); err != nil { + return err + } + constIndex, err := byteImm(args[0].str, "constant") + if err != nil { + return args[0].error(err) } - constIndex, err := byteImm(args[0], "constant") + err = ops.writeIntc(uint(constIndex)) if err != nil { - return ops.error(err) + return args[0].error(err) } - ops.Intc(uint(constIndex)) return nil } -func asmByteC(ops *OpStream, spec *OpSpec, args []string) error { - if len(args) != 1 { - return ops.errorf("%s needs one immediate argument, was given %d", spec.Name, len(args)) +func asmByteC(ops *OpStream, spec *OpSpec, mnemonic token, args []token) *sourceError { + if err := ops.checkArgCount(spec.Name, mnemonic, args, 1); err != nil { + return err + } + constIndex, err := byteImm(args[0].str, "constant") + if err != nil { + return args[0].error(err) } - constIndex, err := byteImm(args[0], "constant") + err = ops.writeBytec(uint(constIndex)) if err != nil { - return ops.error(err) + return args[0].error(err) } - ops.Bytec(uint(constIndex)) return nil } -func asmPushInt(ops *OpStream, spec *OpSpec, args []string) error { - if len(args) != 1 { - return ops.errorf("%s needs one immediate argument, was given %d", spec.Name, len(args)) +func asmPushInt(ops *OpStream, spec *OpSpec, mnemonic token, args []token) *sourceError { + if err := ops.checkArgCount(spec.Name, mnemonic, args, 1); err != nil { + return err } - val, err := strconv.ParseUint(args[0], 0, 64) + val, err := strconv.ParseUint(args[0].str, 0, 64) if err != nil { - return ops.error(err) + return args[0].errorf("unable to parse %#v as integer", args[0].str) } ops.pending.WriteByte(spec.Opcode) var scratch [binary.MaxVarintLen64]byte @@ -588,22 +594,24 @@ func asmPushInt(ops *OpStream, spec *OpSpec, args []string) error { return nil } -func asmPushInts(ops *OpStream, spec *OpSpec, args []string) error { +func asmPushInts(ops *OpStream, spec *OpSpec, mnemonic token, args []token) *sourceError { ops.pending.WriteByte(spec.Opcode) _, err := asmIntImmArgs(ops, args) return err } -func asmPushBytes(ops *OpStream, spec *OpSpec, args []string) error { +func asmPushBytes(ops *OpStream, spec *OpSpec, mnemonic token, args []token) *sourceError { + // asmPushBytes is sometimes used to assemble the "byte" mnemonic, so use + // mnemonic.str instead of spec.Name when reporting errors. if len(args) == 0 { - return ops.errorf("%s needs byte literal argument", spec.Name) + return mnemonic.errorAfterf("%s needs byte literal argument", mnemonic.str) } val, consumed, err := parseBinaryArgs(args) if err != nil { - return ops.error(err) + return args[consumed].errorf("%s %w", mnemonic.str, err) } if len(args) != consumed { - return ops.errorf("%s with extraneous argument", spec.Name) + return args[consumed].errorf("%s with extraneous argument", mnemonic.str) } ops.pending.WriteByte(spec.Opcode) var scratch [binary.MaxVarintLen64]byte @@ -613,9 +621,9 @@ func asmPushBytes(ops *OpStream, spec *OpSpec, args []string) error { return nil } -func asmPushBytess(ops *OpStream, spec *OpSpec, args []string) error { +func asmPushBytess(ops *OpStream, spec *OpSpec, mnemonic token, args []token) *sourceError { ops.pending.WriteByte(spec.Opcode) - _, err := asmByteImmArgs(ops, args) + _, err := asmByteImmArgs(ops, spec, args) return err } @@ -632,66 +640,73 @@ func base32DecodeAnyPadding(x string) (val []byte, err error) { return } -func parseBinaryArgs(args []string) (val []byte, consumed int, err error) { - arg := args[0] +// parseBinaryArgs parses a byte literal argument. It returns the argument, +// interpetted into raw bytes, and the number of tokens consumed. +func parseBinaryArgs(args []token) ([]byte, int, error) { + arg := args[0].str if strings.HasPrefix(arg, "base32(") || strings.HasPrefix(arg, "b32(") { open := strings.IndexRune(arg, '(') close := strings.IndexRune(arg, ')') if close == -1 { - err = errors.New("byte base32 arg lacks close paren") - return + return nil, 0, fmt.Errorf("argument %s lacks closing parenthesis", arg) + } + if close != len(arg)-1 { + return nil, 0, fmt.Errorf("argument %s must end at first closing parenthesis", arg) } - val, err = base32DecodeAnyPadding(arg[open+1 : close]) + val, err := base32DecodeAnyPadding(arg[open+1 : close]) if err != nil { - return + if cie, ok := err.(base32.CorruptInputError); ok { + return nil, 0, base32.CorruptInputError(int64(cie) + int64(open) + 1) + } + return nil, 0, err } - consumed = 1 + return val, 1, nil } else if strings.HasPrefix(arg, "base64(") || strings.HasPrefix(arg, "b64(") { open := strings.IndexRune(arg, '(') close := strings.IndexRune(arg, ')') if close == -1 { - err = errors.New("byte base64 arg lacks close paren") - return + return nil, 0, fmt.Errorf("argument %s lacks closing parenthesis", arg) } - val, err = base64.StdEncoding.DecodeString(arg[open+1 : close]) + if close != len(arg)-1 { + return nil, 0, fmt.Errorf("argument %s must end at first closing parenthesis", arg) + } + val, err := base64.StdEncoding.DecodeString(arg[open+1 : close]) if err != nil { - return + return nil, 0, err } - consumed = 1 + return val, 1, nil } else if strings.HasPrefix(arg, "0x") { - val, err = hex.DecodeString(arg[2:]) + val, err := hex.DecodeString(arg[2:]) if err != nil { - return + return nil, 0, err } - consumed = 1 + return val, 1, nil } else if arg == "base32" || arg == "b32" { if len(args) < 2 { - err = fmt.Errorf("need literal after 'byte %s'", arg) - return + return nil, 0, fmt.Errorf("%s needs byte literal argument", arg) } - val, err = base32DecodeAnyPadding(args[1]) + val, err := base32DecodeAnyPadding(args[1].str) if err != nil { - return + return nil, 1, err // return 1, so that the right token is blamed } - consumed = 2 + return val, 2, nil } else if arg == "base64" || arg == "b64" { if len(args) < 2 { - err = fmt.Errorf("need literal after 'byte %s'", arg) - return + return nil, 0, fmt.Errorf("%s needs byte literal argument", arg) } - val, err = base64.StdEncoding.DecodeString(args[1]) + val, err := base64.StdEncoding.DecodeString(args[1].str) if err != nil { - return + return nil, 1, err } - consumed = 2 + return val, 2, nil } else if len(arg) > 1 && arg[0] == '"' && arg[len(arg)-1] == '"' { - val, err = parseStringLiteral(arg) - consumed = 1 - } else { - err = fmt.Errorf("byte arg did not parse: %v", arg) - return + val, err := parseStringLiteral(arg) + if err != nil { + return nil, 0, err + } + return val, 1, err } - return + return nil, 0, fmt.Errorf("arg did not parse: %v", arg) } func parseStringLiteral(input string) (result []byte, err error) { @@ -736,7 +751,7 @@ func parseStringLiteral(input string) (result []byte, err error) { pos++ continue default: - return nil, fmt.Errorf("invalid escape seq \\%c", char) + return nil, fmt.Errorf("invalid escape sequence \\%c", char) } } if hexSeq { @@ -766,28 +781,29 @@ func parseStringLiteral(input string) (result []byte, err error) { // byte {base64,b64,base32,b32} ... // byte 0x.... // byte "this is a string\n" -func asmByte(ops *OpStream, spec *OpSpec, args []string) error { +func asmByte(ops *OpStream, spec *OpSpec, mnemonic token, args []token) *sourceError { if len(args) == 0 { - return ops.errorf("%s needs byte literal argument", spec.Name) + return mnemonic.errorAfterf("%s needs byte literal argument", spec.Name) } // After backBranchEnabledVersion, control flow is confusing, so if there's // a manual cblock, use push instead of trying to use what's given. if ops.cntBytecBlock > 0 && ops.Version >= backBranchEnabledVersion { // We don't understand control-flow, so use pushbytes - ops.warnf("byte %s used with explicit bytecblock. must pushbytes", args[0]) - pushbytes := OpsByName[ops.Version]["pushbytes"] - return asmPushBytes(ops, &pushbytes, args) + ops.warn(args[0], "byte %s used with explicit bytecblock. must pushbytes", args[0].str) + pushbytes := OpsByName[ops.Version]["pushbytes"] // make sure pushbytes opcode is written + return asmPushBytes(ops, &pushbytes, mnemonic, args) } // There are no backjumps, but there are multiple cblocks. Maybe one is // conditional skipped. Too confusing. if ops.cntBytecBlock > 1 { + // use pushbytes opcode if available pushbytes, ok := OpsByName[ops.Version]["pushbytes"] if ok { - return asmPushBytes(ops, &pushbytes, args) + return asmPushBytes(ops, &pushbytes, mnemonic, args) } - return ops.errorf("byte %s used with manual bytecblocks. Use bytec.", args[0]) + return args[0].errorf("byte %s used with manual bytecblocks. Use bytec.", args[0].str) } // In both of the above clauses, we _could_ track whether a particular @@ -795,49 +811,55 @@ func asmByte(ops *OpStream, spec *OpSpec, args []string) error { val, consumed, err := parseBinaryArgs(args) if err != nil { - return ops.error(err) + return args[consumed].errorf("%s %w", spec.Name, err) } if len(args) != consumed { - return ops.errorf("%s with extraneous argument", spec.Name) + return args[consumed].errorf("%s with extraneous argument", spec.Name) + } + err = ops.byteLiteral(val) + if err != nil { + return args[0].error(err) } - ops.ByteLiteral(val) return nil } // method "add(uint64,uint64)uint64" -func asmMethod(ops *OpStream, spec *OpSpec, args []string) error { - if len(args) == 0 { - return ops.error("method requires a literal argument") +func asmMethod(ops *OpStream, spec *OpSpec, mnemonic token, args []token) *sourceError { + if err := ops.checkArgCount(spec.Name, mnemonic, args, 1); err != nil { + return err } - arg := args[0] + arg := args[0].str if len(arg) > 1 && arg[0] == '"' && arg[len(arg)-1] == '"' { methodSig, err := parseStringLiteral(arg) if err != nil { - return ops.error(err) + return args[0].error(err) } methodSigStr := string(methodSig) err = abi.VerifyMethodSignature(methodSigStr) if err != nil { // Warn if an invalid signature is used. Don't return an error, since the ABI is not // governed by the core protocol, so there may be changes to it that we don't know about - ops.warnf("Invalid ARC-4 ABI method signature for method op: %s", err.Error()) + ops.warn(args[0], "invalid ARC-4 ABI method signature for method op: %w", err) } hash := sha512.Sum512_256(methodSig) - ops.ByteLiteral(hash[0:4]) + err = ops.byteLiteral(hash[:4]) + if err != nil { + return args[0].error(err) + } return nil } - return ops.error("Unable to parse method signature") + return args[0].errorf("unable to parse method signature") } -func asmIntImmArgs(ops *OpStream, args []string) ([]uint64, error) { +func asmIntImmArgs(ops *OpStream, args []token) ([]uint64, *sourceError) { ivals := make([]uint64, len(args)) var scratch [binary.MaxVarintLen64]byte l := binary.PutUvarint(scratch[:], uint64(len(args))) ops.pending.Write(scratch[:l]) for i, xs := range args { - cu, err := strconv.ParseUint(xs, 0, 64) + cu, err := strconv.ParseUint(xs.str, 0, 64) if err != nil { - ops.error(err) + ops.record(xs.error(err)) } l = binary.PutUvarint(scratch[:], cu) ops.pending.Write(scratch[:l]) @@ -847,7 +869,7 @@ func asmIntImmArgs(ops *OpStream, args []string) ([]uint64, error) { return ivals, nil } -func asmIntCBlock(ops *OpStream, spec *OpSpec, args []string) error { +func asmIntCBlock(ops *OpStream, spec *OpSpec, mnemonic token, args []token) *sourceError { ops.pending.WriteByte(spec.Opcode) ivals, err := asmIntImmArgs(ops, args) if err != nil { @@ -857,7 +879,7 @@ func asmIntCBlock(ops *OpStream, spec *OpSpec, args []string) error { // If we previously processed an `int`, we thought we could insert our // own intcblock, but now we see a manual one. if ops.hasPseudoInt { - ops.error("intcblock following int") + return mnemonic.errorf("intcblock following int") } ops.intcRefs = nil ops.intc = ivals @@ -867,7 +889,7 @@ func asmIntCBlock(ops *OpStream, spec *OpSpec, args []string) error { return nil } -func asmByteImmArgs(ops *OpStream, args []string) ([][]byte, error) { +func asmByteImmArgs(ops *OpStream, spec *OpSpec, args []token) ([][]byte, *sourceError) { bvals := make([][]byte, 0, len(args)) rest := args for len(rest) > 0 { @@ -877,7 +899,7 @@ func asmByteImmArgs(ops *OpStream, args []string) ([][]byte, error) { // intcblock, but parseBinaryArgs would have // to return a useful consumed value even in // the face of errors. Hard. - return nil, ops.error(err) + return nil, rest[0].errorf("%s %w", spec.Name, err) } bvals = append(bvals, val) rest = rest[consumed:] @@ -894,9 +916,9 @@ func asmByteImmArgs(ops *OpStream, args []string) ([][]byte, error) { return bvals, nil } -func asmByteCBlock(ops *OpStream, spec *OpSpec, args []string) error { +func asmByteCBlock(ops *OpStream, spec *OpSpec, mnemonic token, args []token) *sourceError { ops.pending.WriteByte(spec.Opcode) - bvals, err := asmByteImmArgs(ops, args) + bvals, err := asmByteImmArgs(ops, spec, args) if err != nil { return err } @@ -905,7 +927,7 @@ func asmByteCBlock(ops *OpStream, spec *OpSpec, args []string) error { // If we previously processed a pseudo `byte`, we thought we could // insert our own bytecblock, but now we see a manual one. if ops.hasPseudoByte { - ops.error("bytecblock following byte/addr/method") + return mnemonic.errorf("bytecblock following byte/addr/method") } ops.bytecRefs = nil ops.bytec = bvals @@ -916,25 +938,28 @@ func asmByteCBlock(ops *OpStream, spec *OpSpec, args []string) error { // addr A1EU... // parses base32-with-checksum account address strings into a byte literal -func asmAddr(ops *OpStream, spec *OpSpec, args []string) error { - if len(args) != 1 { - return ops.errorf("%s needs one immediate argument, was given %d", spec.Name, len(args)) +func asmAddr(ops *OpStream, spec *OpSpec, mnemonic token, args []token) *sourceError { + if err := ops.checkArgCount(spec.Name, mnemonic, args, 1); err != nil { + return err + } + addr, err := basics.UnmarshalChecksumAddress(args[0].str) + if err != nil { + return args[0].error(err) } - addr, err := basics.UnmarshalChecksumAddress(args[0]) + err = ops.byteLiteral(addr[:]) if err != nil { - return ops.error(err) + return args[0].error(err) } - ops.ByteLiteral(addr[:]) return nil } -func asmArg(ops *OpStream, spec *OpSpec, args []string) error { - if len(args) != 1 { - return ops.errorf("%s needs one immediate argument, was given %d", spec.Name, len(args)) +func asmArg(ops *OpStream, spec *OpSpec, mnemonic token, args []token) *sourceError { + if err := ops.checkArgCount(spec.Name, mnemonic, args, 1); err != nil { + return err } - val, err := byteImm(args[0], "argument") + val, err := byteImm(args[0].str, "argument") if err != nil { - return ops.error(err) + return args[0].error(err) } altSpec := *spec if val < 4 { @@ -948,14 +973,14 @@ func asmArg(ops *OpStream, spec *OpSpec, args []string) error { case 3: altSpec = OpsByName[ops.Version]["arg_3"] } - args = []string{} + args = []token{} } - return asmDefault(ops, &altSpec, args) + return asmDefault(ops, &altSpec, mnemonic, args) } -func asmBranch(ops *OpStream, spec *OpSpec, args []string) error { - if len(args) != 1 { - return ops.errorf("%s needs a single label argument", spec.Name) +func asmBranch(ops *OpStream, spec *OpSpec, mnemonic token, args []token) *sourceError { + if err := ops.checkArgCount(spec.Name, mnemonic, args, 1); err != nil { + return err } ops.referToLabel(ops.pending.Len()+1, args[0], ops.pending.Len()+spec.Size) @@ -966,10 +991,10 @@ func asmBranch(ops *OpStream, spec *OpSpec, args []string) error { return nil } -func asmSwitch(ops *OpStream, spec *OpSpec, args []string) error { +func asmSwitch(ops *OpStream, spec *OpSpec, mnemonic token, args []token) *sourceError { numOffsets := len(args) if numOffsets > math.MaxUint8 { - return ops.errorf("%s cannot take more than 255 labels", spec.Name) + return args[math.MaxUint8].errorf("%s cannot take more than 255 labels", spec.Name) } ops.pending.WriteByte(spec.Opcode) ops.pending.WriteByte(byte(numOffsets)) @@ -983,16 +1008,16 @@ func asmSwitch(ops *OpStream, spec *OpSpec, args []string) error { return nil } -func asmSubstring(ops *OpStream, spec *OpSpec, args []string) error { - err := asmDefault(ops, spec, args) +func asmSubstring(ops *OpStream, spec *OpSpec, mnemonic token, args []token) *sourceError { + err := asmDefault(ops, spec, mnemonic, args) if err != nil { return err } // Having run asmDefault, only need to check extra constraints. - start, _ := strconv.ParseUint(args[0], 0, 64) - end, _ := strconv.ParseUint(args[1], 0, 64) + start, _ := strconv.ParseUint(args[0].str, 0, 64) + end, _ := strconv.ParseUint(args[1].str, 0, 64) if end < start { - return ops.error("substring end is before start") + return args[0].errorf("substring end is before start") } return nil } @@ -1016,58 +1041,74 @@ func int8Imm(value string, label string) (byte, error) { return byte(res), err } -func asmItxn(ops *OpStream, spec *OpSpec, args []string) error { +func asmItxn(ops *OpStream, spec *OpSpec, mnemonic token, args []token) *sourceError { if len(args) == 1 { - return asmDefault(ops, spec, args) + return asmDefault(ops, spec, mnemonic, args) } if len(args) == 2 { itxna := OpsByName[ops.Version]["itxna"] - return asmDefault(ops, &itxna, args) + return asmDefault(ops, &itxna, mnemonic, args) } - return ops.errorf("%s expects 1 or 2 immediate arguments", spec.Name) + return mnemonic.errorf("%s expects 1 or 2 immediate arguments", spec.Name) } // asmGitxn substitutes gitna's spec if the are 3 args -func asmGitxn(ops *OpStream, spec *OpSpec, args []string) error { +func asmGitxn(ops *OpStream, spec *OpSpec, mnemonic token, args []token) *sourceError { if len(args) == 2 { - return asmDefault(ops, spec, args) + return asmDefault(ops, spec, mnemonic, args) } if len(args) == 3 { itxna := OpsByName[ops.Version]["gitxna"] - return asmDefault(ops, &itxna, args) + return asmDefault(ops, &itxna, mnemonic, args) } - return ops.errorf("%s expects 2 or 3 immediate arguments", spec.Name) + return mnemonic.errorf("%s expects 2 or 3 immediate arguments", spec.Name) } -func asmItxnField(ops *OpStream, spec *OpSpec, args []string) error { - if len(args) != 1 { - return ops.errorf("%s expects one argument", spec.Name) +func asmItxnField(ops *OpStream, spec *OpSpec, mnemonic token, args []token) *sourceError { + if err := ops.checkArgCount(spec.Name, mnemonic, args, 1); err != nil { + return err } - fs, ok := txnFieldSpecByName[args[0]] + fs, ok := txnFieldSpecByName[args[0].str] if !ok { - return ops.errorf("%s unknown field: %#v", spec.Name, args[0]) + return args[0].errorf("%s unknown field: %#v", spec.Name, args[0].str) } if fs.itxVersion == 0 { - return ops.errorf("%s %#v is not allowed.", spec.Name, args[0]) + return args[0].errorf("%s %#v is not allowed.", spec.Name, args[0].str) } if fs.itxVersion > ops.Version { - return ops.errorf("%s %s field was introduced in v%d. Missed #pragma version?", spec.Name, args[0], fs.itxVersion) + return args[0].errorf("%s %s field was introduced in v%d. Missed #pragma version?", spec.Name, args[0].str, fs.itxVersion) } ops.pending.WriteByte(spec.Opcode) ops.pending.WriteByte(fs.Field()) return nil } -type asmFunc func(*OpStream, *OpSpec, []string) error +type asmFunc func(*OpStream, *OpSpec, token, []token) *sourceError -// Basic assembly. Any extra bytes of opcode are encoded as byte immediates. -func asmDefault(ops *OpStream, spec *OpSpec, args []string) error { - expected := len(spec.OpDetails.Immediates) - if len(args) != expected { +func (ops *OpStream) checkArgCount(name string, mnemonic token, args []token, expected int) *sourceError { + offered := len(args) + if offered != expected { + all := make([]token, len(args)+1) + all[0] = mnemonic + copy(all[1:], args) + line := all[offered].line + col := all[offered].col + len(all[offered].str) // end of last arg (or mnemonic) + if offered > expected { + line = all[expected+1].line + col = all[expected+1].col // start of first extra arg + } if expected == 1 { - return ops.errorf("%s expects 1 immediate argument", spec.Name) + return &sourceError{line, col, fmt.Errorf("%s expects 1 immediate argument", name)} } - return ops.errorf("%s expects %d immediate arguments", spec.Name, expected) + return &sourceError{line, col, fmt.Errorf("%s expects %d immediate arguments", name, expected)} + } + return nil +} + +// Basic assembly. Any extra bytes of opcode are encoded as byte immediates. +func asmDefault(ops *OpStream, spec *OpSpec, mnemonic token, args []token) *sourceError { + if err := ops.checkArgCount(spec.Name, mnemonic, args, len(spec.OpDetails.Immediates)); err != nil { + return err } ops.pending.WriteByte(spec.Opcode) for i, imm := range spec.OpDetails.Immediates { @@ -1077,9 +1118,9 @@ func asmDefault(ops *OpStream, spec *OpSpec, args []string) error { switch imm.kind { case immByte: if imm.Group != nil { - fs, ok := imm.Group.SpecByName(args[i]) + fs, ok := imm.Group.SpecByName(args[i].str) if !ok { - _, err := byteImm(args[i], "") + _, err := byteImm(args[i].str, "") if err == nil { // User supplied a uint, so we see if any of the other immediates take uints for j, otherImm := range spec.OpDetails.Immediates { @@ -1092,43 +1133,44 @@ func asmDefault(ops *OpStream, spec *OpSpec, args []string) error { if isPseudoName { errMsg += " with " + joinIntsOnOr("immediate", len(args)) } - return ops.errorf("%s can only use %#v as immediate %s", errMsg, args[i], strings.Join(correctImmediates, " or ")) + return args[i].errorf("%s can only use %#v as immediate %s", + errMsg, args[i].str, strings.Join(correctImmediates, " or ")) } } if isPseudoName { for numImms, ps := range pseudos { for _, psImm := range ps.OpDetails.Immediates { if psImm.kind == immByte && psImm.Group != nil { - if _, ok := psImm.Group.SpecByName(args[i]); ok { + if _, ok := psImm.Group.SpecByName(args[i].str); ok { numImmediatesWithField = append(numImmediatesWithField, numImms) } } } } if len(numImmediatesWithField) > 0 { - return ops.errorf("%#v field of %s can only be used with %s", args[i], spec.Name, joinIntsOnOr("immediate", numImmediatesWithField...)) + return args[i].errorf("%#v field of %s can only be used with %s", args[i].str, spec.Name, joinIntsOnOr("immediate", numImmediatesWithField...)) } } - return ops.errorf("%s unknown field: %#v", spec.Name, args[i]) + return args[i].errorf("%s unknown field: %#v", spec.Name, args[i].str) } // refine the typestack now, so it is maintained even if there's a version error if fs.Type().Typed() { ops.returns(spec, fs.Type()) } if fs.Version() > ops.Version { - return ops.errorf("%s %s field was introduced in v%d. Missed #pragma version?", - spec.Name, args[i], fs.Version()) + return args[i].errorf("%s %s field was introduced in v%d. Missed #pragma version?", + spec.Name, args[i].str, fs.Version()) } ops.pending.WriteByte(fs.Field()) } else { // simple immediate that must be a number from 0-255 - val, err := byteImm(args[i], imm.Name) + val, err := byteImm(args[i].str, imm.Name) if err != nil { if strings.Contains(err.Error(), "unable to parse") { // Perhaps the field works in a different order for j, otherImm := range spec.OpDetails.Immediates { if otherImm.kind == immByte && otherImm.Group != nil { - if _, match := otherImm.Group.SpecByName(args[i]); match { + if _, match := otherImm.Group.SpecByName(args[i].str); match { correctImmediates = append(correctImmediates, strconv.Itoa(j+1)) } } @@ -1138,21 +1180,21 @@ func asmDefault(ops *OpStream, spec *OpSpec, args []string) error { if isPseudoName { errMsg += " with " + joinIntsOnOr("immediate", len(args)) } - return ops.errorf("%s can only use %#v as immediate %s", errMsg, args[i], strings.Join(correctImmediates, " or ")) + return args[i].errorf("%s can only use %#v as immediate %s", errMsg, args[i].str, strings.Join(correctImmediates, " or ")) } } - return ops.errorf("%s %w", spec.Name, err) + return args[i].errorf("%s %w", spec.Name, err) } ops.pending.WriteByte(val) } case immInt8: - val, err := int8Imm(args[i], imm.Name) + val, err := int8Imm(args[i].str, imm.Name) if err != nil { - return ops.errorf("%s %w", spec.Name, err) + return args[i].errorf("%s %w", spec.Name, err) } ops.pending.WriteByte(val) default: - return ops.errorf("unable to assemble immKind %d", imm.kind) + return args[i].errorf("unable to assemble immKind %d", imm.kind) } } return nil @@ -1160,13 +1202,13 @@ func asmDefault(ops *OpStream, spec *OpSpec, args []string) error { // getImm interprets the arg at index argIndex as an immediate that must be // between -128 and 127 (if signed=true) or between 0 and 255 (if signed=false) -func getImm(args []string, argIndex int, signed bool) (int, bool) { +func getImm(args []token, argIndex int, signed bool) (int, bool) { if len(args) <= argIndex { return 0, false } // We want to parse anything from -128 up to 255. So allow 9 bits. // Normal assembly checking will catch signed as byte, vice versa - n, err := strconv.ParseInt(args[argIndex], 0, 9) + n, err := strconv.ParseInt(args[argIndex].str, 0, 9) if err != nil { return 0, false } @@ -1190,7 +1232,7 @@ func anyTypes(n int) StackTypes { return as } -func typeSwap(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, error) { +func typeSwap(pgm *ProgramKnowledge, args []token) (StackTypes, StackTypes, error) { swapped := StackTypes{StackAny, StackAny} top := len(pgm.stack) - 1 if top >= 0 { @@ -1202,7 +1244,7 @@ func typeSwap(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, err return nil, swapped, nil } -func typeDig(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, error) { +func typeDig(pgm *ProgramKnowledge, args []token) (StackTypes, StackTypes, error) { n, ok := getImm(args, 0, false) if !ok { return nil, nil, nil @@ -1219,7 +1261,7 @@ func typeDig(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, erro return anyTypes(depth), returns, nil } -func typeBury(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, error) { +func typeBury(pgm *ProgramKnowledge, args []token) (StackTypes, StackTypes, error) { n, ok := getImm(args, 0, false) if !ok { return nil, nil, nil @@ -1251,7 +1293,7 @@ func typeBury(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, err return pgm.stack[idx:], returns, nil } -func typeFrameDig(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, error) { +func typeFrameDig(pgm *ProgramKnowledge, args []token) (StackTypes, StackTypes, error) { n, ok := getImm(args, 0, true) if !ok { return nil, nil, nil @@ -1272,7 +1314,7 @@ func typeFrameDig(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, return nil, StackTypes{pgm.stack[idx]}, nil } -func typeFrameBury(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, error) { +func typeFrameBury(pgm *ProgramKnowledge, args []token) (StackTypes, StackTypes, error) { n, ok := getImm(args, 0, true) if !ok { return nil, nil, nil @@ -1313,7 +1355,7 @@ func typeFrameBury(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes return pgm.stack[idx:], returns, nil } -func typeEquals(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, error) { +func typeEquals(pgm *ProgramKnowledge, args []token) (StackTypes, StackTypes, error) { top := len(pgm.stack) - 1 if top >= 0 { // Require arg0 and arg1 to have same avm type @@ -1324,7 +1366,7 @@ func typeEquals(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, e return nil, nil, nil } -func typeDup(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, error) { +func typeDup(pgm *ProgramKnowledge, args []token) (StackTypes, StackTypes, error) { top := len(pgm.stack) - 1 if top >= 0 { return nil, StackTypes{pgm.stack[top], pgm.stack[top]}, nil @@ -1332,7 +1374,7 @@ func typeDup(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, erro return nil, nil, nil } -func typeDupTwo(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, error) { +func typeDupTwo(pgm *ProgramKnowledge, args []token) (StackTypes, StackTypes, error) { topTwo := StackTypes{StackAny, StackAny} top := len(pgm.stack) - 1 if top >= 0 { @@ -1344,7 +1386,7 @@ func typeDupTwo(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, e return nil, append(topTwo, topTwo...), nil } -func typeSelect(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, error) { +func typeSelect(pgm *ProgramKnowledge, args []token) (StackTypes, StackTypes, error) { top := len(pgm.stack) - 1 if top >= 2 { return nil, StackTypes{pgm.stack[top-1].union(pgm.stack[top-2])}, nil @@ -1352,7 +1394,7 @@ func typeSelect(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, e return nil, nil, nil } -func typeSetBit(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, error) { +func typeSetBit(pgm *ProgramKnowledge, args []token) (StackTypes, StackTypes, error) { top := len(pgm.stack) - 1 if top >= 2 { return nil, StackTypes{pgm.stack[top-2]}, nil @@ -1360,7 +1402,7 @@ func typeSetBit(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, e return nil, nil, nil } -func typeCover(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, error) { +func typeCover(pgm *ProgramKnowledge, args []token) (StackTypes, StackTypes, error) { n, ok := getImm(args, 0, false) if !ok { return nil, nil, nil @@ -1382,7 +1424,7 @@ func typeCover(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, er return anyTypes(depth), returns, nil } -func typeUncover(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, error) { +func typeUncover(pgm *ProgramKnowledge, args []token) (StackTypes, StackTypes, error) { n, ok := getImm(args, 0, false) if !ok { return nil, nil, nil @@ -1401,23 +1443,23 @@ func typeUncover(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, } func typeByteMath(resultSize uint64) refineFunc { - return func(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, error) { + return func(pgm *ProgramKnowledge, args []token) (StackTypes, StackTypes, error) { return nil, StackTypes{NewStackType(avmBytes, bound(0, resultSize))}, nil } } -func typeTxField(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, error) { +func typeTxField(pgm *ProgramKnowledge, args []token) (StackTypes, StackTypes, error) { if len(args) != 1 { return nil, nil, nil } - fs, ok := txnFieldSpecByName[args[0]] + fs, ok := txnFieldSpecByName[args[0].str] if !ok { return nil, nil, nil } return StackTypes{fs.ftype}, nil, nil } -func typeStore(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, error) { +func typeStore(pgm *ProgramKnowledge, args []token) (StackTypes, StackTypes, error) { scratchIndex, ok := getImm(args, 0, false) if !ok { return nil, nil, nil @@ -1429,7 +1471,7 @@ func typeStore(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, er return nil, nil, nil } -func typeStores(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, error) { +func typeStores(pgm *ProgramKnowledge, args []token) (StackTypes, StackTypes, error) { top := len(pgm.stack) - 1 if top < 0 { return nil, nil, nil @@ -1452,7 +1494,7 @@ func typeStores(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, e return nil, nil, nil } -func typeLoad(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, error) { +func typeLoad(pgm *ProgramKnowledge, args []token) (StackTypes, StackTypes, error) { scratchIndex, ok := getImm(args, 0, false) if !ok { return nil, nil, nil @@ -1460,7 +1502,7 @@ func typeLoad(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, err return nil, StackTypes{pgm.scratchSpace[scratchIndex]}, nil } -func typeProto(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, error) { +func typeProto(pgm *ProgramKnowledge, args []token) (StackTypes, StackTypes, error) { a, aok := getImm(args, 0, false) _, rok := getImm(args, 1, false) if !aok || !rok { @@ -1475,8 +1517,7 @@ func typeProto(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, er return nil, nil, nil } -func typeLoads(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, error) { - +func typeLoads(pgm *ProgramKnowledge, args []token) (StackTypes, StackTypes, error) { top := len(pgm.stack) - 1 if top < 0 { return nil, nil, nil @@ -1496,7 +1537,7 @@ func typeLoads(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, er return nil, StackTypes{scratchType}, nil } -func typePopN(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, error) { +func typePopN(pgm *ProgramKnowledge, args []token) (StackTypes, StackTypes, error) { n, ok := getImm(args, 0, false) if !ok { return nil, nil, nil @@ -1504,7 +1545,7 @@ func typePopN(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, err return anyTypes(n), nil, nil } -func typeDupN(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, error) { +func typeDupN(pgm *ProgramKnowledge, args []token) (StackTypes, StackTypes, error) { n, ok := getImm(args, 0, false) if !ok { return nil, nil, nil @@ -1523,7 +1564,7 @@ func typeDupN(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, err return nil, copies, nil } -func typePushBytess(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, error) { +func typePushBytess(pgm *ProgramKnowledge, args []token) (StackTypes, StackTypes, error) { types := make(StackTypes, len(args)) for i := range types { types[i] = StackBytes @@ -1532,7 +1573,7 @@ func typePushBytess(pgm *ProgramKnowledge, args []string) (StackTypes, StackType return nil, types, nil } -func typePushInts(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, error) { +func typePushInts(pgm *ProgramKnowledge, args []token) (StackTypes, StackTypes, error) { types := make(StackTypes, len(args)) for i := range types { types[i] = StackUint64 @@ -1541,10 +1582,10 @@ func typePushInts(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, return nil, types, nil } -func typePushInt(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, error) { +func typePushInt(pgm *ProgramKnowledge, args []token) (StackTypes, StackTypes, error) { types := make(StackTypes, len(args)) for i := range types { - val, err := strconv.ParseUint(args[i], 10, 64) + val, err := strconv.ParseUint(args[i].str, 10, 64) if err != nil { types[i] = StackUint64 } else { @@ -1554,12 +1595,12 @@ func typePushInt(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, return nil, types, nil } -func typeBzero(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, error) { +func typeBzero(pgm *ProgramKnowledge, args []token) (StackTypes, StackTypes, error) { // Bzero should only allow its input int to be up to maxStringSize bytes return StackTypes{StackUint64.narrowed(bound(0, maxStringSize))}, StackTypes{StackBytes}, nil } -func typeByte(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, error) { +func typeByte(pgm *ProgramKnowledge, args []token) (StackTypes, StackTypes, error) { if len(args) == 0 { return nil, StackTypes{StackBytes}, nil } @@ -1591,41 +1632,45 @@ func joinIntsOnOr(singularTerminator string, list ...int) string { return errMsg + singularTerminator + "s" } -func pseudoImmediatesError(ops *OpStream, name string, specs map[int]OpSpec) { +func pseudoImmediatesError(ops *OpStream, mnemonic token, specs map[int]OpSpec) { immediateCounts := make([]int, len(specs)) i := 0 for numImms := range specs { immediateCounts[i] = numImms i++ } - ops.error(name + " expects " + joinIntsOnOr("immediate argument", immediateCounts...)) + ops.record(mnemonic.errorf("%s expects %s", mnemonic.str, joinIntsOnOr("immediate argument", immediateCounts...))) } // getSpec finds the OpSpec we need during assembly based on its name, our current version, and the immediates passed in // Note getSpec handles both normal OpSpecs and those supplied by versionedPseudoOps // The returned string is the spec's name, annotated if it was a pseudoOp with no immediates to help disambiguate typetracking errors -func getSpec(ops *OpStream, name string, args []string) (OpSpec, string, bool) { +func getSpec(ops *OpStream, mnemonic token, argCount int) (OpSpec, string, bool) { + name := mnemonic.str pseudoSpecs, ok := ops.versionedPseudoOps[name] if ok { - pseudo, ok := pseudoSpecs[len(args)] + pseudo, ok := pseudoSpecs[argCount] if !ok { // Could be that pseudoOp wants to handle immediates itself so check -1 key pseudo, ok = pseudoSpecs[anyImmediates] if !ok { // Number of immediates supplied did not match any of the pseudoOps of the given name, so we try to construct a mock spec that can be used to track types - pseudoImmediatesError(ops, name, pseudoSpecs) + pseudoImmediatesError(ops, mnemonic, pseudoSpecs) proto, version, ok := mergeProtos(pseudoSpecs) if !ok { return OpSpec{}, "", false } - pseudo = OpSpec{Name: name, Proto: proto, Version: version, OpDetails: OpDetails{asm: func(*OpStream, *OpSpec, []string) error { return nil }}} + pseudo = OpSpec{Name: name, Proto: proto, Version: version, OpDetails: OpDetails{ + asm: func(*OpStream, *OpSpec, token, []token) *sourceError { return nil }, + }} } } pseudo.Name = name if pseudo.Version > ops.Version { - ops.errorf("%s opcode with %s was introduced in v%d", pseudo.Name, joinIntsOnOr("immediate", len(args)), pseudo.Version) + ops.record(mnemonic.errorf("%s opcode with %s was introduced in v%d", + pseudo.Name, joinIntsOnOr("immediate", argCount), pseudo.Version)) } - if len(args) == 0 { + if argCount == 0 { return pseudo, pseudo.Name + " without immediates", true } return pseudo, pseudo.Name, true @@ -1635,7 +1680,7 @@ func getSpec(ops *OpStream, name string, args []string) (OpSpec, string, bool) { var err error spec, err = unknownOpcodeComplaint(name, ops.Version) // unknownOpcodeComplaint's job is to return a nice error, so err != nil - ops.error(err) + ops.record(mnemonic.error(err)) } return spec, spec.Name, ok } @@ -1764,29 +1809,69 @@ func prepareVersionedPseudoTable(version uint64) map[string]map[int]OpSpec { return m } -type lineError struct { - Line int - Err error +type sourceError struct { + Line int + Column int + Err error +} + +func (se sourceError) Error() string { + if se.Column != 0 { + return fmt.Sprintf("%d:%d: %s", se.Line, se.Column, se.Err.Error()) + } + return fmt.Sprintf("%d: %s", se.Line, se.Err.Error()) +} + +func (se sourceError) Unwrap() error { + return se.Err +} + +func errorLinef(line int, format string, a ...interface{}) *sourceError { + return &sourceError{line, 0, fmt.Errorf(format, a...)} } -func (le lineError) Error() string { - return fmt.Sprintf("%d: %s", le.Line, le.Err.Error()) +func typecheck(expected, got StackType) bool { + // Some ops push 'any' and we wait for run time to see what it is. + // Some of those 'any' are based on fields that we _could_ know now but haven't written a more detailed system of typecheck for (yet). + if expected == StackAny && got == StackNone { // Any is lenient, but stack can't be empty + return false + } + if (expected == StackAny) || (got == StackAny) { + return true + } + return expected == got +} + +type token struct { + str string + col int + line int +} + +func (t token) error(err error) *sourceError { + return &sourceError{t.line, t.col, err} } -func (le lineError) Unwrap() error { - return le.Err +func (t token) errorf(format string, args ...interface{}) *sourceError { + return t.error(fmt.Errorf(format, args...)) +} + +func (t token) errorAfterf(format string, args ...interface{}) *sourceError { + return &sourceError{t.line, t.col + len(t.str), fmt.Errorf(format, args...)} } // newline not included since handled in scanner var tokenSeparators = [256]bool{'\t': true, ' ': true, ';': true} -func tokensFromLine(line string) []string { - var tokens []string +// tokensFromLine splits a line into tokens, ignoring comments. tokens are +// annotated with the provided lineno, and column where they are found. +func tokensFromLine(sourceLine string, lineno int) []token { + var tokens []token i := 0 - for i < len(line) && tokenSeparators[line[i]] { - if line[i] == ';' { - tokens = append(tokens, ";") + for i < len(sourceLine) && tokenSeparators[sourceLine[i]] { + if sourceLine[i] == ';' { + tokens = append(tokens, token{";", i, lineno}) } i++ } @@ -1794,28 +1879,28 @@ func tokensFromLine(line string) []string { start := i inString := false // tracked to allow spaces and comments inside inBase64 := false // tracked to allow '//' inside - for i < len(line) { - if !tokenSeparators[line[i]] { // if not space - switch line[i] { + for i < len(sourceLine) { + if !tokenSeparators[sourceLine[i]] { // if not space + switch sourceLine[i] { case '"': // is a string literal? if !inString { - if i == 0 || i > 0 && tokenSeparators[line[i-1]] { + if i == 0 || i > 0 && tokenSeparators[sourceLine[i-1]] { inString = true } } else { - if line[i-1] != '\\' { // if not escape symbol + if sourceLine[i-1] != '\\' { // if not escape symbol inString = false } } case '/': // is a comment? - if i < len(line)-1 && line[i+1] == '/' && !inBase64 && !inString { + if i < len(sourceLine)-1 && sourceLine[i+1] == '/' && !inBase64 && !inString { if start != i { // if a comment without whitespace - tokens = append(tokens, line[start:i]) + tokens = append(tokens, token{sourceLine[start:i], start, lineno}) } return tokens } case '(': // is base64( seq? - prefix := line[start:i] + prefix := sourceLine[start:i] if prefix == "base64" || prefix == "b64" { inBase64 = true } @@ -1832,14 +1917,14 @@ func tokensFromLine(line string) []string { // we've hit a space, end last token unless inString if !inString { - token := line[start:i] - tokens = append(tokens, token) - if line[i] == ';' { - tokens = append(tokens, ";") + s := sourceLine[start:i] + tokens = append(tokens, token{s, start, lineno}) + if sourceLine[i] == ';' { + tokens = append(tokens, token{";", i, lineno}) } if inBase64 { inBase64 = false - } else if token == "base64" || token == "b64" { + } else if s == "base64" || s == "b64" { inBase64 = true } } @@ -1847,9 +1932,9 @@ func tokensFromLine(line string) []string { // gobble up consecutive whitespace (but notice semis) if !inString { - for i < len(line) && tokenSeparators[line[i]] { - if line[i] == ';' { - tokens = append(tokens, ";") + for i < len(sourceLine) && tokenSeparators[sourceLine[i]] { + if sourceLine[i] == ';' { + tokens = append(tokens, token{";", i, lineno}) } i++ } @@ -1858,8 +1943,8 @@ func tokensFromLine(line string) []string { } // add rest of the string if any - if start < len(line) { - tokens = append(tokens, line[start:i]) + if start < len(sourceLine) { + tokens = append(tokens, token{sourceLine[start:i], start, lineno}) } return tokens @@ -1872,22 +1957,32 @@ func (ops *OpStream) trace(format string, args ...interface{}) { fmt.Fprintf(ops.Trace, format, args...) } -func (ops *OpStream) typeErrorf(format string, args ...interface{}) { +func (ops *OpStream) typeErrorf(opcode token, format string, args ...interface{}) { if ops.typeTracking { - ops.errorf(format, args...) + ops.record(opcode.errorf(format, args...)) } } +func reJoin(instruction string, args []token) string { + var buf bytes.Buffer + buf.WriteString(instruction) + for _, t := range args { + buf.WriteString(" ") + buf.WriteString(t.str) + } + return buf.String() +} + // trackStack checks that the typeStack has `args` on it, then pushes `returns` to it. -func (ops *OpStream) trackStack(args StackTypes, returns StackTypes, instruction []string) { +func (ops *OpStream) trackStack(args StackTypes, returns StackTypes, instruction string, tokens []token) { // If in deadcode, allow anything. Maybe it's some sort of onchain data. if ops.known.deadcode { return } argcount := len(args) if argcount > len(ops.known.stack) && ops.known.bottom.AVMType == avmNone { - ops.typeErrorf("%s expects %d stack arguments but stack height is %d", - strings.Join(instruction, " "), argcount, len(ops.known.stack)) + ops.typeErrorf(tokens[0], "%s expects %d stack arguments but stack height is %d", + reJoin(instruction, tokens[1:]), argcount, len(ops.known.stack)) } else { firstPop := true for i := argcount - 1; i >= 0; i-- { @@ -1900,8 +1995,8 @@ func (ops *OpStream) trackStack(args StackTypes, returns StackTypes, instruction ops.trace(", %s", argType) } if !stype.overlaps(argType) { - ops.typeErrorf("%s arg %d wanted type %s got %s", - strings.Join(instruction, " "), i, argType, stype) + ops.typeErrorf(tokens[0], "%s arg %d wanted type %s got %s", + reJoin(instruction, tokens[1:]), i, argType, stype) } } if !firstPop { @@ -1922,49 +2017,55 @@ func (ops *OpStream) trackStack(args StackTypes, returns StackTypes, instruction } // nextStatement breaks tokens into two slices at the first semicolon and expands macros along the way. -func nextStatement(ops *OpStream, tokens []string) (current, rest []string) { +func nextStatement(ops *OpStream, tokens []token) (current, rest []token) { for i := 0; i < len(tokens); i++ { token := tokens[i] - replacement, ok := ops.macros[token] + replacement, ok := ops.macros[token.str] if ok { - tokens = append(tokens[0:i], append(replacement, tokens[i+1:]...)...) + tokens = append(tokens[0:i], append(replacement[1:], tokens[i+1:]...)...) // backup to handle potential re-expansion of the first token in the expansion i-- continue } - if token == ";" { + if token.str == ";" { return tokens[:i], tokens[i+1:] } } return tokens, nil } -type directiveFunc func(*OpStream, []string) error +type directiveFunc func(*OpStream, []token) *sourceError var directives = map[string]directiveFunc{"pragma": pragma, "define": define} // assemble reads text from an input and accumulates the program func (ops *OpStream) assemble(text string) error { if ops.Version > LogicVersion && ops.Version != assemblerNoVersion { - return ops.errorf("Can not assemble version %d", ops.Version) + err := fmt.Errorf("Can not assemble version %d", ops.Version) + ops.record(&sourceError{0, 0, err}) + return err } if strings.TrimSpace(text) == "" { - return ops.errorf("Cannot assemble empty program text") + err := errors.New("Cannot assemble empty program text") + ops.record(&sourceError{0, 0, err}) + return err } fin := strings.NewReader(text) scanner := bufio.NewScanner(fin) for scanner.Scan() { ops.sourceLine++ line := scanner.Text() - tokens := tokensFromLine(line) + tokens := tokensFromLine(line, ops.sourceLine) if len(tokens) > 0 { - if first := tokens[0]; first[0] == '#' { - directive := first[1:] + if first := tokens[0]; first.str[0] == '#' { + directive := first.str[1:] if dFunc, ok := directives[directive]; ok { - _ = dFunc(ops, tokens) - ops.trace("%3d: %s line\n", ops.sourceLine, first) + if err := dFunc(ops, tokens); err != nil { + ops.record(err) + } + ops.trace("%3d: %s line\n", first.line, first.str) } else { - ops.errorf("Unknown directive: %s", directive) + ops.record(first.errorf("unknown directive: %s", directive)) } continue } @@ -1981,22 +2082,22 @@ func (ops *OpStream) assemble(text string) error { if ops.versionedPseudoOps == nil { ops.versionedPseudoOps = prepareVersionedPseudoTable(ops.Version) } - opstring := current[0] + opstring := current[0].str if opstring[len(opstring)-1] == ':' { labelName := opstring[:len(opstring)-1] if _, ok := ops.macros[labelName]; ok { - ops.errorf("Cannot create label with same name as macro: %s", labelName) + ops.record(current[0].errorf("Cannot create label with same name as macro: %s", labelName)) } else { - ops.createLabel(opstring[:len(opstring)-1]) + ops.createLabel(current[0]) } current = current[1:] if len(current) == 0 { ops.trace("%3d: label only\n", ops.sourceLine) continue } - opstring = current[0] + opstring = current[0].str } - spec, expandedName, ok := getSpec(ops, opstring, current[1:]) + spec, expandedName, ok := getSpec(ops, current[0], len(current)-1) if ok { ops.trace("%3d: %s\t", ops.sourceLine, opstring) ops.recordSourceLine() @@ -2007,7 +2108,7 @@ func (ops *OpStream) assemble(text string) error { if spec.refine != nil { nargs, nreturns, err := spec.refine(&ops.known, current[1:]) if err != nil { - ops.typeErrorf("%w", err) + ops.typeErrorf(current[0], "%w", err) } if nargs != nil { args = nargs @@ -2016,9 +2117,10 @@ func (ops *OpStream) assemble(text string) error { returns = nreturns } } - ops.trackStack(args, returns, append([]string{expandedName}, current[1:]...)) - spec.asm(ops, &spec, current[1:]) //nolint:errcheck // ignore error and continue, to collect more errors - + ops.trackStack(args, returns, expandedName, current) + if err := spec.asm(ops, &spec, current[0], current[1:]); err != nil { + ops.record(err) + } if spec.deadens() { // An unconditional branch deadens the following code ops.known.deaden() } @@ -2036,16 +2138,7 @@ func (ops *OpStream) assemble(text string) error { if errors.Is(err, bufio.ErrTooLong) { err = errors.New("line too long") } - ops.error(err) - } - - // backward compatibility: do not allow jumps past last instruction in v1 - if ops.Version <= 1 { - for label, dest := range ops.labels { - if dest == ops.pending.Len() { - ops.errorf("label %#v is too far away", label) - } - } + ops.record(&sourceError{ops.sourceLine, 0, err}) } if ops.Version >= optimizeConstantsEnabledVersion { @@ -2066,21 +2159,23 @@ func (ops *OpStream) assemble(text string) error { return nil } -func (ops *OpStream) cycle(macro string, previous ...string) bool { - replacement, ok := ops.macros[macro] +// cycle return a slice of strings that constitute a cycle, if one is +// found. That is, the first token expands to the second, and so on, with the +// first and last string being the same. +func (ops *OpStream) cycle(macro token, previous []string) []string { + replacement, ok := ops.macros[macro.str] if !ok { - return false + return nil } - if len(previous) > 0 && macro == previous[0] { - ops.errorf("Macro cycle discovered: %s", strings.Join(append(previous, macro), " -> ")) - return true + if len(previous) > 0 && macro.str == previous[0] { + return append(previous, macro.str) } - for _, token := range replacement { - if ops.cycle(token, append(previous, macro)...) { - return true + for _, repl := range replacement[1:] { + if found := ops.cycle(repl, append(previous, macro.str)); found != nil { + return found } } - return false + return nil } // recheckMacroNames goes through previously defined macros and ensures they @@ -2091,8 +2186,8 @@ func (ops *OpStream) recheckMacroNames() error { for macroName := range ops.macros { err := checkMacroName(macroName, ops.Version, ops.labels) if err != nil { + ops.record(ops.macros[macroName][0].error(err)) delete(ops.macros, macroName) - ops.error(err) errored = true } } @@ -2156,57 +2251,58 @@ func checkMacroName(macroName string, version uint64, labels map[string]int) err return nil } -func define(ops *OpStream, tokens []string) error { - if tokens[0] != "#define" { - return ops.errorf("invalid syntax: %s", tokens[0]) +func define(ops *OpStream, tokens []token) *sourceError { + if tokens[0].str != "#define" { + return tokens[0].errorf("invalid syntax: %s", tokens[0].str) } if len(tokens) < 3 { - return ops.errorf("define directive requires a name and body") + return tokens[len(tokens)-1].errorf("define directive requires a name and body") } - name := tokens[1] + name := tokens[1].str err := checkMacroName(name, ops.Version, ops.labels) if err != nil { - return ops.error(err) + return tokens[1].error(err) } saved, ok := ops.macros[name] - ops.macros[name] = tokens[2:len(tokens):len(tokens)] - if ops.cycle(tokens[1]) { + ops.macros[name] = tokens[1:len(tokens):len(tokens)] // include the name itself at the front + if found := ops.cycle(tokens[1], nil); found != nil { if ok { - ops.macros[tokens[1]] = saved + ops.macros[name] = saved // restore previous macro } else { - delete(ops.macros, tokens[1]) + delete(ops.macros, name) // remove new macro that caused cycle } + return tokens[1].errorf("macro expansion cycle discovered: %s", strings.Join(found, " -> ")) } return nil } -func pragma(ops *OpStream, tokens []string) error { - if tokens[0] != "#pragma" { - return ops.errorf("invalid syntax: %s", tokens[0]) +func pragma(ops *OpStream, tokens []token) *sourceError { + if tokens[0].str != "#pragma" { + return tokens[0].errorf("invalid syntax: %s", tokens[0].str) } if len(tokens) < 2 { - return ops.error("empty pragma") + return tokens[0].errorf("empty pragma") } - key := tokens[1] + key := tokens[1].str switch key { case "version": if len(tokens) < 3 { - return ops.error("no version value") + return tokens[1].errorf("no version value") } if len(tokens) > 3 { - return ops.errorf("unexpected extra tokens: %s", strings.Join(tokens[3:], " ")) + return tokens[3].errorf("unexpected extra tokens:%s", reJoin("", tokens[3:])) } - value := tokens[2] var ver uint64 if ops.pending.Len() > 0 { - return ops.error("#pragma version is only allowed before instructions") + return tokens[0].errorf("#pragma version is only allowed before instructions") } + value := tokens[2].str ver, err := strconv.ParseUint(value, 0, 64) if err != nil { - return ops.errorf("bad #pragma version: %#v", value) + return tokens[2].errorf("bad #pragma version: %#v", value) } if ver > AssemblerMaxVersion { - return ops.errorf("unsupported version: %d", ver) + return tokens[2].errorf("unsupported version: %d", ver) } // We initialize Version with assemblerNoVersion as a marker for @@ -2214,24 +2310,27 @@ func pragma(ops *OpStream, tokens []string) error { // version for v1. if ops.Version == assemblerNoVersion { ops.Version = ver - return ops.recheckMacroNames() + if err := ops.recheckMacroNames(); err != nil { + return tokens[2].error(err) + } + return nil } if ops.Version != ver { - return ops.errorf("version mismatch: assembling v%d with v%d assembler", ver, ops.Version) + return tokens[2].errorf("version mismatch: assembling v%d with v%d assembler", ver, ops.Version) } // ops.Version is already correct, or needed to be upped. return nil case "typetrack": if len(tokens) < 3 { - return ops.error("no typetrack value") + return tokens[1].errorf("no typetrack value") } if len(tokens) > 3 { - return ops.errorf("unexpected extra tokens: %s", strings.Join(tokens[3:], " ")) + return tokens[3].errorf("unexpected extra tokens:%s", reJoin("", tokens[3:])) } - value := tokens[2] + value := tokens[2].str on, err := strconv.ParseBool(value) if err != nil { - return ops.errorf("bad #pragma typetrack: %#v", value) + return tokens[2].errorf("bad #pragma typetrack: %#v", value) } prev := ops.typeTracking if !prev && on { @@ -2241,41 +2340,45 @@ func pragma(ops *OpStream, tokens []string) error { return nil default: - return ops.errorf("unsupported pragma directive: %#v", key) + return tokens[0].errorf("unsupported pragma directive: %#v", key) } } func (ops *OpStream) resolveLabels() { - saved := ops.sourceLine raw := ops.pending.Bytes() reported := make(map[string]bool) for _, lr := range ops.labelReferences { - ops.sourceLine = lr.sourceLine // so errors get reported where the label was used - dest, ok := ops.labels[lr.label] + dest, ok := ops.labels[lr.label.str] if !ok { - if !reported[lr.label] { - ops.errorf("reference to undefined label %#v", lr.label) + if !reported[lr.label.str] { + ops.record(lr.label.errorf("reference to undefined label %#v", lr.label.str)) } - reported[lr.label] = true + reported[lr.label.str] = true continue } + // backward compatibility: do not allow jumps past last instruction in v1 + if ops.Version <= 1 { + if dest == ops.pending.Len() { + ops.record(lr.label.errorf("label %#v is too far away", lr.label.str)) + } + } + // All branch targets are encoded as 2 offset bytes. The destination is relative to the end of the // instruction they appear in, which is available in lr.offsetPostion if ops.Version < backBranchEnabledVersion && dest < lr.offsetPosition { - ops.errorf("label %#v is a back reference, back jump support was introduced in v4", lr.label) + ops.record(lr.label.errorf("label %#v is a back reference, back jump support was introduced in v4", lr.label.str)) continue } jump := dest - lr.offsetPosition if jump > 0x7fff { - ops.errorf("label %#v is too far away", lr.label) + ops.record(lr.label.errorf("label %#v is too far away", lr.label.str)) continue } raw[lr.position] = uint8(jump >> 8) raw[lr.position+1] = uint8(jump & 0x0ff) } ops.pending = *bytes.NewBuffer(raw) - ops.sourceLine = saved } // AssemblerDefaultVersion what version of code do we emit by default @@ -2425,14 +2528,14 @@ func (ops *OpStream) optimizeConstants(refs []constReference, constBlock []inter } } if !found { - err = ops.lineErrorf(ops.OffsetToLine[ref.getPosition()], "Value not found in constant block: %v", ref.getValue()) + err = errorLinef(ops.OffsetToLine[ref.getPosition()], "value not found in constant block: %v", ref.getValue()) return } } for _, f := range freqs { if f.freq == 0 { - err = ops.errorf("Member of constant block is not used: %v", f.value) + err = errorLinef(ops.sourceLine, "member of constant block is not used: %v", f.value) return } } @@ -2463,7 +2566,7 @@ func (ops *OpStream) optimizeConstants(refs []constReference, constBlock []inter } } if newIndex == -1 { - return nil, ops.lineErrorf(ops.OffsetToLine[ref.getPosition()], "Value not found in constant block: %v", ref.getValue()) + return nil, errorLinef(ops.OffsetToLine[ref.getPosition()], "value not found in constant block: %v", ref.getValue()) } newBytes := ref.makeNewReference(ops, singleton, newIndex) @@ -2565,12 +2668,12 @@ func (ops *OpStream) prependCBlocks() []byte { out := make([]byte, pbl+outl) pl, err := prebytes.Read(out) if pl != pbl || err != nil { - ops.errorf("wat: %d prebytes, %d to buffer? err=%w", pbl, pl, err) + ops.record(&sourceError{ops.sourceLine, 0, fmt.Errorf("%d prebytes, %d to buffer? %w", pbl, pl, err)}) return nil } ol, err := ops.pending.Read(out[pl:]) if ol != outl || err != nil { - ops.errorf("%d program bytes but %d to buffer. err=%w", outl, ol, err) + ops.record(&sourceError{ops.sourceLine, 0, fmt.Errorf("%d program bytes but %d to buffer. %w", outl, ol, err)}) return nil } @@ -2584,48 +2687,17 @@ func (ops *OpStream) prependCBlocks() []byte { return out } -func (ops *OpStream) error(problem interface{}) error { - return ops.lineError(ops.sourceLine, problem) -} - -func (ops *OpStream) lineError(line int, problem interface{}) error { - var err lineError - switch p := problem.(type) { - case string: - err = lineError{Line: line, Err: errors.New(p)} - case error: - err = lineError{Line: line, Err: p} - default: - err = lineError{Line: line, Err: fmt.Errorf("%#v", p)} - } - ops.Errors = append(ops.Errors, err) - return err -} - -func (ops *OpStream) errorf(format string, a ...interface{}) error { - return ops.error(fmt.Errorf(format, a...)) -} - -func (ops *OpStream) lineErrorf(line int, format string, a ...interface{}) error { - return ops.lineError(line, fmt.Errorf(format, a...)) +// record puts an error onto a list for reporting later. The hope is that the +// assembler can keep going after an error that is "swallowed" this way, so that +// more than one TEAL error can be reported in one pass. By explicitly +// swallowing the error this way, we can use the linter properly. +func (ops *OpStream) record(se *sourceError) { + ops.Errors = append(ops.Errors, *se) } -func (ops *OpStream) warn(problem interface{}) error { - var le *lineError - switch p := problem.(type) { - case string: - le = &lineError{Line: ops.sourceLine, Err: errors.New(p)} - case error: - le = &lineError{Line: ops.sourceLine, Err: p} - default: - le = &lineError{Line: ops.sourceLine, Err: fmt.Errorf("%#v", p)} - } - warning := fmt.Errorf("warning: %w", le) +func (ops *OpStream) warn(t token, format string, a ...interface{}) { + warning := &sourceError{Line: t.line, Column: t.col, Err: fmt.Errorf(format, a...)} ops.Warnings = append(ops.Warnings, warning) - return warning -} -func (ops *OpStream) warnf(format string, a ...interface{}) error { - return ops.warn(fmt.Errorf(format, a...)) } // ReportMultipleErrors issues accumulated warnings and outputs errors to an io.Writer. @@ -3046,12 +3118,12 @@ func disassembleInstrumented(program []byte, labels map[int]string) (text string ds.pcOffset = append(ds.pcOffset, PCOffset{dis.pc, out.Len()}) // Actually do the disassembly - var line string - line, err = disassemble(&dis, &op) + var instruction string + instruction, err = disassemble(&dis, &op) if err != nil { return } - out.WriteString(line) + out.WriteString(instruction) out.WriteRune('\n') dis.pc = dis.nextpc } diff --git a/data/transactions/logic/assembler_test.go b/data/transactions/logic/assembler_test.go index fd3b527b43..713528b72c 100644 --- a/data/transactions/logic/assembler_test.go +++ b/data/transactions/logic/assembler_test.go @@ -558,11 +558,24 @@ pop require.Equal(t, ops1.Program, ops2.Program) } -type Expect struct { +type expect struct { l int + c int s string } +func exp(l int, s string, extra ...int) expect { + e := expect{l: l, c: 0, s: s} + switch len(extra) { + case 0: /* nothing */ + case 1: + e.c = extra[0] + default: + panic(extra) + } + return e +} + func testMatch(t testing.TB, actual, expected string) (ok bool) { defer func() { t.Helper() @@ -613,7 +626,7 @@ func summarize(trace *strings.Builder) string { return msg + "(trace truncated)\n" } -func testProg(t testing.TB, source string, ver uint64, expected ...Expect) *OpStream { +func testProg(t testing.TB, source string, ver uint64, expected ...expect) *OpStream { t.Helper() ops, err := assembleWithTrace(source, ver) if len(expected) == 0 { @@ -663,7 +676,7 @@ func testProg(t testing.TB, source string, ver uint64, expected ...Expect) *OpSt t.FailNow() } } else { - var found *lineError + var found *sourceError for i := range errors { if errors[i].Line == exp.l { found = &errors[i] @@ -674,7 +687,11 @@ func testProg(t testing.TB, source string, ver uint64, expected ...Expect) *OpSt t.Log(fmt.Sprintf("Errors: %v", errors)) } require.NotNil(t, found, "Error %s was not found on line %d", exp.s, exp.l) - msg := found.Unwrap().Error() + if exp.c != 0 { + require.Equal(t, exp.c, found.Column, "Error %s was not found at column %d. %s was.", + exp.s, exp.c, found.Error()) + } + msg := found.Unwrap().Error() // unwrap avoids line,col prefix for easier matching if !testMatch(t, msg, exp.s) { t.Log(summarize(ops.Trace)) t.FailNow() @@ -686,7 +703,7 @@ func testProg(t testing.TB, source string, ver uint64, expected ...Expect) *OpSt return ops } -func testLine(t *testing.T, line string, ver uint64, expected string) { +func testLine(t *testing.T, line string, ver uint64, expected string, col ...int) { t.Helper() // By embedding the source line between two other lines, the // test for the correct line number in the error is more @@ -696,35 +713,36 @@ func testLine(t *testing.T, line string, ver uint64, expected string) { testProg(t, source, ver) return } - testProg(t, source, ver, Expect{2, expected}) + testProg(t, source, ver, exp(2, expected, col...)) } func TestAssembleTxna(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - testLine(t, "txna Accounts 256", AssemblerMaxVersion, "txna i beyond 255: 256") - testLine(t, "txna ApplicationArgs 256", AssemblerMaxVersion, "txna i beyond 255: 256") - testLine(t, "txna Sender 256", AssemblerMaxVersion, "txna unknown field: \"Sender\"") - testLine(t, "gtxna 0 Accounts 256", AssemblerMaxVersion, "gtxna i beyond 255: 256") + testLine(t, "txna Accounts 256", AssemblerMaxVersion, "txna i beyond 255: 256", 14) + testLine(t, "txna ApplicationArgs 256", AssemblerMaxVersion, "txna i beyond 255: 256", 21) + testLine(t, "txna Sender 256", AssemblerMaxVersion, "txna unknown field: \"Sender\"", 5) + testLine(t, "gtxna 0 Accounts 256", AssemblerMaxVersion, "gtxna i beyond 255: 256", 17) testLine(t, "gtxna 0 ApplicationArgs 256", AssemblerMaxVersion, "gtxna i beyond 255: 256") - testLine(t, "gtxna 256 Accounts 0", AssemblerMaxVersion, "gtxna t beyond 255: 256") - testLine(t, "gtxna 0 Sender 256", AssemblerMaxVersion, "gtxna unknown field: \"Sender\"") + testLine(t, "gtxna 256 Accounts 0", AssemblerMaxVersion, "gtxna t beyond 255: 256", 6) + testLine(t, "gtxna 0 Sender 256", AssemblerMaxVersion, "gtxna unknown field: \"Sender\"", 8) testLine(t, "gtxna ApplicationArgs 0 255", AssemblerMaxVersion, "gtxna can only use \"ApplicationArgs\" as immediate 2") testLine(t, "gtxna 0 255 ApplicationArgs", AssemblerMaxVersion, "gtxna can only use \"255\" as immediate 1 or 3") testLine(t, "txn Accounts 256", AssemblerMaxVersion, "txn i beyond 255: 256") testLine(t, "txn ApplicationArgs 256", AssemblerMaxVersion, "txn i beyond 255: 256") - testLine(t, "txn 255 ApplicationArgs", AssemblerMaxVersion, "txn with 2 immediates can only use \"255\" as immediate 2") + testLine(t, "txn 255 ApplicationArgs", AssemblerMaxVersion, "txn with 2 immediates can only use \"255\" as immediate 2", 4) testLine(t, "txn Sender 256", AssemblerMaxVersion, "\"Sender\" field of txn can only be used with 1 immediate") testLine(t, "gtxn 0 Accounts 256", AssemblerMaxVersion, "gtxn i beyond 255: 256") testLine(t, "gtxn 0 ApplicationArgs 256", AssemblerMaxVersion, "gtxn i beyond 255: 256") testLine(t, "gtxn 256 Accounts 0", AssemblerMaxVersion, "gtxn t beyond 255: 256") testLine(t, "gtxn 0 Sender 256", AssemblerMaxVersion, "\"Sender\" field of gtxn can only be used with 2 immediates") - testLine(t, "gtxn ApplicationArgs 0 255", AssemblerMaxVersion, "gtxn with 3 immediates can only use \"ApplicationArgs\" as immediate 2") + testLine(t, "gtxn ApplicationArgs 0 255", AssemblerMaxVersion, "gtxn with 3 immediates can only use \"ApplicationArgs\" as immediate 2", 5) testLine(t, "gtxn 0 255 ApplicationArgs", AssemblerMaxVersion, "gtxn with 3 immediates can only use \"255\" as immediate 1 or 3") testLine(t, "txn Accounts 0", 1, "txn opcode with 2 immediates was introduced in v2") + testLine(t, "txn", 2, "txn expects 1 or 2 immediate arguments") testLine(t, "txn Accounts 0 1", 2, "txn expects 1 or 2 immediate arguments") testLine(t, "txna Accounts 0 1", AssemblerMaxVersion, "txna expects 2 immediate arguments") testLine(t, "txn Accounts 0 1", AssemblerMaxVersion, "txn expects 1 or 2 immediate arguments") @@ -758,14 +776,15 @@ func TestAssembleGlobal(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - testLine(t, "global", AssemblerMaxVersion, "global expects 1 immediate argument") + testLine(t, "global", AssemblerMaxVersion, "global expects 1 immediate argument", 6) + testLine(t, "global aa bb", AssemblerMaxVersion, "global expects 1 immediate argument", 10) testLine(t, "global a", AssemblerMaxVersion, "global unknown field: \"a\"") testProg(t, "global MinTxnFee; int 2; +", AssemblerMaxVersion) testProg(t, "global ZeroAddress; byte 0x12; concat; len", AssemblerMaxVersion) testProg(t, "global MinTxnFee; byte 0x12; concat", AssemblerMaxVersion, - Expect{1, "concat arg 0 wanted type []byte..."}) + exp(1, "concat arg 0 wanted type []byte...", 29)) testProg(t, "int 2; global ZeroAddress; +", AssemblerMaxVersion, - Expect{1, "+ arg 1 wanted type uint64..."}) + exp(1, "+ arg 1 wanted type uint64...", 27)) } func TestAssembleDefault(t *testing.T) { @@ -774,10 +793,10 @@ func TestAssembleDefault(t *testing.T) { source := `byte 0x1122334455 int 1 -+ + + // comment ` - testProg(t, source, AssemblerMaxVersion, Expect{3, "+ arg 0 wanted type uint64 got [5]byte"}) + testProg(t, source, AssemblerMaxVersion, exp(3, "+ arg 0 wanted type uint64 got [5]byte", 1)) } // mutateProgVersion replaces version (first two symbols) in hex-encoded program @@ -792,11 +811,11 @@ func TestOpUint(t *testing.T) { for v := uint64(1); v <= AssemblerMaxVersion; v++ { t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) { ops := newOpStream(v) - ops.IntLiteral(0xcafebabe) + ops.intLiteral(0xcafef00d) prog := ops.prependCBlocks() require.NotNil(t, prog) s := hex.EncodeToString(prog) - expected := mutateProgVersion(v, "012001bef5fad70c22") + expected := mutateProgVersion(v, "xx20018de0fbd70c22") require.Equal(t, expected, s) }) } @@ -809,11 +828,11 @@ func TestOpUint64(t *testing.T) { for v := uint64(1); v <= AssemblerMaxVersion; v++ { t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) { ops := newOpStream(v) - ops.IntLiteral(0xcafebabecafebabe) + ops.intLiteral(0xcafef00dcafef00d) prog := ops.prependCBlocks() require.NotNil(t, prog) s := hex.EncodeToString(prog) - require.Equal(t, mutateProgVersion(v, "012001bef5fad7ecd7aeffca0122"), s) + require.Equal(t, mutateProgVersion(v, "xx20018de0fbd7dc81bcffca0122"), s) }) } } @@ -825,12 +844,12 @@ func TestOpBytes(t *testing.T) { for v := uint64(1); v <= AssemblerMaxVersion; v++ { t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) { ops := newOpStream(v) - ops.ByteLiteral([]byte("abcdef")) + ops.byteLiteral([]byte("abcdef")) prog := ops.prependCBlocks() require.NotNil(t, prog) s := hex.EncodeToString(prog) require.Equal(t, mutateProgVersion(v, "0126010661626364656628"), s) - testProg(t, "byte 0x7; len", v, Expect{1, "...odd length hex string"}) + testProg(t, "byte 0x7; len", v, exp(1, "...odd length hex string", 5)) }) } } @@ -839,8 +858,8 @@ func TestAssembleInt(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - expectedDefaultConsts := "012001bef5fad70c22" - expectedOptimizedConsts := "0181bef5fad70c" + expectedDefaultConsts := "xx20018de0fbd70c22" + expectedOptimizedConsts := "xx818de0fbd70c" for v := uint64(1); v <= AssemblerMaxVersion; v++ { t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) { @@ -849,7 +868,7 @@ func TestAssembleInt(t *testing.T) { expected = expectedOptimizedConsts } - text := "int 0xcafebabe" + text := "int 0xcafef00d" ops := testProg(t, text, v) s := hex.EncodeToString(ops.Program) require.Equal(t, mutateProgVersion(v, expected), s) @@ -893,9 +912,30 @@ func TestAssembleBytes(t *testing.T) { expectedDefaultConsts := "0126010661626364656628" expectedOptimizedConsts := "018006616263646566" - bad := [][]string{ - {"byte", "...needs byte literal argument"}, - {`byte "john" "doe"`, "...with extraneous argument"}, + bad := []struct { + source string + msg string + col int + }{ + {"byte", "...needs byte literal argument", 4}, + {`byte "john" "doe"`, "...with extraneous argument", 12}, + {"byte base64", "... base64 needs...", 5}, // maybe we should report error at end of "base64"? + {"byte base32", "... base32 needs...", 5}, + // these next messages could have the exact column of the problem, but + // would require too much refactoring for the value + {`byte "jo\qhn"`, "...invalid escape sequence...", 5}, + {`byte base64(YWJjZGVm)extrajunk`, "...must end at first closing parenthesis", 5}, + {`byte base64(YWJjZGVm)x`, "...must end at first closing parenthesis", 5}, + {`byte base64(YWJjZGVm`, "...lacks closing parenthesis", 5}, + {`byte base32(MFRGGZDFMY)extrajunk`, "...must end at first closing parenthesis", 5}, + {`byte base32(MFRGGZDFMY)x`, "...must end at first closing parenthesis", 5}, + {`byte base32(MFRGGZDFMY`, "...lacks closing parenthesis", 5}, + {`byte b32 mfrggzdfmy`, "...illegal base32 data at input byte 0", 9}, + {`byte b32 MFrggzdfmy`, "...illegal base32 data at input byte 2", 9}, + {`byte b32(mfrggzdfmy)`, "...illegal base32 data at input byte 4", 5}, + {`byte b32(MFrggzdfmy)`, "...illegal base32 data at input byte 6", 5}, + {`byte base32(mfrggzdfmy)`, "...illegal base32 data at input byte 7", 5}, + {`byte base32(MFrggzdfmy)`, "...illegal base32 data at input byte 9", 5}, } for v := uint64(1); v <= AssemblerMaxVersion; v++ { @@ -916,10 +956,10 @@ func TestAssembleBytes(t *testing.T) { } for _, b := range bad { - testProg(t, b[0], v, Expect{1, b[1]}) + testProg(t, b.source, v, exp(1, b.msg, b.col)) // pushbytes should produce the same errors if v >= 3 { - testProg(t, strings.Replace(b[0], "byte", "pushbytes", 1), v, Expect{1, b[1]}) + testProg(t, strings.Replace(b.source, "byte", "pushbytes", 1), v, exp(1, b.msg, b.col+len("pushs"))) } } }) @@ -970,13 +1010,13 @@ func TestManualCBlocks(t *testing.T) { "bytecblock 0x44 0x55 0x4455; bytec_0; byte 0x55; concat; bytec_2; ==") // But complain if they do not - testProg(t, "intcblock 4; int 3;", 3, Expect{1, "int 3 used without 3 in intcblock"}) - testProg(t, "bytecblock 0x44; byte 0x33;", 3, Expect{1, "byte/addr/method used without value in bytecblock"}) + testProg(t, "intcblock 4; int 3;", 3, exp(1, "value 3 does not appear...", 17)) + testProg(t, "bytecblock 0x44; byte 0x33;", 3, exp(1, "value 0x33 does not appear...", 22)) // Or if the ref comes before the constant block, even if they match - testProg(t, "int 5; intcblock 4;", 3, Expect{1, "intcblock following int"}) - testProg(t, "int 4; intcblock 4;", 3, Expect{1, "intcblock following int"}) - testProg(t, "addr RWXCBB73XJITATVQFOI7MVUUQOL2PFDDSDUMW4H4T2SNSX4SEUOQ2MM7F4; bytecblock 0x44", 3, Expect{1, "bytecblock following byte/addr/method"}) + testProg(t, "int 5; intcblock 4;", 3, exp(1, "intcblock following int", 7)) + testProg(t, "int 4; intcblock 4;", 3, exp(1, "intcblock following int", 7)) + testProg(t, "addr RWXCBB73XJITATVQFOI7MVUUQOL2PFDDSDUMW4H4T2SNSX4SEUOQ2MM7F4; bytecblock 0x44", 3, exp(1, "bytecblock following byte/addr/method")) // But we can't complain precisely once backjumps are allowed, so we force // compile to push*. (We don't analyze the CFG, so we don't know if we can @@ -989,9 +1029,9 @@ func TestManualCBlocks(t *testing.T) { "bytecblock 0x44 0x55 0x4455; byte 0x44; byte 0x55; concat; byte 0x4455; ==", "bytecblock 0x44 0x55 0x4455; pushbytes 0x44; pushbytes 0x55; concat; pushbytes 0x4455; ==") // Can't switch to push* after the fact. - testProg(t, "int 5; intcblock 4;", 4, Expect{1, "intcblock following int"}) - testProg(t, "int 4; intcblock 4;", 4, Expect{1, "intcblock following int"}) - testProg(t, "addr RWXCBB73XJITATVQFOI7MVUUQOL2PFDDSDUMW4H4T2SNSX4SEUOQ2MM7F4; bytecblock 0x44", 4, Expect{1, "bytecblock following byte/addr/method"}) + testProg(t, "int 5; intcblock 4;", 4, exp(1, "intcblock following int", 7)) + testProg(t, "int 4; intcblock 4;", 4, exp(1, "intcblock following int", 7)) + testProg(t, "addr RWXCBB73XJITATVQFOI7MVUUQOL2PFDDSDUMW4H4T2SNSX4SEUOQ2MM7F4; bytecblock 0x44", 4, exp(1, "bytecblock following byte/addr/method", 65)) // Ignore manually added cblocks in deadcode, so they can be added easily to // existing programs. There are proposals to put metadata there. @@ -1022,12 +1062,12 @@ func TestManualCBlocksPreBackBranch(t *testing.T) { testProg(t, "intcblock 10 20; int 10;", backBranchEnabledVersion-1) // By the same token, assembly complains if that intcblock doesn't have the // constant. In v3, and v3 only, it *could* pushint. - testProg(t, "intcblock 10 20; int 30;", backBranchEnabledVersion-1, Expect{1, "int 30 used..."}) + testProg(t, "intcblock 10 20; int 30;", backBranchEnabledVersion-1, exp(1, "value 30 does not appear...")) // Since the second intcblock is dead, the `int 10` "sees" the first block, not the second testProg(t, "intcblock 10 20; b skip; intcblock 3 4 5; skip: int 10;", backBranchEnabledVersion-1) testProg(t, "intcblock 10 20; b skip; intcblock 3 4 5; skip: int 3;", backBranchEnabledVersion-1, - Expect{1, "int 3 used..."}) + exp(1, "value 3 does not appear...", 52)) // Here, the intcblock in effect is unknowable, better to force the user to // use intc (unless pushint is available to save the day). @@ -1038,23 +1078,23 @@ func TestManualCBlocksPreBackBranch(t *testing.T) { // backBranchEnabledVersion-2 does not testProg(t, "intcblock 10 20; txn NumAppArgs; bz skip; intcblock 3 4 5; skip: int 10;", backBranchEnabledVersion-2, - Expect{1, "int 10 used with manual intcblocks. Use intc."}) + exp(1, "int 10 used with manual intcblocks. Use intc.", 65)) testProg(t, "intcblock 10 20; txn NumAppArgs; bz skip; intcblock 3 4 5; skip: int 3;", backBranchEnabledVersion-2, - Expect{1, "int 3 used with manual intcblocks. Use intc."}) + exp(1, "int 3 used with manual intcblocks. Use intc.", 65)) // REPEAT ABOVE, BUT FOR BYTE BLOCKS testProg(t, "bytecblock 0x10 0x20; byte 0x10;", backBranchEnabledVersion-1) - testProg(t, "bytecblock 0x10 0x20; byte 0x30;", backBranchEnabledVersion-1, Expect{1, "byte/addr/method used..."}) + testProg(t, "bytecblock 0x10 0x20; byte 0x30;", backBranchEnabledVersion-1, exp(1, "value 0x30 does not appear...", 27)) testProg(t, "bytecblock 0x10 0x20; b skip; bytecblock 0x03 0x04 0x05; skip: byte 0x10;", backBranchEnabledVersion-1) testProg(t, "bytecblock 0x10 0x20; b skip; bytecblock 0x03 0x04 0x05; skip: byte 0x03;", backBranchEnabledVersion-1, - Expect{1, "byte/addr/method used..."}) + exp(1, "value 0x03 does not appear...", 68)) testProg(t, "bytecblock 0x10 0x20; txn NumAppArgs; bz skip; bytecblock 0x03 0x04 0x05; skip: byte 0x10;", backBranchEnabledVersion-1) testProg(t, "bytecblock 0x10 0x20; txn NumAppArgs; bz skip; bytecblock 0x03 0x04 0x05; skip: byte 0x03;", backBranchEnabledVersion-1) testProg(t, "bytecblock 0x10 0x20; txn NumAppArgs; bz skip; bytecblock 0x03 0x04 0x05; skip: byte 0x10;", backBranchEnabledVersion-2, - Expect{1, "byte 0x10 used with manual bytecblocks. Use bytec."}) + exp(1, "byte 0x10 used with manual bytecblocks. Use bytec.")) testProg(t, "bytecblock 0x10 0x20; txn NumAppArgs; bz skip; bytecblock 0x03 0x04 0x05; skip: byte 0x03;", backBranchEnabledVersion-2, - Expect{1, "byte 0x03 used with manual bytecblocks. Use bytec."}) + exp(1, "byte 0x03 used with manual bytecblocks. Use bytec.")) } func TestAssembleOptimizedConstants(t *testing.T) { @@ -1288,13 +1328,25 @@ int ClearState } } -func TestFieldsFromLine(t *testing.T) { +func stringsOf(tokens []token) []string { + if tokens == nil { + return nil + } + out := make([]string, len(tokens)) + for i, t := range tokens { + out[i] = t.str + } + return out +} + +func TestTokensFromLine(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() check := func(line string, tokens ...string) { t.Helper() - assert.Equal(t, tokensFromLine(line), tokens) + fromLine := stringsOf(tokensFromLine(line, 0)) + assert.Equal(t, fromLine, tokens) } check("op arg", "op", "arg") @@ -1348,38 +1400,29 @@ func TestNextStatement(t *testing.T) { // this test ensures nextStatement splits tokens on semicolons properly // macro testing should be handled in TestMacros ops := newOpStream(AssemblerMaxVersion) - check := func(tokens []string, left []string, right []string) { + check := func(line string, left []string, right []string) { t.Helper() + tokens := tokensFromLine(line, 0) current, next := nextStatement(&ops, tokens) - assert.Equal(t, left, current) - assert.Equal(t, right, next) + assert.Equal(t, left, stringsOf(current)) + assert.Equal(t, right, stringsOf(next)) } - check([]string{"hey,", "how's", ";", ";", "it", "going", ";"}, + check(`hey, how's ; ; it going ;`, []string{"hey,", "how's"}, []string{";", "it", "going", ";"}, ) - check([]string{";"}, - []string{}, - []string{}, - ) + check(";", []string{}, []string{}) - check([]string{";", "it", "going"}, - []string{}, - []string{"it", "going"}, - ) + check(`; it going`, []string{}, []string{"it", "going"}) - check([]string{"hey,", "how's"}, - []string{"hey,", "how's"}, - nil, - ) + check(`hey, how's`, []string{"hey,", "how's"}, nil) - check([]string{`"hey in quotes;"`, "getting", `";"`, ";", "tricky"}, + check(`"hey in quotes;" getting ";" ; tricky`, []string{`"hey in quotes;"`, "getting", `";"`}, []string{"tricky"}, ) - } func TestAssembleRejectNegJump(t *testing.T) { @@ -1392,7 +1435,7 @@ bnz wat int 2` for v := uint64(1); v < backBranchEnabledVersion; v++ { t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) { - testProg(t, source, v, Expect{3, "label \"wat\" is a back reference..."}) + testProg(t, source, v, exp(3, "label \"wat\" is a back reference...", 4)) }) } for v := uint64(backBranchEnabledVersion); v <= AssemblerMaxVersion; v++ { @@ -1446,7 +1489,23 @@ bnz nowhere int 2` for v := uint64(1); v <= AssemblerMaxVersion; v++ { t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) { - testProg(t, source, v, Expect{2, "reference to undefined label \"nowhere\""}) + testProg(t, source, v, exp(2, "reference to undefined label \"nowhere\"", 4)) + }) + } +} + +func TestAssembleRejectDupLabel(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + source := ` + XXX: int 1; pop; + XXX: int 1; pop; // different indent to prove the returned column is from the right label + int 1 +` + for v := uint64(1); v <= AssemblerMaxVersion; v++ { + t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) { + testProg(t, source, v, exp(3, "duplicate label \"XXX\"", 2)) }) } } @@ -1479,8 +1538,8 @@ int 2` for v := uint64(1); v <= AssemblerMaxVersion; v++ { t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) { testProg(t, source, v, - Expect{2, "reference to undefined label \"nowhere\""}, - Expect{4, "txn unknown field: \"XYZ\""}) + exp(2, "reference to undefined label \"nowhere\"", 4), + exp(4, "txn unknown field: \"XYZ\"", 4)) }) } } @@ -1681,20 +1740,48 @@ func TestConstantArgs(t *testing.T) { t.Parallel() for v := uint64(1); v <= AssemblerMaxVersion; v++ { - testProg(t, "int", v, Expect{1, "int needs one immediate argument, was given 0"}) - testProg(t, "int 1 2", v, Expect{1, "int needs one immediate argument, was given 2"}) - testProg(t, "intc", v, Expect{1, "intc needs one immediate argument, was given 0"}) - testProg(t, "intc hi bye", v, Expect{1, "intc needs one immediate argument, was given 2"}) - testProg(t, "byte", v, Expect{1, "byte needs byte literal argument"}) - testProg(t, "bytec", v, Expect{1, "bytec needs one immediate argument, was given 0"}) - testProg(t, "bytec 1 x", v, Expect{1, "bytec needs one immediate argument, was given 2"}) - testProg(t, "addr", v, Expect{1, "addr needs one immediate argument, was given 0"}) - testProg(t, "addr x y", v, Expect{1, "addr needs one immediate argument, was given 2"}) + testProg(t, "int", v, exp(1, "int expects 1 immediate argument", 3)) + testProg(t, "int pay", v) + testProg(t, "int pya", v, exp(1, "unable to parse \"pya\" as integer", 4)) + testProg(t, "int 1 2", v, exp(1, "int expects 1 immediate argument", 6)) + testProg(t, "intc", v, exp(1, "intc expects 1 immediate argument", 4)) + testProg(t, "intc pay", v, exp(1, "unable to parse constant \"pay\" as integer", 5)) // don't accept "pay" constant + testProg(t, "intc hi bye", v, exp(1, "intc expects 1 immediate argument", 8)) + testProg(t, "byte", v, exp(1, "byte needs byte literal argument", 4)) + testProg(t, "byte b32", v, exp(1, "byte b32 needs byte literal argument")) + testProg(t, "byte 0xaa 0xbb", v, exp(1, "byte with extraneous argument", 10)) + testProg(t, "byte b32 MFRGGZDFMY MFRGGZDFMY", v, exp(1, "byte with extraneous argument", 20)) + testProg(t, "bytec", v, exp(1, "bytec expects 1 immediate argument")) + testProg(t, "bytec 1 x", v, exp(1, "bytec expects 1 immediate argument")) + testProg(t, "bytec pay", v, exp(1, "unable to parse constant \"pay\" as integer", 6)) // don't accept "pay" constant + testProg(t, "addr", v, exp(1, "addr expects 1 immediate argument", 4)) + testProg(t, "addr x y", v, exp(1, "addr expects 1 immediate argument", 9)) + testProg(t, "addr x", v, exp(1, "failed to decode address x ...", 5)) + testProg(t, "method", v, exp(1, "method expects 1 immediate argument", 6)) + testProg(t, "method xx yy", v, exp(1, "method expects 1 immediate argument", 10)) } for v := uint64(3); v <= AssemblerMaxVersion; v++ { - testProg(t, "pushint", v, Expect{1, "pushint needs one immediate argument, was given 0"}) - testProg(t, "pushint 3 4", v, Expect{1, "pushint needs one immediate argument, was given 2"}) - testProg(t, "pushbytes", v, Expect{1, "pushbytes needs byte literal argument"}) + testProg(t, "pushint", v, exp(1, "pushint expects 1 immediate argument")) + testProg(t, "pushint 3 4", v, exp(1, "pushint expects 1 immediate argument")) + testProg(t, "pushint x", v, exp(1, "unable to parse \"x\" as integer", 8)) + testProg(t, "pushbytes", v, exp(1, "pushbytes needs byte literal argument")) + testProg(t, "pushbytes b32", v, exp(1, "pushbytes b32 needs byte literal argument")) + testProg(t, "pushbytes b32(MFRGGZDFMY", v, exp(1, "pushbytes argument b32(MFRGGZDFMY lacks closing parenthesis")) + testProg(t, "pushbytes b32(MFRGGZDFMY)X", v, exp(1, "pushbytes argument b32(MFRGGZDFMY)X must end at first closing parenthesis")) + } + + for v := uint64(8); v <= AssemblerMaxVersion; v++ { + testProg(t, "pushints", v) + testProg(t, "pushints 200", v) + testProg(t, "pushints 3 4", v) + + testProg(t, "pushbytess", v) + testProg(t, "pushbytess 0xff", v) + testProg(t, "pushbytess 0xaa 0xbb", v) + + testProg(t, "pushbytess b32(MFRGGZDFMY) b32(MFRGGZDFMY)", v) + testProg(t, "pushbytess b32 MFRGGZDFMY b32 MFRGGZDFMY", v) + testProg(t, "pushbytess b32(MFRGGZDFMY b32(MFRGGZDFMY)", v, exp(1, "pushbytess argument b32(MFRGGZDFMY lacks closing parenthesis")) } } @@ -1703,17 +1790,17 @@ func TestBranchArgs(t *testing.T) { t.Parallel() for v := uint64(2); v <= AssemblerMaxVersion; v++ { - testProg(t, "b", v, Expect{1, "b needs a single label argument"}) - testProg(t, "b lab1 lab2", v, Expect{1, "b needs a single label argument"}) - testProg(t, "int 1; bz", v, Expect{1, "bz needs a single label argument"}) - testProg(t, "int 1; bz a b", v, Expect{1, "bz needs a single label argument"}) - testProg(t, "int 1; bnz", v, Expect{1, "bnz needs a single label argument"}) - testProg(t, "int 1; bnz c d", v, Expect{1, "bnz needs a single label argument"}) + testProg(t, "b", v, exp(1, "b expects 1 immediate argument")) + testProg(t, "b lab1 lab2", v, exp(1, "b expects 1 immediate argument")) + testProg(t, "int 1; bz", v, exp(1, "bz expects 1 immediate argument")) + testProg(t, "int 1; bz a b", v, exp(1, "bz expects 1 immediate argument")) + testProg(t, "int 1; bnz", v, exp(1, "bnz expects 1 immediate argument")) + testProg(t, "int 1; bnz c d", v, exp(1, "bnz expects 1 immediate argument")) } for v := uint64(4); v <= AssemblerMaxVersion; v++ { - testProg(t, "callsub", v, Expect{1, "callsub needs a single label argument"}) - testProg(t, "callsub one two", v, Expect{1, "callsub needs a single label argument"}) + testProg(t, "callsub", v, exp(1, "callsub expects 1 immediate argument")) + testProg(t, "callsub one two", v, exp(1, "callsub expects 1 immediate argument")) } } @@ -1836,7 +1923,7 @@ balance int 1 ==` for v := uint64(2); v < directRefEnabledVersion; v++ { - testProg(t, source, v, Expect{2, "balance arg 0 wanted type uint64 got [1]byte"}) + testProg(t, source, v, exp(2, "balance arg 0 wanted type uint64 got [1]byte")) } for v := uint64(directRefEnabledVersion); v <= AssemblerMaxVersion; v++ { testProg(t, source, v) @@ -1852,7 +1939,7 @@ min_balance int 1 ==` for v := uint64(3); v < directRefEnabledVersion; v++ { - testProg(t, source, v, Expect{2, "min_balance arg 0 wanted type uint64 got [1]byte"}) + testProg(t, source, v, exp(2, "min_balance arg 0 wanted type uint64 got [1]byte")) } for v := uint64(directRefEnabledVersion); v <= AssemblerMaxVersion; v++ { testProg(t, source, v) @@ -1866,25 +1953,25 @@ func TestAssembleAsset(t *testing.T) { for v := uint64(2); v <= AssemblerMaxVersion; v++ { t.Run(strconv.Itoa(int(v)), func(t *testing.T) { testProg(t, "asset_holding_get ABC 1", v, - Expect{1, "asset_holding_get ABC 1 expects 2 stack arguments..."}) + exp(1, "asset_holding_get ABC 1 expects 2 stack arguments...")) testProg(t, "int 1; asset_holding_get ABC 1", v, - Expect{1, "asset_holding_get ABC 1 expects 2 stack arguments..."}) + exp(1, "asset_holding_get ABC 1 expects 2 stack arguments...")) testProg(t, "int 1; int 1; asset_holding_get ABC 1", v, - Expect{1, "asset_holding_get expects 1 immediate argument"}) + exp(1, "asset_holding_get expects 1 immediate argument")) testProg(t, "int 1; int 1; asset_holding_get ABC", v, - Expect{1, "asset_holding_get unknown field: \"ABC\""}) + exp(1, "asset_holding_get unknown field: \"ABC\"")) testProg(t, "byte 0x1234; asset_params_get ABC 1", v, - Expect{1, "asset_params_get ABC 1 arg 0 wanted type uint64..."}) + exp(1, "asset_params_get ABC 1 arg 0 wanted type uint64...")) // Test that AssetUnitName is known to return bytes testProg(t, "int 1; asset_params_get AssetUnitName; pop; int 1; +", v, - Expect{1, "+ arg 0 wanted type uint64..."}) + exp(1, "+ arg 0 wanted type uint64...")) // Test that AssetTotal is known to return uint64 testProg(t, "int 1; asset_params_get AssetTotal; pop; byte 0x12; concat", v, - Expect{1, "concat arg 0 wanted type []byte..."}) + exp(1, "concat arg 0 wanted type []byte...")) testLine(t, "asset_params_get ABC 1", v, "asset_params_get expects 1 immediate argument") testLine(t, "asset_params_get ABC", v, "asset_params_get unknown field: \"ABC\"") @@ -2250,7 +2337,7 @@ func TestStringLiteralParsing(t *testing.T) { s = `"test\a"` result, err = parseStringLiteral(s) - require.EqualError(t, err, "invalid escape seq \\a") + require.EqualError(t, err, "invalid escape sequence \\a") require.Nil(t, result) s = `"test\x10\x1"` @@ -2270,15 +2357,18 @@ func TestPragmas(t *testing.T) { } testProg(t, `#pragma version 100`, assemblerNoVersion, - Expect{1, "unsupported version: 100"}) + exp(1, "unsupported version: 100")) - testProg(t, `int 1`, 99, Expect{0, "Can not assemble version 99"}) + testProg(t, `int 1`, 99, exp(0, "Can not assemble version 99")) // Allow this on the off chance someone needs to reassemble an old logigsig testProg(t, `#pragma version 0`, assemblerNoVersion) testProg(t, `#pragma version a`, assemblerNoVersion, - Expect{1, `bad #pragma version: "a"`}) + exp(1, `bad #pragma version: "a"`)) + + testProg(t, `#pramga version 3`, assemblerNoVersion, + exp(1, "unknown directive: pramga")) // will default to 1 ops := testProg(t, "int 3", assemblerNoVersion) @@ -2292,24 +2382,24 @@ func TestPragmas(t *testing.T) { require.Equal(t, uint64(2), ops.Version) // changing version is not allowed - testProg(t, "#pragma version 1", 2, Expect{1, "version mismatch..."}) - testProg(t, "#pragma version 2", 1, Expect{1, "version mismatch..."}) + testProg(t, "#pragma version 1", 2, exp(1, "version mismatch...")) + testProg(t, "#pragma version 2", 1, exp(1, "version mismatch...")) testProg(t, "#pragma version 2\n#pragma version 1", assemblerNoVersion, - Expect{2, "version mismatch..."}) + exp(2, "version mismatch...")) // repetitive, but fine ops = testProg(t, "#pragma version 2\n#pragma version 2", assemblerNoVersion) require.Equal(t, uint64(2), ops.Version) testProg(t, "\nint 1\n#pragma version 2", assemblerNoVersion, - Expect{3, "#pragma version is only allowed before instructions"}) + exp(3, "#pragma version is only allowed before instructions")) testProg(t, "#pragma run-mode 2", assemblerNoVersion, - Expect{1, `unsupported pragma directive: "run-mode"`}) + exp(1, `unsupported pragma directive: "run-mode"`)) testProg(t, "#pragma versions", assemblerNoVersion, - Expect{1, `unsupported pragma directive: "versions"`}) + exp(1, `unsupported pragma directive: "versions"`)) ops = testProg(t, "#pragma version 1", assemblerNoVersion) require.Equal(t, uint64(1), ops.Version) @@ -2317,33 +2407,33 @@ func TestPragmas(t *testing.T) { ops = testProg(t, "\n#pragma version 1", assemblerNoVersion) require.Equal(t, uint64(1), ops.Version) - testProg(t, "#pragma", assemblerNoVersion, Expect{1, "empty pragma"}) + testProg(t, "#pragma", assemblerNoVersion, exp(1, "empty pragma")) testProg(t, "#pragma version", assemblerNoVersion, - Expect{1, "no version value"}) + exp(1, "no version value")) ops = testProg(t, " #pragma version 5 ", assemblerNoVersion) require.Equal(t, uint64(5), ops.Version) testProg(t, "#pragma version 5 blah", assemblerNoVersion, - Expect{1, "unexpected extra tokens: blah"}) + exp(1, "unexpected extra tokens: blah")) testProg(t, "#pragma typetrack", assemblerNoVersion, - Expect{1, "no typetrack value"}) + exp(1, "no typetrack value")) testProg(t, "#pragma typetrack blah", assemblerNoVersion, - Expect{1, `bad #pragma typetrack: "blah"`}) + exp(1, `bad #pragma typetrack: "blah"`)) testProg(t, "#pragma typetrack false blah", assemblerNoVersion, - Expect{1, "unexpected extra tokens: blah"}) + exp(1, "unexpected extra tokens: blah")) // Currently pragmas don't treat semicolons as newlines. It would probably // be nice to fix this. testProg(t, "#pragma version 5; int 1", assemblerNoVersion, - Expect{1, "unexpected extra tokens: ; int 1"}) + exp(1, "unexpected extra tokens: ; int 1")) testProg(t, "#pragma typetrack false; int 1", assemblerNoVersion, - Expect{1, "unexpected extra tokens: ; int 1"}) + exp(1, "unexpected extra tokens: ; int 1")) } func TestAssemblePragmaVersion(t *testing.T) { @@ -2357,8 +2447,8 @@ int 1 ops1 := testProg(t, "int 1", 1) require.Equal(t, ops1.Program, ops.Program) - testProg(t, text, 0, Expect{1, "version mismatch..."}) - testProg(t, text, 2, Expect{1, "version mismatch..."}) + testProg(t, text, 0, exp(1, "version mismatch...")) + testProg(t, text, 2, exp(1, "version mismatch...")) testProg(t, text, assemblerNoVersion) ops = testProg(t, text, assemblerNoVersion) @@ -2371,8 +2461,8 @@ int 1 ops2 := testProg(t, "int 1", 2) require.Equal(t, ops2.Program, ops.Program) - testProg(t, text, 0, Expect{1, "version mismatch..."}) - testProg(t, text, 1, Expect{1, "version mismatch..."}) + testProg(t, text, 0, exp(1, "version mismatch...")) + testProg(t, text, 1, exp(1, "version mismatch...")) ops = testProg(t, text, assemblerNoVersion) require.Equal(t, ops2.Program, ops.Program) @@ -2389,7 +2479,7 @@ len require.Equal(t, ops2.Program, ops.Program) testProg(t, "#pragma unk", assemblerNoVersion, - Expect{1, `unsupported pragma directive: "unk"`}) + exp(1, `unsupported pragma directive: "unk"`)) } func TestAssembleConstants(t *testing.T) { @@ -2463,7 +2553,7 @@ func TestMethodWarning(t *testing.T) { } require.Len(t, ops.Warnings, 1) - require.Contains(t, ops.Warnings[0].Error(), "Invalid ARC-4 ABI method signature for method op") + require.ErrorContains(t, ops.Warnings[0], "invalid ARC-4 ABI method signature for method op") } } } @@ -2503,29 +2593,29 @@ func TestSwapTypeCheck(t *testing.T) { t.Parallel() /* reconfirm that we detect this type error */ - testProg(t, "int 1; byte 0x1234; +", AssemblerMaxVersion, Expect{1, "+ arg 1..."}) + testProg(t, "int 1; byte 0x1234; +", AssemblerMaxVersion, exp(1, "+ arg 1...")) /* despite swap, we track types */ - testProg(t, "int 1; byte 0x1234; swap; +", AssemblerMaxVersion, Expect{1, "+ arg 0..."}) - testProg(t, "byte 0x1234; int 1; swap; +", AssemblerMaxVersion, Expect{1, "+ arg 1..."}) + testProg(t, "int 1; byte 0x1234; swap; +", AssemblerMaxVersion, exp(1, "+ arg 0...")) + testProg(t, "byte 0x1234; int 1; swap; +", AssemblerMaxVersion, exp(1, "+ arg 1...")) } func TestDigAsm(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - testProg(t, "int 1; dig; +", AssemblerMaxVersion, Expect{1, "dig expects 1 immediate..."}) - testProg(t, "int 1; dig junk; +", AssemblerMaxVersion, Expect{1, "dig unable to parse..."}) + testProg(t, "int 1; dig; +", AssemblerMaxVersion, exp(1, "dig expects 1 immediate...")) + testProg(t, "int 1; dig junk; +", AssemblerMaxVersion, exp(1, "dig unable to parse...")) testProg(t, "int 1; byte 0x1234; int 2; dig 2; +", AssemblerMaxVersion) testProg(t, "byte 0x32; byte 0x1234; int 2; dig 2; +", AssemblerMaxVersion, - Expect{1, "+ arg 1..."}) + exp(1, "+ arg 1...")) testProg(t, "byte 0x32; byte 0x1234; int 2; dig 3; +", AssemblerMaxVersion, - Expect{1, "dig 3 expects 4..."}) + exp(1, "dig 3 expects 4...")) testProg(t, "int 1; byte 0x1234; int 2; dig 12; +", AssemblerMaxVersion, - Expect{1, "dig 12 expects 13..."}) + exp(1, "dig 12 expects 13...")) // Confirm that digging something out does not ruin our knowledge about the types in the middle testProg(t, "int 1; byte 0x1234; byte 0x1234; dig 2; dig 3; +; pop; +", AssemblerMaxVersion, - Expect{1, "+ arg 1..."}) + exp(1, "+ arg 1...")) testProg(t, "int 3; pushbytes \"123456\"; int 1; dig 2; substring3", AssemblerMaxVersion) } @@ -2533,62 +2623,62 @@ func TestDigAsm(t *testing.T) { func TestBuryAsm(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - testProg(t, "int 1; bury; +", AssemblerMaxVersion, Expect{1, "bury expects 1 immediate..."}) - testProg(t, "int 1; bury junk; +", AssemblerMaxVersion, Expect{1, "bury unable to parse..."}) + testProg(t, "int 1; bury; +", AssemblerMaxVersion, exp(1, "bury expects 1 immediate...")) + testProg(t, "int 1; bury junk; +", AssemblerMaxVersion, exp(1, "bury unable to parse...")) testProg(t, "int 1; byte 0x1234; int 2; bury 1; +", AssemblerMaxVersion) // the 2 replaces the byte string testProg(t, "int 2; int 2; byte 0x1234; bury 1; +", AssemblerMaxVersion, - Expect{1, "+ arg 1..."}) + exp(1, "+ arg 1...")) testProg(t, "byte 0x32; byte 0x1234; int 2; bury 3; +", AssemblerMaxVersion, - Expect{1, "bury 3 expects 4..."}) + exp(1, "bury 3 expects 4...")) testProg(t, "int 1; byte 0x1234; int 2; bury 12; +", AssemblerMaxVersion, - Expect{1, "bury 12 expects 13..."}) + exp(1, "bury 12 expects 13...")) // We do not lose track of the ints between ToS and bury index testProg(t, "int 0; int 1; int 2; int 4; bury 3; concat", AssemblerMaxVersion, - Expect{1, "concat arg 1 wanted type []byte..."}) + exp(1, "concat arg 1 wanted type []byte...")) // Even when we are burying into unknown (seems repetitive, but is an easy bug) testProg(t, "int 0; int 0; b LABEL; LABEL: int 1; int 2; int 4; bury 4; concat", AssemblerMaxVersion, - Expect{1, "concat arg 1 wanted type []byte..."}) + exp(1, "concat arg 1 wanted type []byte...")) } func TestEqualsTypeCheck(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - testProg(t, "int 1; byte 0x1234; ==", AssemblerMaxVersion, Expect{1, "== arg 0..."}) - testProg(t, "int 1; byte 0x1234; !=", AssemblerMaxVersion, Expect{1, "!= arg 0..."}) - testProg(t, "byte 0x1234; int 1; ==", AssemblerMaxVersion, Expect{1, "== arg 0..."}) - testProg(t, "byte 0x1234; int 1; !=", AssemblerMaxVersion, Expect{1, "!= arg 0..."}) + testProg(t, "int 1; byte 0x1234; ==", AssemblerMaxVersion, exp(1, "== arg 0...")) + testProg(t, "int 1; byte 0x1234; !=", AssemblerMaxVersion, exp(1, "!= arg 0...")) + testProg(t, "byte 0x1234; int 1; ==", AssemblerMaxVersion, exp(1, "== arg 0...")) + testProg(t, "byte 0x1234; int 1; !=", AssemblerMaxVersion, exp(1, "!= arg 0...")) } func TestDupTypeCheck(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - testProg(t, "byte 0x1234; dup; int 1; +", AssemblerMaxVersion, Expect{1, "+ arg 0..."}) + testProg(t, "byte 0x1234; dup; int 1; +", AssemblerMaxVersion, exp(1, "+ arg 0...")) testProg(t, "byte 0x1234; int 1; dup; +", AssemblerMaxVersion) - testProg(t, "byte 0x1234; int 1; dup2; +", AssemblerMaxVersion, Expect{1, "+ arg 0..."}) - testProg(t, "int 1; byte 0x1234; dup2; +", AssemblerMaxVersion, Expect{1, "+ arg 1..."}) + testProg(t, "byte 0x1234; int 1; dup2; +", AssemblerMaxVersion, exp(1, "+ arg 0...")) + testProg(t, "int 1; byte 0x1234; dup2; +", AssemblerMaxVersion, exp(1, "+ arg 1...")) - testProg(t, "byte 0x1234; int 1; dup; dig 1; len", AssemblerMaxVersion, Expect{1, "len arg 0..."}) - testProg(t, "int 1; byte 0x1234; dup; dig 1; !", AssemblerMaxVersion, Expect{1, "! arg 0..."}) + testProg(t, "byte 0x1234; int 1; dup; dig 1; len", AssemblerMaxVersion, exp(1, "len arg 0...")) + testProg(t, "int 1; byte 0x1234; dup; dig 1; !", AssemblerMaxVersion, exp(1, "! arg 0...")) - testProg(t, "byte 0x1234; int 1; dup2; dig 2; len", AssemblerMaxVersion, Expect{1, "len arg 0..."}) - testProg(t, "int 1; byte 0x1234; dup2; dig 2; !", AssemblerMaxVersion, Expect{1, "! arg 0..."}) + testProg(t, "byte 0x1234; int 1; dup2; dig 2; len", AssemblerMaxVersion, exp(1, "len arg 0...")) + testProg(t, "int 1; byte 0x1234; dup2; dig 2; !", AssemblerMaxVersion, exp(1, "! arg 0...")) } func TestSelectTypeCheck(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - testProg(t, "int 1; int 2; int 3; select; len", AssemblerMaxVersion, Expect{1, "len arg 0..."}) - testProg(t, "byte 0x1234; byte 0x5678; int 3; select; !", AssemblerMaxVersion, Expect{1, "! arg 0..."}) + testProg(t, "int 1; int 2; int 3; select; len", AssemblerMaxVersion, exp(1, "len arg 0...")) + testProg(t, "byte 0x1234; byte 0x5678; int 3; select; !", AssemblerMaxVersion, exp(1, "! arg 0...")) } func TestSetBitTypeCheck(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - testProg(t, "int 1; int 2; int 3; setbit; len", AssemblerMaxVersion, Expect{1, "len arg 0..."}) - testProg(t, "byte 0x1234; int 2; int 3; setbit; !", AssemblerMaxVersion, Expect{1, "! arg 0..."}) + testProg(t, "int 1; int 2; int 3; setbit; len", AssemblerMaxVersion, exp(1, "len arg 0...")) + testProg(t, "byte 0x1234; int 2; int 3; setbit; !", AssemblerMaxVersion, exp(1, "! arg 0...")) } func TestScratchTypeCheck(t *testing.T) { @@ -2597,15 +2687,15 @@ func TestScratchTypeCheck(t *testing.T) { // All scratch slots should start as uint64 testProg(t, "load 0; int 1; +", AssemblerMaxVersion) // Check load and store accurately using the scratch space - testProg(t, "byte 0x01; store 0; load 0; int 1; +", AssemblerMaxVersion, Expect{1, "+ arg 0..."}) + testProg(t, "byte 0x01; store 0; load 0; int 1; +", AssemblerMaxVersion, exp(1, "+ arg 0...")) // Loads should know the type it's loading if all the slots are the same type - testProg(t, "int 0; loads; btoi", AssemblerMaxVersion, Expect{1, "btoi arg 0..."}) + testProg(t, "int 0; loads; btoi", AssemblerMaxVersion, exp(1, "btoi arg 0...")) // Loads only knows the type when slot is a const - testProg(t, "byte 0x01; store 0; int 1; loads; btoi", AssemblerMaxVersion, Expect{1, "btoi arg 0..."}) + testProg(t, "byte 0x01; store 0; int 1; loads; btoi", AssemblerMaxVersion, exp(1, "btoi arg 0...")) // Loads doesnt know the type if its the result of some other expression where we lose information testProg(t, "byte 0x01; store 0; load 0; btoi; loads; btoi", AssemblerMaxVersion) // Stores should only set slots to StackAny if they are not the same type as what is being stored - testProg(t, "byte 0x01; store 0; int 3; byte 0x01; stores; load 0; int 1; +", AssemblerMaxVersion, Expect{1, "+ arg 0..."}) + testProg(t, "byte 0x01; store 0; int 3; byte 0x01; stores; load 0; int 1; +", AssemblerMaxVersion, exp(1, "+ arg 0...")) // ScratchSpace should reset after hitting label in deadcode testProg(t, "byte 0x01; store 0; b label1; label1:; load 0; int 1; +", AssemblerMaxVersion) // But it should reset to StackAny not uint64 @@ -2613,7 +2703,7 @@ func TestScratchTypeCheck(t *testing.T) { // Callsubs should also reset the scratch space testProg(t, "callsub A; load 0; btoi; return; A: byte 0x01; store 0; retsub", AssemblerMaxVersion) // But the scratchspace should still be tracked after the callsub - testProg(t, "callsub A; int 1; store 0; load 0; btoi; return; A: retsub", AssemblerMaxVersion, Expect{1, "btoi arg 0..."}) + testProg(t, "callsub A; int 1; store 0; load 0; btoi; return; A: retsub", AssemblerMaxVersion, exp(1, "btoi arg 0...")) } @@ -2652,7 +2742,7 @@ func TestScratchBounds(t *testing.T) { require.Equal(t, osv.AVMType, avmAny) require.ElementsMatch(t, osv.Bound, static(0)) - testProg(t, "byte 0xff; store 1; load 1; return", AssemblerMaxVersion, Expect{1, "return arg 0 wanted type uint64 ..."}) + testProg(t, "byte 0xff; store 1; load 1; return", AssemblerMaxVersion, exp(1, "return arg 0 wanted type uint64 ...")) } // TestProtoAsm confirms that the assembler will yell at you if you are @@ -2661,7 +2751,7 @@ func TestScratchBounds(t *testing.T) { func TestProtoAsm(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - testProg(t, "proto 0 0", AssemblerMaxVersion, Expect{1, "proto must be unreachable..."}) + testProg(t, "proto 0 0", AssemblerMaxVersion, exp(1, "proto must be unreachable...")) testProg(t, notrack("proto 0 0"), AssemblerMaxVersion) testProg(t, "b a; int 1; a: proto 0 0", AssemblerMaxVersion) // we could flag a `b` to `proto` @@ -2685,9 +2775,9 @@ func TestCoverAsm(t *testing.T) { t.Parallel() testProg(t, `int 4; byte "john"; int 5; cover 2; pop; +`, AssemblerMaxVersion) testProg(t, `int 4; byte "ayush"; int 5; cover 1; pop; +`, AssemblerMaxVersion) - testProg(t, `int 4; byte "john"; int 5; cover 2; +`, AssemblerMaxVersion, Expect{1, "+ arg 1..."}) + testProg(t, `int 4; byte "john"; int 5; cover 2; +`, AssemblerMaxVersion, exp(1, "+ arg 1...")) - testProg(t, `int 4; cover junk`, AssemblerMaxVersion, Expect{1, "cover unable to parse n ..."}) + testProg(t, `int 4; cover junk`, AssemblerMaxVersion, exp(1, "cover unable to parse n ...")) testProg(t, notrack(`int 4; int 5; cover 0`), AssemblerMaxVersion) } @@ -2697,45 +2787,45 @@ func TestUncoverAsm(t *testing.T) { testProg(t, `int 4; byte "john"; int 5; uncover 2; +`, AssemblerMaxVersion) testProg(t, `int 4; byte "ayush"; int 5; uncover 1; pop; +`, AssemblerMaxVersion) testProg(t, `int 1; byte "jj"; byte "ayush"; byte "john"; int 5; uncover 4; +`, AssemblerMaxVersion) - testProg(t, `int 4; byte "ayush"; int 5; uncover 1; +`, AssemblerMaxVersion, Expect{1, "+ arg 1..."}) + testProg(t, `int 4; byte "ayush"; int 5; uncover 1; +`, AssemblerMaxVersion, exp(1, "+ arg 1...")) } func TestTxTypes(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - testProg(t, "itxn_begin; itxn_field Sender", 5, Expect{1, "itxn_field Sender expects 1 stack argument..."}) - testProg(t, "itxn_begin; int 1; itxn_field Sender", 5, Expect{1, "...wanted type address got 1"}) - testProg(t, "itxn_begin; byte 0x56127823; itxn_field Sender", 5, Expect{1, "...wanted type address got [4]byte"}) + testProg(t, "itxn_begin; itxn_field Sender", 5, exp(1, "itxn_field Sender expects 1 stack argument...")) + testProg(t, "itxn_begin; int 1; itxn_field Sender", 5, exp(1, "...wanted type address got 1")) + testProg(t, "itxn_begin; byte 0x56127823; itxn_field Sender", 5, exp(1, "...wanted type address got [4]byte")) testProg(t, "itxn_begin; global ZeroAddress; itxn_field Sender", 5) - testProg(t, "itxn_begin; itxn_field Amount", 5, Expect{1, "itxn_field Amount expects 1 stack argument..."}) - testProg(t, "itxn_begin; byte 0x87123376; itxn_field Amount", 5, Expect{1, "...wanted type uint64 got [4]byte"}) + testProg(t, "itxn_begin; itxn_field Amount", 5, exp(1, "itxn_field Amount expects 1 stack argument...")) + testProg(t, "itxn_begin; byte 0x87123376; itxn_field Amount", 5, exp(1, "...wanted type uint64 got [4]byte")) testProg(t, "itxn_begin; int 1; itxn_field Amount", 5) } func TestBadInnerFields(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - testProg(t, "itxn_begin; int 1000; itxn_field FirstValid", 5, Expect{1, "...is not allowed."}) - testProg(t, "itxn_begin; int 1000; itxn_field FirstValidTime", 5, Expect{1, "...is not allowed."}) - testProg(t, "itxn_begin; int 1000; itxn_field LastValid", 5, Expect{1, "...is not allowed."}) - testProg(t, "itxn_begin; int 32; bzero; itxn_field Lease", 5, Expect{1, "...is not allowed."}) - testProg(t, "itxn_begin; byte 0x7263; itxn_field Note", 5, Expect{1, "...Note field was introduced in v6..."}) - testProg(t, "itxn_begin; global ZeroAddress; itxn_field VotePK", 5, Expect{1, "...VotePK field was introduced in v6..."}) - testProg(t, "itxn_begin; int 32; bzero; itxn_field TxID", 5, Expect{1, "...is not allowed."}) + testProg(t, "itxn_begin; int 1000; itxn_field FirstValid", 5, exp(1, "...is not allowed.")) + testProg(t, "itxn_begin; int 1000; itxn_field FirstValidTime", 5, exp(1, "...is not allowed.")) + testProg(t, "itxn_begin; int 1000; itxn_field LastValid", 5, exp(1, "...is not allowed.")) + testProg(t, "itxn_begin; int 32; bzero; itxn_field Lease", 5, exp(1, "...is not allowed.")) + testProg(t, "itxn_begin; byte 0x7263; itxn_field Note", 5, exp(1, "...Note field was introduced in v6...")) + testProg(t, "itxn_begin; global ZeroAddress; itxn_field VotePK", 5, exp(1, "...VotePK field was introduced in v6...")) + testProg(t, "itxn_begin; int 32; bzero; itxn_field TxID", 5, exp(1, "...is not allowed.")) - testProg(t, "itxn_begin; int 1000; itxn_field FirstValid", 6, Expect{1, "...is not allowed."}) - testProg(t, "itxn_begin; int 1000; itxn_field LastValid", 6, Expect{1, "...is not allowed."}) - testProg(t, "itxn_begin; int 32; bzero; itxn_field Lease", 6, Expect{1, "...is not allowed."}) + testProg(t, "itxn_begin; int 1000; itxn_field FirstValid", 6, exp(1, "...is not allowed.")) + testProg(t, "itxn_begin; int 1000; itxn_field LastValid", 6, exp(1, "...is not allowed.")) + testProg(t, "itxn_begin; int 32; bzero; itxn_field Lease", 6, exp(1, "...is not allowed.")) testProg(t, "itxn_begin; byte 0x7263; itxn_field Note", 6) testProg(t, "itxn_begin; global ZeroAddress; itxn_field VotePK", 6) - testProg(t, "itxn_begin; int 32; bzero; itxn_field TxID", 6, Expect{1, "...is not allowed."}) + testProg(t, "itxn_begin; int 32; bzero; itxn_field TxID", 6, exp(1, "...is not allowed.")) } func TestTypeTracking(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - testProg(t, "+", LogicVersion, Expect{1, "+ expects 2 stack arguments..."}) + testProg(t, "+", LogicVersion, exp(1, "+ expects 2 stack arguments...")) // hitting a label in deadcode starts analyzing again, with unknown stack testProg(t, "b end; label: +; end: b label", LogicVersion) @@ -2745,7 +2835,7 @@ func TestTypeTracking(t *testing.T) { // but we do want to ensure we're not just treating the code after callsub as dead testProg(t, "callsub A; int 1; concat; return; A: int 1; int 2; retsub", LogicVersion, - Expect{1, "concat arg 1 wanted..."}) + exp(1, "concat arg 1 wanted...")) // retsub deadens code, like any unconditional branch testProg(t, "callsub A; +; return; A: int 1; int 2; retsub; concat", LogicVersion) @@ -2761,7 +2851,7 @@ label: + confusion: b label -`, LogicVersion, Expect{7, "+ arg 0 wanted type uint64..."}) +`, LogicVersion, exp(7, "+ arg 0 wanted type uint64...")) // Unless that same error is in dead code. testProg(t, ` @@ -2815,7 +2905,7 @@ done: concat #pragma typetrack true concat -`, LogicVersion, Expect{5, "concat arg 1 wanted type []byte..."}) +`, LogicVersion, exp(5, "concat arg 1 wanted type []byte...")) } func TestMergeProtos(t *testing.T) { @@ -2849,9 +2939,9 @@ func TestGetSpec(t *testing.T) { ops.versionedPseudoOps["dummyPseudo"] = make(map[int]OpSpec) ops.versionedPseudoOps["dummyPseudo"][1] = OpSpec{Name: "b:", Version: AssemblerMaxVersion, Proto: proto("b:")} ops.versionedPseudoOps["dummyPseudo"][2] = OpSpec{Name: ":", Version: AssemblerMaxVersion} - _, _, ok := getSpec(ops, "dummyPseudo", []string{}) + _, _, ok := getSpec(ops, token{str: "dummyPseudo"}, 0) require.False(t, ok) - _, _, ok = getSpec(ops, "nonsense", []string{}) + _, _, ok = getSpec(ops, token{str: "nonsense"}, 0) require.False(t, ok) require.Equal(t, 2, len(ops.Errors)) require.Equal(t, "unknown opcode: nonsense", ops.Errors[1].Err.Error()) @@ -2881,8 +2971,8 @@ func TestReplacePseudo(t *testing.T) { for v := uint64(replaceVersion); v <= AssemblerMaxVersion; v++ { testProg(t, "byte 0x0000; byte 0x1234; replace 0", v) testProg(t, "byte 0x0000; int 0; byte 0x1234; replace", v) - testProg(t, "byte 0x0000; byte 0x1234; replace", v, Expect{1, "replace without immediates expects 3 stack arguments but stack height is 2"}) - testProg(t, "byte 0x0000; int 0; byte 0x1234; replace 0", v, Expect{1, "replace 0 arg 0 wanted type []byte got 0"}) + testProg(t, "byte 0x0000; byte 0x1234; replace", v, exp(1, "replace without immediates expects 3 stack arguments but stack height is 2")) + testProg(t, "byte 0x0000; int 0; byte 0x1234; replace 0", v, exp(1, "replace 0 arg 0 wanted type []byte got 0")) } } @@ -2936,14 +3026,14 @@ func TestAssembleSwitch(t *testing.T) { switch label1 label2 label1: ` - testProg(t, source, AssemblerMaxVersion, NewExpect(3, "reference to undefined label \"label2\"")) + testProg(t, source, AssemblerMaxVersion, Exp(3, "reference to undefined label \"label2\"")) // fail when target index != uint64 testProg(t, ` byte "fail" switch label1 labe11: - `, AssemblerMaxVersion, Expect{3, "switch label1 arg 0 wanted type uint64..."}) + `, AssemblerMaxVersion, exp(3, "switch label1 arg 0 wanted type uint64...")) // No labels is pretty degenerate, but ok, I suppose. It's just a no-op testProg(t, ` @@ -2982,7 +3072,7 @@ int 1 switch %s extra %s `, strings.Join(labels, " "), strings.Join(labels, ":\n")+":\n") - testProg(t, source, AssemblerMaxVersion, Expect{3, "switch cannot take more than 255 labels"}) + testProg(t, source, AssemblerMaxVersion, exp(3, "switch cannot take more than 255 labels")) // allow duplicate label reference source = ` @@ -3199,7 +3289,7 @@ add: #define c woah hey int 1 c`, - AssemblerMaxVersion, Expect{5, "Macro cycle discovered: c -> hey -> x -> d -> c"}, Expect{7, "unknown opcode: c"}, + AssemblerMaxVersion, exp(5, "macro expansion cycle discovered: c -> hey -> x -> d -> c"), exp(7, "unknown opcode: c"), ) testProg(t, ` @@ -3209,13 +3299,13 @@ add: #define c d int 1 c`, - AssemblerMaxVersion, Expect{5, "Macro cycle discovered: c -> d -> x -> c"}, Expect{7, "+ expects..."}, + AssemblerMaxVersion, exp(5, "macro expansion cycle discovered: c -> d -> x -> c"), exp(2, "+ expects...", 12), ) testProg(t, ` #define X X int 3`, - AssemblerMaxVersion, Expect{2, "Macro cycle discovered: X -> X"}, + AssemblerMaxVersion, exp(2, "macro expansion cycle discovered: X -> X"), ) // Check that macros names can't be things like named constants, opcodes, etc. @@ -3230,10 +3320,10 @@ add: #define + hey // since versioned check is now online, we can error here int 1`, assemblerNoVersion, - Expect{3, "Named constants..."}, - Expect{4, "Named constants..."}, - Expect{6, "Macro names cannot be opcodes: +"}, - Expect{8, "Macro names cannot be opcodes: +"}, + exp(3, "Named constants..."), + exp(4, "Named constants..."), + exp(5, "Macro names cannot be opcodes: +"), + exp(8, "Macro names cannot be opcodes: +"), ) // Same check, but this time since no version is given, the versioned check @@ -3247,10 +3337,10 @@ add: #define return hi #define + hey`, assemblerNoVersion, - Expect{3, "Named constants..."}, - Expect{4, "Named constants..."}, - Expect{6, "Macro names cannot be opcodes: +"}, - Expect{8, "Macro names cannot be opcodes: +"}, + exp(3, "Named constants..."), + exp(4, "Named constants..."), + exp(5, "Macro names cannot be opcodes: +"), + exp(8, "Macro names cannot be opcodes: +"), ) testProg(t, ` @@ -3261,7 +3351,8 @@ add: #define ApplicationArgs heyyyyy // no error b/c ApplicationArgs is after v1 int 1`, assemblerNoVersion, - Expect{4, "Macro names cannot be field names: Sender"}, // error happens once version is known + exp(2, "Macro names cannot be field names: Sender"), + exp(5, "Macro names cannot be field names: Sender"), ) // Same check but defaults to AssemblerDefaultVersion instead of pragma @@ -3272,8 +3363,8 @@ add: #define Sender helllooooo #define ApplicationArgs heyyyyy`, assemblerNoVersion, - Expect{4, "Macro names cannot be field names: Sender"}, // error happens once version is auto-set - Expect{5, "Macro names cannot be field names: Sender"}, // and on following line + exp(2, "Macro names cannot be field names: Sender"), + exp(5, "Macro names cannot be field names: Sender"), ) // define needs name and body testLine(t, "#define", AssemblerMaxVersion, "define directive requires a name and body") @@ -3300,20 +3391,20 @@ add: int 1 #define coolLabel 1`, AssemblerMaxVersion, - Expect{4, "Labels cannot be used as macro names: coolLabel"}, + exp(4, "Labels cannot be used as macro names: coolLabel"), ) testProg(t, ` #define coolLabel 1 coolLabel: int 1`, AssemblerMaxVersion, - Expect{3, "Cannot create label with same name as macro: coolLabel"}, + exp(3, "Cannot create label with same name as macro: coolLabel"), ) - // Admittedly these two tests are just for coverage + // These two tests are just for coverage, they really really can't happen ops := newOpStream(AssemblerMaxVersion) - err := define(&ops, []string{"not#define"}) + err := define(&ops, []token{{str: "not#define"}}) require.EqualError(t, err, "0: invalid syntax: not#define") - err = pragma(&ops, []string{"not#pragma"}) + err = pragma(&ops, []token{{str: "not#pragma"}}) require.EqualError(t, err, "0: invalid syntax: not#pragma") } @@ -3329,20 +3420,20 @@ func TestAssembleImmediateRanges(t *testing.T) { testProg(t, "load 255;", AssemblerMaxVersion) testProg(t, "int 1; store -1000;", AssemblerMaxVersion, - Expect{1, "store unable to parse..."}) + exp(1, "store unable to parse...")) testProg(t, "load -100;", AssemblerMaxVersion, - Expect{1, "load unable to parse..."}) + exp(1, "load unable to parse...")) testProg(t, "int 1; store 256;", AssemblerMaxVersion, - Expect{1, "store i beyond 255: 256"}) + exp(1, "store i beyond 255: 256")) testProg(t, "frame_dig -1;", AssemblerMaxVersion) testProg(t, "frame_dig 127;", AssemblerMaxVersion) testProg(t, "int 1; frame_bury -128;", AssemblerMaxVersion) testProg(t, "frame_dig 128;", AssemblerMaxVersion, - Expect{1, "frame_dig unable to parse..."}) + exp(1, "frame_dig unable to parse...")) testProg(t, "int 1; frame_bury -129;", AssemblerMaxVersion, - Expect{1, "frame_bury unable to parse..."}) + exp(1, "frame_bury unable to parse...")) } func TestAssembleMatch(t *testing.T) { @@ -3355,7 +3446,7 @@ func TestAssembleMatch(t *testing.T) { match label1 label2 label1: ` - testProg(t, source, AssemblerMaxVersion, NewExpect(3, "reference to undefined label \"label2\"")) + testProg(t, source, AssemblerMaxVersion, Exp(3, "reference to undefined label \"label2\"")) // No labels is pretty degenerate, but ok, I suppose. It's just a no-op testProg(t, ` @@ -3403,7 +3494,7 @@ int 1 match %s extra %s `, strings.Join(labels, " "), strings.Join(labels, ":\n")+":\n") - testProg(t, source, AssemblerMaxVersion, Expect{3, "match cannot take more than 255 labels"}) + testProg(t, source, AssemblerMaxVersion, exp(3, "match cannot take more than 255 labels")) // allow duplicate label reference source = ` @@ -3452,20 +3543,19 @@ func TestAssemblePushConsts(t *testing.T) { // enforce correct types source = `pushints "1" "2" "3"` - testProg(t, source, AssemblerMaxVersion, Expect{1, `strconv.ParseUint: parsing "\"1\"": invalid syntax`}) + testProg(t, source, AssemblerMaxVersion, exp(1, `strconv.ParseUint: parsing "\"1\"": invalid syntax`)) source = `pushbytess 1 2 3` - testProg(t, source, AssemblerMaxVersion, Expect{1, "byte arg did not parse: 1"}) + testProg(t, source, AssemblerMaxVersion, exp(1, "pushbytess arg did not parse: 1")) source = `pushints 6 4; concat` - testProg(t, source, AssemblerMaxVersion, Expect{1, "concat arg 1 wanted type []byte got uint64"}) + testProg(t, source, AssemblerMaxVersion, exp(1, "concat arg 1 wanted type []byte got uint64")) source = `pushbytess "x" "y"; +` - testProg(t, source, AssemblerMaxVersion, Expect{1, "+ arg 1 wanted type uint64 got []byte"}) + testProg(t, source, AssemblerMaxVersion, exp(1, "+ arg 1 wanted type uint64 got []byte")) } func TestAssembleEmpty(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - emptyExpect := Expect{0, "Cannot assemble empty program text"} emptyPrograms := []string{ "", " ", @@ -3477,7 +3567,7 @@ func TestAssembleEmpty(t *testing.T) { for version := uint64(1); version <= AssemblerMaxVersion; version++ { for _, prog := range emptyPrograms { - testProg(t, prog, version, emptyExpect) + testProg(t, prog, version, exp(0, "Cannot assemble empty program text")) } testProg(t, nonEmpty, version) } @@ -3487,14 +3577,8 @@ func TestReportMultipleErrors(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - assertWithMsg := func(t *testing.T, expectedOutput string, b bytes.Buffer) { - if b.String() != expectedOutput { - t.Errorf("Unexpected output: got %q, want %q", b.String(), expectedOutput) - } - } - ops := &OpStream{ - Errors: []lineError{ + Errors: []sourceError{ {Line: 1, Err: errors.New("error 1")}, {Err: errors.New("error 2")}, {Line: 3, Err: errors.New("error 3")}, @@ -3514,7 +3598,7 @@ test.txt: 3: error 3 test.txt: warning 1 test.txt: warning 2 ` - assertWithMsg(t, expected, b) + require.Equal(t, expected, b.String()) // Test the case where fname is empty b.Reset() @@ -3525,45 +3609,45 @@ test.txt: warning 2 warning 1 warning 2 ` - assertWithMsg(t, expected, b) + require.Equal(t, expected, b.String()) // no errors or warnings at all ops = &OpStream{} b.Reset() ops.ReportMultipleErrors("blah blah", &b) expected = "" - assertWithMsg(t, expected, b) + require.Equal(t, expected, b.String()) // more than 10 errors: file := "great-file.go" - les := []lineError{} + les := []sourceError{} expectedStrs := []string{} for i := 1; i <= 11; i++ { errS := fmt.Errorf("error %d", i) - les = append(les, lineError{i, errS}) + les = append(les, sourceError{i, 5, errS}) if i <= 10 { - expectedStrs = append(expectedStrs, fmt.Sprintf("%s: %d: %s", file, i, errS)) + expectedStrs = append(expectedStrs, fmt.Sprintf("%s: %d:5: %s", file, i, errS)) } } expected = strings.Join(expectedStrs, "\n") + "\n" ops = &OpStream{Errors: les} b.Reset() ops.ReportMultipleErrors(file, &b) - assertWithMsg(t, expected, b) + require.Equal(t, expected, b.String()) // exactly 1 error + filename - ops = &OpStream{Errors: []lineError{{42, errors.New("super annoying error")}}} + ops = &OpStream{Errors: []sourceError{{42, 0, errors.New("super annoying error")}}} b.Reset() ops.ReportMultipleErrors("galaxy.py", &b) expected = "galaxy.py: 1 error: 42: super annoying error\n" - assertWithMsg(t, expected, b) + require.Equal(t, expected, b.String()) // exactly 1 error w/o filename - ops = &OpStream{Errors: []lineError{{42, errors.New("super annoying error")}}} + ops = &OpStream{Errors: []sourceError{{42, 0, errors.New("super annoying error")}}} b.Reset() ops.ReportMultipleErrors("", &b) expected = "1 error: 42: super annoying error\n" - assertWithMsg(t, expected, b) + require.Equal(t, expected, b.String()) } // TestDisassembleBadBranch ensures a clean error when a branch has no target. diff --git a/data/transactions/logic/backwardCompat_test.go b/data/transactions/logic/backwardCompat_test.go index e8353e5d27..84b230bb87 100644 --- a/data/transactions/logic/backwardCompat_test.go +++ b/data/transactions/logic/backwardCompat_test.go @@ -464,22 +464,22 @@ func TestBackwardCompatAssemble(t *testing.T) { // v1 does not allow branching to the last line // v2 makes such programs legal t.Parallel() - source := "int 1; int 1; bnz done; done:" - t.Run("v=default", func(t *testing.T) { - t.Parallel() - testProg(t, source, assemblerNoVersion, Expect{1, "label \"done\" is too far away"}) - }) - - t.Run("v=default", func(t *testing.T) { - t.Parallel() - testProg(t, source, 0, Expect{1, "label \"done\" is too far away"}) - }) - - t.Run("v=default", func(t *testing.T) { - t.Parallel() - testProg(t, source, 1, Expect{1, "label \"done\" is too far away"}) - }) + // Label is ok, it just can't be branched to + source := "int 1; done:" + testProg(t, source, assemblerNoVersion) + testProg(t, source, 0) + testProg(t, source, 1) + + // use multiple lines, so that error report is checked better + source = `int 1; + int 1; + bnz done; + done: +` + testProg(t, source, assemblerNoVersion, exp(3, "label \"done\" is too far away", 5)) + testProg(t, source, 0, exp(3, "label \"done\" is too far away", 5)) + testProg(t, source, 1, exp(3, "label \"done\" is too far away", 5)) for v := uint64(2); v <= AssemblerMaxVersion; v++ { v := v diff --git a/data/transactions/logic/box_test.go b/data/transactions/logic/box_test.go index 323a64e5a8..5f08878a93 100644 --- a/data/transactions/logic/box_test.go +++ b/data/transactions/logic/box_test.go @@ -242,7 +242,7 @@ func TestBoxAvailability(t *testing.T) { TestApps(t, []string{ `byte "self"; int 64; box_create`, `byte "B"; int 10; int 4; box_extract; byte 0x00000000; ==`, - }, nil, 8, ledger, NewExpect(1, fmt.Sprintf("invalid Box reference %#x", 'B'))) + }, nil, 8, ledger, Exp(1, fmt.Sprintf("invalid Box reference %#x", 'B'))) // B is available if indexed by 0 in tx[1].Boxes group := MakeSampleTxnGroup(MakeSampleTxn(), txntest.Txn{ @@ -254,7 +254,7 @@ func TestBoxAvailability(t *testing.T) { TestApps(t, []string{ `byte "self"; int 64; box_create`, `byte "B"; int 10; int 4; box_extract; byte 0x00000000; ==`, - }, group, 8, ledger, NewExpect(1, "no such box")) + }, group, 8, ledger, Exp(1, "no such box")) // B is available if listed by appId in tx[1].Boxes group = MakeSampleTxnGroup(MakeSampleTxn(), txntest.Txn{ @@ -267,7 +267,7 @@ func TestBoxAvailability(t *testing.T) { TestApps(t, []string{ `byte "self"; int 64; box_create`, `byte "B"; int 10; int 4; box_extract; byte 0x00000000; ==`, - }, group, 8, ledger, NewExpect(1, "no such box")) + }, group, 8, ledger, Exp(1, "no such box")) } func TestBoxReadBudget(t *testing.T) { diff --git a/data/transactions/logic/evalAppTxn_test.go b/data/transactions/logic/evalAppTxn_test.go index 22c0e1691d..c72236a878 100644 --- a/data/transactions/logic/evalAppTxn_test.go +++ b/data/transactions/logic/evalAppTxn_test.go @@ -584,12 +584,12 @@ func TestNumInnerPooled(t *testing.T) { TestApps(t, []string{short, long}, grp, LogicVersion, ledger) TestApps(t, []string{long, short}, grp, LogicVersion, ledger) TestApps(t, []string{long, long}, grp, LogicVersion, ledger, - NewExpect(1, "too many inner transactions")) + Exp(1, "too many inner transactions")) grp = append(grp, grp[0]) TestApps(t, []string{short, long, long}, grp, LogicVersion, ledger, - NewExpect(2, "too many inner transactions")) + Exp(2, "too many inner transactions")) TestApps(t, []string{long, long, long}, grp, LogicVersion, ledger, - NewExpect(1, "too many inner transactions")) + Exp(1, "too many inner transactions")) } func TestAssetCreate(t *testing.T) { diff --git a/data/transactions/logic/evalStateful_test.go b/data/transactions/logic/evalStateful_test.go index be36eb6859..3899cdaebb 100644 --- a/data/transactions/logic/evalStateful_test.go +++ b/data/transactions/logic/evalStateful_test.go @@ -383,7 +383,7 @@ func TestBalance(t *testing.T) { // won't assemble in old version teal if v < directRefEnabledVersion { testProg(t, source, ep.Proto.LogicSigVersion, - Expect{1, "balance arg 0 wanted type uint64..."}) + exp(1, "balance arg 0 wanted type uint64...")) return } @@ -399,7 +399,7 @@ func TestBalance(t *testing.T) { } func testApps(t *testing.T, programs []string, txgroup []transactions.SignedTxn, version uint64, ledger *Ledger, - expected ...Expect) *EvalParams { + expected ...expect) *EvalParams { t.Helper() codes := make([][]byte, len(programs)) for i, program := range programs { @@ -427,7 +427,7 @@ func testApps(t *testing.T, programs []string, txgroup []transactions.SignedTxn, return ep } -func testAppsBytes(t *testing.T, programs [][]byte, ep *EvalParams, expected ...Expect) { +func testAppsBytes(t *testing.T, programs [][]byte, ep *EvalParams, expected ...expect) { t.Helper() require.LessOrEqual(t, len(programs), len(ep.TxnGroup)) for i := range ep.TxnGroup { @@ -623,7 +623,7 @@ func TestAppCheckOptedIn(t *testing.T) { testApp(t, "int 1; int 2; app_opted_in; int 0; ==", pre) // in pre, int 2 is an actual app id testApp(t, "byte \"aoeuiaoeuiaoeuiaoeuiaoeuiaoeui01\"; int 2; app_opted_in; int 1; ==", now) testProg(t, "byte \"aoeuiaoeuiaoeuiaoeuiaoeuiaoeui01\"; int 2; app_opted_in; int 1; ==", directRefEnabledVersion-1, - Expect{1, "app_opted_in arg 0 wanted type uint64..."}) + exp(1, "app_opted_in arg 0 wanted type uint64...")) // Receiver opts into 888, the current app in testApp ledger.NewLocals(txn.Txn.Receiver, 888) @@ -698,7 +698,7 @@ byte "ALGO" testApp(t, text, now) testApp(t, strings.Replace(text, "int 1 // account idx", "byte \"aoeuiaoeuiaoeuiaoeuiaoeuiaoeui01\"", -1), now) testProg(t, strings.Replace(text, "int 1 // account idx", "byte \"aoeuiaoeuiaoeuiaoeuiaoeuiaoeui01\"", -1), directRefEnabledVersion-1, - Expect{4, "app_local_get_ex arg 0 wanted type uint64..."}) + exp(4, "app_local_get_ex arg 0 wanted type uint64...")) testApp(t, strings.Replace(text, "int 100 // app id", "int 2", -1), now) // Next we're testing if the use of the current app's id works // as a direct reference. The error is because the receiver @@ -771,7 +771,7 @@ byte "ALGO" testApp(t, text, now) testApp(t, strings.Replace(text, "int 0 // account idx", "byte \"aoeuiaoeuiaoeuiaoeuiaoeuiaoeui00\"", -1), now) testProg(t, strings.Replace(text, "int 0 // account idx", "byte \"aoeuiaoeuiaoeuiaoeuiaoeuiaoeui00\"", -1), directRefEnabledVersion-1, - Expect{3, "app_local_get arg 0 wanted type uint64..."}) + exp(3, "app_local_get arg 0 wanted type uint64...")) testApp(t, strings.Replace(text, "int 0 // account idx", "byte \"aoeuiaoeuiaoeuiaoeuiaoeuiaoeui01\"", -1), now) testApp(t, strings.Replace(text, "int 0 // account idx", "byte \"aoeuiaoeuiaoeuiaoeuiaoeuiaoeui02\"", -1), now, "invalid Account reference") @@ -1033,7 +1033,7 @@ func testAssetsByVersion(t *testing.T, assetsTestProgram string, version uint64) // it wasn't legal to use a direct ref for account testProg(t, `byte "aoeuiaoeuiaoeuiaoeuiaoeuiaoeui00"; int 54; asset_holding_get AssetBalance`, - directRefEnabledVersion-1, Expect{1, "asset_holding_get AssetBalance arg 0 wanted type uint64..."}) + directRefEnabledVersion-1, exp(1, "asset_holding_get AssetBalance arg 0 wanted type uint64...")) // but it is now (empty asset yields 0,0 on stack) testApp(t, `byte "aoeuiaoeuiaoeuiaoeuiaoeuiaoeui00"; int 55; asset_holding_get AssetBalance; ==`, now) // This is receiver, who is in Assets array @@ -1076,7 +1076,7 @@ func testAssetsByVersion(t *testing.T, assetsTestProgram string, version uint64) testApp(t, strings.Replace(assetsTestProgram, "int 55", "int 0", -1), now) // but old code cannot - testProg(t, strings.Replace(assetsTestProgram, "int 0//account", "byte \"aoeuiaoeuiaoeuiaoeuiaoeuiaoeui00\"", -1), directRefEnabledVersion-1, Expect{3, "asset_holding_get AssetBalance arg 0 wanted type uint64..."}) + testProg(t, strings.Replace(assetsTestProgram, "int 0//account", "byte \"aoeuiaoeuiaoeuiaoeuiaoeuiaoeui00\"", -1), directRefEnabledVersion-1, exp(3, "asset_holding_get AssetBalance arg 0 wanted type uint64...")) if version < 5 { // Can't run these with AppCreator anyway @@ -2391,7 +2391,7 @@ int 1 delta := testApp(t, withBytes, ep) // But won't even compile in old teal testProg(t, withBytes, directRefEnabledVersion-1, - Expect{4, "app_local_put arg 0 wanted..."}, Expect{11, "app_local_del arg 0 wanted..."}) + exp(4, "app_local_put arg 0 wanted..."), exp(11, "app_local_del arg 0 wanted...")) require.Equal(t, 0, len(delta.GlobalDelta)) require.Equal(t, 2, len(delta.LocalDeltas)) ledger.Reset() @@ -2900,9 +2900,9 @@ func TestTxnEffects(t *testing.T) { // Look past the logs of tx 0 testApps(t, []string{"byte 0x37; log; int 1", "gtxna 0 Logs 1; byte 0x37; =="}, nil, AssemblerMaxVersion, nil, - Expect{1, "invalid Logs index 1"}) + exp(1, "invalid Logs index 1")) testApps(t, []string{"byte 0x37; log; int 1", "int 6; gtxnas 0 Logs; byte 0x37; =="}, nil, AssemblerMaxVersion, nil, - Expect{1, "invalid Logs index 6"}) + exp(1, "invalid Logs index 6")) } func TestRound(t *testing.T) { @@ -3002,7 +3002,7 @@ func TestPooledAppCallsVerifyOp(t *testing.T) { 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, - Expect{0, "pc=107 dynamic cost budget exceeded, executing ed25519verify: local program cost was 5"}) + 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) diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index f93df4149e..de99cab3a1 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -621,7 +621,7 @@ after: dup pop ` - testProg(t, code, LogicVersion, Expect{12, "+ expects 2 stack arguments..."}) + testProg(t, code, LogicVersion, exp(12, "+ expects 2 stack arguments...")) testAccepts(t, notrack(code), 1) } @@ -2474,17 +2474,17 @@ func TestSubstringFlop(t *testing.T) { // fails in compiler testProg(t, `byte 0xf000000000000000 substring -len`, 2, Expect{2, "substring expects 2 immediate arguments"}) +len`, 2, exp(2, "substring expects 2 immediate arguments")) // fails in compiler testProg(t, `byte 0xf000000000000000 substring 1 -len`, 2, Expect{2, "substring expects 2 immediate arguments"}) +len`, 2, exp(2, "substring expects 2 immediate arguments")) // fails in compiler testProg(t, `byte 0xf000000000000000 substring 4 2 -len`, 2, Expect{2, "substring end is before start"}) +len`, 2, exp(2, "substring end is before start")) // fails at runtime testPanics(t, `byte 0xf000000000000000 @@ -2539,17 +2539,17 @@ func TestExtractFlop(t *testing.T) { // fails in compiler testProg(t, `byte 0xf000000000000000 extract - len`, 5, Expect{2, "extract without immediates expects 3 stack arguments but stack height is 1"}) + len`, 5, exp(2, "extract without immediates expects 3 stack arguments but stack height is 1")) testProg(t, `byte 0xf000000000000000 extract 1 - len`, 5, Expect{2, "extract expects 0 or 2 immediate arguments"}) + len`, 5, exp(2, "extract expects 0 or 2 immediate arguments")) testProg(t, `byte 0xf000000000000000 int 0 int 5 extract3 1 2 - len`, 5, Expect{4, "extract3 expects 0 immediate arguments"}) + len`, 5, exp(4, "extract3 expects 0 immediate arguments")) // fails at runtime err := testPanics(t, `byte 0xf000000000000000 @@ -2751,7 +2751,7 @@ func TestGload(t *testing.T) { } if testCase.errContains != "" { - testApps(t, sources, txgroup, LogicVersion, nil, Expect{testCase.errTxn, testCase.errContains}) + testApps(t, sources, txgroup, LogicVersion, nil, exp(testCase.errTxn, testCase.errContains)) } else { testApps(t, sources, txgroup, LogicVersion, nil) } @@ -4437,7 +4437,7 @@ func testEvaluation(t *testing.T, program string, introduced uint64, tester eval t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) { t.Helper() if v < introduced { - testProg(t, notrack(program), v, Expect{0, "...was introduced..."}) + testProg(t, notrack(program), v, exp(0, "...was introduced...")) return } ops := testProg(t, program, v) @@ -4632,7 +4632,7 @@ func TestBury(t *testing.T) { // bury 0 panics source := "int 3; int 2; int 7; bury 0; int 1; return" - testProg(t, source, 8, Expect{1, "bury 0 always fails"}) + testProg(t, source, 8, exp(1, "bury 0 always fails")) testPanics(t, notrack("int 3; int 2; int 7; bury 0; int 1; return"), 8, "bury outside stack") // bury 1 pops the ToS and replaces the thing "1 down", which becomes the new ToS @@ -5282,13 +5282,13 @@ By Herman Melville`, "", source := fmt.Sprintf(template, hex.EncodeToString([]byte(tc.decoded)), hex.EncodeToString([]byte(tc.encoded)), tc.alph) if tc.error == "" { if LogicVersion < fidoVersion { - testProg(t, source, AssemblerMaxVersion, Expect{0, "unknown opcode..."}) + testProg(t, source, AssemblerMaxVersion, exp(0, "unknown opcode...")) } else { testAccepts(t, source, fidoVersion) } } else { if LogicVersion < fidoVersion { - testProg(t, source, AssemblerMaxVersion, Expect{0, "unknown opcode..."}) + testProg(t, source, AssemblerMaxVersion, exp(0, "unknown opcode...")) } else { err := testPanics(t, source, fidoVersion) require.Error(t, err) @@ -5405,7 +5405,7 @@ func TestOpJSONRef(t *testing.T) { ep.SigLedger = ledger testCases := []struct { source string - previousVersErrors []Expect + previousVersErrors []expect }{ { source: `byte "{\"key0\": 0,\"key1\": \"algo\",\"key2\":{\"key3\": \"teal\", \"key4\":3}, \"key5\": 18446744073709551615 }"; @@ -5413,7 +5413,7 @@ func TestOpJSONRef(t *testing.T) { json_ref JSONUint64; int 0; ==`, - previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, + previousVersErrors: []expect{exp(3, "unknown opcode: json_ref")}, }, { source: `byte "{\"key0\": 0,\"key1\": \"algo\",\"key2\":{\"key3\": \"teal\", \"key4\": 3}, \"key5\": 18446744073709551615 }"; @@ -5421,7 +5421,7 @@ func TestOpJSONRef(t *testing.T) { json_ref JSONUint64; int 18446744073709551615; //max uint64 value ==`, - previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, + previousVersErrors: []expect{exp(3, "unknown opcode: json_ref")}, }, { source: `byte "{\"key0\": 0,\"key1\": \"algo\",\"key2\":{\"key3\": \"teal\", \"key4\": 3}, \"key5\": 18446744073709551615 }"; @@ -5429,7 +5429,7 @@ func TestOpJSONRef(t *testing.T) { json_ref JSONString; byte "algo"; ==`, - previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, + previousVersErrors: []expect{exp(3, "unknown opcode: json_ref")}, }, { source: `byte "{\"key0\": 0,\"key1\": \"\\u0061\\u006C\\u0067\\u006F\",\"key2\":{\"key3\": \"teal\", \"key4\": 3}, \"key5\": 18446744073709551615 }"; @@ -5437,7 +5437,7 @@ func TestOpJSONRef(t *testing.T) { json_ref JSONString; byte "algo"; ==`, - previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, + previousVersErrors: []expect{exp(3, "unknown opcode: json_ref")}, }, { source: `byte "{\"key0\": 0,\"key1\": \"algo\",\"key2\":{\"key3\": \"teal\", \"key4\": {\"key40\": 10}}, \"key5\": 18446744073709551615 }"; @@ -5449,7 +5449,7 @@ func TestOpJSONRef(t *testing.T) { json_ref JSONUint64 int 10 ==`, - previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}, {5, "unknown opcode: json_ref"}}, + previousVersErrors: []expect{exp(3, "unknown opcode: json_ref"), exp(5, "unknown opcode: json_ref")}, }, { source: `byte "{\"key0\": 0,\"key1\": \"algo\",\"key2\":{\"key3\": \"teal\", \"key4\": {\"key40\": 10}}, \"key5\": 18446744073709551615 }"; @@ -5459,7 +5459,7 @@ func TestOpJSONRef(t *testing.T) { json_ref JSONString; byte "teal" ==`, - previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}, {5, "unknown opcode: json_ref"}}, + previousVersErrors: []expect{exp(3, "unknown opcode: json_ref"), exp(5, "unknown opcode: json_ref")}, }, { source: `byte "{\"key0\": 0,\"key1\": \"algo\",\"key2\":{\"key3\": \"\\"teal\\"\", \"key4\": {\"key40\": 10}}, \"key5\": 18446744073709551615 }"; @@ -5469,7 +5469,7 @@ func TestOpJSONRef(t *testing.T) { json_ref JSONString; byte ""teal"" // quotes match ==`, - previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}, {5, "unknown opcode: json_ref"}}, + previousVersErrors: []expect{exp(3, "unknown opcode: json_ref"), exp(5, "unknown opcode: json_ref")}, }, { source: `byte "{\"key0\": 0,\"key1\": \"algo\",\"key2\":{\"key3\": \" teal \", \"key4\": {\"key40\": 10}}, \"key5\": 18446744073709551615 }"; @@ -5479,7 +5479,7 @@ func TestOpJSONRef(t *testing.T) { json_ref JSONString; byte " teal " // spaces match ==`, - previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}, {5, "unknown opcode: json_ref"}}, + previousVersErrors: []expect{exp(3, "unknown opcode: json_ref"), exp(5, "unknown opcode: json_ref")}, }, { source: `byte "{\"key0\": 0,\"key1\": \"algo\",\"key2\":{\"key3\": \"teal\", \"key4\": {\"key40\": 10, \"key40\": \"10\"}}, \"key5\": 18446744073709551615 }"; @@ -5490,7 +5490,7 @@ func TestOpJSONRef(t *testing.T) { byte "{\"key40\": 10, \"key40\": \"10\"}" == `, - previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, + previousVersErrors: []expect{exp(3, "unknown opcode: json_ref")}, }, { source: `byte "{\"rawId\": \"responseId\",\"id\": \"0\",\"response\": {\"attestationObject\": \"based64url_encoded_buffer\",\"clientDataJSON\": \" based64url_encoded_client_data\"},\"getClientExtensionResults\": {},\"type\": \"public-key\"}"; @@ -5498,7 +5498,7 @@ func TestOpJSONRef(t *testing.T) { json_ref JSONObject; byte "{\"attestationObject\": \"based64url_encoded_buffer\",\"clientDataJSON\": \" based64url_encoded_client_data\"}" // object as it appeared in input ==`, - previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, + previousVersErrors: []expect{exp(3, "unknown opcode: json_ref")}, }, { source: `byte "{\"rawId\": \"responseId\",\"id\": \"0\",\"response\": {\"attestationObject\": \"based64url_encoded_buffer\",\"clientD\\u0061taJSON\": \" based64url_encoded_client_data\"},\"getClientExtensionResults\": {},\"type\": \"public-key\"}"; @@ -5506,7 +5506,7 @@ func TestOpJSONRef(t *testing.T) { json_ref JSONObject; byte "{\"attestationObject\": \"based64url_encoded_buffer\",\"clientD\\u0061taJSON\": \" based64url_encoded_client_data\"}" // object as it appeared in input ==`, - previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, + previousVersErrors: []expect{exp(3, "unknown opcode: json_ref")}, }, { source: `byte "{\"rawId\": \"responseId\",\"id\": \"0\",\"response\": {\"attestationObject\": \"based64url_encoded_buffer\",\"clientDataJSON\": \" based64url_encoded_client_data\"},\"getClientExtensionResults\": {},\"type\": \"public-key\"}"; @@ -5516,7 +5516,7 @@ func TestOpJSONRef(t *testing.T) { json_ref JSONString; byte " based64url_encoded_client_data"; ==`, - previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}, {5, "unknown opcode: json_ref"}}, + previousVersErrors: []expect{exp(3, "unknown opcode: json_ref"), exp(5, "unknown opcode: json_ref")}, }, { source: `byte "{\"\\u0072\\u0061\\u0077\\u0049\\u0044\": \"responseId\",\"id\": \"0\",\"response\": {\"attestationObject\": \"based64url_encoded_buffer\",\"clientDataJSON\": \" based64url_encoded_client_data\"},\"getClientExtensionResults\": {},\"type\": \"public-key\"}"; @@ -5524,7 +5524,7 @@ func TestOpJSONRef(t *testing.T) { json_ref JSONString; byte "responseId" ==`, - previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, + previousVersErrors: []expect{exp(3, "unknown opcode: json_ref")}, }, // JavaScript MAX_SAFE_INTEGER { @@ -5533,7 +5533,7 @@ func TestOpJSONRef(t *testing.T) { json_ref JSONUint64; int 9007199254740991; ==`, - previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, + previousVersErrors: []expect{exp(3, "unknown opcode: json_ref")}, }, // maximum uint64 { @@ -5542,7 +5542,7 @@ func TestOpJSONRef(t *testing.T) { json_ref JSONUint64; int 18446744073709551615; ==`, - previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, + previousVersErrors: []expect{exp(3, "unknown opcode: json_ref")}, }, // larger-than-uint64s are allowed if not requested { @@ -5551,7 +5551,7 @@ func TestOpJSONRef(t *testing.T) { json_ref JSONUint64; int 0; ==`, - previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, + previousVersErrors: []expect{exp(3, "unknown opcode: json_ref")}, }, } @@ -5586,57 +5586,57 @@ func TestOpJSONRef(t *testing.T) { failedCases := []struct { source string error string - previousVersErrors []Expect + previousVersErrors []expect }{ { source: `byte "{\"key0\": 1 }"; byte "key0"; json_ref JSONString;`, error: "json: cannot unmarshal number into Go value of type string", - previousVersErrors: []Expect{{1, "unknown opcode: json_ref"}}, + previousVersErrors: []expect{exp(1, "unknown opcode: json_ref")}, }, { source: `byte "{\"key0\": [1] }"; byte "key0"; json_ref JSONString;`, error: "json: cannot unmarshal array into Go value of type string", - previousVersErrors: []Expect{{1, "unknown opcode: json_ref"}}, + previousVersErrors: []expect{exp(1, "unknown opcode: json_ref")}, }, { source: `byte "{\"key0\": {\"key1\":1} }"; byte "key0"; json_ref JSONString;`, error: "json: cannot unmarshal object into Go value of type string", - previousVersErrors: []Expect{{1, "unknown opcode: json_ref"}}, + previousVersErrors: []expect{exp(1, "unknown opcode: json_ref")}, }, { source: `byte "{\"key0\": \"1\" }"; byte "key0"; json_ref JSONUint64;`, error: "json: cannot unmarshal string into Go value of type uint64", - previousVersErrors: []Expect{{1, "unknown opcode: json_ref"}}, + previousVersErrors: []expect{exp(1, "unknown opcode: json_ref")}, }, { source: `byte "{\"key0\": [\"1\"] }"; byte "key0"; json_ref JSONUint64;`, error: "json: cannot unmarshal array into Go value of type uint64", - previousVersErrors: []Expect{{1, "unknown opcode: json_ref"}}, + previousVersErrors: []expect{exp(1, "unknown opcode: json_ref")}, }, { source: `byte "{\"key0\": {\"key1\":1} }"; byte "key0"; json_ref JSONUint64;`, error: "json: cannot unmarshal object into Go value of type uint64", - previousVersErrors: []Expect{{1, "unknown opcode: json_ref"}}, + previousVersErrors: []expect{exp(1, "unknown opcode: json_ref")}, }, { source: `byte "{\"key0\": [1]}"; byte "key0"; json_ref JSONObject;`, error: "json: cannot unmarshal array into Go value of type map[string]json.RawMessage", - previousVersErrors: []Expect{{1, "unknown opcode: json_ref"}}, + previousVersErrors: []expect{exp(1, "unknown opcode: json_ref")}, }, { source: `byte "{\"key0\": 1}"; byte "key0"; json_ref JSONObject;`, error: "json: cannot unmarshal number into Go value of type map[string]json.RawMessage", - previousVersErrors: []Expect{{1, "unknown opcode: json_ref"}}, + previousVersErrors: []expect{exp(1, "unknown opcode: json_ref")}, }, { source: `byte "{\"key0\": \"1\"}"; byte "key0"; json_ref JSONObject;`, error: "json: cannot unmarshal string into Go value of type map[string]json.RawMessage", - previousVersErrors: []Expect{{1, "unknown opcode: json_ref"}}, + previousVersErrors: []expect{exp(1, "unknown opcode: json_ref")}, }, { source: `byte "{\"key0\": 1,\"key1\": \"algo\",\"key2\":{\"key3\": \"teal\", \"key4\": [1,2,3]} }"; byte "key3"; json_ref JSONString;`, error: "key key3 not found in JSON text", - previousVersErrors: []Expect{{1, "unknown opcode: json_ref"}}, + previousVersErrors: []expect{exp(1, "unknown opcode: json_ref")}, }, { source: `byte "{\"key0\": 1,\"key1\": \"algo\",\"key2\":{\"key3\": \"teal\", \"key4\": [1,2,3]}}"; @@ -5646,52 +5646,52 @@ func TestOpJSONRef(t *testing.T) { json_ref JSONString `, error: "key key5 not found in JSON text", - previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}, {5, "unknown opcode: json_ref"}}, + previousVersErrors: []expect{exp(3, "unknown opcode: json_ref"), exp(5, "unknown opcode: json_ref")}, }, { source: `byte "{\"key0\": -0,\"key1\": 2.5,\"key2\": -3}"; byte "key0"; json_ref JSONUint64;`, error: "json: cannot unmarshal number -0 into Go value of type uint64", - previousVersErrors: []Expect{{1, "unknown opcode: json_ref"}}, + previousVersErrors: []expect{exp(1, "unknown opcode: json_ref")}, }, { source: `byte "{\"key0\": 1e10,\"key1\": 2.5,\"key2\": -3}"; byte "key0"; json_ref JSONUint64;`, error: "json: cannot unmarshal number 1e10 into Go value of type uint64", - previousVersErrors: []Expect{{1, "unknown opcode: json_ref"}}, + previousVersErrors: []expect{exp(1, "unknown opcode: json_ref")}, }, { source: `byte "{\"key0\": 0.2e-2,\"key1\": 2.5,\"key2\": -3}"; byte "key0"; json_ref JSONUint64;`, error: "json: cannot unmarshal number 0.2e-2 into Go value of type uint64", - previousVersErrors: []Expect{{1, "unknown opcode: json_ref"}}, + previousVersErrors: []expect{exp(1, "unknown opcode: json_ref")}, }, { source: `byte "{\"key0\": 1.0,\"key1\": 2.5,\"key2\": -3}"; byte "key0"; json_ref JSONUint64;`, error: "json: cannot unmarshal number 1.0 into Go value of type uint64", - previousVersErrors: []Expect{{1, "unknown opcode: json_ref"}}, + previousVersErrors: []expect{exp(1, "unknown opcode: json_ref")}, }, { source: `byte "{\"key0\": 1.0,\"key1\": 2.5,\"key2\": -3}"; byte "key1"; json_ref JSONUint64;`, error: "json: cannot unmarshal number 2.5 into Go value of type uint64", - previousVersErrors: []Expect{{1, "unknown opcode: json_ref"}}, + previousVersErrors: []expect{exp(1, "unknown opcode: json_ref")}, }, { source: `byte "{\"key0\": 1.0,\"key1\": 2.5,\"key2\": -3}"; byte "key2"; json_ref JSONUint64;`, error: "json: cannot unmarshal number -3 into Go value of type uint64", - previousVersErrors: []Expect{{1, "unknown opcode: json_ref"}}, + previousVersErrors: []expect{exp(1, "unknown opcode: json_ref")}, }, { source: `byte "{\"key0\": 18446744073709551616}"; byte "key0"; json_ref JSONUint64;`, error: "json: cannot unmarshal number 18446744073709551616 into Go value of type uint64", - previousVersErrors: []Expect{{1, "unknown opcode: json_ref"}}, + previousVersErrors: []expect{exp(1, "unknown opcode: json_ref")}, }, { source: `byte "{\"key0\": 1,}"; byte "key0"; json_ref JSONString;`, error: "error while parsing JSON text, invalid json text", - previousVersErrors: []Expect{{1, "unknown opcode: json_ref"}}, + previousVersErrors: []expect{exp(1, "unknown opcode: json_ref")}, }, { source: `byte "{\"key0\": 1, \"key0\": \"3\"}"; byte "key0"; json_ref JSONString;`, error: "error while parsing JSON text, invalid json text, duplicate keys not allowed", - previousVersErrors: []Expect{{1, "unknown opcode: json_ref"}}, + previousVersErrors: []expect{exp(1, "unknown opcode: json_ref")}, }, { source: `byte "{\"key0\": 0,\"key1\": \"algo\",\"key2\":{\"key3\": \"teal\", \"key4\": {\"key40\": 10, \"key40\": \"should fail!\"}}}"; @@ -5703,7 +5703,7 @@ func TestOpJSONRef(t *testing.T) { json_ref JSONString `, error: "error while parsing JSON text, invalid json text, duplicate keys not allowed", - previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}, {5, "unknown opcode: json_ref"}, {7, "unknown opcode: json_ref"}}, + previousVersErrors: []expect{exp(3, "unknown opcode: json_ref"), exp(5, "unknown opcode: json_ref"), exp(7, "unknown opcode: json_ref")}, }, { source: `byte "[1,2,3]"; @@ -5711,7 +5711,7 @@ func TestOpJSONRef(t *testing.T) { json_ref JSONUint64 `, error: "error while parsing JSON text, invalid json text, only json object is allowed", - previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, + previousVersErrors: []expect{exp(3, "unknown opcode: json_ref")}, }, { source: `byte "2"; @@ -5719,7 +5719,7 @@ func TestOpJSONRef(t *testing.T) { json_ref JSONUint64 `, error: "error while parsing JSON text, invalid json text, only json object is allowed", - previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, + previousVersErrors: []expect{exp(3, "unknown opcode: json_ref")}, }, { source: `byte "null"; @@ -5727,7 +5727,7 @@ func TestOpJSONRef(t *testing.T) { json_ref JSONUint64 `, error: "error while parsing JSON text, invalid json text, only json object is allowed", - previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, + previousVersErrors: []expect{exp(3, "unknown opcode: json_ref")}, }, { source: `byte "true"; @@ -5735,7 +5735,7 @@ func TestOpJSONRef(t *testing.T) { json_ref JSONUint64 `, error: "error while parsing JSON text, invalid json text, only json object is allowed", - previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, + previousVersErrors: []expect{exp(3, "unknown opcode: json_ref")}, }, { source: `byte "\"sometext\""; @@ -5743,7 +5743,7 @@ func TestOpJSONRef(t *testing.T) { json_ref JSONUint64 `, error: "error while parsing JSON text, invalid json text, only json object is allowed", - previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, + previousVersErrors: []expect{exp(3, "unknown opcode: json_ref")}, }, { source: `byte "{noquotes: \"shouldn't work\"}"; @@ -5752,7 +5752,7 @@ func TestOpJSONRef(t *testing.T) { byte "shouldn't work"; ==`, error: "error while parsing JSON text, invalid json text", - previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, + previousVersErrors: []expect{exp(3, "unknown opcode: json_ref")}, }, // max uint64 + 1 should fail { @@ -5762,7 +5762,7 @@ func TestOpJSONRef(t *testing.T) { int 1; return`, error: "json: cannot unmarshal number 18446744073709551616 into Go value of type uint64", - previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, + previousVersErrors: []expect{exp(3, "unknown opcode: json_ref")}, }, } diff --git a/data/transactions/logic/export_test.go b/data/transactions/logic/export_test.go index 44f90f0400..1a21d9ff15 100644 --- a/data/transactions/logic/export_test.go +++ b/data/transactions/logic/export_test.go @@ -24,10 +24,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 NewExpect(l int, s string) Expect { - return Expect{l, s} -} - func (ep *EvalParams) Reset() { ep.reset() } @@ -45,6 +41,7 @@ func (l *Ledger) DelBoxes(app basics.AppIndex, names ...string) { } var DefaultEvalParams = defaultEvalParams +var Exp = exp var MakeSampleEnv = makeSampleEnv var MakeSampleEnvWithVersion = makeSampleEnvWithVersion var MakeSampleTxn = makeSampleTxn diff --git a/data/transactions/logic/fields_test.go b/data/transactions/logic/fields_test.go index e0819e7073..bedb079079 100644 --- a/data/transactions/logic/fields_test.go +++ b/data/transactions/logic/fields_test.go @@ -232,7 +232,7 @@ func TestAssetParamsFieldsVersions(t *testing.T) { ledger.NewAsset(txn.Sender, 55, basics.AssetParams{}) ep.Proto.LogicSigVersion = v if field.version > v { - testProg(t, text, v, Expect{1, "...was introduced in..."}) + testProg(t, text, v, exp(1, "...was introduced in...")) ops := testProg(t, text, field.version) // assemble in the future ops.Program[0] = byte(v) testAppBytes(t, ops.Program, ep, "invalid asset_params_get field") @@ -282,7 +282,7 @@ func TestAcctParamsFieldsVersions(t *testing.T) { ledger.NewAccount(txn.Sender, 200_000) if field.version > v { // check assembler fails if version before introduction - testProg(t, text, v, Expect{1, "...was introduced in..."}) + testProg(t, text, v, exp(1, "...was introduced in...")) ops := testProg(t, text, field.version) // assemble in the future ops.Program[0] = byte(v) // but set version back to before intro if v < 6 { diff --git a/data/transactions/logic/frames_test.go b/data/transactions/logic/frames_test.go index c46a8dd848..63ff1c21c3 100644 --- a/data/transactions/logic/frames_test.go +++ b/data/transactions/logic/frames_test.go @@ -50,7 +50,7 @@ func TestDupPopN(t *testing.T) { testAccepts(t, "int 1; int 1; int 1; popn 2", fpVersion) testAccepts(t, "int 1; int 0; popn 1", fpVersion) testPanics(t, "int 1; int 0; popn 2", fpVersion) - testProg(t, "int 1; int 0; popn 3", LogicVersion, Expect{1, "popn 3 expects 3..."}) + testProg(t, "int 1; int 0; popn 3", LogicVersion, exp(1, "popn 3 expects 3...")) testPanics(t, notrack("int 1; int 0; popn 3"), fpVersion) testAccepts(t, `int 7; dupn 250; dupn 250; dupn 250; dupn 249; @@ -69,9 +69,9 @@ func TestDupPopNTyping(t *testing.T) { t.Parallel() testProg(t, "int 8; dupn 2; +; pop", LogicVersion) - testProg(t, "int 8; dupn 2; concat; pop", LogicVersion, Expect{1, "...wanted type []byte..."}) + testProg(t, "int 8; dupn 2; concat; pop", LogicVersion, exp(1, "...wanted type []byte...")) - testProg(t, "popn 1", LogicVersion, Expect{1, "...expects 1 stack argument..."}) + testProg(t, "popn 1", LogicVersion, exp(1, "...expects 1 stack argument...")) } func TestSimpleFrame(t *testing.T) { @@ -342,7 +342,7 @@ func TestFrameAccess(t *testing.T) { int 1 return ` - testProg(t, source, fpVersion, Expect{4, "frame_dig above stack"}) + testProg(t, source, fpVersion, exp(4, "frame_dig above stack")) testPanics(t, notrack(source), fpVersion, "frame_dig above stack") source = ` @@ -357,7 +357,7 @@ func TestFrameAccess(t *testing.T) { int 1 return ` - testProg(t, source, fpVersion, Expect{5, "frame_dig above stack"}) + testProg(t, source, fpVersion, exp(5, "frame_dig above stack")) testPanics(t, notrack(source), fpVersion, "frame_dig above stack") // Note that at the moment of frame_bury, the stack IS big enough, because @@ -376,7 +376,7 @@ func TestFrameAccess(t *testing.T) { int 1 return ` - testProg(t, source, fpVersion, Expect{6, "frame_bury above stack"}) + testProg(t, source, fpVersion, exp(6, "frame_bury above stack")) testPanics(t, notrack(source), fpVersion, "frame_bury above stack") } @@ -400,7 +400,7 @@ main: pop // argument popped frame_dig -1 // but then frame_dig used to get at it ` - testProg(t, source, fpVersion, Expect{7, "frame_dig above stack"}) + testProg(t, source, fpVersion, exp(7, "frame_dig above stack")) testPanics(t, notrack(source), fpVersion, "frame_dig above stack") testAccepts(t, ` @@ -427,7 +427,7 @@ main: frame_bury 1; retsub ` - testProg(t, source, fpVersion, Expect{8, "frame_dig above stack"}) + testProg(t, source, fpVersion, exp(8, "frame_dig above stack")) testPanics(t, notrack(source), fpVersion) } @@ -442,7 +442,7 @@ main: proto 1 1 frame_dig -10 // digging down below arguments ` - testProg(t, source, fpVersion, Expect{6, "frame_dig -10 in sub with 1 arg..."}) + testProg(t, source, fpVersion, exp(6, "frame_dig -10 in sub with 1 arg...")) testPanics(t, notrack(source), fpVersion, "frame_dig -10 in sub with 1 arg") testPanics(t, ` @@ -459,7 +459,7 @@ main: proto 1 15 frame_bury -10 // burying down below arguments ` - testProg(t, source, fpVersion, Expect{6, "frame_bury -10 in sub with 1 arg..."}) + testProg(t, source, fpVersion, exp(6, "frame_bury -10 in sub with 1 arg...")) testPanics(t, notrack(source), fpVersion, "frame_bury -10 in sub with 1 arg") // Without `proto`, frame_bury can't be checked by assembler, but still panics diff --git a/data/transactions/logic/resources_test.go b/data/transactions/logic/resources_test.go index c4aed20b71..4437f11837 100644 --- a/data/transactions/logic/resources_test.go +++ b/data/transactions/logic/resources_test.go @@ -62,7 +62,7 @@ func TestAppSharing(t *testing.T) { // 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.NewExpect(1, "unavailable App 500")) + logic.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) @@ -73,7 +73,7 @@ func TestAppSharing(t *testing.T) { // 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.NewExpect(0, "no account")) + logic.Exp(0, "no account")) _, _, ledger := logic.MakeSampleEnv() ledger.NewAccount(appl0.Sender, 100_000) @@ -82,42 +82,42 @@ func TestAppSharing(t *testing.T) { 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.NewExpect(1, "unavailable Local State")) + logic.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.NewExpect(1, "unavailable App 500")) + logic.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.NewExpect(0, "no account: "+appl0.Sender.String())) + logic.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.NewExpect(1, "unavailable Local State "+appl1.Sender.String())) + logic.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.NewExpect(1, "unavailable App 500")) + logic.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.NewExpect(1, "unavailable Local State "+appl1.Sender.String())) + 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.NewExpect(1, "unavailable App 900")) + logic.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 @@ -128,7 +128,7 @@ func TestAppSharing(t *testing.T) { sources := []string{noop, putLocal} appl1.ApplicationArgs = [][]byte{appl0.Sender[:]} // tx1 will try to modify local state exposed in tx0 logic.TestApps(t, sources, txntest.Group(&appl0, &appl1), 9, ledger, - logic.NewExpect(1, "account "+appl0.Sender.String()+" is not opted into 901")) + logic.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) require.Len(t, ep.TxnGroup, 2) @@ -147,12 +147,12 @@ 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.NewExpect(2, "unavailable Local State")) // note that the error message is for Locals, not specialized + logic.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.NewExpect(2, "unavailable Local State")) // note that the error message is for Locals, not specialized + logic.Exp(2, "unavailable Local State")) // note that the error message is for Locals, not specialized } // TestBetterLocalErrors confirms that we get specific errors about the missing @@ -223,7 +223,7 @@ 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.NewExpect(1, "unavailable Asset 400")) + logic.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) @@ -232,9 +232,9 @@ func TestAssetSharing(t *testing.T) { // 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.NewExpect(1, "unavailable Asset 400")) + logic.Exp(1, "unavailable Asset 400")) logic.TestApps(t, []string{getBalance, getBalance}, txntest.Group(&appl0, &appl1), 9, nil, - logic.NewExpect(1, "unavailable Holding")) + logic.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) @@ -242,12 +242,12 @@ func TestAssetSharing(t *testing.T) { // 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.NewExpect(2, "unavailable Holding")) // note that the error message is for Holding, not specialized + logic.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.NewExpect(2, "unavailable Holding")) // note that the error message is for Holding, not specialized + logic.Exp(2, "unavailable Holding")) // note that the error message is for Holding, not specialized } // TestBetterHoldingErrors confirms that we get specific errors about the missing @@ -386,9 +386,9 @@ func TestOtherTxSharing(t *testing.T) { logic.TestApps(t, []string{senderBalance, ""}, txntest.Group(&appl, &send), 9, ledger) logic.TestApps(t, []string{"", senderBalance}, txntest.Group(&send, &appl), 8, ledger, - logic.NewExpect(1, "invalid Account reference")) + logic.Exp(1, "invalid Account reference")) logic.TestApps(t, []string{senderBalance, ""}, txntest.Group(&appl, &send), 8, ledger, - logic.NewExpect(0, "invalid Account reference")) + logic.Exp(0, "invalid Account reference")) } holdingAccess := ` @@ -401,11 +401,11 @@ func TestOtherTxSharing(t *testing.T) { t.Run("keyreg", func(t *testing.T) { appl.ApplicationArgs = [][]byte{senderAcct[:], {200}} logic.TestApps(t, []string{"", holdingAccess}, txntest.Group(&keyreg, &appl), 9, ledger, - logic.NewExpect(1, "unavailable Asset 200")) + logic.Exp(1, "unavailable Asset 200")) withRef := appl withRef.ForeignAssets = []basics.AssetIndex{200} logic.TestApps(t, []string{"", holdingAccess}, txntest.Group(&keyreg, &withRef), 9, ledger, - logic.NewExpect(1, "unavailable Holding "+senderAcct.String())) + logic.Exp(1, "unavailable Holding "+senderAcct.String())) }) t.Run("pay", func(t *testing.T) { // The receiver is available for algo balance reading @@ -415,7 +415,7 @@ func TestOtherTxSharing(t *testing.T) { // 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.NewExpect(1, "invalid Account reference "+otherAcct.String())) + logic.Exp(1, "invalid Account reference "+otherAcct.String())) // The other account becomes accessible because used in CloseRemainderTo withClose := pay @@ -427,7 +427,7 @@ func TestOtherTxSharing(t *testing.T) { // 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.NewExpect(1, "invalid Account reference "+otherAcct.String())) + logic.Exp(1, "invalid Account reference "+otherAcct.String())) }) t.Run("axfer", func(t *testing.T) { @@ -454,9 +454,9 @@ func TestOtherTxSharing(t *testing.T) { // 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.NewExpect(1, "invalid Account reference "+other2Acct.String())) + logic.Exp(1, "invalid Account reference "+other2Acct.String())) logic.TestApps(t, []string{"", holdingAccess}, txntest.Group(&axfer, &appl), 9, ledger, - logic.NewExpect(1, "unavailable Account "+other2Acct.String())) + logic.Exp(1, "unavailable Account "+other2Acct.String())) withClose := axfer withClose.AssetCloseTo = other2Acct @@ -475,7 +475,7 @@ func TestOtherTxSharing(t *testing.T) { 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.NewExpect(1, "unavailable Holding "+senderAcct.String())) + logic.Exp(1, "unavailable Holding "+senderAcct.String())) }) } @@ -550,12 +550,12 @@ int 1 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.NewExpect(1, "invalid Account reference "+senderAcct.String())) + logic.Exp(1, "invalid Account reference "+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.NewExpect(1, "invalid Account reference "+receiverAcct.String())) + logic.Exp(1, "invalid Account reference "+receiverAcct.String())) }) t.Run("pay", func(t *testing.T) { @@ -569,17 +569,17 @@ int 1 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.NewExpect(1, "invalid Account reference "+senderAcct.String())) + logic.Exp(1, "invalid Account reference "+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.NewExpect(1, "invalid Account reference "+receiverAcct.String())) + logic.Exp(1, "invalid Account reference "+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.NewExpect(1, "invalid Account reference "+otherAcct.String())) + logic.Exp(1, "invalid Account reference "+otherAcct.String())) }) t.Run("axfer", func(t *testing.T) { @@ -595,20 +595,20 @@ int 1 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.NewExpect(1, "invalid Account reference "+senderAcct.String())) + logic.Exp(1, "invalid Account reference "+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.NewExpect(1, "unavailable Holding")) + logic.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.NewExpect(0, "unavailable Holding")) + logic.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.NewExpect(1, "unavailable Holding")) + logic.Exp(1, "unavailable Holding")) // Those axfers become possible by adding the asa to the appl's ForeignAssets appl.ForeignAssets = []basics.AssetIndex{asa1} @@ -622,18 +622,18 @@ int 1 // but can't axfer a different asset appl.ApplicationArgs = [][]byte{senderAcct[:], {asa2}} logic.TestApps(t, []string{"", axferToArgs}, txntest.Group(&axfer, &appl), 9, ledger, - logic.NewExpect(1, fmt.Sprintf("unavailable Asset %d", asa2))) + logic.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.NewExpect(1, "invalid Account reference")) + logic.Exp(1, "invalid Account reference")) // 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) appl.ApplicationArgs = [][]byte{{asa2}} // but not asa2 logic.TestApps(t, []string{"", acfgArg}, txntest.Group(&axfer, &appl), 9, ledger, - logic.NewExpect(1, fmt.Sprintf("unavailable Asset %d", asa2))) + logic.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 @@ -655,7 +655,7 @@ int 1 ledger.NewHolding(payAcct, asa1, 1, false) appl.ApplicationArgs = [][]byte{payAcct[:], {asa1}} logic.TestApps(t, []string{"", "", axferToArgs}, txntest.Group(&axfer, &pay, &appl), 9, ledger, - logic.NewExpect(2, "unavailable Holding "+payAcct.String())) + logic.Exp(2, "unavailable Holding "+payAcct.String())) }) t.Run("afrz", func(t *testing.T) { @@ -676,19 +676,19 @@ int 1 // 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.NewExpect(1, "unavailable Holding "+appAcct.String())) + logic.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.NewExpect(1, "unavailable Holding "+senderAcct.String())) + logic.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.NewExpect(0, "invalid Account reference "+receiverAcct.String())) + logic.Exp(0, "invalid Account reference "+receiverAcct.String())) logic.TestApps(t, []string{axferToArgs}, txntest.Group(&appl, &afrz), 9, ledger, - logic.NewExpect(0, "invalid Account reference "+receiverAcct.String())) + logic.Exp(0, "invalid Account reference "+receiverAcct.String())) // otherAcct is the afrz target, it's holding and account are available appl.ApplicationArgs = [][]byte{otherAcct[:], {asa1}} @@ -698,11 +698,11 @@ int 1 // 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.NewExpect(1, fmt.Sprintf("unavailable Asset %d", asa2))) + logic.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.NewExpect(1, "unavailable Holding "+otherAcct.String())) + logic.Exp(1, "unavailable Holding "+otherAcct.String())) // appl can acfg the asset from tx0 (which requires asset available, not holding) appl.ForeignAssets = []basics.AssetIndex{} @@ -710,7 +710,7 @@ int 1 logic.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.NewExpect(1, fmt.Sprintf("unavailable Asset %d", asa2))) + logic.Exp(1, fmt.Sprintf("unavailable Asset %d", asa2))) }) @@ -728,10 +728,10 @@ int 1 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.NewExpect(1, "invalid Account reference "+otherAcct.String())) + logic.Exp(1, "invalid Account reference "+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.NewExpect(1, "axfer Sender: unavailable Holding")) + logic.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) @@ -741,11 +741,11 @@ int 1 // can't be used at all. appl.ApplicationArgs = [][]byte{otherAcct[:], {asa2}} logic.TestApps(t, []string{"", axferToArgs}, txntest.Group(&appl0, &appl), 9, ledger, - logic.NewExpect(1, "unavailable Asset 202")) + logic.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.NewExpect(1, "axfer AssetReceiver: unavailable Holding "+otherAcct.String()+" x 202")) + logic.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 @@ -781,20 +781,20 @@ int 1 // 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.NewExpect(1, "invalid Account reference "+otherAcct.String())) + logic.Exp(1, "invalid Account reference "+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.NewExpect(1, "appl ApplicationID: unavailable Local State "+otherAcct.String())) + logic.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.NewExpect(1, "appl ApplicationID: unavailable Local State "+otherAcct.String())) + logic.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} @@ -807,7 +807,7 @@ int 1 appl.ApplicationArgs = [][]byte{{11}, otherAcct[:], {asa1}} appl0.ForeignApps = []basics.AppIndex{11} logic.TestApps(t, []string{"", innerCallWithBoth}, txntest.Group(&appl0, &appl), 9, ledger, - logic.NewExpect(1, "appl ForeignApps: unavailable Local State "+otherAcct.String())) + logic.Exp(1, "appl ForeignApps: unavailable Local State "+otherAcct.String())) }) From 6ad660f96949ef349c68274fad52d4cbb40ed4c9 Mon Sep 17 00:00:00 2001 From: shiqizng <80276844+shiqizng@users.noreply.github.com> Date: Fri, 26 May 2023 17:04:37 -0400 Subject: [PATCH 119/125] tools: enable consecutive test runs in the block generator (#5409) Co-authored-by: Zeph Grunschlag --- Makefile | 6 +- go.mod | 2 +- tools/block-generator/README.md | 25 +- tools/block-generator/generator/generate.go | 28 +- tools/block-generator/go.mod | 50 + tools/block-generator/go.sum | 948 ++++++++++++++++++ tools/block-generator/run_postgres.sh | 8 +- tools/block-generator/run_runner.sh | 17 +- tools/block-generator/run_tests.sh | 2 +- tools/block-generator/runner/run.go | 23 +- tools/block-generator/runner/runner.go | 4 +- .../scenarios/config.mixed.jumbo.yml | 2 +- .../scenarios/config.payment.full.yml | 4 +- .../scenarios/config.payment.jumbo.yml | 6 +- .../scenarios/config.payment.small.yml | 4 +- tools/block-generator/util/util.go | 46 + 16 files changed, 1122 insertions(+), 53 deletions(-) create mode 100644 tools/block-generator/go.mod create mode 100644 tools/block-generator/go.sum diff --git a/Makefile b/Makefile index 2d179a952f..450ab6999c 100644 --- a/Makefile +++ b/Makefile @@ -202,7 +202,7 @@ rebuild_kmd_swagger: deps # develop -build: buildsrc +build: buildsrc buildsrc-special # We're making an empty file in the go-cache dir to # get around a bug in go build where it will fail @@ -213,6 +213,10 @@ buildsrc: check-go-version crypto/libs/$(OS_TYPE)/$(ARCH)/lib/libsodium.a node_e touch "${GOCACHE}"/file.txt && \ go install $(GOTRIMPATH) $(GOTAGS) $(GOBUILDMODE) -ldflags="$(GOLDFLAGS)" ./... +buildsrc-special: + cd tools/block-generator && \ + go install $(GOTRIMPATH) $(GOTAGS) $(GOBUILDMODE) -ldflags="$(GOLDFLAGS)" ./... + check-go-version: ./scripts/check_golang_version.sh build diff --git a/go.mod b/go.mod index cb5ce71426..f490570eb6 100644 --- a/go.mod +++ b/go.mod @@ -36,7 +36,6 @@ require ( golang.org/x/sys v0.7.0 golang.org/x/text v0.9.0 gopkg.in/sohlich/elogrus.v3 v3.0.0-20180410122755-1fa29e2f2009 - gopkg.in/yaml.v3 v3.0.1 ) require ( @@ -72,4 +71,5 @@ require ( golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 // 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 ) diff --git a/tools/block-generator/README.md b/tools/block-generator/README.md index 4b2fd9b5a6..93e62d546f 100644 --- a/tools/block-generator/README.md +++ b/tools/block-generator/README.md @@ -5,6 +5,7 @@ This tool is used for testing Conduit import performance. It does this by genera ## Scenario Configuration Block generator uses a YAML config file to describe the composition of each randomly generated block. There are three levels of configuration: + 1. Setup 2. Transaction type distribution 3. Transaction type specific configuration @@ -12,7 +13,8 @@ Block generator uses a YAML config file to describe the composition of each rand At the time of writing, the block generator supports **payment** and **asset** transactions. The settings are hopefully, more or less, obvious. Distributions are specified as fractions of 1.0, and the sum of all options must add up to 1.0. Here is an example which uses all of the current options. Notice that the synthetic blocks are not required to follow algod limits, in this case the block size is specified as 19999: -``` + +```yml name: "Mixed (19,999)" genesis_accounts: 10000 genesis_account_balance: 1000000000000 @@ -37,6 +39,7 @@ asset_delete_fraction: 0 ## Modes The block generator can run in one of two _modes_: + 1. standalone **daemon** 2. test suite **runner** @@ -44,7 +47,8 @@ The block generator can run in one of two _modes_: In standalone daemon mode, a block-generator process starts and exposes the mock algod endpoints for **/genesis** and **/v2/blocks/{block}**. If you choose to query them manually, it only supports fetching blocks sequentially. This is due to the fact that it generates a pseudorandom stream of transactions and after each random transaction the state increments to the next. -Here is the help output for **daemon**: +Here is the help output for **daemon**: + ```bash ~$ ./block-generator daemon -h Start the generator daemon in standalone mode. @@ -60,12 +64,13 @@ Flags: ### runner -The runner mode is well suited for runing the same set of tests consistently across many scenarios and for different releases. The runner mode automates this process by starting the **daemon** with many different configurations, managing a postgres database, and running a separate Conduit process configured to use them. +The runner mode is well suited for running the same set of tests consistently across many scenarios and for different releases. The runner mode automates this process by starting the **daemon** with many different configurations, managing a postgres database, and running a separate Conduit process configured to use them. The results of the testing are written to the directory specified by the **--report-directory** option, and include many different metrics. In addition to the report, the Conduit log is written to this directory. The files are named according to the scenario file, and end in "report" or "log". Here is an example report from running with a test duration of "1h": -``` + +```json test_duration_seconds:3600 test_duration_actual_seconds:3600.056457 transaction_pay_total:30024226 @@ -95,6 +100,7 @@ final_uptime_seconds:3600.06 ``` Here is the help output for **runner**: + ```bash ~$ ./block-generator runner -h Run an automated test suite using the block-generator daemon and a provided conduit binary. Results are captured to a specified output directory. @@ -117,15 +123,17 @@ Flags: --validate If set the validator will run after test-duration has elapsed to verify data is correct. An extra line in each report indicates validator success or failure. ``` -## Example Scenario Run using Conduit and Postgres - `run_runner.sh` +## Example Run using Conduit and Postgres in **bash** via `run_runner.sh` A typical **runner** scenario involves: + * a [scenario configuration](#scenario-configuration) file, e.g. [test_config.yml](./test_config.yml) * access to a `conduit` binary to query the block generator's mock Algod endpoint and ingest the synthetic blocks * 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 @@ -137,16 +145,17 @@ First you'll need to get a `conduit` binary. For example you can follow the [dev Assume you've navigated to the `tools/block-generator` directory of the `go-algorand` repo, and: + * saved the conduit binary to `tools/block-generator/conduit` * created a block generator scenario config at `tools/block-generator/scenario.yml` Then you can execute the following command to run the scenario: + ```sh ./run_runner.sh ./conduit scenario.yml ``` ### Scenario Report -If all goes well, the run will generate a directory `tools/block-generator/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 `tmp/OUTPUT_RUN_RUNNER_TEST` +and in that directory you can see the statisticsn of the run in `scenario.report`. diff --git a/tools/block-generator/generator/generate.go b/tools/block-generator/generator/generate.go index 8a619fa4dd..48df2b06c6 100644 --- a/tools/block-generator/generator/generate.go +++ b/tools/block-generator/generator/generate.go @@ -130,7 +130,7 @@ func MakeGenerator(dbround uint64, bkGenesis bookkeeping.Genesis, config Generat rewardsRate: 0, rewardsRecalculationRound: 0, reportData: make(map[TxTypeID]TxData), - nextdbround: dbround, + roundOffset: dbround, } gen.feeSink[31] = 1 @@ -242,8 +242,7 @@ type generator struct { // ledger ledger *ledger.Ledger - // next_account_round in the preloaded database - nextdbround uint64 + roundOffset uint64 } type assetData struct { @@ -286,7 +285,7 @@ func (g *generator) WriteReport(output io.Writer) error { func (g *generator) WriteStatus(output io.Writer) error { response := model.NodeStatusResponse{ - LastRound: g.round + g.nextdbround, + LastRound: g.round + g.roundOffset, } return json.NewEncoder(output).Encode(response) } @@ -378,24 +377,21 @@ func (g *generator) finishRound(txnCount uint64) { // WriteBlock generates a block full of new transactions and writes it to the writer. func (g *generator) WriteBlock(output io.Writer, round uint64) error { - if round < g.nextdbround { + if round < g.roundOffset { return fmt.Errorf("cannot generate block for round %d, already in database", round) } - if round-g.nextdbround != g.round { - return fmt.Errorf("generator only supports sequential block access. Expected %d but received request for %d", g.round+g.nextdbround, round) + if round-g.roundOffset != g.round { + return fmt.Errorf("generator only supports sequential block access. Expected %d but received request for %d", g.round+g.roundOffset, round) } numTxnForBlock := g.txnForRound(g.round) // return genesis block. offset round for non-empty database - if round-g.nextdbround == 0 { + if round-g.roundOffset == 0 { // write the msgpack bytes for a block - block, cert, _ := g.ledger.BlockCert(basics.Round(round - g.nextdbround)) + block, _, _ := g.ledger.BlockCert(basics.Round(round - g.roundOffset)) // return the block with the requested round number block.BlockHeader.Round = basics.Round(round) - encodedblock := rpcs.EncodedBlockCert{ - Block: block, - Certificate: cert, - } + encodedblock := rpcs.EncodedBlockCert{Block: block} blk := protocol.EncodeMsgp(&encodedblock) // write the msgpack bytes for a block _, err := output.Write(blk) @@ -478,8 +474,8 @@ func (g *generator) WriteBlock(output io.Writer, round uint64) error { // WriteDeltas generates returns the deltas for payset. func (g *generator) WriteDeltas(output io.Writer, round uint64) error { - // offset round for non-empty database - if round-g.nextdbround == 0 { + // the first generated round has no statedelta. + if round-g.roundOffset == 0 { data, _ := encode(protocol.CodecHandle, ledgercore.StateDelta{}) _, err := output.Write(data) if err != nil { @@ -487,7 +483,7 @@ func (g *generator) WriteDeltas(output io.Writer, round uint64) error { } return nil } - delta, err := g.ledger.GetStateDeltaForRound(basics.Round(round - g.nextdbround)) + delta, err := g.ledger.GetStateDeltaForRound(basics.Round(round - g.roundOffset)) if err != nil { return fmt.Errorf("err getting state delta for round %d: %w", round, err) } diff --git a/tools/block-generator/go.mod b/tools/block-generator/go.mod new file mode 100644 index 0000000000..306627f71b --- /dev/null +++ b/tools/block-generator/go.mod @@ -0,0 +1,50 @@ +module github.com/algorand/go-algorand/tools/block-generator + +replace github.com/algorand/go-algorand => ../.. + +go 1.17 + +require ( + github.com/algorand/go-algorand v0.0.0-00010101000000-000000000000 + github.com/algorand/go-codec/codec v1.1.9 + 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 + gopkg.in/yaml.v3 v3.0.1 +) + +require ( + github.com/DataDog/zstd v1.5.2 // indirect + github.com/algorand/avm-abi v0.2.0 // indirect + github.com/algorand/falcon v0.0.0-20220727072124-02a2a64c4414 // indirect + github.com/algorand/go-sumhash v0.1.0 // indirect + github.com/algorand/msgp v1.1.53 // indirect + github.com/algorand/oapi-codegen v1.12.0-algorand.0 // indirect + github.com/algorand/websocket v1.4.6 // indirect + 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/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/jmespath/go-jmespath v0.3.0 // indirect + github.com/josharian/intern v1.0.0 // 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/mmcloughlin/addchain v0.4.0 // 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/spf13/pflag v1.0.5 // indirect + golang.org/x/crypto v0.1.0 // indirect + golang.org/x/net v0.9.0 // indirect + golang.org/x/sys v0.7.0 // indirect + gopkg.in/sohlich/elogrus.v3 v3.0.0-20180410122755-1fa29e2f2009 // indirect +) diff --git a/tools/block-generator/go.sum b/tools/block-generator/go.sum new file mode 100644 index 0000000000..bd2732c8fc --- /dev/null +++ b/tools/block-generator/go.sum @@ -0,0 +1,948 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= +cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= +cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= +cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= +cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= +cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= +cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= +cloud.google.com/go v0.98.0/go.mod h1:ua6Ush4NALrHk5QXDWnjvZHN93OuF0HfuEPq9I1X0cM= +cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +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/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/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/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= +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/algorand/avm-abi v0.2.0 h1:bkjsG+BOEcxUcnGSALLosmltE0JZdg+ZisXKx0UDX2k= +github.com/algorand/avm-abi v0.2.0/go.mod h1:+CgwM46dithy850bpTeHh9MC99zpn2Snirb3QTl2O/g= +github.com/algorand/falcon v0.0.0-20220727072124-02a2a64c4414 h1:nwYN+GQ7Z5OOfZwqBO1ma7DSlP7S1YrKWICOyjkwqrc= +github.com/algorand/falcon v0.0.0-20220727072124-02a2a64c4414/go.mod h1:OkQyHlGvS0kLNcIWbC21/uQcnbfwSOQm+wiqWwBG9pQ= +github.com/algorand/go-codec/codec v1.1.9 h1:el4HFSPZhP+YCgOZxeFGB/BqlNkaUIs55xcALulUTCM= +github.com/algorand/go-codec/codec v1.1.9/go.mod h1:YkEx5nmr/zuCeaDYOIhlDg92Lxju8tj2d2NrYqP7g7k= +github.com/algorand/go-deadlock v0.2.2 h1:L7AKATSUCzoeVuOgpTipfCEjdUu5ECmlje8R7lP9DOY= +github.com/algorand/go-deadlock v0.2.2/go.mod h1:Hat1OXKqKNUcN/iv74FjGhF4hsOE2l7gOgQ9ZVIq6Fk= +github.com/algorand/go-sumhash v0.1.0 h1:b/QRhyLuF//vOcicBIxBXYW8bERNoeLxieht/dUYpVg= +github.com/algorand/go-sumhash v0.1.0/go.mod h1:OOe7jdDWUhLkuP1XytkK5gnLu9entAviN5DfDZh6XAc= +github.com/algorand/graphtrace v0.1.0/go.mod h1:HscLQrzBdH1BH+5oehs3ICd8SYcXvnSL9BjfTu8WHCc= +github.com/algorand/msgp v1.1.53 h1:D6HKLyvLE6ltfsf8Apsrc+kqYb/CcOZEAfh1DpkPrNg= +github.com/algorand/msgp v1.1.53/go.mod h1:5K3d58/poT5fPmtiwuQft6GjgSrVEM46KoXdLrID8ZU= +github.com/algorand/oapi-codegen v1.12.0-algorand.0 h1:W9PvED+wAJc+9EeXPONnA+0zE9UhynEqoDs4OgAxKhk= +github.com/algorand/oapi-codegen v1.12.0-algorand.0/go.mod h1:tIWJ9K/qrLDVDt5A1p82UmxZIEGxv2X+uoujdhEAL48= +github.com/algorand/websocket v1.4.6 h1:I0kV4EYwatuUrKtNiwzYYgojgwh6pksDmlqntKG2Woc= +github.com/algorand/websocket v1.4.6/go.mod h1:HJmdGzFtnlUQ4nTzZP6WrT29oGYf1t6Ybi64vROcT+M= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +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/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/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/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/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/chrismcguire/gobberish v0.0.0-20150821175641-1d8adb509a0e h1:CHPYEbz71w8DqJ7DRIq+MXyCQsdibK08vdcQTY4ufas= +github.com/chrismcguire/gobberish v0.0.0-20150821175641-1d8adb509a0e/go.mod h1:6Xhs0ZlsRjXLIiSMLKafbZxML/j30pg9Z1priLuha5s= +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= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= +github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +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/consensys/bavard v0.1.10/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI= +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/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/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +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/daixiang0/gci v0.3.2/go.mod h1:jaASoJmv/ykO9dAAPy31iJnreV19248qKDdVWf3QgC4= +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/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.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d/go.mod h1:tmAIfUFEirG/Y8jhZ9M+h36obRZAk/1fcSpXwAVlfqE= +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= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +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/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/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +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.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= +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.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk= +github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +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-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-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= +github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= +github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= +github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +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/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gofrs/flock v0.7.0/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +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= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +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/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/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y= +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= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +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-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= +github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +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= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v1.0.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= +github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +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/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/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= +github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= +github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= +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/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= +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/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/invopop/yaml v0.1.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q= +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/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/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.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/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/karalabe/usb v0.0.2/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU= +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/konsorten/go-windows-terminal-sequences v1.0.1/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/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +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.9.1/go.mod h1:Pop5HLc+xoc4qhTZ1ip6C0RtP7Z+4VzRLWZZFKqbbjo= +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/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= +github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= +github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y= +github.com/lestrrat-go/blackmagic v1.0.0/go.mod h1:TNgH//0vYSs8VXDCfkZLgIrVTTXQELZffUV0tz3MtdQ= +github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= +github.com/lestrrat-go/iter v1.0.1/go.mod h1:zIdgO1mRKhn8l9vrZJZz9TUMMFbQbLeTsbqPDrJ/OJc= +github.com/lestrrat-go/jwx v1.2.25/go.mod h1:zoNuZymNl5lgdcu6P7K6ie2QRll5HVfF4xwxBBK1NxY= +github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w= +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/matryer/moq v0.2.7/go.mod h1:kITsx543GOENm48TUAQyJ9+SAvFSr7iGQXPoth/VUBk= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +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= +github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +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.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +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/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +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/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +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/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= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY= +github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU= +github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +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/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.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= +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/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +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= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= +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_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/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= +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/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/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= +github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +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/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +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.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +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.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v1.3.0/go.mod h1:BrRVncBjOJa/eUcVVm9CE+oC6as8k+VYr4NY7WCi9V4= +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/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +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.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= +github.com/stretchr/objx v0.1.1/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.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +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/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q= +github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= +github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= +github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +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= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/v2 v2.305.1/go.mod h1:pMEacxZW7o8pg4CrFE7pquyCJJzZvkvdD2RibOCCCGs= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +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/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= +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-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-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-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220321153916-2c7772ba3064/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +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-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= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +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/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= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.5.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.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/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-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-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-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= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +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-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= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +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-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= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +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-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +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/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= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-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/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= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +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-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-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= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +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-20190726091711-fc99dfbffb4e/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-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-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-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= +golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +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-20200523222454-059865788121/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= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +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-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= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +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-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= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-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.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/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/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= +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= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +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.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/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-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= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/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= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= +google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= +google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= +google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= +google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= +google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= +google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU= +google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= +google.golang.org/api v0.62.0/go.mod h1:dKmwPCydfsad4qCH08MSdgWjfHOyfpd4VtDGgRFdavw= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +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-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= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= +google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= +google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211008145708-270636b82663/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211028162531-8db9c33dc351/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +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.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= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +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.28.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= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +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/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.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0/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= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +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= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA= diff --git a/tools/block-generator/run_postgres.sh b/tools/block-generator/run_postgres.sh index 4817961220..2c8175bb9c 100755 --- a/tools/block-generator/run_postgres.sh +++ b/tools/block-generator/run_postgres.sh @@ -5,7 +5,7 @@ # ./block-generator runner \ # -d 5s \ # -i \ -# -c "host=localhost user=algorand password=algorand dbname=algorand port=15432 sslmode=disable" \ +# -c "host=localhost user=algorand password=algorand dbname=generator_db port=15432 sslmode=disable" \ # -r results \ # -s scenarios/config.payment.small.yml @@ -28,7 +28,7 @@ function start_postgres() { -e POSTGRES_PASSWORD=algorand \ -e PGPASSWORD=algorand \ -p $POSTGRES_PORT:5432 \ - postgres + postgres:13-alpine sleep 5 @@ -42,8 +42,8 @@ function shutdown() { trap shutdown EXIT pushd $(dirname "$0") > /dev/null -echo "Starting postgres container at: \n\t\"host=localhost user=algorand password=algorand dbname=algorand port=15432\"" +echo "Starting postgres container at: \n\t\"host=localhost user=algorand password=algorand dbname=generator_db port=15432\"" start_postgres echo "Sleeping, use Ctrl-C to end test." -sleep 1000000000000000 +sleep 100000000 diff --git a/tools/block-generator/run_runner.sh b/tools/block-generator/run_runner.sh index 3c73035f32..5e83966318 100755 --- a/tools/block-generator/run_runner.sh +++ b/tools/block-generator/run_runner.sh @@ -4,6 +4,8 @@ set -e +OUTPUT=../../tmp/OUTPUT_RUN_RUNNER_TEST + CONDUIT_BINARY=$1 if [ -z "$CONDUIT_BINARY" ]; then echo "path to conduit binary is required" @@ -13,8 +15,8 @@ fi POSTGRES_CONTAINER=generator-test-container POSTGRES_PORT=15432 POSTGRES_DATABASE=generator_db -CONFIG=${4:-"$(dirname $0)/test_config.yml"} -echo "Using config file: $CONFIG" +SCENARIO=${2:-"$(dirname $0)/test_config.yml"} +echo "Using scenario config file: $SCENARIO" function start_postgres() { docker rm -f $POSTGRES_CONTAINER > /dev/null 2>&1 || true @@ -27,7 +29,7 @@ function start_postgres() { -e POSTGRES_PASSWORD=algorand \ -e PGPASSWORD=algorand \ -p $POSTGRES_PORT:5432 \ - postgres + postgres:13-alpine sleep 5 @@ -40,7 +42,7 @@ function shutdown() { trap shutdown EXIT -rm -rf OUTPUT_RUN_RUNNER_TEST > /dev/null 2>&1 +rm -rf $OUTPUT > /dev/null 2>&1 echo "Building generator." pushd $(dirname "$0") > /dev/null go build @@ -50,10 +52,9 @@ start_postgres echo "Starting test runner" $(dirname "$0")/block-generator runner \ --conduit-binary "$CONDUIT_BINARY" \ - --report-directory OUTPUT_RUN_RUNNER_TEST \ + --report-directory $OUTPUT \ --test-duration 30s \ --log-level trace \ --postgres-connection-string "host=localhost user=algorand password=algorand dbname=generator_db port=15432 sslmode=disable" \ - --scenario ${CONFIG} \ - --db-round ${2:-0} \ - --genesis-file ${3:-""} \ + --scenario ${SCENARIO} \ + --reset-db diff --git a/tools/block-generator/run_tests.sh b/tools/block-generator/run_tests.sh index 7916f45267..fcfc7279e0 100755 --- a/tools/block-generator/run_tests.sh +++ b/tools/block-generator/run_tests.sh @@ -93,5 +93,5 @@ echo "Log Level: $LOG_LEVEL" -c "$CONNECTION_STRING" \ --report-directory "$REPORT_DIR" \ --log-level "$LOG_LEVEL" \ - --reset + --reset-report-dir diff --git a/tools/block-generator/runner/run.go b/tools/block-generator/runner/run.go index 5e237bd660..2e1f970c5c 100644 --- a/tools/block-generator/runner/run.go +++ b/tools/block-generator/runner/run.go @@ -55,8 +55,8 @@ type Args struct { ResetReportDir bool RunValidation bool KeepDataDir bool - NextDBRound uint64 GenesisFile string + ResetDB bool } type config struct { @@ -122,17 +122,32 @@ func (r *Args) run() error { next.ServeHTTP(w, r) }) } + // get next db round + var nextRound uint64 + var err error + if r.ResetDB { + if err = util.EmptyDB(r.PostgresConnectionString); err != nil { + return fmt.Errorf("emptyDB err: %w", err) + } + nextRound = 0 + } else { + nextRound, err = util.GetNextRound(r.PostgresConnectionString) + if err != nil && err == util.ErrorNotInitialized { + nextRound = 0 + } else if err != nil { + return fmt.Errorf("getNextRound err: %w", err) + } + } // Start services algodNet := fmt.Sprintf("localhost:%d", 11112) metricsNet := fmt.Sprintf("localhost:%d", r.MetricsPort) - generatorShutdownFunc, _ := startGenerator(r.Path, r.NextDBRound, r.GenesisFile, algodNet, blockMiddleware) + generatorShutdownFunc, _ := startGenerator(r.Path, nextRound, r.GenesisFile, algodNet, blockMiddleware) defer func() { // Shutdown generator. if err := generatorShutdownFunc(); err != nil { fmt.Printf("failed to shutdown generator: %s\n", err) } }() - // get conduit config template t, err := template.New("conduit").Parse(conduitConfigTmpl) if err != nil { @@ -157,7 +172,7 @@ func (r *Args) run() error { } // Start conduit - conduitShutdownFunc, err := startConduit(dataDir, r.ConduitBinary, r.NextDBRound) + conduitShutdownFunc, err := startConduit(dataDir, r.ConduitBinary, nextRound) if err != nil { return fmt.Errorf("failed to start Conduit: %w", err) } diff --git a/tools/block-generator/runner/runner.go b/tools/block-generator/runner/runner.go index 580179a2e3..32598b9246 100644 --- a/tools/block-generator/runner/runner.go +++ b/tools/block-generator/runner/runner.go @@ -50,11 +50,11 @@ func init() { RunnerCmd.Flags().StringVarP(&runnerArgs.ReportDirectory, "report-directory", "r", "", "Location to place test reports.") RunnerCmd.Flags().StringVarP(&runnerArgs.LogLevel, "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.") - RunnerCmd.Flags().BoolVarP(&runnerArgs.ResetReportDir, "reset", "", false, "If set any existing report directory will be deleted before running tests.") + RunnerCmd.Flags().BoolVarP(&runnerArgs.ResetReportDir, "reset-report-dir", "", false, "If set any existing report directory will be deleted before running tests.") RunnerCmd.Flags().BoolVarP(&runnerArgs.RunValidation, "validate", "", false, "If set the validator will run after test-duration has elapsed to verify data is correct. An extra line in each report indicates validator success or failure.") 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().Uint64VarP(&runnerArgs.NextDBRound, "next-db-round", "n", 0, "next round on db. It is the next_account_round from Indexer's metastate table.") 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.MarkFlagRequired("scenario") RunnerCmd.MarkFlagRequired("conduit-binary") diff --git a/tools/block-generator/scenarios/config.mixed.jumbo.yml b/tools/block-generator/scenarios/config.mixed.jumbo.yml index d02ee4045d..630ab8deec 100644 --- a/tools/block-generator/scenarios/config.mixed.jumbo.yml +++ b/tools/block-generator/scenarios/config.mixed.jumbo.yml @@ -1,7 +1,7 @@ name: "Mixed (jumbo)" genesis_accounts: 10000 genesis_account_balance: 1000000000000 -tx_per_block: 19999 +tx_per_block: 25000 # transaction distribution tx_pay_fraction: 0.3 diff --git a/tools/block-generator/scenarios/config.payment.full.yml b/tools/block-generator/scenarios/config.payment.full.yml index 22aa0e9701..8143c22bf4 100644 --- a/tools/block-generator/scenarios/config.payment.full.yml +++ b/tools/block-generator/scenarios/config.payment.full.yml @@ -1,6 +1,6 @@ name: "Pay (full)" -genesis_accounts: 10 -genesis_account_balance: 1000000000000000 +genesis_accounts: 10000 +genesis_account_balance: 1000000000000 tx_per_block: 5000 # transaction distribution diff --git a/tools/block-generator/scenarios/config.payment.jumbo.yml b/tools/block-generator/scenarios/config.payment.jumbo.yml index 2d1f0dba2f..23f0a92d66 100644 --- a/tools/block-generator/scenarios/config.payment.jumbo.yml +++ b/tools/block-generator/scenarios/config.payment.jumbo.yml @@ -1,7 +1,7 @@ name: "Pay (jumbo)" -genesis_accounts: 10 -genesis_account_balance: 1000000000000000 -tx_per_block: 19999 +genesis_accounts: 10000 +genesis_account_balance: 1000000000000 +tx_per_block: 25000 # transaction distribution tx_pay_fraction: 1.0 diff --git a/tools/block-generator/scenarios/config.payment.small.yml b/tools/block-generator/scenarios/config.payment.small.yml index e250b2f016..7f01232dbf 100644 --- a/tools/block-generator/scenarios/config.payment.small.yml +++ b/tools/block-generator/scenarios/config.payment.small.yml @@ -1,6 +1,6 @@ name: "Pay (small)" -genesis_accounts: 10 -genesis_account_balance: 1000000000000000 +genesis_accounts: 10000 +genesis_account_balance: 1000000000000 tx_per_block: 100 # transaction distribution diff --git a/tools/block-generator/util/util.go b/tools/block-generator/util/util.go index ad7c73baa5..b36fca9eeb 100644 --- a/tools/block-generator/util/util.go +++ b/tools/block-generator/util/util.go @@ -17,10 +17,19 @@ package util import ( + "database/sql" + "encoding/json" + "errors" "fmt" "os" + "strings" + // import postgres driver + _ "github.com/lib/pq" ) +// ErrorNotInitialized is returned when the database is not initialized. +var ErrorNotInitialized error = errors.New("database not initialized") + // MaybeFail exits if there was an error. func MaybeFail(err error, errfmt string, params ...interface{}) { if err == nil { @@ -30,3 +39,40 @@ func MaybeFail(err error, errfmt string, params ...interface{}) { fmt.Fprintf(os.Stderr, "\nError: %v\n", err) os.Exit(1) } + +// GetNextRound returns the next account round from the metastate table. +func GetNextRound(postgresConnectionString string) (uint64, error) { + conn, err := sql.Open("postgres", postgresConnectionString) + if err != nil { + return 0, fmt.Errorf("postgres connection string did not work: %w", err) + } + defer conn.Close() + query := `SELECT v FROM metastate WHERE k='state';` + var state []uint8 + if err = conn.QueryRow(query).Scan(&state); err != nil { + if strings.Contains(err.Error(), `relation "metastate" does not exist`) { + return 0, ErrorNotInitialized + } + return 0, fmt.Errorf("unable to get next db round: %w", err) + } + kv := make(map[string]uint64) + err = json.Unmarshal(state, &kv) + if err != nil { + return 0, fmt.Errorf("unable to get next account round: %w", err) + } + return kv["next_account_round"], nil +} + +// EmptyDB empties the database. +func EmptyDB(postgresConnectionString string) error { + conn, err := sql.Open("postgres", postgresConnectionString) + if err != nil { + return fmt.Errorf("postgres connection string did not work: %w", err) + } + defer conn.Close() + query := `DROP SCHEMA public CASCADE; CREATE SCHEMA public;` + if _, err = conn.Exec(query); err != nil { + return fmt.Errorf("unable to reset postgres DB: %w", err) + } + return nil +} From aa6a5bbed5de22d7bceb8bd3ef8abb8f178302d3 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Tue, 30 May 2023 14:51:50 -0400 Subject: [PATCH 120/125] AVM: Make apps and app accounts available while creation is "pending" (#5425) --- data/transactions/logic/eval.go | 43 +++++--- data/transactions/logic/resources.go | 41 ++++---- ledger/apptxn_test.go | 150 +++++++++++++++++++++++++++ ledger/double_test.go | 5 +- 4 files changed, 204 insertions(+), 35 deletions(-) diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index 4d24261915..e5e55525e5 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -539,10 +539,16 @@ func (ep *EvalParams) RecordAD(gi int, ad transactions.ApplyData) { } ep.TxnGroup[gi].ApplyData = ad if aid := ad.ConfigAsset; aid != 0 { - ep.available.createdAsas = append(ep.available.createdAsas, aid) + if ep.available.createdAsas == nil { + ep.available.createdAsas = make(map[basics.AssetIndex]struct{}) + } + ep.available.createdAsas[aid] = struct{}{} } if aid := ad.ApplicationID; aid != 0 { - ep.available.createdApps = append(ep.available.createdApps, aid) + if ep.available.createdApps == nil { + ep.available.createdApps = make(map[basics.AppIndex]struct{}) + } + ep.available.createdApps[aid] = struct{}{} } } @@ -954,14 +960,21 @@ func EvalContract(program []byte, gi int, aid basics.AppIndex, params *EvalParam } } - // If this is a creation, make any "0 index" box refs available now that we - // have an appID. + // If this is a creation... if cx.txn.Txn.ApplicationID == 0 { + // make any "0 index" box refs available now that we have an appID. for _, br := range cx.txn.Txn.Boxes { if br.Index == 0 { cx.EvalParams.available.boxes[boxRef{cx.appID, string(br.Name)}] = false } } + // and add the appID to `createdApps` + if cx.EvalParams.Proto.LogicSigVersion >= sharedResourcesVersion { + if cx.EvalParams.available.createdApps == nil { + cx.EvalParams.available.createdApps = make(map[basics.AppIndex]struct{}) + } + cx.EvalParams.available.createdApps[cx.appID] = struct{}{} + } } // Check the I/O budget for reading if this is the first top-level app call @@ -4248,13 +4261,15 @@ func opExtract64Bits(cx *EvalContext) error { // assignAccount is used to convert a stackValue into a 32-byte account value, // enforcing any "availability" restrictions in force. func (cx *EvalContext) assignAccount(sv stackValue) (basics.Address, error) { - _, err := sv.address() + addr, err := sv.address() if err != nil { return basics.Address{}, err } - addr, _, err := cx.accountReference(sv) - return addr, err + if cx.availableAccount(addr) { + return addr, nil + } + return basics.Address{}, fmt.Errorf("invalid Account reference %s", addr) } // accountReference yields the address and Accounts offset designated by a @@ -4323,7 +4338,7 @@ func (cx *EvalContext) availableAccount(addr basics.Address) bool { // Allow an address for an app that was created in group if cx.version >= createdResourcesVersion { - for _, appID := range cx.available.createdApps { + for appID := range cx.available.createdApps { createdAddress := cx.getApplicationAddress(appID) if addr == createdAddress { return true @@ -5199,10 +5214,8 @@ func (cx *EvalContext) availableAsset(aid basics.AssetIndex) bool { } // or was created in group if cx.version >= createdResourcesVersion { - for _, assetID := range cx.available.createdAsas { - if assetID == aid { - return true - } + if _, ok := cx.available.createdAsas[aid]; ok { + return true } } @@ -5241,10 +5254,8 @@ func (cx *EvalContext) availableApp(aid basics.AppIndex) bool { } // or was created in group if cx.version >= createdResourcesVersion { - for _, appID := range cx.available.createdApps { - if appID == aid { - return true - } + if _, ok := cx.available.createdApps[aid]; ok { + return true } } // Or, it can be the current app diff --git a/data/transactions/logic/resources.go b/data/transactions/logic/resources.go index 4db2d5d595..00045100ec 100644 --- a/data/transactions/logic/resources.go +++ b/data/transactions/logic/resources.go @@ -31,8 +31,8 @@ import ( type resources struct { // These resources were created previously in the group, so they can be used // by later transactions. - createdAsas []basics.AssetIndex - createdApps []basics.AppIndex + createdAsas map[basics.AssetIndex]struct{} + createdApps map[basics.AppIndex]struct{} // These resources have been used by some txn in the group, so they are // available. These maps track the availability of the basic objects (often @@ -101,6 +101,16 @@ func (r *resources) fill(tx *transactions.Transaction, ep *EvalParams) { } func (cx *EvalContext) allows(tx *transactions.Transaction, calleeVer uint64) error { + // if the caller is pre-sharing, it can't prepare transactions with + // resources that are not available, so `tx` is surely legal. + if cx.version < sharedResourcesVersion { + // this is important, not just an optimization, because a pre-sharing + // creation txn has access to the app and app account it is currently + // creating (and therefore can pass that access down), but cx.available + // doesn't track that properly until v9's protocol upgrade. See + // TestInnerAppCreateAndOptin for an example. + return nil + } switch tx.Type { case protocol.PaymentTx, protocol.KeyRegistrationTx, protocol.AssetConfigTx: // these transactions don't touch cross-product resources, so no error is possible @@ -110,7 +120,7 @@ func (cx *EvalContext) allows(tx *transactions.Transaction, calleeVer uint64) er case protocol.AssetFreezeTx: return cx.allowsAssetFreeze(&tx.Header, &tx.AssetFreezeTxnFields) case protocol.ApplicationCallTx: - return cx.allowsApplicationCall(&tx.Header, &tx.ApplicationCallTxnFields, cx.version, calleeVer) + return cx.allowsApplicationCall(&tx.Header, &tx.ApplicationCallTxnFields, calleeVer) default: return fmt.Errorf("unknown inner transaction type %s", tx.Type) } @@ -158,13 +168,11 @@ func (cx *EvalContext) allowsHolding(addr basics.Address, ai basics.AssetIndex) return true } // If an ASA was created in this group, then allow holding access for any allowed account. - for _, created := range r.createdAsas { - if created == ai { - return cx.availableAccount(addr) - } + if _, ok := r.createdAsas[ai]; ok { + return cx.availableAccount(addr) } // If the address was "created" by making its app in this group, then allow for available assets. - for _, created := range r.createdApps { + for created := range r.createdApps { if cx.getApplicationAddress(created) == addr { return cx.availableAsset(ai) } @@ -184,17 +192,15 @@ func (cx *EvalContext) allowsLocals(addr basics.Address, ai basics.AppIndex) boo return true } // All locals of created apps are available - for _, created := range r.createdApps { - if created == ai { - return cx.availableAccount(addr) - } + if _, ok := r.createdApps[ai]; ok { + return cx.availableAccount(addr) } if cx.txn.Txn.ApplicationID == 0 && cx.appID == ai { return cx.availableAccount(addr) } // All locals of created app accounts are available - for _, created := range r.createdApps { + for created := range r.createdApps { if cx.getApplicationAddress(created) == addr { return cx.availableApp(ai) } @@ -315,11 +321,10 @@ func (r *resources) fillApplicationCall(ep *EvalParams, hdr *transactions.Header } } -func (cx *EvalContext) allowsApplicationCall(hdr *transactions.Header, tx *transactions.ApplicationCallTxnFields, callerVer, calleeVer uint64) error { - // If an old (pre resource sharing) app is being called from an app that has - // resource sharing enabled, we need to confirm that no new "cross-product" - // resources have become available. - if callerVer < sharedResourcesVersion || calleeVer >= sharedResourcesVersion { +func (cx *EvalContext) allowsApplicationCall(hdr *transactions.Header, tx *transactions.ApplicationCallTxnFields, calleeVer uint64) error { + // If the callee is at least sharedResourcesVersion, then it will check + // availability properly itself. + if calleeVer >= sharedResourcesVersion { return nil } diff --git a/ledger/apptxn_test.go b/ledger/apptxn_test.go index 28a5b10404..a2d7dba115 100644 --- a/ledger/apptxn_test.go +++ b/ledger/apptxn_test.go @@ -869,6 +869,156 @@ func TestInnerRekey(t *testing.T) { }) } +// TestInnerAppCreateAndOptin tests a weird way to create an app and opt it into +// an ASA all from one top-level transaction. Part of the trick is to use an +// inner helper app. The app being created rekeys itself to the inner app, +// which funds the outer app and opts it into the ASA. It could have worked +// differently - the inner app could have just funded the outer app, and then +// the outer app could have opted-in. But this technique tests something +// interesting, that the inner app can perform an opt-in on the outer app, which +// tests that the newly created app's holdings are available. In practice, the +// helper shold rekey it back, but we don't bother here. +func TestInnerAppCreateAndOptin(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + genBalances, addrs, _ := ledgertesting.NewTestGenesis() + + // v31 allows inner appl and inner rekey + ledgertesting.TestConsensusRange(t, 31, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { + dl := NewDoubleLedger(t, genBalances, cv, cfg) + defer dl.Close() + + createasa := txntest.Txn{ + Type: "acfg", + Sender: addrs[0], + AssetParams: basics.AssetParams{Total: 2, UnitName: "$"}, + } + asaID := dl.txn(&createasa).ApplyData.ConfigAsset + require.NotZero(t, asaID) + + // helper app, is called during the creation of an app. When such an + // app is created, it rekeys itself to this helper and calls it. The + // helpers opts the caller into an ASA, and funds the MBR the caller + // needs for that optin. + helper := dl.fundedApp(addrs[0], 1_000_000, + main(` + itxn_begin + int axfer; itxn_field TypeEnum + int `+strconv.Itoa(int(asaID))+`; itxn_field XferAsset + txn Sender; itxn_field Sender // call as the caller! (works because of rekey by caller) + txn Sender; itxn_field AssetReceiver // 0 to self == opt-in + itxn_next + int pay; itxn_field TypeEnum // pay 200kmAlgo to the caller, for MBR + int 200000; itxn_field Amount + txn Sender; itxn_field Receiver + itxn_submit +`)) + // Don't use `main` here, we want to do the work during creation. Rekey + // to the helper and invoke it, trusting it to opt us into the ASA. + createapp := txntest.Txn{ + Type: "appl", + Sender: addrs[0], + Fee: 3 * 1000, // to pay for self, call to helper, and helper's axfer + ApprovalProgram: ` + itxn_begin + int appl; itxn_field TypeEnum + addr ` + helper.Address().String() + `; itxn_field RekeyTo + int ` + strconv.Itoa(int(helper)) + `; itxn_field ApplicationID + txn Assets 0; itxn_field Assets + itxn_submit + int 1 +`, + ForeignApps: []basics.AppIndex{helper}, + ForeignAssets: []basics.AssetIndex{asaID}, + } + appID := dl.txn(&createapp).ApplyData.ApplicationID + require.NotZero(t, appID) + }) +} + +// TestParentGlobals tests that a newly created app can call an inner app, and +// the inner app will have access to the parent globals, even if the originally +// created app ID isn't passed down, because the rule is that "pending" created +// apps are available, starting from v38 +func TestParentGlobals(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + genBalances, addrs, _ := ledgertesting.NewTestGenesis() + + // v38 allows parent access, but we start with v31 to make sure we don't mistakenly change it + ledgertesting.TestConsensusRange(t, 31, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { + dl := NewDoubleLedger(t, genBalances, cv, cfg) + defer dl.Close() + + // helper app, is called during the creation of an app. this app tries + // to access its parent's globals, by using `global CallerApplicationID` + helper := dl.fundedApp(addrs[0], 1_000_000, + main(` + global CallerApplicationID + byte "X" + app_global_get_ex; pop; pop; // we only care that it didn't panic +`)) + + // Don't use `main` here, we want to do the work during creation. + createProgram := ` + itxn_begin + int appl; itxn_field TypeEnum + int ` + strconv.Itoa(int(helper)) + `; itxn_field ApplicationID + itxn_submit + int 1 +` + createapp := txntest.Txn{ + Type: "appl", + Sender: addrs[0], + Fee: 2 * 1000, // to pay for self and call to helper + ApprovalProgram: createProgram, + ForeignApps: []basics.AppIndex{helper}, + } + var creator basics.AppIndex + if ver >= 38 { + creator = dl.txn(&createapp).ApplyData.ApplicationID + require.NotZero(t, creator) + } else { + dl.txn(&createapp, "unavailable App") + } + + // Now, test the same pattern, but do it all inside of yet another outer + // app, to show that the parent is available even if it was, itself + // created as an inner. To do so, we also need to get 0.2 MBR to the + // outer app, since it will be creating the "middle" app. + + outerAppAddress := (creator + 3).Address() // creator called an inner, so next is creator+2, then fund + outer := txntest.Txn{ + Type: "appl", + Sender: addrs[0], + Fee: 3 * 1000, // to pay for self, call to inner create, and its call to helper + ApprovalProgram: ` + itxn_begin + int appl; itxn_field TypeEnum + byte 0x` + hex.EncodeToString(createapp.SignedTxn().Txn.ApprovalProgram) + `; itxn_field ApprovalProgram + byte 0x` + hex.EncodeToString(createapp.SignedTxn().Txn.ClearStateProgram) + `; itxn_field ClearStateProgram + itxn_submit + int 1 +`, + ForeignApps: []basics.AppIndex{creator, helper}, + } + fund := txntest.Txn{ + Type: "pay", + Amount: 200_000, + Sender: addrs[0], + Receiver: outerAppAddress, + } + if ver >= 38 { + dl.txgroup("", &fund, &outer) + } else { + dl.txn(&createapp, "unavailable App") + } + + }) +} + func TestNote(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() diff --git a/ledger/double_test.go b/ledger/double_test.go index 1c53e54b09..16d38e7681 100644 --- a/ledger/double_test.go +++ b/ledger/double_test.go @@ -77,7 +77,10 @@ func (dl *DoubleLedger) txn(tx *txntest.Txn, problem ...string) (stib *transacti dl.eval = nil } else { vb := dl.endBlock() - stib = &vb.Block().Payset[0] + // It should have a stib, but don't panic here because of an earlier problem. + if len(vb.Block().Payset) > 0 { + stib = &vb.Block().Payset[0] + } } }() } From 04fc60d21b268c5201ad79d0317419af24a43f47 Mon Sep 17 00:00:00 2001 From: John Lee Date: Tue, 30 May 2023 17:19:39 -0400 Subject: [PATCH 121/125] CICD: fix ci-build target for buildsrc-special support (#5433) Co-authored-by: Zeph Grunschlag --- scripts/release/mule/Makefile.mule | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/release/mule/Makefile.mule b/scripts/release/mule/Makefile.mule index ae40490c7a..0c73cccf89 100644 --- a/scripts/release/mule/Makefile.mule +++ b/scripts/release/mule/Makefile.mule @@ -28,7 +28,7 @@ ci-integration: SRCROOT=$(SRCPATH) \ test/scripts/e2e.sh -c $(CHANNEL) -n -ci-build: ci-clean buildsrc ci-setup +ci-build: ci-clean build ci-setup CHANNEL=$(CHANNEL) PKG_ROOT=$(PKG_DIR) NO_BUILD=True VARIATIONS=$(OS_TYPE)-$(ARCH) \ scripts/build_packages.sh $(OS_TYPE)/$(ARCH) && \ mkdir -p $(PKG_DIR)/data && \ From 926306631635aa49739a3122f9d211ec86d2e2ef Mon Sep 17 00:00:00 2001 From: Will Winder Date: Mon, 5 Jun 2023 17:04:25 -0400 Subject: [PATCH 122/125] api: Fix errFailedRetrievingStateDelta response. (#5452) --- daemon/algod/api/server/v2/handlers.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/daemon/algod/api/server/v2/handlers.go b/daemon/algod/api/server/v2/handlers.go index a8699c37c0..05aa4ed913 100644 --- a/daemon/algod/api/server/v2/handlers.go +++ b/daemon/algod/api/server/v2/handlers.go @@ -22,7 +22,6 @@ import ( "encoding/base64" "errors" "fmt" - "github.com/algorand/go-algorand/ledger/eval" "io" "math" "net/http" @@ -46,6 +45,7 @@ import ( "github.com/algorand/go-algorand/data/bookkeeping" "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/data/transactions/logic" + "github.com/algorand/go-algorand/ledger/eval" "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/ledger/simulation" "github.com/algorand/go-algorand/logging" @@ -1721,7 +1721,7 @@ func (v2 *Handlers) GetLedgerStateDeltaForTransactionGroup(ctx echo.Context, id } delta, err := tracer.GetDeltaForID(idDigest) if err != nil { - return notFound(ctx, err, errFailedRetrievingStateDelta, v2.Log) + return notFound(ctx, err, fmt.Sprintf(errFailedRetrievingStateDelta, err), v2.Log) } data, err := encode(handle, delta) if err != nil { @@ -1743,7 +1743,7 @@ func (v2 *Handlers) GetTransactionGroupLedgerStateDeltasForRound(ctx echo.Contex } deltas, err := tracer.GetDeltasForRound(basics.Round(round)) if err != nil { - return notFound(ctx, err, errFailedRetrievingStateDelta, v2.Log) + return notFound(ctx, err, fmt.Sprintf(errFailedRetrievingStateDelta, err), v2.Log) } response := struct { Deltas []eval.TxnGroupDeltaWithIds From 4e99d62a842fd07b59d6f259aef9c03472ed50a9 Mon Sep 17 00:00:00 2001 From: John Lee Date: Tue, 6 Jun 2023 11:58:54 -0400 Subject: [PATCH 123/125] Bump buildnumber.dat. --- buildnumber.dat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildnumber.dat b/buildnumber.dat index 573541ac97..d00491fd7e 100644 --- a/buildnumber.dat +++ b/buildnumber.dat @@ -1 +1 @@ -0 +1 From 01a8931305591242dc476d0294c304ffa287b202 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Wed, 7 Jun 2023 14:07:34 -0400 Subject: [PATCH 124/125] algod: adjust numeric version encoding (#5457) --- config/version.go | 2 +- config/version_test.go | 60 ++++++++++++++++++++++++++++++++++++++++ util/s3/s3Helper_test.go | 1 + 3 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 config/version_test.go diff --git a/config/version.go b/config/version.go index e4c5f57df8..2e274a552d 100644 --- a/config/version.go +++ b/config/version.go @@ -72,7 +72,7 @@ func (v Version) AsUInt64() (versionInfo uint64) { versionInfo = uint64(v.Major) versionInfo <<= 16 versionInfo |= uint64(v.Minor) - versionInfo <<= 16 + versionInfo <<= 24 versionInfo |= uint64(v.BuildNumber) return } diff --git a/config/version_test.go b/config/version_test.go new file mode 100644 index 0000000000..1a37cf185b --- /dev/null +++ b/config/version_test.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 . + +package config + +import ( + "fmt" + "testing" + + "github.com/algorand/go-algorand/test/partitiontest" + "github.com/algorand/go-algorand/util/s3" + "github.com/stretchr/testify/require" +) + +func TestAlgodVsUpdatedVersions(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + tests := []struct { + major int + minor int + build int + }{ + {major: 1, minor: 1, build: 32111}, + {major: 2, minor: 0, build: 0}, + {major: 3, minor: 13, build: 170018}, + {major: 3, minor: 15, build: 157}, + {major: 3, minor: 16, build: 0}, + {major: 3, minor: 16, build: 100}, + } + + for _, tt := range tests { + t.Run(fmt.Sprintf("%d.%d.%d", tt.major, tt.minor, tt.build), func(t *testing.T) { + version := Version{Major: tt.major, Minor: tt.minor, BuildNumber: tt.build} + str := version.String() + ver, err := s3.GetVersionFromName("_" + str) + require.NoError(t, err) + require.Equal(t, version.AsUInt64(), ver) + major, minor, patch, err := s3.GetVersionPartsFromVersion(ver) + require.NoError(t, err) + require.Equal(t, uint64(tt.major), major) + require.Equal(t, uint64(tt.minor), minor) + require.Equal(t, uint64(tt.build), patch) + + }) + } +} diff --git a/util/s3/s3Helper_test.go b/util/s3/s3Helper_test.go index 89cf091881..8ba636c2a1 100644 --- a/util/s3/s3Helper_test.go +++ b/util/s3/s3Helper_test.go @@ -231,6 +231,7 @@ func TestGetPartsFromVersion(t *testing.T) { {name: "test 4 (minor)", version: 1*1<<40 + 2*1<<24, expMajor: 1, expMinor: 2, expPatch: 0}, {name: "test 5 (patch)", version: 1*1<<40 + 1, expMajor: 1, expMinor: 0, expPatch: 1}, {name: "test 6 (patch)", version: 1*1<<40 + 2, expMajor: 1, expMinor: 0, expPatch: 2}, + {name: "test 6 (patch)", version: 3298803318784, expMajor: 3, expMinor: 16, expPatch: 0}, } for _, test := range tests { From dfda9e9f8231c55adb2a9d9a64d15e9807af202b Mon Sep 17 00:00:00 2001 From: chris erway Date: Tue, 6 Jun 2023 12:51:18 -0400 Subject: [PATCH 125/125] eval: update endOfBlock --- ledger/eval/eval.go | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/ledger/eval/eval.go b/ledger/eval/eval.go index 914d7cdca2..0607f5a384 100644 --- a/ledger/eval/eval.go +++ b/ledger/eval/eval.go @@ -1323,8 +1323,30 @@ func (eval *BlockEvaluator) endOfBlock() error { if !eval.block.StateProofTracking[protocol.StateProofBasic].StateProofVotersCommitment.IsEqual(expectedVoters) { return fmt.Errorf("StateProofVotersCommitment wrong: %v != %v", eval.block.StateProofTracking[protocol.StateProofBasic].StateProofVotersCommitment, expectedVoters) } - if eval.block.StateProofTracking[protocol.StateProofBasic].StateProofOnlineTotalWeight != expectedVotersWeight { - return fmt.Errorf("StateProofOnlineTotalWeight wrong: %v != %v", eval.block.StateProofTracking[protocol.StateProofBasic].StateProofOnlineTotalWeight, expectedVotersWeight) + if eval.proto.ExcludeExpiredCirculation { + if eval.block.StateProofTracking[protocol.StateProofBasic].StateProofOnlineTotalWeight != expectedVotersWeight { + return fmt.Errorf("StateProofOnlineTotalWeight wrong: %v != %v", eval.block.StateProofTracking[protocol.StateProofBasic].StateProofOnlineTotalWeight, expectedVotersWeight) + } + } else { + if eval.block.StateProofTracking[protocol.StateProofBasic].StateProofOnlineTotalWeight != expectedVotersWeight { + actualVotersWeight := eval.block.StateProofTracking[protocol.StateProofBasic].StateProofOnlineTotalWeight + var highWeight, lowWeight basics.MicroAlgos + if expectedVotersWeight.LessThan(actualVotersWeight) { + highWeight = actualVotersWeight + lowWeight = expectedVotersWeight + } else { + highWeight = expectedVotersWeight + lowWeight = actualVotersWeight + } + const stakeDiffusionFactor = 5 + allowedDelta, overflowed := basics.Muldiv(expectedVotersWeight.Raw, stakeDiffusionFactor, 100) + if overflowed { + return fmt.Errorf("StateProofOnlineTotalWeight overflow: %v != %v", actualVotersWeight, expectedVotersWeight) + } + if (highWeight.Raw - lowWeight.Raw) > allowedDelta { + return fmt.Errorf("StateProofOnlineTotalWeight wrong: %v != %v greater than %d", actualVotersWeight, expectedVotersWeight, allowedDelta) + } + } } if eval.block.StateProofTracking[protocol.StateProofBasic].StateProofNextRound != eval.state.GetStateProofNextRound() { return fmt.Errorf("StateProofNextRound wrong: %v != %v", eval.block.StateProofTracking[protocol.StateProofBasic].StateProofNextRound, eval.state.GetStateProofNextRound())